1. 从零搭后台管理系统,真正卡住人的不是代码
Spring Boot + React (Ant Design Pro) 这套组合,几乎是国内后台管理系统的默认答案。后端 Spring Boot 提供 REST 接口、Spring Security 做鉴权,前端 Ant Design Pro 提供开箱即用的中后台布局、权限菜单和请求层。听起来很顺,但真从零跑一遍,你会发现卡点根本不在写业务代码,而在这些地方:JWT 的 access token 和 refresh token 怎么配合、前端请求层怎么统一注入 token、401 之后怎么自动刷新而不是把用户踢回登录页、前后端分离时跨域和代理怎么配、以及最要命的——大模型接口的 Key 散落在各个配置文件里,换一个模型就要改一遍代码。
这篇就按「Spring Boot + React (Ant Design Pro) 开源脚手架」这条链路,把后台管理系统从零搭起来,重点交付三样能直接复制的东西:application.yml配置骨架、Ant Design Pro 的settings.json与请求层封装片段、以及登录鉴权和接口联调的验证动作。同时把统一 Key/API 通道接进来,让后端调用大模型时不用在每个业务里硬编码密钥。
适合谁看:已经会一点 Java 和 React、想快速起一个带 RBAC 权限的后台管理系统的人;或者手上有个开源脚手架,但前后端联调总是卡在鉴权和请求封装上的开发者。下面所有配置都以「能跑起来、能验证」为标准,不堆概念。
2. 前置准备:脚手架、环境与统一 Key 通道
2.1 脚手架选型与目录结构
开源项目 Spring Ant 这类脚手架的价值在于:它已经把后台管理系统的核心架子搭好了——目录结构、基于角色的访问控制、OpenAPI、统一日志和 Error 处理。前后端分别 Run 起来,一个后台管理系统的骨架就成型了。它的代码集中在 core 文件夹下,很轻量,这点对后续让 AI 在稳定底盘上写业务很关键。
后端核心目录大致是这样:
src/main/java/com/example/ ├── core/ # 框架核心:安全、异常、日志、分页 ├── modules/ # 业务模块 └── Application.java前端基于 Ant Design Pro,请求层、权限、布局都在src/下。你要做的是在这个底盘上接业务,而不是从零造框架——从零搭的框架很难保证质量和有没有安全问题。
2.2 环境版本对齐
版本不一致是联调翻车的高频原因,先把这张表对一遍:
| 组件 | 建议版本 | 说明 |
|---|---|---|
| JDK | 17 | Spring Boot 3.x 要求 |
| Spring Boot | 3.2.x | 与 Spring Security 6 配套 |
| Node.js | 18 LTS | Ant Design Pro 要求 |
| Ant Design Pro | 6.x | 基于 Umi 4 |
| MySQL | 8.0 | 或按脚手架默认 |
2.3 统一 Key/API 通道的接入位置
后台管理系统里,大模型能力通常出现在两个地方:一是运营侧的智能助手、内容生成;二是系统内的数据摘要、工单分类。如果每个业务模块都自己读 Key、自己拼请求地址,后面换模型、加限流、做审计就会非常痛苦。
正确做法是在后端 core 层做一个统一的模型调用客户端,所有业务只依赖这个客户端。TaoToken 提供的就是这样一个统一入口:官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。你只需要在配置里维护一个 base URL 和一个 Key,业务代码不感知具体模型供应商。
先去控制台创建 Key:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,拿到 Key 之后不要写进代码,走环境变量或配置中心。Key 的管理页面在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
3. 可复制配置:application.yml 与请求层封装
3.1 后端 application.yml 骨架
这份配置覆盖数据源、JWT、以及统一模型通道。JWT 部分把 access token 和 refresh token 的有效期分开,这是脚手架里基于 JWT 认证的标准做法。
server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/spring_ant?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: ${DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: false # JWT 配置:access token 短、refresh token 长 security: jwt: secret: ${JWT_SECRET} access-token-expire: 1800 # 30 分钟 refresh-token-expire: 604800 # 7 天 header: Authorization prefix: "Bearer " # 统一模型通道:业务代码只认这个 base-url taotoken: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} default-model: claude-sonnet-4-5 timeout: 60000注意api-key用的是${TAOTOKEN_API_KEY}占位符,实际值通过环境变量注入。这样即使配置文件进了 Git,密钥也不会泄露。default-model只是默认值,业务侧可以覆盖。
3.2 后端统一调用客户端
在 core 层写一个客户端,把 base URL、鉴权头、超时、错误处理都收口。业务模块注入它即可,不直接碰 HTTP。
@Component public class ModelClient { private final RestClient restClient; private final String defaultModel; public ModelClient(@Value("${taotoken.base-url}") String baseUrl, @Value("${taotoken.api-key}") String apiKey, @Value("${taotoken.default-model}") String defaultModel) { this.defaultModel = defaultModel; this.restClient = RestClient.builder() .baseUrl(baseUrl) .defaultHeader("Authorization", "Bearer " + apiKey) .defaultHeader("Content-Type", "application/json") .build(); } public String chat(String prompt) { Map<String, Object> body = Map.of( "model", defaultModel, "messages", List.of(Map.of("role", "user", "content", prompt)) ); return restClient.post() .uri("/v1/chat/completions") .body(body) .retrieve() .body(String.class); } }这段代码的关键点是:baseUrl和apiKey都来自配置,业务侧调用modelClient.chat(...)时完全不知道背后是哪家模型。以后要换模型,改default-model就行。
3.3 前端 settings.json 与请求层封装
Ant Design Pro 的config/settings.json控制布局和主题,先给一份能用的:
{ "navTheme": "light", "layout": "mix", "contentWidth": "Fluid", "fixedHeader": true, "fixSiderbar": true, "pwa": false, "title": "后台管理系统", "token": { "header": { "colorBgHeader": "#001529" } } }请求层是前后端分离联调的核心。Ant Design Pro 默认用 umi-request,我们要做三件事:请求自动带 token、响应统一处理 errorCode、401 时用 refresh token 换新 token 并重放请求。
// src/utils/request.js import { extend } from 'umi-request'; import { getToken, setToken, clearToken } from './auth'; const request = extend({ prefix: '/api', timeout: 15000, errorHandler: (error) => { const { response } = error; if (response && response.status === 401) { return refreshAndRetry(error); } throw error; }, }); request.interceptors.request.use((url, options) => { const token = getToken(); return { url, options: { ...options, headers: { ...options.headers, Authorization: token ? `Bearer ${token}` : '', }, }, }; }); let refreshing = null; async function refreshAndRetry(error) { if (!refreshing) { refreshing = fetch('/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken: getToken('refresh') }), }) .then((res) => res.json()) .then((data) => { setToken(data.accessToken); return data.accessToken; }) .finally(() => { refreshing = null; }); } const newToken = await refreshing; const { options } = error.request; return request(error.request.url, { ...options, headers: { ...options.headers, Authorization: `Bearer ${newToken}` }, }); } export default request;这里用了一个refreshing变量做并发控制:多个请求同时 401 时,只发一次刷新请求,其余等结果。这是踩过的坑——不做并发控制,刷新接口会被打爆,甚至因为 refresh token 被重复使用而失效。
4. 验证请求:登录鉴权与接口联调
4.1 启动与登录验证
后端mvn spring-boot:run,前端npm run dev。先验证登录接口:
curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}'成功返回里应该有accessToken和refreshToken两个字段。把 accessToken 拿去请求受保护接口:
curl http://localhost:8080/api/admin/page \ -H "Authorization: Bearer <accessToken>"用 admin 账号能拿到数据,换成普通 user 账号应该返回 403 或对应的 errorCode。这一步验证的就是基于角色的访问控制是否生效——左侧管理员能访问 admin page、admin sub-page、admin button,普通用户则无权限。
4.2 验证统一模型通道
后端加一个测试接口,调用ModelClient:
curl -X POST http://localhost:8080/api/ai/ping \ -H "Authorization: Bearer <accessToken>" \ -H "Content-Type: application/json" \ -d '{"prompt":"用一句话说明什么是后台管理系统"}'如果返回了模型输出,说明taotoken.base-url和api-key配置正确,统一通道打通。想单独验证模型对话,可以直接用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 试一下同一个 Key,确认 Key 本身可用。
4.3 验证 token 刷新链路
把 access token 有效期临时改成 60 秒,登录后等过期,再点一个需要鉴权的菜单。预期行为是:请求先 401,前端自动调 refresh 接口,拿到新 token 后重放原请求,用户无感知。如果被踢回登录页,说明刷新逻辑没生效,回去检查refreshAndRetry里的并发控制和 refresh token 是否正确传递。
5. 本篇常见错排查
跨域报错 CORS:前后端分离时,前端prefix: '/api'走的是 Umi 代理。检查config/proxy.ts是否把/api指向了http://localhost:8080。生产环境用 Nginx 反代,不要在后端无脑开@CrossOrigin("*")。
401 循环刷新:refresh 接口本身也返回 401 时,refreshAndRetry会再次触发刷新,形成死循环。要在刷新失败时直接clearToken()并跳登录页,别让它继续重试。
JWT 签名不匹配:security.jwt.secret长度不够或前后端不一致都会导致签名校验失败。HS256 要求密钥至少 256 位,用一段足够长的随机字符串,别用123456。
模型接口超时:taotoken.timeout默认 60 秒,长文本生成可能不够。但也不要无脑调大,配合前端 loading 状态管理,避免请求进行中用户继续操作。局部 loading 和全局遮罩要区分开。
errorCode 和 HTTP 状态码混淆:脚手架做了基于 errorCode 和 showType 的统一异常处理,同时对网络错误和 HTTP 层级错误也做了统一处理。前端拦截器里判断 401 要看 HTTP 状态码,业务错误看响应体里的 errorCode,两者别混。
分页参数对不上:Ant Design Pro 的 ProTable 默认传current和pageSize,后端如果用的是page和size,要在请求层做一次映射,否则永远返回第一页。
6. 后续怎么接:从脚手架到业务落地
底盘跑通之后,真正的工作是往modules/里加业务。这时候 AI Coding 的价值就体现出来了:前后端都有 AGENTS.md,它让 AI 知道框架核心在 core 文件夹下、以及前后端怎么对接。你可以直接和 AI 说「我有 xx 业务,有 xx 和 xx 两个角色,分别有什么权限和功能」,AI 能理解已设计好的 RBAC 权限架构,创建业务账号、业务表结构和代码。
但前提是底盘可靠。让 AI 从零搭框架,很难保证质量和有没有安全问题;让 AI 在 Spring Ant 这种核心架构上开发,底盘已经可靠可控,AI 只专注业务实现。这也是为什么统一 Key 通道要放在 core 层——业务代码越干净,AI 生成的东西越不容易跑偏。
如果你要长期在这个项目上做编码和 Agent 集成,可以看下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合持续性的开发场景。Claude Code 相关的接入方式在 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 有说明。接入文档统一在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到鉴权或请求格式问题先翻这里。
最后留一个实操建议:把application.yml里的access-token-expire先设成 120 秒,完整走一遍「登录 → 访问 → 过期 → 自动刷新 → 重放」的链路,确认无误后再改回 30 分钟。这个动作能帮你提前发现 90% 的鉴权联调问题,比事后在线上排查省事得多。