完成后端全部内容,等待后续修补
This commit is contained in:
parent
b91c790f8c
commit
0320a79e33
13
README.md
13
README.md
@ -2,6 +2,17 @@
|
|||||||
采用SpringBoot3 + Vue3编写的前后端分离模版项目,集成多种技术栈,使用JWT校验方案。
|
采用SpringBoot3 + Vue3编写的前后端分离模版项目,集成多种技术栈,使用JWT校验方案。
|
||||||
***
|
***
|
||||||
### 后端功能与技术点
|
### 后端功能与技术点
|
||||||
* 用户注册、用户登录、重置密码
|
用户注册、用户登录、重置密码等基础功能以及对应接口
|
||||||
|
* 采用Mybatis-Plus作为持久层框架,使用更便捷
|
||||||
|
* 采用Redis存储注册/重置操作验证码,带过期时间控制
|
||||||
|
* 采用RabbitMQ积压短信发送任务,再由监听器统一处理
|
||||||
|
* 采用SpringSecurity作为权限校验框架,手动整合Jwt校验方案
|
||||||
|
* 采用Redis进行IP地址限流处理,防刷接口
|
||||||
|
* 视图层对象和数据层对象分离,编写工具方法利用反射快速互相转换
|
||||||
|
* 错误和异常页面统一采用JSON格式返回,前端处理响应更统一
|
||||||
|
* 手动处理跨域,采用过滤器实现
|
||||||
|
* 使用Swagger作为接口文档自动生成,已自动配置登录相关接口
|
||||||
|
* 采用过滤器实现对所有请求自动生成雪花ID方便线上定位问题
|
||||||
|
* 项目整体结构清晰,职责明确,注释全面,开箱即用
|
||||||
|
|
||||||
### 前端功能与技术点
|
### 前端功能与技术点
|
||||||
|
@ -88,6 +88,12 @@
|
|||||||
<artifactId>java-jwt</artifactId>
|
<artifactId>java-jwt</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Swagger文档生成框架 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
@ -53,8 +53,8 @@ public class SecurityConfiguration {
|
|||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
return http
|
return http
|
||||||
.authorizeHttpRequests(conf -> conf
|
.authorizeHttpRequests(conf -> conf
|
||||||
.requestMatchers("/api/auth/**").permitAll()
|
.requestMatchers("/api/auth/**", "/error").permitAll()
|
||||||
.requestMatchers("/error").permitAll()
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
.anyRequest().hasAnyRole(Const.ROLE_DEFAULT)
|
.anyRequest().hasAnyRole(Const.ROLE_DEFAULT)
|
||||||
)
|
)
|
||||||
.formLogin(conf -> conf
|
.formLogin(conf -> conf
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
package com.example.config;
|
||||||
|
|
||||||
|
import com.example.entity.RestBean;
|
||||||
|
import com.example.entity.vo.response.AuthorizeVO;
|
||||||
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||||
|
import io.swagger.v3.oas.models.ExternalDocumentation;
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.Operation;
|
||||||
|
import io.swagger.v3.oas.models.PathItem;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import io.swagger.v3.oas.models.info.License;
|
||||||
|
import io.swagger.v3.oas.models.media.Content;
|
||||||
|
import io.swagger.v3.oas.models.media.MediaType;
|
||||||
|
import io.swagger.v3.oas.models.parameters.QueryParameter;
|
||||||
|
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.models.responses.ApiResponses;
|
||||||
|
import org.springdoc.core.customizers.OpenApiCustomizer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger开发文档相关配置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@SecurityScheme(type = SecuritySchemeType.HTTP, scheme = "Bearer",
|
||||||
|
name = "Authorization", in = SecuritySchemeIn.HEADER)
|
||||||
|
@OpenAPIDefinition(security = { @SecurityRequirement(name = "Authorization") })
|
||||||
|
public class SwaggerConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置文档介绍以及详细信息
|
||||||
|
* @return OpenAPI
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public OpenAPI springShopOpenAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(new Info().title("示例项目 API 文档")
|
||||||
|
.description("欢迎来到本示例项目API测试文档,在这里可以快速进行接口调试")
|
||||||
|
.version("1.0")
|
||||||
|
.license(new License()
|
||||||
|
.name("项目开源地址")
|
||||||
|
.url("https://github.com/Ketuer/SpringBoot-Vue-Template-Jwt")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.externalDocs(new ExternalDocumentation()
|
||||||
|
.description("我们的官方网站")
|
||||||
|
.url("https://itbaima.net")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置自定义的OpenApi相关信息
|
||||||
|
* @return OpenApiCustomizer
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public OpenApiCustomizer customerGlobalHeaderOpenApiCustomizer() {
|
||||||
|
return api -> this.authorizePathItems().forEach(api.getPaths()::addPathItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录接口和退出登录接口手动添加一下
|
||||||
|
* @return PathItems
|
||||||
|
*/
|
||||||
|
private Map<String, PathItem> authorizePathItems(){
|
||||||
|
Map<String, PathItem> map = new HashMap<>();
|
||||||
|
map.put("/api/auth/login", new PathItem()
|
||||||
|
.post(new Operation()
|
||||||
|
.tags(List.of("登录校验相关"))
|
||||||
|
.summary("登录验证接口")
|
||||||
|
.addParametersItem(new QueryParameter()
|
||||||
|
.name("username")
|
||||||
|
.required(true)
|
||||||
|
)
|
||||||
|
.addParametersItem(new QueryParameter()
|
||||||
|
.name("password")
|
||||||
|
.required(true)
|
||||||
|
)
|
||||||
|
.responses(new ApiResponses()
|
||||||
|
.addApiResponse("200", new ApiResponse()
|
||||||
|
.description("OK")
|
||||||
|
.content(new Content().addMediaType("*/*", new MediaType()
|
||||||
|
.example(RestBean.success(new AuthorizeVO()).asJsonString())
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
map.put("/api/auth/logout", new PathItem()
|
||||||
|
.get(new Operation()
|
||||||
|
.tags(List.of("登录校验相关"))
|
||||||
|
.summary("退出登录接口")
|
||||||
|
.responses(new ApiResponses()
|
||||||
|
.addApiResponse("200", new ApiResponse()
|
||||||
|
.description("OK")
|
||||||
|
.content(new Content().addMediaType("*/*", new MediaType()
|
||||||
|
.example(RestBean.success())
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
@ -4,15 +4,14 @@ import com.example.entity.RestBean;
|
|||||||
import com.example.entity.vo.request.EmailRegisterVO;
|
import com.example.entity.vo.request.EmailRegisterVO;
|
||||||
import com.example.entity.vo.request.EmailResetVO;
|
import com.example.entity.vo.request.EmailResetVO;
|
||||||
import com.example.service.AccountService;
|
import com.example.service.AccountService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.Email;
|
import jakarta.validation.constraints.Email;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@ -22,6 +21,7 @@ import java.util.function.Supplier;
|
|||||||
@Validated
|
@Validated
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
|
@Tag(name = "登录校验相关", description = "包括用户登录、注册、验证码请求等操作。")
|
||||||
public class AuthorizeController {
|
public class AuthorizeController {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@ -34,6 +34,7 @@ public class AuthorizeController {
|
|||||||
* @return 是否请求成功
|
* @return 是否请求成功
|
||||||
*/
|
*/
|
||||||
@PostMapping("/ask-code")
|
@PostMapping("/ask-code")
|
||||||
|
@Operation(summary = "请求邮件验证码")
|
||||||
public RestBean<Void> askVerifyCode(@RequestParam @Email String email,
|
public RestBean<Void> askVerifyCode(@RequestParam @Email String email,
|
||||||
HttpServletRequest request){
|
HttpServletRequest request){
|
||||||
return this.messageHandle(() ->
|
return this.messageHandle(() ->
|
||||||
@ -46,7 +47,8 @@ public class AuthorizeController {
|
|||||||
* @return 是否注册成功
|
* @return 是否注册成功
|
||||||
*/
|
*/
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public RestBean<Void> register(@Valid EmailRegisterVO vo){
|
@Operation(summary = "用户注册操作")
|
||||||
|
public RestBean<Void> register(@RequestBody @Valid EmailRegisterVO vo){
|
||||||
return this.messageHandle(() ->
|
return this.messageHandle(() ->
|
||||||
accountService.registerEmailAccount(vo));
|
accountService.registerEmailAccount(vo));
|
||||||
}
|
}
|
||||||
@ -57,7 +59,8 @@ public class AuthorizeController {
|
|||||||
* @return 是否操作成功
|
* @return 是否操作成功
|
||||||
*/
|
*/
|
||||||
@PostMapping("/reset-password")
|
@PostMapping("/reset-password")
|
||||||
public RestBean<Void> reset(@Valid EmailResetVO vo){
|
@Operation(summary = "密码重置操作")
|
||||||
|
public RestBean<Void> reset(@RequestBody @Valid EmailResetVO vo){
|
||||||
return this.messageHandle(() ->
|
return this.messageHandle(() ->
|
||||||
accountService.resetEmailAccountPassword(vo));
|
accountService.resetEmailAccountPassword(vo));
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求日志过滤器,用于记录所有用户请求信息
|
* 请求日志过滤器,用于记录所有用户请求信息
|
||||||
@ -28,14 +29,32 @@ public class RequestLogFilter extends OncePerRequestFilter {
|
|||||||
@Resource
|
@Resource
|
||||||
SnowflakeIdGenerator generator;
|
SnowflakeIdGenerator generator;
|
||||||
|
|
||||||
|
private final Set<String> ignores = Set.of("/swagger-ui", "/v3/api-docs");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
long startTime = System.currentTimeMillis();
|
if(this.isIgnoreUrl(request.getServletPath())) {
|
||||||
this.logRequestStart(request);
|
filterChain.doFilter(request, response);
|
||||||
ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(response);
|
} else {
|
||||||
filterChain.doFilter(request, wrapper);
|
long startTime = System.currentTimeMillis();
|
||||||
this.logRequestEnd(wrapper, startTime);
|
this.logRequestStart(request);
|
||||||
wrapper.copyBodyToResponse();
|
ContentCachingResponseWrapper wrapper = new ContentCachingResponseWrapper(response);
|
||||||
|
filterChain.doFilter(request, wrapper);
|
||||||
|
this.logRequestEnd(wrapper, startTime);
|
||||||
|
wrapper.copyBodyToResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判定当前请求url是否不需要日志打印
|
||||||
|
* @param url 路径
|
||||||
|
* @return 是否忽略
|
||||||
|
*/
|
||||||
|
private boolean isIgnoreUrl(String url){
|
||||||
|
for (String ignore : ignores) {
|
||||||
|
if(url.startsWith(ignore)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
|
springdoc:
|
||||||
|
paths-to-match: /api/**
|
||||||
|
swagger-ui:
|
||||||
|
operations-sorter: alpha
|
||||||
spring:
|
spring:
|
||||||
mail:
|
mail:
|
||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
@ -29,12 +33,9 @@ spring:
|
|||||||
mail-limit: 60
|
mail-limit: 60
|
||||||
flow:
|
flow:
|
||||||
period: 3
|
period: 3
|
||||||
limit: 10
|
limit: 50
|
||||||
block: 30
|
block: 30
|
||||||
cors:
|
cors:
|
||||||
origin: '*'
|
origin: '*'
|
||||||
credentials: false
|
credentials: false
|
||||||
methods: '*'
|
methods: '*'
|
||||||
mybatis-plus:
|
|
||||||
configuration:
|
|
||||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
#生产环境配置
|
#生产环境配置
|
||||||
|
server:
|
||||||
|
port: 80
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: false
|
||||||
|
mybatis-plus:
|
||||||
|
configuration:
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
spring:
|
spring:
|
||||||
mail:
|
mail:
|
||||||
host: smtp.163.com
|
host: smtp.163.com
|
||||||
|
Loading…
x
Reference in New Issue
Block a user