package com.xiaopeng.common.log.aspect; import com.alibaba.fastjson2.JSON; import com.xiaopeng.api.model.account.SysUser; import com.xiaopeng.api.model.operation.XpAdminOperationLog; import com.xiaopeng.api.vo.account.LoginUser; import com.xiaopeng.common.core.utils.ServletUtils; import com.xiaopeng.common.core.utils.StringUtils; import com.xiaopeng.common.core.utils.ip.IpUtils; import com.xiaopeng.common.log.annotation.AdminOperationLog; import com.xiaopeng.common.log.enums.AdminBusinessType; import com.xiaopeng.common.log.enums.AdminOperationType; import com.xiaopeng.common.log.filter.PropertyPreExcludeFilter; import com.xiaopeng.common.log.service.AsyncLogService; import com.xiaopeng.common.security.utils.SecurityUtils; import org.apache.commons.lang3.ArrayUtils; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.time.LocalDateTime; import java.util.Collection; import java.util.Map; /** * 管理后台操作记录切面 */ @Aspect @Component public class AdminOperationLogAspect { private static final Logger log = LoggerFactory.getLogger(AdminOperationLogAspect.class); private static final String API_CLASS = "io.swagger.annotations.Api"; private static final String API_OPERATION_CLASS = "io.swagger.annotations.ApiOperation"; private static final String ADMIN_TAG = "管理后台"; private static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"}; private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); private final ExpressionParser expressionParser = new SpelExpressionParser(); @Resource private AsyncLogService asyncLogService; @Around("@annotation(com.xiaopeng.common.log.annotation.AdminOperationLog) || execution(public * com.xiaopeng..controller..*(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Method method = getMethod(joinPoint); AdminOperationLog annotation = method.getAnnotation(AdminOperationLog.class); ApiOperationMeta apiMeta = getApiOperationMeta(method, joinPoint.getTarget().getClass()); AdminOperationType operationType = resolveOperationType(annotation, apiMeta, method, joinPoint.getArgs()); boolean shouldRecord = shouldRecord(annotation, apiMeta, operationType); if (!shouldRecord) { return joinPoint.proceed(); } Object result = null; Throwable error = null; try { result = joinPoint.proceed(); return result; } catch (Throwable throwable) { error = throwable; throw throwable; } finally { saveLog(joinPoint, method, annotation, apiMeta, operationType, result, error); } } private boolean shouldRecord(AdminOperationLog annotation, ApiOperationMeta apiMeta, AdminOperationType operationType) { if (annotation != null) { return true; } return apiMeta.hasAdminTag() && operationType != AdminOperationType.OTHER; } private void saveLog(ProceedingJoinPoint joinPoint, Method method, AdminOperationLog annotation, ApiOperationMeta apiMeta, AdminOperationType operationType, Object result, Throwable error) { try { XpAdminOperationLog operationLog = new XpAdminOperationLog(); AdminBusinessType businessType = resolveBusinessType(annotation, apiMeta); operationLog.setBusinessType(businessType.getCode()); operationLog.setBusinessTypeName(businessType.getDesc()); operationLog.setOperationType(operationType.getCode()); operationLog.setOperationTypeName(operationType.getDesc()); operationLog.setOperationContent(resolveContent(joinPoint, method, annotation, apiMeta, result)); operationLog.setStatus(error == null ? 0 : 1); if (error != null) { operationLog.setErrorMsg(StringUtils.substring(error.getMessage(), 0, 2000)); } HttpServletRequest request = ServletUtils.getRequest(); if (request != null) { operationLog.setOperationIp(IpUtils.getIpAddr(request)); operationLog.setRequestUri(StringUtils.substring(request.getRequestURI(), 0, 255)); operationLog.setRequestMethod(request.getMethod()); } operationLog.setMethod(joinPoint.getTarget().getClass().getName() + "." + method.getName() + "()"); operationLog.setOperationTime(LocalDateTime.now()); fillOperator(operationLog); fillDefaultValue(operationLog); asyncLogService.saveAdminOperationLog(operationLog); } catch (Exception e) { log.error("保存管理后台操作记录异常:{}", e.getMessage(), e); } } private void fillOperator(XpAdminOperationLog operationLog) { LoginUser loginUser = SecurityUtils.getLoginUser(); if (loginUser == null) { operationLog.setOperatorAccount(SecurityUtils.getUsername()); operationLog.setOperatorName(SecurityUtils.getUsername()); return; } operationLog.setOperatorAccountId(loginUser.getUserid()); operationLog.setOperatorAccount(StringUtils.defaultIfEmpty(loginUser.getUsername(), SecurityUtils.getUsername())); SysUser sysUser = loginUser.getSysUser(); if (sysUser != null) { operationLog.setOperatorName(StringUtils.defaultIfEmpty(sysUser.getNickName(), operationLog.getOperatorAccount())); if (StringUtils.isNotEmpty(sysUser.getUserAccount())) { operationLog.setOperatorAccount(sysUser.getUserAccount()); } if (sysUser.getId() != null) { operationLog.setOperatorAccountId(sysUser.getId()); } } else { operationLog.setOperatorName(operationLog.getOperatorAccount()); } } private void fillDefaultValue(XpAdminOperationLog operationLog) { operationLog.setOperatorName(StringUtils.defaultString(operationLog.getOperatorName())); operationLog.setOperatorAccount(StringUtils.defaultString(operationLog.getOperatorAccount())); operationLog.setOperationIp(StringUtils.defaultString(operationLog.getOperationIp())); operationLog.setRequestUri(StringUtils.defaultString(operationLog.getRequestUri())); operationLog.setRequestMethod(StringUtils.defaultString(operationLog.getRequestMethod())); operationLog.setMethod(StringUtils.defaultString(operationLog.getMethod())); operationLog.setErrorMsg(StringUtils.defaultString(operationLog.getErrorMsg())); } private AdminBusinessType resolveBusinessType(AdminOperationLog annotation, ApiOperationMeta apiMeta) { if (annotation != null && annotation.businessType() != AdminBusinessType.OTHER) { return annotation.businessType(); } String tags = apiMeta.tagsText(); if (tags.contains("订单")) { return AdminBusinessType.ORDER; } if (tags.contains("车辆") || tags.contains("资产")) { return AdminBusinessType.VEHICLE; } if (tags.contains("车队")) { return AdminBusinessType.FLEET; } if (tags.contains("账号") || tags.contains("帐号") || tags.contains("角色") || tags.contains("用户")) { return AdminBusinessType.ACCOUNT; } if (tags.contains("运维") || tags.contains("工单") || tags.contains("任务")) { return AdminBusinessType.OPERATION; } if (tags.contains("交易") || tags.contains("定价") || tags.contains("支付")) { return AdminBusinessType.TRADE; } if (tags.contains("内容")) { return AdminBusinessType.CONTENT; } if (tags.contains("系统")) { return AdminBusinessType.SYSTEM; } if (tags.contains("白名单")) { return AdminBusinessType.WHITELIST; } if (tags.contains("服务中心") || tags.contains("审核")) { return AdminBusinessType.SERVICE; } return AdminBusinessType.OTHER; } private AdminOperationType resolveOperationType(AdminOperationLog annotation, ApiOperationMeta apiMeta, Method method, Object[] args) { AdminOperationType expressionType = resolveOperationTypeExpression(annotation, method, args); if (expressionType != AdminOperationType.OTHER) { return expressionType; } if (annotation != null && annotation.operationType() != AdminOperationType.OTHER) { return annotation.operationType(); } String text = apiMeta.valueText(); if (text.contains("批量冻结")) { return AdminOperationType.BATCH_FREEZE; } if (text.contains("重置密码")) { return AdminOperationType.RESET_PASSWORD; } if (text.contains("解冻") || text.contains("解封")) { return AdminOperationType.UNFREEZE; } if (text.contains("冻结")) { return AdminOperationType.FREEZE; } if (text.contains("启用") && text.contains("禁用")) { return AdminOperationType.SWITCH_STATUS; } if (text.contains("启用")) { return AdminOperationType.ENABLE; } if (text.contains("禁用") || text.contains("停用")) { return AdminOperationType.DISABLE; } if (text.contains("新增") || text.contains("添加") || text.contains("保存") || text.contains("创建")) { return AdminOperationType.ADD; } if (text.contains("编辑") || text.contains("修改") || text.contains("更新") || text.contains("设置")) { return AdminOperationType.EDIT; } if (text.contains("删除")) { return AdminOperationType.DELETE; } if (text.contains("导入")) { return AdminOperationType.IMPORT; } if (text.contains("导出")) { return AdminOperationType.EXPORT; } if (text.contains("审核")) { return AdminOperationType.AUDIT; } if (text.contains("发布")) { return AdminOperationType.RELEASE; } if (text.contains("发放")) { return AdminOperationType.ISSUE; } return AdminOperationType.OTHER; } private AdminOperationType resolveOperationTypeExpression(AdminOperationLog annotation, Method method, Object[] args) { if (annotation == null || StringUtils.isEmpty(annotation.operationTypeExpression())) { return AdminOperationType.OTHER; } return parseOperationType(parseExpression(annotation.operationTypeExpression(), method, args, null)); } private AdminOperationType parseOperationType(Object value) { if (value == null) { return AdminOperationType.OTHER; } if (value instanceof AdminOperationType) { return (AdminOperationType) value; } String typeValue = String.valueOf(value); for (AdminOperationType type : AdminOperationType.values()) { if (type.name().equalsIgnoreCase(typeValue) || String.valueOf(type.getCode()).equals(typeValue) || type.getDesc().equals(typeValue)) { return type; } } return AdminOperationType.OTHER; } private String resolveContent(ProceedingJoinPoint joinPoint, Method method, AdminOperationLog annotation, ApiOperationMeta apiMeta, Object result) { String content = ""; if (annotation != null && StringUtils.isNotEmpty(annotation.contentExpression())) { content = parseExpression(annotation.contentExpression(), method, joinPoint.getArgs(), result); } if (StringUtils.isEmpty(content) && annotation != null && StringUtils.isNotEmpty(annotation.content())) { content = annotation.content(); } if (StringUtils.isEmpty(content)) { content = apiMeta.valueText(); } if (StringUtils.isEmpty(content)) { content = method.getName(); } boolean appendRequestData = annotation == null || annotation.appendRequestData(); if (appendRequestData) { String[] excludeParamNames = annotation == null ? new String[0] : annotation.excludeParamNames(); String requestData = argsArrayToString(joinPoint.getArgs(), excludeParamNames); if (StringUtils.isNotEmpty(requestData)) { content = content + ":" + requestData; } } return StringUtils.substring(content, 0, 2000); } private String parseExpression(String expression, Method method, Object[] args, Object result) { try { EvaluationContext context = new StandardEvaluationContext(); String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); for (int i = 0; i < args.length; i++) { context.setVariable("p" + i, args[i]); context.setVariable("a" + i, args[i]); context.setVariable("arg" + i, args[i]); if (parameterNames != null && i < parameterNames.length) { context.setVariable(parameterNames[i], args[i]); } } context.setVariable("result", result); Object value = expressionParser.parseExpression(expression).getValue(context); return value == null ? "" : String.valueOf(value); } catch (Exception e) { log.warn("解析管理后台操作记录表达式失败:{}", expression, e); return ""; } } private ApiOperationMeta getApiOperationMeta(Method method, Class targetClass) { String[] apiTags = getClassApiTags(targetClass); for (Annotation annotation : method.getAnnotations()) { if (API_OPERATION_CLASS.equals(annotation.annotationType().getName())) { return ApiOperationMeta.of(annotation, apiTags); } } return ApiOperationMeta.empty(apiTags); } private String[] getClassApiTags(Class targetClass) { for (Annotation annotation : targetClass.getAnnotations()) { if (API_CLASS.equals(annotation.annotationType().getName())) { try { Method tagsMethod = annotation.annotationType().getMethod("tags"); return (String[]) tagsMethod.invoke(annotation); } catch (Exception e) { return new String[0]; } } } return new String[0]; } private Method getMethod(ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); return signature.getMethod(); } private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { StringBuilder params = new StringBuilder(); if (paramsArray != null && paramsArray.length > 0) { for (Object o : paramsArray) { if (StringUtils.isNotNull(o) && !isFilterObject(o)) { try { params.append(JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames))).append(" "); } catch (Exception ignored) { } } } } return params.toString().trim(); } private PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) { return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); } @SuppressWarnings("rawtypes") private boolean isFilterObject(final Object o) { Class clazz = o.getClass(); if (clazz.isArray()) { return clazz.getComponentType().isAssignableFrom(MultipartFile.class); } if (Collection.class.isAssignableFrom(clazz)) { Collection collection = (Collection) o; for (Object value : collection) { return value instanceof MultipartFile; } } if (Map.class.isAssignableFrom(clazz)) { Map map = (Map) o; for (Object value : map.entrySet()) { Map.Entry entry = (Map.Entry) value; return entry.getValue() instanceof MultipartFile; } } return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } private static class ApiOperationMeta { private final String value; private final String[] tags; private ApiOperationMeta(String value, String[] tags) { this.value = value == null ? "" : value; this.tags = tags == null ? new String[0] : tags; } static ApiOperationMeta empty(String[] apiTags) { return new ApiOperationMeta("", apiTags); } static ApiOperationMeta of(Annotation annotation, String[] apiTags) { try { Method valueMethod = annotation.annotationType().getMethod("value"); Method tagsMethod = annotation.annotationType().getMethod("tags"); String[] operationTags = (String[]) tagsMethod.invoke(annotation); return new ApiOperationMeta((String) valueMethod.invoke(annotation), mergeTags(operationTags, apiTags)); } catch (Exception e) { return empty(apiTags); } } private static String[] mergeTags(String[] operationTags, String[] apiTags) { if (operationTags == null || operationTags.length == 0) { return apiTags; } if (apiTags == null || apiTags.length == 0) { return operationTags; } String[] merged = new String[operationTags.length + apiTags.length]; System.arraycopy(operationTags, 0, merged, 0, operationTags.length); System.arraycopy(apiTags, 0, merged, operationTags.length, apiTags.length); return merged; } boolean hasAdminTag() { return tagsText().contains(ADMIN_TAG); } String valueText() { return value; } String tagsText() { if (tags.length == 0) { return ""; } return String.join(",", tags); } } }