)
1. 为什么我要用 Spec-Driven 方式搭这个 JavaAI 零花钱项目骨架先说清楚这个项目是什么一个给鸿蒙 APP 配套的零花钱智能管理后端服务主打家庭成员零花钱看板、收支记录、语音交互记账、价值零花钱统计这些能力。适合谁看适合已经会写 Spring Boot、但还没把 AI 编码工具真正用进日常开发流程的 Java 开发者。你能从这篇里拿到什么一套可复制的工程骨架、一份能直接跑的 ccSwitch 配置、一段 SpringAI 接入 Qwen 的验证代码以及一次从规格文档到可运行接口的完整动作。我这次不打算用「让 AI 随便写点代码」的方式开局。原因很直接AI 编码最大的问题不是写不出来而是写得太随意。同一个需求你今天让它写一版明天让它改一版两次的包结构、命名风格、异常处理可能完全不一样。项目稍微大一点代码就开始互相打架。Spec-Driven 的核心逻辑就一句话AI 是执行的肌肉规格是项目的大脑。所有代码生成、迭代、修复、校验都必须严格遵循提前定义好的规范文档。你先把「要做什么、用什么技术、按什么风格写」定死再让 AI 去填代码它就不会跑偏。具体到操作层面我先把四份核心文档产出来code-style-guide.md编码规范、mission.md项目使命与功能约束、tech-stack.md技术约束、roadmap.md迭代路线。这四份文档是后续所有 AI 编码的唯一标准后面每一轮生成代码我都会让 ClaudeCode 先读它们再动手。工程骨架这一阶段我定为 M0目标是搭出可构建、可迭代、可校验的标准化结构统一全项目的代码结构、依赖版本、校验规则和构建流程。M0 跑通之后M1 的认证与家庭域、M2 的零花钱核心业务都可以直接往上叠不用回头改地基。技术选型上我锁定了这套组合JDK 25、Spring Boot 4.1.0、SpringAI 2.0.0、PostgreSQL 18、MyBatis、Maven 3.9.16部署方式是 Docker 镜像打包上云服务器。AI 编码通道用 ClaudeCode 配合 ccSwitch 管理多模型主力模型走 Qwen 系列。这套组合的好处是版本新、AI 原生适配好SpringAI 直接提供了模型调用的抽象层不用自己封装 HTTP 客户端。下面我按「先定规范、再搭骨架、最后验证」的顺序把每一步的可复制配置都摊开讲。你跟着做能拿到一个能跑起来的后端工程以及一套能持续用的 AI 编码工作流。2. TaoToken 前置准备给 ClaudeCode 配一条稳定的模型通道在动手写代码之前得先把 AI 编码工具的模型通道打通。ClaudeCode 本身是个命令行 Agent它需要一个能调用的模型后端。我这边用 ccSwitch 来管理多模型通道好处是切换模型不用改环境变量改一个配置文件就行。先说清楚 TaoToken 在这里的角色它是一个模型 API 聚合服务提供统一的调用入口你拿到一个 API Key 之后就能通过它调用包括 Qwen 在内的多种模型。对于 ClaudeCode 这种需要频繁调用的场景统一入口比每个模型单独配一套 Key 要省事得多。你需要准备的东西第一一个可用的 API Key。到 TaoToken 控制台的 API Keys 页面创建一个创建时给它起个能认出来的名字比如claudecode-dev方便后面区分用途。创建完把 Key 复制出来注意这个 Key 只显示一次丢了就得重建。第二确认你要用的模型 ID。Qwen 系列在 TaoToken 上的模型标识需要和你在 ccSwitch 里填的保持一致常见的是qwen-max、qwen-plus这类。具体以你控制台里模型列表显示的为准别凭记忆填。第三ClaudeCode 的安装。如果你还没装用 npm 全局装一下就行npm install -g anthropic-ai/claude-code装完之后先别急着跑因为默认它指向的是官方通道我们要把它改到自己的通道上。这一步靠环境变量和 ccSwitch 配合完成。关于 Base URL这里要特别注意ClaudeCode 走的是 Anthropic 兼容协议所以 Base URL 要填 TaoToken 的 API 地址https://taotoken.net/api不要带任何多余路径。Key 就填你刚才创建的那个。Model ID 填你在控制台确认过的 Qwen 模型标识。我建议你先在终端里用 curl 验证一下 Key 能不能通再往 ClaudeCode 里配。验证命令curl https://taotoken.net/api/v1/messages \ -H x-api-key: 你的Key \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: qwen-max, max_tokens: 64, messages: [{role: user, content: 回复 ok}] }如果返回里能看到模型输出说明通道是通的。如果返回 401那就是 Key 有问题如果返回模型不存在的错误那就是 Model ID 填错了。这两个错误后面排障章节会细讲。通道打通之后ClaudeCode 就能正常发起请求了。这时候你再回到 IDEA 里把工程建起来让 ClaudeCode 在工程目录下工作它就能读到你的规格文档按规范生成代码。有一点要提醒模型调用是有成本的尤其是让 AI 一次性生成大段代码的时候token 消耗会比你想象得快。我的做法是设计探讨阶段用便宜的对话模型等方案定稿了再用编码能力强的模型去生成代码减少来回试错的次数。这个习惯能帮你省下不少费用。3. 可复制配置ccSwitch 通道与 SpringAI 接入参数这一节是全文最干的部分我把 ccSwitch 的配置片段、Maven 依赖坐标、SpringAI 的接入参数全部摊开你直接复制改改就能用。3.1 ccSwitch 配置文件ccSwitch 的配置一般放在用户目录下的配置文件中我用的是 JSON 格式。下面这份是我实际在用的结构你可以照着改{ providers: [ { name: taotoken-qwen, type: anthropic, baseUrl: https://taotoken.net/api, apiKey: sk-你的Key, models: [ { id: qwen-max, name: Qwen Max, maxTokens: 8192 }, { id: qwen-plus, name: Qwen Plus, maxTokens: 8192 } ], active: true } ], current: taotoken-qwen }几个关键点type填anthropic因为 ClaudeCode 走的是 Anthropic 协议baseUrl就是https://taotoken.net/api不要加/v1之类的后缀ccSwitch 会自己拼apiKey填你创建的那个 Keymodels数组里可以放多个模型切换的时候改current字段就行。改完配置后重启一下 ClaudeCode 会话让它重新读取配置。你可以在 ClaudeCode 里发一句「你现在用的是哪个模型」来确认切换是否生效。3.2 Maven 依赖坐标工程用 Maven 管理依赖pom.xml里核心的坐标如下。Spring Boot 用 4.1.0SpringAI 用 2.0.0数据库驱动用 PostgreSQLparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version4.1.0/version relativePath/ /parent properties java.version25/java.version spring-ai.version2.0.0/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version${spring-ai.version}/version /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.4/version /dependency dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId scoperuntime/scope /dependency dependency groupIdorg.apache.commons/groupId artifactIdcommons-lang3/artifactId /dependency /dependencies注意 SpringAI 2.0.0 的 starter 命名和 1.x 有区别spring-ai-starter-model-openai是新的命名方式。如果你用的是别的模型协议starter 名字会不一样但结构是一样的。3.3 SpringAI 接入 Qwen 的 application.ymlSpringAI 通过 OpenAI 兼容协议接入 Qwen配置写在application.yml里spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: qwen-max temperature: 0.7 max-tokens: 2048 datasource: url: jdbc:postgresql://localhost:5432/pocketmoney username: ${DB_USER} password: ${DB_PASSWORD} driver-class-name: org.postgresql.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.pocketmoney.domain这里base-url填https://taotoken.net/apiSpringAI 会自动在末尾拼上/v1/chat/completions这类路径。api-key我用环境变量注入避免把 Key 硬编码进代码仓库。model填qwen-max和 ccSwitch 里保持一致。3.4 目录结构工程骨架的目录结构我按分层的方式组织方便后续按模块叠加pocketmoney-backend/ ├── pom.xml ├── docs/ │ ├── mission.md │ ├── tech-stack.md │ ├── code-style-guide.md │ └── roadmap.md ├── src/main/java/com/pocketmoney/ │ ├── PocketMoneyApplication.java │ ├── config/ │ │ └── AiConfig.java │ ├── controller/ │ │ └── HealthController.java │ ├── service/ │ │ └── AiChatService.java │ └── domain/ ├── src/main/resources/ │ ├── application.yml │ └── mapper/ └── src/test/java/com/pocketmoney/docs目录放四份规格文档这是 Spec-Driven 的根基AI 每次生成代码前都要读。config放 SpringAI 的配置类controller放接口service放业务逻辑domain放实体。这个结构不复杂但足够清晰后面加模块直接往对应目录里塞就行。配置都就位之后下一步就是让 ClaudeCode 按规格生成代码然后验证接口能不能跑通。4. 验证请求从规格文档到可运行接口配置写完不算完得验证整条链路是通的。这一节我演示一次完整的动作让 ClaudeCode 读规格文档生成一个 AI 对话接口然后实际发请求确认返回正常。4.1 先让 ClaudeCode 读规格在 IDEA 的终端里进入工程目录启动 ClaudeCode 会话。第一句话我通常这么说请先阅读 docs 目录下的 mission.md、tech-stack.md、code-style-guide.md 然后告诉我这个项目的技术约束和编码规范要点。这一步的目的是让 Agent 把规格加载进上下文。它会返回一份摘要你核对一下有没有理解偏差。如果它把技术栈说错了说明文档写得不够明确回去补文档别急着让它写代码。4.2 生成 AI 对话接口确认理解无误后发第二条指令基于 docs 下的规格文档实现一个 AI 对话接口 路径 POST /api/ai/chat接收 JSON 参数 {message: 用户输入} 调用 SpringAI 的 ChatClient 返回模型回复返回结构 {reply: 模型输出}。 按 code-style-guide.md 的规范写包含 controller、service 和配置类。ClaudeCode 会开始生成代码。它一般会先创建AiConfig配置类注入ChatClient.Builder然后写AiChatService最后写AiChatController。生成过程中它可能会编译校验如果报错会自己修。生成完之后你检查一下几个点包名对不对、有没有按规范加注释、异常处理有没有做。有问题就直接在会话里说「controller 里缺少参数校验补上」它会改。4.3 启动服务并验证代码就位后启动 Spring Boot 应用mvn spring-boot:run看到Started PocketMoneyApplication就说明起来了。然后用 curl 发一个请求curl -X POST http://localhost:8080/api/ai/chat \ -H Content-Type: application/json \ -d {message: 帮我记一笔零花钱收入 50 元}如果返回类似下面的结构说明整条链路通了{ reply: 已记录零花钱收入 50 元。当前余额已更新。 }这里模型返回的内容是它自己组织的不一定和上面完全一样但结构对就行。如果返回 500去看日志大概率是 API Key 没注入或者模型 ID 不对。4.4 验证通过后提交代码接口跑通之后第一件事是提交代码。在 IDEA 里把工程初始化成 Git 仓库配置好远程地址然后提交git init git add . git commit -m M0: 工程骨架与 AI 对话接口 git remote add origin 你的仓库地址 git push -u origin main提交之前确认.gitignore里排除了target/和本地配置文件别把编译产物和密钥推上去。这一步做完M0 的骨架就算落地了后面 M1 的认证模块可以直接在这个基础上加。5. 本篇常见错误排查配置和验证过程中最容易撞上几个固定错误。我把它们列出来你对着日志查就行。5.1 401 错误Key 无效或没传对报错长这样{error:{type:authentication_error,message:invalid x-api-key}}原因通常是三个Key 复制的时候带了空格、Key 已经失效、或者请求头字段名写错了。ClaudeCode 走 Anthropic 协议时用的是x-api-key头SpringAI 走 OpenAI 协议时用的是Authorization: Bearer头两者不一样。先确认你当前是在哪条链路上报的错再检查对应的头。如果是 SpringAI 报 401检查application.yml里的api-key有没有正确读到环境变量。可以在启动日志里搜一下配置加载情况或者临时把 Key 直接写进去测试确认是环境变量的问题还是 Key 本身的问题。5.2 local proxy failed本地代理配置冲突报错长这样Error: connect ECONNREFUSED 127.0.0.1:7890 local proxy failed这是 ClaudeCode 或 npm 读到了系统里的代理配置但代理服务没开。检查环境变量HTTP_PROXY、HTTPS_PROXY有没有被设置如果有就清掉unset HTTP_PROXY unset HTTPS_PROXYWindows 上用set HTTP_PROXY清空。清完之后重启终端再试。这个错误和网络环境有关确保你的终端能直连到 API 地址就行。5.3 reading choices响应结构解析失败报错长这样java.lang.NullPointerException: Cannot invoke java.util.List.get(int) because choices is null这是 SpringAI 解析模型响应时没拿到预期的choices字段。常见原因是 Base URL 配错了请求打到了错误的路径上返回的根本不是模型响应。检查base-url是不是https://taotoken.net/api有没有多加或少加路径段。另一个原因是模型 ID 不存在服务端返回了错误结构SpringAI 按正常结构解析就空了。去控制台核对模型 ID。5.4 OAuth 相关报错如果你在 ClaudeCode 里看到 OAuth 相关的提示说明它还在尝试走官方登录流程没读到 ccSwitch 的配置。检查 ccSwitch 配置文件路径对不对current字段指向的 provider 是不是你配的那个。改完配置要重启 ClaudeCode 会话它不会热加载。5.5 三件套检查清单不管报什么错先核对这三样Base URL 是不是https://taotoken.net/api、Key 是不是当前有效的、Model ID 是不是控制台里存在的。这三样对了大部分连接问题都能排除。如果三样都对还报错把完整报错贴到会话里让 ClaudeCode 帮你分析它读得到上下文定位会比你自己翻日志快。6. 后续怎么用这套骨架继续叠加功能M0 跑通之后这个工程就是一个能持续迭代的基座了。我的用法是每开一个新模块先在docs下补一份该模块的规格文档然后让 ClaudeCode 读规格生成代码生成完自己验证验证通过就提交。整个过程你只做两件事——写规格、审结果。这套流程跑顺之后你会发现瓶颈不在写代码而在两件事一是规格写得够不够清楚二是 token 费用扛不扛得住。规格写得模糊AI 就会自由发挥返工次数上去费用也跟着涨。所以我现在写规格会尽量把边界条件、异常场景、返回结构都写死宁可前期多花十分钟也别让 AI 猜。费用这块我的经验是分阶段用不同模型。设计探讨阶段用便宜的对话模型方案定稿后用编码能力强的模型生成代码。另外注意模型的闲时和忙时价差有些模型能差一倍把思考类的工作放在忙时做把生成类的工作放到闲时跑能省不少。工程骨架已经就位下一阶段就是往里面填业务了。认证与家庭域、零花钱核心业务、AI 语音交互都可以按同样的 Spec-Driven 流程往下推。你先把这套骨架在自己机器上跑通后面加功能就是重复「写规格、生成、验证、提交」这个循环。需要创建 API Key 或者查接入文档的话可以从这几个入口进API Keys 页面在https://taotoken.net/api-keys接入文档在https://taotoken.net/doc。想先试试模型对话效果用https://taotoken.net/chat就行。如果你打算长期用这套流程做编码和 Agent 开发Coding Plan 页面https://taotoken.net/coding-plan里有更划算的套餐说明。