资讯动态

SpringBoot3+Vben5.0企业级管理后台升级实践与踩坑复盘

发布时间:2026/9/29 18:29:06 来源:尧图企业网站定制
上周刚把公司一个内部管理后台从 SpringBoot2 Vue2 整套迁移到 SpringBoot3 Vben5.0 组合这次不是局部升级是推倒重来的那种。整个过程比预想的要麻烦——Spring Security 6 的配置风格跟 5.x 完全不是一回事Vben5 的工程结构也跟老 Vben2 差了一大截中间翻车多次。但搭完之后回头看这套组合确实是目前企业级管理后台最值得投入的方向。这篇文章是这次实践的完整复盘从骨架初始化、认证授权改造、权限模型、前端对接到联调部署每个环节都会过一遍。代码基于实际工程简化的核心片段可以直接抄但更重要的是把背后的选择逻辑和踩过的坑讲清楚。1. 势在必行的技术栈升级这套组合解决了什么问题1.1 SpringBoot3 不是“升级一个版本”那么简单很多团队现在还停在 SpringBoot2.x JDK8 的舒适区里觉得系统能跑就不动。但 SpringBoot 2.7 的 OSS 支持在 2023 年 11 月已经停止后续只有商业付费的扩展支持。这意味着安全漏洞修复、依赖版本更新都会越来越滞后企业项目里这是实打实的合规风险。SpringBoot3 强制要求 JDK17 起步JDK17 是 LTS 版本带来了文本块、switch 表达式、密封类这些语法糖更重要的是对虚拟线程JDK21 正式落地但 JDK17 也能感受到底层 GC 和性能优化。对管理后台这种 IO 密集型的应用来说吞吐量提升是能感知到的。但真正让老手翻车的是两个底层变化一个是javax.*命名空间全部迁移到了jakarta.*另一个是 Spring Security 6 把配置方式彻底改了。任何从 SpringBoot2 老项目复制过来的代码第一波编译报错基本都是这些。这不是换个版本号那么简单是牵一发动全身的架构调整。1.2 Vben5.0 相比老 Vben Admin 的本质变化Vben Admin 2.x 我用了很久熟悉的同学都知道它的价值在于开箱即用的布局、鉴权、动态菜单和封装好的请求层。但 2.x 有一个问题——整个工程像是把所有功能揉在一起升级依赖非常痛苦你想换个 UI 组件库代价极大。Vben5.0 把这个思路完全重构了改成 monorepo 结构核心逻辑全部抽到packages/目录下UI 层拆成apps/web-antd、apps/web-ele、apps/web-naive等独立应用。你选哪套 UI 就启动哪个应用业务代码和框架代码解耦后续升级 Vben 版本只是更新 packages 依赖不用动业务。这个设计思想我认为是企业级项目选型时最关键的一点——它解决的是长期可维护性问题。Vben5 内置的权限模块、多语言、主题切换、动态路由、标签页缓存都已经模块化和业务代码隔离。接后端的时候你只需要关心请求层和权限 store 怎么对接剩下的框架层基本不用动。这也是为什么我这次敢把老后台翻新成这套组合——前端工作量能省一半以上。1.3 企业级管理后台的核心需求到底有哪些抛开具体业务管理后台的底层需求其实是稳定的用户认证登录、登出、token 管理、会话过期处理权限控制接口级别的鉴权、按钮级别的权限控制、动态菜单可维护性代码结构清晰、依赖升级路径平滑、团队上手成本可控稳定性统一异常处理、统一响应结构、日志链路完整下面所有章节都是围绕这四件事展开的。先把需求框架定下来再谈技术选型就不会被花哨的框架带偏。2. 后端骨架搭建SpringBoot3 工程初始化与依赖陷阱2.1 工程初始化与 Maven 关键依赖我用的是 SpringBoot 3.2.4 JDK17Maven 工程直接通过spring-boot-starter-parent管理版本。核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-oauth2-resource-server/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.12.5/version /dependency这里有个细节要注意MyBatis-Plus 从 3.5.x 开始针对 SpringBoot3 单独出了 starter包名是mybatis-plus-spring-boot3-starter千万别用老的mybatis-plus-boot-starter否则启动时会直接初始化失败报一堆 SqlSessionFactory 相关的错。数据库驱动也用上了新的com.mysql:mysql-connector-j这个坐标替代了老旧的mysql-connector-java官方在 MySQL Connector/J 8.0.31 之后做了改名。如果你从老项目复制 pom很容易在这里卡一下。2.2 配置文件里的关键项application.yml里除了常规的数据源配置有几个点值得单独说明spring: datasource: url: jdbc:mysql://localhost:3306/admin_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password data: redis: host: localhost port: 6379 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0serverTimezone不设的话5.x 时代的老驱动会报时间错乱新驱动虽然默认处理了但建议还是显式声明Asia/Shanghai避免部署到不同时区的服务器时出现时间误差。Jackson 的全局日期格式务必配好。管理后台里LocalDateTime太常见了如果不在全局层面统一格式前端拿到的就是2024-04-01T12:00:00这种 ISO 格式展示层还要再处理一遍纯属给自己找事。2.3 Jakarta 命名空间迁移老代码复制的第一波报错我这次踩的坑非常典型从老后台复制一个WebMvcConfigurer接口的实现类过来结果javax.servlet.http.HttpServletRequest直接编译不过。SpringBoot3 全面切换到 Jakarta EE 9javax.servlet必须改成jakarta.servlet包括HttpServletRequest、HttpServletResponse、Filter这些。这不是改一个 import 就行的事——你项目里凡是依赖了老 javax 的第三方库都要排查。比如某些老版本的knife4j、pagehelper如果没有适配 Jakarta 就会在运行时直接 ClassNotFound。我最后统一用的knife4j-openapi3-jakarta-spring-boot-starterOpenAPI3 规范文档访问地址是/doc.html这一点在后面的 Security 放行配置里还会提到。3. Security6 OAuth2 认证授权改造老经验基本要作废3.1 从 WebSecurityConfigurerAdapter 到 SecurityFilterChain 的思维转换如果你以前写 Spring Security大概率写过这样的代码extends WebSecurityConfigurerAdapter重写configure(HttpSecurity http)方法。Spring Security 6 里这个父类已经被彻底删掉了官方推荐方式是通过SecurityFilterChainBean 来声明配置。我刚开始也不习惯但适应之后发现新方式其实更灵活——原来的配置全部堆在一个继承类里现在可以拆成多个SecurityFilterChainBean按模块拆分安全规则比如/auth/**走一套放行逻辑/open/**完全匿名/api/**走 JWT 校验。这在多模块大型项目里优势很明显。3.2 两种 JWT 方案的取舍围绕 SpringBoot3 Security6 OAuth2 这块社区里有两种主流的 JWT 对接方案我在选型时做了对比方案实现方式适用场景维护成本自定义 JWT 过滤器自己写 OncePerRequestFilter解析 token手动注入 SecurityContext单体管理后台、团队对 Security 源码熟悉较低逻辑完全可控OAuth2 Resource Server引入spring-boot-starter-oauth2-resource-server配置 JwtDecoder 和 JwtAuthenticationConverter多服务架构、后续要接独立授权中心的场景中高框架自动化程度高单体后台我最终选择了资源服务器方案 jjwt负责签发 token。签发用 jjwt 是因为它 API 简单解析交给框架的 JwtDecoder 是因为它天然集成进 Security 的过滤链不需要手动做SecurityContextHolder的赋值。如果你的团队对 Security 处理机制不熟自定义过滤器更直观但要注意处理 token 过期、签名异常等边界情况稍不留神就会漏掉。3.3 Security 配置文件完整示例这是整个后端最核心的一段代码配置的是无状态会话 JWT 校验 统一认证异常响应Configuration EnableWebSecurity EnableMethodSecurity public class SecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(Customizer.withDefaults()) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/auth/login, /auth/captcha, /error).permitAll() .requestMatchers(/doc.html, /webjars/**, /v3/api-docs/**).permitAll() .anyRequest().authenticated()) .exceptionHandling(ex - ex .authenticationEntryPoint((request, response, authException) - { response.setContentType(application/json;charsetUTF-8); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); }) .accessDeniedHandler((request, response, accessDeniedException) - { response.setContentType(application/json;charsetUTF-8); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write({\code\:403,\message\:\无权限访问\}); })) .oauth2ResourceServer(oauth2 - oauth2 .jwt(jwt - jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))); return http.build(); } Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter converter new JwtGrantedAuthoritiesConverter(); converter.setAuthoritiesClaimName(authorities); converter.setAuthorityPrefix(); JwtAuthenticationConverter jwtAuthenticationConverter new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(converter); return jwtAuthenticationConverter; } }这段配置里有几个值得展开说明的点第一csrf必须关闭。前后端分离架构天然免疫 CSRF 攻击因为 token 不在 Cookie 里攻击者没法伪造请求头开着反而会让 POST 接口全部 403。第二sessionCreationPolicy(STATELESS)是前后端分离的标配。服务端不创建 session认证状态全部由前端携带的 JWT 决定。我们曾经漏配这行发现登录后用户信息被存在了 Redis session 里前端不带 Cookie 就 404排查了很久才发现是老配置没改干净。第三自定义authenticationEntryPoint非常关键。默认情况下未认证请求返回的是空 body 或 HTML 错误页前端没法统一处理。我们把它改成 JSON 结构前端 axios 拦截器直接判断 HTTP 401 状态码和响应体里的code字段逻辑就很干净。关于dispatcherTypeMatchers或者那些 Filter 放行细节单体后台项目里基本用不到不展开。3.4 登录接口与 JWT 签发过程登录接口本身不复杂但要注意密码加密不能再用明文。我用的 BCryptPasswordEncoder这也是 Spring Security 默认推荐的编码器RestController RequestMapping(/auth) public class AuthController { private final AuthenticationManager authenticationManager; private final JwtTokenProvider jwtTokenProvider; private final SysUserService userService; PostMapping(/login) public ResultLoginResponse login(RequestBody Valid LoginRequest request) { Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); String token jwtTokenProvider.createToken(authentication); // 查询当前用户的权限标识列表比如 sys:user:add、sys:user:edit ListString permissions userService.getPermissionsByUsername(request.getUsername()); return Result.success(LoginResponse.of(token, permissions)); } }AuthenticationManager需要手动暴露成 Bean否则注入不进来Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); }JWT 签发工具类用 jjwt 0.12.x 的新 API跟老版本差异很大Component public class JwtTokenProvider { private final SecretKey key; private final long expiration 86400000L; // 24小时 public JwtTokenProvider(Value(${app.jwt.secret}) String secret) { this.key Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); } public String createToken(Authentication authentication) { Date now new Date(); return Jwts.builder() .subject(authentication.getName()) .claim(authorities, authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority).toList()) .issuedAt(now) .expiration(new Date(now.getTime() expiration)) .signWith(key) .compact(); } public String getUsername(String token) { return Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(token) .getPayload() .getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().verifyWith(key).build().parseSignedClaims(token); return true; } catch (JwtException | IllegalArgumentException e) { return false; } } }这里必须强调一个特别容易踩的坑HS256 签名算法的密钥长度至少需要 32 字节256 位。开发时我用了一个 10 个字符的字符串当 secret启动后一登录就抛WeakKeyException当时还以为是依赖版本问题排查半天才发现是密钥强度不够。生产环境请用 32 字节以上的随机字符串最好通过环境变量注入别写死在配置文件里提交到 Git。4. RBAC 权限模型与后端接口鉴权表结构、注解与动态菜单4.1 数据库表设计五张基础表企业级后台的权限模型绝大多数场景用 RBAC基于角色的访问控制就够了。我设计了五张表用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。核心结构如下CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50) DEFAULT , status TINYINT DEFAULT 1 COMMENT 1正常 0禁用, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(50) NOT NULL UNIQUE, role_name VARCHAR(50) NOT NULL ); CREATE TABLE sys_menu ( id BIGINT PRIMARY KEY AUTO_INCREMENT, parent_id BIGINT DEFAULT 0, menu_name VARCHAR(50) NOT NULL, menu_type TINYINT COMMENT 1目录 2菜单 3按钮, path VARCHAR(200) COMMENT 前端路由路径, component VARCHAR(200) COMMENT 前端组件路径, perm VARCHAR(100) COMMENT 权限标识如 sys:user:add, sort_order INT DEFAULT 0, visible TINYINT DEFAULT 1 ); CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ); CREATE TABLE sys_role_menu ( role_id BIGINT NOT NULL, menu_id BIGINT NOT NULL, PRIMARY KEY (role_id, menu_id) );这里有个设计上的关键点菜单表里既有前端路由path、component又有后端权限标识perm用一种数据结构同时支撑两套逻辑。目录和菜单节点用于生成前端的动态路由和侧边栏按钮节点menu_type3不参与路由生成只用于权限标识的聚合。4.2 方法级鉴权PreAuthorize 的最佳实践在启动类或配置类上加上EnableMethodSecurity后就可以在 Controller 方法上用注解做细粒度接口鉴权RestController RequestMapping(/users) public class UserController { GetMapping PreAuthorize(hasAuthority(sys:user:list)) public ResultPageResultSysUserVO page(PageQuery query) { return Result.success(userService.page(query)); } PostMapping PreAuthorize(hasAuthority(sys:user:add)) public ResultVoid add(RequestBody Valid SysUserDTO dto) { userService.add(dto); return Result.success(); } PutMapping(/{id}) PreAuthorize(hasAuthority(sys:user:edit)) public ResultVoid edit(PathVariable Long id, RequestBody Valid SysUserDTO dto) { userService.edit(id, dto); return Result.success(); } }hasAuthority判断的就是 JWT 里authoritiesclaim 中的权限标识和菜单表里的perm字段一一对应。但有一点要提醒接口鉴权不能只依赖前端隐藏按钮。Vben5 的按钮权限只是 UI 层面的控制如果用户绕过前端直接调接口后端不校验就会出大问题。所以后端每个写操作接口都要求配上PreAuthorize这是安全底线。4.3 动态菜单返回结构登录成功或刷新用户信息时后端需要返回当前用户可见的菜单树。基本逻辑是根据用户 ID 关联角色再关联菜单筛出menu_type为目录和菜单的节点按父子关系组装成树public ListMenuNodeVO getMenusByUsername(String username) { // 1. 根据用户名查角色 // 2. 根据角色查菜单 // 3. 按 parentId 组装树形结构 // 返回包含 path、component、name、children 的 MenuNodeVO }前端拿到这棵树后动态生成路由而不是在路由配置里写死。这样不同角色登录后看到的侧边栏就是不一样的天然实现菜单级权限控制。5. 前端 Vben5.0 工程初始化与后端对接的关键调整5.1 拉取工程与本地启动Vben5 官方推荐用 pnpm 操作第一步拉取模板pnpm create vbenlatest按提示选择你想要的 UI 组件库我选的 Ant Design Vue。安装依赖cd vben-project pnpm install pnpm dev启动后访问本地地址看到 Vben 的默认工作台页面说明基础环境没问题。Vben5 是 monorepo 结构业务代码主要在apps/web-antd/src下面后续改的路径基本都在这里。5.2 环境变量与请求层对接前端工程根目录下有.env文件核心要改的是 API 网关地址VITE_GLOB_API_URL/api我们采用的是 Nginx 反向代理方式前端统一请求/api前缀由 Nginx 转发到后端8080端口。这样即解决跨域又方便后续在后端前面加网关。Vben5 内置了vben/request模块请求层的配置在src/api/request.ts里。核心要接两个拦截器请求拦截器里把后端返回的 token 加到 Authorization 头响应拦截器里统一处理 HTTP 401 和业务错误码。const request createRequest({ baseURL: import.meta.env.VITE_GLOB_API_URL, headers: { Content-Type: application/json }, }); // 请求拦截注入 token request.instance.interceptors.request.use((config) { const token useAccessStore.getState().accessToken; if (token) { config.headers.Authorization Bearer ${token}; } return config; }); // 响应拦截统一处理 401 request.instance.interceptors.response.use( (response) response, (error) { if (error.response?.status 401) { useAccessStore.getState().setAccessToken(); router.push(/auth/login); } return Promise.reject(error); } );这里有一个 Vben5 使用上的细节它内部的 store 状态管理用的是 zustanduseAccessStore是全局单例token 的存取走setAccessToken和getAccessToken千万不要自己再引入 Vuex 或 Pinia 去存一份否则刷新页面后 token 状态对不上登录态就断了。5.3 登录流程与权限集填充登录页调后端POST /auth/login成功后在useAccessStore里种 token、种用户信息。后端在登录接口里返回了权限标识列表Vben5 的权限模块会自动读取并生成可用路由的访问控制async function handleLogin(values: LoginParams) { const { token, permissions } await loginApi(values); const accessStore useAccessStore.getState(); accessStore.setAccessToken(token); accessStore.setAccessCodes(permissions); // 跳转首页 router.push(/); }菜单和路由的生成Vben5 有两种做法一种是前端静态路由配置 后端返回菜单做过滤另一种完全后端返回动态路由。我这次用的是前者——前端把路由表全部声明好后端只返回用户可见的菜单 path前端根据 path 做过滤。这样路由组件是静态 import 的打包时不会把建好的 chunk 拆散首屏加载更容易控制。6. 前后端联调踩坑实录从 401 到跨域再到尾部斜杠6.1 坑一Security 默认未认证响应不是 JSON第一次联调前端调/users接口拿到一个空白的 403 页面浏览器控制台只有Failed to load resource: the server responded with a status of 403后端日志什么输出都没有。原因就是 Spring Security 默认的Http403ForbiddenEntryPoint返回的是纯状态码没有响应体。这个问题的解决方式已经在第 3.3 节配置过了——通过exceptionHandling自定义authenticationEntryPoint和accessDeniedHandler让未认证和未授权的情况都返回统一 JSON。这个步骤不要拖到联调阶段才做后端骨架搭建的时候就应该配好否则前后端联调会被这种低级问题浪费大半天。6.2 坑二CORS 配置和 Security 的叠加效应后端配了跨域前端也配了代理结果跨域还是报错。排查下来发现是 Spring Security 的过滤链优先于 Spring MVC 的 CORS 处理Security 没允许跨域的话请求根本到不了 Controller 层。在 Security 配置里直接链式声明.cors(Customizer.withDefaults())还不够必须提供一个CorsConfigurationSourceBeanBean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config new CorsConfiguration(); config.setAllowedOriginPatterns(List.of(*)); config.setAllowedMethods(List.of(GET, POST, PUT, DELETE, OPTIONS)); config.setAllowedHeaders(List.of(*)); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return source; }开发环境我们最终是靠 Nginx 代理解决的跨域配置更多是留给测试环境直连后端时用。如果你也走代理这个 Bean 可以保留生产环境不影响性能。6.3 坑三放行路径匹配规则我们把/auth/login配了permitAll()但测试时发现带 Swagger 的路径还是 401。检查后发现knife4j的静态资源不止/doc.html一个还有/webjars/**、/v3/api-docs/**、/swagger-ui/**等一堆路径Security 默认全部拦截。放行规则要把这些资源路径都配上.requestMatchers(/auth/login, /auth/captcha, /error).permitAll() .requestMatchers( /doc.html, /webjars/**, /v3/api-docs/**, /swagger-ui/**, /favicon.ico ).permitAll()另外注意 Spring Security 6 里路径匹配默认使用PathPattern不是 Spring 5 时代的AntPathMatcher。对于大多数需求两者差异不大但PathPattern对尾部斜杠的处理更严格——请求/api/users/和/api/users会被视为不同路径如果你的网关或代理层做了路径改写很容易出现 404。我之前用 Nginx 的proxy_pass http://127.0.0.1:8080/api/时尾部带不带斜杠行为完全不同花了一个下午才定位到这个问题。6.4 坑四LocalDateTime 序列化格式不一致前端表格里显示2024-04-01T12:00:00这种格式客户看着别扭测试直接提了 bug。后端全局配置了 Jackson 的date-format但只对java.util.Date生效LocalDateTime需要额外处理。最省事的做法是在application.yml里加spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8然后给实体字段加JsonFormat兜底JsonFormat(pattern yyyy-MM-dd HH:mm:ss) private LocalDateTime createdAt;或者全局配置Jackson2ObjectMapperBuilderCustomizer我倾向后者因为不用每个字段都加注解统一行为不容易漏。7. 打包部署与生产稳定性细节7.1 前端构建与 Nginx 配置Vben5 前端构建pnpm build产物在apps/web-antd/dist目录。把整个目录拷贝到服务器Nginx 配置如下server { listen 80; server_name admin.example.com; root /var/www/admin; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }try_files那行必须要有否则前端路由在页面刷新时比如直接访问/system/user会 404。这是所有 history 模式单页应用的通用问题Vben5 也不例外。7.2 后端 JVM 参数与日志预留后端打包mvn clean package -DskipTests启动命令java -Xms512m -Xmx1024m -jar admin-server.jar --spring.profiles.activeprod管理后台这类应用堆内存不要一上来就给太大512 到 1024 足够。关键是-Xms和-Xmx设置为相同值避免运行期堆扩容导致性能抖动。日志方面生产环境别只在控制台输出配置logback-spring.xml按天滚动至少保留 30 天。我习惯把日志目录放在/var/log/admin/单独挂盘避免日志撑爆系统盘。这里面有个小技巧把 Spring Security 的认证失败日志单独输出到一个文件以后排查撞库攻击或者暴力破解会方便很多。7.3 生产环境最容易忽略的几个配置第一生产环境打开 MyBatis-Plus 的 SQL 日志会影响性能spring.profiles.activeprod时要把log-impl置空。第二JWT 的 secret 不能是开发默认值必须通过环境变量注入。第三注意 Redis 的序列化方式默认的 JDK 序列化在分布式环境下拿到 key 会是一长串看不懂的二进制字符串建议统一改成 Jackson 序列化。最后再多说一句企业级后台的“企业级”三个字不是靠框架和酷炫功能撑起来的而是靠这些边界细节堆出来的——统一的响应结构、清晰的异常链路、完善的权限校验、优雅的部署方案。这套 SpringBoot3 Vben5.0 组合在这几个方面能帮你省下大量自研成本值得投入时间去掌握。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑