2026 Mid-Year Review
从零打造一个跨端 TodoList API:iTodo 项目技术揭秘
这是一篇关于我个人项目 iTodo 的技术复盘。它是一个用 Spring Boot 4 + PostgreSQL + Redis 构建的跨端待办事项 HTTP API,同时也是我第一次系统性地用 Claude(AI 结对开发) 从规格文档一路写到腾讯云生产部署的完整实践。
这篇文章不只讲”我做了什么”,更想讲清楚三件事:架构怎么搭的、工程规范怎么定的、以及 怎么和 AI 协作把它落地的。如果你也在做后端项目、或者好奇 AI 辅助开发的真实流程,希望能给你一些可复用的思路。
一、这个项目是什么
iTodo 是一个面向 小程序端、网页端、App 端 的待办事项后端服务,只提供 HTTP API,不含前端界面。功能上对标成熟的任务管理产品:清单管理、任务的完成/重要/”我的一天”、子任务步骤、标签、提醒、多端增量同步、全文搜索。
它刻意做成了一个”麻雀虽小五脏俱全”的工程样板:
- 认证体系完整:邮箱、手机号、微信小程序三种登录方式,JWT + Refresh Token 轮换。
- 多端同步:服务端作为唯一数据源,客户端按版本号拉取增量变更。
- 工程规范严格:统一响应结构、全局异常、Flyway 迁移、DTO 隔离、限流、审计日志。
- 生产可部署:腾讯云轻量服务器 + Nginx HTTPS + systemd 托管,一套完整的部署与安全清单。
二、技术栈与选型思路
| 层次 | 选型 | 为什么 |
|---|---|---|
| 语言 | Java 21 LTS (Temurin) | 用上 Record、模式匹配、虚拟线程红利,长期支持 |
| 框架 | Spring Boot 4.0.6 + Spring MVC | 生态最成熟,团队/AI 都熟 |
| 安全 | Spring Security 7 + JWT (jjwt) | 无状态认证,天然适配多端 |
| 数据库 | PostgreSQL 17 + HikariCP | 强一致、TIMESTAMPTZ/INET/UUID 原生支持好 |
| ORM | MyBatis-Plus 3.5.16 | 简单 CRUD 零 SQL,复杂查询回落到 XML |
| 缓存/限流 | Redis 8.4 + Redisson | 分布式限流、令牌桶 |
| 迁移 | Flyway | 数据库变更版本化,杜绝手改生产库 |
| 文档 | springdoc-openapi | 代码即文档,自动生成 Swagger UI |
| 工具 | Lombok + MapStruct + Hutool | 减样板、DTO 映射、常用工具 |
| 观测 | Actuator + Micrometer | 健康检查与指标 |
选型的一条隐藏主线是:每一层都选”约束性强、约定优于配置”的方案。因为项目从一开始就打算和 AI 协作,工具越”有脾气”(有明确的正确用法),AI 生成的代码就越不容易跑偏。
三、整体架构
3.1 按功能分包(package-by-feature)
项目没有采用传统的 controller / service / mapper 三大目录横切,而是 按业务模块垂直切分:
1
2
3
4
5
6
7
8
9
10
11
12
com.example.itodo
├── auth // 认证:登录/注册/刷新/登出、微信小程序
├── user // 用户资料
├── todo // 清单、任务、步骤、智能视图(核心域)
├── tag // 标签
├── reminder // 提醒
├── sync // 多端同步(bootstrap + changes)
├── search // 搜索
├── security // JWT、过滤器、SecurityConfig
├── infra // 基础设施,如限流 ratelimit
├── config // OpenApi / Jackson / Redisson / WebMvc / Actuator
└── common // 统一响应、错误、分页、TraceId 过滤器
每个功能包内自带 entity / mapper / dto / Service / Controller。这样做的好处是:改一个功能,相关代码都在一个包里,对人和 AI 都更友好——给 AI 的上下文可以精确到”只看 todo 包”。
3.2 分层与数据流
一次请求的完整链路是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Client
│ Authorization: Bearer <JWT>
▼
JwtAuthenticationFilter ──► 解析 JWT,注入 CurrentUser
▼
Controller (@RestController, /api/v1/**)
│ @Valid 校验 DTO;@AuthenticationPrincipal 拿当前用户
▼
Service ──► 业务逻辑 + 权限校验(一切查询按 ownerId 收敛)
│ 写操作同时写入 sync_changes
▼
Mapper (MyBatis-Plus) ──► PostgreSQL
▲
ApiResponseAdvice ──► 出参统一包装 + 注入 traceId
两个贯穿始终的横切设计值得单独说:
统一响应结构。 所有接口出参都被包装成 ApiResponse<T>:
1
2
3
4
5
6
public record ApiResponse<T>(
boolean success,
T data,
ApiError error,
String traceId
) { ... }
Controller 里只需 return ApiResponse.ok(data),而 traceId 由 ApiResponseAdvice(一个 ResponseBodyAdvice)在写出响应前自动从 MDC 补齐。前端拿到的永远是同一个信封结构,成功看 data,失败看 error,排障看 traceId。
全链路 TraceId。 TraceIdFilter 在请求入口生成/透传 traceId 并放入 MDC,日志和响应体都带上它。线上出问题时,用户报一个 traceId 就能串起整条日志。
四、几个核心设计亮点
4.1 认证:多身份 + Refresh Token 轮换
一个用户可以绑定多个”身份来源”(邮箱、手机号、微信),核心表结构是 users + user_identities 分离:
users存资料,email/phone唯一,且有一条CHECK约束保证三者至少有其一。user_identities用(provider, provider_subject)唯一识别第三方身份,微信登录就落在这里。refresh_tokens按设备记录,存的是 token 的哈希而非明文,支持”退出当前设备”和”退出所有设备”。
安全上的几条硬规矩(写进了 CLAUDE.md):短时效 access token + 轮换 refresh token、只存 refresh token 哈希、绝不打印密码/token、不使用带凭据的通配符 CORS。
一个真实的坑:微信
jscode2session返回的Content-Type是text/plain(但 body 其实是 JSON),RestClient.body(Xxx.class)会直接抛UnknownContentTypeException。解决办法是先读成String再用ObjectMapper手动解析。这类”文档没写、只有踩了才知道”的细节,正是需要沉淀进项目记忆的东西。
4.2 多端同步:版本号驱动的增量拉取
同步是这个项目最有意思的部分。策略很克制但很实用:
- 服务端是唯一数据源(server-authoritative),MVP 阶段不做离线写入合并、不做字段级冲突。
- 每一次 增/删/改 都往
sync_changes追加一条记录:(user_id, resource_type, resource_id, operation, version, changed_at)。 - 客户端首次登录调
GET /sync/bootstrap拉全量,之后调GET /sync/changes?sinceVersion=N拉增量。 - 客户端本地保存”最后同步版本”,版本太旧就重新 bootstrap。
这个设计的精髓是 用一张 append-only 的变更日志表,把”多端一致性”这个复杂问题降维成”拉取比我新的记录”。代码层面所有写操作都通过一个统一的 SyncChangeService 落变更,新增资源类型只要扩展 SyncResourceType 枚举即可。
4.3 限流:Redisson 令牌桶保护认证接口
登录/注册/刷新这类接口是暴力破解的重灾区,用 Redisson 的分布式限流在 infra.ratelimit 里统一挡在前面,参数通过 RateLimitProperties 外部化配置。
4.4 软删除与乐观锁
所有业务表都有 deleted_at(软删除)和 created_at / updated_at。核心的 todos 表还有一个 version BIGINT,既服务于同步的版本推进,也为未来的乐观锁留了口子。
五、工程规范:这才是项目的”骨架”
代码谁都会写,但一个项目能不能持续演进、能不能让 AI 稳定产出正确代码,靠的是规范。iTodo 把规范拆成了几套明确的约定。
5.1 CLAUDE.md:给 AI 的”项目宪法”
CLAUDE.md 是整个 AI 协作的核心。它不是文档,而是 每次对话都会被加载的强约束,用最精炼的语言告诉 AI”在这个项目里什么必须做、什么绝不能做”。它包含几个板块:
- Tech Stack:钉死版本,避免 AI 用错 API。
- Commands:
mvn test/mvn clean package/mvn spring-boot:run,让 AI 知道怎么验证。 - Architecture Rules:API 一律
/api/v1;进出用 DTO 绝不裸露实体;写请求必须 Jakarta Validation;schema 变更必须走 Flyway;所有用户数据查询必须按当前用户收敛权限。 - Security Rules:不提交任何密钥、从环境变量读密钥、生产隐藏 Swagger/Actuator。
- Verification:改动完成前必须跑
mvn test、确认 Flyway 干净迁移、检查/actuator/health、保持 OpenAPI 与行为一致。
写好 CLAUDE.md 的心得:规则要短、要绝对、要可验证。像”用户数据查询必须 scope by authenticated user”这种一句话规则,胜过一整页解释,因为 AI 每次生成 Controller/Service 时都会自动带上这条约束。
5.2 接口规范
- 路径:统一
/api/v1前缀,资源用复数名词,动作用子路径(如POST /todos/{id}/complete)。 - 信封:出参一律
ApiResponse<T>,绝不裸返实体或 Map。 - 入参:写操作用独立的
XxxRequestDTO +@Valid;查询用XxxQueryRequest;输出用XxxResponse。三者严格分离,实体永不出现在 API 边界。 - 映射:DTO ↔ 实体用 MapStruct(
XxxDtoMapper)在编译期生成,零反射。 - 分页:统一
PageResponse<T>。 - 文档:每个 Controller/方法用 springdoc 的
@Tag/@Operation注解,Swagger UI 自动生成,并额外维护一份手写的docs/openapi/openapi.yaml作为契约。
5.3 数据库规范
从 V1__init_schema.sql 能提炼出一套非常一致的建表约定:
- 主键:业务表用
UUID PRIMARY KEY DEFAULT gen_random_uuid()(靠pgcrypto),日志类表(sync_changes/audit_logs)用BIGSERIAL自增。 - 时间:一律
TIMESTAMPTZ(带时区),created_at / updated_at默认now()。 - 软删除:业务表统一
deleted_at TIMESTAMPTZ。 - 归属:每张业务表都带
owner_id / user_id外键,并ON DELETE CASCADE,从数据层保证数据隔离。 - 约束优先:唯一性、外键、
CHECK尽量放到数据库层(如ck_users_identity保证用户至少有一种身份)。 - 索引贴合查询:大量使用 部分索引 精确匹配业务过滤,例如:
1 2
CREATE INDEX idx_todos_owner_due_date ON todos(owner_id, due_date) WHERE deleted_at IS NULL;
查”我的一天”“重要”“按截止日期”这些智能视图时,走的都是
owner_id打头的复合索引,且天然跳过已删除数据。 - 迁移铁律:任何 schema 变更都写成新的
V{n}__xxx.sql,绝不手改生产库。
生产环境的一次教训:早期生产库曾绕过 Flyway 手工建表,导致
flyway_schema_history记录了 V1/V2”已应用”,但实际有表缺失,微信登录直接报relation "user_identities" does not exist。修复方式是写一个幂等的CREATE TABLE IF NOT EXISTS脚本补齐、且不动 Flyway 历史。这件事把”永远走 Flyway”这条规矩从纸面变成了肌肉记忆。
5.4 文档规范:OpenSpec 先行
项目在 docs/openspec/ 下维护了一整套 中文规格文档,在写代码之前就把系统说清楚:
project.md:目标、MVP 范围、明确的”不做什么”。feature-modules.md:功能模块总览表 + 每个模块的完整接口清单。auth.md / lists.md / tasks.md / sync.md:分域细则。development-plan.md:分 6 个阶段的开发计划,每个阶段都有明确的”交付物”和”验收标准”。
这套文档还有一条硬约束:因为仓库是公开的,所有文档、示例配置里不得出现任何真实密钥,命名也用中性表达、不体现任何第三方品牌。
5.5 安全与部署规范
docs/deployment/ 下有完整的生产落地方案:itodo.service(systemd)、nginx.conf.example(HTTPS 反代 + 安全响应头)、security-checklist.md(安全清单)、restore-test.md(备份恢复演练)。核心原则:只开必要端口、数据库/Redis 不出公网、生产关闭 Swagger、密钥全走 OS 环境变量。
六、如何用 Claude 开发这个项目
这可能是最多人好奇的部分。我的做法不是”让 AI 帮我写个函数”,而是把 AI 当成一个 需要明确规格和护栏的结对工程师。整个流程是这样的:
步骤 1:先写规格,再写代码(Spec-Driven)
在动第一行代码之前,先和 AI 一起把 docs/openspec/ 写出来——目标、范围、模块、接口、分阶段计划。规格是 AI 的地图。有了它,后面每次让 AI 写某个模块,都能引用”按 feature-modules.md 里 任务 部分的接口清单实现”,而不是让它自由发挥。
步骤 2:用 CLAUDE.md 立护栏
把不可违背的规则沉淀进 CLAUDE.md(见第五章)。它会被自动加载,等于给 AI 装了一层”永远在线”的约束。这一步是把项目质量从”靠每次提示词”变成”靠制度保证”的关键。
步骤 3:按阶段推进,小步验证
严格按 development-plan.md 的 6 个阶段走:基础设施 → 认证 → 清单任务 → 标签提醒同步 → 部署加固 → 增强。每个阶段都有验收标准,完成一个阶段就用 mvn clean package 验证一次,再进下一个。绝不一次让 AI 生成一大坨。
步骤 4:把”验证”做成闭环
这是踩坑最多、也最重要的一环。几条血泪经验:
- 一定要带
clean编译。 不带clean时 Maven 可能判定”文件没变”跳过重编,给出 假的 BUILD SUCCESS,把真实的编译错误藏起来。验证一律mvn clean compile / clean package。 - 一条
.java只能有一个 public 顶层类型。 曾经把多个public record塞进一个文件导致编译失败,后来每个类型独立成文件。 - 用到
UUID必须import java.util.UUID。Todo.ownerId是UUID,好几个 Service 都因漏 import 在生产编译时才暴露。 - 常量类不是枚举。
TodoStatus是一组public static final String(不是 enum),消费方不能写TodoStatus.ACTIVE.name(),方法参数也得用String。这类”看起来像枚举其实是字符串”的约定,必须明确告诉 AI。
步骤 5:沉淀项目记忆
把上面这些坑、这些约定,持续写进项目的长期记忆里(AI 协作时的 memory 文件)。这样 同一个错误不会犯第二次——下次 AI 再碰 UUID、碰 TodoStatus、碰 Flyway,都会自动想起历史教训。这一步让 AI 协作真正产生了”复利”。
一句话总结这套方法论
规格给方向,CLAUDE.md 立规矩,分阶段控节奏,clean 验证兜底,记忆防复发。
AI 写代码的速度不是瓶颈,”让它每次都写对”才是。而”写对”靠的不是更聪明的提示词,是更好的工程约束。
七、你可以从这个项目学到什么
即使不用 AI,这个项目本身也是一份不错的后端工程范本。我认为最值得借鉴的几点:
- 统一响应 + 全链路 TraceId:让前后端协作和线上排障都变简单,成本极低,收益极高。
- package-by-feature 分包:改动内聚,给人和 AI 的上下文都更清晰。
- DTO 三分法(Request / QueryRequest / Response)+ MapStruct:实体永不出边界,编译期映射零反射。
- append-only 变更日志做多端同步:把复杂的一致性问题降维成”拉取增量”。
- 数据库层兜底一切:UUID 主键、TIMESTAMPTZ、软删除、部分索引、CHECK 约束、
ON DELETE CASCADE,把正确性尽量下沉到数据库。 - Flyway 迁移铁律:永不手改生产库,一次教训终身受用。
- Spec-Driven + CLAUDE.md 的 AI 协作范式:规格先行、护栏立规、闭环验证、记忆防坑。
八、写在最后
iTodo 对我而言不只是”又做了一个 TodoList”。它验证了一个假设:当工程规范足够清晰、验证闭环足够严密时,AI 结对开发能稳定产出生产级代码。 AI 没有替代思考,反而逼着我把每一条约定都显式化——路径规范、DTO 规范、建表规范、安全规范,全都得写下来、说清楚。而这些被显式化的规范,最终既服务了 AI,也让项目本身变得更健壮。
如果你也想试试 AI 辅助开发,我的建议是:别急着让它写代码,先花时间和它一起把规格和规矩定下来。 磨刀不误砍柴工。
项目技术栈:Java 21 · Spring Boot 4 · PostgreSQL 17 · Redis 8.4 · MyBatis-Plus · Flyway · JWT。全程采用 Spec-Driven + CLAUDE.md 护栏的 AI 结对开发模式。