项目开发总结

本次项目开发学习前前后后花费了数半个月,虽然学习的项目章节不太多,但也受益匪浅

不论是从最开始的环境搭建,还是之后的微服务、小程序、网关配置等等方面,也是学到了很多技巧,但这其中我也遇到了很多困难,本次开发总结我将详细讲述我的开发流程,其中遇到的一些困难,以及如何去解决这些困难

环境配置

首先是在最开始的环境配置当中,我们需要先下载虚拟机环境(模拟公司、企业当中的服务器)

image-20240711091119491

可看到虚拟机环境已经帮我们配置好了,这些服务是一些正在运行当中的服务

然后就是虚拟机也需要配置一下内网IP方便后续服务的访问

image-20240711091843448

内网配置好后在配置本地ip

image-20240711092123041

可以看到本地服务地址与虚拟机地址是一样的,然后后面就是一些服务

image-20240711092321796

在这之后我们就可以成功的访问服务了

我们先从本地访问服务域名,通过本地hosts文件配置,将服务地址传输到虚拟机,再通过虚拟机nginx服务将其反向代理到各个服务

image-20240711093843963

Maven私服

Maven私服配置根据服务不同,进行相应配置

git代码管理

对于代码的管理,我觉得是相对重要的,这在开发当中是必不可少的

分支管理
  • 不同功能的分支形成代码隔离,发布时从主分支进行代码发布,测试在测试分支,开发在开发分支,减少出错。。
  • 分支之间互不影响,开发者可以独立开发,提高团队开发效率
分支管理策略

master

主分支,用于部署生产环境的分支,是所有分支的主干,与当前线上代码保持统一

dev

开发分支,开发各类分支的合并分支,即当前最新功能分支

feature

功能分支,一般用于新技术的开发,开发完成后合并到master/dev分支,随后便可删除分支

release

用于测试的分支,如遇bug,将其推到 fixbug分支进行修复

fixbug

bug修复分支,完成修复后,将其合并到master/dev

tag标签

master发布开发环境后,用tag来进行版本标记

# 列出所有本地分支
$ git branch
 
# 列出所有远程分支
$ git branch -r
 
# 列出所有本地分支和远程分支
$ git branch -a
 
# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]
 
# 新建一个分支,并切换到该分支
$ git checkout -b [branch]
 
# 新建一个分支,指向指定commit
$ git branch [branch] [commit]
 
# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]
 
# 切换到指定分支,并更新工作区
$ git checkout [branch-name]
 
# 切换到上一个分支
$ git checkout -
 
# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]
 
# 合并指定分支到当前分支
$ git merge [branch] # 快速合并
$ git merge --no-ff [branch] # 非快速合并(建议使用)
 
# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]
 
# 删除分支
$ git branch -d [branch-name]
 
# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]

然后后面就是一些分支开发测试(这里就不做多的篇幅解释)

image-20240711100917580

项目列表

这些服务后续会用到

image-20240711101111389

Jenkins

在学习本次项目之前,我也才只接触Jenkins过一次,那还是在上次2023年的一次靶机练习,记得当时也是打的迷迷糊糊,不过经过本次学习后,我也更深层次的体验到了Jenkins的用法

从最开始的任务构建到参数、版本号、微服务地址、服务名称、端口号、git地址等等一系列的配置,在到脚本构建,一键运行,就完成了项目的一个搭建,通过测试便可查看是否搭建成功

image-20240711111202282

image-20240711111228419

测试成功后,将Jenkins中web钩子设定,绑定git,测试推送

Jenkins总结

从Jenkins的使用流程来看,我认为Jenkins就是用于将git进行连接配置,然后直接启动服务的中间件,他可以直接将服务上传到docker容器中(前提是在环境中已经配置好了docker,Maven,git),他方便我们对服务的一个管理,是一个强大的中间件。

登录业务

关于登录业务,我觉得文档上给的图就已经完美诠释了整个登录流程

image-20240717144904295

  1. 首先前端先从WebManager 后端服务管理中获取 验证码 (并携带key)

  2. 后端服务管理生成验证码

  3. 讲验证码和key值传入Redis

  4. Redis返回值

  5. 后端返回验证码图传给前端

  6. 前端输入账号密码验证码

  7. 后端从Redis中进行匹配验证码

  8. Redis返回结果

  9. 后端进行校验

    1. 假如验证码匹配失败,就返回验证码错误
    2. 假如匹配成功,从Redis中删除验证码值,返回后端
  10. 进行用户名密码校验

    1. 错误:返回前端用户名或密码错误
    2. 成功:生成token,返回登录成功

验证码功能的实现

这个功能的实现文档中是基于算法做的验证码,我在实际的过程中使用的是4位随机数所做的验证码,其实也大差不差

后端实现过程

从git拉取代码 sl-express-ms-web-manager

在idea中进行基础jdkManven配置 然后就可以开始实现了

hutool验证码文档说明

39

@Override
public void createCaptcha(String key, HttpServletResponse response) throws IOException {
        //1. 生成验证码,指定宽、高、字符个数、干扰线数
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(115, 38, 0, 10);
        //1.1 自己创建一个随机数范围,验证码为四位随机数
        String random = "123456789abcdefghijklmnopqrstuvwsyz";
        RandomGenerator randomGenerator = new RandomGenerator(random,4);
        lineCaptcha.setGenerator(randomGenerator);
        //1.2 获取生成的验证码值
        lineCaptcha.createCode();
        String Code = lineCaptcha.getCode();

        //2. 将验证码的值写入到redis,有效期为1分钟
        String redisKey = CAPTCHA_REDIS_KEY + key;
        this.stringRedisTemplate.opsForValue().set(redisKey, Code, Duration.ofMinutes(1));

        //输出到页面
        response.setHeader(HttpHeaders.PRAGMA, "No-cache");
        response.setHeader(HttpHeaders.CACHE_CONTROL, "No-cache");
        response.setDateHeader(HttpHeaders.EXPIRES, 0L);
        lineCaptcha.write(response.getOutputStream());
    }

在Controller中调用Service中的方法:

image-20240703165756274

测试

可以看到验证码

image-20240703165907670

这样这么一个接口就完成了

然后接下来我们还需要将前端接口进行连接

前端部署

同样的前端,我们先需要将代码拉取

拉取project-slwl-admin-vue代码

image-20240717152419712

修改.env.production.stu文件,设置前后端交互接口改为本机地址和端口:

image-20240704171035290

提交到git中,使用Jenkins部署

选择develop分支,开始构建

image-20240704171130051

访问地址进行测试:http://admin.sl-express.com/

image-20240704171240463

可以看到请求为200

image-20240704171414493

登录功能实现

刚刚实现的是验证码功能,登录功能还没有实现,我们现在紧接着实现登录

AuthServiceImpl中实现代码

image-20240704174459771

image-20240704174515533

Controller中调用:

image-20240704174750039

测试

可以看到测试有数据

image-20240704174842874

权限管家

权限管家是一个通用的权限管理中台服务,在权限管家中可以管理用户,管理后台系统的菜单,以及角色的管理。

改造登录接口为权限管家的接口

image-20240705150255954

测试

可以看到测试成功

image-20240705150518581

到这里我们的登录接口也完成了

用户端登录和统一网关

问题

首先是最开始的微信小程序,

在登录接口和小程序登录代码实现完成后

小程序还是会出现400系列错误问题

然后就是token实现后不出token的问题

解决

对于登录后还是会出现400系错误的问题

这个是由于此项目是我们学校老师为我们配置后出现的小程序账号、密钥不同的原因,导致数据不能正常进行传递引发的客户端错误

通过项目测试打印出账号,进行更换实现成功配置

(这边报错的图忘记截出来了只有最后的实现了)

实现流程

接下来就该实现用户端的登录和网关配置

因为用户端是基于微信小程序开发的

微信小程序登录的流程:

image-20240717155248822

  1. 点击按钮动态获取phoneCode
  2. wx.login()获取登录code
  3. request向后端发送code、phoneCode
  4. 后端接收并校验登录凭据appid+appsecret+code
  5. 微信接口进行处理
  6. 返回session_key+openid等
  7. 通过openid 向数据库进行用户查询
  8. 后端接收返回用户信息,如果是不存在说明新用户,返回null
  9. 后端向微信接口获取用户手机号接口
  10. 微信进行内部处理
  11. 返回手机号
  12. 后端新增用户或更新用户手机号
  13. 生成token
  14. 将token传入微信小程序
  15. 微信小程序wx.request()请求头携带token
  16. 后端校验token,获取到openid
  17. 后端进行处理
  18. 将响应数据返回微信小程序

登录实现

env.js中修改本地接口

image-20240706174048764

可以看到本地请求为502

这是由于本地网关并没有配置导致的后面会进行配置

image-20240706174430919

请求参数(2个参数,code:登录临时凭证,phoneCode:获取手机号的凭证):

image-20240706174413712

拉取后端代码 sl-express-ms-web-customer

登录接口

登录接口的实现是在com.sl.ms.web.customer.controller.UserController#login

image-20240706174821877

可以看到代码是由UserLoginVO响应数据对象和UserLoginRequestVO请求参数对象组成

我们现看看UserLoginVO响应数据对象

package com.sl.ms.web.customer.vo.user;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginVO {

    @ApiModelProperty("微信唯一标识符")
    private String openid;
    @ApiModelProperty("短令牌,有效期较短")
    private String accessToken;
    @ApiModelProperty("长令牌,有效期较长")
    private String refreshToken;
    @ApiModelProperty("是否绑定手机号 0否 1是")
    private Integer binding;

}

UserLoginRequestVO请求参数对象组成

package com.sl.ms.web.customer.vo.user;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * C端用户登录
 */
@Data
public class UserLoginRequestVO {

    @ApiModelProperty("登录临时凭证")
    private String code;

    @ApiModelProperty("手机号临时凭证")
    private String phoneCode;
}

用户微服务

用户端通过微信登陆流程图

流程图

在Jenkins部署sl-express-ms-user-service

image-20240708105311036

image-20240708105407141

数据库已在服务器部署

小程序登录

@Value("${sl.wechat.appid}")
private String appid;
@Value("${sl.wechat.secret}")
private String secret;

public static final String LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
private static final int TIMEOUT = 20000;

@Override
public JSONObject getOpenid(String code) throws IOException {
    //文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
    //1. 封装参数
    Map<String, Object> requestParam = MapUtil.<String, Object>builder().put("appid", this.appid) //小程序 appId
            .put("secret", this.secret) //小程序 appSecret
            .put("js_code", code) //	登录时获取的 code,可通过wx.login获取
            .put("grant_type", "authorization_code") //授权类型
            .build();

    //2. 发送get请求
    HttpResponse response = HttpRequest.get(LOGIN_URL) //设置get请求url
            .form(requestParam) //设置表单参数
            .timeout(TIMEOUT) //设置超时时间,20s
            .execute();//执行请求
    if (response.isOk()) {
        // 3. 解析响应的结果,如果出现错误抛出异常
        JSONObject jsonObject = JSONUtil.parseObj(response.body());
        if (jsonObject.containsKey("errcode")) {
            throw new SLWebException(jsonObject.toString());
        }
        return jsonObject;
    }
    String errMsg = StrUtil.format("调用微信登录接口出错! code = {}", code);
    throw new SLWebException(errMsg);
}

image-20240708105610880

通过小程序登录测试

image-20240708105827124

获取手机号

想要获取到手机号的要先获取到微信access_token,在获取到手机号,否则获取不到

com.sl.ms.web.customer.service.impl

image-20240709104615345

image-20240709104632774

    @Test
    void getPhone() throws IOException {
        //在测试用例中输入自己获取到的 phonecode
        String phone = wechatService.getPhone("1aaaaa1111");
        System.out.println(phone);
    }
实现登录

com.sl.ms.web.customer.service.impl.MemberServiceImpl#login

image-20240709104923601

生成token实现,这里采用的是RSA加密方式(密钥信息在nacos中的sl-express-ms-web-customer.properties配置)

com.sl.ms.web.customer.service.impl.TokenServiceImp

image-20240709105243144

启动服务,在小程序进行测试

image-20240709105534105

可以看到登录成功

双token三验证

单token存在的问题

在日常使用中,我们会频繁的进行登录,而在登录的过程中,会先生成jwt的token,前端接收到token将其保存下来,在进行后端服务请求的时候,在请求中携带token,服务端对token进行校验和鉴权,这种模式叫做 单token模式

在单token模式中,会出现token有效时间的问题,有效时间太短容易过期,太长容易被攻击截取。

在另一方面,token是无状态的,就是说我们不能自如的控制token的状态,无法让他主动失效。

方案设计

解决问题

  • token有效期长不安全

    • 登录成功后,生成2个token,分别是:access_token、refresh_token,前者有效期短(如:5分钟),后者的有效期长(如:24小时)

    • 正常请求后端服务时,携带access_token,如果发现access_token失效,就通过refresh_token到后台服务中换取新的access_token和refresh_token,这个可以理解为token的续签

    • 以此往复,直至refresh_token过期,需要用户重新登录

  • token的无状态性

    • 为了使token有状态,也就是后端可以控制其提前失效,需要将refresh_token设计成只能使用一次

    • 需要将refresh_token存储到redis中,并且要设置过期时间

    • 这样的话,服务端如果检测到用户token有安全隐患(如:异地登录),只需要将refresh_token失效即可

代码实现

生成刷新token

生成刷新refresh_token的主要逻辑有两点:

  • 生成jwt格式的token,有效期时间一般小时为单位
  • 将token存入到redis,使token有状态,并且确保只能使用一次

com.sl.ms.web.customer.service.impl.TokenServiceImpl

public static final String REDIS_REFRESH_TOKEN_PREFIX = "SL_CUSTOMER_REFRESH_TOKEN_";

image-20240709111838201

刷新token

刷新token的动作是在refresh_token过期之后进行的,主要实现关键点有:

  • 校验refresh_token是否被伪造以及是否在有效期内
  • 从redis中查询,是否不存在,如果不存在说明已经失效或已经使用过,如果存在,就需要将其删除
  • 重新生成一对token,响应结果

com.sl.ms.web.customer.service.impl.TokenServiceImpl

image-20240709112251066

在Controller中调用上述方法

com.sl.ms.web.customer.controller.UserController

image-20240709112416024

Service中调用

com.sl.ms.web.customer.service.impl.MemberServiceImpl

image-20240709112532232

完善用户端登录

用户端登录成功后,需要在补一个token,之前已经写过一个了

image-20240710082924254

测试

image-20240710083035707

网关

网关作用

网关能够更好的管理项目分配,每一个客户端都将通过网关传递到服务端

网关就是每个客户端的中间商,他还能对token、以及用户的权限进行有效的校验

比如说:司机不能登录到快递员端,快递员不能登录到司机端

image-20240710095125453

拉取代码

sl-express-gateway中拉取网关代码:

拉取代码后,创建develop分支进行开发

bootstrap-local.yml

server:
  port: 9527
  tomcat:
    uri-encoding: UTF-8
    threads:
      max: 1000
      min-spare: 30
spring:
  cloud:
    nacos:
      username: nacos
      password: nacos
      server-addr: 192.168.150.101:8848
      discovery:
        namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
        ip: 192.168.150.1
      config:
        namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
    gateway:
      routes:
        - id: sl-express-ms-web-manager #路由标识,需要唯一
          uri: lb://sl-express-ms-web-manager #最终请求转发的微服务,指定的是微服务名,并开启负载均衡
          predicates: #断言
            - Path=/manager/**
          filters: #配置过滤器
            - ManagerToken #自定义局部过滤器
            - StripPrefix=1 #去掉第一个路径,例如:/manager/user/login -转发到下游微服务-> /user/login
            - AddRequestHeader=X-Request-From, sl-express-gateway  #转发到下游微服务携带的请求头,增强安全性
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origin-patterns: "*"
            allowed-headers: "*"
            allow-credentials: true
            allowed-methods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      discovery:
        locator:
          enabled: true #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
itcast:
  authority:
    host: ${authority.host} #authority服务地址,根据实际情况更改
    port: ${authority.port} #authority服务端口
    timeout: ${authority.timeout} #http请求的超时时间
    public-key-file: auth/pub.key
    applicationId: ${authority.applicationId}

#角色id
role:
  manager: ${role.manager}
  courier: ${role.courier}
  driver: ${role.driver}

sl:
  noAuthPaths:
    - /courier/login/account
    - /courier/swagger-ui.html
    - /courier/webjars/
    - /courier/swagger-resources
    - /courier/v2/api-docs
    - /courier/doc.html
    - /customer/user/login
    - /customer/user/refresh
    - /customer/swagger-ui.html
    - /customer/webjars/
    - /customer/swagger-resources
    - /customer/v2/api-docs
    - /customer/doc.html
    - /driver/login/account
    - /driver/swagger-ui.html
    - /driver/webjars/
    - /driver/swagger-resources
    - /driver/v2/api-docs
    - /driver/doc.html
    - /manager/login
    - /manager/webjars/
    - /manager/swagger-resources
    - /manager/v2/api-docs
    - /manager/doc.html
    - /manager/captcha
  jwt:
    public-key: ${sl.jwt.user-secret-key}
自定义过滤器配置

image-20240710095615927

过滤器实现

在过滤器中主要实现以下几个功能:

  • 白名单放行
  • 请求头中的token是否有效
  • 权限是否匹配(校验角色)
  • 向下游微服务传递解析token的数据以及token值

代码实现:

/**
 * 管理端token校验的过滤器
 */
@Component
public class ManagerTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {

    @Resource
    private MyConfig myConfig;
    @Resource
    private TokenCheckService tokenCheckService;
    @Value("${role.manager}")
    private List<Long> managerRoleIds; //获取配置文件中的管理员角色id

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            //1. 校验请求路径,如果是白名单,直接放行
            String path = exchange.getRequest().getPath().toString();
            if (StrUtil.startWithAny(path, this.myConfig.getNoAuthPaths())) {
                //直接放行
                return chain.filter(exchange);
            }

            //2. 获取请求头中的token,进行校验,如果为空或校验失效,响应401
            String token = exchange.getRequest().getHeaders().getFirst(Constants.GATEWAY.AUTHORIZATION);
            if (StrUtil.isEmpty(token)) {
                //设置响应状态为401
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                //拦截请求
                return exchange.getResponse().setComplete();
            }
            //校验token
            AuthUserInfoDTO authUserInfoDTO = null;
            try {
                authUserInfoDTO = this.tokenCheckService.parserToken(token);
            } catch (Exception e) {
                //token不可用,不做处理
            }

            if (ObjectUtil.isEmpty(authUserInfoDTO)) {
                //token不可用,设置响应状态为401
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                //拦截请求
                return exchange.getResponse().setComplete();
            }

            //3. 校验权限,如果是非管理员不能登录
            AuthTemplate authTemplate = AuthTemplateFactory.get(token);
            //3.1 获取用户拥有的角色id列表
            List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(authUserInfoDTO.getUserId()).getData();
            //3.2 取交集,判断用户拥有的角色是否与预定的角色列表是否有交集
            Collection<Long> intersection = CollUtil.intersection(roleIds, this.managerRoleIds);
            if (CollUtil.isEmpty(intersection)) {
                //无交集,说明没有权限,设置响应状态码为400
                exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
                return exchange.getResponse().setComplete();
            }

            //4. 校验通过,向下游传递用户信息和token
            exchange.getRequest().mutate().header(Constants.GATEWAY.USERINFO, JSONUtil.toJsonStr(authUserInfoDTO));
            exchange.getRequest().mutate().header(Constants.GATEWAY.TOKEN, token);
            //4.1 校验通过放行
            return chain.filter(exchange);
        };
    }

}
测试

将网关启动,测试http://127.0.0.1:9527/manager/doc.html#/home看能不能正常看到页面

image-20240710100121839

配置前端

将测试URL改为VUE_APP_BASE_URL = 'http://192.168.150.1:9527/manager'

image-20240710100159599

将代码上传git

在Jenkins构造项目

访问http://admin.sl-express.com/

image-20240710100755861

验证码端口可以看到是9527,部署成功

image-20240710100922312

代码优化

优化一

将校验逻辑抽取到独立的类中,这样在每个端的GatewayFilterFactory中就可以共用了。

com.sl.gateway.filter.TokenGatewayFilter

image-20240717161839857

package com.sl.gateway.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.itheima.auth.factory.AuthTemplateFactory;
import com.itheima.auth.sdk.AuthTemplate;
import com.itheima.auth.sdk.dto.AuthUserInfoDTO;
import com.itheima.auth.sdk.service.TokenCheckService;
import com.sl.gateway.config.MyConfig;
import com.sl.transport.common.constant.Constants;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;

@Component
public class TokenGatewayFilter implements GatewayFilter {

    @Resource
    private MyConfig myConfig;
    @Resource
    private TokenCheckService tokenCheckService;
    @Value("${role.manager}")
    private List<Long> managerRoleIds; //获取配置文件中的管理员角色id

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1. 校验请求路径,如果是白名单,直接放行
        String path = exchange.getRequest().getPath().toString();
        if (StrUtil.startWithAny(path, this.myConfig.getNoAuthPaths())) {
            //直接放行
            return chain.filter(exchange);
        }

        //2. 获取请求头中的token,进行校验,如果为空或校验失效,响应401
        String token = exchange.getRequest().getHeaders().getFirst(Constants.GATEWAY.AUTHORIZATION);
        if (StrUtil.isEmpty(token)) {
            //设置响应状态为401
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            //拦截请求
            return exchange.getResponse().setComplete();
        }
        //校验token
        AuthUserInfoDTO authUserInfoDTO = null;
        try {
            authUserInfoDTO = this.tokenCheckService.parserToken(token);
        } catch (Exception e) {
            //token不可用,不做处理
        }

        if (ObjectUtil.isEmpty(authUserInfoDTO)) {
            //token不可用,设置响应状态为401
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            //拦截请求
            return exchange.getResponse().setComplete();
        }

        //3. 校验权限,如果是非管理员不能登录
        AuthTemplate authTemplate = AuthTemplateFactory.get(token);
        //3.1 获取用户拥有的角色id列表
        List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(authUserInfoDTO.getUserId()).getData();
        //3.2 取交集,判断用户拥有的角色是否与预定的角色列表是否有交集
        Collection<Long> intersection = CollUtil.intersection(roleIds, this.managerRoleIds);
        if (CollUtil.isEmpty(intersection)) {
            //无交集,说明没有权限,设置响应状态码为400
            exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
            return exchange.getResponse().setComplete();
        }

        //4. 校验通过,向下游传递用户信息和token
        exchange.getRequest().mutate().header(Constants.GATEWAY.USERINFO, JSONUtil.toJsonStr(authUserInfoDTO));
        exchange.getRequest().mutate().header(Constants.GATEWAY.TOKEN, token);
        //4.1 校验通过放行
        return chain.filter(exchange);
    }
}

com.sl.gateway.filter.ManagerTokenGatewayFilterFactory

image-20240717161734922

优化二

通过前面的优化一已经将过滤器抽取到一个独立的类中,这样就可以在多个过滤器工厂中通用了,但也是存在一些问题的,例如:

  • 不同的终端校验的角色id是不同的
  • 用户端是不需要校验角色的
  • 用户端校验token的逻辑与其他三端是不一样的
  • 用户端请求头中的token参数名与其他三端也不一样

基于这些问题,就能够意识到,单纯的抽取代码是不够的,需要进一步的优化。想一想,该怎么优化呢?

优化的思路就是,在TokenGatewayFilter中保留四端通用的逻辑,不同的逻辑抽取到接口中,由四端具体的实现。

自定义AuthFilter接口:

com.sl.gateway.filter.AuthFilter

image-20240717161552569

总结

在这次实训中,后面其实还有挺多章节的内容,我没有完成,我只是将其完整的看了一遍

虽然没有完成整个实训,但我已经粗略的领略到了项目开发流程,从开始的环境搭建到后面微服务的实现,我学习到了中间件的使用,容器的配置,git的管理等等,这些都是在平常学习中不会遇到的问题,所以这次实训也是为我带来了一些别样的收获