搭建简易分布式+网关+统一认证系统(附带jdk8资源)
目录
2、JDK8资源地址:关注公众号,回复:jdk8 小牛 - 全栈工程师 | 个人官网
1、首先打开IDEA编辑器,点击新建项目。版本:springboot2.3.12.RELEASE
按照以上模块创建流程,继续创建cloud-user、cloud-gateway、cloud-common。
一、分布式。
1、什么是分布式
分布式系统(Distributed System)是指由多个独立的计算机节点通过网络连接组成的系统,这些节点协同工作,对用户呈现为一个统一的整体。
核心特征
- 多节点性:系统由多台计算机组成,通常分布在不同的地理位置
- 并发性:各节点可以并行处理任务
- 无共享架构:节点之间不共享内存或时钟,通过消息传递通信
- 透明性:对用户隐藏分布特性,表现为单一系统形象
- 故障独立性:单个节点故障不会导致整个系统崩溃
关键技术组件
- 分布式通信:RPC、消息队列、gRPC等
- 共识算法:Paxos、Raft等
- 分布式存储:HDFS、Ceph等
- 资源调度:YARN、Mesos等
- 服务发现:ZooKeeper、etcd等
2、为什么需要分布式
单个计算机的局限性
- 单机硬件存在物理上限(CPU、内存、I/O)
- 成本非线性增长(高端服务器价格昂贵)
- 存在单点故障风险
多个计算机的优势
- 通过增加普通服务器提升整体性能
- 线性扩展能力
- 成本效益更高
- Google搜索每天处理数十亿查询,必须依赖分布式架构才能实现毫秒级响应。
分布式系统通过冗余设计持有高可用性
- 数据多副本存储(如HDFS默认3副本)
- 故障自动检测与恢复
- 服务无状态设计,可快速迁移
地理分布需求
- 就近服务用户,降低延迟(CDN原理)
- 符合数据主权法规(GDPR等)
- 灾难恢复能力(异地多活)
- 跨国电商需要在各大洲部署分布式系统,确保各地用户购物体验一致。
3、言而总之
一台机器扛不住,需要多个机器一起扛。有难同当。
二、网关
1、什么是网关
网关是连接异构网络的枢纽设备/服务,主要工作在OSI第4-7层,承担协议转换、流量调度和安全管控三大核心职能。现代API网关(如Kong/Nginx)已成为微服务架构的"智能交通警察",实现请求路由、负载均衡、鉴权限流等关键功能。在云原生场景中,网关更演变为服务网格的流量控制平面,通过协议转换能力(如HTTP/gRPC互转)实现系统间无缝对接,是构建高可用分布式系统的核心组件。
2、网关的作用
网关是连接不同网络系统的"智能枢纽",主要发挥三大核心作用:
协议转换:像翻译官一样,让使用不同通信协议的系统能相互理解(如把HTTP请求转为银行系统的私有协议)
流量管控:担任"网络交警"的角色,智能分配请求流量、控制访问速度、保障系统不被突发流量冲垮
安全防护:作为统一的安全检查站,集中处理身份验证、权限控制、防攻击等安全事务
现代网关(如API网关)更是微服务架构的"总调度室",既协调各服务高效协作,又对外提供统一入口。它就像公司的前台接待,既分流来访者到对应部门,又核查身份确保安全,让整个系统运转更顺畅、更安全。
3、言而总之
保安
三、认证
1、认证在计算机中,大家应该不陌生。主要有一下三点作用。
- 身份核验(Authentication)
- 通过密码、短信验证码、指纹、token、session等方式验证"你是你"
- 典型案例:微信扫码登录第三方网站时,需在手机端确认授权
- 权限管控(Authorization)
- 确定用户能访问哪些资源
- 例如:普通员工只能查看自己的考勤记录,而HR可以查看全公司数据
- 访问审计(Auditing)
- 记录所有登录尝试和操作行为
- 典型应用:银行APP会记录每一次登录的设备和地点
2、言而总之
请出示证件
四、环境准备阶段
首先选择一个自己喜欢的编辑器方便开发,我选择Intellij IDEA,准备好JDK8(最常用的版本、因为稳定,稳定比过花哨),nacos(服务注册与发现),Maven,redis。
1、Intellij IDEA官网地址:

2、JDK8资源地址:关注公众号,回复:jdk8 小牛 - 全栈工程师 | 个人官网
3、nacos下载地址:Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方社区 | Nacos 官网Nacos 提供动态服务发现、配置和管理,助力用户在私有云、混合云、公有云等环境中快速构建和交付微服务平台,提升业务复用和创新交付速度,为用户赢得市场竞争力。
https://nacos.io/

Nacos的使用教程可以前往这个博主文章:Nacos使用教程,还不会使用Nacos,看这一篇就够了!-CSDN博客
Maven官网下载链接:Maven – 下载 Maven

Redis的github链接地址:https://github.com/microsoftarchive/redis

五、开始系统搭建
1、首先打开IDEA编辑器,点击新建项目。版本:springboot2.3.12.RELEASE


2、创建模块

选择好后直接下一步创建

按照以上模块创建流程,继续创建cloud-user、cloud-gateway、cloud-common。
3、cloud-auth模块代码
项目结构

pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.com</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>cloud-auth</artifactId>
<packaging>jar</packaging>
<name>cloud-auth</name>
<description>cloud-auth</description>
<properties>
</properties>
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--验证参数-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.com</groupId>
<artifactId>cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</exclusion>
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</exclusion>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>org.com.cloudAuth.CloudAuthApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
yml文件
server:
port: 9092
servlet:
context-path: /api
spring:
application:
name: authService
main:
allow-bean-definition-overriding: true
profiles:
active: dev
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HN
auth:
excludePaths: # 不需要登录 即可访问的地址
- /api/login/**
origins:
- 127.0.0.1:9090
CommonConfig
package org.com.cloudAuth.config;
import org.com.cloudCommon.redis.RedisTool;
import org.com.cloudCommon.utils.JwtUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class CommonConfig<V> {
@Bean
public RedisTool<V> redisTool(RedisTemplate<String, V> redisTemplate) {
return new RedisTool<>(redisTemplate);
}
@Bean("redisTemplate")
public RedisTemplate<String, V> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, V> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
//设置value的序列化方式
template.setValueSerializer(RedisSerializer.json());
//设置hash的key的序列化方式
template.setHashKeySerializer(RedisSerializer.string());
//设置hash的value的序列化方式
template.setHashValueSerializer(RedisSerializer.json());
return template;
}
@Bean
public JwtUtil<V> jwtUtil(RedisTool<V> redisTool) {
return new JwtUtil<>(redisTool);
}
}
ExceptionAdvice
package org.com.cloudAuth.config;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudCommon.enums.ResultCode;
import org.com.cloudCommon.exception.BusinessException;
import org.com.cloudCommon.model.R;
import org.com.cloudCommon.exception.AuthException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.validation.ConstraintViolationException;
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
public R<String> handleException(Exception e) {
log.error("系统异常", e);
return R.fail(ResultCode.SystemError);
}
@ExceptionHandler(BusinessException.class)
public R<String> BusinessException(BusinessException e) {
log.error(e.getMessage());
return R.fail(e.getResultCode());
}
@ExceptionHandler(AuthException.class)
public R<String> AuthException(AuthException e) {
log.error(e.getMessage());
return R.fail(e.getResultCode());
}
@ExceptionHandler(NoHandlerFoundException.class)
public R<String> NoHandlerFoundException(NoHandlerFoundException e) {
log.error("请求路径不存在 {}", e.getMessage());
return R.fail(ResultCode.NotFindError);
}
@ExceptionHandler(IllegalStateException.class)
public R<String> IllegalStateException(IllegalStateException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public R<String> HttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(IllegalArgumentException.class)
public R<String> IllegalArgumentException(IllegalArgumentException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R<String> HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(ConstraintViolationException.class)
public R<String> ConstraintViolationException(ConstraintViolationException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
}
WebConfig
package org.com.cloudAuth.config;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudAuth.pro.AuthPro;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import javax.annotation.Resource;
@Configuration
@Slf4j
public class WebConfig extends WebMvcConfigurationSupport {
@Resource
private AuthPro authPro;
/**
* 配置跨域
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(authPro.getOrigins())
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowedHeaders("Content-Type", "Authorization") // 允许的请求头
.maxAge(3600)
.allowCredentials(true);
log.info("cloud-auth认证中心跨域配置完成");
}
}
AuthController
package org.com.cloudAuth.controller;
import com.sun.istack.internal.NotNull;
import org.com.cloudAuth.service.AuthService;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.model.R;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.constraints.NotEmpty;
import java.util.Map;
@RestController
@RequestMapping("/auth")
@Validated
public class AuthController {
@Resource
private AuthService authService;
@PostMapping("/token")
public R<String> getToken(@RequestBody @NotNull UserInfo userInfo) {
String token = authService.createToken(userInfo);
return R.ok(token);
}
@GetMapping("check")
public R<Map<String, Object>> validateToken(@RequestParam @NotEmpty String token, @RequestParam String role) {
Map<String, Object> map = authService.validateToken(token, role);
return R.ok(map);
}
}
AuthPro
package org.com.cloudAuth.pro;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@ConfigurationProperties(prefix = "auth")
@Component
public class AuthPro {
/**
* 允许跨域的域名列表
*/
private String[] origins;
/**
* 排除路径
*/
private String[] excludePaths;
}
AuthService
package org.com.cloudAuth.pro;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@ConfigurationProperties(prefix = "auth")
@Component
public class AuthPro {
/**
* 允许跨域的域名列表
*/
private String[] origins;
/**
* 排除路径
*/
private String[] excludePaths;
}
AuthServiceImpl
package org.com.cloudAuth.service.impl;
import org.com.cloudAuth.service.AuthService;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.enums.RoleEnum;
import org.com.cloudCommon.exception.AuthException;
import org.com.cloudCommon.utils.JwtUtil;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Map;
@Service(value = "authService")
public class AuthServiceImpl implements AuthService {
@Resource
private JwtUtil<Long> jwtUtil;
@Override
public String createToken(UserInfo userInfo) {
return jwtUtil.generateJwtToken(userInfo);
}
@Override
public Map<String, Object> validateToken(String token, String role) throws AuthException {
return jwtUtil.checkTokenToClaims(token, RoleEnum.getByType(role));
}
}
启动类
package org.com.cloudAuth;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CloudAuthApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(CloudAuthApplication.class);
springApplication.setBannerMode(Banner.Mode.OFF);
springApplication.run(args);
}
}
4、cloud-common模块代码
项目结构

pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.com</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>cloud-common</artifactId>
<packaging>jar</packaging>
<name>cloud-common</name>
<description>cloud-common</description>
<properties>
</properties>
<dependencies>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<!--redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<!--数据库交互-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mysql-plus.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>org.com.cloudCommon.CloudCommonApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
UserInfo
package org.com.cloudCommon.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* Description 用户信息
* date 2024-08-28 12:49:24
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user_info")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@TableId(value = "user_id")
private String userId;
/**
* 邮箱
*/
private String email;
/**
* 头像
*/
private String avatar;
/**
* 微信ID
*/
private String openId;
/**
* 手机号
*/
private String phone;
/**
* 昵称
*/
private String nickName;
/**
* 0:直接添加 1:同意后添加
*/
private Integer joinType;
/**
* 角色 admin:管理员 user:普通用户
*/
private String role;
/**
* 0:女 1男
*/
private Integer sex;
/**
* 密码
*/
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
/**
* 个性签名
*/
private String personalSignature;
/**
* 状态
*/
private Integer status;
/**
* 创建时间
*/
private Long createTime;
/**
* 最后一次登录时间
*/
private Long lastLoginTime;
/**
* 地区
*/
private String areaName;
/**
* 地区编号
*/
private String areaCode;
/**
* 退出登录时间
*/
private Long lastOffTime;
}
JoinTypeEnum
package org.com.cloudCommon.enums;
import lombok.Getter;
@Getter
public enum JoinTypeEnum {
JOIN(0, "直接加入"),
APPLY(1, "需要同意");
private final int joinType;
private final String description;
JoinTypeEnum(int joinType, String description) {
this.joinType = joinType;
this.description = description;
}
}
ResultCode
package org.com.cloudCommon.enums;
import lombok.Getter;
import lombok.Setter;
@Getter
public enum ResultCode {
Success(1, "操作成功"),
Fail(0, "操作失败"),
NotFindError(10001, "未查询到信息"),
SaveError(10002, "保存信息失败"),
UpdateError(10003, "更新信息失败"),
DeleteError(100012, "删除信息失败"),
ValidateError(10004, "数据检验失败"),
StatusHasValid(10005, "状态已经被启用"),
StatusHasInvalid(10006, "状态已经被禁用"),
SystemError(10007, "系统异常"),
BusinessError(10008, "业务异常"),
TransferStatusError(10010, "当前状态不正确,请勿重复提交"),
NotGrant(10011, "没有操作该功能的权限,请联系管理员"),
CODE_10013(10013, "请求地址不存在"),
CODE_500(500, "抱歉,服务器出差?"),
CODE_10015(10015, "未登录"),
CODE_10016(10016, "无权限"),
CODE_10017(10017, "请求参数错误"),
CODE_10018(10018, "邮箱验证码发送失败"),
CODE_900(900, "登录超时");
@Setter
private Integer code;
@Setter
private String msg;
ResultCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
RoleEnum
package org.com.cloudCommon.enums;
import lombok.Getter;
import java.util.Objects;
@Getter
public enum RoleEnum {
ADMIN("ADMIN"),
USER("USER");
private final String role;
RoleEnum(String role) {
this.role = role;
}
public static RoleEnum getByType(String name) {
for (RoleEnum roleEnum : RoleEnum.values()) {
if (name != null && roleEnum.toString() == name) {
return roleEnum;
}
}
return null;
}
}
UserStatusEnum
package org.com.cloudCommon.enums;
import lombok.Getter;
@Getter
public enum UserStatusEnum {
SHUT_DOWN(0, "封禁"),
NORMAL(1, "正常");
private final int type;
private final String description;
UserStatusEnum(int type, String description) {
this.type = type;
this.description = description;
}
}
zeEnum
package org.com.cloudCommon.enums;
import lombok.Getter;
@Getter
public enum ZeEnum {
ZE_EMAIL("^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"),
ZE_USERNAME("^.{1,7}$"),
ZE_PASSWORD("^[a-zA-Z0-9]{3,10}$");
private final String ze;
ZeEnum(String ze) {
this.ze = ze;
}
}
AuthException
package org.com.cloudCommon.exception;
import lombok.Getter;
import org.com.cloudCommon.enums.ResultCode;
@Getter
public class AuthException extends RuntimeException {
private final ResultCode resultCode;
public AuthException(ResultCode codeEnum) {
super(codeEnum.getMsg());
this.resultCode = codeEnum;
}
public AuthException(ResultCode codeEnum, String msg) {
super(msg);
codeEnum.setMsg(msg);
this.resultCode = codeEnum;
}
}
BusinessException
package org.com.cloudCommon.exception;
import lombok.Getter;
import org.com.cloudCommon.enums.ResultCode;
@Getter
public class BusinessException extends RuntimeException {
private final ResultCode resultCode;
public BusinessException(ResultCode codeEnum) {
super(codeEnum.getMsg());
this.resultCode = codeEnum;
}
public BusinessException(ResultCode codeEnum, String msg) {
super(msg);
codeEnum.setMsg(msg);
this.resultCode = codeEnum;
}
/**
* 重写fillInStackTrace 业务异常不需要堆栈信息,提高效率
*/
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
TokenUserInfoDto
package org.com.cloudCommon.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.com.cloudCommon.entity.UserInfo;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TokenUserInfoDto {
private UserInfo userInfo;
private String token;
}
UserInfoDto
package org.com.cloudCommon.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.com.cloudCommon.entity.UserInfo;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoDto {
private UserInfo userInfo;
}
R
package org.com.cloudCommon.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.com.cloudCommon.enums.ResultCode;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class R<T> implements Serializable {
private static final long serialVersionUID = -1L;
private Integer code;
private String msg;
private T result;
private R(T result, Integer code, String msg) {
this.result = result;
this.code = code;
this.msg = msg;
}
private R(T result, ResultCode resultCode) {
this.result = result;
this.msg = resultCode.getMsg();
this.code = resultCode.getCode();
}
public static R<String> ok() {
return new R<>("", ResultCode.Success);
}
public static <T> R<T> ok(T data) {
return new R<>(data, ResultCode.Success);
}
public static R<String> fail(ResultCode resultCode) {
return new R<>("", resultCode);
}
public static R<String> fail(ResultCode resultCode, String msg) {
return new R<>("", resultCode.getCode(), msg);
}
}
RedisTool
package org.com.cloudCommon.redis;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Data
public class RedisTool<V> {
private RedisTemplate<String, V> redisTemplate;
/**
* 构造函数
*
* @param template RedisTemplate
*/
public RedisTool(RedisTemplate<String, V> template) {
this.redisTemplate = template;
}
/**
* 批量删除对应的value
*
* @param key 键
*/
public void delete(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.stream(key).collect(Collectors.toList()));
}
}
}
/**
* 获取缓存
*
* @param key 键
* @return 值
*/
public V get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 存入缓存
*
* @param key 键
* @param value 值
*/
public void set(String key, V value) {
try {
redisTemplate.opsForValue().set(key, value);
} catch (Exception ignore) {
log.error("设置redis失败 Key:{} value:{}", key, value);
}
}
/**
* 带有时效性存入缓存
*
* @param key 键
* @param value 值
* @param time 失效
*/
public void setX(String key, V value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
} catch (Exception e) {
log.error("设置redis失败 Key:{} value:{}", key, value);
}
}
/**
* 设置失效时间
*
* @param key 键
* @param time 失效
*/
public void expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 获取队列
*
* @param key 键
* @return 队列
*/
public List<V> getQueuelist(String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 从队列中取出一个元素
*
* @param key 键
* @return 队列中的一个元素
*/
public V rPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* 加入队列
*
* @param key 键
* @param value 值
* @param time 失效
*/
public void lPush(String key, V value, long time) {
try {
redisTemplate.opsForList().leftPush(key, value);
if (time > 0) {
expire(key, time);
}
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 从队列中取出一个元素
*
* @param key 键
* @return 队列中的一个元素
*/
public Long remove(String key, Object value) {
try {
return redisTemplate.opsForList().remove(key, 1, value);
} catch (Exception e) {
log.error(e.getMessage());
return 0L;
}
}
/**
* 向集合中添加元素
*
* @param key 键
* @param value 值
*/
public void addSet(String key, V value) {
redisTemplate.opsForSet().add(key, value);
}
/**
* 移除集合中的元素
*
* @param key 键
* @param value 值
*/
public void removeSet(String key, V value) {
redisTemplate.opsForSet().remove(key, value);
}
/**
* 获取集合
*
* @param key 键
* @return 集合
*/
public List<V> getSet(String key) {
Long size = redisTemplate.opsForSet().size(key);
if (size != null && size > 0) {
return redisTemplate.opsForSet().randomMembers(key, size);
} else {
return new ArrayList<>();
}
}
/**
* 获取集合中的一个元素
*
* @param key 键
* @return 集合中的一个元素
*/
public V getSetOne(String key) {
if (getSet(key) != null && !getSet(key).isEmpty()) {
return getSet(key).get(0);
}
return null;
}
/**
* 批量存入缓存
*
* @param key 键
* @param values 值
* @param time 失效
*/
public void lPushAll(String key, List<V> values, long time) {
try {
redisTemplate.opsForList().leftPushAll(key, values);
if (time > 0) {
expire(key, time);
}
} catch (Exception e) {
log.error("批量存入redis失败:\n", e);
}
}
/**
* 哈希表加入
*
* @param key 键
* @param hk 哈希表字段
* @param hv 哈希表值
*/
public void putHash(String key, String hk, Object hv) {
try {
redisTemplate.opsForHash().put(key, hk, hv);
} catch (Exception e) {
log.error("缓存哈希加入失败:\n", e);
}
}
/**
* 获取哈希表中指定字段的值
*
* @param key 键
* @param hk 哈希表字段
* @return 哈希表中指定字段的值
*/
public Object getHash(String key, String hk) {
try {
return redisTemplate.opsForHash().get(key, hk);
} catch (Exception e) {
log.error("缓存哈希获取失败:\n", e);
}
return null;
}
/**
* 获取键的剩余过期时间
*
* @param key 键
* @return 剩余过期时间(秒)
*/
public int ttl(String key) {
Long t = redisTemplate.getExpire(key);
t = t == null ? 0 : t;
return Math.toIntExact(t);
}
}
DataTool
package org.com.cloudCommon.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.com.cloudCommon.enums.ResultCode;
import org.com.cloudCommon.enums.ZeEnum;
import org.com.cloudCommon.exception.BusinessException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class DataTool {
public static void checkAffectRow(int result, String message) throws BusinessException {
if (result == 0) {
message = message.isEmpty() ? ResultCode.Fail.getMsg() : message;
throw new BusinessException(ResultCode.Fail, message);
}
}
public static boolean zeCheckStr(ZeEnum zeEnum, String str) {
Pattern ze = Pattern.compile(zeEnum.getZe());
Matcher matcher = ze.matcher(str);
return matcher.matches();
}
public static String getUserId() {
return 'U' + getRandomNumber(11);
}
public static String getRandomNumber(Integer count) {
return RandomStringUtils.random(count, false, true);
}
}
JwtUtil
package org.com.cloudCommon.utils;
import io.jsonwebtoken.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.enums.ResultCode;
import org.com.cloudCommon.enums.RoleEnum;
import org.com.cloudCommon.exception.AuthException;
import org.com.cloudCommon.redis.RedisTool;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Data
@NoArgsConstructor
public class JwtUtil<V> {
/**
* 签名密钥
*/
private static final String secretKey = "J+/0Nr8/LeRpjIXuvhfbFz18RBp99/uHzg+26SPByks=";
/**
* token过期时间
*/
private final Long tokenExpire = 2592000L;
private final String role = "role";
private final String userId = "userId";
private final String nickName = "nickName";
private RedisTool<V> redisTool;
public JwtUtil(RedisTool<V> redisTool) {
this.redisTool = redisTool;
}
/**
* 生成token 默认30天时效
*/
public String generateJwtToken(UserInfo userInfo) {
return generateJwtToken(userInfo, tokenExpire);
}
/**
* 生成token
*/
public String generateJwtToken(UserInfo userInfo, long expirationMillis) {
Date now = new Date();
Date expirationDate = new Date(now.getTime() + expirationMillis);
return Jwts.builder()
// 头部
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
//将用户的唯一标识作为主题设置到JWT令牌中
.claim(role, userInfo.getRole())
.claim(userId, userInfo.getUserId())
.claim(nickName, userInfo.getNickName())
//设置令牌的签发时间
.setIssuedAt(now)
//设置过期时间
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS256, secretKey + userInfo.getLastOffTime())
.compact();
}
/**
* 解析token
*/
public Claims parseJwtToken(String token) {
V offTme = redisTool.get(token);
if (offTme == null) {
throw new AuthException(ResultCode.CODE_10015);
}
// 创建JwtParser对象,用于解析JWT令牌
JwtParserBuilder jwtParserBuilder = Jwts.parserBuilder();
JwtParser jwtParser = jwtParserBuilder.setSigningKey(secretKey + offTme) //设置验证签名所使用的密钥(与生成JWT时使用的密钥相同)
.build();
// 解析JWT令牌并验证签名
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token); // 解析JWT令牌
return claimsJws.getBody();
}
/**
* 抛出token异常
*/
public Map<String, Object> checkTokenToClaims(String token, RoleEnum roleEnum) throws AuthException {
if (token == null || token.isEmpty()) {
throw new AuthException(ResultCode.CODE_10015);
}
Map<String, Object> map = new HashMap<>();
try {
Claims claims = parseJwtToken(token);
map.put(role, claims.get(role));
map.put(userId, claims.get(userId));
map.put(nickName, claims.get(nickName));
if (roleEnum == null) {
return map;
}
switch (roleEnum) {
case ADMIN:
if (!claims.get(role).equals(RoleEnum.ADMIN.getRole())) {
throw new AuthException(ResultCode.CODE_10016);
}
break;
case USER:
if (!claims.get(role).equals(RoleEnum.USER.getRole())) {
throw new AuthException(ResultCode.CODE_10016);
}
break;
default:
break;
}
} catch (Exception ignored) {
redisTool.delete(token);
throw new AuthException(ResultCode.CODE_900);
}
return map;
}
}
Md5Util
package org.com.cloudCommon.utils;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudCommon.enums.ResultCode;
import org.com.cloudCommon.exception.BusinessException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Optional;
/**
* 写一个MD5算法,运行结果与MySQL的md5()函数相同
* 将明文密码转成MD5密码
* 123456->e10adc3949ba59abbe56e057f20f883e
*/
@Slf4j
public class Md5Util {
/**
* 将明文密码转成MD5密码
*/
public static String encodeByMd5(String string) {
try {
//Java中MessageDigest类封装了MD5和SHA算法
MessageDigest md5 = MessageDigest.getInstance("MD5");
//调用MD5算法,即返回16个byte类型的值
byte[] byteArray = md5.digest(string.getBytes());
//注意:MessageDigest只能将String转成byte[],接下来的事情,由我们程序员来完成
return byteArrayToHexString(byteArray);
} catch (Exception e) {
log.error("异常: ", e);
return "fc5ae9ce578fa6bff964d0052e91b2d143";
}
}
/**
* 将byte[]转在16进制字符串
*/
private static String byteArrayToHexString(byte[] byteArray) {
StringBuilder sb = new StringBuilder();
//遍历
for (byte b : byteArray) {//16次
//取出每一个byte类型,进行转换
String hex = byteToHexString(b);
//将转换后的值放入StringBuffer中
sb.append(hex);
}
return sb.toString();
}
/**
* 将byte转成16进制字符串
*/
private static String byteToHexString(byte b) {//-31转成e1,10转成0a,。。。
//将byte类型赋给int类型
int n = b;
//如果n是负数
if (n < 0) {
//转正数
//-31的16进制数,等价于求225的16进制数
n = 256 + n;
}
//商(14),数组的下标
int d1 = n / 16;
//余(1),数组的下标
int d2 = n % 16;
//通过下标取值
return hex[d1] + hex[d2];
}
public static String Md5Method(String[] args) {
Optional<String> reduce = Arrays.stream(args).sorted().reduce((a, b) -> a + b);
if (reduce.isPresent()) {
return encodeByMd5(reduce.get());
}
throw new BusinessException(ResultCode.BusinessError);
}
private static final String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
public static void main(String[] args) {
String g11942322362 = Md5Method(new String[]{"G15200011049"});
System.out.println(g11942322362);
}
}
启动类
package org.com.cloudCommon;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CloudCommonApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(CloudCommonApplication.class);
springApplication.setBannerMode(Banner.Mode.OFF);
springApplication.run(args);
}
}
5、cloud-gateway模块代码
项目结构

pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.com</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>cloud-gateway</artifactId>
<packaging>jar</packaging>
<name>cloud-gateway</name>
<description>cloud-gateway</description>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.com</groupId>
<artifactId>cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>org.com.cloudGateway.CloudGatewayApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
yml文件
server:
port: 9090
spring:
application:
name: gatewayService
main:
allow-bean-definition-overriding: true
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
allowedHeaders: "*"
allowCredentials: true
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HN
userService:
ribbon: # 负载均衡配置
NFLoadBalancerRuleClassName:
com.alibaba.cloud.nacos.ribbon.NacosRule
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名
- userService
- socketService
gateway:
adminPaths: # 角色认证路径
- /api/userService/admin
excludePaths: # 排除认证路径
- /api/userService/public
- /api/notifyService/notify
authIp: # 认证服务地址
http://127.0.0.1:9092
authPath: # 认证服务路径
/api/auth/check
serviceNames: # 服务名列表
- userService
socketNames: # socket服务名列表
- socketService
GatewayConfig
package org.com.cloudGateway.config;
import org.com.cloudGateway.filter.AuthenticationFilter;
import org.com.cloudGateway.pro.GatewayPro;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.reactive.function.client.WebClient;
import javax.annotation.Resource;
@Configuration
public class GatewayConfig {
@Resource
private GatewayPro appContext;
@Bean
public AuthenticationFilter authenticationFilter(WebClient.Builder webClientBuilder) {
return new AuthenticationFilter(webClientBuilder);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder, AuthenticationFilter authenticationFilter) {
RouteLocatorBuilder.Builder routes = builder.routes();
for (String serviceName : appContext.getServiceNames()) {
routes.route(serviceName, r -> r.path("/api/" + serviceName + "/**")
.filters(f ->
f.filter((exchange, chain) -> {
// 指定路径不需要认证
String path = exchange.getRequest().getPath().toString();
for (String excludePath : appContext.getExcludePaths()) {
if (path.startsWith(excludePath)) {
return chain.filter(exchange);
}
}
return authenticationFilter.filter(exchange, chain);
}).filter((exchange, chain) -> {
// 获取原始路径
String originalPath = exchange.getRequest().getURI().getRawPath();
// 拆分路径段
String[] parts = originalPath.split("/");
// 构造新的路径,去掉第二段(索引为2,因为 parts[0] 是空字符串)
if (parts.length > 2) {
StringBuilder newPath = new StringBuilder();
newPath.append("/").append(parts[1]); // 保留第1段(如 "api")
for (int i = 3; i < parts.length; i++) {
newPath.append("/").append(parts[i]);
}
// 使用 mutate 创建新的 exchange 请求
ServerHttpRequest newRequest = exchange.getRequest().mutate()
.path(newPath.toString())
.build();
exchange = exchange.mutate().request(newRequest).build();
}
return chain.filter(exchange);
})
)
.uri("lb://" + serviceName)); // 此处为下游微服务的URI 这里使用 lb:// 前缀来启用负载均衡
}
for (String serviceName : appContext.getSocketNames()) {
routes.route(serviceName, r -> r.path("/" + serviceName + "/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://" + serviceName));
}
return routes.build();
}
}
AuthenticationFilter
package org.com.cloudGateway.filter;
import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudCommon.enums.ResultCode;
import org.com.cloudCommon.enums.RoleEnum;
import org.com.cloudCommon.model.R;
import org.com.cloudGateway.pro.GatewayPro;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author 小牛
* @version 1.0
* @description 认证过滤器
* 用于验证用户身份和权限的过滤器,在请求到达微服务之前进行拦截。
* @date 2025/4/7 23:09
*/
@Component
@Slf4j
public class AuthenticationFilter implements GatewayFilter {
@Resource
private GatewayPro gatewayPro;
// 在网关初始化时创建专用线程池
private static final ExecutorService URL_ENCODING_EXECUTOR =
Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2, // 通常CPU核数×2
new ThreadFactoryBuilder()
.setNameFormat("url-encoder-pool-%d")
.setDaemon(true)
.build()
);
private final WebClient.Builder webClientBuilder;
public AuthenticationFilter(WebClient.Builder webClientBuilder) {
this.webClientBuilder = webClientBuilder;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求头中的Authorization(JWT Token)
List<String> token = exchange.getRequest().getHeaders().get("token");
if (token == null || token.isEmpty() || token.get(0).isEmpty()) {
return unauthorizedResponse(exchange, R.fail(ResultCode.CODE_10015));
}
String uri = gatewayPro.getAuthIp() + gatewayPro.getAuthPath() + "?" + "token" + "=" + token.get(0) + "&" + "role" + "=" + buildRoleEnum(exchange.getRequest().getPath().toString()).getRole();
// 调用认证微服务验证Token的有效性
return webClientBuilder.build()
.get()
.uri(uri) // 调用认证微服务的验证接口
.retrieve()
.bodyToMono(R.class)
.flatMap(R -> {
if (Objects.equals(R.getCode(), ResultCode.Success.getCode())) {
// Token有效,继续处理请求
Map<String, Object> data = (Map<String, Object>) R.getResult();
if (data != null) {
try {
exchange.getRequest().mutate()
.header("userId", String.valueOf(data.get("userId")))
.header("role", String.valueOf(data.get("role")))
.header("nickName", asyncUrlEncode(String.valueOf(data.get("nickName"))).get()).build();
} catch (ExecutionException | InterruptedException e) {
log.error("URL编码失败", e);
return Mono.error(new RuntimeException(e));
}
}
return chain.filter(exchange);
} else {
return unauthorizedResponse(exchange, R);
}
});
}
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, R R) {
// 创建响应的 JSON 内容
String jsonResponse = JSONObject.toJSONString(R);
// 将 JSON 转化为 ByteBuffer
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
DataBuffer dataBuffer = bufferFactory.wrap(jsonResponse.getBytes(StandardCharsets.UTF_8));
// 设置响应头 Content-Type 为 application/json
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 将 JSON 写入响应体
return exchange.getResponse().writeWith(Mono.just(dataBuffer));
}
private RoleEnum buildRoleEnum(String path) {
for (String adminPath : gatewayPro.getAdminPaths()) {
if (path.startsWith(adminPath)) {
return RoleEnum.ADMIN;
}
}
return RoleEnum.USER;
}
// 使用CompletableFuture进行异步URL编码
public CompletableFuture<String> asyncUrlEncode(String value) {
return CompletableFuture.supplyAsync(() -> {
try {
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
log.error("URL编码失败", e);
throw new RuntimeException(e);
}
}, URL_ENCODING_EXECUTOR); // 使用专用线程池
}
}
GatewayPro
package org.com.cloudGateway.pro;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "gateway")
public class GatewayPro {
/**
* admin路径
*/
private String[] adminPaths;
/**
* 认证微服务路径
*/
private String authPath;
/**
* 认证微服务ip地址
*/
private String authIp;
/**
* 排除路径
*/
private String[] excludePaths;
/**
* 服务名称列表
*/
private String[] serviceNames;
/**
* 服务地址列表
*/
private String[] socketNames;
}
启动类
package org.com.cloudGateway;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CloudGatewayApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(CloudGatewayApplication.class);
springApplication.setBannerMode(Banner.Mode.OFF);
springApplication.run(args);
}
}
6、cloud-user模块代码
项目结构

pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.com</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>cloud-user</artifactId>
<packaging>jar</packaging>
<name>cloud-user</name>
<description>cloud-user</description>
<properties>
</properties>
<dependencies>
<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-openfeign</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--验证参数-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--数据库交互-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- 数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.com</groupId>
<artifactId>cloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>org.com.cloudUser.CloudUserApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
yml文件
server:
port: 9091
servlet:
context-path: /api
spring:
application:
name: userService
main:
allow-bean-definition-overriding: true
mvc:
throw-exception-if-no-handler-found: true
profiles:
active: dev
datasource:
url: jdbc:mysql://localhost:3306/neocalf?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
username: root
password: LU960422
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: HikariCPDatasource
minimum-idle: 5
idle-timeout: 180000
maximum-pool-size: 10
auto-commit: true
max-lifetime: 180000
connection-timeout: 3000
connection-test-query: SELECT 1
redis:
database: 0
host: 127.0.0.1
port: 6379
jedis:
pool:
max-active: 20
max-wait: -1
max-idle: 10
min-idle: 0
timeout: 2000
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HN
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
type-aliases-package: com.neoCalf.neoCommon.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
feign:
client:
config:
default:
connectTimeout: 20000
readTimeout: 20000
CommonConfig
package org.com.cloudUser.config;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudCommon.redis.RedisTool;
import org.com.cloudCommon.utils.JwtUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
@Slf4j
public class CommonConfig<V> {
@Bean
public RedisTool<V> redisTool(RedisTemplate<String, V> redisTemplate) {
return new RedisTool<>(redisTemplate);
}
@Bean("redisTemplate")
public RedisTemplate<String, V> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, V> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
//设置value的序列化方式
template.setValueSerializer(RedisSerializer.json());
//设置hash的key的序列化方式
template.setHashKeySerializer(RedisSerializer.string());
//设置hash的value的序列化方式
template.setHashValueSerializer(RedisSerializer.json());
return template;
}
@Bean
public JwtUtil<V> jwtUtil(RedisTool<V> redisTool) {
return new JwtUtil<>(redisTool);
}
}
ExceptionAdvice
package org.com.cloudUser.config;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudCommon.enums.ResultCode;
import org.com.cloudCommon.exception.AuthException;
import org.com.cloudCommon.exception.BusinessException;
import org.com.cloudCommon.model.R;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.validation.ConstraintViolationException;
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
public R<String> handleException(Exception e) {
log.error("系统异常", e);
return R.fail(ResultCode.SystemError);
}
@ExceptionHandler(BusinessException.class)
public R<String> BusinessException(BusinessException e) {
log.error(e.getMessage());
return R.fail(e.getResultCode());
}
@ExceptionHandler(AuthException.class)
public R<String> AuthException(AuthException e) {
log.error(e.getMessage());
return R.fail(e.getResultCode());
}
@ExceptionHandler(NoHandlerFoundException.class)
public R<String> NoHandlerFoundException(NoHandlerFoundException e) {
log.error("请求路径不存在 {}", e.getMessage());
return R.fail(ResultCode.NotFindError);
}
@ExceptionHandler(IllegalStateException.class)
public R<String> IllegalStateException(IllegalStateException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public R<String> HttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(IllegalArgumentException.class)
public R<String> IllegalArgumentException(IllegalArgumentException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R<String> HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(ConstraintViolationException.class)
public R<String> ConstraintViolationException(ConstraintViolationException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
@ExceptionHandler(ServletRequestBindingException.class)
public R<String> ServletRequestBindingException(ServletRequestBindingException e) {
log.error("参数错误 {}", e.getMessage());
return R.fail(ResultCode.CODE_10017);
}
}
LoginController
package org.com.cloudUser.controller;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.model.R;
import org.com.cloudCommon.model.dto.TokenUserInfoDto;
import org.com.cloudUser.service.LoginService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
@RestController
@RequestMapping("/public")
@Validated
public class LoginController {
@Resource
private LoginService loginService;
@PostMapping("/login")
public R<TokenUserInfoDto> login(@RequestBody @NotNull UserInfo userInfo) {
TokenUserInfoDto tokenUserInfoDto = loginService.login(userInfo.getEmail(), userInfo.getPassword());
return R.ok(tokenUserInfoDto);
}
@PostMapping("/register")
public R<String> register(@RequestBody @NotNull UserInfo userInfo) {
loginService.register(userInfo);
return R.ok();
}
}
UserInfoController
package org.com.cloudUser.controller;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.model.R;
import org.com.cloudUser.service.UserInfoService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/info")
@Validated
public class UserInfoController {
@Resource
private UserInfoService userInfoService;
@GetMapping("/findUserInfo")
public R<UserInfo> findUserInfo(HttpServletRequest request) {
String userId = request.getHeader("userId");
UserInfo userInfo = userInfoService.findUserInfo(userId);
return R.ok(userInfo);
}
}
AuthServiceFeign
package org.com.cloudUser.feigns;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.model.R;
import org.jetbrains.annotations.NotNull;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 本方法没有用到,只是为了方便理解Feign的使用。
*/
@FeignClient(value = "authService", path = "/api")
public interface AuthServiceFeign {
/**
* 获取token
*/
@PostMapping(value = "/auth/token")
R<String> getToken(@RequestBody @NotNull UserInfo userInfo);
}
UserInfoMapper
package org.com.cloudUser.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.com.cloudCommon.entity.UserInfo;
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
LoginServiceImpl
package org.com.cloudUser.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.enums.*;
import org.com.cloudCommon.exception.BusinessException;
import org.com.cloudCommon.model.dto.TokenUserInfoDto;
import org.com.cloudCommon.utils.DataTool;
import org.com.cloudCommon.utils.JwtUtil;
import org.com.cloudCommon.utils.Md5Util;
import org.com.cloudUser.mapper.UserInfoMapper;
import org.com.cloudUser.service.LoginService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
@Service(value = "loginService")
@Slf4j
public class LoginServiceImpl implements LoginService {
@Resource
private UserInfoMapper userInfoMapper;
@Resource
private JwtUtil<Long> jwtUtil;
@Override
@Transactional(rollbackFor = Exception.class)
public void register(UserInfo userInfo) {
String email = userInfo.getEmail();
if (
email == null ||
!DataTool.zeCheckStr(ZeEnum.ZE_USERNAME, userInfo.getNickName()) ||
!DataTool.zeCheckStr(ZeEnum.ZE_PASSWORD, userInfo.getPassword()) ||
!DataTool.zeCheckStr(ZeEnum.ZE_EMAIL, email)
) {
throw new BusinessException(ResultCode.CODE_10017);
}
// 校验邮箱是否已注册
LambdaQueryWrapper<UserInfo> userInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
userInfoLambdaQueryWrapper.eq(UserInfo::getEmail, email);
UserInfo user = userInfoMapper.selectOne(userInfoLambdaQueryWrapper);
if (user != null) {
throw new BusinessException(ResultCode.CODE_10017, "邮箱已注册");
}
userInfo.setUserId(DataTool.getUserId());
userInfo.setRole(RoleEnum.USER.getRole());
long date = new Date().getTime();
userInfo.setJoinType(JoinTypeEnum.APPLY.getJoinType());
userInfo.setStatus(UserStatusEnum.NORMAL.getType());
userInfo.setCreateTime(date);
userInfo.setPassword(Md5Util.encodeByMd5(userInfo.getPassword()));
userInfo.setLastOffTime(date);
int result = userInfoMapper.insert(userInfo);
DataTool.checkAffectRow(result, "服务器出差了...");
}
@Override
public TokenUserInfoDto login(String email, String password) {
LambdaQueryWrapper<UserInfo> userInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
userInfoLambdaQueryWrapper.eq(UserInfo::getEmail, email);
userInfoLambdaQueryWrapper.eq(UserInfo::getPassword, Md5Util.encodeByMd5(password));
UserInfo userInfo = userInfoMapper.selectOne(userInfoLambdaQueryWrapper);
if (userInfo == null) {
throw new BusinessException(ResultCode.CODE_10017, "账号或密码错误");
}
return commonLogin(userInfo);
}
public TokenUserInfoDto commonLogin(UserInfo userInfo) {
// 修改最后一次登录时间
LambdaUpdateWrapper<UserInfo> userInfoLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
userInfoLambdaUpdateWrapper.eq(UserInfo::getUserId, userInfo.getUserId());
userInfoLambdaUpdateWrapper.set(UserInfo::getLastLoginTime, new Date().getTime());
userInfoMapper.update(null, userInfoLambdaUpdateWrapper);
return getTokenUserInfoDto(userInfo);
}
public TokenUserInfoDto getTokenUserInfoDto(UserInfo userInfo) {
String token = jwtUtil.generateJwtToken(userInfo);
return new TokenUserInfoDto(userInfo, token);
}
}
UserInfoServiceImpl
package org.com.cloudUser.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudUser.mapper.UserInfoMapper;
import org.com.cloudUser.service.UserInfoService;
import org.springframework.stereotype.Service;
@Service(value = "userInfoService")
@Slf4j
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Override
public UserInfo findUserInfo(String userId) {
return getById(userId);
}
}
LoginService
package org.com.cloudUser.service;
import org.com.cloudCommon.entity.UserInfo;
import org.com.cloudCommon.model.dto.TokenUserInfoDto;
public interface LoginService {
/**
* 注册
*/
void register(UserInfo userInfo);
/**
* 邮箱登录
*/
TokenUserInfoDto login(String email, String password);
}
UserInfoService
package org.com.cloudUser.service;
import org.com.cloudCommon.entity.UserInfo;
public interface UserInfoService {
/**
* 根据用户id查询用户信息
*/
UserInfo findUserInfo(String userId);
}
启动类
package org.com.cloudUser;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import static org.springframework.boot.Banner.Mode.OFF;
@EnableAsync //开启异步支持
@EnableAspectJAutoProxy //开启切片支持
@EnableTransactionManagement
@EnableFeignClients
@EnableDiscoveryClient
@MapperScan("org.com.cloudUser.mapper")
@SpringBootApplication
public class CloudUserApplication {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(CloudUserApplication.class);
springApplication.setBannerMode(OFF);
springApplication.run(args);
}
}
7、父工程pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.com</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud</name>
<description>cloud</description>
<packaging>pom</packaging>
<modules>
<module>cloud-auth</module>
<module>cloud-gateway</module>
<module>cloud-common</module>
<module>cloud-user</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<jwt-version>0.11.2</jwt-version>
<redisson.version>3.12.3</redisson.version>
<fastjson.version>1.2.66</fastjson.version>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
<spring-cloud.alibaba.version>2.2.5.RELEASE</spring-cloud.alibaba.version>
<mysql.version>8.0.23</mysql.version>
<mysql-plus.version>3.5.2</mysql-plus.version>
<hutool-all.version>4.6.3</hutool-all.version>
<commons.lang3.version>3.4</commons.lang3.version>
<commons.codec.version>1.9</commons.codec.version>
<commons.io.version>2.5</commons.io.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--工具-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
<!--apache common-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons.codec.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--数据库交互-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mysql-plus.version}</version>
</dependency>
<!-- 数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt-version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt-version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>org.cloud.CloudApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
含大厂在职人员的交流社区,关注公众号 回复:大厂 。小牛 - 全栈工程师 | 个人官网
更多推荐




所有评论(0)