소스 검색

feat:操作日志记录功能完成

wangzaijun 7 달 전
부모
커밋
ed43a1a553
32개의 변경된 파일862개의 추가작업 그리고 76개의 파일을 삭제
  1. 27 3
      readme.md
  2. 3 19
      service-base/src/main/java/com/simuwang/base/common/support/dos/BaseEntity.java
  3. 36 0
      service-base/src/main/java/com/simuwang/base/common/support/dos/DataEntity.java
  4. 38 0
      service-base/src/main/java/com/simuwang/base/components/MyBatisPlusMetaObjectHandler.java
  5. 7 1
      service-base/src/main/java/com/simuwang/base/config/DaqProperties.java
  6. 12 6
      service-base/src/main/java/com/simuwang/base/config/DataSourceAutoConfig.java
  7. 0 20
      service-base/src/main/java/com/simuwang/base/config/MybatisPlusPageInterceptor.java
  8. 9 0
      service-base/src/main/java/com/simuwang/base/mapper/system/SysLogMapper.java
  9. 44 0
      service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysLogDO.java
  10. 2 2
      service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysMenuDO.java
  11. 2 2
      service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysRoleDO.java
  12. 2 2
      service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysUserDO.java
  13. 22 0
      service-base/src/main/java/com/simuwang/base/pojo/dto/sys/LogQuery.java
  14. 21 0
      service-base/src/main/java/com/simuwang/base/pojo/vo/sys/SysLogVO.java
  15. 193 0
      service-base/src/main/java/com/simuwang/logging/Logging.java
  16. 10 0
      service-base/src/main/java/com/simuwang/logging/LoggingService.java
  17. 82 0
      service-base/src/main/java/com/simuwang/logging/SystemLog.java
  18. 40 0
      service-base/src/main/java/com/simuwang/shiro/utils/UserUtils.java
  19. 3 4
      service-deploy/src/main/java/com/simuwang/deploy/components/ApiAop.java
  20. 106 0
      service-deploy/src/main/java/com/simuwang/deploy/components/LoggingAspect.java
  21. 3 1
      service-deploy/src/main/java/com/simuwang/deploy/config/GlobalErrorController.java
  22. 18 1
      service-deploy/src/main/java/com/simuwang/deploy/config/WebConfig.java
  23. 2 0
      service-deploy/src/main/resources/application.yml
  24. 15 10
      service-manage/src/main/java/com/simuwang/manage/api/LoginController.java
  25. 49 0
      service-manage/src/main/java/com/simuwang/manage/api/system/SysLogController.java
  26. 7 0
      service-manage/src/main/java/com/simuwang/manage/api/system/SysMenuController.java
  27. 8 0
      service-manage/src/main/java/com/simuwang/manage/api/system/SysRoleController.java
  28. 8 0
      service-manage/src/main/java/com/simuwang/manage/api/system/SysUserController.java
  29. 3 3
      service-manage/src/main/java/com/simuwang/manage/service/LoginService.java
  30. 68 0
      service-manage/src/main/java/com/simuwang/manage/service/impl/system/SysLogServiceImpl.java
  31. 2 2
      service-manage/src/main/java/com/simuwang/manage/service/impl/system/SysMenuServiceImpl.java
  32. 20 0
      service-manage/src/main/java/com/simuwang/manage/service/system/SysLogService.java

+ 27 - 3
readme.md

@@ -34,7 +34,8 @@
 - [x] shiro 基于jwt的登录认证+授权
 - [x] 自定义基于rsa的密码匹配器 `ShiroRsaCredentialsMatcher`
 - [x] 可实现的用户信息适配器 `LoginAuthAdapter`
-- [ ] 用户信息接入数据库数据
+- [x] 用户信息接入数据库数据
+- [x] 系统管理-用户、角色、菜单和操作日志
 
 
 ### FAQ
@@ -49,7 +50,30 @@
 </dependency>
 ```
 > 封装了一些常用对象、工具类和指标计算方法
->
 
 2. 部分依赖不可删除,删除就报错,比如joda-time
-> data-calc包目前有强依赖的第三方包,待优化后可控制
+> data-calc包目前有强依赖的第三方包,待优化后可控制
+
+3. 关于全局统一处理的说明
+> 全局统一处理是要求所有接口必须返回一样的数据结构,目前在系统中定义为
+```json
+{
+    "code": 2000,
+    "msg": "",
+    "data": null
+}
+```
+> 此结构中的data属性就是目前成功返回的数据对象,统一的对象结构为 `com.smppw.common.pojo.ResultVo`。
+> 
+> 约定:当接口返回结果是String类型时把返回字符串作为上述结构的data,当接口返回`com.smppw.common.pojo.ResultVo`时直接返回该对象;
+> 其他属性都用此对象的data接收
+
+4. 关于操作日志记录
+> 系统中定义了注解`SystemLog`来标记哪些接口需要记录用户操作,该注解必须针对方法标记,并且无法标记多种操作;当标记在类上时,仅取value字段作为上级模块名
+
+5. 关于封装的公共方法和抽象对象包`com.simuwang.base.common.support.*`的说明
+> 系统定义数据库操作数据抽象类 `BaseEntity<?>`,基础查询请求参数抽象类 `BaseQuery`,基础视图对象 `BaseVO`,一系列支持新增和修改请求的抽象类 `*Cmd`
+> 和抽象的service方法 `IService`;包括常见的单表分页和自己实现的多表关联的分页案例。
+> 
+> 其中 BaseEntity对象可以转换为BaseVO对象,只需要实现其中的toVo方法即可。详情参考 `SysUserController`类的使用方法
+

+ 3 - 19
service-base/src/main/java/com/simuwang/base/common/support/dos/BaseEntity.java

@@ -1,7 +1,7 @@
 package com.simuwang.base.common.support.dos;
 
+import com.baomidou.mybatisplus.annotation.FieldFill;
 import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.simuwang.base.common.conts.Constants;
 import com.simuwang.base.common.support.vo.BaseVO;
 import lombok.Getter;
@@ -24,29 +24,13 @@ public abstract class BaseEntity<VO extends BaseVO> implements Serializable {
     /**
      * 创建人
      */
-    @TableField("creatorid")
+    @TableField(value = "creatorid", fill = FieldFill.INSERT)
     private Integer creatorId;
     /**
      * 创建时间
      */
-    @TableField("createtime")
+    @TableField(value = "createtime", fill = FieldFill.INSERT)
     private Date createTime;
-    /**
-     * 更新人
-     */
-    @TableField("updaterid")
-    private Integer updaterId;
-    /**
-     * 更新时间
-     */
-    @TableField("updatetime")
-    private Date updateTime;
-    /**
-     * 是否有效标识
-     */
-    @TableLogic(value = "1", delval = "0")
-    @TableField("isvalid")
-    private Integer valid;
 
     /**
      * 当前数据库对象转vo对象,尽量用属性复制的方法,少用反射

+ 36 - 0
service-base/src/main/java/com/simuwang/base/common/support/dos/DataEntity.java

@@ -0,0 +1,36 @@
+package com.simuwang.base.common.support.dos;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.simuwang.base.common.support.vo.BaseVO;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * @author wangzaijun
+ * @date 2023/11/7 17:23
+ * @description base数据模型
+ */
+@Setter
+@Getter
+public abstract class DataEntity<VO extends BaseVO> extends BaseEntity<VO> {
+    /**
+     * 更新人
+     */
+    @TableField(value = "updaterid", fill = FieldFill.INSERT_UPDATE)
+    private Integer updaterId;
+    /**
+     * 更新时间
+     */
+    @TableField(value = "updatetime", fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+    /**
+     * 是否有效标识
+     */
+    @TableLogic(value = "1", delval = "0")
+    @TableField(value = "isvalid", fill = FieldFill.INSERT)
+    private Integer valid;
+}

+ 38 - 0
service-base/src/main/java/com/simuwang/base/components/MyBatisPlusMetaObjectHandler.java

@@ -0,0 +1,38 @@
+package com.simuwang.base.components;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.simuwang.shiro.core.ShiroUser;
+import com.simuwang.shiro.utils.UserUtils;
+import org.apache.ibatis.reflection.MetaObject;
+
+import java.util.Date;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/14 15:58
+ * @description 默认的字段填充
+ */
+public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        ShiroUser loginUser = UserUtils.getLoginUser();
+        this.setFieldValByName("creatorId", loginUser.getUserId(), metaObject);
+        this.strictInsertFill(metaObject, "createTime", Date::new, Date.class);
+        if (metaObject.hasGetter("valid")) {
+            this.setFieldValByName("valid", 1, metaObject);
+        }
+        if (metaObject.hasGetter("updaterId")) {
+            this.setFieldValByName("updaterId", loginUser.getUserId(), metaObject);
+        }
+        if (metaObject.hasGetter("updateTime")) {
+            this.strictInsertFill(metaObject, "updateTime", Date::new, Date.class);
+        }
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        ShiroUser loginUser = UserUtils.getLoginUser();
+        this.setFieldValByName("updaterId", loginUser.getUserId(), metaObject);
+        this.strictUpdateFill(metaObject, "updateTime", Date::new, Date.class);
+    }
+}

+ 7 - 1
service-base/src/main/java/com/simuwang/base/config/DaqProperties.java

@@ -10,8 +10,14 @@ import java.util.List;
 @Setter
 @Getter
 @Configuration
-@ConfigurationProperties(prefix = "simuwang")
+@ConfigurationProperties(prefix = DaqProperties.DAQ_CONFIG_PREFIX)
 public class DaqProperties {
+    public static final String DAQ_CONFIG_PREFIX = "simuwang";
+
+    /**
+     * 是否启用操作日志记录功能
+     */
+    private Boolean enableLogging = Boolean.TRUE;
     /**
      * 默认的密码
      */

+ 12 - 6
service-base/src/main/java/com/simuwang/base/config/DataSourceAutoConfig.java

@@ -4,7 +4,9 @@ import cn.hutool.core.collection.ListUtil;
 import com.baomidou.mybatisplus.core.MybatisConfiguration;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
+import com.simuwang.base.components.MyBatisPlusMetaObjectHandler;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,12 +31,10 @@ public class DataSourceAutoConfig {
     @Qualifier(DataSourceConfiguration.DS_DATA_DAQ)
     private DataSource dataSource;
 
-    @Autowired
-    private MybatisPlusInterceptor mybatisPlusInterceptor;
-
     @Primary
     @Bean(name = "sqlSessionFactory")
     public SqlSessionFactory sqlSessionFactory() throws Exception {
+        // sql session 配置
         final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
         sessionFactory.setDataSource(this.dataSource);
         // 扫描追加多个包下的资源文件
@@ -47,15 +47,21 @@ public class DataSourceAutoConfig {
             }
         }
         sessionFactory.setMapperLocations(resources.toArray(new Resource[]{}));
-        // 添加mybatis-plus分页拦截插件
+
+        // 添加mybatis-plus配置
         MybatisConfiguration configuration = new MybatisConfiguration();
-        configuration.addInterceptor(this.mybatisPlusInterceptor);
+        // 添加分页插件
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+        configuration.addInterceptor(interceptor);
         // 设置下划线转驼峰
         configuration.setMapUnderscoreToCamelCase(true);
         sessionFactory.setConfiguration(configuration);
-        // 关闭横幅
+
+        // 全局配置
         GlobalConfig globalConfig = new GlobalConfig();
         globalConfig.setBanner(false);
+        globalConfig.setMetaObjectHandler(new MyBatisPlusMetaObjectHandler());
         sessionFactory.setGlobalConfig(globalConfig);
         return sessionFactory.getObject();
     }

+ 0 - 20
service-base/src/main/java/com/simuwang/base/config/MybatisPlusPageInterceptor.java

@@ -1,20 +0,0 @@
-package com.simuwang.base.config;
-
-import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
-import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
-import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class MybatisPlusPageInterceptor {
-
-    @Bean("mybatisPlusInterceptor")
-    public MybatisPlusInterceptor mybatisPlusInterceptor() {
-        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
-        // 添加分页插件
-        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
-        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
-        return interceptor;
-    }
-}

+ 9 - 0
service-base/src/main/java/com/simuwang/base/mapper/system/SysLogMapper.java

@@ -0,0 +1,9 @@
+package com.simuwang.base.mapper.system;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.simuwang.base.pojo.dos.sys.SysLogDO;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SysLogMapper extends BaseMapper<SysLogDO> {
+}

+ 44 - 0
service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysLogDO.java

@@ -0,0 +1,44 @@
+package com.simuwang.base.pojo.dos.sys;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.simuwang.base.common.support.dos.BaseEntity;
+import com.simuwang.base.pojo.vo.sys.SysLogVO;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+@TableName("sys_log")
+public class SysLogDO extends BaseEntity<SysLogVO> {
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+    private String title;
+    private Integer type;
+    private String requestUri;
+    private String method;
+    private String remoteAddr;
+    private Long executeTime;
+    private String params;
+    private String result;
+    private Boolean hasException;
+    private String exception;
+
+    @Override
+    public SysLogVO toVo() {
+        SysLogVO vo = new SysLogVO();
+        vo.setId(this.id);
+        vo.setTitle(this.title);
+        vo.setType(this.type);
+        vo.setRequestUri(this.requestUri);
+        vo.setMethod(this.method);
+        vo.setRemoteAddr(this.remoteAddr);
+        vo.setExecuteTime(this.executeTime);
+        vo.setParams(this.params);
+        vo.setResult(this.result);
+        vo.setHasException(this.hasException);
+        vo.setException(this.exception);
+        return vo;
+    }
+}

+ 2 - 2
service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysMenuDO.java

@@ -3,7 +3,7 @@ package com.simuwang.base.pojo.dos.sys;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.simuwang.base.common.support.dos.BaseEntity;
+import com.simuwang.base.common.support.dos.DataEntity;
 import com.simuwang.base.pojo.vo.sys.SysMenuVO;
 import lombok.Getter;
 import lombok.Setter;
@@ -19,7 +19,7 @@ import java.util.Objects;
 @Setter
 @Getter
 @TableName("sys_menu")
-public class SysMenuDO extends BaseEntity<SysMenuVO> {
+public class SysMenuDO extends DataEntity<SysMenuVO> {
     /**
      * 菜单ID
      */

+ 2 - 2
service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysRoleDO.java

@@ -3,7 +3,7 @@ package com.simuwang.base.pojo.dos.sys;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.simuwang.base.common.support.dos.BaseEntity;
+import com.simuwang.base.common.support.dos.DataEntity;
 import com.simuwang.base.pojo.vo.sys.SysRoleVO;
 import lombok.Getter;
 import lombok.Setter;
@@ -17,7 +17,7 @@ import lombok.Setter;
 @Setter
 @Getter
 @TableName("sys_role")
-public class SysRoleDO extends BaseEntity<SysRoleVO> {
+public class SysRoleDO extends DataEntity<SysRoleVO> {
     /**
      * 角色ID
      */

+ 2 - 2
service-base/src/main/java/com/simuwang/base/pojo/dos/sys/SysUserDO.java

@@ -3,7 +3,7 @@ package com.simuwang.base.pojo.dos.sys;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.simuwang.base.common.support.dos.BaseEntity;
+import com.simuwang.base.common.support.dos.DataEntity;
 import com.simuwang.base.pojo.vo.sys.SysUserVO;
 import lombok.Getter;
 import lombok.Setter;
@@ -17,7 +17,7 @@ import lombok.Setter;
 @Setter
 @Getter
 @TableName("sys_user")
-public class SysUserDO extends BaseEntity<SysUserVO> {
+public class SysUserDO extends DataEntity<SysUserVO> {
     /**
      * 用户ID
      */

+ 22 - 0
service-base/src/main/java/com/simuwang/base/pojo/dto/sys/LogQuery.java

@@ -0,0 +1,22 @@
+package com.simuwang.base.pojo.dto.sys;
+
+import com.simuwang.base.common.support.query.PageQuery;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class LogQuery extends PageQuery {
+    /**
+     * 深度分页优化,取当前页的最大id传递
+     */
+    private Integer maxId;
+    /**
+     * 操作类型
+     */
+    private Integer type;
+    /**
+     * 是否异常
+     */
+    private Boolean hasException;
+}

+ 21 - 0
service-base/src/main/java/com/simuwang/base/pojo/vo/sys/SysLogVO.java

@@ -0,0 +1,21 @@
+package com.simuwang.base.pojo.vo.sys;
+
+import com.simuwang.base.common.support.vo.BaseVO;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class SysLogVO extends BaseVO {
+    private Integer id;
+    private String title;
+    private Integer type;
+    private String requestUri;
+    private String method;
+    private String remoteAddr;
+    private Long executeTime;
+    private String params;
+    private String result;
+    private Boolean hasException;
+    private String exception;
+}

+ 193 - 0
service-base/src/main/java/com/simuwang/logging/Logging.java

@@ -0,0 +1,193 @@
+package com.simuwang.logging;
+
+import com.simuwang.base.common.conts.Constants;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/14 14:04
+ * @description 系统操作日志记录
+ */
+public class Logging implements Serializable {
+    public static final long serialVersionUID = Constants.DEFAULT_SERIAL_ID;
+
+    private String title;
+    private Integer type;
+    private String requestUri;
+    private String method;
+    private String remoteAddr;
+    private Long executeTime;
+    private String params;
+    private String result;
+    private Boolean hasException;
+    private String exception;
+
+    /**
+     * 私有化无参构造器
+     */
+    private Logging() {
+
+    }
+
+    /**
+     * 提供一个包含所有参数的构造器,方便子类继承后扩展
+     *
+     * @param title       标题
+     * @param type        日志类型
+     * @param requestUri  请求地址
+     * @param method      请求method
+     * @param params      请求参数
+     * @param result      执行结果
+     * @param executeTime 执行所用时间,单位:毫秒
+     * @param hasException   是否有异常
+     * @param exception   异常详情
+     */
+    public Logging(String title, Integer type, String requestUri, String method, String remoteAddr, Long executeTime,
+                   String params, String result, Boolean hasException, String exception) {
+        this.title = title;
+        this.type = type;
+        this.requestUri = requestUri;
+        this.method = method;
+        this.remoteAddr = remoteAddr;
+        this.executeTime = executeTime;
+        this.params = params;
+        this.result = result;
+        this.hasException = hasException;
+        this.exception = exception;
+    }
+
+    /**
+     * 一个构造logging的builder对象的方法
+     *
+     * @return Builder对象
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public String getRequestUri() {
+        return requestUri;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public String getRemoteAddr() {
+        return remoteAddr;
+    }
+
+    public Long getExecuteTime() {
+        return executeTime;
+    }
+
+    public String getParams() {
+        return params;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public String getException() {
+        return exception;
+    }
+
+    public Boolean getHasException() {
+        return hasException;
+    }
+
+    /**
+     * logging a builder
+     */
+    public static class Builder implements Serializable {
+        @Serial
+        private static final long serialVersionUID = Constants.DEFAULT_SERIAL_ID;
+
+        private String title;
+        private Integer type;
+        private String requestUri;
+        private String method;
+        private String remoteAddr;
+        private Long executeTime;
+        private String params;
+        private String result;
+        private Boolean hasException;
+        private String exception;
+
+        private Builder() {
+
+        }
+
+        public Builder title(String title) {
+            this.title = title;
+            return this;
+        }
+
+        public Builder type(int type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder requestUri(String requestUri) {
+            this.requestUri = requestUri;
+            return this;
+        }
+
+        public Builder method(String method) {
+            this.method = method;
+            return this;
+        }
+
+        public Builder remoteAddr(String remoteAddr) {
+            this.remoteAddr = remoteAddr;
+            return this;
+        }
+
+        public Builder params(String params) {
+            this.params = params;
+            return this;
+        }
+
+        public void result(String result) {
+            this.result = result;
+        }
+
+        public void executeTime(Long executeTime) {
+            this.executeTime = executeTime;
+        }
+
+        public void hasException(Boolean hasException) {
+            this.hasException = hasException;
+        }
+
+        public void exception(String exception) {
+            this.exception = exception;
+        }
+
+        public Logging build() {
+            Logging logging = new Logging();
+            logging.title = this.title;
+            logging.type = this.type;
+            logging.requestUri = this.requestUri;
+            logging.method = this.method;
+            logging.remoteAddr = this.remoteAddr;
+            logging.params = this.params;
+            logging.result = this.result;
+            logging.executeTime = this.executeTime;
+            logging.hasException = this.hasException;
+            logging.exception = this.exception;
+            return logging;
+        }
+    }
+}

+ 10 - 0
service-base/src/main/java/com/simuwang/logging/LoggingService.java

@@ -0,0 +1,10 @@
+package com.simuwang.logging;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/14 14:05
+ * @description 系统操作日志保存服务
+ */
+public interface LoggingService {
+    void asyncSave(Logging logging);
+}

+ 82 - 0
service-base/src/main/java/com/simuwang/logging/SystemLog.java

@@ -0,0 +1,82 @@
+package com.simuwang.logging;
+
+import java.lang.annotation.*;
+
+/**
+ * 系统操作日志注解,必须标注在方法上,当类上也被标注时,只取value作为上级模块名
+ *
+ * @author fpwag
+ */
+@Target(value = {ElementType.TYPE, ElementType.METHOD})
+@Retention(value = RetentionPolicy.RUNTIME)
+@Documented
+public @interface SystemLog {
+    /**
+     * 描述
+     */
+    String value();
+
+    /**
+     * 日志类型
+     */
+    Type type() default Type.QUERY;
+
+    /**
+     * 是否保存接口返回结果,默认不保存
+     *
+     * @return /
+     */
+    boolean saveResult() default false;
+
+    /**
+     * 日志类型
+     */
+    enum Type {
+        /**
+         * 查询
+         */
+        QUERY(1),
+        /**
+         * 新增
+         */
+        INSERT(2),
+        /**
+         * 修改
+         */
+        UPDATE(3),
+        /**
+         * 删除
+         */
+        DELETE(5),
+        /**
+         * 登录
+         */
+        LOGIN(6),
+        /**
+         * 退出登录
+         */
+        LOGOUT(7),
+        /**
+         * 导入
+         */
+        IMPORT(8),
+        /**
+         * 导出
+         */
+        EXPORT(9),
+        /**
+         * 其他
+         */
+        OTHER(0);
+
+        private final int value;
+
+        Type(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return this.value;
+        }
+    }
+}

+ 40 - 0
service-base/src/main/java/com/simuwang/shiro/utils/UserUtils.java

@@ -0,0 +1,40 @@
+package com.simuwang.shiro.utils;
+
+import com.simuwang.shiro.core.ShiroUser;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/14 15:39
+ * @description 登录用户工具
+ */
+public class UserUtils {
+    public static Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+    /**
+     * 获取当前登录的用户信息
+     *
+     * @return 当前登录用户
+     */
+    public static ShiroUser getLoginUser() {
+        Subject subject = SecurityUtils.getSubject();
+        return getLoginUser(subject);
+    }
+
+    /**
+     * 获取当前登录的用户信息
+     *
+     * @return 当前登录用户
+     */
+    public static ShiroUser getLoginUser(Subject subject) {
+        Object principal = subject.getPrincipal();
+        if (principal instanceof ShiroUser shiroUser) {
+            return shiroUser;
+        }
+        throw new AuthenticationException();
+    }
+}

+ 3 - 4
service-deploy/src/main/java/com/simuwang/deploy/components/ApiAop.java

@@ -9,12 +9,12 @@ import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Pointcut;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
-@Order(1)
+import java.util.Objects;
+
 @Aspect
 @Component
 public class ApiAop {
@@ -39,8 +39,7 @@ public class ApiAop {
 
     @Around("pointcut()")
     public Object spentTime(ProceedingJoinPoint thisJoinPoint) throws Throwable {
-        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-        HttpServletRequest request = sra.getRequest();
+        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
         String url = request.getServletPath();
         Object[] args = thisJoinPoint.getArgs();
         if (this.logger.isInfoEnabled()) {

+ 106 - 0
service-deploy/src/main/java/com/simuwang/deploy/components/LoggingAspect.java

@@ -0,0 +1,106 @@
+package com.simuwang.deploy.components;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.URLUtil;
+import cn.hutool.json.JSONUtil;
+import com.simuwang.logging.Logging;
+import com.simuwang.logging.LoggingService;
+import com.simuwang.logging.SystemLog;
+import jakarta.servlet.http.HttpServletRequest;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.util.StopWatch;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.util.Objects;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/14 14:07
+ * @description 系统日志拦截切面
+ */
+@Order(2)
+@Aspect
+public class LoggingAspect {
+    private final LoggingService service;
+
+    public LoggingAspect(LoggingService service) {
+        this.service = service;
+    }
+
+    @Around("@annotation(annotation)")
+    public Object around(ProceedingJoinPoint joinPoint, SystemLog annotation) {
+        Class<?> clazz = AopUtils.getTargetClass(joinPoint.getTarget());
+        SystemLog systemLog = clazz.getAnnotation(SystemLog.class);
+        String title = annotation.value();
+        if (systemLog != null && StrUtil.isNotBlank(systemLog.value())) {
+            title = systemLog.value() + "-" + title;
+        }
+        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+        Logging.Builder builder = Logging.builder().title(title).type(annotation.type().getValue()).method(request.getMethod())
+                .requestUri(URLUtil.getPath(request.getRequestURI())).remoteAddr(this.getIpAddr(request))
+                .params(joinPoint.getArgs() == null ? Objects.toString(request.getParameterMap()) : JSONUtil.toJsonStr(joinPoint.getArgs()));
+        StopWatch watch = new StopWatch();
+        watch.start();
+        Object result = null;
+        try {
+            result = joinPoint.proceed();
+            if (annotation.saveResult()) {
+                builder.result(JSONUtil.toJsonStr(result));
+            }
+        } catch (Throwable throwable) {
+            builder.exception(ExceptionUtil.stacktraceToString(throwable));
+        } finally {
+            watch.stop();
+            builder.executeTime(watch.getTotalTimeMillis());
+            this.service.asyncSave(builder.build());
+        }
+        return result;
+    }
+
+    public String getIpAddr(HttpServletRequest request) {
+        if (request == null) {
+            return "unknown";
+        }
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Real-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
+    }
+
+    private String getMultistageReverseProxyIp(String ip) {
+        // 多级反向代理检测
+        if (ip != null && ip.indexOf(",") > 0) {
+            final String[] ips = ip.trim().split(",");
+            for (String subIp : ips) {
+                if (!isUnknown(subIp)) {
+                    ip = subIp;
+                    break;
+                }
+            }
+        }
+        return ip;
+    }
+
+    private boolean isUnknown(String checkString) {
+        return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
+    }
+}

+ 3 - 1
service-deploy/src/main/java/com/simuwang/deploy/config/GlobalErrorController.java

@@ -4,6 +4,7 @@ import com.simuwang.base.common.exception.ErrorInfo;
 import com.simuwang.deploy.components.ErrorInfoBuilder;
 import com.smppw.common.pojo.ResultVo;
 import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.boot.web.servlet.error.ErrorController;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Controller;
@@ -39,7 +40,8 @@ public class GlobalErrorController implements ErrorController {
      */
     @RequestMapping
     @ResponseBody
-    public ResultVo<?> error(HttpServletRequest request) {
+    public ResultVo<?> error(HttpServletRequest request, HttpServletResponse response) {
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
         ErrorInfo errorInfo = errorInfoBuilder.getErrorInfo(request);
         return ResultVo.fail(errorInfo.getStatusCode(), errorInfo.getError());
     }

+ 18 - 1
service-deploy/src/main/java/com/simuwang/deploy/config/WebConfig.java

@@ -1,14 +1,31 @@
 package com.simuwang.deploy.config;
 
+import com.simuwang.base.config.DaqProperties;
+import com.simuwang.deploy.components.LoggingAspect;
 import com.simuwang.deploy.components.RequestIdInterceptor;
+import com.simuwang.logging.LoggingService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
+    private final LoggingService loggingService;
+
+    public WebConfig(LoggingService loggingService) {
+        this.loggingService = loggingService;
+    }
+
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
-       registry.addInterceptor(new RequestIdInterceptor()).addPathPatterns("/**");
+        registry.addInterceptor(new RequestIdInterceptor()).addPathPatterns("/**");
+    }
+
+    @Bean
+    @ConditionalOnProperty(prefix = DaqProperties.DAQ_CONFIG_PREFIX, name = "enable-logging", havingValue = "true")
+    public LoggingAspect loggingAspect() {
+        return new LoggingAspect(this.loggingService);
     }
 }

+ 2 - 0
service-deploy/src/main/resources/application.yml

@@ -56,6 +56,8 @@ email:
 
 # 配置
 simuwang:
+  # 操作日志功能是否启用,启用了才会记录操作日志
+  enable-logging: true
   default-pwd: "QWER1234!@#$"
   token-expire: 86400
   tokenSecret: qwertyuiopasdfghjklzxcvbnm1234567890qwertyuiopasdfghjklzxcvbnm12

+ 15 - 10
service-manage/src/main/java/com/simuwang/manage/api/LoginController.java

@@ -2,16 +2,16 @@ package com.simuwang.manage.api;
 
 import cn.hutool.core.map.MapUtil;
 import com.simuwang.base.config.DaqProperties;
+import com.simuwang.logging.SystemLog;
 import com.simuwang.manage.dto.LoginUser;
 import com.simuwang.manage.dto.UserInfoVO;
 import com.simuwang.manage.service.LoginService;
 import com.simuwang.shiro.core.ShiroToken;
 import com.simuwang.shiro.core.ShiroUser;
 import com.simuwang.shiro.core.jwt.JwtContext;
+import com.simuwang.shiro.utils.UserUtils;
 import com.smppw.common.pojo.ResultVo;
 import com.smppw.common.pojo.enums.status.ResultCode;
-import jakarta.servlet.http.HttpServletResponse;
-import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authz.annotation.RequiresAuthentication;
 import org.apache.shiro.subject.Subject;
 import org.springframework.web.bind.annotation.*;
@@ -21,6 +21,7 @@ import java.util.Map;
 /**
  * 登录相关接口
  */
+@SystemLog(value = "登录相关")
 @RestController
 @RequestMapping("/v1")
 public class LoginController {
@@ -39,6 +40,7 @@ public class LoginController {
      *
      * @return /
      */
+    @SystemLog(value = "获取公钥", type = SystemLog.Type.QUERY)
     @GetMapping("rsa-key")
     public Map<String, Object> getRsaKey() {
         return MapUtil.<String, Object>builder("rsaKey", this.properties.getSecurityRsa().getPublicKey()).build();
@@ -48,37 +50,40 @@ public class LoginController {
      * 用户登录
      *
      * @param loginUser 登录用户账号和密码
-     * @param response  响应体对象
+     *                  //     * @param response  响应体对象
      * @return /
      */
+    @SystemLog(value = "登录", type = SystemLog.Type.LOGIN)
     @PostMapping("login")
-    public ResultVo<String> login(@RequestBody LoginUser loginUser, HttpServletResponse response) {
+    public ResultVo<String> login(@RequestBody LoginUser loginUser) {
         ShiroToken shiroToken = new ShiroToken(loginUser.getUsername(), loginUser.getPassword());
-        Subject subject = SecurityUtils.getSubject();
+        Subject subject = UserUtils.getSubject();
         subject.login(shiroToken);
 
         String token = jwtContext.generateToken(loginUser.getUsername());
         this.jwtContext.setUserCache(token);
-        response.setHeader(JwtContext.HEADER, token);
-        response.setHeader("Access-control-Expost-Headers", JwtContext.HEADER);
+        // 加这response会导致响应头和shiro的默认字符集存在冲突,导致接口500
+//        response.setHeader(JwtContext.HEADER, token);
+//        response.setHeader("Access-control-Expost-Headers", JwtContext.HEADER);
         return ResultVo.ok(ResultCode.SUCCESS.getCode(), "登录成功", token);
     }
 
     /**
      * 退出登录接口,登录用户才能访问
      */
+    @SystemLog(value = "登出", type = SystemLog.Type.LOGOUT)
     @RequiresAuthentication
     @PostMapping("/logout")
     public ResultVo<Boolean> logout() {
-        Subject subject = SecurityUtils.getSubject();
-        ShiroUser shiroUser = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
+        Subject subject = UserUtils.getSubject();
+        ShiroUser shiroUser = UserUtils.getLoginUser(subject);
         this.jwtContext.cleanUserCache(shiroUser.getUsername());
         subject.logout();
         return ResultVo.ok(ResultCode.SUCCESS.getCode(), "退出成功", true);
     }
 
     /**
-     * 获取当前用户的角色权限信息
+     * 获取当前用户的角色权限信息(只在登录时获取一次)
      *
      * @return 当前登录用户的角色权限信息
      */

+ 49 - 0
service-manage/src/main/java/com/simuwang/manage/api/system/SysLogController.java

@@ -0,0 +1,49 @@
+package com.simuwang.manage.api.system;
+
+import com.simuwang.base.common.support.MybatisPage;
+import com.simuwang.base.pojo.dto.sys.LogQuery;
+import com.simuwang.base.pojo.vo.sys.SysLogVO;
+import com.simuwang.logging.SystemLog;
+import com.simuwang.manage.service.system.SysLogService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 日志管理接口
+ */
+@SystemLog(value = "日志管理")
+@RestController
+@RequestMapping("/v1/sys/log")
+public class SysLogController {
+    private final SysLogService service;
+
+    public SysLogController(SysLogService service) {
+        this.service = service;
+    }
+
+    /**
+     * 分页接口
+     *
+     * @param query 日志分页参数
+     * @return /
+     */
+    @GetMapping("page")
+    public MybatisPage<SysLogVO> page(LogQuery query) {
+        return this.service.findPage(query);
+    }
+
+    /**
+     * 清空日志,要有权限才能清空
+     *
+     * @return /
+     */
+    @SystemLog(value = "清空日志", type = SystemLog.Type.DELETE)
+    @RequiresPermissions("SYS:LOG:TRUNCATE")
+    @DeleteMapping
+    public boolean truncate() {
+        return this.service.truncate();
+    }
+}

+ 7 - 0
service-manage/src/main/java/com/simuwang/manage/api/system/SysMenuController.java

@@ -8,6 +8,7 @@ import com.simuwang.base.pojo.dto.sys.MenuEditCmd;
 import com.simuwang.base.pojo.dto.sys.MenuQuery;
 import com.simuwang.base.pojo.dto.sys.MenuTreeQuery;
 import com.simuwang.base.pojo.vo.sys.SysMenuVO;
+import com.simuwang.logging.SystemLog;
 import com.simuwang.manage.dto.MenuTreeDTO;
 import com.simuwang.manage.service.system.SysMenuService;
 import jakarta.validation.Valid;
@@ -18,6 +19,7 @@ import java.util.List;
 /**
  * 菜单管理相关接口
  */
+@SystemLog(value = "菜单管理")
 @RestController
 @RequestMapping("/v1/sys/menu")
 public class SysMenuController {
@@ -47,6 +49,7 @@ public class SysMenuController {
      * @param query 查询条件
      * @return /
      */
+    @SystemLog(value = "菜单树", saveResult = true)
     @GetMapping("tree")
     public List<MenuTreeDTO> menuTrees(MenuTreeQuery query) {
         return this.service.listMenuTrees(query);
@@ -57,6 +60,7 @@ public class SysMenuController {
      *
      * @param command 菜单信息
      */
+    @SystemLog(value = "新增", type = SystemLog.Type.INSERT)
     @PostMapping("save")
     public boolean save(@Valid @RequestBody MenuAddCmd command) {
         this.service.insert(command);
@@ -68,6 +72,7 @@ public class SysMenuController {
      *
      * @param command 菜单id+菜单信息
      */
+    @SystemLog(value = "修改", type = SystemLog.Type.UPDATE)
     @PostMapping("update")
     public boolean update(@Valid @RequestBody MenuEditCmd command) {
         this.service.update(command);
@@ -79,6 +84,7 @@ public class SysMenuController {
      *
      * @param command 菜单id+状态对象
      */
+    @SystemLog(value = "更新状态", type = SystemLog.Type.UPDATE)
     @PostMapping("update-status")
     public boolean updateStatus(@Valid @RequestBody UpdateStatusCmd command) {
         this.service.updateStatus(command);
@@ -90,6 +96,7 @@ public class SysMenuController {
      *
      * @param command 菜单id对象
      */
+    @SystemLog(value = "删除", type = SystemLog.Type.DELETE)
     @PostMapping("del")
     public boolean deleteById(@Valid @RequestBody DeleteByIdCmd command) {
         this.service.delete(command);

+ 8 - 0
service-manage/src/main/java/com/simuwang/manage/api/system/SysRoleController.java

@@ -10,6 +10,7 @@ import com.simuwang.base.pojo.dto.sys.RoleEditCmd;
 import com.simuwang.base.pojo.dto.sys.RoleMenuAssignCmd;
 import com.simuwang.base.pojo.dto.sys.RoleQuery;
 import com.simuwang.base.pojo.vo.sys.SysRoleVO;
+import com.simuwang.logging.SystemLog;
 import com.simuwang.manage.service.system.SysRoleService;
 import jakarta.validation.Valid;
 import org.springframework.web.bind.annotation.*;
@@ -19,6 +20,7 @@ import java.util.List;
 /**
  * 角色管理相关接口
  */
+@SystemLog("角色管理")
 @RestController
 @RequestMapping("/v1/sys/role")
 public class SysRoleController {
@@ -34,6 +36,7 @@ public class SysRoleController {
      * @param query 角色列表分页请求参数
      * @return /
      */
+    @SystemLog(value = "分页搜索", saveResult = true)
     @GetMapping("page")
     public MybatisPage<SysRoleVO> page(RoleQuery query) {
         return this.service.findPage(query);
@@ -44,6 +47,7 @@ public class SysRoleController {
      *
      * @param command 角色信息
      */
+    @SystemLog(value = "新增", type = SystemLog.Type.INSERT)
     @PostMapping("save")
     public boolean save(@Valid @RequestBody RoleAddCmd command) {
         this.service.insert(command);
@@ -55,6 +59,7 @@ public class SysRoleController {
      *
      * @param command 角色id+角色信息
      */
+    @SystemLog(value = "修改", type = SystemLog.Type.UPDATE)
     @PostMapping("update")
     public boolean update(@Valid @RequestBody RoleEditCmd command) {
         this.service.update(command);
@@ -66,6 +71,7 @@ public class SysRoleController {
      *
      * @param command 角色id+状态对象
      */
+    @SystemLog(value = "更新状态", type = SystemLog.Type.UPDATE)
     @PostMapping("update-status")
     public boolean updateStatus(@Valid @RequestBody UpdateStatusCmd command) {
         this.service.updateStatus(command);
@@ -77,6 +83,7 @@ public class SysRoleController {
      *
      * @param command 角色id对象
      */
+    @SystemLog(value = "删除", type = SystemLog.Type.DELETE)
     @PostMapping("del")
     public boolean deleteById(@Valid @RequestBody DeleteByIdCmd command) {
         this.service.delete(command);
@@ -88,6 +95,7 @@ public class SysRoleController {
      *
      * @param command 角色id对象
      */
+    @SystemLog(value = "分配权限", type = SystemLog.Type.INSERT)
     @PostMapping("assign-perms")
     public boolean assignPermissions(@Valid @RequestBody RoleMenuAssignCmd command) {
         this.service.assignPerms(command);

+ 8 - 0
service-manage/src/main/java/com/simuwang/manage/api/system/SysUserController.java

@@ -8,6 +8,7 @@ import com.simuwang.base.pojo.dto.UpdateStatusCmd;
 import com.simuwang.base.pojo.dto.sys.*;
 import com.simuwang.base.pojo.vo.sys.SysRoleUserVO;
 import com.simuwang.base.pojo.vo.sys.SysUserVO;
+import com.simuwang.logging.SystemLog;
 import com.simuwang.manage.service.system.SysUserService;
 import jakarta.validation.Valid;
 import org.springframework.web.bind.annotation.*;
@@ -17,6 +18,7 @@ import java.util.List;
 /**
  * 用户管理相关接口
  */
+@SystemLog(value = "用户管理")
 @RestController
 @RequestMapping("/v1/sys/user")
 public class SysUserController {
@@ -32,6 +34,7 @@ public class SysUserController {
      * @param query 用户列表分页请求参数
      * @return /
      */
+    @SystemLog(value = "分页搜索", saveResult = true)
     @GetMapping("page")
     public MybatisPage<SysUserVO> page(UserQuery query) {
         return this.service.findPage(query);
@@ -53,6 +56,7 @@ public class SysUserController {
      *
      * @param command 用户信息
      */
+    @SystemLog(value = "新增", type = SystemLog.Type.INSERT)
     @PostMapping("save")
     public boolean save(@Valid @RequestBody UserAddCmd command) {
         this.service.insert(command);
@@ -64,6 +68,7 @@ public class SysUserController {
      *
      * @param command 用户id+用户信息
      */
+    @SystemLog(value = "修改", type = SystemLog.Type.UPDATE)
     @PostMapping("update")
     public boolean update(@Valid @RequestBody UserEditCmd command) {
         this.service.update(command);
@@ -75,6 +80,7 @@ public class SysUserController {
      *
      * @param command 用户id+状态对象
      */
+    @SystemLog(value = "更新状态", type = SystemLog.Type.UPDATE)
     @PostMapping("update-status")
     public boolean updateStatus(@Valid @RequestBody UpdateStatusCmd command) {
         this.service.updateStatus(command);
@@ -86,6 +92,7 @@ public class SysUserController {
      *
      * @param command 用户id对象
      */
+    @SystemLog(value = "删除", type = SystemLog.Type.DELETE)
     @PostMapping("del")
     public boolean deleteById(@Valid @RequestBody DeleteByIdCmd command) {
         this.service.delete(command);
@@ -97,6 +104,7 @@ public class SysUserController {
      *
      * @param command 用户绑定的角色对象
      */
+    @SystemLog(value = "用户角色绑定", type = SystemLog.Type.UPDATE)
     @PostMapping("bind-roles")
     public boolean bindRoles(@Valid @RequestBody UserRoleBindCmd command) {
         this.service.bindRoles(command);

+ 3 - 3
service-manage/src/main/java/com/simuwang/manage/service/LoginService.java

@@ -5,11 +5,11 @@ import com.simuwang.base.components.UserAuthService;
 import com.simuwang.base.pojo.dos.sys.SysMenuDO;
 import com.simuwang.base.pojo.dos.sys.SysRoleDO;
 import com.simuwang.base.pojo.dos.sys.SysUserDO;
-import com.simuwang.manage.dto.UserInfoVO;
 import com.simuwang.manage.dto.MenuTreeDTO;
+import com.simuwang.manage.dto.UserInfoVO;
 import com.simuwang.manage.dto.UserRoleDTO;
 import com.simuwang.shiro.core.ShiroUser;
-import org.apache.shiro.SecurityUtils;
+import com.simuwang.shiro.utils.UserUtils;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -34,7 +34,7 @@ public class LoginService {
      * @return /
      */
     public UserInfoVO getUserInfo() {
-        ShiroUser shiroUser = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
+        ShiroUser shiroUser = UserUtils.getLoginUser();
         Integer userId = shiroUser.getUserId();
         String username = shiroUser.getUsername();
         SysUserDO userInfo = this.userAuthService.getUserInfoByUsername(username);

+ 68 - 0
service-manage/src/main/java/com/simuwang/manage/service/impl/system/SysLogServiceImpl.java

@@ -0,0 +1,68 @@
+package com.simuwang.manage.service.impl.system;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.simuwang.base.common.support.MybatisPage;
+import com.simuwang.base.common.support.query.PageQuery;
+import com.simuwang.base.common.support.service.IService;
+import com.simuwang.base.mapper.system.SysLogMapper;
+import com.simuwang.base.pojo.dos.sys.SysLogDO;
+import com.simuwang.base.pojo.dto.sys.LogQuery;
+import com.simuwang.base.pojo.vo.sys.SysLogVO;
+import com.simuwang.logging.Logging;
+import com.simuwang.manage.service.system.SysLogService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SysLogServiceImpl implements SysLogService {
+    private final SysLogMapper mapper;
+
+    public SysLogServiceImpl(SysLogMapper mapper) {
+        this.mapper = mapper;
+    }
+
+    @Override
+    public String[] selectColumns() {
+        return IService.extColumns(DEFAULT_COLUMNS, "title", "id", "type", "remote_addr",
+                "request_uri", "method", "params", "result", "has_exception", "execute_time", "exception");
+    }
+
+    @Override
+    public <Q extends PageQuery> void wrapQuery(QueryWrapper<SysLogDO> wrapper, Q query) {
+        LogQuery params = (LogQuery) query;
+        wrapper.like(StrUtil.isNotBlank(params.getKeyword()), "title", params.getKeyword())
+                .gt(params.getMaxId() != null, "id", params.getMaxId())
+                .eq(params.getHasException() != null, "has_exception", params.getHasException())
+                .between(StrUtil.isAllNotBlank(params.getBeginTime(), params.getEndTime()),
+                        "updatetime", params.getBeginTime(), params.getEndTime());
+    }
+
+    @Override
+    public MybatisPage<SysLogVO> convertPage(Page<SysLogDO> page, QueryWrapper<SysLogDO> wrapper) {
+        IPage<SysLogDO> entityPage = this.mapper.selectPage(page, wrapper);
+        return MybatisPage.ofList(entityPage, SysLogDO::toVo);
+    }
+
+    @Override
+    public void asyncSave(Logging logging) {
+        SysLogDO entity = new SysLogDO();
+        entity.setTitle(logging.getTitle());
+        entity.setType(logging.getType());
+        entity.setRemoteAddr(logging.getRemoteAddr());
+        entity.setRequestUri(logging.getRequestUri());
+        entity.setMethod(logging.getMethod());
+        entity.setParams(logging.getParams());
+        entity.setResult(logging.getResult());
+        entity.setHasException(logging.getHasException());
+        entity.setException(logging.getException());
+        entity.setExecuteTime(logging.getExecuteTime());
+        this.mapper.insert(entity);
+    }
+
+    @Override
+    public boolean truncate() {
+        return false;
+    }
+}

+ 2 - 2
service-manage/src/main/java/com/simuwang/manage/service/impl/system/SysMenuServiceImpl.java

@@ -22,7 +22,7 @@ import com.simuwang.base.pojo.vo.sys.SysMenuVO;
 import com.simuwang.manage.dto.MenuTreeDTO;
 import com.simuwang.manage.service.system.SysMenuService;
 import com.simuwang.shiro.core.ShiroUser;
-import org.apache.shiro.SecurityUtils;
+import com.simuwang.shiro.utils.UserUtils;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -75,7 +75,7 @@ public class SysMenuServiceImpl implements SysMenuService {
     @Override
     public List<MenuTreeDTO> listMenuTrees(MenuTreeQuery query) {
         // 先获取用户的所有菜单
-        ShiroUser shiroUser = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
+        ShiroUser shiroUser = UserUtils.getLoginUser();
         Integer userId = shiroUser.getUserId();
         List<SysMenuDO> dataList = this.userAuthService.listUserMenuByUserId(userId);
         // 当有查询条件时

+ 20 - 0
service-manage/src/main/java/com/simuwang/manage/service/system/SysLogService.java

@@ -0,0 +1,20 @@
+package com.simuwang.manage.service.system;
+
+import com.simuwang.base.common.support.service.IService;
+import com.simuwang.base.pojo.dos.sys.SysLogDO;
+import com.simuwang.base.pojo.vo.sys.SysLogVO;
+import com.simuwang.logging.LoggingService;
+
+/**
+ * @author wangzaijun
+ * @date 2024/9/14 14:30
+ * @description 日志管理服务
+ */
+public interface SysLogService extends IService<SysLogVO, SysLogDO>, LoggingService {
+    /**
+     * 清空日志接口
+     *
+     * @return /
+     */
+    boolean truncate();
+}