# 移动端任务与账户平台前后端对接文档 > 版本:V1.0 > 更新日期:2026-10-09 > 适用项目:`olons_server` > 数据源:MySQL `olons_admin` 中的 `hg_*` 业务表 ## 1. 文档说明 本文档用于移动端与 Olons Server 后端联调。内容依据《移动端任务与账户平台需求文档》、当前代码实现和现有数据库表结构整理。 接口分为两类: - **已实现**:当前代码已经开放,可以直接联调。 - **待实现**:依据需求和现有数据模型定义的目标契约,不表示服务当前已经开放。 当前认证、个人中心、首页内容、任务订单和手工商城接口已实现。其余接口应按本文档契约逐步开发,实施时如果字段或状态发生调整,必须同步更新本文档后再联调。 ## 2. 能力范围 | 领域 | 状态 | 说明 | | --- | --- | --- | | 注册、登录 | 已实现 | 创建会员、资料、CNY 钱包并签发登录会话 | | 退出 | 已实现 | 撤销当前 Redis 会话 | | 个人资料、密码 | 已实现 | 会员概要、头像、登录密码和交易密码 | | 首页、内容、Banner、等级 | 已实现 | 只返回已发布且已生效的配置和内容 | | 任务、订单、任务提交 | 已实现 | 大厅、资格预检、领单、查询和凭证提交;审核结算由后台负责 | | 钱包、账户明细 | 已实现 | 钱包余额随会员资料返回;账户明细读取不可变流水表并支持分页、业务类型和收支方向筛选 | | 收款账户、取出 | 已实现(沙箱) | 加密绑定、试算、申请、资金冻结和记录查询;上线前仍需支付与合规评审 | | 储入 | 待实现 | 需在支付与合规评审后开放 | | 手工商城 | 已实现 | 已发布商品列表、分类筛选和详情;当前只提供客服咨询动作 | | 客服、通知、上传 | 待实现 | 部分数据表已存在 | ## 3. 公共协议 ### 3.1 基础路径 ```text {BASE_URL}/v1 ``` 接口不再包含应用名路径段,旧前缀不再提供兼容路由。 生产和测试环境均应使用 HTTPS。 ### 3.2 公共请求头 | Header | 必填 | 说明 | | --- | --- | --- | | `Content-Type: application/json` | JSON 请求必填 | 请求编码 | | `Accept: application/json` | 建议 | 响应编码 | | `X-Request-ID` | 建议 | 客户端生成 UUID,用于链路追踪 | | `X-Device-ID` | 建议 | 安装级随机标识,不使用 IMEI 等硬件标识 | | `Authorization: Bearer ` | 受保护接口必填 | 登录成功后取得的用户会话 | | `Idempotency-Key` | 取出提交必填 | 8–80 位请求唯一键;相同业务重试必须复用,不同请求禁止复用 | `tenant_id` 由后端部署配置确定。客户端不得在 Header、Query 或 Body 中传入租户身份。 ### 3.3 登录会话 当前 Token 是 32 字节随机数的 Base64URL 编码,不是 JWT。前端不得解析 Token 或从中推断用户信息。 - App 使用系统安全存储;Web 不在普通 LocalStorage 中长期保存敏感会话。 - Token 不得进入 URL、埋点、普通日志或错误上报。 - HTTP `401`:清除本地会话,保存当前页面地址并跳转登录。 - HTTP `403`:展示无权限或账户访问受限,不直接按登录过期处理。 - “记住密码”只属于客户端安全存储策略,不作为接口字段上传,严禁明文保存密码。 ### 3.4 时间、金额和比例 - 时间统一为 RFC 3339 UTC,例如 `2026-10-09T10:00:00.123Z`。 - 金额统一使用最小货币单位整数。CNY 的 `amount=29800` 表示 `298.00` 元。 - 比例统一使用万分比。`rate=500` 表示 `5%`。 - 前端不得使用浮点数参与资金计算,页面展示值不参与后端结算。 金额对象示例: ```json { "amount": 29800, "currency": "CNY" } ``` ### 3.5 成功响应 ```json { "code": 0, "data": {}, "request_id": "29ece743-7474-4f32-8141-31f994da68e9", "server_time": "2026-10-09T10:00:00.123Z" } ``` `data` 可为对象、列表或 `null`。前端同时判断 HTTP 状态码和业务 `code`。 ### 3.6 失败响应 ```json { "code": 30000, "reason": "PHONE_INVALID_FORMAT", "message": "validation failed", "details": { "field": "phone" }, "request_id": "29ece743-7474-4f32-8141-31f994da68e9", "server_time": "2026-10-09T10:00:00.123Z" } ``` | 字段 | 用途 | | --- | --- | | `code` | 稳定业务码,用于程序分支 | | `reason` | 稳定英文原因,用于本地化文案映射 | | `message` | 后端降级文案 | | `details` | 错误字段、等待时间等结构化详情 | | `request_id` | 客服和日志排查依据 | ### 3.7 分页 ```http GET /v1/task-orders?page=1&page_size=20&status=pending ``` - `page` 从 1 开始,默认 1。 - `page_size` 默认 20,最大 100。 - 默认按创建时间倒序。 - 排序字段必须使用后端白名单。 ```json { "code": 0, "data": { "items": [], "page": 1, "page_size": 20, "total": 0, "has_next": false }, "request_id": "uuid", "server_time": "2026-10-09T10:00:00Z" } ``` ### 3.8 重复提交 - 注册接口不使用 `Idempotency-Key`,由账户名、手机号等数据库唯一约束最终防重。 - 任务领取、任务提交、储入和取出等高风险写操作必须由后端使用业务单号、状态机和账务约束防止重复执行。 - 前端在写请求执行中禁用按钮;发生超时后先查询业务单状态,不盲目再次提交。 - 钱包入账以业务单号及 `hg_wallet_ledger.idempotency_key` 等后端约束为准。 ## 4. 认证接口 ### 4.1 注册(已实现) ```http POST /v1/auth/register Content-Type: application/json ``` 未登录可访问。 ```json { "username": "alice01", "phone": "13800138000", "password": "Secret123", "referral_code": "ABCD2345", "privacy_accepted": true } ``` | 字段 | 类型 | 必填 | 规则 | | --- | --- | --- | --- | | `username` | string | 是 | 1–10 位小写字母或数字;后端去空格并转小写 | | `phone` | string | 是 | 中国大陆 11 位手机号,不带 `+86` | | `password` | string | 是 | 6–72 字节,不做 MD5 | | `referral_code` | string | 否 | 1–32 位字母或数字,必须匹配正常会员邀请码 | | `privacy_accepted` | boolean | 是 | 必须为 `true` | 注册事务同时写入: - `hg_app_member`:会员主档。 - `hg_app_member_profile`:会员资料。 - `hg_wallet_account`:CNY 零余额钱包。 成功响应: ```json { "code": 0, "data": { "member_id": 9, "member_no": "MABCDEFGHIJKLMNOPQRST", "username": "alice01", "phone": "13800138000", "invite_code": "EFGH6789", "token": "random-base64url-token", "token_type": "Bearer", "expires_at": "2026-10-23T10:00:00Z" }, "request_id": "uuid", "server_time": "2026-10-09T10:00:00Z" } ``` 注册成功即自动登录。前端保存 Token 后直接进入首页,不再调用登录接口。 ### 4.2 登录(已实现) ```http POST /v1/auth/login Content-Type: application/json ``` ```json { "phone": "13800138000", "password": "Secret123" } ``` 成功响应与注册相同。连续登录失败达到 10 次时,该账号在 15 分钟窗口内被限流。 ### 4.3 退出(已实现) ```http POST /v1/auth/logout Authorization: Bearer ``` 后端删除当前会话,响应 `data=null`。缺少、格式错误或已失效的 Token 返回 HTTP 401 和 `AUTH_REQUIRED`。前端无论请求是否成功都清除本地凭证。 ### 4.4 认证错误 | HTTP | `reason` | 说明 | | --- | --- | --- | | 400 | `MALFORMED_REQUEST` | JSON 不合法、字段类型错误或存在未知字段 | | 400 | `USERNAME_INVALID_FORMAT` | 账户名格式错误 | | 400 | `PHONE_INVALID_FORMAT` | 手机号格式错误 | | 400 | `PASSWORD_INVALID_FORMAT` | 密码格式错误 | | 400 | `REFERRAL_CODE_INVALID` | 推荐码无效 | | 400 | `PRIVACY_POLICY_NOT_ACCEPTED` | 未同意隐私政策 | | 401 | `INVALID_CREDENTIALS` | 手机号或密码错误 | | 409 | `ACCOUNT_ALREADY_REGISTERED` | 账户名、手机号或其他唯一字段冲突 | | 429 | `RATE_LIMITED` | 登录失败次数过多 | | 503 | `SERVICE_UNAVAILABLE` | Redis 或敏感数据密钥未正确配置 | ## 5. 个人中心(已实现) ### 5.1 当前会员概要(已实现) ```http GET /v1/me ``` ```json { "code": 0, "data": { "member_id": 9, "member_no": "MABCDEFGHIJKLMNOPQRST", "username": "alice01", "phone": "13800138000", "avatar_url": "", "invite_code": "EFGH6789", "level": { "id": 1, "code": "normal", "name": "普通会员", "icon_url": "", "commission_rate": 180 }, "member_status": "active", "member_category": "real", "credit_score": 100, "wallet": { "available_amount": 0, "frozen_amount": 0, "total_income": 0, "total_expense": 0, "currency": "CNY", "status": "active", "version": 0 } }, "request_id": "uuid", "server_time": "2026-10-09T10:00:00Z" } ``` ### 5.2 更换头像(已实现) ```http PATCH /v1/me/avatar ``` ```json { "attachment_id": 101 } ``` 附件必须是 `app_id=olons_server`、归属当前会员、状态有效且 MIME 类型为图片的 `hg_sys_attachment` 记录。上传接口未实现前,前端不能自行生成可用附件 ID。 成功响应 `data`: ```json { "attachment_id": 101, "avatar_url": "https://cdn.example.com/avatar.png" } ``` ### 5.3 修改登录密码(已实现) ```http PUT /v1/me/password ``` ```json { "old_password": "OldSecret123", "new_password": "NewSecret456" } ``` 新密码为 6–72 字节且不能与当前密码相同。修改成功后撤销该会员在密码修改前签发的所有会话,包括当前会话;前端收到成功响应后清理 Token 并返回登录页。 ### 5.4 设置或修改交易密码(已实现) ```http PUT /v1/me/trade-password ``` ```json { "login_password": "Secret123", "old_trade_password": "123456", "new_trade_password": "654321" } ``` 交易密码固定为 6 位数字。首次设置时不传 `old_trade_password`;已设置时必须传入正确的旧交易密码。每次操作都需要 `login_password` 二次确认。 ### 5.5 个人中心错误 | HTTP | `reason` | 说明 | | --- | --- | --- | | 400 | `AVATAR_ATTACHMENT_INVALID` | 附件无效、非图片或不属于当前会员 | | 400 | `CURRENT_PASSWORD_INVALID` | 当前登录密码错误 | | 400 | `LOGIN_PASSWORD_INVALID` | 设置交易密码时二次确认的登录密码错误 | | 400 | `NEW_PASSWORD_INVALID` | 新登录密码格式错误或与原密码相同 | | 400 | `OLD_TRADE_PASSWORD_REQUIRED` | 已设置交易密码但未传旧密码 | | 400 | `OLD_TRADE_PASSWORD_INVALID` | 旧交易密码错误 | | 400 | `TRADE_PASSWORD_INVALID` | 新交易密码不是 6 位数字或与原密码相同 | | 404 | `ACCOUNT_NOT_FOUND` | 会员不存在或已停用 | | 409 | `ACCOUNT_CHANGED` | 会员安全信息已被并发修改,需重新操作 | ## 6. 首页和内容(已实现) ### 6.1 首页聚合(已实现) ```http GET /v1/home ``` ```json { "code": 0, "data": { "profile": { "username": "alice01", "avatar_url": "", "level_name": "普通会员" }, "wallet": { "available_amount": 100000, "frozen_amount": 0, "currency": "CNY" }, "features": { "task_enabled": true, "recharge_enabled": false, "withdrawal_enabled": false, "manual_mall_enabled": true }, "banners": [], "notices": [], "task_summary": { "today_count": 0, "today_commission_amount": 0, "yesterday_commission_amount": 0 } }, "request_id": "uuid", "server_time": "2026-10-09T10:00:00Z" } ``` `features` 由当前已发布的 `hg_site_config_version.config_json` 生成,前端不得写死。关闭的功能入口应显示后端提供的原因。 功能开关支持位于 `features` 对象内或配置根节点。找不到已发布且已到生效时间的配置时,所有开关默认为 `false`。 `task_summary.today_count` 按当日创建的任务订单统计;今日和昨日佣金仅统计 `hg_task_submission.submission_status=4` 的已结算数据。 ### 6.2 Banner(已实现) ```http GET /v1/banners?position=home_top ``` `position` 默认为 `home_top`,只允许小写字母、数字、下划线和连字符。接口只返回已发布、处于投放时间内且未删除的 Banner。 Banner 跳转类型: | 数据库值 | API `target_type` | 处理 | | --- | --- | --- | | 1 | `none` | 不跳转,不返回 `target_value` | | 2 | `internal_route` | 只接受单斜线开头的站内路由 | | 3 | `external_url` | 只接受带有有效域名的 HTTPS URL | 不安全或不识别的跳转值降级为 `none`。 ### 6.3 内容详情(已实现) ```http GET /v1/contents/{content_key} ``` `privacy_policy` 和 `user_agreement` 允许未登录访问,其他内容需要 Bearer Token。目前数据库中尚无这两个公开内容键,在后台发布前请求会返回 `404 RESOURCE_NOT_FOUND`。 内容详情建议字段: ```json { "content_key": "privacy_policy", "content_type": "policy", "title": "用户隐私政策", "summary": "", "body_html": "

...

", "body_text": "...", "version": 3, "published_at": "2026-10-09T08:00:00Z" } ``` `asset_ids` 始终返回数组。`body_html` 来自后台已清洗的发布版本;前端仍需禁止脚本、事件属性和危险 URL scheme。 ### 6.4 会员等级(已实现) ```http GET /v1/member-levels ``` 只返回已启用等级及其当前已发布、已生效版本,按后台 `sort` 排序。 列表项: ```json { "id": 1, "code": "VIP1", "name": "普通会员", "icon_url": "", "upgrade_amount": 50000, "gift_amount": 2000, "daily_order_limit": 10, "commission_rate": 180, "daily_withdraw_limit": 4, "rules": {}, "version": 1 } ``` ### 6.5 荣誉榜(已实现) ```http GET /v1/leaderboard ``` 接口要求登录,返回当前租户内已完成任务数量排名前 50 的有效会员。排名只统计 `hg_trade_order.order_type=2 AND order_status=4` 的真实任务订单;昵称由服务端脱敏,前端不得还原或展示手机号。 ```json { "code": 0, "data": [ { "rank": 1, "username": "风***淡", "avatar_url": "https://cdn.example.com/avatar.png", "completed_count": 12580, "status": "领取成功" } ] } ``` 没有已完成任务时返回空数组,不使用模拟排行数据填充页面。 ### 6.6 首页和内容错误 | HTTP | `reason` | 说明 | | --- | --- | --- | | 400 | `BANNER_POSITION_INVALID` | Banner 位置参数格式错误 | | 400 | `CONTENT_KEY_INVALID` | 内容键格式错误 | | 404 | `RESOURCE_NOT_FOUND` | 内容不存在、未发布或未生效 | ## 7. 任务和订单(已实现) ### 7.1 接口清单 | 方法 | 接口 | 用途 | | --- | --- | --- | | GET | `/v1/task-halls` | 当前等级可访问的任务大厅 | | GET | `/v1/tasks/eligibility?hall_id=1` | 领取资格预检 | | GET | `/v1/tasks/stats` | 总资产、今日/昨日佣金、领取数量 | | POST | `/v1/task-orders/claim` | 领取或匹配任务 | | GET | `/v1/task-orders` | 订单分页 | | GET | `/v1/task-orders/{order_id}` | 订单详情 | | POST | `/v1/task-orders/{order_id}/submissions` | 提交任务证据 | 本章接口全部要求登录。数据来源为 `hg_task_hall`、`hg_task`、`hg_trade_order`、`hg_task_submission`、`hg_wallet_account` 和 `hg_wallet_ledger`,仅处理 `order_type=2` 的任务订单,不会混入手工商城订单。 任务开关和领单规则读取当前已发布且已生效的 `hg_site_config_version.config_json`。支持以下配置键(可放在根节点或嵌套对象中): | 配置键 | 含义 | 未配置时 | | --- | --- | --- | | `task_enabled` | 任务功能开关 | `false`,关闭领单 | | `task_minimum_amount` | 最低可用余额,单位分 | `0` | | `task_business_start_at` | 营业开始时间,格式 `HH:mm` 或 `HH:mm:ss` | `00:00:00` | | `task_business_end_at` | 营业结束时间,格式 `HH:mm` 或 `HH:mm:ss` | `23:59:59` | | `task_deadline_minutes` | 订单完成时限,分钟 | `15` | 开关采用失败关闭策略:找不到有效配置或没有 `task_enabled=true` 时,可以读取大厅、统计及历史订单,但资格预检返回 `TASK_DISABLED`,领单被拒绝。 ### 7.2 领取资格预检 ```http GET /v1/tasks/eligibility?hall_id=1 ``` ```json { "code": 0, "data": { "eligible": false, "reason": "INSUFFICIENT_BALANCE", "message": "可用余额不足", "available_amount": 10000, "minimum_amount": 50000, "today_count": 2, "daily_limit": 5, "pending_order_id": 0, "business_start_at": "09:00:00+08:00", "business_end_at": "22:00:00+08:00" }, "request_id": "uuid", "server_time": "2026-10-09T10:00:00Z" } ``` 预检包括营业时间、会员状态、钱包状态、最低余额、当日数量、未完成订单及等级范围。预检只供展示,领取时后端必须在事务内再次校验。 判断顺序为:任务开关、营业时间、钱包状态、最低余额、等级每日上限、未完成订单。`risk_status` 是会员类别(`1=真人`、`2=假人`),不是风控状态,不能据此拒绝领单。 ### 7.3 领取任务 ```http POST /v1/task-orders/claim ``` ```json { "hall_id": 1 } ``` 成功返回订单摘要,包括 `id`、`order_no`、`status`、`product`、`total_amount`、`commission_amount`、`currency` 和 `deadline_at`。 领单事务会锁定当前会员和钱包记录,重新校验大厅等级、账户状态、最低余额、会员等级每日领取次数和未完成订单。校验通过后先创建 `status=matching`、`order_type=2` 的派单中订单,此时商品、数量、金额和佣金均为空或 `0`,与后台订单列表的“派单中/匹配订单”原型一致。会员的 `auto_dispatch=1` 时,达到网站设置的 `autoOrderDelaySeconds` 延时后由系统自动匹配:从当前已发布、在有效期内、币种为 CNY,且价格同时满足会员可用余额与 `autoOrderMinAmount`/`autoOrderMaxAmount` 区间的 `hg_product` 中随机选取商品,按会员当前等级版本计算佣金,并将订单原子更新为 `status=pending`。匹配成功时在同一事务内从可用余额扣除订单本金并转入冻结余额,同时生成可用余额支出和冻结余额收入流水。订单完成时释放冻结本金、返回可用余额并另行入账佣金;订单超时时也必须释放并退回本金。`auto_dispatch=2` 或自动匹配暂时找不到商品时,订单保持“派单中”,管理员可在后台通过“匹配订单”人工指定商品,人工匹配也执行相同的本金扣除和冻结流程。自动和人工匹配共用同一状态条件,并发时只有一方能更新成功。新注册会员默认开启自动派单,管理员可在会员编辑弹窗修改派单模式。 | HTTP | `reason` | 说明 | | --- | --- | --- | | 409 | `PENDING_ORDER_EXISTS` | 存在未完成订单 | | 422 | `OUTSIDE_BUSINESS_HOURS` | 不在营业时间 | | 422 | `INSUFFICIENT_BALANCE` | 可用余额不足 | | 422 | `DAILY_LIMIT_REACHED` | 已达当日上限 | | 422 | `LEVEL_NOT_ELIGIBLE` | 等级不符合 | | 422 | `ACCOUNT_RESTRICTED` | 账户状态受限 | | 422 | `TASK_DISABLED` | 任务开关未开启或没有已生效配置 | | 503 | `NO_TASK_AVAILABLE` | 当前没有可匹配任务 | ### 7.4 订单列表 ```http GET /v1/task-orders?page=1&page_size=20&status=pending ``` `status` 可选:`all`、`matching`、`pending`、`processing`、`completed`、`expired`、`frozen`、`cancelled`、`failed`。 ```json { "items": [ { "id": 101, "order_no": "TO202610090001A1B2C3D4", "task_id": 1, "product": { "id": 20, "name": "示例商品", "cover_url": "https://cdn.example.com/product.png" }, "quantity": 1, "unit_price_amount": 29800, "total_amount": 29800, "commission_amount": 1490, "currency": "CNY", "status": "pending", "status_text": "待处理", "message": "请在截止时间前完成", "created_at": "2026-10-09T10:00:00Z", "updated_at": "2026-10-09T10:01:00Z", "deadline_at": "2026-10-09T10:15:00Z" } ], "page": 1, "page_size": 20, "total": 1 } ``` ### 7.5 订单详情与提交 订单详情额外返回任务说明、商品和规则快照、状态时间线、失败原因、关联账变及 `allowed_actions`。倒计时使用响应 `server_time` 与 `deadline_at` 的差值。 ```http POST /v1/task-orders/{order_id}/submissions ``` ```json {} ``` 成功返回 `submission_no`、`status=settled`、`submitted_at` 和 `order_status=completed`。重复提交不得生成多笔提交记录或佣金流水。 正常情况下仅 `pending` 订单允许提交。前端按原型提供“立即提交”确认操作,不要求用户填写凭证。接口仍兼容可选的 `evidence_text` 和 `evidence_attachment_ids`:文字最长 2000 字符、附件最多 9 个;附件必须属于当前租户和当前会员、`app_id=olons_server`、状态有效,且类型为图片或 PDF。提交事务对订单、钱包加锁,超时订单自动置为 `expired`;有效提交创建唯一的已结算记录、佣金流水并增加可用余额,最后把订单置为 `completed`。后台审核页保留已结算记录供查询,冻结、强制处理和人工补偿只作为异常兜底。 订单详情返回: - `task`:任务标题、说明和详情 HTML; - `rule_snapshot`:领单时固化的佣金、等级和截止时间; - `submission`:凭证、审核状态与驳回原因; - `timeline`:创建、匹配、提交、审核、结算、完成时间点; - `ledgers`:以订单号或提交号关联的钱包流水; - `allowed_actions`:待处理为 `submit`、`contact_support`,超时/冻结/失败仅为 `contact_support`;提交成功后不再提供重复提交操作。 ### 7.6 任务统计 ```http GET /v1/tasks/stats ``` ```json { "available_amount": 100000, "frozen_amount": 29800, "total_asset_amount": 129800, "today_commission_amount": 1490, "yesterday_commission_amount": 1000, "today_claim_count": 2, "today_completed_count": 1 } ``` 佣金只统计 `hg_task_submission.submission_status=4` 的已结算记录,今日领取和完成数量只统计任务订单。日期边界按数据库 UTC 日期计算。 ### 7.7 通用校验错误 | HTTP | `reason` | 说明 | | --- | --- | --- | | 400 | `HALL_ID_INVALID` | 大厅 ID 缺失或格式错误 | | 400 | `ORDER_ID_INVALID` | 订单 ID 格式错误 | | 400 | `PAGE_INVALID` / `PAGE_SIZE_INVALID` | 分页参数错误,`page_size` 最大 100 | | 400 | `ORDER_STATUS_INVALID` | 状态筛选值不在允许范围内 | | 400 | `EVIDENCE_INVALID` | 凭证内容或附件不符合要求 | | 404 | `ORDER_NOT_FOUND` | 订单不存在或不属于当前会员 | | 409 | `ORDER_STATE_INVALID` | 当前状态不允许提交或已经提交过 | | 409 | `ORDER_EXPIRED` | 提交时订单已超时 | ## 8. 手工商城(已实现) | 方法 | 接口 | 说明 | | --- | --- | --- | | GET | `/v1/manual-products?page=1&page_size=20&category_id=0` | `product_type=2` 的已发布商品 | | GET | `/v1/manual-products/{product_id}` | 商品详情和可执行动作 | 两个接口均要求登录,只返回同时满足以下条件的商品: - `hg_product.product_type=2`; - 商品 `publish_status=3`,未被软删除且在展示有效期内; - 所属 `hg_product_category.status=1`,并且分类未被软删除。 ### 8.1 商品列表 ```http GET /v1/manual-products?page=1&page_size=20&category_id=0 ``` `category_id=0` 或不传表示全部启用分类,指定正整数时按分类筛选。推荐商品优先,再按后台 `sort` 和商品 ID 排序。响应示例: ```json { "items": [ { "id": 4, "product_no": "DEMO-P-03", "category": { "id": 2, "code": "DEMO_DAILY", "name": "演示日用百货" }, "name": "已发布演示商品", "shop_name": "Olons 演示店", "price_amount": 12900, "currency": "CNY", "cover_url": "https://cdn.example.com/product.png", "summary": "商品说明摘要", "claimed_count": 1, "is_recommended": true, "is_limited": true, "start_at": "2026-10-01T00:00:00Z", "end_at": "2026-10-31T23:59:59Z", "available_actions": ["contact_support"] } ], "page": 1, "page_size": 20, "total": 1 } ``` 金额单位为分。现有 `hg_product` 没有独立摘要字段,`summary` 由商品详情转换为纯文本后截取前 120 个字符。`claimed_count` 统计该商品 `order_type=3` 且状态不是已取消或失败的订单。`is_limited` 在商品配置了 `end_at` 时为 `true`,客户端使用该字段区分限时商品,不根据分类名称推断;`start_at`、`end_at` 为商品展示有效期。 ### 8.2 商品详情 ```http GET /v1/manual-products/4 ``` 详情在列表字段基础上增加: ```json { "payment_type": "balance", "detail_text": "已发布演示商品,用于展示。", "created_at": "2026-10-05T14:32:29Z", "updated_at": "2026-10-08T14:32:29Z" } ``` 当前版本只支持用户主动咨询,不创建商城订单,也不扣款或冻结余额,因此 `available_actions` 固定返回 `["contact_support"]`。后续开放购买时必须另行增加下单接口、库存校验、幂等与资金事务,不能复用咨询动作直接扣款。 运营详情不会以原始 HTML 返回。后端移除标签、脚本和样式内容后输出 `detail_text`,避免持久型 XSS;`cover_url` 仅接受不包含用户凭据的 HTTPS 地址,不符合要求时返回空字符串。 ### 8.3 错误码 | HTTP | `reason` | 说明 | | --- | --- | --- | | 400 | `CATEGORY_ID_INVALID` | 分类 ID 不是非负整数 | | 400 | `PRODUCT_ID_INVALID` | 商品 ID 缺失或格式错误 | | 400 | `PAGE_INVALID` / `PAGE_SIZE_INVALID` | 分页参数错误,`page_size` 最大 100 | | 404 | `PRODUCT_NOT_FOUND` | 商品不存在、未发布、已过展示期、已删除或分类已停用 | ## 9. 钱包和明细(待实现) ### 9.1 钱包概要 ```http GET /v1/wallet ``` ```json { "code": 0, "data": { "available_amount": 100000, "frozen_amount": 29800, "total_income": 50000, "total_expense": 10000, "currency": "CNY", "status": "active", "version": 12 }, "request_id": "uuid", "server_time": "2026-10-09T10:00:00Z" } ``` ### 9.2 账户明细 ```http GET /v1/wallet/ledgers?page=1&page_size=20&biz_type=task&direction=income ``` ```json { "id": 1001, "ledger_no": "WL202610090001", "biz_type": "task", "biz_no": "TO202610090001", "entry_type": "commission", "direction": "income", "amount": 1490, "before_amount": 100000, "after_amount": 101490, "currency": "CNY", "remark": "任务佣金", "occurred_at": "2026-10-09T10:30:00Z", "resource": { "type": "task_order", "id": 101 } } ``` 钱包流水只能由后端业务事务生成,移动端没有新增、修改或删除流水的接口。 ## 10. 储入(待实现) 本组接口在支付通道、收款主体和合规评审完成前只允许沙箱联调。 | 方法 | 接口 | 用途 | | --- | --- | --- | | GET | `/v1/recharge/channels` | 可用通道、金额范围和手续费规则 | | POST | `/v1/recharge-orders` | 创建储入订单 | | GET | `/v1/recharge-orders` | 储入记录分页 | | GET | `/v1/recharge-orders/{order_id}` | 储入详情 | 创建请求: ```json { "channel_id": 1, "amount": 50000, "currency": "CNY", "voucher_attachment_ids": [], "remark": "" } ``` 响应包含 `recharge_no`、`status`、`request_amount`、`payable_amount`、`currency`、`expire_at` 和经过校验的支付指引。不通过客服聊天发送临时个人收款账户。 公开状态:`pending_payment`、`reviewing`、`processing`、`succeeded`、`failed`、`expired`、`cancelled`。当前人工审核流程创建后直接进入 `reviewing`;`pending_payment` 仅为后续接入真实支付通道和支付回调时保留。失败、驳回或过期必须返回 `reason_code` 和 `reason_text`。 ## 11. 收款账户和取出(已实现,沙箱) 本组接口已完成代码和现有数据库的读接口联调。在支付通道、付款主体、实名要求、反洗钱策略和法务评审完成前,只允许沙箱使用,不得连接真实付款通道。 取出功能读取当前已发布且已生效的 `hg_site_config_version.config_json`。仅当根节点或嵌套对象中的 `withdrawal_enabled=true` 时允许试算和提交;没有有效配置时按失败关闭策略返回 `WITHDRAWAL_DISABLED`。收款账户查询与绑定不受该开关影响。 ### 11.1 收款账户 ```http GET /v1/payment-accounts POST /v1/payment-accounts POST /v1/attachments/images ``` 先分别上传微信和支付宝二维码。图片接口使用 `multipart/form-data`,字段名为 `file`,仅接受不超过 5MB 的 JPG、PNG、WebP,返回附件 `id` 和可访问的 `url`。随后提交新增请求: ```json { "bank_name": "中国工商银行", "holder_name": "张三", "identity_no": "11010119900101123X", "account_no": "6222000000001234", "wechat_qr_attachment_id": 101, "alipay_qr_attachment_id": 102 } ``` 绑卡页面按原型固定为银行卡账户,字段规则如下: | 字段 | 是否必填 | 说明 | | --- | --- | --- | | `holder_name` | 是 | 2–64 个字符 | | `identity_no` | 是 | 15 位或 18 位身份证号,18 位末位允许 X | | `account_no` | 是 | 8–32 位数字,空格和连字符会被移除 | | `bank_name` | 是 | 2–100 个字符 | | `wechat_qr_attachment_id` | 是 | 当前会员上传的有效图片附件 | | `alipay_qr_attachment_id` | 是 | 当前会员上传的有效图片附件,不能与微信二维码共用同一附件 | 姓名、身份证号和银行卡号使用 AES-256-GCM 独立随机 nonce 加密,身份证号和银行卡号另存 HMAC-SHA256 检索哈希。列表只返回脱敏信息,绝不返回明文或密文。绑定不要求支付密码;支付密码只在取出提交时校验。当前产品不设置银行卡后台审核流程,绑定成功后直接写入 `verify_status=verified`,可立即用于取出。 列表项示例: ```json { "id": 1, "account_type": "bank_card", "bank_code": "BANK", "bank_name": "中国工商银行", "holder_mask": "张*", "identity_no_mask": "1101**********123X", "account_no_mask": "6222********1234", "wechat_qr_attachment_id": 101, "alipay_qr_attachment_id": 102, "verify_status": "verified", "is_default": true, "created_at": "2026-10-09T10:00:00Z", "updated_at": "2026-10-09T10:00:00Z" } ``` ### 11.2 取出试算与提交 ```http POST /v1/withdrawal-orders/quote POST /v1/withdrawal-orders GET /v1/withdrawal-orders?page=1&page_size=20&status=all GET /v1/withdrawal-orders/{order_id} ``` 试算请求: ```json { "payment_account_id": 1, "amount": 50000, "currency": "CNY" } ``` 试算根据已核验收款账户选择启用且支持取出的 `hg_payment_channel`。优先匹配账户类型对应通道,不存在时使用人工通道。手续费支持固定金额、万分比和两者相加三种模式;比例手续费向上取整到分。 试算返回金额、渠道、可用余额、当日次数、额度范围和 5 分钟后的 `quote_expires_at`,但不冻结余额: ```json { "payment_account_id": 1, "account_mask": "6222********1234", "channel_id": 3, "channel_code": "bank", "channel_name": "银行卡", "request_amount": 50000, "fee_amount": 100, "actual_amount": 49900, "currency": "CNY", "minimum_amount": 1, "maximum_amount": 100000, "available_amount": 100000, "today_count": 0, "daily_limit": 4, "quote_expires_at": "2026-10-09T10:05:00Z" } ``` 提交请求: ```http POST /v1/withdrawal-orders Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 Content-Type: application/json ``` ```json { "payment_account_id": 1, "amount": 50000, "currency": "CNY", "trade_password": "654321" } ``` 后端不信任试算结果,提交时重新读取通道并计算手续费和到账金额。`Idempotency-Key` 会结合租户和会员派生稳定业务单号;相同请求重试返回原订单并标记 `idempotent_replay=true`,同一个键用于不同账户、金额或币种时返回 `IDEMPOTENCY_CONFLICT`,不依赖额外幂等记录表。 提交事务执行以下操作: 1. 锁定会员,确认账户有效、交易密码未变化; 2. 校验收款账户属于当前会员且已核验; 3. 锁定并重新校验支付通道规则; 4. 锁定 CNY 钱包,检查钱包状态、可用余额及会员等级每日取出次数; 5. 创建 `pending_review` 取出订单和加密收款账户快照; 6. 可用余额减少申请金额,冻结余额增加申请金额; 7. 分别写入可用余额支出和冻结余额收入两条不可变流水。 订单、钱包和两条流水在同一数据库事务内提交,任一步失败全部回滚。提交阶段不增加 `total_expense`;后台付款成功时才把冻结金额转为最终支出,失败、驳回或取消时必须解冻退回。后台状态处理不属于本章客户端接口。 公开状态为 `pending_review`、`reviewing`、`processing`、`succeeded`、`failed`、`rejected`、`cancelled`。列表支持上述状态过滤和标准分页;详情、列表均返回脱敏收款快照、金额、手续费、状态、失败原因和各状态时间,不返回 `account_snapshot_cipher`。 ### 11.3 错误码 | HTTP | `reason` | 说明 | | --- | --- | --- | | 400 | `ACCOUNT_TYPE_INVALID` | 收款账户类型不支持 | | 400 | `PAYMENT_ACCOUNT_FIELDS_INVALID` | 动态必填字段或格式错误 | | 400 | `TRADE_PASSWORD_INVALID` | 交易密码格式或内容错误 | | 400 | `CURRENCY_INVALID` | 当前仅支持 CNY | | 400 | `IDEMPOTENCY_KEY_INVALID` | 取出提交未提供合规的幂等键 | | 400 | `WITHDRAWAL_STATUS_INVALID` | 列表状态筛选值错误 | | 409 | `PAYMENT_ACCOUNT_EXISTS` | 相同类型和账号已经绑定 | | 409 | `IDEMPOTENCY_CONFLICT` | 同一幂等键被用于不同请求 | | 422 | `TRADE_PASSWORD_NOT_SET` | 尚未设置交易密码 | | 422 | `PAYMENT_ACCOUNT_INVALID` | 收款账户不存在、已删除或未核验 | | 422 | `WITHDRAWAL_DISABLED` | 取出功能未开启 | | 422 | `WITHDRAWAL_AMOUNT_INVALID` | 金额不在通道范围、超过余额或手续费不合理 | | 422 | `WITHDRAWAL_DAILY_LIMIT_REACHED` | 已达到会员等级每日次数上限 | | 422 | `INSUFFICIENT_BALANCE` | 可用余额不足 | | 422 | `ACCOUNT_RESTRICTED` | 会员或钱包状态受限 | | 429 | `RATE_LIMITED` | 交易密码连续错误次数过多,15 分钟后重试 | | 503 | `WITHDRAWAL_CHANNEL_UNAVAILABLE` | 没有可用的取出通道 | ## 12. 客服、通知和上传 ### 12.1 客服(已实现) ```http GET /v1/customer-services ``` 响应字段包括 `id`、`service_code`、`name`、`avatar_url`、`work_time`、`online_status`、`service_url`、`backup_contact` 和 `privacy_notice`。 只返回 `hg_customer_service.status=1` 且未删除的客服,按 `sort`、`id` 排序。`online_status` 映射为 `online`、`offline` 或 `busy`。头像和客服地址仅返回不含用户凭据的 HTTPS URL,不符合要求时返回空字符串。 客服 URL 必须经服务端域名白名单校验。前端 WebView/iframe 只开启必要权限,并显示第三方服务来源和隐私提示。 ### 12.2 通知 ```http GET /v1/notices?page=1&page_size=20 GET /v1/notices/{notice_id} POST /v1/notices/{notice_id}/read ``` 当前已实现通知列表接口。列表包含当前会员通知和 `member_id=0` 的全体通知,只返回有效期内且未撤回记录,按创建时间倒序排列。 ```json { "items": [ { "id": 1, "title": "系统维护通知", "content": "通知内容", "source_type": "real", "start_at": "2026-10-13T00:00:00Z", "end_at": "2026-10-20T00:00:00Z", "created_at": "2026-10-13T00:00:00Z" } ], "page": 1, "page_size": 20, "total": 1 } ``` `source_type` 为 `real`(系统通知)或 `operational_simulation`(活动通知)。详情和已读接口尚未实现;当前数据库没有会员通知已读关系表,如需精确已读状态,实现前应补充数据模型。 ### 12.3 上传 ```http POST /v1/uploads ``` ```json { "scene": "task_evidence", "file_name": "evidence.jpg", "mime_type": "image/jpeg", "size": 102400 } ``` 推荐流程为“申请凭证 → 上传隔离区 → 安全扫描 → 绑定业务”。业务接口只接受扫描通过且属于当前会员的附件 ID。 | 场景 | 类型 | 建议限制 | | --- | --- | --- | | `avatar` | JPEG、PNG、WebP | 5 MiB,校验像素尺寸 | | `task_evidence` | JPEG、PNG、WebP、PDF | 单文件 10 MiB,最多 9 个 | | `payment_qr` | JPEG、PNG、WebP | 5 MiB,内容校验 | | `recharge_voucher` | JPEG、PNG、WebP、PDF | 单文件 10 MiB | ## 13. 状态枚举 数据库数字状态是内部实现。API 对前端返回稳定字符串和 `status_text`,前端不得依赖数据库数值。 `hg_app_member.risk_status` 字段名沿用现有数据库,实际业务含义是会员类别:`1` 真人、`2` 假人。API 统一使用 `member_category=real|fake`,不将该字段用于判断账户是否可登录。 | 领域 | API 状态 | | --- | --- | | 会员 | `active`、`disabled`、`cancelled` | | 会员类别 | `real`、`fake` | | 任务订单 | `matching`、`pending`、`processing`、`completed`、`expired`、`frozen`、`cancelled`、`failed` | | 任务提交 | `reviewing`、`approved`、`rejected`、`settled`、`reversed` | | 储入 | `pending_payment`、`reviewing`、`processing`、`succeeded`、`failed`、`expired`、`cancelled` | | 取出 | `pending_review`、`reviewing`、`processing`、`succeeded`、`failed`、`rejected`、`cancelled` | | 收款账户核验 | `pending`、`verified`、`rejected` | | 客服 | `online`、`offline`、`busy` | 已实现领域按本表映射;待实现领域在开发前仍需与 HotGo 字典和正式状态流转确认。 ## 14. 通用错误与重试 | HTTP | `reason` | 前端处理 | | --- | --- | --- | | 400 | `MALFORMED_REQUEST` | 根据 `details.field` 定位表单字段 | | 401 | `AUTH_REQUIRED` | 清除会话并跳转登录 | | 403 | `FORBIDDEN` | 显示无权限或账户访问受限 | | 404 | `RESOURCE_NOT_FOUND` | 显示内容下线或记录不存在 | | 405 | `METHOD_NOT_ALLOWED` | 作为开发错误记录,不重试 | | 409 | `CONFLICT` | 刷新业务资源后让用户决定下一步 | | 422 | 具体业务原因码 | 显示原因和恢复入口 | | 429 | `RATE_LIMITED` | 按 `retry_after_seconds` 倒计时 | | 500 | `INTERNAL` | 展示通用错误和 `request_id` | | 503 | `SERVICE_UNAVAILABLE` | 安全读请求可限次重试 | - GET 在网络错误或 503 时可指数退避,最多自动重试 2 次。 - POST、PUT、PATCH 默认不自动重试;超时后先查询业务资源。 - 400、401、403、404、409、422 不做网络层自动重试。 ## 15. 页面与接口映射 | 页面 | 首屏接口 | 操作接口 | | --- | --- | --- | | `/login` | — | `POST /auth/login` | | `/register` | `GET /contents/privacy_policy` | `POST /auth/register` | | `/home` | `GET /home` | Banner、快捷入口、内容详情 | | `/receive` | `GET /tasks/eligibility`、`GET /tasks/stats` | `POST /task-orders/claim` | | 记录页 | `GET /task-orders` | 跳转订单详情 | | `/orderDetail` | `GET /task-orders/{id}` | `POST /task-orders/{id}/submissions` | | `/mall` | `GET /manual-products` | 查看详情或客服 | | `/customerService` | `GET /customer-services` | 打开安全 WebView/iframe | | `/mine` | `GET /me` | 钱包、收款账户、密码、通知、退出 | | `/recharge` | `GET /recharge/channels`、`GET /member-levels` | `POST /recharge-orders` | | `/withdrawal` | `GET /wallet`、`GET /payment-accounts` | 试算、提交取出 | | `/depositRecord` | `GET /recharge-orders` | 查看详情 | | `/withdrawalRecord` | `GET /withdrawal-orders` | 查看详情 | | `/bill` | `GET /wallet/ledgers` | 跳转关联业务详情 | 表中省略公共前缀 `/v1`。 ## 16. API 与数据表映射 | 领域 | 主表 | 关联表 | | --- | --- | --- | | 认证、会员资料 | `hg_app_member` | `hg_app_member_profile`、`hg_app_member_level` | | 收款账户 | `hg_app_member_payment_account` | `hg_sys_attachment` | | 钱包 | `hg_wallet_account` | `hg_wallet_ledger` | | 首页配置 | `hg_site_config_version` | `hg_banner`、`hg_content`、`hg_content_version` | | 会员等级 | `hg_app_member_level` | `hg_app_member_level_version` | | 任务大厅 | `hg_task_hall` | `hg_task_hall_level` | | 任务订单 | `hg_task`、`hg_trade_order` | `hg_task_level`、`hg_product`、`hg_task_submission`、`hg_wallet_ledger` | | 手工商城 | `hg_product` | `hg_product_category`、`hg_sys_attachment` | | 储入 | `hg_recharge_order` | `hg_payment_channel`、`hg_wallet_account`、`hg_wallet_ledger` | | 取出 | `hg_withdrawal_order` | `hg_app_member_payment_account`、`hg_wallet_account`、`hg_wallet_ledger` | | 客服 | `hg_customer_service` | 第三方会话映射 | | 通知 | `hg_app_member_notice` | 已读关系待补充 | ## 17. 前端实现要求 ### 17.1 请求层 - 集中注入 `BASE_URL`、`X-Request-ID`、`X-Device-ID` 和 `Authorization`。 - 集中处理统一响应、401、429、超时和断网。 - 搜索、分页和页签切换时取消过期请求,避免旧响应覆盖新状态。 - 日志不记录密码、交易密码、Token、完整手机号、银行卡和收款码。 ### 17.2 页面状态 每个页面至少覆盖: - 首次加载、下拉刷新、加载更多、空状态、可重试失败、不可重试业务失败。 - 写请求期间按钮 loading 和防重复点击。 - 服务端返回的状态文案、失败原因与恢复入口。 - 320–430 像素宽度可用;键盘弹出时表单按钮可达;触控区域至少 44×44 像素。 ### 17.3 语言和文案 - `reason`、`status` 等枚举固定使用英文。 - 前端语言包映射用户文案,不把后端 `message` 作为唯一文案源。 - 本文档统一使用“储入”“取出”“领取”。如产品最终改为“充值”“提现”“接单”,前后端文档与语言包必须同时调整。 ## 18. 联调顺序 1. **P0 认证**:注册、登录、退出、会话过期和错误映射。 2. **P0 账户首页**:`/me`、`/home`、Banner、内容、等级和钱包概要。 3. **P0 任务闭环**:预检、领取、订单列表、详情、提交、审核状态和账变。 4. **P1 内容服务**:手工商城、客服、通知和上传。 5. **P1 资金闭环**:收款账户、储入、取出、回调、审核和对账;开始前必须完成支付与法务评审。 ## 19. 联调验收清单 - [ ] 未登录用户只能访问登录、注册和公开协议。 - [ ] 注册成功后自动进入登录态,不重复调用登录。 - [ ] 401 清理会话并返回登录;403 不误判为会话过期。 - [ ] 页签快速切换时旧请求不会覆盖新数据。 - [ ] 重复点击任务领取或提交不生成重复订单、提交或账变。 - [ ] 订单、钱包、账变和统计口径一致。 - [ ] 储入和取出失败、驳回、超时及取消均有原因和恢复路径。 - [ ] 手机号、密码、交易密码、银行卡和 Token 不进入日志或埋点。 - [ ] 富文本、商品名、公告和客服链接无可执行恶意内容。 - [ ] 金额均按最小货币单位传输,无浮点精度差异。 - [ ] 断网、超时、429 和 5xx 均有清晰恢复方式。 ## 20. 待确认事项 1. 登录注册是否加入图片验证码、短信验证或设备风控挑战。 2. 是否新增刷新 Token、设备会话列表和“退出所有设备”。 3. 任务、储入和取出数据库状态值的最终字典与允许转换。 4. 任务领取是随机匹配、规则匹配还是用户选择,以及截止时间的正式存储位置。 5. 会员等级版本中储入、取出、任务数和佣金规则的正式 JSON Schema。 6. 支付通道、收款主体、实名要求、取出审核和适用地区的合规结论。 7. 客服使用自建系统还是第三方 WebView/iframe,以及允许传递的用户信息。 8. 通知是否要求逐会员已读记录;如需要,数据库应补充相应关系表。