lambdaQuery()
26 | .eq(User::getToken, token));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/auth/src/main/java/top/rizon/springbestpractice/auth/AuthenticationInterceptor.java:
--------------------------------------------------------------------------------
1 | package top.rizon.springbestpractice.auth;
2 |
3 | import lombok.RequiredArgsConstructor;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.springframework.stereotype.Component;
6 | import top.rizon.springbestpractice.common.exception.AuthFailedException;
7 | import top.rizon.springbestpractice.common.handler.AbstractAuthHandler;
8 | import top.rizon.springbestpractice.common.utils.AuthUtil;
9 | import top.rizon.springbestpractice.dao.po.User;
10 |
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 |
14 | /**
15 | * @author Rizon
16 | * @date 2019/12/25
17 | */
18 | @Component
19 | @RequiredArgsConstructor
20 | public class AuthenticationInterceptor extends AbstractAuthHandler {
21 | private static final String AUTH_HEADER = "AUTH-TOKEN";
22 | private final AuthService authService;
23 | private final AuthObjMapper authObjMapper;
24 |
25 | @Override
26 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
27 | String token = request.getHeader(AUTH_HEADER);
28 | if (StringUtils.isEmpty(token)) {
29 | throw new AuthFailedException("NOT_FOUND_AUTH_TOKEN",AUTH_HEADER);
30 | }
31 | User user = authService.queryUserCacheable(token);
32 | if (user == null) {
33 | throw new AuthFailedException();
34 | }
35 | AuthUtil.setAuthUser(request, authObjMapper.toAuthUser(user));
36 | return true;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/auth/src/main/java/top/rizon/springbestpractice/auth/SimpleAuthUser.java:
--------------------------------------------------------------------------------
1 | package top.rizon.springbestpractice.auth;
2 |
3 | import lombok.Data;
4 | import top.rizon.springbestpractice.common.model.dto.AuthUser;
5 |
6 | /**
7 | * @author Rizon
8 | * @date 2019/12/25
9 | */
10 | @Data
11 | public class SimpleAuthUser implements AuthUser {
12 | private Long id;
13 | private String name;
14 | private String token;
15 |
16 | @Override
17 | public long getId() {
18 | return id;
19 | }
20 |
21 | @Override
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | @Override
27 | public String getToken() {
28 | return token;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/auth/src/test/java/top/rizon/springbestpractice/AppTest.java:
--------------------------------------------------------------------------------
1 | package top.rizon.springbestpractice;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertTrue;
6 |
7 | /**
8 | * Unit test for simple App.
9 | */
10 | public class AppTest
11 | {
12 | /**
13 | * Rigorous Test :-)
14 | */
15 | @Test
16 | public void shouldAnswerWithTrue()
17 | {
18 | assertTrue( true );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | spring-best-practice
7 | top.rizon.springbestpractice
8 | 0.0.1-SNAPSHOT
9 |
10 | 4.0.0
11 | common
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | org.apache.maven.plugins
20 | maven-compiler-plugin
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/common/src/main/java/top/rizon/springbestpractice/common/aspect/AutoRepairPageAspect.java:
--------------------------------------------------------------------------------
1 | package top.rizon.springbestpractice.common.aspect;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.apache.commons.collections4.CollectionUtils;
5 | import org.apache.commons.lang3.reflect.FieldUtils;
6 | import org.aspectj.lang.ProceedingJoinPoint;
7 | import org.aspectj.lang.annotation.Around;
8 | import org.aspectj.lang.annotation.Aspect;
9 | import org.aspectj.lang.annotation.Pointcut;
10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
11 | import org.springframework.stereotype.Component;
12 | import top.rizon.springbestpractice.common.model.response.PageResponse;
13 |
14 | import java.lang.annotation.*;
15 | import java.lang.reflect.Field;
16 | import java.util.Collection;
17 |
18 | /**
19 | * 分页查询的切片处理
20 | * 只对controller包下的类生效
21 | * 当分页查询的页码参数大于最后一页,则改为最后一页的页码重新查询数据
22 | * 使用配置 {@code base-server.auto-repair-page} 全局禁用该功能
23 | * 使用注解 {@link DisableAutoRepairPage} 在方法上禁用该功能
24 | *
25 | *
26 | * 其实spring对于aop的实现是通过动态代理(jdk的动态代理或者cglib的动态代理),
27 | * 它只是使用了aspectJ的Annotation,并没有使用它的编译期和织入器,
28 | * 所以受限于这点,有些增强就做不到,比如 调用自己的方法就无法走代理
29 | *
30 | *
31 | * 参考:
32 | * AspectJ语法:https://blog.csdn.net/sunlihuo/article/details/52701548
33 | * AspectJ与Spring Aop的区别:https://zhuanlan.zhihu.com/p/50612298
34 | *
35 | * @author Rizon
36 | * @date 2019-09-25
37 | */
38 | @Slf4j
39 | @Aspect
40 | @Component
41 | @ConditionalOnProperty(matchIfMissing = true, value = "base-server.auto-repair-page", havingValue = "true")
42 | public class AutoRepairPageAspect {
43 |
44 | /**
45 | * className+ 表示匹配子类型
46 | */
47 | @Pointcut("execution(top.rizon.springbestpractice.common.model.response.Response+ top.rizon.springbestpractice..controller..*(..))" +
48 | " && !@annotation(top.rizon.springbestpractice.common.aspect.AutoRepairPageAspect.DisableAutoRepairPage)")
49 | public void methodPointcut() {
50 | }
51 |
52 | @Around("methodPointcut()")
53 | public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
54 | Object result = joinPoint.proceed();
55 | try {
56 | //只有返回的是PageResponseParam 才会处理
57 | if (!(result instanceof PageResponse)) {
58 | return result;
59 | }
60 | PageResponse> pageResponse = (PageResponse>) result;
61 | /*
62 | 不进行空数据的判断,只判断页码
63 | if (!hasEmptyData(pageResponse)) {
64 | return result;
65 | }
66 | */
67 | if (pageResponse.getPagination() == null) {
68 | return result;
69 | }
70 | if (pageResponse.getPagination().getTotalPage() < 1) {
71 | //当总页码为0时,则设置当前页码为1 并直接返回结果
72 | pageResponse.getPagination().setPage(1);
73 | return result;
74 | }
75 | if (pageResponse.getPagination().getPage() <= pageResponse.getPagination().getTotalPage()) {
76 | return result;
77 | }
78 | //如果页码是超过了最后一页那么将页面改为最后一页 重新执行方法
79 |
80 | //设置新的页码 因为response中的pageParam对象和参数中的pageParam对象是同一个对象,所以
81 | pageResponse.getPagination().setPage((int) pageResponse.getPagination().getTotalPage());
82 | //重新执行方法
83 | log.info("page greatThan totalPage, reset page and reProceed method");
84 | return joinPoint.proceed();
85 | } catch (Exception ex) {
86 | log.error("aop process PageResponseParam failed", ex);
87 | }
88 | return result;
89 | }
90 |
91 | /**
92 | * 是否包含任意一个 使用了tag标记的空值属性
93 | *
94 | * @return
95 | */
96 | private boolean hasEmptyData(Object dataObj) throws IllegalAccessException {
97 | for (Field field : FieldUtils.getFieldsListWithAnnotation(dataObj.getClass(), ResDataTag.class)) {
98 |
99 | ResDataTag tag = field.getAnnotation(ResDataTag.class);
100 | Object value = FieldUtils.readField(field, dataObj, true);
101 |
102 | if (tag.nullAsEmpty() && value == null) {
103 | return true;
104 | }
105 | if (value instanceof Collection && CollectionUtils.isEmpty((Collection>) value)) {
106 | return true;
107 | }
108 | //递归查看
109 | if (hasEmptyData(value)) {
110 | return true;
111 | }
112 | }
113 | return false;
114 | }
115 |
116 | @Target(ElementType.FIELD)
117 | @Retention(RetentionPolicy.RUNTIME)
118 | public @interface ResDataTag {
119 | /**
120 | * 如果值是null是否也看作返回了空对象去重新计算页面查询数据
121 | * 默认false
122 | * 不推荐使用null作为判断条件
123 | */
124 | boolean nullAsEmpty() default false;
125 | }
126 |
127 | /**
128 | * 禁用自动修复页码功能
129 | */
130 | @Target(ElementType.METHOD)
131 | @Retention(RetentionPolicy.RUNTIME)
132 | @Inherited
133 | public @interface DisableAutoRepairPage {
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/common/src/main/java/top/rizon/springbestpractice/common/aspect/MethodLogAspect.java:
--------------------------------------------------------------------------------
1 | package top.rizon.springbestpractice.common.aspect;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.aspectj.lang.ProceedingJoinPoint;
5 | import org.aspectj.lang.annotation.AfterReturning;
6 | import org.aspectj.lang.annotation.Around;
7 | import org.aspectj.lang.annotation.Aspect;
8 | import org.aspectj.lang.annotation.Pointcut;
9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
10 | import org.springframework.stereotype.Component;
11 |
12 | /**
13 | * 拦截方法 打印执行日志
14 | * @author Rizon
15 | * @date 2019-03-18
16 | */
17 | @Slf4j
18 | @Aspect
19 | @ConditionalOnProperty(value="dev.log.method-process", havingValue = "true" ,matchIfMissing = true)
20 | @Component
21 | public class MethodLogAspect {
22 |
23 | @Pointcut("execution(* top.rizon.springbestpractice..AopExampleService.*(..)) && !bean(methodLogAspect)")
24 | public void methodPointcut() {
25 | }
26 |
27 |
28 | @Around("methodPointcut()")
29 | public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
30 | long start = System.currentTimeMillis();
31 | try {
32 | Object result = joinPoint.proceed();
33 | long end = System.currentTimeMillis();
34 | log.info("执行" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()
35 | + "方法," + parseParams(joinPoint.getArgs()) + ",耗时:" + (end - start) + " ms!");
36 | return result;
37 | } catch (Throwable e) {
38 | long end = System.currentTimeMillis();
39 | log.error(joinPoint + ",耗时:" + (end - start) + " ms,抛出异常 :" + e.getMessage());
40 | throw e;
41 | }
42 | }
43 |
44 | @AfterReturning(returning = "ret", pointcut = "methodPointcut()")
45 | public void doAfterReturning(Object ret) throws Throwable {
46 | log.info("返回值:" + ret);
47 | }
48 |
49 | private String parseParams(Object[] parames) {
50 |
51 | if (null == parames || parames.length <= 0) {
52 | return "该方法没有参数";
53 |
54 | }
55 | StringBuilder param = new StringBuilder("请求参数 # 个:[ ");
56 | int i = 0;
57 | for (Object obj : parames) {
58 | i++;
59 | if (i == 1) {
60 | param.append(obj.toString());
61 | continue;
62 | }
63 | param.append(" ,").append(obj.toString());
64 | }
65 | return param.append(" ]").toString().replace("#", String.valueOf(i));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/common/src/main/java/top/rizon/springbestpractice/common/cache/CacheHandlerInterceptor.java:
--------------------------------------------------------------------------------
1 | package top.rizon.springbestpractice.common.cache;
2 |
3 | import lombok.RequiredArgsConstructor;
4 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
5 |
6 | import javax.servlet.http.HttpServletRequest;
7 | import javax.servlet.http.HttpServletResponse;
8 |
9 | /**
10 | * 清理 RequestScopedCacheManager的缓存
11 | *
12 | * @author Rizon
13 | * @date 2019/12/13
14 | */
15 | @RequiredArgsConstructor
16 | public class CacheHandlerInterceptor extends HandlerInterceptorAdapter {
17 |
18 | private final RequestScopedCacheManager requestScopedCacheManager;
19 |
20 |
21 | @Override
22 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
23 | requestScopedCacheManager.clearCaches();
24 | return true;
25 | }
26 |
27 | /**
28 | * afterCompletion与postHandler不同,即使抛出异常后也会被执行
29 | */
30 | @Override
31 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
32 | requestScopedCacheManager.clearCaches();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/common/src/main/java/top/rizon/springbestpractice/common/cache/RequestScopedCacheManager.java:
--------------------------------------------------------------------------------
1 | package top.rizon.springbestpractice.common.cache;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.springframework.cache.Cache;
5 | import org.springframework.cache.CacheManager;
6 | import org.springframework.cache.concurrent.ConcurrentMapCache;
7 |
8 | import java.util.Collection;
9 | import java.util.Map;
10 | import java.util.concurrent.ConcurrentHashMap;
11 |
12 | /**
13 | * request请求域的缓存工具
14 | *
15 | * @author Rizon
16 | * @date 2019/12/13
17 | */
18 | public class RequestScopedCacheManager implements CacheManager {
19 |
20 | private static final ThreadLocal