From 055987541c1f88ec11c9a355850707bf7221c089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E8=A8=80?= <2439534736@qq.com> Date: Fri, 9 Jan 2026 11:41:19 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E6=94=B9=E8=BF=9B=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=BC=82=E5=B8=B8=E7=B1=BB=202.=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E4=BB=8B=E7=BB=8D=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 173 ++++++++++++------ pom.xml | 4 + .../exception/GlobalExceptionHandler.java | 80 ++++++++ .../exception/GlobalExceptionResolver.java | 53 ------ .../controller/business/UserController.java | 3 +- .../xf/basedemo/model/res/LoginInfoRes.java | 2 + 6 files changed, 209 insertions(+), 106 deletions(-) create mode 100644 src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionHandler.java delete mode 100644 src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionResolver.java diff --git a/README.md b/README.md index c8f1cec..591139e 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,136 @@ -## 拿来即用springboot基础脚手架 +# XF-Boot-Base (Spring Boot Base Demo) + +
+ +Spring Boot +Java +Nacos +MyBatis Plus + +
+
+ +[![GitHub stars](https://img.shields.io/github/stars/RemainderTime/spring-boot-base-demo?style=social&label=Stars)](https://github.com/RemainderTime/spring-boot-base-demo) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](./LICENSE) --- -### 项目介绍 -[![](https://img.shields.io/badge/-@remaindertime-FC5531?style=flat&logo=csdn&logoColor=FC5531&labelColor=424242)](https://blog.csdn.net/qq_39818325?type=blog) -[![GitHub Stars](https://img.shields.io/github/stars/RemainderTime/spring-boot-base-demo?style=social)](https://github.com/RemainderTime/spring-boot-base-demo) -![](https://img.shields.io/badge/jdk-1.8+-blue.svg) -![](https://img.shields.io/badge/springboot-3.3.3-{徽标颜色}.svg) -![](https://img.shields.io/badge/springdoc-2.6.0-{徽标颜色}.svg) -![](https://img.shields.io/badge/elasticsearch-8.16.0-005571.svg) -![](https://img.shields.io/badge/redis-3.3.3-FF4438.svg) ---- -> 这是一个基于 **Spring Boot 3.3.3** 的快速构建单体架构脚手架,旨在帮助开发者快速搭建高效、稳定的项目基础框架。项目集成了多种常用的技术组件与功能,涵盖从用户认证到数据加密、从全局异常处理到搜索引擎操作,适合个人学习与企业级单体应用开发。 +### 「 为企业级开发而生的高效脚手架 」 -#### 分支 -- master 快速上手开发spring boot 用户端单体应用 -- feature/admin-auth-spring-security 基于master分支集成spring官方鉴权框架spring security框架,可用于后台管理系统后端项目,实现RBAC模型(角色 → 用户 → 菜单 → 权限)基于角色的访问控制 -- feature/admin-auth-sa-token 基于master分支集成国产权限框架sa-token,可用于后台管理系统后端项目,实现RBAC模型(角色 → 用户 → 菜单 → 权限)基于角色的访问控制 -- component/rocketmq-and-es 基于master分支集成消息队列原生RocketMQ5.x与原生Elasticsearch 8.x,提供消息队列与搜索引擎服务,实现消息持久化与全文检索 -- feature/master-payment 基于master分支集成支付宝沙盒功能(H5支付、APP支付) +

+ 💎 关于项目  |  + ⚡ 核心亮点  |  + 🌿 生态全景  |  + 🚀 快速运行  |  + 📅 项目日志 +

+
-### 集成技术与功能亮点 +
-- 身份认证与授权(JWT):基于 JWT 实现用户认证与授权,确保系统安全性。 -- 数据加密(RSA):提供 RSA 非对称加密支持,保障敏感数据安全。 -- 持久层框架(MyBatis Plus):简化数据库操作,提供高效的 CRUD 支持。 -- 数据库(MySQL):采用 MySQL 作为默认数据库,易于扩展和维护。 -- 数据连接池(Hikari):高性能数据源管理,优化数据库连接效率。 -- 缓存(Redis):支持分布式缓存,提升系统响应速度与并发能力。 -- 接口文档(springdoc-openapi):自动生成标准化 API 文档,便于调试与集成。 -- 模板引擎(Thymeleaf):支持动态页面渲染,提升前后端协同效率。 -- 容器化支持(Docker):内置 Dockerfile,轻松实现环境部署与迁移。 -- 搜索引擎(Elasticsearch 8.x):集成最新版本 Elasticsearch Java 客户端,提供高效的全文检索与复杂查询功能。 -- 全局异常处理:统一管理异常,提升代码可维护性与调试效率。 -- 拦截器支持:轻松实现请求拦截与权限控制。 +## � 关于项目 -### 项目优势 -**全面适配 Spring Boot 3.x** -- 所有组件已全面升级为支持 Spring Boot 3.x 的最新版本。解决了开发者在版本升级中遇到的各种不兼容和适配问题,大大减少了升级带来的额外工作量,让项目开发更加顺畅。 +**XF-Boot-Base** 并非仅仅是一个简单的 "Hello World" 示例,而是一个经过精心打磨、具备生产级标准的 **Spring Boot 3.3** 全栈开发底座。 -**初学者友好** -- 提供清晰的代码结构与详细的配置说明,帮助初学者快速上手微服务与单体架构的开发实践。 +我们深入分析了企业单体应用到微服务架构演进过程中的痛点,构建了一套**模块化、可插拔、高扩展**的基础框架。从底层的 **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以及配置优化 -5. 替换swagger依赖支持spring boot3.x (knife4j->springdoc-openapi) -6. 新增请求头工具类 -7. 参数校验异常捕获优化 -8. 登录拦截器注册为spring容器管理 -9. 新增本地日志配置文件 + +## ⚡ 核心亮点 + +
+ +| 🚀 **前沿技术** | 🔐 **安全无忧** | 🐳 **云原生友好** | +| :--- | :--- | :--- | +| 紧跟 **Spring Boot 3.x** 生态,
基于 **Java 17 LTS** 构建,
享受最新技术红利。 | 深度整合 **JWT** 令牌认证
& **RSA** 非对称加密,
为数据安全保驾护航。 | 内置 **Dockerfile** 脚本,
支持 **Docker Compose** 编排,
部署快人一步。 | + +| 💾 **数据增强** | 🔌 **微服务预装** | 🛠 **极致体验** | +| :--- | :--- | :--- | +| **MyBatis Plus** 强力驱动,
**Dynamic Datasource**
轻松驾驭多数据源场景。 | 原生集成 **Nacos**,
配置中心与注册中心开箱即用,
平滑过渡微服务。 | **SpringDoc (OpenAPI 3)**
自动生成精美文档,
调试开发得心应手。 | + +
--- -如果这个项目对你有帮助,请随手点个 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 +* 🛡️ **安全加固**: 优化全局异常拦截与参数校验机制 + +--- + +## 📈 关注趋势 + +[![Star History Chart](https://api.star-history.com/svg?repos=RemainderTime/spring-boot-base-demo&type=Date)](https://star-history.com/#RemainderTime/spring-boot-base-demo&Date) + +
+
+ +**喜欢这个项目?请点个 Star ⭐ 支持作者持续更新!** + +
+ + + diff --git a/pom.xml b/pom.xml index 1b95392..d2a9b18 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,10 @@ org.springframework.boot spring-boot-starter-actuator + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-starter-data-redis diff --git a/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionHandler.java b/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0bcc700 --- /dev/null +++ b/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionHandler.java @@ -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 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 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 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 handleSystemException(Exception e, HttpServletRequest request) { + // 生产级关键点:必须记录异常堆栈,否则无法排查 BUG + log.error("系统发生未知异常 [URL:{}]", request.getRequestURI(), e); + return new GenericResponse<>(500, null, "系统内部繁忙,请稍后再试"); + } +} diff --git a/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionResolver.java b/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionResolver.java deleted file mode 100644 index 09ad30f..0000000 --- a/src/main/java/cn/xf/basedemo/common/exception/GlobalExceptionResolver.java +++ /dev/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(); - } -} diff --git a/src/main/java/cn/xf/basedemo/controller/business/UserController.java b/src/main/java/cn/xf/basedemo/controller/business/UserController.java index f18ef7d..5633410 100644 --- a/src/main/java/cn/xf/basedemo/controller/business/UserController.java +++ b/src/main/java/cn/xf/basedemo/controller/business/UserController.java @@ -8,6 +8,7 @@ import cn.xf.basedemo.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** @@ -27,7 +28,7 @@ public class UserController { @Operation(summary = "用户登录", description = "用户登录") @PostMapping("/login") - public RetObj login(@RequestBody LoginInfoRes res) { + public RetObj login(@RequestBody @Validated LoginInfoRes res) { return userService.login(res); } diff --git a/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java b/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java index 7c7a5d0..d719e92 100644 --- a/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java +++ b/src/main/java/cn/xf/basedemo/model/res/LoginInfoRes.java @@ -1,6 +1,7 @@ package cn.xf.basedemo.model.res; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -16,6 +17,7 @@ public class LoginInfoRes { /** * 登录密文 */ + @NotBlank(message = "登录密文不能为空") @Schema(name = "登录密文") private String encryptedData; }