项目介绍
用户中心
功能介绍
提供了一套基于 Session 的用户中心,提供以下功能
- 登入
- 登出
- 注册
- 用户信息管理
- 增
- 删
- 改
- 查
- 登录用户查询
- ID 查询
- 列表查询
- 分页查询
- 用户态记录
数据库表
create table user
(
id bigint auto_increment comment 'id'
primary key,
userAccount varchar(256) not null comment '账号',
userRole varchar(256) default 'user' not null comment '用户角色:user / admin',
userPassword varchar(512) not null comment '密码',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
constraint uni_userAccount
unique (userAccount)
)
comment '用户';
标准 CRUD
功能介绍
提供了一套标准的 CRUD 解决方案
- Sample 信息管理
- 增
- 删
- 改
- 查
- ID 查询
- 列表查询
- 分页查询
- 接口鉴权
数据库表
(
id bigint not null comment 'id'
primary key,
sampleTest varchar(256) not null comment '样例文本',
sampleStatus int default 0 not null comment '样例状态',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除'
)
comment '样例';
机制介绍
前端友好响应机制
机制原理
- 抽象出统一的响应形式 → 规范前端响应的格式
- 将响应流程封装成方法 → 增强后端代码可读性
- 抽象出规范错误码描述 → 规范错误描述的格式
机制实现
统一响应
/**
* 通用返回对象
*
* @param <T>
*/
@Data
public class BaseResponse<T> implements Serializable {
private int code;
private T data;
private String message;
private String description;
public BaseResponse(int code, T data, String messgae, String description) {
this.code = code;
this.data = data;
this.message = messgae;
this.description = description;
}
public BaseResponse(int code, T data) {
this(code, data, "", "");
}
public BaseResponse(ErrorCode errorCode) {
this(errorCode.getCode(), null, errorCode.getMessage(), errorCode.getDescription());
}
public BaseResponse(ErrorCode errorCode, String message, String description) {
this(errorCode.getCode(), null, message, description);
}
public BaseResponse(ErrorCode errorCode, String description) {
this(errorCode.getCode(), null, errorCode.getMessage(), description);
}
public BaseResponse(int errorCode, String message, String description) {
this(errorCode, null, message, description);
}
}
封装方法
/**
* 返回工具类
*
*/
public class ResultUtils {
/**
* 成功
*
* @param data
* @param <T>
* @return
*/
public static <T> BaseResponse<T> success(T data){
return new BaseResponse<T>(ErrorCode.SUCCESS.getCode(),data,ErrorCode.SUCCESS.getMessage(),"");
}
/**
* 失败
*
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode){
return new BaseResponse(errorCode);
}
/**
* 失败
*
* @param errorCode
* @param message
* @param description
* @return
*/
public static BaseResponse error(ErrorCode errorCode, String message, String description) {
return new BaseResponse(errorCode,message,description);
}
/**
* 失败
*
* @param errorCode
* @param message
* @param description
* @return
*/
public static BaseResponse error(int errorCode, String message, String description) {
return new BaseResponse(errorCode,message,description);
}
}
错误码
/**
* 错误码
*/
public enum ErrorCode {
SUCCESS(200, "ok",""),
// 客户端错误(请求相关)
PARAMS_ERROR(40000, "请求参数错误",""),
PARAMS_NULL(40001,"请求参数为空",""),
REQUEST_NULL(40002,"请求为空",""),
NOT_FOUND_ERROR(40400, "请求数据不存在",""),
// 客户端错误(权限相关)
NOT_LOGIN_ERROR(40100, "未登录",""),
NO_AUTH_ERROR(40101, "无权限",""),
FORBIDDEN_ERROR(40300, "禁止访问",""),
// 服务端错误
SYSTEM_ERROR(50000, "系统内部异常",""),
OPERATION_ERROR(50001, "操作失败","");
private final int code;
private final String message;
private final String description;
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
ErrorCode(int code, String message, String description) {
this.code = code;
this.message = message;
this.description = description;
}
}
机制使用
机制载入
一般配合统一异常处理机制一起使用
复制 common 文件夹下的
- BaseResponse
- ErrorCode
- ResultUtils
即可使用
机制使用
在 Controller 中统一使用 BaseResponse<T>
返回正常响应
@GetMapping("/hello")
public BaseResponse<String> hello() {
return ResultUtils.success("hello");
}
在 全局异常处理器 或 需要返回错误响应的方法中 中统一使用 BaseResponse<T>
返回异常响应
统一异常处理机制
机制原理
- 声明自定义异常
- AOP 统一捕获并处理异常
机制实现
业务异常
/**
* 自定义异常
*/
public class BusinessException extends RuntimeException {
private final int code;
private final String description;
public BusinessException(String message, int code, String description) {
super(message);
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = errorCode.getDescription();
}
public BusinessException(ErrorCode errorCode, String description) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = description;
}
}
拦截器配置
全局异常处理器
@RestControllerAdvice
@Slf4j
/**
* 全局异常处理器
*/
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)//过滤,只捕获特定异常
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
log.error("businessException:" + e.getMessage(), e);
return ResultUtils.error(e.getCode(), e.getMessage(), e.getDescription());
}
@ExceptionHandler(RuntimeException.class)//过滤,只捕获特定异常
public BaseResponse<?> runtimeExceptionHandler(HttpServletResponse res, RuntimeException e) {
log.error("runtimeException", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage(), "");
}
}
机制使用
机制载入
统一异常处理机制依赖于前端友好响应机制,需要先载入前端友好响应机制,在此基础上
复制 exception 文件夹下的
- BusinessException
- GlobalExceptionHandler
机制使用
在需要抛出业务异常的位置抛出 BusinessException
即可
运行时异常无需额外配置,在该抛出异常的位置正常抛出异常即可
请求响应日志机制
机制原理
AOP
机制实现
拦截器配置
/**
* 请求响应日志 AOP
*
**/
@Aspect
@Component
@Slf4j
public class LogInterceptor {
/**
* 拦截
* 范围:controller 目录下的所有方法
*
* @param point
* @return
* @throws Throwable
*/
@Around("execution(* com.example.statefulbackend.controller.*.*(..))")
public Object logInterceptor(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;
}
}
机制使用
机制载入
在 Maven 中导入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
机制使用
复制 aop 文件夹下的 LogInterceptor 即可
会话注解鉴权机制
机制原理
本质上就是自定义注解
注解 + 注解处理器
机制实现
用户态信息获取
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
/**
* 获取当前登录用户
*
* @param request
* @return
*/
@Override
public User getLoginUser(HttpServletRequest request) {
// 先判断是否已登录
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User currentUser = (User) userObj;
if (currentUser == null || currentUser.getId() == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
// 从数据库查询(追求性能的话可以注释,直接走缓存)
long userId = currentUser.getId();
currentUser = this.getById(userId);
if (currentUser == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
//返回脱敏对象
return getSafeUser(currentUser);
}
...
}
注解
/**
* 权限校验
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {
/**
* 有任何一个角色
*
* @return
*/
String[] hasRole() default "";
/**
* 必须有某个角色
*
* @return
*/
String mustRole() default "";
}
注解处理器
/**
* 权限校验 AOP
*/
@Aspect
@Component
public class AuthInterceptor {
@Resource
private UserService userService;
/**
* 执行拦截
*
* @param joinPoint
* @param authCheck
* @return
*/
@Around("@annotation(authCheck)")
public Object authCheckInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
//获取注解属性
//获取 anyRole 数组中的元素
List<String> anyRole = Arrays
.stream(authCheck.hasRole())
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
//获取 mustRole 数组中的元素
String mustRole = authCheck.mustRole();
//获取请求属性对象
// RequestContextHolder是Spring提供的一个工具类
// currentRequestAttributes()方法返回当前线程的请求属性对象。
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();
}
}
机制使用
机制载入
依赖于 userService 中的 用户态信息获取方法,仅本项目可用
机制使用
在需要拦截的方法前加注解,一般放在 Controller 中
@AuthCheck(mustRole = "admin")
@PostMapping("/list/page")
public BaseResponse<Page<SampleVO>> listSampleByPage(SampleQueryRequest sampleQueryRequest, HttpServletRequest request) {
// 方法体省略
}
@AuthCheck(hasRole = {"admin","user"})
@PostMapping("/list/page")
public BaseResponse<Page<SampleVO>> listSampleByPage(SampleQueryRequest sampleQueryRequest, HttpServletRequest request) {
// 方法体省略
}
标准增删改查机制(带分页查询)
机制原理
- 标准化
- 请求
- 响应
- 数据库表
- 复用 MyBatis Plus 中现成的方法
- 配置 MyBatis Plus 中的分页插件
- 实现合法性检验方法
机制实现
请求响应标准化
- common
- IdRequest
- PageRequest
- controller
- SampleController
- model
- domain
- Sample
- dto
- sample
- SampleAddRequest
- SampleQueryRequest
- SampleUpdateRequest
- sample
- enums
- SampleStatusEnum
- vo
- SampleVO
- domain
数据库表标准化
-- 样例表
create table if not exists sample
(
id bigint auto_increment comment 'id' primary key,
sampleTest varchar(256) not null comment '样例文本',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除',
) comment '样例';
增删改查方法实现
在 Controller 中按照以下顺序声明接口方法
- 请求判空
- 数据获取
- 服务调用
在此处直接使用从 IService 中继承的现有方法 - 结果返回
分页插件配置
/**
* MyBatis Plus 配置
*
*/
@Configuration
@MapperScan("com.example.statefulbackend.mapper")
public class MyBatisPlusConfig {
/**
* 拦截器配置
*
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
合法性检验方法
@Service
public class SampleServiceImpl extends ServiceImpl<SampleMapper, Sample>
implements SampleService {
@Override
public void validSample(Sample sample, boolean isAdd) {
//数据判空
if (sample == null) {
throw new BusinessException(ErrorCode.PARAMS_NULL);
}
//数据获取
String sampleTest = sample.getSampleTest();
Integer sampleStatus = sample.getSampleStatus();
//新增时验证
if (isAdd) {
if (StringUtils.isAnyBlank(sampleTest)|| ObjectUtils.anyNull(sampleStatus)) {
throw new BusinessException(ErrorCode.PARAMS_NULL);
}
}
//属性合法性验证
if (StringUtils.isNotBlank(sampleTest) && sampleTest.length() > 8192) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "内容过长");
}
if (sampleStatus != null && !SampleStatusEnum.getValues().contains(sampleStatus)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "状态不符合要求");
}
}
}
机制使用
机制载入
复制以下文件
-
common
- IdRequest
- PageRequest
-
controller
- SampleController
-
model
- domain
- Sample
- dto
- sample
- SampleAddRequest
- SampleQueryRequest
- SampleUpdateRequest
- sample
- enums
- SampleStatusEnum
- vo
- SampleVO
- domain
机制使用
- 基于标准化数据库表进行客制化改造
- 使用 MybatisX-Generator 生成以下内容
- <Entity>.java
- <EntityService>.java
- <EntityServiceImpl>.java
- <EntityMapper>.java
- <EntityMapper>.xml
- 客制化改造
- 生成的内容
- enums、vo
- 将 SampleController 和 dto 文件夹下所有文件 中所有 <Sample> 相关内容重命名为 <Entity>
解决方案介绍
后端跨域问题解决方案
解决方案原理
在请求头中加入 Access-Control-Allow-Origin
相关配置信息
解决方案实现
拦截器配置
重写 WebMvcConfigurer 的方式以解决跨域问题
@Configuration
public class WebMvcConfg implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
//当**Credentials为true时,**Origin不能为星号,需为具体的ip地址【如果接口不带cookie,ip无需设成具体ip】
.allowedOrigins("*")
//是否允许证书 不再默认开启
.allowCredentials(false)
//设置允许的方法
.allowedMethods("*")
//跨域允许时间
.maxAge(3600);
}
}
解决方案使用
- 复制上述拦截器配置到项目中
- 根据实际需求配置
addCorsMappings(CorsRegistry registry)
方法中的代码
接口文档管理解决方案
解决方案原理
knife4j是一款基于Swagger的自动化接口文档管理工具。它通过解析项目中的Swagger注解,自动生成接口文档,并提供了一系列功能来增强接口文档的可读性、可维护性和可测试性。
knife4j的主要原理是通过解析项目中的Swagger注解,生成接口文档的HTML页面。在项目启动时,knife4j会扫描项目中的Swagger注解,并根据注解的配置信息,自动生成接口文档的相关内容,如API接口列表、接口参数、接口响应等。生成的接口文档可以通过浏览器访问,以便团队成员和其他开发者查看和测试接口。
解决方案实现
拦截器配置
/**
* Knife4j 接口文档配置
*
*/
@Configuration
@EnableSwagger2
@Profile("dev")
public class Knife4jConfig {
@Bean
public Docket defaultApi2() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("project-backend")
.description("project-backend")
.version("1.0")
.build())
.select()
//TODO 指定 Controller 扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.example.statefulbackend.controller"))
.paths(PathSelectors.any())
.build();
}
}
接口注解配置(可选)
//Knife 配置样例
//启动后,打开 http://localhost:8080/doc.html 查看文档
@Api(tags = "首页模块")
@RestController
public class IndexController {
@ApiImplicitParam(name = "name",value = "姓名",required = true)
@ApiOperation(value = "向客人问好")
@GetMapping("/sayHi")
public ResponseEntity<String> sayHi(@RequestParam(value = "name")String name){
return ResponseEntity.ok("Hi:"+name);
}
}
解决方案使用
-
依赖导入
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>
-
复制上述拦截器配置到项目中
-
根据实际需求配置
defaultApi2()
方法中的代码
项目源码
https://github.com/Ba11ooner/stateful-backend
标签:errorCode,return,String,开放平台,stateful,API,null,public,description From: https://www.cnblogs.com/ba11ooner/p/17658535.html