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) OperateLogAspect::ignoreArgs); + } + + // 处理Map + if (Map.class.isAssignableFrom(clazz)) { + return ignoreArgs(((Map) object).values()); + } + + return object instanceof MultipartFile + || object instanceof HttpServletRequest + || object instanceof HttpServletResponse + || object instanceof BindingResult; + } + + private static T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class annotationClass) { + return ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(annotationClass); + } + + private static T getClassAnnotation(ProceedingJoinPoint joinPoint, Class annotationClass) { + return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass); + } +} \ No newline at end of file diff --git a/maku-framework/src/main/java/net/maku/framework/operatelog/dto/OperateLogDTO.java b/maku-framework/src/main/java/net/maku/framework/operatelog/dto/OperateLogDTO.java new file mode 100644 index 0000000..f080a9f --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/operatelog/dto/OperateLogDTO.java @@ -0,0 +1,83 @@ +package net.maku.framework.operatelog.dto; + + +import lombok.Data; + +/** + * 操作日志 + * + * @author 阿沐 babamu@126.com + * MAKU + */ +@Data +public class OperateLogDTO { + /** + * 用户ID + */ + private Long userId; + + /** + * 操作人 + */ + private String realName; + + /** + * 模块名 + */ + private String module; + + /** + * 操作名 + */ + private String name; + + /** + * 请求URI + */ + private String reqUri; + + /** + * 请求方法 + */ + private String reqMethod; + + /** + * 请求参数 + */ + private String reqParams; + + /** + * 操作IP + */ + private String ip; + + /** + * 登录地点 + */ + private String address; + + /** + * User Agent + */ + private String userAgent; + + /** + * 操作类型 + */ + private Integer operateType; + + /** + * 执行时长 + */ + private Integer duration; + + /** + * 操作状态 + */ + private Integer status; + + /** + * 返回消息 + */ + private String resultMsg; +} diff --git a/maku-framework/src/main/java/net/maku/framework/operatelog/enums/OperateTypeEnum.java b/maku-framework/src/main/java/net/maku/framework/operatelog/enums/OperateTypeEnum.java new file mode 100644 index 0000000..3897cd8 --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/operatelog/enums/OperateTypeEnum.java @@ -0,0 +1,45 @@ +package net.maku.framework.operatelog.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 操作类型 + * + * @author 阿沐 babamu@126.com + * MAKU + */ +@Getter +@AllArgsConstructor +public enum OperateTypeEnum { + /** + * 查询 + */ + GET(1), + /** + * 新增 + */ + INSERT(2), + /** + * 修改 + */ + UPDATE(3), + /** + * 删除 + */ + DELETE(4), + /** + * 导出 + */ + EXPORT(5), + /** + * 导入 + */ + IMPORT(6), + /** + * 其它 + */ + OTHER(0); + + private final int value; +} diff --git a/maku-framework/src/main/java/net/maku/framework/operatelog/service/OperateLogService.java b/maku-framework/src/main/java/net/maku/framework/operatelog/service/OperateLogService.java new file mode 100644 index 0000000..86d9d71 --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/operatelog/service/OperateLogService.java @@ -0,0 +1,28 @@ +package net.maku.framework.operatelog.service; + +import lombok.AllArgsConstructor; +import net.maku.framework.common.cache.RedisCache; +import net.maku.framework.common.cache.RedisKeys; +import net.maku.framework.operatelog.dto.OperateLogDTO; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +/** + * 操作日志服务 + * + * @author 阿沐 babamu@126.com + * MAKU + */ +@Service +@AllArgsConstructor +public class OperateLogService { + private final RedisCache redisCache; + + @Async + public void saveLog(OperateLogDTO log) { + String key = RedisKeys.getLogKey(); + + // 保存到Redis队列 + redisCache.leftPush(key, log, RedisCache.NOT_EXPIRE); + } +}