spring boot 使用AOP+自定义注解+反射实现操作日志记录修改前数据和修改后对比数据,并保存至日志表

这篇具有很好参考价值的文章主要介绍了spring boot 使用AOP+自定义注解+反射实现操作日志记录修改前数据和修改后对比数据,并保存至日志表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、添加aop starter依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

二:自定义字段翻译注解。(修改功能时,需要显示如某字段修改前为张三,修改后为李四,name字段对应的中文注释)

package com.test.common.annotation;

import java.lang.annotation.*;

/**
 * 写入日志表时,字段对应的中文注释
 */
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Documented                 //说明该注解将被包含在javadoc中
public @interface FieldMeta {
    /**
     * 汉字全称
     * @return
     */
    String value();
}

使用FieldMeta自定义注解,看个人业务自行觉得是否需要重新定义实体

package com.test.customer.domain;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.test.common.annotation.FieldMeta;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.test.common.core.domain.BaseEntity;


public class TestDangAnDetail extends BaseEntity {
    private static final long serialVersionUID = 1L;

    /**
     * 次数
     */
   
    @FieldMeta("总次数")
    private Long CountNum;
    
}

parseClass调用查询方法的类(查询数据工具类)

//抽象类
public interface ContentParser {
    
        /**
         * 获取信息返回查询出的对象
         *
         * @param joinPoint       查询条件的参数
         * @param dbAccessLogger 注解
         * @return 获得的结果
         */
        Object getOldResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName);
    
        /**
         * 获取信息返回查询出的对象
         *
         * @param joinPoint       查询条件的参数
         * @param dbAccessLogger 注解
         * @return 获得的结果
         */
        Object getNewResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName);
    
    }

实现类 :通过该实现类获取更新前后的数据。
该实现类的实现原理为:获取入参出入的id值,获取sqlSessionFactory,通过sqlSessionFactory获取selectByPrimaryKey()该方法,执行该方法可获取id对应数据更新操作前后的数据。

    @Component
    public class DefaultContentParse implements ContentParser {
        /**
         * 获取更新方法的第一个参数解析其id
         * @param joinPoint       查询条件的参数
         * @param enableModifyLog 注解类容
         * @return
         */
        @Override
        public Object getOldResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) {
            Object info = joinPoint.getArgs()[0];
            Object id = ReflectionUtils.getFieldValue(info, "id");
            Assert.notNull(id,"未解析到id值,请检查入参是否正确");
            Class<?> aClass = enableModifyLog.serviceClass();
            Object result = null;
            try {
                SqlSessionFactory sqlSessionFactory = SpringUtil.getBean(sqlSessionFactoryName);
                Object instance = Proxy.newProxyInstance(
                        aClass.getClassLoader(),
                        new Class[]{aClass},
                        new MyInvocationHandler(sqlSessionFactory.openSession().getMapper(aClass))
                );
                Method selectByPrimaryKey = aClass.getDeclaredMethod("selectByPrimaryKey", Long.class);
                //调用查询方法
                result = selectByPrimaryKey.invoke(instance, id);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return  result;
        }
    
        @Override
        public Object getNewResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) {
            return getOldResult(joinPoint,enableModifyLog,sqlSessionFactoryName);
        }
    
    }

三:定义AOP切面

注意:如不返回操作是否成功状态可能会导致前端出现警告,JSON为空不能被解析

import com.test.common.annotation.FieldMeta;
import com.test.common.core.domain.entity.SysUser;
import com.test.common.enums.BusinessStatus;
import com.test.common.enums.OperatorType;
import com.test.common.utils.*;
import com.test.customer.domain.UserDataOperationLogs;
import com.test.customer.service.IUserDataOperationLogsService;
import com.test.framework.service.OperateLog;
import com.test.common.enums.BusinessType;
import com.test.framework.service.ContentParser;
import com.test.system.domain.SysOperLog;
import com.test.system.service.ISysOperLogService;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 拦截@EnableGameleyLog注解的方法
 * 将具体修改存储到数据库中
 */
@Aspect
@Component
@Slf4j
public class OperateAspect {

    @Autowired
    private IUserDataOperationLogsService iUserDataOperationLogsService;

    @Autowired
    private DefaultContentParse defaultContentParse;

    @Autowired
    private ApplicationContext applicationContext;


    // 环绕通知
    @Around("@annotation(operateLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable{
        Map<String, Object> oldMap=new HashMap<>();
        UserDataOperationLogs lg = new UserDataOperationLogs();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 当不传默认operateType时 根据Method类型自动匹配
        setAnnotationType(request, operateLog);

        // fixme 1.0.9开始不再提供自动存入username功能,请在存储实现类中自行存储
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        Object[] args=joinPoint.getArgs(); // 请求参数

        // 获取当前的用户
        SysUser currentUser = ShiroUtils.getSysUser();

        lg.setCreateBy(currentUser.getLoginName());
        lg.setStatus(0l);
        lg.setUpdateTime(new Date());
        if (OperateType.UPDATE.equals(operateLog.operateType())) {
            try {
               ContentParser contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass());
                Object oldObject = contentParser.getResult(joinPoint, operateLog);
                if (operateLog.needDefaultCompare()) {
                    oldMap = (Map<String, Object>) objectToMap(oldObject); // 存储修改前的对象
                    lg.setStoreId((Long) oldMap.get("storeId"));
                    lg.setItemId((Long) oldMap.get("itemId"));
                }
            } catch (Exception e) {
               e.printStackTrace();
                log.error("service加载失败:", e);
            }
        }
        // joinPoint.proceed()执行前是前置通知,执行后是后置通知
        Object object=joinPoint.proceed();
        if (OperateType.UPDATE.equals(operateLog.operateType())) {
            ContentParser contentParser;
            try {
               String responseParams=JSON.toJSONString(object);
               contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass());
                object = contentParser.getResult(joinPoint, operateLog);
            } catch (Exception e) {
               log.error("service加载失败:", e);
            }
            // 默认不进行比较,可以自己在logService中自定义实现,降低对性能的影响
            if (operateLog.needDefaultCompare()) {
               lg.setContent(defaultDealUpdate(object, oldMap));
            }
            // 如果使用默认缓存 则需要更新到最新的数据
            if(operateLog.defaultCache()
                  && operateLog.parseclass().equals(DefaultContentParse.class)){
                defaultContentParse.updateCache(joinPoint, operateLog,object);
            }
        } else{
           String responseParams=JSON.toJSONString(object);
        }
        //保存当前日志到数据库中
        int logs = iUserDataOperationLogsService.insertUserDataOperationLogs(lg);
        log.info("新增用户操作数据日志成功");
        //返回用户的操作是否成功
        AjaxResult ajaxResult = logs > 0 ? success() : error();
        return ajaxResult;
    }

    private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap){
        try {
            Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
            StringBuilder str = new StringBuilder();
            Object finalNewObject = newObject;
            oldMap.forEach((k, v) -> {
                Object newResult = newMap.get(k);
                if (null!=v && !v.equals(newResult)) {
                    Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                    FieldMeta dataName = field.getAnnotation(FieldMeta.class);
                    SysUser currentUser = ShiroUtils.getSysUser();
                    if (null!=dataName) {
                        str.append("【"+currentUser.getLoginName()+"】").append("修改了【").append(dataName.value() +"】").append("将【").append(v).append("】").append(" 改为:【").append(newResult).append("】。\n");
                    }
                }
            });
            return str.toString();
        } catch (Exception e) {
            log.error("比较异常", e);
            throw new RuntimeException("比较异常",e);
        }
    }

    private Map<?, ?> objectToMap(Object obj) {
        if (obj == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 如果使用JPA请自己打开这条配置
        // mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class);
        Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);
        return mappedObject;
    }

    private void setAnnotationType(HttpServletRequest request,OperateLog modifyLog){
        if(!modifyLog.operateType().equals(OperateType.NONE)){
            return;
        }
        String method=request.getMethod();
        if(RequestMethod.GET.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.SELECT);
        } else if(RequestMethod.POST.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.ADD);
        } else if(RequestMethod.PUT.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.UPDATE);
        } else if(RequestMethod.DELETE.name().equalsIgnoreCase(method)){
            ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.DELETE);
        }
    }
}

或者写成


	//环绕通知切面,切点:DBAccessLogger 更新拦截数据
	@Aspect
    @Component
    @Order(1)
    public class DBAccessLoggerAspect {
    
        // 注入Service用于把日志保存数据库
        @Autowired
        private LogService logService;
        
        @Around("@annotation(com.****.log.DBAccessLogger)") // 环绕通知
        public Object execute(ProceedingJoinPoint pjp) throws Exception {
            // 获得当前访问的class
            Class<?> className = pjp.getTarget().getClass();
            // 获得访问的方法名
            String methodName = pjp.getSignature().getName();
            @SuppressWarnings("rawtypes")
            Class[] argClass = ((MethodSignature) pjp.getSignature()).getParameterTypes();
            // 操作结果,默认为成功
            Long operResult = DictLogConstant.LOGS_OPER_SUCCESS;
            //返回值
            Object rvt = null;
            Method method = className.getMethod(methodName, argClass);
            DBAccessLogger dbAcessLoggerannotation = method.getAnnotation(DBAccessLogger.class);
            String accessTable = dbAcessLoggerannotation.accessTable();
            DBOperationType accessType = dbAcessLoggerannotation.accessType();
            DatabaseEnum databaseEnum = dbAcessLoggerannotation.accessDatasource();
            String accessDatasource = databaseEnum.constName;
            //crd操作直接执行方法
            if (accessType == DBOperationType.DELETE || accessType == DBOperationType.SELECT || accessType == DBOperationType.CREATE) {
                try {
                    rvt = pjp.proceed();
                } catch (Throwable e) {
                    e.printStackTrace();
                    throw new RuntimeException(e.getMessage());
                }
                // 如果没有返回结果,则不将该日志操作写入数据库。
                if (rvt == null) return rvt;
            }
            if ((accessType == DBOperationType.DELETE)
                    && ((CUDResult) rvt).getReturnVal() == 0) {
                operResult = DictLogConstant.LOGS_OPER_FAILURE;
            }
            if (accessTable != null) {
                if (accessType == DBOperationType.SELECT) {
                    Log sysLog = new Log();
					/**
					设置要存放的日志信息
					**/
                    logService.createLog(sysLog);
    
                }
                else if (accessType == DBOperationType.DELETE || accessType == DBOperationType.CREATE) {
                    for (Long recordId : ((CUDResult) rvt).getRecordIds()) {
                        Log sysLog = new Log();
                      	/**
						设置要存放的日志信息
						**/
                        logService.createLog(sysLog);
                    }
                }
                else {
                    //更新操作
                    Log sysLog = new Log();
                    /**
                   设置日志信息
                   **/
                    //获取更行前的数据
                    Map<String, Object> oldMap = null;
                    Object oldObject;
                    try {
                        ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass());
                        //获取更行前的数据
                        oldObject = contentParser.getOldResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName);
                        oldMap = (Map<String, Object>) objectToMap(oldObject);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //执行service
                    Object serviceReturn;
                    try {
                        serviceReturn = pjp.proceed();
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                        throw new RuntimeException(throwable.getMessage());
                    }
                    CUDResult crudResult = (CUDResult) serviceReturn;
                    Object afterResult = null;
                    try {
                        ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass());
                        //更新后的数据
                        afterResult = contentParser.getNewResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //修改前后的变化
                    sysLog.setOperation(accessType.getValue());
                    try {
                        String updatedefrent = defaultDealUpdate(afterResult, oldMap);
                        sysLog.setParams(updatedefrent);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                   /**
                   设置日志信息
                   **/
                    logService.createLog(sysLog);
                    return serviceReturn;
                }
            }
    
            if (operResult.longValue() == DictLogConstant.LOGS_OPER_FAILURE) {
                // 当数据库的UPDATE 和 DELETE操作没有对应的数据记录存在时,抛出异常
                throw new DBAccessException(accessType.getValue() + "的数据记录不存在!");
            }
            return rvt;
        }
        private Map<?, ?> objectToMap(Object obj) {
            if (obj == null) {
                return null;
            }
            ObjectMapper mapper = new ObjectMapper();
            mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            //如果使用JPA请自己打开这条配置
            //mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class);
            Map<?, ?> mappedObject = mapper.convertValue(obj, Map.class);
            return mappedObject;
        }
    
        /**
         *
         * @param newObject 更新过后的结果
         * @param oldMap 更新前的结果
         * @return
         */
        private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap) {
            try {
                Map<String, Object> newMap = (Map<String, Object>) objectToMap(newObject);
                StringBuilder str = new StringBuilder();
                Object finalNewObject = newObject;
                oldMap.forEach((k, v) -> {
                    Object newResult = newMap.get(k);
                    if (v != null && !v.equals(newResult)) {
                        Field field = ReflectionUtils.getAccessibleField(finalNewObject, k);
                        //获取类上的注解
                        DataName dataName = field.getAnnotation(DataName.class);
                        if (field.getType().getName().equals("java.util.Date")) {
                            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            if (dataName != null) {
                                str.append("【").append(dataName.name()).append("】从【")
                                        .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n");
                            } else {
                                str.append("【").append(field.getName()).append("】从【")
                                        .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n");
                            }
                        } else {
                            if (dataName != null) {
                                str.append("【").append(dataName.name()).append("】从【")
                                        .append(v).append("】改为了【").append(newResult).append("】;\n");
                            } else {
                                str.append("【").append(field.getName()).append("】从【")
                                        .append(v).append("】改为了【").append(newResult).append("】;\n");
                            }
                        }
                    }
                });
                return str.toString();
            } catch (Exception e) {
                throw new RuntimeException("比较异常", e);
            }
        }
    }

四:自定义日志注解

package com.test.framework.service;

import com.test.common.utils.IService;
import com.test.common.utils.OperateType;
import com.test.framework.service.impl.DefaultContentParse;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 记录编辑详细信息的标注
 * @author 
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface OperateLog {

   // 模块名称
   String moduleCode() default "";

   // 操作的类型 可以直接调用OperateType 不传时根据METHOD自动确定
   OperateType operateType() default OperateType.NONE;

   // 获取编辑信息的解析类,目前为使用id获取,复杂的解析需要自己实现,默认不填写则使用默认解析类
    Class parseclass() default DefaultContentParse.class;

    // 查询数据库所调用的class文件
    Class serviceclass() default IService.class;

    // 具体业务操作名称
    String handleName() default "";

    // 是否需要默认的改动比较
    boolean needDefaultCompare() default false;

    // id的类型
    Class idType() default Long.class;

    // 是否使用默认本地缓存
    boolean defaultCache() default false;


}

五、@OperateLog注解使用

/**
 * 修改记录详情
 * @OperateLog:AOP自定义日志注解
 */
@PostMapping("/editSave")
@OperateLog(moduleCode="editSave", operateType= OperateType.UPDATE, handleName="修改档案信息", needDefaultCompare=true)
@ResponseBody
public AjaxResult editSave(TCustomerItemRecDetail tCustomerItemRecDetail){
    return toAjax(testDangAnDetailService.updateTestDangAnDetail(tCustomerItemRecDetail));
}

修改功能时,需要实现我们自定义的IService接口,并重写 selectById 方法,在修改前我们需要根据主键id去数据库查询对应的信息,然后在和修改后的值进行比较。

springboot记录数据修改前后内容,spring,架构,spring boot,java,后端文章来源地址https://www.toymoban.com/news/detail-818246.html

六:IService接口

/**
 * 修改时需要记录修改前后的值时,需要根据主键id去查询修改前的值时需要
 * @author zhang
 */
public interface IService<T, S> {
    T selectById(S id);
}

到了这里,关于spring boot 使用AOP+自定义注解+反射实现操作日志记录修改前数据和修改后对比数据,并保存至日志表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Spring Boot入门(23):记录接口日志再也不难!用AOP和自定义注解给Spring Boot加上日志拦截器!

    Spring Boot入门(23):记录接口日志再也不难!用AOP和自定义注解给Spring Boot加上日志拦截器!

            在上两期中,我们着重介绍了如何集成使用 Logback 与 log4j2 日志框架的使用,今天我们讲解的主题依旧跟日志有关,不过不是使用何种开源框架,而是自己动手造。         Spring的核心之一AOP;AOP翻译过来叫面向切面编程, 核心就是这个切面. 切面表示从业务逻辑中

    2024年02月11日
    浏览(9)
  • 对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)

    对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)

    一个web系统,从接口的使用范围也可以分为对内和对外两种,对内的接口主要限于一些我们内部系统的调用,多是通过内网进行调用,往往不用考虑太复杂的鉴权操作。但是,对于对外的接口,我们就不得不重视这个问题,外部接口没有做鉴权的操作就直接发布到互联网,而

    2024年04月29日
    浏览(57)
  • 【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换&读写分离】—— 案例实战

    【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换&读写分离】—— 案例实战

                                                 💧 S p r i n g A O P + 主从数据源切换 + 读写分离 + 自定义注解案例实战! color{#FF1493}{Spring AOP + 主从数据源切换 + 读写分离 + 自定义注解 案例实战!} Sp r in g A OP + 主从数据源切换 + 读写分离 + 自定义注解案例

    2024年02月15日
    浏览(10)
  • Spring5框架——AOP操作:通过Aspectj注解方式和配置文件方式来实现

    Spring5框架——AOP操作:通过Aspectj注解方式和配置文件方式来实现

    o((⊙﹏⊙))o. ** 之前的博客介绍了什么是AOP,以及AOP的底层原理,AOP主要是在原本的基础上添加一些之外的功能但是添加的功能是不会修改原定的代码,接下来为你介绍的是Aspectj注解,Spring 框架一般都是基于 AspectJ 实现 AOP 操作。AspectJ 不是 Spring 组成部分,独立 AOP 框架,一

    2024年02月16日
    浏览(4)
  • Java实战:Spring Boot实现AOP记录操作日志

    本文将详细介绍如何在Spring Boot应用程序中使用Aspect Oriented Programming(AOP)来实现记录操作日志的功能。我们将探讨Spring Boot集成AOP的基本概念,以及如何使用Spring Boot实现AOP记录操作日志。最后,我们将通过一个具体示例来演示整个实现过程。本文适合已经具备Spring Boot基础

    2024年02月22日
    浏览(10)
  • spring自定义注解+aop+@BindingParam

    2.1 声明切面注解  2.1.1切面对应枚举  2.2 声明绑定参数注解 4.1 ThreadLocalUtil  4.2  自定义异常

    2024年02月14日
    浏览(7)
  • springboot3使用自定义注解+AOP+redis优雅实现防重复提交

    springboot3使用自定义注解+AOP+redis优雅实现防重复提交

      ⛰️个人主页:     蒾酒 🔥 系列专栏 :《spring boot实战》 🌊 山高路远,行路漫漫,终有归途 目录 写在前面 实现思路 实现步骤 1.定义防重复提交注解 2.编写一个切面去发现该注解然后执行防重复提交逻辑 3.测试 依赖条件 1.接口上标记防重复提交注解 2.接口测试 写在最

    2024年04月11日
    浏览(9)
  • 使用Spring Boot AOP实现日志记录

    使用Spring Boot AOP实现日志记录

    目录 介绍 1.1 什么是AOP 1.2 AOP体系与概念 AOP简单实现 2.1 新建一个SpringBoot项目,无需选择依赖 2.2 设置好本地Maven配置后,在pom.xml文件里添加添加maven依赖 2.3 创建一个业务类接口 2.4 在实体类实现接口业务  2.5 在单元测试运行结果 2.6 创建切面类 2.7 再次运行测试  总结 1.

    2024年02月14日
    浏览(14)
  • Spring Boot 3自定义注解+拦截器+Redis实现高并发接口限流

    在当今互联网应用开发中,高并发访问是一个常见的挑战。为了保障系统的稳定性和可靠性,我们需要对接口进行限流,防止因过多的请求导致系统崩溃。 本文将介绍如何利用Spring Boot 3中的自定义注解、拦截器和Redis实现高并发接口限流,帮助程序员解决这一挑战。 1. 自定

    2024年04月28日
    浏览(13)
  • Spring Boot入门(14):使用Mybatis-Plus轻松实现高效自定义SQL操作!

            在上几期,我们既讲了如何整合Mybatis-Plus进行数据库的增删改查,也讲解了如何使用MP的 Wrapper 构造器,但若是遇到复杂业务逻辑,如多表联查、动态拼接条件等,这些操作往往会让代码变得冗长且难以维护。但是,有了Mybatis-Plus这个优秀的框架,我们可以轻松实现

    2024年02月10日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包