diff --git a/maku-framework/src/main/java/net/maku/framework/common/cache/RedisKeys.java b/maku-framework/src/main/java/net/maku/framework/common/cache/RedisKeys.java
index 35e63fc..c61c3d2 100644
--- a/maku-framework/src/main/java/net/maku/framework/common/cache/RedisKeys.java
+++ b/maku-framework/src/main/java/net/maku/framework/common/cache/RedisKeys.java
@@ -22,4 +22,8 @@ public class RedisKeys {
return "sys:access:" + accessToken;
}
+ public static String getLogKey() {
+ return "sys:log";
+ }
+
}
diff --git a/maku-framework/src/main/java/net/maku/framework/operatelog/annotations/OperateLog.java b/maku-framework/src/main/java/net/maku/framework/operatelog/annotations/OperateLog.java
new file mode 100644
index 0000000..bc318c2
--- /dev/null
+++ b/maku-framework/src/main/java/net/maku/framework/operatelog/annotations/OperateLog.java
@@ -0,0 +1,33 @@
+package net.maku.framework.operatelog.annotations;
+
+import net.maku.framework.operatelog.enums.OperateTypeEnum;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 操作日志
+ *
+ * @author 阿沐 babamu@126.com
+ * MAKU
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface OperateLog {
+ /**
+ * 模块名
+ */
+ String module() default "";
+
+ /**
+ * 操作名
+ */
+ String name() default "";
+
+ /**
+ * 操作类型
+ */
+ OperateTypeEnum[] type() default OperateTypeEnum.OTHER;
+}
diff --git a/maku-framework/src/main/java/net/maku/framework/operatelog/aspect/OperateLogAspect.java b/maku-framework/src/main/java/net/maku/framework/operatelog/aspect/OperateLogAspect.java
new file mode 100644
index 0000000..90e6a6e
--- /dev/null
+++ b/maku-framework/src/main/java/net/maku/framework/operatelog/aspect/OperateLogAspect.java
@@ -0,0 +1,171 @@
+package net.maku.framework.operatelog.aspect;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import net.maku.framework.common.constant.Constant;
+import net.maku.framework.common.utils.AddressUtils;
+import net.maku.framework.common.utils.HttpContextUtils;
+import net.maku.framework.common.utils.IpUtils;
+import net.maku.framework.common.utils.JsonUtils;
+import net.maku.framework.operatelog.annotations.OperateLog;
+import net.maku.framework.operatelog.dto.OperateLogDTO;
+import net.maku.framework.operatelog.service.OperateLogService;
+import net.maku.framework.security.user.SecurityUser;
+import net.maku.framework.security.user.UserDetail;
+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.http.HttpHeaders;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.IntStream;
+
+/**
+ * 操作日志,切面处理类
+ *
+ * @author 阿沐 babamu@126.com
+ * MAKU
+ */
+@Aspect
+@Component
+@AllArgsConstructor
+public class OperateLogAspect {
+ private final OperateLogService operateLogService;
+
+ @Around("@annotation(operateLog)")
+ public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable {
+ // 记录开始时间
+ LocalDateTime startTime = LocalDateTime.now();
+ try {
+ //执行方法
+ Object result = joinPoint.proceed();
+
+ //保存日志
+ saveLog(joinPoint, operateLog, startTime, Constant.SUCCESS);
+
+ return result;
+ } catch (Exception e) {
+ //保存日志
+ saveLog(joinPoint, operateLog, startTime, Constant.FAIL);
+ throw e;
+ }
+ }
+
+
+ private void saveLog(ProceedingJoinPoint joinPoint, OperateLog operateLog, LocalDateTime startTime, Integer status) {
+ OperateLogDTO log = new OperateLogDTO();
+
+ // 执行时长
+ long duration = LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis();
+ log.setDuration((int) duration);
+ // 用户信息
+ UserDetail user = SecurityUser.getUser();
+ if (user != null) {
+ log.setUserId(user.getId());
+ log.setRealName(user.getRealName());
+ }
+ // 操作类型
+ log.setOperateType(operateLog.type()[0].getValue());
+ // 设置module值
+ log.setModule(operateLog.module());
+ // 设置name值
+ log.setName(operateLog.name());
+
+ // 如果没有指定module值,则从tag读取
+ if (StrUtil.isBlank(log.getModule())) {
+ Tag tag = getClassAnnotation(joinPoint, Tag.class);
+ if (tag != null) {
+ log.setModule(tag.name());
+ }
+ }
+
+ // 如果没有指定name值,则从operation读取
+ if (StrUtil.isBlank(log.getName())) {
+ Operation operation = getMethodAnnotation(joinPoint, Operation.class);
+ if (operation != null) {
+ log.setName(operation.summary());
+ }
+ }
+
+ // 请求相关
+ HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
+ if (request != null) {
+ log.setIp(IpUtils.getIpAddr(request));
+ log.setAddress(AddressUtils.getAddressByIP(log.getIp()));
+ log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
+ log.setReqUri(request.getRequestURI());
+ log.setReqMethod(request.getMethod());
+ }
+
+ log.setReqParams(obtainMethodArgs(joinPoint));
+ log.setStatus(status);
+
+ // 保存操作日志
+ operateLogService.saveLog(log);
+ }
+
+ private String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
+ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+ String[] argNames = methodSignature.getParameterNames();
+ Object[] argValues = joinPoint.getArgs();
+
+ // 拼接参数
+ Map args = MapUtil.newHashMap(argValues.length);
+ for (int i = 0; i < argNames.length; i++) {
+ String argName = argNames[i];
+ Object argValue = argValues[i];
+ args.put(argName, ignoreArgs(argValue) ? "[ignore]" : argValue);
+ }
+
+ return JsonUtils.toJsonString(args);
+ }
+
+ private static boolean ignoreArgs(Object object) {
+ Class> clazz = object.getClass();
+
+ // 处理数组
+ if (clazz.isArray()) {
+ return IntStream.range(0, Array.getLength(object))
+ .anyMatch(index -> ignoreArgs(Array.get(object, index)));
+ }
+
+ // 处理集合
+ if (Collection.class.isAssignableFrom(clazz)) {
+ return ((Collection>) object).stream()
+ .anyMatch((Predicate