payment-rpc 已完成基础能力,但 cmd/app 还没有暴露用户侧支付接口。
# 任务
验收标准
流程
调用链
1
2
3
4
5
6
HTTP 请求
-> AuthMiddleware
-> Handler:解析参数
-> Logic:编排业务、做网关校验
-> mall-rpc:读取订单
-> payment-rpc:创建或查询支付其中,
.api用于定义HTTP协议handler负责解析请求,由goctl生成logic实现业务逻辑ServiceContext保存RPC客户端
实现API与Handler并生成
首先在mall.api中增加请求与响应的定义,然后在mall
service种增加支付相关的handler
这里如何定义来自三部分:
- 任务描述POST/GET的路由里只有id参数,因此req只需要接收路径参数
- 响应相关字段来自
payment.proto,决定了下游要返回哪些数据 - 写法参考现有
.api内容,决定了写法风格
使用make api-all通过goctl生成相关代码,包括handler、logic、routes和swagger生成的json
接入payment-rpc
在config.go中添加rpc配置,让payment-rpc接入
再在cmd/app/etc/config.yaml中配置rpc相关参数,包括etcd地址,兜底endpoint
最后在cmd/app/internal/svc/service_context.go中初始化客户端
总体逻辑
create_payment_logic.go中实现了创建支付的逻辑
- 首先获取已认证用户的user_id,然后使用
loadPaymentOrder()查询订单,这个方法单独写在了一个helper文件中:- 首先从鉴权中间件写入的ctx获取用户
- 接下来查询订单,传入请求时务必带上orderid,查询订单是否存在
- 接下来验证订单,验证订单用户是否正确,传入金额是否合法且正确,金额不能由前端提交,而是从mall-rpc查询订单并使用服务器保存的payamount,否则用户可以篡改金额
- 最后返回订单,调用paymentRPC创建订单
query_payment_logic.go实现了查询订单的逻辑,和创建类似,这里查询时不能直接拿URL中的id调用QueryPayment,因为RPC请求没有user_id,顺序如下:
1
2
3
4
查询本人订单
-> 确认订单归属和金额
-> QueryPayment(order_id)
-> 检查返回流水仍属于当前订单和用户返回时再次检查是否是同一订单,防止跨服务数据异常时把其他用户的流水返回
查询接口定义类似于:
1
2GET /api/mall/orders/abc123/pay/query Authorization: Bearer 用户A的Token这里abc123只是用户提交的参数,而token只能证明请求者是A,不能证明abc123属于请求者A,登录认证和授权是两码事
若网关直接写orderid=req.id,则rpc只知道有人要查询订单abc123,而不能直到当前用户是谁,订单属于谁,用户有无权限查询
这是典型的IDOR(越权访问)漏洞:服务器只检查资源id存不存在,却没检查资源属于谁
所以这种处理应该读取用户id,根据mall-rpc查询orderid+用户id,确认订单属于该用户,才调用rpc,并检查返回流水是否一致
综上:
URL、请求体、查询参数中的数据都不可信。 Token 只能证明用户是谁,不能自动证明用户拥有某个资源。 操作资源前,必须用“资源 ID + 当前用户 ID”做归属校验。
返回二维码信息
目前payment-rpc的queryresp并不返回二维码,实际行为是POST发起支付时返回二维码,而query的GET只有状态
如果要通过GET返回二维码,可以在创建支付时把返回的qrcode保存到支付流水,查询再从流水返回,调用链如下:
1
2
3
4
5
6
7
CreatePayment
-> 支付宝预下单
-> 保存 qr_code 到 payments 表
QueryPayment
-> 查询 payments 流水
-> 返回 status + qr_code- 在model层的payments添加qrcode相应字段
- 修改protobuf中的payment和query用到的resp
- 修改vars中的字段,并在query的逻辑层返回的响应添加对应参数
- 修改HTTP API类型,在mall.api的resp中添加对应字段
- 修改网关logic映射,在payment中添加qrcode
- 重新生成代码,并补充测试
测试
单测前三种样例成功,在cmd/app/internal/logic/test/下创建逻辑测试文件夹,测试:
接下来在生产环境中进行http层测试
/api/auth/code/send:
向邮箱发送验证码,传入body内容为email,返回200 OK
/api/auth/register:
注册渠道,提供邮箱,密码,用户名与验证码,传入body,返回200 OK
/api/auth/login/username:
用户名登录,提供用户名和密码,可以登录获取token
/api/mall/orders/:order_id/pay:
调用支付前,现从admin创建一个商品和seckill,方便用户创建订单
用户登录后,凭借token创建订单,获取订单id
调用支付接口,传入token鉴权,并传入订单id创建订单,创建成功后针对查询接口/api/mall/orders/:order_id/pay/query测试三点:
- 当前用户是否可以查询订单
- 当前用户是否可以查询非自己的订单
- 能不能对已支付的订单重复支付