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