| | |
| | | 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; |
| | |
| | | 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; |
| | | |
| | | /** |
| | | * 日志记录 |
| | |
| | | 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() { |
| | |
| | | |
| | | @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 { |