refactor(common): 操作日志采集与落库解耦为端口-适配器
common 层日志切面改为只依赖 framework 层定义的 OperationLogPort 端口, 日志采集逻辑从 common.aspect.LogAspect 迁移至 framework.web.log.OperationLogAspect, 由 system.adapter.log.OperationLogAdapter 实现端口落库 sys_log 表, 通过 LogConverter 完成载荷到实体的转换,消除 common 对 system 的反向依赖。 同步将 system.security.adapter 下的适配器迁移至 system.adapter.security, 保持 system.adapter.<业务域> 的目录组织一致。 版本 4.6.0 -> 4.7.0
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>youlai-boot</artifactId>
|
||||
<version>4.6.0</version>
|
||||
<version>4.7.0</version>
|
||||
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
|
||||
|
||||
<parent>
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.Set;
|
||||
* 由 system 模块提供适配器实现,framework 层通过此接口获取角色权限集合,
|
||||
* 不直接依赖 system 模块的 {@code RoleMenuService}。
|
||||
*
|
||||
* @see com.youlai.boot.system.security.adapter.PermissionAdapter
|
||||
* @see com.youlai.boot.system.adapter.security.PermissionAdapter
|
||||
*/
|
||||
public interface PermissionPort {
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.youlai.boot.framework.security.model.SecurityUser;
|
||||
* 由 system 模块提供适配器实现,framework 层通过此接口获取认证数据,
|
||||
* 不直接依赖 system 模块的 {@code UserService} / {@code UserSocialService}。
|
||||
*
|
||||
* @see com.youlai.boot.system.security.adapter.UserAuthenticationAdapter
|
||||
* @see com.youlai.boot.system.adapter.security.UserAuthenticationAdapter
|
||||
*/
|
||||
public interface UserAuthenticationPort {
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.youlai.boot.framework.web.log;
|
||||
|
||||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 操作日志通用载荷
|
||||
* <p>由操作日志切面采集,经 {@link OperationLogPort} 交给各业务模块落库</p>
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public record OperationLog(
|
||||
LogModuleEnum module,
|
||||
ActionTypeEnum actionType,
|
||||
String title,
|
||||
String content,
|
||||
Long operatorId,
|
||||
String operatorName,
|
||||
String requestUri,
|
||||
String requestMethod,
|
||||
String ip,
|
||||
String province,
|
||||
String city,
|
||||
String device,
|
||||
String os,
|
||||
String browser,
|
||||
Integer status,
|
||||
String errorMsg,
|
||||
Integer executionTime,
|
||||
LocalDateTime createTime
|
||||
) {
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.common.aspect;
|
||||
package com.youlai.boot.framework.web.log;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
@@ -8,8 +8,6 @@ import com.youlai.boot.common.enums.ActionTypeEnum;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.model.entity.SysLog;
|
||||
import com.youlai.boot.system.service.LogService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
@@ -27,7 +25,7 @@ import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 操作日志切面
|
||||
* <p>@Log 标注的方法执行后异步写入 sys_log 表</p>
|
||||
* <p>@Log 标注的方法执行后异步采集操作日志,通过 {@link OperationLogPort} 落库</p>
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 0.0.1
|
||||
@@ -35,18 +33,18 @@ import java.util.concurrent.Executor;
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LogAspect {
|
||||
public class OperationLogAspect {
|
||||
|
||||
private static final int MAX_ERROR_MSG_LENGTH = 1000;
|
||||
|
||||
private final LogService logService;
|
||||
private final OperationLogPort operationLogPort;
|
||||
private final Executor operationLogExecutor;
|
||||
|
||||
public LogAspect(
|
||||
LogService logService,
|
||||
public OperationLogAspect(
|
||||
OperationLogPort operationLogPort,
|
||||
@Qualifier("operationLogExecutor") Executor operationLogExecutor
|
||||
) {
|
||||
this.logService = logService;
|
||||
this.operationLogPort = operationLogPort;
|
||||
this.operationLogExecutor = operationLogExecutor;
|
||||
}
|
||||
|
||||
@@ -69,13 +67,13 @@ public class LogAspect {
|
||||
} finally {
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
try {
|
||||
SysLog entity = buildEntity(logAnnotation, elapsed, failure, userId, username);
|
||||
if (entity != null) {
|
||||
OperationLog operationLog = buildOperationLog(logAnnotation, elapsed, failure, userId, username);
|
||||
if (operationLog != null) {
|
||||
operationLogExecutor.execute(() -> {
|
||||
try {
|
||||
logService.save(entity);
|
||||
operationLogPort.record(operationLog);
|
||||
} catch (Exception e) {
|
||||
log.error("保存操作日志失败 title={} uri={}", entity.getTitle(), entity.getRequestUri(), e);
|
||||
log.error("保存操作日志失败 title={} uri={}", operationLog.title(), operationLog.requestUri(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -85,7 +83,7 @@ public class LogAspect {
|
||||
}
|
||||
}
|
||||
|
||||
private SysLog buildEntity(Log ann, long elapsed, Throwable failure, Long userId, String username) {
|
||||
private OperationLog buildOperationLog(Log ann, long elapsed, Throwable failure, Long userId, String username) {
|
||||
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attrs == null) return null;
|
||||
|
||||
@@ -96,29 +94,28 @@ public class LogAspect {
|
||||
LogModuleEnum module = ann.module();
|
||||
ActionTypeEnum action = ann.value();
|
||||
|
||||
SysLog entity = new SysLog();
|
||||
entity.setModule(module);
|
||||
entity.setActionType(action);
|
||||
entity.setTitle(StrUtil.blankToDefault(ann.title(), module.getLabel() + "-" + action.getLabel()));
|
||||
entity.setContent(ann.content());
|
||||
entity.setOperatorId(userId);
|
||||
entity.setOperatorName(username);
|
||||
entity.setRequestUri(req.getRequestURI());
|
||||
entity.setRequestMethod(req.getMethod());
|
||||
entity.setIp(ip);
|
||||
entity.setDevice(getDevice(ua));
|
||||
entity.setOs(getOs(ua));
|
||||
entity.setBrowser(getBrowser(ua));
|
||||
entity.setStatus(failure == null ? 1 : 0);
|
||||
entity.setErrorMsg(failure == null ? null : truncate(failure.getMessage(), MAX_ERROR_MSG_LENGTH));
|
||||
entity.setExecutionTime((int) Math.min(elapsed, Integer.MAX_VALUE));
|
||||
entity.setCreateTime(LocalDateTime.now());
|
||||
|
||||
IpRegion region = parseRegion(ip);
|
||||
entity.setProvince(region.province);
|
||||
entity.setCity(region.city);
|
||||
|
||||
return entity;
|
||||
return new OperationLog(
|
||||
module,
|
||||
action,
|
||||
StrUtil.blankToDefault(ann.title(), module.getLabel() + "-" + action.getLabel()),
|
||||
ann.content(),
|
||||
userId,
|
||||
username,
|
||||
req.getRequestURI(),
|
||||
req.getMethod(),
|
||||
ip,
|
||||
region.province(),
|
||||
region.city(),
|
||||
getDevice(ua),
|
||||
getOs(ua),
|
||||
getBrowser(ua),
|
||||
failure == null ? 1 : 0,
|
||||
failure == null ? null : truncate(failure.getMessage(), MAX_ERROR_MSG_LENGTH),
|
||||
(int) Math.min(elapsed, Integer.MAX_VALUE),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
}
|
||||
|
||||
private IpRegion parseRegion(String ip) {
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.youlai.boot.framework.web.log;
|
||||
|
||||
/**
|
||||
* 操作日志落库端口
|
||||
* <p>由各业务模块实现,framework 层通过此接口写入操作日志,不依赖具体落库实现。
|
||||
* 如 system 模块的 {@code OperationLogAdapter} 落库 sys_log 表。</p>
|
||||
*
|
||||
* @see com.youlai.boot.system.adapter.log.OperationLogAdapter
|
||||
* @author Ray.Hao
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public interface OperationLogPort {
|
||||
|
||||
/**
|
||||
* 记录操作日志
|
||||
*
|
||||
* @param operationLog 操作日志载荷
|
||||
*/
|
||||
void record(OperationLog operationLog);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.youlai.boot.system.adapter.log;
|
||||
|
||||
import com.youlai.boot.framework.web.log.OperationLog;
|
||||
import com.youlai.boot.framework.web.log.OperationLogPort;
|
||||
import com.youlai.boot.system.converter.LogConverter;
|
||||
import com.youlai.boot.system.model.entity.SysLog;
|
||||
import com.youlai.boot.system.service.LogService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 操作日志适配器
|
||||
* <p>实现 framework 层的 {@link OperationLogPort},将切面采集的操作日志落库 sys_log 表</p>
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 0.0.1
|
||||
* @see OperationLogPort
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OperationLogAdapter implements OperationLogPort {
|
||||
|
||||
private final LogService logService;
|
||||
private final LogConverter logConverter;
|
||||
|
||||
/**
|
||||
* 记录操作日志
|
||||
* <p>通过 {@link LogConverter} 将操作日志载荷转换为 {@link SysLog} 后落库</p>
|
||||
*
|
||||
* @param operationLog 操作日志载荷
|
||||
*/
|
||||
@Override
|
||||
public void record(OperationLog operationLog) {
|
||||
logService.save(logConverter.toEntity(operationLog));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.system.security.adapter;
|
||||
package com.youlai.boot.system.adapter.security;
|
||||
|
||||
import com.youlai.boot.framework.security.port.PermissionPort;
|
||||
import com.youlai.boot.system.service.RoleMenuService;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.boot.system.security.adapter;
|
||||
package com.youlai.boot.system.adapter.security;
|
||||
|
||||
import com.youlai.boot.common.enums.SocialPlatformEnum;
|
||||
import com.youlai.boot.framework.security.model.SecurityUser;
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.youlai.boot.system.converter;
|
||||
|
||||
import com.youlai.boot.framework.web.log.OperationLog;
|
||||
import com.youlai.boot.system.model.entity.SysLog;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
/**
|
||||
* 系统日志对象转换器
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 0.0.1
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface LogConverter {
|
||||
|
||||
SysLog toEntity(OperationLog operationLog);
|
||||
}
|
||||
Reference in New Issue
Block a user