init
This commit is contained in:
91
bashi-framework/pom.xml
Normal file
91
bashi-framework/pom.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>bashi</artifactId>
|
||||
<groupId>com.bashi</groupId>
|
||||
<version>2.4.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>bashi-framework</artifactId>
|
||||
|
||||
<description>
|
||||
framework框架核心
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- BEGIN 如果想使用 Tomcat 注释掉以下代码 -->
|
||||
<!-- SpringBoot Web容器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- websocket -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<!-- web 容器使用 undertow 性能更强 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
<!-- END -->
|
||||
|
||||
<!-- BEGIN 如果想使用 Tomcat 解除以下代码注释 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- END -->
|
||||
|
||||
<!-- SpringBoot 拦截器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringBoot 校验框架 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里数据库连接池 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.sun</groupId>
|
||||
<artifactId>jconsole</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.sun</groupId>
|
||||
<artifactId>tools</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- 系统模块-->
|
||||
<dependency>
|
||||
<groupId>com.bashi</groupId>
|
||||
<artifactId>bashi-system</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.bashi</groupId>
|
||||
<artifactId>bashi-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bashi.framework.app;
|
||||
|
||||
public enum AppTypeEnums {
|
||||
|
||||
TBS("TBS","泰巴适"),
|
||||
TATA("TATA","她他"),
|
||||
MIYU("MIYU","秘约"),
|
||||
APP591("591","591"),
|
||||
MUSPAN("MUSPAN","幕Span"),
|
||||
QIANZHI("QIANZHI","仟指");
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
|
||||
AppTypeEnums(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static AppTypeEnums getByCode(String code){
|
||||
AppTypeEnums[] values = AppTypeEnums.values();
|
||||
for (AppTypeEnums value : values) {
|
||||
if(value.getCode().equals(code)){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return AppTypeEnums.TBS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bashi.framework.app;
|
||||
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Component
|
||||
public class AppTypeFactory {
|
||||
|
||||
private static final String TYPE = "TYPE";
|
||||
|
||||
public String getAppType(){
|
||||
String type = getHeaderType();
|
||||
AppTypeEnums appTypeEnums = AppTypeEnums.getByCode(type);
|
||||
return appTypeEnums.getCode();
|
||||
}
|
||||
|
||||
public String getHeaderType(){
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
return request.getHeader(TYPE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.bashi.framework.aspectj;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bashi.common.annotation.DataScope;
|
||||
import com.bashi.common.core.domain.BaseEntity;
|
||||
import com.bashi.common.core.domain.entity.SysRole;
|
||||
import com.bashi.common.core.domain.entity.SysUser;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import com.bashi.common.utils.reflect.ReflectUtils;
|
||||
import com.bashi.common.utils.spring.SpringUtils;
|
||||
import com.bashi.framework.web.service.TokenService;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据过滤处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class DataScopeAspect {
|
||||
|
||||
/**
|
||||
* 全部数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_ALL = "1";
|
||||
|
||||
/**
|
||||
* 自定数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_CUSTOM = "2";
|
||||
|
||||
/**
|
||||
* 部门数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_DEPT = "3";
|
||||
|
||||
/**
|
||||
* 部门及以下数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
|
||||
|
||||
/**
|
||||
* 仅本人数据权限
|
||||
*/
|
||||
public static final String DATA_SCOPE_SELF = "5";
|
||||
|
||||
/**
|
||||
* 数据权限过滤关键字
|
||||
*/
|
||||
public static final String DATA_SCOPE = "dataScope";
|
||||
|
||||
// 配置织入点
|
||||
@Pointcut("@annotation(com.bashi.common.annotation.DataScope)")
|
||||
public void dataScopePointCut() {
|
||||
}
|
||||
|
||||
@Before("dataScopePointCut()")
|
||||
public void doBefore(JoinPoint point) throws Throwable {
|
||||
clearDataScope(point);
|
||||
handleDataScope(point);
|
||||
}
|
||||
|
||||
protected void handleDataScope(final JoinPoint joinPoint) {
|
||||
// 获得注解
|
||||
DataScope controllerDataScope = getAnnotationLog(joinPoint);
|
||||
if (controllerDataScope == null) {
|
||||
return;
|
||||
}
|
||||
// 获取当前的用户
|
||||
LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
|
||||
if (Validator.isNotNull(loginUser)) {
|
||||
SysUser currentUser = loginUser.getUser();
|
||||
// 如果是超级管理员,则不过滤数据
|
||||
if (Validator.isNotNull(currentUser) && !currentUser.isAdmin()) {
|
||||
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
|
||||
controllerDataScope.userAlias(), controllerDataScope.isUser());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据范围过滤
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param user 用户
|
||||
* @param userAlias 别名
|
||||
*/
|
||||
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, boolean isUser) {
|
||||
StringBuilder sqlString = new StringBuilder();
|
||||
|
||||
// 将 "." 提取出,不写别名为单表查询,写别名为多表查询
|
||||
deptAlias = StrUtil.isNotBlank(deptAlias) ? deptAlias + "." : "";
|
||||
userAlias = StrUtil.isNotBlank(userAlias) ? userAlias + "." : "";
|
||||
|
||||
for (SysRole role : user.getRoles()) {
|
||||
String dataScope = role.getDataScope();
|
||||
if (DATA_SCOPE_ALL.equals(dataScope)) {
|
||||
sqlString = new StringBuilder();
|
||||
break;
|
||||
} else if (DATA_SCOPE_CUSTOM.equals(dataScope)) {
|
||||
sqlString.append(StrUtil.format(
|
||||
" OR {}dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ",
|
||||
deptAlias, role.getRoleId()));
|
||||
} else if (DATA_SCOPE_DEPT.equals(dataScope)) {
|
||||
sqlString.append(StrUtil.format(" OR {}dept_id = {} ",
|
||||
deptAlias, user.getDeptId()));
|
||||
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
|
||||
sqlString.append(StrUtil.format(
|
||||
" OR {}dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
|
||||
deptAlias, user.getDeptId(), user.getDeptId()));
|
||||
} else if (DATA_SCOPE_SELF.equals(dataScope)) {
|
||||
if (isUser) {
|
||||
sqlString.append(StrUtil.format(" OR {}user_id = {} ",
|
||||
userAlias, user.getUserId()));
|
||||
} else {
|
||||
// 数据权限为仅本人且没有userAlias别名不查询任何数据
|
||||
sqlString.append(" OR 1=0 ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(sqlString.toString())) {
|
||||
putDataScope(joinPoint, sqlString.substring(4));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在注解,如果存在就获取
|
||||
*/
|
||||
private DataScope getAnnotationLog(JoinPoint joinPoint) {
|
||||
Signature signature = joinPoint.getSignature();
|
||||
MethodSignature methodSignature = (MethodSignature) signature;
|
||||
Method method = methodSignature.getMethod();
|
||||
|
||||
if (method != null) {
|
||||
return method.getAnnotation(DataScope.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接权限sql前先清空params.dataScope参数防止注入
|
||||
*/
|
||||
private void clearDataScope(final JoinPoint joinPoint) {
|
||||
Object params = joinPoint.getArgs()[0];
|
||||
if (Validator.isNotNull(params)) {
|
||||
putDataScope(joinPoint, "");
|
||||
}
|
||||
}
|
||||
|
||||
private static void putDataScope(JoinPoint joinPoint, String sql) {
|
||||
Object params = joinPoint.getArgs()[0];
|
||||
if (Validator.isNotNull(params)) {
|
||||
if (params instanceof BaseEntity) {
|
||||
BaseEntity baseEntity = (BaseEntity) params;
|
||||
baseEntity.getParams().put(DATA_SCOPE, sql);
|
||||
} else {
|
||||
Map<String, Object> invoke = ReflectUtils.invokeGetter(params, "params");
|
||||
invoke.put(DATA_SCOPE, sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.bashi.framework.aspectj;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import com.bashi.common.annotation.DataSource;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 多数据源处理
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Aspect
|
||||
@Order(-500)
|
||||
@Component
|
||||
public class DataSourceAspect {
|
||||
|
||||
@Pointcut("@annotation(com.bashi.common.annotation.DataSource)"
|
||||
+ "|| @within(com.bashi.common.annotation.DataSource)")
|
||||
public void dsPointCut() {
|
||||
}
|
||||
|
||||
@Around("dsPointCut()")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
DataSource dataSource = getDataSource(point);
|
||||
|
||||
if (Validator.isNotNull(dataSource)) {
|
||||
DynamicDataSourceContextHolder.poll();
|
||||
String source = dataSource.value().getSource();
|
||||
DynamicDataSourceContextHolder.push(source);
|
||||
}
|
||||
|
||||
try {
|
||||
return point.proceed();
|
||||
} finally {
|
||||
// 销毁数据源 在执行方法之后
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要切换的数据源
|
||||
*/
|
||||
public DataSource getDataSource(ProceedingJoinPoint point) {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
|
||||
if (Objects.nonNull(dataSource)) {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.bashi.framework.aspectj;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.bashi.common.annotation.Log;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.enums.BusinessStatus;
|
||||
import com.bashi.common.utils.SecurityUtils;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import com.bashi.common.utils.spring.SpringUtils;
|
||||
import com.bashi.framework.app.AppTypeFactory;
|
||||
import com.bashi.framework.util.AgentUtils;
|
||||
import com.bashi.framework.web.service.AsyncService;
|
||||
import com.bashi.system.domain.SysOperLog;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 操作日志记录处理
|
||||
*
|
||||
* @author nohi
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LogAspect {
|
||||
|
||||
@Autowired
|
||||
private AppTypeFactory appTypeFactory;
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
|
||||
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
|
||||
handleLog(joinPoint, controllerLog, null, jsonResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截异常操作
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
*/
|
||||
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
|
||||
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
|
||||
handleLog(joinPoint, controllerLog, e, null);
|
||||
}
|
||||
|
||||
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
StringBuilder logString = new StringBuilder();
|
||||
logString.append("record logs:");
|
||||
// 获得注解
|
||||
if (controllerLog == null || (!controllerLog.isPrint() && !controllerLog.isSaveDb())) {
|
||||
return;
|
||||
}
|
||||
// 获取当前的用户
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
LoginUser loginUser = SecurityUtils.getLoginUserNoException();
|
||||
// *========数据库日志=========*//
|
||||
SysOperLog operLog = new SysOperLog();
|
||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||
// 请求的地址
|
||||
String ip = ServletUtils.getClientIP(request);
|
||||
operLog.setOperIp(ip);
|
||||
operLog.setOperUrl(request.getRequestURI());
|
||||
// 设置请求方式
|
||||
operLog.setRequestMethod(request.getMethod());
|
||||
logString.append(String.format("ip=%s;",ip));
|
||||
if (loginUser != null) {
|
||||
logString.append(String.format("userName=%s;",loginUser.getUsername()));
|
||||
operLog.setOperName(loginUser.getUsername());
|
||||
}
|
||||
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
|
||||
logString.append(String.format("url=%s;method=%s;title=%s;",operLog.getOperUrl(),operLog.getRequestMethod(),controllerLog.title()));
|
||||
logString.append(String.format("userAgent=%s;agentCode=%s;appHeader=%s;",
|
||||
userAgent,
|
||||
AgentUtils.getAgentCode(request),appTypeFactory.getHeaderType()));
|
||||
if (e != null) {
|
||||
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
||||
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
|
||||
logString.append(String.format("exception=%s;",e.getMessage()));
|
||||
}
|
||||
// 设置方法名称
|
||||
String className = joinPoint.getTarget().getClass().getName();
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
operLog.setMethod(className + "." + methodName + "()");
|
||||
// 处理设置注解上的参数
|
||||
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult,logString);
|
||||
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
logString.append("StartTime:").append(dateFormat.format(new Date(startTime)));
|
||||
long endTime = System.currentTimeMillis();
|
||||
logString.append(";EndTime:").append(dateFormat.format(new Date(endTime)));
|
||||
logString.append(";CostTime:").append(endTime - startTime).append("ms");
|
||||
AsyncService bean = SpringUtils.getBean(AsyncService.class);
|
||||
if(controllerLog.isPrint()){
|
||||
bean.recordLog(logString);
|
||||
}
|
||||
if(controllerLog.isSaveDb()){
|
||||
bean.recordOper(operLog);
|
||||
}
|
||||
} catch (Exception exp) {
|
||||
// 记录本地异常日志
|
||||
log.error("==前置通知异常==");
|
||||
log.error("异常信息:{}", exp.getMessage());
|
||||
exp.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解中对方法的描述信息 用于Controller层注解
|
||||
*
|
||||
* @param log 日志
|
||||
* @param operLog 操作日志
|
||||
* @throws Exception
|
||||
*/
|
||||
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult,
|
||||
StringBuilder logString) throws Exception {
|
||||
// 设置action动作
|
||||
operLog.setBusinessType(log.businessType().ordinal());
|
||||
// 设置标题
|
||||
operLog.setTitle(log.title());
|
||||
// 设置操作人类别
|
||||
operLog.setOperatorType(log.operatorType().ordinal());
|
||||
// 是否需要保存request,参数和值
|
||||
if (log.isSaveRequestData()) {
|
||||
// 获取参数的信息,传入到数据库中。
|
||||
setRequestValue(joinPoint, operLog,logString);
|
||||
}
|
||||
// 是否需要保存response,参数和值
|
||||
if (log.isSaveResponseData() && jsonResult != null) {
|
||||
String jsonResultString = JSON.toJSONString(jsonResult);
|
||||
operLog.setJsonResult(StringUtils.substring(jsonResultString, 0, 2000));
|
||||
logString.append(String.format("result=%s;",jsonResultString));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的参数,放到log中
|
||||
*
|
||||
* @param operLog 操作日志
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog,StringBuilder logString) throws Exception {
|
||||
String requestMethod = operLog.getRequestMethod();
|
||||
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
|
||||
String params = argsArrayToString(joinPoint.getArgs());
|
||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
||||
logString.append(String.format("params=%s;",params));
|
||||
} else {
|
||||
Map<String, String[]> paramsMap = ServletUtils.getRequest().getParameterMap();
|
||||
String params = toMapString(paramsMap);
|
||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
||||
logString.append(String.format("params=%s;",params));
|
||||
}
|
||||
}
|
||||
|
||||
private String toMapString(Map<String, String[]> paramsMap){
|
||||
StringBuilder paramString = new StringBuilder();
|
||||
paramString.append("{");
|
||||
for(Map.Entry<String, String[]> paramEntry : paramsMap.entrySet()) {
|
||||
paramString.append(paramEntry.getKey()).append(":");
|
||||
if (Objects.nonNull(paramEntry.getValue())) {
|
||||
paramString.append(org.apache.commons.lang3.StringUtils.join(paramEntry.getValue(),","));
|
||||
}
|
||||
paramString.append(";");
|
||||
}
|
||||
paramString.append("}");
|
||||
return paramString.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数拼装
|
||||
*/
|
||||
private String argsArrayToString(Object[] paramsArray) {
|
||||
String params = "";
|
||||
if (paramsArray != null && paramsArray.length > 0) {
|
||||
for (Object o : paramsArray) {
|
||||
if (o != null && !isFilterObject(o)) {
|
||||
try {
|
||||
Object jsonObj = JSON.toJSONString(o);
|
||||
params += jsonObj + " ";
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要过滤的对象。
|
||||
*
|
||||
* @param o 对象信息。
|
||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean isFilterObject(final Object o) {
|
||||
Class<?> clazz = o.getClass();
|
||||
if (clazz.isArray()) {
|
||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
Collection collection = (Collection) o;
|
||||
for (Object value : collection) {
|
||||
return value instanceof MultipartFile;
|
||||
}
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
Map map = (Map) o;
|
||||
for (Object value : map.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) value;
|
||||
return entry.getValue() instanceof MultipartFile;
|
||||
}
|
||||
}
|
||||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
||||
|| o instanceof BindingResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.bashi.framework.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.math.Calculator;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 无符号计算生成器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class UnsignedMathGenerator implements CodeGenerator {
|
||||
|
||||
private static final long serialVersionUID = -5514819971774091076L;
|
||||
|
||||
private static final String operators = "+-*";
|
||||
|
||||
/**
|
||||
* 参与计算数字最大长度
|
||||
*/
|
||||
private final int numberLength;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public UnsignedMathGenerator() {
|
||||
this(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param numberLength 参与计算最大数字位数
|
||||
*/
|
||||
public UnsignedMathGenerator(int numberLength) {
|
||||
this.numberLength = numberLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
final int limit = getLimit();
|
||||
int min = RandomUtil.randomInt(limit);
|
||||
int max = RandomUtil.randomInt(min, limit);
|
||||
String number1 = Integer.toString(max);
|
||||
String number2 = Integer.toString(min);
|
||||
number1 = StrUtil.padAfter(number1, this.numberLength, CharUtil.SPACE);
|
||||
number2 = StrUtil.padAfter(number2, this.numberLength, CharUtil.SPACE);
|
||||
|
||||
return number1 + RandomUtil.randomChar(operators) + number2 + '=';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
int result;
|
||||
try {
|
||||
result = Integer.parseInt(userInputCode);
|
||||
} catch (NumberFormatException e) {
|
||||
// 用户输入非数字
|
||||
return false;
|
||||
}
|
||||
|
||||
final int calculateResult = (int) Calculator.conversion(code);
|
||||
return result == calculateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码长度
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.numberLength * 2 + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据长度获取参与计算数字最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
private int getLimit() {
|
||||
return Integer.parseInt("1" + StrUtil.repeat('0', this.numberLength));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import de.codecentric.boot.admin.server.config.EnableAdminServer;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
|
||||
import org.springframework.boot.task.TaskExecutorBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.thymeleaf.dialect.IDialect;
|
||||
import org.thymeleaf.spring5.ISpringTemplateEngine;
|
||||
import org.thymeleaf.spring5.SpringTemplateEngine;
|
||||
import org.thymeleaf.templateresolver.ITemplateResolver;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* springboot-admin server配置类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAdminServer
|
||||
public class AdminServerConfig {
|
||||
|
||||
@Lazy
|
||||
@Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||
@ConditionalOnMissingBean(Executor.class)
|
||||
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决 admin 与 项目 页面的交叉引用 将 admin 的路由放到最后
|
||||
* @param properties
|
||||
* @param templateResolvers
|
||||
* @param dialects
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
|
||||
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
|
||||
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
|
||||
SpringTemplateEngine engine = new SpringTemplateEngine();
|
||||
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
|
||||
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
|
||||
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
|
||||
dialects.orderedStream().forEach(engine::addDialect);
|
||||
Set<ITemplateResolver> templateResolvers1 = engine.getTemplateResolvers();
|
||||
templateResolvers1 = templateResolvers1.stream()
|
||||
.sorted(Comparator.comparing(ITemplateResolver::getOrder))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
engine.setTemplateResolvers(templateResolvers1);
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 程序注解配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
// 表示通过aop框架暴露该代理对象,AopContext能够访问
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
public class ApplicationConfig {
|
||||
/**
|
||||
* 时区配置
|
||||
*/
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
|
||||
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.bashi.common.exception.CustomException;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 异步配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
public class AsyncConfig extends AsyncConfigurerSupport {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("scheduledExecutorService")
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 异步执行需要使用权限框架自带的包装线程池 保证权限信息的传递
|
||||
*/
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
return new DelegatingSecurityContextExecutorService(scheduledExecutorService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行异常处理
|
||||
*/
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return (throwable, method, objects) -> {
|
||||
throwable.printStackTrace();
|
||||
throw new CustomException(
|
||||
"Exception message - " + throwable.getMessage()
|
||||
+ ", Method name - " + method.getName()
|
||||
+ ", Parameter value - " + Arrays.toString(objects));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import cn.hutool.captcha.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 验证码配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class CaptchaConfig {
|
||||
|
||||
private final int width = 160;
|
||||
private final int height = 60;
|
||||
private final Color background = Color.PINK;
|
||||
private final Font font = new Font("Arial", Font.BOLD, 48);
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*/
|
||||
@Bean(name = "CircleCaptcha")
|
||||
public CircleCaptcha getCircleCaptcha() {
|
||||
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(width, height);
|
||||
captcha.setBackground(background);
|
||||
captcha.setFont(font);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线段干扰的验证码
|
||||
*/
|
||||
@Bean(name = "LineCaptcha")
|
||||
public LineCaptcha getLineCaptcha() {
|
||||
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(width, height);
|
||||
captcha.setBackground(background);
|
||||
captcha.setFont(font);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*/
|
||||
@Bean(name = "ShearCaptcha")
|
||||
public ShearCaptcha getShearCaptcha() {
|
||||
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(width, height);
|
||||
captcha.setBackground(background);
|
||||
captcha.setFont(font);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
|
||||
import com.alibaba.druid.util.Utils;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.*;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* druid 配置多数据源
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Configuration
|
||||
public class DruidConfig {
|
||||
|
||||
/**
|
||||
* 去除监控页面底部的广告
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
|
||||
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
|
||||
{
|
||||
// 获取web监控页面的参数
|
||||
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
|
||||
// 提取common.js的配置路径
|
||||
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
|
||||
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
|
||||
final String filePath = "support/http/resources/js/common.js";
|
||||
// 创建filter进行过滤
|
||||
Filter filter = new Filter()
|
||||
{
|
||||
@Override
|
||||
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
|
||||
{
|
||||
}
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
chain.doFilter(request, response);
|
||||
// 重置缓冲区,响应头不会被重置
|
||||
// response.resetBuffer();
|
||||
// 获取common.js
|
||||
String text = Utils.readFromResource(filePath);
|
||||
// 正则替换banner, 除去底部的广告信息
|
||||
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
|
||||
text = text.replaceAll("powered.*?shrek.wang</a>", "");
|
||||
response.getWriter().write(text);
|
||||
}
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
}
|
||||
};
|
||||
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
|
||||
registrationBean.setFilter(filter);
|
||||
registrationBean.addUrlPatterns(commonJsPattern);
|
||||
return registrationBean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import feign.*;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* openfeign配置类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@EnableFeignClients("${feign.package}")
|
||||
@Configuration
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
public class FeignConfig {
|
||||
|
||||
@Bean
|
||||
public OkHttpClient okHttpClient(){
|
||||
return new OkHttpClient.Builder()
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(120, TimeUnit.SECONDS)
|
||||
.connectionPool(new ConnectionPool())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Contract feignContract() {
|
||||
return new SpringMvcContract();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Logger.Level feignLoggerLevel() {
|
||||
return Logger.Level.BASIC;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Request.Options feignRequestOptions() {
|
||||
return new Request.Options(10, TimeUnit.SECONDS, 60,TimeUnit.SECONDS,true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Retryer feignRetry() {
|
||||
return new Retryer.Default();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bashi.common.filter.RepeatableFilter;
|
||||
import com.bashi.common.filter.XssFilter;
|
||||
import com.bashi.framework.config.properties.XssProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Filter配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class FilterConfig {
|
||||
|
||||
@Autowired
|
||||
private XssProperties xssProperties;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Bean
|
||||
public FilterRegistrationBean xssFilterRegistration() {
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setDispatcherTypes(DispatcherType.REQUEST);
|
||||
registration.setFilter(new XssFilter());
|
||||
registration.addUrlPatterns(StrUtil.splitToArray(xssProperties.getUrlPatterns(), ","));
|
||||
registration.setName("xssFilter");
|
||||
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
|
||||
Map<String, String> initParameters = new HashMap<String, String>();
|
||||
initParameters.put("excludes", xssProperties.getExcludes());
|
||||
initParameters.put("enabled", xssProperties.getEnabled());
|
||||
registration.setInitParameters(initParameters);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Bean
|
||||
public FilterRegistrationBean someFilterRegistration() {
|
||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||
registration.setFilter(new RepeatableFilter());
|
||||
registration.addUrlPatterns("/*");
|
||||
registration.setName("repeatableFilter");
|
||||
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
|
||||
return registration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* jackson 配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public BeanPostProcessor objectMapperBeanPostProcessor() {
|
||||
return new BeanPostProcessor() {
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (!(bean instanceof ObjectMapper)) {
|
||||
return bean;
|
||||
}
|
||||
ObjectMapper objectMapper = (ObjectMapper) bean;
|
||||
// 全局配置序列化返回 JSON 处理
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
simpleModule.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE);
|
||||
simpleModule.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
|
||||
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
objectMapper.registerModule(simpleModule);
|
||||
objectMapper.setTimeZone(TimeZone.getDefault());
|
||||
JsonUtils.init(objectMapper);
|
||||
log.info("初始化 jackson 配置");
|
||||
return bean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.bashi.common.core.mybatisplus.methods.InsertAll;
|
||||
import com.bashi.framework.mybatisplus.CreateAndUpdateMetaObjectHandler;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* mybatis-plus配置类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@EnableTransactionManagement(proxyTargetClass = true)
|
||||
@Configuration
|
||||
// 指定要扫描的Mapper类的包的路径
|
||||
@MapperScan("${mybatis-plus.mapperPackage}")
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor());
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
|
||||
// 阻断插件
|
||||
// interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页插件,自动识别数据库类型
|
||||
* https://baomidou.com/guide/interceptor-pagination.html
|
||||
*/
|
||||
public PaginationInnerInterceptor paginationInnerInterceptor() {
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
|
||||
// 设置数据库类型为mysql
|
||||
paginationInnerInterceptor.setDbType(DbType.MYSQL);
|
||||
// 设置最大单页限制数量,默认 500 条,-1 不受限制
|
||||
paginationInnerInterceptor.setMaxLimit(-1L);
|
||||
return paginationInnerInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐观锁插件
|
||||
* https://baomidou.com/guide/interceptor-optimistic-locker.html
|
||||
*/
|
||||
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
|
||||
return new OptimisticLockerInnerInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果是对全表的删除或更新操作,就会终止该操作
|
||||
* https://baomidou.com/guide/interceptor-block-attack.html
|
||||
*/
|
||||
// public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
|
||||
// return new BlockAttackInnerInterceptor();
|
||||
// }
|
||||
|
||||
/**
|
||||
* sql性能规范插件(垃圾SQL拦截)
|
||||
* 如有需要可以启用
|
||||
*/
|
||||
// public IllegalSQLInnerInterceptor illegalSQLInnerInterceptor() {
|
||||
// return new IllegalSQLInnerInterceptor();
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 元对象字段填充控制器
|
||||
* https://baomidou.com/guide/auto-fill-metainfo.html
|
||||
*/
|
||||
@Bean
|
||||
public MetaObjectHandler metaObjectHandler() {
|
||||
return new CreateAndUpdateMetaObjectHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* sql注入器配置
|
||||
* https://baomidou.com/guide/sql-injector.html
|
||||
*/
|
||||
@Bean
|
||||
public ISqlInjector sqlInjector() {
|
||||
return new DefaultSqlInjector() {
|
||||
@Override
|
||||
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
|
||||
List<AbstractMethod> methodList = super.getMethodList(mapperClass,tableInfo);
|
||||
methodList.add(new InsertAll());
|
||||
return methodList;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bashi.framework.config.properties.RedissonProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.codec.JsonJacksonCodec;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.spring.cache.CacheConfig;
|
||||
import org.redisson.spring.cache.RedissonSpringCacheManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* redis配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig extends CachingConfigurerSupport {
|
||||
|
||||
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
|
||||
private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
|
||||
|
||||
@Autowired
|
||||
private RedisProperties redisProperties;
|
||||
|
||||
@Autowired
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
@ConditionalOnMissingBean(RedissonClient.class)
|
||||
public RedissonClient redisson() throws IOException {
|
||||
String prefix = REDIS_PROTOCOL_PREFIX;
|
||||
if (redisProperties.isSsl()) {
|
||||
prefix = REDISS_PROTOCOL_PREFIX;
|
||||
}
|
||||
Config config = new Config();
|
||||
config.setThreads(redissonProperties.getThreads())
|
||||
.setNettyThreads(redissonProperties.getNettyThreads())
|
||||
.setCodec(JsonJacksonCodec.INSTANCE)
|
||||
.setTransportMode(redissonProperties.getTransportMode());
|
||||
|
||||
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
// 解决jackson2无法反序列化LocalDateTime的问题
|
||||
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
JavaTimeModule timeModule = new JavaTimeModule();
|
||||
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
om.registerModule(timeModule);
|
||||
config.setCodec(new JsonJacksonCodec(om));
|
||||
// 使用单机模式
|
||||
config.useSingleServer()
|
||||
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
|
||||
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
|
||||
.setDatabase(redisProperties.getDatabase())
|
||||
.setPassword(StrUtil.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
|
||||
.setTimeout(singleServerConfig.getTimeout())
|
||||
.setRetryAttempts(singleServerConfig.getRetryAttempts())
|
||||
.setRetryInterval(singleServerConfig.getRetryInterval())
|
||||
.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
|
||||
.setClientName(singleServerConfig.getClientName())
|
||||
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
|
||||
.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
|
||||
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
|
||||
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
|
||||
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
|
||||
.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
|
||||
return Redisson.create(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 整合spring-cache
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedissonClient redissonClient) {
|
||||
Map<String, CacheConfig> config = new HashMap<>();
|
||||
config.put("redissonCacheMap", new CacheConfig(30*60*1000, 10*60*1000));
|
||||
return new RedissonSpringCacheManager(redissonClient, config, JsonJacksonCodec.INSTANCE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import com.bashi.common.config.BsConfig;
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.framework.interceptor.RepeatSubmitInterceptor;
|
||||
|
||||
/**
|
||||
* 通用配置
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Configuration
|
||||
public class ResourcesConfig implements WebMvcConfigurer
|
||||
{
|
||||
@Autowired
|
||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
||||
{
|
||||
/** 本地文件上传路径 */
|
||||
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + BsConfig.getProfile() + "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义拦截规则
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry)
|
||||
{
|
||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*/
|
||||
@Bean
|
||||
public CorsFilter corsFilter()
|
||||
{
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
// 设置访问源地址
|
||||
config.addAllowedOriginPattern("*");
|
||||
// 设置访问源请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 设置访问源请求方法
|
||||
config.addAllowedMethod("*");
|
||||
// 对接口配置跨域设置
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.bashi.framework.security.filter.JwtAuthenticationTokenFilter;
|
||||
import com.bashi.framework.security.handle.AuthenticationEntryPointImpl;
|
||||
import com.bashi.framework.security.handle.LogoutSuccessHandlerImpl;
|
||||
import de.codecentric.boot.admin.server.config.AdminServerProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* spring security配置
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
@EnableWebSecurity(debug = false)
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter
|
||||
{
|
||||
/**
|
||||
* 自定义用户认证逻辑
|
||||
*/
|
||||
@Resource
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
/**
|
||||
* 认证失败处理类
|
||||
*/
|
||||
@Autowired
|
||||
private AuthenticationEntryPointImpl unauthorizedHandler;
|
||||
|
||||
/**
|
||||
* 退出处理类
|
||||
*/
|
||||
@Autowired
|
||||
private LogoutSuccessHandlerImpl logoutSuccessHandler;
|
||||
|
||||
/**
|
||||
* token认证过滤器
|
||||
*/
|
||||
@Autowired
|
||||
private JwtAuthenticationTokenFilter authenticationTokenFilter;
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
*/
|
||||
@Autowired
|
||||
private CorsFilter corsFilter;
|
||||
|
||||
@Autowired
|
||||
private AdminServerProperties adminServerProperties;
|
||||
|
||||
@Autowired
|
||||
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
|
||||
|
||||
/**
|
||||
* 解决 无法直接注入 AuthenticationManager
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Bean
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception
|
||||
{
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* anyRequest | 匹配所有请求路径
|
||||
* access | SpringEl表达式结果为true时可以访问
|
||||
* anonymous | 匿名可以访问
|
||||
* denyAll | 用户不能访问
|
||||
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
|
||||
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
|
||||
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
|
||||
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
|
||||
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
|
||||
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
|
||||
* permitAll | 用户可以任意访问
|
||||
* rememberMe | 允许通过remember-me登录的用户访问
|
||||
* authenticated | 用户登录后可访问
|
||||
*/
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception
|
||||
{
|
||||
httpSecurity
|
||||
// CSRF禁用,因为不使用session
|
||||
.csrf().disable()
|
||||
// 认证失败处理类
|
||||
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
|
||||
// 基于token,所以不需要session
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
// 过滤请求
|
||||
.authorizeRequests()
|
||||
// 对于登录login 验证码captchaImage 允许匿名访问
|
||||
.antMatchers("/login", "/captchaImage",
|
||||
"/customer/login","/customer/open/**"
|
||||
).anonymous()
|
||||
.antMatchers(
|
||||
HttpMethod.GET,
|
||||
"/*.html",
|
||||
"/**/*.html",
|
||||
"/**/*.css",
|
||||
"/download/**",
|
||||
"/**/*.js"
|
||||
).permitAll()
|
||||
.antMatchers("/ws/**").permitAll()
|
||||
.antMatchers("/open/common/upload").permitAll()
|
||||
.antMatchers("/app/home/**").permitAll()
|
||||
.antMatchers("/pay/notify/**").permitAll()
|
||||
.antMatchers("/profile/**").anonymous()
|
||||
.antMatchers("/common/download**").anonymous()
|
||||
.antMatchers("/common/download/resource**").anonymous()
|
||||
.antMatchers("/doc.html").anonymous()
|
||||
.antMatchers("/swagger-resources/**").anonymous()
|
||||
.antMatchers("/webjars/**").anonymous()
|
||||
.antMatchers("/*/api-docs").anonymous()
|
||||
// .antMatchers("/druid/**").anonymous()
|
||||
// Spring Boot Admin Server 的安全配置
|
||||
.antMatchers(adminServerProperties.getContextPath()).anonymous()
|
||||
.antMatchers(adminServerProperties.getContextPath() + "/**").anonymous()
|
||||
// Spring Boot Actuator 的安全配置
|
||||
// .antMatchers("/actuator").anonymous()
|
||||
// .antMatchers("/actuator/**").anonymous()
|
||||
// 除上面外的所有请求全部需要鉴权认证
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.headers().frameOptions().disable()
|
||||
.and()
|
||||
.apply(smsCodeAuthenticationSecurityConfig);
|
||||
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
|
||||
// 添加JWT filter
|
||||
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
// 添加CORS filter
|
||||
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
|
||||
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 强散列哈希加密实现
|
||||
*/
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder()
|
||||
{
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份认证接口
|
||||
*/
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception
|
||||
{
|
||||
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 服务相关配置
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Component
|
||||
public class ServerConfig
|
||||
{
|
||||
/**
|
||||
* 获取完整的请求路径,包括:域名,端口,上下文访问路径
|
||||
*
|
||||
* @return 服务地址
|
||||
*/
|
||||
public String getUrl()
|
||||
{
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
return getDomain(request);
|
||||
}
|
||||
|
||||
public static String getDomain(HttpServletRequest request)
|
||||
{
|
||||
StringBuffer url = request.getRequestURL();
|
||||
String contextPath = request.getServletContext().getContextPath();
|
||||
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.bashi.framework.security.sms.*;
|
||||
import com.bashi.framework.web.service.CodeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/13</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
@Component
|
||||
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
|
||||
|
||||
@Autowired
|
||||
private IDuteUserDetailsService userDetailsService;
|
||||
@Autowired
|
||||
private CodeService codeService;
|
||||
@Autowired
|
||||
private SmsAuthenticationSuccessHandler smsAuthenticationSuccessHandler;
|
||||
@Autowired
|
||||
private SmsAuthenticationFailureHandler smsAuthenticationFailureHandler;
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) {
|
||||
// 手机号登陆
|
||||
SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();
|
||||
smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
|
||||
smsAuthenticationFilter.setAuthenticationSuccessHandler(smsAuthenticationSuccessHandler);
|
||||
smsAuthenticationFilter.setAuthenticationFailureHandler(smsAuthenticationFailureHandler);
|
||||
smsAuthenticationFilter.setCodeService(codeService);
|
||||
|
||||
SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
|
||||
smsAuthenticationProvider.setUserDetailsService(userDetailsService);
|
||||
smsAuthenticationProvider.setCodeService(codeService);
|
||||
|
||||
http.authenticationProvider(smsAuthenticationProvider)
|
||||
.addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||
import com.bashi.framework.config.properties.SwaggerProperties;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.models.auth.In;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.*;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Swagger 文档配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
@EnableKnife4j
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Autowired
|
||||
private SwaggerProperties swaggerProperties;
|
||||
|
||||
/**
|
||||
* 创建API
|
||||
*/
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.enable(swaggerProperties.getEnabled())
|
||||
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
|
||||
.apiInfo(apiInfo())
|
||||
// 设置哪些接口暴露给Swagger展示
|
||||
.select()
|
||||
// 扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
|
||||
// 扫描指定包中的swagger注解
|
||||
// .apis(RequestHandlerSelectors.basePackage("com.bashi.project.tool.swagger"))
|
||||
// 扫描所有 .apis(RequestHandlerSelectors.any())
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
/* 设置安全模式,swagger可以设置访问token */
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts())
|
||||
.pathMapping(swaggerProperties.getPathMapping());
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全模式,这里指定token通过Authorization头请求头传递
|
||||
*/
|
||||
private List<SecurityScheme> securitySchemes() {
|
||||
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
|
||||
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
|
||||
return apiKeyList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全上下文
|
||||
*/
|
||||
private List<SecurityContext> securityContexts() {
|
||||
List<SecurityContext> securityContexts = new ArrayList<>();
|
||||
securityContexts.add(
|
||||
SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
|
||||
.build());
|
||||
return securityContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的安全上引用
|
||||
*/
|
||||
private List<SecurityReference> defaultAuth() {
|
||||
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||
authorizationScopes[0] = authorizationScope;
|
||||
List<SecurityReference> securityReferences = new ArrayList<>();
|
||||
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
|
||||
return securityReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加摘要信息
|
||||
*/
|
||||
private ApiInfo apiInfo() {
|
||||
// 用ApiInfoBuilder进行定制
|
||||
SwaggerProperties.Contact contact = swaggerProperties.getContact();
|
||||
return new ApiInfoBuilder()
|
||||
// 设置标题
|
||||
.title(swaggerProperties.getTitle())
|
||||
// 描述
|
||||
.description(swaggerProperties.getDescription())
|
||||
// 作者信息
|
||||
.contact(new Contact(contact.getName(), contact.getUrl(), contact.getEmail()))
|
||||
// 版本
|
||||
.version(swaggerProperties.getVersion())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.bashi.common.utils.Threads;
|
||||
import com.bashi.framework.config.properties.ThreadPoolProperties;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 线程池配置
|
||||
*
|
||||
* @author Lion Li
|
||||
**/
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
@Autowired
|
||||
private ThreadPoolProperties threadPoolProperties;
|
||||
|
||||
@Bean("threadPoolTaskExecutor")
|
||||
@ConditionalOnProperty(prefix = "threadPoolTaskExecutor", name = "enabled", havingValue = "true")
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
|
||||
executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
|
||||
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
|
||||
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
|
||||
executor.setThreadNamePrefix("new-591-");
|
||||
RejectedExecutionHandler handler;
|
||||
switch (threadPoolProperties.getRejectedExecutionHandler()) {
|
||||
case "CallerRunsPolicy":
|
||||
handler = new ThreadPoolExecutor.CallerRunsPolicy();
|
||||
break;
|
||||
case "DiscardOldestPolicy":
|
||||
handler = new ThreadPoolExecutor.DiscardOldestPolicy();
|
||||
break;
|
||||
case "DiscardPolicy":
|
||||
handler = new ThreadPoolExecutor.DiscardPolicy();
|
||||
break;
|
||||
default:
|
||||
handler = new ThreadPoolExecutor.AbortPolicy();
|
||||
break;
|
||||
}
|
||||
executor.setRejectedExecutionHandler(handler);
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行周期性或定时任务
|
||||
*/
|
||||
@Bean(name = "scheduledExecutorService")
|
||||
protected ScheduledExecutorService scheduledExecutorService() {
|
||||
return new ScheduledThreadPoolExecutor(threadPoolProperties.getCorePoolSize(),
|
||||
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()) {
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
Threads.printException(r, t);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import org.hibernate.validator.HibernateValidator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
|
||||
/**
|
||||
* 校验框架配置类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class ValidatorConfig {
|
||||
|
||||
/**
|
||||
* 配置校验框架 快速返回模式
|
||||
*/
|
||||
@Bean
|
||||
public Validator validator() {
|
||||
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
|
||||
.configure()
|
||||
.failFast(true)
|
||||
.buildValidatorFactory();
|
||||
return validatorFactory.getValidator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
@Configuration
|
||||
public class WebSocketConfig {
|
||||
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter(){
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import com.bashi.framework.dto.WebSocketMessageDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnError;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.PathParam;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ServerEndpoint("/ws/{uuid}")
|
||||
@Component
|
||||
@Slf4j
|
||||
public class WebSocketServer {
|
||||
|
||||
/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
|
||||
private static int onlineCount = 0;
|
||||
/**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
|
||||
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
|
||||
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
|
||||
private Session session;
|
||||
/**接收userId*/
|
||||
private String uuidKey ="";
|
||||
|
||||
/**
|
||||
* 连接建立成功调用的方法*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session,@PathParam("uuid") String uuid) {
|
||||
this.session = session;
|
||||
this.uuidKey =uuid;
|
||||
if(webSocketMap.containsKey(uuid)){
|
||||
webSocketMap.remove(uuid);
|
||||
webSocketMap.put(uuid,this);
|
||||
//加入set中
|
||||
}else{
|
||||
webSocketMap.put(uuid,this);
|
||||
//加入set中
|
||||
addOnlineCount();
|
||||
//在线数加1
|
||||
}
|
||||
|
||||
log.info("用户连接:"+uuid+",当前在线人数为:" + getOnlineCount());
|
||||
|
||||
try {
|
||||
sendMessage("连接成功");
|
||||
} catch (IOException e) {
|
||||
log.error("用户:"+uuid+",网络异常!!!!!!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接关闭调用的方法
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose() {
|
||||
if(webSocketMap.containsKey(uuidKey)){
|
||||
webSocketMap.remove(uuidKey);
|
||||
//从set中删除
|
||||
subOnlineCount();
|
||||
}
|
||||
log.info("用户退出:"+ uuidKey +",当前在线人数为:" + getOnlineCount());
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 收到客户端消息后调用的方法
|
||||
// *
|
||||
// * @param message 客户端发送过来的消息*/
|
||||
// @OnMessage
|
||||
// public void onMessage(String message, Session session) {
|
||||
// log.info("用户消息:"+userId+",报文:"+message);
|
||||
// //可以群发消息
|
||||
// //消息保存到数据库、redis
|
||||
// if(StringUtils.isNotBlank(message)){
|
||||
// try {
|
||||
// //解析发送的报文
|
||||
// JSONObject jsonObject = JSON.parseObject(message);
|
||||
// //追加发送人(防止串改)
|
||||
// jsonObject.put("fromUserId",this.userId);
|
||||
// String toUserId=jsonObject.getString("toUserId");
|
||||
// //传送给对应toUserId用户的websocket
|
||||
// if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
|
||||
// webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
|
||||
// }else{
|
||||
// log.error("请求的userId:"+toUserId+"不在该服务器上");
|
||||
// //否则不在这个服务器上,发送到mysql或者redis
|
||||
// }
|
||||
// }catch (Exception e){
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
*
|
||||
* @param session
|
||||
* @param error
|
||||
*/
|
||||
@OnError
|
||||
public void onError(Session session, Throwable error) {
|
||||
log.error("用户错误:"+this.uuidKey +",原因:"+error.getMessage());
|
||||
error.printStackTrace();
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendMessage(String message) throws IOException {
|
||||
this.session.getBasicRemote().sendText(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送自定义消息
|
||||
* */
|
||||
/*public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
|
||||
log.info("发送消息到:"+userId+",报文:"+message);
|
||||
if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
|
||||
webSocketMap.get(userId).sendMessage(message);
|
||||
}else{
|
||||
log.error("用户"+userId+",不在线!");
|
||||
}
|
||||
}*/
|
||||
|
||||
public static void sendInfoAll(WebSocketMessageDTO message) throws IOException {
|
||||
for (WebSocketServer value : webSocketMap.values()) {
|
||||
value.sendMessage(JsonUtils.toJsonString(message));
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized int getOnlineCount() {
|
||||
return onlineCount;
|
||||
}
|
||||
|
||||
public static synchronized void addOnlineCount() {
|
||||
WebSocketServer.onlineCount++;
|
||||
}
|
||||
|
||||
public static synchronized void subOnlineCount() {
|
||||
WebSocketServer.onlineCount--;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.bashi.framework.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.websocket.OnClose;
|
||||
import javax.websocket.OnError;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.PathParam;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
//@ServerEndpoint("/ws/{userId}")
|
||||
//@Component
|
||||
@Slf4j
|
||||
public class WebSocketServerDemo {
|
||||
|
||||
/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
|
||||
private static int onlineCount = 0;
|
||||
/**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
|
||||
private static ConcurrentHashMap<String, WebSocketServerDemo> webSocketMap = new ConcurrentHashMap<>();
|
||||
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
|
||||
private Session session;
|
||||
/**接收userId*/
|
||||
private String userId="";
|
||||
|
||||
/**
|
||||
* 连接建立成功调用的方法*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session,@PathParam("userId") String userId) {
|
||||
this.session = session;
|
||||
this.userId=userId;
|
||||
if(webSocketMap.containsKey(userId)){
|
||||
webSocketMap.remove(userId);
|
||||
webSocketMap.put(userId,this);
|
||||
//加入set中
|
||||
}else{
|
||||
webSocketMap.put(userId,this);
|
||||
//加入set中
|
||||
addOnlineCount();
|
||||
//在线数加1
|
||||
}
|
||||
|
||||
log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
|
||||
|
||||
try {
|
||||
sendMessage("连接成功");
|
||||
} catch (IOException e) {
|
||||
log.error("用户:"+userId+",网络异常!!!!!!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接关闭调用的方法
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose() {
|
||||
if(webSocketMap.containsKey(userId)){
|
||||
webSocketMap.remove(userId);
|
||||
//从set中删除
|
||||
subOnlineCount();
|
||||
}
|
||||
log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 收到客户端消息后调用的方法
|
||||
// *
|
||||
// * @param message 客户端发送过来的消息*/
|
||||
// @OnMessage
|
||||
// public void onMessage(String message, Session session) {
|
||||
// log.info("用户消息:"+userId+",报文:"+message);
|
||||
// //可以群发消息
|
||||
// //消息保存到数据库、redis
|
||||
// if(StringUtils.isNotBlank(message)){
|
||||
// try {
|
||||
// //解析发送的报文
|
||||
// JSONObject jsonObject = JSON.parseObject(message);
|
||||
// //追加发送人(防止串改)
|
||||
// jsonObject.put("fromUserId",this.userId);
|
||||
// String toUserId=jsonObject.getString("toUserId");
|
||||
// //传送给对应toUserId用户的websocket
|
||||
// if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
|
||||
// webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
|
||||
// }else{
|
||||
// log.error("请求的userId:"+toUserId+"不在该服务器上");
|
||||
// //否则不在这个服务器上,发送到mysql或者redis
|
||||
// }
|
||||
// }catch (Exception e){
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
*
|
||||
* @param session
|
||||
* @param error
|
||||
*/
|
||||
@OnError
|
||||
public void onError(Session session, Throwable error) {
|
||||
log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
|
||||
error.printStackTrace();
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现服务器主动推送
|
||||
*/
|
||||
public void sendMessage(String message) throws IOException {
|
||||
this.session.getBasicRemote().sendText(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送自定义消息
|
||||
* */
|
||||
public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
|
||||
log.info("发送消息到:"+userId+",报文:"+message);
|
||||
if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
|
||||
webSocketMap.get(userId).sendMessage(message);
|
||||
}else{
|
||||
log.error("用户"+userId+",不在线!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendInfoAll(String message) throws IOException {
|
||||
for (WebSocketServerDemo value : webSocketMap.values()) {
|
||||
value.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized int getOnlineCount() {
|
||||
return onlineCount;
|
||||
}
|
||||
|
||||
public static synchronized void addOnlineCount() {
|
||||
WebSocketServerDemo.onlineCount++;
|
||||
}
|
||||
|
||||
public static synchronized void subOnlineCount() {
|
||||
WebSocketServerDemo.onlineCount--;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bashi.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 验证码 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "captcha")
|
||||
public class CaptchaProperties {
|
||||
|
||||
/**
|
||||
* 验证码开关
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 验证码类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 验证码类别
|
||||
*/
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* 数字验证码位数
|
||||
*/
|
||||
private Integer numberLength;
|
||||
|
||||
/**
|
||||
* 字符验证码长度
|
||||
*/
|
||||
private Integer charLength;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.bashi.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.redisson.config.TransportMode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Redisson 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* 线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int threads;
|
||||
|
||||
/**
|
||||
* Netty线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int nettyThreads;
|
||||
|
||||
/**
|
||||
* 传输模式
|
||||
*/
|
||||
private TransportMode transportMode;
|
||||
|
||||
/**
|
||||
* 单机服务配置
|
||||
*/
|
||||
private SingleServerConfig singleServerConfig;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class SingleServerConfig {
|
||||
|
||||
/**
|
||||
* 客户端名称
|
||||
*/
|
||||
private String clientName;
|
||||
|
||||
/**
|
||||
* 最小空闲连接数
|
||||
*/
|
||||
private int connectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* 连接池大小
|
||||
*/
|
||||
private int connectionPoolSize;
|
||||
|
||||
/**
|
||||
* 连接空闲超时,单位:毫秒
|
||||
*/
|
||||
private int idleConnectionTimeout;
|
||||
|
||||
/**
|
||||
* 命令等待超时,单位:毫秒
|
||||
*/
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
|
||||
*/
|
||||
private int retryAttempts;
|
||||
|
||||
/**
|
||||
* 命令重试发送时间间隔,单位:毫秒
|
||||
*/
|
||||
private int retryInterval;
|
||||
|
||||
/**
|
||||
* 发布和订阅连接的最小空闲连接数
|
||||
*/
|
||||
private int subscriptionConnectionMinimumIdleSize;
|
||||
|
||||
/**
|
||||
* 发布和订阅连接池大小
|
||||
*/
|
||||
private int subscriptionConnectionPoolSize;
|
||||
|
||||
/**
|
||||
* 单个连接最大订阅数量
|
||||
*/
|
||||
private int subscriptionsPerConnection;
|
||||
|
||||
/**
|
||||
* DNS监测时间间隔,单位:毫秒
|
||||
*/
|
||||
private int dnsMonitoringInterval;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.bashi.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* swagger 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "swagger")
|
||||
public class SwaggerProperties {
|
||||
|
||||
/**
|
||||
* 验证码类型
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 设置请求的统一前缀
|
||||
*/
|
||||
private String pathMapping;
|
||||
/**
|
||||
* 验证码类别
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 数字验证码位数
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 字符验证码长度
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 联系方式
|
||||
*/
|
||||
private Contact contact;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class Contact{
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
**/
|
||||
private String name;
|
||||
/**
|
||||
* 联系人url
|
||||
**/
|
||||
private String url;
|
||||
/**
|
||||
* 联系人email
|
||||
**/
|
||||
private String email;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.bashi.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 线程池 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "thread-pool")
|
||||
public class ThreadPoolProperties {
|
||||
|
||||
/**
|
||||
* 是否开启线程池
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* 核心线程池大小
|
||||
*/
|
||||
private int corePoolSize;
|
||||
|
||||
/**
|
||||
* 最大可创建的线程数
|
||||
*/
|
||||
private int maxPoolSize;
|
||||
|
||||
/**
|
||||
* 队列最大长度
|
||||
*/
|
||||
private int queueCapacity;
|
||||
|
||||
/**
|
||||
* 线程池维护线程所允许的空闲时间
|
||||
*/
|
||||
private int keepAliveSeconds;
|
||||
|
||||
/**
|
||||
* 线程池对拒绝任务(无线程可用)的处理策略
|
||||
*/
|
||||
private String rejectedExecutionHandler;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.bashi.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* token 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "token")
|
||||
public class TokenProperties {
|
||||
|
||||
/**
|
||||
* 令牌自定义标识
|
||||
*/
|
||||
private String header;
|
||||
|
||||
/**
|
||||
* 令牌秘钥
|
||||
*/
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* 令牌有效期(默认30分钟)
|
||||
*/
|
||||
private int expireTime;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.bashi.framework.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* xss过滤 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "xss")
|
||||
public class XssProperties {
|
||||
|
||||
/**
|
||||
* 过滤开关
|
||||
*/
|
||||
private String enabled;
|
||||
|
||||
/**
|
||||
* 排除链接(多个用逗号分隔)
|
||||
*/
|
||||
private String excludes;
|
||||
|
||||
/**
|
||||
* 匹配链接
|
||||
*/
|
||||
private String urlPatterns;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.bashi.framework.constant;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/15</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
public enum CodeType {
|
||||
|
||||
LOGIN,
|
||||
JOIN_MECHANIC,
|
||||
JOIN_SHOP,
|
||||
CUSTOMER_REGISTER,
|
||||
CUSTOMER_FORGET_PASSWORD,
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.bashi.framework.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WebSocketMessageDTO {
|
||||
|
||||
private WebSocketMessageType type = WebSocketMessageType.ORDER_NOTIFY;
|
||||
private String city;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bashi.framework.dto;
|
||||
|
||||
public enum WebSocketMessageType {
|
||||
ORDER_NOTIFY
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bashi.framework.interceptor;
|
||||
|
||||
import com.bashi.common.annotation.RepeatSubmit;
|
||||
import com.bashi.common.core.domain.AjaxResult;
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 防止重复提交拦截器
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Component
|
||||
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
|
||||
{
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
|
||||
{
|
||||
if (handler instanceof HandlerMethod)
|
||||
{
|
||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||
Method method = handlerMethod.getMethod();
|
||||
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
|
||||
if (annotation != null)
|
||||
{
|
||||
if (this.isRepeatSubmit(request))
|
||||
{
|
||||
AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
|
||||
ServletUtils.renderString(response, JsonUtils.toJsonString(ajaxResult));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.preHandle(request, response, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否重复提交由子类实现具体的防重复提交的规则
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract boolean isRepeatSubmit(HttpServletRequest request);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.bashi.framework.interceptor.impl;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.common.core.redis.RedisCache;
|
||||
import com.bashi.common.filter.RepeatedlyRequestWrapper;
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import com.bashi.framework.interceptor.RepeatSubmitInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 判断请求url和数据是否和上一次相同,
|
||||
* 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
|
||||
{
|
||||
public final String REPEAT_PARAMS = "repeatParams";
|
||||
|
||||
public final String REPEAT_TIME = "repeatTime";
|
||||
|
||||
// 令牌自定义标识
|
||||
@Value("${token.header}")
|
||||
private String header;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 间隔时间,单位:秒 默认10秒
|
||||
*
|
||||
* 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
|
||||
*/
|
||||
private int intervalTime = 10;
|
||||
|
||||
public void setIntervalTime(int intervalTime)
|
||||
{
|
||||
this.intervalTime = intervalTime;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean isRepeatSubmit(HttpServletRequest request)
|
||||
{
|
||||
String nowParams = "";
|
||||
if (request instanceof RepeatedlyRequestWrapper)
|
||||
{
|
||||
RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
|
||||
try {
|
||||
nowParams = IoUtil.readUtf8(repeatedlyRequest.getInputStream());
|
||||
} catch (IOException e) {
|
||||
log.warn("读取流出现问题!");
|
||||
}
|
||||
}
|
||||
|
||||
// body参数为空,获取Parameter的数据
|
||||
if (Validator.isEmpty(nowParams))
|
||||
{
|
||||
nowParams = JsonUtils.toJsonString(request.getParameterMap());
|
||||
}
|
||||
Map<String, Object> nowDataMap = new HashMap<String, Object>();
|
||||
nowDataMap.put(REPEAT_PARAMS, nowParams);
|
||||
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
|
||||
|
||||
// 请求地址(作为存放cache的key值)
|
||||
String url = request.getRequestURI();
|
||||
|
||||
// 唯一值(没有消息头则使用请求地址)
|
||||
String submitKey = request.getHeader(header);
|
||||
if (Validator.isEmpty(submitKey))
|
||||
{
|
||||
submitKey = url;
|
||||
}
|
||||
|
||||
// 唯一标识(指定key + 消息头)
|
||||
String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
|
||||
|
||||
Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
|
||||
if (sessionObj != null)
|
||||
{
|
||||
Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
|
||||
if (sessionMap.containsKey(url))
|
||||
{
|
||||
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
|
||||
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String, Object> cacheMap = new HashMap<String, Object>();
|
||||
cacheMap.put(url, nowDataMap);
|
||||
redisCache.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断参数是否相同
|
||||
*/
|
||||
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
|
||||
{
|
||||
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
|
||||
String preParams = (String) preMap.get(REPEAT_PARAMS);
|
||||
return nowParams.equals(preParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两次间隔时间
|
||||
*/
|
||||
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
|
||||
{
|
||||
long time1 = (Long) nowMap.get(REPEAT_TIME);
|
||||
long time2 = (Long) preMap.get(REPEAT_TIME);
|
||||
if ((time1 - time2) < (this.intervalTime * 1000))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bashi.framework.manager;
|
||||
|
||||
import com.bashi.common.utils.Threads;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 确保应用退出时能关闭后台线程
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j(topic = "sys-user")
|
||||
@Component
|
||||
public class ShutdownManager {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("scheduledExecutorService")
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
shutdownAsyncManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止异步执行任务
|
||||
*/
|
||||
private void shutdownAsyncManager() {
|
||||
try {
|
||||
log.info("====关闭后台任务任务线程池====");
|
||||
Threads.shutdownAndAwaitTermination(scheduledExecutorService);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.bashi.framework.mybatisplus;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.bashi.common.utils.SecurityUtils;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MP注入处理器
|
||||
* @author Lion Li
|
||||
* @date 2021/4/25
|
||||
*/
|
||||
public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
//根据属性名字设置要填充的值
|
||||
if (metaObject.hasGetter("createTime")) {
|
||||
if (metaObject.getValue("createTime") == null) {
|
||||
this.setFieldValByName("createTime", new Date(), metaObject);
|
||||
}
|
||||
}
|
||||
if (metaObject.hasGetter("createBy")) {
|
||||
if (metaObject.getValue("createBy") == null) {
|
||||
this.setFieldValByName("createBy", SecurityUtils.getUsername(), metaObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
if (metaObject.hasGetter("updateBy")) {
|
||||
if (metaObject.getValue("updateBy") == null) {
|
||||
this.setFieldValByName("updateBy", SecurityUtils.getUsername(), metaObject);
|
||||
}
|
||||
}
|
||||
if (metaObject.hasGetter("updateTime")) {
|
||||
if (metaObject.getValue("updateTime") == null) {
|
||||
this.setFieldValByName("updateTime", new Date(), metaObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.bashi.framework.security.filter;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.utils.SecurityUtils;
|
||||
import com.bashi.framework.web.service.TokenService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* token过滤器 验证token有效性
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
|
||||
{
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
LoginUser loginUser = tokenService.getLoginUser(request);
|
||||
if (Validator.isNotNull(loginUser) && Validator.isNull(SecurityUtils.getAuthentication())) {
|
||||
tokenService.verifyToken(loginUser);
|
||||
if(loginUser.getUser() != null){
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
}else if(loginUser.getCustomer() != null){
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.bashi.framework.security.handle;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.bashi.common.core.domain.AjaxResult;
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 认证失败处理类 返回未授权
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Component
|
||||
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -8970718410437077606L;
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
|
||||
throws IOException
|
||||
{
|
||||
int code = HttpStatus.HTTP_UNAUTHORIZED;
|
||||
String msg = StrUtil.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
|
||||
ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(code, msg)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.bashi.framework.security.handle;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.common.core.domain.AjaxResult;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import com.bashi.framework.web.service.AsyncService;
|
||||
import com.bashi.framework.web.service.TokenService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 自定义退出处理类 返回成功
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Configuration
|
||||
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
private AsyncService asyncService;
|
||||
|
||||
/**
|
||||
* 退出处理
|
||||
*/
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
LoginUser loginUser = tokenService.getLoginUser(request);
|
||||
if (Validator.isNotNull(loginUser)) {
|
||||
String userName = loginUser.getUsername();
|
||||
// 删除用户缓存记录
|
||||
tokenService.delLoginUser(loginUser.getToken());
|
||||
// 记录用户退出日志
|
||||
asyncService.recordLogininfor(userName, Constants.LOGOUT, "退出成功", request);
|
||||
}
|
||||
ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(HttpStatus.HTTP_OK, "退出成功")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.bashi.framework.security.sms;
|
||||
|
||||
import com.bashi.common.core.domain.model.LoginPhoneBody;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/15</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
public interface IDuteUserDetailsService extends UserDetailsService {
|
||||
|
||||
UserDetails loadUserByMobile(LoginPhoneBody username) throws UsernameNotFoundException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bashi.framework.security.sms;
|
||||
|
||||
public enum LoginTypeEnums {
|
||||
USER(1,"用户"),
|
||||
MECHANIC(2,"服务人员"),
|
||||
SHOP(3,"商家"),
|
||||
MP_OPEN_ID(4,"公众号openId登录"),
|
||||
MP_PHONE(5,"公众号手机登录"),
|
||||
CUSTOMER_PASSWORD(6,"客户密码登陆"),
|
||||
;
|
||||
|
||||
private final Integer code;
|
||||
private final String msg;
|
||||
|
||||
LoginTypeEnums(Integer code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static String getMsgByCode(Integer code){
|
||||
LoginTypeEnums[] values = LoginTypeEnums.values();
|
||||
for (LoginTypeEnums value : values) {
|
||||
if(value.getCode().equals(code)){
|
||||
return value.getMsg();
|
||||
}
|
||||
}
|
||||
return "用户";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bashi.framework.security.sms;
|
||||
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.common.core.domain.AjaxResult;
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import com.bashi.common.utils.MessageUtils;
|
||||
import com.bashi.framework.web.service.AsyncService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/14</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SmsAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
@Autowired
|
||||
private AsyncService asyncService;
|
||||
public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||
AjaxResult error = AjaxResult.error(exception.getMessage(), null);
|
||||
String mobile = request.getParameter(SPRING_SECURITY_FORM_MOBILE_KEY);
|
||||
asyncService.recordLogininfor(mobile, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write(JsonUtils.toJsonString(error));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.bashi.framework.security.sms;
|
||||
|
||||
import com.bashi.framework.web.service.CodeService;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/13</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
|
||||
private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
|
||||
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/sms/login", "POST");
|
||||
private boolean postOnly = true;
|
||||
|
||||
private CodeService codeService;
|
||||
|
||||
public CodeService getCodeService() {
|
||||
return codeService;
|
||||
}
|
||||
|
||||
public void setCodeService(CodeService codeService) {
|
||||
this.codeService = codeService;
|
||||
}
|
||||
|
||||
public SmsAuthenticationFilter() {
|
||||
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
|
||||
}
|
||||
|
||||
public SmsAuthenticationFilter(AuthenticationManager authenticationManager) {
|
||||
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
|
||||
if (this.postOnly && !"POST".equals(request.getMethod())) {
|
||||
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
|
||||
} else {
|
||||
String mobile = this.obtainMobile(request);
|
||||
mobile = mobile != null ? mobile : "";
|
||||
mobile = mobile.trim();
|
||||
/*String mobileCode = this.obtainMobileCode(request);
|
||||
mobileCode = mobileCode != null ? mobileCode : "";
|
||||
mobileCode = mobileCode.trim();
|
||||
if(!codeService.check(mobile, CodeType.LOGIN,mobileCode)){
|
||||
throw new AuthenticationServiceException("验证码错误");
|
||||
}*/
|
||||
SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile);
|
||||
this.setDetails(request, authRequest);
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String obtainMobile(HttpServletRequest request) {
|
||||
return request.getParameter(this.mobileParameter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String obtainMobileCode(HttpServletRequest request) {
|
||||
return request.getParameter("mobileCode");
|
||||
}
|
||||
|
||||
protected void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) {
|
||||
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
|
||||
public void setMobileParameter(String mobileParameter) {
|
||||
Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
|
||||
this.mobileParameter = mobileParameter;
|
||||
}
|
||||
|
||||
public String getMobileParameter() {
|
||||
return mobileParameter;
|
||||
}
|
||||
|
||||
|
||||
public void setPostOnly(boolean postOnly) {
|
||||
this.postOnly = postOnly;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bashi.framework.security.sms;
|
||||
|
||||
import com.bashi.common.core.domain.model.LoginPhoneBody;
|
||||
import com.bashi.common.utils.JsonUtils;
|
||||
import com.bashi.framework.web.service.CodeService;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/13</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
public class SmsAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private IDuteUserDetailsService userDetailsService;
|
||||
|
||||
private CodeService codeService;
|
||||
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication;
|
||||
|
||||
LoginPhoneBody mobile = (LoginPhoneBody) authenticationToken.getPrincipal();
|
||||
|
||||
UserDetails userDetails = userDetailsService.loadUserByMobile(mobile);
|
||||
|
||||
// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
|
||||
SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(userDetails, userDetails.getAuthorities());
|
||||
|
||||
authenticationResult.setDetails(authenticationToken.getDetails());
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
// 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
|
||||
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
public IDuteUserDetailsService getUserDetailsService() {
|
||||
return userDetailsService;
|
||||
}
|
||||
|
||||
public void setUserDetailsService(IDuteUserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
public CodeService getCodeService() {
|
||||
return codeService;
|
||||
}
|
||||
|
||||
public void setCodeService(CodeService codeService) {
|
||||
this.codeService = codeService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.bashi.framework.security.sms;
|
||||
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.common.core.domain.entity.SysUser;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.utils.DateUtils;
|
||||
import com.bashi.common.utils.MessageUtils;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import com.bashi.framework.web.service.AsyncService;
|
||||
import com.bashi.framework.web.service.TokenService;
|
||||
import com.bashi.system.service.ISysUserService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/14</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SmsAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
@Autowired
|
||||
private AsyncService asyncService;
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException {
|
||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||
asyncService.recordLogininfor(loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
|
||||
recordLoginInfo(loginUser.getUser());
|
||||
// 生成token
|
||||
String token = tokenService.createToken(loginUser);
|
||||
Map<String, Object> ajax = new HashMap<>();
|
||||
ajax.put("token",token);
|
||||
ajax.put("code",200);
|
||||
// ajax.put("data",loginUser);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write(objectMapper.writeValueAsString(ajax));
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*/
|
||||
public void recordLoginInfo(SysUser user) {
|
||||
user.setLoginIp(ServletUtils.getClientIP());
|
||||
user.setLoginDate(DateUtils.getNowDate());
|
||||
user.setUpdateBy(user.getUserName());
|
||||
userService.updateUserProfile(user);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.bashi.framework.security.sms;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.SpringSecurityCoreVersion;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/13</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private final Object principal;
|
||||
|
||||
/**
|
||||
* This constructor can be safely used by any code that wishes to create a
|
||||
* <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
|
||||
* will return <code>false</code>.
|
||||
*
|
||||
*/
|
||||
public SmsAuthenticationToken(Object principal) {
|
||||
super(null);
|
||||
this.principal = principal;
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor should only be used by <code>AuthenticationManager</code> or
|
||||
* <code>AuthenticationProvider</code> implementations that are satisfied with
|
||||
* producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
|
||||
* authentication token.
|
||||
* @param principal
|
||||
* @param authorities
|
||||
*/
|
||||
public SmsAuthenticationToken(Object principal,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
super.setAuthenticated(true); // must use super, as we override
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
|
||||
Assert.isTrue(!isAuthenticated,
|
||||
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
|
||||
super.setAuthenticated(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eraseCredentials() {
|
||||
super.eraseCredentials();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.bashi.framework.util;
|
||||
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import org.apache.http.HttpHeaders;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class AgentUtils {
|
||||
|
||||
public enum MarketEnum {
|
||||
none("none","未知"),
|
||||
iPhone("iPhone","苹果"),
|
||||
MP("MP","公众号"),
|
||||
huawei("huawei","华为"),
|
||||
xiaomi("xiaomi","小米"),
|
||||
a360("a360","360"),
|
||||
vivo("vivo","vivo"),
|
||||
baidu("baidu591","百度"),
|
||||
share("share","分享"),
|
||||
;
|
||||
|
||||
private String code;
|
||||
private String name;
|
||||
|
||||
MarketEnum(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static String getMarketValue(String key){
|
||||
MarketEnum[] values = MarketEnum.values();
|
||||
for (MarketEnum value : values) {
|
||||
if(value.getCode().equals(key)){
|
||||
return value.getName();
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final static String APP_MARKET = "channelId";
|
||||
|
||||
private final static String VERSION_NAME = "versionName";
|
||||
|
||||
public static String getVersion(HttpServletRequest request){
|
||||
return request.getHeader(VERSION_NAME);
|
||||
}
|
||||
|
||||
public static String getAgentCode(HttpServletRequest request){
|
||||
return request.getHeader(APP_MARKET);
|
||||
}
|
||||
|
||||
public static boolean checkIos(HttpServletRequest request){
|
||||
String agent = request.getHeader(HttpHeaders.USER_AGENT);
|
||||
if(agent.contains("iPhone")|| agent.contains("iPod") || agent.contains("iPad")){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getAgent(HttpServletRequest request){
|
||||
String market = request.getHeader(APP_MARKET);
|
||||
if(StringUtils.isBlank(market)){ // 可能是IOS,也可能谁都不是
|
||||
String agent = request.getHeader(HttpHeaders.USER_AGENT);
|
||||
if(agent.contains("iPhone")|| agent.contains("iPod") || agent.contains("iPad")){
|
||||
return MarketEnum.iPhone.getName();
|
||||
}else{
|
||||
return MarketEnum.none.getName();
|
||||
}
|
||||
}
|
||||
return MarketEnum.getMarketValue(market);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.bashi.framework.web.exception;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.bashi.common.core.domain.AjaxResult;
|
||||
import com.bashi.common.exception.BaseException;
|
||||
import com.bashi.common.exception.CustomException;
|
||||
import com.bashi.common.exception.DemoModeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AccountExpiredException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 基础异常
|
||||
*/
|
||||
@ExceptionHandler(BaseException.class)
|
||||
public AjaxResult baseException(BaseException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
@ExceptionHandler(CustomException.class)
|
||||
public AjaxResult businessException(CustomException e) {
|
||||
if (Validator.isNull(e.getCode())) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
return AjaxResult.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
public AjaxResult handlerNoFoundException(Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return AjaxResult.error(HttpStatus.HTTP_NOT_FOUND, "路径不存在,请检查路径是否正确");
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public AjaxResult handleAuthorizationException(AccessDeniedException e) {
|
||||
log.error(e.getMessage());
|
||||
return AjaxResult.error(HttpStatus.HTTP_FORBIDDEN, "没有权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccountExpiredException.class)
|
||||
public AjaxResult handleAccountExpiredException(AccountExpiredException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(UsernameNotFoundException.class)
|
||||
public AjaxResult handleUsernameNotFoundException(UsernameNotFoundException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public AjaxResult handleException(Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义验证异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public AjaxResult validatedBindException(BindException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String message = e.getAllErrors().get(0).getDefaultMessage();
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义验证异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public AjaxResult constraintViolationException(ConstraintViolationException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String message = e.getConstraintViolations().iterator().next().getMessage();
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义验证异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Object validExceptionHandler(MethodArgumentNotValidException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示模式异常
|
||||
*/
|
||||
@ExceptionHandler(DemoModeException.class)
|
||||
public AjaxResult demoModeException(DemoModeException e) {
|
||||
return AjaxResult.error("演示模式,不允许操作");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import com.bashi.common.utils.ip.AddressUtils;
|
||||
import com.bashi.system.domain.SysLogininfor;
|
||||
import com.bashi.system.domain.SysOperLog;
|
||||
import com.bashi.system.service.ISysLogininforService;
|
||||
import com.bashi.system.service.ISysOperLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 异步工厂(产生任务用)
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j(topic = "sys-user")
|
||||
@Async
|
||||
@Component
|
||||
public class AsyncService {
|
||||
|
||||
@Autowired
|
||||
private ISysLogininforService iSysLogininforService;
|
||||
|
||||
@Autowired
|
||||
private ISysOperLogService iSysOperLogService;
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param status 状态
|
||||
* @param message 消息
|
||||
* @param args 列表
|
||||
*/
|
||||
public void recordLogininfor(final String username, final String status, final String message,
|
||||
HttpServletRequest request, final Object... args) {
|
||||
final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
|
||||
final String ip = ServletUtils.getClientIP(request);
|
||||
|
||||
String address = AddressUtils.getRealAddressByIP(ip);
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append(getBlock(ip));
|
||||
s.append(address);
|
||||
s.append(getBlock(username));
|
||||
s.append(getBlock(status));
|
||||
s.append(getBlock(message));
|
||||
// 打印信息到日志
|
||||
log.info(s.toString(), args);
|
||||
// 获取客户端操作系统
|
||||
String os = userAgent.getOs().getName();
|
||||
// 获取客户端浏览器
|
||||
String browser = userAgent.getBrowser().getName();
|
||||
// 封装对象
|
||||
SysLogininfor logininfor = new SysLogininfor();
|
||||
logininfor.setUserName(username);
|
||||
logininfor.setIpaddr(ip);
|
||||
logininfor.setLoginLocation(address);
|
||||
logininfor.setBrowser(browser);
|
||||
logininfor.setOs(os);
|
||||
logininfor.setMsg(message);
|
||||
// 日志状态
|
||||
if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status)) {
|
||||
logininfor.setStatus(Constants.SUCCESS);
|
||||
} else if (Constants.LOGIN_FAIL.equals(status)) {
|
||||
logininfor.setStatus(Constants.FAIL);
|
||||
}
|
||||
// 插入数据
|
||||
iSysLogininforService.insertLogininfor(logininfor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志记录
|
||||
*
|
||||
* @param operLog 操作日志信息
|
||||
*/
|
||||
public void recordOper(final SysOperLog operLog) {
|
||||
// 远程查询操作地点
|
||||
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
|
||||
iSysOperLogService.insertOperlog(operLog);
|
||||
}
|
||||
|
||||
private String getBlock(Object msg) {
|
||||
if (msg == null) {
|
||||
msg = "";
|
||||
}
|
||||
return "[" + msg.toString() + "]";
|
||||
}
|
||||
|
||||
public void recordLog(final StringBuilder logBuild) {
|
||||
log.info(logBuild.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
import com.bashi.framework.constant.CodeType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/15</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
@Component
|
||||
public class CodeService {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
private static final String AUTH_CODE = "AUTH:CODE";
|
||||
|
||||
private static final String CODE = "DUTE:CODE:";
|
||||
|
||||
private static final String TEST_ACCOUNT = "15312531253";
|
||||
|
||||
public void put(String mobile,CodeType type,String code){
|
||||
redisTemplate.opsForValue().set(CODE + type.name() + ":" + mobile,code, Duration.ofMinutes(11));
|
||||
}
|
||||
|
||||
public boolean check(String mobile, CodeType type, String code){
|
||||
if(TEST_ACCOUNT.equals(mobile)){
|
||||
return true;
|
||||
}
|
||||
if(StringUtils.isBlank(code)){
|
||||
return false;
|
||||
}
|
||||
String authCode = redisTemplate.opsForValue().get(AUTH_CODE);
|
||||
if(StringUtils.isNotEmpty(authCode)){
|
||||
String[] allCodeArray = authCode.split(",");
|
||||
for (String allCode : allCodeArray) {
|
||||
if(allCode.equals(code)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
String s = redisTemplate.opsForValue().get(CODE + type.name() + ":" + mobile);
|
||||
return code.equals(s);
|
||||
}
|
||||
|
||||
public void putAuthCode(String code){
|
||||
redisTemplate.opsForValue().set(AUTH_CODE,code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/30</p>
|
||||
*
|
||||
* @author dute7liang
|
||||
*/
|
||||
public interface CustomerService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.bashi.common.core.domain.entity.SysRole;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 自定义权限实现,ss取自SpringSecurity首字母
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Service("ss")
|
||||
public class PermissionService
|
||||
{
|
||||
/** 所有权限标识 */
|
||||
private static final String ALL_PERMISSION = "*:*:*";
|
||||
|
||||
/** 管理员角色权限标识 */
|
||||
private static final String SUPER_ADMIN = "admin";
|
||||
|
||||
private static final String ROLE_DELIMETER = ",";
|
||||
|
||||
private static final String PERMISSION_DELIMETER = ",";
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
/**
|
||||
* 验证用户是否具备某权限
|
||||
*
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
public boolean hasPermi(String permission)
|
||||
{
|
||||
if (Validator.isEmpty(permission))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
|
||||
if (Validator.isNull(loginUser) || Validator.isEmpty(loginUser.getPermissions()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return hasPermissions(loginUser.getPermissions(), permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
|
||||
*
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否不具备某权限
|
||||
*/
|
||||
public boolean lacksPermi(String permission)
|
||||
{
|
||||
return hasPermi(permission) != true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否具有以下任意一个权限
|
||||
*
|
||||
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
|
||||
* @return 用户是否具有以下任意一个权限
|
||||
*/
|
||||
public boolean hasAnyPermi(String permissions)
|
||||
{
|
||||
if (Validator.isEmpty(permissions))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
|
||||
if (Validator.isNull(loginUser) || Validator.isEmpty(loginUser.getPermissions()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Set<String> authorities = loginUser.getPermissions();
|
||||
for (String permission : permissions.split(PERMISSION_DELIMETER))
|
||||
{
|
||||
if (permission != null && hasPermissions(authorities, permission))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否拥有某个角色
|
||||
*
|
||||
* @param role 角色字符串
|
||||
* @return 用户是否具备某角色
|
||||
*/
|
||||
public boolean hasRole(String role)
|
||||
{
|
||||
if (Validator.isEmpty(role))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
|
||||
if (Validator.isNull(loginUser) || Validator.isEmpty(loginUser.getUser().getRoles()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (SysRole sysRole : loginUser.getUser().getRoles())
|
||||
{
|
||||
String roleKey = sysRole.getRoleKey();
|
||||
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StrUtil.trim(role)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否不具备某角色,与 isRole逻辑相反。
|
||||
*
|
||||
* @param role 角色名称
|
||||
* @return 用户是否不具备某角色
|
||||
*/
|
||||
public boolean lacksRole(String role)
|
||||
{
|
||||
return hasRole(role) != true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户是否具有以下任意一个角色
|
||||
*
|
||||
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
|
||||
* @return 用户是否具有以下任意一个角色
|
||||
*/
|
||||
public boolean hasAnyRoles(String roles)
|
||||
{
|
||||
if (Validator.isEmpty(roles))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
|
||||
if (Validator.isNull(loginUser) || Validator.isEmpty(loginUser.getUser().getRoles()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (String role : roles.split(ROLE_DELIMETER))
|
||||
{
|
||||
if (hasRole(role))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含权限
|
||||
*
|
||||
* @param permissions 权限列表
|
||||
* @param permission 权限字符串
|
||||
* @return 用户是否具备某权限
|
||||
*/
|
||||
private boolean hasPermissions(Set<String> permissions, String permission)
|
||||
{
|
||||
return permissions.contains(ALL_PERMISSION) || permissions.contains(StrUtil.trim(permission));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
import com.bashi.common.core.domain.entity.SysUser;
|
||||
|
||||
/**
|
||||
* <p>created on 2021/7/15</p>
|
||||
*
|
||||
* @author zhangliang
|
||||
*/
|
||||
public interface RegisterEventService {
|
||||
|
||||
void event(SysUser user);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.common.core.domain.entity.SysUser;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.core.redis.RedisCache;
|
||||
import com.bashi.common.exception.CustomException;
|
||||
import com.bashi.common.exception.user.CaptchaException;
|
||||
import com.bashi.common.exception.user.CaptchaExpireException;
|
||||
import com.bashi.common.exception.user.UserPasswordNotMatchException;
|
||||
import com.bashi.common.utils.DateUtils;
|
||||
import com.bashi.common.utils.MessageUtils;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import com.bashi.framework.config.properties.CaptchaProperties;
|
||||
import com.bashi.system.service.ISysUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Component
|
||||
public class SysLoginService {
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Resource
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private CaptchaProperties captchaProperties;
|
||||
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
@Autowired
|
||||
private AsyncService asyncService;
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
* @return 结果
|
||||
*/
|
||||
public String login(String username, String password, String code, String uuid) {
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
if (captchaProperties.getEnabled()) {
|
||||
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
||||
String captcha = redisCache.getCacheObject(verifyKey);
|
||||
redisCache.deleteObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
// 用户验证
|
||||
Authentication authentication = null;
|
||||
try {
|
||||
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
|
||||
authentication = authenticationManager
|
||||
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
||||
} catch (Exception e) {
|
||||
if (e instanceof BadCredentialsException) {
|
||||
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"), request);
|
||||
throw new UserPasswordNotMatchException();
|
||||
} else {
|
||||
asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage(), request);
|
||||
throw new CustomException(e.getMessage());
|
||||
}
|
||||
}
|
||||
asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
|
||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||
recordLoginInfo(loginUser.getUser());
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*/
|
||||
public void recordLoginInfo(SysUser user) {
|
||||
user.setLoginIp(ServletUtils.getClientIP());
|
||||
user.setLoginDate(DateUtils.getNowDate());
|
||||
user.setUpdateBy(user.getUserName());
|
||||
userService.updateUserProfile(user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.bashi.common.core.domain.entity.SysUser;
|
||||
import com.bashi.system.service.ISysMenuService;
|
||||
import com.bashi.system.service.ISysRoleService;
|
||||
|
||||
/**
|
||||
* 用户权限处理
|
||||
*
|
||||
* @author duteliang
|
||||
*/
|
||||
@Component
|
||||
public class SysPermissionService
|
||||
{
|
||||
@Autowired
|
||||
private ISysRoleService roleService;
|
||||
|
||||
@Autowired
|
||||
private ISysMenuService menuService;
|
||||
|
||||
/**
|
||||
* 获取角色数据权限
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 角色权限信息
|
||||
*/
|
||||
public Set<String> getRolePermission(SysUser user)
|
||||
{
|
||||
Set<String> roles = new HashSet<String>();
|
||||
// 管理员拥有所有权限
|
||||
if (user.isAdmin())
|
||||
{
|
||||
roles.add("admin");
|
||||
}
|
||||
else
|
||||
{
|
||||
roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单数据权限
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 菜单权限信息
|
||||
*/
|
||||
public Set<String> getMenuPermission(SysUser user)
|
||||
{
|
||||
Set<String> perms = new HashSet<String>();
|
||||
// 管理员拥有所有权限
|
||||
if (user.isAdmin())
|
||||
{
|
||||
perms.add("*:*:*");
|
||||
}
|
||||
else
|
||||
{
|
||||
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.bashi.framework.web.service;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.bashi.common.constant.Constants;
|
||||
import com.bashi.common.core.domain.model.LoginUser;
|
||||
import com.bashi.common.core.redis.RedisCache;
|
||||
import com.bashi.common.utils.ServletUtils;
|
||||
import com.bashi.common.utils.ip.AddressUtils;
|
||||
import com.bashi.framework.config.properties.TokenProperties;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* token验证处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
public class TokenService {
|
||||
|
||||
protected static final long MILLIS_SECOND = 1000;
|
||||
|
||||
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
|
||||
|
||||
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private TokenProperties tokenProperties;
|
||||
|
||||
/**
|
||||
* 获取用户身份信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
public LoginUser getLoginUser(HttpServletRequest request) {
|
||||
// 获取请求携带的令牌
|
||||
String token = getToken(request);
|
||||
if (Validator.isNotEmpty(token)) {
|
||||
Claims claims = parseToken(token);
|
||||
// 解析对应的权限以及用户信息
|
||||
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
|
||||
String userKey = getTokenKey(uuid);
|
||||
LoginUser user = redisCache.getCacheObject(userKey);
|
||||
return user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户身份信息
|
||||
*/
|
||||
public void setLoginUser(LoginUser loginUser) {
|
||||
if (Validator.isNotNull(loginUser) && Validator.isNotEmpty(loginUser.getToken())) {
|
||||
refreshToken(loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户身份信息
|
||||
*/
|
||||
public void delLoginUser(String token) {
|
||||
if (Validator.isNotEmpty(token)) {
|
||||
String userKey = getTokenKey(token);
|
||||
redisCache.deleteObject(userKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建令牌
|
||||
*
|
||||
* @param loginUser 用户信息
|
||||
* @return 令牌
|
||||
*/
|
||||
public String createToken(LoginUser loginUser) {
|
||||
String token = IdUtil.fastUUID();
|
||||
loginUser.setToken(token);
|
||||
setUserAgent(loginUser);
|
||||
refreshToken(loginUser);
|
||||
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put(Constants.LOGIN_USER_KEY, token);
|
||||
return createToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
|
||||
*
|
||||
* @param loginUser
|
||||
* @return 令牌
|
||||
*/
|
||||
public void verifyToken(LoginUser loginUser) {
|
||||
long expireTime = loginUser.getExpireTime();
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
|
||||
refreshToken(loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌有效期
|
||||
*
|
||||
* @param loginUser 登录信息
|
||||
*/
|
||||
public void refreshToken(LoginUser loginUser) {
|
||||
loginUser.setLoginTime(System.currentTimeMillis());
|
||||
loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE);
|
||||
// 根据uuid将loginUser缓存
|
||||
String userKey = getTokenKey(loginUser.getToken());
|
||||
redisCache.setCacheObject(userKey, loginUser, tokenProperties.getExpireTime(), TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户代理信息
|
||||
*
|
||||
* @param loginUser 登录信息
|
||||
*/
|
||||
public void setUserAgent(LoginUser loginUser) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = ServletUtils.getClientIP();
|
||||
loginUser.setIpaddr(ip);
|
||||
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
||||
loginUser.setBrowser(userAgent.getBrowser().getName());
|
||||
loginUser.setOs(userAgent.getOs().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据声明生成令牌
|
||||
*
|
||||
* @param claims 数据声明
|
||||
* @return 令牌
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims) {
|
||||
String token = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(SignatureAlgorithm.HS512, tokenProperties.getSecret()).compact();
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取数据声明
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 数据声明
|
||||
*/
|
||||
private Claims parseToken(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(tokenProperties.getSecret())
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户名
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
Claims claims = parseToken(token);
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求token
|
||||
*
|
||||
* @param request
|
||||
* @return token
|
||||
*/
|
||||
private String getToken(HttpServletRequest request) {
|
||||
String token = request.getHeader(tokenProperties.getHeader());
|
||||
if (Validator.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
|
||||
token = token.replace(Constants.TOKEN_PREFIX, "");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private String getTokenKey(String uuid) {
|
||||
return Constants.LOGIN_TOKEN_KEY + uuid;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user