目录
引
其实进入项目一段时间了,不过目前工作的第一个项目不是web开发,为了复习巩固,便把之前做的用来练手的一套
https://gitee.com/Mman173/mode——包括后台管理系统前后端系统源码与前后台数据库但不包括前台前后端系统源码,注意分辨
另外前端的前后台由于放在另一篇文章中了,感兴趣的可以前往查看:微服务全栈+docker部署——小试牛刀or毕业设计②——前后台后端与前端
最后我提一嘴,如果要在自己的服务器上部署,包括mysql等密码与权限最好不要设置弱密码以防被黑客删库!
启动docker服务
nacos
docker run --name nacos --restart=always --env JVM_XMS=256m --env JVM_XMX=256m --env MODE=standalone -p 8848:8848 -d nacos/nacos-server:2.0.2
docker run --name nacos --restart=always -e JVM_XMS=256m -e JVM_XMX=256m --env MODE=standalone -p 8848:8848 -d nacos/nacos-server:2.0.2
docker run --name nacos --restart=always --env JVM_XMS=128m --env JVM_XMX=128m --env MODE=standalone -p 8848:8848 -d nacos/nacos-server:2.0.2
mysql5.7
docker run --name mysql5.7 -v /home/mysql5.7/data:/var/lib/mysql -v /home/mysql5.7/conf.d:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:5.7
redis
docker run --name redis -v /home/redis:/usr/local/etc/redis -p 6379:6379 -d redis:5.0.12
前置
jdk17、springboot2.7.1、springcloud2021.0.1
在父项目下快速创建maven项目进行配置
jdk17需要禁用启动优化否则报错->jdk17 none and -noverify were deprecated
maven结构
根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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.star</groupId>
<artifactId>cmPlus</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cm-framework</module>
<module>cm-medicine</module>
<module>cm-formulation</module>
<module>cm-disease</module>
<module>cm-user</module>
<module>cm-admin</module>
<module>cm-security</module>
<module>cm-gateway</module>
<module>cm-adminRole</module>
<module>cm-adminRole</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<springboot.version>2.7.1</springboot.version>
<springcloud.version>2021.0.1</springcloud.version>
<springcloud.alibaba.version>2021.0.1.0</springcloud.alibaba.version>
<alibaba.fastjson.vesion>1.2.33</alibaba.fastjson.vesion>
<jwt.version>0.9.0</jwt.version>
<mybatisplus.version>3.5.1</mybatisplus.version>
<aliyun.oss.version>3.10.2</aliyun.oss.version>
<alibaba.easyexcel.version>3.0.5</alibaba.easyexcel.version>
<velocity.version>1.7</velocity.version>
<freeMarker.version>2.3.30</freeMarker.version>
<hutool-all.version>5.5.9</hutool-all.version>
<java-jwt.version>3.10.3</java-jwt.version>
<easypoi.version>3.2.0</easypoi.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 这里引入最新的SpringCloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 这里引入最新的SpringCloudAlibaba依赖,2021.0.1.0版本支持SpringBoot2.6.X -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${springcloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${alibaba.fastjson.vesion}</version>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!--mybatisPlus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<!--阿里云OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
<!--阿里easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${alibaba.easyexcel.version}</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- Swagger第三方ui依赖 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<!-- freemarker模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>${freeMarker.version}</version>
</dependency>
<!-- velocity模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<!--easypoi-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>${easypoi.version}</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>${easypoi.version}</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>${easypoi.version}</version>
</dependency>
<!--腾讯云COS-->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.89</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
公共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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cmPlus</artifactId>
<groupId>com.star</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cm-framework</artifactId>
<dependencies>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--nacos服务注册-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--junit-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatisPlus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<!--mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!--Swagger第三方ui依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
</dependency>
<!--freemarker模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!--velocity模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
<!--easypoi-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
</dependency>
<!--腾讯云COS-->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
</dependency>
</dependencies>
</project>
安全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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cmPlus</artifactId>
<groupId>com.star</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cm-security</artifactId>
<dependencies>
<!--SpringSecurity启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.star</groupId>
<artifactId>cm-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
用户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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cmPlus</artifactId>
<groupId>com.star</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cm-user</artifactId>
<dependencies>
<dependency>
<groupId>com.star</groupId>
<artifactId>cm-security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- 引入部署插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<mainClass>com.star.UserApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
管理员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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cmPlus</artifactId>
<groupId>com.star</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cm-admin</artifactId>
<dependencies>
<dependency>
<groupId>com.star</groupId>
<artifactId>cm-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- 引入部署插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<mainClass>com.star.AdminApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
其他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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cmPlus</artifactId>
<groupId>com.star</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cm-medicine</artifactId>
<dependencies>
<dependency>
<groupId>com.star</groupId>
<artifactId>cm-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- 引入部署插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<mainClass>com.star.MedicineApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
初始application.yml配置
比如药品服务配置 其余服务更改端口和服务名即可
server:
port: 9501
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
main:
allow-bean-definition-overriding: true
allow-circular-references: true
datasource:
url: jdbc:mysql://xxxxxx:3306/cmPlus?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: xxxxxx
port: 6379
servlet:
multipart:
max-file-size: 100MB
max-request-size: 200MB
cloud:
nacos:
discovery:
server-addr: xxxxxx:8848
# 将ephemeral修改为false,表示非临时实例
ephemeral: false
application:
name: medicineService
mybatis-plus:
configuration:
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: delFlag
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto
统筹配置
响应格式
ResponseResult
@SuppressWarnings("rawtypes")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public ResponseResult() {
this.code = AppHttpCodeEnum.SUCCESS.getCode();
this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult() {
ResponseResult result = new ResponseResult();
return result;
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
if (data != null) {
result.setData(data);
}
return result;
}
public static ResponseResult errorResult(AppHttpCodeEnum enums) {
return setAppHttpCodeEnum(enums, enums.getMsg());
}
public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg) {
return setAppHttpCodeEnum(enums, msg);
}
public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums) {
return okResult(enums.getCode(), enums.getMsg());
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg) {
return okResult(enums.getCode(), msg);
}
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
AppHttpCodeEnum
public enum AppHttpCodeEnum {
// 成功
SUCCESS(200, "操作成功"),
// 登录
NEED_LOGIN(401, "需要登录后操作"),
NO_OPERATOR_AUTH(403, "无权限操作"),
SYSTEM_ERROR(500, "出现错误"),
USERNAME_EXIST(501, "用户名已存在"),
PHONENUMBER_EXIST(502, "手机号已存在"),
EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写用户名"),
LOGIN_ERROR(505, "用户名或密码错误"),
CONTENT_NOT_NULL(506, "评论内容不能为空"),
FILE_TYPE_ERROR(507, "文件类型错误"),
USERNAME_NOT_NULL(508, "用户名不能为空"),
NICKNAME_NOT_NULL(509, "昵称不能为空"),
PASSWORD_NOT_NULL(510, "密码不能为空"),
EMAIL_NOT_NULL(511, "邮箱不能为空"),
NICKNAME_EXIST(512, "昵称已存在");
int code;
String msg;
AppHttpCodeEnum(int code, String errorMessage) {
this.code = code;
this.msg = errorMessage;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
字面量
SystemConstants
public class SystemConstants {
public static final String SYSTEM_NAME = "cmPlus";
}
Bean拷贝
BeanCopyUtils
public class BeanCopyUtils {
private BeanCopyUtils() {
}
public static <V> V copyBean(Object source, Class<V> clazz) {
// 创建目标对象
V result = null;
try {
result = clazz.getDeclaredConstructor().newInstance();
// 实现属性拷贝
BeanUtils.copyProperties(source, result);
} catch (Exception e) {
e.printStackTrace();
}
// 返回结果
return result;
}
public static <O, V> List<V> copyBeanList(List<O> list, Class<V> clazz) {
return list.stream()
.map(o -> copyBean(o, clazz))
.collect(Collectors.toList());
}
}
redis和jwt配置
FastJsonRedisSerializer
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}
RedisConfig
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
JwtUtil
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000一个小时
//设置秘钥明文
public static final String JWT_KEY = "star";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("sg") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String to ke n = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}
/**
* 生成加密后的秘钥 secretKey
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
RedisCache
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
@Resource
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 递增hash结构的value值
*
* @param key Redis键
* @param hKey Hash键
*/
public void incrementCacheMapValue(String key, String hKey, long v) {
redisTemplate.boundHashOps(key).increment(hKey, v);
}
}
WebMvc配置解决跨域问题
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
整合springSecurity
思路
登录:
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService
在这个实现类中去查询数据库
注意配置passwordEncoder为BCryptPasswordEncoder
校验:
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
现行统一配置
为springboot2.7或者springsecurity5.7的配置
SecurityConfig
@Configuration
public class SecurityConfig {
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Resource
AuthenticationEntryPointImpl authenticationEntryPoint;
@Resource
AccessDeniedHandlerImpl accessDeniedHandler;
@Resource
HttpSecurity http;
@Resource
AuthenticationConfiguration authenticationConfiguration;
// AuthenticationManager全局注册
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain() throws Exception {
http
// 关闭csrf
.csrf().disable()
// 不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login").anonymous()
// 注销接口需要认证
.antMatchers("/logout").authenticated()
// jwt过滤器测试用
.antMatchers("/user").authenticated()
// .antMatchers("/upload").authenticated()
//放行swagger
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/v2/**", "/api/**").permitAll()
// 除上面外的所有请求全部不需要认证即可访问
.anyRequest().permitAll();
// 配置异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
http.logout().disable();
// 把jwtAuthenticationTokenFilter添加到SpringSecurity的过滤器链中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 允许跨域
http.cors();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
WebUtils
public class WebUtils {
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
*/
public static void renderString(HttpServletResponse response, String string) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void setDownLoadHeader(String filename, ServletContext context, HttpServletResponse response) throws UnsupportedEncodingException {
String mimeType = context.getMimeType(filename);//获取文件的mime类型
response.setHeader("content-type", mimeType);
String fname = URLEncoder.encode(filename, "UTF-8");
response.setHeader("Content-disposition", "attachment; filename=" + fname);
// response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// response.setCharacterEncoding("utf-8");
}
}
登录登出方法实现
LoginUser
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserid();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
UserInfoVo
@Data
@Accessors(chain = true)
@ApiModel(value = "UserInfo用户信息vo对象", description = "用户表")
public class UserInfoVo {
@ApiModelProperty("主键")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty("用户名")
@TableField("user_name")
private String userName;
@ApiModelProperty("登录号")
@TableField("userid")
private String userid;
@ApiModelProperty("昵称")
@TableField("nick_name")
private String nickName;
@ApiModelProperty("密码")
@TableField("pass")
private String pass;
@ApiModelProperty("用户类型:0代表普通用户,1代表管理员")
@TableField("type")
private String type;
@ApiModelProperty("账号状态(0正常 1停用)")
@TableField("status")
private String status;
@ApiModelProperty("邮箱")
@TableField("email")
private String email;
@ApiModelProperty("手机号")
@TableField("phonenumber")
private String phonenumber;
@ApiModelProperty("用户性别(0男,1女,2未知)")
@TableField("sex")
private String sex;
@ApiModelProperty("头像")
@TableField("avatar")
private String avatar;
}
UserLoginVo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginVo {
private String token;
private UserInfoVo userInfo;
}
Controller
@RestController
public class UserLoginController {
@Resource
private IUserService userService;
@ApiOperation(value = "登录")
@PostMapping("/login")
public ResponseResult login(@RequestBody User user) {
return userService.login(user);
}
@ApiOperation(value = "登出")
@PostMapping("/logout")
public ResponseResult logout() {
return userService.logout();
}
}
ServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private AuthenticationManager authenticationManager;
@Resource
private RedisCache redisCache;
@Override
public ResponseResult login(User user) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserid(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 判断是否认证通过
if (Objects.isNull(authenticate)) {
throw new SystemException(AppHttpCodeEnum.LOGIN_ERROR);
}
// 获取userid生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
// 将用户信息存入redis
redisCache.setCacheObject("loginUser:" + userId, loginUser);
// 将token和userinfo封装返回,将user转换为userinfoVo
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
UserLoginVo blogUserLoginVo = new UserLoginVo(jwt, userInfoVo);
return ResponseResult.okResult(blogUserLoginVo);
}
@Override
public ResponseResult logout() {
// 获取token解析出userid
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getUser().getId();
// 删除redis的用户信息
redisCache.deleteObject("loginUser:" + userId);
return ResponseResult.okResult();
}
}
UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据账号查询用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserid, username);
User user = userMapper.selectOne(queryWrapper);
//判断是否查到用户 如果没查到抛出异常
if (Objects.isNull(user)) {
throw new RuntimeException("用户不存在");
}
//返回用户信息
// TODO 查询权限信息封装
return new LoginUser(user);
}
}
登录校验过滤器
JwtAuthenticationTokenFilter
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取请求头中的token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
// 不传token不需要登录的接口直接放行
filterChain.doFilter(request, response);
return;
}
// 解析并获取userId
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
// token超时或非法,响应前端需要重新登录401
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(result));
return;
}
String userId = claims.getSubject();
// 利用userId在redis中查找用户信息
LoginUser loginUser = redisCache.getCacheObject("loginUser:" + userId);
//如果获取不到
if (Objects.isNull(loginUser)) {
//说明登录过期 提示重新登录
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(result));
return;
}
// 存入SecurityContextHolder
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 放行
filterChain.doFilter(request, response);
}
}
认证授权失败处理
AuthenticationEntryPointImpl
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
authException.printStackTrace();
ResponseResult result = null;
if (authException instanceof BadCredentialsException) {
result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(), authException.getMessage());
} else if (authException instanceof InsufficientAuthenticationException) {
result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
} else {
result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), "认证或授权失败");
}
// BadCredentialsException 用户名或密码错误
// InsufficientAuthenticationException 未登录/未携带token
// 响应方式
WebUtils.renderString(response, JSON.toJSONString(result));
}
}
AccessDeniedHandlerImpl
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
accessDeniedException.printStackTrace();
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);
// 响应方式
WebUtils.renderString(response, JSON.toJSONString(result));
}
}
统一异常处理
SystemException
public class SystemException extends RuntimeException {
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public SystemException(AppHttpCodeEnum httpCodeEnum) {
super(httpCodeEnum.getMsg());
this.code = httpCodeEnum.getCode();
this.msg = httpCodeEnum.getMsg();
}
}
GlobalExceptionHandler
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(SystemException.class)
public ResponseResult systemExceptionHandler(SystemException e) {
// 打印异常信息
log.error("出现异常! {}", e);
// 从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(e.getCode(), e.getMsg());
}
@ExceptionHandler(Exception.class)
public ResponseResult exceptionHandler(Exception e) {
// 打印异常信息
log.error("出现异常! {}", e);
// 从异常对象中获取提示信息封装返回
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());
}
}
SecurityUtils
public class SecurityUtils {
/**
* 获取用户
**/
public static LoginUser getLoginUser() {
return (LoginUser) getAuthentication().getPrincipal();
}
/**
* 获取Authentication
*/
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
public static Boolean isAdmin() {
Long id = getLoginUser().getUser().getId();
return id != null && 1L == id;
}
public static Long getUserId() {
return getLoginUser().getUser().getId();
}
}
MyMetaObjectHandler
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Long userId = null;
// try {
userId = SecurityUtils.getUserId();
// } catch (Exception e) {
// e.printStackTrace();
// userId = -1L;//表示是自己创建
// }
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("createBy", userId, metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", userId, metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName(" ", SecurityUtils.getUserId(), metaObject);
}
}
springsecurity文件结构
集成网关管理接口
代码生成文件、药品接口代码进行初步调试编写网关服务配置
application.yml
server:
port: 9888
spring:
application:
name: API-GATEWAY
cloud:
nacos:
discovery:
server-addr: xxxxxx:8848
# 将ephemeral修改为false,表示非临时实例
ephemeral: true
gateway:
routes:
# admin router
- id: admin_router
uri: lb://API-ADMIN
predicates:
- Path=/file/admin/upload,/file/admin,/file/admin/**,/file/admin/del/batch,/file/admin/page,/menu/admin,/menu/admin/**,/menu/admin/del/batch,/menu/page,/menu/admin/icons,/role/admin,/role/admin/**,/role/admin/del/batch,/role/admin/page,/role/admin/roleMenu/**,/user/admin/login,/user/admin/register,/user/admin/userid/**,/user/admin,/user/admin/**,/user/admin/del/batch,/user/admin/userName/**,/user/admin/password,/user/admin/page,/user/admin/export,/user/admin/import
# disease router
- id: disease_router
uri: lb://API-DISEASE
predicates:
- Path=/disease,/disease/**,/disease/del/batch,/disease/page
# formulation router
- id: formulation_router
uri: lb://API-FORMULATION
predicates:
- Path=/formulation,/formulation/**,/formulation/del/batch,/formulation/page
# medicine router
- id: medicine_router
uri: lb://API-MEDICINE
predicates:
- Path=/medicine,/medicine/**,/medicine/del/batch,/medicine/page
# user router
- id: user_router
uri: lb://API-USER
predicates:
- Path=/user/login,/user/logout,/user,/user/**,/user/del/batch,/user/page
#网关跨域配置
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"