|
@@ -48,7 +48,10 @@ import java.util.List;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 平台操作记录切面
|
|
|
|
|
|
|
+ * 平台操作记录切面。
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 通过 {@link PlatformOperationLog} 注解拦截业务方法,记录操作内容、入参、操作者与请求信息。
|
|
|
|
|
+ * </p>
|
|
|
*
|
|
*
|
|
|
* @author system
|
|
* @author system
|
|
|
* @since 2025-01-XX
|
|
* @since 2025-01-XX
|
|
@@ -70,15 +73,26 @@ public class PlatformOperationLogAspect {
|
|
|
private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
|
private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
|
|
private final TemplateParserContext templateParserContext = new TemplateParserContext("#{", "}");
|
|
private final TemplateParserContext templateParserContext = new TemplateParserContext("#{", "}");
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 操作记录切点:拦截带 {@link PlatformOperationLog} 注解的方法。
|
|
|
|
|
+ */
|
|
|
@Pointcut("@annotation(shop.alien.storeplatform.annotation.PlatformOperationLog)")
|
|
@Pointcut("@annotation(shop.alien.storeplatform.annotation.PlatformOperationLog)")
|
|
|
public void operationLogPointcut() {
|
|
public void operationLogPointcut() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 环绕通知:先执行业务逻辑,再生成并保存操作日志。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param joinPoint 连接点
|
|
|
|
|
+ * @return 业务方法返回值
|
|
|
|
|
+ * @throws Throwable 业务方法执行异常
|
|
|
|
|
+ */
|
|
|
@Around("operationLogPointcut()")
|
|
@Around("operationLogPointcut()")
|
|
|
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
|
|
Method method = signature.getMethod();
|
|
Method method = signature.getMethod();
|
|
|
PlatformOperationLog annotation = method.getAnnotation(PlatformOperationLog.class);
|
|
PlatformOperationLog annotation = method.getAnnotation(PlatformOperationLog.class);
|
|
|
|
|
+ // 变更前快照:用于“修改子账号”差异日志
|
|
|
SubAccountSnapshot beforeSnapshot = buildSubAccountSnapshot(annotation, joinPoint.getArgs());
|
|
SubAccountSnapshot beforeSnapshot = buildSubAccountSnapshot(annotation, joinPoint.getArgs());
|
|
|
|
|
|
|
|
Object result;
|
|
Object result;
|
|
@@ -100,6 +114,15 @@ public class PlatformOperationLogAspect {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 判断是否需要记录操作日志。
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 返回值为 {@link R} 时,只有成功结果才记录。
|
|
|
|
|
+ * </p>
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param result 业务方法返回值
|
|
|
|
|
+ * @return 是否记录日志
|
|
|
|
|
+ */
|
|
|
private boolean shouldRecord(Object result) {
|
|
private boolean shouldRecord(Object result) {
|
|
|
if (result instanceof R) {
|
|
if (result instanceof R) {
|
|
|
return R.isSuccess((R<?>) result);
|
|
return R.isSuccess((R<?>) result);
|
|
@@ -107,6 +130,15 @@ public class PlatformOperationLogAspect {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建操作日志实体。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param joinPoint 连接点
|
|
|
|
|
+ * @param annotation 注解信息
|
|
|
|
|
+ * @param result 业务方法返回值
|
|
|
|
|
+ * @param beforeSnapshot 子账号变更前快照
|
|
|
|
|
+ * @return 操作日志实体
|
|
|
|
|
+ */
|
|
|
private StorePlatformOperationLog buildLogRecord(ProceedingJoinPoint joinPoint, PlatformOperationLog annotation, Object result,
|
|
private StorePlatformOperationLog buildLogRecord(ProceedingJoinPoint joinPoint, PlatformOperationLog annotation, Object result,
|
|
|
SubAccountSnapshot beforeSnapshot) {
|
|
SubAccountSnapshot beforeSnapshot) {
|
|
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
|
@@ -114,6 +146,7 @@ public class PlatformOperationLogAspect {
|
|
|
Object[] args = joinPoint.getArgs();
|
|
Object[] args = joinPoint.getArgs();
|
|
|
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
|
|
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
|
|
|
|
|
|
|
|
|
|
+ // SpEL 上下文:支持 #p0/#arg0/参数名/返回值
|
|
|
EvaluationContext context = new StandardEvaluationContext();
|
|
EvaluationContext context = new StandardEvaluationContext();
|
|
|
if (args != null) {
|
|
if (args != null) {
|
|
|
for (int i = 0; i < args.length; i++) {
|
|
for (int i = 0; i < args.length; i++) {
|
|
@@ -140,6 +173,11 @@ public class PlatformOperationLogAspect {
|
|
|
return logRecord;
|
|
return logRecord;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 填充操作者信息。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param logRecord 操作日志实体
|
|
|
|
|
+ */
|
|
|
private void fillOperatorInfo(StorePlatformOperationLog logRecord) {
|
|
private void fillOperatorInfo(StorePlatformOperationLog logRecord) {
|
|
|
try {
|
|
try {
|
|
|
JSONObject userInfo = LoginUserUtil.getCurrentUserInfo();
|
|
JSONObject userInfo = LoginUserUtil.getCurrentUserInfo();
|
|
@@ -167,6 +205,11 @@ public class PlatformOperationLogAspect {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 填充请求信息。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param logRecord 操作日志实体
|
|
|
|
|
+ */
|
|
|
private void fillRequestInfo(StorePlatformOperationLog logRecord) {
|
|
private void fillRequestInfo(StorePlatformOperationLog logRecord) {
|
|
|
try {
|
|
try {
|
|
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
@@ -174,6 +217,7 @@ public class PlatformOperationLogAspect {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
HttpServletRequest request = attributes.getRequest();
|
|
HttpServletRequest request = attributes.getRequest();
|
|
|
|
|
+ // 记录请求方法/路径/IP
|
|
|
logRecord.setRequestMethod(request.getMethod());
|
|
logRecord.setRequestMethod(request.getMethod());
|
|
|
logRecord.setRequestPath(request.getRequestURI());
|
|
logRecord.setRequestPath(request.getRequestURI());
|
|
|
logRecord.setIpAddress(getIpAddress(request));
|
|
logRecord.setIpAddress(getIpAddress(request));
|
|
@@ -182,6 +226,12 @@ public class PlatformOperationLogAspect {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取请求 IP 地址,优先从代理头读取。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param request HttpServletRequest
|
|
|
|
|
+ * @return IP 地址
|
|
|
|
|
+ */
|
|
|
private String getIpAddress(HttpServletRequest request) {
|
|
private String getIpAddress(HttpServletRequest request) {
|
|
|
String ip = request.getHeader("X-Forwarded-For");
|
|
String ip = request.getHeader("X-Forwarded-For");
|
|
|
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
@@ -202,8 +252,21 @@ public class PlatformOperationLogAspect {
|
|
|
return ip;
|
|
return ip;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析日志内容。
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 优先处理账号相关自定义内容,其次为角色统一模板,最后回退到注解 SpEL。
|
|
|
|
|
+ * </p>
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param annotation 注解信息
|
|
|
|
|
+ * @param context SpEL 上下文
|
|
|
|
|
+ * @param args 方法参数
|
|
|
|
|
+ * @param beforeSnapshot 子账号变更前快照
|
|
|
|
|
+ * @return 日志内容
|
|
|
|
|
+ */
|
|
|
private String resolveContent(PlatformOperationLog annotation, EvaluationContext context, Object[] args,
|
|
private String resolveContent(PlatformOperationLog annotation, EvaluationContext context, Object[] args,
|
|
|
SubAccountSnapshot beforeSnapshot) {
|
|
SubAccountSnapshot beforeSnapshot) {
|
|
|
|
|
+ // 先处理自定义的账号相关日志模板
|
|
|
if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("移除角色")) {
|
|
if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("移除角色")) {
|
|
|
String content = buildAccountRoleRemoveContent(args);
|
|
String content = buildAccountRoleRemoveContent(args);
|
|
|
if (StringUtils.hasText(content)) {
|
|
if (StringUtils.hasText(content)) {
|
|
@@ -222,6 +285,7 @@ public class PlatformOperationLogAspect {
|
|
|
return content;
|
|
return content;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ // 角色相关日志的统一格式
|
|
|
if ("角色操作记录".equals(annotation.module())) {
|
|
if ("角色操作记录".equals(annotation.module())) {
|
|
|
return buildRoleOperationContent(annotation.type(), args);
|
|
return buildRoleOperationContent(annotation.type(), args);
|
|
|
}
|
|
}
|
|
@@ -234,6 +298,13 @@ public class PlatformOperationLogAspect {
|
|
|
return annotation.type();
|
|
return annotation.type();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建子账号变更前快照,仅用于“修改子账号”日志差异对比。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param annotation 注解信息
|
|
|
|
|
+ * @param args 方法参数
|
|
|
|
|
+ * @return 变更前快照
|
|
|
|
|
+ */
|
|
|
private SubAccountSnapshot buildSubAccountSnapshot(PlatformOperationLog annotation, Object[] args) {
|
|
private SubAccountSnapshot buildSubAccountSnapshot(PlatformOperationLog annotation, Object[] args) {
|
|
|
if (annotation == null || args == null || args.length == 0) {
|
|
if (annotation == null || args == null || args.length == 0) {
|
|
|
return null;
|
|
return null;
|
|
@@ -272,6 +343,13 @@ public class PlatformOperationLogAspect {
|
|
|
return new SubAccountSnapshot(phone, accountName, roleId);
|
|
return new SubAccountSnapshot(phone, accountName, roleId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建“修改子账号”日志内容,仅记录变更字段。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param args 方法参数
|
|
|
|
|
+ * @param beforeSnapshot 变更前快照
|
|
|
|
|
+ * @return 日志内容
|
|
|
|
|
+ */
|
|
|
private String buildUpdateSubAccountContent(Object[] args, SubAccountSnapshot beforeSnapshot) {
|
|
private String buildUpdateSubAccountContent(Object[] args, SubAccountSnapshot beforeSnapshot) {
|
|
|
if (beforeSnapshot == null || args == null || args.length == 0) {
|
|
if (beforeSnapshot == null || args == null || args.length == 0) {
|
|
|
return null;
|
|
return null;
|
|
@@ -300,6 +378,12 @@ public class PlatformOperationLogAspect {
|
|
|
return "修改子账号," + changed;
|
|
return "修改子账号," + changed;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建“新增子账号”日志内容。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param args 方法参数
|
|
|
|
|
+ * @return 日志内容
|
|
|
|
|
+ */
|
|
|
private String buildCreateSubAccountContent(Object[] args) {
|
|
private String buildCreateSubAccountContent(Object[] args) {
|
|
|
if (args == null || args.length == 0) {
|
|
if (args == null || args.length == 0) {
|
|
|
return null;
|
|
return null;
|
|
@@ -323,6 +407,14 @@ public class PlatformOperationLogAspect {
|
|
|
return content.toString();
|
|
return content.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 追加字段变更描述。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param builder 内容构建器
|
|
|
|
|
+ * @param label 字段中文名
|
|
|
|
|
+ * @param oldValue 旧值
|
|
|
|
|
+ * @param newValue 新值
|
|
|
|
|
+ */
|
|
|
private void appendChange(StringBuilder builder, String label, String oldValue, String newValue) {
|
|
private void appendChange(StringBuilder builder, String label, String oldValue, String newValue) {
|
|
|
if (builder.length() > 0) {
|
|
if (builder.length() > 0) {
|
|
|
builder.append(";");
|
|
builder.append(";");
|
|
@@ -334,6 +426,9 @@ public class PlatformOperationLogAspect {
|
|
|
.append(StringUtils.hasText(newValue) ? newValue : "空");
|
|
.append(StringUtils.hasText(newValue) ? newValue : "空");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 子账号变更前快照。
|
|
|
|
|
+ */
|
|
|
private static class SubAccountSnapshot {
|
|
private static class SubAccountSnapshot {
|
|
|
private final String phone;
|
|
private final String phone;
|
|
|
private final String accountName;
|
|
private final String accountName;
|
|
@@ -346,6 +441,12 @@ public class PlatformOperationLogAspect {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建“移除角色”日志内容。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param args 方法参数
|
|
|
|
|
+ * @return 日志内容
|
|
|
|
|
+ */
|
|
|
private String buildAccountRoleRemoveContent(Object[] args) {
|
|
private String buildAccountRoleRemoveContent(Object[] args) {
|
|
|
if (args == null || args.length < 2) {
|
|
if (args == null || args.length < 2) {
|
|
|
return null;
|
|
return null;
|
|
@@ -367,6 +468,12 @@ public class PlatformOperationLogAspect {
|
|
|
return String.format("移除用户%s的%s角色", accountName, roleName);
|
|
return String.format("移除用户%s的%s角色", accountName, roleName);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析账号名称,允许读取已删除用户。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param userId 用户ID
|
|
|
|
|
+ * @return 账号名称
|
|
|
|
|
+ */
|
|
|
private String resolveAccountName(Integer userId) {
|
|
private String resolveAccountName(Integer userId) {
|
|
|
StoreUser storeUser = storeUserMapper.getUserIncludeDeleted(userId);
|
|
StoreUser storeUser = storeUserMapper.getUserIncludeDeleted(userId);
|
|
|
if (storeUser == null) {
|
|
if (storeUser == null) {
|
|
@@ -382,6 +489,12 @@ public class PlatformOperationLogAspect {
|
|
|
return StringUtils.hasText(name) ? name : "未知";
|
|
return StringUtils.hasText(name) ? name : "未知";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析角色名称。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param roleId 角色ID
|
|
|
|
|
+ * @return 角色名称
|
|
|
|
|
+ */
|
|
|
private String resolveRoleName(Long roleId) {
|
|
private String resolveRoleName(Long roleId) {
|
|
|
String roleName = storePlatformRoleQueryService.getRoleNameById(roleId);
|
|
String roleName = storePlatformRoleQueryService.getRoleNameById(roleId);
|
|
|
if (!StringUtils.hasText(roleName)) {
|
|
if (!StringUtils.hasText(roleName)) {
|
|
@@ -390,6 +503,13 @@ public class PlatformOperationLogAspect {
|
|
|
return roleName;
|
|
return roleName;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建操作入参 JSON 字符串。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param parameterNames 参数名列表
|
|
|
|
|
+ * @param args 参数值列表
|
|
|
|
|
+ * @return JSON 字符串
|
|
|
|
|
+ */
|
|
|
private String buildParamsJson(String[] parameterNames, Object[] args) {
|
|
private String buildParamsJson(String[] parameterNames, Object[] args) {
|
|
|
try {
|
|
try {
|
|
|
if (args == null || args.length == 0) {
|
|
if (args == null || args.length == 0) {
|