Commit 4e07ac0d by 仲光辉

蛋壳创意科技技术测试--Seata 测试用例 init 2020/10/23 16:28 (ZGH.MercyModest)

parents
# Created by .ignore support plugin (hsz.mobi)
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.idea
*.iml
**/target
**/**/target
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>01-common-parent</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>01-01-common-util</artifactId>
<packaging>jar</packaging>
<description>蛋壳创意科技技术测试--seata-common-util</description>
</project>
\ No newline at end of file
package cn.dankal.test.common.entity;
import lombok.Getter;
import lombok.Setter;
/**
* 公共常量类
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/23
*/
public interface CommonConstant {
/**
* ResultCodeEnum
*/
enum ResultCodeEnum {
/**
* code: 200
* message: 成功
*/
SUCCESS("200", "成功"),
/**
* code: 201
* message: 失败
*/
FAIL("201", "失败"),
/**
* code: 2012
* message: 服务异常
*/
SERVICE_ERROR("2012", "服务异常");
/**
* 响应状态码
*/
@Getter
private final String code;
/**
* 响应信息
*/
@Getter
@Setter
private String message;
ResultCodeEnum(String code, String message) {
this.code = code;
this.message = message;
}
}
interface CommonConst {
/**
* 响应信息: no response data
*/
String MESSAGE_NO_DATA = "no response data";
}
}
package cn.dankal.test.common.result;
import cn.dankal.test.common.entity.CommonConstant;
import cn.dankal.test.common.entity.CommonConstant.ResultCodeEnum;
import lombok.*;
import lombok.experimental.Accessors;
/**
* {@link Getter}{@link Accessors}
* 响应实体: Result
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/23
*/
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Result<T> {
/**
* 错误码
*/
private String code;
/**
* 响应数据
*/
private T data;
/**
* 响应消息
*/
private String message;
/**
* 是否请求成功
*/
private Boolean isSuccess;
public Result(boolean isSuccess, T data, ResultCodeEnum resultCodeEnum) {
this.isSuccess = isSuccess;
this.data = data;
this.code = resultCodeEnum.getCode();
this.message = resultCodeEnum.getMessage();
}
/**
* 成功响应结果
*
* @param data 响应数据
* @return Result<T>
*/
public static <T> Result<T> success(T data) {
return new Result<>(true, data, ResultCodeEnum.SUCCESS);
}
/**
* 失败响应结果
*
* @param message 失败提示信息
* @return Result<String>
*/
public static Result<String> error(String message) {
CommonConstant.ResultCodeEnum resultCodeEnum2Fair = ResultCodeEnum.FAIL;
resultCodeEnum2Fair.setMessage(message);
return new Result<>(false, CommonConstant.CommonConst.MESSAGE_NO_DATA, resultCodeEnum2Fair);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>01-common-parent</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>01-02-orm-util</artifactId>
<packaging>jar</packaging>
<description>蛋壳创意科技技术测试--seata-orm-util</description>
<dependencies>
<!--seata-spring-cloud-alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--seata-spring-boot-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!--mybatis-plus-spring-boot-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--druid-spring-boot-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--project inner dependence-->
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>01-01-common-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.dankal.test.common.orm.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link Configuration}{@link MapperScan}
* 公共 MybatisPlus 配置文件
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@Configuration
@MapperScan(basePackages = {"cn.dankal.test.*.repository"})
public class CommonMybatisPlusConfig {
/**
* MybatisPlus 分页插件
*
* @return MybatisPlusInterceptor
* @see MybatisPlusInterceptor
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
return new MybatisPlusInterceptor();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>01-common-parent</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>01-03-service-util</artifactId>
<packaging>jar</packaging>
<description>蛋壳创意科技技术测试--seata-service-util</description>
<dependencies>
<!--spring-boot-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--alibaba-nacos-discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--alibaba-nacos-config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--project inner dependence-->
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>01-01-common-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.dankal.test.common.service.advice;
import cn.dankal.test.common.result.Result;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* {@link ErrorController}{@link RestController}
* 自定义异常控制器: 404
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/21
*/
@RestController
public class NotFoundExceptionController implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@GetMapping({"/error"})
public Result<String> handlerNoFundException(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
// 判断是否404异常
if (HttpStatus.NOT_FOUND.value() == statusCode) {
return Result.error("请求资源不存在");
}
// 其TA未处理异常
String errorMessage = " --> 未处理异常,请联系后台管理员";
return Result.error(errorMessage);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>test-in-seata</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>01-common-parent</artifactId>
<packaging>pom</packaging>
<description>蛋壳创意科技技术测试--seata-common-project</description>
<modules>
<module>01-01-common-util</module>
<module>01-02-orm-util</module>
<module>01-03-service-util</module>
</modules>
<dependencies>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>test-in-seata</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>02-order-service</artifactId>
<packaging>jar</packaging>
<description>蛋壳创意科技技术测试--seata-order-service</description>
<dependencies>
<!--project inner dependence-->
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>01-02-orm-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>01-03-service-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--client dependence-->
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>04-01-storage-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.dankal.test.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* {@link SpringBootApplication}{@link EnableDiscoveryClient}
* OrderService 启动类
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"cn.dankal.test"})
@SpringBootApplication(scanBasePackages = {"cn.dankal.test"}, exclude = DataSourceAutoConfiguration.class)
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
package cn.dankal.test.order.advice;
import cn.dankal.test.common.entity.CommonConstant;
import cn.dankal.test.common.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* {@link RestControllerAdvice}{@link Slf4j}
* order-service: 全局异常处理
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/23
*/
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler({Exception.class})
public Result<Object> handlerException(Exception exception) {
log.error("order-service 全局异常处理: {}", exception.getMessage());
return new Result<>()
.setMessage(exception.getMessage())
.setCode(CommonConstant.ResultCodeEnum.FAIL.getCode())
.setIsSuccess(false)
.setData(false);
}
}
package cn.dankal.test.order.controller;
import cn.dankal.test.common.entity.CommonConstant;
import cn.dankal.test.common.result.Result;
import cn.dankal.test.order.model.Order;
import cn.dankal.test.order.service.OrderService;
import io.seata.core.exception.TransactionException;
import org.springframework.web.bind.annotation.*;
/**
* {@link RestController} {@link RequestMapping}
* OrderApiController
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@RestController
@RequestMapping("/api/order")
public class OrderApiController {
/**
* orderService
*/
private final OrderService orderService;
public OrderApiController(OrderService orderService) {
this.orderService = orderService;
}
/**
* 下订单
*
* @param order order info
* @return 操作结果(boolean)
*/
@PostMapping("/place/order")
public Result<Boolean> placeOrder(@RequestBody Order order) throws TransactionException {
boolean placeOrder = orderService.placeOrder(order);
String message = CommonConstant.ResultCodeEnum.SUCCESS.getMessage();
String code = CommonConstant.ResultCodeEnum.SUCCESS.getCode();
if (!placeOrder) {
message = CommonConstant.ResultCodeEnum.FAIL.getMessage();
code = CommonConstant.ResultCodeEnum.FAIL.getCode();
}
return new Result<Boolean>()
.setIsSuccess(placeOrder)
.setData(placeOrder)
.setMessage(message)
.setCode(code);
}
@GetMapping("/place/order/exception")
public Result<Boolean> placeOrderException() throws TransactionException {
// 无法正常回滚
Boolean execResult = orderService.placeOrderException();
//手动调用 API 回滚
//Boolean execResult = orderService.placeOrderExceptionRollbackByApi();
String message = CommonConstant.ResultCodeEnum.SUCCESS.getMessage();
String code = CommonConstant.ResultCodeEnum.SUCCESS.getCode();
if (!execResult) {
message = CommonConstant.ResultCodeEnum.FAIL.getMessage();
code = CommonConstant.ResultCodeEnum.FAIL.getCode();
}
return new Result<Boolean>()
.setIsSuccess(execResult)
.setData(execResult)
.setMessage(message)
.setCode(code);
}
}
package cn.dankal.test.order.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
/**
* TODO
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@Data
@Accessors(chain = true)
@TableName("order_tbl")
public class Order {
/**
* id
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* userId
*/
private String userId;
/**
* 商品编码
*/
private String commodityCode;
/**
* 商品数量
*/
private Integer count;
/**
* 订单金额
*/
private BigDecimal money;
}
package cn.dankal.test.order.repository;
import cn.dankal.test.order.model.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* {@link BaseMapper}
* OrderMapper
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
public interface OrderMapper extends BaseMapper<Order> {
}
package cn.dankal.test.order.service;
import cn.dankal.test.order.model.Order;
import com.baomidou.mybatisplus.extension.service.IService;
import io.seata.core.exception.TransactionException;
/**
* {@link IService}
* OrderService
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
public interface OrderService extends IService<Order> {
/**
* 下订单
*
* @param order 订单信息 {userId,commodityCode,count}
* @return 如果操作成功则返回 true,否则返回 false
*/
boolean placeOrder(Order order) throws TransactionException;
/**
* 测试 事务提交异常(seata 无法执行事务回滚的操作)
*
* @return 如果操作成功则返回 true,否则 返回 false
*/
Boolean placeOrderException();
/**
* 测试 事务提交异常 (使用 Seata 提供的 API进行手动回滚)
*
* @return 如果操作成功则返回 true,否则 返回 false
* @throws TransactionException transactionException 手动回滚事务异常
*/
Boolean placeOrderExceptionRollbackByApi() throws TransactionException;
}
package cn.dankal.test.order.service.impl;
import cn.dankal.test.client.StorageServiceClient;
import cn.dankal.test.common.result.Result;
import cn.dankal.test.order.model.Order;
import cn.dankal.test.order.repository.OrderMapper;
import cn.dankal.test.order.service.OrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.spring.annotation.GlobalTransactional;
import io.seata.tm.api.GlobalTransactionContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Random;
/**
* {@link OrderService} {@link ServiceImpl}
* OrderServiceImpl
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
/**
* StorageServiceClient
*/
private final StorageServiceClient storageServiceClient;
public OrderServiceImpl(StorageServiceClient storageServiceClient) {
this.storageServiceClient = storageServiceClient;
}
@GlobalTransactional(rollbackFor = {Exception.class})
@Override
public boolean placeOrder(Order order) throws TransactionException {
// 参数校验
if (null == order) {
return false;
}
String commodityCode = order.getCommodityCode();
Integer count = order.getCount();
if (count <= 0) {
return false;
}
if (StringUtils.isBlank(commodityCode)) {
return false;
}
String userId = order.getUserId();
if (StringUtils.isBlank(userId)) {
return false;
}
// 扣减库存
Boolean deduct = storageServiceClient.deduct(commodityCode, count);
if (deduct == null || !deduct) {
// 库存扣减失败 事务回滚
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
return false;
}
// 设置订单金额
order.setMoney(BigDecimal.valueOf(new Random().nextDouble()*1000));
// 创建订单
return this.save(order);
}
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public Boolean placeOrderException() {
Order order = new Order()
.setMoney(BigDecimal.valueOf(-1))
.setUserId("nil")
.setCount(-1)
.setCommodityCode("nil");
this.save(order);
// 全局异常捕获器位于全局事务的内层
// storage-server --> cn.dankal.test.storage.advice.ExceptionAdvice.handlerException 已经将异常处理
// 所以 seata 在此处无法感知异常的发生,所以事务是正常提交的
Result<Object> execResult = storageServiceClient.deductWithException();
System.out.println("deductWithException --> result = " + execResult);
return false;
}
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public Boolean placeOrderExceptionRollbackByApi() throws TransactionException {
Order order = new Order()
.setMoney(BigDecimal.valueOf(-1))
.setUserId("nil")
.setCount(-1)
.setCommodityCode("nil");
this.save(order);
Result<Object> execResult = storageServiceClient.deductWithException();
if (!execResult.getIsSuccess()) {
// 操作异常 手动混滚
GlobalTransactionContext.reload(RootContext.getXID()).rollback();
// 直接返回 当前事务已经回滚不可再执行任何事务型操作
return false;
}
// TODO etc
// this.update(null);
return true;
}
}
server:
port: 7001
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: register.mercymodest.com:58080
alibaba:
seata:
tx-service-group: test-order-server
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/seata_order?allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=UTF-8
logging:
level:
io.seata: info
org.springframework.boot.autoconfigure: info
cn.dankal.test: debug
debug: true
seata:
enabled: true
application-id: seata-server
tx-service-group: test-order-server
enable-auto-data-source-proxy: true
config:
type: nacos
nacos:
namespace:
serverAddr: register.mercymodest.com:58080
group: SEATA_GROUP
username: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
server-addr: register.mercymodest.com:58080
group : "SEATA_GROUP"
namespace:
username: "nacos"
password: "nacos"
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
mapper-locations: classpath*:/mybatis/**/*.xml
spring:
cloud:
nacos:
config:
server-addr: register.mercymodest.com:58080
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.dankal.test.order.repository.OrderMapper">
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>test-in-seata</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>03-storage-service</artifactId>
<packaging>jar</packaging>
<description>蛋壳创意科技技术测试--seata-storage-service</description>
<dependencies>
<!--project inner dependence-->
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>01-02-orm-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>01-03-service-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.dankal.test.storage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* {@link SpringBootApplication}{@link EnableDiscoveryClient}
* StorageService 启动类
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = {"cn.dankal.test"},exclude = DataSourceAutoConfiguration.class)
public class StorageServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StorageServiceApplication.class, args);
}
}
package cn.dankal.test.storage.advice;
import cn.dankal.test.common.entity.CommonConstant;
import cn.dankal.test.common.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* {@link RestControllerAdvice}{@link Slf4j}
* storage-service : 全局异常处理器
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/23
*/
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler({Exception.class})
public Result<Object> handlerException(Exception exception) {
log.error("storage 服务内部全局异常处理: {}", exception.getMessage());
return new Result<>()
.setMessage(exception.getMessage())
.setCode(CommonConstant.ResultCodeEnum.FAIL.getCode())
.setIsSuccess(false)
.setData(false);
}
}
package cn.dankal.test.storage.controller;
import cn.dankal.test.common.entity.CommonConstant;
import cn.dankal.test.common.result.Result;
import cn.dankal.test.storage.service.StorageService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* {@link RestController}{@link RequestMapping}
* StorageApiController
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@RestController
@RequestMapping("/api/storage")
public class StorageApiController {
/**
* storageService
*/
private final StorageService storageService;
public StorageApiController(StorageService storageService) {
this.storageService = storageService;
}
/**
* 扣减库存
*
* @param commodityCode 商品编码
* @param count 商品数量
* @return 操作结果(boolean)
*/
@GetMapping("/{commodityCode}/{count}")
public Boolean deduct(
@PathVariable("commodityCode") String commodityCode,
@PathVariable("count") Integer count
) {
return storageService.deduct(commodityCode, count);
}
/**
* 扣减库存异常
*
* @return Result<Object>
*/
@GetMapping("/deduct/exception")
public Result<Object> deductWithException() {
storageService.deductWithException();
return new Result<>()
.setMessage("throw an exception to test")
.setCode(CommonConstant.ResultCodeEnum.FAIL.getCode())
.setIsSuccess(false)
.setData(false);
}
}
package cn.dankal.test.storage.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* TODO
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@Data
@Accessors(chain = true)
@TableName("storage_tbl")
public class Storage {
/**
* id
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 商品编码
*/
private String commodityCode;
/**
* 数量
*/
private Long count;
}
package cn.dankal.test.storage.repository;
import cn.dankal.test.storage.model.Storage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* {@link BaseMapper}
* StorageMapper
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
public interface StorageMapper extends BaseMapper<Storage> {
}
package cn.dankal.test.storage.service;
import cn.dankal.test.storage.model.Storage;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* {@link IService}
* StorageService
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
public interface StorageService extends IService<Storage> {
/**
* 商品库存扣减
*
* @param commodityCode 商品编码
* @param count 扣减数量
* @return 如果操作成功则返回 true,否则 返回 false
* <br/>
* - 商品库存不足 返回 false
* <br/>
* - 商品库存扣减失败 返回 false
* @throws IllegalArgumentException ex 非法 commodityCode
*/
boolean deduct(String commodityCode, int count) throws IllegalArgumentException;
/**
* 测试 事务提交异常
*
* @return 异常信息
*/
Boolean deductWithException();
}
package cn.dankal.test.storage.service.impl;
import cn.dankal.test.storage.model.Storage;
import cn.dankal.test.storage.repository.StorageMapper;
import cn.dankal.test.storage.service.StorageService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link StorageService} {@link ServiceImpl}
* StorageServiceImpl
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@Slf4j
@Service
public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage> implements StorageService {
@Transactional(rollbackFor = Exception.class)
@Override
public boolean deduct(String commodityCode, int count) throws IllegalArgumentException {
LambdaQueryWrapper<Storage> storageQueryWrapper = new LambdaQueryWrapper<Storage>()
.eq(Storage::getCommodityCode, commodityCode);
Storage storage = this.getOne(storageQueryWrapper);
if (null == storage) {
// 商品不存在
throw new IllegalArgumentException("商品不存在: " + commodityCode);
}
Long realCount = storage.getCount();
if (realCount < count) {
if (realCount==0){
log.warn("商品 {} 库存不足: {}", commodityCode, realCount);
}
return false;
}
// 扣减库存
storage.setCount(realCount - count);
this.updateById(storage);
return true;
}
@Override
public Boolean deductWithException() {
throw new RuntimeException("测试分布式事务提交异常");
}
}
server:
port: 8001
spring:
application:
name: storage-service
cloud:
nacos:
discovery:
server-addr: register.mercymodest.com:58080
alibaba:
seata:
tx-service-group: test-order-server
datasource:
url: jdbc:mysql://127.0.0.1:3306/seata_storage?allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
logging:
level:
io.seata: info
org.springframework.boot.autoconfigure: info
cn.dankal.test: debug
debug: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
mapper-locations: classpath*:/mybatis/**/*.xml
seata:
enabled: true
application-id: seata-server
tx-service-group: test-order-server
enable-auto-data-source-proxy: true
config:
type: nacos
nacos:
namespace:
serverAddr: register.mercymodest.com:58080
group: SEATA_GROUP
username: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
server-addr: register.mercymodest.com:58080
group : "SEATA_GROUP"
namespace:
username: "nacos"
password: "nacos"
spring:
cloud:
nacos:
config:
server-addr: register.mercymodest.com:58080
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.dankal.test.storage.repository.StorageMapper">
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>04-client-parent</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>04-01-storage-client</artifactId>
<dependencies>
<!--project inner dependence -->
<dependency>
<groupId>cn.dankal</groupId>
<artifactId>01-01-common-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package cn.dankal.test.client;
import cn.dankal.test.client.fallback.StorageServiceFallback;
import cn.dankal.test.common.result.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* {@link FeignClient}
* StorageServiceClient: storage-service
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
* @see StorageServiceFallback
*/
@FeignClient(value = "storage-service", fallback = StorageServiceFallback.class)
public interface StorageServiceClient {
/**
* 扣减库存
*
* @param commodityCode 商品编码
* @param count 商品数量
* @return 操作结果(boolean)
*/
@GetMapping("/api/storage/{commodityCode}/{count}")
Boolean deduct(
@PathVariable("commodityCode") String commodityCode,
@PathVariable("count") Integer count
);
/**
* 测试 事务提交异常
*
* @return 如果操作成功则返回 true,否则 返回 false
*/
@GetMapping("/api/storage/deduct/exception")
Result<Object> deductWithException();
}
package cn.dankal.test.client.fallback;
import cn.dankal.test.client.StorageServiceClient;
import cn.dankal.test.common.entity.CommonConstant;
import cn.dankal.test.common.result.Result;
import org.springframework.stereotype.Component;
/**
* {@link StorageServiceClient}{@link Component}
* StorageServiceFallback
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/10/22
*/
@Component
public class StorageServiceFallback implements StorageServiceClient {
@Override
public Boolean deduct(String commodityCode, Integer count) {
return null;
}
@Override
public Result<Object> deductWithException() {
return new Result<>()
.setMessage("StorageServiceFallback : 服务降级")
.setCode(CommonConstant.ResultCodeEnum.FAIL.getCode())
.setIsSuccess(false)
.setData(false);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>test-in-seata</artifactId>
<groupId>cn.dankal</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>04-client-parent</artifactId>
<packaging>pom</packaging>
<description>蛋壳创意科技技术测试--seata-client-parent</description>
<modules>
<module>04-01-storage-client</module>
</modules>
<dependencies>
<!--spring-cloud-openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>01-common-parent</module>
<module>02-order-service</module>
<module>03-storage-service</module>
<module>04-client-parent</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<groupId>cn.dankal</groupId>
<artifactId>test-in-seata</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>蛋壳创意科技技术测试--seata-parent</description>
<properties>
<java.version>1.8</java.version>
<spring.cloud-version>Hoxton.SR8</spring.cloud-version>
<spring-cloud.alibaba.version>2.2.1.RELEASE</spring-cloud.alibaba.version>
<mybatis-plus.version>3.4.0</mybatis-plus.version>
<druid.version>1.1.22</druid.version>
<seata-spring-boot.version>1.3.0</seata-spring-boot.version>
<fastjson.version>1.2.73</fastjson.version>
</properties>
<dependencyManagement>
<dependencies>
<!--spring-cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-cloud-alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--druid datasource-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--seata-spring-boot-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata-spring-boot.version}</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--seata-spring-cloud-alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>${spring-cloud.alibaba.version}</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
\ No newline at end of file
/*
Navicat Premium Data Transfer
Source Server : 1A-local-mysql5.7
Source Server Type : MySQL
Source Server Version : 50727
Source Host : localhost:3306
Source Schema : seata_order
Target Server Type : MySQL
Target Server Version : 50727
File Encoding : 65001
Date: 23/10/2020 15:39:53
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
CREATE DATABASE IF NOT EXISTS seata_order;
USE seata_order;
-- ----------------------------
-- Table structure for order_tbl
-- ----------------------------
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT 0,
`money` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
/*
Navicat Premium Data Transfer
Source Server : 1A-local-mysql5.7
Source Server Type : MySQL
Source Server Version : 50727
Source Host : localhost:3306
Source Schema : seata_storage
Target Server Type : MySQL
Target Server Version : 50727
File Encoding : 65001
Date: 23/10/2020 15:41:38
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
CREATE DATABASE IF NOT EXISTS seata_storage;
USE seata_storage;
-- ----------------------------
-- Table structure for storage_tbl
-- ----------------------------
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment