官网:https://seata.io/zh-cn/index.html 源码: https://github.com/seata/seata 官方Demo: https://github.com/seata/seata-samples
Seata Server部署:DB存储模式+Nacos(注册&配置中心)部署
步骤一:下载安装包
https://github.com/seata/seata/releases 如下(示例):
步骤二:建表(仅db模式)
全局事务会话信息由3块内容构成,全局事务–>分支事务–>全局锁,对应表global_table、branch_table、lock_table 创建数据库seata,执行sql脚本,文件在script/server/db/mysql.sql(seata源码)中 如下(示例):
mysql.sql脚本如下(示例):
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
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
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
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_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
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);
步骤三:修改store.mode
启动包: seata–>conf–>application.yml,修改store.mode=“db”。
步骤四:修改数据库连接
启动包: seata–>conf–>application.yml,修改store.db相关属性。
如下(示例):
步骤五:配置Nacos
将Seata Server注册到Nacos,修改conf目录下的application.yml
如下(示例):
application.yml文件如下(示例):
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: ip:4560
kafka-appender:
bootstrap-servers: ip:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: ip:8848
namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
group: SEATA_GROUP
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: ip:8848
group: SEATA_GROUP
namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
cluster: default
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
store:
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://ip:3306/seata?rewriteBatchedStatements=true
user: root
password: root密码
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,*.css,*.js,*.html,*.map,*.svg,*.png,*.ico,/console-fe/public
使用nacos时要注意group要和seata server中的group一致,默认group是"DEFAULT_GROUP",示例中修改成了SEATA_GROUP
seataServer.properties文件的配置在/seata/script/config-center/config.txt直接复制粘贴,然后修改配置信息 文件目录如下(示例): config.txt如下(示例): config.txt配置如下(示例):
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=db
store.lock.mode=file
store.session.mode=file
#Used for password encryption
store.publicKey=
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=username
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
nacos创建文件,粘贴配置 如下(示例): 如下(示例): 需要注意的事项 配置事务分组, 要与客户端配置的事务分组一致 (客户端properties配置:spring.cloud.alibaba.seata.tx‐service‐group=default_tx_group) 如下(示例):
步骤六:启动Seata Server
一般而言,需要修改默认端口减少黑客通过默认端口入侵服务器的可能,这里我端口修改成了9091
修改完配置之后,进行压缩,上传到服务器上,给予文件权限,进行解压,去到seata/bin目录下,启动seata-server.sh 最后使用./seata-server.sh -h 106.14.132.94 -p 8091 & 启动 如下(示例): 由于新版本支持了skywalking而我这里没有配置它,所以有这个提示,这个不影响,我们可以查看日志:cat /opt/seata/logs/start.out 发现已经启动起来了 如下(示例):
检查安全组,防火墙的端口是否正常,然后通过部署的ip:seata配置的端口直接进行服务,application.yml中登录用户默认配置的是seata 如下(示例): 浏览器直接服务登录 如下(示例): 可切换成中文,如下(示例):
Seata Client接入:(普通下单业务场景)
用户下单,整个业务逻辑由三个微服务构成:
- 库存服务:对给定的商品扣除库存数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
如下(示例): 这里开多个微服务
创建spring-cloud-alibaba-seata-demo项目
项目代码:https://gitee.com/java_wxid/java_wxid/tree/master/demo/spring-cloud-alibaba-seata-demo 项目结构如下(示例): 删除src文件夹
修改pom.xml
代码如下(示例):
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-alibaba-seata-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud-alibaba-seata-demo</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>mysql-common</module>
<module>account-service</module>
<module>order-service</module>
<module>storage-service</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
<seata.version>1.5.1</seata.version>
<mysql-jdbc.version>5.1.48</mysql-jdbc.version>
<mybatis.version>2.1.1</mybatis.version>
<druid.version>1.2.6</druid.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>${mysql-jdbc.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
创建order-service微服务
修改pom.xml
代码如下(示例):
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-alibaba-seata-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order-service</name>
<description>Demo project for Seata</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--nacos 注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-common</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--加入sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--加入actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>mysql-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建bootstrap.yml
代码如下(示例):
#bootstrap.yml优先级比application.yml优先级高
spring:
#prefix?{spring.profile.active}.${file-extension}
#nacos会根据当前环境去拼接配置名称查找相应配置文件,
#示例:{spring.application.name}-{spring.profiles.active}-{spring.cloud.nacos.config.file-extension}
#获取到值:nacos-autoconfig-service-dev.yml
profiles:
#开发环境dev,测试环境test,生产环境prod
active: dev
application:
#配置应用的名称,用于获取配置
name: order-service
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: ip:8848
config:
#nacos配置中心地址
server-addr: ip:8848
#配置中心的命名空间id
namespace: 9e50b6d9-6c3d-4e7a-b701-10f085e4b98d
#配置分组,默认没有也可以
group: DEFAULT_GROUP
#配置文件后缀,用于拼接配置配置文件名称,目前只支持yaml和properties
file-extension: yaml
#配置自动刷新
refresh-enabled: true
#配置文件的前缀,默认是application.name的值,如果配了prefix,就取prefix的值
#prefix: nacos-autoconfig-service-${spring.profile.active}
# 配置编码
encode: UTF-8
username: nacos
password: nacos
创建application.yml
代码如下(示例):
server:
port: 8020
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://139.224.137.74:3306/seata_at_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: ca0a997ee4770063
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid
修改OrderServiceApplication
代码如下(示例):
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(scanBasePackages = "com.example")
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
创建FeignConfig
代码如下(示例):
package com.example.order.config;
import feign.Logger;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
创建OrderController
代码如下(示例):
package com.example.order.controller;
import com.example.datasource.entity.Order;
import com.example.datasource.vo.ResultVo;
import com.example.order.service.OrderService;
import com.example.order.vo.OrderVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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("/order")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/createOrder")
public ResultVo createOrder(@RequestBody OrderVo orderVo) throws Exception {
log.info("收到下单请求,用户:{}, 商品编号:{}", orderVo.getUserId(), orderVo.getCommodityCode());
Order order = orderService.saveOrder(orderVo);
return ResultVo.ok().put("order",order);
}
}
创建AccountFeignService
代码如下(示例):
package com.example.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "account-service",path = "/account")
@Repository
public interface AccountFeignService {
@RequestMapping("/debit")
Boolean debit(@RequestParam("userId") String userId,@RequestParam("money") int money);
}
创建FallbackAccountFeignServiceFactory
代码如下(示例):
package com.example.order.feign;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.tm.api.GlobalTransactionContext;
import org.apache.commons.lang.StringUtils;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class FallbackAccountFeignServiceFactory implements FallbackFactory<AccountFeignService> {
@Override
public AccountFeignService create(Throwable throwable) {
return new AccountFeignService() {
@Override
public Boolean debit(String userId, int money) {
log.info("账户服务异常降级了");
return false;
}
};
}
}
创建StorageFeignService
代码如下(示例):
package com.example.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name="storage-service",path="/storage")
@Repository
public interface StorageFeignService {
@RequestMapping(path = "/deduct")
Boolean deduct(@RequestParam("commodityCode") String commodityCode,@RequestParam("count") Integer count);
}
创建OrderService
代码如下(示例):
package com.example.order.service;
import com.example.datasource.entity.Order;
import com.example.order.vo.OrderVo;
import io.seata.core.exception.TransactionException;
public interface OrderService {
Order saveOrder(OrderVo orderVo) throws TransactionException;
}
创建OrderServiceImpl
代码如下(示例):
package com.example.order.service.impl;
import com.example.order.feign.AccountFeignService;
import com.example.order.feign.StorageFeignService;
import com.example.order.service.OrderService;
import com.example.order.vo.OrderVo;
import com.example.datasource.entity.Order;
import com.example.datasource.entity.OrderStatus;
import com.example.datasource.mapper.OrderMapper;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountFeignService accountFeignService;
@Autowired
private StorageFeignService storageFeignService;
@Override
@GlobalTransactional(name="createOrder",rollbackFor=Exception.class)
public Order saveOrder(OrderVo orderVo) {
log.info("=============用户下单=================");
log.info("当前 XID: {}", RootContext.getXID());
Order order = new Order();
order.setUserId(orderVo.getUserId());
order.setCommodityCode(orderVo.getCommodityCode());
order.setCount(orderVo.getCount());
order.setMoney(orderVo.getMoney());
order.setStatus(OrderStatus.INIT.getValue());
Integer saveOrderRecord = orderMapper.insert(order);
log.info("保存订单{}", saveOrderRecord > 0 ? "成功" : "失败");
storageFeignService.deduct(orderVo.getCommodityCode(), orderVo.getCount());
Boolean debit= accountFeignService.debit(orderVo.getUserId(), orderVo.getMoney());
if(!debit){
throw new RuntimeException("账户服务异常降级了");
}
Integer updateOrderRecord = orderMapper.updateOrderStatus(order.getId(),OrderStatus.SUCCESS.getValue());
log.info("更新订单id:{} {}", order.getId(), updateOrderRecord > 0 ? "成功" : "失败");
return order;
}
}
创建OrderVo
代码如下(示例):
package com.example.order.vo;
import lombok.Data;
@Data
public class OrderVo {
private String userId;
private String commodityCode;
private Integer count;
private Integer money;
}
创建db.sql
代码如下(示例):
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
`money` int(11) DEFAULT '0',
`status` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) 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 KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
创建storage-service微服务
项目结构如下(示例):
修改pom.xml
代码如下(示例):
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-alibaba-seata-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>storage-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>storage-service</name>
<description>Demo project for Seata</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--nacos 注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-common</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>mysql-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建bootstrap.yml
代码如下(示例):
#bootstrap.yml优先级比application.yml优先级高
spring:
#prefix?{spring.profile.active}.${file-extension}
#nacos会根据当前环境去拼接配置名称查找相应配置文件,
#示例:{spring.application.name}-{spring.profiles.active}-{spring.cloud.nacos.config.file-extension}
#获取到值:nacos-autoconfig-service-dev.yml
profiles:
#开发环境dev,测试环境test,生产环境prod
active: dev
application:
#配置应用的名称,用于获取配置
name: storage-service
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 106.14.132.94:8848
config:
#nacos配置中心地址
server-addr: 106.14.132.94:8848
#配置中心的命名空间id
namespace: 9e50b6d9-6c3d-4e7a-b701-10f085e4b98d
#配置分组,默认没有也可以
group: DEFAULT_GROUP
#配置文件后缀,用于拼接配置配置文件名称,目前只支持yaml和properties
file-extension: yaml
#配置自动刷新
refresh-enabled: true
#配置文件的前缀,默认是application.name的值,如果配了prefix,就取prefix的值
#prefix: nacos-autoconfig-service-${spring.profile.active}
# 配置编码
encode: UTF-8
username: nacos
password: nacos
创建application.yml
代码如下(示例):
server:
port: 8040
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://139.224.137.74:3306/seata_at_storage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: ca0a997ee4770063
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid
修改StorageServiceApplication
代码如下(示例):
package com.example.storage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.example")
public class StorageServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StorageServiceApplication.class, args);
}
}
创建StorageController
代码如下(示例):
package com.example.storage.controller;
import com.example.storage.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/storage")
public class StorageController {
@Autowired
private StorageService storageService;
@RequestMapping(path = "/deduct")
public Boolean deduct(String commodityCode, Integer count) {
storageService.deduct(commodityCode, count);
return true;
}
}
创建StorageService
代码如下(示例):
package com.example.storage.service;
public interface StorageService {
void deduct(String commodityCode, int count);
}
创建StorageServiceImpl
代码如下(示例):
package com.example.storage.service.impl;
import com.example.datasource.entity.Storage;
import com.example.datasource.mapper.StorageMapper;
import com.example.storage.service.StorageService;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
@Autowired
private StorageMapper storageMapper;
@Transactional
@Override
public void deduct(String commodityCode, int count){
log.info("=============扣减库存=================");
log.info("当前 XID: {}", RootContext.getXID());
checkStock(commodityCode,count);
log.info("开始扣减 {} 库存", commodityCode);
Integer record = storageMapper.reduceStorage(commodityCode,count);
log.info("扣减 {} 库存结果:{}", commodityCode, record > 0 ? "操作成功" : "扣减库存失败");
}
private void checkStock(String commodityCode, int count){
log.info("检查 {} 库存", commodityCode);
Storage storage = storageMapper.findByCommodityCode(commodityCode);
if (storage.getCount() < count) {
log.warn("{} 库存不足,当前库存:{}", commodityCode, count);
throw new RuntimeException("库存不足");
}
}
}
创建db.sql
代码如下(示例):
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
insert into `storage_tbl` (`id`, `commodity_code`, `count`) values('1','1000','1000');
-- 微服务对应数据库中添加undo_log表(仅AT模式)
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) 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 KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
创建account-service微服务
项目结构如下(示例):
修改pom.xml
代码如下(示例):
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-alibaba-seata-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>account-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>account-service</name>
<description>Demo project for Seata</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos 注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-common</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>mysql-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建bootstrap.yml
代码如下(示例):
#bootstrap.yml优先级比application.yml优先级高
spring:
#prefix?{spring.profile.active}.${file-extension}
#nacos会根据当前环境去拼接配置名称查找相应配置文件,
#示例:{spring.application.name}-{spring.profiles.active}-{spring.cloud.nacos.config.file-extension}
#获取到值:nacos-autoconfig-service-dev.yml
profiles:
#开发环境dev,测试环境test,生产环境prod
active: dev
application:
#配置应用的名称,用于获取配置
name: account-service
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: ip:8848
config:
#nacos配置中心地址
server-addr: ip:8848
#配置中心的命名空间id
namespace: 9e50b6d9-6c3d-4e7a-b701-10f085e4b98d
#配置分组,默认没有也可以
group: DEFAULT_GROUP
#配置文件后缀,用于拼接配置配置文件名称,目前只支持yaml和properties
file-extension: yaml
#配置自动刷新
refresh-enabled: true
#配置文件的前缀,默认是application.name的值,如果配了prefix,就取prefix的值
#prefix: nacos-autoconfig-service-${spring.profile.active}
# 配置编码
encode: UTF-8
username: nacos
password: nacos
创建application.yml
代码如下(示例):
server:
port: 8050
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://ip:3306/seata_at_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: ca0a997ee4770063
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid
修改AccountServiceApplication
代码如下(示例):
package com.example.account;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.example")
public class AccountServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AccountServiceApplication.class, args);
}
}
创建AccountController
代码如下(示例):
package com.example.account.controller;
import com.example.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
@RequestMapping("/debit")
public Boolean debit(String userId, int money) throws Exception {
accountService.debit(userId, money);
return true;
}
}
创建AccountService
代码如下(示例):
package com.example.account.service;
public interface AccountService {
void debit(String userId, int money) ;
}
创建AccountServiceImpl
代码如下(示例):
package com.example.account.service.impl;
import com.example.account.service.AccountService;
import com.example.datasource.entity.Account;
import com.example.datasource.mapper.AccountMapper;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
public class AccountServiceImpl implements AccountService {
private static final String ERROR_USER_ID = "1002";
@Autowired
private AccountMapper accountMapper;
@Transactional
@Override
public void debit(String userId, int money){
log.info("=============用户账户扣款=================");
log.info("当前 XID: {}", RootContext.getXID());
checkBalance(userId, money);
log.info("开始扣减用户 {} 余额", userId);
Integer record = accountMapper.reduceBalance(userId,money);
log.info("扣减用户 {} 余额结果:{}", userId, record > 0 ? "操作成功" : "扣减余额失败");
}
private void checkBalance(String userId, int money){
log.info("检查用户 {} 余额", userId);
Account account = accountMapper.selectByUserId(userId);
if (account.getMoney() < money) {
log.warn("用户 {} 余额不足,当前余额:{}", userId, account.getMoney());
throw new RuntimeException("余额不足");
}
}
}
创建db.sql
代码如下(示例):
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
insert into `account_tbl` (`id`, `user_id`, `money`) values('1','1','1000');
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) 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 KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
创建mysql-common模块
项目结构如下(示例):
创建MybatisConfig
代码如下(示例):
package com.example.datasource.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan("com.example.datasource.mapper")
public class MybatisConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath*:mybatis/**/*-mapper.xml"));
org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration();
configuration.setUseGeneratedKeys(true);
configuration.setUseColumnLabel(true);
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
}
创建Account
代码如下(示例):
package com.example.datasource.entity;
import lombok.Data;
@Data
public class Account {
private Integer id;
private String userId;
private Integer money;
}
创建Order
代码如下(示例):
package com.example.datasource.entity;
import lombok.Data;
@Data
public class Order {
private Integer id;
private String userId;
private String commodityCode;
private Integer count;
private Integer money;
private Integer status;
}
创建OrderStatus
代码如下(示例):
package com.example.datasource.entity;
public enum OrderStatus {
INIT(0),
SUCCESS(1),
FAIL(-1);
private final int value;
OrderStatus(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
创建Storage
代码如下(示例):
package com.example.datasource.entity;
import lombok.Data;
@Data
public class Storage {
private Integer id;
private String commodityCode;
private Integer count;
}
创建AccountMapper
代码如下(示例):
package com.example.datasource.mapper;
import com.example.datasource.entity.Account;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
@Repository
public interface AccountMapper {
@Select("select id, user_id, money from account_tbl WHERE user_id = #{userId}")
Account selectByUserId(@Param("userId") String userId);
@Update("update account_tbl set money =money-#{money} where user_id = #{userId}")
int reduceBalance(@Param("userId") String userId, @Param("money") Integer money);
}
创建OrderMapper
代码如下(示例):
package com.example.datasource.mapper;
import com.example.datasource.entity.Order;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderMapper {
@Insert("INSERT INTO order_tbl (user_id, commodity_code, count, status, money) VALUES (#{userId}, #{commodityCode}, #{count}, #{status}, #{money})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
int insert(Order record);
@Update("UPDATE order_tbl SET status = #{status} WHERE id = #{id}")
int updateOrderStatus(@Param("id") Integer id, @Param("status") int status);
}
创建StorageMapper
代码如下(示例):
package com.example.datasource.mapper;
import com.example.datasource.entity.Storage;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
@Repository
public interface StorageMapper {
@Select("SELECT id,commodity_code,count FROM storage_tbl WHERE commodity_code = #{commodityCode}")
Storage findByCommodityCode(@Param("commodityCode") String commodityCode);
@Update("UPDATE storage_tbl SET count = count - #{count} WHERE commodity_code = #{commodityCode}")
int reduceStorage(@Param("commodityCode") String commodityCode,@Param("count") Integer count);
}
创建ResultVo
代码如下(示例):
package com.example.datasource.vo;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class ResultVo extends HashMap<String, Object> {
public ResultVo() {
put("code", 0);
put("msg", "success");
}
public static ResultVo error(int code, String msg) {
ResultVo r = new ResultVo();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static ResultVo ok(String msg) {
ResultVo r = new ResultVo();
r.put("msg", msg);
return r;
}
public static ResultVo ok(Map<String, Object> map) {
ResultVo r = new ResultVo();
r.putAll(map);
return r;
}
public static ResultVo ok() {
return new ResultVo();
}
@Override
public ResultVo put(String key, Object value) {
super.put(key, value);
return this;
}
}
开始验证seata是否正常工作
- account_tbl表里面我给了一条记录,userid为1的有1000块钱;
- storage_tbl表里面我给了一条记录,商品编号1000的库存有1000;
- 假设这个编号1000的商品现在的价格是20块钱
正常减库存
我userid为1的用户需要买30个,走正常逻辑是可以正常下订单,订单表里会多一条记录,同时account_tbl表的金额也会减少,storage_tbl表的库存也会减少;
使用apifox或者postman调用接口:http://localhost:8020/order/createOrder
{
"userId": "1",
"commodityCode": "1000",
"count": 30,
"money": 20
}
如下(示例): account_tbl表数据变化 如下(示例): order_tbl表数据变化 如下(示例): storage_tbl表数据变化 如下(示例):订单服务控制台打印: 如下(示例):
不正常减库存
我userid为1的用户需要买10000个,走正常逻辑是不可以正常下订单,第一个钱不够,第二个库存也不够,所以订单表里不会多一条记录,同时account_tbl表的金额不会改变,storage_tbl表的库存不会改变;
使用apifox或者postman调用接口:http://localhost:8020/order/createOrder
{
"userId": "1",
"commodityCode": "1000",
"count": 10000,
"money": 20
}
如下(示例): 订单服务控制台打印: 如下(示例): 库存服务控制台打印: 如下(示例): 三个表都没有发生变化,业务逻辑正确,seata可以正常工作。
|