SpringBoot自定义注解实战

概述

  • SpringBoot自定义注解实战,日志拦截,登陆拦截及重复提交示例

定义注解

/**
 * 日志打印注解
 */
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    
    
}

/**
 * 登陆拦截注解
 */
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {
    
    
}

/**
 * 重复提交注解
 */
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Repeat {
    
    
    /**
     * 重复提交标识,支持spel表达式
     */
    String key() default "";
    /**
     * 过期时间,单位/秒,0表示立即过期
     */
    long expire() default 10;
}

编写切面

/**
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 10:48
 */
@Aspect
@Component
public class RequestProcess {
    
    

    /**
     * 拦截的注解
     */
    @Pointcut("@annotation(com.yubest.demo.anno.Log) " +
            "|| @annotation(com.yubest.demo.anno.Login) " +
            "|| @annotation(com.yubest.demo.anno.Repeat)")
    public void point() {
    
     }

    @Around("point()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
    
    
        //获取request对象
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String uri = request.getRequestURI();

        Map<String, String> headerMap = new HashMap<>(16);
        Enumeration<String> headers = request.getHeaderNames();
        //获取请求头
        while (headers.hasMoreElements()) {
    
    
            String name = headers.nextElement();
            headerMap.put(name, request.getHeader(name));
        }

        //获取方法
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = pjp.getTarget().getClass().getDeclaredMethod(methodSignature.getMethod().getName(),
                methodSignature.getMethod().getParameterTypes());

        List<String> argNames = new ArrayList<>(10);
        List<Object> argValues = new ArrayList<>(10);

        //获取方法的参数名称
        String[] params = new LocalVariableTableParameterNameDiscoverer().getParameterNames(method);
        //获取方法的参数值
        Object[] paramValues = pjp.getArgs();
        if (null != params) {
    
    
            for (int i = 0; i < params.length; i++) {
    
    
                if (paramValues[i] instanceof MultipartFile
                        || paramValues[i] instanceof HttpServletRequest
                        || paramValues[i] instanceof HttpServletResponse) {
    
    
                    continue;
                }
                argNames.add(params[i]);
                argValues.add(paramValues[i]);
            }
        }

        //封装请求信息
        Context context = new Context()
                .setUri(uri)
                .setMethod(method)
                .setArgNames(argNames)
                .setArgValues(argValues)
                .setHeaders(headerMap);

        //拦截处理器进行请求处理
        new HanlderChain()
                //@see com.yubest.demo.anno.handler.LogHandler
                .register(new LogHandler())
                //@see com.yubest.demo.anno.handler.RepeatHandler
                .register(new RepeatHandler())
                //@see com.yubest.demo.anno.handler.LoginHandler
                .register(new LoginHandler())
                .execute(context);

        return pjp.proceed();
    }

}

/**
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 11:46
 */
@Data
@Accessors(chain = true)
public class Context {
    
    

    private String uri;

    private Method method;

    private List<String> argNames;

    private List<Object> argValues;

    private Map<String, String> headers;
}

/**
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 11:01
 */
public class HanlderChain {
    
    

    private List<Handler> handlers;

    public HanlderChain() {
    
    
        this.handlers = new ArrayList<>(10);
    }

    public HanlderChain register(Handler handler) {
    
    
        handlers.add(handler);
        return this;
    }

    public void execute(Context context) {
    
    
        for (Handler handler : handlers) {
    
    
            handler.execute(context);
        }
    }
}

编写注解对应的处理逻辑

/**
 * 定义统一处理逻辑接口
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 10:58
 */
public interface Handler {
    
    

    void execute(Context context);
}

/**
 * 日志处理器
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 10:57
 */
@Slf4j
public class LogHandler implements Handler {
    
    

    @Override
    public void execute(Context context) {
    
    
        if (null == context.getMethod().getAnnotation(Log.class)) {
    
    
            return;
        }
        try {
    
    
            Map<String, Object> map = new HashMap<>();
            if (null != context.getArgNames()) {
    
    
                for (int i = 0; i < context.getArgNames().size(); i++) {
    
    
                    map.put(context.getArgNames().get(i), context.getArgValues().get(i));
                }
            }
            log.info("请求uri:{}, 方法:{}, 参数:{}", context.getUri(), context.getMethod().getName(), new ObjectMapper().writeValueAsString(map));
        } catch (JsonProcessingException e) {
    
    
            log.error("Json转换异常", e);
        }
    }
}

/**
 * 登陆处理器
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 10:57
 */
public class LoginHandler implements Handler {
    
    

    private final static String TOKEN = "access-token";

    @Override
    public void execute(Context context) {
    
    
        if (null == context.getMethod().getAnnotation(Login.class)) {
    
    
            return;
        }
        String token = context.getHeaders().get(TOKEN);
        if (!StringUtils.hasText(token)) {
    
    
            throw new RuntimeException("未登陆");
        }
        LoginBean login = CacheUtil.get(token, LoginBean.class);
        if (null == login) {
    
    
            if (StringUtils.hasText(token)) {
    
    
                throw new RuntimeException("未登陆");
            }
        }
    }
}

/**
 * 重复提交处理器
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 10:58
 */
@Slf4j
public class RepeatHandler implements Handler {
    
    

    @Override
    public void execute(Context context) {
    
    
        Repeat repeat = context.getMethod().getAnnotation(Repeat.class);
        if (null == repeat) {
    
    
            return;
        }
        // SpEL表达式的上下文
        EvaluationContext spelContext = new StandardEvaluationContext();
        for (int i = 0; i < context.getArgNames().size(); i++) {
    
    
            // 方法的入参全部set进SpEL的上下文
            spelContext.setVariable(context.getArgNames().get(i), context.getArgValues().get(i));
        }
        Object key = new SpelExpressionParser().parseExpression(repeat.key()).getValue(spelContext);
        if (null != CacheUtil.get(key.toString(), String.class)) {
    
    
            throw new RuntimeException("请忽重复提交");
        }
        CacheUtil.set(key.toString(), UUID.randomUUID().toString(), repeat.expire());
    }

}

编写控制层

/**
 * @Author hweiyu
 * @Description
 * @Date 2021/3/1 14:01
 */
@RestController
public class DemoController {
    
    
    /**
     * 日志测试
     * @param username
     * @param password
     * @return
     */
    @Log
    @PostMapping(value = "/login")
    public Response<String> login(@RequestParam("username") String username, @RequestParam("password") String password) {
    
    
        String token = UUID.randomUUID().toString();
        CacheUtil.set(token, new LoginBean()
                .setUsername(username)
                .setPhone("18888888888")
                .setUserId(1L), 3600L);
        return Response.success(token);
    }

    /**
     * 日志,登陆测试
     * @param request
     * @return
     */
    @Log
    @Login
    @GetMapping(value = "/info")
    public Response<LoginBean> info(HttpServletRequest request) {
    
    
        LoginBean login = CacheUtil.get(request.getHeader("access-token"), LoginBean.class);
        return Response.success(login);
    }

    /**
     * 日志,重复提交测试
     * 以com.yubest.demo.dto.DemoReqDTO中的a属性作为重复提交的标识
     * @param reqDTO
     * @return
     */
    @Log
    @Repeat(key = "#reqDTO.a", expire = 10)
    @PostMapping(value = "/checkRepeat")
    public Response<Void> checkRepeat(@RequestBody DemoReqDTO reqDTO) {
    
    
        return Response.success();
    }
}

/**
 * @Author hweiyu
 * @Description
 * @Date 2021/3/1 14:01
 */
@Data
@Accessors(chain = true)
public class DemoReqDTO implements Serializable {
    
    

    private static final long serialVersionUID = 1019466745376831818L;

    private String a;

    private String b;

}

/**
 * @Author hweiyu
 * @Description
 * @Date 2021/3/23 14:07
 */
@Data
@Accessors(chain = true)
public class LoginBean implements Serializable {
    
    

    private String username;

    private Long userId;

    private String phone;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Response<T> implements Serializable {
    
    

    private static final long serialVersionUID = 4921114729569667431L;

    //状态码,200为成功,其它为失败
    private Integer code;

    //消息提示
    private String message;

    //数据对象
    private T data;

    //成功状态码
    public static final int SUCCESS = 200;

    //失败状态码
    public static final int ERROR = 1000;

    public static <R> Response<R> success() {
    
    
        return new Response<>(SUCCESS, "success", null);
    }

    public static <R> Response<R> success(R data) {
    
    
        return new Response<>(SUCCESS, "success", data);
    }

    public static <R> Response<R> error(String msg) {
    
    
        return new Response<>(ERROR, msg, null);
    }

}

测试

//1.日志测试
curl -X POST \
  http://localhost:8080/login \
  -H 'Content-Type: application/json' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -F username=hello \
  -F password=world
  
//1.打印日志示例
2021-03-23 16:02:30.144  INFO 17144 --- [nio-8080-exec-9] com.yubest.demo.anno.handler.LogHandler  : 请求uri:/login, 方法:login, 参数:{"password":"world","username":"hello"}

//2.登录权限测试
//2.1登录
curl -X POST \
  http://localhost:8080/login \
  -H 'Content-Type: application/json' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -F username=hello \
  -F password=world
  
//2.1返回
{
    "code": 200,
    "message": "success",
    "data": "ef8e371d-446e-4a94-9150-4e8fe379cc61"
}

//2.2使用2.1返回的token调添加@Login的接口
curl -X GET \
  http://localhost:8080/info \
  -H 'access-token: ef8e371d-446e-4a94-9150-4e8fe379cc61'
  
//2.2返回成功示例
{
    "code": 200,
    "message": "success",
    "data": {
        "username": "hello",
        "userId": 1,
        "phone": "18888888888"
    }
}

//2.3用错误的token
curl -X GET \
  http://localhost:8080/info \
  -H 'access-token: xxxxxxxxxxxxxxxxx'
  
//2.3返回失败示例
{
    "code": 1000,
    "message": "未登陆",
    "data": null
}

//3重复提交请求
curl -X POST \
  http://localhost:8080/checkRepeat \
  -H 'Content-Type: application/json' \
  -H 'cache-control: no-cache' \
  -d '{
    "a": "value1",
    "b": "value2"
}'

//3正常返回
{
    "code": 200,
    "message": "success",
    "data": null
}

//3连续请求时返回
{
    "code": 1000,
    "message": "请忽重复提交",
    "data": null
}
  • 码云 https://gitee.com/hweiyu/spring-boot-anno.git

猜你喜欢

转载自blog.csdn.net/vbhfdghff/article/details/115126991