AI 写的代码总是跑偏?今天我们来聊聊这个问题。
松哥最近在用 CodeBuddy 开发项目,发现了一个让人头疼的问题。
每次让 CodeBuddy 帮我写功能,我只是随口说了一句需求,结果 AI 理解的和我想要的不一样:
每次都要反复沟通、反复修改,效率极低。
后来松哥发现了 OpenSpec 这个工具,专门解决这个问题。它的核心思想是:
先写规范,再写代码。让 AI 和你在动手之前就达成共识。
今天松哥就带大家用 OpenSpec + CodeBuddy,从零开发一个简易的 CRM(客户关系管理)系统,手把手教你怎么用规范驱动开发提升效率。
我们要开发的 CRM 系统,技术栈如下:
层次 | 技术选型 |
|---|---|
后端框架 | Spring Boot 3.x |
数据库 | MySQL 8.0 |
ORM | MyBatis-Plus |
缓存 | 暂不引入(保持简单) |
认证 | JWT |
前端 | Vue 3 + Element Plus |
构建工具 | Maven |
OpenSpec 是一个 Node.js 工具,需要先安装 Node.js(版本 20.19.0 或以上)。
去 Node.js 官网 下载安装即可。
安装完成后,验证:
node -v
# 输出类似:v22.0.0
npm -v
# 输出类似:10.0.0
npm install -g @fission-ai/openspec@latest
安装完成后,验证:
openspec --version
# 输出类似:openspec/1.x.x
安装完成后,用腾讯云账号登录即可。
用 Spring Initializr 创建项目,配置如下:
Project: Maven
Language: Java
Spring Boot: 3.2.x
Group: com.example
Artifact: crm
Java: 17
Dependencies:
- Spring Web
- Spring Data JPA(或 MyBatis-Plus)
- MySQL Driver
- Spring Security
- Lombok
- Validation
下载解压后,用 IDE 打开项目。
在项目根目录打开终端,执行:
# 进入项目目录
cd /path/to/your/crm-project
# 初始化 OpenSpec,指定 CodeBuddy 工具
openspec init --tools codebuddy
执行后,OpenSpec 会在项目中创建以下目录结构:
crm-project/
├── openspec/
│ ├── specs/ # 系统规范(源代码真理)
│ └── changes/ # 待开发的功能变更
├── .codebuddy/
│ ├── skills/ # OpenSpec 注入的 AI 技能
│ └── commands/ # OpenSpec 注入的斜杠命令
└── src/
└── ...(Spring Boot 源码)
这时候 OpenSpec 已经把自己的"技能"注入到了 CodeBuddy 里,后续在 CodeBuddy 对话框里就可以直接使用 /opsx:propose 等命令了。
在开始写代码之前,我们先梳理一下 CRM 系统要做哪些功能。
一个基础的 CRM 系统,通常包含以下模块:
CRM 系统
├── 用户管理
│ ├── 登录 / 登出
│ └── 用户信息管理
├── 客户管理
│ ├── 客户列表
│ ├── 新增 / 编辑客户
│ ├── 客户详情
│ └── 客户搜索
├── 跟进记录
│ ├── 添加跟进记录
│ ├── 查看跟进历史
│ └── 跟进提醒
└── 销售机会
├── 机会列表
├── 阶段管理(销售漏斗)
└── 赢单 / 输单
我们按照以下顺序逐步开发:
每一步都用 OpenSpec 先写规范,再让 CodeBuddy 实现。
打开 CodeBuddy 对话框,输入:
/opsx:propose add-user-auth
CodeBuddy 会自动创建以下文件:
openspec/changes/add-user-auth/
├── proposal.md ← 为什么做、做什么
├── specs/
│ └── auth/
│ └── spec.md ← 具体需求和场景
├── design.md ← 技术方案
└── tasks.md ← 实现清单
OpenSpec 生成的 proposal.md 大概是这样的:
# 提议:用户认证模块
## 意图
CRM 系统需要用户认证,确保只有授权用户才能访问系统数据。
## 范围
- 用户登录接口(用户名 + 密码)
- JWT Token 生成和验证
- 登出接口
- 当前用户信息查询
## 方案
使用 Spring Security + JWT 实现无状态认证。
Token 存储在客户端,服务端不维护 Session。
specs/auth/spec.md 大概是这样的:
# 认证规范
## 新增需求
### 需求:用户登录
系统 SHALL 在用户提供正确的用户名和密码后,返回 JWT Token。
#### 场景:登录成功
- 给定:用户名 admin,密码正确
- 当:POST /api/auth/login
- 那么:返回 200,包含 token 字段
- 并且:token 有效期为 24 小时
#### 场景:密码错误
- 给定:用户名 admin,密码错误
- 当:POST /api/auth/login
- 那么:返回 401,包含错误信息
### 需求:Token 验证
系统 SHALL 对所有非公开接口验证 JWT Token。
#### 场景:Token 有效
- 给定:请求头携带有效 Token
- 当:访问任意受保护接口
- 那么:正常返回数据
#### 场景:Token 无效或过期
- 给定:请求头携带无效或过期 Token
- 当:访问任意受保护接口
- 那么:返回 401
design.md 大概是这样的:
# 设计:用户认证
## 技术方案
- Spring Security 配置白名单(/api/auth/** 不需要认证)
- JWT 工具类:生成、解析、验证 Token
- JwtAuthenticationFilter:每次请求验证 Token
- UserDetailsService:从数据库加载用户信息
## 数据库设计
表名:sys_user
字段:
- id(主键)
- username(用户名,唯一)
- password(密码,BCrypt 加密)
- real_name(真实姓名)
- status(状态:0 禁用,1 启用)
- create_time(创建时间)
- update_time(更新时间)
## 接口设计
POST /api/auth/login → 登录
POST /api/auth/logout → 登出
GET /api/auth/me → 当前用户信息
tasks.md 大概是这样的:
# 任务
## 1. 数据库
- [ ] 1.1 创建 sys_user 表
- [ ] 1.2 插入初始管理员账号(admin/123456)
## 2. 实体和 Mapper
- [ ] 2.1 创建 SysUser 实体类
- [ ] 2.2 创建 SysUserMapper 接口
## 3. JWT 工具
- [ ] 3.1 引入 jjwt 依赖
- [ ] 3.2 创建 JwtUtils 工具类
## 4. Spring Security 配置
- [ ] 4.1 创建 SecurityConfig 配置类
- [ ] 4.2 创建 JwtAuthenticationFilter 过滤器
- [ ] 4.3 创建 UserDetailsServiceImpl 实现类
## 5. 认证接口
- [ ] 5.1 创建 AuthController
- [ ] 5.2 实现登录接口
- [ ] 5.3 实现登出接口
- [ ] 5.4 实现当前用户信息接口
## 6. 测试
- [ ] 6.1 测试登录成功
- [ ] 6.2 测试密码错误
- [ ] 6.3 测试 Token 验证
:::success重要:在让 CodeBuddy 开始写代码之前,先仔细阅读这些规范文件,确认它们符合你的预期。如果有不对的地方,直接修改文件内容即可。
:::
确认规范无误后,在 CodeBuddy 对话框输入:
/opsx:apply
CodeBuddy 会按照 tasks.md 的清单,逐个任务实现代码:
CodeBuddy:正在实现 add-user-auth...
✓ 1.1 创建 sys_user 表(src/main/resources/sql/init.sql)
✓ 1.2 插入初始管理员账号
✓ 2.1 创建 SysUser 实体类
✓ 2.2 创建 SysUserMapper 接口
✓ 3.1 引入 jjwt 依赖(pom.xml)
✓ 3.2 创建 JwtUtils 工具类
✓ 4.1 创建 SecurityConfig 配置类
✓ 4.2 创建 JwtAuthenticationFilter 过滤器
✓ 4.3 创建 UserDetailsServiceImpl 实现类
✓ 5.1 创建 AuthController
✓ 5.2 实现登录接口
✓ 5.3 实现登出接口
✓ 5.4 实现当前用户信息接口
✓ 6.1-6.3 编写测试用例
所有任务完成!
/opsx:verify
CodeBuddy 会检查实现是否符合规范:
CodeBuddy:验证 add-user-auth...
完整性:✓ 所有任务完成
正确性:✓ 接口符合规范定义
一致性:✓ 代码风格与设计文档一致
可以归档!
/opsx:archive
CodeBuddy:归档 add-user-auth...
✓ 规范已合并到 openspec/specs/auth/spec.md
✓ 变更已移至 openspec/changes/archive/
准备好开发下一个功能!
/opsx:propose add-customer-management
proposal.md:
# 提议:客户管理模块
## 意图
CRM 的核心是管理客户信息。需要支持客户的增删改查,
以及按条件搜索客户。
## 范围
- 客户列表(分页 + 搜索)
- 新增客户
- 编辑客户信息
- 删除客户(软删除)
- 客户详情
## 方案
使用 MyBatis-Plus 实现 CRUD,支持逻辑删除。
分页使用 MyBatis-Plus 的 Page 对象。
specs/customer/spec.md:
# 客户管理规范
## 新增需求
### 需求:客户列表
系统 SHALL 支持分页查询客户列表,并支持按姓名、手机号、公司搜索。
#### 场景:分页查询
- 给定:已登录用户
- 当:GET /api/customers?page=1&size=10
- 那么:返回第 1 页,每页 10 条客户数据
- 并且:包含总记录数
#### 场景:按姓名搜索
- 给定:已登录用户
- 当:GET /api/customers?keyword=张三
- 那么:返回姓名或公司名包含"张三"的客户
### 需求:新增客户
系统 SHALL 支持新增客户,手机号不能重复。
#### 场景:新增成功
- 给定:手机号未被使用
- 当:POST /api/customers,携带客户信息
- 那么:返回 200,客户创建成功
#### 场景:手机号重复
- 给定:手机号已存在
- 当:POST /api/customers
- 那么:返回 400,提示"手机号已存在"
### 需求:软删除
系统 SHALL 使用软删除,删除后数据不从数据库物理删除。
#### 场景:删除客户
- 给定:客户存在
- 当:DELETE /api/customers/{id}
- 那么:客户 deleted 字段置为 1,不再出现在列表中
design.md:
# 设计:客户管理
## 数据库设计
表名:crm_customer
字段:
- id(主键)
- name(客户姓名,必填)
- phone(手机号,唯一)
- email(邮箱)
- company(公司名称)
- position(职位)
- source(来源:1 官网,2 转介绍,3 广告,4 其他)
- status(状态:1 潜在,2 意向,3 成交,4 流失)
- owner_id(负责人 ID,关联 sys_user)
- remark(备注)
- deleted(逻辑删除:0 正常,1 已删除)
- create_time
- update_time
## 接口设计
GET /api/customers → 客户列表(分页 + 搜索)
POST /api/customers → 新增客户
GET /api/customers/{id} → 客户详情
PUT /api/customers/{id} → 编辑客户
DELETE /api/customers/{id} → 删除客户
tasks.md:
# 任务
## 1. 数据库
- [ ] 1.1 创建 crm_customer 表
## 2. 实体和 Mapper
- [ ] 2.1 创建 Customer 实体类(含 @TableLogic 逻辑删除)
- [ ] 2.2 创建 CustomerMapper 接口
- [ ] 2.3 创建 CustomerDTO(请求参数)
- [ ] 2.4 创建 CustomerVO(返回数据)
## 3. Service 层
- [ ] 3.1 创建 CustomerService 接口
- [ ] 3.2 实现 CustomerServiceImpl
- [ ] 3.3 实现分页查询(含关键词搜索)
- [ ] 3.4 实现新增(手机号唯一性校验)
- [ ] 3.5 实现编辑
- [ ] 3.6 实现软删除
## 4. Controller 层
- [ ] 4.1 创建 CustomerController
- [ ] 4.2 实现所有 CRUD 接口
- [ ] 4.3 添加参数校验(@Valid)
## 5. 测试
- [ ] 5.1 测试分页查询
- [ ] 5.2 测试手机号重复校验
- [ ] 5.3 测试软删除
/opsx:apply
CodeBuddy 会生成以下关键代码:
Customer 实体类(自动生成):
@Data
@TableName("crm_customer")
publicclass Customer {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String phone;
private String email;
private String company;
private String position;
private Integer source;
private Integer status;
private Long ownerId;
private String remark;
@TableLogic// MyBatis-Plus 逻辑删除
private Integer deleted;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
CustomerController(自动生成):
@RestController
@RequestMapping("/api/customers")
@RequiredArgsConstructor
publicclass CustomerController {
privatefinal CustomerService customerService;
@GetMapping
public Result<Page<CustomerVO>> list(CustomerQueryDTO query) {
return Result.ok(customerService.page(query));
}
@PostMapping
public Result<Void> add(@Valid @RequestBody CustomerDTO dto) {
customerService.add(dto);
return Result.ok();
}
@GetMapping("/{id}")
public Result<CustomerVO> detail(@PathVariable Long id) {
return Result.ok(customerService.getById(id));
}
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id,
@Valid @RequestBody CustomerDTO dto) {
customerService.update(id, dto);
return Result.ok();
}
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
customerService.delete(id);
return Result.ok();
}
}
/opsx:archive
/opsx:propose add-followup-records
这个模块的核心规范要点:
specs/followup/spec.md 关键需求:
### 需求:添加跟进记录
系统 SHALL 支持为客户添加跟进记录,记录跟进方式和内容。
#### 场景:添加成功
- 给定:客户存在,当前用户已登录
- 当:POST /api/customers/{customerId}/followups
- 那么:跟进记录创建成功,关联到该客户
### 需求:跟进历史
系统 SHALL 按时间倒序展示客户的所有跟进记录。
#### 场景:查看历史
- 给定:客户有 5 条跟进记录
- 当:GET /api/customers/{customerId}/followups
- 那么:返回 5 条记录,最新的在最前面
design.md 数据库设计:
表名:crm_followup
字段:
- id(主键)
- customer_id(客户 ID,外键)
- type(跟进方式:1 电话,2 微信,3 邮件,4 拜访,5 其他)
- content(跟进内容,必填)
- next_follow_time(下次跟进时间,可选)
- creator_id(创建人 ID)
- create_time
/opsx:apply
/opsx:archive
/opsx:propose add-opportunity-management
specs/opportunity/spec.md 关键需求:
### 需求:销售阶段流转
系统 SHALL 支持销售机会在不同阶段之间流转。
阶段定义:
1. 初步接触
2. 需求确认
3. 方案报价
4. 商务谈判
5. 赢单(终态)
6. 输单(终态)
#### 场景:推进阶段
- 给定:机会处于"需求确认"阶段
- 当:PUT /api/opportunities/{id}/stage,body: {stage: 3}
- 那么:阶段更新为"方案报价"
#### 场景:标记赢单
- 给定:机会处于任意非终态阶段
- 当:PUT /api/opportunities/{id}/win
- 那么:阶段更新为"赢单",记录赢单时间
### 需求:销售漏斗统计
系统 SHALL 提供各阶段机会数量和金额的统计。
#### 场景:查看漏斗
- 给定:系统中有若干销售机会
- 当:GET /api/opportunities/funnel
- 那么:返回每个阶段的机会数量和预计金额合计
design.md 数据库设计:
表名:crm_opportunity
字段:
- id(主键)
- customer_id(关联客户)
- title(机会标题)
- amount(预计金额)
- stage(阶段:1-6)
- expected_close_date(预计成交日期)
- owner_id(负责人)
- win_time(赢单时间)
- lose_reason(输单原因)
- deleted(逻辑删除)
- create_time
- update_time
/opsx:apply
/opsx:archive
当你对 OpenSpec 熟悉之后,可以同时开发多个功能,互不干扰。
/opsx:propose add-data-export
/opsx:propose add-dashboard-stats
这时候 openspec/changes/ 目录下会有两个文件夹:
openspec/changes/
├── add-data-export/
│ ├── proposal.md
│ ├── specs/
│ ├── design.md
│ └── tasks.md
└── add-dashboard-stats/
├── proposal.md
├── specs/
├── design.md
└── tasks.md
# 先实现数据导出
/opsx:apply add-data-export
# 切换到统计看板
/opsx:apply add-dashboard-stats
# 回来继续数据导出
/opsx:apply add-data-export
当多个功能都完成后,可以一次性归档:
/opsx:bulk-archive
CodeBuddy 会检查所有已完成的变更,并一次性归档。
随着功能不断增加,openspec/specs/ 目录会积累完整的系统规范:
openspec/specs/
├── auth/
│ └── spec.md ← 认证规范
├── customer/
│ └── spec.md ← 客户管理规范
├── followup/
│ └── spec.md ← 跟进记录规范
└── opportunity/
└── spec.md ← 销售机会规范
这些规范文件就是你系统的"活文档",随时可以查阅。
用 CLI 查看项目状态:
# 查看所有变更
openspec list
# 查看某个变更的详情
openspec show add-customer-management
# 交互式仪表板(可视化查看)
openspec view
A: 直接修改规范文件!
OpenSpec 生成的规范只是一个起点,你可以随时修改 proposal.md、specs/、design.md、tasks.md 中的任何内容。
修改完成后,再执行 /opsx:apply,CodeBuddy 会按照修改后的规范来实现代码。
A: 两种处理方式:
方式一:直接告诉 CodeBuddy 修复
这段代码有问题:[粘贴代码]
错误信息是:[粘贴错误]
请修复
方式二:更新规范后重新实现
如果 Bug 是因为规范描述不清楚导致的,先更新 specs/ 中的场景描述,然后重新执行 /opsx:apply。
A: 完全可以!
OpenSpec 的设计哲学是"棕地优先"(Brownfield-first),专门为修改现有系统设计的。
对于已有的代码,可以先用 /opsx:explore 让 CodeBuddy 分析现有代码,然后再提议新功能:
/opsx:explore
帮我分析一下现有的用户模块,了解当前的实现方式
CodeBuddy 会读取现有代码,然后在后续的规范中自动考虑现有实现。
A: 把 openspec/ 目录提交到 Git 仓库。
团队成员可以:
openspec/specs/ 了解系统现有功能openspec/changes/ 了解正在开发的功能这样,规范就成了团队的"共同语言"。
A: 支持 20+ 种,包括:
好啦,今天松哥带大家完整走了一遍用 OpenSpec + CodeBuddy 开发 CRM 系统的流程。
1. openspec init --tools codebuddy ← 初始化
2. /opsx:propose <功能名> ← 生成规范
3. 审查并修改规范文件 ← 人工确认
4. /opsx:apply ← AI 实现代码
5. /opsx:verify ← 验证实现
6. /opsx:archive ← 归档,进入下一个功能
传统 AI 编程 | OpenSpec + CodeBuddy |
|---|---|
随口说需求,AI 可能理解错 | 先写规范,双方达成共识 |
代码写完才发现方向不对 | 规范阶段就能发现问题 |
上下文越来越长,AI 容易遗忘 | 规范写在文件里,永不丢失 |
团队协作靠口头沟通 | 规范文件就是共同语言 |
不知道 AI 做了什么 | tasks.md 清单一目了然 |
好啦,今天的教程就到这里。如果你在实践过程中遇到了问题,或者有什么心得体会,欢迎小伙伴们在评论区留言交流!
如果觉得这篇文章对你有帮助,别忘了点赞转发,让更多小伙伴看到~