前端转 NestJS 全栈实践:从表单页面到微信业务系统

从表单平台到微信业务系统,我如何借助 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 提供了一个可操作的入口,让前端经验能够继续往服务端延伸。

如果你也想开始,可以先选一个自己熟悉的业务。报名表、反馈收集、简单后台都可以,范围小一些更容易看清整个过程。

建议按这个顺序推进:

  1. 跑通一次提交。 页面输入、接口接收、数据库保存、后台查询都能连接起来。
  2. 补齐规则。 明确认证、权限、格式与业务校验,并处理用户能理解的错误。
  3. 检查数据生命周期。 重复提交、修改、取消、已有数据兼容分别怎么处理。
  4. 接入外部服务。 根据需求逐步增加文件存储、授权或支付,了解回调与失败分支。
  5. 完成一次交付。 配置环境、执行迁移、发布,再从浏览器验证关键业务。

过程中可以持续使用 AI,但每完成一个关键功能,都试着回答:这段代码依赖哪些数据?允许哪些状态变化?失败后会留下什么?我怎样证明它做到了?

这个表单项目最值得整理的地方,就是这些具体问题。从动态字段到服务端规则,从页面上的支付结果到可信的交易证据,再到提交与核销的状态管理,前端熟悉的业务逐渐延伸成了完整的系统。

如果你正在尝试前端转全栈,欢迎分享自己选的第一个项目,以及最难跨过去的那个问题。

相关推荐
开开心心就好1 小时前
二维码批量生成导出工具,离线可用完全免费
java·前端·人工智能·智能手机·github·excel·visual studio
web打印社区2 小时前
Lodop 提示未安装或请升级:Chrome 里先分清该装哪套
开发语言·前端·javascript·chrome·websocket·http
重生之我复员之后重当黄毛2 小时前
vscode配置c/c++环境
c语言·前端·visual studio
COOLMO研究AI2 小时前
企业官网服务页的信息架构怎么设计:从用户问题到语义化 HTML
前端·css·html
Alice-YUE2 小时前
React 渲染机制速通:Render 树、Fiber 树与更新调度
前端·react.js·前端框架·react·fiber·前端性能·渲染机制
恋猫de小郭2 小时前
Meta 分享怎么用 AI 迁移 Compose 项目不烧心
android·前端·flutter
Ai-_Man3 小时前
请问豆包的智能体聊天记录该怎么弄
开发语言·前端·javascript·人工智能·小程序·ecmascript·电脑
艺杯羹3 小时前
Anthropic突袭发布Claude Code Mods!用TypeScript编写智能体中间件:告别AI误删与失控
人工智能·typescript·系统架构
IT_陈寒3 小时前
SpringBoot自动配置把我坑惨了:这些隐式规则要小心
前端·人工智能·后端