Commit a5cb02dd authored by Fei 张飞's avatar Fei 张飞

初始化项目

parents
/.idea/
/target/
/yst-dch-score.iml
/interface/target/
/interface/interface.iml
/logs/
*.log
##针对mvn install 乱码
解决方式如下:
1.idea配置utf-8,父配置文件增加utf-8的配置,
2.打开File | Settings | Build, Execution, Deployment | Build Tools | Maven | Runner在VM Options中添加-Dfile.encoding=GBK,切记一定是GBK
##采用技术版本说明
1.JDK11+
2.maven3.6+
3.springboot2.3.1.RELEASE
4.spring-boot-starter-data-jpa2.3.1.RELEASE
5.querydsl4.3.1
##项目启动说明
1.依次mvn install core、interface
2.编译yst-dch mvn compile
3.确保mvn compile 来编译生成jpaDsl所需的Q开头类. 请采用jdk11 maven3.6+
4.直接启动YstLxyApplication即可.
##项目测试地址
http://localhost:8080/el-train/doc.html
1.需要登录
登录用户:user
登录密码:123456
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/
/.mvn/
/mvnw
/mvnw.cmd
<?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>
<artifactId>yst-dch-score</artifactId>
<groupId>com.elitesland</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>core</name>
<description>Elitesland YST core framework</description>
<properties>
<java.version>11</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<!--华为云仓库-->
<repository>
<id>huaweicloud</id>
<name>huaweicloud-maven</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
</repository>
<!--阿里云仓库-->
<repository>
<id>aliyun</id>
<name>aliyun-maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<!-- 阿里云spring仓库 -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://maven.aliyun.com/repository/spring</url>
</repository>
<!-- 中央仓库 -->
<repository>
<id>central</id>
<name>maven-central</name>
<url>http://central.maven.org/maven2/</url>
</repository>
</repositories>
</project>
#! /bin/shell
# Copyright 2019-2029 elitesland(https://elitesland.com)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#======================================================================
# 项目重启shell脚本
# 先调用shutdown.sh停服
# 然后调用startup.sh启动服务
#
# author: Mir
# date: 2020-2-18
#======================================================================
# 项目名称
APPLICATION="yst-cms-svr"
# 停服
echo stop ${APPLICATION} Application...
sh shutdown.sh
# 启动服务
echo start ${APPLICATION} Application...
sh startup.sh
\ No newline at end of file
#! /bin/shell
# Copyright 2019-2029 elitesland(https://elitesland.com)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#======================================================================
# 项目停服shell脚本
# 通过项目名称查找到PID
# 然后kill -9 pid
#
# author: Mir
# date: 2020-2-18
#======================================================================
# 项目名称
APPLICATION="yst-cms-svr"
# 项目启动jar包名称
APPLICATION_JAR="${APPLICATION}.jar"
PID=$(ps -ef | grep ${APPLICATION_JAR} | grep -v grep | awk '{ print $2 }')
if [ -z "$PID" ]
then
echo ${APPLICATION} is already stopped
else
echo kill ${PID}
kill -9 ${PID}
echo ${APPLICATION} stopped successfully
fi
\ No newline at end of file
rem Copyright 2019-2029 elitesland(https://elitesland.com)
rem
rem Licensed under the Apache License, Version 2.0 (the "License");
rem you may not use this file except in compliance with the License.
rem You may obtain a copy of the License at
rem
rem http://www.apache.org/licenses/LICENSE-2.0
rem
rem Unless required by applicable law or agreed to in writing, software
rem distributed under the License is distributed on an "AS IS" BASIS,
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.
rem ======================================================================
rem windows启动脚本
rem
rem author: Mir
rem date: 2020-2-18
rem ======================================================================
rem startup jar
java -jar ../lib/yst-cms-svr.jar --spring.config.location=../config/ --elbootplus.isEnableAnsi=false
pause
\ No newline at end of file
#! /bin/shell
# Copyright 2019-2029 elitesland(https://elitesland.com)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#======================================================================
# 项目启动shell脚本
# config目录: 配置文件目录
# logs目录: 项目运行日志目录
# logs/spring-boot-plus_startup.log: 记录启动日志
# logs/back目录: 项目运行日志备份目录
# nohup后台运行
#
# author: Mir
# date: 2020-2-18
#======================================================================
# 项目名称
APPLICATION="yst-cms-svr"
# 项目启动jar包名称
APPLICATION_JAR="${APPLICATION}.jar"
# bin目录绝对路径
BIN_PATH=$(cd $(dirname "$0"); pwd)
# 进入bin目录
cd $(dirname "$0")
# 返回到上一级项目根目录路径
cd ..
# 打印项目根目录绝对路径
# `pwd` 执行系统命令并获得结果
BASE_PATH=$(pwd)
# 外部配置文件绝对目录,如果是目录需要/结尾,也可以直接指定文件
# 如果指定的是目录,spring则会读取目录中的所有配置文件
CONFIG_DIR=${BASE_PATH}"/config/"
# 项目日志输出绝对路径
LOG_DIR=${BASE_PATH}"/logs"
LOG_FILE="${APPLICATION}.log"
LOG_PATH="${LOG_DIR}/${LOG_FILE}"
# 日志备份目录
LOG_BACK_DIR="${LOG_DIR}/back/"
# 项目启动日志输出绝对路径
LOG_STARTUP_PATH="${LOG_DIR}/${APPLICATION}_startup.log"
# 当前时间
NOW=$(date --date='0 days ago' "+%Y-%m-%d-%H-%M-%S")
NOW_PRETTY=$(date --date='0 days ago' "+%Y-%m-%d %H:%M:%S")
# 启动日志
STARTUP_LOG="================================================ ${NOW_PRETTY} ================================================\n"
# 如果logs文件夹不存在,则创建文件夹
if [ ! -d "${LOG_DIR}" ]; then
mkdir "${LOG_DIR}"
fi
# 如果logs/back文件夹不存在,则创建文件夹
if [ ! -d "${LOG_BACK_DIR}" ]; then
mkdir "${LOG_BACK_DIR}"
fi
# 如果项目运行日志存在,则重命名备份
if [ -f "${LOG_PATH}" ]; then
mv ${LOG_PATH} "${LOG_BACK_DIR}/${APPLICATION}_back_${NOW}.log"
fi
# 创建新的项目运行日志
echo "" > ${LOG_PATH}
# 如果项目启动日志不存在,则创建,否则追加
echo ${STARTUP_LOG} >> ${LOG_STARTUP_PATH}
#==========================================================================================
# JVM Configuration
# -Xmx1g:设置JVM最大可用内存为1G。
# -Xms1g:设置JVM初始内存41。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存
# -Xmn512m:设置年轻代大小为512m。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。
# 持久代一般固定大小为64m,所以增大年轻代,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
# -XX:MetaspaceSize=64m:存储class的内存大小,该值越大触发Metaspace GC的时机就越晚
# -XX:MaxMetaspaceSize=320m:限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序
# -XX:-OmitStackTraceInFastThrow:解决重复异常不打印堆栈信息问题
#==========================================================================================
JAVA_OPT="-server -Xms1g -Xmx1g -Xmn512m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow"
#=======================================================
# 将命令启动相关日志追加到日志文件
#=======================================================
# 输出项目名称
STARTUP_LOG="${STARTUP_LOG}application name: ${APPLICATION}\n"
# 输出jar包名称
STARTUP_LOG="${STARTUP_LOG}application jar name: ${APPLICATION_JAR}\n"
# 输出项目bin路径
STARTUP_LOG="${STARTUP_LOG}application bin path: ${BIN_PATH}\n"
# 输出项目根目录
STARTUP_LOG="${STARTUP_LOG}application root path: ${BASE_PATH}\n"
# 打印日志路径
STARTUP_LOG="${STARTUP_LOG}application log path: ${LOG_PATH}\n"
# 打印JVM配置
STARTUP_LOG="${STARTUP_LOG}application JAVA_OPT : ${JAVA_OPT}\n"
# 打印启动命令
STARTUP_LOG="${STARTUP_LOG}application background startup command: nohup java ${JAVA_OPT} -jar ${BASE_PATH}/lib/${APPLICATION_JAR} --spring.config.location=${CONFIG_DIR} --logging.config=${CONFIG_DIR}logback.xml > ${LOG_PATH} 2>&1 &\n"
#======================================================================
# 执行启动命令:后台启动项目,并将日志输出到项目根目录下的logs文件夹下
#======================================================================
nohup java ${JAVA_OPT} -jar ${BASE_PATH}/lib/${APPLICATION_JAR} --spring.config.location=${CONFIG_DIR} --logging.config=${CONFIG_DIR}logback.xml > ${LOG_PATH} 2>&1 &
# 进程ID
PID=$(ps -ef | grep ${APPLICATION_JAR} | grep -v grep | awk '{ print $2 }')
STARTUP_LOG="${STARTUP_LOG}application pid: ${PID}\n"
# 启动日志追加到启动日志文件中
echo -e ${STARTUP_LOG} >> ${LOG_STARTUP_PATH}
# 打印启动日志
echo -e ${STARTUP_LOG}
# 打印项目日志
tail -f ${LOG_PATH}
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019-2029 elitesland(https://elitesland.com)
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<assembly>
<!-- 可自定义,这里指定的是项目环境 -->
<!-- spring-boot-plus-server-1.0.1-RELEASE-local.tar.gz -->
<!-- <id>${project.version}-${profileActive}</id> -->
<id>assembly</id>
<!-- 打包的类型,如果有N个,将会打N个类型的包 -->
<formats>
<format>tar.gz</format>
<!-- <format>zip</format> -->
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!--
0755->即用户具有读/写/执行权限,组用户和其它用户具有读写权限;
0644->即用户具有读写权限,组用户和其它用户具有只读权限;
-->
<!-- 将src/bin目录下的所有文件输出到打包后的bin目录中 -->
<fileSet>
<directory>${basedir}/src/bin</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
<includes>
<include>**.sh</include>
<include>**.bat</include>
</includes>
</fileSet>
<!-- 指定输出target/classes中的配置文件到config目录中 -->
<fileSet>
<directory>${basedir}/target/classes/config</directory>
<outputDirectory>config</outputDirectory>
<fileMode>0644</fileMode>
<includes>
<include>*</include>
</includes>
</fileSet>
<!-- 将项目启动jar打包到lib目录中 -->
<fileSet>
<directory>${basedir}/target</directory>
<outputDirectory>lib</outputDirectory>
<fileMode>0755</fileMode>
<includes>
<include>${project.build.finalName}.jar</include>
</includes>
</fileSet>
<!-- 指定日志目录 -->
<fileSet>
<directory>${basedir}/src/logs</directory>
<outputDirectory>logs</outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
<!-- 包含根目录下的文件 -->
<fileSet>
<directory>${basedir}</directory>
<includes>
<include>NOTICE</include>
<include>LICENSE</include>
</includes>
</fileSet>
</fileSets>
</assembly>
\ No newline at end of file
package com.elitesland.core.base;
/**
* <p>
* REST API 响应码
* </p>
*
* @author Mir
* @date 2018-11-08
*/
public enum ApiCode {
/**
* Http response entity status code, corresponding to HTTP Status Code
*/
SUCCESS(200, "操作成功"),
UNAUTHORIZED(401, "请先登录"),
NOT_PERMISSION(403, "没有权限"),
NOT_FOUND(404, "你请求的资源不存在"),
FAIL(500, "操作失败"),
LOGIN_EXCEPTION(4000, "登录失败"),
SYSTEM_EXCEPTION(5000, "系统异常!"),
PARAMETER_EXCEPTION(5001, "请求参数校验异常"),
PARAMETER_PARSE_EXCEPTION(5002, "请求参数解析异常"),
HTTP_MEDIA_TYPE_EXCEPTION(5003, "HTTP Media 类型异常"),
SPRING_BOOT_PLUS_EXCEPTION(5100, "系统处理异常"),
BUSINESS_EXCEPTION(5101, "业务处理异常"),
DAO_EXCEPTION(5102, "数据库处理异常"),
VERIFICATION_CODE_EXCEPTION(5103, "验证码校验异常"),
AUTHENTICATION_EXCEPTION(5104, "登录授权异常"),
UNAUTHENTICATED_EXCEPTION(5105, "身份认证不正确"),
UNAUTHORIZED_EXCEPTION(5106, "没有访问权限"),
UNAUTHORIZED_ANONYMOUS(5107, "匿名用户,未授权访问"),
NO_USER_FOUND_EXCEPTION(5108, "用户信息未找到,系统异常")
;
private final int code;
private final String msg;
ApiCode(final int code, final String msg) {
this.code = code;
this.msg = msg;
}
public static ApiCode getApiCode(int code) {
ApiCode[] ecs = ApiCode.values();
for (ApiCode ec : ecs) {
if (ec.getCode() == code) {
return ec;
}
}
return SUCCESS;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
package com.elitesland.core.base;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.val;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* REST API 返回结果
* </p>
*
* @author Mir
* @date 2018-11-08
*/
@Data
@Accessors(chain = true)
@Builder
@AllArgsConstructor
public class ApiResult<T> implements Serializable {
private static final long serialVersionUID = 7722914707623525357L;
/**
* 响应码
*/
private int code;
/**
* 响应消息
*/
private String msg;
/**
* 是否成功
*/
private boolean success;
/**
* 响应数据
*/
private T data;
/**
* 响应时间
*/
// @JSONField(format = "yyyy-MM-ddTHH:mm:ss")
// @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime time = LocalDateTime.now();
public ApiResult() {
}
public static <T> ApiResult<T> result(boolean flag) {
if (flag) {
return ok();
}
return fail();
}
public static <T> ApiResult<T> result(ApiCode apiCode) {
return result(apiCode, null);
}
public static <T> ApiResult<T> result(ApiCode apiCode, T data) {
return result(apiCode, null, data);
}
public static <T> ApiResult<T> result(ApiCode apiCode, String msg, T data) {
boolean success = false;
if (apiCode.getCode() == ApiCode.SUCCESS.getCode()) {
success = true;
}
String message = apiCode.getMsg();
if (StrUtil.isNotBlank(msg)) {
message = msg;
}
return ApiResult.<T>builder()
.code(apiCode.getCode())
.msg(message)
.data(data)
.success(success)
.time(LocalDateTime.now())
.build();
}
public static <T> ApiResult<T> ok() {
return ok(null);
}
public static <T> ApiResult<T> ok(T data) {
return result(ApiCode.SUCCESS, data);
}
public static <T> ApiResult<T> ok(T data, String msg) {
return result(ApiCode.SUCCESS, msg, data);
}
public static <T> ApiResult<Map<String, T>> okMap(String key, T value) {
Map<String, T> map = new HashMap<>(1);
map.put(key, value);
return ok(map);
}
public static Map<String, Object> toMap(ApiResult<?> result) {
val map = new HashMap<String, Object>();
map.put("code", result.code);
map.put("msg", result.msg);
map.put("data", result.data);
map.put("success", result.success);
map.put("time", result.time);
return map;
}
public static <T> ApiResult<T> fail(ApiCode apiCode) {
return result(apiCode, null);
}
public static <T> ApiResult<T> fail(String msg) {
return result(ApiCode.FAIL, msg, null);
}
public static <T> ApiResult<T> fail(ApiCode apiCode, T data) {
if (ApiCode.SUCCESS == apiCode) {
throw new RuntimeException("失败结果状态码不能为" + ApiCode.SUCCESS.getCode());
}
return result(apiCode, data);
}
public static <T> ApiResult<Map<String, T>> fail(String key, T value) {
Map<String, T> map = new HashMap<>(1);
map.put(key, value);
return result(ApiCode.FAIL, map);
}
public static <T> ApiResult<T> fail() {
return fail(ApiCode.FAIL);
}
}
\ No newline at end of file
package com.elitesland.core.base;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
/**
* <pre>
* JPA数据实体类的基础类,用于唯一ID生成策略绑定
* 审计字段、版本和逻辑删除标记,
* 未来扩展租户ID信息
* </pre>
*
* @author Moz
* @date 3/17/2020
*/
@Data
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class BaseModel {
@Id
@GenericGenerator(name = "el-id", strategy = "com.elitesland.core.util.IdGenerator")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "el-id")
@JsonSerialize(using = ToStringSerializer.class)
@ApiModelProperty("账号唯一ID")
@Column(columnDefinition = "bigint default 0 comment '唯一编号ID'")
private Long id;
@ApiModelProperty(value = "租户ID")
@Column(columnDefinition = "bigint default 0 comment '租户ID'")
@JsonSerialize(using = ToStringSerializer.class)
Long tenantId;
@ApiModelProperty(value = "备注")
@Column(columnDefinition = "varchar(255) default '' comment '备注'")
private String remark;
@CreatedBy
@Column(columnDefinition = "bigint default 0 comment '记录创建者ID'", updatable = false)
@JsonSerialize(using = ToStringSerializer.class)
private Long createUserId;
@CreatedDate
@Column(columnDefinition = "timestamp default NULL comment '记录创建时间'", updatable = false)
private LocalDateTime createTime = LocalDateTime.now();
@LastModifiedBy
@Column(columnDefinition = "bigint default 0 comment '记录最后更新者ID'")
@JsonSerialize(using = ToStringSerializer.class)
private Long modifyUserId;
@LastModifiedDate
@Column(columnDefinition = "timestamp default NULL comment '记录最后更新时间'")
private LocalDateTime modifyTime = LocalDateTime.now();
@ApiModelProperty(value = "逻辑删除,0:未删除,1:已删除")
@Column(columnDefinition = "int default 0 comment '逻辑删除,0:未删除,1:已删除'")
private Integer deleteFlag;
@ApiModelProperty(value = "版本")
@Column(columnDefinition = "int default 0 comment '版本信息,前端不用传'")
private Integer auditDataVersion;
}
package com.elitesland.core.base;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
/**
* @author Mir
* @date 2018-11-08
*/
@ApiModel("分页")
@Data
@Accessors(chain = true)
@Builder
public class PagingVO<T> implements Serializable {
private static final long serialVersionUID = -1683800405530086022L;
@ApiModelProperty("总行数")
@JsonProperty("total")
private Long total = 0L;
@ApiModelProperty("数据列表")
@JsonProperty("records")
private List<T> records = Collections.emptyList();
public PagingVO() {
}
public PagingVO(long total, List<T> records) {
this.total = total;
this.records = records;
}
public PagingVO(IPage<T> page) {
this.total = page.getTotal();
this.records = page.getRecords();
}
@Override
public String toString() {
return "Paging{" +
"total=" + total +
", records=" + records +
'}';
}
}
package com.elitesland.core.base.param;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.elitesland.core.base.ApiCode;
import com.elitesland.core.exception.BusinessException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.val;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 可排序查询参数对象
*
* @author Mir
* @date 2019-08-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("可排序查询参数对象")
public abstract class AbstractOrderQueryParam extends QueryParam {
private static final long serialVersionUID = 57714391204790143L;
@ApiModelProperty(value = "排序")
private List<OrderItem> orders;
public List<OrderItem> getOrders() {
return orders;
}
public void setOrders(List<OrderItem> orders) {
this.orders = orders;
}
public void defaultOrder(OrderItem orderItem) {
this.defaultOrders(Collections.singletonList(orderItem));
}
public void defaultOrders(List<OrderItem> orderItems) {
if (CollectionUtils.isEmpty(orderItems)) {
return;
}
this.orders = orderItems;
}
@JsonIgnore
public PageRequest getPageRequest() {
val orderBys = CollectionUtils.isEmpty(orders) ? new ArrayList<Sort.Order>() :
orders.stream().map(o -> new Sort.Order(
o.isAsc() ? Sort.Direction.ASC : Sort.Direction.DESC,
o.getColumn()
)).collect(Collectors.toList());
return PageRequest.of(getCurrent(), getSize(), Sort.by(orderBys));
}
/**
* 单表查询,排序方法
* @param query
* @param entityPathBase 实体对象
*/
@JsonIgnore
public void fillOrders(JPAQuery<?> query, EntityPathBase entityPathBase) {
val pageRequest = getPageRequest();
pageRequest.getSort().forEach(s -> {
val orderbyExpr = new PathBuilder(entityPathBase.getClass(),
entityPathBase.getMetadata().getElement().toString());
query.orderBy(new OrderSpecifier(s.isAscending() ?
Order.ASC : Order.DESC, orderbyExpr.get(s.getProperty())));
});
}
/**
* 多表关联查询,排序方法。要求:排序字段格式需为 “实体对象名称.字段名称”
* @param query
*/
@JsonIgnore
public void fillOrders(JPAQuery<?> query) {
val pageRequest = getPageRequest();
pageRequest.getSort().forEach(s -> {
val sects = s.getProperty().split("\\.");
if (sects.length > 1) {
val orderbyExpr = new PathBuilder<>(Object.class, sects[0]);
query.orderBy(new OrderSpecifier(s.isAscending() ?
Order.ASC : Order.DESC, orderbyExpr.get(sects[1])));
} else {
throw new BusinessException(ApiCode.PARAMETER_PARSE_EXCEPTION, "排序字段需要指定所属hql的实体对象,对象名称.字段名称");
}
});
}
@JsonIgnore
public void setPaging(JPAQuery<?> query) {
query.offset(getCurrent() * getSize());
query.limit(getSize());
}
}
package com.elitesland.core.base.param;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* @author Mir
* @date 2018-11-08
*/
@Data
@ApiModel("ID参数")
public class IdParam implements Serializable {
private static final long serialVersionUID = -5353973980674510450L;
@NotBlank(message = "ID不能为空")
private String id;
}
package com.elitesland.core.base.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
/**
* @author Mir
* @date 2018-11-08
*/
@ApiModel("主键状态VO")
public class IdStatusParam implements Serializable {
private static final long serialVersionUID = -7581307955242965701L;
@ApiModelProperty("主键ID")
private String id;
@ApiModelProperty("状态,1:启用 0:禁用")
private Integer status;
}
package com.elitesland.core.base.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
/**
* 名称参数
*
* @author Mir
* @date 2018-11-08
*/
@ApiModel("名称参数")
public class NameParam implements Serializable {
private static final long serialVersionUID = -3710501706034574149L;
@ApiModelProperty("名称")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "NameParam{" +
"name='" + name + '\'' +
'}';
}
}
package com.elitesland.core.base.param;
import com.elitesland.core.constant.CommonConstant;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 查询参数
*
* @author Mir
* @date 2018-11-08
*/
@Data
@ApiModel("查询参数对象")
public abstract class QueryParam implements Serializable {
private static final long serialVersionUID = -3263921252635611410L;
@ApiModelProperty(value = "页码,默认为1", example = "0")
private Integer current = CommonConstant.DEFAULT_PAGE_INDEX;
@ApiModelProperty(value = "页大小,默认为10", example = "10")
private Integer size = CommonConstant.DEFAULT_PAGE_SIZE;
@ApiModelProperty(value = "搜索字符串", example = "")
private String keyword;
public Integer getCurrent() {
return current > 0 ? current - 1 : 0;
}
public Integer getSize() {
return size;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public void setCurrent(Integer current) {
if (current == null || current < 0) {
this.current = CommonConstant.DEFAULT_PAGE_INDEX;
} else {
this.current = current;
}
}
public void setSize(Integer size) {
if (size == null || size <= 0) {
this.size = CommonConstant.DEFAULT_PAGE_SIZE;
} else {
this.size = size;
}
}
}
package com.elitesland.core.config;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
/**
* <pre>
*
* </pre>
* @author Mir
* @date 2020-06-20
*/
@Configuration
public class QueryDSLConfig {
@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager em) {
return new JPAQueryFactory(em);
}
}
package com.elitesland.core.config.json;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author Mir
*/
@Configuration
public class LocalDateTimeSerializerConfig {
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
private String pattern;
private LocalDateTimeSerializer localDateTimeSerializer;
private LocalDateTimeDeserializer localDateTimeDeserializer;
@Autowired
public void setLocalDateTimeSerializer(LocalDateTimeSerializer localDateTimeSerializer) {
this.localDateTimeSerializer = localDateTimeSerializer;
}
@Autowired
public void setLocalDateTimeDeserializer(LocalDateTimeDeserializer localDateTimeDeserializer) {
this.localDateTimeDeserializer = localDateTimeDeserializer;
}
@Bean
public LocalDateTimeSerializer localDateTimeSerializer() {
return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));
}
@Bean
public LocalDateTimeDeserializer localDateTimeDeserializer() {
return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(pattern));
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> {
jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, localDateTimeSerializer);
jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer);
// jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
// jacksonObjectMapperBuilder.serializerByType(Long.TYPE, ToStringSerializer.instance);
};
}
}
package com.elitesland.core.constant;
public interface CacheKey {
}
package com.elitesland.core.constant;
/**
* 常量
*
* @author Mir
* @date 2018-11-08
*/
public interface CommonConstant {
/**
* 默认页码为1
*/
Integer DEFAULT_PAGE_INDEX = 0;
/**
* 默认页大小为10
*/
Integer DEFAULT_PAGE_SIZE = 10;
/**
* 登录用户
*/
String LOGIN_SYS_USER = "loginSysUser";
/**
* 登录token
*/
String JWT_DEFAULT_TOKEN_NAME = "token";
/**
* JWT用户名
*/
String JWT_USERNAME = "username";
/**
* JWT刷新新token响应状态码
*/
int JWT_REFRESH_TOKEN_CODE = 460;
/**
* JWT刷新新token响应状态码,
* Redis中不存在,但jwt未过期,不生成新的token,返回361状态码
*/
int JWT_INVALID_TOKEN_CODE = 461;
/**
* JWT Token默认密钥
*/
String JWT_DEFAULT_SECRET = "666666";
/**
* JWT 默认过期时间,3600L,单位秒
*/
Long JWT_DEFAULT_EXPIRE_SECOND = 3600L;
/**
* 初始密码
*/
String INIT_PWD = "123456";
/**
* 默认头像
*/
String DEFAULT_HEAD_URL = "";
/**
* 管理员角色名称
*/
String ADMIN_ROLE_NAME = "管理员";
String ADMIN_LOGIN = "adminLogin";
/**
* 验证码token
*/
String VERIFY_TOKEN = "verifyToken";
/**
* 图片
*/
String IMAGE = "image";
/**
* JPEG
*/
String JPEG = "JPEG";
/**
* base64前缀
*/
String BASE64_PREFIX = "data:image/png;base64,";
String AUDIT_CREATOR = "creator";
String AUDIT_CREATED = "created";
String AUDIT_UPDATER = "updater";
String AUDIT_UPDATED = "updated";
}
package com.elitesland.core.constant;
/**
* <p>
* redis key 常量
* </p>
*
* @author Mir
* @date 2019-05-23
**/
public interface CommonRedisKey {
/**
* 登录用户token信息key
*/
String LOGIN_TOKEN = "login:token:%s";
/**
* 登录用户信息key
*/
String LOGIN_USER = "login:user:%s";
/**
* 登录用户盐值信息key
*/
String LOGIN_SALT = "login:salt:%s";
/**
* 登录用户username token
*/
String LOGIN_USER_TOKEN = "login:user:token:%s:%s";
/**
* 验证码
*/
String VERIFY_CODE = "verify.code:%s";
}
package com.elitesland.core.constant;
/**
* <p>
* 日期格式常量
* </p>
*
* @author Mir
* @date 2018-11-08
*/
public interface DatePattern {
/**
* 年-月-日
*/
String yyyy_MM_dd = "yyyy-MM-dd";
/**
* 年-月-日 时:分
*/
String yyyy_MM_dd_HH_mm = "yyyy-MM-dd HH:mm";
/**
* 年-月-日 时:分:秒
*/
String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
/**
* 年-月-日 时:分:秒:毫秒
*/
String yyyy_MM_dd_HH_mm_ss_S = "yyyy-MM-dd HH:mm:ss.S";
/**
* 时:分
*/
String HH_mm = "HH:mm";
/**
* 时:分:秒
*/
String HH_mm_ss = "HH:mm:ss";
/**
* 时:分:秒:毫秒
*/
String HH_mm_ss_S = "HH:mm:ss:S";
}
package com.elitesland.core.constant;
/**
* @author Mir
* @date 2020/03/18
*/
public interface HttpConstant {
String CHARSET_ENCODING_UTF8 = "UTF-8";
String CONTENT_TYPE_JSON = "application/json";
}
package com.elitesland.core.demo.service;
import org.springframework.stereotype.Service;
/**
* <p>
* 功能说明
* </p>
*
* @author Mir
* @date 2020/6/28
*/
@Service
public class DemoService {
public Integer add(int a, int b){
return a + b;
}
}
package com.elitesland.core.exception;
import org.springframework.security.core.AuthenticationException;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/24
*/
public class BadCaptchaException extends AuthenticationException {
private static final long serialVersionUID = 8337958032785176060L;
public BadCaptchaException(String msg) {
super(msg);
}
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.core.exception;
/**
* <pre>
* 统一关于错误配置信息 异常
* </pre>
*
* @author Mir
* @date 2020-06-20
*/
public class BadConfigurationException extends RuntimeException {
private static final long serialVersionUID = -5628549105671311130L;
/**
* Constructs a new runtime exception with {@code null} as its
* detail message. The cause is not initialized, and may subsequently be
* initialized by a call to {@link #initCause}.
*/
public BadConfigurationException() {
super();
}
/**
* Constructs a new runtime exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public BadConfigurationException(String message) {
super(message);
}
/**
* Constructs a new runtime exception with the specified detail message and
* cause. <p>Note that the detail message associated with
* {@code cause} is <i>not</i> automatically incorporated in
* this runtime exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public BadConfigurationException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new runtime exception with the specified cause and a
* detail message of {@code (cause==null ? null : cause.toString())}
* (which typically contains the class and detail message of
* {@code cause}). This constructor is useful for runtime exceptions
* that are little more than wrappers for other throwables.
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public BadConfigurationException(Throwable cause) {
super(cause);
}
/**
* Constructs a new runtime exception with the specified detail
* message, cause, suppression enabled or disabled, and writable
* stack trace enabled or disabled.
*
* @param message the detail message.
* @param cause the cause. (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @param enableSuppression whether or not suppression is enabled
* or disabled
* @param writableStackTrace whether or not the stack trace should
* be writable
* @since 1.7
*/
protected BadConfigurationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
package com.elitesland.core.exception;
import org.springframework.security.core.AuthenticationException;
/**
* <p>
* JWT Token验证失败的异常
* </p>
*
* @author Mir
* @date 2020/7/4
*/
public class BadJwtTokenException extends AuthenticationException {
private static final long serialVersionUID = 7542672984694936118L;
public BadJwtTokenException(String msg) {
super(msg);
}
}
package com.elitesland.core.exception;
import com.elitesland.core.base.ApiCode;
import lombok.Getter;
import java.util.function.Supplier;
/**
* <pre>
* 业务异常
* </pre>
*
* @author Mir
* @date 2020/6/22
*/
@Getter
public class BusinessException extends RuntimeException implements Supplier<BusinessException> {
private static final long serialVersionUID = -2205002357611194846L;
private ApiCode code;
public BusinessException() {
super();
}
public BusinessException(String message) {
super(message);
}
public BusinessException(ApiCode code, String message) {
super(message);
this.code = code;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public BusinessException(Throwable cause) {
super(cause);
}
public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
@Override
public BusinessException get() {
return this;
}
}
package com.elitesland.core.exception;
import org.springframework.security.core.AuthenticationException;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/24
*/
public class CaptchaExpiredException extends AuthenticationException {
private static final long serialVersionUID = -2209433381756411634L;
public CaptchaExpiredException(String msg) {
super(msg);
}
}
package com.elitesland.core.exception.handler;
import lombok.extern.slf4j.Slf4j;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/22
*/
@Slf4j
//@ControllerAdvice
public class GlobalExceptionHandler {
// @ExceptionHandler(BadRequestException.class)
// public ApiResult<Object> handleBadRequestException(BadRequestException e) {
// log.error(ThrowableUtil.getStackTrace(e));
// return ApiResult.fail(ApiCode.VERIFICATION_CODE_EXCEPTION, ThrowableUtil.getStackTrace(e));
// }
// @ExceptionHandler(BusinessException.class)
// @ResponseBody
// public ApiResult<Object> handleBusinessException(BusinessException e) {
// log.error(ThrowableUtil.getStackTrace(e));
// return ApiResult.fail(e.getCode(), e.getMessage());
// }
// @ExceptionHandler(BadJwtTokenException.class)
// public ApiResult<Object> handleAuthenticationException(AuthenticationException e) {
// log.error(ThrowableUtil.getStackTrace(e));
// return ApiResult.fail(ApiCode.VERIFICATION_CODE_EXCEPTION, ThrowableUtil.getStackTrace(e));
// }
}
package com.elitesland.core.exception.rest;
import com.elitesland.core.base.ApiCode;
import com.elitesland.core.base.ApiResult;
import com.elitesland.core.exception.BusinessException;
import com.elitesland.core.vo.RequestBindingErrorVO;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/24
*/
@RestController
@Slf4j
public class FilterErrorController extends BasicErrorController {
public FilterErrorController() {
super(new DefaultErrorAttributes(), new ErrorProperties());
}
@Override
@RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
var body = getErrorAttributes(request, ErrorAttributeOptions.of(
ErrorAttributeOptions.Include.MESSAGE,
ErrorAttributeOptions.Include.BINDING_ERRORS,
ErrorAttributeOptions.Include.STACK_TRACE,
ErrorAttributeOptions.Include.EXCEPTION));
// val status = getStatus(request);
log.error(body.toString());
val r = ApiResult.fail(ApiCode.FAIL, body.get("message"));
body = ApiResult.toMap(r);
val o = request.getAttribute("org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR");
if (o instanceof BusinessException) {
val e = (BusinessException) o;
val result = ApiResult.fail(e.getCode() == null ? ApiCode.FAIL : e.getCode(), e.getMessage());
body = ApiResult.toMap(result);
}
if (o instanceof MethodArgumentNotValidException) {
List<RequestBindingErrorVO> errors = ((MethodArgumentNotValidException) o).getBindingResult().getFieldErrors().stream()
.map(e -> {
val vo = new RequestBindingErrorVO();
vo.setField(e.getField()).setMsg(e.getDefaultMessage());
return vo;
}).collect(Collectors.toList());
val result = ApiResult.fail(ApiCode.PARAMETER_EXCEPTION, errors);
body = ApiResult.toMap(result);
}
return ResponseEntity.of(Optional.of(body));
}
@Override
public String getErrorPath() {
return "error/error";
}
}
package com.elitesland.core.micro;
import java.sql.Timestamp;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Clock Pool
* <p>
* 利用ScheduledExecutorService实现高并发场景下System.currentTimeMillis()的性能问题的优化.
*
* @author Mir
* @date 3/17/2020
*
* https://github.com/yu120/neural
*/
public enum ClockPool {
// ====
INSTANCE(1);
private final long period;
private final AtomicLong nowTime;
private boolean started = false;
private ScheduledExecutorService executorService;
ClockPool(long period) {
this.period = period;
this.nowTime = new AtomicLong(System.currentTimeMillis());
}
/**
* The initialize scheduled executor service
*/
public void initialize() {
if (started) {
return;
}
this.executorService = new ScheduledThreadPoolExecutor(1, r -> {
Thread thread = new Thread(r, "system-clock");
thread.setDaemon(true);
return thread;
});
executorService.scheduleAtFixedRate(() -> nowTime.set(System.currentTimeMillis()),
this.period, this.period, TimeUnit.MILLISECONDS);
Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
started = true;
}
/**
* The get current time milliseconds
*
* @return long time
*/
public long currentTimeMillis() {
return started ? nowTime.get() : System.currentTimeMillis();
}
/**
* The get string current time
*
* @return string time
*/
public String currentTime() {
return new Timestamp(currentTimeMillis()).toString();
}
/**
* The destroy of executor service
*/
public void destroy() {
if (executorService != null) {
executorService.shutdown();
}
}
}
package com.elitesland.core.micro;
import java.net.InetAddress;
import java.util.concurrent.ThreadLocalRandom;
/**
* 基于Twitter的Snowflake算法实现分布式高效有序ID生产黑科技(sequence)——升级版Snowflake
*
* <br>
* SnowFlake的结构如下(每部分用-分开):<br>
* <br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* <br>
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
* <br>
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下START_TIME属性)。
* 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* <br>
* 10位的数据机器位,可以部署在1024个节点,包括5位dataCenterId和5位workerId<br>
* <br>
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
* <br>
* <br>
* 加起来刚好64位,为一个Long型。<br>
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),
* 并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
* <p>
* <p>
* 特性:
* 1.支持自定义允许时间回拨的范围<p>
* 2.解决跨毫秒起始值每次为0开始的情况(避免末尾必定为偶数,而不便于取余使用问题)<p>
* 3.解决高并发场景中获取时间戳性能问题<p>
* 4.支撑根据IP末尾数据作为workerId
* 5.时间回拨方案思考:1024个节点中分配10个点作为时间回拨序号(连续10次时间回拨的概率较小)
*
* @author Mir
* @date 3/17/2020
*
* https://github.com/yu120/neural
*/
public class Snowflake {
/**
* 起始时间戳
**/
private final static long START_TIME = 1519740777809L;
/**
* dataCenterId占用的位数:2
**/
private final static long DATA_CENTER_ID_BITS = 2L;
/**
* workerId占用的位数:8
**/
private final static long WORKER_ID_BITS = 8L;
/**
* 序列号占用的位数:12(表示只允许workId的范围为:0-4095)
**/
private final static long SEQUENCE_BITS = 12L;
/**
* workerId可以使用范围:0-255
**/
private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
/**
* dataCenterId可以使用范围:0-3
**/
private final static long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
private final static long WORKER_ID_SHIFT = SEQUENCE_BITS;
private final static long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
private final static long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
/**
* 用mask防止溢出:位与运算保证计算的结果范围始终是 0-4095
**/
private final static long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
private final long workerId;
private final long dataCenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
private static byte LAST_IP = 0;
private final boolean clock;
private final long timeOffset;
private final boolean randomSequence;
private final ThreadLocalRandom tlr = ThreadLocalRandom.current();
public Snowflake(long dataCenterId) {
this(dataCenterId, 0x000000FF & getLastIPAddress(), false, 5L, false);
}
public Snowflake(long dataCenterId, boolean clock, boolean randomSequence) {
this(dataCenterId, 0x000000FF & getLastIPAddress(), clock, 5L, randomSequence);
}
/**
* 基于Snowflake创建分布式ID生成器
*
* @param dataCenterId 数据中心ID,数据范围为0~255
* @param workerId 工作机器ID,数据范围为0~3
* @param clock true表示解决高并发下获取时间戳的性能问题
* @param timeOffset 允许时间回拨的毫秒量,建议5ms
* @param randomSequence true表示使用毫秒内的随机序列(超过范围则取余)
*/
public Snowflake(long dataCenterId, long workerId, boolean clock, long timeOffset, boolean randomSequence) {
if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
throw new IllegalArgumentException("Data Center Id can't be greater than " +
MAX_DATA_CENTER_ID + " or less than 0");
}
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException("Worker Id can't be greater than " +
MAX_WORKER_ID + " or less than 0");
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
this.clock = clock;
this.timeOffset = timeOffset;
this.randomSequence = randomSequence;
}
/**
* 获取ID
*
* @return long
*/
public synchronized Long nextId() {
long currentTimestamp = this.timeGen();
// 闰秒:如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常
if (currentTimestamp < lastTimestamp) {
// 校验时间偏移回拨量
long offset = lastTimestamp - currentTimestamp;
if (offset > timeOffset) {
throw new RuntimeException("Clock moved backwards, refusing to generate id for [" + offset + "ms]");
}
try {
// 时间回退timeOffset毫秒内,则允许等待2倍的偏移量后重新获取,解决小范围的时间回拨问题
this.wait(offset << 1);
} catch (Exception e) {
throw new RuntimeException(e);
}
// 再次获取
currentTimestamp = this.timeGen();
// 再次校验
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards, refusing to generate id for [" + offset + "ms]");
}
}
// 同一毫秒内序列直接自增
if (lastTimestamp == currentTimestamp) {
// randomSequence为true表示随机生成允许范围内的序列起始值并取余数,否则毫秒内起始值为0L开始自增
long tempSequence = sequence + 1;
if (randomSequence && tempSequence > SEQUENCE_MASK) {
tempSequence = tempSequence % SEQUENCE_MASK;
}
// 通过位与运算保证计算的结果范围始终是 0-4095
sequence = tempSequence & SEQUENCE_MASK;
if (sequence == 0) {
currentTimestamp = this.tilNextMillis(lastTimestamp);
}
} else {
// randomSequence为true表示随机生成允许范围内的序列起始值,否则毫秒内起始值为0L开始自增
sequence = randomSequence ? tlr.nextLong(SEQUENCE_MASK + 1) : 0L;
}
lastTimestamp = currentTimestamp;
long currentOffsetTime = currentTimestamp - START_TIME;
/*
* 1.左移运算是为了将数值移动到对应的段(41、5、5,12那段因为本来就在最右,因此不用左移)
* 2.然后对每个左移后的值(la、lb、lc、sequence)做位或运算,是为了把各个短的数据合并起来,合并成一个二进制数
* 3.最后转换成10进制,就是最终生成的id
*/
return (currentOffsetTime << TIMESTAMP_LEFT_SHIFT) |
// 数据中心位
(dataCenterId << DATA_CENTER_ID_SHIFT) |
// 工作ID位
(workerId << WORKER_ID_SHIFT) |
// 毫秒序列化位
sequence;
}
/**
* 保证返回的毫秒数在参数之后(阻塞到下一个毫秒,直到获得新的时间戳)——CAS
*
* @param lastTimestamp last timestamp
* @return next millis
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
// 如果发现时间回拨,则自动重新获取(可能会处于无限循环中)
timestamp = this.timeGen();
}
return timestamp;
}
/**
* 获得系统当前毫秒时间戳
*
* @return timestamp 毫秒时间戳
*/
private long timeGen() {
return clock ? ClockPool.INSTANCE.currentTimeMillis() : System.currentTimeMillis();
}
/**
* 用IP地址最后几个字节标示
* <p>
* eg:192.168.1.30->30
*
* @return last IP
*/
private static byte getLastIPAddress() {
if (LAST_IP != 0) {
return LAST_IP;
}
try {
InetAddress inetAddress = InetAddress.getLocalHost();
byte[] addressByte = inetAddress.getAddress();
LAST_IP = addressByte[addressByte.length - 1];
} catch (Exception e) {
throw new RuntimeException("Unknown Host Exception", e);
}
return LAST_IP;
}
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.core.util;
/**
* @author: liaojinlong
* @date: 2020/6/9 17:02
* @since: 1.0
* @see {@link SpringContextHolder}
* 针对某些初始化方法,在SpringContextHolder 初始化前时,<br>
* 可提交一个 提交回调任务。<br>
* 在SpringContextHolder 初始化后,进行回调使用
*/
public interface CallBack {
/**
* 回调执行方法
*/
void executor();
/**
* 本回调任务名称
* @return /
*/
default String getCallBackName() {
return Thread.currentThread().getId() + ":" + this.getClass().getName();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.core.util;
/**
* 常用静态常量
*
* @author Zheng Jie
* @date 2018-12-26
*/
public class ElAdminConstant {
/**
* 用于IP定位转换
*/
public static final String REGION = "内网IP|内网IP";
/**
* win 系统
*/
public static final String WIN = "win";
/**
* mac 系统
*/
public static final String MAC = "mac";
/**
* 常用接口
*/
public static class Url {
// 免费图床
public static final String SM_MS_URL = "https://sm.ms/api";
// IP归属地查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.core.util;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.UserAgent;
//import org.lionsoul.ip2region.DataBlock;
//import org.lionsoul.ip2region.DbConfig;
//import org.lionsoul.ip2region.DbSearcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
/**
* @author Zheng Jie
* 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
*/
public class ElStringUtils extends org.apache.commons.lang3.StringUtils {
private static final Logger log = LoggerFactory.getLogger(ElStringUtils.class);
private static boolean ipLocal = false;
private static File file = null;
//TODO 需要实现IP对地域的转换功能
// private static DbConfig config;
private static final char SEPARATOR = '_';
private static final String UNKNOWN = "unknown";
static {
// SpringContextHolder.addCallBacks(() -> {
// ElStringUtils.ipLocal = SpringContextHolder.getProperties("ip.local-parsing", false, Boolean.class);
// if (ipLocal) {
// /*
// * 此文件为独享 ,不必关闭
// */
// String path = "ip2region/ip2region.db";
// String name = "ip2region.db";
// try {
// config = new DbConfig();
// file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name);
// } catch (Exception e) {
// log.error(e.getMessage(), e);
// }
// }
// });
}
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
public static String toCamelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == SEPARATOR) {
upperCase = true;
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
public static String toCapitalizeCamelCase(String s) {
if (s == null) {
return null;
}
s = toCamelCase(s);
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
static String toUnderScoreCase(String s) {
if (s == null) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
boolean nextUpperCase = true;
if (i < (s.length() - 1)) {
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
}
if ((i > 0) && Character.isUpperCase(c)) {
if (!upperCase || !nextUpperCase) {
sb.append(SEPARATOR);
}
upperCase = true;
} else {
upperCase = false;
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 获取ip地址
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
String comma = ",";
String localhost = "127.0.0.1";
if (ip.contains(comma)) {
ip = ip.split(",")[0];
}
if (localhost.equals(ip)) {
// 获取本机真正的ip地址
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
}
}
return ip;
}
/**
* 根据ip获取详细地址
*/
public static String getCityInfo(String ip) {
if (ipLocal) {
return getLocalCityInfo(ip);
} else {
return getHttpCityInfo(ip);
}
}
/**
* 根据ip获取详细地址
*/
public static String getHttpCityInfo(String ip) {
String api = String.format(ElAdminConstant.Url.IP_URL, ip);
JSONObject object = JSONUtil.parseObj(HttpUtil.get(api));
return object.get("addr", String.class);
}
/**
* 根据ip获取详细地址
*/
public static String getLocalCityInfo(String ip) {
// try {
// DataBlock dataBlock = new DbSearcher(config, file.getPath())
// .binarySearch(ip);
// String region = dataBlock.getRegion();
// String address = region.replace("0|", "");
// char symbol = '|';
// if (address.charAt(address.length() - 1) == symbol) {
// address = address.substring(0, address.length() - 1);
// }
// return address.equals(ElAdminConstant.REGION) ? "内网IP" : address;
// } catch (Exception e) {
// log.error(e.getMessage(), e);
// }
return "待实现";
}
public static String getBrowser(HttpServletRequest request) {
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
Browser browser = userAgent.getBrowser();
return browser.getName();
}
/**
* 获得当天是周几
*/
public static String getWeekDay() {
String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
if (w < 0) {
w = 0;
}
return weekDays[w];
}
/**
* 获取当前机器的IP
*
* @return /
*/
public static String getLocalIp() {
InetAddress addr;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
return "unknown";
}
byte[] ipAddr = addr.getAddress();
StringBuilder ipAddrStr = new StringBuilder();
for (int i = 0; i < ipAddr.length; i++) {
if (i > 0) {
ipAddrStr.append(".");
}
ipAddrStr.append(ipAddr[i] & 0xFF);
}
return ipAddrStr.toString();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.core.util;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
/**
* 加密
* @author Zheng Jie
* @date 2018-11-23
*/
public class EncryptUtils {
private static final String STR_PARAM = "Passw0rd";
private static Cipher cipher;
private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
private static DESKeySpec getDesKeySpec(String source) throws Exception {
if (source == null || source.length() == 0){
return null;
}
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
String strKey = "Passw0rd";
return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
}
/**
* 对称加密
*/
public static String desEncrypt(String source) throws Exception {
DESKeySpec desKeySpec = getDesKeySpec(source);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
return byte2hex(
cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
}
/**
* 对称解密
*/
public static String desDecrypt(String source) throws Exception {
byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
DESKeySpec desKeySpec = getDesKeySpec(source);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
byte[] retByte = cipher.doFinal(src);
return new String(retByte);
}
private static String byte2hex(byte[] inStr) {
String stmp;
StringBuilder out = new StringBuilder(inStr.length * 2);
for (byte b : inStr) {
stmp = Integer.toHexString(b & 0xFF);
if (stmp.length() == 1) {
// 如果是0至F的单位字符串,则添加0
out.append("0").append(stmp);
} else {
out.append(stmp);
}
}
return out.toString();
}
private static byte[] hex2byte(byte[] b) {
int size = 2;
if ((b.length % size) != 0){
throw new IllegalArgumentException("长度不是偶数");
}
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += size) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
}
package com.elitesland.core.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.val;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.time.LocalDateTime;
/**
* @author Mir
* @date 2018-11-08
*/
public final class HttpServletResponseUtil {
private static String UTF8 = "UTF-8";
private static String CONTENT_TYPE = "application/json";
private HttpServletResponseUtil() {
throw new AssertionError();
}
public static void printJSON(HttpServletResponse response, Object object) throws Exception {
val mapper = new ObjectMapper();
val javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, SpringContextHolder.getBean(LocalDateTimeSerializer.class));
javaTimeModule.addDeserializer(LocalDateTime.class, SpringContextHolder.getBean(LocalDateTimeDeserializer.class));
mapper.registerModule(javaTimeModule);
response.setCharacterEncoding(UTF8);
response.setContentType(CONTENT_TYPE);
PrintWriter printWriter = response.getWriter();
printWriter.write(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object));
printWriter.flush();
printWriter.close();
}
}
package com.elitesland.core.util;
import com.elitesland.core.micro.Snowflake;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import java.io.Serializable;
/**
* 适用于JPA的基于Snowflake算法的ID生成器
*
* @author Moz
* @date 3/17/2020
*/
public class IdGenerator implements IdentifierGenerator {
private final Snowflake snowflake = new Snowflake(1);
@Override
public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException {
return snowflake.nextId();
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.core.util;
import org.springframework.data.domain.Page;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 分页工具
* @author Mir
* @date 2018-12-10
*/
public class PageUtil extends cn.hutool.core.util.PageUtil {
/**
* List 分页
*/
public static List toPage(int page, int size , List list) {
int fromIndex = page * size;
int toIndex = page * size + size;
if(fromIndex > list.size()){
return new ArrayList();
} else if(toIndex >= list.size()) {
return list.subList(fromIndex,list.size());
} else {
return list.subList(fromIndex,toIndex);
}
}
/**
* Page 数据处理,预防redis反序列化报错
*/
public static Map<String,Object> toPage(Page page) {
Map<String,Object> map = new LinkedHashMap<>(2);
map.put("content",page.getContent());
map.put("totalElements",page.getTotalElements());
return map;
}
/**
* 自定义分页
*/
public static Map<String,Object> toPage(Object object, Object totalElements) {
Map<String,Object> map = new LinkedHashMap<>(2);
map.put("content",object);
map.put("totalElements",totalElements);
return map;
}
}
package com.elitesland.core.util;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* <pre>
* 封装请求,并替换getParameter从原有的Parameter获取值,为从Attribute中获取值
* </pre>
*
* @author Mir
* @date 2020/6/16
*/
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
public ParameterRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
return (String) super.getAttribute(name);
}
}
This diff is collapsed.
package com.elitesland.core.util;
import lombok.val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
/**
* <pre>
* 将请求的body的二进制内容转为字符串
* </pre>
*
* @author Mir
* @date 2020/6/16
*/
public class RequestUtil {
private static final Logger log = LoggerFactory.getLogger(RequestUtil.class);
private RequestUtil() {
}
public static String obtainBody(ServletRequest request) {
BufferedReader br = null;
val sb = new StringBuilder();
try {
br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
br.close();
} catch (IOException e) {
log.error("request body error");
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
log.error("request reader close error");
}
}
}
return sb.toString();
}
}
package com.elitesland.core.util;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author https://www.cnblogs.com/nihaorz/p/10690643.html
* @description Rsa 工具类,公钥私钥生成,加解密
* @date 2020-05-18
**/
public class RsaUtils {
private static final String SRC = "123456";
public static void main(String[] args) throws Exception {
System.out.println("\n");
RsaKeyPair keyPair = generateKeyPair();
System.out.println("公钥:" + keyPair.getPublicKey());
System.out.println("私钥:" + keyPair.getPrivateKey());
System.out.println("\n");
test1(keyPair);
System.out.println("\n");
test2(keyPair);
System.out.println("\n");
}
/**
* 公钥加密私钥解密
*/
private static void test1(RsaKeyPair keyPair) throws Exception {
System.out.println("***************** 公钥加密私钥解密开始 *****************");
String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
System.out.println("加密前:" + RsaUtils.SRC);
System.out.println("加密后:" + text1);
System.out.println("解密后:" + text2);
if (RsaUtils.SRC.equals(text2)) {
System.out.println("解密字符串和原始字符串一致,解密成功");
} else {
System.out.println("解密字符串和原始字符串不一致,解密失败");
}
System.out.println("***************** 公钥加密私钥解密结束 *****************");
}
/**
* 私钥加密公钥解密
* @throws Exception /
*/
private static void test2(RsaKeyPair keyPair) throws Exception {
System.out.println("***************** 私钥加密公钥解密开始 *****************");
String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
System.out.println("加密前:" + RsaUtils.SRC);
System.out.println("加密后:" + text1);
System.out.println("解密后:" + text2);
if (RsaUtils.SRC.equals(text2)) {
System.out.println("解密字符串和原始字符串一致,解密成功");
} else {
System.out.println("解密字符串和原始字符串不一致,解密失败");
}
System.out.println("***************** 私钥加密公钥解密结束 *****************");
}
/**
* 公钥解密
*
* @param publicKeyText 公钥
* @param text 待解密的信息
* @return /
* @throws Exception /
*/
public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
return new String(result);
}
/**
* 私钥加密
*
* @param privateKeyText 私钥
* @param text 待加密的信息
* @return /
* @throws Exception /
*/
public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64.encodeBase64String(result);
}
/**
* 私钥解密
*
* @param privateKeyText 私钥
* @param text 待解密的文本
* @return /
* @throws Exception /
*/
public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
return new String(result);
}
/**
* 公钥加密
*
* @param publicKeyText 公钥
* @param text 待加密的文本
* @return /
*/
public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64.encodeBase64String(result);
}
/**
* 构建RSA密钥对
*
* @return /
* @throws NoSuchAlgorithmException /
*/
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
return new RsaKeyPair(publicKeyString, privateKeyString);
}
/**
* RSA密钥对对象
*/
public static class RsaKeyPair {
private final String publicKey;
private final String privateKey;
public RsaKeyPair(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
}
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.core.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import java.util.ArrayList;
import java.util.List;
/**
* @author Jie
* @date 2019-01-07
*/
@Slf4j
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
private static final List<CallBack> CALL_BACKS = new ArrayList<>();
private static boolean addCallback = true;
/**
* 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
* 在SpringContextHolder 初始化后,进行回调使用
*
* @param callBack 回调函数
*/
public synchronized static void addCallBacks(CallBack callBack) {
if (addCallback) {
SpringContextHolder.CALL_BACKS.add(callBack);
} else {
log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
callBack.executor();
}
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @param defaultValue 默认值
* @param requiredType 返回类型
* @return /
*/
public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
T result = defaultValue;
try {
result = getBean(Environment.class).getProperty(property, requiredType);
} catch (Exception ignored) {}
return result;
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @return /
*/
public static String getProperties(String property) {
return getProperties(property, null, String.class);
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @param requiredType 返回类型
* @return /
*/
public static <T> T getProperties(String property, Class<T> requiredType) {
return getProperties(property, null, requiredType);
}
/**
* 检查ApplicationContext不为空.
*/
private static void assertContextInjected() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
}
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
private static void clearHolder() {
log.debug("清除SpringContextHolder中的ApplicationContext:"
+ applicationContext);
applicationContext = null;
}
@Override
public void destroy() {
SpringContextHolder.clearHolder();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextHolder.applicationContext != null) {
log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
}
SpringContextHolder.applicationContext = applicationContext;
if (addCallback) {
for (CallBack callBack : SpringContextHolder.CALL_BACKS) {
callBack.executor();
}
CALL_BACKS.clear();
}
SpringContextHolder.addCallback = false;
}
}
package com.elitesland.core.util;
import lombok.val;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* <pre>
* 整理异常中的堆栈信息,返回信息字符串
* </pre>
*
* @author Mir
* @date 2020/6/22
*/
public class ThrowableUtil {
public static String getStackTrace(Throwable e) {
val sw = new StringWriter();
try (val pw = new PrintWriter(sw)) {
e.printStackTrace(pw);
return sw.toString();
}
}
}
package com.elitesland.core.util;
import java.util.UUID;
/**
* @author Mir
* @date 2018-11-08
*/
public class UUIDUtil {
/**
* 随机生成UUID
*
* @return UUID字符串,不含连接符
*/
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
package com.elitesland.core.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/7/6
*/
@Data
@Accessors(chain = true)
@ApiModel("请求对象参数绑定错误")
public class RequestBindingErrorVO implements Serializable {
private static final long serialVersionUID = -4297055572902473228L;
@ApiModelProperty("错误字段")
String field;
@ApiModelProperty("错误信息")
String msg;
}
package com.elitesland.security;
import com.elitesland.system.entity.SysRoleDO;
import com.elitesland.system.repo.SysPermissionRepo;
import com.elitesland.system.repo.SysRoleRepo;
import lombok.val;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import javax.annotation.Resource;
import java.util.Collection;
/**
* <pre>
* 动态鉴权的核心,根据访问的URL,获取对应的角色列表
* </pre>
*
* @author Mir
* @date 2020/6/15
*/
public class DynamicFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Resource
private RequestMatcherCreator requestMatcherCreator;
@Resource
private SysPermissionRepo sysPermissionRepo;
@Resource
private SysRoleRepo sysRoleRepo;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
return SecurityConfig.createList();
//
// val request = ((FilterInvocation) object).getRequest();
//
// val permissions = sysPermissionRepo.findAll();
// val metaResources = permissions.stream()
// .map(p -> Tuples.of(p.getPattern(), p.getHttpMethod()))
// .collect(Collectors.toSet());
// val requestMatchers = requestMatcherCreator.convertToRequestMatcher(metaResources);
//
// val reqMatcher = requestMatchers.stream().filter(m -> m.matches(request))
// .findAny();
// if (reqMatcher.isPresent()) {
// val antPathRequestMatcher = (AntPathRequestMatcher) reqMatcher.get();
//
// val pattern = antPathRequestMatcher.getPattern();
// val permRoleCodes = permissions.stream().filter(p -> p.getPattern().equals(pattern))
// .flatMap(p -> p.getRoles().stream().map(SysRoleDO::getCode))
// .toArray(String[]::new);
// return SecurityConfig.createList(permRoleCodes);
// }
//
// return SecurityConfig.createList();
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
val roleList = sysRoleRepo.findAll();
val roleCodeList = roleList.stream().map(SysRoleDO::getCode).toArray(String[]::new);
return SecurityConfig.createList(roleCodeList);
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
package com.elitesland.security;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.elitesland.core.util.RequestUtil;
import lombok.val;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import static com.elitesland.security.filter.PreLoginFilter.LOGIN_CAPTCHA_KEY;
import static com.elitesland.security.filter.PreLoginFilter.LOGIN_CAPTCHA_UID;
import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY;
import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY;
/**
* <pre>
* JSON格式登录信息处理器
* </pre>
*
* @author Mir
* @date 2020/6/16
*/
public class JsonLoginProcessor implements LoginPostProcessor {
private static ThreadLocal<String> passwordThreadLocal = new ThreadLocal<>();
private static ThreadLocal<String> captchaThreadLocal = new ThreadLocal<>();
private static ThreadLocal<String> capuidThreadLocal = new ThreadLocal<>();
@Override
public LoginTypeEnum getLoginType() {
return LoginTypeEnum.JSON;
}
@Override
public String obtainsUsername(ServletRequest request) {
val requestWrapper = new HttpServletRequestWrapper((HttpServletRequest) request);
val body = RequestUtil.obtainBody(requestWrapper);
JSONObject jsonObj = JSONUtil.parseObj(body);
passwordThreadLocal.set(jsonObj.getStr(SPRING_SECURITY_FORM_PASSWORD_KEY));
captchaThreadLocal.set(jsonObj.getStr(LOGIN_CAPTCHA_KEY));
capuidThreadLocal.set(jsonObj.getStr(LOGIN_CAPTCHA_UID));
return jsonObj.getStr(SPRING_SECURITY_FORM_USERNAME_KEY);
}
@Override
public String obtainPassword(ServletRequest request) {
val password = passwordThreadLocal.get();
passwordThreadLocal.remove();
return password;
}
@Override
public String obtainCaptcha(ServletRequest request) {
val captcha = captchaThreadLocal.get();
captchaThreadLocal.remove();
return captcha;
}
@Override
public String obtainCapUid(ServletRequest request) {
val capUid = capuidThreadLocal.get();
capuidThreadLocal.remove();
return capUid;
}
}
package com.elitesland.security;
import com.elitesland.core.exception.BadJwtTokenException;
import com.elitesland.security.config.bean.JwtProperties;
import com.elitesland.security.handle.JsonAuthenticationEntryPoint;
import com.elitesland.security.service.OnlineUserService;
import com.elitesland.security.service.entity.OnlineUserDO;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/16
*/
@Slf4j
public class JwtTokenFilter extends GenericFilterBean {
private final JwtProperties jwtProperties;
private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
private final AuthenticationEntryPoint authenticationEntryPoint = new JsonAuthenticationEntryPoint();
public JwtTokenFilter(JwtProperties jwtProperties, OnlineUserService onlineUserService, TokenProvider tokenProvider) {
this.jwtProperties = jwtProperties;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.debug("JWT token filter triggerred");
val httpServletRequest = (HttpServletRequest) servletRequest;
try {
val token = resolveToken(httpServletRequest);
token.ifPresent(t -> {
OnlineUserDO onlineUserDO = null;
try {
onlineUserDO = onlineUserService.getOne(jwtProperties.getOnlineKey() + t);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
}
if (onlineUserDO != null && org.springframework.util.StringUtils.hasText(t)) {
Authentication authentication = tokenProvider.getAuthentication(t);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(t);
}
});
} catch (BadJwtTokenException e) {
authenticationEntryPoint.commence(
(HttpServletRequest) servletRequest,
(HttpServletResponse) servletResponse,
e);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private Optional<String> resolveToken(HttpServletRequest request) throws BadJwtTokenException {
val bearerToken = request.getHeader(jwtProperties.getHeader());
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(jwtProperties.getTokenStartWith())) {
return Optional.of(bearerToken.replace(jwtProperties.getTokenStartWith(), ""));
} else {
log.debug("Illegal token: {}", bearerToken);
// throw new BadJwtTokenException("非法JWT令牌");
}
return Optional.empty();
}
}
package com.elitesland.security;
import javax.servlet.ServletRequest;
/**
* <pre>
* 多种登录方式统一信息提取接口
* </pre>
*
* @author Mir
* @date 2020/6/16
*/
public interface LoginPostProcessor {
/**
* 从请求查询参数中,获取登录类型,表单还是JSON
*
* @return 登录类型枚举值
*/
LoginTypeEnum getLoginType();
/**
* 从请求中获取登录用户账号
*
* @param request 请求对象
* @return 登录用户账号
*/
String obtainsUsername(ServletRequest request);
/**
* 从请求中获取登录密码
*
* @param request 请求对象
* @return 登录密码
*/
String obtainPassword(ServletRequest request);
/**
* 从请求中获取验证码
*
* @param request 请求对象
* @return 验证码字符串
*/
String obtainCaptcha(ServletRequest request);
/**
* 从请求中获取验证码缓存UID
*
* @param request 请求对象
* @return 验证码缓存UID
*/
String obtainCapUid(ServletRequest request);
}
package com.elitesland.security;
/**
* <pre>
* 登录方式分类
* </pre>
*
* @author Mir
* @date 2020/6/16
*/
public enum LoginTypeEnum {
//表单登录
FORM("表单登录"),
//JSON登录
JSON("JSON登录");
private final String desc;
LoginTypeEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return this.desc;
}
}
package com.elitesland.security;
import cn.hutool.core.util.StrUtil;
import com.elitesland.core.exception.BadCaptchaException;
import com.elitesland.core.util.ParameterRequestWrapper;
import com.elitesland.core.util.RedisUtils;
import com.elitesland.security.handle.JsonAuthenticationEntryPoint;
import lombok.val;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY;
import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/16
*/
public class PreLoginFilter extends GenericFilterBean {
public static final String LOGIN_TYPE_KEY = "login_type";
public static final String LOGIN_CAPTCHA_KEY = "captcha";
public static final String LOGIN_CAPTCHA_UID = "capuid";
private final RequestMatcher requiredAuthenticationRequestMatcher;
private final Map<LoginTypeEnum, LoginPostProcessor> processors = new HashMap<>();
private RedisUtils redisUtils = null;
private final AuthenticationEntryPoint authenticationEntryPoint = new JsonAuthenticationEntryPoint();
public PreLoginFilter(String loginProcessingUrl, Collection<LoginPostProcessor> loginPostProcessors, RedisUtils redisUtils) {
Assert.notNull(loginProcessingUrl, "loginProcessingUrl must not be empty");
requiredAuthenticationRequestMatcher = new AntPathRequestMatcher(loginProcessingUrl, "POST");
this.redisUtils = redisUtils;
val loginPostProcessor = defaultLoginProcessor();
processors.put(loginPostProcessor.getLoginType(), loginPostProcessor);
if (!CollectionUtils.isEmpty(loginPostProcessors)) {
loginPostProcessors.forEach(e -> processors.put(e.getLoginType(), e));
}
}
private LoginTypeEnum getTypeFromReq(ServletRequest request) {
val parameter = request.getParameter(LOGIN_TYPE_KEY);
var i = 1;
if (parameter != null) {
i = Integer.parseInt(parameter);
}
val values = LoginTypeEnum.values();
return values[i];
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException, AuthenticationException {
val parameterRequestWrapper = new ParameterRequestWrapper((HttpServletRequest) servletRequest);
if (requiredAuthenticationRequestMatcher.matches((HttpServletRequest) servletRequest)) {
val typeFromReq = getTypeFromReq(servletRequest);
val loginPostProcessor = processors.get(typeFromReq);
val username = loginPostProcessor.obtainsUsername(servletRequest);
val password = loginPostProcessor.obtainPassword(servletRequest);
val captchaStr = loginPostProcessor.obtainCaptcha(servletRequest);
val capUid = loginPostProcessor.obtainCapUid(servletRequest);
val code = (String) redisUtils.get(capUid);
if (StrUtil.isBlank(code)) {
authenticationEntryPoint.commence(
(HttpServletRequest) servletRequest,
(HttpServletResponse) servletResponse,
new BadCredentialsException("验证码不存在或已过期"));
return;
}
redisUtils.del(capUid);
if (StrUtil.isBlank(captchaStr) || !captchaStr.equalsIgnoreCase(code)) {
authenticationEntryPoint.commence(
(HttpServletRequest) servletRequest,
(HttpServletResponse) servletResponse,
new BadCaptchaException("验证码错误")
);
return;
}
parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_USERNAME_KEY, username);
parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_PASSWORD_KEY, password);
}
filterChain.doFilter(parameterRequestWrapper, servletResponse);
}
private LoginPostProcessor defaultLoginProcessor() {
return new LoginPostProcessor() {
@Override
public LoginTypeEnum getLoginType() {
return LoginTypeEnum.FORM;
}
@Override
public String obtainsUsername(ServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
}
@Override
public String obtainPassword(ServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
}
@Override
public String obtainCaptcha(ServletRequest request) {
return request.getParameter(LOGIN_CAPTCHA_KEY);
}
@Override
public String obtainCapUid(ServletRequest request) {
return request.getParameter(LOGIN_CAPTCHA_UID);
}
};
}
}
package com.elitesland.security;
import org.springframework.security.web.util.matcher.RequestMatcher;
import reactor.util.function.Tuple2;
import java.util.Set;
/**
* <pre>
* 函数式接口,用于创建对应的函数工厂
* </pre>
*
* @author Mir
* @date 2020/6/15
*/
public interface RequestMatcherCreator {
Set<RequestMatcher> convertToRequestMatcher(Set<Tuple2<String, String>> metaResources);
}
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.security;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.elitesland.core.util.RedisUtils;
import com.elitesland.security.config.bean.JwtProperties;
import com.elitesland.security.service.entity.JwtUserDto;
import com.elitesland.system.entity.SysRoleDO;
import com.elitesland.system.service.SysUserService;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author /
*/
@Slf4j
@Component
public class TokenProvider implements InitializingBean {
private final JwtProperties properties;
private final RedisUtils redisUtils;
public static final String AUTHORITIES_KEY = "auth";
public static final String USER_ID = "userid";
private JwtParser jwtParser;
private JwtBuilder jwtBuilder;
private final SysUserService sysUserService;
public TokenProvider(JwtProperties properties, RedisUtils redisUtils, SysUserService sysUserService) {
this.properties = properties;
this.redisUtils = redisUtils;
this.sysUserService = sysUserService;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
Key key = Keys.hmacShaKeyFor(keyBytes);
jwtParser = Jwts.parserBuilder()
.setSigningKey(key)
.build();
jwtBuilder = Jwts.builder()
.signWith(key, SignatureAlgorithm.HS512);
}
/**
* 创建Token 设置永不过期,
* Token 的时间有效性转到Redis 维护
*
* @param authentication /
* @return /
*/
public String createToken(Authentication authentication) {
/*
* 获取权限列表
*/
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
val user = (JwtUserDto) authentication.getPrincipal();
return jwtBuilder
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.setIssuer(properties.getIssuer())
.setAudience(properties.getAudience())
.claim(AUTHORITIES_KEY, authorities)
.claim(USER_ID, user.getUser().getId())
.setSubject(authentication.getName())
.compact();
}
/**
* 依据Token 获取鉴权信息
*
* @param token /
* @return /
*/
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Object userId = claims.get(USER_ID);
val userOpt = sysUserService.getById((Long) userId).get();
Collection<? extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
// User principal = new User(claims.getSubject(), "******", authorities);
val principal = new JwtUserDto(
userOpt,
new ArrayList<Long>(),
AuthorityUtils.createAuthorityList(userOpt.getRoles().stream().map(SysRoleDO::getCode).toArray(String[]::new))
);
val auth = new UsernamePasswordAuthenticationToken(principal, token, authorities);
auth.setDetails(userId);
return auth;
}
public Claims getClaims(String token) {
return jwtParser
.parseClaimsJws(token)
.getBody();
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
}
package com.elitesland.security.config;
import com.elitesland.core.base.ApiCode;
import com.elitesland.core.base.ApiResult;
import com.elitesland.core.util.HttpServletResponseUtil;
import com.elitesland.security.TokenProvider;
import com.elitesland.security.config.bean.JwtProperties;
import com.elitesland.security.config.bean.LoginProperties;
import com.elitesland.security.service.OnlineUserService;
import com.elitesland.security.service.entity.JwtUserDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 功能说明
* </p>
*
* @author Mir
* @date 2020/7/4
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class JwtConfig {
private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
private final JwtProperties properties;
private final LoginProperties loginProperties;
@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return (request, response, authentication) -> {
if (response.isCommitted()) {
log.debug("Response has already been committed!!!");
return;
}
val token = tokenProvider.createToken(authentication);
val jwtUserDto = (JwtUserDto) authentication.getPrincipal();
onlineUserService.save(jwtUserDto, token, request);
Map<String, Object> authInfo = new HashMap<>(2) {
private static final long serialVersionUID = -7157230580904317221L;
{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUserDto);
}
};
if (loginProperties.isSingleLogin()) {
onlineUserService.checkLoginOnUser(jwtUserDto.getUsername(), token);
}
try {
HttpServletResponseUtil.printJSON(response, ApiResult.ok(authInfo));
} catch (Exception e) {
e.printStackTrace();
}
};
}
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, exception) -> {
try {
if (exception instanceof BadCredentialsException) {
HttpServletResponseUtil.printJSON(response, ApiResult.fail(ApiCode.AUTHENTICATION_EXCEPTION, "账号密码不正确"));
} else {
HttpServletResponseUtil.printJSON(response, ApiResult.fail(exception.getMessage()));
}
} catch (Exception e) {
e.printStackTrace();
}
};
}
}
package com.elitesland.security.config;
import com.elitesland.core.util.RedisUtils;
import com.elitesland.security.JsonLoginProcessor;
import com.elitesland.security.LoginPostProcessor;
import com.elitesland.security.TokenProvider;
import com.elitesland.security.config.bean.JwtProperties;
import com.elitesland.security.filter.JwtTokenFilter;
import com.elitesland.security.filter.PreLoginFilter;
import com.elitesland.security.handle.JsonAuthenticationEntryPoint;
import com.elitesland.security.handle.SimpleAccessDeniedException;
import com.elitesland.security.service.OnlineUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
import java.util.Collection;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/15
*/
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@RequiredArgsConstructor
public class SecurityConfig {
private static final String LOGIN_PROCESSING_URL = "/process";
private final JwtProperties jwtProperties;
private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
@Bean
public JsonLoginProcessor jsonLoginProcessor() {
return new JsonLoginProcessor();
}
@Bean
public PreLoginFilter preLoginFilter(Collection<LoginPostProcessor> loginPostProcessors, RedisUtils redisUtils) {
return new PreLoginFilter(LOGIN_PROCESSING_URL, loginPostProcessors, redisUtils);
}
@Bean
public JwtTokenFilter jwtTokenFilter() {
return new JwtTokenFilter(jwtProperties, onlineUserService, tokenProvider);
}
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
static class SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Resource
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Resource
private AuthenticationFailureHandler authenticationFailureHandler;
@Resource
private PreLoginFilter preLoginFilter;
@Resource
private JwtTokenFilter jwtTokenFilter;
@Resource
private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
@Resource
private AccessDecisionManager accessDecisionManager;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(new JsonAuthenticationEntryPoint())
.accessDeniedHandler(new SimpleAccessDeniedException())
.and()
.authorizeRequests()
.antMatchers("/sys/**").permitAll()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated()
.withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
.and()
.addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginProcessingUrl(LOGIN_PROCESSING_URL)
.successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler);
}
private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
return new ObjectPostProcessor<>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);
o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
return o;
}
};
}
}
}
package com.elitesland.security.config;
import com.elitesland.security.DynamicFilterInvocationSecurityMetadataSource;
import com.elitesland.security.RequestMatcherCreator;
import com.elitesland.security.config.bean.JwtProperties;
import com.elitesland.security.config.bean.LoginProperties;
import com.elitesland.security.service.entity.JwtUserDto;
import com.elitesland.system.entity.SysRoleDO;
import com.elitesland.system.service.SysUserService;
import lombok.val;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* <pre>
* 用于Spring Security配置的,初始配置工作
* </pre>
*
* @author Mir
* @date 2020/6/15
*/
@Configuration
@EnableJpaAuditing
public class SecuritySetupConfig {
@Bean
@ConfigurationProperties(prefix = "login", ignoreUnknownFields = true)
public LoginProperties captchaProperties() {
return new LoginProperties();
}
@Bean
@ConfigurationProperties(prefix = "jwt", ignoreUnknownFields = true)
public JwtProperties securityProperties() {
return new JwtProperties();
}
@Bean
public RequestMatcherCreator requestMatcherCreator() {
return metaResources -> metaResources.stream()
.map(r -> new AntPathRequestMatcher(r.getT1(), r.getT2()))
.collect(Collectors.toSet());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource() {
return new DynamicFilterInvocationSecurityMetadataSource();
}
@Bean
public RoleVoter roleVoter() {
return new RoleVoter();
}
@Bean
public AccessDecisionManager affirmativeBase(List<AccessDecisionVoter<?>> decisionVoters) {
return new AffirmativeBased(decisionVoters);
}
@Bean
public UserDetailsManager userDetailsManager() {
return new UserDetailsManager() {
@Resource
private SysUserService sysUserService;
@Override
public void createUser(UserDetails user) {
}
@Override
public void updateUser(UserDetails user) {
}
@Override
public void deleteUser(String username) {
}
@Override
public void changePassword(String oldPassword, String newPassword) {
}
@Override
public boolean userExists(String username) {
return sysUserService.isUserExists(username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
val sysUserOpt = sysUserService.getUserByUsernameSec(username);
return sysUserOpt.map(u -> new JwtUserDto(
u,
new ArrayList<Long>(),
AuthorityUtils.createAuthorityList(u.getRoles().stream().map(SysRoleDO::getCode).toArray(String[]::new))
)).orElse(null);
}
};
}
@Bean
public AuditorAware<Long> auditorProvider() {
return new SpringSecurityAuditorAware();
}
}
package com.elitesland.security.config;
import lombok.val;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional;
/**
* @author Mir
* @date 3/17/2020
*/
public class SpringSecurityAuditorAware implements AuditorAware<Long> {
/**
* 从SecurityContext中,获取当前登录用的用户ID
*
* @return 当前登录用户的用户ID
*/
@Override
public Optional<Long> getCurrentAuditor() {
val details = SecurityContextHolder.getContext().getAuthentication().getDetails();
if (details instanceof Long) {
return Optional.of((Long) details);
}
return Optional.of(0L);
}
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.security.config.bean;
/**
* <pre>
* 验证码配置枚举
* </pre>
*
* @author Mir
* @date 2020-06-20
*/
public enum CaptchaCodeEnum {
/**
* 算数
*/
arithmetic,
/**
* 中文
*/
chinese,
/**
* 中文闪图
*/
chinese_gif,
/**
* 闪图
*/
gif,
spec
}
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.elitesland.security.config.bean;
import lombok.Data;
/**
* <pre>
* 登录验证码配置信息
* </pre>
*
* @author Mir
* @date 2020-06-20
*/
@Data
public class CaptchaInfo {
/**
* 验证码配置
*/
private CaptchaCodeEnum codeType;
/**
* 验证码有效期 分钟
*/
private Long expiration = 2L;
/**
* 验证码内容长度
*/
private int length = 2;
/**
* 验证码宽度
*/
private int width = 111;
/**
* 验证码高度
*/
private int height = 36;
}
package com.elitesland.security.handle;
import com.elitesland.core.base.ApiCode;
import com.elitesland.core.base.ApiResult;
import com.elitesland.core.exception.BadJwtTokenException;
import com.elitesland.core.util.HttpServletResponseUtil;
import lombok.SneakyThrows;
import lombok.val;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/24
*/
public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint {
@SneakyThrows
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
val result = ApiResult.fail(ApiCode.AUTHENTICATION_EXCEPTION);
if (e instanceof InsufficientAuthenticationException) {
result.setMsg("当前用户没有该操作权限");
}
if(e instanceof BadCredentialsException){
result.setMsg(e.getMessage());
}
if(e instanceof BadJwtTokenException){
result.setCode(ApiCode.UNAUTHENTICATED_EXCEPTION.getCode());
result.setMsg(e.getMessage());
}
HttpServletResponseUtil.printJSON(httpServletResponse, result);
}
}
package com.elitesland.security.rest;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.RestController;
/**
* <pre>
* [功能说明]
* </pre>
*
* @author Mir
* @date 2020/6/21
*/
@RestController
@Api(value = "", tags = {""})
public class OnlineController {
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment