复习时重点看
每个系列中第一个视频为前一个系列的面试总结。
每个系列开头部分有总体流程图介绍
总体学习路线及技术
对项目课程的总结:
1. 第一天:项目概述
- 介绍项目的背景和目标。
- 环境搭建和准备。
- 学习如何在面试中介绍你的项目,涵盖项目背景、团队规模、技术架构等。
2. 第二天:运费微服务
- 讲解物流中的运费计算算法。
- 重点是编写运费计算的代码,适合技术较弱的同学,确保掌握这一模块。
3. 第三天:支付微服务 - 编写代码
- 对接微信支付和支付宝支付,按官方文档编写代码。 (代码比较复杂,要求debug读懂,支付模块和物流不绑定,每个企业或多或少懂,为面试亮点)
第三天:支付微服务 - 补齐业务逻辑
- 以业务为主,补全支付模块的业务逻辑和缺失部分,复杂度较高。(核心)
4. 第四天:路线规划 - Neo4j入门
- 引入Neo4j图数据库,进行路线规划。
第五天:路线规划微服务
- 深入讲解如何使用Neo4j实现物流的路线规划,重点是图数据库的应用。
5. 第六天:智能调度 - 调度任务(调度模块流程较长,较为复杂)
- 讲解智能调度的核心功能,包括如何安排运输任务和匹配车辆。
第七天:智能调度 - 运输任务
- 进一步讲解运输任务的匹配和优化,涵盖详细的调度逻辑。
第八天:智能调度 - 作业范围
- 讨论调度中的作业范围,确定网点和快递员的派送任务。
第九天:智能调度 - 取派件调度
6. 第十天:物流信息微服务
- 使用多级缓存(如Caffeine和Redis),解决缓存击穿、穿透和雪崩问题。(不是特别难)
7. 第十一天:分布式日志与链路追踪
- 项目上线排查问题。讲解分布式日志和链路追踪,使用Skywalking和其他框架进行故障排查。
8. 第十二天:项目实战
- 结合前面学习的内容,进行项目组实战,解决实际问题。
这十二天的课程内容紧密围绕物流项目,涵盖微服务开发、支付集成、智能调度、路线规划及系统优化等多个方面,旨在帮助大家掌握实战技能,提升面试和工作中的竞争力。希望大家在每一天的学习中都能积极参与,掌握每个模块的核心知识点。
项目概述
了解项目背景
神领物流是一个基于微服务架构体系的【生产级】物流项目系统,最接近企业真实场景,按照企业标准编写。物流公司在扩张后要打造一套新的TMS运输系统。业务侧重于展示车辆调研、线路规划等核心业务流程,操作智能化,大幅度提升人效及管控效率。
教学级的项目会在真实项目基础上做阉割。此项目除去自己开发的代码,其他代码也都在项目中。
项目组属于开发部门,开发部门有仓储系统WMS、运配系统TMS、单据系统OMS、计费系统BMS。项目四组。TMS项目组目前共8人,其中前端3人,后端5人,分模块开发。
神领物流系统类似顺丰速运,是向C端用户提供快递服务的系统。竞品有:顺丰、中通、圆通、京东快递等。 项目产品主要有4端产品:
注:c端为互联网,b端为企业
1 用户端:基于微信小程序开发,外部客户使用,可以寄件、查询物流信息等。 2 快递员端:基于安卓开发的手机APP,公司内部的快递员使用,可以接收取派件任务等。 3 司机端:基于安卓开发的手机APP,公司内的司机用,可以接收运输任务、上报位置等。 4 后台系统管理:基于vue开发,PC端使用,公司内部管理员用户使用,可以进行基础数据维护、订单管理、运单管理等。
功能架构:
RabbitMQ:消息队列,xxl.job:任务调度,seata:分布式事务,阿里云:发短信,图片视频等信息存储到oss,美团leaf:分布式id(面试点,面试官没见过但重要),支付宝微信:支付,skywalking:微服务出现问题进行链路追踪
MySQL:核心数据,MongoDB:1存储数据量大 2增删改查多的非核心数据 3适合存坐标数据
Elasticsearch:查询复杂,海量的数据,但对于微服务太大,用MongoDB解决
Redis:基于内存缓存的场景,分布式锁
Neo4j:求两点之间的最短路径(面试点,没用过但好用)
技术架构:
流程: 1:四个端所有请求先通过nginx反向代理到网关中 2:在网关层进行验证能否登录,若可以,请求会分发到微服务(通过nacos获得微服务的地址,所有微服务都要将自己的信息,配置注册到nacos中) 3:微服务相互调用使用feign组件。(其中负载均衡由ribbon实现,熔断降级由sentinel实现,都是配置好的)
注:服务熔断是一种保护机制,用来防止一个微服务的故障蔓延到整个系统。当一个微服务出现问题时,熔断器会暂时中断对该服务的调用,避免影响其他服务的正常运行。
服务降级是指在服务不可用或者响应超时的情况下,提供一个降级方案,比如返回一个预设的默认值或执行一个备用逻辑,以确保系统的基本功能可用。
4:每一个微服务都对外提供增删改查的mvc层操作,都是由SpringBoot创建,用的mvc层框架组成为Spring+SpringMVC+Mybatisplus(如果操作MySQL为Mybatisplus,neo4j和MongoDB则用SpringData系列的框架,例如操作Reids时使用的为SpringDataRedis)
一些基础依赖服务: redisson:分布式加锁 Jenkins(老头):持续集成,项目写完用他部署 seata:分布式事务 美团leaf:生成订单id EagleMap:操作地图(高德,百度) skywalking:进行链路追踪 传智权限管家: 抽取出来管理权限的 MyBatis-Plus:操作数据库的 Knife4j:底层基于Swagger提供接口文档 Hutool:工具类(语法直观简单)
服务名称 | 版本 | 备注 |
---|---|---|
MySQL | 8.0.29 | 业务数据存储 |
Redis | 7.0.4 | 用作缓存以及分布式锁 |
RabbitMQ | 3.9.17 | 需要集成官方的延迟队列插件 |
Nacos | v2.1.0 | 配置中心与注册中心解决方案 |
Neo4j | 4.4.5 | 基于图数据库计算运输路线 |
xxl-job | 2.3.0 | 分布式定时任务框架 |
MongoDB | 4.4 | 存储轨迹、作业范围等数据 |
Seata | 1.5.2 | 分布式事务解决方案 |
Elasticsearch | 7.17.5 | 分布式全文索引解决方案 |
Skywalking | 9.1.0 | 链路追踪解决方案 |
Graylog | 4.3 | 分布式日志解决方案 |
Leaf | 1.0.1 | 美团点评分布式ID生成系统 |
EagleMap | 1.0 | 黑马程序员自研开源项目 EagleMap - 地图服务中台 黑马程序员·研究院 |
权限管家 | 1.0.7 | 黑马程序员自研开源项目 权限管家 - 后端服务: 传智权限管家系统基于SpringCloud(Hoxton.SR3) +SpringBoot(2.2.5.RELEASE) 的微服务框架,是一款标准的RBAC权限关系理系统,并实现了多应用统一认证鉴权,统一权限管理的脚手架 |
nexus | 2.15.1 | maven私服 |
gogs | 0.12 | git代码管理 |
Jenkins | lts-jdk11 | 持续集成 |
实际使用:
具体写代码在某个微服务中写。
查看微服务启动成功否,在nacos注册中心中。
修改服务配置IP,在nacos配置中心中。
写一个定时任务,在xxl.job中
知道在哪里找代码即可,不用背代码
业务功能流程:
说明:
用户在【用户端】填写订单信息并下单后,生成订单
系统会根据订单地址范围内生成【取件任务】,
快递员上门取件成功后,根据重量计算运费,选择到付或寄付,基于订单生成【运单,运输过程基于运单转变状态】
用户对订单进行支付(wx或支付宝),会产生【交易单】
支付完成后由自动调度系统进行调度运输,并生成【运输单】,每一次运输是一个运输任务对应一个运输单
快件开始运输,会经历起始营业部、分拣中心、转运中心(转运中心到转运可能需要循环多次)、分拣中心、终点营业部之间的转运运输,在此期间会有多个【运输任务】
运输中依次往下个网点运输到达终点网点后,根据范围,排班,系统会生成【派件任务】,选择快递员进行派件作业
最后,用户将进行签收或拒收操作
开发模式、团队分工和开发环境的设置:
一、整体开发模式
项目采用前后端分离的开发模式:
前端已经开发完成,包括小程序、后台管理页面和APP。
后端开发采用微服务架构,共分为五个开发小组,每个小组负责一部分微服务。
二、开发环境
项目分为三个开发环境:
开发环境:开发人员在本地使用IDE进行开发和调试。
测试环境:代码经过本地测试后发布到测试环境,由测试团队进行测试,发现bug后进行修复。
生产环境:测试完成后,代码发布到生产环境,供外部互联网用户访问。
在学习过程中,模拟两个环境(开发环境和测试环境),其中测试环境通过虚拟机模拟,生产环境未搭建。
三、团队分工
项目分为五个开发小组:
第一组:负责基础工程,包括网关和基础服务。
其他小组:各自负责不同的微服务模块。
四、项目结构
项目采用独立的Project方式,每个微服务都是一个独立的项目,而不是聚合到一个项目中。这种方式有以下优点:
安全性:新的程序员只能接触到自己负责的代码,减少了代码泄漏的风险。
降低耦合度:各个微服务独立,避免了不必要的耦合。
减少编译错误:减少因为其他组的微服务出现错误而不会修改
五、依赖管理
由于各个微服务独立,可能会有相互依赖的情况,这通过Maven私服来解决:
公共依赖包:如parent工程、common依赖等发布到Maven私服中。
依赖解决:开发组通过Maven配置文件(配置坐标)从私服拉取依赖包,确保项目编译和运行。
六、拉取代码
新人入职后,只需要拉取自己负责的代码进行开发和调试:
只拉取相关工程:避免拉取无关代码,减少编译时间和不必要的错误。
解决依赖问题:通过向老员工获取setting.xml配置文件或整个仓库数据,解决依赖包的问题。
七、公司内部环境
公司会维护一个统一的测试环境或开发环境服务器,运行所有的微服务。开发人员可以在本地调试自己的服务,并通过内网调用其他服务。这种方式避免了在本地运行所有微服务的负担。
微服务调用关系:
四个前端(请求前缀路径不同)的请求发送到网关
四个微服务中接收对应四个端的请求。
四个微服务(写controller方法端口发送的请求地址)接收到请求之后,再去找基础微服务(基础微服务就是按照业务来划分的)
搭建项目环境
神领物流项目使用的JDK版本为11
开发环境说明
说明:
加入开发团队后,首先需要从git仓库中拉取项目代码
拉取到项目代码后,项目会通过Maven私服下载所需要的jar包
开发工程师按照需求文档进行业务开发,在开发完成后将代码推送到git仓库
代码推送到git后,git会通知Jenkins(自动化部署系统)
Jenkins接收到通知后,拉取到最新的代码,并且将服务部署到服务器
导入虚拟机
为了模拟企业中的开发环境,需要通过VMware导入linux虚拟机,该虚拟机中已经安装了课程中所需要的各种环境,包括,git、maven私服、Jenkins、MySQL、RabbitMQ等。
果核剥壳:网页直接下载,这里下载16.2.5版本
密钥(三选一即可)
ZF3RO-FHED2-M80TY-8QYGC-NPKYF
YF390-OHF8P-M81RQ-2DXQE-M2UT6
ZF71R-DMX85-08DQY-8YMNC-PPHV8
账号:root 密码:123321
在本linux中gogs代替git用来搭建个人仓库(gitlab太大),用法一样
通过dps命令可以查询上述列表,dps是自定义命令。 自定义命令方法如下:
vim ~/.bashrc
#增加如下内容
alias dps='docker ps --format "table{
{.ID}}\t{
{.Names}}\t{
{.Status}}\t{
{.Ports}}"'
#保存退出
#生效
source ~/.bashrc
使用finalshell连接centos7虚拟机
用户名:root 密码:123321
使用本地MySQLWorkbench连接虚拟机mysql
开启防火墙端口,虚拟机输入以下命令
sudo /sbin/iptables -I INPUT -p tcp --dport 3306 -j ACCEPT
连接时conection name,default schema都不用输入
用户名密码:root/123
连接成功
配置本机hosts
在本机hosts文件中设置如下配置:
192.168.150.101 git.sl-express.com
192.168.150.101 maven.sl-express.com
192.168.150.101 jenkins.sl-express.com
192.168.150.101 auth.sl-express.com
192.168.150.101 rabbitmq.sl-express.com
192.168.150.101 nacos.sl-express.com
192.168.150.101 neo4j.sl-express.com
192.168.150.101 xxl-job.sl-express.com
192.168.150.101 eaglemap.sl-express.com
192.168.150.101 seata.sl-express.com
192.168.150.101 skywalking.sl-express.com
192.168.150.101 api.sl-express.com
192.168.150.101 admin.sl-express.com
在linux上安装了nginx,根据域名的不同反向代理到指定的端口上
安装utools,安装hosts插件,使用管理员运行,在开发环境中复制以下配置。(双击绿点生效)
打开浏览器测试:NameBright - Coming Soon,显示以下页面则成功
服务列表
说明:如果通过域名访问,无需设置端口。
名称 | 地址 | 用户名/密码 | 端口 |
---|---|---|---|
git | NameBright - Coming Soon | sl/sl123 | 10880 |
nexus(maven私服) | NameBright - Coming Soon | admin/admin123 | 8081 |
jenkins | NameBright - Coming Soon | root/123 | 8090 |
权限管家 | NameBright - Coming Soon | admin/123456 | 8764 |
RabbitMQ | NameBright - Coming Soon | sl/sl321 | 15672 |
MySQL | - | root/123 | 3306 |
nacos | NameBright - Coming Soon | nacos/nacos | 8848 |
neo4j | NameBright - Coming Soon | neo4j/neo4j123 | 7474 |
xxl-job | NameBright - Coming Soon | admin/123456 | 28080 |
EagleMap | NameBright - Coming Soon | eagle/eagle | 8484 |
seata | NameBright - Coming Soon | seata/seata | 7091 |
Gateway | NameBright - Coming Soon | - | 9527 |
admin | NameBright - Coming Soon | - | 80 |
skywalking | NameBright - Coming Soon | - | 48080 |
Redis | - | 123321 | 6379 |
MongoDB | - | sl/123321 | 27017 |
对于gogs(git),用到哪个仓库将哪个仓库的代码下载。例如搜索到网关后,复制链接下载
每一个仓库两个分支,complete为写好的用于参考。master为自己编写的
配置Maven私服
公司的jar包会在上面发布
在本地的maven(建议版本为3.6.x)配置中配置,配置文件参考如下: settings.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<settings
xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- 本地仓库 -->
<localRepository>F:\maven\repository</localRepository>
<!-- 配置私服中deploy的账号 -->
<servers>
<server>
<id>sl-releases</id>
<username>deployment</username>
<password>deployment123</password>
</server>
<server>
<id>sl-snapshots</id>
<username>deployment</username>
<password>deployment123</password>
</server>
</servers>
<!-- 使用阿里云maven镜像,排除私服资源库 -->
<mirrors>
<mirror>
<id>mirror</id>
<mirrorOf>central,jcenter,!sl-releases,!sl-snapshots</mirrorOf>
<name>mirror</name>
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>sl</id>
<!-- 配置项目deploy的地址 -->
<properties>
<altReleaseDeploymentRepository>
sl-releases::default::http://maven.sl-express.com/nexus/content/repositories/releases/
</altReleaseDeploymentRepository>
<altSnapshotDeploymentRepository>
sl-snapshots::default::http://maven.sl-express.com/nexus/content/repositories/snapshots/
</altSnapshotDeploymentRepository>
</properties>
<!-- 配置项目下载依赖的私服地址 -->
<repositories>
<repository>
<id>sl-releases</id>
<url>http://maven.sl-express.com/nexus/content/repositories/releases/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>sl-snapshots</id>
<url>http://maven.sl-express.com/nexus/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<!-- 激活配置 -->
<activeProfile>sl</activeProfile>
</activeProfiles>
</settings>
仓库依赖管理:
本地开发环境依赖管理分为两个仓库:
私服仓库:存放公司内部的依赖包。
中央仓库:使用阿里云镜像下载开源依赖包。
项目中的依赖分为开源依赖(从中央仓库下载)和公司内部依赖(从私服下载)。
私服仓库分为:
snapshot 仓库:存放开发中的临时版本,不稳定。
release 仓库:存放稳定版本,建议主要依赖该版本。
可以通过私服页面查看,管理,搜索不同版本的依赖包。
服务版本
nexus(maven私服)中可以查看依赖包
服务名 | 版本号 |
---|---|
sl-express-parent | 1.4 |
sl-express-common | 1.2-SNAPSHOT |
其他微服务 | 1.1-SNAPSHOT |
开发任务
接下来是你加入到开发一组后接到的第一个任务,具体内容是:
后台管理系统只允许管理员登录,非管理员(司机或快递员)是没有权限登录的,现在的情况是,任何角色的人都能登录到后台管理系统,应该是当非管理员登录时需要提示没有权限。 这个可以算是一个bug修复的工作。接下来,你需要思考下,该如何解决这个问题。
解决步骤:(其中找文档,了解上下文逻辑可能比写代码更费时间)
1 先确定鉴权工作是在哪里完成的(当前要做的功能在哪个微服务中解决,可以问项目经理)
通过前面的系统架构,可以得知是在网关中完成的
2 拉取到网关的代码(从项目经理处获取git账号密码)
3 阅读鉴权的业务逻辑
4 了解权限系统(要权限系统的文档,了解代码的上下文)
5 动手编码解决问题
6 部署,功能测试
部署后台管理系统
构建前端
总共有四个前端,这里先部署pc端
pc管理端是需要将前端开发的vue进行编译,发布成html,然后通过nginx进行访问,这个过程已经在Jenkins中配置,执行点击发布即可。
Jenkins地址:NameBright - Coming Soon
找到前端开发的vue进行构建
实际执行的底层命令
将打包后的html等静态文件拷贝到虚拟机指定目录下:
接下来的部分日志展示了通过 SSH 执行的部署操作:
说明:
1.SSH 脚本:
rm -rf /itcast/admin-web/*:这是一个 Linux 命令,用于删除目标目录 /itcast/admin-web/ 中的所有文件和子目录。rm -rf 强制删除所有内容而不提示确认。
cp /usr/local/src/jenkins/workspace/project-slwl-admin-vue/dist/* /itcast/admin-web/ -R:这是另一个 Linux 命令,用于将构建后的文件从 Jenkins 工作空间中的 dist 目录复制到目标目录 /itcast/admin-web/。-R 选项表示递归复制,包括所有子目录和文件。
2.SSH 执行:
[SSH] executing...:表示开始通过 SSH 执行上述脚本。
[SSH] completed:表示脚本执行完成。
[SSH] exit-status: 0:表示脚本执行成功。exit-status: 0 是一个标准的退出状态码,表示操作成功完成。
查看nginx配置文件内容
nginx所在目录:/usr/local/src/nginx/conf
反向代理
在在浏览器中输入以下域名admin.sl-express.com,nginx反向代理转发到构建的vue前端中
这里的验证码需要启动后台的微服务提供
构建后端微服务
所以需要在Jenkins中,依次启动这几个服务,类似如下构建(需要找到对应的构建任务进行构建,使用默认参数进行构建):
说明:
- 停止并删除旧的 Docker 容器和镜像(如果存在)。
- 使用 Dockerfile 构建一个新的 Docker 镜像。
- 启动新构建的 Docker 容器。
可以在dockers或者nacos中查看具体的微服务(例如网关gateway有没有启动)
部署完成后,即可在前端查看验证码
测试发现bug
拉取网关代码
步骤:
- 在本地创建 sl-express 文件夹后用idea打开,该目录存放项目课程期间所有的代码(企业内只需要拉取一个微服务进行修改,自己学习则拉取全部,每拉取一个点file新建一个project)
配置环境
(1)jdk配置为11
电脑切换不同的JDK版本使用
( 2)maven地址配置
注:这里我们新建一个项目专属的maven,更改地址为新建maven地址
git中拉取代码
注:需要启动虚拟机,才可以使用服务列表的服务
在git中搜索找到gateway分支,拉取master分支
点击cancel(this会挤掉之前的项目,new会新开一个窗口)
将导入项目识别为maven项目
这里idea报错Could not find artifact com.sl-express:sl-express-parent:pom:1.4 in central (https://repo.maven.apache.org/maven2),重新查看maven私服配置发现未配置maven-conf-setting.xml文件(复制上文更改好的setting.xml文件)。
检查后发现本地仓库已经拉取到相关依赖,但idea仍然报错。
问题转变为maven本地仓库有包但idea识别不到
找到方法:删除对应依赖目录下的_remote.repositories文件(未解决问题)
后删除maven仓库文件,重新加载项目使其重新下载依赖
解决报错Could not find artifact com.sl-express:sl-express-parent:pom:1.4 in central
注意:重新加载前看自己的maven配置是否正确
仍有报错Plugin 'org.springframework.boot:spring-boot-maven-plugin:' not found错误
判断原因:清理缓存后,再次打开项目会重新对仓库中已有的文件构造index,所以找到了仓库中的文件进行构建索引
如果仍有报错,则添加版本号。(对应仓库下有版本号文件夹时)
说明:该工程会依赖 sl-express-parent,此maven依赖是通过私服拉取到的。
如果想在之后导出项目中全部代码,则在git中将parent,common也拉取下来。
权限管家
说明
在神领物流项目中,快递员、司机、管理人员都是在权限管家中进行管理的,所以他们的登录都是需要对接权限管家完成的。
具体权限管家的介绍说明参见:权限管家使用说明 (注:虚拟机中已安装,无需再部署)
测试用户登录及校验token
前面已经了解了权限管家,接下来我们将基于权限管家在sl-express-gateway
中进行测试用户的登录以及对于token的校验。
说明
对接权限管家需要引入依赖(该依赖已经导入,并且在parent中指定了版本号):
<dependency>
<groupId>com.itheima.em.auth</groupId>
<artifactId>itcast-auth-spring-boot-starter</artifactId>
</dependency>
该依赖已经上传到maven中央仓库,可以直接下载,地址:https://mvnrepository.com/artifact/com.itheima.em.auth/itcast-auth-spring-boot-starter
nacos ip设置为192.168.150.1原因
nacos,网关,支付等服务都在jenkins中跑,在虚拟机中 。如果nacos不注册为192.168.150.1,泽本地会随机注册一个ip地址(如10.11.121.11)导致与网关不在同一网段下,访问不到运费服务。
此时若想访问运费服务则需将该服务发到虚拟机中启动,这样就不能在本地debug。
需要变化的配置在nacos中(根据服务名称在nacos中寻找)如下文配置文件中的
role: manager: ${role.manager} courier: ${role.courier} driver: ${role.driver}
使用*sl-express-gateway*进行模糊查找,点击编辑
#sl-express-gateway.properties如下:
#权限系统的配置
authority.host = 192.168.150.101
authority.port = 8764
authority.timeout = 10000 #超时时间
#应用id
authority.applicationId = 981194468570960001
#角色id(有四种角色可以进行管理)
role.manager = 986227712144197857,989278284569131905,996045142395786081,996045927523359809
#快递员角色
role.courier = 989559057641637825
#司机角色
role.driver = 989559028277315009
#RSA公钥
sl.jwt.public-key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC6of/EqnM2008gRpFAJJd3iGF5o6P6SuJOcKq4iJQ+62EF4WKGIGQunJjPwVNQFqDuT7ko9bRFZNnMba9A5GrFELtAK7tzO9l19JgFcCBQoU3J6ehPCCunRKz52qJuzS0yiJp0dbB2i6cb7mSCftwZavmcpzhsBaOGQd23AnAmQIDAQAB
其中applicationId、角色id都是需要在权限系统中找到。
角色id需要在数据库表中查询,表为:itcast_auth.itcast_auth_role
测试
测试用例在AuthTemplateTest中:
@Test
public void testLogin() {
//登录
Result<LoginDTO> result = this.authTemplate.opsForLogin()
.token("shenlingadmin", "123456");
String token = result.getData().getToken().getToken();
System.out.println("token为:" + token);
UserDTO user = result.getData().getUser();
System.out.println("user信息:" + user);
//查询角色
Result<List<Long>> resultRole = AuthTemplateFactory.get(token).opsForRole()
.findRoleByUserId(user.getId());
System.out.println(resultRole);
}
token校验测试:
@Test
public void checkToken() {
String token = "xxx.xx.xxx"; //上面方法中生成的token
AuthUserInfoDTO authUserInfo = this.tokenCheckService.parserToken(token);
System.out.println(authUserInfo);
System.out.println(JSONUtil.toJsonStr(authUserInfo));
}
说明:权限管家生成的token采用的是RSA非对称加密方式,项目中配置的公钥一定要与权限系统中使用的公钥一致,否则会出现无法校验token的情况。
即项目中的公钥从虚拟机的权限管家中获取
阅读鉴权代码
流程
首先需要明确的一点是四个终端都是通过sl-express-gateway
进行验证与鉴权的,下面以管理员请求流程为例,其他的流程类似。
不同终端的鉴权实现(自定义过滤器)
不同终端进入Gateway的请求路径不同,且对token的校验和鉴权逻辑不同,故通过在网关中对不同终端创建不同的过滤器来实现。 请求路径如下:
快递员端:/courier/**
用户端:/customer/**
司机端:/driver/**
管理端:/manager/**
其中,配置文件中自定义过滤器配置了4个(对应四个过滤器类)
本地配置文件中的路由配置项
其中自定义过滤器CourierToken会自动匹配对应的类CourierTokenGatewayFilterFactory
原理:当Spring Boot应用启动时,Spring Cloud Gateway会:
1:扫描所有Spring Bean:Spring Boot会扫描所有使用@Component、@Service、@Repository、@Controller等注解标记的类,并将它们注册为Spring Bean。
2:加载过滤器工厂:Spring Cloud Gateway会查找所有继承自AbstractGatewayFilterFactory的Bean,并将它们作为过滤器工厂加载。
3:匹配配置文件中的过滤器:根据配置文件中的过滤器名称(如CourierToken),Spring Cloud Gateway会自动查找并应用相应的过滤器工厂类实例。
具体的业务逻辑,在自定义TokenGatewayFilter
类中的filter()方法中完成。执行流程是先校验token的有效性,再进行鉴权。
bug发现
由于auth()方法直接返回true,导致所有角色都能通过校验,也就是所有角色的用户都能登录到后台管理系统,这里就是bug原因的根本所在。
auth()方法逻辑(根据不同角色判断是否有权限)
1:获取AuthTemplate对象
2:查询登录用户对应角色
通过authTemplate对象的 opsForRole() 方法获取与角色操作相关接口,并调用findRoleByUserId 方法查询指定用户的角色ID。
authUserInfoDTO.getUserId() 获取当前用户的ID,作为查询角色的参数。
getData() 方法用于从 Result 对象中提取实际的数据(即用户角色ID列表)
3:与配置的访问角色取交集
CollUtil.intersection 是 Hutool 工具包中的方法,用于计算两个集合的交集。
@Value("${role.manager}"): 这个注解告诉Spring框架,要从配置文件中读取以 role.manager 开头的配置项,并将其的值注入到 managerRoleIds 变量中。
在配置文件中配置好了nacos,在sl-express-gateway.properties中寻找
#sl-express-gateway.properties如下:
#权限系统的配置
authority.host = 192.168.150.101
authority.port = 8764
authority.timeout = 10000 #超时时间
#应用id
authority.applicationId = 981194468570960001
#角色id(有四种角色可以进行管理)
role.manager = 986227712144197857,989278284569131905,996045142395786081,996045927523359809
#快递员角色
role.courier = 989559057641637825
#司机角色
role.driver = 989559028277315009
#RSA公钥
sl.jwt.public-key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC6of/EqnM2008gRpFAJJd3iGF5o6P6SuJOcKq4iJQ+62EF4WKGIGQunJjPwVNQFqDuT7ko9bRFZNnMba9A5GrFELtAK7tzO9l19JgFcCBQoU3J6ehPCCunRKz52qJuzS0yiJp0dbB2i6cb7mSCftwZavmcpzhsBaOGQd23AnAmQIDAQAB
4:判断是否有交集从而判断是否有权限
测试
将本地springboot的Gateway服务启动起来,访问swagger测试 即可看到【管理后台微服务接口文档】
出现上述错误,在jenkins中构建该项目即可。
随便测试个接口,会发现响应401
测试登录接口,需要先获取验证码再进行登录:key的值后续在登录时还要输入
以创建的zhangsan账号登录,密码123456
设置全局参数Authorization及其值(上文token的值),刷新页面生效
构建sl-express-ms-base
服务的实例,才能使用行政机构的服务。
使用管理员的账号测试,主要测试鉴权服务
部署服务
部署
首先将本地开发的代码进行git提交
项目的发布,我们采用Jenkins持续集成的方式,在提供的虚拟机中已经部署好了Jenkins,我们只需要进行简单的操作即可完成部署。
第一步,浏览器打开:sl-express.com (账号:root/123)
第二步,按照如下数字标识进行操作
选择默认参数:
第三步在jenkins中进行构建项目
注:需要把本地的gateway项目停止,否则nacos中会注册两个网关服务(会报错)
测试上传的服务
停止本地服务后,在jenkinbs中构建project-slwl-admin-vue服务
在admin.sl-express.com登录进行测试
测试成功,快递员zhangsan权限不足,不能登录
注:本地服务只能用swagger进行测试,上传后用jenkins启动gateway后,前端才能显示验证码
作业
注:只有用户端不需要鉴权,登录成功就返回true。且check只返回用户id
注:司机端和用户端均为小程序,后续开发后进行测试
作业1:司机端
package com.sl.gateway.filter;
import cn.hutool.core.collection.CollUtil;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
/**
* 司机端token拦截处理
*/
@Component
@Slf4j
public class DriverTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> implements AuthFilter {
@Resource
private MyConfig myConfig;
@Resource
private TokenCheckService tokenCheckService;
//怎么使用@Value?@Value(${role.Driver})报错
@Value("${role.Driver}")
List<Long> roleDriverIds;
@Override
public GatewayFilter apply(Object config) {
return new TokenGatewayFilter(this.myConfig, this);
}
@Override
public AuthUserInfoDTO check(String token) {
//校验token
return this.tokenCheckService.parserToken(token);
}
@Override
//通过用户信息自带的角色id和在配置文件查出来的有权限的角色id做交集,看有无权限
public Boolean auth(String token, AuthUserInfoDTO authUserInfoDTO, String path) {
//通过AuthTemplateFactory的.get(token)方法构造AuthTemplate对象,用来获取用户角色信息
AuthTemplate authTemplate = AuthTemplateFactory.get(token);
//此处传来的??AuthUserInfoDTO信息哪里得到,TokenGateway和DriverTokenGateway的调用顺序
Long userId = authUserInfoDTO.getUserId();
//获取userId对应的角色信息
//.findRoleByUserId(userId)方法返回的时Result<List<Long>>对象,要用.getData获得数据
List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(userId).getData();
//使用工具CollUtil.intersection测试是否有交集
Collection<Long> intersection = CollUtil.intersection(roleIds, roleDriverIds);
return intersection.isEmpty();
}
}
作业2 :用户端
package com.sl.gateway.filter;
import cn.hutool.jwt.JWTUtil;
import com.itheima.auth.sdk.dto.AuthUserInfoDTO;
import com.sl.gateway.config.MyConfig;
import com.sl.gateway.properties.JwtProperties;
import com.sl.transport.common.constant.Constants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 用户端token拦截处理
*/
@Slf4j
@Component
public class CustomerTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> implements AuthFilter {
@Resource
private MyConfig myConfig;
@Resource
private JwtProperties jwtProperties;
@Override
public GatewayFilter apply(Object config) {
return new TokenGatewayFilter(this.myConfig, this);
}
@Override
public AuthUserInfoDTO check(String token) {
// 普通用户的token没有权力对接权限系统,需要自定实现
//即无法使用AuthTemplate对象,AuthUserInfoDTO,TokenService对象中的方法
//自定义解析Token即可
// 得到DefaultJwtParser
Map<String, Object> claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(jwtProperties.getPublicKey().getBytes(StandardCharsets.UTF_8))
// 使用 parseClaimsJws 方法解析JWT令牌,并提取其中的声明(claims)。
.parseClaimsJws(token).getBody();
//AuthUserInfoDTO.bulider(),不可使用builder方法,AuthUserInfoDTO为拉取的依赖,未注入Bean容器
AuthUserInfoDTO authUserInfoDTO = new AuthUserInfoDTO();
//这里只需封装userId信息即可
Long userId = Long.valueOf(claims.get("userId").toString());
authUserInfoDTO.setUserId(userId);
return authUserInfoDTO;
}
@Override
public Boolean auth(String token, AuthUserInfoDTO authUserInfoDTO, String path) {
//普通用户不需要校验角色
//所以只需要token解析进行判断即check
return true;
}
@Override
public String tokenHeaderName() {
return Constants.GATEWAY.ACCESS_TOKEN;
}
}
修改:
public AuthUserInfoDTO check(String token) {
// 普通用户的token没有权力对接权限系统,需要自定实现
//即无法使用AuthTemplate对象,AuthUserInfoDTO,TokenService对象中的方法
//自定义解析Token即可
try {
// 基于JwtUtils解析token获取Claims内容
Map<String, Object> claims = JwtUtils.checkToken(token,jwtProperties.getPublicKey());
//这里只需封装userId信息即可
Long userId = Long.valueOf(claims.get("userId").toString());
//AuthUserInfoDTO.bulider(),不可使用builder方法,AuthUserInfoDTO为拉取的依赖,未注入Bean容器
AuthUserInfoDTO authUserInfoDTO = new AuthUserInfoDTO();
authUserInfoDTO.setUserId(userId);
return authUserInfoDTO;
}catch (Exception e) {
log.error(">>>>>>>>>>>>>>>>>> 解析用户登录token失败 >>>>>>>>>>>>>>>>");
return null;
}
}
作业3: tokengateway整体逻辑
(1)获取请求路径看是否在白名单中
(2) 从请求头header中获取Token(微服务想通过网关访问则需携带token的头信息),有权限放行,无权限报错
(4)调用authFilter.check(token)方法校验Token,捕获异常并记录日志,如果Token无效,返回401未授权状态。
(5)调用authFilter.auth(token, authUserInfoDTO, path)方法进行权限验证,捕获异常并记录日志,如果鉴权失败,返回400错误请求状态。
(6)增加参数,向下游微服务传递参数(向下游微服务传递用户信息和Token)用以访问其他微服务
(7)如果校验和鉴权通过则放行,请求传递给过滤器链中的下一个过滤器。
了解代码具体怎么写的后再回头看看整体流程
面试问题
总结:项目公司介绍,鉴权流程,用到哪些技术
技术:VMare虚拟机,jenkins部署服务,gogs(git)提交代码,nacos存储可变的配置(在本地配置文件中配置),maven配置私服(从私服拉取代码逻辑),权限管家的auth鉴权check解析令牌服务,柯knf4j(swagger)测试代码,
面试官问:
- 能介绍一下你做的这个神领物流项目么? (背景 分为几个端 用到哪些技术)
- 你们开发部门的组成是怎样的?
- 你们的项目权限是如何管理的呢?
- 你们公司项目是如何部署的呢?