🏷️ 前言
在实际的企业级开发中,我们往往面临这样一个场景:业务初期不需要复杂的微服务架构,一个稳健、标准、高扩展的单体应用才是最佳选择。
网上的资源大部分还停留在 Boot 2.x 甚至 JDK 8 的时代,配置也多半是"玩具级"的------只有基础 CURD,像RSA+JWT 鉴权 、全局异常兜底 、COSR跨域 、Redis 双重缓存 以及标准化的 Docker 部署这些生产必备的组件,基本找不到现成整合好的。
既然找不到标准的,就自己动手搭一套。这篇博客就是为了解决这个痛点:搭建一套打磨好的生产级底座,确保你拉下来改个包名,就能直接投入开发,别再浪费时间重复造轮子了。
源码地址 :https://github.com/RemainderTime/spring-boot-base-demo
(建议 Clone 下来对照阅读,本文基于 master 分支)
⚙️ 环境准备
- JDK : 17+ (Spring Boot 3.x 硬性要求)
- Maven: 3.8+
- MySQL: 8.0+
- Redis: 5.0+
- Nacos: 2.x (作为配置中心,可选但推荐)
🚀 实践步骤
1. 工程核心依赖管理
使用 Maven 创建标准 Spring Boot 工程,核心依赖如下 (pom.xml):
XML
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
</parent>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.8</mybatis-plus.version>
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
<springdoc.version>2.6.0</springdoc.version>
</properties>
<dependencies>
<!-- Web 模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT (鉴权核心) -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
<!-- SpringDoc (OpenAPI 3 文档) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
</dependencies>
2. Nacos 配置变革 (Configuration)
Spring Boot 2.4+ 推荐使用 import 方式导入配置,摒弃 bootstrap.yml。
本地引导配置 ( src/main/resources/application.yml):
bash
server:
port: 8089
shutdown: graceful
spring:
profiles:
active: dev
application:
name: xf-boot-base
cloud:
nacos:
config:
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
file-extension: yml
group: DEFAULT_GROUP
namespace: YOUR_NAMESPACE_ID # 你的 Nacos 命名空间ID
# 核心改动:通过 import 导入远程配置
config:
import:
- nacos:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
Nacos 远程配置内容 ( xf-boot-base-dev.yml) :
直接新建配置,粘贴以下生产级参数。注意我们定义了一个 global属性来存放 RSA 秘钥。
bash
spring:
datasource:
dynamic:
primary: master
hikari:
maximum-pool-size: 10
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/xf-boot-base?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: 127.0.0.1
port: 6379
lettuce:
pool:
maxActive: 100
maxIdle: 30
# 自定义全局配置
global:
# RSA 私钥 (用于解密前端传来的密码)
rsaPrivateKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL8XlRALVBay7dCw...
扩展点与注意事项:
- Nacos 动态刷新 : 如果需要在不重启服务的情况下动态调整日志级别或业务开关,可以在类上添加
@RefreshScope注解。本项目的GlobalConfig类已经具备此能力(属性注入),修改 Nacos 配置后会自动生效。
3. 核心基础配置 (Infrastructure)
在写业务前,必须先把基础设施搭好:跨域、分页、文档。
跨域配置 ( GlobalCorsConfig.java) :
前后端分离必踩之坑,这里直接上万能配方。
java
@Configuration
public class GlobalCorsConfig {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*"); // 允许所有域
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true); // 允许 Cookie
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0); // 优先级最高
return bean;
}
}
MyBatis Plus 分页配置 ( MybatisPlusConfig.java):
java
@Configuration
@MapperScan("cn.xf.basedemo.mappers")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
Swagger/SpringDoc 配置 ( SwaggerGroupApi.java):
java
@Configuration
public class SwaggerGroupApi {
@Bean
public OpenAPI apiInfo() {
return new OpenAPI()
.info(new Info().title("XF-Boot-Base API").version("v1.0"))
// 配置 JWT 安全认证按钮
.components(new Components()
.addSecuritySchemes("Authorization",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")))
.addSecurityItem(new SecurityRequirement().addList("Authorization"));
}
}
Redis 序列化配置 ( RedisConfig.java):
java
@Configuration
public class RedisConfig {
@Resource
private RedisConnectionFactory factory;
@Bean
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); // 关键点:使用 JSON 序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
扩展点与注意事项:
- Redis 序列化 : 生产环境中强烈建议使用
GenericJackson2JsonRedisSerializer或Jackson2JsonRedisSerializer替代默认的 JDK 序列化,这样在 Redis Desktop Manager 中看到的 VALUE 才是可读的 JSON 字符串,方便调试与排查问题。
4. 全局异常处理 (Global Exception)
这是区分"Demo"和"产品"的关键。通过 @RestControllerAdvice 统一接管异常。
全局异常处理器 ( GlobalExceptionHandler.java):
java
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
// 1. 处理自定义登录异常
@ExceptionHandler(LoginException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public GenericResponse<Void> handleLoginException(LoginException e) {
log.warn("认证失败: {}", e.getMessage());
return new GenericResponse<>(403, null, e.getMessage());
}
// 2. 处理参数校验异常 (@Validated 触发)
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public GenericResponse<Void> handleValidationException(Exception e, HttpServletRequest request) {
String errorMsg = "参数校验失败";
if (e instanceof MethodArgumentNotValidException) {
errorMsg = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage();
}
return new GenericResponse<>(400, null, errorMsg);
}
// 3. 兜底系统异常
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public GenericResponse<Void> handleSystemException(Exception e, HttpServletRequest request) {
log.error("系统未知异常 [URL:{}]", request.getRequestURI(), e);
return new GenericResponse<>(500, null, "系统繁忙,请稍后再试");
}
}
5. 核心业务:安全登录闭环 (Authentication)
常规明文传输密码已无法通过安全审计。我们实现了 RSA前端加密 -> 后端解密 -> 验证 -> 生成JWT -> Redis缓存 的完整链路。
Service 层 ( UserServiceImpl.java):
java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private GlobalConfig globalConfig; // 注入 Nacos 中的 RSA 私钥
@Override
public RetObj login(LoginInfoRes res) {
try {
// 1. RSA 解密前端传来的密文
String loginJson = RSAUtils.privateDecryption(res.getEncryptedData(),
RSAUtils.getPrivateKey(globalConfig.getRsaPrivateKey()));
LoginInfo loginInfo = objectMapper.readValue(loginJson, LoginInfo.class);
// 2. 数据库查验账号密码
User user = userMapper.selectOne(new QueryWrapper<User>()
.eq("account", loginInfo.getAccount())
.eq("password", loginInfo.getPwd()));
if (user == null) return RetObj.error("账号或密码错误");
// 3. 生成 Token 并双重缓存
String token = JwtTokenUtils.createToken(user.getId());
LoginUser loginUser = convertToLoginUser(user, token);
// 缓存1:Token -> UserInfo (用于鉴权)
redisTemplate.opsForValue().set("token:" + token, JSONObject.toJSONString(loginUser), 3600, TimeUnit.SECONDS);
// 缓存2:UserId -> Token (用于踢人下线)
redisTemplate.opsForValue().set("user_login_token:" + user.getId(), token, 3600, TimeUnit.SECONDS);
return RetObj.success(loginUser);
} catch (Exception e) {
log.error("登录异常", e);
return RetObj.error("登录失败");
}
}
}
扩展点与注意事项:
- 关于 RSA 加密 : 我们使用
RSAUtils.privateDecryption进行密码解密。请务必去RSAUtils工具类中生成你自己的一对公私钥,并将私钥 配置到 Nacos 的global.rsaPrivateKey中,公钥提供给前端用于加密密码。
网关级拦截器 ( TokenInterceptor.java) :
拦截所有请求,解析 Token 并注入 ThreadLocal
java
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// ... (略去白名单校验)
// 1. 提取 Token
String token = request.getHeader("Authorization");
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
token = token.substring(7);
} else {
throw new LoginException("未登录");
}
// 2. Redis 校验
String userInfoJson = (String) redisTemplate.opsForValue().get("token:" + token);
if (StringUtils.isEmpty(userInfoJson)) {
throw new LoginException("会话已过期");
}
// 3. 自动续期
redisTemplate.expire(token, 86700, TimeUnit.SECONDS);
// 4. 写入上下文,Controller 直接用
SessionContext.getInstance().set(JSONObject.parseObject(userInfoJson, LoginUser.class));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
SessionContext.getInstance().clear(); // 必须清理,防止内存泄漏
}
}
扩展点与注意事项:
- ThreadLocal 内存泄漏防范 : 在
afterCompletion方法中,必须 调用SessionContext.getInstance().clear()。虽然 ThreadLocal 在线程销毁时会被回收,但在 Web 容器的线程池场景下,线程是复用的,如果不清理,上一个用户的 UserInfo 可能会残留在当前线程中,造成严重的数据泄露 BUG。
6. 可视化演示与前端实现 (Visual Demo & Frontend)
为了直观展示效果,我们提供了简单的 Web 页面进行模拟登录测试。
用户表结构 ( xf_user) :
账号:admin,密码:123456
sql
CREATE TABLE `xf_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) DEFAULT NULL COMMENT '姓名',
`account` varchar(32) DEFAULT NULL COMMENT '账号',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 这里的密码实际存储时应为加密后的,演示为了直观使用明文(配合代码逻辑调整)
INSERT INTO `xf_user` VALUES (1001, '又菜又爱玩', 'admin', '123456', '13800138000', NOW());
Web 页面路由控制器 ( WebController.java):
java
@Controller
@RequestMapping("web")
public class WebController {
// 跳转登录页
@RequestMapping(value = "/login")
public String login() {
return "login";
}
// 统一页面跳转
@RequestMapping(value = "/{url}", method = RequestMethod.GET)
public String skipUrl(@PathVariable(name = "url") String url) {
return url;
}
}
前端登录实现 ( login.html):

使用 Vue + ElementUI,集成了 JSEncrypt 进行 RSA 加密。
javascript
<script>
new Vue({
el: '#app',
data: {
rsaPublicKey: '...', // 你的 RSA 公钥
account: '',
pwd: ''
},
methods: {
login: function () {
var json = JSON.stringify({account: this.account, pwd: this.pwd});
// RSA 公钥加密
var cipher = this.encryptByPublicKey(json);
axios.post("http://localhost:8089/user/login", {
encryptedData: cipher,
}).then(function (response) {
if (response.data.code == 200) {
// 登录成功跳转,URL携带 Token
var param = "?token=" + response.data.data.token + "&name=" + encodeURI(encodeURI(response.data.data.name));
window.location.href = "success" + param
} else {
alert(response.data.message)
}
});
},
encryptByPublicKey: function (val) {
let encryptor = new JSEncrypt()
encryptor.setPublicKey(this.rsaPublicKey)
return encryptor.encrypt(val)
}
}
});
</script>
登录演示:
- 登录页 : 访问
http://localhost:8089/web/login

- 成功页: 登录成功后跳转,URL 中携带了 Token,页面解析并展示。

7. Docker 容器化部署 (Deployment)
SpringBoot 3.x 强制 JDK 17,因此 Dockerfile 基础镜像必须升级。
标准 Dockerfile ( Dockerfile):
bash
# 必须使用 JDK 17 基础镜像
FROM openjdk:17-jdk-alpine
MAINTAINER xiongfeng
WORKDIR /app
COPY target/xf-boot-base-*.jar app.jar
EXPOSE 8089
# 优化 JVM 参数,并支持环境变量注入
ENTRYPOINT ["java", \
"-Djava.security.egd=file:/dev/urandom", \
"-Dfile.encoding=UTF-8", \
"-Duser.timezone=Asia/Shanghai", \
"-jar", "app.jar"]
构建与启动命令:
bash
# 1. 本地编译打包 (跳过单元测试)
mvn clean package -DskipTests
# 2. jar包上传服务器后构建镜像 (注意最后的点)
docker build -t xf-boot-base:1.0.1 .
# 3. 启动容器
docker run -d --name xf-boot-base -p 8089:8089 \
-e NACOS_SERVER_ADDR=192.168.1.100:8848 \
xf-boot-base:1.0.1
📝 总结
至此,一套代码规范、安全可靠、配置灵活的 Spring Boot 3.3 生产级底座就搭建完成了。
- 架构:单体应用,但保留了 Nacos 扩展能力。
- 安全:RSA+JWT+Redis 全套方案。
- 规范:全局异常、统一响应、Swagger 文档一应俱全。
你可以直接基于此框架开发业务,或者作为公司内部脚手架的雏形。