ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot 3.x + JDK17+Nacos+JWT+Docker 生产级单体脚手架搭建指南

Spring Boot 3.x + JDK17+Nacos+JWT+Docker 生产级单体脚手架搭建指南 Spring Boot 3.x 出来之后身边不少朋友还在 JDK8 Spring Boot 2.x 的组合里观望问得最多的一句话就是“到底值不值得升级”。我的结论很直接如果你是新项目直接上 Spring Boot 3.x JDK17别犹豫。JDK17 是 LTS 版本Spring Boot 3.x 官方最低要求就是它生态已经成熟。这套组合配合 Nacos 做注册和配置、JWT 做无状态认证、Docker 做交付能在一个单体项目里把所有生产级要素都补齐。这套脚手架我前前后后踩了大半个月的坑今天把完整思路和能直接抄走的配置都摊开讲清楚。适合谁看想从 2.x 迁移的老手、刚工作一两年的后端开发、以及准备给团队搭基础工程的负责人。内容从 JDK17 装环境开始一直到 Docker 部署全程可以跟着复现。1. 技术选型为什么锁定这个组合1.1 为什么是 Spring Boot 3.x JDK17不是 JDK8 也不是 JDK21先说最容易被忽略的一点Spring Boot 3.x 用了Jakarta EE 命名空间原本的javax.*包全部改成了jakarta.*。这不仅仅是改个 import 的事很多老第三方库如果不升级直接编译不过。所以从 2.x 升 3.x 不是换版本号那么简单得把依赖体系整个过一遍。JDK17 的选择理由很实在它是 LTS 版本免费商用官方支持到 2029 年以后。对比 JDK817 带来的实际收益不只是语法糖ZGC 在大堆内存场景下的停顿改善很明显record、switch表达式、文本块这些特性让代码简洁不少。有人会问为什么不用 JDK21因为 Spring Boot 3.x 在 17 上跑得已经很稳21 的虚拟线程虽然诱人但对单体项目来说还不是刚需没必要在基础工程里引入额外变量。还有一点要注意Spring Boot 3.x 对第三方依赖的最低版本有硬性要求比如 MyBatis 得 3.5.5、MySQL 驱动得 8.0.33。如果你项目里还躺着老版本的 druid 或者小众 ORM升级前最好先查一遍兼容性清单别等编译爆红了再手忙脚乱。1.2 Nacos、JWT、Docker 在单体项目里分别解决什么问题这套组合里Nacos 同时承担注册中心和配置中心两个角色。有人会觉得单体架构用不上注册中心其实不然。单体项目一旦部署多个实例做负载均衡实例上下线就需要被网关或负载设备感知到而配置中心的价值更大数据库连接池参数、开关类配置、接口阈值这些都能动态调整不用重启应用。替代品里 Consul、Apollo 都够好但 Nacos 对 Spring Cloud Alibaba 生态的支持、控制台的易用性、中文文档的完善度综合起来是最适合 Java 团队的。JWT 解决的是无状态认证。传统 Session 方案在单体里也没问题但要考虑分布式扩展时 Session 同步的麻烦。JWT 令牌自带用户标识和过期时间服务端不需要存会话天然适合前后端分离和后续拆微服务。代价是令牌一旦签发在过期前很难主动吊销所以后面我会讲续签和黑名单的折中方案。Docker 解决的是环境一致性问题。团队里十个开发、五套系统配置总有人 MySQL 版本不对、Redis 没装好。用 Docker 把 MySQL、Redis、Nacos 这些基础设施统一编排起来一条命令拉起整套依赖环境开发机和生产环境的差异被压缩到最小。这套组合还有一个隐藏收益沉淀下来的 docker-compose 文件、Nacos 配置模板、JWT 工具类可以直接复用到下一个项目。我见过太多团队每个项目从零搭环境配置还搭得五花八门脚手架的意义就是把这些公共成本一次性付掉。1.3 为什么坚持做单体而不是一开始就拆微服务选型阶段必须面对一个问题既然上了 Nacos为什么不干脆拆微服务我的经验是大部分业务场景单体更合适。微服务带来的服务间通信开销、分布式事务复杂度、链路追踪成本在小团队和中等体量业务面前都是纯负担。单体先把业务模型跑通等某个模块确实出现独立扩缩容需求时再基于清晰的模块边界拆出去这是成本最低的演进路径。所以这个脚手架的定位是代码上单体基础设施上保留微服务时代的扩展能力。Nacos、JWT 这些选型让将来拆服务时不用推翻重来。2. 环境准备JDK17 与工程初始化2.1 JDK17 安装与配置JDK17 的安装本身不复杂但有几个地方容易踩坑。Windows 用户建议直接下载Oracle JDK17 或 Eclipse TemurinAdoptium的.msi安装包前者安装完自动配好 JAVA_HOME后者需要手动加环境变量。Linux 服务器上更推荐的是一键安装sudo apt install openjdk-17-jdk装完先验证版本java -version这里有个坑如果之前装过 JDK8PATH里可能还残留旧路径要确认JAVA_HOME指向的是 17 而不是 8。IDEA 社区版里切换 Project SDK 时也要手动指向 17。体感上很多新手在“JDK17 下载与安装”这一步卡住其实是没搞清楚渠道。带 Oracle 账号才能下载的版本、清华镜像的 OpenJDK、sdkman 安装的版本三者都能用优先选 Temurin 最省心。2.2 用 IDEA 初始化 Spring Boot 3.x 工程IDEA 社区版没有 Spring Initializr 的集成向导但有两个替代路径都很顺畅。路径一去 start.spring.io 生成基础工程再导入。页面里选择Java 17、Spring Boot 3.x依赖勾选 Spring Web、Validation、Spring Data Redis、MySQL Driver、Lombok。生成后解压在 IDEA 里直接 Open 这个 Maven 项目。路径二手动建一个空 Maven 工程pom.xml里用 parent 继承 Spring Boot 父工程parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent然后添加所需 starter。我推荐路径二因为它让你清楚每个依赖是怎么进来的而不是无脑勾选。工程创建后的第一件事是确认 Maven 的 JDK 编译级别。在pom.xml显式声明properties java.version17/java.version maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties不显式声明的话Maven 默认可能用 JDK8 编译导致各种诡异报错。2.3 依赖版本统一管理的实操做法Spring Boot 的 parent POM 已经管理了大多数核心依赖版本但第三方组件需要自己锁版本尤其是这几个Nacos 客户端、JJWT 库、MySQL 驱动。忽略版本管理的话团队里 A 用了 2.2.0 的 JJWT、B 用了 2.3.0接口签名还不一样联调时到处是雷。我的习惯是单独抽一个spring-boot-dependencies-custom的 BOM 模块或者直接在父pom.xml里用dependencyManagement统一声明dependencyManagement dependencies dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2023.0.1.0/version typepom/type scopeimport/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency /dependencies /dependencyManagement版本统一之后项目里加依赖只需要写 groupId 和 artifactId不用反复查版本号升级时也只改一处。3. Nacos 落地注册中心与配置中心一次搞定3.1 用 Docker 跑 Nacos 并开启鉴权Nacos 的部署方式选择很多开发机推荐 Docker 单机版生产环境建议至少主备。单机启动最简单的方式docker run -d --name nacos \ -p 8848:8848 \ -p 9848:9848 \ -e MODEstandalone \ -e NACOS_AUTH_ENABLEtrue \ nacos/nacos-server:v2.3.2这里加粗说明两个端口8848 是 HTTP 端口9848 是 gRPC 端口Spring Cloud Alibaba 从 2021 版本开始走 gRPC 通信遗漏 9848 端口会导致服务注册后心跳报错这是最容易踩的坑。环境变量里NACOS_AUTH_ENABLEtrue就是开启鉴权。Nacos 控制台默认账号密码是nacos/nacos开启鉴权后第一次登录一定要改密码否则相当于裸奔。更稳妥的做法是在启动参数里预置密钥-e NACOS_AUTH_TOKEN此处填一个自定义的Base64编码密钥网上有不少关于 Nacos 未授权访问漏洞的讨论核心原因就是没开鉴权或者密钥用默认值如果你负责生产环境的 Nacos务必检查这两项。3.2 服务注册与数据源连接配置应用接入 Nacos 做注册中心依赖配一下dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependencyapplication.yml里的关键配置spring: application: name: demo-server cloud: nacos: server-addr: 127.0.0.1:8848 username: nacos password: 修改后的密码 discovery: namespace: dev group: DEFAULT_GROUP config: namespace: dev file-extension: yaml refresh-enabled: true注意file-extension必须设成 yaml因为 Nacos 控制台新建配置时你可以选 Data ID 格式但客户端这边要明确告知解析格式否则默认按 properties 解析配置内容识别不了。如果项目里用了达梦这类国产数据库Nacos 客户端和驱动也要适配。Nacos 2.5.4 连接达梦数据库时需要在 Nacos 的配置文件里把数据源驱动换成达梦的 JDBC 驱动类并在application.properties里指向达梦的连接 URL。这块在官方文档里写得比较简略实操时要是发现 Nacos 后台能打开但配置持久化报错第一反应就查数据库驱动和方言配置。3.3 配置分组、命名空间与多环境管理Nacos 配置管理的三个层次容易搞混命名空间Namespace、分组Group、Data ID。我拿文件夹类比一下命名空间相当于整个环境隔离dev、test、prod 各一套配置文件互不干扰。分组相当于项目分类同一个环境里多个项目用分组区分。Data ID相当于文件名Spring Boot 应用默认找{spring.application.name}.{file-extension}这个 Data ID。实际项目里配置分层的标准做法是公共配置放一个 Data ID各环境差异配置放不同命名空间。比如demo-server.yaml # 公共配置端口、应用名、日志级别 demo-server-datasource.yaml # 数据源配置 demo-server-redis.yaml # 缓存配置然后通过spring.cloud.nacos.config.extension-configs引入多个 Data IDspring: cloud: nacos: config: extension-configs: ->RefreshScope Component ConfigurationProperties(prefix order) public class OrderConfig { private Integer timeoutSeconds; private Boolean autoCancel; // getter / setter }在 Nacos 控制台修改配置后几秒内应用就能感知。但有三个坑必须提醒第一Value注入的字段必须配合RefreshScope才能刷新如果是static字段刷新会失效因为静态字段不属于实例作用域。第二数据源连接池参数刷新不一定能完全热生效。比如最大连接数连接池初始化后内部可能不会动态调整最保险的做法是刷新回调里重建连接池或者接受这类参数需要重启。第三动态刷新日志默认级别是 INFO遇到“明明改了没生效”的疑难问题先把com.alibaba.nacos的日志级别调到 DEBUG看客户端是否真的收到了推送大概率是网络或配置格式问题。4. JWT 认证从登录到鉴权的完整实现4.1 JWT 令牌从登录到校验的完整链路JWT 认证流程画出来其实很简单但每个环节都有实现细节。链路是用户提交账号密码外加验证码登录接口校验通过后生成 JWT 令牌。前端拿到令牌后每次请求在Authorization请求头带上Bearer token。后端拦截器或过滤器解析令牌校验签名和过期时间从令牌里取出用户信息放入上下文。接口层从上下文拿当前用户做权限判断。JWT 的结构分三部分Header签名算法和类型、Payload用户信息、签发时间、过期时间、Signature签名三部分用点号连接。签名的作用是防止令牌内容被篡改服务端只保留密钥不用存储会话。核心工具类我习惯用 JJWT 0.11.5 实现。生成令牌private String createToken(String userId, String username) { long now System.currentTimeMillis(); return Jwts.builder() .setSubject(userId) .claim(username, username) .setIssuedAt(new Date(now)) .setExpiration(new Date(now EXPIRE_MILLIS)) .signWith(secretKey, SignatureAlgorithm.HS256) .compact(); }解析令牌public Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody(); }这里有个重要细节HS256 的密钥长度必须大于等于 256 位32 字节否则 JJWT 会抛WeakKeyException或签名异常。很多新手用短字符串当密钥总是报错查了半天才发现是长度不够。4.2 集成 Spring Security 还是拦截器这是单体项目里讨论最多的问题之一。我的建议分情况如果是纯后端 API 且认证模式单一用拦截器完全够如果涉及复杂权限模型、方法级权限、OAuth2 集成直接上 Spring Security。我用 Spring Security 的做法是自定义一个OncePerRequestFilter专门解析和校验 JWT把认证信息放进SecurityContextHolderComponent public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token resolveToken(request); if (token ! null jwtService.validateToken(token)) { String userId jwtService.getUserId(token); UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(userId, null, List.of(new SimpleGrantedAuthority(ROLE_USER))); SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } }然后 SecurityConfig 里把这些接口列为白名单其他路径统一需要认证http .csrf(csrf - csrf.disable()) .sessionManagement(session - session.stateless()) .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/login, /api/auth/captcha).permitAll() .anyRequest().authenticated()) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);注意csrf必须关闭因为 JWT 模式是无状态 APICSRF 令牌机制反而会干扰。跨域配置要单独处理在 CorsFilter 里放行带Authorization头的请求。4.3 验证码、Token 续签与安全加固登录验证码是 SPA 项目里常见的配置。实现方式后端生成一个随机的 4 位或 6 位验证码用 Redis 存起来key 为 uuidvalue 为验证码同时把验证码图片的 Base64 返回给前端。用户提交登录时带上 uuid 和输入值后端对比后再走账号密码校验。Redis 存储验证码的过期时间建议 5 分钟校验通过后立即删除。不管是网上给的 5 分钟还是更短验证码必须有失效时间和单次有效限制防止暴力重放。Token 续签是 JWT 体系里的经典难题。JWT 令牌本身无状态过期后你没法自己续最主流的方案是双令牌机制Access Token 有效期短30 分钟Refresh Token 有效期长7 天。Access Token 过期后前端用 Refresh Token 调一个刷新接口换取新的 Access Token。实现时有两个细节Refresh Token 接口要校验 Refresh Token 的签名、过期时间和是否在黑名单里更换令牌时要让旧 Access Token 立即失效做法是把令牌放进 Redis 黑名单TTL 设为剩余有效期。这样既享受了 JWT 无状态的优点又补上了“主动吊销”的短板。安全加固方面结合网上讨论的 JWT 漏洞总结至少要做这几件事:密钥复杂度要高不能是硬编码的短字符串建议配置中心下发定期轮转。Payload 里不要放敏感信息JWT 是 Base64 编码不是加密谁都能解码看内容。kidKey ID参数如果用了必须校验密钥来源防止攻击者通过篡改kid注入恶意密钥。设置合理的过期时间并配合 Refresh Token 机制尽量缩小令牌泄露的暴露窗口。5. Docker 化部署让环境和应用一起交付5.1 编写 Dockerfile 构建应用镜像JDK17 应用打镜像最推荐的思路是多阶段构建。第一阶段用 Maven 容器编译第二阶段用精简 JRE 运行这样最终镜像体积能缩小一大半。# 构建阶段 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests -B # 运行阶段 FROM eclipse-temurin:17-jre-jammy WORKDIR /app COPY --frombuilder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]写 Dockerfile 时有三个容易被忽略的点第一mvn dependency:go-offline这一步很关键它先把依赖下载缓存到镜像层之后每次改代码重新构建时只要pom.xml没变就走缓存构建速度快很多。第二Java 应用跑在容器里要注意内存参数。JVM 默认可能申请宿主机四分之一的内存这在开发机没问题但生产环境很容易把容器内存撑爆。建议显式设置堆内存java -Xms256m -Xmx512m -jar app.jar第三时区问题。容器默认是 UTC 时区Java 应用打日志时间和本地差 8 小时镜像里补一句ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone5.2 docker-compose 编排 MySQL、Redis 与 Nacos基础设施用 docker-compose 编排一条命令拉起全套依赖。这是整个脚手架里性价比最高的部分我直接给一个能用的模板version: 3.8 services: mysql: image: mysql:8.0 container_name: scaffold-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: scaffold TZ: Asia/Shanghai ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.0 container_name: scaffold-redis command: redis-server --requirepass redis123456 ports: - 6379:6379 volumes: - redis-data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 nacos: image: nacos/nacos-server:v2.3.2 container_name: scaffold-nacos environment: MODE: standalone NACOS_AUTH_ENABLE: true NACOS_AUTH_TOKEN: xxxxxxxx SPRING_DATASOURCE_PLATFORM: mysql MYSQL_SERVICE_HOST: mysql MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: root123456 ports: - 8848:8848 - 9848:9848 depends_on: mysql: condition: service_healthy app: build: . container_name: scaffold-app depends_on: mysql: condition: service_healthy nacos: condition: service_started ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: dev TZ: Asia/Shanghai volumes: mysql-data: redis-data:注意depends_on加condition: service_healthy是一种优雅的启动顺序控制方式。直接depends_on只是启动顺序容器启动不代表服务就绪MySQL 还没初始化完成应用就连库必定失败。加了健康检查之后等 MySQL 真正 ready 再启动应用这个坑能省下大量排查时间。Redis 主从配置如果也要纳入编排建议用redis:7.0起两个服务master 端口 6379slave 通过--replicaof master 6379指定主库然后用redis-sentinel或者业务侧读写分离去适配。5.3 Docker Desktop 在 Windows 上的安装与启动Windows 用户装 Docker Desktop最常遇到的报错是Virtualization support not detected或者Docker Desktop failed to start。这类问题 90% 是 Windows 虚拟化相关功能没开。排查顺序应该是这样打开“任务管理器 → 性能”确认虚拟化已启用。如果没启用去 BIOS/UEFI 里打开 Intel VT-x 或 AMD-V。打开“控制面板 → 程序 → 启用或关闭 Windows 功能”勾选“适用于 Linux 的 Windows 子系统WSL2”和“虚拟机平台”。执行wsl --set-default-version 2把默认版本切成 WSL2。重启 Docker Desktop。Windows 家庭版用户还要注意Hyper-V 功能默认没有需要先安装 WSL2 再启动 Docker Desktop。这一步处理完之后之前卡住的 docker 命令基本就能跑起来了。另外一点Docker Desktop 的资源设置里内存建议至少分给 4GB否则同时跑 MySQL、Redis、Nacos 三个容器会频繁 OOM。6. 生产环境避坑高频问题与排查实录6.1 Nacos 疑难杂症排查清单Nacos 日常使用中我遇到最多的问题列一个排查表。现象可能原因处理方式服务注册成功但调用报“找不到实例”服务名不一致或 gRPC 端口 9848 未开放先核对应用名再检查防火墙和端口映射应用启动报 Nacos 连接超时Nacos 服务未就绪客户端配置地址错误Nacos 先起后再启动应用检查 server-addr改了配置不刷新缺少RefreshScope或 Data ID 文件扩展名不对补注解检查file-extension: yaml控制台修改密码报request error, please try again later!Nacos 鉴权密钥未配置或 token 缓存异常配置NACOS_AUTH_TOKEN清浏览器缓存重新登录Nacos 页面能打开但配置持久化失败使用外部数据库时驱动不匹配或连接失败检查 MySQL 驱动、建表 SQL、数据库地址这里特别说下配置中心动态刷新不生效的问题。之前帮人排查过一次后台配置改了半天应用日志里完全没有刷新的痕迹。最后发现是 Spring Boot 3.x 里bootstrap上下文默认关闭导致 Nacos 配置在应用启动初期没加载进来。解决办法是引入依赖并开启 bootstrapdependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-bootstrap/artifactId /dependency或者在application.yml里用spring.config.import方式导入 Nacos 配置这个在 Spring Cloud 2021 之后更推荐。另外Nacos 2.5.4 连接达梦数据库这类场景除了换驱动还要确认 Nacos 的配置文件里spring.jpa.database-platform、spring.datasource.driver-class-name都同步改掉缺失一项就会出现“页面正常但数据写不进库”的怪现象。6.2 JWT 与 Spring Security 高频问题JWT Spring Security 的集成是单体脚手架里出错率最高的部分。常见问题有三个第一个是AuthenticationEntryPoint没配置导致未登录请求返回 403 而不是 401 JSON。Spring Security 默认行为对表单登录友好但对 API 不友好。需要自定义一个类Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或凭证已过期\}); } }然后在 SecurityConfig 里配置http.exceptionHandling(e - e.authenticationEntryPoint(restAuthenticationEntryPoint))。第二个是Spring Security 放行路径与拦截器冲突。如果同时存在自定义 HandlerInterceptor 和 Spring Security 过滤器注意/api/auth/login虽然是 Security 白名单但自定义拦截器可能仍然会执行并尝试解析 token结果登录接口被自己的拦截器拦了。解决办法是在自定义拦截器里也加上白名单判断或者干脆统一用 Spring Security 的过滤器链路。第三个是跨域配置与携带 Authorization 头的兼容问题。前端如果是独立域名必须配置 CORS 且允许Authorization请求头Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:5173) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }注意allowedOrigins不要用*配allowCredentials(true)浏览器会报错必须写具体域名。另外 Spring Security 的 CORS 配置和 Spring MVC 的 CORS 配置如果同时存在容易产生两套 filter 链建议在 SecurityConfig 的http.cors()里统一管理。6.3 Docker 容器应用的日志与运维应用容器化之后日志处理方式跟以前完全不同。容器里的应用不该把日志写到文件而是输出到 stdout/stderr由 Docker 统一收集。Spring Boot 默认日志就输出到控制台这点刚好符合容器习惯。排查问题时的标准命令# 查看应用实时日志 docker logs -f scaffold-app # 进入容器排查 docker exec -it scaffold-app bash # 查看容器资源占用 docker stats scaffold-app日志里发现容器频繁重启最常见的原因是 JVM 堆内存设置超过容器可用内存导致被 OOM Killed。这时候用docker inspect scaffold-app | grep -i memory看容器限额同时把 JVM 的-Xmx调低。更规范的做法是在启动命令里用-XX:MaxRAMPercentage75.0让 JVM 自动感知容器内存配额避免硬编码 512m 和容器实际大小不匹配。还有个日常运维小技巧docker-compose 里统一约定容器名称与网络别名这样应用里配置 Nacos 地址时可以直接写nacos:8848配置 MySQL 地址写mysql:3306不用在不同的部署环境里改配置。这套约定在开发、测试、生产里都能用配合 Nacos 配置文件中心的server-addr环境变量实现了“代码不变、配置随环境走”。结尾一些真实的体会脚手架搭完后再回看这半个多月踩的坑我心里最大的感受是真正耗时间的地方往往不在写业务代码而在基础设施的协作细节。比如 Nacos 开启鉴权之后服务注册密码不对导致的应用启动失败比如 Docker Desktop 虚拟化没打开白白排查了半天比如 Spring Security 白名单和自定义拦截器的叠加逻辑光看文档自己是想不到的。最后分享一个后续可以扩展的方向这套脚手架里虽然做的是单体但 Nacos、JWT、Docker 这些基础设施已经为拆分留好了口子。想更进一步可以把通用能力独立成模块比如抽出scaffold-common放 JWT 工具类、统一返回结构、异常处理再往上可以接 Spring Cloud Gateway 做统一入口把认证从单体应用里剥离出来变成网关层的公共逻辑。但是这些都是后话先把单体跑稳、把基础设施沉淀好比什么都重要。
返回列表