ソースを参照

新增登录日志,操作日志记录

xusl 2 年 前
コミット
9225883a25
21 ファイル変更1041 行追加1 行削除
  1. 6 0
      backend/pom.xml
  2. 48 0
      backend/src/main/java/com/jiayue/ssi/annotation/OperateLog.java
  3. 232 0
      backend/src/main/java/com/jiayue/ssi/aspectj/OperateLogAspect.java
  4. 20 0
      backend/src/main/java/com/jiayue/ssi/backenum/BusinessStatus.java
  5. 59 0
      backend/src/main/java/com/jiayue/ssi/backenum/BusinessType.java
  6. 37 0
      backend/src/main/java/com/jiayue/ssi/backenum/HttpMethod.java
  7. 24 0
      backend/src/main/java/com/jiayue/ssi/backenum/OperatorType.java
  8. 4 0
      backend/src/main/java/com/jiayue/ssi/controller/SysLogininforController.java
  9. 5 0
      backend/src/main/java/com/jiayue/ssi/controller/SysMenuController.java
  10. 6 0
      backend/src/main/java/com/jiayue/ssi/controller/SysParameterController.java
  11. 6 0
      backend/src/main/java/com/jiayue/ssi/controller/SysRoleController.java
  12. 7 0
      backend/src/main/java/com/jiayue/ssi/controller/SysUserController.java
  13. 2 0
      backend/src/main/java/com/jiayue/ssi/controller/UserLoginController.java
  14. 249 0
      backend/src/main/java/com/jiayue/ssi/entity/SysOperLog.java
  15. 28 0
      backend/src/main/java/com/jiayue/ssi/factory/OperateLogFactory.java
  16. 24 0
      backend/src/main/java/com/jiayue/ssi/filter/PropertyPreExcludeFilter.java
  17. 52 0
      backend/src/main/java/com/jiayue/ssi/mapper/SysOperLogMapper.java
  18. 51 0
      backend/src/main/java/com/jiayue/ssi/service/SysOperLogService.java
  19. 77 0
      backend/src/main/java/com/jiayue/ssi/service/impl/SysOperLogServiceImpl.java
  20. 14 1
      backend/src/main/java/com/jiayue/ssi/util/ServletUtils.java
  21. 90 0
      backend/src/main/resources/mapper/system/SysOperLogMapper.xml

+ 6 - 0
backend/pom.xml

@@ -214,6 +214,12 @@
             <artifactId>UserAgentUtils</artifactId>
             <version>1.21</version>
         </dependency>
+        <!-- 阿里JSON解析器 -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+            <version>2.0.23</version>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 48 - 0
backend/src/main/java/com/jiayue/ssi/annotation/OperateLog.java

@@ -0,0 +1,48 @@
+package com.jiayue.ssi.annotation;
+
+import com.jiayue.ssi.backenum.BusinessType;
+import com.jiayue.ssi.backenum.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义操作日志记录注解
+ *
+ * @author ruoyi
+ *
+ */
+@Target({ ElementType.PARAMETER, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface OperateLog
+{
+    /**
+     * 模块
+     */
+    public String title() default "";
+
+    /**
+     * 功能
+     */
+    public BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 操作人类别
+     */
+    public OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 是否保存请求的参数
+     */
+    public boolean isSaveRequestData() default true;
+
+    /**
+     * 是否保存响应的参数
+     */
+    public boolean isSaveResponseData() default true;
+
+    /**
+     * 排除指定的请求参数
+     */
+    public String[] excludeParamNames() default {};
+}

+ 232 - 0
backend/src/main/java/com/jiayue/ssi/aspectj/OperateLogAspect.java

@@ -0,0 +1,232 @@
+package com.jiayue.ssi.aspectj;
+
+import cn.hutool.json.JSONUtil;
+import com.jiayue.ssi.annotation.OperateLog;
+import com.jiayue.ssi.backenum.BusinessStatus;
+import com.jiayue.ssi.backenum.HttpMethod;
+import com.jiayue.ssi.entity.SysOperLog;
+import com.jiayue.ssi.entity.SysUser;
+import com.jiayue.ssi.factory.OperateLogFactory;
+import com.jiayue.ssi.filter.PropertyPreExcludeFilter;
+import com.jiayue.ssi.util.IPUtils;
+import com.jiayue.ssi.util.RyStringUtils;
+import com.jiayue.ssi.util.SecurityContextUtil;
+import com.jiayue.ssi.util.ServletUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.NamedThreadLocal;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+import com.alibaba.fastjson2.JSON;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 操作日志记录处理
+ *
+ * @author ruoyi
+ */
+@Aspect
+@Component
+public class OperateLogAspect {
+    private static final Logger log = LoggerFactory.getLogger(OperateLogAspect.class);
+
+    /**
+     * 排除敏感属性字段
+     */
+    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
+
+    /**
+     * 计算操作消耗时间
+     */
+    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
+
+    /**
+     * 处理请求前执行
+     */
+    @Before(value = "@annotation(controllerLog)")
+    public void boBefore(JoinPoint joinPoint, OperateLog controllerLog) {
+        TIME_THREADLOCAL.set(System.currentTimeMillis());
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, OperateLog controllerLog, Object jsonResult) {
+        handleLog(joinPoint, controllerLog, null, jsonResult);
+    }
+
+    /**
+     * 拦截异常操作
+     *
+     * @param joinPoint 切点
+     * @param e         异常
+     */
+    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, OperateLog controllerLog, Exception e) {
+        handleLog(joinPoint, controllerLog, e, null);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, OperateLog controllerLog, final Exception e, Object jsonResult) {
+        try {
+            // 获取当前的用户
+            SysUser sysUser = SecurityContextUtil.getSysUser();
+
+            // *========数据库日志=========*//
+            SysOperLog operLog = new SysOperLog();
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 请求的地址
+            String ip = IPUtils.getIpAddr();
+            operLog.setOperIp(ip);
+            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
+            if (sysUser != null) {
+                operLog.setOperName(sysUser.getUsername());
+            }
+
+            if (e != null) {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 设置方法名称
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 设置请求方式
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 处理设置注解上的参数
+            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
+            // 设置消耗时间
+            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
+            // 保存数据库
+            OperateLogFactory.recordOper(operLog);
+        } catch (Exception exp) {
+            // 记录本地异常日志
+            log.error("异常信息:{}", exp.getMessage());
+            exp.printStackTrace();
+        } finally {
+            TIME_THREADLOCAL.remove();
+        }
+    }
+
+    /**
+     * 获取注解中对方法的描述信息 用于Controller层注解
+     *
+     * @param log     日志
+     * @param operLog 操作日志
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(JoinPoint joinPoint, OperateLog log, SysOperLog operLog, Object jsonResult) throws Exception {
+        // 设置action动作
+        operLog.setBusinessType(log.businessType().ordinal());
+        // 设置标题
+        operLog.setTitle(log.title());
+        // 设置操作人类别
+        operLog.setOperatorType(log.operatorType().ordinal());
+        // 是否需要保存request,参数和值
+        if (log.isSaveRequestData()) {
+            // 获取参数的信息,传入到数据库中。
+            setRequestValue(joinPoint, operLog, log.excludeParamNames());
+        }
+        // 是否需要保存response,参数和值
+        if (log.isSaveResponseData() && RyStringUtils.isNotNull(jsonResult)) {
+            operLog.setJsonResult(RyStringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
+        }
+    }
+
+    /**
+     * 获取请求的参数,放到log中
+     *
+     * @param operLog 操作日志
+     * @throws Exception 异常
+     */
+    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception {
+        String requestMethod = operLog.getRequestMethod();
+        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)|| HttpMethod.DELETE.name().equals(requestMethod)) {
+            String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames,ServletUtils.getRequest());
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+        } else {
+            Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
+            operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
+        }
+    }
+
+    /**
+     * 参数拼装
+     */
+    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames, ServletRequest request) {
+        Map<String,String> map = new HashMap<>(16);
+        Enumeration enumeration=request.getParameterNames();
+        while(enumeration.hasMoreElements()){
+            String name=(String)enumeration.nextElement();//得到name的名字。
+//            System.out.print("属性"+name);
+            String value=request.getParameter(name);//是通过页面中的name属性得到值。
+//            System.out.println(",值:"+value);
+            map.put(name,value);
+        }
+        return JSONUtil.parseObj(map).toString();
+//        String params = "";
+//        if (paramsArray != null && paramsArray.length > 0) {
+//            for (Object o : paramsArray) {
+//                if (RyStringUtils.isNotNull(o) && !isFilterObject(o)) {
+//                    try {
+//                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
+//                        params += jsonObj.toString() + " ";
+//                    } catch (Exception e) {
+//                    }
+//                }
+//            }
+//        }
+//        return params.trim();
+    }
+
+    /**
+     * 忽略敏感属性
+     */
+    public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) {
+        return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
+    }
+
+    /**
+     * 判断是否需要过滤的对象。
+     *
+     * @param o 对象信息。
+     * @return 如果是需要过滤的对象,则返回true;否则返回false。
+     */
+    @SuppressWarnings("rawtypes")
+    public boolean isFilterObject(final Object o) {
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray()) {
+            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
+        } else if (Collection.class.isAssignableFrom(clazz)) {
+            Collection collection = (Collection) o;
+            for (Object value : collection) {
+                return value instanceof MultipartFile;
+            }
+        } else 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;
+    }
+}

+ 20 - 0
backend/src/main/java/com/jiayue/ssi/backenum/BusinessStatus.java

@@ -0,0 +1,20 @@
+package com.jiayue.ssi.backenum;
+
+/**
+ * 操作状态
+ *
+ * @author ruoyi
+ *
+ */
+public enum BusinessStatus
+{
+    /**
+     * 成功
+     */
+    SUCCESS,
+
+    /**
+     * 失败
+     */
+    FAIL,
+}

+ 59 - 0
backend/src/main/java/com/jiayue/ssi/backenum/BusinessType.java

@@ -0,0 +1,59 @@
+package com.jiayue.ssi.backenum;
+
+/**
+ * 业务操作类型
+ *
+ * @author ruoyi
+ */
+public enum BusinessType
+{
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 新增
+     */
+    INSERT,
+
+    /**
+     * 修改
+     */
+    UPDATE,
+
+    /**
+     * 删除
+     */
+    DELETE,
+
+    /**
+     * 授权
+     */
+    GRANT,
+
+    /**
+     * 导出
+     */
+    EXPORT,
+
+    /**
+     * 导入
+     */
+    IMPORT,
+
+    /**
+     * 强退
+     */
+    FORCE,
+
+    /**
+     * 生成代码
+     */
+    GENCODE,
+
+    /**
+     * 清空数据
+     */
+    CLEAN,
+}

+ 37 - 0
backend/src/main/java/com/jiayue/ssi/backenum/HttpMethod.java

@@ -0,0 +1,37 @@
+package com.jiayue.ssi.backenum;
+
+import org.springframework.lang.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 请求方式
+ *
+ * @author ruoyi
+ */
+public enum HttpMethod
+{
+    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
+
+    private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
+
+    static
+    {
+        for (HttpMethod httpMethod : values())
+        {
+            mappings.put(httpMethod.name(), httpMethod);
+        }
+    }
+
+    @Nullable
+    public static HttpMethod resolve(@Nullable String method)
+    {
+        return (method != null ? mappings.get(method) : null);
+    }
+
+    public boolean matches(String method)
+    {
+        return (this == resolve(method));
+    }
+}

+ 24 - 0
backend/src/main/java/com/jiayue/ssi/backenum/OperatorType.java

@@ -0,0 +1,24 @@
+package com.jiayue.ssi.backenum;
+
+/**
+ * 操作人类别
+ *
+ * @author ruoyi
+ */
+public enum OperatorType
+{
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 后台用户
+     */
+    MANAGE,
+
+    /**
+     * 手机端用户
+     */
+    MOBILE
+}

+ 4 - 0
backend/src/main/java/com/jiayue/ssi/controller/SysLogininforController.java

@@ -4,6 +4,8 @@ import cn.hutool.core.date.DateUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jiayue.ssi.annotation.InterfaceLimit;
+import com.jiayue.ssi.annotation.OperateLog;
+import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.entity.SysLogininfor;
 import com.jiayue.ssi.service.SysLogininforService;
 import com.jiayue.ssi.util.DateUtils;
@@ -66,6 +68,7 @@ public class SysLogininforController {
      */
     @PostMapping(value = "/delLoginInfo")
     @InterfaceLimit
+    @OperateLog(title = "登录日志", businessType = BusinessType.DELETE)
     public ResponseVO delLoginInfo(String infoId) {
         if (StringUtils.isEmpty(infoId)) {
             return ResponseVO.fail("id不能为空!");
@@ -89,6 +92,7 @@ public class SysLogininforController {
      */
     @PostMapping("/cleanLogininfor")
     @InterfaceLimit
+    @OperateLog(title = "登录日志", businessType = BusinessType.CLEAN)
     public ResponseVO cleanLogininfor() {
         try {
             boolean bo = sysLogininforService.cleanLogininfor();

+ 5 - 0
backend/src/main/java/com/jiayue/ssi/controller/SysMenuController.java

@@ -5,6 +5,8 @@ import java.util.List;
 import java.util.Map;
 
 import cn.hutool.core.util.NumberUtil;
+import com.jiayue.ssi.annotation.OperateLog;
+import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.constant.UserConstants;
 import com.jiayue.ssi.entity.SysMenu;
 import com.jiayue.ssi.service.SysMenuService;
@@ -52,6 +54,7 @@ public class SysMenuController {
      */
     @PostMapping
     @InterfaceLimit
+    @OperateLog(title = "菜单管理", businessType = BusinessType.INSERT)
     public ResponseVO add(@RequestBody SysMenu menu) {
         if (RyStringUtils.isEmpty(menu.getMenuName())) {
             return ResponseVO.fail("菜单名称不能为空!");
@@ -114,6 +117,7 @@ public class SysMenuController {
      */
     @PutMapping
     @InterfaceLimit
+    @OperateLog(title = "菜单管理", businessType = BusinessType.UPDATE)
     public ResponseVO update(@RequestBody SysMenu menu) {
         if (menu.getMenuId() == null) {
             return ResponseVO.fail("主键为空不能修改!");
@@ -195,6 +199,7 @@ public class SysMenuController {
      */
     @DeleteMapping
     @InterfaceLimit
+    @OperateLog(title = "菜单管理", businessType = BusinessType.DELETE)
     public ResponseVO delete(String menuId) {
         if (org.apache.commons.lang3.StringUtils.isEmpty(menuId)) {
             return ResponseVO.fail("删除菜单的id不能为空!");

+ 6 - 0
backend/src/main/java/com/jiayue/ssi/controller/SysParameterController.java

@@ -3,6 +3,8 @@ package com.jiayue.ssi.controller;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jiayue.ssi.annotation.InterfaceLimit;
+import com.jiayue.ssi.annotation.OperateLog;
+import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.entity.SysParameter;
 import com.jiayue.ssi.service.SysParameterService;
@@ -34,6 +36,7 @@ public class SysParameterController {
      */
     @InterfaceLimit
     @PostMapping()
+    @OperateLog(title = "参数管理", businessType = BusinessType.INSERT)
     public ResponseVO add(@RequestBody SysParameter sysParameter) {
         if (StringUtils.isEmpty(sysParameter.getSysKey())) {
             return ResponseVO.fail("参数名不能为空!");
@@ -79,7 +82,9 @@ public class SysParameterController {
      * @param sysParameter 参数
      * @return 执行结果
      */
+    @InterfaceLimit
     @PutMapping
+    @OperateLog(title = "参数管理", businessType = BusinessType.UPDATE)
     public ResponseVO update(@RequestBody SysParameter sysParameter) {
         SysParameter existSysParameter = sysParameterService.getById(sysParameter.getId());
         if (existSysParameter == null) {
@@ -131,6 +136,7 @@ public class SysParameterController {
      */
     @DeleteMapping
     @InterfaceLimit
+    @OperateLog(title = "参数管理", businessType = BusinessType.DELETE)
     public ResponseVO delete(String id) {
         if (StringUtils.isEmpty(id)) {
             return ResponseVO.fail("id不能为空!");

+ 6 - 0
backend/src/main/java/com/jiayue/ssi/controller/SysRoleController.java

@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jiayue.ssi.annotation.InterfaceLimit;
 
+import com.jiayue.ssi.annotation.OperateLog;
+import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.entity.SysParameter;
 import com.jiayue.ssi.entity.SysRole;
 
@@ -83,6 +85,7 @@ public class SysRoleController {
      */
     @InterfaceLimit
     @PostMapping()
+    @OperateLog(title = "角色管理", businessType = BusinessType.INSERT)
     public ResponseVO add(@RequestBody SysRole role) {
         try {
             ///////加校验/////
@@ -107,7 +110,9 @@ public class SysRoleController {
      * @param role 参数
      * @return 执行结果
      */
+    @InterfaceLimit
     @PutMapping
+    @OperateLog(title = "角色管理", businessType = BusinessType.UPDATE)
     public ResponseVO update(@RequestBody SysRole role) {
         SysRole existRole = roleService.getById(role.getRoleId());
         if (existRole == null) {
@@ -141,6 +146,7 @@ public class SysRoleController {
      */
     @PostMapping(value = "/delRole")
     @InterfaceLimit
+    @OperateLog(title = "角色管理", businessType = BusinessType.DELETE)
     public ResponseVO delRole(String roleId) {
         if (StringUtils.isEmpty(roleId)) {
             return ResponseVO.fail("id不能为空!");

+ 7 - 0
backend/src/main/java/com/jiayue/ssi/controller/SysUserController.java

@@ -5,6 +5,8 @@ import cn.hutool.crypto.SmUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.jiayue.ssi.annotation.InterfaceLimit;
+import com.jiayue.ssi.annotation.OperateLog;
+import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.config.SendMailUtil;
 import com.jiayue.ssi.entity.SysUser;
 import com.jiayue.ssi.service.SysUserService;
@@ -82,6 +84,7 @@ public class SysUserController {
      */
     @PostMapping
     @InterfaceLimit
+    @OperateLog(title = "用户管理", businessType = BusinessType.INSERT)
     public ResponseVO add(@RequestBody SysUser user) {
         if (StringUtils.isEmpty(user.getUsername())) {
             return ResponseVO.fail("用户账号不能为空!");
@@ -141,6 +144,7 @@ public class SysUserController {
      * @return 执行结果
      */
     @PutMapping
+    @OperateLog(title = "用户管理", businessType = BusinessType.UPDATE)
     public ResponseVO update(@RequestBody SysUser user) {
         SysUser existUser = sysUserService.getById(user.getId());
         if (existUser == null) {
@@ -205,6 +209,7 @@ public class SysUserController {
      */
     @PostMapping(value = "/resetPassword")
     @InterfaceLimit
+    @OperateLog(title = "用户管理", businessType = BusinessType.OTHER)
     public ResponseVO resetPassword(String id) {
         if (StringUtils.isEmpty(id)) {
             return ResponseVO.fail("重置密码缺失id!");
@@ -234,6 +239,7 @@ public class SysUserController {
      */
     @PostMapping(value = "/delUser")
     @InterfaceLimit
+    @OperateLog(title = "用户管理", businessType = BusinessType.DELETE)
     public ResponseVO delete(String id) {
         if (StringUtils.isEmpty(id)) {
             return ResponseVO.fail("id不能为空!");
@@ -258,6 +264,7 @@ public class SysUserController {
      */
     @PostMapping(value = "/updatePassword")
     @InterfaceLimit
+    @OperateLog(title = "用户管理", businessType = BusinessType.UPDATE)
     public ResponseVO updatePassword(String id, String oldPassword, String newPassword, String confirmPassword) {
         if (StringUtils.isEmpty(id)) {
             return ResponseVO.fail("修改密码缺失id!");

+ 2 - 0
backend/src/main/java/com/jiayue/ssi/controller/UserLoginController.java

@@ -3,6 +3,8 @@ package com.jiayue.ssi.controller;
 import cn.hutool.captcha.CaptchaUtil;
 import cn.hutool.captcha.CircleCaptcha;
 import com.jiayue.ssi.annotation.InterfaceLimit;
+import com.jiayue.ssi.annotation.OperateLog;
+import com.jiayue.ssi.backenum.BusinessType;
 import com.jiayue.ssi.config.SendMailUtil;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.entity.SysMenu;

+ 249 - 0
backend/src/main/java/com/jiayue/ssi/entity/SysOperLog.java

@@ -0,0 +1,249 @@
+package com.jiayue.ssi.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.util.Date;
+
+/**
+ * 操作日志记录表 oper_log
+ *
+ * @author ruoyi
+ */
+public class SysOperLog extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /** 日志主键 */
+    private Long operId;
+
+    /** 操作模块 */
+    private String title;
+
+    /** 业务类型(0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据) */
+    private Integer businessType;
+
+    /** 业务类型数组 */
+    private Integer[] businessTypes;
+
+    /** 请求方法 */
+    private String method;
+
+    /** 请求方式 */
+    private String requestMethod;
+
+    /** 操作类别(0其它 1后台用户 2手机端用户) */
+    private Integer operatorType;
+
+    /** 操作人员 */
+    private String operName;
+
+    /** 部门名称 */
+    private String deptName;
+
+    /** 请求url */
+    private String operUrl;
+
+    /** 操作地址 */
+    private String operIp;
+
+    /** 操作地点 */
+    private String operLocation;
+
+    /** 请求参数 */
+    private String operParam;
+
+    /** 返回参数 */
+    private String jsonResult;
+
+    /** 操作状态(0正常 1异常) */
+    private Integer status;
+
+    /** 错误消息 */
+    private String errorMsg;
+
+    /** 操作时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date operTime;
+
+    /** 消耗时间 毫秒*/
+    private Long costTime;
+
+    public Long getOperId()
+    {
+        return operId;
+    }
+
+    public void setOperId(Long operId)
+    {
+        this.operId = operId;
+    }
+
+    public String getTitle()
+    {
+        return title;
+    }
+
+    public void setTitle(String title)
+    {
+        this.title = title;
+    }
+
+    public Integer getBusinessType()
+    {
+        return businessType;
+    }
+
+    public void setBusinessType(Integer businessType)
+    {
+        this.businessType = businessType;
+    }
+
+    public Integer[] getBusinessTypes()
+    {
+        return businessTypes;
+    }
+
+    public void setBusinessTypes(Integer[] businessTypes)
+    {
+        this.businessTypes = businessTypes;
+    }
+
+    public String getMethod()
+    {
+        return method;
+    }
+
+    public void setMethod(String method)
+    {
+        this.method = method;
+    }
+
+    public String getRequestMethod()
+    {
+        return requestMethod;
+    }
+
+    public void setRequestMethod(String requestMethod)
+    {
+        this.requestMethod = requestMethod;
+    }
+
+    public Integer getOperatorType()
+    {
+        return operatorType;
+    }
+
+    public void setOperatorType(Integer operatorType)
+    {
+        this.operatorType = operatorType;
+    }
+
+    public String getOperName()
+    {
+        return operName;
+    }
+
+    public void setOperName(String operName)
+    {
+        this.operName = operName;
+    }
+
+    public String getDeptName()
+    {
+        return deptName;
+    }
+
+    public void setDeptName(String deptName)
+    {
+        this.deptName = deptName;
+    }
+
+    public String getOperUrl()
+    {
+        return operUrl;
+    }
+
+    public void setOperUrl(String operUrl)
+    {
+        this.operUrl = operUrl;
+    }
+
+    public String getOperIp()
+    {
+        return operIp;
+    }
+
+    public void setOperIp(String operIp)
+    {
+        this.operIp = operIp;
+    }
+
+    public String getOperLocation()
+    {
+        return operLocation;
+    }
+
+    public void setOperLocation(String operLocation)
+    {
+        this.operLocation = operLocation;
+    }
+
+    public String getOperParam()
+    {
+        return operParam;
+    }
+
+    public void setOperParam(String operParam)
+    {
+        this.operParam = operParam;
+    }
+
+    public String getJsonResult()
+    {
+        return jsonResult;
+    }
+
+    public void setJsonResult(String jsonResult)
+    {
+        this.jsonResult = jsonResult;
+    }
+
+    public Integer getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(Integer status)
+    {
+        this.status = status;
+    }
+
+    public String getErrorMsg()
+    {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg)
+    {
+        this.errorMsg = errorMsg;
+    }
+
+    public Date getOperTime()
+    {
+        return operTime;
+    }
+
+    public void setOperTime(Date operTime)
+    {
+        this.operTime = operTime;
+    }
+
+    public Long getCostTime()
+    {
+        return costTime;
+    }
+
+    public void setCostTime(Long costTime)
+    {
+        this.costTime = costTime;
+    }
+}

+ 28 - 0
backend/src/main/java/com/jiayue/ssi/factory/OperateLogFactory.java

@@ -0,0 +1,28 @@
+package com.jiayue.ssi.factory;
+
+import com.jiayue.ssi.entity.SysOperLog;
+import com.jiayue.ssi.service.SysOperLogService;
+import com.jiayue.ssi.util.AddressUtils;
+import com.jiayue.ssi.util.SpringUtils;
+
+import java.util.TimerTask;
+
+/**
+ * 操作日志工厂
+ *
+ * @author xsl
+ * @since 2023/03/30
+ */
+public class OperateLogFactory {
+    /**
+     * 操作日志记录
+     *
+     * @param operLog 操作日志信息
+     * @return
+     */
+    public static void recordOper(final SysOperLog operLog) {
+        // 远程查询操作地点
+        operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+        SpringUtils.getBean(SysOperLogService.class).insertOperlog(operLog);
+    }
+}

+ 24 - 0
backend/src/main/java/com/jiayue/ssi/filter/PropertyPreExcludeFilter.java

@@ -0,0 +1,24 @@
+package com.jiayue.ssi.filter;
+
+import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
+
+/**
+ * 排除JSON敏感属性
+ *
+ * @author ruoyi
+ */
+public class PropertyPreExcludeFilter extends SimplePropertyPreFilter
+{
+    public PropertyPreExcludeFilter()
+    {
+    }
+
+    public PropertyPreExcludeFilter addExcludes(String... filters)
+    {
+        for (int i = 0; i < filters.length; i++)
+        {
+            this.getExcludes().add(filters[i]);
+        }
+        return this;
+    }
+}

+ 52 - 0
backend/src/main/java/com/jiayue/ssi/mapper/SysOperLogMapper.java

@@ -0,0 +1,52 @@
+package com.jiayue.ssi.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.jiayue.ssi.entity.SysLogininfor;
+import com.jiayue.ssi.entity.SysOperLog;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 操作日志 数据层
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface SysOperLogMapper extends BaseMapper<SysOperLog> {
+    /**
+     * 新增操作日志
+     *
+     * @param operLog 操作日志对象
+     */
+    public void insertOperlog(SysOperLog operLog);
+
+    /**
+     * 查询系统操作日志集合
+     *
+     * @param operLog 操作日志对象
+     * @return 操作日志集合
+     */
+    public List<SysOperLog> selectOperLogList(SysOperLog operLog);
+
+    /**
+     * 批量删除系统操作日志
+     *
+     * @param operIds 需要删除的操作日志ID
+     * @return 结果
+     */
+    public int deleteOperLogByIds(Long[] operIds);
+
+    /**
+     * 查询操作日志详细
+     *
+     * @param operId 操作ID
+     * @return 操作日志对象
+     */
+    public SysOperLog selectOperLogById(Long operId);
+
+    /**
+     * 清空操作日志
+     */
+    public void cleanOperLog();
+}

+ 51 - 0
backend/src/main/java/com/jiayue/ssi/service/SysOperLogService.java

@@ -0,0 +1,51 @@
+package com.jiayue.ssi.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.jiayue.ssi.entity.SysMenu;
+import com.jiayue.ssi.entity.SysOperLog;
+
+import java.util.List;
+
+/**
+ * 操作日志 服务层
+ *
+ * @author ruoyi
+ */
+public interface SysOperLogService extends IService<SysOperLog> {
+    /**
+     * 新增操作日志
+     *
+     * @param operLog 操作日志对象
+     */
+    void insertOperlog(SysOperLog operLog);
+//
+//    /**
+//     * 查询系统操作日志集合
+//     *
+//     * @param operLog 操作日志对象
+//     * @return 操作日志集合
+//     */
+//    public List<SysOperLog> selectOperLogList(SysOperLog operLog);
+//
+//    /**
+//     * 批量删除系统操作日志
+//     *
+//     * @param operIds 需要删除的操作日志ID
+//     * @return 结果
+//     */
+//    public int deleteOperLogByIds(Long[] operIds);
+//
+//    /**
+//     * 查询操作日志详细
+//     *
+//     * @param operId 操作ID
+//     * @return 操作日志对象
+//     */
+//    public SysOperLog selectOperLogById(Long operId);
+//
+//    /**
+//     * 清空操作日志
+//     */
+//    public void cleanOperLog();
+}

+ 77 - 0
backend/src/main/java/com/jiayue/ssi/service/impl/SysOperLogServiceImpl.java

@@ -0,0 +1,77 @@
+package com.jiayue.ssi.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.jiayue.ssi.entity.SysOperLog;
+import com.jiayue.ssi.mapper.SysOperLogMapper;
+import com.jiayue.ssi.service.SysOperLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 操作日志 服务层处理
+ *
+ * @author ruoyi
+ */
+@Service
+public class SysOperLogServiceImpl  extends ServiceImpl<SysOperLogMapper, SysOperLog> implements SysOperLogService {
+    @Autowired
+    private SysOperLogMapper operLogMapper;
+
+    /**
+     * 新增操作日志
+     *
+     * @param operLog 操作日志对象
+     */
+    @Override
+    public void insertOperlog(SysOperLog operLog)
+    {
+        operLogMapper.insertOperlog(operLog);
+    }
+
+//    /**
+//     * 查询系统操作日志集合
+//     *
+//     * @param operLog 操作日志对象
+//     * @return 操作日志集合
+//     */
+//    @Override
+//    public List<SysOperLog> selectOperLogList(SysOperLog operLog)
+//    {
+//        return operLogMapper.selectOperLogList(operLog);
+//    }
+//
+//    /**
+//     * 批量删除系统操作日志
+//     *
+//     * @param operIds 需要删除的操作日志ID
+//     * @return 结果
+//     */
+//    @Override
+//    public int deleteOperLogByIds(Long[] operIds)
+//    {
+//        return operLogMapper.deleteOperLogByIds(operIds);
+//    }
+//
+//    /**
+//     * 查询操作日志详细
+//     *
+//     * @param operId 操作ID
+//     * @return 操作日志对象
+//     */
+//    @Override
+//    public SysOperLog selectOperLogById(Long operId)
+//    {
+//        return operLogMapper.selectOperLogById(operId);
+//    }
+//
+//    /**
+//     * 清空操作日志
+//     */
+//    @Override
+//    public void cleanOperLog()
+//    {
+//        operLogMapper.cleanOperLog();
+//    }
+}

+ 14 - 1
backend/src/main/java/com/jiayue/ssi/util/ServletUtils.java

@@ -15,6 +15,7 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
 import java.util.Collections;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -81,7 +82,19 @@ public class ServletUtils
      */
     public static Map<String, String[]> getParams(ServletRequest request)
     {
-        final Map<String, String[]> map = request.getParameterMap();
+        Map<String, String[]> map = new HashMap<>();
+        Enumeration enumeration=request.getParameterNames();
+        while(enumeration.hasMoreElements()){
+            String name=(String)enumeration.nextElement();//得到name的名字。
+            if (!"0".equals(name)){
+                System.out.print("属性"+name);
+                String value=request.getParameter(name);//是通过页面中的name属性得到值。
+                System.out.println(",值:"+value);
+                String[] strArray = {value};
+                map.put(name,strArray);
+            }
+        }
+
         return Collections.unmodifiableMap(map);
     }
 

+ 90 - 0
backend/src/main/resources/mapper/system/SysOperLogMapper.xml

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jiayue.ssi.mapper.SysOperLogMapper">
+
+	<resultMap type="SysOperLog" id="SysOperLogResult">
+		<id     property="operId"         column="oper_id"        />
+		<result property="title"          column="title"          />
+		<result property="businessType"   column="business_type"  />
+		<result property="method"         column="method"         />
+		<result property="requestMethod"  column="request_method" />
+		<result property="operatorType"   column="operator_type"  />
+		<result property="operName"       column="oper_name"      />
+		<result property="deptName"       column="dept_name"      />
+		<result property="operUrl"        column="oper_url"       />
+		<result property="operIp"         column="oper_ip"        />
+		<result property="operLocation"   column="oper_location"  />
+		<result property="operParam"      column="oper_param"     />
+		<result property="jsonResult"     column="json_result"    />
+		<result property="status"         column="status"         />
+		<result property="errorMsg"       column="error_msg"      />
+		<result property="operTime"       column="oper_time"      />
+		<result property="costTime"       column="cost_time"      />
+		<result property="createBy"       column="create_by"      />
+		<result property="createTime"     column="create_time"    />
+		<result property="updateTime"     column="update_time"    />
+		<result property="updateBy"       column="update_by"      />
+		<result property="remark"         column="remark"         />
+		<result property="delFlag"         column="del_flag"         />
+	</resultMap>
+
+	<sql id="selectOperLogVo">
+        select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time
+        from sys_oper_log
+    </sql>
+
+	<insert id="insertOperlog" parameterType="SysOperLog">
+		insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time)
+        values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate())
+	</insert>
+
+	<select id="selectOperLogList" parameterType="SysOperLog" resultMap="SysOperLogResult">
+		<include refid="selectOperLogVo"/>
+		<where>
+			<if test="title != null and title != ''">
+				AND title like concat('%', #{title}, '%')
+			</if>
+			<if test="businessType != null">
+				AND business_type = #{businessType}
+			</if>
+			<if test="businessTypes != null and businessTypes.length > 0">
+			    AND business_type in
+			    <foreach collection="businessTypes" item="businessType" open="(" separator="," close=")">
+		 			#{businessType}
+		        </foreach>
+			</if>
+			<if test="status != null">
+				AND status = #{status}
+			</if>
+			<if test="operName != null and operName != ''">
+				AND oper_name like concat('%', #{operName}, '%')
+			</if>
+			<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+				and date_format(oper_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
+			</if>
+			<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+				and date_format(oper_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
+			</if>
+		</where>
+		order by oper_id desc
+	</select>
+
+	<delete id="deleteOperLogByIds" parameterType="Long">
+ 		delete from sys_oper_log where oper_id in
+ 		<foreach collection="array" item="operId" open="(" separator="," close=")">
+ 			#{operId}
+        </foreach>
+ 	</delete>
+
+ 	<select id="selectOperLogById" parameterType="Long" resultMap="SysOperLogResult">
+		<include refid="selectOperLogVo"/>
+		where oper_id = #{operId}
+	</select>
+
+	<update id="cleanOperLog">
+        truncate table sys_oper_log
+    </update>
+
+</mapper>