当我们使用Spring Boot发布后台接口时,如果多个接口存在相同的操作,比如对参数进行非空校验或验签。这些操作都是在具体业务代码之前,如果我们每个接口都进行独立编码,即使我们将验签操作封装成方法,也会有大量重复代码。这时我们可以使用AOP实现拦截器,对参数进行非空校验或验签。

1、引包

首先引包。

<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version></dependency>

aspectjweaver用于引入AOP的相关的注解,如@Aspect@Pointcut

2、自定义异常类

新建GlobalException异常类,用于捕捉全局异常。

public class GlobalException extends Exception { private static final long serialVersionUID = -238091758285157331L; private String code; private String message; public GlobalException() { super(); } public GlobalException(String message) { super(message); this.message = message; } public GlobalException(String code, String message) { super(code + ": " + message); this.code = code; this.message = message; } public GlobalException(String message, Throwable throwable) { super(message, throwable); this.message = message; } public GlobalException(Throwable throwable) { super(throwable); } public String getCode() { return code; } @Override public String getMessage() { return message; } @Override public String toString() { return code + ": " + message; }}

3、全局异常处理器

新建全局异常处理器,用户处理捕捉到的异常。

import com.alibaba.fastjson.JSONObject;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.servlet.http.HttpServletRequest;@RestControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler(value = Exception.class) @ResponseBody public JSONObject exceptionHandler(HttpServletRequestrequest, Exception e) { return ReturnMessage.createReturnMessage("4000", e.getMessage()); }}

4、自定义AOP

自定义AOP,对接口入参进行非空校验和验签;并对接口返回参数进行签名。

import cn.wbnull.springbootdemo.boot.GlobalException;import cn.wbnull.springbootdemo.constant.DemoConstants;import cn.wbnull.springbootdemo.util.JSONUtils;import cn.wbnull.springbootdemo.util.StringUtils;import com.alibaba.fastjson.JSONObject;import org.apache.commons.codec.digest.DigestUtils;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Aspect@Componentpublic class SignAop { /** * 声明一个切入点,范围为controller包下所有的类 * 注:作为切入点签名的方法必须返回void类型 */ @Pointcut("execution(public *cn.wbnull.springbootdemo.controller.*.*(..))") private void signAop() { } /** * 前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常) * * @param joinPoint * @throws Exception */ @Before("signAop()") public void doBefore(JoinPoint joinPoint) throws Exception { Object[] objects =joinPoint.getArgs(); String sign = objects[0].toString(); String timestamp = objects[1].toString(); String data = objects[2].toString(); if (StringUtils.isEmpty(sign) || StringUtils.isEmpty(timestamp)|| StringUtils.isEmpty(data)){ throw new GlobalException("sign or timestamp or data is null"); } String md5String = "data=" + data + "&key=1234567890&timestamp=" + timestamp; String signNow = DigestUtils.md5Hex(md5String); if (!sign.equalsIgnoreCase(signNow)) { throw new GlobalException("sign is error"); } } /** * 后置通知:在某连接点正常完成后执行的通知,通常在一个匹配的方法返回的时候执行 * * @param joinPoint * @param params * @return */ @AfterReturning(value = "signAop()", returning = "params") public JSONObject doAfterReturning(JoinPointjoinPoint, JSONObject params) { String data = JSONUtils.getJSONString(params, DemoConstants.DATA); long timestamp = System.currentTimeMillis() / 1000; String md5String = "data="+ data + "&key=1234567890&timestamp=" + timestamp; String sign = DigestUtils.md5Hex(md5String); params.put(DemoConstants.TIMESTAMP, timestamp); params.put(DemoConstants.SIGN, sign); return params; }}

5、控制器

新建登录接口控制器LoginController

import com.alibaba.fastjson.JSONObject;import org.springframework.context.annotation.Scope;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController@Scope("prototype")public class LoginController extends BaseController { @PostMapping(value = "/login") public JSONObject login( @RequestParam(value = "sign") String sign, @RequestParam(value = "timestamp") Stringtimestamp, @RequestParam(value = "data") String data ) throws Exception { return baseService.login(data); }}

6、服务类

新建登录接口服务类LoginService

import cn.wbnull.springbootdemo.util.JSONUtils;import cn.wbnull.springbootdemo.util.LoggerUtils;import com.alibaba.fastjson.JSONObject;import org.springframework.stereotype.Service;@Servicepublic class LoginService { public JSONObject login(Object data) throws Exception { JSONObject responseParams = new JSONObject(); JSONObject requestParams = JSONObject.parseObject(data.toString()); if (JSONUtils.getJSONString(requestParams, "username").equalsIgnoreCase( JSONUtils.getJSONString(requestParams, "password"))) { responseParams.put("code", "1000"); responseParams.put("message", "SUCCESS"); } else { responseParams.put("code", "2000"); responseParams.put("message", "FAIL"); } return responseParams; }}

7、测试

1)、当传入参数缺少时,接口自动提示(未进入AOP):

2)、当传入参数正常,但存在某参数为空时,AOP校验参数是否为空:

3)、当传入参数正常且都不为空时,AOP执行@Before注释的方法进行验签操作:

4)、若签名校验通过,则执行具体业务代码,并在接口返回时进入到@AfterReturning注释的方法进行签名返回:

8、日志记录

增加日志记录,更方便直观的查看代码执行顺序。

1)、引包

<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency>

2)、配置日志记录格式log4j.properties

log4j.rootLogger=debug,Console,logInfo,logError,logDebuglog4j.category.org.springframework=debug,Console,logInfo,logError,logDebug#输出到控制台log4j.appender.Console=org.apache.log4j.ConsoleAppenderlog4j.appender.Console.Target=System.outlog4j.appender.Console.layout=org.apache.log4j.PatternLayoutlog4j.appender.Console.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss,SSS}][%C.%M(%F:%L)] || %m%n#输出到文件log4j.appender.logInfo=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.logInfo.File=${pwd}/SpringBootDemoLogs/SpringBootDemoLog_log4j.appender.logInfo.Append=truelog4j.appender.logInfo.DatePattern=yyyy-MM-dd'.log'log4j.appender.logInfo.Threshold=ALLlog4j.appender.logInfo.layout=org.apache.log4j.PatternLayoutlog4j.appender.logInfo.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss,SSS}][%C.%M(%F:%L)] || %m%n#错误日志单独记录log4j.appender.logError=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.logError.File=${pwd}/SpringBootDemoLogs/SpringBootDemoErrorLog_log4j.appender.logError.Append=truelog4j.appender.logError.DatePattern=yyyy-MM-dd'.log'log4j.appender.logError.Threshold=ERRORlog4j.appender.logError.layout=org.apache.log4j.PatternLayoutlog4j.appender.logError.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss,SSS}][%C.%M(%F:%L)] || %m%n#Debug日志单独记录log4j.appender.logDebug=org.apache.log4j.DailyRollingFileAppenderlog4j.appender.logDebug.File=${pwd}/SpringBootDemoLogs/SpringBootDemoDebugLog_log4j.appender.logDebug.Append=truelog4j.appender.logDebug.DatePattern=yyyy-MM-dd'.log'log4j.appender.logDebug.Threshold=DEBUGlog4j.appender.logDebug.layout=org.apache.log4j.PatternLayoutlog4j.appender.logDebug.filter.infoFilter=org.apache.log4j.varia.LevelRangeFilterlog4j.appender.logDebug.filter.infoFilter.LevelMin=DEBUGlog4j.appender.logDebug.filter.infoFilter.LevelMax=DEBUGlog4j.appender.logDebug.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss,SSS}][%C.%M(%F:%L)] || %m%n

3)、Logger工具类LoggerUtils

import org.apache.log4j.Logger;public class LoggerUtils { private static Logger logger; public static Logger getLogger() { if (logger == null) { logger = Logger.getLogger("SpringBootDemoLogs"); } return logger; }}

4)、SignAop增加日志记录

@Before("signAop()")public void doBefore(JoinPoint joinPoint) throws Exception { //code String strLog = "[" + Thread.currentThread().getId() + "]" + "[请求方法] " + joinPoint.getSignature().getName() + " ||"; LoggerUtils.getLogger().info(strLog + "[请求参数] sign=" + sign + ",timestamp=" + timestamp + ",data=" + data);  //code}@AfterReturning(value = "signAop()", returning = "params")public JSONObject doAfterReturning(JoinPoint joinPoint, JSONObject params) { //code String strLog = "[" + Thread.currentThread().getId() + "]" + "[请求方法] " + joinPoint.getSignature().getName() + " ||"; LoggerUtils.getLogger().info(strLog + "[返回参数] " + params); return params;}

5)、Postman请求,日志记录如下

[INFO][2018-09-25 23:59:46,483][cn.wbnull.springbootdemo.boot.aop.SignAop.doBefore(SignAop.java:50)] || [21][请求方法] login ||[请求参数] sign=fafb23d6e7daae823ca3d4a15a2ab4bd,timestamp=1537888124,data={"username":"123","password":"123"}[INFO][2018-09-25 23:59:46,499][cn.wbnull.springbootdemo.controller.LoginController.login(LoginController.java:26)] || [21] LoginController[INFO][2018-09-25 23:59:46,604][cn.wbnull.springbootdemo.service.LoginService.login(LoginService.java:30)] || [21] LoginService[INFO][2018-09-25 23:59:46,615][cn.wbnull.springbootdemo.boot.aop.SignAop.doAfterReturning(SignAop.java:84)] || [21][请求方法] login ||[返回参数] {"code":"1000","sign":"18954bd1a668e0d6fc98e4e15386352f","message":"SUCCESS","timestamp":1537891186}


GitHubhttps://github.com/dkbnull/SpringBootDemo

CSDNhttps://blog.csdn.net/dkbnull/article/details/82847647


本篇文章来源于微信公众号: 程序员null



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2018-09-27