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)")
|
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());
|
|
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 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);
|
if (businessType == AdminBusinessType.OTHER || operationType == AdminOperationType.OTHER) {
|
return;
|
}
|
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);
|
}
|
}
|
}
|