first commit

This commit is contained in:
zc
2026-02-27 10:16:46 +08:00
commit 0ee56404c2
705 changed files with 47675 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
package top.ysoft.admin.config.log;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.ysoft.admin.system.mapper.LogMapper;
import top.ysoft.admin.system.service.UserService;
import top.continew.starter.log.annotation.ConditionalOnEnabledLog;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.web.autoconfigure.trace.TraceProperties;
/**
* 日志配置
*
* @author Charles7c
* @since 2022/12/24 23:15
*/
@Configuration
@ConditionalOnEnabledLog
public class LogConfiguration {
/**
* 日志持久层接口本地实现类
*/
@Bean
public LogDao logDao(UserService userService, LogMapper logMapper, TraceProperties traceProperties) {
return new LogDaoLocalImpl(userService, logMapper, traceProperties);
}
}

View File

@@ -0,0 +1,163 @@
package top.ysoft.admin.config.log;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import top.ysoft.admin.auth.enums.AuthTypeEnum;
import top.ysoft.admin.auth.model.req.*;
import top.ysoft.admin.common.constant.SysConstants;
import top.ysoft.admin.system.enums.LogStatusEnum;
import top.ysoft.admin.system.mapper.LogMapper;
import top.ysoft.admin.system.model.entity.LogDO;
import top.ysoft.admin.system.service.UserService;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.core.util.StrUtils;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.model.LogRecord;
import top.continew.starter.log.model.LogRequest;
import top.continew.starter.log.model.LogResponse;
import top.continew.starter.web.autoconfigure.trace.TraceProperties;
import top.continew.starter.web.model.R;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
import java.util.Set;
/**
* 日志持久层接口本地实现类
*
* @author Charles7c
* @since 2023/12/16 23:55
*/
@RequiredArgsConstructor
public class LogDaoLocalImpl implements LogDao {
private final UserService userService;
private final LogMapper logMapper;
private final TraceProperties traceProperties;
@Async
@Override
public void add(LogRecord logRecord) {
LogDO logDO = new LogDO();
// 设置请求信息
LogRequest logRequest = logRecord.getRequest();
this.setRequest(logDO, logRequest);
// 设置响应信息
LogResponse logResponse = logRecord.getResponse();
this.setResponse(logDO, logResponse);
// 设置基本信息
logDO.setDescription(logRecord.getDescription());
logDO.setModule(StrUtils.blankToDefault(logRecord.getModule(), null, m -> m
.replace("API", StringConstants.EMPTY)
.trim()));
logDO.setTimeTaken(logRecord.getTimeTaken().toMillis());
logDO.setCreateTime(LocalDateTime.ofInstant(logRecord.getTimestamp(), ZoneId.systemDefault()));
// 设置操作人
this.setCreateUser(logDO, logRequest, logResponse);
logMapper.insert(logDO);
}
/**
* 设置请求信息
*
* @param logDO 日志信息
* @param logRequest 请求信息
*/
private void setRequest(LogDO logDO, LogRequest logRequest) {
logDO.setRequestMethod(logRequest.getMethod());
logDO.setRequestUrl(logRequest.getUrl().toString());
logDO.setRequestHeaders(JSONUtil.toJsonStr(logRequest.getHeaders()));
logDO.setRequestBody(logRequest.getBody());
logDO.setIp(logRequest.getIp());
logDO.setAddress(logRequest.getAddress());
logDO.setBrowser(logRequest.getBrowser());
logDO.setOs(StrUtil.subBefore(logRequest.getOs(), " or", false));
}
/**
* 设置响应信息
*
* @param logDO 日志信息
* @param logResponse 响应信息
*/
private void setResponse(LogDO logDO, LogResponse logResponse) {
Map<String, String> responseHeaders = logResponse.getHeaders();
logDO.setResponseHeaders(JSONUtil.toJsonStr(responseHeaders));
logDO.setTraceId(responseHeaders.get(traceProperties.getTraceIdName()));
String responseBody = logResponse.getBody();
logDO.setResponseBody(responseBody);
// 状态
Integer statusCode = logResponse.getStatus();
logDO.setStatusCode(statusCode);
logDO.setStatus(statusCode >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : LogStatusEnum.SUCCESS);
if (StrUtil.isNotBlank(responseBody)) {
R result = JSONUtil.toBean(responseBody, R.class);
if (!result.isSuccess()) {
logDO.setStatus(LogStatusEnum.FAILURE);
logDO.setErrorMsg(result.getMsg());
}
}
}
/**
* 设置操作人
*
* @param logDO 日志信息
* @param logRequest 请求信息
* @param logResponse 响应信息
*/
private void setCreateUser(LogDO logDO, LogRequest logRequest, LogResponse logResponse) {
String requestUri = URLUtil.getPath(logDO.getRequestUrl());
// 解析退出接口信息
String responseBody = logResponse.getBody();
if (requestUri.startsWith(SysConstants.LOGOUT_URI) && StrUtil.isNotBlank(responseBody)) {
R result = JSONUtil.toBean(responseBody, R.class);
logDO.setCreateUser(Convert.toLong(result.getData(), null));
return;
}
// 解析登录接口信息
if (requestUri.startsWith(SysConstants.LOGIN_URI) && LogStatusEnum.SUCCESS.equals(logDO.getStatus())) {
String requestBody = logRequest.getBody();
logDO.setDescription(JSONUtil.toBean(requestBody, LoginReq.class).getAuthType().getDescription() + "登录");
// 解析账号登录用户为操作人
if (requestBody.contains(AuthTypeEnum.ACCOUNT.getValue())) {
AccountLoginReq authReq = JSONUtil.toBean(requestBody, AccountLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByUsername(authReq.getUsername())
.getId()));
return;
} else if (requestBody.contains(AuthTypeEnum.EMAIL.getValue())) {
EmailLoginReq authReq = JSONUtil.toBean(requestBody, EmailLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByEmail(authReq.getEmail()).getId()));
return;
} else if (requestBody.contains(AuthTypeEnum.PHONE.getValue())) {
PhoneLoginReq authReq = JSONUtil.toBean(requestBody, PhoneLoginReq.class);
logDO.setCreateUser(ExceptionUtils.exToNull(() -> userService.getByPhone(authReq.getPhone()).getId()));
return;
}
}
// 解析 Token 信息
Map<String, String> requestHeaders = logRequest.getHeaders();
String headerName = HttpHeaders.AUTHORIZATION;
boolean isContainsAuthHeader = CollUtil.containsAny(requestHeaders.keySet(), Set.of(headerName, headerName
.toLowerCase()));
if (MapUtil.isNotEmpty(requestHeaders) && isContainsAuthHeader) {
String authorization = requestHeaders.getOrDefault(headerName, requestHeaders.get(headerName
.toLowerCase()));
String token = authorization.replace(SaManager.getConfig()
.getTokenPrefix() + StringConstants.SPACE, StringConstants.EMPTY);
logDO.setCreateUser(Convert.toLong(StpUtil.getLoginIdByToken(token)));
}
}
}

View File

@@ -0,0 +1,22 @@
package top.ysoft.admin.config.satoken;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 密码配置属性
*
* @author Charles7c
* @since 2024/6/15 22:15
*/
@Data
@Component
@ConfigurationProperties(prefix = "auth.password")
public class LoginPasswordProperties {
/**
* 排除(放行)路径配置
*/
private String[] excludes = new String[0];
}

View File

@@ -0,0 +1,46 @@
package top.ysoft.admin.config.satoken;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import top.ysoft.admin.common.context.UserContextHolder;
/**
* Sa-Token 扩展拦截器
*
* @author Charles7c
* @since 2024/10/10 20:25
*/
public class SaExtensionInterceptor extends SaInterceptor {
public SaExtensionInterceptor(SaParamFunction<Object> auth) {
super(auth);
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
boolean flag = super.preHandle(request, response, handler);
if (flag && StpUtil.isLogin()) {
UserContextHolder.getContext();
UserContextHolder.getExtraContext();
}
return flag;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception e) throws Exception {
try {
super.afterCompletion(request, response, handler, e);
} finally {
UserContextHolder.clearContext();
}
}
}

View File

@@ -0,0 +1,77 @@
package top.ysoft.admin.config.satoken;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.sign.SaSignUtil;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.ysoft.admin.common.context.UserContext;
import top.ysoft.admin.common.context.UserContextHolder;
import top.ysoft.admin.open.sign.OpenApiSignTemplate;
import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.validation.CheckUtils;
import java.util.List;
/**
* Sa-Token 配置
*
* @author Charles7c
* @author chengzi
* @since 2022/12/19 22:13
*/
@Configuration
@RequiredArgsConstructor
public class SaTokenConfiguration {
private final SaTokenExtensionProperties properties;
private final LoginPasswordProperties loginPasswordProperties;
private final OpenApiSignTemplate signTemplate;
/**
* Sa-Token 权限认证配置
*/
@Bean
public StpInterface stpInterface() {
return new SaTokenPermissionImpl();
}
/**
* SaToken 拦截器配置
*/
@Bean
public SaInterceptor saInterceptor() {
SaManager.setSaSignTemplate(signTemplate);
return new SaExtensionInterceptor(handle -> SaRouter.match(StringConstants.PATH_PATTERN)
.notMatch(properties.getSecurity().getExcludes())
.check(r -> {
// 如果包含 sign进行 API 接口参数签名验证
SaRequest saRequest = SaHolder.getRequest();
List<String> paramNames = saRequest.getParamNames();
if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) {
try {
SaSignUtil.checkRequest(saRequest);
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
return;
}
// 不包含 sign 参数,进行普通登录验证
StpUtil.checkLogin();
if (SaRouter.isMatchCurrURI(loginPasswordProperties.getExcludes())) {
return;
}
UserContext userContext = UserContextHolder.getContext();
CheckUtils.throwIf(userContext.isPasswordExpired(), "密码已过期,请修改密码");
}));
}
}

View File

@@ -0,0 +1,29 @@
package top.ysoft.admin.config.satoken;
import cn.dev33.satoken.stp.StpInterface;
import top.ysoft.admin.common.context.UserContext;
import top.ysoft.admin.common.context.UserContextHolder;
import java.util.ArrayList;
import java.util.List;
/**
* Sa-Token 权限认证实现
*
* @author Charles7c
* @since 2023/3/1 22:28
*/
public class SaTokenPermissionImpl implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
UserContext userContext = UserContextHolder.getContext();
return new ArrayList<>(userContext.getPermissions());
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
UserContext userContext = UserContextHolder.getContext();
return new ArrayList<>(userContext.getRoleCodes());
}
}