• 【java_wxid项目】【第七章】【Spring Cloud Alibaba Seata集成】


    主项目链接:https://gitee.com/java_wxid/java_wxid
    项目架构及博文总结:

    项目模块:
    前期规划,实现部分

    java_wxid   
    ├── demo                                                            // 演示模块
    │     └── 模块名称:apache-mybatis-demo模块                            //Apache Mybatis集成(已实现并有博文总结)
    │     └── 模块名称:apache-shardingsphere-demo模块                     //Apache ShardingSphere集成(已实现并有博文总结)
    │     └── 模块名称:design-demo模块                                    //设计模式实战落地(已实现并有博文总结)
    │     └── 模块名称:elasticsearch-demo模块                             //ElasticSearch集成(已实现并有博文总结)
    │     └── 模块名称:mongodb-demo模块                                   //MongoDB集成(已实现并有博文总结)
    │     └── 模块名称:redis-demo模块                                     //Redis集成(已实现并有博文总结)
    │     └── 模块名称:spring-boot-demo模块                               //Spring Boot快速构建应用(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-alibaba-nacos-demo模块                //Spring Cloud Alibaba Nacos集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-alibaba-seata-demo模块                //Spring Cloud Alibaba Seata集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-alibaba-sentinel-demo模块             //Spring Cloud Alibaba Sentinel集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-gateway-demo模块                      //Spring Cloud Gateway集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-hystrix-demo模块                      //Spring Cloud Hystrix集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-open-feign-demo模块                   //Spring Cloud Open Feign集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-ribbon-demo模块                       //Spring Cloud Ribbon集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-security-oauth2-demo模块              //Spring Cloud Security Oauth2集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-security-oauth2-sso-client-demo模块   //Spring Cloud Security Oauth2集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-skywalking-demo模块                   //Spring Cloud Skywalking集成(已实现并有博文总结)
    │     └── 模块名称:spring-cloud-stream-demo模块                       //Spring Cloud Stream集成(已实现并有博文总结)
    │     └── 模块名称:swagger-demo模块                                   //springfox-swagger2集成(已实现并有博文总结)
    │     └── 模块名称:xxl-job模块                                        //xxl-job集成(已实现并有博文总结)
    │     └── 模块名称:apache-spark-demo模块                              //Apache Spark集成
    │     └── 模块名称:etl-hdfs-hive-hbase-demo模块                       //ETL、HDFS、Hive、Hbase集成
    │     └── 模块名称:ddd-mode-demo模块                                  //DDD领域设计
    │     └── 模块名称:netty-demo模块                                     //Netty集成
    │     └── 模块名称:vue-demo模块                                       //前端vue集成
    ├── document                                                        // 文档
    │     └── JavaKnowledgeDocument                                     //java知识点
    │           └── java基础知识点.md                     
    │           └── mq知识点.md
    │           └── mysql知识点.md
    │           └── redis知识点.md
    │           └── springcould知识点.md
    │           └── spring知识点.md
    │     └── FounderDocument                                           //创始人
    │           └── 创始人.md
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    系列文章:快速集成各种微服务相关的技术,帮助大家可以快速集成到自己的项目中,节约开发时间。
    提示:系列文章还未全部完成,后续的文章,会慢慢补充进去的。

    官网: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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    步骤三:修改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/**,/api/v1/auth/login
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    使用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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131

    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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    创建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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    创建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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    创建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/*
          filter:
            stat:
              log-slow-sql: true
              slow-sql-millis: 1000
              merge-sql: false
            wall:
              config:
                multi-statement-allow: true
    
    seata:
      application-id: ${spring.application.name}
      # seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应
      tx-service-group: default_tx_group
      registry:
        # 指定nacos作为注册中心
        type: nacos
        nacos:
          application: seata-server
          server-addr: 106.14.132.94:8848
          namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
          group: SEATA_GROUP
      config:
        # 指定nacos作为配置中心
        type: nacos
        nacos:
          server-addr: 106.14.132.94:8848
          namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
          group: SEATA_GROUP
          data-id: seataServer.properties
    
    #暴露actuator端点
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    feign:
      sentinel:
        enabled: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    修改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);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Configuration
    public class FeignConfig {
        
        @Bean
        public Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
        
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @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);
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @FeignClient(name = "account-service",path = "/account")
    @Repository
    public interface AccountFeignService {
        
        @RequestMapping("/debit")
        Boolean debit(@RequestParam("userId") String userId,@RequestParam("money") int money);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @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("账户服务异常降级了");
                    // 解决 feign整合sentinel降级导致Seata失效的处理  此方案不可取
                    /*if(!StringUtils.isEmpty(RootContext.getXID())){
                        //通过xid获取GlobalTransaction调用rollback回滚
                        //可以让库存服务回滚  能解决问题吗?  绝对不能用
                        try {
                            GlobalTransactionContext.reload(RootContext.getXID()).rollback();
                        } catch (TransactionException e) {
                            e.printStackTrace();
                        }
                    }*/
                    return false;
                }
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @FeignClient(name="storage-service",path="/storage")
    @Repository
    public interface StorageFeignService {
        
        @RequestMapping(path = "/deduct")
        Boolean deduct(@RequestParam("commodityCode") String commodityCode,@RequestParam("count") Integer count);
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    创建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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    创建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;
    
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
        
        @Autowired
        private OrderMapper orderMapper;
        
        @Autowired
        private AccountFeignService accountFeignService;
        
        @Autowired
        private StorageFeignService storageFeignService;
        
        @Override
        //@Transactional
        @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){
                // 解决 feign整合sentinel降级导致Seata失效的处理
                throw new RuntimeException("账户服务异常降级了");
            }
            
            //更新订单
            Integer updateOrderRecord = orderMapper.updateOrderStatus(order.getId(),OrderStatus.SUCCESS.getValue());
            log.info("更新订单id:{} {}", order.getId(), updateOrderRecord > 0 ? "成功" : "失败");
            
            return order;
            
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    创建OrderVo

    代码如下(示例):

    package com.example.order.vo;
    
    import lombok.Data;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Data
    public class OrderVo {
        private String userId;
        /**商品编号**/
        private String commodityCode;
        
        private Integer count;
        
        private Integer money;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    创建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';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    创建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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    创建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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    创建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/*
          filter:
            stat:
              log-slow-sql: true
              slow-sql-millis: 1000
              merge-sql: false
            wall:
              config:
                multi-statement-allow: true
    
    seata:
      application-id: ${spring.application.name}
      # seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应
      tx-service-group: default_tx_group
      registry:
        # 指定nacos作为注册中心
        type: nacos
        nacos:
          application: seata-server
          server-addr: 106.14.132.94:8848
          namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
          group: SEATA_GROUP
      config:
        # 指定nacos作为配置中心
        type: nacos
        nacos:
          server-addr: 106.14.132.94:8848
          namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
          group: SEATA_GROUP
          data-id: seataServer.properties
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    修改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);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    创建StorageService

    代码如下(示例):

    package com.example.storage.service;
    
    public interface StorageService {
        
        /**
         * 扣减库存
         * @param commodityCode 商品编号
         * @param count 扣除数量
         */
        void deduct(String commodityCode, int count);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @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("库存不足");
            }
            
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    创建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';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    创建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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    创建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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    创建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/*
          filter:
            stat:
              log-slow-sql: true
              slow-sql-millis: 1000
              merge-sql: false
            wall:
              config:
                multi-statement-allow: true
    seata:
      application-id: ${spring.application.name}
      # seata 服务分组,要与服务端配置service.vgroup_mapping的后缀对应
      tx-service-group: default_tx_group
      registry:
        # 指定nacos作为注册中心
        type: nacos
        nacos:
          application: seata-server
          server-addr: ip:8848
          namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
          group: SEATA_GROUP
      config:
        # 指定nacos作为配置中心
        type: nacos
        nacos:
          server-addr: ip:8848
          namespace: 50f661a7-0180-4277-bcee-5cfb55c213cd
          group: SEATA_GROUP
          data-id: seataServer.properties
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    修改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);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @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;
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    创建AccountService

    代码如下(示例):

    package com.example.account.service;
    
    public interface AccountService {
        
        /**
         * 用户账户扣款
         * @param userId
         * @param money 从用户账户中扣除的金额
         */
        void debit(String userId, int money) ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    创建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;
    
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Service
    @Slf4j
    public class AccountServiceImpl implements AccountService {
        
        private static final String ERROR_USER_ID = "1002";
        
        @Autowired
        private AccountMapper accountMapper;
        
        /**
         * 扣减用户金额
         * @param userId
         * @param money
         */
        @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);
            
    //        if (ERROR_USER_ID.equals(userId)) {
    //            // 模拟异常
    //            throw new RuntimeException("account branch exception");
    //        }
            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("余额不足");
            }
            
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    创建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';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Configuration
    @MapperScan("com.example.datasource.mapper")
    public class MybatisConfig {
        
        /**
         * 从配置文件获取属性构造datasource,注意前缀,这里用的是druid,根据自己情况配置,
         * 原生datasource前缀取"spring.datasource"
         *
         * @return
         */
        @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();
            //使用jdbc的getGeneratedKeys获取数据库自增主键值
            configuration.setUseGeneratedKeys(true);
            //使用列别名替换列名
            configuration.setUseColumnLabel(true);
            //自动使用驼峰命名属性映射字段,如userId ---> user_id
            configuration.setMapUnderscoreToCamelCase(true);
            factoryBean.setConfiguration(configuration);
            
            return factoryBean.getObject();
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    创建Account

    代码如下(示例):

    package com.example.datasource.entity;
    
    import lombok.Data;
    
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Data
    public class Account {
        private Integer id;
        
        private String userId;
        
        private Integer money;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    创建Order

    代码如下(示例):

    package com.example.datasource.entity;
    
    import lombok.Data;
    
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Data
    public class Order {
        private Integer id;
        
        private String userId;
        /** 商品编号 */
        private String commodityCode;
        
        private Integer count;
        
        private Integer money;
        
        private Integer status;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    创建OrderStatus

    代码如下(示例):

    package com.example.datasource.entity;
    
    public enum OrderStatus {
        /**
         * INIT
         */
        INIT(0),
        /**
         * SUCCESS
         */
        SUCCESS(1),
        /**
         * FAIL
         */
        FAIL(-1);
        
        private final int value;
        
        OrderStatus(int value) {
            this.value = value;
        }
        
        public int getValue() {
            return value;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    创建Storage

    代码如下(示例):

    package com.example.datasource.entity;
    
    import lombok.Data;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @Data
    public class Storage {
        private Integer id;
        
        private String commodityCode;
        
        private Integer count;
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    
    @Repository
    public interface AccountMapper {
        
        /**
         * 查询账户
         * @param userId
         * @return
         */
        @Select("select id, user_id, money from account_tbl WHERE user_id = #{userId}")
        Account selectByUserId(@Param("userId") String userId);
        
        /**
         * 扣减余额
         * @param userId 用户id
         * @param money 要扣减的金额
         * @return
         */
        @Update("update account_tbl set money =money-#{money} where user_id = #{userId}")
        int reduceBalance(@Param("userId") String userId, @Param("money") Integer money);
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    
    @Repository
    public interface OrderMapper {
        
        /**
         * 保存订单
         * @param record
         * @return
         */
        @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);
        
        /**
         * 更新订单状态
         * @param id
         * @param status
         * @return
         */
        @Update("UPDATE order_tbl SET status = #{status} WHERE id = #{id}")
        int updateOrderStatus(@Param("id") Integer id, @Param("status") int status);
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    创建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;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    
    @Repository
    public interface StorageMapper {
        
        /**
         * 获取库存
         * @param commodityCode 商品编号
         * @return
         */
        @Select("SELECT id,commodity_code,count FROM storage_tbl WHERE commodity_code = #{commodityCode}")
        Storage findByCommodityCode(@Param("commodityCode") String commodityCode);
        
        /**
         * 扣减库存
         * @param commodityCode 商品编号
         * @param count  要扣减的库存
         * @return
         */
        @Update("UPDATE storage_tbl SET count = count - #{count} WHERE commodity_code = #{commodityCode}")
        int reduceStorage(@Param("commodityCode") String commodityCode,@Param("count") Integer count);
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    创建ResultVo

    代码如下(示例):

    package com.example.datasource.vo;
    
    import lombok.Data;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author: liaozhiwei
     * @Description: TODO
     * @Date: Created in 19:22 2022/8/30
     */
    @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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    开始验证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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如下(示例):
    在这里插入图片描述
    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如下(示例):
    在这里插入图片描述订单服务控制台打印:
    如下(示例):
    在这里插入图片描述库存服务控制台打印:
    如下(示例):
    在这里插入图片描述三个表都没有发生变化,业务逻辑正确,seata可以正常工作。

  • 相关阅读:
    JavaS
    贪心算法解决会场安排问题
    【Mysql】第7篇--JdbcTemplate(jdbc模版)
    Springboot Actuator未授权访问漏洞复现
    Mythical Beings里的DAO治理指南——Mythical DAO使用方法浅析
    服务器冗余常见问题及解答汇总
    如何在MATLAB中创建一个矩阵?如何在MATLAB中执行矩阵运算?如何在MATLAB中绘制图形?
    对于jQuery选择器和动画效果停止动画的实战心得【前端jQuery框架】
    第05章 Tableau数据可视化
    新购服务器开荒记录(服务器安装宝塔、Nginx、Java、Python、pip、Node、npm)
  • 原文地址:https://blog.csdn.net/java_wxid/article/details/126592324