根据aop切面和自定义注解实现添加操作日志信息,记录每个操作者具体做了什么事

引入spring及其他工具类依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.16</version>
        </dependency>
        <!-- 连接,版本你们自己选择 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.connector}</version>
        </dependency>
 
        <dependency>
            <groupId>net.sourceforge.jtds</groupId>
            <artifactId>jtds</artifactId>
            <version>${sqlserver-jtds}</version>
        </dependency>
 
 
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.boot}</version>
        </dependency>
 
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis.plus.boot}</version>
        </dependency>
        <!-- hutool工具类,几乎包含所以通用方法 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.3</version>
        </dependency>

然后创建操作日志的表,表字段如下

CREATE TABLE `config_log_operating` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '操作日志表',
  `dotable` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作的表名',
  `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '日志的标题,动作',
  `do_id` int unsigned DEFAULT NULL COMMENT '操作的表id',
  `summary` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '内容概括',
  `operator` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作人',
  `operator_id` int unsigned DEFAULT NULL COMMENT '操作人id',
  `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作时的ip',
  `create_date` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `api` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '调用的接口信息',
  `api_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '接口请求类型',
  `source` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'admin' COMMENT '来源,admin:管理后台;pc:网页端;wap:手机端;wx:微信小程序;userAdmin:用户后台',
  `source_url` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '请求接口前页面的浏览器地址',
  `visit_time` int unsigned DEFAULT '0' COMMENT '切面执行时长,用于查看反射的效率',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `config_log_operating_create_date_IDX` (`create_date`) USING BTREE,
  KEY `config_log_operating_do_id_IDX` (`do_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=463 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='操作日志表,一般用于管理后台'

相关controller,service,mapper代码你们自己创建,这里就不发了。

然后添加操作日志代码使用到了字符串灵活sql语句的执行,详情请看我​​​​​​字符串sql语句执行封装,借用hutool工具类_影耀子的博客-优快云博客

​这篇文章,你也可以改成你自己封装的字符串sql语句执行的工具类

然后自定义注解代码如下:

/**
 * @author 影耀子(YingYew)
 * @Description: 操作日志自定义注解
 * @ClassName: AddHandleLog
 * @date 2022/7/12 17:25
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AddHandleLog {

    /**
     * 日志的标题信息
     * @return
     */
    String title() default "";

    /**
     * 需要查询的sql语句,必须包含id,id即为操作的表的id
     * @return
     */
    String sql() default "";

    /**
     * sql所需参数,一般为id,非参数值,参数值是根据此属性从接口方法参数中获取
     * @return
     */
    String sqlParameter() default "id";

    /**
     * 日志的内容概述,如:联系人电话:${tel} | 找酒主题:${subject},有的联系
     * @return
     */
    String summary() default "";

    /**
     * 日志操作的表名
     * @return
     */
    String table() default "";


}

然后就是最重要的切面类中的代码,添加操作日志的实现就是通过切面中的环绕通知实现的

对了,环绕通知中有捕获异常,所以使用了记录异常日志信息的方法,详情请参阅我

使用aop切面记录程序接口异常日志信息,包含完整方法名,参数类型,参数名,参数值及接口信息,异常信息_影耀子的博客-优快云博客

这篇文章,如果你不需要记录异常日志信息的话,删掉catch中的添加异常日志的代码即可

还有一点需要注意:就是删除请求使用的是delete请求,如果删除的业务使用了其他请求的话,便不会添加操作日志,因为数据库中已经查不到这条信息了。

/**
 * @author 影耀子(YingYew)
 * @Description: 添加操作日志的切面类
 * @ClassName: AddLogAop
 * @date 2022/7/20 14:38
 */
@Component
@Aspect
public class AddLogAop {

    // 操作日志的相关service
    @Autowired(required = false)
    private ILogOperatingService logOperatingService;
    // 异常日志的相关service,不需要记录异常日志信息的话删掉这个service即可
    @Autowired(required = false)
    private IExceptionLogService exceptionLogService;


    /**
     * 只针对管理后台
     */
    @Pointcut("execution(* com.lookvin.admin.*.controller.*.*(..))")
    public void pointCutServer(){}


    /**
     * 环绕通知
     * @param jp
     */
    @Around("pointCutServer()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        assert requestAttributes != null;
        HttpServletRequest request = (HttpServletRequest) requestAttributes.
                resolveReference(RequestAttributes.REFERENCE_REQUEST);

        assert request != null;
        MethodSignature methodSignature = (MethodSignature) jp.getSignature();
        Method method = methodSignature.getMethod();


        Object[] args = jp.getArgs();   // 获取访问的方法的参数
        String[] argNames = {};       // 参数名
        if (args != null && args.length > 0) {
            argNames = methodSignature.getParameterNames();   // 获取参数名
        }
        Map<String,Object> parameters = new HashMap<>();
        for (int i = 0; i < argNames.length; i++) {
            parameters.put(argNames[i],args[i]);
        }
        List<LogOperating> logOperatings = new ArrayList<>();
        if ("DELETE".equals(request.getMethod())){
            logOperatings = addLogOperating(parameters, method, request, System.currentTimeMillis());
        }
//            System.out.println("执行前");
        Object proceed = jp.proceed();
        if (ObjectUtil.isNotEmpty(proceed) && proceed.getClass() == R.class){
            R r = (R)proceed;
            if (r.isSuccess()){
                // 正常返回添加日志
                if (!"DELETE".equals(request.getMethod())){
                    logOperatings = addLogOperating(parameters, method, request, System.currentTimeMillis());
                }
                if (logOperatings.size() > 0){
                    logOperatingService.saveBatch(logOperatings);
                }
            }

        }

//            System.out.println("执行后");

        return proceed;
    }


    /**
     * 添加操作日志
     * @param parameters  方法参数,key为方法名,value为方法值
     * @param method    方法
     */
    private List<LogOperating> addLogOperating(Map<String,Object> parameters, Method method, HttpServletRequest request, Long starttime) {
        AddHandleLog addHandleLog = method.getAnnotation(AddHandleLog.class);
        if (addHandleLog == null){
            return new ArrayList<>();
        }

        String[] split = addHandleLog.sqlParameter().split("\\.");// sql所需参数
        String sql = addHandleLog.sql();    // 要执行的sql语句


        Object sqlParameterVal = null; // sql所需参数值

        if (split.length > 1){
            Object parameter = parameters.get(split[0]);    // 获取参数对象
            for (int i = 1; i < split.length; i++) {
                if (i==split.length-1){
                    sqlParameterVal = BeanUtil.getFieldValue(parameter,split[i]);   // 根据字段名获取值
                }else {
                    parameter = BeanUtil.getFieldValue(parameter,split[i]);
                }
            }
        }else {
            sqlParameterVal = parameters.get(split[0]);
        }
        if (ArrayUtil.isArray(sqlParameterVal)){
            // 代表数组,将数组转为字符串
            sqlParameterVal = ArrayUtil.join(sqlParameterVal,",");
        }
        if (sqlParameterVal != null && Collection.class.isAssignableFrom(sqlParameterVal.getClass())){
            // 如果是集合,将集合转为字符串
            sqlParameterVal = CollUtil.join((List<?>)sqlParameterVal, ",");
        }
        
        // 我是使用jwt加解密用户信息的,这个是在拦截器中将用户信息存进Attribute属性中的,你也可以根据自己业务去修改成你自己的记录用户信息的办法
        Claims userClaims = (Claims) request.getAttribute("user_claims");
        // 这是获取到此请求是从哪里来的,可以根据自己业务修改
        String soure = (String) request.getAttribute("soure");
        // 获取ip地址
        String clientIP = ServletUtil.getClientIP(request);

        List<Entity> query = HutoolDbUtil.query(sql, sqlParameterVal);

        List<LogOperating> logOperatingList = new ArrayList<>();
        String pattern = "(\\$\\{)(.*?)(})";
        for (Entity entity : query) {
            String summary = addHandleLog.summary();    // 内容摘要
            Matcher summaryMatcher = Pattern.compile(pattern).matcher(summary);

            while (summaryMatcher.find()){
                String group = summaryMatcher.group(2);
                summary = summary.replace("${"+group+"}",entity.getStr(group)==null?"null":entity.getStr(group));
            }
            String title = addHandleLog.title();
            Matcher titleMatcher = Pattern.compile(pattern).matcher(title);
            while (titleMatcher.find()){
                String group = titleMatcher.group(2);
                title = title.replace("${"+group+"}",entity.getStr(group)==null?"null":entity.getStr(group));  // 防止entity.getStr(group)为null空指针
            }

            LogOperating logOperating = new LogOperating()
                    .setDotable(addHandleLog.table())
                    .setDoId(entity.getInt("id"))
                    .setTitle(title)
                    .setSummary(summary)
                    .setOperator(userClaims.getSubject())
                    .setIp(clientIP)
                    .setApi(request.getRequestURL().toString())
                    .setSource(soure)
                    // 根据自己业务修改,此字段是记录发送此请求时浏览器当前地址
                    .setSourceUrl(request.getHeader("RefererUrl"))
                    .setOperatorId(Integer.valueOf(userClaims.getId()))
                    .setApiType(request.getMethod())
                    ;

            long endtime = System.currentTimeMillis();
            logOperating.setVisitTime(Math.toIntExact(endtime - starttime));
            logOperatingList.add(logOperating);
        }

        return logOperatingList;
    }


}

最主要的代码我们都已经编写完了。最后就是如何使用它了,我这里给你们一个接口示例

@RestController
@RequestMapping("/admin/admin/role")
@Validated
public class RoleController extends BaseController {

    interface RoleLog{
        String SUMMARY = "角色名称:${name},角色所属用户类型:${typeStr}";
        String SQL = "select id,name,case type when 0 then '管理员' else '用户角色' end as typeStr from admin_role where id = ?";
        String TABLE = "admin_role"; // 操作的表
    }

    @Autowired
    private IRoleService roleService;


    /**
     * 添加角色
     * @param role 角色信息
     * @return
     */
    @PostMapping(value = "",name = "ROLE_ADD")
    @AddHandleLog(title = "添加角色信息",table =RoleLog.TABLE,
            sql = RoleLog.SQL,
            summary = RoleLog.SUMMARY, sqlParameter = "role.id"
    )
    public R add(@RequestBody @Validated Role role){
        boolean save = roleService.save(role);
        if (save){
            return R.SUCCESS();
        }
        return R.FAIL();
    }

    /**
     * 修改角色信息
     * @param id 角色id
     * @param role 角色信息
     * @return
     */
    @PutMapping(value = "/{id}",name = "ROLE_UPDATE")
    @AddHandleLog(title = "修改角色信息",table =RoleLog.TABLE,
            sql = RoleLog.SQL,
            summary = RoleLog.SUMMARY
    )
    public R update(@PathVariable(name = "id") Integer id,
                    @RequestBody @Validated Role role){
        role.setId(id);
        boolean b = roleService.updateById(role);
        if (b){
            return R.SUCCESS();
        }
        return R.FAIL();

    }

    /**
     * 删除角色
     * @param id 角色id
     * @return
     */
    @DeleteMapping(value = "/{id}",name = "ROLE_DELETE")
    @AddHandleLog(title = "删除角色信息",table =RoleLog.TABLE,
            sql = RoleLog.SQL,
            summary = RoleLog.SUMMARY
    )
    public R delete(@PathVariable(name = "id") Integer id){
        boolean b = roleService.removeById(id);
        if (b){
            return R.SUCCESS();
        }
        return R.FAIL();
    }


    /**
     * 分配权限
     * @param assignPermission 分配权限的参数对象
     * @return
     */
    @PutMapping(value = "/assignPermission",name = "ROLE_ASSIGN_PERMISSION")
    @AddHandleLog(title = "分配权限",table = RoleLog.TABLE,
            sql = "with with_permission_role as (" +
                        "select  ar.id,ar.name,(case ar.`type` when 0 then '管理后台角色' else '普通用户角色' end) as typeStr,arp.permission_id " +
                            "from admin_role ar left join admin_role_permission arp " +
                            "on ar.id =arp.role_id where ar.id= ?" +
                    ")select with_permission_role.id,with_permission_role.name as roleName,with_permission_role.typeStr ," +
                        "(case admin_permission.name is null when true then '取消了所有' else concat('分配了',admin_permission.name) end) as permissionName," +
                        "now(3) as dateTime from with_permission_role left join admin_permission " +
                        "on with_permission_role.permission_id = admin_permission.id",
            summary = "为${typeStr}-${roleName}${permissionName}权限,分配时间:${dateTime}",
            sqlParameter = "assignPermission.id"
    )
    public R assignPermission(@RequestBody @Validated RoleAssignPermission assignPermission){

        Boolean b = roleService.assignRoles(assignPermission);
        if (b){
            return R.SUCCESS();
        }
        return R.FAIL();
    }
}

最后给你们看下我记录的操作日志信息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值