Files
observer/server/README.md
T

8.5 KiB
Raw Blame History

视野 后端服务

野生动物实时识别 App「视野」(Flutter 客户端见 ../flutter_app/)的支付与授权后端。

功能模块

模块 说明
账号 手机号+密码注册/登录(POST /api/v1/auth/register / login),密码 bcrypt 哈希存储,登录签发自签名 token(auth.secret),授权绑定手机号账号、同账号多设备共享
订单 客户端 GET /api/v1/plans 拉取套餐价格 → POST /api/v1/orders 创建订单并返回支付参数(微信 prepay / 支付宝 orderStr);未支付订单 2 小时惰性关闭,同账号 2 小时内复用 created 订单防重复扣款
支付回调 微信支付 / 支付宝异步回调验签落授权(/api/v1/payment 分组,按渠道应答格式直返,不做统一包装)
订单确认 客户端支付成功后 POST /api/v1/orders/{orderId}/confirm 幂等通知,加速授权刷新
授权查询 GET /api/v1/licenseBearer token)返回授权状态,App 识别入口强制服务端校验
套餐 config.yml plans 节点配置三档套餐(改价 = 改配置重启),价格整数分1000 / 5600 / 18000
后台管理端 server_admin/Vue3 + Element Plus)管理页面:订单查询、账号/授权管理(手动授权/撤销);构建产物由后端 /admin/ 托管,登录页输入 token 后以 X-Admin-Token 头鉴权(config.yml admin.token

架构与数据流

Flutter App ── POST /auth/register|login ─► 账号注册/登录,签发 token
     │
     ├─► POST /orders (Bearer token) ──► 后端下单(校验 planId/channel
     │        │
     │◄───────┘ orderId + payParams
     │
     ├─► 拉起微信/支付宝 SDK 支付
     │
     ├─► 微信/支付宝服务端异步回调 ─► 验签 → 落授权(幂等,绑账号)
     │
     └─► POST /orders/{id}/confirm (Bearer token) ─► 幂等确认(回调已落授权则直接返回)
     │
     └─► GET /license (Bearer token) ─► 返回 {active, expiresAt}
  • 授权以服务端为准:客户端本地缓存 expiresAt 仅用于主页展示,识别入口强制查询服务端
  • 有效期语义:自然日day 当天 24:00 失效;week/month 自生效日起自然日累计),服务端计算 expiresAt,客户端只读不判权

数据表

说明 关键字段
payment_order 支付订单 order_id(PK)、phone_numplan_idchannel(wechat/alipay)、amount_centsstatus(created/paid/closed)、wx_trade_no(UNIQUE)、alipay_trade_no(UNIQUE)、created_atpaid_at
license 手机号账号与授权 phone_num(PK)、password(bcrypt)、expires_at(未充值 NULL)、created_atupdated_at

建表与迁移见 技术设计.md(新库直接建表;存量库以 PRAGMA user_version 版本化迁移)。

API 清单

响应统一格式 {"code": 0, "message": "ok", "data": {...}}code != 0 视为失败。接口只允许 GET / POST。

POST /api/v1/auth/register

手机号注册(与登录无关的公开接口,无需 token)。请求:{"phone": "13800000000", "password": "..."}。已注册手机号返回错误。注册成功即创建账号(未充值,无额度)。

POST /api/v1/auth/login

手机号登录。请求:{"phone": "13800000000", "password": "..."}。响应 data: {"token": "..."}HMAC 自签名,有效期 auth.token_ttl,默认 30 天)。后续登录态接口携带 Authorization: Bearer <token>

GET /api/v1/plans

套餐列表(需 Bearer token)。响应 data

{
  "list": [
    {"planId": "day", "days": 1, "priceCents": 1000},
    {"planId": "week", "days": 7, "priceCents": 5600},
    {"planId": "month", "days": 30, "priceCents": 18000}
  ]
}

套餐来自 config.yml plans 节点(静态定价,改价改配置后重启服务生效);客户端不硬编码价格。展示名由客户端按 days 拼「N天」派生,无独立 label 字段。

POST /api/v1/orders

创建订单,返回渠道支付参数(需 Bearer token,手机号由 token 解出)。

请求(JSON body):

{"planId": "week", "channel": "wechat"}
  • channelwechat | alipay
  • planIdday | week | month(需在 config.yml plans 节点存在,金额由服务端按配置锁定,客户端不可改价)

响应 data

{
  "orderId": "O20260822001",
  "payParams": {
    "partnerId": "1900xxxxx",
    "prepayId": "wx...",
    "nonceStr": "...",
    "timeStamp": "1728000000",
    "sign": "...",
    "packageValue": "Sign=WXPay"
  }
}
  • channel == wechatpayParams 为微信 APP 支付统一下单返回参数(后端签名)
  • channel == alipaypayParams{"orderStr": "alipay_sdk=..."}(后端调 alipay.trade.app.pay 生成)

POST /api/v1/orders/{orderId}/confirm

客户端支付成功后通知(幂等,需 Bearer token)。服务端以异步回调为准落授权,confirm 仅用于加速刷新;回调已到账时直接返回成功。

请求:{}(无参数)。响应:data: {"status": "paid"|"created"}

返回 created(回调未到账)时客户端按 3s → 10s → 30s 递增重试 confirm(最多 3 次),期间禁止重新下单;仍未成功回退 GET /license 查询(回调到账后自然变为 active)。回调为唯一授权来源,confirm 仅加速刷新。

GET /api/v1/license

查询账号授权状态(需 Bearer token,手机号由 token 解出)。

响应 data

{"active": true, "expiresAt": "2026-08-29T23:59:59+08:00"}

active: falseexpiresAt 已过期(含未充值账号)→ 客户端展示付费墙/充值引导。

支付回调(微信/支付宝,勿直连)

渠道 路径 说明
微信支付(APP 支付 V3 POST /api/v1/payment/wechat/notify 解密+验签回调,out_trade_no → 落授权
支付宝(APP 支付) POST /api/v1/payment/alipay/notify RSA2 验签回调,out_trade_no → 落授权

管理端接口(/api/v1/admin,需请求头 X-Admin-Token = config.yml admin.token

方法 路径 说明
GET /admin/orders 订单列表,筛选参数 phoneNum/status/startAt/endAt + page/size,返回 {total, list}
GET /admin/licenses 账号/授权列表,筛选 phoneNum + page/size(含未充值账号)
POST /admin/licenses/grant 手动授权 {"phoneNum":"13800000000","planId":"day"},自然日叠加
POST /admin/licenses/revoke 撤销授权 {"phoneNum":"13800000000"}(清授权保留账号)

管理页面(订单/授权)由 server_admin/ 构建产物提供,访问 http://<host>/admin/。金额均为整数分,前端展示 ÷100 转元。

使用说明

  1. 配置 config.yml:监听端口、数据库路径、登录 token 签名密钥 auth.secret(必填,换值即全员下线)、套餐 plans 节点、微信支付(appid/mchid/商户私钥/证书序列号/APIv3 密钥)、支付宝(appid/应用私钥/支付宝公钥)、管理端 admin.token;SQLite 库由服务启动时自动建表并迁移,无需手工初始化
  2. go build ./... 编译验证
  3. 本地运行 go run main.go;服务层白盒测试:GF_GCFG_FILE=biz/service/testdata/config.yml go test ./biz/service/(独立测试库,见 biz/service/testdata/
  4. 后台管理端:cd server_admin && npm run build(构建产物输出到 server/admin_dist/,由后端 /admin/ 托管);开发联调 npm run devVite 代理 /api:8080)。首次访问 /admin/ 进入登录页,输入 config.yml admin.token 对应的管理 token(存浏览器 localStorage,随请求携带;token 不内嵌构建产物)
  5. Docker Compose 部署见 Dockerfile / docker-compose.yml./data 运行时数据目录挂载持久化,容器重建不丢数据)
  6. 客户端对接:flutter_app/lib/config/app_config.dartapiBaseUrl、微信/支付宝 AppID 与 URL Scheme 替换为真实值

金额与安全约定

  • 金额一律整数分price_cents / amount_cents 为 INT64),禁止浮点元;前端展示 ÷100
  • 支付回调必须验签(微信 APIv3 验签/解密、支付宝 RSA2),回调处理幂等wx_trade_no/alipay_trade_no 唯一约束,重复回调直接忽略)
  • 授权落库以较晚到期为准:已有授权未过期时新套餐顺延叠加,禁止覆盖缩短