概述
- 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 {
String key() default "";
long expire() default 10;
}
编写切面
@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 {
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()
.register(new LogHandler())
.register(new RepeatHandler())
.register(new LoginHandler())
.execute(context);
return pjp.proceed();
}
}
@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;
}
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);
}
}
}
编写注解对应的处理逻辑
public interface Handler {
void execute(Context context);
}
@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);
}
}
}
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("未登陆");
}
}
}
}
@Slf4j
public class RepeatHandler implements Handler {
@Override
public void execute(Context context) {
Repeat repeat = context.getMethod().getAnnotation(Repeat.class);
if (null == repeat) {
return;
}
EvaluationContext spelContext = new StandardEvaluationContext();
for (int i = 0; i < context.getArgNames().size(); i++) {
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());
}
}
编写控制层
@RestController
public class DemoController {
@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);
}
@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);
}
@Log
@Repeat(key = "#reqDTO.a", expire = 10)
@PostMapping(value = "/checkRepeat")
public Response<Void> checkRepeat(@RequestBody DemoReqDTO reqDTO) {
return Response.success();
}
}
@Data
@Accessors(chain = true)
public class DemoReqDTO implements Serializable {
private static final long serialVersionUID = 1019466745376831818L;
private String a;
private String b;
}
@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;
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