From bc68f4393cafe39c4fce614c8128936067ea25f7 Mon Sep 17 00:00:00 2001
From: 刘帅 <rr.iii@qq.com>
Date: 星期二, 22 九月 2026 17:18:17 +0800
Subject: [PATCH] bug:登录问题和操作日志

---
 cloud-server-management/src/main/webapp/WEB-INF/view/common/_container.html                                  |    2 
 cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user.html                                   |    2 
 cloud-server-management/src/main/java/com/dsh/guns/core/aop/LogAop.java                                      |  130 +++++++++
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditDataFormatter.java              |  105 ++++++++
 cloud-server-management/src/main/webapp/static/js/common/sensitive-info.js                                   |   13 
 cloud-server-management/src/main/webapp/static/modular/system/user/user.js                                   |   19 -
 cloud-server-gateway/src/main/java/cn/mb/cloud/gateway/filter/AccountAuditGlobalFilter.java                  |   22 +
 cloud-server-management/src/main/webapp/WEB-INF/view/password.html                                           |   77 ++++-
 cloud-server-management/src/main/java/com/dsh/guns/core/aop/ManagementOperationAuditAspect.java              |  198 +++++++++++++++
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditColumnCommentResolver.java      |   67 +++++
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/OperatorController.java    |   11 
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/ManagementFeignAuditInterceptor.java |   42 --
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/LoginController.java     |   21 +
 cloud-server-other/src/main/java/com/dsh/other/audit/AccountAuditWebConfig.java                              |   12 
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditField.java                      |   13 +
 cloud-server-management/src/main/java/com/dsh/guns/modular/system/model/User.java                            |   30 +-
 cloud-server-other/src/main/java/com/dsh/other/audit/FeignAuditConfiguration.java                            |   10 
 17 files changed, 649 insertions(+), 125 deletions(-)

diff --git a/cloud-server-gateway/src/main/java/cn/mb/cloud/gateway/filter/AccountAuditGlobalFilter.java b/cloud-server-gateway/src/main/java/cn/mb/cloud/gateway/filter/AccountAuditGlobalFilter.java
index d4bbe97..63b2092 100644
--- a/cloud-server-gateway/src/main/java/cn/mb/cloud/gateway/filter/AccountAuditGlobalFilter.java
+++ b/cloud-server-gateway/src/main/java/cn/mb/cloud/gateway/filter/AccountAuditGlobalFilter.java
@@ -44,7 +44,9 @@
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         ServerHttpRequest request = exchange.getRequest();
         String uri = request.getURI().getRawPath();
-        if (uri.startsWith("/internal/audit-logs")) return chain.filter(exchange);
+        if (uri.startsWith("/internal/audit-logs") || !shouldAudit(request.getMethodValue(), uri)) {
+            return chain.filter(exchange);
+        }
         long startedAt = System.currentTimeMillis();
         return chain.filter(exchange).then(Mono.<Void>fromRunnable(() -> writeAudit(exchange, startedAt))
                 .subscribeOn(Schedulers.boundedElastic()));
@@ -106,12 +108,28 @@
         String lower = uri.toLowerCase();
         if (lower.contains("login") || lower.contains("token")) return "LOGIN";
         if (lower.matches(".*(status|state|ticket|payment|finance|refund|withdraw|balance).*")) return "STATUS_CHANGE";
-        if ("POST".equalsIgnoreCase(method)) return "CREATE";
+        if (lower.matches(".*(add|create|save|insert|commit|submit|upload).*")) return "CREATE";
         if ("PUT".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method)) return "UPDATE";
         if ("DELETE".equalsIgnoreCase(method)) return "DELETE";
+        if (lower.matches(".*(edit|update|modify|change|set|reset|freeze|unfreeze|enable|disable|audit|approve|reject).*")) return "UPDATE";
         if (lower.matches(".*(phone|mobile|idcard|identity|bank|wallet).*")) return "VIEW_SENSITIVE";
         return "READ";
     }
+
+    /**
+     * 网关只记录外部用户真正触发的业务变更、登录和敏感数据访问。
+     * POST 在本系统中大量用于列表/详情查询,不能再按 HTTP 方法一概当作新增。
+     */
+    private boolean shouldAudit(String method, String uri) {
+        String lower = uri == null ? "" : uri.toLowerCase();
+        if (lower.matches(".*(login|token|phone|mobile|idcard|identity|bank|wallet).*")) return true;
+        if ("PUT".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method)
+                || "DELETE".equalsIgnoreCase(method)) return true;
+        return "POST".equalsIgnoreCase(method)
+                && lower.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 riskLevel(String uri, String type) {
         String lower = uri.toLowerCase();
         if ("STATUS_CHANGE".equals(type) || lower.matches(".*(finance|payment|refund|withdraw|balance|ticket).*")) return "CRITICAL";
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/core/aop/LogAop.java b/cloud-server-management/src/main/java/com/dsh/guns/core/aop/LogAop.java
index 79fc41e..666d611 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/core/aop/LogAop.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/core/aop/LogAop.java
@@ -8,7 +8,10 @@
 import com.dsh.guns.core.log.factory.LogTaskFactory;
 import com.dsh.guns.core.support.HttpKit;
 import com.dsh.guns.core.util.Contrast;
+import com.dsh.guns.modular.system.audit.ManagementAuditWriter;
+import com.dsh.guns.modular.system.audit.AuditDataFormatter;
 import com.dsh.guns.modular.system.model.User;
+import com.dsh.guns.modular.system.service.IUserService;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Around;
@@ -19,8 +22,13 @@
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.HashMap;
 import java.lang.reflect.Method;
 import java.util.Map;
+import java.util.UUID;
 
 /**
  * 日志记录
@@ -33,6 +41,8 @@
 public class LogAop {
 
     private Logger log = LoggerFactory.getLogger(this.getClass());
+    @Resource private ManagementAuditWriter auditWriter;
+    @Resource private IUserService userService;
 
     @Pointcut(value = "@annotation(com.dsh.guns.core.common.annotion.BussinessLog)")
     public void cutService() {
@@ -40,17 +50,119 @@
 
     @Around("cutService()")
     public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
-
-        //先执行业务
-        Object result = point.proceed();
-
+        String beforeData = beforeData();
         try {
-            handle(point);
-        } catch (Exception e) {
-            log.error("日志记录出错!", e);
+            Object result = point.proceed();
+            appendUnifiedAudit(point, "SUCCESS", null, beforeData);
+            return result;
+        } catch (Throwable e) {
+            appendUnifiedAudit(point, "FAIL", e.getMessage(), beforeData);
+            throw e;
         }
+    }
 
-        return result;
+    /**
+     * @BussinessLog 是后台已有的“需要记录的用户操作”白名单。
+     * 在这里写统一审计,能够覆盖 /mgr/add 等直接访问管理端的请求,
+     * 并避免将其内部 Feign 调用和 POST 查询误记为操作。
+     */
+    private void appendUnifiedAudit(ProceedingJoinPoint point, String result, String errorMessage, String beforeData) {
+        try {
+            Method method = ((MethodSignature) point.getSignature()).getMethod();
+            BussinessLog annotation = method.getAnnotation(BussinessLog.class);
+            User user = UserExt.getUser();
+            if (annotation == null || user == null) return;
+
+            HttpServletRequest request = HttpKit.getRequest();
+            Map<String, Object> audit = new HashMap<String, Object>();
+            audit.put("traceId", UUID.randomUUID().toString().replace("-", ""));
+            audit.put("sourceService", "mb-cloud-management");
+            audit.put("sourceType", "HTTP");
+            audit.put("operationType", operationType(annotation.value()));
+            audit.put("riskLevel", "NORMAL");
+            audit.put("operationName", annotation.value());
+            audit.put("requestMethod", request.getMethod());
+            audit.put("requestUri", request.getRequestURI());
+            // 审计表字段为 varchar,必须在调用审计服务前序列化;直接传 Map 会导致
+            // Spring 无法绑定到 SysAccountAuditLog.requestParams(String),从而整条日志写入失败。
+            audit.put("requestParams", AuditDataFormatter.format(maskedParameters(HttpKit.getRequestParameters()), User.class));
+            audit.put("beforeData", beforeData);
+            audit.put("afterData", afterData(beforeData));
+            audit.put("userId", user.getId());
+            audit.put("userAccount", user.getAccount());
+            audit.put("clientIp", HttpKit.getIp());
+            audit.put("result", result);
+            audit.put("errorMessage", maskText(errorMessage));
+            audit.put("createdAt", new Date());
+            auditWriter.append(audit);
+        } catch (Exception auditException) {
+            // 审计失败不能影响原有业务。
+            log.error("统一操作审计记录出错", auditException);
+        }
+    }
+
+    private String operationType(String operationName) {
+        String value = operationName == null ? "" : operationName;
+        if (value.contains("删除")) return "DELETE";
+        if (value.contains("添加") || value.contains("新增")) return "CREATE";
+        if (value.contains("冻结") || value.contains("解冻") || value.contains("审核") || value.contains("状态")) return "STATUS_CHANGE";
+        return "UPDATE";
+    }
+
+    private Map<String, String> maskedParameters(Map<String, String> parameters) {
+        Map<String, String> masked = new HashMap<String, String>();
+        for (Map.Entry<String, String> entry : parameters.entrySet()) {
+            String key = entry.getKey();
+            masked.put(key, key != null && key.toLowerCase().matches(".*(password|token|secret|idcard|identity|bank|card|mobile|phone).*")
+                    ? "***" : entry.getValue());
+        }
+        return masked;
+    }
+
+    private String maskText(String text) {
+        return text == null ? null : text.replaceAll("(?i)(password|token|authorization|idcard|bankCard)\\s*[:=]\\s*[^,\\s&}]+", "$1=***");
+    }
+
+    /** 员工修改前后均从数据库读取真实记录,避免将前端提交值误当作旧值。 */
+    private String beforeData() {
+        try {
+            HttpServletRequest request = HttpKit.getRequest();
+            if (!"/mgr/edit".equals(request.getRequestURI())) return null;
+            Integer userId = Integer.valueOf(request.getParameter("id"));
+            return userSnapshot(userService.getById(userId));
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+    private String afterData(String beforeData) {
+        if (beforeData == null) return null;
+        try {
+            Integer userId = Integer.valueOf(HttpKit.getRequest().getParameter("id"));
+            return userSnapshot(userService.getById(userId));
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+    private String userSnapshot(User target) {
+        if (target == null) return null;
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("id", target.getId());
+        data.put("account", target.getAccount());
+        data.put("name", target.getName());
+        data.put("phone", target.getPhone());
+        data.put("birthday", target.getBirthday());
+        data.put("sex", target.getSex());
+        data.put("email", target.getEmail());
+        data.put("roleid", target.getRoleid());
+        data.put("deptid", target.getDeptid());
+        data.put("status", target.getStatus());
+        data.put("objectType", target.getObjectType());
+        data.put("objectId", target.getObjectId());
+        data.put("idCard", target.getIdCard());
+        data.put("avatar", target.getAvatar());
+        return AuditDataFormatter.format(data, User.class);
     }
 
     private void handle(ProceedingJoinPoint point) throws Exception {
@@ -103,4 +215,4 @@
 //        LogManager.me().executeLog(LogTaskFactory.bussinessLog(1, bussinessName, className, methodName, msg));
 
     }
-}
\ No newline at end of file
+}
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/core/aop/ManagementOperationAuditAspect.java b/cloud-server-management/src/main/java/com/dsh/guns/core/aop/ManagementOperationAuditAspect.java
new file mode 100644
index 0000000..bf69902
--- /dev/null
+++ b/cloud-server-management/src/main/java/com/dsh/guns/core/aop/ManagementOperationAuditAspect.java
@@ -0,0 +1,198 @@
+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; }
+    }
+}
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditColumnCommentResolver.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditColumnCommentResolver.java
new file mode 100644
index 0000000..57b8e60
--- /dev/null
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditColumnCommentResolver.java
@@ -0,0 +1,67 @@
+package com.dsh.guns.modular.system.audit;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** 从数据库字段备注自动解析审计展示名;每个实体只查询一次并缓存。 */
+@Component
+public class AuditColumnCommentResolver {
+    @Resource private JdbcTemplate jdbcTemplate;
+    private final Map<Class<?>, Map<String, String>> cache = new ConcurrentHashMap<Class<?>, Map<String, String>>();
+
+    public Map<String, String> resolve(Class<?> entityType) {
+        if (entityType == null) return Collections.emptyMap();
+        Map<String, String> cached = cache.get(entityType);
+        if (cached != null) return cached;
+        Map<String, String> labels = load(entityType);
+        cache.put(entityType, labels);
+        return labels;
+    }
+
+    private Map<String, String> load(Class<?> entityType) {
+        TableName tableName = entityType.getAnnotation(TableName.class);
+        if (tableName == null || tableName.value().trim().isEmpty()) return Collections.emptyMap();
+        try {
+            List<Map<String, Object>> columns = jdbcTemplate.queryForList(
+                    "SELECT COLUMN_NAME, COLUMN_COMMENT FROM information_schema.COLUMNS "
+                            + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?", tableName.value());
+            Map<String, String> comments = new LinkedHashMap<String, String>();
+            for (Map<String, Object> column : columns) {
+                Object name = column.get("COLUMN_NAME");
+                Object comment = column.get("COLUMN_COMMENT");
+                if (name != null && comment != null && !String.valueOf(comment).trim().isEmpty()) {
+                    comments.put(String.valueOf(name), concise(String.valueOf(comment)));
+                }
+            }
+            Map<String, String> result = new LinkedHashMap<String, String>();
+            for (Class<?> type = entityType; type != null && type != Object.class; type = type.getSuperclass()) {
+                for (Field field : type.getDeclaredFields()) {
+                    TableField tableField = field.getAnnotation(TableField.class);
+                    String column = tableField == null || tableField.value().trim().isEmpty() ? field.getName() : tableField.value();
+                    String comment = comments.get(column);
+                    if (comment != null) result.put(field.getName(), comment);
+                }
+            }
+            return result;
+        } catch (Exception ignored) {
+            return Collections.emptyMap();
+        }
+    }
+
+    private String concise(String comment) {
+        int bracket = comment.indexOf('(');
+        int asciiBracket = comment.indexOf('(');
+        int cut = bracket >= 0 ? bracket : asciiBracket;
+        return cut > 0 ? comment.substring(0, cut).trim() : comment;
+    }
+}
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditDataFormatter.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditDataFormatter.java
new file mode 100644
index 0000000..4d4a5b5
--- /dev/null
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditDataFormatter.java
@@ -0,0 +1,105 @@
+package com.dsh.guns.modular.system.audit;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import io.swagger.annotations.ApiModelProperty;
+import cn.mb.cloud.common.core.util.SpringContextHolder;
+
+import java.lang.reflect.Field;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** 审计数据统一脱敏并将常用字段名转换为中文,供日志页面直接展示。 */
+public final class AuditDataFormatter {
+    private static final Map<String, String> FIELD_NAMES = new LinkedHashMap<String, String>();
+    static {
+        FIELD_NAMES.put("id", "ID"); FIELD_NAMES.put("ids", "ID列表");
+        FIELD_NAMES.put("name", "名称"); FIELD_NAMES.put("username", "用户名"); FIELD_NAMES.put("userName", "用户名");
+        FIELD_NAMES.put("account", "账号"); FIELD_NAMES.put("phone", "手机号"); FIELD_NAMES.put("mobile", "手机号");
+        FIELD_NAMES.put("password", "密码"); FIELD_NAMES.put("oldPwd", "旧密码"); FIELD_NAMES.put("newPwd", "新密码");
+        FIELD_NAMES.put("idCard", "身份证号"); FIELD_NAMES.put("identity", "身份信息");
+        FIELD_NAMES.put("status", "状态"); FIELD_NAMES.put("state", "状态"); FIELD_NAMES.put("type", "类型");
+        FIELD_NAMES.put("birthday", "生日"); FIELD_NAMES.put("sex", "性别"); FIELD_NAMES.put("roleid", "角色ID");
+        FIELD_NAMES.put("deptid", "部门ID"); FIELD_NAMES.put("avatar", "头像"); FIELD_NAMES.put("jstime", "入职时间");
+        FIELD_NAMES.put("email", "邮箱"); FIELD_NAMES.put("objectType", "归属类型"); FIELD_NAMES.put("objectId", "归属对象ID");
+        FIELD_NAMES.put("remark", "备注"); FIELD_NAMES.put("reason", "原因"); FIELD_NAMES.put("content", "内容");
+        FIELD_NAMES.put("operatorId", "运营商ID"); FIELD_NAMES.put("storeId", "门店ID");
+        FIELD_NAMES.put("userId", "用户ID"); FIELD_NAMES.put("roleId", "角色ID"); FIELD_NAMES.put("roleIds", "角色ID列表");
+        FIELD_NAMES.put("cityId", "城市ID"); FIELD_NAMES.put("courseId", "课程ID");
+        FIELD_NAMES.put("createdAt", "创建时间"); FIELD_NAMES.put("updateTime", "更新时间");
+    }
+
+    private AuditDataFormatter() { }
+
+    public static String format(Object value) {
+        return format(value, null);
+    }
+
+    /** entityType 非空时,字段中文名完全由实体注解决定。 */
+    public static String format(Object value, Class<?> entityType) {
+        if (value == null) return null;
+        Object json = value instanceof String ? parse((String) value) : JSON.toJSON(value);
+        return mask(JSON.toJSONString(localize(json, entityType)));
+    }
+
+    private static Object parse(String value) {
+        try { return JSON.parse(value); } catch (Exception ignored) { return value; }
+    }
+
+    private static Object localize(Object value, Class<?> entityType) {
+        if (value instanceof JSONObject) {
+            JSONObject source = (JSONObject) value;
+            JSONObject target = new JSONObject(true);
+            for (Map.Entry<String, Object> entry : source.entrySet()) {
+                target.put(displayName(entry.getKey(), entityType), localize(entry.getValue(), null));
+            }
+            return target;
+        }
+        if (value instanceof Map) {
+            JSONObject target = new JSONObject(true);
+            for (Object item : ((Map<?, ?>) value).entrySet()) {
+                Map.Entry<?, ?> entry = (Map.Entry<?, ?>) item;
+                target.put(displayName(String.valueOf(entry.getKey()), entityType), localize(entry.getValue(), null));
+            }
+            return target;
+        }
+        if (value instanceof JSONArray) {
+            JSONArray target = new JSONArray();
+            for (Object item : (JSONArray) value) target.add(localize(item, null));
+            return target;
+        }
+        return value;
+    }
+
+    private static String displayName(String field, Class<?> entityType) {
+        String annotated = annotationName(entityType, field);
+        if (annotated != null) return annotated;
+        String name = FIELD_NAMES.get(field);
+        return name == null ? field : name;
+    }
+
+    private static String annotationName(Class<?> entityType, String fieldName) {
+        if (entityType == null) return null;
+        for (Class<?> type = entityType; type != null && type != Object.class; type = type.getSuperclass()) {
+            try {
+                Field field = type.getDeclaredField(fieldName);
+                AuditField audit = field.getAnnotation(AuditField.class);
+                if (audit != null) return audit.value();
+                ApiModelProperty api = field.getAnnotation(ApiModelProperty.class);
+                if (api != null && api.value() != null && !api.value().trim().isEmpty()) return api.value();
+            } catch (NoSuchFieldException ignored) { }
+        }
+        try {
+            String comment = SpringContextHolder.getBean(AuditColumnCommentResolver.class).resolve(entityType).get(fieldName);
+            if (comment != null && !comment.trim().isEmpty()) return comment;
+        } catch (Exception ignored) {
+            // 审计展示名解析失败时回退字段名,不能影响业务。
+        }
+        return null;
+    }
+
+    private static String mask(String text) {
+        return text.replaceAll("(?i)(\\\"?(password|oldpwd|newpwd|token|authorization|secret|idcard|identity|bank(card)?|mobile|phone|密码|手机号|身份证号)\\\"?\\s*[:=]\\s*)\\\"[^\\\"]*\\\"", "$1\\\"***\\\"");
+    }
+}
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditField.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditField.java
new file mode 100644
index 0000000..728134c
--- /dev/null
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AuditField.java
@@ -0,0 +1,13 @@
+package com.dsh.guns.modular.system.audit;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** 实体字段在审计日志中的中文展示名。 */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface AuditField {
+    String value();
+}
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/ManagementFeignAuditInterceptor.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/ManagementFeignAuditInterceptor.java
index 67ce572..2f5856f 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/ManagementFeignAuditInterceptor.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/ManagementFeignAuditInterceptor.java
@@ -5,47 +5,15 @@
 import feign.RequestTemplate;
 import org.springframework.stereotype.Component;
 
-import javax.annotation.Resource;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-/** Spring 会把该 RequestInterceptor 应用于管理端全部 Feign 客户端。 */
+/** 仅透传敏感数据访问所需的内部标识;Feign 调用不是用户操作,不写审计日志。 */
 @Component
 public class ManagementFeignAuditInterceptor implements RequestInterceptor {
-    @Resource private ManagementAuditWriter auditWriter;
-
     @Override
     public void apply(RequestTemplate template) {
-        try {
-            Map<String, Object> audit = new HashMap<String, Object>();
-            String url = template.url();
-            if (url != null && url.startsWith("/internal/sensitive/")
-                    && UserExt.getUser() != null && "admin".equalsIgnoreCase(UserExt.getUser().getAccount())) {
-                template.header("X-Internal-Sensitive-Access", "true");
-            }
-            audit.put("traceId", UUID.randomUUID().toString().replace("-", ""));
-            audit.put("sourceService", "mb-cloud-management");
-            audit.put("sourceType", "FEIGN");
-            audit.put("operationType", "FEIGN_CALL");
-            audit.put("riskLevel", riskLevel(url));
-            audit.put("operationName", template.feignTarget() == null ? "Feign调用" : template.feignTarget().name());
-            audit.put("requestMethod", template.method());
-            audit.put("requestUri", url);
-            audit.put("requestParams", template.body() == null ? null : "bodyBytes=" + template.body().length);
-            audit.put("userId", UserExt.getUser() == null ? null : UserExt.getUser().getId());
-            audit.put("userAccount", UserExt.getUser() == null ? null : UserExt.getUser().getAccount());
-            audit.put("result", "REQUESTED");
-            audit.put("createdAt", new Date());
-            auditWriter.append(audit);
-        } catch (Exception ignored) {
-            // 审计异常绝不能阻塞原 Feign 调用。
+        String url = template.url();
+        if (url != null && url.startsWith("/internal/sensitive/")
+                && UserExt.getUser() != null && "admin".equalsIgnoreCase(UserExt.getUser().getAccount())) {
+            template.header("X-Internal-Sensitive-Access", "true");
         }
-    }
-
-    private String riskLevel(String url) {
-        String value = url == null ? "" : url.toLowerCase();
-        return value.matches(".*(ticket|payment|finance|refund|withdraw|balance|status|state).*") ? "CRITICAL" : "NORMAL";
     }
 }
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/OperatorController.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/OperatorController.java
index 8136128..2aafef8 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/OperatorController.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/OperatorController.java
@@ -145,9 +145,14 @@
         TOperator byId = operatorService.getById(id);
         // 运营商名称
         model.addAttribute(byId.getName());
-        User byId1 = userService.getById(byId.getUserId());
-        model.addAttribute("userName",byId1.getName());
-        model.addAttribute("phone",byId1.getPhone());
+        // 历史数据可能存在 t_operator.userId 未同步的情况,不能因此让编辑页手机号丢失。
+        User byId1 = byId.getUserId() == null ? null : userService.getById(byId.getUserId());
+        if (byId1 == null) {
+            byId1 = userService.getOne(new QueryWrapper<User>()
+                    .eq("objectId", id).eq("objectType", 2));
+        }
+        model.addAttribute("userName", byId1 == null ? "" : byId1.getName());
+        model.addAttribute("phone", byId1 == null ? "" : byId1.getPhone());
         model.addAttribute("data",byId);
         // 获取全部的省
         List<TOperatorCity> list = operatorCityService.list(new QueryWrapper<TOperatorCity>().eq("operatorId", id).eq("pid",0));
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/LoginController.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/LoginController.java
index 524fb74..a6f83f7 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/LoginController.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/LoginController.java
@@ -1,6 +1,5 @@
 package com.dsh.guns.modular.system.controller.system;
 
-import cn.hutool.crypto.SecureUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.dsh.course.feignClient.account.AppUserClient;
 import com.dsh.course.feignClient.account.model.TAppUser;
@@ -99,8 +98,20 @@
     @PostMapping("/updatePassword")
     @ResponseBody
 //    @ResponseBody
-    public ResultUtil update(String username,String sms_code,String passwordS) {
+    public ResultUtil update(String username, String sms_code, String password, String passwordS) {
+        if (ToolUtil.isEmpty(username) || ToolUtil.isEmpty(sms_code)
+                || ToolUtil.isEmpty(password) || ToolUtil.isEmpty(passwordS)) {
+            return ResultUtil.error("账号、验证码和新密码不能为空");
+        }
 
+        String newPassword = AESUtils.decrypt(password);
+        String confirmedPassword = AESUtils.decrypt(passwordS);
+        if (ToolUtil.isEmpty(newPassword) || ToolUtil.isEmpty(newPassword.trim())) {
+            return ResultUtil.error("新密码无效");
+        }
+        if (!newPassword.equals(confirmedPassword)) {
+            return ResultUtil.error("两次输入的新密码不一致");
+        }
 
         String value = redisUtil.getValue(username);
         if (!"123456".equals(sms_code) && (ToolUtil.isEmpty(value) || !value.equals(sms_code))) {
@@ -116,8 +127,10 @@
 //        if (User.getCode() == null) {
 //            User.setCode(UUIDUtil.getNumberRandom(16));
 //        }
-        User.setPassword(SecureUtil.md5(passwordS));
-        userService.updateById(User);
+        User.setPassword(MD5.md5(newPassword));
+        if (!userService.updateById(User)) {
+            return ResultUtil.error("密码修改失败,请稍后重试");
+        }
         return ResultUtil.success();
 
 //        return "/login.html";
diff --git a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/model/User.java b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/model/User.java
index 519169e..4bde309 100644
--- a/cloud-server-management/src/main/java/com/dsh/guns/modular/system/model/User.java
+++ b/cloud-server-management/src/main/java/com/dsh/guns/modular/system/model/User.java
@@ -6,6 +6,7 @@
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.dsh.guns.modular.system.audit.AuditField;
 
 import java.io.Serializable;
 import java.util.Date;
@@ -27,19 +28,20 @@
      * 主键id
      */
 	@TableId(value="id", type= IdType.AUTO)
+	@AuditField("员工ID")
 	private Integer id;
     /**
      * 头像
      */
-	private String avatar;
+	@AuditField("头像") private String avatar;
     /**
      * 账号
      */
-	private String account;
+	@AuditField("账号") private String account;
     /**
      * 密码
      */
-	private String password;
+	@AuditField("密码") private String password;
     /**
      * md5密码盐
      */
@@ -47,35 +49,35 @@
     /**
      * 名字
      */
-	private String name;
+	@AuditField("员工姓名") private String name;
     /**
      * 生日
      */
-	private Date birthday;
+	@AuditField("生日") private Date birthday;
     /**
      * 性别(1:男 2:女)
      */
-	private Integer sex;
+	@AuditField("性别") private Integer sex;
     /**
      * 电子邮件
      */
-	private String email;
+	@AuditField("邮箱") private String email;
     /**
      * 电话
      */
-	private String phone;
+	@AuditField("手机号") private String phone;
     /**
      * 角色id
      */
-	private String roleid;
+	@AuditField("角色ID") private String roleid;
     /**
      * 部门id
      */
-	private Integer deptid;
+	@AuditField("部门ID") private Integer deptid;
     /**
      * 状态(1:启用  2:冻结  3:删除)
      */
-	private Integer status;
+	@AuditField("状态") private Integer status;
     /**
      * 创建时间
      */
@@ -88,14 +90,14 @@
 	 * 对象类型(1=平台,2=城市管理员,3=门店)
 	 */
 	@TableField("objectType")
-	private Integer objectType;
+	@AuditField("归属类型") private Integer objectType;
 	/**
 	 * objectId
 	 */
 	@TableField("objectId")
-	private Integer objectId;
+	@AuditField("归属对象ID") private Integer objectId;
 	@TableField("idCard")
-	private String idCard;
+	@AuditField("身份证号") private String idCard;
 
 	public String getIdCard() {
 		return idCard;
diff --git a/cloud-server-management/src/main/webapp/WEB-INF/view/common/_container.html b/cloud-server-management/src/main/webapp/WEB-INF/view/common/_container.html
index ed60b1f..3f2ac19 100644
--- a/cloud-server-management/src/main/webapp/WEB-INF/view/common/_container.html
+++ b/cloud-server-management/src/main/webapp/WEB-INF/view/common/_container.html
@@ -64,7 +64,7 @@
     <script src="${ctxPath}/js/common/web-upload-object.js"></script>
     <script src="${ctxPath}/js/common/ztree-object.js"></script>
     <script src="${ctxPath}/js/common/Feng.js"></script>
-    <script src="${ctxPath}/js/common/sensitive-info.js"></script>
+    <script src="${ctxPath}/js/common/sensitive-info.js?v=20260922-employee-idcard"></script>
     <script src="${ctxPath}/js/plugins/sweetalert/sweetalert.min.js"></script>
     <script src="${ctxPath}/layui/layui.js"></script>
 
diff --git a/cloud-server-management/src/main/webapp/WEB-INF/view/password.html b/cloud-server-management/src/main/webapp/WEB-INF/view/password.html
index 615ea6c..34be3d3 100644
--- a/cloud-server-management/src/main/webapp/WEB-INF/view/password.html
+++ b/cloud-server-management/src/main/webapp/WEB-INF/view/password.html
@@ -60,7 +60,7 @@
         <img style="margin-left: 44px" src="${ctxPath}/img/logo.png" width="100px" height="120px"/>
         <br/>
         <h4 style="color: red;">${tips!}</h4>
-        <form class="m-t" role="form" action="${ctxPath}/updatePassword" method="post">
+        <form id="passwordForm" class="m-t" role="form" onsubmit="return update(event);">
             <div class="item">
                 <img src="${ctxPath}/img/account.png" />
                 <input class="itemInput" type="text" name="username" id="username"  placeholder="请输入账号" required="" value="">
@@ -79,9 +79,8 @@
                 <input class="itemInput" type="password" name="passwordS" id="passwordS"  placeholder="请确认新密码" required="" value="">
             </div>
 
-            <button onclick="update()" class="btn full-width m-b" style="background-color: #0573EA;color: white;">保存</button>
-            <button  class="btn full-width m-b" style="background-color: #0573EA;color: white;" onclick="getBack()">关闭</button>
-            <button type="submit" id="submit" style="display: none;"></button>
+            <button id="saveButton" type="submit" class="btn full-width m-b" style="background-color: #0573EA;color: white;">保存</button>
+            <button type="button" class="btn full-width m-b" style="background-color: #0573EA;color: white;" onclick="getBack()">关闭</button>
             </p>
         </form>
     </div>
@@ -165,26 +164,68 @@
         return decryptedStr.toString();
     }
 
-    function update() {
-        let password = $('#password').val();
-        password = encrypt(password);
-        $('#password').val(password);
+    function update(event) {
+        event.preventDefault();
 
+        var username = $.trim($('#username').val());
+        var smsCode = $.trim($('#sms_code').val());
+        var password = $('#password').val();
+        var passwordS = $('#passwordS').val();
+
+        if (!username) {
+            alert('请输入账号');
+            $('#username').focus();
+            return false;
+        }
+        if (!smsCode) {
+            alert('请输入验证码');
+            $('#sms_code').focus();
+            return false;
+        }
+        if (!password || !$.trim(password)) {
+            alert('请输入新密码');
+            $('#password').focus();
+            return false;
+        }
+        if (!passwordS || !$.trim(passwordS)) {
+            alert('请确认新密码');
+            $('#passwordS').focus();
+            return false;
+        }
+        if (password !== passwordS) {
+            alert('两次输入的新密码不一致');
+            $('#passwordS').focus();
+            return false;
+        }
+
+        var $saveButton = $('#saveButton');
+        $saveButton.prop('disabled', true).text('保存中...');
         $.ajax({
-            url: 'your-submit-url',
+            url: Feng.ctxPath + '/updatePassword',
             type: 'POST',
-            data: $('#submit').serialize(),
-            success: function(response) {
-                // 根据响应的msg字段设置对应的提示
-                    // 成功提示
-                    alert("修改成功");
-                window.location.href = Feng.ctxPath+"/login";
+            data: {
+                username: username,
+                sms_code: smsCode,
+                password: encrypt(password),
+                passwordS: encrypt(passwordS)
             },
-            error: function() {
-                // 错误处理
-                alert('当前账号不存在');
+            success: function(response) {
+                if (response && (response.status === 200 || response.code === 200)) {
+                    alert(response.msg || '修改成功');
+                    window.location.href = Feng.ctxPath + '/login';
+                    return;
+                }
+                alert((response && response.msg) || '修改失败,请稍后重试');
+            },
+            error: function(xhr) {
+                var response = xhr.responseJSON;
+                alert((response && response.msg) || '修改失败,请检查网络后重试');
+            },
+            complete: function() {
+                $saveButton.prop('disabled', false).text('保存');
             }
         });
+        return false;
     }
 
 
diff --git a/cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user.html b/cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user.html
index 151674a..695bf8e 100644
--- a/cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user.html
+++ b/cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user.html
@@ -82,7 +82,7 @@
 		</div>
 	</div>
 </div>
-<script src="${ctxPath}/modular/system/user/user.js"></script>
+<script src="${ctxPath}/modular/system/user/user.js?v=20260922-sensitive-view"></script>
 <script src="${ctxPath}/layui/layui.js" charset="utf-8"></script>
 <script>
 	laydate.render({
diff --git a/cloud-server-management/src/main/webapp/static/js/common/sensitive-info.js b/cloud-server-management/src/main/webapp/static/js/common/sensitive-info.js
index 98490ba..2fff1ed 100644
--- a/cloud-server-management/src/main/webapp/static/js/common/sensitive-info.js
+++ b/cloud-server-management/src/main/webapp/static/js/common/sensitive-info.js
@@ -1,6 +1,6 @@
 /** Admin-only sensitive-information viewer. Every request is audited by the server. */
 var SensitiveInfo = {
-    view: function (url) {
+    view: function (url, showIdCard) {
         layer.confirm('查看完整敏感信息将记录操作日志,是否继续?', {
             icon: 3,
             title: '敏感信息查看'
@@ -20,10 +20,17 @@
                         Feng.info('未查询到手机号');
                         return;
                     }
-                    var content = '<div class="form-horizontal" style="padding:20px"><div class="form-group">'
+                    var fields = '<div class="form-group">'
                         + '<label class="col-sm-4 control-label">手机号:</label><div class="col-sm-8">'
                         + '<p class="form-control-static">' + SensitiveInfo.escape(String(phone))
-                        + '</p></div></div></div>';
+                        + '</p></div></div>';
+                    if (showIdCard && data.idCard) {
+                        fields += '<div class="form-group">'
+                            + '<label class="col-sm-4 control-label">身份证号:</label><div class="col-sm-8">'
+                            + '<p class="form-control-static">' + SensitiveInfo.escape(String(data.idCard))
+                            + '</p></div></div>';
+                    }
+                    var content = '<div class="form-horizontal" style="padding:20px">' + fields + '</div>';
                     layer.open({type: 1, title: '敏感信息', area: ['480px', 'auto'], content: content});
                 },
                 error: function (xhr) {
diff --git a/cloud-server-management/src/main/webapp/static/modular/system/user/user.js b/cloud-server-management/src/main/webapp/static/modular/system/user/user.js
index d5e4a31..1ba8abc 100644
--- a/cloud-server-management/src/main/webapp/static/modular/system/user/user.js
+++ b/cloud-server-management/src/main/webapp/static/modular/system/user/user.js
@@ -120,25 +120,8 @@
     }
 };
 
-/** The button is rendered only for admin; the API repeats the permission check and records an audit event. */
 MgrUser.viewSensitiveData = function () {
-    if (!this.check()) return;
-    layer.confirm('查看完整手机号和身份证号将记录审计日志,是否继续?', {icon: 3, title: '敏感信息查看'}, function (index) {
-        $.ajax({
-            url: Feng.ctxPath + '/system/sensitive/employee/' + MgrUser.seItem.id,
-            type: 'GET',
-            success: function (data) {
-                if (!data) { Feng.info('未查询到员工信息'); return; }
-                var html = '<div style="padding:20px;line-height:28px">'
-                    + '<div>姓名:' + (data.name || '') + '</div>'
-                    + '<div>手机号:' + (data.phone || '') + '</div>'
-                    + '<div>身份证号:' + (data.idCard || '') + '</div></div>';
-                layer.open({type: 1, title: '员工敏感信息(已记录审计日志)', area: ['420px', '260px'], content: html});
-            },
-            error: function () { Feng.error('无权查看敏感信息或查询失败'); }
-        });
-        layer.close(index);
-    });
+    if (this.check()) SensitiveInfo.view('/system/sensitive/employee/' + this.seItem.id, true);
 };
 
 /**
diff --git a/cloud-server-other/src/main/java/com/dsh/other/audit/AccountAuditWebConfig.java b/cloud-server-other/src/main/java/com/dsh/other/audit/AccountAuditWebConfig.java
index b2fd235..b9362ca 100644
--- a/cloud-server-other/src/main/java/com/dsh/other/audit/AccountAuditWebConfig.java
+++ b/cloud-server-other/src/main/java/com/dsh/other/audit/AccountAuditWebConfig.java
@@ -1,13 +1,11 @@
 package com.dsh.other.audit;
 
 import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
-import javax.annotation.Resource;
-
+/**
+ * 审计由网关的外部请求及管理端的业务注解统一产生。
+ * 不再在 other 服务拦截全部 HTTP 请求,否则管理端 Feign 调用会被误记为用户操作。
+ */
 @Configuration
-public class AccountAuditWebConfig implements WebMvcConfigurer {
-    @Resource private AccountAuditInterceptor accountAuditInterceptor;
-    @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(accountAuditInterceptor).addPathPatterns("/**"); }
+public class AccountAuditWebConfig {
 }
diff --git a/cloud-server-other/src/main/java/com/dsh/other/audit/FeignAuditConfiguration.java b/cloud-server-other/src/main/java/com/dsh/other/audit/FeignAuditConfiguration.java
index 741046f..d87a3cc 100644
--- a/cloud-server-other/src/main/java/com/dsh/other/audit/FeignAuditConfiguration.java
+++ b/cloud-server-other/src/main/java/com/dsh/other/audit/FeignAuditConfiguration.java
@@ -1,15 +1,9 @@
 package com.dsh.other.audit;
 
 import feign.Logger;
-import com.dsh.other.service.SysAccountAuditLogService;
 import org.springframework.context.annotation.Bean;
 
-/** 为本服务全部 OpenFeign 客户端启用审计;请求参数正文不会被落库。 */
+/** Feign 是服务间实现细节,不能作为用户操作写入审计表。 */
 public class FeignAuditConfiguration {
-    /** 创建由 Spring 注入审计服务的 Feign 日志器。 */
-    @Bean
-    public Logger feignLogger(SysAccountAuditLogService auditLogService) {
-        return new FeignAuditLogger(auditLogService);
-    }
-    @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; }
+    @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.NONE; }
 }

--
Gitblit v1.7.1