项目初始化

This commit is contained in:
2025-05-12 08:39:57 +08:00
parent a209978f7a
commit 68e9c20b34
371 changed files with 45000 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
package com.zg.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.zg.common.constant.UserConstants;
import com.zg.common.core.text.Convert;
import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.aspectj.lang.annotation.DataScope;
import com.zg.framework.security.LoginUser;
import com.zg.framework.security.context.PermissionContextHolder;
import com.zg.framework.web.domain.BaseEntity;
import com.zg.project.system.domain.SysRole;
import com.zg.project.system.domain.SysUser;
/**
* 数据过滤处理
*
* @author zg
*/
@Aspect
@Component
public class DataScopeAspect
{
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
{
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
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());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission);
}
}
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param deptAlias 部门别名
* @param userAlias 用户别名
* @param permission 权限字符
*/
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
StringBuilder sqlString = new StringBuilder();
List<String> conditions = new ArrayList<String>();
List<String> scopeCustomIds = new ArrayList<String>();
user.getRoles().forEach(role -> {
if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
scopeCustomIds.add(Convert.toStr(role.getRoleId()));
}
});
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE))
{
continue;
}
if (!StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
{
continue;
}
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
conditions.add(dataScope);
break;
}
else if (DATA_SCOPE_CUSTOM.equals(dataScope))
{
if (scopeCustomIds.size() > 1)
{
// 多个自定数据权限使用in查询避免多次拼接。
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
}
else
{
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))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
{
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))
{
if (StringUtils.isNotBlank(userAlias))
{
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
}
else
{
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
}
conditions.add(dataScope);
}
// 角色都不包含传递过来的权限字符这个时候sqlString也会为空所以要限制一下,不查询任何数据
if (StringUtils.isEmpty(conditions))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
}
if (StringUtils.isNotBlank(sqlString.toString()))
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
/**
* 拼接权限sql前先清空params.dataScope参数防止注入
*/
private void clearDataScope(final JoinPoint joinPoint)
{
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParams().put(DATA_SCOPE, "");
}
}
}

View File

@@ -0,0 +1,72 @@
package com.zg.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.zg.common.utils.StringUtils;
import com.zg.framework.aspectj.lang.annotation.DataSource;
import com.zg.framework.datasource.DynamicDataSourceContextHolder;
/**
* 多数据源处理
*
* @author zg
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect
{
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.zg.framework.aspectj.lang.annotation.DataSource)"
+ "|| @within(com.zg.framework.aspectj.lang.annotation.DataSource)")
public void dsPointCut()
{
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable
{
DataSource dataSource = getDataSource(point);
if (StringUtils.isNotNull(dataSource))
{
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try
{
return point.proceed();
}
finally
{
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point)
{
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource))
{
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}

View File

@@ -0,0 +1,256 @@
package com.zg.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.zg.common.core.text.Convert;
import com.zg.common.enums.HttpMethod;
import com.zg.common.filter.PropertyPreExcludeFilter;
import com.zg.common.utils.ExceptionUtil;
import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.ServletUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.ip.IpUtils;
import com.zg.framework.aspectj.lang.annotation.Log;
import com.zg.framework.aspectj.lang.enums.BusinessStatus;
import com.zg.framework.manager.AsyncManager;
import com.zg.framework.manager.factory.AsyncFactory;
import com.zg.framework.security.LoginUser;
import com.zg.project.monitor.domain.SysOperLog;
import com.zg.project.system.domain.SysUser;
/**
* 操作日志记录处理
*
* @author zg
*/
@Aspect
@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");
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog)
{
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{
try
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr();
operLog.setOperIp(ip);
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(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
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();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
{
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request参数和值
if (log.isSaveRequestData())
{
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog, log.excludeParamNames());
}
// 是否需要保存response参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
{
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
/**
* 获取请求的参数放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception
{
String requestMethod = operLog.getRequestMethod();
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name()))
{
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
}
else
{
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
}
}
/**
* 参数拼装
*/
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
{
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
/**
* 忽略敏感属性
*/
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;
}
}
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;
}
}

View File

@@ -0,0 +1,89 @@
package com.zg.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.zg.common.exception.ServiceException;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.ip.IpUtils;
import com.zg.framework.aspectj.lang.annotation.RateLimiter;
import com.zg.framework.aspectj.lang.enums.LimitType;
/**
* 限流处理
*
* @author zg
*/
@Aspect
@Component
public class RateLimiterAspect
{
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
private RedisTemplate<Object, Object> redisTemplate;
private RedisScript<Long> limitScript;
@Autowired
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
{
this.redisTemplate = redisTemplate;
}
@Autowired
public void setLimitScript(RedisScript<Long> limitScript)
{
this.limitScript = limitScript;
}
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
{
int time = rateLimiter.time();
int count = rateLimiter.count();
String combineKey = getCombineKey(rateLimiter, point);
List<Object> keys = Collections.singletonList(combineKey);
try
{
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("服务器限流异常,请稍候再试");
}
}
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
{
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
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();
}
}

View File

@@ -0,0 +1,19 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 匿名访问不鉴权注解
*
* @author zg
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous
{
}

View File

@@ -0,0 +1,33 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 数据权限过滤注解
*
* @author zg
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
/**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取多个权限用逗号分隔开来
*/
public String permission() default "";
}

View File

@@ -0,0 +1,28 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.zg.framework.aspectj.lang.enums.DataSourceType;
/**
* 自定义多数据源切换注解
*
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*
* @author zg
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}

View File

@@ -0,0 +1,197 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import com.zg.common.utils.poi.ExcelHandlerAdapter;
/**
* 自定义导出Excel数据注解
*
* @author zg
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel
{
/**
* 导出时在excel中排序
*/
public int sort() default Integer.MAX_VALUE;
/**
* 导出到Excel中的名字.
*/
public String name() default "";
/**
* 日期格式, 如: yyyy-MM-dd
*/
public String dateFormat() default "";
/**
* 如果是字典类型请设置字典的type值 (如: sys_user_sex)
*/
public String dictType() default "";
/**
* 读取内容转表达式 (如: 0=男,1=女,2=未知)
*/
public String readConverterExp() default "";
/**
* 分隔符,读取字符串组内容
*/
public String separator() default ",";
/**
* BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
*/
public int scale() default -1;
/**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
/**
* 导出时在excel中每个列的高度
*/
public double height() default 14;
/**
* 导出时在excel中每个列的宽度
*/
public double width() default 16;
/**
* 文字后缀,如% 90 变成90%
*/
public String suffix() default "";
/**
* 当值为空时,字段的默认值
*/
public String defaultValue() default "";
/**
* 提示信息
*/
public String prompt() default "";
/**
* 是否允许内容换行
*/
public boolean wrapText() default false;
/**
* 设置只能选择不能输入的列内容.
*/
public String[] combo() default {};
/**
* 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解.
*/
public boolean comboReadDict() default false;
/**
* 是否需要纵向合并单元格,应对需求:含有list集合单元格)
*/
public boolean needMerge() default false;
/**
* 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
*/
public boolean isExport() default true;
/**
* 另一个类中的属性名称,支持多级获取,以小数点隔开
*/
public String targetAttr() default "";
/**
* 是否自动统计数据,在最后追加一行统计数据总和
*/
public boolean isStatistics() default false;
/**
* 导出类型0数字 1字符串 2图片
*/
public ColumnType cellType() default ColumnType.STRING;
/**
* 导出列头背景颜色
*/
public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT;
/**
* 导出列头字体颜色
*/
public IndexedColors headerColor() default IndexedColors.WHITE;
/**
* 导出单元格背景颜色
*/
public IndexedColors backgroundColor() default IndexedColors.WHITE;
/**
* 导出单元格字体颜色
*/
public IndexedColors color() default IndexedColors.BLACK;
/**
* 导出字段对齐方式
*/
public HorizontalAlignment align() default HorizontalAlignment.CENTER;
/**
* 自定义数据处理器
*/
public Class<?> handler() default ExcelHandlerAdapter.class;
/**
* 自定义数据处理器参数
*/
public String[] args() default {};
/**
* 字段类型0导出导入1仅导出2仅导入
*/
Type type() default Type.ALL;
public enum Type
{
ALL(0), EXPORT(1), IMPORT(2);
private final int value;
Type(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
public enum ColumnType
{
NUMERIC(0), STRING(1), IMAGE(2), TEXT(3);
private final int value;
ColumnType(int value)
{
this.value = value;
}
public int value()
{
return this.value;
}
}
}

View File

@@ -0,0 +1,18 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Excel注解集
*
* @author zg
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excels
{
public Excel[] value();
}

View File

@@ -0,0 +1,51 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.zg.framework.aspectj.lang.enums.BusinessType;
import com.zg.framework.aspectj.lang.enums.OperatorType;
/**
* 自定义操作日志记录注解
*
* @author zg
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模块
*/
public String title() default "";
/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}

View File

@@ -0,0 +1,40 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.zg.common.constant.CacheConstants;
import com.zg.framework.aspectj.lang.enums.LimitType;
/**
* 限流注解
*
* @author zg
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter
{
/**
* 限流key
*/
public String key() default CacheConstants.RATE_LIMIT_KEY;
/**
* 限流时间,单位秒
*/
public int time() default 60;
/**
* 限流次数
*/
public int count() default 100;
/**
* 限流类型
*/
public LimitType limitType() default LimitType.DEFAULT;
}

View File

@@ -0,0 +1,24 @@
package com.zg.framework.aspectj.lang.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.zg.framework.aspectj.lang.enums.DesensitizedType;
import com.zg.framework.config.SensitiveJsonSerializer;
/**
* 数据脱敏注解
*
* @author zg
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive
{
DesensitizedType desensitizedType();
}

View File

@@ -0,0 +1,20 @@
package com.zg.framework.aspectj.lang.enums;
/**
* 操作状态
*
* @author zg
*
*/
public enum BusinessStatus
{
/**
* 成功
*/
SUCCESS,
/**
* 失败
*/
FAIL,
}

View File

@@ -0,0 +1,59 @@
package com.zg.framework.aspectj.lang.enums;
/**
* 业务操作类型
*
* @author zg
*/
public enum BusinessType
{
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 强退
*/
FORCE,
/**
* 生成代码
*/
GENCODE,
/**
* 清空数据
*/
CLEAN,
}

View File

@@ -0,0 +1,19 @@
package com.zg.framework.aspectj.lang.enums;
/**
* 数据源
*
* @author zg
*/
public enum DataSourceType
{
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}

View File

@@ -0,0 +1,59 @@
package com.zg.framework.aspectj.lang.enums;
import java.util.function.Function;
import com.zg.common.utils.DesensitizedUtil;
/**
* 脱敏类型
*
* @author zg
*/
public enum DesensitizedType
{
/**
* 姓名第2位星号替换
*/
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
/**
* 密码,全部字符都用*代替
*/
PASSWORD(DesensitizedUtil::password),
/**
* 身份证中间10位星号替换
*/
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{3}[Xx]|\\d{4})", "$1** **** ****$2")),
/**
* 手机号中间4位星号替换
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
/**
* 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换
*/
EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")),
/**
* 银行卡号保留最后4位其他星号替换
*/
BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")),
/**
* 车牌号码,包含普通车辆、新能源车辆
*/
CAR_LICENSE(DesensitizedUtil::carLicense);
private final Function<String, String> desensitizer;
DesensitizedType(Function<String, String> desensitizer)
{
this.desensitizer = desensitizer;
}
public Function<String, String> desensitizer()
{
return desensitizer;
}
}

View File

@@ -0,0 +1,20 @@
package com.zg.framework.aspectj.lang.enums;
/**
* 限流类型
*
* @author zg
*/
public enum LimitType
{
/**
* 默认策略全局限流
*/
DEFAULT,
/**
* 根据请求者IP进行限流
*/
IP
}

View File

@@ -0,0 +1,24 @@
package com.zg.framework.aspectj.lang.enums;
/**
* 操作人类别
*
* @author zg
*/
public enum OperatorType
{
/**
* 其它
*/
OTHER,
/**
* 后台用户
*/
MANAGE,
/**
* 手机端用户
*/
MOBILE
}

View File

@@ -0,0 +1,30 @@
package com.zg.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 zg
*/
@Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要扫描的Mapper类的包的路径
@MapperScan("com.zg.project.**.mapper")
public class ApplicationConfig
{
/**
* 时区配置
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
{
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}

View File

@@ -0,0 +1,83 @@
package com.zg.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 zg
*/
@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.zg.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;
}
}

View File

@@ -0,0 +1,126 @@
package com.zg.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.zg.common.utils.spring.SpringUtils;
import com.zg.framework.aspectj.lang.enums.DataSourceType;
import com.zg.framework.config.properties.DruidProperties;
import com.zg.framework.datasource.DynamicDataSource;
/**
* druid 配置多数据源
*
* @author zg
*/
@Configuration
public class DruidConfig
{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@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);
}
@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 备选数据源集合
* @param sourceName 数据源名称
* @param beanName bean名称
*/
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
{
try
{
DataSource dataSource = SpringUtils.getBean(beanName);
targetDataSources.put(sourceName, dataSource);
}
catch (Exception e)
{
}
}
/**
* 去除监控页面底部的广告
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
{
// 获取web监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取common.js的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
final String filePath = "support/http/resources/js/common.js";
// 创建filter进行过滤
Filter filter = new Filter()
{
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取common.js
String text = Utils.readFromResource(filePath);
// 正则替换banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
@Override
public void destroy()
{
}
};
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

View File

@@ -0,0 +1,52 @@
package com.zg.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.zg.common.constant.Constants;
/**
* Redis使用FastJson序列化
*
* @author zg
*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
}
}

View File

@@ -0,0 +1,58 @@
package com.zg.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.zg.common.filter.RepeatableFilter;
import com.zg.common.filter.XssFilter;
import com.zg.common.utils.StringUtils;
/**
* Filter配置
*
* @author zg
*/
@Configuration
public class FilterConfig
{
@Value("${xss.excludes}")
private String excludes;
@Value("${xss.urlPatterns}")
private String urlPatterns;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
public FilterRegistrationBean xssFilterRegistration()
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
registration.setName("xssFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("excludes", excludes);
registration.setInitParameters(initParameters);
return registration;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean someFilterRegistration()
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
}

View File

@@ -0,0 +1,79 @@
package com.zg.framework.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 读取代码生成相关配置
*
* @author zg
*/
@Component
@ConfigurationProperties(prefix = "gen")
public class GenConfig
{
/** 作者 */
public static String author;
/** 生成包路径 */
public static String packageName;
/** 自动去除表前缀 */
public static boolean autoRemovePre;
/** 表前缀 */
public static String tablePrefix;
/** 是否允许生成文件覆盖到本地(自定义路径) */
public static boolean allowOverwrite;
public static String getAuthor()
{
return author;
}
public void setAuthor(String author)
{
GenConfig.author = author;
}
public static String getPackageName()
{
return packageName;
}
public void setPackageName(String packageName)
{
GenConfig.packageName = packageName;
}
public static boolean getAutoRemovePre()
{
return autoRemovePre;
}
public void setAutoRemovePre(boolean autoRemovePre)
{
GenConfig.autoRemovePre = autoRemovePre;
}
public static String getTablePrefix()
{
return tablePrefix;
}
public void setTablePrefix(String tablePrefix)
{
GenConfig.tablePrefix = tablePrefix;
}
public static boolean isAllowOverwrite()
{
return allowOverwrite;
}
public void setAllowOverwrite(boolean allowOverwrite)
{
GenConfig.allowOverwrite = allowOverwrite;
}
}

View File

@@ -0,0 +1,43 @@
package com.zg.framework.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import com.zg.common.constant.Constants;
/**
* 资源文件配置加载
*
* @author zg
*/
@Configuration
public class I18nConfig implements WebMvcConfigurer
{
@Bean
public LocaleResolver localeResolver()
{
SessionLocaleResolver slr = new SessionLocaleResolver();
// 默认语言
slr.setDefaultLocale(Constants.DEFAULT_LOCALE);
return slr;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor()
{
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
// 参数名
lci.setParamName("lang");
return lci;
}
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(localeChangeInterceptor());
}
}

View File

@@ -0,0 +1,68 @@
package com.zg.framework.config;
import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/**
* 验证码文本生成器
*
* @author zg
*/
public class KaptchaTextCreator extends DefaultTextCreator
{
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
@Override
public String getText()
{
Integer result = 0;
Random random = new Random();
int x = random.nextInt(10);
int y = random.nextInt(10);
StringBuilder suChinese = new StringBuilder();
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)
{
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 if (randomoperands == 2)
{
if (x >= y)
{
result = x - y;
suChinese.append(CNUMBERS[x]);
suChinese.append("-");
suChinese.append(CNUMBERS[y]);
}
else
{
result = y - x;
suChinese.append(CNUMBERS[y]);
suChinese.append("-");
suChinese.append(CNUMBERS[x]);
}
}
suChinese.append("=?@" + result);
return suChinese.toString();
}
}

View File

@@ -0,0 +1,132 @@
package com.zg.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.zg.common.utils.StringUtils;
/**
* Mybatis支持*匹配扫描包
*
* @author zg
*/
@Configuration
public class MyBatisConfig
{
@Autowired
private Environment env;
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
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>();
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
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;
}
public Resource[] resolveMapperLocations(String[] mapperLocations)
{
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<Resource>();
if (mapperLocations != null)
{
for (String mapperLocation : mapperLocations)
{
try
{
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
}
catch (IOException e)
{
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
{
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
String configLocation = env.getProperty("mybatis.configLocation");
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
return sessionFactory.getObject();
}
}

View File

@@ -0,0 +1,69 @@
package com.zg.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配置
*
* @author zg
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public DefaultRedisScript<Long> limitScript()
{
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 限流脚本
*/
private String limitScriptText()
{
return "local key = KEYS[1]\n" +
"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" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', key, time)\n" +
"end\n" +
"return tonumber(current);";
}
}

View File

@@ -0,0 +1,71 @@
package com.zg.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.zg.common.constant.Constants;
import com.zg.framework.interceptor.RepeatSubmitInterceptor;
/**
* 通用配置
*
* @author zg
*/
@Configuration
public class ResourcesConfig implements WebMvcConfigurer
{
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
/** 本地文件上传路径 */
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
.addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
/** swagger配置 */
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());
}
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
}
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter()
{
CorsConfiguration config = new CorsConfiguration();
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}

View File

@@ -0,0 +1,111 @@
package com.zg.framework.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 读取项目相关配置
*
* @author zg
*/
@Component
@ConfigurationProperties(prefix = "zg")
public class RuoYiConfig
{
/** 项目名称 */
private String name;
/** 版本 */
private String version;
/** 版权年份 */
private String copyrightYear;
/** 上传路径 */
private static String profile;
/** 获取地址开关 */
private static boolean addressEnabled;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getVersion()
{
return version;
}
public void setVersion(String version)
{
this.version = version;
}
public String getCopyrightYear()
{
return copyrightYear;
}
public void setCopyrightYear(String copyrightYear)
{
this.copyrightYear = copyrightYear;
}
public static String getProfile()
{
return profile;
}
public void setProfile(String profile)
{
RuoYiConfig.profile = profile;
}
public static boolean isAddressEnabled()
{
return addressEnabled;
}
public void setAddressEnabled(boolean addressEnabled)
{
RuoYiConfig.addressEnabled = addressEnabled;
}
/**
* 获取导入上传路径
*/
public static String getImportPath()
{
return getProfile() + "/import";
}
/**
* 获取头像上传路径
*/
public static String getAvatarPath()
{
return getProfile() + "/avatar";
}
/**
* 获取下载路径
*/
public static String getDownloadPath()
{
return getProfile() + "/download/";
}
/**
* 获取上传路径
*/
public static String getUploadPath()
{
return getProfile() + "/upload";
}
}

View File

@@ -0,0 +1,57 @@
//package com.zg.framework.config;
//
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.scheduling.quartz.SchedulerFactoryBean;
//import javax.sql.DataSource;
//import java.util.Properties;
//
///**
// * 定时任务配置单机部署建议默认走内存如需集群需要创建qrtz数据库表/打开类注释)
// *
// * @author zg
// */
//@Configuration
//public class ScheduleConfig
//{
// @Bean
// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
// {
// SchedulerFactoryBean factory = new SchedulerFactoryBean();
// factory.setDataSource(dataSource);
//
// // quartz参数
// Properties prop = new Properties();
// prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
// prop.put("org.quartz.scheduler.instanceId", "AUTO");
// // 线程池配置
// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
// prop.put("org.quartz.threadPool.threadCount", "20");
// prop.put("org.quartz.threadPool.threadPriority", "5");
// // JobStore配置
// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
// // 集群配置
// prop.put("org.quartz.jobStore.isClustered", "true");
// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10");
// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
//
// // sqlserver 启用
// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
// prop.put("org.quartz.jobStore.misfireThreshold", "12000");
// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
// factory.setQuartzProperties(prop);
//
// factory.setSchedulerName("RuoyiScheduler");
// // 延时启动
// factory.setStartupDelay(1);
// factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// // 可选QuartzScheduler
// // 启动时更新己存在的Job这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
// factory.setOverwriteExistingJobs(true);
// // 设置自动启动默认为true
// factory.setAutoStartup(true);
//
// return factory;
// }
//}

View File

@@ -0,0 +1,147 @@
package com.zg.framework.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
import com.zg.framework.config.properties.PermitAllUrlProperties;
import com.zg.framework.security.filter.JwtAuthenticationTokenFilter;
import com.zg.framework.security.handle.AuthenticationEntryPointImpl;
import com.zg.framework.security.handle.LogoutSuccessHandlerImpl;
/**
* spring security配置
*
* @author zg
*/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@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;
/**
* 身份验证实现
*/
@Bean
public AuthenticationManager authenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return new ProviderManager(daoAuthenticationProvider);
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问非remember-me下自动登录
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数参数表示IP地址如果用户IP和参数匹配则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
// CSRF禁用因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login",
"/register",
"/captchaImage",
"/AutoInventory/**",
"/ws/**",
"/information/device/**",
"/MatchScan/**",
"/Visual/**",
"/HdInventory/**").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,67 @@
package com.zg.framework.config;
import java.io.IOException;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.zg.common.utils.SecurityUtils;
import com.zg.framework.aspectj.lang.annotation.Sensitive;
import com.zg.framework.aspectj.lang.enums.DesensitizedType;
import com.zg.framework.security.LoginUser;
/**
* 数据脱敏序列化过滤
*
* @author zg
*/
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer
{
private DesensitizedType desensitizedType;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException
{
if (desensitization())
{
gen.writeString(desensitizedType.desensitizer().apply(value));
}
else
{
gen.writeString(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
throws JsonMappingException
{
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass()))
{
this.desensitizedType = annotation.desensitizedType();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
/**
* 是否需要脱敏处理
*/
private boolean desensitization()
{
try
{
LoginUser securityUser = SecurityUtils.getLoginUser();
// 管理员不脱敏
return !securityUser.getUser().isAdmin();
}
catch (Exception e)
{
return true;
}
}
}

View File

@@ -0,0 +1,32 @@
package com.zg.framework.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.zg.common.utils.ServletUtils;
/**
* 服务相关配置
*
* @author zg
*/
@Component
public class ServerConfig
{
/**
* 获取完整的请求路径,包括:域名,端口,上下文访问路径
*
* @return 服务地址
*/
public String getUrl()
{
HttpServletRequest request = ServletUtils.getRequest();
return getDomain(request);
}
public static String getDomain(HttpServletRequest request)
{
StringBuffer url = request.getRequestURL();
String contextPath = request.getServletContext().getContextPath();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
}
}

View File

@@ -0,0 +1,124 @@
package com.zg.framework.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
/**
* Swagger2的接口配置
*
* @author zg
*/
@Configuration
public class SwaggerConfig
{
/** 系统基础配置 */
@Autowired
private RuoYiConfig ruoyiConfig;
/** 是否开启swagger */
@Value("${swagger.enabled}")
private boolean enabled;
/** 设置请求的统一前缀 */
@Value("${swagger.pathMapping}")
private String pathMapping;
/**
* 创建API
*/
@Bean
public Docket createRestApi()
{
return new Docket(DocumentationType.OAS_30)
// 是否启用Swagger
.enable(enabled)
// 用来创建该API的基本信息展示在文档的页面中自定义展示的信息
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的swagger注解
// .apis(RequestHandlerSelectors.basePackage("com.zg.project.tool.swagger"))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
/* 设置安全模式swagger可以设置访问token */
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
.pathMapping(pathMapping);
}
/**
* 安全模式这里指定token通过Authorization头请求头传递
*/
private List<SecurityScheme> securitySchemes()
{
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts()
{
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}
/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth()
{
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
/**
* 添加摘要信息
*/
private ApiInfo apiInfo()
{
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("标题若依管理系统_接口文档")
// 描述
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
// 作者信息
.contact(new Contact(ruoyiConfig.getName(), null, null))
// 版本
.version("版本号:" + ruoyiConfig.getVersion())
.build();
}
}

View File

@@ -0,0 +1,63 @@
package com.zg.framework.config;
import com.zg.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 zg
**/
@Configuration
public class ThreadPoolConfig
{
// 核心线程池大小
private int corePoolSize = 50;
// 最大可创建的线程数
private int maxPoolSize = 200;
// 队列最大长度
private int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
@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;
}
/**
* 执行周期性或定时任务
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService()
{
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy())
{
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
Threads.printException(r, t);
}
};
}
}

View File

@@ -0,0 +1,89 @@
package com.zg.framework.config.properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
/**
* druid 配置属性
*
* @author zg
*/
@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);
/**
* 用来检测连接是否有效的sql要求是一个查询语句常用select 'x'。如果validationQuery为nulltestOnBorrow、testOnReturn、testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}

View File

@@ -0,0 +1,73 @@
package com.zg.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.zg.framework.aspectj.lang.annotation.Anonymous;
/**
* 设置Anonymous注解允许匿名访问的url
*
* @author zg
*/
@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;
}
}

View File

@@ -0,0 +1,26 @@
package com.zg.framework.datasource;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源
*
* @author zg
*/
public class DynamicDataSource extends AbstractRoutingDataSource
{
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey()
{
return DynamicDataSourceContextHolder.getDataSourceType();
}
}

View File

@@ -0,0 +1,45 @@
package com.zg.framework.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源切换处理
*
* @author zg
*/
public class DynamicDataSourceContextHolder
{
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量ThreadLocal为每个使用该变量的线程提供独立的变量副本
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
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();
}
}

View File

@@ -0,0 +1,56 @@
package com.zg.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.zg.common.utils.ServletUtils;
import com.zg.framework.interceptor.annotation.RepeatSubmit;
import com.zg.framework.web.domain.AjaxResult;
/**
* 防止重复提交拦截器
*
* @author zg
*/
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (handler instanceof HandlerMethod)
{
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null)
{
if (this.isRepeatSubmit(request, annotation))
{
AjaxResult ajaxResult = AjaxResult.error(annotation.message());
ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));
return false;
}
}
return true;
}
else
{
return true;
}
}
/**
* 验证是否重复提交由子类实现具体的防重复提交的规则
*
* @param request 请求信息
* @param annotation 防重复注解参数
* @return 结果
* @throws Exception
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

View File

@@ -0,0 +1,31 @@
package com.zg.framework.interceptor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解防止表单重复提交
*
* @author zg
*
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
public int interval() default 5000;
/**
* 提示消息
*/
public String message() default "不允许重复提交,请稍后再试";
}

View File

@@ -0,0 +1,110 @@
package com.zg.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.zg.common.constant.CacheConstants;
import com.zg.common.filter.RepeatedlyRequestWrapper;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.http.HttpHelper;
import com.zg.framework.interceptor.RepeatSubmitInterceptor;
import com.zg.framework.interceptor.annotation.RepeatSubmit;
import com.zg.framework.redis.RedisCache;
/**
* 判断请求url和数据是否和上一次相同
* 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
*
* @author zg
*/
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
{
public final String REPEAT_PARAMS = "repeatParams";
public final String REPEAT_TIME = "repeatTime";
// 令牌自定义标识
@Value("${token.header}")
private String header;
@Autowired
private RedisCache redisCache;
@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参数为空获取Parameter的数据
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());
// 请求地址作为存放cache的key值
String url = request.getRequestURI();
// 唯一值(没有消息头则使用请求地址)
String submitKey = StringUtils.trimToEmpty(request.getHeader(header));
// 唯一标识指定key + url + 消息头)
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
if (sessionObj != null)
{
Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
if (sessionMap.containsKey(url))
{
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))
{
return true;
}
}
}
Map<String, Object> cacheMap = new HashMap<String, Object>();
cacheMap.put(url, nowDataMap);
redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);
return false;
}
/**
* 判断参数是否相同
*/
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
{
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
String preParams = (String) preMap.get(REPEAT_PARAMS);
return nowParams.equals(preParams);
}
/**
* 判断两次间隔时间
*/
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
{
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
if ((time1 - time2) < interval)
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,55 @@
package com.zg.framework.manager;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.zg.common.utils.Threads;
import com.zg.common.utils.spring.SpringUtils;
/**
* 异步任务管理器
*
* @author zg
*/
public class AsyncManager
{
/**
* 操作延迟10毫秒
*/
private final int OPERATE_DELAY_TIME = 10;
/**
* 异步操作任务调度线程池
*/
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
/**
* 单例模式
*/
private AsyncManager(){}
private static AsyncManager me = new AsyncManager();
public static AsyncManager me()
{
return me;
}
/**
* 执行任务
*
* @param task 任务
*/
public void execute(TimerTask task)
{
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/**
* 停止任务线程池
*/
public void shutdown()
{
Threads.shutdownAndAwaitTermination(executor);
}
}

View File

@@ -0,0 +1,39 @@
package com.zg.framework.manager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
/**
* 确保应用退出时能关闭后台线程
*
* @author zg
*/
@Component
public class ShutdownManager
{
private static final Logger logger = LoggerFactory.getLogger("sys-user");
@PreDestroy
public void destroy()
{
shutdownAsyncManager();
}
/**
* 停止异步执行任务
*/
private void shutdownAsyncManager()
{
try
{
logger.info("====关闭后台任务任务线程池====");
AsyncManager.me().shutdown();
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,102 @@
package com.zg.framework.manager.factory;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zg.common.constant.Constants;
import com.zg.common.utils.LogUtils;
import com.zg.common.utils.ServletUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.ip.AddressUtils;
import com.zg.common.utils.ip.IpUtils;
import com.zg.common.utils.spring.SpringUtils;
import com.zg.project.monitor.domain.SysLogininfor;
import com.zg.project.monitor.domain.SysOperLog;
import com.zg.project.monitor.service.ISysLogininforService;
import com.zg.project.monitor.service.ISysOperLogService;
import eu.bitwalker.useragentutils.UserAgent;
/**
* 异步工厂(产生任务用)
*
* @author zg
*/
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);
}
};
}
}

View File

@@ -0,0 +1,268 @@
package com.zg.framework.redis;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
/**
* spring redis 工具类
*
* @author zg
**/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key)
{
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection)
{
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey)
{
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}

View File

@@ -0,0 +1,69 @@
package com.zg.framework.security;
/**
* 用户登录对象
*
* @author zg
*/
public class LoginBody
{
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 验证码
*/
private String code;
/**
* 唯一标识
*/
private String uuid;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String getCode()
{
return code;
}
public void setCode(String code)
{
this.code = code;
}
public String getUuid()
{
return uuid;
}
public void setUuid(String uuid)
{
this.uuid = uuid;
}
}

View File

@@ -0,0 +1,266 @@
package com.zg.framework.security;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.alibaba.fastjson2.annotation.JSONField;
import com.zg.project.system.domain.SysUser;
/**
* 登录用户身份权限
*
* @author zg
*/
public class LoginUser implements UserDetails
{
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户唯一标识
*/
private String token;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 权限列表
*/
private Set<String> permissions;
/**
* 用户信息
*/
private SysUser user;
public LoginUser()
{
}
public LoginUser(SysUser user, Set<String> permissions)
{
this.user = user;
this.permissions = permissions;
}
public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)
{
this.userId = userId;
this.deptId = deptId;
this.user = user;
this.permissions = permissions;
}
public Long getUserId()
{
return userId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
public Long getDeptId()
{
return deptId;
}
public void setDeptId(Long deptId)
{
this.deptId = deptId;
}
public String getToken()
{
return token;
}
public void setToken(String token)
{
this.token = token;
}
@JSONField(serialize = false)
@Override
public String getPassword()
{
return user.getPassword();
}
@Override
public String getUsername()
{
return user.getUserName();
}
/**
* 账户是否未过期,过期无法验证
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonExpired()
{
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonLocked()
{
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isCredentialsNonExpired()
{
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isEnabled()
{
return true;
}
public Long getLoginTime()
{
return loginTime;
}
public void setLoginTime(Long loginTime)
{
this.loginTime = loginTime;
}
public String getIpaddr()
{
return ipaddr;
}
public void setIpaddr(String ipaddr)
{
this.ipaddr = ipaddr;
}
public String getLoginLocation()
{
return loginLocation;
}
public void setLoginLocation(String loginLocation)
{
this.loginLocation = loginLocation;
}
public String getBrowser()
{
return browser;
}
public void setBrowser(String browser)
{
this.browser = browser;
}
public String getOs()
{
return os;
}
public void setOs(String os)
{
this.os = os;
}
public Long getExpireTime()
{
return expireTime;
}
public void setExpireTime(Long expireTime)
{
this.expireTime = expireTime;
}
public Set<String> getPermissions()
{
return permissions;
}
public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}
public SysUser getUser()
{
return user;
}
public void setUser(SysUser user)
{
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
return null;
}
}

View File

@@ -0,0 +1,11 @@
package com.zg.framework.security;
/**
* 用户注册对象
*
* @author zg
*/
public class RegisterBody extends LoginBody
{
}

View File

@@ -0,0 +1,28 @@
package com.zg.framework.security.context;
import org.springframework.security.core.Authentication;
/**
* 身份验证信息
*
* @author zg
*/
public class AuthenticationContextHolder
{
private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();
public static Authentication getContext()
{
return contextHolder.get();
}
public static void setContext(Authentication context)
{
contextHolder.set(context);
}
public static void clearContext()
{
contextHolder.remove();
}
}

View File

@@ -0,0 +1,27 @@
package com.zg.framework.security.context;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.zg.common.core.text.Convert;
/**
* 权限信息
*
* @author zg
*/
public class PermissionContextHolder
{
private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
public static void setContext(String permission)
{
RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
RequestAttributes.SCOPE_REQUEST);
}
public static String getContext()
{
return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
RequestAttributes.SCOPE_REQUEST));
}
}

View File

@@ -0,0 +1,44 @@
package com.zg.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.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.security.LoginUser;
import com.zg.framework.security.service.TokenService;
/**
* token过滤器 验证token有效性
*
* @author zg
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,34 @@
package com.zg.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.zg.common.constant.HttpStatus;
import com.zg.common.utils.ServletUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.web.domain.AjaxResult;
/**
* 认证失败处理类 返回未授权
*
* @author zg
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException
{
int code = HttpStatus.UNAUTHORIZED;
String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
}
}

View File

@@ -0,0 +1,52 @@
package com.zg.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.zg.common.constant.Constants;
import com.zg.common.utils.ServletUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.manager.AsyncManager;
import com.zg.framework.manager.factory.AsyncFactory;
import com.zg.framework.security.LoginUser;
import com.zg.framework.security.service.TokenService;
import com.zg.framework.web.domain.AjaxResult;
/**
* 自定义退出处理类 返回成功
*
* @author zg
*/
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
@Autowired
private TokenService tokenService;
/**
* 退出处理
*
* @return
*/
@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, "退出成功"));
}
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
}
}

View File

@@ -0,0 +1,159 @@
package com.zg.framework.security.service;
import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.zg.common.constant.Constants;
import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.security.LoginUser;
import com.zg.framework.security.context.PermissionContextHolder;
import com.zg.project.system.domain.SysRole;
/**
* RuoYi首创 自定义权限实现ss取自SpringSecurity首字母
*
* @author zg
*/
@Service("ss")
public class PermissionService
{
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
PermissionContextHolder.setContext(permission);
return hasPermissions(loginUser.getPermissions(), permission);
}
/**
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
*
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
return hasPermi(permission) != true;
}
/**
* 验证用户是否具有以下任意一个权限
*
* @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String permissions)
{
if (StringUtils.isEmpty(permissions))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
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;
}
}
return false;
}
/**
* 判断用户是否拥有某个角色
*
* @param role 角色字符串
* @return 用户是否具备某角色
*/
public boolean hasRole(String role)
{
if (StringUtils.isEmpty(role))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
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;
}
}
return false;
}
/**
* 验证用户是否不具备某角色,与 isRole逻辑相反。
*
* @param role 角色名称
* @return 用户是否不具备某角色
*/
public boolean lacksRole(String role)
{
return hasRole(role) != true;
}
/**
* 验证用户是否具有以下任意一个角色
*
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String roles)
{
if (StringUtils.isEmpty(roles))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
return false;
}
for (String role : roles.split(Constants.ROLE_DELIMETER))
{
if (hasRole(role))
{
return true;
}
}
return false;
}
/**
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Set<String> permissions, String permission)
{
return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}
}

View File

@@ -0,0 +1,181 @@
package com.zg.framework.security.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.zg.common.constant.CacheConstants;
import com.zg.common.constant.Constants;
import com.zg.common.constant.UserConstants;
import com.zg.common.exception.ServiceException;
import com.zg.common.exception.user.BlackListException;
import com.zg.common.exception.user.CaptchaException;
import com.zg.common.exception.user.CaptchaExpireException;
import com.zg.common.exception.user.UserNotExistsException;
import com.zg.common.exception.user.UserPasswordNotMatchException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.MessageUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.ip.IpUtils;
import com.zg.framework.manager.AsyncManager;
import com.zg.framework.manager.factory.AsyncFactory;
import com.zg.framework.redis.RedisCache;
import com.zg.framework.security.LoginUser;
import com.zg.framework.security.context.AuthenticationContextHolder;
import com.zg.project.system.domain.SysUser;
import com.zg.project.system.service.ISysConfigService;
import com.zg.project.system.service.ISysUserService;
/**
* 登录校验方法
*
* @author zg
*/
@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);
if (captcha == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
throw new CaptchaExpireException();
}
redisCache.deleteObject(verifyKey);
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);
}
}

View File

@@ -0,0 +1,86 @@
package com.zg.framework.security.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.zg.common.constant.CacheConstants;
import com.zg.common.exception.user.UserPasswordNotMatchException;
import com.zg.common.exception.user.UserPasswordRetryLimitExceedException;
import com.zg.common.utils.SecurityUtils;
import com.zg.framework.redis.RedisCache;
import com.zg.framework.security.context.AuthenticationContextHolder;
import com.zg.project.system.domain.SysUser;
/**
* 登录密码方法
*
* @author zg
*/
@Component
public class SysPasswordService
{
@Autowired
private RedisCache redisCache;
@Value(value = "${user.password.maxRetryCount}")
private int maxRetryCount;
@Value(value = "${user.password.lockTime}")
private int lockTime;
/**
* 登录账户密码错误次数缓存键名
*
* @param username 用户名
* @return 缓存键key
*/
private String getCacheKey(String username)
{
return CacheConstants.PWD_ERR_CNT_KEY + username;
}
public void validate(SysUser user)
{
Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
String username = usernamePasswordAuthenticationToken.getName();
String password = usernamePasswordAuthenticationToken.getCredentials().toString();
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
if (retryCount == null)
{
retryCount = 0;
}
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
{
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
}
if (!matches(user, password))
{
retryCount = retryCount + 1;
redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);
throw new UserPasswordNotMatchException();
}
else
{
clearLoginRecordCache(username);
}
}
public boolean matches(SysUser user, String rawPassword)
{
return SecurityUtils.matchesPassword(rawPassword, user.getPassword());
}
public void clearLoginRecordCache(String loginName)
{
if (redisCache.hasKey(getCacheKey(loginName)))
{
redisCache.deleteObject(getCacheKey(loginName));
}
}
}

View File

@@ -0,0 +1,88 @@
package com.zg.framework.security.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.zg.common.constant.UserConstants;
import com.zg.common.utils.StringUtils;
import com.zg.project.system.domain.SysRole;
import com.zg.project.system.domain.SysUser;
import com.zg.project.system.service.ISysMenuService;
import com.zg.project.system.service.ISysRoleService;
/**
* 用户权限处理
*
* @author zg
*/
@Component
public class SysPermissionService
{
@Autowired
private ISysRoleService roleService;
@Autowired
private ISysMenuService menuService;
/**
* 获取角色数据权限
*
* @param user 用户信息
* @return 角色权限信息
*/
public Set<String> getRolePermission(SysUser user)
{
Set<String> roles = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin())
{
roles.add("admin");
}
else
{
roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
}
return roles;
}
/**
* 获取菜单数据权限
*
* @param user 用户信息
* @return 菜单权限信息
*/
public Set<String> getMenuPermission(SysUser user)
{
Set<String> perms = new HashSet<String>();
// 管理员拥有所有权限
if (user.isAdmin())
{
perms.add("*:*:*");
}
else
{
List<SysRole> roles = user.getRoles();
if (!CollectionUtils.isEmpty(roles))
{
// 设置permissions属性以便数据权限匹配权限
for (SysRole role : roles)
{
if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && !role.isAdmin())
{
Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
role.setPermissions(rolePerms);
perms.addAll(rolePerms);
}
}
}
else
{
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
}
}
return perms;
}
}

View File

@@ -0,0 +1,115 @@
package com.zg.framework.security.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.zg.common.constant.CacheConstants;
import com.zg.common.constant.Constants;
import com.zg.common.constant.UserConstants;
import com.zg.common.exception.user.CaptchaException;
import com.zg.common.exception.user.CaptchaExpireException;
import com.zg.common.utils.MessageUtils;
import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.manager.AsyncManager;
import com.zg.framework.manager.factory.AsyncFactory;
import com.zg.framework.redis.RedisCache;
import com.zg.framework.security.RegisterBody;
import com.zg.project.system.domain.SysUser;
import com.zg.project.system.service.ISysConfigService;
import com.zg.project.system.service.ISysUserService;
/**
* 注册校验方法
*
* @author zg
*/
@Component
public class SysRegisterService
{
@Autowired
private ISysUserService userService;
@Autowired
private ISysConfigService configService;
@Autowired
private RedisCache redisCache;
/**
* 注册
*/
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 = "用户密码不能为空";
}
else if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
msg = "账户长度必须在2到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 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid)
{
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
if (captcha == null)
{
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha))
{
throw new CaptchaException();
}
}
}

View File

@@ -0,0 +1,232 @@
package com.zg.framework.security.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.zg.common.constant.CacheConstants;
import com.zg.common.constant.Constants;
import com.zg.common.utils.ServletUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.ip.AddressUtils;
import com.zg.common.utils.ip.IpUtils;
import com.zg.common.utils.uuid.IdUtils;
import com.zg.framework.redis.RedisCache;
import com.zg.framework.security.LoginUser;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* token验证处理
*
* @author zg
*/
@Component
public class TokenService
{
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
// 令牌自定义标识
@Value("${token.header}")
private String header;
// 令牌秘钥
@Value("${token.secret}")
private String secret;
// 令牌有效期默认30分钟
@Value("${token.expireTime}")
private int expireTime;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L;
@Autowired
private RedisCache redisCache;
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
log.error("获取用户信息异常'{}'", e.getMessage());
}
}
return null;
}
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser)
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
refreshToken(loginUser);
}
}
/**
* 删除用户身份信息
*/
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
}
/**
* 创建令牌
*
* @param loginUser 用户信息
* @return 令牌
*/
public String createToken(LoginUser loginUser)
{
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
claims.put(Constants.JWT_USERNAME, loginUser.getUsername());
return createToken(claims);
}
/**
* 验证令牌有效期相差不足20分钟自动刷新缓存
*
* @param loginUser 登录信息
* @return 令牌
*/
public void verifyToken(LoginUser loginUser)
{
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY)
{
refreshToken(loginUser);
}
}
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
/**
* 设置用户代理信息
*
* @param loginUser 登录信息
*/
public void setUserAgent(LoginUser loginUser)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
{
Claims claims = parseToken(token);
return claims.getSubject();
}
/**
* 获取请求token
*
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private String getTokenKey(String uuid)
{
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
}
}

View File

@@ -0,0 +1,65 @@
package com.zg.framework.security.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.stereotype.Service;
import com.zg.common.enums.UserStatus;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.MessageUtils;
import com.zg.common.utils.StringUtils;
import com.zg.framework.security.LoginUser;
import com.zg.project.system.domain.SysUser;
import com.zg.project.system.service.ISysUserService;
/**
* 用户验证处理
*
* @author zg
*/
@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;
@Override
public UserDetails loadUserByUsername(String username)
{
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);
}
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}

View File

@@ -0,0 +1,28 @@
package com.zg.framework.task;
import org.springframework.stereotype.Component;
import com.zg.common.utils.StringUtils;
/**
* 定时任务调度测试
*
* @author zg
*/
@Component("ryTask")
public class RyTask
{
public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
{
System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
}
public void ryParams(String params)
{
System.out.println("执行有参方法:" + params);
}
public void ryNoParams()
{
System.out.println("执行无参方法");
}
}

View File

@@ -0,0 +1,194 @@
package com.zg.framework.web.controller;
import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zg.common.constant.HttpStatus;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.PageUtils;
import com.zg.common.utils.SecurityUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.sql.SqlUtil;
import com.zg.framework.security.LoginUser;
import com.zg.framework.web.domain.AjaxResult;
import com.zg.framework.web.page.PageDomain;
import com.zg.framework.web.page.TableDataInfo;
import com.zg.framework.web.page.TableSupport;
/**
* web层通用数据处理
*
* @author zg
*/
public class BaseController
{
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 将前台传递过来的日期格式的字符串自动转化为Date类型
*/
@InitBinder
public void initBinder(WebDataBinder binder)
{
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
{
@Override
public void setAsText(String text)
{
setValue(DateUtils.parseDate(text));
}
});
}
/**
* 设置请求分页数据
*/
protected void startPage()
{
PageUtils.startPage();
}
/**
* 设置请求排序数据
*/
protected void startOrderBy()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
{
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
PageHelper.orderBy(orderBy);
}
}
/**
* 清理分页的线程变量
*/
protected void clearPage()
{
PageUtils.clearPage();
}
/**
* 响应请求分页数据
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TableDataInfo getDataTable(List<?> list)
{
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(new PageInfo(list).getTotal());
return rspData;
}
/**
* 返回成功
*/
public AjaxResult success()
{
return AjaxResult.success();
}
/**
* 返回成功消息
*/
public AjaxResult success(String message)
{
return AjaxResult.success(message);
}
/**
* 返回成功消息
*/
public AjaxResult success(Object data)
{
return AjaxResult.success(data);
}
/**
* 返回失败消息
*/
public AjaxResult error()
{
return AjaxResult.error();
}
/**
* 返回失败消息
*/
public AjaxResult error(String message)
{
return AjaxResult.error(message);
}
/**
* 返回警告消息
*/
public AjaxResult warn(String message)
{
return AjaxResult.warn(message);
}
/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected AjaxResult toAjax(int rows)
{
return rows > 0 ? AjaxResult.success() : AjaxResult.error();
}
/**
* 响应返回结果
*
* @param result 结果
* @return 操作结果
*/
protected AjaxResult toAjax(boolean result)
{
return result ? success() : error();
}
/**
* 获取用户缓存信息
*/
public LoginUser getLoginUser()
{
return SecurityUtils.getLoginUser();
}
/**
* 获取登录用户id
*/
public Long getUserId()
{
return getLoginUser().getUserId();
}
/**
* 获取登录部门id
*/
public Long getDeptId()
{
return getLoginUser().getDeptId();
}
/**
* 获取登录用户名
*/
public String getUsername()
{
return getLoginUser().getUsername();
}
}

View File

@@ -0,0 +1,216 @@
package com.zg.framework.web.domain;
import java.util.HashMap;
import java.util.Objects;
import com.zg.common.constant.HttpStatus;
import com.zg.common.utils.StringUtils;
/**
* 操作消息提醒
*
* @author zg
*/
public class AjaxResult extends HashMap<String, Object>
{
private static final long serialVersionUID = 1L;
/** 状态码 */
public static final String CODE_TAG = "code";
/** 返回内容 */
public static final String MSG_TAG = "msg";
/** 数据对象 */
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult()
{
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (StringUtils.isNotNull(data))
{
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success()
{
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data)
{
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg)
{
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data)
{
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult warn(String msg)
{
return AjaxResult.warn(msg, null);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult warn(String msg, Object data)
{
return new AjaxResult(HttpStatus.WARN, msg, data);
}
/**
* 返回错误消息
*
* @return 错误消息
*/
public static AjaxResult error()
{
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(String msg)
{
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 错误消息
*/
public static AjaxResult error(String msg, Object data)
{
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(int code, String msg)
{
return new AjaxResult(code, msg, null);
}
/**
* 是否为成功消息
*
* @return 结果
*/
public boolean isSuccess()
{
return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
}
/**
* 是否为警告消息
*
* @return 结果
*/
public boolean isWarn()
{
return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
}
/**
* 是否为错误消息
*
* @return 结果
*/
public boolean isError()
{
return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
}
/**
* 方便链式调用
*
* @param key 键
* @param value 值
* @return 数据对象
*/
@Override
public AjaxResult put(String key, Object value)
{
super.put(key, value);
return this;
}
}

View File

@@ -0,0 +1,118 @@
package com.zg.framework.web.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* Entity基类
*
* @author zg
*/
public class BaseEntity implements Serializable
{
private static final long serialVersionUID = 1L;
/** 搜索值 */
@JsonIgnore
private String searchValue;
/** 创建者 */
private String createBy;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/** 更新者 */
private String updateBy;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/** 备注 */
private String remark;
/** 请求参数 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, Object> params;
public String getSearchValue()
{
return searchValue;
}
public void setSearchValue(String searchValue)
{
this.searchValue = searchValue;
}
public String getCreateBy()
{
return createBy;
}
public void setCreateBy(String createBy)
{
this.createBy = createBy;
}
public Date getCreateTime()
{
return createTime;
}
public void setCreateTime(Date createTime)
{
this.createTime = createTime;
}
public String getUpdateBy()
{
return updateBy;
}
public void setUpdateBy(String updateBy)
{
this.updateBy = updateBy;
}
public Date getUpdateTime()
{
return updateTime;
}
public void setUpdateTime(Date updateTime)
{
this.updateTime = updateTime;
}
public String getRemark()
{
return remark;
}
public void setRemark(String remark)
{
this.remark = remark;
}
public Map<String, Object> getParams()
{
if (params == null)
{
params = new HashMap<>();
}
return params;
}
public void setParams(Map<String, Object> params)
{
this.params = params;
}
}

View File

@@ -0,0 +1,115 @@
package com.zg.framework.web.domain;
import java.io.Serializable;
import com.zg.common.constant.HttpStatus;
/**
* 响应信息主体
*
* @author zg
*/
public class R<T> implements Serializable
{
private static final long serialVersionUID = 1L;
/** 成功 */
public static final int SUCCESS = HttpStatus.SUCCESS;
/** 失败 */
public static final int FAIL = HttpStatus.ERROR;
private int code;
private String msg;
private T data;
public static <T> R<T> ok()
{
return restResult(null, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data)
{
return restResult(data, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data, String msg)
{
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> fail()
{
return restResult(null, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg)
{
return restResult(null, FAIL, msg);
}
public static <T> R<T> fail(T data)
{
return restResult(data, FAIL, "操作失败");
}
public static <T> R<T> fail(T data, String msg)
{
return restResult(data, FAIL, msg);
}
public static <T> R<T> fail(int code, String msg)
{
return restResult(null, code, msg);
}
private static <T> R<T> restResult(T data, int code, String msg)
{
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
public int getCode()
{
return code;
}
public void setCode(int code)
{
this.code = code;
}
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
public T getData()
{
return data;
}
public void setData(T data)
{
this.data = data;
}
public static <T> Boolean isError(R<T> ret)
{
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(R<T> ret)
{
return R.SUCCESS == ret.getCode();
}
}

View File

@@ -0,0 +1,240 @@
package com.zg.framework.web.domain;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import com.zg.common.utils.Arith;
import com.zg.common.utils.ip.IpUtils;
import com.zg.framework.web.domain.server.Cpu;
import com.zg.framework.web.domain.server.Jvm;
import com.zg.framework.web.domain.server.Mem;
import com.zg.framework.web.domain.server.Sys;
import com.zg.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;
/**
* 服务器相关信息
*
* @author zg
*/
public class Server
{
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>();
public Cpu getCpu()
{
return cpu;
}
public void setCpu(Cpu cpu)
{
this.cpu = cpu;
}
public Mem getMem()
{
return mem;
}
public void setMem(Mem mem)
{
this.mem = mem;
}
public Jvm getJvm()
{
return 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;
}
public void copyTo() throws Exception
{
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
setCpuInfo(hal.getProcessor());
setMemInfo(hal.getMemory());
setSysInfo();
setJvmInfo();
setSysFiles(si.getOperatingSystem());
}
/**
* 设置CPU信息
*/
private void setCpuInfo(CentralProcessor processor)
{
// CPU信息
long[] prevTicks = processor.getSystemCpuLoadTicks();
Util.sleep(OSHI_WAIT_SECOND);
long[] ticks = processor.getSystemCpuLoadTicks();
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()];
long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
cpu.setCpuNum(processor.getLogicalProcessorCount());
cpu.setTotal(totalCpu);
cpu.setSys(cSys);
cpu.setUsed(user);
cpu.setWait(iowait);
cpu.setFree(idle);
}
/**
* 设置内存信息
*/
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());
sys.setComputerIp(IpUtils.getHostIp());
sys.setOsName(props.getProperty("os.name"));
sys.setOsArch(props.getProperty("os.arch"));
sys.setUserDir(props.getProperty("user.dir"));
}
/**
* 设置Java虚拟机
*/
private void setJvmInfo() throws UnknownHostException
{
Properties props = System.getProperties();
jvm.setTotal(Runtime.getRuntime().totalMemory());
jvm.setMax(Runtime.getRuntime().maxMemory());
jvm.setFree(Runtime.getRuntime().freeMemory());
jvm.setVersion(props.getProperty("java.version"));
jvm.setHome(props.getProperty("java.home"));
}
/**
* 设置磁盘信息
*/
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);
}
}
/**
* 字节转换
*
* @param size 字节大小
* @return 转换后值
*/
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);
}
else if (size >= kb)
{
float f = (float) size / kb;
return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
}
else
{
return String.format("%d B", size);
}
}
}

View File

@@ -0,0 +1,79 @@
package com.zg.framework.web.domain;
import java.util.ArrayList;
import java.util.List;
/**
* Tree基类
*
* @author zg
*/
public class TreeEntity extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 父菜单名称 */
private String parentName;
/** 父菜单ID */
private Long parentId;
/** 显示顺序 */
private Integer orderNum;
/** 祖级列表 */
private String ancestors;
/** 子部门 */
private List<?> children = new ArrayList<>();
public String getParentName()
{
return parentName;
}
public void setParentName(String parentName)
{
this.parentName = parentName;
}
public Long getParentId()
{
return parentId;
}
public void setParentId(Long parentId)
{
this.parentId = parentId;
}
public Integer getOrderNum()
{
return orderNum;
}
public void setOrderNum(Integer orderNum)
{
this.orderNum = orderNum;
}
public String getAncestors()
{
return ancestors;
}
public void setAncestors(String ancestors)
{
this.ancestors = ancestors;
}
public List<?> getChildren()
{
return children;
}
public void setChildren(List<?> children)
{
this.children = children;
}
}

View File

@@ -0,0 +1,93 @@
package com.zg.framework.web.domain;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.zg.common.constant.UserConstants;
import com.zg.common.utils.StringUtils;
import com.zg.project.system.domain.SysDept;
import com.zg.project.system.domain.SysMenu;
/**
* Treeselect树结构实体类
*
* @author zg
*/
public class TreeSelect implements Serializable
{
private static final long serialVersionUID = 1L;
/** 节点ID */
private Long id;
/** 节点名称 */
private String label;
/** 节点禁用 */
private boolean disabled = false;
/** 子节点 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<TreeSelect> children;
public TreeSelect()
{
}
public TreeSelect(SysDept dept)
{
this.id = dept.getDeptId();
this.label = dept.getDeptName();
this.disabled = StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus());
this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}
public TreeSelect(SysMenu menu)
{
this.id = menu.getMenuId();
this.label = menu.getMenuName();
this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getLabel()
{
return label;
}
public void setLabel(String label)
{
this.label = label;
}
public boolean isDisabled()
{
return disabled;
}
public void setDisabled(boolean disabled)
{
this.disabled = disabled;
}
public List<TreeSelect> getChildren()
{
return children;
}
public void setChildren(List<TreeSelect> children)
{
this.children = children;
}
}

View File

@@ -0,0 +1,101 @@
package com.zg.framework.web.domain.server;
import com.zg.common.utils.Arith;
/**
* CPU相关信息
*
* @author zg
*/
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;
public int getCpuNum()
{
return cpuNum;
}
public void setCpuNum(int cpuNum)
{
this.cpuNum = cpuNum;
}
public double getTotal()
{
return Arith.round(Arith.mul(total, 100), 2);
}
public void setTotal(double total)
{
this.total = total;
}
public double getSys()
{
return Arith.round(Arith.mul(sys / total, 100), 2);
}
public void setSys(double sys)
{
this.sys = sys;
}
public double getUsed()
{
return Arith.round(Arith.mul(used / total, 100), 2);
}
public void setUsed(double used)
{
this.used = used;
}
public double getWait()
{
return Arith.round(Arith.mul(wait / total, 100), 2);
}
public void setWait(double wait)
{
this.wait = wait;
}
public double getFree()
{
return Arith.round(Arith.mul(free / total, 100), 2);
}
public void setFree(double free)
{
this.free = free;
}
}

View File

@@ -0,0 +1,130 @@
package com.zg.framework.web.domain.server;
import java.lang.management.ManagementFactory;
import com.zg.common.utils.Arith;
import com.zg.common.utils.DateUtils;
/**
* JVM相关信息
*
* @author zg
*/
public class Jvm
{
/**
* 当前JVM占用的内存总数(M)
*/
private double total;
/**
* JVM最大可用内存总数(M)
*/
private double max;
/**
* JVM空闲内存(M)
*/
private double free;
/**
* JDK版本
*/
private String version;
/**
* JDK路径
*/
private String home;
public double getTotal()
{
return Arith.div(total, (1024 * 1024), 2);
}
public void setTotal(double total)
{
this.total = total;
}
public double getMax()
{
return Arith.div(max, (1024 * 1024), 2);
}
public void setMax(double max)
{
this.max = max;
}
public double getFree()
{
return Arith.div(free, (1024 * 1024), 2);
}
public void setFree(double free)
{
this.free = free;
}
public double getUsed()
{
return Arith.div(total - free, (1024 * 1024), 2);
}
public double getUsage()
{
return Arith.mul(Arith.div(total - free, total, 4), 100);
}
/**
* 获取JDK名称
*/
public String getName()
{
return ManagementFactory.getRuntimeMXBean().getVmName();
}
public String getVersion()
{
return version;
}
public void setVersion(String version)
{
this.version = version;
}
public String getHome()
{
return home;
}
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());
}
/**
* 运行参数
*/
public String getInputArgs()
{
return ManagementFactory.getRuntimeMXBean().getInputArguments().toString();
}
}

View File

@@ -0,0 +1,61 @@
package com.zg.framework.web.domain.server;
import com.zg.common.utils.Arith;
/**
* 內存相关信息
*
* @author zg
*/
public class Mem
{
/**
* 内存总量
*/
private double total;
/**
* 已用内存
*/
private double used;
/**
* 剩余内存
*/
private double free;
public double getTotal()
{
return Arith.div(total, (1024 * 1024 * 1024), 2);
}
public void setTotal(long total)
{
this.total = total;
}
public double getUsed()
{
return Arith.div(used, (1024 * 1024 * 1024), 2);
}
public void setUsed(long used)
{
this.used = used;
}
public double getFree()
{
return Arith.div(free, (1024 * 1024 * 1024), 2);
}
public void setFree(long free)
{
this.free = free;
}
public double getUsage()
{
return Arith.mul(Arith.div(used, total, 4), 100);
}
}

View File

@@ -0,0 +1,84 @@
package com.zg.framework.web.domain.server;
/**
* 系统相关信息
*
* @author zg
*/
public class Sys
{
/**
* 服务器名称
*/
private String computerName;
/**
* 服务器Ip
*/
private String computerIp;
/**
* 项目路径
*/
private String userDir;
/**
* 操作系统
*/
private String osName;
/**
* 系统架构
*/
private String osArch;
public String getComputerName()
{
return computerName;
}
public void setComputerName(String computerName)
{
this.computerName = computerName;
}
public String getComputerIp()
{
return computerIp;
}
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;
}
}

View File

@@ -0,0 +1,114 @@
package com.zg.framework.web.domain.server;
/**
* 系统文件相关信息
*
* @author zg
*/
public class SysFile
{
/**
* 盘符路径
*/
private String dirName;
/**
* 盘符类型
*/
private String sysTypeName;
/**
* 文件类型
*/
private String typeName;
/**
* 总大小
*/
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;
}
}

View File

@@ -0,0 +1,145 @@
package com.zg.framework.web.exception;
import javax.servlet.http.HttpServletRequest;
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.zg.common.constant.HttpStatus;
import com.zg.common.core.text.Convert;
import com.zg.common.exception.DemoModeException;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.html.EscapeUtil;
import com.zg.framework.web.domain.AjaxResult;
/**
* 全局异常处理器
*
* @author zg
*/
@RestControllerAdvice
public class GlobalExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 权限校验异常
*/
@ExceptionHandler(AccessDeniedException.class)
public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return AjaxResult.error(e.getMessage());
}
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
{
log.error(e.getMessage(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 请求路径中缺少必需的路径变量
*/
@ExceptionHandler(MissingPathVariableException.class)
public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
* 请求参数类型不匹配
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
String value = Convert.toStr(e.getValue());
if (StringUtils.isNotEmpty(value))
{
value = EscapeUtil.clean(value);
}
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value));
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult handleBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult handleDemoModeException(DemoModeException e)
{
return AjaxResult.error("演示模式,不允许操作");
}
}

View File

@@ -0,0 +1,101 @@
package com.zg.framework.web.page;
import com.zg.common.utils.StringUtils;
/**
* 分页数据
*
* @author zg
*/
public class PageDomain
{
/** 当前记录起始索引 */
private Integer pageNum;
/** 每页显示记录数 */
private Integer pageSize;
/** 排序列 */
private String orderByColumn;
/** 排序的方向desc或者asc */
private String isAsc = "asc";
/** 分页参数合理化 */
private Boolean reasonable = true;
public String getOrderBy()
{
if (StringUtils.isEmpty(orderByColumn))
{
return "";
}
return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc;
}
public Integer getPageNum()
{
return pageNum;
}
public void setPageNum(Integer pageNum)
{
this.pageNum = pageNum;
}
public Integer getPageSize()
{
return pageSize;
}
public void setPageSize(Integer pageSize)
{
this.pageSize = pageSize;
}
public String getOrderByColumn()
{
return orderByColumn;
}
public void setOrderByColumn(String orderByColumn)
{
this.orderByColumn = orderByColumn;
}
public String getIsAsc()
{
return isAsc;
}
public void setIsAsc(String isAsc)
{
if (StringUtils.isNotEmpty(isAsc))
{
// 兼容前端排序类型
if ("ascending".equals(isAsc))
{
isAsc = "asc";
}
else if ("descending".equals(isAsc))
{
isAsc = "desc";
}
this.isAsc = isAsc;
}
}
public Boolean getReasonable()
{
if (StringUtils.isNull(reasonable))
{
return Boolean.TRUE;
}
return reasonable;
}
public void setReasonable(Boolean reasonable)
{
this.reasonable = reasonable;
}
}

View File

@@ -0,0 +1,85 @@
package com.zg.framework.web.page;
import java.io.Serializable;
import java.util.List;
/**
* 表格分页数据对象
*
* @author zg
*/
public class TableDataInfo implements Serializable
{
private static final long serialVersionUID = 1L;
/** 总记录数 */
private long total;
/** 列表数据 */
private List<?> rows;
/** 消息状态码 */
private int code;
/** 消息内容 */
private String msg;
/**
* 表格数据对象
*/
public TableDataInfo()
{
}
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<?> list, long total)
{
this.rows = list;
this.total = total;
}
public long getTotal()
{
return total;
}
public void setTotal(long total)
{
this.total = total;
}
public List<?> getRows()
{
return rows;
}
public void setRows(List<?> rows)
{
this.rows = rows;
}
public int getCode()
{
return code;
}
public void setCode(int code)
{
this.code = code;
}
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
}

View File

@@ -0,0 +1,56 @@
package com.zg.framework.web.page;
import com.zg.common.core.text.Convert;
import com.zg.common.utils.ServletUtils;
/**
* 表格数据处理
*
* @author zg
*/
public class TableSupport
{
/**
* 当前记录起始索引
*/
public static final String PAGE_NUM = "pageNum";
/**
* 每页显示记录数
*/
public static final String PAGE_SIZE = "pageSize";
/**
* 排序列
*/
public static final String ORDER_BY_COLUMN = "orderByColumn";
/**
* 排序的方向 "desc" 或者 "asc".
*/
public static final String IS_ASC = "isAsc";
/**
* 分页参数合理化
*/
public static final String REASONABLE = "reasonable";
/**
* 封装分页对象
*/
public static PageDomain getPageDomain()
{
PageDomain pageDomain = new PageDomain();
pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1));
pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10));
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
return pageDomain;
}
public static PageDomain buildPageRequest()
{
return getPageDomain();
}
}

View File

@@ -0,0 +1,14 @@
package com.zg.framework.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

View File

@@ -0,0 +1,101 @@
package com.zg.framework.websocket.config;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.zg.project.Inventory.AutoInventory.service.IRfidService;
import com.zg.project.Inventory.AutoInventory.utils.SpringContextUtils;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint("/ws/socket")
@Component
public class WebSocketServer {
public static final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Session, String> sessionToDeviceMap = new ConcurrentHashMap<>();
// rfidService 不在构造方法中注入!
private IRfidService rfidService;
@OnOpen
public void onOpen(Session session) {
// 延迟注入,解决 NPE 问题
if (rfidService == null) {
rfidService = SpringContextUtils.getBean(IRfidService.class);
}
System.out.println("🟡 建立WebSocket连接等待设备发送连接信息 JSON...");
}
@OnMessage
public void onMessage(String message, Session session) {
try {
if (rfidService == null) {
rfidService = SpringContextUtils.getBean(IRfidService.class);
}
JSONObject json = JSON.parseObject(message);
String deviceId = json.getString("deviceId");
String ip = json.getString("ip");
int port = json.getIntValue("port");
sessionMap.put(deviceId, session);
sessionToDeviceMap.put(session, deviceId);
boolean connected = rfidService.connect(deviceId, ip, port);
if (connected) {
sendMessage(session, "设备[" + deviceId + "]连接成功!");
} else {
sendMessage(session, "设备[" + deviceId + "]连接失败!");
}
} catch (Exception e) {
sendMessage(session, "参数解析失败:" + e.getMessage());
}
}
@OnClose
public void onClose(Session session) {
if (rfidService == null) {
rfidService = SpringContextUtils.getBean(IRfidService.class);
}
String deviceId = sessionToDeviceMap.remove(session);
if (deviceId != null) {
// 移除 WebSocket 映射
sessionMap.remove(deviceId);
// 断开设备连接(包含 deviceIdToConnIdMap / connIdToDeviceMap / deviceConnectState 清理)
rfidService.disconnect(deviceId);
System.out.println("WebSocket连接断开已清理资源 -> deviceId = " + deviceId);
} else {
System.out.println("WebSocket断开但未能获取到 deviceId");
}
}
@OnError
public void onError(Session session, Throwable throwable) {
System.err.println("WebSocket连接错误: " + throwable.getMessage());
}
public static void sendMessage(Session session, String message) {
try {
if (session != null && session.isOpen()) {
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
System.err.println("消息发送失败: " + e.getMessage());
}
}
public static void sendMessageToDevice(String deviceId, String message) {
Session session = sessionMap.get(deviceId);
sendMessage(session, message);
}
}