first commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
|
||||
-- the table to store GlobalSession data
|
||||
DROP TABLE IF EXISTS `global_table`;
|
||||
CREATE TABLE IF NOT EXISTS `global_table`
|
||||
(
|
||||
`xid` VARCHAR(128) NOT NULL,
|
||||
`transaction_id` BIGINT,
|
||||
`status` TINYINT NOT NULL,
|
||||
`application_id` VARCHAR(32),
|
||||
`transaction_service_group` VARCHAR(32),
|
||||
`transaction_name` VARCHAR(128),
|
||||
`timeout` INT,
|
||||
`begin_time` BIGINT,
|
||||
`application_data` VARCHAR(2000),
|
||||
`gmt_create` DATETIME,
|
||||
`gmt_modified` DATETIME,
|
||||
PRIMARY KEY (`xid`),
|
||||
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
|
||||
KEY `idx_transaction_id` (`transaction_id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- the table to store BranchSession data
|
||||
DROP TABLE IF EXISTS `branch_table`;
|
||||
CREATE TABLE IF NOT EXISTS `branch_table`
|
||||
(
|
||||
`branch_id` BIGINT NOT NULL,
|
||||
`xid` VARCHAR(128) NOT NULL,
|
||||
`transaction_id` BIGINT,
|
||||
`resource_group_id` VARCHAR(32),
|
||||
`resource_id` VARCHAR(256),
|
||||
`branch_type` VARCHAR(8),
|
||||
`status` TINYINT,
|
||||
`client_id` VARCHAR(64),
|
||||
`application_data` VARCHAR(2000),
|
||||
`gmt_create` DATETIME(6),
|
||||
`gmt_modified` DATETIME(6),
|
||||
PRIMARY KEY (`branch_id`),
|
||||
KEY `idx_xid` (`xid`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- the table to store lock data
|
||||
DROP TABLE IF EXISTS `lock_table`;
|
||||
CREATE TABLE IF NOT EXISTS `lock_table`
|
||||
(
|
||||
`row_key` VARCHAR(128) NOT NULL,
|
||||
`xid` VARCHAR(128),
|
||||
`transaction_id` BIGINT,
|
||||
`branch_id` BIGINT NOT NULL,
|
||||
`resource_id` VARCHAR(256),
|
||||
`table_name` VARCHAR(32),
|
||||
`pk` VARCHAR(36),
|
||||
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
|
||||
`gmt_create` DATETIME,
|
||||
`gmt_modified` DATETIME,
|
||||
PRIMARY KEY (`row_key`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_branch_id` (`branch_id`),
|
||||
KEY `idx_xid` (`xid`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
DROP TABLE IF EXISTS `distributed_lock`;
|
||||
CREATE TABLE IF NOT EXISTS `distributed_lock`
|
||||
(
|
||||
`lock_key` CHAR(20) NOT NULL,
|
||||
`lock_value` VARCHAR(20) NOT NULL,
|
||||
`expire` BIGINT,
|
||||
primary key (`lock_key`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
|
||||
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
|
||||
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
|
||||
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?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>jeecg-cloud-test-seata</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<description>分布式事务测试模块</description>
|
||||
<artifactId>jeecg-cloud-test-seata-account</artifactId>
|
||||
|
||||
</project>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package org.jeecg;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 分布式事务-账户服务
|
||||
* @author zyf
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SeataAccountApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SeataAccountApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package org.jeecg.modules.test.seata.account.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.modules.test.seata.account.service.SeataAccountService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author zyf
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/seata/account")
|
||||
public class SeataAccountController {
|
||||
|
||||
@Autowired
|
||||
private SeataAccountService accountService;
|
||||
|
||||
@PostMapping("/reduceBalance")
|
||||
public void reduceBalance(Long userId, BigDecimal amount) {
|
||||
accountService.reduceBalance(userId, amount);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.jeecg.modules.test.seata.account.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Description: 账户
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@TableName("account")
|
||||
public class SeataAccount {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 余额
|
||||
*/
|
||||
private BigDecimal balance;
|
||||
|
||||
private Date lastUpdateTime;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.test.seata.account.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.test.seata.account.entity.SeataAccount;
|
||||
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Mapper
|
||||
public interface SeataAccountMapper extends BaseMapper<SeataAccount> {
|
||||
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package org.jeecg.modules.test.seata.account.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @Description: 账户接口
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
public interface SeataAccountService {
|
||||
/**
|
||||
* 扣减金额
|
||||
* @param userId 用户 ID
|
||||
* @param amount 扣减金额
|
||||
*/
|
||||
void reduceBalance(Long userId, BigDecimal amount);
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package org.jeecg.modules.test.seata.account.service.impl;
|
||||
|
||||
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import io.seata.core.context.RootContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jeecg.modules.test.seata.account.entity.SeataAccount;
|
||||
import org.jeecg.modules.test.seata.account.mapper.SeataAccountMapper;
|
||||
import org.jeecg.modules.test.seata.account.service.SeataAccountService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SeataAccountServiceImpl implements SeataAccountService {
|
||||
@Resource
|
||||
private SeataAccountMapper accountMapper;
|
||||
|
||||
/**
|
||||
* 事务传播特性设置为 REQUIRES_NEW 开启新的事务
|
||||
*/
|
||||
@DS("account")
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
|
||||
public void reduceBalance(Long userId, BigDecimal amount) {
|
||||
log.info("xid:"+ RootContext.getXID());
|
||||
log.info("=============ACCOUNT START=================");
|
||||
SeataAccount account = accountMapper.selectById(userId);
|
||||
Assert.notNull(account, "用户不存在");
|
||||
BigDecimal balance = account.getBalance();
|
||||
log.info("下单用户{}余额为 {},商品总价为{}", userId, balance, amount);
|
||||
|
||||
if (balance.compareTo(amount)==-1) {
|
||||
log.warn("用户 {} 余额不足,当前余额:{}", userId, balance);
|
||||
throw new RuntimeException("余额不足");
|
||||
}
|
||||
log.info("开始扣减用户 {} 余额", userId);
|
||||
BigDecimal currentBalance = account.getBalance().subtract(amount);
|
||||
account.setBalance(currentBalance);
|
||||
accountMapper.updateById(account);
|
||||
log.info("扣减用户 {} 余额成功,扣减后用户账户余额为{}", userId, currentBalance);
|
||||
log.info("=============ACCOUNT END=================");
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 5002
|
||||
spring:
|
||||
application:
|
||||
name: seata-account
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
import-check:
|
||||
enabled: false
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
autoconfigure:
|
||||
exclude: com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/jeecg_account?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
sql:
|
||||
init:
|
||||
schema-locations: classpath:sql/schema-account.sql
|
||||
seata:
|
||||
enable-auto-data-source-proxy: false
|
||||
service:
|
||||
grouplist:
|
||||
default: 127.0.0.1:8091
|
||||
vgroup-mapping:
|
||||
springboot-seata-group: default
|
||||
# seata 事务组编号 用于TC集群名
|
||||
tx-service-group: springboot-seata-group
|
||||
|
||||
# 无用配置,为了避免扫码全代码导致启动慢
|
||||
minidao:
|
||||
base-package: org.jeecg.modules.jmreport.*
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for account
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `account`;
|
||||
CREATE TABLE `account` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`balance` decimal(10, 2) NULL DEFAULT NULL,
|
||||
`last_update_time` timestamp NULL DEFAULT current_timestamp() ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of account
|
||||
-- ----------------------------
|
||||
INSERT INTO `account` VALUES (1, 50.00, '2022-03-16 17:02:53');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for undo_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `undo_log`;
|
||||
CREATE TABLE `undo_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`branch_id` bigint(20) NOT NULL,
|
||||
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`rollback_info` longblob NOT NULL,
|
||||
`log_status` int(11) NOT NULL,
|
||||
`log_created` datetime(0) NOT NULL,
|
||||
`log_modified` datetime(0) NOT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?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>jeecg-cloud-test-seata</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<description>分布式事务测试模块</description>
|
||||
<artifactId>jeecg-cloud-test-seata-order</artifactId>
|
||||
|
||||
</project>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package org.jeecg;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
/**
|
||||
* @author zyf
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableFeignClients
|
||||
public class SeataOrderApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SeataOrderApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.jeecg.modules.test.seata.order.controller;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import org.jeecg.modules.test.seata.order.dto.PlaceOrderRequest;
|
||||
import org.jeecg.modules.test.seata.order.service.SeataOrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/test/seata/order")
|
||||
@Tag(name = "seata测试")
|
||||
public class SeataOrderController {
|
||||
|
||||
@Autowired
|
||||
private SeataOrderService orderService;
|
||||
|
||||
/**
|
||||
* 自由下单
|
||||
*/
|
||||
@PostMapping("/placeOrder")
|
||||
@Operation(summary = "自由下单")
|
||||
public String placeOrder(@Validated @RequestBody PlaceOrderRequest request) {
|
||||
orderService.placeOrder(request);
|
||||
return "下单成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试商品库存不足-异常回滚
|
||||
*/
|
||||
@PostMapping("/test1")
|
||||
@Operation(summary = "测试商品库存不足")
|
||||
public String test1() {
|
||||
//商品单价10元,库存20个,用户余额50元,模拟一次性购买22个。 期望异常回滚
|
||||
orderService.placeOrder(new PlaceOrderRequest(1L, 1L, 22));
|
||||
return "下单成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试用户账户余额不足-异常回滚
|
||||
*/
|
||||
@PostMapping("/test2")
|
||||
@Operation(summary = "测试用户账户余额不足")
|
||||
public String test2() {
|
||||
//商品单价10元,库存20个,用户余额50元,模拟一次性购买6个。 期望异常回滚
|
||||
orderService.placeOrder(new PlaceOrderRequest(1L, 1L, 6));
|
||||
return "下单成功";
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.jeecg.modules.test.seata.order.dto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
/**
|
||||
* @Description: 订单请求对象
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PlaceOrderRequest {
|
||||
|
||||
@NotNull
|
||||
private Long userId;
|
||||
|
||||
@NotNull
|
||||
private Long productId;
|
||||
|
||||
@NotNull
|
||||
private Integer count;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.jeecg.modules.test.seata.order.dto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @Description: 余额请求对象
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ReduceBalanceRequest {
|
||||
|
||||
private Long userId;
|
||||
private Integer price;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.jeecg.modules.test.seata.order.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
/**
|
||||
* @Description: 库存请求对象
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ReduceStockRequest {
|
||||
|
||||
private Long productId;
|
||||
private Integer amount;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package org.jeecg.modules.test.seata.order.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.jeecg.modules.test.seata.order.enums.OrderStatus;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @Description: 订单
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@TableName("p_order")
|
||||
public class SeataOrder {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private Long productId;
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private OrderStatus status;
|
||||
/**
|
||||
* 数量
|
||||
*/
|
||||
private Integer count;
|
||||
/**
|
||||
* 总金额
|
||||
*/
|
||||
private BigDecimal totalPrice;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package org.jeecg.modules.test.seata.order.enums;
|
||||
|
||||
/**
|
||||
* @Description: 订单状态
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
public enum OrderStatus {
|
||||
/**
|
||||
* INIT
|
||||
*/
|
||||
INIT,
|
||||
/**
|
||||
* SUCCESS
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* FAIL
|
||||
*/
|
||||
FAIL
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.jeecg.modules.test.seata.order.feign;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author zyf
|
||||
*/
|
||||
@FeignClient(value ="seata-account")
|
||||
public interface AccountClient {
|
||||
|
||||
/**
|
||||
* 扣减余额
|
||||
* @param userId
|
||||
* @param amount
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/test/seata/account/reduceBalance")
|
||||
String reduceBalance(@RequestParam("userId") Long userId, @RequestParam("amount") BigDecimal amount);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.jeecg.modules.test.seata.order.feign;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 分布式事务产品feign客户端
|
||||
* @author: zyf
|
||||
* @date: 2022/04/21
|
||||
*/
|
||||
@FeignClient(value ="seata-product")
|
||||
public interface ProductClient {
|
||||
/**
|
||||
* 扣减库存
|
||||
*
|
||||
* @param productId
|
||||
* @param count
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/test/seata/product/reduceStock")
|
||||
BigDecimal reduceStock(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.test.seata.order.mapper;
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.test.seata.order.entity.SeataOrder;
|
||||
|
||||
@Mapper
|
||||
public interface SeataOrderMapper extends BaseMapper<SeataOrder> {
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.jeecg.modules.test.seata.order.service;
|
||||
|
||||
|
||||
import org.jeecg.modules.test.seata.order.dto.PlaceOrderRequest;
|
||||
|
||||
/**
|
||||
* @Description: 订单接口
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
public interface SeataOrderService {
|
||||
/**
|
||||
* 下单
|
||||
*
|
||||
* @param placeOrderRequest 订单请求参数
|
||||
*/
|
||||
void placeOrder(PlaceOrderRequest placeOrderRequest);
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.jeecg.modules.test.seata.order.service.impl;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.spring.annotation.GlobalTransactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.modules.test.seata.order.dto.PlaceOrderRequest;
|
||||
import org.jeecg.modules.test.seata.order.entity.SeataOrder;
|
||||
import org.jeecg.modules.test.seata.order.enums.OrderStatus;
|
||||
import org.jeecg.modules.test.seata.order.feign.AccountClient;
|
||||
import org.jeecg.modules.test.seata.order.feign.ProductClient;
|
||||
import org.jeecg.modules.test.seata.order.mapper.SeataOrderMapper;
|
||||
import org.jeecg.modules.test.seata.order.service.SeataOrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @Description: 订单服务类
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SeataOrderServiceImpl implements SeataOrderService {
|
||||
|
||||
@Resource
|
||||
private SeataOrderMapper orderMapper;
|
||||
@Resource
|
||||
private AccountClient accountClient;
|
||||
@Resource
|
||||
private ProductClient productClient;
|
||||
|
||||
@DS("order")
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@GlobalTransactional
|
||||
public void placeOrder(PlaceOrderRequest request) {
|
||||
log.info("xid:"+RootContext.getXID());
|
||||
log.info("=============ORDER START=================");
|
||||
Long userId = request.getUserId();
|
||||
Long productId = request.getProductId();
|
||||
Integer count = request.getCount();
|
||||
log.info("收到下单请求,用户:{}, 商品:{},数量:{}", userId, productId, count);
|
||||
|
||||
|
||||
SeataOrder order = SeataOrder.builder()
|
||||
.userId(userId)
|
||||
.productId(productId)
|
||||
.status(OrderStatus.INIT)
|
||||
.count(count)
|
||||
.build();
|
||||
|
||||
orderMapper.insert(order);
|
||||
log.info("订单一阶段生成,等待扣库存付款中");
|
||||
// 扣减库存并计算总价
|
||||
BigDecimal amount = productClient.reduceStock(productId, count);
|
||||
// 扣减余额
|
||||
String str = accountClient.reduceBalance(userId, amount);
|
||||
// feign响应被二次封装,判断使主事务回滚
|
||||
JSONObject jsonObject = JSONObject.parseObject(str);
|
||||
if (jsonObject.getInteger("code") != 200) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
order.setStatus(OrderStatus.SUCCESS);
|
||||
order.setTotalPrice(amount);
|
||||
orderMapper.updateById(order);
|
||||
log.info("订单已成功下单");
|
||||
log.info("=============ORDER END=================");
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 5001
|
||||
spring:
|
||||
application:
|
||||
name: seata-order
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
import-check:
|
||||
enabled: false
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
autoconfigure:
|
||||
exclude: com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://127.0.0.1:3306/jeecg_order?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
sql:
|
||||
init:
|
||||
schema-locations: classpath:sql/schema-order.sql
|
||||
seata:
|
||||
enable-auto-data-source-proxy: false
|
||||
service:
|
||||
grouplist:
|
||||
default: 127.0.0.1:8091
|
||||
vgroup-mapping:
|
||||
springboot-seata-group: default
|
||||
# seata 事务组编号 用于TC集群名
|
||||
tx-service-group: springboot-seata-group
|
||||
|
||||
# 无用配置,为了避免扫码全代码导致启动慢
|
||||
minidao:
|
||||
base-package: org.jeecg.modules.jmreport.*
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for p_order
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `p_order`;
|
||||
CREATE TABLE `p_order` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NULL DEFAULT NULL,
|
||||
`product_id` int(11) NULL DEFAULT NULL,
|
||||
`count` int(11) NULL DEFAULT NULL,
|
||||
`total_price` decimal(10, 2) NULL DEFAULT NULL,
|
||||
`status` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
`add_time` timestamp NULL DEFAULT current_timestamp(),
|
||||
`last_update_time` timestamp NULL DEFAULT current_timestamp() ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 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` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`branch_id` bigint(20) NOT NULL,
|
||||
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`rollback_info` longblob NOT NULL,
|
||||
`log_status` int(11) NOT NULL,
|
||||
`log_created` datetime(0) NOT NULL,
|
||||
`log_modified` datetime(0) NOT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?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>jeecg-cloud-test-seata</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<description>分布式事务测试模块</description>
|
||||
<artifactId>jeecg-cloud-test-seata-product</artifactId>
|
||||
|
||||
</project>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.jeecg;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author zyf
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SeataProductApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SeataProductApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.jeecg.modules.test.seata.product.controller;
|
||||
|
||||
import org.jeecg.modules.test.seata.product.service.SeataProductService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author zyf
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/seata/product")
|
||||
public class SeataProductController {
|
||||
|
||||
@Autowired
|
||||
private SeataProductService seataProductService;
|
||||
|
||||
@PostMapping("/reduceStock")
|
||||
public BigDecimal reduceStock(Long productId, Integer count) {
|
||||
return seataProductService.reduceStock(productId, count);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.jeecg.modules.test.seata.product.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
/**
|
||||
* @Description: 产品
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@TableName("product")
|
||||
public class SeataProduct {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
/**
|
||||
* 价格
|
||||
*/
|
||||
private BigDecimal price;
|
||||
/**
|
||||
* 库存
|
||||
*/
|
||||
private Integer stock;
|
||||
|
||||
private Date lastUpdateTime;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.jeecg.modules.test.seata.product.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.test.seata.product.entity.SeataProduct;
|
||||
|
||||
|
||||
/**
|
||||
* @Description: TODO
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Mapper
|
||||
public interface SeataProductMapper extends BaseMapper<SeataProduct> {
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package org.jeecg.modules.test.seata.product.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @Description: 产品接口
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
public interface SeataProductService {
|
||||
/**
|
||||
* 扣减库存
|
||||
*
|
||||
* @param productId 商品 ID
|
||||
* @param count 扣减数量
|
||||
* @return 商品总价
|
||||
*/
|
||||
BigDecimal reduceStock(Long productId, Integer count);
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.jeecg.modules.test.seata.product.service.impl;
|
||||
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import io.seata.core.context.RootContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
import org.jeecg.modules.test.seata.product.entity.SeataProduct;
|
||||
import org.jeecg.modules.test.seata.product.mapper.SeataProductMapper;
|
||||
import org.jeecg.modules.test.seata.product.service.SeataProductService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @Description: 产品服务类
|
||||
* @author: zyf
|
||||
* @date: 2022/01/24
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SeataProductServiceImpl implements SeataProductService {
|
||||
|
||||
@Resource
|
||||
private SeataProductMapper productMapper;
|
||||
|
||||
/**
|
||||
* 事务传播特性设置为 REQUIRES_NEW 开启新的事务
|
||||
*/
|
||||
@DS("product")
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
|
||||
@Override
|
||||
public BigDecimal reduceStock(Long productId, Integer count) {
|
||||
log.info("xid:"+ RootContext.getXID());
|
||||
log.info("=============PRODUCT START=================");
|
||||
// 检查库存
|
||||
SeataProduct product = productMapper.selectById(productId);
|
||||
Assert.notNull(product, "商品不存在");
|
||||
Integer stock = product.getStock();
|
||||
log.info("商品编号为 {} 的库存为{},订单商品数量为{}", productId, stock, count);
|
||||
|
||||
if (stock < count) {
|
||||
log.warn("商品编号为{} 库存不足,当前库存:{}", productId, stock);
|
||||
throw new RuntimeException("库存不足");
|
||||
}
|
||||
log.info("开始扣减商品编号为 {} 库存,单价商品价格为{}", productId, product.getPrice());
|
||||
// 扣减库存
|
||||
int currentStock = stock - count;
|
||||
product.setStock(currentStock);
|
||||
productMapper.updateById(product);
|
||||
BigDecimal totalPrice = product.getPrice().multiply(new BigDecimal(count));
|
||||
log.info("扣减商品编号为 {} 库存成功,扣减后库存为{}, {} 件商品总价为 {} ", productId, currentStock, count, totalPrice);
|
||||
log.info("=============PRODUCT END=================");
|
||||
return totalPrice;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
server:
|
||||
port: 5003
|
||||
spring:
|
||||
application:
|
||||
name: seata-product
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
import-check:
|
||||
enabled: false
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
autoconfigure:
|
||||
exclude: com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://127.0.0.1:3306/jeecg_product?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
sql:
|
||||
init:
|
||||
schema-locations: classpath:sql/schema-product.sql
|
||||
seata:
|
||||
enable-auto-data-source-proxy: false
|
||||
service:
|
||||
grouplist:
|
||||
default: 127.0.0.1:8091
|
||||
vgroup-mapping:
|
||||
springboot-seata-group: default
|
||||
# seata 事务组编号 用于TC集群名
|
||||
tx-service-group: springboot-seata-group
|
||||
|
||||
# 无用配置,为了避免扫码全代码导致启动慢
|
||||
minidao:
|
||||
base-package: org.jeecg.modules.jmreport.*
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for product
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `product`;
|
||||
CREATE TABLE `product` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`price` decimal(10, 2) NULL DEFAULT NULL,
|
||||
`stock` int(11) NULL DEFAULT NULL,
|
||||
`last_update_time` timestamp NULL DEFAULT current_timestamp() ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of product
|
||||
-- ----------------------------
|
||||
INSERT INTO `product` VALUES (1, 10.00, 20, '2022-01-13 09:52:50');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for undo_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `undo_log`;
|
||||
CREATE TABLE `undo_log` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`branch_id` bigint(20) NOT NULL,
|
||||
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
|
||||
`rollback_info` longblob NOT NULL,
|
||||
`log_status` int(11) NOT NULL,
|
||||
`log_created` datetime(0) NOT NULL,
|
||||
`log_modified` datetime(0) NOT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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>jeecg-cloud-test</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-cloud-test-seata</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>jeecg-cloud-test-seata-account</module>
|
||||
<module>jeecg-cloud-test-seata-product</module>
|
||||
<module>jeecg-cloud-test-seata-order</module>
|
||||
</modules>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter-cloud</artifactId>
|
||||
<version>${jeecgboot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter-seata</artifactId>
|
||||
<version>3.8.2-RC</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Reference in New Issue
Block a user