Idea+maven+spring-cloud项目搭建系列--13 整合MyBatis-Plus多数据源dynamic-datasource

前言:对于同一个系统,不同的租户需要自己独立分隔的数据库(每个数据库的表结构可以是相同的),同时也要支持跨数据源的查询;并且支持分布式事务,如果这里不使用分库分表插件,需要怎样实现?本文采用MyBatis-Plus下的dynamic-datasource 进行实现。

MyBatis-Plus的dynamic-datasource 官网;

开始整合:

1 spring-cloud 整合多数据源:

1.1 maven pom jar包,如果启动发生问题则需要排除版本jar 包冲突的问题:

<!-- mybatis 多数据源切换依赖jar -->
<!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter -->
<dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
     <version>3.5.1</version>
 </dependency>
 <!-- mybatis plus 方便后续业务开发 -->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.1.1</version>
 </dependency>
  <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-extension</artifactId>
     <version>3.4.2</version>
 </dependency>
  <!-- mysql的连接器 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
  <!-- mysql的分页插件便于业务查询分页处理 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.1.6</version>
    <exclusions>
        <exclusion>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>3.1</version>
</dependency>
 <!-- druid的连接池 -->
<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid-spring-boot-starter</artifactId>
     <version>1.2.8</version>
 </dependency>
  <!-- xxl job 定时任务 便于后续定时任务的扩展,如果不需要可以手动去除 -->
<dependency>
     <groupId>com.xuxueli</groupId>
     <artifactId>xxl-job-core</artifactId>
     <version>2.3.0</version>
 </dependency>
  <!-- web jar 便于hppt 通信 -->
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  <!-- feign 接口 便于服务之间的通信,如果不需要可以手动去除 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>
   <!-- lombok 自动生成get set 等方法便于开发  -->
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <optional>true</optional>
 </dependency>

1.2 bootstrap.yml 文件配置数据源信息:

server:
  port: 8010
  
spring:
  autoconfigure:
    # 排除原有的连接池装配
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  # dynamic-datasource-spring-boot-starter 动态数据源的配置内容
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master
          username: root
          password: 123
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        slave1:
          url: jdbc:mysql://localhost:3306/slaveone
          username: root
          password: 123
          driver-class-name: com.mysql.jdbc.Driver
        slave2:
          url: jdbc:mysql://localhost:3306/slavetwo?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
          username: root
          password: 123
          driver-class-name: com.mysql.jdbc.Driver

      # druid 公共配置 参数: https://www.jianshu.com/p/f8b720737b20
      druid:
        # 连接池初始化大小
        initialSize: 5
        # 最小空闲连接数
        minIdle: 5
        # 最大连接数
        maxActive: 30
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        # 配置一个连接在池中最大生存的时间,单位是毫秒
        maxEvictableIdleTimeMillis: 900000
        # 配置检测连接是否有效
        validationQuery: SELECT 'x'
        # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,
        # 如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
        testWhileIdle: true
        # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
        testOnBorrow: false
        # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
        testOnReturn: false
        #  	是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,
        # 比如说oracle。在mysql下建议关闭。
        poolPreparedStatements: true
        # 	要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 属性类型是字符串,通过别名的方式配置扩展插件,监控统计用的filter:stat;
        # 日志用的filter:log4j防御sql注入的filter:wall
        filters: stat,wall,slf4j,config
        # 公用监控数据
        useGlobalDataSourceStat: true
        # 慢日志输出
        stat:
          log-slow-sql: true
          merge-sql: true
          # 10 秒
          slow-sql-millis: 10000

# mybatis mapper 包扫描路径
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml

# xxl job 任务地址
xxl:
  job:
    admin:
      addresses: http://localhost:8080/xxl-job-admin
    accessToken: lgx123456
    executor:
      appname: dev-bluegrass-dynamic-service
      logpath:
      logretentiondays: 10
      ip: localhost
      port: 9087




1.3 启动类增加路径扫描:

package org.lgx.bluegrass.bluegrassdynamic;

import org.lgx.bluegrass.api.constant.BaseConstant;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
// Feign 路径扫描
@EnableFeignClients("xxxx")
// mappper 包路径扫描
@MapperScan(basePackages ={"xxx.bluegrassdynamic.mapper"})
public class BluegrassDynamicApplication {
    public static ConfigurableApplicationContext applicationContext;
    public static void main(String[] args) {
        applicationContext = SpringApplication.run(BluegrassDynamicApplication.class, args);
    }

}

2 多数据源主动切换:

考虑方法:虽然使用@Ds 可以在类中进行切换,但是需要在每个类或者方法上增加改注解,如果增加错误或者漏加,就会切错数据库或者默认访问到主库中;那么是否可以有一个切面或者拦截器,可以在http 请求进入到方法之前,收到完成一次切换;
2.1 定义拦截器根据当前登录的用户的便签完成数据源的切换:

import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class HttpRequestDynamic  extends HandlerInterceptorAdapter {
    final static ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            // 不是 httpreuqest 请求直接放行
            return true;
        }
        // 当前用户
        String userDb = request.getHeader("userDb");
        if (StringUtils.hasText(userDb)){
            // 获取用户所属数据源
            String dbName = getDbName(userDb);
            if (StringUtils.hasText(dbName)){
                // 切换数据源
                DynamicDataSourceContextHolder.push(dbName);
                threadLocal.set(true);
            }
        }

        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在方法执行完毕或者执行报错后,移除数据源
        if (null != threadLocal.get() && threadLocal.get()){
            DynamicDataSourceContextHolder.clear();
        }
        threadLocal.remove();
    }
    private String getDbName(String userDb) {
        // 此处根据用户便签动态映射配置的数据库
        return userDb;
    }
}

2.2 将拦截器交由spring 管理:


import org.lgx.bluegrass.bluegrassdynamic.interceptor.HttpRequestDynamic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfiguration implements WebMvcConfigurer {
    @Autowired
    private HttpRequestDynamic httpRequestDynamic;

    /**
     * 拦截器配置
     *
     * @param registry 注册类
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	// 除了请求报错和请求静态资源外拦截一切http 请求
        registry.addInterceptor(httpRequestDynamic).addPathPatterns("/**")
                .excludePathPatterns(
                        "/file/get/*",
                        "/image/view/*",
                        "/**/error"
                );
    }
}

2.3 此处扩展,对xxl job 定时任务进行aop拦截:



import com.xxl.job.core.context.XxlJobHelper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class XxlJobHandlerMonitor {

    @Pointcut("@annotation(com.xxl.job.core.handler.annotation.XxlJob)")
    public void xxlJobCut() {
    }

    @Before(value = "xxlJobCut()")
    public void permissionCheck(JoinPoint joinPoint) {

    }

    @Around(value = "xxlJobCut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String param = XxlJobHelper.getJobParam();
        // 解析参数
        // 根据参数所带的目标表数据库,可以进行

        System.out.println("\"123\" = " + "123");

        return joinPoint.proceed();
    }
}

2.4 此处扩展,对feign 进行拦截(可以将user的便签信息设置到header中,同各个服务的拦截器协同作用):
方式1 在feign 的http 请求发送之前,构建header:



import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.lgx.bluegrass.api.constant.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @Description
 * @Date 2021/4/16 15:39
 * @Author lgx
 * @Version 1.0
 */
@Configuration
public class FeignConfiguration implements RequestInterceptor {
    private Logger logger = LoggerFactory.getLogger(FeignConfiguration.class);

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        if (attributes != null) {
            String authorization = attributes.getRequest().getHeader(Constant.X_AUTHORIZATION);
            if (null != authorization) {
                requestTemplate.header(Constant.X_AUTHORIZATION, authorization);
            }
            String dbName = attributes.getRequest().getHeader(Constant.dbName);
            if (null != dbName) {
                requestTemplate.header(Constant.dbName, dbName);
            }
            String userDb = attributes.getRequest().getHeader(Constant.userDb);
            if (null != userDb) {
                requestTemplate.header(Constant.userDb, userDb);
            }
        }
    }
}

Constant.java:

public class Constant{
        public static final String BASIC_AUTH_TOKEN_PREFIX = "Basic";
        public static final String X_AUTHORIZATION = "X-Authorization";
        public static final String dbName = "dbName";
        public static final String userDb = "userDb";
}

方式二,feign 方法中增加header 参数:
feign 接口定义:

import org.lgx.bluegrass.api.service.impl.SyncFeignDynamicServiceFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "dynamic", url = "http://localhost:8010", fallback = SyncFeignDynamicServiceFallBack.class)
public interface SyncFeignDynamicService {
	// @RequestHeader("userDb") 将用户所属数据库信息放入header 请求头中
    @RequestMapping("/user/getUserById")
    Object getUserById(@RequestParam("userId") Integer userId,@RequestHeader("userDb") String userDb) ;

}

controller测试方法,配合拦截器使用在进入该方法之前已经完成数据源切换:

  @RequestMapping("/getUserById")
    public User getUserById(@RequestParam("userId") Integer userId) {
        return userService.getUserById(userId);
    }

3 业务中跨数据源使用 :

3.1 增加bean 的获取类,通过改工具获取spring 容器的bean:



import org.lgx.bluegrass.bluegrassdynamic.BluegrassDynamicApplication;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextUtils {

    public static <T> T getBean(String beanName){
        return (T) BluegrassDynamicApplication.applicationContext.getBean(beanName);
    }
    public static <T> T getBean(Class classz){
        return (T) BluegrassDynamicApplication.applicationContext.getBean(classz);
    }
}

BluegrassDynamicApplication.applicationContext 对应启动类,在类启动后已经对其进行赋值:
在这里插入图片描述
3.2 service 中业务调用:
首先需要明确自己需要跨哪几个库进行数据的增删改查;然后在创建方法后,有几种选择可以完成数据源的切换;
(1)可以在方法上增加改数据源的标识:
在这里插入图片描述
(2)也可以手动切换数据库:

//切换数据源
* DynamicDataSourceContextHolder.push("配置的数据库名称");
* //中间的业务操作。。。
* //可以最后选择清理掉此数据源
* DynamicDataSourceContextHolder.clear();

手动切换请务必保证push和clear 的成对性;

3.2 跨表操作数据要想保证各个数据库数据的一致性,需要使用@DSTransactional 对方法进行标记,开启分布式事务;
在这里插入图片描述

3.3 切换数据源查询需要注意数据源切换失效的几个事项:

  • 同一个方法中进行方法this调用,因为使用的不是spring 加强的bean ,此时即使后面方法使用@DS 进行标记也会失败,此时要么从spring 容器中获取需要的bean,或者将方法移入其它的类;可以通过本文中已经放入的工具类完成 bean 的获取:ApplicationContextUtils.getBean(类.getClass());
  • 在跨数据源进行数据的修改时需要使用 @DSTransactional 而不是 sprin原生的@Transactional 注解,进行分布式事务的保证;
  • 因为@DSTransactional 实际上也是采用的aop 模式进行的加强,所有原有对@Transactional 事务失效的方式@DSTransactional 也会失效;

4 总结:

通过引入多数据源dynamic-datasource ,druid ,mysql-connector,pagehelper,mybatis-plus 完成对spring-cloud 的多数据源整合,通过使用@DS 和 @DSTransactional 保证数据源的切换和分布式事务的支持;

5 参考:

5.1 mybatis-plus 多数据源;
5.2 多数源配置其一(dynamic-datasource);
5.3 mybatis-plus 多数据源,源码解析;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用dd复制将乌班图系统(Ubuntu22.04)完整迁移到新硬盘并扩容

我的折磨历程 开始的时候用乌班图的时候&#xff0c;不懂事&#xff0c;根目录太小了&#xff0c;后来就满了&#xff0c;就就感觉完全没法用&#xff0c;看着现在硬盘贼便宜&#xff0c;去狗东买了个新的硬盘。感觉挂载硬盘并不能解决我的问题&#xff0c;最后选择了保留系统数…

ython和PyTorch实现ChatGPT批量AI智能写作

怎么实现用chatgpt批量写作 ChatGPT是一种针对文本生成的自然语言处理工具&#xff0c;它可以用于生成大量的文本内容。但是&#xff0c;由于ChatGPT需要的计算资源较大&#xff0c;处理时间较长&#xff0c;因此在批量写作时需要考虑花费的时间和资源。 以下是一些步骤&…

又一个免费GPT-4工具 Cursor,程序员写代码将被颠覆

每天都被openai震撼到&#xff0c; 他们家被广为人知的产品是chatgpt&#xff0c;就是那个聊天工具。现在已经开始有越来越多的产品集成openai&#xff0c;比如微软的office&#xff0c;bing。现在又一个工具出现&#xff0c;一个叫Cursor的编辑器已经集成了openai的GPT-4&…

Spring系列(六) --- SpringBoot 与 Servlet 的比较及 Spring 读取配置文件的方式

SpringSpringBoot VS ServletSpring 读取配置文件的方式yml 和 properties 的区别SpringBoot VS Servlet Spring 读取配置文件的方式 1 Value 注解获取单个配置项 如在 yml 中定义一个 qq 音乐的 token; 然后输出, 如下: 2 针对对象的读取: ConfigurationProperties 在 yml 中…

YOLOv5添加辅助训练头

1. 介绍 思路 添加 Aux head 的主要原因是让网络中间层学到更多信息,有更丰富的梯度信息帮助训练。这里要注意,好的梯度信息能够让相同参数量的网络学的更好。 作者原文为: By letting the shallower auxiliary head directly learn the information that lead head has l…

【C#基础】泛型的概念?有什么例子?在游戏中有什么可以使用的地方?

概念 让chatGpt来为我们讲解。 在C#中&#xff0c;泛型是一种允许开发人员编写可重用代码&#xff0c;可以处理多种数据类型的特性。 使用泛型&#xff0c;可以创建类、方法、接口和委托这种不属于任何特定数据的类型&#xff0c;但可以处理满足某些约束条件的任何数据类型。…

手机银行评测系列:北京银行“京彩生活”7.0从用户视角出发,实现沉浸式体验重塑

易观&#xff1a;2023年3月28日&#xff0c;北京银行发布“京彩生活”APP 7.0版本&#xff0c;从旅程再造、特色金融、场景生态、平台联动、协同经营、体验管理和安全守护七大方面全面升级&#xff0c;从用户视角出发&#xff0c;重塑用户旅程&#xff0c;简化操作流程&#xf…

PDF Extra(安卓)

首先&#xff0c;软件是一个一体化的扫描仪和编辑器&#xff0c;工具主要包含有编辑&#xff0c;创建&#xff0c;转换&#xff0c;阅读和查看&#xff0c;其它等等多个功能类型。 编辑里面包含有编辑文本和图像&#xff0c;填写并签署&#xff0c;组织页面&#xff0c;压缩&am…

PLG 基础概念和关键点

什么是 PLGPLG 是 Product Led Growth 的缩写&#xff0c;常翻译为产品增长或产品主导型增长。这个概念最早是风投公司 OpenView 2016年提出的。定义&#xff1a;PLG 是一个聚焦终端用户的增长模型&#xff0c;依赖于产品自身作为获取、转化、扩展客户的核心动力。• 以产品来驱…

入行软件测试7年,才知道原来字节跳动这么容易进

当前就业环境&#xff0c;裁员、失业消息满天飞&#xff0c;好像有一份工作就不错了&#xff0c;更别说高薪了。其实这只是一方面&#xff0c;而另一方面&#xff0c;各大企业依然求贤若渴&#xff0c;高技术人才依然紧缺&#xff0c;只要你技术过硬&#xff0c;拿个年薪50w不是…

vue3快速上手

Vue3快速上手 1.Vue3简介 2020年9月18日&#xff0c;Vue.js发布3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;海贼王&#xff09;耗时2年多、2600次提交、30个RFC、600次PR、99位贡献者github上的tags地址&#xff1a;https://github.com/vuejs/vue-next/release…

核心 Android 调节音量的过程

核心 Android 系统提供的调节音量的方法 核心 Android 系统提供了多种调节音量的方法&#xff0c;这些方法主要包括如下这些。 如在 Android Automotive 调节音量的过程 中我们看到的&#xff0c;CarAudioService 最终在 CarAudioDeviceInfo 中 (packages/services/Car/servi…

开源DataX集成可视化项目Datax-Web的使用

上一篇文章我们已经搭建好了 Datax-Web 后台&#xff0c;这篇文章我们具体讲一下如何通过Datax-Web来配置&#xff0c;同步MySQL数据库。 目标 MySql数据库全量同步 1.执行器配置 1、"调度中心OnLine:"右侧显示在线的"调度中心"列表, 任务执行结束后, 将会…

红黑树、B树以及B+树及应用

目录 一.二叉查找树(二叉搜索树&#xff0c;BST) 1.1查找操作 1.2插入操作 1.3删除操作 1.4支持重复数据的二叉查找树 1.5二叉查找树的性能分析 二.平衡二叉查找树 2.1定义 2.2红黑树 2.3为什么红黑树这么受欢迎 三.哈希表为什么不能替代二叉查找树 四.MySQL数据库索…

基于springboot实现学生综合成绩测评系统【源码】分享

基于springboot实现学生综合成绩测评系统演示开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包…

机器学习模型部署PMML

PMML 简介 预测模型标记语言PMML&#xff08;Predictive Model Markup Language&#xff09;是一套与平台和环境无关的模型表示语言&#xff0c;是目前表示机器学习模型的实际标准。从2001年发布的PMML1.1&#xff0c;到2019年最新4.4&#xff0c;PMML标准已经由最初的6个模型…

python+vue高校教务选课管理系统django

1. 简介&#xff1a;学生选课系统是学校管理中不可缺少的一部分&#xff0c;它将方便教师发布课程信息、学生浏览课程信息和选课&#xff0c;为两者提供充分的课程信息和方便的查询手段&#xff0c;极大的提高人事劳资管理的效率。 2. 功能&#xff1a;实现基于B/S架构学…

使用SpringBoot+JWT+MybatisPlus实现简单的登陆状态验证

文章目录前言一、JWT是什么&#xff1f;二、使用步骤1.创建项目&#xff0c;导入依赖&#xff0c;配置、引入工具类2.编写LoginController和UserController3.编写跨域拦截器和token验证拦截器4.全局拦截器配置三、业务逻辑四、测试总结前言 登陆功能是每个系统的最基本功能&am…

国内怎么注册chatgpt4-gptchat账号用多久

GPT &#xff08;Generative Pre-trained Transformer&#xff09;是一种强大的自然语言处理人工智能技术&#xff0c;可以用于各种语言任务&#xff0c;包括机器翻译、问答系统、自然语言生成等。作为一种技术&#xff0c;GPT并没有一个固定的玩法&#xff0c;但可以通过各种工…

VMware(虚拟机)安装CentOS7并且连接XShell教程

VMware&#xff08;虚拟机&#xff09;安装CentOS7 安装准备&#xff08;本文安装Centos7.6版本&#xff09; Centos7.6下载地址 创建 选择自定义 下一步默认 选择稍后安装 选择好下一步 修改下一步 根据自己的需求修改 虚拟机内存分配默认即可 先用默认后面…