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>
|
<groupId>com.youlai</groupId>
|
||||||
<artifactId>youlai-boot</artifactId>
|
<artifactId>youlai-boot</artifactId>
|
||||||
<version>4.6.0</version>
|
<version>4.7.0</version>
|
||||||
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
|
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import java.util.Set;
|
|||||||
* 由 system 模块提供适配器实现,framework 层通过此接口获取角色权限集合,
|
* 由 system 模块提供适配器实现,framework 层通过此接口获取角色权限集合,
|
||||||
* 不直接依赖 system 模块的 {@code RoleMenuService}。
|
* 不直接依赖 system 模块的 {@code RoleMenuService}。
|
||||||
*
|
*
|
||||||
* @see com.youlai.boot.system.security.adapter.PermissionAdapter
|
* @see com.youlai.boot.system.adapter.security.PermissionAdapter
|
||||||
*/
|
*/
|
||||||
public interface PermissionPort {
|
public interface PermissionPort {
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.youlai.boot.framework.security.model.SecurityUser;
|
|||||||
* 由 system 模块提供适配器实现,framework 层通过此接口获取认证数据,
|
* 由 system 模块提供适配器实现,framework 层通过此接口获取认证数据,
|
||||||
* 不直接依赖 system 模块的 {@code UserService} / {@code UserSocialService}。
|
* 不直接依赖 system 模块的 {@code UserService} / {@code UserSocialService}。
|
||||||
*
|
*
|
||||||
* @see com.youlai.boot.system.security.adapter.UserAuthenticationAdapter
|
* @see com.youlai.boot.system.adapter.security.UserAuthenticationAdapter
|
||||||
*/
|
*/
|
||||||
public interface UserAuthenticationPort {
|
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.core.util.StrUtil;
|
||||||
import cn.hutool.http.useragent.UserAgent;
|
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.enums.LogModuleEnum;
|
||||||
import com.youlai.boot.common.util.IPUtils;
|
import com.youlai.boot.common.util.IPUtils;
|
||||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
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 jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
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
|
* @author Ray.Hao
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
@@ -35,18 +33,18 @@ import java.util.concurrent.Executor;
|
|||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LogAspect {
|
public class OperationLogAspect {
|
||||||
|
|
||||||
private static final int MAX_ERROR_MSG_LENGTH = 1000;
|
private static final int MAX_ERROR_MSG_LENGTH = 1000;
|
||||||
|
|
||||||
private final LogService logService;
|
private final OperationLogPort operationLogPort;
|
||||||
private final Executor operationLogExecutor;
|
private final Executor operationLogExecutor;
|
||||||
|
|
||||||
public LogAspect(
|
public OperationLogAspect(
|
||||||
LogService logService,
|
OperationLogPort operationLogPort,
|
||||||
@Qualifier("operationLogExecutor") Executor operationLogExecutor
|
@Qualifier("operationLogExecutor") Executor operationLogExecutor
|
||||||
) {
|
) {
|
||||||
this.logService = logService;
|
this.operationLogPort = operationLogPort;
|
||||||
this.operationLogExecutor = operationLogExecutor;
|
this.operationLogExecutor = operationLogExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,13 +67,13 @@ public class LogAspect {
|
|||||||
} finally {
|
} finally {
|
||||||
long elapsed = System.currentTimeMillis() - start;
|
long elapsed = System.currentTimeMillis() - start;
|
||||||
try {
|
try {
|
||||||
SysLog entity = buildEntity(logAnnotation, elapsed, failure, userId, username);
|
OperationLog operationLog = buildOperationLog(logAnnotation, elapsed, failure, userId, username);
|
||||||
if (entity != null) {
|
if (operationLog != null) {
|
||||||
operationLogExecutor.execute(() -> {
|
operationLogExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
logService.save(entity);
|
operationLogPort.record(operationLog);
|
||||||
} catch (Exception e) {
|
} 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();
|
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
if (attrs == null) return null;
|
if (attrs == null) return null;
|
||||||
|
|
||||||
@@ -96,29 +94,28 @@ public class LogAspect {
|
|||||||
LogModuleEnum module = ann.module();
|
LogModuleEnum module = ann.module();
|
||||||
ActionTypeEnum action = ann.value();
|
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);
|
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) {
|
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.framework.security.port.PermissionPort;
|
||||||
import com.youlai.boot.system.service.RoleMenuService;
|
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.common.enums.SocialPlatformEnum;
|
||||||
import com.youlai.boot.framework.security.model.SecurityUser;
|
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