mirror of
https://github.com/RemainderTime/spring-boot-base-demo.git
synced 2026-02-06 15:10:56 +08:00
1.优化改进全局异常类
2.项目介绍调整
This commit is contained in:
173
README.md
173
README.md
@@ -1,67 +1,136 @@
|
|||||||
## 拿来即用springboot基础脚手架
|
# XF-Boot-Base (Spring Boot Base Demo)
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<img src="https://img.shields.io/badge/Spring_Boot-3.3.3-6DB33F?style=for-the-badge&logo=spring-boot&logoColor=white" alt="Spring Boot"/>
|
||||||
|
<img src="https://img.shields.io/badge/Java-17%2B-ED8B00?style=for-the-badge&logo=openjdk&logoColor=white" alt="Java"/>
|
||||||
|
<img src="https://img.shields.io/badge/Nacos-2.x-00C7D5?style=for-the-badge&logo=AlibabaCloud&logoColor=white" alt="Nacos"/>
|
||||||
|
<img src="https://img.shields.io/badge/MyBatis_Plus-3.5.8-00599C?style=for-the-badge&logo=Spring&logoColor=white" alt="MyBatis Plus"/>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
[](https://github.com/RemainderTime/spring-boot-base-demo)
|
||||||
|
[](./LICENSE)
|
||||||
|
|
||||||
---
|
---
|
||||||
### 项目介绍
|
|
||||||
|
|
||||||
[](https://blog.csdn.net/qq_39818325?type=blog)
|
### 「 为企业级开发而生的高效脚手架 」
|
||||||
[](https://github.com/RemainderTime/spring-boot-base-demo)
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
---
|
|
||||||
> 这是一个基于 **Spring Boot 3.3.3** 的快速构建单体架构脚手架,旨在帮助开发者快速搭建高效、稳定的项目基础框架。项目集成了多种常用的技术组件与功能,涵盖从用户认证到数据加密、从全局异常处理到搜索引擎操作,适合个人学习与企业级单体应用开发。
|
|
||||||
|
|
||||||
#### 分支
|
<p align="center">
|
||||||
- master 快速上手开发spring boot 用户端单体应用
|
<a href="#-关于项目">💎 关于项目</a> |
|
||||||
- feature/admin-auth-spring-security 基于master分支集成spring官方鉴权框架spring security框架,可用于后台管理系统后端项目,实现RBAC模型(角色 → 用户 → 菜单 → 权限)基于角色的访问控制
|
<a href="#-核心亮点">⚡ 核心亮点</a> |
|
||||||
- feature/admin-auth-sa-token 基于master分支集成国产权限框架sa-token,可用于后台管理系统后端项目,实现RBAC模型(角色 → 用户 → 菜单 → 权限)基于角色的访问控制
|
<a href="#-生态全景">🌿 生态全景</a> |
|
||||||
- component/rocketmq-and-es 基于master分支集成消息队列原生RocketMQ5.x与原生Elasticsearch 8.x,提供消息队列与搜索引擎服务,实现消息持久化与全文检索
|
<a href="#-快速运行">🚀 快速运行</a> |
|
||||||
- feature/master-payment 基于master分支集成支付宝沙盒功能(H5支付、APP支付)
|
<a href="#-项目日志">📅 项目日志</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
### 集成技术与功能亮点
|
<br/>
|
||||||
|
|
||||||
- 身份认证与授权(JWT):基于 JWT 实现用户认证与授权,确保系统安全性。
|
## <20> 关于项目
|
||||||
- 数据加密(RSA):提供 RSA 非对称加密支持,保障敏感数据安全。
|
|
||||||
- 持久层框架(MyBatis Plus):简化数据库操作,提供高效的 CRUD 支持。
|
|
||||||
- 数据库(MySQL):采用 MySQL 作为默认数据库,易于扩展和维护。
|
|
||||||
- 数据连接池(Hikari):高性能数据源管理,优化数据库连接效率。
|
|
||||||
- 缓存(Redis):支持分布式缓存,提升系统响应速度与并发能力。
|
|
||||||
- 接口文档(springdoc-openapi):自动生成标准化 API 文档,便于调试与集成。
|
|
||||||
- 模板引擎(Thymeleaf):支持动态页面渲染,提升前后端协同效率。
|
|
||||||
- 容器化支持(Docker):内置 Dockerfile,轻松实现环境部署与迁移。
|
|
||||||
- 搜索引擎(Elasticsearch 8.x):集成最新版本 Elasticsearch Java 客户端,提供高效的全文检索与复杂查询功能。
|
|
||||||
- 全局异常处理:统一管理异常,提升代码可维护性与调试效率。
|
|
||||||
- 拦截器支持:轻松实现请求拦截与权限控制。
|
|
||||||
|
|
||||||
### 项目优势
|
**XF-Boot-Base** 并非仅仅是一个简单的 "Hello World" 示例,而是一个经过精心打磨、具备生产级标准的 **Spring Boot 3.3** 全栈开发底座。
|
||||||
**全面适配 Spring Boot 3.x**
|
|
||||||
- 所有组件已全面升级为支持 Spring Boot 3.x 的最新版本。解决了开发者在版本升级中遇到的各种不兼容和适配问题,大大减少了升级带来的额外工作量,让项目开发更加顺畅。
|
|
||||||
|
|
||||||
**初学者友好**
|
我们深入分析了企业单体应用到微服务架构演进过程中的痛点,构建了一套**模块化、可插拔、高扩展**的基础框架。从底层的 **JDK 17** 优化,到顶层的 API 接口规范;从**JWT 安全认证**的丝滑接入,到 **Docker 容器化**的一键部署。XF-Boot-Base 旨在消除重复造轮子的时间成本,让开发者能够专注于核心业务逻辑的实现。
|
||||||
- 提供清晰的代码结构与详细的配置说明,帮助初学者快速上手微服务与单体架构的开发实践。
|
|
||||||
|
|
||||||
**高扩展性**
|
无论你是想要快速验证想法的独立开发者,还是寻找稳健基石的架构师,这里都有你需要的最佳实践。
|
||||||
- 丰富的功能集成,涵盖了开发中常见的场景,减少重复开发工作量,同时为定制化需求预留了扩展空间。
|
|
||||||
|
|
||||||
**稀缺的最新技术操作示例**
|
|
||||||
- 最新版本的 Elasticsearch 8.x 集成、Java 客户端操作示例和现代化 API 设计,让开发者能够轻松掌握分布式搜索引擎的使用。
|
|
||||||
|
|
||||||
### 版本更新 2024-10-12
|
|
||||||
|
|
||||||
---
|
---
|
||||||
1. springboot版本升级3.x
|
|
||||||
2. mybatis plus版本升级3.x
|
## ⚡ 核心亮点
|
||||||
3. dynamic mybatis plus版本升级3.x
|
|
||||||
4. redis版本升级3.x以及配置优化
|
<div align="center">
|
||||||
5. 替换swagger依赖支持spring boot3.x (knife4j->springdoc-openapi)
|
|
||||||
6. 新增请求头工具类
|
| 🚀 **前沿技术** | 🔐 **安全无忧** | 🐳 **云原生友好** |
|
||||||
7. 参数校验异常捕获优化
|
| :--- | :--- | :--- |
|
||||||
8. 登录拦截器注册为spring容器管理
|
| 紧跟 **Spring Boot 3.x** 生态,<br>基于 **Java 17 LTS** 构建,<br>享受最新技术红利。 | 深度整合 **JWT** 令牌认证<br>& **RSA** 非对称加密,<br>为数据安全保驾护航。 | 内置 **Dockerfile** 脚本,<br>支持 **Docker Compose** 编排,<br>部署快人一步。 |
|
||||||
9. 新增本地日志配置文件
|
|
||||||
|
| 💾 **数据增强** | 🔌 **微服务预装** | 🛠 **极致体验** |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **MyBatis Plus** 强力驱动,<br>**Dynamic Datasource**<br>轻松驾驭多数据源场景。 | 原生集成 **Nacos**,<br>配置中心与注册中心开箱即用,<br>平滑过渡微服务。 | **SpringDoc (OpenAPI 3)**<br>自动生成精美文档,<br>调试开发得心应手。 |
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
如果这个项目对你有帮助,请随手点个 Star ⭐ 支持一下吧!🎉✨ 你的支持是我持续优化的动力!❤️
|
|
||||||
|
## 🌿 生态全景
|
||||||
|
|
||||||
|
我们采用 **"核心 + 插件化"** 的分支管理策略,以 `master` 为稳定基石,通过不同分支满足多样化的业务需求。
|
||||||
|
|
||||||
|
| 🌳 分支标识 | 🎯 定位 | 📝 功能描述 | 🏭 最佳应用场景 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **`master`** | **核心底座** | 标准化脚手架,含 JWT/Nacos/Redis | 🚀 快速启动标准单体项目 |
|
||||||
|
| `feature/admin-auth-spring-security` | **安全堡垒** | Spring Security 官方方案 (RBAC) | 🏦 金融级、政府级后台系统 |
|
||||||
|
| `feature/admin-auth-sa-token` | **敏捷权限** | Sa-Token 轻量级权限控制 | ⚡ 中小型项目、国内快速开发 |
|
||||||
|
| `component/rocketmq-and-es` | **高并发** | RocketMQ 5.x + Elasticsearch 8.x | 📈 海量日志、搜索、高吞吐业务 |
|
||||||
|
| `feature/master-payment` | **商业变现** | 支付宝沙盒支付 (H5/App) | 💳 电商、会员订阅、SaaS 平台 |
|
||||||
|
|
||||||
|
> **💡 提示**: 所有分支均基于 `master` 演进,可根据项目规模灵活 `git merge` 所需功能模块。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速运行
|
||||||
|
|
||||||
|
### 🛠️ 环境依赖
|
||||||
|
* **JDK**: 17 +
|
||||||
|
* **Maven**: 3.8 +
|
||||||
|
* **MySQL**: 8.0 +
|
||||||
|
* **Redis**: 5.0 +
|
||||||
|
* **Nacos**: 2.x (可选)
|
||||||
|
|
||||||
|
### 🏃♂️ 启动步骤
|
||||||
|
|
||||||
|
> **Step 1: 获取源码**
|
||||||
|
> ```bash
|
||||||
|
> git clone https://github.com/RemainderTime/spring-boot-base-demo.git
|
||||||
|
> cd spring-boot-base-demo
|
||||||
|
> ```
|
||||||
|
|
||||||
|
> **Step 2: 数据准备**
|
||||||
|
> * 创建数据库 `xf_boot_base`
|
||||||
|
> * 修改 `src/main/resources/application.yml` 配好你的数据库账号密码
|
||||||
|
> * *(注:实体类完善,表结构建议通过 JPA 或手动创建)*
|
||||||
|
|
||||||
|
> **Step 3: 启动服务**
|
||||||
|
>
|
||||||
|
> **方式 A: 本地 Maven 运行**
|
||||||
|
> ```bash
|
||||||
|
> mvn spring-boot:run
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> **方式 B: Docker Compose 一键编排**
|
||||||
|
> ```bash
|
||||||
|
> cd src/main/resources/docker
|
||||||
|
> docker-compose -f boot-docker-compose.yml up -d
|
||||||
|
> ```
|
||||||
|
|
||||||
|
> **Step 4: 探索接口**
|
||||||
|
> 打开浏览器访问: `http://localhost:8080/doc.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 项目日志
|
||||||
|
|
||||||
|
### v1.0.1 (2024-10-12)
|
||||||
|
* ⬆️ **内核升级**: Spring Boot 3.3.3 & MyBatis Plus 3.5.8
|
||||||
|
* 📝 **文档重构**: 全面拥抱 SpringDoc OpenAPI,弃用旧版 Swagger
|
||||||
|
* 🛡️ **安全加固**: 优化全局异常拦截与参数校验机制
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 关注趋势
|
||||||
|
|
||||||
|
[](https://star-history.com/#RemainderTime/spring-boot-base-demo&Date)
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
**喜欢这个项目?请点个 Star ⭐ 支持作者持续更新!**
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
pom.xml
4
pom.xml
@@ -46,6 +46,10 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package cn.xf.basedemo.common.exception;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.validation.FieldError;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 全局异常处理类 (使用 @RestControllerAdvice 替代旧版
|
||||||
|
* HandlerExceptionResolver)
|
||||||
|
* @Author: xiongfeng
|
||||||
|
* @Date: 2025/1/9
|
||||||
|
* @Version: 2.0
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理登录/认证异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(LoginException.class)
|
||||||
|
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||||
|
public GenericResponse<Void> handleLoginException(LoginException e, HttpServletRequest request) {
|
||||||
|
log.warn("认证失败 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getCode(), e.getMessage());
|
||||||
|
return new GenericResponse<>(e.getCode(), null, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理业务逻辑异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BusinessException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST) // 或者使用 HttpStatus.OK,根据前端约定
|
||||||
|
public GenericResponse<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
||||||
|
log.warn("业务异常 [URL:{}]: code={}, message={}", request.getRequestURI(), e.getCode(), e.getMessage());
|
||||||
|
return new GenericResponse<>(e.getCode(), null, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理参数校验异常 (处理 @Valid / @Validated 触发的异常)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public GenericResponse<Void> handleValidationException(Exception e, HttpServletRequest request) {
|
||||||
|
BindingResult bindingResult = null;
|
||||||
|
if (e instanceof MethodArgumentNotValidException) {
|
||||||
|
bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
|
||||||
|
} else if (e instanceof BindException) {
|
||||||
|
bindingResult = ((BindException) e).getBindingResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
String errorMsg = "参数校验失败";
|
||||||
|
if (bindingResult != null && bindingResult.hasErrors()) {
|
||||||
|
FieldError fieldError = bindingResult.getFieldError();
|
||||||
|
if (fieldError != null) {
|
||||||
|
errorMsg = fieldError.getDefaultMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.warn("参数校验失败 [URL:{}]: {}", request.getRequestURI(), errorMsg);
|
||||||
|
return new GenericResponse<>(ResponseCode.USER_INPUT_ERROR.getCode(), null, errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有未知的系统异常 (兜底)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public GenericResponse<Void> handleSystemException(Exception e, HttpServletRequest request) {
|
||||||
|
// 生产级关键点:必须记录异常堆栈,否则无法排查 BUG
|
||||||
|
log.error("系统发生未知异常 [URL:{}]", request.getRequestURI(), e);
|
||||||
|
return new GenericResponse<>(500, null, "系统内部繁忙,请稍后再试");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package cn.xf.basedemo.common.exception;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Description: 全局异常捕获类(所有异常(包括拦截器、Controller、视图))HandlerExceptionResolver更底层
|
|
||||||
* @ClassName: GlobalExceptionResolver
|
|
||||||
* @Author: xiongfeng
|
|
||||||
* @Date: 2025/8/23 23:30
|
|
||||||
* @Version: 1.0
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class GlobalExceptionResolver implements HandlerExceptionResolver {
|
|
||||||
@Override
|
|
||||||
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
|
|
||||||
try (PrintWriter writer = response.getWriter()) {
|
|
||||||
if (ex instanceof LoginException) {
|
|
||||||
response.setStatus(HttpStatus.FORBIDDEN.value());
|
|
||||||
LoginException le = (LoginException) ex;
|
|
||||||
writer.write(new ObjectMapper().writeValueAsString(
|
|
||||||
new GenericResponse<>(le.getCode(), null, le.getMessage())
|
|
||||||
));
|
|
||||||
} else if (ex instanceof BusinessException) {
|
|
||||||
BusinessException be = (BusinessException) ex;
|
|
||||||
response.setStatus(HttpStatus.BAD_REQUEST.value());
|
|
||||||
writer.write(new ObjectMapper().writeValueAsString(
|
|
||||||
new GenericResponse<>(be.getCode(), null, be.getMessage())
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
|
||||||
writer.write(new ObjectMapper().writeValueAsString(
|
|
||||||
new GenericResponse<>(500, null, "系统异常")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} catch (IOException ioEx) {
|
|
||||||
log.error("写响应失败", ioEx);
|
|
||||||
}
|
|
||||||
return new ModelAndView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import cn.xf.basedemo.service.UserService;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +28,7 @@ public class UserController {
|
|||||||
|
|
||||||
@Operation(summary = "用户登录", description = "用户登录")
|
@Operation(summary = "用户登录", description = "用户登录")
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public RetObj login(@RequestBody LoginInfoRes res) {
|
public RetObj login(@RequestBody @Validated LoginInfoRes res) {
|
||||||
|
|
||||||
return userService.login(res);
|
return userService.login(res);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.xf.basedemo.model.res;
|
package cn.xf.basedemo.model.res;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,6 +17,7 @@ public class LoginInfoRes {
|
|||||||
/**
|
/**
|
||||||
* 登录密文
|
* 登录密文
|
||||||
*/
|
*/
|
||||||
|
@NotBlank(message = "登录密文不能为空")
|
||||||
@Schema(name = "登录密文")
|
@Schema(name = "登录密文")
|
||||||
private String encryptedData;
|
private String encryptedData;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user