first commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(), "密码已过期,请修改密码");
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user