Qyu 4 months ago
parent 44da55b9bf
commit 72807fa65c

@ -0,0 +1,64 @@
<?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>huacai</artifactId>
<groupId>com.huacai</groupId>
<version>3.8.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>huacai-framework</artifactId>
<description>
framework框架核心
</description>
<dependencies>
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- 验证码 -->
<dependency>
<groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
<!-- 系统模块-->
<dependency>
<groupId>com.huacai</groupId>
<artifactId>huacai-system</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,213 @@
package com.huacai.framework.aspectj;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.huacai.common.annotation.DataScope;
import com.huacai.common.core.domain.BaseEntity;
import com.huacai.common.core.domain.entity.SysRole;
import com.huacai.common.core.domain.entity.SysUser;
import com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.core.text.Convert;
import com.huacai.common.utils.SecurityUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.framework.security.context.PermissionContextHolder;
/**
*
* AOPSQL
*
* @author huacai
*/
// 标记为AOP切面类
@Aspect
// 注册为Spring组件
@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";
/**
* SQL
*/
public static final String DATA_SCOPE = "dataScope";
/**
*
*
* @param point
* @param controllerDataScope @DataScope
* @throws Throwable
*/
@Before("@annotation(controllerDataScope)") // 切点表达式:拦截所有带有@DataScope注解的方法
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
// 清除之前可能残留的数据权限参数,防止干扰
clearDataScope(point);
// 处理数据权限生成过滤SQL
handleDataScope(point, controllerDataScope);
}
/**
*
*
* @param joinPoint
* @param controllerDataScope @DataScope
*/
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{
// 获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser))
{
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员,则不过滤数据(直接放行)
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{
// 获取注解中的权限字符(默认为上下文权限)
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
// 生成数据权限过滤SQL
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias(), permission);
}
}
}
/**
* SQL
*
* @param joinPoint
* @param user
* @param deptAlias SQL
* @param userAlias SQL
* @param permission
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
// 构建数据过滤SQL片段
StringBuilder sqlString = new StringBuilder();
// 记录已处理的权限类型,避免重复
List<String> conditions = new ArrayList<String>();
// 遍历用户拥有的角色,根据角色的数据权限生成过滤条件
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
// 如果不是自定义权限且已处理过该权限类型,则跳过
if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
{
continue;
}
// 如果角色没有指定的权限,则跳过
if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
&& !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
// 根据数据权限类型拼接SQL条件
if (DATA_SCOPE_ALL.equals(dataScope))
{
// 全部数据权限清空SQL无需过滤
sqlString = new StringBuilder();
conditions.add(dataScope);
break; // 已满足最高权限,无需继续处理其他角色
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
// 自定义数据权限:查询角色关联的部门
sqlString.append(StringUtils.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))
{
// 本部门数据权限部门ID等于用户所属部门
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
// 本部门及子部门数据权限部门ID在用户部门及子部门中
sqlString.append(StringUtils.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))
{
// 仅本人数据权限用户ID等于当前用户
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 没有用户别名时,设置一个无效条件(不查询任何数据)
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
// 记录已处理的权限类型
conditions.add(dataScope);
}
// 如果没有匹配的权限条件,设置无效条件(不查询任何数据)
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
// 如果生成了有效的SQL条件则设置到方法参数中
if (StringUtils.isNotBlank(sqlString.toString()))
{
// 获取方法的第一个参数假设为BaseEntity子类
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
// 将SQL条件设置到参数的扩展属性中用于MyBatis在XML中拼接
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
// 注substring(4)是为了去掉开头的" OR "
}
}
}
/**
* SQLparams.dataScopeSQL
*/
private void clearDataScope(final JoinPoint joinPoint)
{
// 获取方法的第一个参数假设为BaseEntity子类
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
// 清空数据权限参数
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}

@ -0,0 +1,88 @@
package com.huacai.framework.aspectj;
import java.util.Objects;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.huacai.common.annotation.DataSource;
import com.huacai.common.utils.StringUtils;
import com.huacai.framework.datasource.DynamicDataSourceContextHolder;
/**
*
*
*
* @author huacai
*/
// 标记此类为AOP切面类用于定义切入点和通知
@Aspect
// 设置切面执行顺序为1值越小优先级越高确保在事务等其他切面之前执行
@Order(1)
// 标记为Spring组件使其被容器扫描并管理
@Component
public class DataSourceAspect
{
// 日志记录器,用于记录数据源切换相关日志
protected Logger logger = LoggerFactory.getLogger(getClass());
// 定义切入点:匹配所有标注了@DataSource注解的方法或包含@DataSource注解的类中的所有方法
@Pointcut("@annotation(com.huacai.common.annotation.DataSource)"
+ "|| @within(com.huacai.common.annotation.DataSource)")
public void dsPointCut()
{
// 切入点方法体为空,仅作为标记
}
// 定义环绕通知,在切入点匹配的方法执行前后进行增强处理
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable
{
// 获取当前方法或类上的DataSource注解
DataSource dataSource = getDataSource(point);
// 如果注解存在(即需要切换数据源)
if (StringUtils.isNotNull(dataSource))
{
// 将数据源类型存入上下文持有器,供动态数据源选择使用
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try
{
// 执行目标方法(即被切入的业务方法)
return point.proceed();
}
finally
{
// 方法执行完成后,清除数据源上下文,避免影响后续操作
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
*
* @DataSource
*/
public DataSource getDataSource(ProceedingJoinPoint point)
{
// 获取连接点的方法签名,用于获取目标方法信息
MethodSignature signature = (MethodSignature) point.getSignature();
// 从目标方法上查找DataSource注解
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
// 如果方法上存在注解,直接返回
if (Objects.nonNull(dataSource))
{
return dataSource;
}
// 方法上没有注解时从目标类上查找DataSource注解并返回
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}

@ -0,0 +1,319 @@
package com.huacai.framework.aspectj;
import java.util.Collection;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
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.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson2.JSON;
import com.huacai.common.annotation.Log;
import com.huacai.common.core.domain.entity.SysUser;
import com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.enums.BusinessStatus;
import com.huacai.common.enums.HttpMethod;
import com.huacai.common.filter.PropertyPreExcludeFilter;
import com.huacai.common.utils.SecurityUtils;
import com.huacai.common.utils.ServletUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.common.utils.ip.IpUtils;
import com.huacai.framework.manager.AsyncManager;
import com.huacai.framework.manager.factory.AsyncFactory;
import com.huacai.system.domain.SysOperLog;
/**
*
* AOP@Log
*
* @author huacai
*/
// 标记此类为AOP切面类
@Aspect
// 注册为Spring组件使其被容器管理
@Component
public class LogAspect
{
// 定义日志记录器,用于记录切面内部的日志信息
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/** 排除敏感属性字段(如密码等,避免日志中记录敏感信息) */
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/** 计算操作消耗时间的线程本地变量(每个线程独立存储,避免线程安全问题) */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
/**
*
*
*
* @param joinPoint
* @param controllerLog @Log
*/
@Before(value = "@annotation(controllerLog)") // 切点表达式:拦截所有带有@Log注解的方法
public void boBefore(JoinPoint joinPoint, Log controllerLog)
{
// 记录当前时间戳到线程本地变量,用于后续计算方法执行耗时
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
/**
*
*
*
* @param joinPoint
* @param controllerLog @Log
* @param jsonResult
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
// 调用日志处理核心方法异常参数为null表示无异常
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
*
*
*
* @param joinPoint
* @param controllerLog @Log
* @param e
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{
// 调用日志处理核心方法,传入异常对象
handleLog(joinPoint, controllerLog, e, null);
}
/**
*
*
*
* @param joinPoint
* @param controllerLog @Log
* @param e null
* @param jsonResult null
*/
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{
try
{
// 获取当前登录用户信息从Security上下文获取
LoginUser loginUser = SecurityUtils.getLoginUser();
// *========数据库日志实体构建=========*//
SysOperLog operLog = new SysOperLog();
// 默认设置操作状态为成功
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 获取客户端IP地址
String ip = IpUtils.getIpAddr();
operLog.setOperIp(ip);
// 获取请求URL并截断避免过长
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
// 如果存在登录用户,设置操作人及部门信息
if (loginUser != null)
{
operLog.setOperName(loginUser.getUsername());
SysUser currentUser = loginUser.getUser();
if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept()))
{
operLog.setDeptName(currentUser.getDept().getDeptName());
}
}
// 如果存在异常,更新操作状态为失败并记录异常信息
if (e != null)
{
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); // 截断异常信息避免过长
}
// 设置操作方法名(全类名.方法名()
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式GET/POST等
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理@Log注解上的参数完善日志信息
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 计算方法执行耗时(当前时间 - 开始时间)
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
// 异步执行日志保存(避免阻塞主线程)
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
catch (Exception exp)
{
// 记录本地异常日志(切面自身异常)
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
finally
{
// 清除线程本地变量,避免内存泄漏
TIME_THREADLOCAL.remove();
}
}
/**
*
*
* @param joinPoint
* @param log @Log
* @param operLog
* @param jsonResult
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
{
// 设置业务类型(从注解中获取)
operLog.setBusinessType(log.businessType().ordinal());
// 设置操作标题(从注解中获取)
operLog.setTitle(log.title());
// 设置操作人类别(从注解中获取)
operLog.setOperatorType(log.operatorType().ordinal());
// 如果注解配置需要保存请求数据,则处理请求参数
if (log.isSaveRequestData())
{
// 获取请求参数并设置到日志实体
setRequestValue(joinPoint, operLog, log.excludeParamNames());
}
// 如果注解配置需要保存响应数据且结果不为空,则处理响应结果
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
{
// 将响应结果转为JSON并截断避免过长
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
/**
*
*
* @param joinPoint
* @param operLog
* @param excludeParamNames
* @throws Exception
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
{
// 获取请求参数Map从request中获取
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
String requestMethod = operLog.getRequestMethod();
// 如果参数Map为空且是PUT/POST请求通常这类请求参数在请求体中
if (StringUtils.isEmpty(paramsMap)
&& (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)))
{
// 从方法参数中解析请求参数
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000)); // 截断参数避免过长
}
else
{
// 将参数Map转为JSON同时排除敏感字段
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
}
}
/**
*
*
* @param paramsArray
* @param excludeParamNames
* @return
*/
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
// 过滤空对象和不需要记录的对象(如文件上传、请求响应对象等)
if (StringUtils.isNotNull(o) && !isFilterObject(o))
{
try
{
// 将参数对象转为JSON同时排除敏感字段
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
// 转换失败时忽略(避免影响主流程)
}
}
}
}
return params.trim(); // 去除首尾空格
}
/**
*
*
* @param excludeParamNames
* @return
*/
public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
{
// 合并默认排除字段和注解中指定的排除字段
return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
}
/**
*
*
* @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;
}
}
// 如果是Map判断值是否为文件上传对象
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,125 @@
package com.huacai.framework.aspectj;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import com.huacai.common.annotation.RateLimiter;
import com.huacai.common.enums.LimitType;
import com.huacai.common.exception.ServiceException;
import com.huacai.common.utils.StringUtils;
import com.huacai.common.utils.ip.IpUtils;
/**
*
* AOP@RateLimiterRedis
*
* @author huacai
*/
// 标记为AOP切面类
@Aspect
// 注册为Spring组件
@Component
public class RateLimiterAspect
{
// 日志记录器
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
// Redis模板用于执行Lua脚本
private RedisTemplate<Object, Object> redisTemplate;
// 限流Lua脚本从RedisConfig中注入
private RedisScript<Long> limitScript;
// 注入RedisTemplatesetter注入避免循环依赖
@Autowired
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
{
this.redisTemplate = redisTemplate;
}
// 注入限流脚本
@Autowired
public void setLimitScript(RedisScript<Long> limitScript)
{
this.limitScript = limitScript;
}
/**
*
*
* @param point
* @param rateLimiter @RateLimiter
* @throws Throwable
*/
@Before("@annotation(rateLimiter)") // 切点表达式:拦截所有带有@RateLimiter注解的方法
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
{
// 从注解中获取限流时间窗口(秒)
int time = rateLimiter.time();
// 从注解中获取时间窗口内的最大请求数
int count = rateLimiter.count();
// 生成限流key结合注解key、限流类型和方法信息
String combineKey = getCombineKey(rateLimiter, point);
// 将key放入集合Lua脚本需要KEYS参数
List<Object> keys = Collections.singletonList(combineKey);
try
{
// 执行Redis限流脚本返回当前请求计数
Long number = redisTemplate.execute(limitScript, keys, count, time);
// 如果计数超过限制,抛出限流异常
if (StringUtils.isNull(number) || number.intValue() > count)
{
throw new ServiceException("访问过于频繁,请稍候再试");
}
// 记录限流日志
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey);
}
catch (ServiceException e)
{
// 限流异常直接抛出
throw e;
}
catch (Exception e)
{
// 其他异常包装为运行时异常
throw new RuntimeException("服务器限流异常,请稍候再试");
}
}
/**
* key
* key + IP + +
*
* @param rateLimiter @RateLimiter
* @param point
* @return key
*/
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
{
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
// 如果限流类型为IP则拼接客户端IP
if (rateLimiter.limitType() == LimitType.IP)
{
stringBuffer.append(IpUtils.getIpAddr()).append("-");
}
// 获取方法签名信息
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
// 拼接类名和方法名
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
return stringBuffer.toString();
}
}

@ -0,0 +1,40 @@
package com.huacai.framework.config;
import java.util.TimeZone;
import org.mybatis.spring.annotation.MapperScan;
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;
/**
*
*
* @author huacai
*/
/**
*
* SpringAOP
*
* @author huacai
*/
// 标记此类为Spring配置类相当于XML配置文件
@Configuration
// 启用AspectJ自动代理exposeProxy = true表示暴露代理对象允许通过AopContext访问当前代理对象
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定MyBatis Mapper接口的扫描路径自动生成Mapper实现类
@MapperScan("com.huacai.**.mapper")
public class ApplicationConfig
{
/**
* Jackson
* JSON/
*/
// 定义一个Bean用于自定义Jackson的ObjectMapper构建器
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
// 返回一个自定义配置器,设置时区为系统默认时区
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}

@ -0,0 +1,83 @@
package com.huacai.framework.config;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import static com.google.code.kaptcha.Constants.*;
/**
*
*
* @author huacai
*/
@Configuration
public class CaptchaConfig
{
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean()
{
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yesno
properties.setProperty(KAPTCHA_BORDER, "yes");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath()
{
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yesno
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.huacai.framework.config.KaptchaTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}

@ -0,0 +1,166 @@
package com.huacai.framework.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.huacai.common.enums.DataSourceType;
import com.huacai.common.utils.spring.SpringUtils;
import com.huacai.framework.config.properties.DruidProperties;
import com.huacai.framework.datasource.DynamicDataSource;
/**
* Druid
* 广
*
* @author huacai
*/
// 标记为Spring配置类
@Configuration
public class DruidConfig
{
/**
*
*
* @param druidProperties Druid
* @return
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.master") // 绑定配置文件中的主库配置
public DataSource masterDataSource(DruidProperties druidProperties)
{
// 构建Druid数据源
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
// 应用配置属性(如用户名、密码、连接池参数等)
return druidProperties.dataSource(dataSource);
}
/**
*
*
* @param druidProperties Druid
* @return
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.slave") // 绑定配置文件中的从库配置
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
/**
*
*
*
* @param masterDataSource
* @return
*/
@Bean(name = "dynamicDataSource")
@Primary // 优先使用该数据源
public DynamicDataSource dataSource(DataSource masterDataSource)
{
// 存储备选数据源(主库+从库)
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
// 添加从数据源(如果存在)
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
// 创建动态数据源(默认数据源为主库)
return new DynamicDataSource(masterDataSource, targetDataSources);
}
/**
*
*
* @param targetDataSources Map
* @param sourceName SLAVE
* @param beanName SpringBean
*/
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
{
try
{
// 从Spring容器中获取数据源Bean如果存在
DataSource dataSource = SpringUtils.getBean(beanName);
targetDataSources.put(sourceName, dataSource);
}
catch (Exception e)
{
// 从库不存在时忽略(不影响主库使用)
}
}
/**
* Druid广
* common.js广
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
{
// 获取Druid监控页面的配置参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取监控页面的URL模式默认为/druid/*
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
// 构建common.js的URL模式如/druid/js/common.js
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
// common.js在Druid jar包中的路径
final String filePath = "support/http/resources/js/common.js";
// 创建自定义过滤器用于修改common.js内容
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
{
// 先执行过滤器链获取原始的common.js内容
chain.doFilter(request, response);
// 重置响应缓冲区(不重置响应头)
response.resetBuffer();
// 从Druid资源中读取common.js内容
String text = Utils.readFromResource(filePath);
// 正则替换,去除底部的广告链接
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
// 将修改后的内容写入响应
response.getWriter().write(text);
}
@Override
public void destroy()
{
// 销毁方法(无操作)
}
};
// 注册过滤器只对common.js的URL生效
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

@ -0,0 +1,80 @@
package com.huacai.framework.config;
import java.nio.charset.Charset;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.filter.Filter;
import com.huacai.common.constant.Constants;
/**
* Redis使FastJson
* RedisFastJson2
*
* @author huacai
* @param <T>
*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
// 默认字符集为UTF-8用于字节数组与字符串的转换
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
// 自动类型过滤器基于系统常量中的JSON白名单字符串创建用于反序列化时限制可解析的类
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
// 序列化的目标类类型
private Class<T> clazz;
/**
*
*
* @param clazz
*/
public FastJson2JsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
/**
*
*
* @param t
* @return null
* @throws SerializationException
*/
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
// 使用FastJson将对象转为JSON字符串开启WriteClassName特性以记录类名用于反序列化
// 再将JSON字符串按默认字符集转为字节数组
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
/**
*
*
* @param bytes
* @return nullnull
* @throws SerializationException
*/
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
// 将字节数组按默认字符集转为JSON字符串
String str = new String(bytes, DEFAULT_CHARSET);
// 使用FastJson将JSON字符串解析为指定类型的对象应用自动类型过滤器限制可解析的类
return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
}
}

@ -0,0 +1,89 @@
package com.huacai.framework.config;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Value;
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 com.huacai.common.filter.RepeatableFilter;
import com.huacai.common.filter.XssFilter;
import com.huacai.common.utils.StringUtils;
/**
* Filter
* XSS
*
* @author huacai
*/
@Configuration
public class FilterConfig
{
// 从配置文件中读取XSS过滤的排除路径不需要过滤的URL
@Value("${xss.excludes}")
private String excludes;
// 从配置文件中读取XSS过滤的URL模式需要过滤的URL
@Value("${xss.urlPatterns}")
private String urlPatterns;
/**
* XSS
* xss.enabled=trueBean
*
* @return XSSBean
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
public FilterRegistrationBean xssFilterRegistration()
{
// 创建过滤器注册Bean
FilterRegistrationBean registration = new FilterRegistrationBean();
// 设置过滤器适用的调度类型(此处为请求类型)
registration.setDispatcherTypes(DispatcherType.REQUEST);
// 设置要注册的过滤器实例XSS过滤器用于防止XSS攻击
registration.setFilter(new XssFilter());
// 拆分URL模式字符串为数组设置过滤器拦截的URL
registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
// 设置过滤器名称
registration.setName("xssFilter");
// 设置过滤器执行顺序(最高优先级,优先于其他过滤器执行)
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
// 创建初始化参数Map
Map<String, String> initParameters = new HashMap<String, String>();
// 设置排除路径参数从配置文件读取的excludes
initParameters.put("excludes", excludes);
// 为过滤器设置初始化参数
registration.setInitParameters(initParameters);
// 返回配置好的过滤器注册Bean
return registration;
}
/**
*
* 便
*
* @return RepeatableFilterBean
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean someFilterRegistration()
{
// 创建过滤器注册Bean
FilterRegistrationBean registration = new FilterRegistrationBean();
// 设置要注册的过滤器实例RepeatableFilter
registration.setFilter(new RepeatableFilter());
// 设置过滤器拦截所有URL
registration.addUrlPatterns("/*");
// 设置过滤器名称
registration.setName("repeatableFilter");
// 设置过滤器执行顺序(最低优先级,在其他过滤器之后执行)
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
// 返回配置好的过滤器注册Bean
return registration;
}
}

@ -0,0 +1,91 @@
package com.huacai.framework.config;
import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/**
*
* *
*
* @author huacai
*/
public class KaptchaTextCreator extends DefaultTextCreator
{
// 数字字符串字符串数组包含0-10的字符串表示
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
/**
*
*
*
* @return "运算式=?@结果"
*/
@Override
public String getText()
{
// 存储运算结果
Integer result = 0;
// 随机数生成器
Random random = new Random();
// 生成两个0-9之间的随机数
int x = random.nextInt(10);
int y = random.nextInt(10);
// 用于构建验证码文本的字符串构建器
StringBuilder suChinese = new StringBuilder();
// 随机生成运算类型0-乘法1-除法/加法2-减法)
int randomoperands = random.nextInt(3);
// 处理乘法运算
if (randomoperands == 0)
{
result = x * y;
suChinese.append(CNUMBERS[x]); // 拼接第一个数字
suChinese.append("*"); // 拼接乘号
suChinese.append(CNUMBERS[y]); // 拼接第二个数字
}
// 处理除法或加法运算
else if (randomoperands == 1)
{
// 若x不为0且y能被x整除则进行除法运算
if ((x != 0) && y % x == 0)
{
result = y / x;
suChinese.append(CNUMBERS[y]); // 拼接被除数
suChinese.append("/"); // 拼接除号
suChinese.append(CNUMBERS[x]); // 拼接除数
}
// 否则进行加法运算
else
{
result = x + y;
suChinese.append(CNUMBERS[x]); // 拼接第一个数字
suChinese.append("+"); // 拼接加号
suChinese.append(CNUMBERS[y]); // 拼接第二个数字
}
}
// 处理减法运算
else
{
// 若x大于等于y用x减y
if (x >= y)
{
result = x - y;
suChinese.append(CNUMBERS[x]); // 拼接被减数
suChinese.append("-"); // 拼接减号
suChinese.append(CNUMBERS[y]); // 拼接减数
}
// 否则用y减x保证结果非负
else
{
result = y - x;
suChinese.append(CNUMBERS[y]); // 拼接被减数
suChinese.append("-"); // 拼接减号
suChinese.append(CNUMBERS[x]); // 拼接减数
}
}
// 拼接表达式后缀和结果(格式为"=?@结果",用于后续解析验证)
suChinese.append("=?@" + result);
// 返回生成的验证码文本
return suChinese.toString();
}
}

@ -0,0 +1,186 @@
package com.huacai.framework.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import com.huacai.common.utils.StringUtils;
/**
* Mybatis*
* MyBatis
*
* @author huacai
*/
@Configuration
public class MyBatisConfig
{
// 注入环境变量对象用于读取配置文件中的MyBatis相关配置
@Autowired
private Environment env;
// 默认的资源匹配模式,用于扫描类文件
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
/**
*
*
*
* @param typeAliasesPackage
* @return
*/
public static String setTypeAliasesPackage(String typeAliasesPackage)
{
// 创建资源模式解析器,用于解析资源路径
ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
// 创建元数据读取工厂,用于读取类资源的元数据
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
// 存储所有匹配到的包路径
List<String> allResult = new ArrayList<String>();
try
{
// 按逗号分割配置的包路径,处理多个包的情况
for (String aliasesPackage : typeAliasesPackage.split(","))
{
// 存储当前包路径匹配到的结果
List<String> result = new ArrayList<String>();
// 构建资源扫描路径classpath*: + 转换为资源路径的包名 + /**/*.class
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
// 根据路径获取所有匹配的资源(.class文件
Resource[] resources = resolver.getResources(aliasesPackage);
// 遍历资源,提取包路径
if (resources != null && resources.length > 0)
{
MetadataReader metadataReader = null;
for (Resource resource : resources)
{
// 仅处理可读的资源
if (resource.isReadable())
{
// 读取资源的元数据
metadataReader = metadataReaderFactory.getMetadataReader(resource);
try
{
// 获取类的全限定名,提取其所在的包路径并添加到结果中
result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
}
// 去重并添加到总结果中
if (result.size() > 0)
{
HashSet<String> hashResult = new HashSet<String>(result);
allResult.addAll(hashResult);
}
}
// 将去重后的包路径拼接为字符串返回
if (allResult.size() > 0)
{
typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
}
else
{
// 未找到任何包时抛出异常
throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
}
}
catch (IOException e)
{
e.printStackTrace();
}
return typeAliasesPackage;
}
/**
* MapperResource
*
* @param mapperLocations Mapper
* @return Resource
*/
public Resource[] resolveMapperLocations(String[] mapperLocations)
{
// 创建资源模式解析器
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
// 存储解析后的资源
List<Resource> resources = new ArrayList<Resource>();
if (mapperLocations != null)
{
// 遍历每个Mapper路径解析为Resource对象
for (String mapperLocation : mapperLocations)
{
try
{
// 根据路径获取所有匹配的资源Mapper.xml文件
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
}
catch (IOException e)
{
// 忽略解析异常
}
}
}
// 转换为Resource数组返回
return resources.toArray(new Resource[resources.size()]);
}
/**
* SqlSessionFactoryMyBatis
*
* @param dataSource
* @return SqlSessionFactory
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
{
// 从环境变量中读取MyBatis配置的类型别名包路径
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
// 从环境变量中读取Mapper文件路径
String mapperLocations = env.getProperty("mybatis.mapperLocations");
// 从环境变量中读取MyBatis配置文件路径
String configLocation = env.getProperty("mybatis.configLocation");
// 处理类型别名包路径,支持通配符
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
// 添加SpringBoot的VFS实现用于扫描类路径下的资源
VFS.addImplClass(SpringBootVFS.class);
// 创建SqlSessionFactoryBean
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 设置数据源
sessionFactory.setDataSource(dataSource);
// 设置处理后的类型别名包路径
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
// 解析并设置Mapper文件路径
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
// 设置MyBatis配置文件路径
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
// 创建并返回SqlSessionFactory实例
return sessionFactory.getObject();
}
}

@ -0,0 +1,95 @@
package com.huacai.framework.config;
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 org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis
* RedisTemplate
*
* @author huacai
*/
// 标记为Spring配置类
@Configuration
// 启用缓存注解支持(如@Cacheable等
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
/**
* RedisTemplate
* Redis
*
* @param connectionFactory Redis
* @return RedisTemplate
*/
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
// 设置Redis连接工厂
template.setConnectionFactory(connectionFactory);
// 使用FastJson2作为JSON序列化器
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
// 使用StringRedisSerializer序列化Redis的key
template.setKeySerializer(new StringRedisSerializer());
// 使用FastJson2序列化Redis的value
template.setValueSerializer(serializer);
// Hash的key也使用StringRedisSerializer序列化
template.setHashKeySerializer(new StringRedisSerializer());
// Hash的value使用FastJson2序列化
template.setHashValueSerializer(serializer);
// 初始化RedisTemplate
template.afterPropertiesSet();
return template;
}
/**
* RedisLua
*
*
* @return Redis
*/
@Bean
public DefaultRedisScript<Long> limitScript()
{
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 设置脚本内容
redisScript.setScriptText(limitScriptText());
// 设置脚本返回值类型
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* Lua
*
*
* @return Lua
*/
private String limitScriptText()
{
return "local key = KEYS[1]\n" + // 限流key如接口+IP
"local count = tonumber(ARGV[1])\n" + // 最大请求次数
"local time = tonumber(ARGV[2])\n" + // 时间窗口(秒)
"local current = redis.call('get', key);\n" + // 获取当前计数
"if current and tonumber(current) > count then\n" + // 如果当前计数超过限制
" return tonumber(current);\n" + // 返回当前计数(表示限流)
"end\n" +
"current = redis.call('incr', key)\n" + // 计数+1
"if tonumber(current) == 1 then\n" + // 如果是第一次计数
" redis.call('expire', key, time)\n" + // 设置过期时间(时间窗口)
"end\n" +
"return tonumber(current);"; // 返回当前计数(未限流)
}
}

@ -0,0 +1,89 @@
package com.huacai.framework.config;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
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.huacai.common.config.HuaCaiConfig;
import com.huacai.common.constant.Constants;
import com.huacai.framework.interceptor.RepeatSubmitInterceptor;
/**
*
*
*
* @author huacai
*/
// 标记为Spring配置类
@Configuration
// 实现WebMvcConfigurer接口自定义Spring MVC配置
public class ResourcesConfig implements WebMvcConfigurer
{
// 注入重复提交拦截器(用于防止表单重复提交)
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
/**
*
*
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
/** 本地文件上传路径映射 */
// 映射以Constants.RESOURCE_PREFIX开头的URL到本地文件系统的上传目录
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
.addResourceLocations("file:" + HuaCaiConfig.getProfile() + "/");
/** Swagger UI资源映射 */
// 映射Swagger UI的URL到META-INF中的资源目录
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic()); // 设置缓存控制5小时
}
/**
*
*
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
{
// 注册重复提交拦截器,拦截所有请求
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
}
/**
*
* 访
*/
@Bean
public CorsFilter corsFilter()
{
// 创建跨域配置对象
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许携带cookie
// 设置允许访问的源(*表示允许所有域,实际生产环境应指定具体域名)
config.addAllowedOriginPattern("*");
// 设置允许的请求头(*表示允许所有头)
config.addAllowedHeader("*");
// 设置允许的请求方法(*表示允许所有方法GET/POST/PUT等
config.addAllowedMethod("*");
// 设置预检请求的有效期1800秒避免频繁预检
config.setMaxAge(1800L);
// 创建路径映射源,注册跨域配置
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config); // 对所有路径生效
// 返回跨域过滤器
return new CorsFilter(source);
}
}

@ -0,0 +1,148 @@
package com.huacai.framework.config;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
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 com.huacai.framework.config.properties.PermitAllUrlProperties;
import com.huacai.framework.security.filter.JwtAuthenticationTokenFilter;
import com.huacai.framework.security.handle.AuthenticationEntryPointImpl;
import com.huacai.framework.security.handle.LogoutSuccessHandlerImpl;
/**
* spring security
*
* @author huacai
*/
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
/**
*
*/
@Autowired
private UserDetailsService userDetailsService;
/**
*
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/**
* 退
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* token
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
*
*/
@Autowired
private CorsFilter corsFilter;
/**
* 访
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
/**
* anyRequest |
* access | SpringEltrue访
* anonymous | 访
* denyAll | 访
* fullyAuthenticated | 访remember-me
* hasAnyAuthority | 访
* hasAnyRole | 访
* hasAuthority | 访
* hasIpAddress | IPIP访
* hasRole | 访
* permitAll | 访
* rememberMe | remember-me访
* authenticated | 访
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
httpSecurity
// CSRF禁用因为不使用session
.csrf().disable()
// 禁用HTTP响应标头
.headers().cacheControl().disable().and()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
// 添加Logout filter
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,46 @@
package com.huacai.framework.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.huacai.common.utils.ServletUtils;
/**
*
*
*
* @author huacai
*/
// 注册为Spring组件
@Component
public class ServerConfig
{
/**
*
* 访
*
* @return http://localhost:8080/huacai
*/
public String getUrl()
{
// 获取当前请求对象
HttpServletRequest request = ServletUtils.getRequest();
// 生成并返回完整服务地址
return getDomain(request);
}
/**
*
*
* @param request HTTP
* @return ++
*/
public static String getDomain(HttpServletRequest request)
{
// 获取请求的完整URL如http://localhost:8080/huacai/user/list
StringBuffer url = request.getRequestURL();
// 获取上下文路径(如/huacai
String contextPath = request.getServletContext().getContextPath();
// 截取域名+端口部分拼接上下文路径结果如http://localhost:8080/huacai
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
}
}

@ -0,0 +1,84 @@
package com.huacai.framework.config;
import com.huacai.common.utils.Threads;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 线
* 使线线线
*
* @author huacai
**/
@Configuration
public class ThreadPoolConfig
{
// 核心线程池大小:线程池维护的核心线程数量,即使线程空闲也不会被销毁
private int corePoolSize = 50;
// 最大可创建的线程数:线程池允许创建的最大线程数量,当核心线程都在工作且任务队列满时会创建新线程直到达到此值
private int maxPoolSize = 200;
// 队列最大长度:用于存放等待执行任务的阻塞队列容量
private int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间:当线程数量超过核心线程数时,多余的空闲线程的存活时间(单位:秒)
private int keepAliveSeconds = 300;
/**
* 线
*
*
* @return ThreadPoolTaskExecutor
*/
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize); // 设置最大线程数
executor.setCorePoolSize(corePoolSize); // 设置核心线程数
executor.setQueueCapacity(queueCapacity); // 设置任务队列容量
executor.setKeepAliveSeconds(keepAliveSeconds); // 设置空闲线程存活时间
// 线程池对拒绝任务(无线程可用且队列已满)的处理策略:使用调用者所在线程执行任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 线
*
*
* @return ScheduledExecutorService
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService()
{
return new ScheduledThreadPoolExecutor(
corePoolSize, // 核心线程数
// 线程工厂:设置线程名称格式为"schedule-pool-%d",且为守护线程
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
// 拒绝策略:使用调用者所在线程执行任务
new ThreadPoolExecutor.CallerRunsPolicy()
) {
/**
*
*
*
* @param r
* @param t null
*/
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
// 打印任务执行过程中发生的异常
Threads.printException(r, t);
}
};
}
}

@ -0,0 +1,22 @@
package com.huacai.framework.config;
import com.huacai.framework.interceptor.DemoEnvironmentInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private DemoEnvironmentInterceptor demoEnvironmentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 演示环境拦截器
registry.addInterceptor(demoEnvironmentInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login", "/logout", "/captchaImage"); // 排除登录等必要接口
}
}

@ -0,0 +1,89 @@
package com.huacai.framework.config.properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
/**
* druid
*
* @author huacai
*/
@Configuration
public class DruidProperties
{
@Value("${spring.datasource.druid.initialSize}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
private int maxWait;
@Value("${spring.datasource.druid.connectTimeout}")
private int connectTimeout;
@Value("${spring.datasource.druid.socketTimeout}")
private int socketTimeout;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
private boolean testOnReturn;
public DruidDataSource dataSource(DruidDataSource datasource)
{
/** 配置初始化大小、最小、最大 */
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
/** 配置获取连接等待超时的时间 */
datasource.setMaxWait(maxWait);
/** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */
datasource.setConnectTimeout(connectTimeout);
/** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */
datasource.setSocketTimeout(socketTimeout);
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* sqlselect 'x'validationQuerynulltestOnBorrowtestOnReturntestWhileIdle
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}

@ -0,0 +1,73 @@
package com.huacai.framework.config.properties;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.huacai.common.annotation.Anonymous;
/**
* Anonymous访url
*
* @author huacai
*/
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
private ApplicationContext applicationContext;
private List<String> urls = new ArrayList<>();
public String ASTERISK = "*";
@Override
public void afterPropertiesSet()
{
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取方法上边的注解 替代path variable 为 *
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
// 获取类上边的注解, 替代path variable 为 *
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
});
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException
{
this.applicationContext = context;
}
public List<String> getUrls()
{
return urls;
}
public void setUrls(List<String> urls)
{
this.urls = urls;
}
}

@ -0,0 +1,44 @@
package com.huacai.framework.datasource;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*
* Spring AbstractRoutingDataSource
* 线
*
* @author huacai
*/
public class DynamicDataSource extends AbstractRoutingDataSource
{
/**
*
*
* @param defaultTargetDataSource 使
* @param targetDataSources keyvalue
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
// 设置默认目标数据源
super.setDefaultTargetDataSource(defaultTargetDataSource);
// 设置目标数据源映射关系
super.setTargetDataSources(targetDataSources);
// 初始化数据源,使配置生效
super.afterPropertiesSet();
}
/**
* 使
* keytargetDataSources
*
* @return
*/
@Override
protected Object determineCurrentLookupKey()
{
// 从上下文持有者中获取当前线程绑定的数据源类型
return DynamicDataSourceContextHolder.getDataSourceType();
}
}

@ -0,0 +1,45 @@
package com.huacai.framework.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*
* @author huacai
*/
public class DynamicDataSourceContextHolder
{
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使ThreadLocalThreadLocal使线
* 线线
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
*
*/
public static void setDataSourceType(String dsType)
{
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
*
*/
public static String getDataSourceType()
{
return CONTEXT_HOLDER.get();
}
/**
*
*/
public static void clearDataSourceType()
{
CONTEXT_HOLDER.remove();
}
}

@ -0,0 +1,60 @@
package com.huacai.framework.interceptor;
import com.huacai.common.exception.DemoEnvironmentException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* GET
*/
// 注册为Spring组件
@Component
public class DemoEnvironmentInterceptor implements HandlerInterceptor {
// 从配置文件中读取是否启用演示环境(默认不启用)
@Value("${demo.env.enabled:false}")
private boolean isDemoEnvironment;
// 需要排除的URL这些URL即使在演示环境也允许所有请求
private static final String[] EXCLUDE_URLS = {
"/login", // 登录
"/logout", // 注销
"/captchaImage" // 验证码
};
/**
*
* GET
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果不是演示环境,直接放行
if (!isDemoEnvironment) {
return true;
}
// 获取当前请求的URI
String requestURI = request.getRequestURI();
// 检查请求URI是否在排除列表中如果是则放行
for (String excludeUrl : EXCLUDE_URLS) {
if (requestURI.contains(excludeUrl)) {
return true;
}
}
// 拦截所有非GET请求演示环境不允许修改操作
String method = request.getMethod();
if (!"GET".equalsIgnoreCase(method)) {
throw new DemoEnvironmentException("演示环境,不允许进行此操作");
}
// GET请求放行
return true;
}
}

@ -0,0 +1,77 @@
package com.huacai.framework.interceptor;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import com.alibaba.fastjson2.JSON;
import com.huacai.common.annotation.RepeatSubmit;
import com.huacai.common.core.domain.AjaxResult;
import com.huacai.common.utils.ServletUtils;
/**
*
*
*
* @author huacai
*/
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
{
/**
*
*
*
* @param request HTTP
* @param response HTTP
* @param handler Controller
* @return true-false-
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
// 判断处理器是否为HandlerMethod即Controller中的方法
if (handler instanceof HandlerMethod)
{
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取当前请求对应的方法对象
Method method = handlerMethod.getMethod();
// 检查方法上是否标注了@RepeatSubmit注解
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null)
{
// 调用子类实现的方法判断是否为重复提交
if (this.isRepeatSubmit(request, annotation))
{
// 若为重复提交,构建错误响应对象
AjaxResult ajaxResult = AjaxResult.error(annotation.message());
// 将错误响应转换为JSON并写入响应流
ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));
// 返回false中断请求处理
return false;
}
}
// 非重复提交,继续处理请求
return true;
}
else
{
// 非Controller方法请求直接放行
return true;
}
}
/**
*
*
*
* @param request
* @param annotation
* @return true-false-
* @throws Exception
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

@ -0,0 +1,145 @@
package com.huacai.framework.interceptor.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
import com.huacai.common.annotation.RepeatSubmit;
import com.huacai.common.constant.CacheConstants;
import com.huacai.common.core.redis.RedisCache;
import com.huacai.common.filter.RepeatedlyRequestWrapper;
import com.huacai.common.utils.StringUtils;
import com.huacai.common.utils.http.HttpHelper;
import com.huacai.framework.interceptor.RepeatSubmitInterceptor;
/**
* URL
* URL10
*
* @author huacai
*/
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
{
// 用于存储请求参数的Map键名
public final String REPEAT_PARAMS = "repeatParams";
// 用于存储请求时间的Map键名
public final String REPEAT_TIME = "repeatTime";
// 从配置文件中获取令牌的请求头自定义标识如Token的Header名称
@Value("${token.header}")
private String header;
// 注入Redis缓存工具用于存储请求记录
@Autowired
private RedisCache redisCache;
/**
*
*
* @param request HTTP
* @param annotation
* @return true-false-
*/
@SuppressWarnings("unchecked")
@Override
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation)
{
// 存储当前请求的参数
String nowParams = "";
// 如果请求是可重复读取的包装类(解决请求体只能读一次的问题)
if (request instanceof RepeatedlyRequestWrapper)
{
RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
// 获取请求体中的参数
nowParams = HttpHelper.getBodyString(repeatedlyRequest);
}
// 如果body参数为空则获取URL参数如GET请求的query参数
if (StringUtils.isEmpty(nowParams))
{
nowParams = JSON.toJSONString(request.getParameterMap());
}
// 存储当前请求的数据(参数和时间)
Map<String, Object> nowDataMap = new HashMap<String, Object>();
nowDataMap.put(REPEAT_PARAMS, nowParams); // 当前请求参数
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); // 当前请求时间戳
// 获取请求的URL作为缓存的一部分key
String url = request.getRequestURI();
// 从请求头中获取令牌(作为唯一标识,若为空则用空字符串)
String submitKey = StringUtils.trimToEmpty(request.getHeader(header));
// 构建Redis缓存的key前缀 + URL + 令牌确保同一用户同一URL的唯一性
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
// 从Redis中获取之前的请求记录
Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
if (sessionObj != null)
{
// 将缓存中的数据转换为Map
Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
// 判断缓存中是否存在当前URL的请求记录
if (sessionMap.containsKey(url))
{
// 获取上一次请求的数据
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
// 比较参数是否相同且时间间隔是否小于注解配置的间隔时间
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))
{
// 满足条件,视为重复提交
return true;
}
}
}
// 若不是重复提交则将当前请求数据存入Redis
Map<String, Object> cacheMap = new HashMap<String, Object>();
cacheMap.put(url, nowDataMap);
// 设置缓存过期时间为注解配置的间隔时间(毫秒)
redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);
// 不是重复提交
return false;
}
/**
*
*
* @param nowMap Map
* @param preMap Map
* @return true-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);
}
/**
*
*
* @param nowMap Map
* @param preMap Map
* @param interval
* @return true-false-
*/
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
{
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
// 若当前时间与上一次时间的差值小于间隔时间,则视为重复提交
if ((time1 - time2) < interval)
{
return true;
}
return false;
}
}

@ -0,0 +1,70 @@
package com.huacai.framework.manager;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.huacai.common.utils.Threads;
import com.huacai.common.utils.spring.SpringUtils;
/**
*
*
*
* @author huacai
*/
public class AsyncManager
{
/**
* 10
* 10
*/
private final int OPERATE_DELAY_TIME = 10;
/**
* 线
* Spring线beanscheduledExecutorService
*/
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
/**
* -
* new
*/
private AsyncManager(){}
/**
*
* 线
*/
private static AsyncManager me = new AsyncManager();
/**
*
*
* @return
*/
public static AsyncManager me()
{
return me;
}
/**
*
* 线OPERATE_DELAY_TIME
*
* @param task TimerTask
*/
public void execute(TimerTask task)
{
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/**
* 线
* 线线
*/
public void shutdown()
{
Threads.shutdownAndAwaitTermination(executor);
}
}

@ -0,0 +1,50 @@
package com.huacai.framework.manager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
/**
*
* 退线线
*
* @author huacai
*/
@Component
public class ShutdownManager
{
// 日志记录器,使用"sys-user"日志名
private static final Logger logger = LoggerFactory.getLogger("sys-user");
/**
*
* @PreDestroySpringBean
*/
@PreDestroy
public void destroy()
{
// 关闭异步任务管理器
shutdownAsyncManager();
}
/**
* 线
* AsyncManagershutdown线
*/
private void shutdownAsyncManager()
{
try
{
// 记录关闭线程池的日志
logger.info("====关闭后台任务任务线程池====");
// 获取异步任务管理器单例并调用shutdown方法
AsyncManager.me().shutdown();
}
catch (Exception e)
{
// 记录关闭过程中发生的异常
logger.error(e.getMessage(), e);
}
}
}

@ -0,0 +1,102 @@
package com.huacai.framework.manager.factory;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.huacai.common.constant.Constants;
import com.huacai.common.utils.LogUtils;
import com.huacai.common.utils.ServletUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.common.utils.ip.AddressUtils;
import com.huacai.common.utils.ip.IpUtils;
import com.huacai.common.utils.spring.SpringUtils;
import com.huacai.system.domain.SysLogininfor;
import com.huacai.system.domain.SysOperLog;
import com.huacai.system.service.ISysLogininforService;
import com.huacai.system.service.ISysOperLogService;
import eu.bitwalker.useragentutils.UserAgent;
/**
*
*
* @author huacai
*/
public class AsyncFactory
{
private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
/**
*
*
* @param username
* @param status
* @param message
* @param args
* @return task
*/
public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args)
{
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getIpAddr();
return new TimerTask()
{
@Override
public void run()
{
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(LogUtils.getBlock(ip));
s.append(address);
s.append(LogUtils.getBlock(username));
s.append(LogUtils.getBlock(status));
s.append(LogUtils.getBlock(message));
// 打印信息到日志
sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().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 (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
{
logininfor.setStatus(Constants.SUCCESS);
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus(Constants.FAIL);
}
// 插入数据
SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
}
};
}
/**
*
*
* @param operLog
* @return task
*/
public static TimerTask recordOper(final SysOperLog operLog)
{
return new TimerTask()
{
@Override
public void run()
{
// 远程查询操作地点
operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
}
};
}
}

@ -0,0 +1,46 @@
package com.huacai.framework.security.context;
import org.springframework.security.core.Authentication;
/**
*
* 线Spring SecurityAuthentication
* 线线
*
* @author huacai
*/
public class AuthenticationContextHolder
{
// 线程本地变量用于存储当前线程的Authentication对象
// ThreadLocal确保每个线程只能访问自己的变量副本实现线程隔离
private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
/**
* 线
*
* @return 线Authenticationnull
*/
public static Authentication getContext()
{
return contextHolder.get();
}
/**
* 线
*
* @param context Authentication
*/
public static void setContext(Authentication context)
{
contextHolder.set(context);
}
/**
* 线
* 线
*/
public static void clearContext()
{
contextHolder.remove();
}
}

@ -0,0 +1,43 @@
package com.huacai.framework.security.context;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.huacai.common.core.text.Convert;
/**
*
*
*
* @author huacai
*/
public class PermissionContextHolder
{
// 权限上下文在请求属性中的存储键名
private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
/**
*
*
*
* @param permission
*/
public static void setContext(String permission)
{
// 获取当前请求的属性对象将权限信息存入作用域为REQUEST仅当前请求有效
RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
RequestAttributes.SCOPE_REQUEST);
}
/**
*
*
*
* @return
*/
public static String getContext()
{
// 从当前请求属性中获取权限信息,转换为字符串后返回
return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
RequestAttributes.SCOPE_REQUEST));
}
}

@ -0,0 +1,66 @@
package com.huacai.framework.security.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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 com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.utils.SecurityUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.framework.web.service.TokenService;
/**
* JWT
* JWTSpring Security
* OncePerRequestFilter
*
* @author huacai
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
// 注入令牌服务,用于处理令牌的解析、验证等操作
@Autowired
private TokenService tokenService;
/**
*
*
* @param request HTTP
* @param response HTTP
* @param chain
* @throws ServletException Servlet
* @throws IOException IO
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
// 从请求中获取登录用户信息通过TokenService解析请求中的令牌
LoginUser loginUser = tokenService.getLoginUser(request);
// 若登录用户信息存在且当前Security上下文没有认证信息
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
// 验证令牌的有效性(如是否过期、是否被篡改等)
tokenService.verifyToken(loginUser);
// 创建认证令牌对象,包含用户信息和权限集合
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
// 设置认证详情如请求IP、会话ID等
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 将认证信息存入Security上下文供后续权限校验使用
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
// 继续执行过滤器链,将请求传递给下一个过滤器或目标资源
chain.doFilter(request, response);
}
}

@ -0,0 +1,48 @@
package com.huacai.framework.security.handle;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
import com.huacai.common.constant.HttpStatus;
import com.huacai.common.core.domain.AjaxResult;
import com.huacai.common.utils.ServletUtils;
import com.huacai.common.utils.StringUtils;
/**
*
* Spring SecurityAuthenticationEntryPoint
*
* @author huacai
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
// 序列化版本号,用于对象序列化时的版本控制
private static final long serialVersionUID = -8970718410437077606L;
/**
*
* 访
*
* @param request HTTP
* @param response HTTP
* @param e
* @throws IOException IO
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException
{
// 设置错误状态码为401未授权
int code = HttpStatus.UNAUTHORIZED;
// 构建错误消息包含请求的URI和认证失败提示
String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
// 将错误信息封装为AjaxResult对象转换为JSON并写入响应流
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
}
}

@ -0,0 +1,62 @@
package com.huacai.framework.security.handle;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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 com.alibaba.fastjson2.JSON;
import com.huacai.common.constant.Constants;
import com.huacai.common.core.domain.AjaxResult;
import com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.utils.MessageUtils;
import com.huacai.common.utils.ServletUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.framework.manager.AsyncManager;
import com.huacai.framework.manager.factory.AsyncFactory;
import com.huacai.framework.web.service.TokenService;
/**
* 退
* Spring SecurityLogoutSuccessHandler退
*
* @author huacai
*/
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
// 注入令牌服务,用于处理登录用户的令牌信息
@Autowired
private TokenService tokenService;
/**
* 退
*
* @param request HTTP
* @param response HTTP
* @param authentication
* @throws IOException IO
* @throws ServletException Servlet
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
// 从请求中获取当前登录用户信息
LoginUser loginUser = tokenService.getLoginUser(request);
// 若登录用户信息存在
if (StringUtils.isNotNull(loginUser))
{
String userName = loginUser.getUsername();
// 从缓存中删除该用户的登录记录(使令牌失效)
tokenService.delLoginUser(loginUser.getToken());
// 异步记录用户退出日志(使用异步管理器执行日志记录任务)
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
}
// 向客户端返回退出成功的响应JSON格式的成功消息
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));
}
}

@ -0,0 +1,302 @@
package com.huacai.framework.web.domain;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import com.huacai.common.utils.Arith;
import com.huacai.common.utils.ip.IpUtils;
import com.huacai.framework.web.domain.server.Cpu;
import com.huacai.framework.web.domain.server.Jvm;
import com.huacai.framework.web.domain.server.Mem;
import com.huacai.framework.web.domain.server.Sys;
import com.huacai.framework.web.domain.server.SysFile;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.CentralProcessor.TickType;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;
/**
*
* CPUJVM
*
* @author huacai
*/
public class Server
{
// OSHI库获取硬件信息时的等待时间毫秒
private static final int OSHI_WAIT_SECOND = 1000;
/**
* CPU
*/
private Cpu cpu = new Cpu();
/**
*
*/
private Mem mem = new Mem();
/**
* JVM
*/
private Jvm jvm = new Jvm();
/**
*
*/
private Sys sys = new Sys();
/**
*
*/
private List<SysFile> sysFiles = new LinkedList<SysFile>();
// 获取CPU信息
public Cpu getCpu()
{
return cpu;
}
// 设置CPU信息
public void setCpu(Cpu cpu)
{
this.cpu = cpu;
}
// 获取内存信息
public Mem getMem()
{
return mem;
}
// 设置内存信息
public void setMem(Mem mem)
{
this.mem = mem;
}
// 获取JVM信息
public Jvm getJvm()
{
return jvm;
}
// 设置JVM信息
public void setJvm(Jvm jvm)
{
this.jvm = jvm;
}
// 获取服务器信息
public Sys getSys()
{
return sys;
}
// 设置服务器信息
public void setSys(Sys sys)
{
this.sys = sys;
}
// 获取磁盘信息列表
public List<SysFile> getSysFiles()
{
return sysFiles;
}
// 设置磁盘信息列表
public void setSysFiles(List<SysFile> sysFiles)
{
this.sysFiles = sysFiles;
}
/**
*
* 使OSHI
*
* @throws Exception
*/
public void copyTo() throws Exception
{
SystemInfo si = new SystemInfo(); // OSHI系统信息入口
HardwareAbstractionLayer hal = si.getHardware(); // 硬件抽象层
// 设置CPU信息
setCpuInfo(hal.getProcessor());
// 设置内存信息
setMemInfo(hal.getMemory());
// 设置服务器基础信息
setSysInfo();
// 设置JVM信息
setJvmInfo();
// 设置磁盘信息
setSysFiles(si.getOperatingSystem());
}
/**
* CPU
*
* @param processor OSHICPU
*/
private void setCpuInfo(CentralProcessor processor)
{
// 获取CPU负载滴答数第一次采样
long[] prevTicks = processor.getSystemCpuLoadTicks();
// 等待一段时间让CPU负载有变化
Util.sleep(OSHI_WAIT_SECOND);
// 获取CPU负载滴答数第二次采样
long[] ticks = processor.getSystemCpuLoadTicks();
// 计算各状态的CPU时间两次采样的差值
long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; // 低优先级用户态时间
long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; // 硬中断时间
long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; // 软中断时间
long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; // 被其他虚拟机占用的时间
long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; // 系统态时间
long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; // 用户态时间
long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; // IO等待时间
long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; // 空闲时间
// 总CPU时间
long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
// 设置CPU核心数
cpu.setCpuNum(processor.getLogicalProcessorCount());
// 设置总CPU时间用于计算使用率
cpu.setTotal(totalCpu);
// 设置系统态时间
cpu.setSys(cSys);
// 设置用户态时间
cpu.setUsed(user);
// 设置IO等待时间
cpu.setWait(iowait);
// 设置空闲时间
cpu.setFree(idle);
}
/**
*
*
* @param memory OSHI
*/
private void setMemInfo(GlobalMemory memory)
{
// 设置总内存(字节)
mem.setTotal(memory.getTotal());
// 设置已使用内存(总内存 - 可用内存)
mem.setUsed(memory.getTotal() - memory.getAvailable());
// 设置空闲内存
mem.setFree(memory.getAvailable());
}
/**
*
*/
private void setSysInfo()
{
Properties props = System.getProperties(); // 系统属性
// 设置服务器名称(主机名)
sys.setComputerName(IpUtils.getHostName());
// 设置服务器IP地址
sys.setComputerIp(IpUtils.getHostIp());
// 设置操作系统名称
sys.setOsName(props.getProperty("os.name"));
// 设置系统架构
sys.setOsArch(props.getProperty("os.arch"));
// 设置项目部署路径
sys.setUserDir(props.getProperty("user.dir"));
}
/**
* JVM
*
* @throws UnknownHostException
*/
private void setJvmInfo() throws UnknownHostException
{
Properties props = System.getProperties();
// 设置JVM总内存字节
jvm.setTotal(Runtime.getRuntime().totalMemory());
// 设置JVM最大内存字节
jvm.setMax(Runtime.getRuntime().maxMemory());
// 设置JVM空闲内存字节
jvm.setFree(Runtime.getRuntime().freeMemory());
// 设置JDK版本
jvm.setVersion(props.getProperty("java.version"));
// 设置JDK安装路径
jvm.setHome(props.getProperty("java.home"));
}
/**
*
*
* @param os OSHI
*/
private void setSysFiles(OperatingSystem os)
{
FileSystem fileSystem = os.getFileSystem(); // 文件系统
List<OSFileStore> fsArray = fileSystem.getFileStores(); // 磁盘分区列表
// 遍历所有磁盘分区,封装信息
for (OSFileStore fs : fsArray)
{
long free = fs.getUsableSpace(); // 可用空间(字节)
long total = fs.getTotalSpace(); // 总空间(字节)
long used = total - free; // 已使用空间
SysFile sysFile = new SysFile();
sysFile.setDirName(fs.getMount()); // 挂载点路径
sysFile.setSysTypeName(fs.getType()); // 文件系统类型
sysFile.setTypeName(fs.getName()); // 分区名称
sysFile.setTotal(convertFileSize(total)); // 总大小(格式化)
sysFile.setFree(convertFileSize(free)); // 剩余大小(格式化)
sysFile.setUsed(convertFileSize(used)); // 已使用大小(格式化)
// 计算使用率(保留两位小数)
sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100));
sysFiles.add(sysFile);
}
}
/**
* BKBMBGB
*
* @param size
* @return 1.5 GB
*/
public String convertFileSize(long size)
{
long kb = 1024;
long mb = kb * 1024;
long gb = mb * 1024;
if (size >= gb)
{
return String.format("%.1f GB", (float) size / gb);
}
else if (size >= mb)
{
float f = (float) size / mb;
return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); // 大于100MB时取整
}
else if (size >= kb)
{
float f = (float) size / kb;
return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); // 大于100KB时取整
}
else
{
return String.format("%d B", size);
}
}
}

@ -0,0 +1,129 @@
package com.huacai.framework.web.domain.server;
import com.huacai.common.utils.Arith;
/**
* CPU
* CPU使
*
* @author huacai
*/
public class Cpu
{
/**
*
*/
private int cpuNum;
/**
* CPU使
*/
private double total;
/**
* CPU使
*/
private double sys;
/**
* CPU使
*/
private double used;
/**
* CPU
*/
private double wait;
/**
* CPU
*/
private double free;
// 获取CPU核心数
public int getCpuNum()
{
return cpuNum;
}
// 设置CPU核心数
public void setCpuNum(int cpuNum)
{
this.cpuNum = cpuNum;
}
/**
* CPU使
* 使 * 1002
*/
public double getTotal()
{
return Arith.round(Arith.mul(total, 100), 2);
}
// 设置CPU总使用率原始值
public void setTotal(double total)
{
this.total = total;
}
/**
* CPU使
* (使 / 使) * 1002
*/
public double getSys()
{
return Arith.round(Arith.mul(sys / total, 100), 2);
}
// 设置CPU系统使用率原始值
public void setSys(double sys)
{
this.sys = sys;
}
/**
* CPU使
* (使 / 使) * 1002
*/
public double getUsed()
{
return Arith.round(Arith.mul(used / total, 100), 2);
}
// 设置CPU用户使用率原始值
public void setUsed(double used)
{
this.used = used;
}
/**
* CPU
* ( / 使) * 1002
*/
public double getWait()
{
return Arith.round(Arith.mul(wait / total, 100), 2);
}
// 设置CPU当前等待率原始值
public void setWait(double wait)
{
this.wait = wait;
}
/**
* CPU
* ( / 使) * 1002
*/
public double getFree()
{
return Arith.round(Arith.mul(free / total, 100), 2);
}
// 设置CPU当前空闲率原始值
public void setFree(double free)
{
this.free = free;
}
}

@ -0,0 +1,162 @@
package com.huacai.framework.web.domain.server;
import java.lang.management.ManagementFactory;
import com.huacai.common.utils.Arith;
import com.huacai.common.utils.DateUtils;
/**
* JVM
* JVM使
*
* @author huacai
*/
public class Jvm
{
/**
* JVM()
*/
private double total;
/**
* JVM()
*/
private double max;
/**
* JVM()
*/
private double free;
/**
* JDK
*/
private String version;
/**
* JDK
*/
private String home;
/**
* JVM(M)
* / (1024*1024)2
*/
public double getTotal()
{
return Arith.div(total, (1024 * 1024), 2);
}
// 设置当前JVM占用的内存总数(原始字节数)
public void setTotal(double total)
{
this.total = total;
}
/**
* JVM(M)
* / (1024*1024)2
*/
public double getMax()
{
return Arith.div(max, (1024 * 1024), 2);
}
// 设置JVM最大可用内存总数(原始字节数)
public void setMax(double max)
{
this.max = max;
}
/**
* JVM(M)
* / (1024*1024)2
*/
public double getFree()
{
return Arith.div(free, (1024 * 1024), 2);
}
// 设置JVM空闲内存(原始字节数)
public void setFree(double free)
{
this.free = free;
}
/**
* JVM使(M)
* ( - ) / (1024*1024)2
*/
public double getUsed()
{
return Arith.div(total - free, (1024 * 1024), 2);
}
/**
* JVM使()
* (使 / ) * 100
*/
public double getUsage()
{
return Arith.mul(Arith.div(total - free, total, 4), 100);
}
/**
* JDK
* Bean
*/
public String getName()
{
return ManagementFactory.getRuntimeMXBean().getVmName();
}
// 获取JDK版本
public String getVersion()
{
return version;
}
// 设置JDK版本
public void setVersion(String version)
{
this.version = version;
}
// 获取JDK安装路径
public String getHome()
{
return home;
}
// 设置JDK安装路径
public void setHome(String home)
{
this.home = home;
}
/**
* JDK
*
*/
public String getStartTime()
{
return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate());
}
/**
* JDK
*
*/
public String getRunTime()
{
return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate());
}
/**
* JVM
* Bean
*/
public String getInputArgs()
{
return ManagementFactory.getRuntimeMXBean().getInputArguments().toString();
}
}

@ -0,0 +1,81 @@
package com.huacai.framework.web.domain.server;
import com.huacai.common.utils.Arith;
/**
*
* 使使
*
* @author huacai
*/
public class Mem
{
/**
*
*/
private double total;
/**
*
*/
private double used;
/**
*
*/
private double free;
/**
* GB
* / (1024*1024*1024)2
*/
public double getTotal()
{
return Arith.div(total, (1024 * 1024 * 1024), 2);
}
// 设置内存总量(原始字节数)
public void setTotal(long total)
{
this.total = total;
}
/**
* GB
* / (1024*1024*1024)2
*/
public double getUsed()
{
return Arith.div(used, (1024 * 1024 * 1024), 2);
}
// 设置已用内存(原始字节数)
public void setUsed(long used)
{
this.used = used;
}
/**
* GB
* / (1024*1024*1024)2
*/
public double getFree()
{
return Arith.div(free, (1024 * 1024 * 1024), 2);
}
// 设置剩余内存(原始字节数)
public void setFree(long free)
{
this.free = free;
}
/**
* 使
* ( / ) * 100
*/
public double getUsage()
{
return Arith.mul(Arith.div(used, total, 4), 100);
}
}

@ -0,0 +1,95 @@
package com.huacai.framework.web.domain.server;
/**
*
* IP
*
* @author huacai
*/
public class Sys
{
/**
*
*/
private String computerName;
/**
* IP
*/
private String computerIp;
/**
*
*/
private String userDir;
/**
*
*/
private String osName;
/**
* x86_64
*/
private String osArch;
// 获取服务器名称
public String getComputerName()
{
return computerName;
}
// 设置服务器名称
public void setComputerName(String computerName)
{
this.computerName = computerName;
}
// 获取服务器IP地址
public String getComputerIp()
{
return computerIp;
}
// 设置服务器IP地址
public void setComputerIp(String computerIp)
{
this.computerIp = computerIp;
}
// 获取项目部署路径
public String getUserDir()
{
return userDir;
}
// 设置项目部署路径
public void setUserDir(String userDir)
{
this.userDir = userDir;
}
// 获取操作系统名称
public String getOsName()
{
return osName;
}
// 设置操作系统名称
public void setOsName(String osName)
{
this.osName = osName;
}
// 获取系统架构
public String getOsArch()
{
return osArch;
}
// 设置系统架构
public void setOsArch(String osArch)
{
this.osArch = osArch;
}
}

@ -0,0 +1,129 @@
package com.huacai.framework.web.domain.server;
/**
*
* 使
*
* @author huacai
*/
public class SysFile
{
/**
* C:/D:/
*/
private String dirName;
/**
* NTFSext4
*/
private String sysTypeName;
/**
*
*/
private String typeName;
/**
* 100GB
*/
private String total;
/**
*
*/
private String free;
/**
* 使
*/
private String used;
/**
* 使
*/
private double usage;
// 获取盘符路径
public String getDirName()
{
return dirName;
}
// 设置盘符路径
public void setDirName(String dirName)
{
this.dirName = dirName;
}
// 获取盘符类型
public String getSysTypeName()
{
return sysTypeName;
}
// 设置盘符类型
public void setSysTypeName(String sysTypeName)
{
this.sysTypeName = sysTypeName;
}
// 获取文件类型描述
public String getTypeName()
{
return typeName;
}
// 设置文件类型描述
public void setTypeName(String typeName)
{
this.typeName = typeName;
}
// 获取总大小
public String getTotal()
{
return total;
}
// 设置总大小
public void setTotal(String total)
{
this.total = total;
}
// 获取剩余大小
public String getFree()
{
return free;
}
// 设置剩余大小
public void setFree(String free)
{
this.free = free;
}
// 获取已使用大小
public String getUsed()
{
return used;
}
// 设置已使用大小
public void setUsed(String used)
{
this.used = used;
}
// 获取资源使用率
public double getUsage()
{
return usage;
}
// 设置资源使用率
public void setUsage(double usage)
{
this.usage = usage;
}
}

@ -0,0 +1,192 @@
package com.huacai.framework.web.exception;
import javax.servlet.http.HttpServletRequest;
import com.huacai.common.exception.DemoEnvironmentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import com.huacai.common.constant.HttpStatus;
import com.huacai.common.core.domain.AjaxResult;
import com.huacai.common.exception.DemoModeException;
import com.huacai.common.exception.ServiceException;
import com.huacai.common.utils.StringUtils;
/**
*
*
* @author huacai
*/
// 标记为全局控制器增强,用于统一处理控制器层的异常
@RestControllerAdvice
public class GlobalExceptionHandler
{
// 日志记录器,用于记录异常信息
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
*
*/
// 声明处理DemoEnvironmentException类型异常的方法
@ExceptionHandler(DemoEnvironmentException.class)
public AjaxResult handleDemoEnvironmentException(DemoEnvironmentException e) {
// 记录异常信息到日志
log.error(e.getMessage(), e);
// 返回包含错误信息的AjaxResult
return AjaxResult.error(e.getMessage());
}
/**
*
*/
// 声明处理AccessDeniedException类型异常的方法同时接收HttpServletRequest参数
@ExceptionHandler(AccessDeniedException.class)
public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request)
{
// 获取请求的URI
String requestURI = request.getRequestURI();
// 记录权限校验失败的日志,包含请求地址和异常信息
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
// 返回403状态码和权限不足提示信息的AjaxResult
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
/**
*
*/
// 声明处理HttpRequestMethodNotSupportedException类型异常的方法
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request)
{
// 获取请求的URI
String requestURI = request.getRequestURI();
// 记录不支持请求方式的日志,包含请求地址和不支持的方法
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
// 返回包含异常信息的AjaxResult
return AjaxResult.error(e.getMessage());
}
/**
*
*/
// 声明处理ServiceException类型异常的方法
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
{
// 记录业务异常信息到日志
log.error(e.getMessage(), e);
// 获取业务异常中的错误代码
Integer code = e.getCode();
// 根据错误代码是否存在返回包含对应信息的AjaxResult
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
*
*/
// 声明处理MissingPathVariableException类型异常的方法
@ExceptionHandler(MissingPathVariableException.class)
public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
{
// 获取请求的URI
String requestURI = request.getRequestURI();
// 记录缺少路径变量的异常日志
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
// 返回包含具体缺少的路径变量信息的AjaxResult
return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
*
*/
// 声明处理MethodArgumentTypeMismatchException类型异常的方法
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
{
// 获取请求的URI
String requestURI = request.getRequestURI();
// 记录参数类型不匹配的异常日志
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
// 返回包含参数名、要求类型和实际值的错误信息的AjaxResult
return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
}
/**
*
*/
// 声明处理RuntimeException类型异常的方法作为运行时异常的兜底处理
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
// 获取请求的URI
String requestURI = request.getRequestURI();
// 记录未知运行时异常的日志
log.error("请求地址'{}',发生未知异常.", requestURI, e);
// 返回包含异常信息的AjaxResult
return AjaxResult.error(e.getMessage());
}
/**
*
*/
// 声明处理Exception类型异常的方法作为所有未被特定处理的异常的兜底
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e, HttpServletRequest request)
{
// 获取请求的URI
String requestURI = request.getRequestURI();
// 记录系统异常的日志
log.error("请求地址'{}',发生系统异常.", requestURI, e);
// 返回包含异常信息的AjaxResult
return AjaxResult.error(e.getMessage());
}
/**
*
*/
// 声明处理BindException类型异常的方法表单绑定验证失败
@ExceptionHandler(BindException.class)
public AjaxResult handleBindException(BindException e)
{
// 记录验证异常信息到日志
log.error(e.getMessage(), e);
// 获取第一个验证错误的默认信息
String message = e.getAllErrors().get(0).getDefaultMessage();
// 返回包含验证错误信息的AjaxResult
return AjaxResult.error(message);
}
/**
*
*/
// 声明处理MethodArgumentNotValidException类型异常的方法请求体参数验证失败
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
// 记录参数验证异常信息到日志
log.error(e.getMessage(), e);
// 获取字段验证错误的默认信息
String message = e.getBindingResult().getFieldError().getDefaultMessage();
// 返回包含验证错误信息的AjaxResult
return AjaxResult.error(message);
}
/**
*
*/
// 声明处理DemoModeException类型异常的方法
@ExceptionHandler(DemoModeException.class)
public AjaxResult handleDemoModeException(DemoModeException e)
{
// 返回演示模式不允许操作的提示信息
return AjaxResult.error("演示模式,不允许操作");
}
}

@ -0,0 +1,182 @@
package com.huacai.framework.web.service;
import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.huacai.common.constant.Constants;
import com.huacai.common.core.domain.entity.SysRole;
import com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.utils.SecurityUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.framework.security.context.PermissionContextHolder;
/**
* huacai ssSpringSecurity
*
* @author huacai
*/
@Service("ss")
public class PermissionService
{
/**
*
*
* @param permission
* @return true-false-
*/
public boolean hasPermi(String permission)
{
// 权限字符串为空时直接返回false
if (StringUtils.isEmpty(permission))
{
return false;
}
// 获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
// 若用户信息不存在或用户权限集合为空返回false
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
// 将当前验证的权限存入权限上下文
PermissionContextHolder.setContext(permission);
// 检查用户权限集合中是否包含该权限
return hasPermissions(loginUser.getPermissions(), permission);
}
/**
* hasPermi
*
* @param permission
* @return true-false-
*/
public boolean lacksPermi(String permission)
{
return !hasPermi(permission);
}
/**
*
*
* @param permissions PERMISSION_DELIMETER
* @return true-false-
*/
public boolean hasAnyPermi(String permissions)
{
// 权限列表为空时返回false
if (StringUtils.isEmpty(permissions))
{
return false;
}
// 获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
// 若用户信息不存在或用户权限集合为空返回false
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
// 将当前验证的权限列表存入权限上下文
PermissionContextHolder.setContext(permissions);
// 获取用户的权限集合
Set<String> authorities = loginUser.getPermissions();
// 拆分权限列表字符串,逐个检查是否拥有该权限
for (String permission : permissions.split(Constants.PERMISSION_DELIMETER))
{
if (permission != null && hasPermissions(authorities, permission))
{
return true;
}
}
// 所有权限都不具备时返回false
return false;
}
/**
*
*
* @param role
* @return true-false-
*/
public boolean hasRole(String role)
{
// 角色字符串为空时返回false
if (StringUtils.isEmpty(role))
{
return false;
}
// 获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
// 若用户信息不存在或用户角色集合为空返回false
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
return false;
}
// 遍历用户的角色列表,检查是否包含目标角色或超级管理员角色
for (SysRole sysRole : loginUser.getUser().getRoles())
{
String roleKey = sysRole.getRoleKey();
if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
{
return true;
}
}
// 不包含目标角色时返回false
return false;
}
/**
* hasRole
*
* @param role
* @return true-false-
*/
public boolean lacksRole(String role)
{
return !hasRole(role);
}
/**
*
*
* @param roles ROLE_DELIMETER
* @return true-false-
*/
public boolean hasAnyRoles(String roles)
{
// 角色列表为空时返回false
if (StringUtils.isEmpty(roles))
{
return false;
}
// 获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
// 若用户信息不存在或用户角色集合为空返回false
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
return false;
}
// 拆分角色列表字符串,逐个检查是否拥有该角色
for (String role : roles.split(Constants.ROLE_DELIMETER))
{
if (hasRole(role))
{
return true;
}
}
// 所有角色都不具备时返回false
return false;
}
/**
*
*
* @param permissions
* @param permission
* @return true-false-
*/
private boolean hasPermissions(Set<String> permissions, String permission)
{
// 若包含"全部权限"标识或直接包含目标权限则返回true
return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}
}

@ -0,0 +1,181 @@
package com.huacai.framework.web.service;
import javax.annotation.Resource;
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 com.huacai.common.constant.CacheConstants;
import com.huacai.common.constant.Constants;
import com.huacai.common.constant.UserConstants;
import com.huacai.common.core.domain.entity.SysUser;
import com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.core.redis.RedisCache;
import com.huacai.common.exception.ServiceException;
import com.huacai.common.exception.user.BlackListException;
import com.huacai.common.exception.user.CaptchaException;
import com.huacai.common.exception.user.CaptchaExpireException;
import com.huacai.common.exception.user.UserNotExistsException;
import com.huacai.common.exception.user.UserPasswordNotMatchException;
import com.huacai.common.utils.DateUtils;
import com.huacai.common.utils.MessageUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.common.utils.ip.IpUtils;
import com.huacai.framework.manager.AsyncManager;
import com.huacai.framework.manager.factory.AsyncFactory;
import com.huacai.framework.security.context.AuthenticationContextHolder;
import com.huacai.system.service.ISysConfigService;
import com.huacai.system.service.ISysUserService;
/**
*
*
* @author huacai
*/
@Component
public class SysLoginService
{
@Autowired
private TokenService tokenService;
@Resource
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Autowired
private ISysUserService userService;
@Autowired
private ISysConfigService configService;
/**
*
*
* @param username
* @param password
* @param code
* @param uuid
* @return
*/
public String login(String username, String password, String code, String uuid)
{
// 验证码校验
validateCaptcha(username, code, uuid);
// 登录前置校验
loginPreCheck(username, password);
// 用户验证
Authentication authentication = null;
try
{
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
finally
{
AuthenticationContextHolder.clearContext();
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
}
/**
*
*
* @param username
* @param code
* @param uuid
* @return
*/
public void validateCaptcha(String username, String code, String uuid)
{
boolean captchaEnabled = configService.selectCaptchaEnabled();
if (captchaEnabled)
{
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
if (captcha == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
}
}
/**
*
* @param username
* @param password
*/
public void loginPreCheck(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
throw new UserNotExistsException();
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// IP黑名单校验
String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
throw new BlackListException();
}
}
/**
*
*
* @param userId ID
*/
public void recordLoginInfo(Long userId)
{
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(IpUtils.getIpAddr());
sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser);
}
}

@ -0,0 +1,118 @@
package com.huacai.framework.web.service;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.huacai.common.constant.CacheConstants;
import com.huacai.common.core.domain.entity.SysUser;
import com.huacai.common.core.redis.RedisCache;
import com.huacai.common.exception.user.UserPasswordNotMatchException;
import com.huacai.common.exception.user.UserPasswordRetryLimitExceedException;
import com.huacai.common.utils.SecurityUtils;
import com.huacai.framework.security.context.AuthenticationContextHolder;
/**
*
*
*
* @author huacai
*/
@Component
public class SysPasswordService
{
// 注入Redis缓存工具用于存储密码错误次数
@Autowired
private RedisCache redisCache;
// 从配置文件读取密码最大重试次数
@Value(value = "${user.password.maxRetryCount}")
private int maxRetryCount;
// 从配置文件读取密码错误锁定时间(分钟)
@Value(value = "${user.password.lockTime}")
private int lockTime;
/**
*
*
* @param username
* @return keyPWD_ERR_CNT_KEY +
*/
private String getCacheKey(String username)
{
return CacheConstants.PWD_ERR_CNT_KEY + username;
}
/**
*
*
*
* @param user
*/
public void validate(SysUser user)
{
// 从安全上下文获取当前认证信息(包含用户名和提交的密码)
Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
String username = usernamePasswordAuthenticationToken.getName();
String password = usernamePasswordAuthenticationToken.getCredentials().toString();
// 从Redis获取该用户的密码错误次数
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
// 若缓存中无记录初始化错误次数为0
if (retryCount == null)
{
retryCount = 0;
}
// 若错误次数超过最大限制,抛出密码重试次数超限异常
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
{
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
}
// 验证提交的密码与用户存储的密码是否匹配
if (!matches(user, password))
{
// 密码不匹配错误次数加1并更新Redis缓存设置锁定时间
retryCount = retryCount + 1;
redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
// 抛出密码不匹配异常
throw new UserPasswordNotMatchException();
}
else
{
// 密码匹配,清除该用户的登录错误记录缓存
clearLoginRecordCache(username);
}
}
/**
*
*
* @param user
* @param rawPassword
* @return true-false-
*/
public boolean matches(SysUser user, String rawPassword)
{
return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
}
/**
*
*
*
* @param loginName
*/
public void clearLoginRecordCache(String loginName)
{
// 若缓存中存在该用户的错误记录,则删除
if (redisCache.hasKey(getCacheKey(loginName)))
{
redisCache.deleteObject(getCacheKey(loginName));
}
}
}

@ -0,0 +1,97 @@
package com.huacai.framework.web.service;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.huacai.common.core.domain.entity.SysRole;
import com.huacai.common.core.domain.entity.SysUser;
import com.huacai.system.service.ISysMenuService;
import com.huacai.system.service.ISysRoleService;
/**
*
* /
*
* @author huacai
*/
@Component
public class SysPermissionService
{
// 注入角色服务,用于查询角色相关的权限信息
@Autowired
private ISysRoleService roleService;
// 注入菜单服务,用于查询菜单相关的权限信息
@Autowired
private ISysMenuService menuService;
/**
*
*
* @param user
* @return "admin""user"
*/
public Set<String> getRolePermission(SysUser user)
{
// 用于存储角色权限的集合(去重)
Set<String> roles = new HashSet<String>();
// 若用户是管理员通常判断用户ID是否为超级管理员ID
if (user.isAdmin())
{
// 管理员拥有"admin"角色权限
roles.add("admin");
}
else
{
// 普通用户查询该用户ID对应的角色权限并添加到集合中
roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
}
return roles;
}
/**
*
*
* @param user
* @return "system:user:list""*:*:*"
*/
public Set<String> getMenuPermission(SysUser user)
{
// 用于存储菜单权限的集合(去重)
Set<String> perms = new HashSet<String>();
// 若用户是管理员
if (user.isAdmin())
{
// 管理员拥有所有菜单权限(用"*:*:*"表示)
perms.add("*:*:*");
}
else
{
// 获取用户关联的角色列表
List<SysRole> roles = user.getRoles();
// 若用户拥有角色
if (!CollectionUtils.isEmpty(roles))
{
// 遍历每个角色,查询该角色对应的菜单权限
for (SysRole role : roles)
{
// 查询角色ID对应的菜单权限集合
Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
// 为角色对象设置权限集合(用于数据权限匹配)
role.setPermissions(rolePerms);
// 将角色的菜单权限添加到用户的总权限集合中
perms.addAll(rolePerms);
}
}
else
{
// 若用户没有关联角色直接查询该用户ID对应的菜单权限
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
}
}
return perms;
}
}

@ -0,0 +1,144 @@
package com.huacai.framework.web.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.huacai.common.constant.CacheConstants;
import com.huacai.common.constant.Constants;
import com.huacai.common.constant.UserConstants;
import com.huacai.common.core.domain.entity.SysUser;
import com.huacai.common.core.domain.model.RegisterBody;
import com.huacai.common.core.redis.RedisCache;
import com.huacai.common.exception.user.CaptchaException;
import com.huacai.common.exception.user.CaptchaExpireException;
import com.huacai.common.utils.MessageUtils;
import com.huacai.common.utils.SecurityUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.framework.manager.AsyncManager;
import com.huacai.framework.manager.factory.AsyncFactory;
import com.huacai.system.service.ISysConfigService;
import com.huacai.system.service.ISysUserService;
/**
*
*
*
* @author huacai
*/
@Component
public class SysRegisterService
{
// 注入用户服务,用于用户信息的校验和注册操作
@Autowired
private ISysUserService userService;
// 注入系统配置服务,用于获取验证码开关等配置
@Autowired
private ISysConfigService configService;
// 注入Redis缓存工具用于验证码的获取和删除
@Autowired
private RedisCache redisCache;
/**
*
*
*
* @param registerBody
* @return
*/
public String register(RegisterBody registerBody)
{
// 初始化消息变量、用户名和密码
String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword();
// 创建系统用户对象并设置用户名
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
// 检查验证码开关是否开启
boolean captchaEnabled = configService.selectCaptchaEnabled();
if (captchaEnabled)
{
// 验证验证码有效性
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
}
// 校验用户名是否为空
if (StringUtils.isEmpty(username))
{
msg = "用户名不能为空";
}
// 校验密码是否为空
else if (StringUtils.isEmpty(password))
{
msg = "用户密码不能为空";
}
// 校验用户名长度是否符合要求2-20个字符
else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
msg = "账户长度必须在2到20个字符之间";
}
// 校验密码长度是否符合要求5-20个字符
else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
msg = "密码长度必须在5到20个字符之间";
}
// 校验用户名是否已存在
else if (!userService.checkUserNameUnique(sysUser))
{
msg = "保存用户'" + username + "'失败,注册账号已存在";
}
// 所有校验通过,执行注册逻辑
else
{
// 设置用户昵称(默认与用户名相同)
sysUser.setNickName(username);
// 加密密码(使用安全工具类进行加密)
sysUser.setPassword(SecurityUtils.encryptPassword(password));
// 调用用户服务注册用户
boolean regFlag = userService.registerUser(sysUser);
if (!regFlag)
{
// 注册失败
msg = "注册失败,请联系系统管理人员";
}
else
{
// 注册成功,异步记录注册日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success")));
}
}
// 返回注册结果消息
return msg;
}
/**
*
*
* @param username
* @param code
* @param uuid Redis
* @throws CaptchaExpireException
* @throws CaptchaException
*/
public void validateCaptcha(String username, String code, String uuid)
{
// 构建Redis中存储验证码的键前缀 + uuid
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
// 从Redis获取验证码
String captcha = redisCache.getCacheObject(verifyKey);
// 无论验证成功与否都删除Redis中的验证码防止重复使用
redisCache.deleteObject(verifyKey);
// 验证码不存在(已过期或未生成)
if (captcha == null)
{
throw new CaptchaExpireException();
}
// 验证码不匹配(忽略大小写)
if (!code.equalsIgnoreCase(captcha))
{
throw new CaptchaException();
}
}
}

@ -0,0 +1,280 @@
package com.huacai.framework.web.service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.huacai.common.constant.CacheConstants;
import com.huacai.common.constant.Constants;
import com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.core.redis.RedisCache;
import com.huacai.common.utils.ServletUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.common.utils.ip.AddressUtils;
import com.huacai.common.utils.ip.IpUtils;
import com.huacai.common.utils.uuid.IdUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* Token
* JWT
*
* @author huacai
*/
@Component
public class TokenService
{
// 日志记录器
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
// 从配置文件获取令牌在请求头中的自定义标识(如"Authorization"
@Value("${token.header}")
private String header;
// 从配置文件获取令牌签名秘钥用于JWT的生成和验证
@Value("${token.secret}")
private String secret;
// 从配置文件获取令牌有效期分钟默认30分钟
@Value("${token.expireTime}")
private int expireTime;
// 时间常量1秒毫秒
protected static final long MILLIS_SECOND = 1000;
// 时间常量1分钟毫秒
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
// 时间常量20分钟毫秒用于令牌自动刷新判断
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
// 注入Redis缓存工具用于存储登录用户信息
@Autowired
private RedisCache redisCache;
/**
*
* Redis
*
* @param request HTTP
* @return LoginUsernull
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
// 从请求中获取令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
// 解析令牌获取声明信息
Claims claims = parseToken(token);
// 从声明中获取登录用户唯一标识uuid
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
// 生成Redis中存储用户信息的键
String userKey = getTokenKey(uuid);
// 从Redis获取登录用户信息
LoginUser user = redisCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
log.error("获取用户信息异常'{}'", e.getMessage());
}
}
return null;
}
/**
*
*
*
* @param loginUser
*/
public void setLoginUser(LoginUser loginUser)
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
refreshToken(loginUser);
}
}
/**
*
* 退
*
* @param token
*/
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
}
/**
*
* JWTRedis
*
* @param loginUser
* @return JWT
*/
public String createToken(LoginUser loginUser)
{
// 生成UUID作为令牌唯一标识
String token = IdUtils.fastUUID();
// 设置令牌到登录用户信息中
loginUser.setToken(token);
// 设置用户代理信息浏览器、操作系统、IP等
setUserAgent(loginUser);
// 刷新令牌有效期并缓存用户信息
refreshToken(loginUser);
// 构建JWT的声明信息
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
// 生成并返回JWT令牌
return createToken(claims);
}
/**
*
* 20
*
* @param loginUser
*/
public void verifyToken(LoginUser loginUser)
{
// 获取令牌过期时间
long expireTime = loginUser.getExpireTime();
// 获取当前时间
long currentTime = System.currentTimeMillis();
// 若剩余时间小于等于20分钟则刷新令牌
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
refreshToken(loginUser);
}
}
/**
*
*
*
* @param loginUser
*/
public void refreshToken(LoginUser loginUser)
{
// 设置当前登录时间
loginUser.setLoginTime(System.currentTimeMillis());
// 计算过期时间(当前时间 + 配置的有效期)
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 生成Redis缓存键
String userKey = getTokenKey(loginUser.getToken());
// 将用户信息存入Redis并设置过期时间
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
/**
*
* User-AgentIP
*
* @param loginUser
*/
public void setUserAgent(LoginUser loginUser)
{
// 解析User-Agent字符串
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
// 获取客户端IP地址
String ip = IpUtils.getIpAddr();
// 设置IP地址
loginUser.setIpaddr(ip);
// 设置登录地理位置根据IP解析
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
// 设置浏览器名称
loginUser.setBrowser(userAgent.getBrowser().getName());
// 设置操作系统名称
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
/**
* JWT
*
* @param claims
* @return JWT
*/
private String createToken(Map<String, Object> claims)
{
// 使用HS512算法签名生成JWT令牌
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
/**
* JWT
*
* @param token JWT
* @return Claims
*/
private Claims parseToken(String token)
{
// 使用秘钥解析令牌,获取声明信息
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
*
*
* @param token JWT
* @return
*/
public String getUsernameFromToken(String token)
{
Claims claims = parseToken(token);
return claims.getSubject();
}
/**
*
* "Bearer "
*
* @param request HTTP
* @return null
*/
private String getToken(HttpServletRequest request)
{
// 从请求头获取令牌
String token = request.getHeader(header);
// 若令牌存在且以指定前缀开头,则去除前缀
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
/**
* Redis
*
* @param uuid
* @return LOGIN_TOKEN_KEY + uuid
*/
private String getTokenKey(String uuid)
{
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
}
}

@ -0,0 +1,94 @@
package com.huacai.framework.web.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.huacai.common.core.domain.entity.SysUser;
import com.huacai.common.core.domain.model.LoginUser;
import com.huacai.common.enums.UserStatus;
import com.huacai.common.exception.ServiceException;
import com.huacai.common.utils.MessageUtils;
import com.huacai.common.utils.StringUtils;
import com.huacai.system.service.ISysUserService;
/**
*
* Spring SecurityUserDetailsService
*
* @author huacai
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
// 日志记录器
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
// 注入用户服务,用于查询用户信息
@Autowired
private ISysUserService userService;
// 注入密码服务,用于密码验证
@Autowired
private SysPasswordService passwordService;
// 注入权限服务,用于获取用户的菜单权限
@Autowired
private SysPermissionService permissionService;
/**
*
* Spring Security
*
* @param username
* @return UserDetails
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
// 根据用户名查询系统用户信息
SysUser user = userService.selectUserByUserName(username);
// 验证用户是否存在
if (StringUtils.isNull(user))
{
log.info("登录用户:{} 不存在.", username);
throw new ServiceException(MessageUtils.message("user.not.exists"));
}
// 验证用户是否已被删除
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException(MessageUtils.message("user.password.delete"));
}
// 验证用户是否已被停用
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException(MessageUtils.message("user.blocked"));
}
// 验证用户密码(检查错误次数等)
passwordService.validate(user);
// 创建并返回登录用户详情对象
return createLoginUser(user);
}
/**
*
* Spring SecurityLoginUser
*
* @param user
* @return LoginUser
*/
public UserDetails createLoginUser(SysUser user)
{
// 构建LoginUser对象包含用户ID、部门ID、用户信息及菜单权限集合
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}

@ -0,0 +1,40 @@
<?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>huacai</artifactId>
<groupId>com.huacai</groupId>
<version>3.8.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>huacai-generator</artifactId>
<description>
generator代码生成
</description>
<dependencies>
<!--velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<!-- collections工具类 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.huacai</groupId>
<artifactId>huacai-common</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,116 @@
package com.huacai.generator.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
*
*
*
* @author huacai
*/
@Component
// 指定配置属性的前缀为"gen",用于绑定配置文件中以"gen."开头的属性
@ConfigurationProperties(prefix = "gen")
// 指定配置文件的位置为类路径下的generator.yml
@PropertySource(value = { "classpath:generator.yml" })
public class GenConfig
{
/** 作者(代码生成时注释中的作者信息) */
public static String author;
/** 生成代码的包路径(基础包结构) */
public static String packageName;
/** 是否自动去除表前缀生成类名时是否排除表前缀默认false */
public static boolean autoRemovePre;
/** 表前缀(生成类名时需要排除的前缀,多个前缀可用逗号分隔) */
public static String tablePrefix;
/**
*
*
* @return
*/
public static String getAuthor()
{
return author;
}
/**
*
*
* @param author
*/
@Value("${author}")
public void setAuthor(String author)
{
GenConfig.author = author;
}
/**
*
*
* @return "com.huacai.modules.system"
*/
public static String getPackageName()
{
return packageName;
}
/**
*
*
* @param packageName
*/
@Value("${packageName}")
public void setPackageName(String packageName)
{
GenConfig.packageName = packageName;
}
/**
*
*
* @return true-false-
*/
public static boolean getAutoRemovePre()
{
return autoRemovePre;
}
/**
*
*
* @param autoRemovePre
*/
@Value("${autoRemovePre}")
public void setAutoRemovePre(boolean autoRemovePre)
{
GenConfig.autoRemovePre = autoRemovePre;
}
/**
*
*
* @return "sys_"
*/
public static String getTablePrefix()
{
return tablePrefix;
}
/**
*
*
* @param tablePrefix
*/
@Value("${tablePrefix}")
public void setTablePrefix(String tablePrefix)
{
GenConfig.tablePrefix = tablePrefix;
}
}

@ -0,0 +1,298 @@
package com.huacai.generator.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.huacai.common.annotation.Log;
import com.huacai.common.core.controller.BaseController;
import com.huacai.common.core.domain.AjaxResult;
import com.huacai.common.core.page.TableDataInfo;
import com.huacai.common.core.text.Convert;
import com.huacai.common.enums.BusinessType;
import com.huacai.generator.domain.GenTable;
import com.huacai.generator.domain.GenTableColumn;
import com.huacai.generator.service.IGenTableColumnService;
import com.huacai.generator.service.IGenTableService;
/**
*
* CRUD
*
* @author huacai
*/
@RestController
@RequestMapping("/tool/gen")
public class GenController extends BaseController
{
// 注入代码生成表服务,处理表结构相关业务逻辑
@Autowired
private IGenTableService genTableService;
// 注入代码生成表字段服务,处理表字段相关业务逻辑
@Autowired
private IGenTableColumnService genTableColumnService;
/**
*
*
*
* @param genTable
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:list')")
@GetMapping("/list")
public TableDataInfo genList(GenTable genTable)
{
// 开启分页
startPage();
// 查询表结构列表
List<GenTable> list = genTableService.selectGenTableList(genTable);
// 返回分页数据
return getDataTable(list);
}
/**
*
* ID
*
* @param tableId ID
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:query')")
@GetMapping(value = "/{tableId}")
public AjaxResult getInfo(@PathVariable Long tableId)
{
// 查询表结构详情
GenTable table = genTableService.selectGenTableById(tableId);
// 查询所有表结构(用于关联表选择)
List<GenTable> tables = genTableService.selectGenTableAll();
// 查询该表的字段列表
List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId);
// 封装返回数据
Map<String, Object> map = new HashMap<String, Object>();
map.put("info", table);
map.put("rows", list);
map.put("tables", tables);
return success(map);
}
/**
*
*
*
* @param genTable
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:list')")
@GetMapping("/db/list")
public TableDataInfo dataList(GenTable genTable)
{
// 开启分页
startPage();
// 查询数据库表列表
List<GenTable> list = genTableService.selectDbTableList(genTable);
// 返回分页数据
return getDataTable(list);
}
/**
*
* ID
*
* @param tableId ID
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:list')")
@GetMapping(value = "/column/{tableId}")
public TableDataInfo columnList(Long tableId)
{
TableDataInfo dataInfo = new TableDataInfo();
// 查询表字段列表
List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId);
dataInfo.setRows(list);
dataInfo.setTotal(list.size());
return dataInfo;
}
/**
*
*
*
* @param tables
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:import')")
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
@PostMapping("/importTable")
public AjaxResult importTableSave(String tables)
{
// 将表名字符串转换为数组
String[] tableNames = Convert.toStrArray(tables);
// 查询表信息列表
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
// 导入表结构
genTableService.importGenTable(tableList);
return success();
}
/**
*
*
*
* @param genTable
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:edit')")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult editSave(@Validated @RequestBody GenTable genTable)
{
// 验证编辑参数
genTableService.validateEdit(genTable);
// 更新表结构信息
genTableService.updateGenTable(genTable);
return success();
}
/**
*
* ID
*
* @param tableIds ID
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:remove')")
@Log(title = "代码生成", businessType = BusinessType.DELETE)
@DeleteMapping("/{tableIds}")
public AjaxResult remove(@PathVariable Long[] tableIds)
{
genTableService.deleteGenTableByIds(tableIds);
return success();
}
/**
*
* IDControllerService
*
* @param tableId ID
* @return
* @throws IOException IO
*/
@PreAuthorize("@ss.hasPermi('tool:gen:preview')")
@GetMapping("/preview/{tableId}")
public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException
{
// 生成代码并返回key为文件名value为代码内容
Map<String, String> dataMap = genTableService.previewCode(tableId);
return success(dataMap);
}
/**
*
* ZIP
*
* @param response HTTP
* @param tableName
* @throws IOException IO
*/
@PreAuthorize("@ss.hasPermi('tool:gen:code')")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/download/{tableName}")
public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException
{
// 生成代码字节数组ZIP格式
byte[] data = genTableService.downloadCode(tableName);
// 输出ZIP文件到响应流
genCode(response, data);
}
/**
*
*
*
* @param tableName
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:code')")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/genCode/{tableName}")
public AjaxResult genCode(@PathVariable("tableName") String tableName)
{
genTableService.generatorCode(tableName);
return success();
}
/**
*
*
*
* @param tableName
* @return
*/
@PreAuthorize("@ss.hasPermi('tool:gen:edit')")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@GetMapping("/synchDb/{tableName}")
public AjaxResult synchDb(@PathVariable("tableName") String tableName)
{
genTableService.synchDb(tableName);
return success();
}
/**
*
* ZIP
*
* @param response HTTP
* @param tables
* @throws IOException IO
*/
@PreAuthorize("@ss.hasPermi('tool:gen:code')")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/batchGenCode")
public void batchGenCode(HttpServletResponse response, String tables) throws IOException
{
// 将表名字符串转换为数组
String[] tableNames = Convert.toStrArray(tables);
// 批量生成代码字节数组ZIP格式
byte[] data = genTableService.downloadCode(tableNames);
// 输出ZIP文件到响应流
genCode(response, data);
}
/**
* ZIP
* ZIP
*
* @param response HTTP
* @param data ZIP
* @throws IOException IO
*/
private void genCode(HttpServletResponse response, byte[] data) throws IOException
{
response.reset();
// 设置跨域相关响应头
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
// 设置文件下载响应头
response.setHeader("Content-Disposition", "attachment; filename=\"huacai.zip\"");
response.addHeader("Content-Length", "" + data.length);
// 设置内容类型为二进制流
response.setContentType("application/octet-stream; charset=UTF-8");
// 将字节数组写入响应输出流
IOUtils.write(data, response.getOutputStream());
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save