从表单平台到微信业务系统,我如何借助 AI 补齐后端能力
行业寒冬,加上 vibe coding 带来的冲击,我开始重新考虑自己的能力边界:除了写页面,我能不能把一个业务从需求一路做到可运行的系统?
这种不确定感不是突然出现的。当 AI 已经能快速生成页面代码时,我意识到"会写组件"可能不再是足够的答案,真正稀缺的是有人能为整个业务负责:数据放在哪里、状态怎么流转、出错时谁来兜底。
这个想法也和团队的实际情况有关。后端只有两个人,平时比较忙,而我也想尝试新的项目。于是,我从 mjoptim-form 表单项目开始,借助 AI,用 NestJS 做后端,尝试承担前后端的完整开发。
这篇文章围绕 mjoptim-form,整理从表单页面延伸到 NestJS 后端的实践。希望给同样做前端、想尝试 AI 辅助全栈开发的朋友一些具体参考:哪些已有经验可以继续用,哪些问题需要重新学习,以及怎样判断 AI 生成的代码是否满足业务要求。
文中的代码根据项目实现节选或简化,省略了日志、配置和部分业务分支;示意代码会单独注明。这里重点讲思路,完整实现还需要处理各自的业务条件。
1. 从表单项目开始,把前端熟悉的业务往后延伸
选择表单项目作为实践起点,很适合从前端经验出发。
动态表单、字段联动、校验、预览、文件上传,这些都是前端常见的工作。把这些熟悉的需求往后延伸,就会自然遇到接口、数据库、认证和支付。
mjoptim-form 的业务包括后台配置表单、H5 填写与提交、付费采集、数据管理和核销。它既包含前端熟悉的编辑器与填写页面,也包含提交记录、支付状态和核销结果的管理。
项目分成三个应用:
text
apps/
├── admin/ 管理后台:配置业务、管理数据
├── web/ H5:用户参与业务
└── server/ NestJS:接口、业务规则、数据库与外部服务
前端继续使用 Vue 3,管理端使用 Element Plus,H5 使用 Vant;后端使用 NestJS、Prisma 和 PostgreSQL。项目当前已使用 NestJS 11,本文讨论的是现有实现。
三个应用的分工和请求链路,可以用一张图先建立整体印象:

以表单为例,一条基本流程可以这样理解:
text
管理员配置字段与联动规则
↓
API 保存表单配置
↓
H5 获取配置,渲染表单
↓
用户填写并提交
↓
服务端检查规则,保存提交记录
↓
付费场景继续进入支付、结果查询和核销
过去站在前端的角度,关注点往往是组件怎么渲染、校验怎么提示、请求怎么发出去。接上后端之后,还需要回答:配置如何保存?哪些字段应该提交?金额从哪里来?有人直接构造请求怎么办?
这些问题都来自熟悉的业务,很适合作为学习后端的入口。
2. NestJS:先理解一个请求怎么走
对于有 JavaScript、TypeScript 经验的前端开发者,NestJS 的语言和工具链比较容易接近。真正需要花时间理解的是它怎样组织后端代码。
开始之前,可以先看看前端已有的经验会对应到哪里。这张表不是概念速查,而是我在项目里反复对照的一份"翻译表":
| 前端已有经验 | 后端对应概念 | 需要新增的认识 |
|---|---|---|
| TypeScript 类型 | DTO、class-validator、ValidationPipe | 类型声明不能验证网络请求,运行时仍需校验 |
| 页面 / 功能模块拆分 | Module、Controller、Service | 接口入口、业务规则与依赖组装需要区分职责 |
| 前端路由与按钮控制 | Guard、JWT、权限校验 | 浏览器中的限制不能替代服务端校验 |
| Axios 请求与错误提示 | 接口契约、异常、日志 | 错误既要便于用户理解,也要可供排查 |
| 页面数据和状态管理 | Prisma 模型、事务、业务状态 | 数据需要跨请求、跨进程保持一致 |
这张表里的每一行,后面都会落到具体案例。入门时,可以先抓住三个角色:
| 角色 | 主要职责 | 可以先这样理解 |
|---|---|---|
| Controller | 声明路由,接收请求,调用业务逻辑 | API 的入口 |
| Service | 执行业务规则,组织数据读写和外部调用 | 完成这件事的过程 |
| Module | 声明模块里的 Controller、Provider 和依赖 | 把相关能力组装起来 |
表单项目中,获取表单的入口很短。下面是相关代码的节选:
ts
@Controller('h5')
export class H5Controller {
constructor(private readonly h5Service: H5Service) {}
@Get('forms/:code')
async getForm(@Param('code') code: string) {
return this.h5Service.getFormByCode(code);
}
}
结合全局 /api 前缀,这个接口就是 GET /api/h5/forms/:code。
这里没有在 Controller 中堆满数据库查询和规则判断,而是交给 Service。之后,Service 再通过 Prisma 访问数据库。这条调用关系,比一开始记住全部装饰器更有用:
text
HTTP 请求 → Controller → Service → Prisma → PostgreSQL
构造函数里的 H5Service 由 NestJS 的依赖注入机制提供。Module 需要正确声明相关 Provider,跨模块使用时还要处理导入和导出。可以先理解成:谁负责提供这个能力,谁需要使用它。
这个结构也方便测试。测试 Controller 时,可以替换 Service;测试业务逻辑时,可以控制数据库依赖或使用测试数据库。
当然,有 Module、Controller、Service,并不自动意味着代码已经合理拆分。项目中的 H5Service 承载了较多业务。随着功能增加,规则判断、支付适配、库存处理仍然需要继续整理。
TypeScript 类型,不能代替请求校验
这是前端转后端时很容易忽略的一点。
给参数写上 number,不会让外部请求自动变成合法数字;把 JSON 断言成某个类型,也不会让里面的数据自动符合这个类型。
TypeScript 主要帮助开发阶段检查代码,网络请求需要运行时验证。NestJS 常见的做法是 DTO 配合 class-validator、class-transformer 和 ValidationPipe。
例如,下面是一个用于说明机制的 DTO:
ts
export class PaymentQuantityDto {
@IsInt()
@Min(1)
quantity: number;
}
只有配合相应的验证管道,装饰器才会参与请求检查。即使 quantity 合法,服务端还要继续检查表单是否开放、收款字段是否存在、库存是否足够。
格式合法和业务允许,是两层不同的判断。
项目启用了 ValidationPipe,同时存在大量动态字段,不能认为启用一个全局管道,就已经完成所有提交校验。
3. 表单联动:前端展示和后端判断要对得上
动态表单是这个项目里很能体现前后端职责变化的部分。
表单模型中,fields 保存字段配置,advanced_rules 保存高级联动规则,两者使用 JSON。这样可以表达不同字段类型与组合规则,而不必每增加一个表单字段就新增一列数据库字段。
H5 根据配置决定展示什么,管理后台根据配置提供编辑和预览。但服务端收到提交后,也必须根据规则重新判断。
后台的字段编辑器就是这套配置的生产者,左边是组件库,中间是表单预览,右侧是字段属性:

一次提交在三个应用之间如何流转,可以用下面的图来对照:

用一个演示场景说明:
- 用户选择"个人报名",只需要姓名和手机号。
- 用户选择"企业报名",额外显示企业名称,并要求填写。
如果前端隐藏了企业名称,后端仍无条件要求它必填,就会出现"页面没有这个输入框,提交却提示没填"的问题。
反过来,如果服务端完全相信前端的校验结果,直接构造请求的人就可以跳过这些规则。
表单项目的处理过程包括:读取字段与规则、计算可见字段,再针对可见字段做必填、格式、白名单与唯一性等检查。简化后的顺序如下:
ts
const fields = form.fields as unknown as FormField[];
const advancedRules = form.advanced_rules;
const visibleFields = this.getVisibleFields(
fields,
data,
advancedRules,
);
const requiredError = this.validateRequired(visibleFields, data);
if (requiredError) {
return { code: 40003, message: requiredError };
}
这里的类型断言只是让 TypeScript 接受当前用法。配置结构是否正确、规则是否有效,仍然需要配置保存与执行环节的校验。
隐藏字段,也可能需要检查
表单里还有签名字段。这又带来另一类问题:如果字段当前被隐藏,但提交请求仍携带签名 URL,要不要检查?
项目中会检查实际携带的签名值,其来源需要符合配置的 COS/CDN 域名和签名目录要求。这项来源检查在可见字段筛选之前执行。
原因在于,字段隐藏决定的是交互和必填规则,不能让请求里已有的不可信内容绕过检查。
这给前端开发者一个很具体的提醒:页面上看不到一个字段,不代表服务端收不到它。
一个容易忽略的失败点:多出来的那个斜杠
签名来源校验是一段很容易被低估的代码,它的回归用例里专门覆盖了一个细微场景,值得单独拿出来说。
上传接口拼接文件地址时用的是模板字符串:
ts
const webUrl = process.env.COS_WEB_URL || '';
const fileUrl = `${webUrl}/${filename}`;
只要配置值结尾带了一个斜杠,例如 https://cdn.example.com/forms/,拼出来的地址就会变成 https://cdn.example.com/forms//signatures/...------路径里出现了两个连续的斜杠。
而校验如果直接拿配置字符串去比对前缀,配置里是单斜杠、实际值是双斜杠,就会匹配失败,把合法请求判成"签名数据无效"。测试里那条 COS_WEB_URL 以斜杠结尾时接受上传接口生成的签名 URL 的用例,固定的就是这个行为。
现在的实现不再依赖字面量比对,而是把两边都解析成 URL,归一化路径中的连续斜杠,再按 origin 加 signatures/ 目录前缀判断:
ts
const normalizedPath = base.pathname.replace(/\/{2,}/g, '/');
const basePath = normalizedPath.endsWith('/') ? normalizedPath : `${normalizedPath}/`;
return `${basePath}signatures/`;
这里的问题不在签名算法,而在两个字符串的格式假设不一致。构造方环境变量怎么写、校验方按什么格式比对,只要有一边没约定,就会在某个特定环境上失败------本地正常、另一套环境报错的差异,往往就出在这种最不起眼的拼接上。
这也是前端转后端要慢慢建立的习惯:配置项和环境变量不是"一遍配好就不用管"的常量,它们的格式同样是需要被约束和校对的。
规则可以分开实现,语义必须一致
这个项目的管理端、H5 和后端有各自的规则处理。独立构建时,不一定适合直接共用所有实现,但同一组配置应该得到一致结果。
适合用来核对的场景包括:条件是单值还是多值、多个条件使用 AND 还是 OR、多个显示规则如何组合、隐藏字段是否必填、旧配置如何兼容。
这类业务值得准备一组共同的输入与预期结果,让不同端分别执行。否则,编辑器预览、H5 展示、后端提交可能各自看起来正确,放在一起却无法完成业务。
4. 支付:前端负责展示,服务端核实金额和结果
付费表单让"哪些数据可以相信"变得非常具体。
浏览器可以展示单价和总额,但浏览器提交的价格可以被修改。因此,服务端需要从自己保存的表单配置里读取价格,再根据数量计算金额。
表单项目的相关实现使用 Decimal。下面是计算部分的简化节选:
ts
const unitPrice = new Decimal(
paymentField.current_price ?? paymentField.amount ?? 0,
);
const fieldTotal = unitPrice.mul(quantity);
totalAmount = totalAmount.add(fieldTotal);
这里的关键是单价来自服务端配置,提交里的 unitPrice 不能决定订单金额。数量还需要按业务约束单独检查,单价可信不意味着整个请求已经可信。
Decimal 也帮助避免直接用 JavaScript 浮点数做金额计算带来的精度问题。与支付平台交互时,还要明确单位:有的接口使用元的字符串,有的使用整数分,转换规则需要统一。
浏览器显示支付成功,不等于服务端完成结算
前端调起支付之后,会收到返回结果,也可能跳转到结果页。但订单最终状态需要来自服务端核验过的支付平台证据。
表单项目接入了微信支付和支付宝两条渠道,它们的通知格式并不相同,下面分别说明。先看微信:以 V3 异步通知为例,服务端需要保留原始请求体,检查签名信息、验签、解密,再核对交易与本地记录。金额、币种和订单状态都属于需要检查的内容。
为什么强调原始请求体?因为验签可能需要精确的原始字节。JSON 解析后再序列化,即使表达的是相同数据,生成的文本也可能不同。
项目的启动配置启用了 rawBody,相关 HTTP 测试也覆盖了支付请求体处理。
从前端页面看,支付涉及浏览器、服务端、数据库和支付平台四方,时序关系如下:

结果页还需要考虑"通知尚未到达""暂时查询失败""仍在处理中"等状态。一次查询失败不能直接被解释为支付失败。
这里还有一点值得前端注意:"支付成功"是三个不同的时刻。浏览器拿到的结果、平台通知到达服务端、订单状态最终生效,这三件事可能先后发生,也可能因为网络原因错开。页面只负责把用户能看到的那一步展示出来,不能让页面成为订单状态的来源。
重复通知,要避免重复处理
支付通知可能重复到达。如果每次通知都执行库存转换或其他操作,业务数据就可能出问题。
只写"先查询是否已支付,如果没有就更新"还不够:两个请求可能同时查到未支付,然后都继续执行。
支付宝这条线的结算,用了带状态条件的更新来争取一次状态转换。下面把两种写法的差别放在一起对照:

对应到代码,关键部分是这样的简化节选:
ts
const changed = await tx.payment.updateMany({
where: {
id: payment.id,
status: 'created',
from: null,
transaction_id: null,
},
data: {
status: 'paid',
from: 'alipay',
transaction_id: evidence.transactionId,
},
});
这里的条件由数据库参与判断。只有符合旧状态的记录才会更新,随后根据影响行数判断自己是否完成了状态转换。
如果没有更新到记录,不能简单宣布成功。完整实现会重新读取,检查是否已经由相同支付渠道、相同交易号和相同金额完成结算;相符才按重复处理,否则属于冲突。
这段处理与相关库存转换在同一个数据库事务中。这样,其中一步失败时,可以回滚事务里的变更。
但事务的范围也要说清楚:数据库事务不能让一次外部支付请求自动回滚。幂等需要逐个处理入口和副作用,不能因为有一段事务,就认为整个系统不会重复执行。
5. 从页面数据到业务模型:提交、支付和核销各有职责
前端经常把字段配置、填写结果和支付状态组合在一个页面里。到了数据库,需要先区分它们代表的业务对象。
后台的列表页把这件事摆得很清楚:同一行里同时出现了提交数、发布状态、核销开关,它们分别属于不同的业务对象:

表单项目中的几个核心模型及其关系,可以再用一张图看清:

它们各自的职责可以这样理解:
| 模型 | 保存的内容 | 对应的业务问题 |
|---|---|---|
| Form | 表单配置、联动规则、开放状态等 | 用户填的是哪一张表单? |
| Submission | 填写数据、来源、核销信息等 | 这一次提交了什么? |
| Payment | 金额、渠道、交易号、支付状态等 | 相关支付进行到哪一步? |
| Inventory | 按收款字段管理的总量、锁定量和已售量 | 这个收款字段还有多少可用库存? |
| Verifier | 核销员信息、所属表单和统计信息 | 谁参与这张表单的核销? |
Form 关联提交记录,Submission 关联支付记录。用户提交成功和支付成功因此可以分别表达:填写数据已经保存,不代表付款已经完成。
同样,核销状态与支付状态承担不同职责。核销记录还包含核销人和核销时间,不能只根据前端按钮是否变灰来判断是否已完成。
核销需要检查记录属于哪张表单
核销这块逻辑在服务端单独的核销模块里(不是前面提到的 H5Service)。它会先找到表单和提交记录,检查提交是否属于当前表单,再检查是否已核销、是否存在核销码。下面是相关判断的节选:
ts
if (submission.form_id !== form.id) {
return { code: 40301, message: '无权限操作该数据' };
}
if (submission.verified_status === 'verified') {
return { code: 40001, message: '该数据已核销,请勿重复操作' };
}
if (!submission.verification_code) {
return { code: 40002, message: '该数据无核销码' };
}
记录存在只是第一步,还需要核对它与当前业务的关系。对于前端开发者,这意味着页面上的路由参数和记录 ID,最终都要由服务端结合身份与业务范围检查。
这段判断可以阻止已核销记录再次走普通流程,但"先查询再更新"不等于并发安全。如果两个请求同时通过检查,还需要带旧状态条件的更新或其他数据库机制来保证一次状态转换。前面支付结算里的条件更新,就是可以参考的思路。
设计模型时,也要考虑后续状态
收款字段的库存区分 locked 和 sold,分别表示待支付占用与已售出。这使得"提交后还没付款"和"已经付款"能够区别处理,也意味着支付失败、取消、超时等路径需要考虑占用的释放。
模型拆开以后,业务过程会更清楚。前端仍然可以把它们汇总成列表展示,服务端则要维护各条记录之间的关系和状态变化。
6. AI 协作:把业务要求写到可以检查
AI 让我有机会把全栈实践推进起来。面对不熟悉的框架和后端代码,它可以帮助解释概念、整理模型、生成代码和补充测试。
但项目越涉及金额、状态和跨模块操作,对需求描述与结果检查的要求就越高。
结合表单项目,可以把适合这类业务的 AI 协作方式整理为下面几步。这是可参考的方法,不代表每次开发都严格按同一顺序执行。
先给业务边界,再让它写代码
"帮我写一个表单提交接口"可以生成一个入口,却没有说明这个入口应当遵守什么规则。
把业务补充清楚,结果就有了可检查的依据。例如,下面这段是根据项目整理的示例 Prompt,并非原始对话:
text
阅读现有表单配置、H5 提交和 Prisma 模型,再处理提交校验。
要求:
1. 根据服务端保存的联动规则计算可见字段。
2. 必填检查只针对当前可见字段。
3. 请求实际携带签名时,检查来源,即使该字段隐藏。
4. 付费字段单价取自服务端配置,不采用请求中的单价。
5. 保持现有返回格式和旧配置兼容。
先说明涉及哪些文件、哪些业务判断,再实现并补关键用例。
不要重写无关页面或改变已有支付状态定义。
输入、输出、规则、兼容范围都清楚后,才能检查生成代码有没有做到。
可以对照一下这两种写法的差别:
text
改写前:
帮我写一个表单提交接口。
问题:AI 会自己决定校验放在哪一层、金额从哪取、
隐藏字段要不要管。生成结果看似完整,但每一个
业务判断都可能和项目实际要求不一致,检查时也
没有依据,只能靠通读代码去猜。
改写后:
阅读现有表单配置、H5 提交和 Prisma 模型,再处理提交校验。
要求:...(见上方完整示例)
区别:把"输入、规则、兼容边界"写清楚之后,每一句要求
都变成一条可以逐项核对的条件。AI 的输出从"一个
能跑的接口"变成"一份可以验收的实现"。
这个对比里最关键的不是 Prompt 变长了,而是要求变成了可检查的条件。"单价不能来自请求"可以被核对,"写好一点"不能。
先让 AI 阅读已有项目
项目已有模型、接口契约、配置方式和命名习惯。让 AI 在这些边界里修改,比让它重新设计一套系统更容易与现有业务衔接。
项目里有项目说明、AI 指令文件,以及设计和计划文档。这些文档可以保存稳定的约束,例如三端的职责、数据库迁移方式、发布配置和测试要求。
不过,文档也可能滞后。让 AI 阅读项目时,还需要要求它对照依赖文件、模型和实际调用路径,避免只依据早期设计生成与当前实现不兼容的代码。
人重点检查业务决定
生成的接口是否能运行是一件事,业务是否正确还需要看具体判断。
我建议优先检查:价格来自哪里、身份与权限如何确定、哪些旧状态允许更新、失败会留下什么数据、重复请求会不会重复产生副作用。
这些问题常常比代码风格更影响交付。一个接口完全可以写得整齐、返回成功,却在金额或状态处理上出错。
下面是一次比较典型的修正过程,按项目里的处理方式整理,用于说明"怎么发现 AI 的问题"。
当时要让 AI 处理支付通知的结算:收到通知后,把订单从未支付改成已支付。它给出的写法很自然:
ts
const payment = await tx.payment.findUnique({ where: { id } });
if (payment.status !== 'paid') {
await tx.payment.update({
where: { id },
data: { status: 'paid', transaction_id: evidence.transactionId },
});
await this.convertLockedToSold(formId, submissionId, tx);
}
这段代码读起来没问题,单独跑也一定成功。问题在并发下才暴露 :两条重复通知同时到达时,两个请求可能都在第一时间查到 created,于是双双进入更新分支,库存转换被执行两次。
发现它的方式不是等线上出错,而是按"如果这条通知来了两次会怎样"去推演。把场景问清楚之后,问题就显而易见了。
修正的做法是把判断交给数据库,用带上旧状态条件的更新争取唯一一次转换:
ts
const changed = await tx.payment.updateMany({
where: { id: payment.id, status: 'created', from: null, transaction_id: null },
data: { status: 'paid', from: 'alipay', transaction_id: evidence.transactionId },
});
if (changed.count === 0) {
// 没有抢到状态转换:重新读取,核对是否为相同的重复通知
}
改完之后,两条通知里只有一条能更新成功,另一条走"重新核对"的分支。这里真正需要人判断的是**"重复到达时应该发生什么"**,AI 不知道这个业务前提,所以它能写出正确的语法,却写不出正确的语义。
这也是我在协作里的一个固定动作:先让 AI 生成一版,再拿异常场景去问它。两个请求同时到达会怎样?通知重复会怎样?中途失败会留下什么数据?大部分问题都是这样被问出来的,而不是被读出来的。
对于解释不清楚的关键逻辑,可以继续让 AI 按具体场景推演:两个请求一起到达时各走哪条分支?库存转换失败会回滚什么?重复通知的交易号不一致时怎么办?
最后还要用测试和运行结果核对推演,不能把 AI 的说明本身当成验证。
7. 从"能启动"到"能交付",还要补哪些工作
后端接入数据库和外部平台之后,运行环境也会影响业务。
数据库变更需要迁移
Prisma schema 帮助声明模型,Prisma Client 帮助读写数据。但修改 schema 并不会自动解决所有环境的数据库演进。
新增字段时,需要考虑已有记录如何处理、默认值是否合理、索引是否需要调整,以及代码发布与迁移的顺序。
迁移文件把这些变化纳入版本管理,也方便在不同环境执行和核对。开发阶段方便的同步命令,不能直接代替正式迁移管理。
前后端传递数据还会遇到类型边界。表单项目使用 BigInt ID,序列化时转成字符串。前端收到后应保留字符串,避免 Number(id) 导致大整数精度丢失。
授权状态需要跨进程保存
项目的微信授权接入使用 Redis 保存短期 state 和登录票据,并设置过期时间。消费时使用 Lua 原子读删,防止同一个一次性凭据被重复使用。
这与浏览器里的状态管理不同。如果状态只保存在一个服务进程的内存里,进程重启就会丢失,多实例请求也不一定落到同一个进程。
这里的 Redis 用途很具体:保存短期授权状态。并不意味着项目已经把所有业务数据都缓存起来。
测试要对应真正担心的问题
表单项目中有规则、支付和 HTTP 等测试,可以对应不同问题:
| 担心的问题 | 适合的验证方式 |
|---|---|
| 隐藏字段、金额、状态分支是否正确 | 单元测试与规则用例 |
| 请求体解析、路由、响应是否正确 | HTTP 集成测试 |
| 联动配置能否通过接口保存并读取 | 连接数据库的集成测试 |
| 页面提交与业务操作能否连起来 | 前后端联调与流程验证 |
例如,签名测试检查不可信来源和隐藏字段携带签名的情况;支付宝测试覆盖金额冲突、重复通知和条件更新竞争分支;高级配置集成测试通过接口检查联动配置的持久化。
Mock 可以帮助检查分支和调用,数据库集成测试则能检查真实数据的保存与查询。如果要进一步验证真实并发下的锁和事务行为,还需要专门的数据库并发测试,不能把 Mock 中模拟的竞争当成完整证明。
测试范围还需要持续维护,已有用例无法保证所有未来改动都正确。
支付请求,要经过真实 HTTP 链路验证
表单项目保留微信 V2 XML 通知处理,也有 V3 JSON 通知入口。这两类请求的解析方式和校验条件不同,不能只把请求体当成一个普通对象来测试。
支付入口的 HTTP 测试会检查:XML 与 JSON 是否进入正确的处理路径、V3 原始请求体和签名头是否保留、缺少原始请求体时是否拒绝,以及验签失败如何响应。
这类测试把请求体解析和 Controller 一起纳入验证。即使 Service 单独调用看起来正确,请求经过解析中间件之后,也可能已经不符合验签要求。
对于习惯通过 Axios 发送 JSON 的前端开发者,外部平台回调是一个很好的提醒:接口还需要准确处理 Content-Type、请求头、原始数据和响应格式。
发布方式要与构建产物对应
项目有 GitLab CI/CD 配置,后端通过镜像、Helm 和 Kubernetes 发布,前端有独立的 COS/CDN 发布配置。
管理端、H5 和 API 独立发布,还需要核对前端使用的 API 地址、服务端跨域配置与微信授权回调地址。某个应用构建成功,不代表三个应用在目标环境已经能够完成同一条业务流程。
对于刚开始尝试全栈的前端开发者,不必先复刻这套基础设施。先确保环境配置、数据库迁移、日志、健康检查和发布后的关键流程验证有明确做法,再根据团队条件增加复杂度。
8. 给前端开发者的一条实践路径
回到我开始做这件事的背景:团队后端人手有限,我也想尝试新项目,于是从表单项目走出了第一步。AI 和 NestJS 提供了一个可操作的入口,让前端经验能够继续往服务端延伸。
如果你也想开始,可以先选一个自己熟悉的业务。报名表、反馈收集、简单后台都可以,范围小一些更容易看清整个过程。
建议按这个顺序推进:
- 跑通一次提交。 页面输入、接口接收、数据库保存、后台查询都能连接起来。
- 补齐规则。 明确认证、权限、格式与业务校验,并处理用户能理解的错误。
- 检查数据生命周期。 重复提交、修改、取消、已有数据兼容分别怎么处理。
- 接入外部服务。 根据需求逐步增加文件存储、授权或支付,了解回调与失败分支。
- 完成一次交付。 配置环境、执行迁移、发布,再从浏览器验证关键业务。
过程中可以持续使用 AI,但每完成一个关键功能,都试着回答:这段代码依赖哪些数据?允许哪些状态变化?失败后会留下什么?我怎样证明它做到了?
这个表单项目最值得整理的地方,就是这些具体问题。从动态字段到服务端规则,从页面上的支付结果到可信的交易证据,再到提交与核销的状态管理,前端熟悉的业务逐渐延伸成了完整的系统。
如果你正在尝试前端转全栈,欢迎分享自己选的第一个项目,以及最难跨过去的那个问题。