核心业务流程
自定义上传题目流程:
用户答题流程:
AI 创建题目流程:
时序图:
架构设计
在对登录用户的权限进行判断时,不再通过条件判断,编写一大串代码去实现,可以通过写一个Java注解,如
package com.yupi.qidada.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 权限校验 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthCheck { /** * 必须有某个角色 * * @return */ String mustRole() default ""; }
这是一个Java注解,用于权限验证。它有两个属性:mustRole和excludeRole。mustRole表示必须有某个角色才能访问该方法,excludeRole表示没有某个角色才能访问该方法。这个注解可以应用于方法上,用于限制访问权限。
实现原理:
- 使用@Target和@Retention注解来定义注解的作用范围和生命周期。@Target(ElementType.METHOD)表示这个注解只能应用于方法上,@Retention(RetentionPolicy.RUNTIME)表示这个注解在运行时可用。
- 使用@interface关键字定义一个名为AuthCheck的注解,包含两个属性:mustRole和excludeRole。
- 在AuthCheck注解中,使用default关键字为mustRole属性设置一个默认值。
- 使用ElementType.METHOD表示这个注解只能应用于方法上。
- 使用RetentionPolicy.RUNTIME表示这个注解在运行时可用。
用途:
这个注解可以用于权限验证,限制访问权限。例如,在一个方法上添加@AuthCheck(mustRole="admin"),表示只有具有admin角色的用户才能访问该方法。
package com.yupi.qidada.aop; import com.yupi.qidada.annotation.AuthCheck; import com.yupi.qidada.common.ErrorCode; import com.yupi.qidada.exception.BusinessException; import com.yupi.qidada.model.entity.User; import com.yupi.qidada.model.enums.UserRoleEnum; import com.yupi.qidada.service.UserService; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; /** * 权限校验 AOP * */ @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 { String mustRole = authCheck.mustRole(); RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); // 当前登录用户 User loginUser = userService.getLoginUser(request); UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole); // 不需要权限,放行 if (mustRoleEnum == null) { return joinPoint.proceed(); } // 必须有该权限才通过 UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole()); if (userRoleEnum == null) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 如果被封号,直接拒绝 if (UserRoleEnum.BAN.equals(userRoleEnum)) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } // 必须有管理员权限 if (UserRoleEnum.ADMIN.equals(mustRoleEnum)) { // 用户没有管理员权限,拒绝 if (!UserRoleEnum.ADMIN.equals(userRoleEnum)) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR); } } // 通过权限校验,放行 return joinPoint.proceed(); } }
这是一个基于Spring AOP(面向切面编程)的权限校验拦截器。它主要用于在方法执行前,对请求进行权限校验。
1. 首先,定义了一个名为`AuthInterceptor`的类,并使用`@Aspect`和`@Component`注解进行标注,表示这是一个切面类,并且会自动被Spring容器管理。
2. 在`AuthInterceptor`类中,定义了一个名为`doInterceptor`的方法,该方法使用`@Around`注解进行标注,表示这是一个环绕通知,会在目标方法执行前后进行拦截。
3. 在`doInterceptor`方法中,首先获取`AuthCheck`注解中的`mustRole`属性值,然后通过`UserService`获取当前登录用户的信息。
4. 判断`mustRole`属性值是否为空,如果为空,则表示不需要权限,直接放行。
5. 如果`mustRole`属性值不为空,则需要进行权限校验。首先判断当前登录用户的角色是否与`mustRole`属性值相等,如果相等,则表示用户具有该权限,放行。
6. 如果当前登录用户的角色与`mustRole`属性值不相等,则判断用户是否被封号。如果被封号,则抛出`BusinessException`异常。
7. 如果用户没有被封号,则判断用户是否具有管理员权限。如果具有管理员权限,则放行。
8. 如果用户不具有管理员权限,则抛出`BusinessException`异常。
示例:
/** * 应用审核 * @param reviewRequest * @param request * @return */ @PostMapping("/review") @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) public BaseResponse<Boolean> doAppReview(@RequestBody ReviewRequest reviewRequest, HttpServletRequest request) { ThrowUtils.throwIf(reviewRequest == null, ErrorCode.PARAMS_ERROR); Long id = reviewRequest.getId(); Integer reviewStatus = reviewRequest.getReviewStatus(); // 校验 ReviewStatusEnum reviewStatusEnum = ReviewStatusEnum.getEnumByValue(reviewStatus); if (id == null || reviewStatusEnum == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 判断是否存在 App oldApp = appService.getById(id); ThrowUtils.throwIf(oldApp == null, ErrorCode.NOT_FOUND_ERROR); // 已是该状态 if (oldApp.getReviewStatus().equals(reviewStatus)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请勿重复审核"); } // 更新审核状态 User loginUser = userService.getLoginUser(request); App app = new App(); app.setId(id); app.setReviewStatus(reviewStatus); app.setReviewerId(loginUser.getId()); app.setReviewTime(new Date()); boolean result = appService.updateById(app); ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); return ResultUtils.success(true); }
全局执行器
为了简化外部调用,需要根据不同的应用类别和评分策略,选择对应的策略执行,因此需要一个全局执行器。
2 种实现方式:
1)编程式,在内部计算选用何种策略:
@Service @Deprecated public class ScoringStrategyContext { @Resource private CustomScoreScoringStrategy customScoreScoringStrategy; @Resource private CustomTestScoringStrategy customTestScoringStrategy; /** * 评分 * * @param choiceList * @param app * @return * @throws Exception */ public UserAnswer doScore(List<String> choiceList, App app) throws Exception { AppTypeEnum appTypeEnum = AppTypeEnum.getEnumByValue(app.getAppType()); AppScoringStrategyEnum appScoringStrategyEnum = AppScoringStrategyEnum.getEnumByValue(app.getScoringStrategy()); if (appTypeEnum == null || appScoringStrategyEnum == null) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略"); } // 根据不同的应用类别和评分策略,选择对应的策略执行 switch (appTypeEnum) { case SCORE: switch (appScoringStrategyEnum) { case CUSTOM: return customScoreScoringStrategy.doScore(choiceList, app); case AI: break; } break; case TEST: switch (appScoringStrategyEnum) { case CUSTOM: return customTestScoringStrategy.doScore(choiceList, app); case AI: break; } break; } throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略"); } }
优点是直观清晰,缺点是不利于扩展和维护。
2)声明式,在每个策略类中通过接口声明对应的生效条件,适合比较规律的策略选取场景。
接口:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component public @interface ScoringStrategyConfig { int appType(); int scoringStrategy(); }
给策略实现类补充注解:
@ScoringStrategyConfig(appType = 0, scoringStrategy = 0)
全局执行器:
@Service public class ScoringStrategyExecutor { // 策略列表 @Resource private List<ScoringStrategy> scoringStrategyList; /** * 评分 * * @param choiceList * @param app * @return * @throws Exception */ public UserAnswer doScore(List<String> choiceList, App app) throws Exception { Integer appType = app.getAppType(); Integer appScoringStrategy = app.getScoringStrategy(); if (appType == null || appScoringStrategy == null) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略"); } // 根据注解获取策略 for (ScoringStrategy strategy : scoringStrategyList) { if (strategy.getClass().isAnnotationPresent(ScoringStrategyConfig.class)) { ScoringStrategyConfig scoringStrategyConfig = strategy.getClass().getAnnotation(ScoringStrategyConfig.class); if (scoringStrategyConfig.appType() == appType && scoringStrategyConfig.scoringStrategy() == appScoringStrategy) { return strategy.doScore(choiceList, app); } } } throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略"); } }
因为用了 ScoringStrategyConfig 注解,所以这个实现类被加上了 component 注解,因此可以被spring 管理扫描到。 然后 @Resoure 注入的时候,会通过 ScoringStrategy 类型找到所有实现 ScoringStrategy 接口的实现类
标签:项目,app,笔记,ErrorCode,mustRole,import,权限,注解 From: https://www.cnblogs.com/qimoxuan/p/18413002