对boot项目拆分成cloud项目的笔记

引言:这里我用的是新版本的技术栈

spring-boot-starter-parent                        ==>3.2.5

mybatis-spring-boot-starter                      ==>3.0.3

mybatis-plus-boot-starter                         ==>3.5.5

spring-cloud-dependencies                     ==>2023.0.1

spring-cloud-alibaba-dependencies        ==>2022.0.0.0

nacos                                                       ==>v2.3.2-slim

笔记也可以参考黑马的笔记:Docs

下面这些是我自己拆分自己项目的过程及其笔记。

一、引入项目中关键依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
    </parent>

    <properties>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
        <mybatis.spring>3.0.3</mybatis.spring>
        <mybatisPlus>3.5.5</mybatisPlus>
    </properties>


    <dependencyManagement>
        <dependencies>
            <!--mybatis的起步依赖-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring}</version>
            </dependency>
            <!--mybatisPlus的起步依赖-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatisPlus}</version>
            </dependency>
            <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud alibaba-->
            <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>
        </dependencies>
    </dependencyManagement>

二 、在docker中安装mysql和nacos

1、创建通用网络

这里hm-net是之前跟着黑马学习学习创建的 ,这里就也用这个网络

docker network create hm-net
2、安装mysql

在虚拟机的root目录下建一个mysql目录用于存放信息,也可以在随便的目录创建

在mysql目录里面创建三个包,conf,data和init,到时候创建mysql容器的时候自动挂载。init包主要放初始化的sql,让创建mysql的时候自动加载你需要的数据库信息,如果你是之后再创建也可以不在init包里面放信息,在conf包里面加载默认配置,文件名为xx.cnf,该文件的配置信息如下:

[client]
default_character_set=utf8mb4
[mysql]
default_character_set=utf8mb4
[mysqld]
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci
init_connect='SET NAMES utf8mb4'

运行下面指令创建mysql容器。下面所有-v :左边都是你自己创建的包,:右边是系统中创建的位置,用自己创建的包进行挂载。

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e TZ=Asia/Shanghai \
  -e MYSQL_ROOT_PASSWORD=123 \
  -v /root/mysql/data:/var/lib/mysql \
  -v /root/mysql/conf:/etc/mysql/conf.d \
  -v /root/mysql/init:/docker-entrypoint-initdb.d \
  --network hm-net\
  mysql
3、安装nacos

可以参考我其他博客,里面也有介绍:学习springcloud中Nacos笔记-CSDN博客

用database连上虚拟机里面的mysql,运行下面sql:

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
/******************************************/
/*   表名称 = config_info                  */
/******************************************/
CREATE TABLE `config_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) DEFAULT NULL COMMENT 'group_id',
  `content` longtext NOT NULL COMMENT 'content',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  `c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description',
  `c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage',
  `effect` varchar(64) DEFAULT NULL COMMENT '配置生效的描述',
  `type` varchar(64) DEFAULT NULL COMMENT '配置的类型',
  `c_schema` text COMMENT '配置的模式',
  `encrypted_data_key` text NOT NULL COMMENT '密钥',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
 
/******************************************/
/*   表名称 = config_info_aggr             */
/******************************************/
CREATE TABLE `config_info_aggr` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
  `content` longtext NOT NULL COMMENT '内容',
  `gmt_modified` datetime NOT NULL COMMENT '修改时间',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
 
 
/******************************************/
/*   表名称 = config_info_beta             */
/******************************************/
CREATE TABLE `config_info_beta` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL COMMENT 'content',
  `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  `encrypted_data_key` text NOT NULL COMMENT '密钥',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
 
/******************************************/
/*   表名称 = config_info_tag              */
/******************************************/
CREATE TABLE `config_info_tag` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
  `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL COMMENT 'content',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
 
/******************************************/
/*   表名称 = config_tags_relation         */
/******************************************/
CREATE TABLE `config_tags_relation` (
  `id` bigint(20) NOT NULL COMMENT 'id',
  `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
  `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
  `nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',
  PRIMARY KEY (`nid`),
  UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
  KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
 
/******************************************/
/*   表名称 = group_capacity               */
/******************************************/
CREATE TABLE `group_capacity` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
 
/******************************************/
/*   表名称 = his_config_info              */
/******************************************/
CREATE TABLE `his_config_info` (
  `id` bigint(20) unsigned NOT NULL COMMENT 'id',
  `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',
  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
  `content` longtext NOT NULL COMMENT 'content',
  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `src_user` text COMMENT 'source user',
  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
  `op_type` char(10) DEFAULT NULL COMMENT 'operation type',
  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
  `encrypted_data_key` text NOT NULL COMMENT '密钥',
  PRIMARY KEY (`nid`),
  KEY `idx_gmt_create` (`gmt_create`),
  KEY `idx_gmt_modified` (`gmt_modified`),
  KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
 
 
/******************************************/
/*   表名称 = tenant_capacity              */
/******************************************/
CREATE TABLE `tenant_capacity` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
 
 
CREATE TABLE `tenant_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `kp` varchar(128) NOT NULL COMMENT 'kp',
  `tenant_id` varchar(128) default '' COMMENT 'tenant_id',
  `tenant_name` varchar(128) default '' COMMENT 'tenant_name',
  `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
  `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
  `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
  `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
  KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
 
CREATE TABLE `users` (
	`username` varchar(50) NOT NULL PRIMARY KEY COMMENT 'username',
	`password` varchar(500) NOT NULL COMMENT 'password',
	`enabled` boolean NOT NULL COMMENT 'enabled'
);
 
CREATE TABLE `roles` (
	`username` varchar(50) NOT NULL COMMENT 'username',
	`role` varchar(50) NOT NULL COMMENT 'role',
	UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
 
CREATE TABLE `permissions` (
    `role` varchar(50) NOT NULL COMMENT 'role',
    `resource` varchar(255) NOT NULL COMMENT 'resource',
    `action` varchar(8) NOT NULL COMMENT 'action',
    UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
 
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
 
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');

配置nacos/custom.env文件,再将这个文件放在指定目录(root)下

PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=192.168.147.130
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=123
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
NACOS_AUTH_ENABLE=true
NACOS_AUTH_IDENTITY_KEY=nacos
NACOS_AUTH_IDENTITY_VALUE=nacos
NACOS_AUTH_TOKEN=SecretKey012345678901234567890123456789012345678901234567890123456789

拉取并安装nacos的镜像和容器

在本项目中我用的是nacos6这个名字,用于区分其他容器,端口也不一样,还要加载的配置文件也不一样,上面的配置文件可以参考我之前的博客。

docker run -d \
--name nacos6 \
--env-file ./nacos/quick-nacos.env \
-p 8868:8848 \
-p 9868:9848 \
-p 9869:9849 \
--restart=always \
nacos/nacos-server:v2.3.2-slim

用下面指令查看日志,最后面那个是容器名

docker logs -f nacos6

注意:这里需要注意,在新版本的nacos中,在java代码的配置里面需要加入nacos的用户名和密码,否则会报错。

spring:
  profiles:
    active: dev
  application:
    name: user-service #微服务名称
  cloud:
    nacos:
      server-addr: 192.168.147.130:8868
      username: nacos #用户名
      password: nacos #密码
4、一些常用的dockers指令

其中dps和dis可以去看看黑马的docker课,里面有自定义配置dps和dis,一般开机你都去停止那几个容器再去开启,因为有时候如果默认也是开启的,可能会存在一些bug,有时候你运行项目没事,有时候会出bug。所以你开虚拟机的时候顺便两个都停止再开启一边准没事。

docker start mysql
docker stop mysql
docker start nacos6
docker stop nacos6
dis
dps

三、拆分项目

1、原则

这里直接套用黑马的笔记,我用的是纵向拆分

明确了拆分目标,接下来就是拆分方式了。我们在做服务拆分时一般有两种方式:

  • 纵向拆分

  • 横向拆分

所谓纵向拆分,就是按照项目的功能模块来拆分。例如黑马商城中,就有用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等。那么按照功能模块将他们拆分为一个个服务,就属于纵向拆分。这种拆分模式可以尽可能提高服务的内聚性。

横向拆分,是看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。例如用户登录是需要发送消息通知,记录风控数据,下单时也要发送短信,记录风控数据。因此消息发送、风控数据记录就是通用的业务功能,因此可以将他们分别抽取为公共服务:消息中心服务、风控管理服务。这样可以提高业务的复用性,避免重复开发。同时通用业务一般接口稳定性较强,也不会使服务之间过分耦合。

2、创建单一服务模块

新建模块,按你业务来起名,下面有三个模块是我已经拆分好的了,就是新建模块选好maven和自己jdk版本,起好名,点击ok即可。

给对应服务模块也要创建对应的数据库:

3、给模块引入依赖

这里注意一般业务模块的依赖都差不多,但是上面我抽取出来api模块和之前的common模块都是工具包,依赖会有所不同,下面我展示user-service和quick-api模块的依赖

 user-service:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.quick</groupId>
        <artifactId>quick-pickup</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>user-service</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>

        <dependency>
            <groupId>com.quick</groupId>
            <artifactId>quick-commen</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--hutool工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-ui -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-ui</artifactId>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--数据库-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <!--nacos 服务注册发现,将不同服务注册到nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--openFeign 用于不同服务之间互相调用别的服务接口-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--负载均衡器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <!--OK http 的依赖 是发http请求的连接池,用于不同服务之间发起调用接口请求的连接池 -->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>

        <!--api 这里放openFeign用到的接口,在这个api里面自定义接口,给各个服务调用-->
        <dependency>
            <groupId>com.quick</groupId>
            <artifactId>quick-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>


    </dependencies>


    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 quick-api:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.quick</groupId>
        <artifactId>quick-pickup</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>quick-api</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>com.quick</groupId>
            <artifactId>quick-commen</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--openFeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        
        <!--负载均衡器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-ui -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-ui</artifactId>
        </dependency>

    </dependencies>



</project>
4、给每个服务做好配置文件

这里我只展示user-service

application.yml :

下面的一些配置需要看具体配置,其中的swagger我用的是swagger3,不同swagger配置不一样,swagger3可以参考我的一篇博客:SpringBoot3+支持Knife4j 4.0以上_knife4j-openapi3-jakarta-spring-boot-starter-CSDN博客

还有服务端口,服务名称,扫描的包等等需要按自己项目修改。

这里注意,新版本由于鉴权问题所以一定要加上nacos的用户名和密码

server:
  port: 8082 # 服务端口
spring:
  profiles:
    active: dev # 环境配置,dev开发环境,test测试环境,prod生产环境
  application:
    name: user-service #微服务名称
  cloud:
    nacos:
      server-addr: 192.168.147.130:8868 # nacos地址
      username: nacos #用户名
      password: nacos #密码
      discovery:
        group: QUICK_CLOUD # 配置服务注册分组
    openfeign:
      okhttp:
        enabled: true # 开启okhttp
  datasource:
      driver-class-name: ${quick.datasource.driver-class-name}
      url: jdbc:mysql://${quick.datasource.host}:${quick.datasource.port}/${quick.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
      username: ${quick.datasource.username}
      password: ${quick.datasource.password}

# springdoc-openapi项目配置
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'C端用户接口'
      paths-to-match: '/**'
      packages-to-scan: com.quick.user.controller # 配置需要扫描的controller包路径
knife4j:
  enable: true
  setting:
    language: zh_cn

mybatis-plus:
  type-aliases-package: com.quick.user.domain.po # 配置实体类存放的包路径
  global-config:
    db-config:
      update-strategy: not_null
      id-type: auto #配置实体类中id默认自增
      logic-delete-field: deleted # 配置逻辑删除字段
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
    map-underscore-to-camel-case: true

logging:
  level:
    com:
      quick:
        user:
          mapper: debug
          service: info
          controller: info
quick:
  jwt:
    user-secret-key: quick
    user-ttl: 72000000
    user-token-name: authentication
  wechat:
    appid: ${quick.wechat.appid}
    secret: ${quick.wechat.secret}

下面是不同环境下的yml,如果你用的是dev就会用到application-dev.yml ,如果是local就是application-local.yml

application-dev.yml :

quick:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    host: 192.168.147.130 #虚拟机端口
    port: 3306
    database: quick-user #自己给自己对应服务建立的对应数据库
    username: #自己数据库用户名
    password: #自己数据库用户名

  wechat:

    #小程序:
    appid: #自己小程序的appid
    secret: #自己小程序的secret

application-local.yml :

quick:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    host: 192.168.147.130 #虚拟机端口
    port: 3306
    database: quick-user #自己给自己对应服务建立的对应数据库
    username: #自己数据库用户名
    password: #自己数据库用户名

  wechat:

    #小程序:
    appid: #自己小程序的appid
    secret: #自己小程序的secret

5、给每个服务创建对应的包

6、启动项目观察nacos

输入访问nacos地址:http://192.168.147.130:8868/nacos/ (路径工具自己配置)

可以看到成功注册上去。

四、用OpenFeign实现服务调用

1、引入依赖

(操作对象:服务模块,user-service等等)

在之前拆分项目的时候里面也有该依赖

  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
2、在服务启动类启用OpenFeign

(操作对象:服务模块,user-service等等)

添加注解@EnableFeignClients,这个注解后面那个属性在后面再解释

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@EnableCaching//开启缓存注解功能
@EnableScheduling//开启任务调度
@MapperScan("com.quick.user.mapper")
//basePackages标记FeignClients扫描的包,defaultConfiguration标记日志级别配置类使得对该模块全局生效
@EnableFeignClients(basePackages = "com.quick.api.client",defaultConfiguration = DefaultFeignConfig.class)
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }

}
3、编写OpenFeign客户端

(操作对象:api模块,sky-api)

也可以在服务模块里面写,但是这样子多个服务都写有点臃肿,抽取出来更加方便各个服务调用,需要调用就引入api模块依赖即可

客户端针对性的写,比如user-service模块的服务需要调用到store-service服务,就写对应操作store-service服务的客户端。下面给出user-service服务用到的客户端

//调用同一个服务下的不同接口,用contextId区分
@FeignClient(contextId = "store", name = "store-service")
public interface StoreClient {

    //通过店铺id查询店铺所有信息
    @GetMapping("/user/store/{storeId}")
    @Operation(summary = "通过店铺id查询店铺所有信息")
    Result<Store> getByStoreId (@PathVariable Long storeId);

}
//调用同一个服务下的不同接口,用contextId区分
@FeignClient(contextId = "mark",name = "store-service")
public interface MarkClient {

    @GetMapping("/user/mark/getMarkListByUserId")
    @Operation(summary = "通过用户id查看用户对所有店铺的评分")
    Result<List<Mark>> getMarkListByUserId(@RequestParam("userId") Long userId);

}

由上面可见调用了两个客户端,在写客户端的时候需要在注解配置name,指定客户端调用的是哪个模块的接口,指定name为服务模块的服务名称,但是上面两个客户端都是store-service针对服务的客户端,如果写两个就会报错,所以需要区分两个客户端,所以得用contextId来区分。

 客户端里面的接口和对应模块的接口一致,下面给出对应一个的模块里面的controller接口和客户端比较

MarkController:

@RestController("userMarkController")
@RequestMapping("/user/mark")
@Tag(name = "C端-评分接口")
@Slf4j
@RequiredArgsConstructor
public class MarkController {

    @Resource
    private IMarkService markService;

    //通过用户id查看用户对所有店铺的评分
    @GetMapping("/getMarkListByUserId")
    @Operation(summary = "通过用户id查看用户对所有店铺的评分")
    public Result<List<Mark>> getMarkListByUserId(@RequestParam("userId") Long userId){

        QueryWrapper<Mark>queryWrapper=new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(Mark::getUserId,userId);
        List<Mark> markList = markService.list(queryWrapper);
        return Result.success(markList);
    }

}

MarkClient:

//调用同一个服务下的不同接口,用contextId区分
@FeignClient(contextId = "mark",name = "store-service")
public interface MarkClient {

    @GetMapping("/user/mark/getMarkListByUserId")
    @Operation(summary = "通过用户id查看用户对所有店铺的评分")
    Result<List<Mark>> getMarkListByUserId(@RequestParam("userId") Long userId);

}

 上面可以看出,接口基本一致,但是在Client客户端里面要注意里面的路径信息必须完整,不能只复制异步的controller的一部分地址,要完整。

4、编写客户端用到的实体类

就是下面的dto。

5、给api模块加上日志配置

可能这个功能不算在OpenFeign里面,但是在api模块里面也顺便讲了

public class DefaultFeignConfig {

//    NONE:不记录任何日志信息,这是默认值。
//    BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
//    HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
//    FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

    //声明日志级别配置
    @Bean
    public Logger.Level feignLogLevel() {
        return Logger.Level.FULL;
    }

}

加上这个配置类后,在之前谈过的在启动类加上@EnableFeignClients注解里面有个属性就是是这个配置在那个服务里面生效。就是里面的defaultConfiguration属性,加上这个配置类的信息。

//basePackages标记FeignClients扫描的包,defaultConfiguration标记日志级别配置类使得对该模块全局生效
@EnableFeignClients(basePackages = "com.quick.api.client",defaultConfiguration = DefaultFeignConfig.class)

6、使用FeignClient

在需要用到别的服务的时候,用FeignClient里面自定义写好的接口。

用@RequiredArgsConstructor这个注解就能通过构造函数的方式对客户端接口的注入,导入也可以用@Resource或者@Autowired讲工厂里面的bean对象注入来使用

@Service
@Slf4j
@RequiredArgsConstructor 
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {

    private final MarkClient markClient;
    private final StoreClient storeClient;

}

在接口实现里面调用

        // 调用mark-service获取当前用户的所有评分
        List<Mark> markList = markClient.getMarkListByUserId(currentId).getData();
        log.info("当前用户的所有评分:{}",markList);
        // 同时查询相关的Store信息并设置到UserMarkVO中
        Store store =storeClient.getByStoreId(mark.getStoreId()).getData();
        log.info("store:{}",store);

这里面的实体对象都是api包下的实体,实现不耦合。

接口测试:

之前用的log日志打印也出现了,成功用Feign实现跨服务调用。

五、网关

1、网关路由

创建网关模块,当成一个微服务

这里我已经建好了,下一步我们需要引入依赖

在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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.quick</groupId>
        <artifactId>quick-pickup</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>quick-gateway</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.quick</groupId>
            <artifactId>quick-commen</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--负载均衡-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

然后是配置文件application.yaml

记录路由规则一定要配置好,关系到你能够达到其他服务。当然用到鉴权,nacos用户名密码也要配置。

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.147.130:8868 # nacos地址
      username: nacos #用户名
      password: nacos #密码
      discovery:
        group: QUICK_CLOUD # 配置服务注册分组
    gateway:
      #使用nacos的配置,所以这里不用配置
      routes:
        # 路由规则id,自定义,唯一
        - id: store-service  #店铺管理微服务
          uri: lb://store-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/admin/category/**,/user/category/**,/admin/store/**,/user/store/** # 这里是以请求路径作为判断规则
        - id: user-service  #用户服务微服务
          uri: lb://user-service
          predicates:
            - Path=/user/user/**
        - id: decorate-service  #装饰微服务
          uri: lb://decorate-service
          predicates:
            - Path=/user/home-image/**,/admin/home-image/**,/user/work-contribution/**,/admin/work-contribution/**
        - id: communicate-service  #交互服务微服务
          uri: lb://communicate-service
          predicates:
            - Path=/admin/feedback/**,/user/feedback/**,/admin/notice/**,/user/notice/**
        - id: employee-service  #管理员服务微服务
          uri: lb://employee-service
          predicates:
            - Path=/admin/employee/**

编写启动类:

注意:这里不知道为什么启动的时候报下面的错误:

当然对于解决办法我是在启动注解上加上exclude= {DataSourceAutoConfiguration.class}解决这个需要引入数据库配置问题。

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-06-10T01:59:16.276+08:00 ERROR 22352 --- [gateway] [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})// 排除数据源自动配置
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

测试:

启动各个服务:

访问http://localhost:8080/admin/employee/1 ,查看我管理员的数据:

成功访问!

2、网关登录校验
(1)初步配置

登录校验需要用到jwt,这里我们需要讲jwt的相关代码及其一些相关校验路径代码粘贴到网关模块,这里我先做网页端的登录校验

代码如下:

注意:这里只是解析jwt的工具还有路径配置,各自可能有不一样的配置

@Component
@ConfigurationProperties(prefix = "quick.jwt")
@Data
public class JwtProperties {

    /**
     * 管理端员工生成jwt令牌相关配置
     */
    private String adminSecretKey;
    private long adminTtl;
    private String adminTokenName;

    /**
     * 用户端微信用户生成jwt令牌相关配置
     */
    private String userSecretKey;
    private long userTtl;
    private String userTokenName;

}
@Data
@Component
@ConfigurationProperties(prefix = "quick.auth")
public class AuthProperties {
    private List<String> includeAdminPaths;
    private List<String> includeUserPaths;
    private List<String> excludeAdminPaths;
    private List<String> excludeUserPaths;
}

@Component
public class JwtUtil {
    @Autowired
    private JwtProperties jwtProperties;
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

    public String createAdminToken(Map<String,Object> claims) {
        return JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);
    }

}

相关application.yaml配置如下:

quick:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: quick
    # 设置jwt过期时间
    admin-ttl: 72000000
    # 设置前端传递过来的令牌名称
    admin-token-name: token

    user-secret-key: quick
    user-ttl: 72000000
    user-token-name: authentication

  auth:
    excludeAdminPaths:
      /admin/employee/login
    excludeUserPaths:
      /user/user/login
    includeAdminPaths:
      /admin/**
    includeUserPaths:
      /user/**

需要用到的其他模块抛异常的代码:

@Getter
public class CommonException extends RuntimeException{
    private int code;

    public CommonException(String message, int code) {
        super(message);
        this.code = code;
    }

    public CommonException(String message, Throwable cause, int code) {
        super(message, cause);
        this.code = code;
    }

    public CommonException(Throwable cause, int code) {
        super(cause);
        this.code = code;
    }
}
public class UnauthorizedException extends CommonException{

    public UnauthorizedException(String message) {
        super(message, 401);
    }

    public UnauthorizedException(String message, Throwable cause) {
        super(message, cause, 401);
    }

    public UnauthorizedException(Throwable cause) {
        super(cause, 401);
    }
}
(2)定义一个登录校验的过滤器

 需要校验需要在上游建立一个校验器,即在网关定义一个登录校验的过滤器

先说代码:

@Component
@RequiredArgsConstructor//用构造函数注入
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    //注入配置类,可以用构造函数注入,也可以用@Autowired注入
    private final JwtProperties jwtProperties;
    private final AuthProperties authProperties;
    //路径匹配器
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1.获取Request
        ServerHttpRequest request = exchange.getRequest();

        // 2.判断是否需要做登录校验拦截
        //路径校验

        if (isExcludeAdmin(request.getPath().toString())) {
            //放行
            return chain.filter(exchange);
        }

        // 3.获取token
        String token=null;
        List<String> headers = request.getHeaders().get(jwtProperties.getAdminTokenName());
        if (headers != null&& !headers.isEmpty()){
            //默认拿到的token对应的请求头只有一个,所以拿集合中的第一个
            token = headers.get(0);
            log.info("token:{}",token);
        }

        // 用户id
        Long empId =null;
        // 4.校验并解析token
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:{}", empId);

            //3、通过,放行
        } catch (UnauthorizedException e) {
            // 401 用状态码标志未拦截,设置响应状态码为401
            //拿到响应
            ServerHttpResponse response = exchange.getResponse();
            //设置响应状态码401
            response.setRawStatusCode(401);
            // 返回
            return response.setComplete();
        }

        // 5.传递用户信息 swe
        String adminInfo=empId.toString();
        log.info("adminInfo:{}",adminInfo);
        ServerWebExchange swe = exchange.mutate()
                //请求头需约定好,前面是key,后面是value,将数据传出去
                .request(builder -> builder.header(JwtClaimsConstant.ADMININFO, adminInfo))
                .build();

        // 6.放行,将获取到的信息传到下一个过滤器
        return chain.filter(swe);

    }

    // 判断是否排除路径
    private boolean isExcludeAdmin(String path) {
        for (String pathPattern : authProperties.getExcludeAdminPaths()) {
            if (antPathMatcher.match(pathPattern,path)) {
                // 如果匹配到返回true
                return true;
            }
        }
        // 匹配不到返回false
        return false;
    }

    //定义过滤器优先级
    @Override
    public int getOrder() {
        //优先级,数字越小优先级越高
        return 0;
    }

}

在代码里面登录操作是排除掉不用被拦截的,其他的需要被拦截。如果是登录操作,则放行。实现登录操作后,拿到token进行校验,校验成功则需要拿到用户信息传到下游微服务,则需要根据自定义格式传递。

这里我在common模块里面定义了传递的标准。

代码如下:

public class JwtClaimsConstant {

    public static final String EMP_ID = "empId";
    public static final String USER_ID = "userId";
    public static final String ADMININFO="admin-info";
    public static final String USERINFO="user-info";

}

这里我们可以重新启动一下测试是否拦截到其他路径:

之前访问那个路径已经访问不到了

(3)微服务获取用户

网关传递用户信息下去了,微服务当然要获取用户信息。我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

这里我在common模块统一写一个获取上游用户信息的拦截器,再结合用于保存登录用户的ThreadLocal工具。

代码如下:

public class AdminContext {

    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }

    public static void removeCurrentId() {
        threadLocal.remove();
    }

}

 拦截器代码如下:

public class AdminInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的用户信息
        String userInfo = request.getHeader(JwtClaimsConstant.ADMININFO);
        // 2.判断是否为空
        if (StrUtil.isNotBlank(userInfo)) {
            // 不为空,保存到ThreadLocal
            AdminContext.setCurrentId(Long.valueOf(userInfo));
        }
        // 3.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        AdminContext.removeCurrentId();
    }
}

根据之前定义的统一规范JwtClaimsConstant.ADMININFO接收到网关传递的信息,获取到用户信息。

还需在common模块编写SpringMVC的配置类,配置登录拦截器:

//实现WebMvcConfigurer接口,重写addInterceptors方法,添加自定义拦截器
@Configuration
@ConditionalOnClass(DispatcherServlet.class)//判断是否引入了SpringMVC的依赖,网关没有mvc依赖所以不会引入
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器,默认拦截所有路径,如果需要则可以自己配置拦截路径
        registry.addInterceptor(new AdminInfoInterceptor()).addPathPatterns("/admin/**");
    }
}

注意:在common模块这个配置类默认是不会生效的,因为他不会被其他包扫描到。

基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring文件的org.springframework.boot.autoconfigure.AutoConfiguration.imports

中:

注意:不能用spring.factories,具体参考我这篇博客:Springboot3+自动装配-CSDN博客

代码如下:

 com.quick.config.WebMvcConfiguration
 com.quick.config.MyBatisConfiguration

下面我也给出那个文件名吧,有点长:

org.springframework.boot.autoconfigure.AutoConfiguration.imports
(4)OpenFeign传递用户

前端发起的请求都会经过网关再到微服务,在微服务中间互相调用会用到OpenFeign,这里我们可以在Api配置通过OpenFeign传递用户,让每一个由OpenFeign发起的请求自动携带登录用户信息。

借助Feign中提供的一个拦截器接口,我们在Api模块编写一个拦截器。

//声明请求拦截器,其他服务互相调用接口的时候带上用户信息
    //由于服务之间的互相调用只是调用接口方法,不能传用户信息,调用的接口也不能自动带上网关拦截的用户信息,所以只能通过拦截器
    //这样使得在调用的时候自动带上用户信息
    @Bean
    public RequestInterceptor userInfoRequestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                Long adminId = AdminContext.getCurrentId();
                if (adminId != null){
                    //admin-info建议统一定义
                    requestTemplate.header(JwtClaimsConstant.ADMININFO,adminId.toString() );
                }
            }
        };
    }

现在微服务之间通过OpenFeign调用时也会传递登录用户信息了.

测试:

测试是否下游微服务能拿到当前操作人的id:

这些天在忙ddl,可能得过些天才继续更新,这也算自己做的笔记吧......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乄bluefox

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值