package com.dsh.guns.core.aop;
|
|
import com.alibaba.fastjson.JSON;
|
import com.dsh.guns.config.UserExt;
|
import com.dsh.guns.core.common.annotion.BussinessLog;
|
import com.dsh.guns.core.support.HttpKit;
|
import com.dsh.guns.modular.system.audit.ManagementAuditWriter;
|
import com.dsh.guns.modular.system.audit.AuditDataFormatter;
|
import com.dsh.guns.modular.system.model.TOperator;
|
import com.dsh.guns.modular.system.model.User;
|
import com.dsh.guns.modular.system.service.TOperatorService;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.reflect.MethodSignature;
|
import org.springframework.stereotype.Component;
|
import org.springframework.ui.Model;
|
|
import javax.annotation.Resource;
|
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletResponse;
|
import javax.servlet.http.HttpServletRequest;
|
import java.lang.reflect.Method;
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.UUID;
|
|
/**
|
* 管理后台的兜底操作审计。旧的 @BussinessLog 仍由 LogAop 记录;这里补齐
|
* 各业务页面未标注注解的新增、修改、删除、审核、状态变更等操作。
|
*/
|
@Aspect
|
@Component
|
public class ManagementOperationAuditAspect {
|
@Resource private ManagementAuditWriter auditWriter;
|
@Resource private TOperatorService operatorService;
|
|
@Around("execution(public * com.dsh.guns.modular.system.controller..*.*(..))")
|
public Object recordOperation(ProceedingJoinPoint point) throws Throwable {
|
HttpServletRequest request = currentRequest();
|
Method method = ((MethodSignature) point.getSignature()).getMethod();
|
boolean shouldRecord = request != null && method.getAnnotation(BussinessLog.class) == null
|
&& shouldAudit(request.getMethod(), request.getRequestURI());
|
String beforeData = shouldRecord ? beforeData(request) : null;
|
try {
|
Object result = point.proceed();
|
if (shouldRecord) append(point, request, "SUCCESS", null, beforeData);
|
return result;
|
} catch (Throwable ex) {
|
if (shouldRecord) append(point, request, "FAIL", ex.getMessage(), beforeData);
|
throw ex;
|
}
|
}
|
|
private void append(ProceedingJoinPoint point, HttpServletRequest request, String result, String error, String beforeData) {
|
try {
|
User user = UserExt.getUser();
|
if (user == null) return;
|
String uri = request.getRequestURI();
|
Map<String, Object> audit = new LinkedHashMap<String, Object>();
|
audit.put("traceId", UUID.randomUUID().toString().replace("-", ""));
|
audit.put("sourceService", "mb-cloud-management");
|
audit.put("sourceType", "HTTP");
|
audit.put("operationType", operationType(uri, request.getMethod()));
|
audit.put("riskLevel", riskLevel(uri));
|
audit.put("operationName", operationName(uri, request.getMethod()));
|
audit.put("requestMethod", request.getMethod());
|
audit.put("requestUri", uri);
|
audit.put("requestParams", requestData(request));
|
audit.put("beforeData", beforeData);
|
audit.put("afterData", afterData(request, beforeData));
|
audit.put("userId", user.getId());
|
audit.put("userAccount", user.getAccount());
|
audit.put("clientIp", request.getRemoteAddr());
|
audit.put("result", result);
|
audit.put("errorMessage", mask(error));
|
audit.put("createdAt", new Date());
|
auditWriter.append(audit);
|
} catch (Exception ignored) {
|
// 审计链路不能影响后台业务。
|
}
|
}
|
|
private boolean shouldAudit(String method, String uri) {
|
String value = uri == null ? "" : uri.toLowerCase();
|
if (value.startsWith("/accountauditlog") || value.startsWith("/system/sensitive")) return false;
|
if ("PUT".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) return true;
|
return "POST".equalsIgnoreCase(method) && value.matches(".*(add|create|save|insert|edit|update|modify|delete|remove|del|change|set|reset|freeze|unfreeze|enable|disable|commit|submit|upload|audit|approve|reject|refund|withdraw|payment).*" );
|
}
|
|
private String operationType(String uri, String method) {
|
String value = uri.toLowerCase();
|
if ("DELETE".equalsIgnoreCase(method) || value.matches(".*(delete|remove|del).*")) return "DELETE";
|
if (value.matches(".*(add|create|save|insert|commit|submit|upload).*")) return "CREATE";
|
if (value.matches(".*(change|state|status|freeze|unfreeze|enable|disable|audit|approve|reject|refund|withdraw|payment).*")) return "STATUS_CHANGE";
|
return "UPDATE";
|
}
|
|
/** 将管理端接口翻译为日志中可直接阅读的中文说明。 */
|
private String operationName(String uri, String method) {
|
String value = uri == null ? "" : uri.toLowerCase();
|
String action;
|
if ("DELETE".equalsIgnoreCase(method) || value.matches(".*(delete|remove|del).*")) action = "删除";
|
else if (value.matches(".*(add|create|save|insert|commit|submit|upload).*")) action = "新增";
|
else if (value.matches(".*(audit|approve|reject).*")) action = "审核";
|
else if (value.matches(".*(freeze|unfreeze|enable|disable|change|state|status).*")) action = "变更状态";
|
else if (value.matches(".*reset.*")) action = "重置";
|
else action = "修改";
|
return action + resourceName(value) + (resourceName(value).startsWith("接口") ? "(" + uri + ")" : "");
|
}
|
|
private String resourceName(String uri) {
|
String[][] names = {
|
{"operatoruser", "运营商员工"}, {"operator", "运营商"}, {"store", "门店"},
|
{"coursepackage", "课程包"}, {"course", "课程"}, {"coach", "教练"},
|
{"student", "学员"}, {"referee", "裁判"}, {"competition", "赛事"},
|
{"ticket", "门票"}, {"coupon", "优惠券"}, {"goods", "商品"}, {"pointmerchar", "积分商品"},
|
{"site", "场地"}, {"city", "城市"}, {"notice", "公告"},
|
{"question", "题目"}, {"advertisement", "广告"}, {"finance", "财务"},
|
{"vip", "会员"}, {"welfare", "福利"}, {"gate", "闸机"},
|
{"agreement", "协议"}, {"discount", "折扣"}, {"record", "记录"}
|
};
|
for (String[] item : names) if (uri.contains(item[0])) return item[1];
|
return "接口";
|
}
|
|
private String riskLevel(String uri) {
|
return uri.toLowerCase().matches(".*(finance|payment|refund|withdraw|balance).*" ) ? "CRITICAL" : "NORMAL";
|
}
|
|
private String requestData(HttpServletRequest request) {
|
Map<String, Object> data = new LinkedHashMap<String, Object>();
|
Map<String, String[]> parameters = request.getParameterMap();
|
if (parameters != null && !parameters.isEmpty()) data.put("params", parameters);
|
return AuditDataFormatter.format(data);
|
}
|
|
/**
|
* 可按资源逐步扩展真实快照读取。运营商管理是当前明确缺失的场景,
|
* 在执行更新前/后直接从数据库读取,避免把前端提交值误写成“修改前”。
|
*/
|
private String beforeData(HttpServletRequest request) {
|
if (!request.getRequestURI().toLowerCase().contains("/operator/")) return null;
|
Integer id = integerParameter(request, "id", "operatorId", "operatorIdOne");
|
if (id == null) return null;
|
TOperator operator = operatorService.getById(id);
|
return operatorSnapshot(operator);
|
}
|
|
private String afterData(HttpServletRequest request, String beforeData) {
|
if (beforeData != null && request.getRequestURI().toLowerCase().contains("/operator/")) {
|
Integer id = integerParameter(request, "id", "operatorId", "operatorIdOne");
|
TOperator operator = id == null ? null : operatorService.getById(id);
|
return operatorSnapshot(operator);
|
}
|
return requestData(request);
|
}
|
|
private Integer integerParameter(HttpServletRequest request, String... names) {
|
for (String name : names) {
|
try {
|
String value = request.getParameter(name);
|
if (value != null && !value.trim().isEmpty()) return Integer.valueOf(value);
|
} catch (NumberFormatException ignored) {
|
// 尝试下一个可能的主键参数名。
|
}
|
}
|
return null;
|
}
|
|
/** 显式字段快照,避免 ActiveRecord/代理对象序列化不稳定,并确保日志字段全为中文。 */
|
private String operatorSnapshot(TOperator operator) {
|
if (operator == null) return null;
|
Map<String, Object> snapshot = new LinkedHashMap<String, Object>();
|
snapshot.put("运营商ID", operator.getId());
|
snapshot.put("运营商名称", operator.getName());
|
snapshot.put("关联用户ID", operator.getUserId());
|
snapshot.put("运营范围类型", operator.getType());
|
snapshot.put("商户绑定状态", operator.getStatus());
|
snapshot.put("运营商状态", operator.getState());
|
snapshot.put("微信商户号", operator.getMerchantNumber());
|
snapshot.put("支付宝商户号", operator.getMerchantNumberAli());
|
snapshot.put("微信商户全称", operator.getMerchantName());
|
return AuditDataFormatter.format(snapshot);
|
}
|
|
private String mask(String text) {
|
if (text == null) return null;
|
return text.replaceAll("(?i)(\\\"?(password|token|authorization|secret|idcard|identity|bank(card)?|mobile|phone)\\\"?\\s*[:=]\\s*)\\\"[^\\\"]*\\\"", "$1\\\"***\\\"");
|
}
|
|
private HttpServletRequest currentRequest() {
|
try { return HttpKit.getRequest(); } catch (Exception ignored) { return null; }
|
}
|
}
|