使用seata实现分布式事务管理

配置

版本说明:
springCloud Alibaba组件版本关系
我用的是spring cloud Alibaba 2.2.1.RELEASE 、springboot 2.2.1.RELEASE、nacos 2.0.1、seata1.2.0,jdk1.8

seata 主要用于在分布式系统中对数据库进行事务回滚,保证全局事务的一致性。

seata的使用非常简单,主要是在涉及到跨服务调用并且要保证各服务事务的一致性的方法上添加注解@GlobalTransactional即可。重点和难点在seata的配置,因此本篇文章的重点也放在seata的配置上。

特别说明:seata和各组件版本对应关系非常重要,如果引用的seata版本跟springboot,cloud不匹配,会产生各种各样的问题,因此务必引起重视。

一、seata的下载和安装

下载地址:Releases · apache/incubator-seata · GitHub

找到1.2.0版本,拉到Assets部分展开,找到自己需要的安装包并下载。

windows的下载后解压即可。

下面说下docker的安装方式:

#拉取Seata镜像
docker pull seataio/seata-server:1.2.0

#运行镜像
docker run --name seata-server -p 8091:8091 -d  seataio/seata-server:1.2.0

#复制seata的配置文件到主机
docker cp seata-server:/seata-server  /root/seata

#停止删除服务
docker stop seata-server
docker rm seata-server

#重新运行镜像
docker run -d --restart always  --name  seata-server -p 8091:8091  -v /root/seata:/seata-server -e SEATA_IP=192.168.200.131 -e SEATA_PORT=8091 seataio/seata-server:1.2.0

#注意:SEATA_IP、SEATA_PORT 一定要重新指定下,不然用docker自动分配的虚拟ip,服务是访问不到的
 

 以下配置相同,无论是Windows、Linux还是docker,只是配置文件位置不同,下面以Windows为例。

1、修改seata-server-1.2.0\seata\conf\file.conf文件,标红部分是需要修改的,其他部分可忽略或根据个人需要修改。

## transaction log store, only used in seata-server
store {
  ## store mode: file、db
  mode = "db"        #!!!!!!!!!这里改为db

  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://192.168.200.131:3306/seata"    #你的msyql地址
    user = "root"    #用户名
    password = "root"    #密码

    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}

2、修改seata-server-1.2.0\seata\conf\registry.conf文件

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"        #改为nacos

  nacos {
    application = "seata-server"
    serverAddr = "192.168.200.199"        #nacos注册中心地址
    namespace = ""
    cluster = "default"
    username = "nacos"        #nacos用户名
    password = "nacos"        #nacos密码

  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"        #!!!!!!!!!改为nacos

  nacos {
    serverAddr = "192.168.200.199"        #!!!!!!!!!配置中心地址
    namespace = "" #配置所在的命名空间,不配置就是public
    group = "SEATA_GROUP"
    username = "nacos"        #!!!!!!!!!用户名
    password = "nacos"        #!!!!!!!!!密码
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

 3、首先启动Nacos服务,然后再启动Seata,查看nacos服务注册中心是否注册上了Seata

双击上图圈中的bat文件,如果一切正常,将会看到started的提示,如果双击后闪退,可采用cmd的方式打开,即可看到启动失败的原因。比如jdk版本和seata版本不对应也会导致闪退。我的jdk最初是17,跟seata不对应,后面提示信息后,调整为jdk8就可正常启动了。说句题外话,jdk17安装后无需进行环境变量配置,并且会将本机的jre设置为最新的版本17。

nacos查看seata是否注册进来:

默认在public的命名空间下,端口号是8091。

二、搭建Seata运行环境

1、在mysql数据库中创建名为seata的库,并创建以下3张数据表

-- -------------------------------- 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_gmt_modified_status` (`gmt_modified`, `status`),KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;-- 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 = InnoDBDEFAULT CHARSET = utf8;-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(`row_key`        VARCHAR(128) NOT NULL,`xid`            VARCHAR(96),`transaction_id` BIGINT,`branch_id`      BIGINT       NOT NULL,`resource_id`    VARCHAR(256),`table_name`     VARCHAR(32),`pk`             VARCHAR(36),`gmt_create`     DATETIME,`gmt_modified`   DATETIME,PRIMARY KEY (`row_key`),KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;

 2、要在参与全局事务的每个数据库中都加入undo_log这张表

-- 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`
(`id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',`branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',`xid`           VARCHAR(100) 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     NOT NULL COMMENT 'create datetime',`log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

 3、从官方github仓库拿到参考配置做修改 incubator-seata/script/client/spring at develop · apache/incubator-seata · GitHub
加到你项目的application.yml中,红色部分需修改为自己的应用名。

seata:
  enabled: true
  application-id: shopping-mall
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: true
  config:
    type: nacos
    nacos:
      namespace:
      serverAddr: 127.0.0.1:8848
      group: SEATA_GROUP
      userName: "nacos"
      password: "nacos"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace:
      userName: "nacos"
      password: "nacos"

 

 4、由于Seata1.2.0支持从Nacos读取配置,所以我们还需要一个bootstrap.yml读取配置信息

#Nacos同springcloud-config-样, 在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。
#springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application

#当前服务端口号
server:
  port: 2002

spring:
  application:
    name: seata-storage-service #当前服务名称
  main:
    allow-bean-definition-overriding: true
  cloud:
    loadbalancer:
      retry:
        enabled: false
    nacos:
      discovery:
        server-addr: 192.168.200.199 #通过虚拟IP访问Nginx主服务器,然后反向代理到其中一台nacos注册中心
      config:
        server-addr: 192.168.200.199 #通过虚拟IP访问Nginx主服务器,然后反向代理到其中一台nacos配置中心

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
  ReadTimeout: 5000 #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  connectTimeout: 5000  #指的是建立连接后从服务器读取到可用资源所用的时间
 

 在需要用到seata的服务添加上依赖

  <!--SpringCloudAlibaba的seata分布式事务管理-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <version>2.2.0.RELEASE</version>
             <!--需要排除掉自带的seata-spring-boot-starter,否则无法启动,并提示jar冲突-->
                ​​​<exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--指定与安装的seata版本一致,重要!-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.2.0</version>
        </dependency>

jar引用这里还需要注意,如果只引入下面的依赖,将不能实现事务回滚,seata 1.2版本需要引用spring-cloud-starter-alibaba-seata依赖。这个依赖里才有xid传递的功能。 seata-spring-boot-starter依赖并没有xid传递的功能。spring-cloud-alibaba-seata需要和seata-spring-boot-starter一起引入并注意它们的版本对应关系,并在此之前引入nacos注册和配置中心的依赖。

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.2.0</version>
        </dependency>

 6、将Seata项目克隆或下载到本地 GitHub - apache/incubator-seata: :fire: Seata is an easy-to-use, high-performance, open source distributed transaction solution.

 重点是下面红色标记的文件:

下载到本地后进入\seata\script\config-center目录修改config.txt为以下内容:

service.vgroupMapping.my_test_tx_group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.200.131:3306/seata?useUnicode=true   
store.db.user=root
store.db.password=root

store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

 

 运行仓库中提供的nacos脚本,将以上信息提交到nacos控制台,如果有需要更改,可直接通过控制台更改。如果电脑中安装有git,双击后在弹出的窗口中选择使用git运行sh命令,就会看到seata的配置同步到nacos配置中心。

 注意:如果你的nacos地址不是本机,需要修改脚本nacos-config.sh,他是默认使用本机nacos的,修改地方如下

 

 导入完成后,在nacos配置中心,即可看到导入的配置文件,都归到SEATA_GROUP组下,足足有7页之多

三、测试事务是否成功

经过以上配置,就可以验证seata是否生效了

    @GlobalTransactional@Transactional@Overridepublic SubmitOrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {System.out.println("seata分布式事务id:"+RootContext.getXID());//用于在同一线程共享数据,减少方法传参confirmVoThreadLocal.set(orderSubmitVo);SubmitOrderResponseVo responseVo=new SubmitOrderResponseVo();responseVo.setCode(0);MemberRespVo memberRespVo = LoginUserInterceptor.localUser.get();String redisOrderKey=OrderConstants.USER_TOKEN_PREFIX + memberRespVo.getId();String redisToken = stringredisTemplate.opsForValue().get(redisOrderKey);//验证提交的令牌与后台令牌是否一致//验证的逻辑【验证令牌和删除令牌要保证原子性】String orderToken = orderSubmitVo.getOrderToken();// 使用lua脚本解锁,保证原子性 0 验证失败 1 验证并删除成功String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";//原子验证令牌,若成功则删除令牌,保证订单提交的幂等性Long result = stringredisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(redisOrderKey), orderToken);if(result==0L){//验证失败,直接返回responseVo.setCode(1);return responseVo;}else {//令牌验证成功,执行创建订单等业务流程OrderCreateTo order = createOrder();//验价//1.后台计算的价格BigDecimal payPrice = order.getOrder().getPayAmount();//2.前端提交的价格BigDecimal payPrice1 = orderSubmitVo.getPayPrice();//不用比较相等,只需前端和后台价格相差的绝对值小于0.01元即1分,即视为相等if(Math.abs(payPrice.subtract(payPrice1).doubleValue())<0.01){//3、创建订单saveOrder(order);//订单创建成功后立即锁定库存,防止缺货WareSkuLockVo lockVo=new WareSkuLockVo();lockVo.setOrderSn(order.getOrder().getOrderSn());List<OrderItemVo> orderItemVos = order.getOrderItems().stream().map(item -> {OrderItemVo orderItemVo = new OrderItemVo();orderItemVo.setSkuId(item.getSkuId());orderItemVo.setTitle(item.getSkuName());orderItemVo.setCount(item.getSkuQuantity());return orderItemVo;}).collect(Collectors.toList());lockVo.setLocks(orderItemVos);//4、远程锁库存R r = wmsFeignService.lockStock(lockVo);if(r.getCode()==0){//锁定库存成功responseVo.setOrder(order.getOrder());int i=10/0;return responseVo;}else{//锁定库存失败throw new NoStockException(1L);// responseVo.setCode(3);// return responseVo;}}else{//价格不相等返回2responseVo.setCode(2);return responseVo;}}}

上面的代码中,我在订单服务中保存订单的方法中添加了@GlobalTransactional注解,该服务远程调用了库存服务的wmsFeignService.lockStock(lockVo)的方法,实现当订单创建成功扣减库存,为了模拟发生异常实现全局事务回滚,在扣减库存处添加了以下代码:

int i=10/0;

 当前在订单服务,由于我们添加了@Transactional注解,异常发生时触发订单服务数据回滚,而库存服务属于远程调用的服务,数据在另一个数据库中,此时实现库存服务事务回滚依靠的就是seata了,通过引入@GlobalTransactional来实现。来看控制台,实现了库存服务的数据回滚。

这里还有一个地方需要注意,只需要在当前服务的方法上添加@GlobalTransactional注解,远程调用的库存服务无需添加该注解,只需要添加@Transactional即可。

seata回滚成功后会将业务数据恢复到方法调用前的样子,同时seata数据库中的global_table等表格中的数据也会清除,如果想查看seata操作数据的流程,可在业务方法上添加断点调试,就可看到中途的数据演变,下面是global_table在方法执行时seata插入的一条数据:

注意:上面的seata使用的是AT模式保证全局事务的一致性,在高并发情景下可能并不适用。

 本文参考自:Seata1.2.0安装配置Nacos注册配置中心,以及实际运行案例_seata 1.2.0-CSDN博客

向原博主表示感谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/64406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【总结】动态规划

线性dp LeetCode题单&#xff0c; 从记忆化搜索到递推 Pre&#xff1a; 从最初状态到最终状态等价&#xff0c;那么从最终状态开始和最初状态开始结果一样。 递归时不会产生其他负面结果&#xff0c;即无论何时进入递归&#xff0c;只要递归参数相同&#xff0c;结果就相同。 …

RabbitMQ中的异步Confirm模式:提升消息可靠性的利器

在现代分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff09;扮演着至关重要的角色&#xff0c;它能够解耦系统组件、提高系统的可扩展性和可靠性。RabbitMQ作为一款广泛使用的消息队列中间件&#xff0c;提供了多种机制来确保消息的可靠传递。其中&#xff…

买卖预测工具

设计一个用于在交易市场中寻找确定性或大概率盈利的买卖预测工具是一个具有挑战性但非常有潜力的项目。你可以通过以下几个步骤进行思路规划&#xff1a; 1. 明确目标 大概率盈利&#xff1a;工具的目的是找出大概率盈利的交易机会。不能完全依赖于100%确定性&#xff0c;因为…

【数据结构】数据结构简要介绍

数据结构是计算机科学中用于组织、管理和存储数据的方式&#xff0c;以便于高效地访问和修改数据。 数据结构的分类&#xff1a; 数据结构可以大致分为两类&#xff1a;线性结构和非线性结构。 1. 线性结构 线性结构中的数据按顺序排列&#xff0c;每个元素有唯一的前驱和后…

note 41:账务系统开发规范

目录 系统设计 防重控制 流量控制 并发控制 异常处理 备份机制 系统开发​​​​​​​ 前端队列操作 外系统交互 ​​​​​​​​​​​​​​ 系统设计 防重控制 对于进入到系统中的数据&#xff08;文件导入、手工录入、系统直连等&#xff09;以及本系统发往外…

Circular Spanning Tree(树的性质)

Circular Spanning Tree 本道题目加深理解树的性质&#xff1a; 思路&#xff1a; 首先考虑什么情况是NO&#xff0c;那么不难想当字符串全是0的时候一定是不行的&#xff0c;因为这样就构成环了&#xff0c;还有一种情况是1的个数为奇数的时候是不行的&#xff0c;一棵树中为…

linux安装nginxs报错:openssl not found

系统&#xff1a; linux 版本&#xff1a;centOS7 nginx版本&#xff1a;nginx-1.20.2 linux安装nginx时 执行下面命令时报错&#xff1a; ./configure --with-http_stub_status_module --with-http_ssl_module --prefix/usr/local/nginxchecking for OpenSSL library ... not …

【论文笔记】Contrastive Learning for Sign Language Recognition and Translation

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Contrastive Learning for…

docker redis安装

一.镜像拉取 docker pull redis:5.0新建文件 touch /home/redis/redis.conf touch /home/redis/redis_6379.pid # bind 192.168.1.100 10.0.0.1 # bind 127.0.0.1 ::1 #bind 127.0.0.1protected-mode noport 6379tcp-backlog 511requirepass roottimeout 0tcp-keepali…

【CSS in Depth 2 精译_096】16.4:CSS 中的三维变换 + 16.5:本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第五部分 添加动效 ✔️【第 16 章 变换】 ✔️ 16.1 旋转、平移、缩放与倾斜 16.1.1 变换原点的更改16.1.2 多重变换的设置16.1.3 单个变换属性的设置 16.2 变换在动效中的应用 16.2.1 放大图标&am…

小程序租赁系统开发的优势与实践探索

内容概要 小程序租赁系统开发正在引起广泛关注&#xff0c;特别是在数字化快速发展的今天。很多企业开始意识到&#xff0c;小程序不仅能为他们带来更多的客户&#xff0c;还能极大地提高管理效率。借助小程序&#xff0c;用户在租赁时可以更加方便地浏览和选择产品&#xff0…

机器人C++开源库The Robotics Library (RL)使用手册(二)

由于RL库采用跨平台CMake源码,可以轻松在win、ubantu等平台部署、编译,win通常用VS编译器,为了便于使用、阅读,需要将CMake编译成VS工程。 1、准备三个工具:CMake、VS、QT 为了在Windows上编译RL和依赖项,您需要安装一个编译器(例如。,Visual Studio 2017)和跨平台构…

如何在LabVIEW中更好地使用ActiveX控件?

在LabVIEW中&#xff0c;ActiveX控件可以帮助实现与其他应用程序或第三方组件的集成&#xff08;例如Microsoft Excel、Word、Internet Explorer等&#xff09;。以下是一些建议&#xff0c;帮助您更好地在LabVIEW中使用ActiveX控件&#xff1a; ​ 1. 理解ActiveX控件的基本原…

如何使用Python从SACS结构数据文件中提取节点数据信息并导出到EXCEL

在现代工程设计中&#xff0c;结构分析和数据处理是不可或缺的一部分。特别是在海洋工程、桥梁建设等领域&#xff0c;SACS文件被广泛应用。这种文件格式包含了结构模型的各种重要信息&#xff0c;包括节点&#xff08;JOINT&#xff09;、构件&#xff08;ELEMENT&#xff09;…

如何判断一个学术论文是否具有真正的科研价值?ChatGPT如何提供帮助?

目录 1.创新性与学术贡献的超级加分✔ 2.科研过程中的各个环节—从0到1✔ 3.创新性与理论深度的完美结合✔ 4.论证与写作的清晰性✔ 5.数据整理和文献回顾——效率与精准并存✔ 6.创新性要求辅助✔ 总结 宝子们&#xff0c;学术论文写作的旅程是不是感觉像是走进了迷雾森…

学习threejs,THREE.CircleGeometry 二维平面圆形几何体

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.CircleGeometry 圆形…

【微服务】SpringBoot 自定义消息转换器使用详解

目录 一、前言 二、SpringBoot 内容协商介绍 2.1 什么是内容协商 2.2 内容协商机制深入理解 2.2.1 内容协商产生的场景 2.3 内容协商实现的常用方式 2.3.1 前置准备 2.3.2 通过HTTP请求头 2.3.2.1 操作示例 2.3.3 通过请求参数 三、SpringBoot 消息转换器介绍 3.1 H…

深入理解Composer自动加载机制

Composer是PHP生态系统中最常用的依赖管理工具之一&#xff0c;它不仅能够帮助开发者管理项目的依赖关系&#xff0c;还能够自动加载这些依赖项。自动加载机制是Composer的核心功能之一&#xff0c;通过自动加载&#xff0c;开发者可以在运行时按需加载所需的类和文件&#xff…

【游戏设计原理】35 - 委员会设计

一、 分析并总结 核心内容 定义&#xff1a;委员会设计&#xff08;Design by Committee&#xff09;是指游戏开发团队通过集体协作完成设计&#xff0c;这种模式结合了多样化的创意和个体专长&#xff0c;但也可能因缺乏一致性而导致设计的混乱。优势&#xff1a;多样性带来…

【Java】IO流练习

IO流练习 题干&#xff1a; 根据指定要求&#xff0c;完成电话记录、 注册、登录 注册 题干&#xff1a; 完成【注册】功能&#xff1a; 要求&#xff1a; 用户输入用户名、密码存入users.txt文件中 若users.txt文件不存在&#xff0c;创建该文件若users.txt文件存在 输入…