做个人IP的人,早晚会遇到一个坎:内容散落在各个平台,想统计、想排期、想复盘的时候,发现自己还在用 Excel 和手机备忘录。我花了四个月时间给自己写了一套个人IP内容矩阵系统,前后端一手包办,也就是现在说的全栈项目。这套系统把内容生产、多平台适配、排期发布、数据回收整条链路串了起来,目前已经稳定跑了半年。
技术栈是 Java 系:后端用 Spring Boot 3,前端用 Vue 3 + TypeScript,MySQL 存业务数据,Redis 做缓存和分布式锁,Quartz 负责定时调度,部署走 Docker Compose。代码量前后端加起来两万行左右,麻雀虽小五脏俱全。如果你正在做一个类似的内容管理工具,或者想找一个完整的全栈项目来研究生产级代码怎么写、一个功能从需求到上线是怎么一步步落地的,这篇内容应该能给你不少参考。
我会顺着自己真实的开发顺序讲:先拆需求和技术选型,再给架构和数据模型,接着落到核心模块的代码实现,然后是前端工程化与联调,最后是质量保障和踩坑记录。文章偏实操,里面的表和代码都是能直接照抄那种。
1. 项目全景:个人IP内容矩阵系统到底在做什么
1.1 需求拆解:从“发内容”到“内容矩阵”
内容矩阵和普通发帖最大的区别,是同一个内容源要被多个平台、多种形式复用。一条主题写完之后,要根据平台调性拆成公众号长文、短视频口播稿、种草文案,甚至还要配几张封面图。每个版本的发布时机、账号、状态都不一样。如果只靠一个“文章表”加一个“是否发布”字段,需求稍微一变代码就得跟着大改。
所以第一件事是把需求拆成四个核心域:
- 内容域:管理内容素材,以及各平台的适配版本和审核状态。一条原始主题对应多个版本,版本之间共享素材但不共享发布状态。
- 账号域:管理各内容平台的账号信息、授权 token、到期时间。账号和 token 分开存,因为 token 更新频率远高于账号基本资料。
- 排期域:把内容版本、账号、发布时间绑定成计划。计划要支持取消、改期,而且发布结果要可追溯。
- 数据域:从各平台拉取阅读量、点赞、评论、涨粉数据,按日聚合,面向报表。
这四个域看起来是传统业务系统,实际写起来细节不少。内容有状态,需要状态机;账号授权方式各平台不统一,需要适配层;排期要落地成可靠的任务,涉及分布式锁和幂等;统计要面对大量异步写入,得想清楚怎么攒批、怎么聚合。
我第一版犯过的错,是试图把所有平台发文的差异都抽象成一张“发布记录”表,结果不同平台的字段差距太大,一张表里塞满了空字段,查询逻辑越来越难写。后来改成主表存公共信息、扩展表存平台特有信息,才算把这个问题理顺。产品上这种“先想当然、后撞墙、再打补丁”的过程非常典型,做内容管理类系统的人大概率都会经历一遍。
1.2 技术选型背后的真实权衡
技术栈这件事没有标准答案,只有权衡。最终我的组合是:Spring Boot 3.x + MyBatis-Plus + MySQL 8 + Redis + Quartz,前端 Vue 3 + TypeScript + Vite + Pinia + Element Plus。如果你正好在走 Java 全栈学习路线,这套组合基本覆盖了从后端到前端的全链路核心环节。
为什么后端选 Java 而不是 Node 或 Go?核心原因是这个项目我要长期维护,Java 生态的稳定性、资料量、以及后续拉人协作的容易程度都占优。Spring Boot 3 的自动配置很克制,帮我省了大量样板代码,又不会像某些重型框架那样把业务逻辑绑死。数据库用 MySQL 8 是常规选择,事务支持成熟,内容系统常见的查询模式都能处理得很好。Redis 主要干三件事:缓存热点数据、做分布式锁防止定时任务重复执行、暂存各平台的临时 token。Quartz 承担排期调度,虽然 Spring 自带的 @Scheduled 也能做,但排期任务可能跨天跨周,Quartz 的持久化和集群部署支持更踏实,给后面扩展多实例留了空间。
前端选 Vue 3 是因为组合式 API 写起来很顺手,配合 TypeScript 后,接口返回结构可以直接用类型约束,少了很多字段拼错的低级 bug。Element Plus 的表格、表单、日期选择器恰好是管理后台的高频组件,省下大量造轮子的时间。
现在流行说 AI 全栈,其实核心还是人得把架构想清楚,把 AI 当成加速器,而不是靠它兜底。后面第 5 节我会单独讲 Clouse Code 这类工具在大型代码库里的落地姿势。
1.3 AI辅助编码:Claude Code在大型代码库中的落地姿势
这个项目写到中期,我开始用 Claude Code 辅助写代码。这里有一个很重要的体会:AI 编程工具能不能在大型代码库中发挥真正价值,关键不在于模型本身多强,而在于你怎么给它一个可理解的上下文。
我的做法是三步走。第一步,项目根目录固定放一份 CLAUDE.md,把模块结构、依赖关系、编码约定、核心术语写进去,让 AI 每次进入项目时先读它,相当于给它画了一张地图。第二步,不把 AI 当“生成器”,而是当“结对程序员”——我先写好接口签名和主体流程,让 AI 去填充方法体、生成单元测试。第三步,让 AI 做仓库级别的分析任务,比如“找出所有在 Service 层直接调用外部 HTTP 接口的地方,统一收敛到适配器”,它的检索能力确实比人肉翻代码快得多。
但 AI 的盲区也很明显。它容易写出“看起来合理、跑起来就报错”的代码,尤其是牵扯 Spring 动态代理、MyBatis 的 SQL 映射、跨模块事务这类隐式机制时。我的原则是:AI 生成的代码必须经过人工 review,并且只有被单元测试覆盖的部分才允许合入。这个规矩一次都没破过,因为破一次后面就开始失控。
2. 系统架构与核心数据模型设计
2.1 分层架构:别把后端写成一坨
架构上我沿用经典的 Controller-Service-Repository 三层,但在细节上加了几个硬约束。
Controller 层只做参数接收和响应封装,不允许出现业务逻辑。任何 if/else 出现在 Controller 里,review 时都会被驳回。Service 层是业务逻辑的归属地,但也不是所有方法都往一个类里塞,我按业务域拆成 ContentService、AccountService、ScheduleService、StatService 多个 Service,避免上帝类膨胀到几千行。Repository 层只负责数据访问,Controller 里直接注入 Mapper 这种情况是绝对禁止的。
跨层之间用 DTO 而不是 Entity。Entity 和数据库表一一对应,直接暴露给上层有个问题:数据库字段一改,接口结构跟着变,前后端联调全乱。前端接口单独定义 VO,Service 内部用 DTO,各层各司其职,权限和字段暴露范围都清晰。
统一返回结构和异常处理也是初期就要定下来的。我的返回对象用 Java record 实现:
public record ApiResult<T>(int code, String message, T data) { public static <T> ApiResult<T> ok(T data) { return new ApiResult<>(0, "ok", data); } public static <T> ApiResult<T> error(int code, String message) { return new ApiResult<>(code, message, null); } }配上 @RestControllerAdvice 做全局异常捕获,把业务异常、参数校验异常、未知异常分别映射到不同 code。前端拿到非 0 的 code 统一弹提示,不用每个接口各自写一套错误处理。这个约定在项目后期几乎所有模块都在受益,尤其是新加接口时,根本不用再想异常怎么返回。
2.2 数据模型:内容、账号、排期、统计四张核心表怎么设计
设计核心表时,我围绕内容、账号、排期、统计四个域,一共落成大概 12 张表。这里挑最核心的几张讲。
内容域拆了两张表:content_article 存原始主题、标题、正文素材和素材类型;content_version 存某个平台的具体适配版本,包括 platform_type、title、content、cover_url、status、publish_time。拆两张表的原因很简单:一条原始素材要适配多个平台,这是典型的 1:N 关系,如果不拆表,就得不停复制冗余字段,改一个标题要好几个地方同步。
账号域是 account_info 表,存平台类型、账号名称、头像、授权信息。授权 token 单独拆成 account_token 表,因为 token 有时效,刷新频率远高于账号基本资料,拆开后更新 token 不会频繁锁住账户主表,也方便独立设置过期策略。
排期域是 schedule_task 表,记录要发布的内容版本 ID、账号 ID、计划发布时间、实际发布时间、任务状态。实际发布记录单独存 publish_record,每次真实调用平台接口都记一条,不管成功失败都记,这样出了问题才有的回溯。统计域是 stats_daily 表,按 日期 + 内容版本 + 平台 维度聚合阅读量、点赞数、评论数、涨粉数。查询基本是固定维度 group by,所以建联合索引即可。
表结构最容易被忽略的是状态字段。我一开始用 TINYINT 存状态,枚举一多代码里全是魔法数字,后来改成 VARCHAR 存状态枚举字符,可读性明显提升。存储空间略大一点,但换来的是排查效率大幅提升,这个亏我替大家吃过了。
这里给出 content_version 表的简化 DDL,实际建表会多几个索引,主体结构就是这样:
CREATE TABLE content_version ( id BIGINT PRIMARY KEY AUTO_INCREMENT, article_id BIGINT NOT NULL, platform_type VARCHAR(32) NOT NULL, title VARCHAR(512) NOT NULL, content MEDIUMTEXT NOT NULL, cover_url VARCHAR(1024), status VARCHAR(32) NOT NULL, publish_time DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_article (article_id), KEY idx_status (status) );2.3 API设计规范:RESTful还是内部RPC
个人项目没有造 RPC 的必要,HTTP + JSON 就是最可靠的方案。接口设计上我遵循几个原则:资源名用复数名词,方法用 HTTP 动词。比如 GET /api/v1/contents 列表、POST /api/v1/contents 新建、PUT /api/v1/contents/{id} 更新。操作类接口,比如发布、审核、撤回,用 POST /api/v1/contents/{id}/publish 这种形式,语义清晰,前端也好记。
鉴权用 JWT。登录后后端签发 token,前端每次请求带 Authorization 头。JWT 本身无状态,配合 Redis 的 token 黑名单实现退出即失效。为什么不用 session?前后端分离部署,将来还要接 App 和小程序,无状态 token 的通用性更好。这里提个细节:token 里不要塞太多业务数据,我刚开始把用户昵称、头像都塞进去,后来发现改昵称还得等 token 过期才能生效,改成只放 userId 和角色权限,其他信息从库里现查。
列表查询统一了一套 Query 参数规范:page、size、sortBy、sortOrder,再加各业务过滤参数。前端封装一个通用 request 函数,所有 GET 自动拼接 query,非 GET 自动序列化 JSON,响应统一解析后端 code。这套约定的收益在联调阶段体现得最明显,前端和后端对接一个新列表页基本不需要来回商量参数格式。
3. 核心模块的代码实现与模式落地
3.1 内容模块:状态机驱动的内容流转
内容状态是这套系统里最容易写乱的部分。一条内容会经历:草稿 DRAFT -> 待审核 PENDING_REVIEW -> 已适配 ADAPTED -> 已排期 SCHEDULED -> 已发布 PUBLISHED -> 已归档 ARCHIVED,还可能从已排期撤回为已适配,从已发布修改后回到待审核。状态转移不能随便跳,如果在每个 Service 方法里都写 if (status == X) 的校验,十几个状态一组合,代码根本没法维护。
我用了状态机模式。先定义枚举:
public enum ContentState { DRAFT, PENDING_REVIEW, ADAPTED, SCHEDULED, PUBLISHED, ARCHIVED; public boolean canTransitionTo(ContentState target) { return switch (this) { case DRAFT -> target == PENDING_REVIEW; case PENDING_REVIEW -> target == ADAPTED || target == DRAFT; case ADAPTED -> target == SCHEDULED || target == PENDING_REVIEW; case SCHEDULED -> target == PUBLISHED || target == ADAPTED; case PUBLISHED -> target == ARCHIVED || target == PENDING_REVIEW; case ARCHIVED -> false; }; } }所有状态变更走一个统一方法:先校验 canTransitionTo,不通过就抛业务异常;通过后更新状态,同时不管成功失败都写一条 content_log。这样任何一次状态变更都可追溯,出问题直接查日志表就能定位是谁、在什么时候、把什么状态改成了什么状态。
有人可能注意到,“已发布”也允许回到“待审核”,这是业务上的真实需求:内容发布后数据表现不好,要允许撤回修改后再重新上线。如果没有状态机约束,这种变更很容易漏掉数据中其他关联表的联动更新,到时候光补数据就是噩梦。
3.2 多平台发布:策略模式与适配层
各内容平台的开放接口差异极大。有的走 OAuth,有的走简单签名,有的图文分开传,有的要传视频文件。如果在业务代码里写一堆 if platform == A / else if == B,新增一个平台就是一次大手术,所有代码都得翻一遍。
我用策略模式加适配器解决。先定义一个统一发布接口:
public interface PlatformPublisher { PublishResult publish(PublishRequest request); }每个平台一个实现类,比如 WechatPublisher、DouyinPublisher、XiaohongshuPublisher。应用层通过工厂方法按 platformType 获取实现,业务代码完全不知道具体平台内部逻辑。新增平台只加一个实现类加一行工厂映射,老代码一行不用动。
真正费劲的是 token 刷新。有的平台 token 几小时过期,有的几个月,有的请求失败后还要重新授权。我单独做了 TokenService,统一管理 token 的加密存储、过期前自动刷新、失败后单次重试。同时所有外呼统一封装 HttpService,超时、重试、日志都在这一层收敛。这个设计在后端排查问题时帮了大忙,所有外呼记录都带 traceId,顺着 traceId 一查就能定位到具体请求和失败原因。
3.3 排期调度:定时任务如何做到可靠
定时发布是内容矩阵系统的核心功能。Quartz 的 cron 表达式支持到秒级,我把后台扫描频率设为每 30 秒扫一次 schedule_task 表,凡到点且状态 PENDING 的任务就触发发布。
但定时任务最大的坑是重复执行。单实例部署没问题,一旦将来扩成多实例,同一任务可能被多个节点同时捞起来执行。解决方案是加分布式锁,我用 Redis 的 SETNX + EXPIRE:
String lockKey = "publish_lock:" + taskId; boolean locked = redis.setIfAbsent(lockKey, "1", Duration.ofMinutes(5)); if (!locked) { return; // 另一个实例已经拿到锁 }锁的超时时间必须大于任务可能的执行时间,否则任务没跑完锁就过期了,还是会重复执行。我的经验是锁时间给足,同时任务内部再做一次数据库状态校验:只有状态还是 PENDING 才真正调平台接口,调完立刻更新状态。即便极端情况下锁意外失效,数据库状态判断也能兜底。
发布失败也要处理。失败任务进入 FAILED 状态,记录失败原因;后台每 5 分钟捞取失败次数小于 3 的 FAILED 任务重新执行,超过次数就发告警邮件。这套机制代码量不大,但对用户体验影响直接——排好的发稿计划不会因为一次网络抖动就整个崩掉。
3.4 数据统计与报表:聚合查询的优化
统计模块最麻烦的点是数据来源多、写入频繁。我每天凌晨从各平台拉取前一天的阅读、点赞、评论、涨粉数据,写入 stats_daily,查询侧要支持按时间范围、平台、内容维度任意组合,报表页一打开就要展示最近 30 天趋势。
一开始用最简单的 SQL sum + group by 硬查,数据量到几万条之后报表接口响应接近 2 秒。做了两个优化:第一,统计查询全部走 Redis 缓存,key 按维度加时间范围设计,缓存失效 10 分钟;第二,把高频查询的聚合结果物化成 stats_report 表,每天凌晨写入当日全量汇总,报表接口读这张表而不是原始明细。两个优化叠加后,响应时间从 2 秒降到 80 毫秒以内。数据本身是日更的,10 分钟缓存延迟完全可接受。
数据统计有个容易踩的坑:不同平台对“阅读量”的定义不同,有的叫曝光有的叫阅读。读取数据时如果不加处理直接存,报表里的数字会缺乏可比性。我的做法是读取时先做一遍字段映射,统一成系统内部的语义字段,“阅读量”就指同一个概念,报表数字口径才不会乱。
4. 前端工程化与全栈联调
4.1 前端架构:组件化、状态管理与路由权限
前端页面分为工作台、内容管理、排期日历、数据报表、账号管理五个模块。组件设计核心原则:页面组件管布局和数据请求,通用组件管展示和交互。比如内容表格组件只接收 data 和 emit 交互事件,删除、编辑、发布这些操作逻辑由页面层来实现。组件可以复用,页面之间的行为差异由页面层自己控制,不会为了让一个组件适配所有场景把代码写成一坨。
Pinia 主要管理两类全局状态:用户登录态(token 加用户信息)、账号列表缓存。其他业务数据不放进全局 store,而是组件内 request 获取。全局 store 一旦滥用,跨页面数据同步会是噩梦——A 页面改了数据,B 页面还持有旧引用,刷新也不知道该不该触发。把 store 边界卡死在这两类状态后,这类问题基本消失。
路由权限用路由守卫加动态路由。用户登录后拉取可访问菜单,动态添加到路由表;未登录访问任何页面重定向到登录页。配合后端接口的权限校验,前后端各挡一层。如果只做前端跳转,接口照样能被绕过,所以后端权限是必须的。
4.2 前后端联调规范:从Mock到真实接口
联调是全栈项目容易忽略但实际占用大量时间的环节。我给自己定了一条规矩:所有请求必须经过 src/api 目录下定义好的函数,不直接在组件里写 axios。每个接口对应一个函数,参数和返回类型都定义得清清楚楚。这样改动接口时只需要动一个文件,全项目搜索相同调用即可。
Mock 阶段用 Vite 本地 mock 插件。后端接口文档先定好,前端按文档模拟返回数据,等后端联调时再切 baseURL。前端不阻塞,后端不焦虑,最后一天就能完成整体联调。联调时最容易出问题的不是业务逻辑,而是字段命名、空值处理、编码格式。我的经验是项目一开始就手动维护一份前后端共享的类型定义,后端返回结构和这份定义严格一致,配合 TypeScript 类型检查,很多字段不匹配在编译期就暴露了。
5. 生产级代码的质量保障与最佳实践
5.1 生产级代码规范:从命名到Commit信息
全栈项目一个人写,最容易在代码规范上放飞自我。项目一开始就定了一套约定:后端用 Spring 官方编码规范,前端用 ESLint + Prettier,提交信息遵循 Conventional Commits 格式。这些规矩不是摆设,它们让三个月后的我依然能快速读懂代码。很多个人项目毁就毁在“只有自己看得懂”,实际上连自己也看不懂。
Commit 信息必须写清楚,比如 fix(content): 修复发布状态偶发不一致、feat(stat): 新增按平台维度导出报表。半年后的 git log 就是一份项目演进史。别用 update 或 fix bug 这种糊弄自己的信息,不然想定位某次改动改了什么,翻遍 commit 都找不到。
自审 review 同样严格:不解释清楚的命名、没有覆盖测试的新逻辑、超过 200 行的 Service 方法,统统打回重写。这过程确实拖慢开发速度,但长期维护下来的代码质量提升非常可观。生产级代码最佳实践的标准,说到底就是“陌生人在不看文档的情况下能不能改得动你的代码”。
5.2 自动化测试:从单测到端到端
业务系统测试重点不是覆盖率数字好看,而是核心链路不回归。我的测试策略分三层:
- 单元测试:覆盖状态机、策略工厂、Token 刷新这类纯逻辑,用 JUnit 5 + Mockito 做隔离测试,不连数据库。
- 集成测试:用 Testcontainers 启动 MySQL 和 Redis,跑真实的 Repository 操作和 Service 层核心流程。
- 端到端测试:前端用 Playwright,覆盖登录、新建内容、排期发布、查看报表几条主路径。
单测有几个标准场景:状态机测试覆盖每个状态允许和禁止的转移路径;策略工厂测试验证不同 platformType 返回正确的实现类;Token 刷新测试模拟过期、刷新成功、刷新失败三个场景。这些测试写起来很快,但后续改需求时保护价值巨大,改坏了哪条状态路径跑一遍单测立刻能发现。
有一类测试我持保留态度:为了覆盖率硬补的快照测试。快照测试维护成本高,稍微改点 UI 就大面积变红,最后沦为摆设,没人逐条看变化是否正确。我更愿意把资源投在核心业务链路的真实断言上。
5.3 CI/CD流水线实战
部署用 Docker Compose,环境是单台云服务器。CI/CD 用 GitLab CI,Pipeline 阶段依次是:lint、单测、构建镜像、推送镜像、SSH 部署。每步都在容器里执行,保证本地跑得过的过程,CI 上也能跑过。
构建镜像有个细节。Java 后端 Dockerfile 分多阶段构建,第一阶段用 maven 镜像编译出 jar,第二阶段用 jre 镜像运行,最终镜像体积能从 800MB 降到 200MB 以内。前端镜像更简单,直接用 nginx 官方镜像,把构建产物拷进去加一份 nginx.conf,但要注意配置好 SPA 路由的 try_files,否则刷新页面就 404。
部署环节做最小化滚动更新:先拉新镜像,docker compose up -d,等健康检查通过后移除旧容器。这个流程如果能完全自动化最好,但个人项目阶段手动盯一下问题不大,毕竟发布频率通常不高。把精力优先投在自动化测试上,比投在部署自动化上的性价比更高。
6. 踩坑实录:常见问题与排查技巧
6.1 定时任务一跑就重,数据发了两遍
有一次用户反馈内容发了两遍,我查了半天发现是 Quartz 任务和 Redis 锁的联动问题。当时锁设了 60 秒过期,但发布请求因为网络超时实际执行了 3 分钟,锁早过期了,另一个实例在锁过期后重新获取锁,又把任务执行了一遍。整个过程排查了半小时,最后证据集中在两条 publish_record 记录上,时间差只有几秒。
解决方案是把锁时间调到 10 分钟,更关键的是在任务内部加了数据库状态幂等判断:执行发布前先查一次 schedule_task 的状态,必须是 PENDING 才继续;发布接口调用完立即把状态改成 SUCCESS。这样即便锁不幸失效,数据库的乐观判断也能挡住第二次执行。这类问题给所有做任务调度的系统提了个醒:锁和状态判断是互补关系,业务层的幂等校验永远是最后一道防线。
6.2 多平台API鉴权不统一,适配层写成了蜘蛛网
刚开始写平台适配层时,我把 token 刷新逻辑散落在各个 Publisher 里。结果不同平台刷新失败返回的错误格式不一样,有的返回 401,有的返回业务错误码,处理逻辑越写越分散,新增平台时还得把旧逻辑都翻一遍,体验极差。
后来把所有鉴权逻辑统一收敛到 TokenService,所有外呼统一走 HttpService,在这一层做错误码映射和单次重试。改动之后新增平台时只需要关心平台协议本身,鉴权框架是现成的,可维护性提升了一个档次。经验是:涉及多个外部系统的代码,公共横切逻辑一定要提到独立层,别让它散落在各实现类里。统一出口的价值,往往要到排查问题时才真正体会到。
6.3 统计报表越查越慢,MySQL直接带不动
统计报表第一次变慢是在数据量到 5 万条左右,按日聚合查询在 MySQL 上要跑 1 秒多,接口整体响应 2 秒,用户体验已经很差了。后来加了物化表加缓存,响应降到毫秒级,效果立竿见影。
这个优化过程有一个重要原则:不要在用户请求路径上做太重的计算。统计报表是典型的可预计算、可缓存场景,一定把聚合计算放到离线任务里做,在线请求直接读结果。如果实在要实时聚合,也要通过索引、分页、限制时间范围来控制查询成本,而不是让用户每次都全量扫库。
6.4 前端状态不同步,页面显示已发布、库里还是草稿
有一次前端列表页一直显示内容状态是草稿,但库里已经改成已发布。排查发现是列表查询做了全局缓存,重新进入页面时直接拿了旧缓存,没有重新请求。这个问题的根源是状态管理边界不清,缓存的位置选错了。
我的做法是把列表查询缓存删掉,所有列表数据每次进入页面重新请求,只有更新频率极低的全局用户态和账号列表才做缓存。从此之后没再出现过类似的状态不同步问题。后来我在代码注释里写了一条约定:列表页不要做全局缓存,需要交互反馈的页面跳转后必须刷新。前端的问题很多时候不是响应慢,而是数据不对,给用户带来的困惑远大于性能优化带来的快感。
写到最后说点实在的。这套个人IP内容矩阵系统从立项到稳定运行,迭代了大约四个月。回头看,最有价值的不是用了多华丽的技术,而是每个模块都踩过真实的坑、有过真实的取舍。状态机模式、策略模式这些设计模式,读书时觉得是理论,真正遇到“不加模式就改不动代码”的场景,才知道它们存在的意义。
如果你也要做类似的全栈项目,我的建议是:先别急着写代码,把业务链路拆清楚、数据模型设计好、统一返回和异常处理定好,这三个基础扎实了,后面所有模块的推进速度会快很多。代码写坏了可以重构,数据模型设计错了,改起来才是最痛苦的。
最后分享一个近期习惯:让 AI 辅助重构时,一定给它一个受控范围,比如只动某个 Service、只改某个模块,每次只重构一小块并保证对应测试全绿。这套做法目前还在帮我持续降低维护成本,也是我接下来想继续深挖的方向。