---
url: /notes/项目/寻爱-万用信息展示网站/index.md
---
## 项目地址

编程导航：https://www.code-nav.cn/course/1806250609774997505

github代码地址：https://github.com/731016/lovefinder/

### 主要技术栈

权限注解

```java
/**
 * 权限校验
 *
 * @author yupi
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {

    /**
     * 有任何一个角色
     *
     * @return
     */
    String[] anyRole() default "";

    /**
     * 必须有某个角色
     *
     * @return
     */
    String mustRole() default "";
}
```

何谓注解？
Annotation （注解） 是 Java5 开始引入的新特性，可以看作是一种特殊的注释，主要用于修饰类、方法或者变量，提供某些信息供程序在编译或者运行时使用。
注解本质是一个继承了Annotation 的特殊接口：@Target(ElementType.METHOD)
JDK 提供了很多内置的注解（比如 @Override、@Deprecated），同时，我们还可以自定义注解。注解的解析方法有哪几种？注解只有被解析之后才会生效，
常见的解析方法有两种：
编译期直接扫描：编译器在编译 Java 代码的时候扫描对应的注解并处理，比如某个方法使用@Override 注解，编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
运行期通过反射处理：像框架中自带的注解(比如 Spring 框架的 @Value、@Component)都是通过反射来进行处理的。
原文链接：https://javaguide.cn/java/basis/java-basic-questions-03.html

aop拦截

```java
/**
 * 权限校验 AOP
 *
 * @author yupi
 */
@Aspect
@Component
public class AuthInterceptor {

    @Resource
    private UserService userService;

    /**
     * 执行拦截
     *
     * @param joinPoint
     * @param authCheck
     * @return
     */
    @Around("@annotation(authCheck)")
    public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
        List<String> anyRole = Arrays.stream(authCheck.anyRole()).filter(StringUtils::isNotBlank).collect(Collectors.toList());
        String mustRole = authCheck.mustRole();
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 当前登录用户
        User user = userService.getLoginUser(request);
        // 拥有任意权限即通过
        if (CollectionUtils.isNotEmpty(anyRole)) {
            String userRole = user.getUserRole();
            if (!anyRole.contains(userRole)) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
            }
        }
        // 必须有所有权限才通过
        if (StringUtils.isNotBlank(mustRole)) {
            String userRole = user.getUserRole();
            if (!mustRole.equals(userRole)) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
            }
        }
        // 通过权限校验，放行
        return joinPoint.proceed();
    }
}

/**
     * 创建
     *
     * @param tagSearchHistory
     * @return
     */
    @PostMapping("/add")
    public BaseResponse<Boolean> addSearchHistory(@RequestBody SearchHistory tagSearchHistory) {
        ...
    }

    /**
     * 删除
     *
     * @param deleteRequest
     * @return
     */
    @AuthCheck(mustRole = "admin")
    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteSearchHistory(@RequestBody DeleteRequest deleteRequest) {
        ...
    }
```

全局请求日志

```java
/**
 * 请求响应日志 AOP
 *
 * @author yupi
 **/
@Aspect
@Component
@Slf4j
public class LogInterceptor {

    /**
     * 执行拦截
     */
    @Around("execution(* com.yupi.lovefinder.controller.*.*(..))")
    public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
        // 计时
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 获取请求路径
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 生成请求唯一 id
        String requestId = UUID.randomUUID().toString();
        String url = httpServletRequest.getRequestURI();
        // 获取请求参数
        Object[] args = point.getArgs();
        String reqParam = "[" + StringUtils.join(args, ", ") + "]";
        // 输出请求日志
        log.info("request start，id: {}, path: {}, ip: {}, params: {}", requestId, url,
                httpServletRequest.getRemoteHost(), reqParam);
        // 执行原方法
        Object result = point.proceed();
        // 输出响应日志
        stopWatch.stop();
        long totalTimeMillis = stopWatch.getTotalTimeMillis();
        log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
        return result;
    }
}
```

本地缓存caffeine

```java
// TagMap 缓存
    private final Cache<String, Map<String, List<Tag>>> tagMapCache = Caffeine.newBuilder().build();

    // 整个 TagMap 缓存 key
    private static final String FULL_TAG_MAP_KEY = "f";

// 优先取缓存
        Map<String, List<Tag>> tagMap = tagMapCache.get(FULL_TAG_MAP_KEY, key -> tagList.stream().map(tag -> {
            // 精简
            Tag newTag = new Tag();
            newTag.setTagName(tag.getTagName());
            newTag.setCategory(tag.getCategory());
            newTag.setPostNum(tag.getPostNum());
            return newTag;
            // 按类别分组
        }).collect(Collectors.groupingBy(Tag::getCategory)));

// 清除缓存
        tagMapCache.invalidate(FULL_TAG_MAP_KEY);
```
