# spring-boot-anno **Repository Path**: hweiyu/spring-boot-anno ## Basic Information - **Project Name**: spring-boot-anno - **Description**: SpringBoot自定义注解实战 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-03-19 - **Last Updated**: 2022-05-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 概述 * SpringBoot自定义注解实战,日志拦截,登陆拦截及重复提交示例 ### 定义注解 ```java /** * 日志打印注解 */ @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 0; } ``` ### 编写切面 ```java /** * @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 headerMap = new HashMap<>(16); Enumeration 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 argNames = new ArrayList<>(10); List 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 argNames; private List argValues; private Map headers; } /** * @Author hweiyu * @Description * @Date 2021/3/23 11:01 */ public class HanlderChain { private List 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); } } } ``` ### 编写注解对应的处理逻辑 ```java /** * 定义统一处理逻辑接口 * @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 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()); } } ``` ### 编写控制层 ```java /** * @Author hweiyu * @Description * @Date 2021/3/1 14:01 */ @RestController public class DemoController { /** * 日志测试 * @param username * @param password * @return */ @Log @PostMapping(value = "/login") public Response 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 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 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 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 Response success() { return new Response<>(SUCCESS, "success", null); } public static Response success(R data) { return new Response<>(SUCCESS, "success", data); } public static Response error(String msg) { return new Response<>(ERROR, msg, null); } } ``` ### 测试 ```curl //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 } ```