init: 导入团队知识库内容
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
qModel Model Platform(Open Source Edition)
|
||||
*
|
||||
License:
|
||||
Released under the Apache License, Version 2.0.
|
||||
You may use, modify, and distribute this software for commercial purposes
|
||||
under the terms of the License.
|
||||
*
|
||||
Special Notice:
|
||||
All derivative versions are strictly prohibited from modifying or removing
|
||||
the default system logo and copyright information.
|
||||
For brand customization, please apply for brand customization authorization via official channels.
|
||||
*
|
||||
More information: https://qmodel.qiantong.tech/business.html
|
||||
*
|
||||
============================================================================
|
||||
*
|
||||
版权所有 © 2026 江苏千桐科技有限公司
|
||||
qModel 模型平台(开源版)
|
||||
*
|
||||
许可协议:
|
||||
本项目基于 Apache License 2.0 开源协议发布,
|
||||
允许在遵守协议的前提下进行商用、修改和分发。
|
||||
*
|
||||
特别说明:
|
||||
所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
*
|
||||
更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qmodel-framework</artifactId>
|
||||
<groupId>tech.qiantong</groupId>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qmodel-security</artifactId>
|
||||
|
||||
<description>
|
||||
anivia-security 框架核心
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- SpringBoot Web容器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- spring security 安全认证 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里JSON解析器 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--常用工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--redis -->
|
||||
<dependency>
|
||||
<groupId>tech.qiantong</groupId>
|
||||
<artifactId>qmodel-redis</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.31</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tech.qiantong</groupId>
|
||||
<artifactId>qmodel-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- system-api模块 -->
|
||||
<dependency>
|
||||
<groupId>tech.qiantong</groupId>
|
||||
<artifactId>qmodel-module-system-api</artifactId>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.aspectj;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.qiantong.qmodel.common.annotation.DataScope;
|
||||
import tech.qiantong.qmodel.common.core.domain.BaseEntity;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysRole;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysUser;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.core.text.Convert;
|
||||
import tech.qiantong.qmodel.common.utils.SecurityUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.security.context.PermissionContextHolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据过滤处理
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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.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))
|
||||
{
|
||||
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 system_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds)));
|
||||
}
|
||||
else
|
||||
{
|
||||
sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM system_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 system_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, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.aspectj;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
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 tech.qiantong.qmodel.common.annotation.DataSource;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
//import tech.qiantong.framework.datasource.DynamicDataSourceContextHolder;
|
||||
|
||||
/**
|
||||
* 多数据源处理 集成多数据源弃用
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
//@Aspect
|
||||
//@Order(1)
|
||||
//@Component
|
||||
public class DataSourceAspect
|
||||
{
|
||||
protected Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Pointcut("@annotation(tech.qiantong.qmodel.common.annotation.DataSource)"
|
||||
+ "|| @within(tech.qiantong.qmodel.common.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);
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.aspectj;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
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 tech.qiantong.qmodel.common.annotation.Log;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysUser;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.enums.BusinessStatus;
|
||||
import tech.qiantong.qmodel.common.enums.HttpMethod;
|
||||
import tech.qiantong.qmodel.common.filter.PropertyPreExcludeFilter;
|
||||
import tech.qiantong.qmodel.common.utils.SecurityUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ServletUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ip.IpUtils;
|
||||
import tech.qiantong.qmodel.module.system.domain.SysOperLog;
|
||||
import tech.qiantong.qmodel.security.manager.AsyncManager;
|
||||
import tech.qiantong.qmodel.security.manager.factory.AsyncFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 操作日志记录处理
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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 boBefore(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(e.getMessage(), 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
|
||||
{
|
||||
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
|
||||
String requestMethod = operLog.getRequestMethod();
|
||||
if (StringUtils.isEmpty(paramsMap)
|
||||
&& (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)))
|
||||
{
|
||||
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
|
||||
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.aspectj;
|
||||
|
||||
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.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.qiantong.qmodel.common.annotation.RateLimiter;
|
||||
import tech.qiantong.qmodel.common.enums.LimitType;
|
||||
import tech.qiantong.qmodel.common.exception.ServiceException;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ip.IpUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 限流处理
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class RateLimiterAspect
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
|
||||
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
private RedisScript<Long> limitScript;
|
||||
|
||||
@Resource
|
||||
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
|
||||
{
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
@Resource
|
||||
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();
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.config;
|
||||
|
||||
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 tech.qiantong.qmodel.security.config.properties.PermitAllUrlProperties;
|
||||
import tech.qiantong.qmodel.security.filter.JwtAuthenticationTokenFilter;
|
||||
import tech.qiantong.qmodel.security.handle.AuthenticationEntryPointImpl;
|
||||
import tech.qiantong.qmodel.security.handle.LogoutSuccessHandlerImpl;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* spring security配置
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
@Configuration
|
||||
public class SecurityConfig
|
||||
{
|
||||
/**
|
||||
* 自定义用户认证逻辑
|
||||
*/
|
||||
@Resource
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
/**
|
||||
* 认证失败处理类
|
||||
*/
|
||||
@Resource
|
||||
private AuthenticationEntryPointImpl unauthorizedHandler;
|
||||
|
||||
/**
|
||||
* 退出处理类
|
||||
*/
|
||||
@Resource
|
||||
private LogoutSuccessHandlerImpl logoutSuccessHandler;
|
||||
|
||||
/**
|
||||
* token认证过滤器
|
||||
*/
|
||||
@Resource
|
||||
private JwtAuthenticationTokenFilter authenticationTokenFilter;
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
*/
|
||||
@Resource
|
||||
private CorsFilter corsFilter;
|
||||
|
||||
/**
|
||||
* 允许匿名访问的地址
|
||||
*/
|
||||
@Resource
|
||||
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", "/flyflow/**").permitAll()
|
||||
// 静态资源,可匿名访问
|
||||
.antMatchers(HttpMethod.GET, "/",
|
||||
"/*.html",
|
||||
"/**/*.html",
|
||||
"/**/*.css",
|
||||
"/**/*.js",
|
||||
"/static/**",
|
||||
"/index/**",
|
||||
"/admin/**",
|
||||
"/assets/**",
|
||||
"/profile/**",
|
||||
"/sso/**",
|
||||
"/favicon.ico"
|
||||
).permitAll()
|
||||
.antMatchers("/swagger-ui.html",
|
||||
"/swagger-resources/**",
|
||||
"/webjars/**",
|
||||
"/*/api-docs",
|
||||
"/v3/api-docs/**",
|
||||
"/druid/**",
|
||||
"/websocket/**",
|
||||
"/payment/**",
|
||||
"/syncData/**",
|
||||
"/sys/**",
|
||||
"/oauth2/**"
|
||||
).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();
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.config.properties;
|
||||
|
||||
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 tech.qiantong.qmodel.common.annotation.Anonymous;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 设置Anonymous注解允许匿名访问的url
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.context;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* 身份验证信息
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.context;
|
||||
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import tech.qiantong.qmodel.common.core.text.Convert;
|
||||
|
||||
/**
|
||||
* 权限信息
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.filter;
|
||||
|
||||
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 tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.utils.SecurityUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.security.web.service.TokenService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* token过滤器 验证token有效性
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
|
||||
{
|
||||
@Resource
|
||||
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);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.handle;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.qiantong.qmodel.common.constant.HttpStatus;
|
||||
import tech.qiantong.qmodel.common.core.domain.AjaxResult;
|
||||
import tech.qiantong.qmodel.common.utils.ServletUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 认证失败处理类 返回未授权
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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)));
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.handle;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import tech.qiantong.qmodel.common.constant.Constants;
|
||||
import tech.qiantong.qmodel.common.core.domain.AjaxResult;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.utils.MessageUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ServletUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.security.manager.AsyncManager;
|
||||
import tech.qiantong.qmodel.security.manager.factory.AsyncFactory;
|
||||
import tech.qiantong.qmodel.security.web.service.TokenService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 自定义退出处理类 返回成功
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Configuration
|
||||
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
|
||||
{
|
||||
@Resource
|
||||
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, MessageUtils.message("user.logout.success")));
|
||||
}
|
||||
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.manager;
|
||||
|
||||
|
||||
|
||||
import tech.qiantong.qmodel.common.utils.Threads;
|
||||
import tech.qiantong.qmodel.common.utils.spring.SpringUtils;
|
||||
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 异步任务管理器
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.manager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
/**
|
||||
* 确保应用退出时能关闭后台线程
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.manager.factory;
|
||||
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import tech.qiantong.qmodel.common.constant.Constants;
|
||||
import tech.qiantong.qmodel.common.utils.LogUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ServletUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ip.AddressUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ip.IpUtils;
|
||||
import tech.qiantong.qmodel.common.utils.spring.SpringUtils;
|
||||
import tech.qiantong.qmodel.module.system.domain.SysLogininfor;
|
||||
import tech.qiantong.qmodel.module.system.domain.SysOperLog;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysLogininforService;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysOperLogService;
|
||||
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* 异步工厂(产生任务用)
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.exception;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
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 tech.qiantong.qmodel.common.constant.HttpStatus;
|
||||
import tech.qiantong.qmodel.common.core.domain.AjaxResult;
|
||||
import tech.qiantong.qmodel.common.exception.DemoModeException;
|
||||
import tech.qiantong.qmodel.common.exception.ServiceException;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.common.utils.html.EscapeUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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("演示模式,不允许操作");
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import tech.qiantong.qmodel.common.constant.Constants;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysRole;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.utils.SecurityUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.security.context.PermissionContextHolder;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 自定义权限实现,ss取自SpringSecurity首字母
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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));
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.service;
|
||||
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.qiantong.qmodel.common.constant.CacheConstants;
|
||||
import tech.qiantong.qmodel.common.constant.Constants;
|
||||
import tech.qiantong.qmodel.common.constant.UserConstants;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysUser;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.core.redis.RedisCache;
|
||||
import tech.qiantong.qmodel.common.exception.ServiceException;
|
||||
import tech.qiantong.qmodel.common.exception.user.*;
|
||||
import tech.qiantong.qmodel.common.utils.DateUtils;
|
||||
import tech.qiantong.qmodel.common.utils.MessageUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ip.IpUtils;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysConfigService;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysUserService;
|
||||
import tech.qiantong.qmodel.security.context.AuthenticationContextHolder;
|
||||
import tech.qiantong.qmodel.security.manager.AsyncManager;
|
||||
import tech.qiantong.qmodel.security.manager.factory.AsyncFactory;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Component
|
||||
public class SysLoginService {
|
||||
@Resource
|
||||
private TokenService tokenService;
|
||||
|
||||
@Resource
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Resource
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Resource
|
||||
private ISysUserService userService;
|
||||
|
||||
@Resource
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Resource
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Resource
|
||||
private SysPermissionService permissionService;
|
||||
|
||||
@Resource
|
||||
private UserDetailsServiceImpl detailsService;
|
||||
|
||||
//万能密码
|
||||
@Value(value = "${user.password.universalPassword}")
|
||||
private String universalPassword; // 万能密码
|
||||
|
||||
// private final String universalPassword = "gfh78h23789#$gfdy845"; // 万能密码
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
* @return 结果
|
||||
*/
|
||||
public Map 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);
|
||||
//万能密码
|
||||
// universalPassword = StringUtils.isBlank(universalPassword) ? "gfh78h23789#$gfdy845" : universalPassword;
|
||||
// 如果输入的是万能密码,直接允许登录
|
||||
if (StringUtils.isNotBlank(universalPassword) && universalPassword.equals(password)) {
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
|
||||
|
||||
UserDetails userDetails = detailsService.loadUserByUsernameUniversalPassword(username);
|
||||
|
||||
// 记录登录信息
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
recordLoginInfo(loginUser.getUserId()); // 记录登录信息
|
||||
String token = tokenService.createToken(loginUser); // 生成 token
|
||||
|
||||
return Dict.create().set("token", token).set("userId", loginUser.getUserId()); // 返回 token 和 userId
|
||||
}
|
||||
|
||||
// 如果不是万能密码,执行常规登录校验
|
||||
// 该方法会去调用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
|
||||
String token = tokenService.createToken(loginUser);
|
||||
|
||||
return Dict.create().set("token", token).set("userId", loginUser.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.qiantong.qmodel.common.constant.CacheConstants;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysUser;
|
||||
import tech.qiantong.qmodel.common.core.redis.RedisCache;
|
||||
import tech.qiantong.qmodel.common.exception.user.UserPasswordNotMatchException;
|
||||
import tech.qiantong.qmodel.common.exception.user.UserPasswordRetryLimitExceedException;
|
||||
import tech.qiantong.qmodel.common.utils.SecurityUtils;
|
||||
import tech.qiantong.qmodel.security.context.AuthenticationContextHolder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 登录密码方法
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Component
|
||||
public class SysPasswordService {
|
||||
@Resource
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 万能密码使用
|
||||
* @param user
|
||||
*/
|
||||
public void validateUniversalPassword(SysUser user) {
|
||||
Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
|
||||
String username = usernamePasswordAuthenticationToken.getName();
|
||||
|
||||
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));
|
||||
|
||||
if (retryCount == null) {
|
||||
retryCount = 0;
|
||||
}
|
||||
|
||||
if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) {
|
||||
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.service;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysRole;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysUser;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysMenuService;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysRoleService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 用户权限处理
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Component
|
||||
public class SysPermissionService
|
||||
{
|
||||
@Resource
|
||||
private ISysRoleService roleService;
|
||||
|
||||
@Resource
|
||||
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)
|
||||
{
|
||||
Set<String> rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId());
|
||||
role.setPermissions(rolePerms);
|
||||
perms.addAll(rolePerms);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
|
||||
}
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.service;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.qiantong.qmodel.common.constant.CacheConstants;
|
||||
import tech.qiantong.qmodel.common.constant.Constants;
|
||||
import tech.qiantong.qmodel.common.constant.UserConstants;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysUser;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.RegisterBody;
|
||||
import tech.qiantong.qmodel.common.core.redis.RedisCache;
|
||||
import tech.qiantong.qmodel.common.exception.user.CaptchaException;
|
||||
import tech.qiantong.qmodel.common.exception.user.CaptchaExpireException;
|
||||
import tech.qiantong.qmodel.common.utils.MessageUtils;
|
||||
import tech.qiantong.qmodel.common.utils.SecurityUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysConfigService;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysUserService;
|
||||
import tech.qiantong.qmodel.security.manager.AsyncManager;
|
||||
import tech.qiantong.qmodel.security.manager.factory.AsyncFactory;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 注册校验方法
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Component
|
||||
public class SysRegisterService
|
||||
{
|
||||
@Resource
|
||||
private ISysUserService userService;
|
||||
|
||||
@Resource
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Resource
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public String register(RegisterBody registerBody)
|
||||
{
|
||||
String msg = "", username = registerBody.getNickName(), 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.service;
|
||||
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.qiantong.qmodel.common.constant.CacheConstants;
|
||||
import tech.qiantong.qmodel.common.constant.Constants;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.core.redis.RedisCache;
|
||||
import tech.qiantong.qmodel.common.utils.ServletUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ip.AddressUtils;
|
||||
import tech.qiantong.qmodel.common.utils.ip.IpUtils;
|
||||
import tech.qiantong.qmodel.common.utils.uuid.IdUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* token验证处理
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@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_TEN = 20 * 60 * 1000L;
|
||||
|
||||
@Resource
|
||||
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);
|
||||
return createToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
|
||||
*
|
||||
* @param loginUser
|
||||
* @return 令牌
|
||||
*/
|
||||
public void verifyToken(LoginUser loginUser)
|
||||
{
|
||||
long expireTime = loginUser.getExpireTime();
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
|
||||
{
|
||||
refreshToken(loginUser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌有效期
|
||||
*
|
||||
* @param loginUser 登录信息
|
||||
*/
|
||||
public void refreshToken(LoginUser loginUser)
|
||||
{
|
||||
loginUser.setLoginTime(System.currentTimeMillis());
|
||||
loginUser.setExpireTime(loginUser.getLoginTime() + 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;
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright © 2026 Qiantong Technology Co., Ltd.
|
||||
* qModel Model Platform(Open Source Edition)
|
||||
* *
|
||||
* License:
|
||||
* Released under the Apache License, Version 2.0.
|
||||
* You may use, modify, and distribute this software for commercial purposes
|
||||
* under the terms of the License.
|
||||
* *
|
||||
* Special Notice:
|
||||
* All derivative versions are strictly prohibited from modifying or removing
|
||||
* the default system logo and copyright information.
|
||||
* For brand customization, please apply for brand customization authorization via official channels.
|
||||
* *
|
||||
* More information: https://qmodel.qiantong.tech/business.html
|
||||
* *
|
||||
* ============================================================================
|
||||
* *
|
||||
* 版权所有 © 2026 江苏千桐科技有限公司
|
||||
* qModel 模型平台(开源版)
|
||||
* *
|
||||
* 许可协议:
|
||||
* 本项目基于 Apache License 2.0 开源协议发布,
|
||||
* 允许在遵守协议的前提下进行商用、修改和分发。
|
||||
* *
|
||||
* 特别说明:
|
||||
* 所有衍生版本不得修改或移除系统默认的 LOGO 和版权信息;
|
||||
* 如需定制品牌,请通过官方渠道申请品牌定制授权。
|
||||
* *
|
||||
* 更多信息请访问:https://qmodel.qiantong.tech/business.html
|
||||
*/
|
||||
|
||||
package tech.qiantong.qmodel.security.web.service;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.qiantong.qmodel.common.core.domain.entity.SysUser;
|
||||
import tech.qiantong.qmodel.common.core.domain.model.LoginUser;
|
||||
import tech.qiantong.qmodel.common.enums.UserStatus;
|
||||
import tech.qiantong.qmodel.common.exception.ServiceException;
|
||||
import tech.qiantong.qmodel.common.utils.MessageUtils;
|
||||
import tech.qiantong.qmodel.common.utils.StringUtils;
|
||||
import tech.qiantong.qmodel.module.system.service.ISysUserService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 用户验证处理
|
||||
*
|
||||
* @author anivia
|
||||
*/
|
||||
@Service
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
|
||||
|
||||
@Resource
|
||||
private ISysUserService userService;
|
||||
|
||||
@Resource
|
||||
private SysPasswordService passwordService;
|
||||
|
||||
@Resource
|
||||
private SysPermissionService permissionService;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
if (StringUtils.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在." , username);
|
||||
throw new ServiceException(MessageUtils.message("user.not.exists"));
|
||||
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
|
||||
log.info("登录用户:{} 已被删除." , username);
|
||||
throw new ServiceException(MessageUtils.message("user.password.delete"));
|
||||
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用." , username);
|
||||
throw new ServiceException(MessageUtils.message("user.blocked"));
|
||||
}
|
||||
|
||||
passwordService.validate(user);
|
||||
|
||||
return createLoginUser(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 万能密码使用
|
||||
* @param username
|
||||
* @return
|
||||
* @throws UsernameNotFoundException
|
||||
*/
|
||||
public UserDetails loadUserByUsernameUniversalPassword(String username) throws UsernameNotFoundException {
|
||||
SysUser user = userService.selectUserByUserName(username);
|
||||
if (StringUtils.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在." , username);
|
||||
throw new ServiceException(MessageUtils.message("user.not.exists"));
|
||||
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
|
||||
log.info("登录用户:{} 已被删除." , username);
|
||||
throw new ServiceException(MessageUtils.message("user.password.delete"));
|
||||
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用." , username);
|
||||
throw new ServiceException(MessageUtils.message("user.blocked"));
|
||||
}
|
||||
|
||||
passwordService.validateUniversalPassword(user);
|
||||
|
||||
return createLoginUser(user);
|
||||
}
|
||||
|
||||
public UserDetails createLoginUser(SysUser user) {
|
||||
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user