SpringBoot -- 使用AOP和自定义注解实现在方法级别上对入参校验

  受其他项目启发,想做一个通过aop和自定义注解实现的入参校验。看了下网上的资料都不太符合我的想法,我是想着只在方法上加注解,把需要校验的字段传入注解中,而不是在要校验的每个字段上面加注解,因为我觉得后面这种形式对代码侵入性太大了,对于需要校验的字段,每个上面都要加上一个甚至几个的注解,所以我想写一个只在方法上对入参的校验,下面就上代码吧。

  1. 自定义注解还是用的aop来实现的,所以第一步还是要引入aop的依赖的。
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
  1. 定义注解,我这里定义了4个注解,感觉应该可以应对大部分的校验了,至少能应对我们公司的业务需要了。这四个注解主要实现了:非空校验、字段长度校验、对日期格式的校验、身份证号校验、手机号校验、邮箱校验和正则校验。下面是这4个注解的代码:
/**
 * @Author: TheBigBlue
 * @Description: 参数校验,校验不为空、身份证号、手机号、Email
 * @Date: 2019/6/18
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamValidate {

    /**
     * 需要校验非空的字段
     */
    String[] notNull() default {};

    /**
     * 需要校验身份证的字段
     */
    String[] idNo() default {};

    /**
     * 需要校验手机号的字段
     */
    String[] phone() default {};

    /**
     * 需要校验邮箱的字段
     */
    String[] email() default {};
}

/**
 * @Author: TheBigBlue
 * @Description: 校验字段长度
 * @Date: 2019/6/18
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LengthValidate {

    /**
     * 需要校验的字段
     */
    String[] value();

    /**
     * 最小长度
     */
    int minLength() default 1;

    /**
     * 最大长度
     */
    int maxLength() default 30;
}

/**
 * @Author: TheBigBlue
 * @Description:
 * @Date: 2019/6/18
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DateValidate {

    /**
     * 需要校验的字段
     */
    String[] value();

    /**
     * 校验的日期格式
     */
    String pattern() default "yyyy-MM-dd HH:mm:ss";
}

/**
 * @Author: TheBigBlue
 * @Description: 正则校验
 * @Date: 2019/6/18
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValidate {

    /**
     * 需要校验的字段
     */
    String[] value();

    /**
     * 自定义的正则表达式
     */
    String regex();
}

  1. 接下来是写切面和确认切点,使用环绕通知或前置通知,对方法之前增强,获取注解中对应的字段,校验。下面是切面的代码,其中主要还用到了另外4个工具类来分别实现上面4个注解的校验。
      主要思路是对所有类所有方法织入,如果该方法无参数,则直接执行该方法,如果该方法有参数,则获取该方法的请求参数和参数值并映射为map对应,然后获取该方法的注解。如果注解包含上面那几种校验的注解,则对应校验之,如果校验失败,则返回统一的错误信息,如果校验成功,参数没有问题,则执行该方法。
/**
 * @Author: TheBigBlue
 * @Description: 参数校验切面
 * @Date: 2019/6/17
 */
@Component
@Aspect
@Order(1002)
public class ParamValidateAspect {

    //对包下所有类的所有方法增强
    private final String executeExpr = "execution(* com.caxs.app..*.*(..))";

    /**
     * @Author: TheBigBlue
     * @Description: 拦截请求,校验参数
     * @Date: 2019/6/20
     * @Param joinPoint:
     * @Return:
     **/
    @Around(executeExpr)
    public Object paramValidate(ProceedingJoinPoint joinPoint) throws Throwable {
        //请求参数值
        Object[] args = joinPoint.getArgs();
        //请求的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        JsonResponse resp = JsonResponse.success();
        if (!ObjectIsNullUtil.isNullOrEmpty(args)) {
            List<Object> filteredArgs = Arrays.stream(args)
                    //空值转为空串,防止后面get报错
                    .map(arg -> arg == null ? "" : arg)
                    .collect(Collectors.toList());
            //获取参数名称
            LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
            String[] params = paramNames.getParameterNames(method);
            //拼接请求参数和参数值
            Map<String, Object> rqsParams = IntStream.range(0, filteredArgs.size())
                    .boxed()
                    .collect(Collectors.toMap(j -> params[j], j -> filteredArgs.get(j)));

            Annotation[] annos = method.getDeclaredAnnotations();
            //如果注解不为空,则检查是否有参数校验注解
            if (!ObjectIsNullUtil.isNullOrEmpty(annos)) {
                for (Annotation anno : annos) {
                    if (anno instanceof DateValidate) {
                        //校验日期
                        resp = ValidateDateUtil.validateDate(anno, rqsParams);
                    } else if (anno instanceof LengthValidate) {
                        //校验字段长度
                        resp = ValidateLengthUtil.validateLength(anno, rqsParams);
                    } else if (anno instanceof ParamValidate) {
                        //校验不为空、身份证号、手机号、Email
                        resp = ValidateParamUtil.validateParam(anno, rqsParams);
                    } else if (anno instanceof RegexValidate) {
                        //校验正则
                        resp = ValidateRegexUtil.validateRegex(anno, rqsParams);
                    }
                    //有校验不通过,返回校验失败
                    if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(resp.getCode())) {
                        return resp;
                    }
                }
            }
        }
        return joinPoint.proceed();
    }

}

  1. 上面获取到对应的校验注解调用对应的工具类进行对应的校验,然后下面是四个工具类和一个通用工具类的代码:
      先是一个通用工具类,主要提供:反射获取该属性的属性值,获取所有需要校验的字段的属性名和属性值
/**
 * @Author: TheBigBlue
 * @Description:
 * @Date: 2019/6/19
 */
public class CommonAspectUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(CommonAspectUtil.class);

    /**
     * @Author: TheBigBlue
     * @Description: 根据对象和属性名获取属性值
     * @Date: 2019/6/19
     * @Param targetObj:
     * @Param fileName:
     * @Return:
     **/
    public static Object getFieldByObjectAndFileName(Object targetObj, String fileName) throws Exception {
        Method methdo = targetObj.getClass().getMethod(getGetterNameByFieldName(fileName));
        return methdo.invoke(targetObj);
    }

    /**
     * @Author: TheBigBlue
     * @Description: 根据属性名得到该属性的getter方法名
     * @Date: 2019/6/19
     * @Param fieldName:
     * @Return:
     **/
    public static String getGetterNameByFieldName(String fieldName) {
        return "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }

    /**
     * @Author: TheBigBlue
     * @Description: 获取所有需要校验的字段的属性名和属性值
     * @Date: 2019/6/19
     * @Param fieldNames: 需要校验的字段数组
     * @Param rqsParams: 属性名和属性值map
     * @Return:
     **/
    public static List<FieldInfo> getFieldInfoList(String[] fieldNames, Map<String, Object> rqsParams) {
        List<FieldInfo> fieldInfoList = new ArrayList<>();
        for (String fieldName : fieldNames) {
            try {
                if (fieldName.indexOf(".") > 0) {
                    //只校验对应对象中的属性
                    String[] split = fieldName.split("\\.");
                    //根据对象和属性名获取属性值
                    Object fieldValue = getFieldByObjectAndFileName(rqsParams.get(split[0]), split[1]);
                    fieldInfoList.add(new FieldInfo(split[1], fieldValue));
                } else if (fieldName.indexOf(":all") > 0) {
                    //TODO 所有对象中属性都校验
                } else {
                    //校验单属性日期格式
                    fieldInfoList.add(new FieldInfo(fieldName, rqsParams.get(fieldName)));
                }
            } catch (Exception e) {
                LOGGER.error("获取" + fieldName + "属性信息错误!", e);
            }
        }
        return fieldInfoList;
    }
}

  然后是日期校验工具类。

/**
 * @Author: TheBigBlue
 * @Description: 校验日期
 * @Date: 2019/6/19
 */
public class ValidateDateUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ValidateDateUtil.class);

    /**
     * @Author: TheBigBlue
     * @Description: 校验日期格式
     * @Date: 2019/6/19
     * @Param anno: 注解对象
     * @Param rqsParams: 请求参数和参数值
     * @Return:
     **/
    public static JsonResponse validateDate(Annotation anno, Map<String, Object> rqsParams) {
        DateValidate dateValidateAnno = (DateValidate) anno;
        //日期格式
        String pattern = dateValidateAnno.pattern();
        //需要校验的字段
        String[] fieldNames = dateValidateAnno.value();
        //获取所有需要校验的字段的属性名和属性值
        List<FieldInfo> fieldInfoList = CommonAspectUtil.getFieldInfoList(fieldNames, rqsParams);
        for (FieldInfo fieldInfo : fieldInfoList) {
            //校验每个属性,有一个不符合则返回
            JsonResponse fail = validate(fieldInfo.getFieldName(), fieldInfo.getFieldValue(), pattern);
            if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(fail.getCode())) {
                return fail;
            }
        }
        return JsonResponse.success();
    }

    /**
     * @Author: TheBigBlue
     * @Description: 校验日期格式
     * @Date: 2019/6/19
     * @Param fieldName: 属性名称
     * @Param fieldValue: 属性值
     * @Param pattern: 日期格式
     * @Return:
     **/
    private static JsonResponse validate(String fieldName, Object fieldValue, String pattern) {
        if (ObjectIsNullUtil.isNullOrEmpty(fieldValue)) {
            LOGGER.info(fieldName + "参数为空!");
            return JsonResponse.fail(fieldName + "参数为空!");
        } else {
            //校验日期格式
            try {
                String dateStr = (String) fieldValue;
                SimpleDateFormat format = new SimpleDateFormat(pattern);
                // 设置lenient为false.否则SimpleDateFormat会比较宽松地验证日期
                format.setLenient(false);
                format.parse((String) fieldValue);
                //保证格式完全匹配
                if (dateStr.length() != pattern.length()) {
                    throw new BusinessException();
                }
                return JsonResponse.success();
            } catch (Exception e) {
                LOGGER.error(fieldName + "日期格式错误。", e);
                return JsonResponse.fail(fieldName + "日期格式错误!");
            }
        }
    }
}

  然后是字段长度校验工具类。

/**
 * @Author: TheBigBlue
 * @Description: 校验字段长度
 * @Date: 2019/6/19
 */
public class ValidateLengthUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ValidateLengthUtil.class);

    /**
     * @Author: TheBigBlue
     * @Description: 校验字段长度
     * @Date: 2019/6/19
     * @Param anno: 注解对象
     * @Param rqsParams: 请求参数和参数值
     * @Return:
     **/
    public static JsonResponse validateLength(Annotation anno, Map<String, Object> rqsParams) {
        LengthValidate lengthValidateAnno = (LengthValidate) anno;
        //需要校验的字段
        String[] fieldNames = lengthValidateAnno.value();
        //最小长度
        int minLength = lengthValidateAnno.minLength();
        //最大长度
        int maxLength = lengthValidateAnno.maxLength();
        //获取所有需要校验的字段的属性名和属性值
        List<FieldInfo> fieldInfoList = CommonAspectUtil.getFieldInfoList(fieldNames, rqsParams);
        for (FieldInfo fieldInfo : fieldInfoList) {
            //校验每个属性,有一个不符合则返回
            JsonResponse fail = valiedate(fieldInfo.getFieldName(), fieldInfo.getFieldValue(), minLength, maxLength);
            if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(fail.getCode())) {
                return fail;
            }
        }
        return JsonResponse.success();
    }

    /**
     * @Author: TheBigBlue
     * @Description:
     * @Date: 2019/6/19 校验字段长度
     * @Param fieldName: 属性名称
     * @Param fieldValue: 属性值
     * @Param minValue: 最小长度
     * @Param maxValue: 最大长度
     * @Return:
     **/
    private static JsonResponse valiedate(String fieldName, Object fieldValue, int minValue, int maxValue) {
        if (ObjectIsNullUtil.isNullOrEmpty(fieldValue)) {
            LOGGER.info(fieldName + "参数为空!");
            return JsonResponse.fail(fieldName + "参数为空!");
        } else {
            //校验字段长度
            try {
                int length = ((String) fieldValue).length();
                if (length < minValue) {
                    return JsonResponse.fail(fieldName + "参数长度过短,不得少于" + minValue);
                } else if (length > maxValue) {
                    return JsonResponse.fail(fieldName + "参数长度过长,不得大于" + maxValue);
                }
            } catch (Exception e) {
                LOGGER.error(fieldName + "校验字段长度错误。");
                return JsonResponse.fail(fieldName + "校验字段长度错误。");
            }
        }
        return JsonResponse.success();
    }

}

  然后是正则格式校验工具类。

/**
 * @Author: TheBigBlue
 * @Description: 校验正则
 * @Date: 2019/6/19
 */
public class ValidateRegexUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ValidateRegexUtil.class);

    /**
     * @Author: TheBigBlue
     * @Description: 校验正则
     * @Date: 2019/6/19
     * @Param anno: 注解对象
     * @Param rqsParams: 请求参数和参数值
     * @Return:
     **/
    public static JsonResponse validateRegex(Annotation anno, Map<String, Object> rqsParams) {
        RegexValidate regexValidateAnno = (RegexValidate) anno;
        //需要校验的字段
        String[] fieldNames = regexValidateAnno.value();
        //正则表达式
        String regex = regexValidateAnno.regex();
        //获取所有需要校验的字段的属性名和属性值
        List<FieldInfo> fieldInfoList = CommonAspectUtil.getFieldInfoList(fieldNames, rqsParams);
        for (FieldInfo fieldInfo : fieldInfoList) {
            //校验每个属性,有一个不符合则返回
            JsonResponse fail = validate(fieldInfo.getFieldName(), fieldInfo.getFieldValue(), regex);
            if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(fail.getCode())) {
                LOGGER.info("正则格式错误!");
                return fail;
            }
        }
        return JsonResponse.success();
    }

    /**
     * @Author: TheBigBlue
     * @Description: 校验正则格式
     * @Date: 2019/6/19
     * @Param fieldName: 属性名称
     * @Param fieldValue: 属性值
     * @Param regex: 正则格式
     * @Return:
     **/
    public static JsonResponse validate(String fieldName, Object fieldValue, String regex) {
        if (ObjectIsNullUtil.isNullOrEmpty(fieldValue)) {
            LOGGER.info(fieldName + "参数为空!");
            return JsonResponse.fail(fieldName + "参数为空!");
        } else {
            //校验正则格式
            try {
                Pattern pattern = Pattern.compile(regex);
                if (!pattern.matcher((String) fieldValue).matches()) {
                    LOGGER.info(fieldName + "正则格式错误。");
                    return JsonResponse.fail(fieldName + "正则格式错误!");
                }
                return JsonResponse.success();
            } catch (Exception e) {
                LOGGER.error(fieldName + "正则格式错误。", e);
                return JsonResponse.fail(fieldName + "正则格式错误!");
            }
        }
    }
}

  然后是非空校验、身份证校验、手机号校验、邮箱校验工具类,其实后面三个就是正则校验,只不过正则表达式是已经写好的。

/**
 * @Author: TheBigBlue
 * @Description: 校验不为空、身份证号、手机号、Email
 * @Date: 2019/6/19
 */
public class ValidateParamUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ValidateParamUtil.class);
    private static final String NOT_NULL = "不为空";
    private static final String ID_NO = "身份证号";
    private static final String PHONE = "手机号";
    private static final String EMAIL = "邮箱";
    private static final String ID_NO_REGEX = "(^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)";
    private static final String PHONE_REGEX = "^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147))\\d{8}$";
    private static final String EMIAL_REGEX = "^([\\w-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([\\w-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$";

    /**
     * @Author: TheBigBlue
     * @Description: 校验不为空、身份证号、手机号、Email
     * @Date: 2019/6/19
     * @Param anno: 注解对象
     * @Param rqsParams: 请求参数和参数值
     * @Return:
     **/
    public static JsonResponse validateParam(Annotation anno, Map<String, Object> rqsParams) {
        ParamValidate paramValidateAnno = (ParamValidate) anno;
        //需要校验非空的字段
        String[] notNullFieldNames = paramValidateAnno.notNull();
        JsonResponse notNullFail = chooseTyp(notNullFieldNames, rqsParams, NOT_NULL);
        if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(notNullFail.getCode())) {
            return notNullFail;
        }

        //需要校验身份证的字段
        String[] idNoFieldNames = paramValidateAnno.idNo();
        JsonResponse idNoFail = chooseTyp(idNoFieldNames, rqsParams, ID_NO);
        if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(idNoFail.getCode())) {
            return idNoFail;
        }

        //需要校验手机号的字段
        String[] phoneFieldNames = paramValidateAnno.phone();
        JsonResponse phoneFail = chooseTyp(phoneFieldNames, rqsParams, PHONE);
        if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(phoneFail.getCode())) {
            return phoneFail;
        }

        //需要校验Email的字段
        String[] emailFieldNames = paramValidateAnno.email();
        JsonResponse emailFail = chooseTyp(emailFieldNames, rqsParams, EMAIL);
        if (!ApiEnum.RSLT_CDE_000000.getCode().equalsIgnoreCase(emailFail.getCode())) {
            return emailFail;
        }

        return JsonResponse.success();
    }

    /**
     * @Author: TheBigBlue
     * @Description: 校验不同类型
     * @Date: 2019/6/19
     * @Param fieldNames: 需要校验的字段
     * @Param rqsParams: 请求参数和参数值
     * @Param typ: 校验类型
     * @Return:
     **/
    public static JsonResponse chooseTyp(String[] fieldNames, Map<String, Object> rqsParams, String typ) {
        if (!ObjectIsNullUtil.isNullOrEmpty(fieldNames)) {
            //获取所有需要校验的字段的属性名和属性值
            List<FieldInfo> fieldInfoList = CommonAspectUtil.getFieldInfoList(fieldNames, rqsParams);
            for (FieldInfo fieldInfo : fieldInfoList) {
                //校验每个属性,有一个不符合则返回
                JsonResponse fail = validate(fieldInfo.getFieldName(), fieldInfo.getFieldValue(), typ);
                if (!fail.getCode().equalsIgnoreCase(ApiEnum.RSLT_CDE_000000.getCode())) {
                    return fail;
                }
            }
        }
        return JsonResponse.success();
    }

    /**
     * @Author: TheBigBlue
     * @Description: 校验日期格式
     * @Date: 2019/6/19
     * @Param fieldName: 属性名称
     * @Param fieldValue: 属性值
     * @Param typ: 校验类型
     * @Return:
     **/
    private static JsonResponse validate(String fieldName, Object fieldValue, String typ) {
        if (ObjectIsNullUtil.isNullOrEmpty(fieldValue)) {
            //校验不为空
            LOGGER.info(fieldName + "参数为空!");
            return JsonResponse.fail(fieldName + "参数为空!");
        } else {
            switch (typ) {
                //校验非空,上面已拦截
                case NOT_NULL:
                    return JsonResponse.success();
                //校验身份证号
                case ID_NO:
                    return validateRegex(fieldName, fieldValue, ID_NO, ID_NO_REGEX);
                //校验手机号
                case PHONE:
                    return validateRegex(fieldName, fieldValue, PHONE, PHONE_REGEX);
                //校验Email
                case EMAIL:
                    return validateRegex(fieldName, fieldValue, EMAIL, EMIAL_REGEX);
                //其他情况暂不支持
                default:
                    return JsonResponse.fail("不支持的校验类型!");
            }
        }
    }

    /**
     * @Author: TheBigBlue
     * @Description: 校验正则格式
     * @Date: 2019/6/19
     * @Param fieldName: 属性名称
     * @Param fieldValue: 属性值
     * @Param regex: 正则格式
     * @Return:
     **/
    public static JsonResponse validateRegex(String fieldName, Object fieldValue, String typ, String regex) {
        //校验日期格式
        try {
            Pattern pattern = Pattern.compile(regex);
            if (!pattern.matcher((String) fieldValue).matches()) {
                LOGGER.info(fieldName + typ + "格式错误。");
                return JsonResponse.fail(fieldName + typ + "格式错误。");
            }
            return JsonResponse.success();
        } catch (Exception e) {
            LOGGER.error(fieldName + typ + "格式错误。", e);
            return JsonResponse.fail(fieldName + typ + "格式错误。");
        }
    }

}

  1. 然后就可以使用自定义注解了,类似于下面这两种格式,即目前上面的代码支持直接写参数名称,或者是一个对象里的属性,用.来表示,同时这些注解都是默认实现了非空校验的,即如果一个字段要校验非空和身份证,只需要写一个校验身份证的就行。另外,我本来想支持第三种形式的,就是字段:all的格式,就是不管你这个对象或者map套map多少层,只要是这个字段,我都校验,但因为时间原因,就先不写了,这个基本够我用了。

在这里插入图片描述
在这里插入图片描述

  1. 最后,在我们相互交流相互学习中,也欢迎各位大神如果有更好的工具和开源项目能够分享给我们大家,谢谢各位。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot中使用AOP自定义权限注解可以通过以下步骤实现: 1. 首先,在pom.xml文件中添加Spring Boot AOP的依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` \[1\] 2. 创建一个自定义的注解,用于标记需要进行权限控制的方法或类。例如,可以创建一个名为@CustomPermission的注解。 3. 创建一个切面类,用于定义权限控制的逻辑。在切面类中,可以使用@Before、@After、@Around等注解来定义在方法执行前、执行后或者环绕方法执行时需要执行的逻辑。在这个切面类中,可以通过获取方法数、注解等信息来进行权限校验和控制。 4. 在Spring Boot的配置类中,使用@EnableAspectJAutoProxy注解来启用AOP功能。 5. 在需要进行权限控制的方法或类上,添加自定义的权限注解@CustomPermission。 通过以上步骤,就可以在Spring Boot中使用AOP自定义权限注解来实现权限控制了。使用AOP可以更加灵活地对方法进行拦截和处理,同时可以通过自定义注解来标记需要进行权限控制的方法或类。\[2\]\[3\] #### 引用[.reference_title] - *1* [springboot+mybatis+aop+注解实现数据权限](https://blog.csdn.net/weixin_42935902/article/details/116758260)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [springboot+自定义注解+AOP实现权限控制(一)](https://blog.csdn.net/byteArr/article/details/103984725)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值