liujie
5 天以前 08c181231519361f1b7a533d1c7d8b00b776e787
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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));
            String afterData = afterData(request, beforeData);
            AuditDataFormatter.ChangeSet changes = AuditDataFormatter.changes(beforeData, afterData);
            audit.put("beforeData", changes.getBefore());
            audit.put("afterData", changes.getAfter());
            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 (Throwable 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; }
    }
}