对于后端这一块,耽误了好久,还特地去补习了 SQL。
这是 2018 年学习 Udacity 微信小程序课程时的笔记,使用的是当时的 Wafer / qcloud 方案。原稿里的 12 张截图没有恢复,商品、路由和请求的关系按文字保留,SQL 与订单分组则补成了可以单独阅读的示例。它记录课程架构,没有重新搭建当年的整套服务。
先把三个部分连起来
原来把后端分成了业务逻辑、路由、数据下载三部分。更准确地说,前两个主要在服务端,最后一个是小程序发起请求并读取结果:
- 业务逻辑决定要查什么数据、验证什么条件,以及怎样组织返回结果。
- 路由把 URL 和 HTTP 方法对应到处理函数。
- 客户端请求访问路由,检查返回结果,再更新页面。
课程中,控制器把数据放进 ctx.state.data,交给后面的响应处理逻辑包装。把值写进 state 本身,不等于已经向客户端发送了 HTTP 响应。
商品列表与详情
列表是从 product 表读取多条商品记录。详情则按商品 ID 查询一条记录。原稿提到“确保 ID 是数字”,这里还需要区分合法的整数 ID 和仅仅能够转成数字的输入。
整理时补充的校验例子:
1 | function parseProductId(raw) { |
如果数据库约定商品 ID 为正整数,这样可以拒绝负数、小数、空字符串和超出安全整数范围的值。具体规则应与实际表结构一致。
查询时绑定参数,例如:
1 | SELECT id, name, image, price |
不要把客户端传来的字符串直接拼进 SQL。数据库驱动如果返回数组,取得第一项之前,也应先判断有没有查到记录。商品不存在和数据库执行失败,是两种不同情况。
小程序怎样拿到结果
当年的课程用 qcloud.request 发起请求。路由、响应中间件和客户端约定共同决定返回格式;不能只凭 HTTP 状态码判断业务是否完成。
例如,当响应正文约定为下面这样的结构时:
1 | { |
HTTP 请求成功后,还要检查正文中的 code。使用原生 wx.request 时,响应正文在回调参数的 res.data 中;若使用 SDK 封装,则以该 SDK 回调拿到的对象为准。原稿直接写 res.code,缺少这层区分。
商品查询通常使用 GET。课程中的“立即购买”使用 POST,因为它会改变服务端状态。
购买与身份
原稿记录了购买操作依赖用户身份,因此在处理函数前使用验证中间件。服务端应当从已经验证的身份上下文取得用户标识,而不是相信请求体里自报的用户 ID。
这里的“立即购买”是课程业务动作的名称。原稿没有完整记录支付流程、库存和事务处理,不能因为接口返回成功,就把它描述成真实支付已经完成。当前保留下来的重点是路由、身份上下文和订单数据之间的关系。
三张表怎样连成订单列表
原来的订单查询使用三张表:
| 表 | 这里使用的字段 | 含义 |
|---|---|---|
order_user | id、user、create_time | 订单及所属用户 |
order_product | order_id、product_id、count | 订单中的商品明细 |
product | id、name、image、price | 商品资料 |
整理后的查询如下,问号绑定经过验证的用户标识:
1 | SELECT |
原稿简写 SQL 时出现了 order.product_id,这个别名并不存在;应当使用订单明细表中的 product_id。字段说明里也把 order_product.id 与商品 ID 混在了一起,这里改为明确的 order_product.product_id。
LEFT JOIN 会保留左表中的订单,即使没有匹配到商品明细。因此后面的代码要处理商品字段为空的情况。
另外,此查询取得的是 product 表里的当前价格。实际成交价通常需要在订单中保留快照,不能在商品改价之后,仍然用当前价格当作历史成交价格。原课程片段没有完整展示这一部分表结构。
把多行查询结果分组
原来的循环给订单重新编了从 1 开始的序号,还依赖同一订单的行始终连续。下面是本次整理补充的分组函数,保留真实订单 ID,并允许输入行不连续:
1 | function groupOrders(rows) { |
查询结果可以先交给 groupOrders,再由控制器放到课程约定的数据位置。这个函数只负责整理已经查到的数据,身份验证、数据库查询和响应包装仍由各自的部分完成。
本次检查了 ID 校验、SQL 连接关系与分组结果,没有重新接通微信登录和购买接口。
客户端响应结构可对照 微信小程序 wx.request 文档;订单表与课程调用方式保留的是原笔记中的背景。

