Files
airz-uniapp/docs/积分规则与开发契约.md
T
hujun 2a9ff403c4 feat: 接入内容服务端真实接口并优化页面交互
- 迁移内容数据到服务端接口,移除本地模拟数据
- 首页新增推荐课程加载与未读消息检查
- 资讯、课程、搜索页面支持分页加载与错误重试
- 登录页新增恢复课程、消息、帮助页的路由逻辑
- 评估分析页新增处理中状态动效与阶段提示
- API 环境切换到正式域名 airz.hqling.cloud
2026-10-01 20:26:41 +08:00

23 KiB
Raw Blame History

积分规则与开发契约

记录日期:2026-10-01。版本:points-backend-20261001-v2。状态:后端及真实HTTP隔离开发检查完成,业务规则未定稿,默认不入账,原表未迁移、未部署。归属既有 POINT-001—004,不新增任务编号。POINT-002保持开发中;DEC-POINT/POINT-001仍待确认,POINT-003未实施,POINT-004页面尚未接真实积分。

本文件作为积分实施入口;数据库具体增量见积分数据库方案。接口源码与真实HTTP隔离检查已完成,未部署;页面仍是本机体验签到。未获规则确认时不填真实奖励,配置默认false/null。

0. 最新协议与隔离开发检查

87个真实HTTP请求/1079项检查通过,经过实际ThinkPHP路由、Redis Token鉴权和积分控制器。覆盖匿名/无效/停用/删除/撤销、Cookie/旧Token头拒绝、允许Origin的匿名OPTIONS、JSON/原始query/错误方法、草案/发布/暂停/坏规则、跨日/版本变化、实际丢弃已提交成功响应后的原键确认、本人快照/隐私/旧NULL与0流水、余额边界、坏回执和Redis不可用。

新脚本tests/points_http.php仅取明确私密环境的本机3309连接,创建独立合成数据库、临时ROOT/RUNTIME、随机ENV_PREFIX和Redis DB15随机Token作用域;不读取正式.env,不启动worker或调用模型。成功后自建数据库、Token、临时服务/文件已清理,共享fa_user/fa_user_score_log结构/数据及原Redis队列摘要保持。所有权标记限制目录清理;清理失败返回非0并保留受限目录及安全定位,不能声称资源已移除。

主服务238项、HTTP输入190项纯检查通过。原表23项只读预检通过,仍4增量、14类质量计数各项0,applied=false。迁移新增非NULL历史时间质量计数,正UINT流水ID并入invalidLedgerIntegers;合法NULL历史时间保留,非NULL时间范围0—253402271999,确保上海ISO8601四位年份。首次快照坏主键固定409/POINT_LEDGER_INVALID。当前业务配置保持false/null,页面、原表迁移及部署未完成。

迁移纯110项、独立合成库apply/备份/Windows ACL/恢复89项通过。共享隔离库只复制结构,第一新库合成2会员4流水;apply4增量、重复为0,完整双表schema+NDJSON恢复第二新库并逐列摘要一致。目录3文件ACL及读写probe通过,修正Windows原生绝对路径及父级路径校验,纯路径边界覆盖;两新库、私密INI/备份/probe已清理,源表摘要保持。安全verification保留于C:/Users/JUN/AppData/Local/Temp/airz-points-migration-20261001/apply-20261001-115749-d50a5cd87fd5a84a/verification.json。仅为开发演练,未执行共享原表DDL或生产操作。

0.1 历史:0.48后端基础检查

已新增 PointsRules、UserPoints、v1 Points 控制器、五条路由、默认未发布配置、显式迁移脚本及测试。通用后台score/level、通用模型更新与旧User::score均已拒绝写入;新服务直接在账户锁内限定更新score并保存完整流水,不重算level。新会员0分、数据库默认0级及Auth注册1级初始化保持。schema.sql和ScoreLog中文注释已同步目标结构,不能据此声称目标库已经增加字段。

纯检查237项服务、98项写保护、84项迁移通过。隔离MySQL3309随机前缀合成表45项检查通过,包含同键/异键双PHP并发、锁等待跨日和真实流水insert故障回滚;测试表及临时文件已精确清理。原fa_user/fa_user_score_log结构和数据摘要保持。另22项只读迁移入口检查通过,重复预检仍为4增量、13类质量计数均0,applied=false;本轮修复PHP7.4 PDO不追踪原始START事务导致预检commit失败,改PDO事务接口与RR只读快照。

没有迁移原表、运行apply/备份/Windows ACL、调用模型/worker/Redis/HTTP、编译页面或部署。提交结果未知和连接故障由纯替身覆盖,不是实际断网或生产到账证据;页面状态、真实HTTP、迁移应用与恢复仍须后续开发检查。

1. 首阶段建议与待确认项

建议先实施 POINT-002 的账户余额、流水、每日签到,再接 POINT-004 对应页面部分。POINT-003 邀请关系和可信奖励独立实施;不因完成签到而标记整个积分模块完成。

项目 首阶段建议 状态与理由
每日签到 已登录且状态正常的会员,每个业务日一次,每次 10 分 待确认数额;沿用既有页面建议,不表示已批准真实奖励
业务日 服务端 Asia/Shanghai,00:00 至次日 00:00,不提供补签 待确认;设备时间、时区及登录连续天数不作为依据
连续签到加成 不启用,始终固定当期奖励 待确认;不需要新增连续签到表
完善资料奖励 首阶段关闭,奖励数额为 null 必须后续确定完整标准、可信事件和一次性范围;进入档案或编辑资料不发奖
邀请奖励 首阶段关闭,奖励数额为 null 有效邀请条件、双方奖励、次数和归因期限待定;复制、分享、绑定码不直接发奖
积分用途 首阶段只提供余额与流水,不启用提现、现金兑换、AI 次数抵扣或贷款优惠 待确认权益范围;不能把积分说成授信、信用分、放款条件
有效期 首阶段建议不自动过期 待确认;若需要到期扣减,须先设计到期批次/消费抵扣,不靠定时清零余额实现
会员等级 新签到服务首阶段建议仅变更 score,不新增等级或权益联动 待确认;旧 User::score 会按余额改 level,需要明确兼容处理后实施
历史体验积分 不导入、不补发真实积分 本机记录可篡改,匿名也可签到;正式余额只取服务端既有 score
历史服务端数据 原 score、流水、日期、备注、业务键保留 不按流水合计重建余额、不自动清零、不擅自修复差异

首阶段的业务确认应至少覆盖“每天 10 分、上海每日一次、不补签”“资料/邀请奖励关闭”“权益、有效期及等级处理”。在明确确认前,设计状态保持 draft;未来配置的安全默认值必须为未发布、停止新入账,奖励数额不得用页面示例兜底。

2. 已核实的源码现状

位置 当前能力或风险 实施处理
积分页面 余额为本机签到日期数乘 10;设备日期;固定体验码 AI2680;没有积分 API 接真实 API 后移除该余额算法及固定码,清理体验缓存但不换算奖励
账户服务 已有登录、账户变化防护;资料 DTO 无 score/share_code 新增同分包积分服务,复用会话而不扩充无关资料字段
请求基础 有 Token 变化及 401 防护;当前错误仅保留 message 实施时保留现有调用兼容,增加可识别的 HTTP 状态/errorCode,区别确定失败与结果未知
数据库结构 fa_user.score 为有符号 INT;流水已具业务键、事件唯一及用户时间索引 复用余额和流水;目标库实际结构仍须迁移前只读核实
通用积分方法 旧score()已明确拒绝,通用模型不能更新score/level 使用UserPoints领域服务,不再吞异常或随积分重算等级
后台会员模型 模型事件、后台增改/批量及编辑表单已保护score/level 积分/等级只读;任意SQL仍不经过模型守卫,新增写方须遵守共同账户锁

最初设计依据源码和schema.sql;本轮仅对显式隔离库读取实际结构/数据并在随机合成表检查事务。正式库未连接,隔离原积分表仍缺两个新列和两个索引,不能把合成表或结构文件当作正式迁移证据。

3. 规则发布与事件身份

已增加application/extra/points.php,独立于模型调用额度。默认 enabled=false,rule_version/check_in_reward/expiry_policy/level_policy/benefits_policy均null;API映射ruleVersion/checkInReward。发布必须提供完整版本、1—1000的规范整数奖励及none/unchanged/none三项策略;这一范围是配置校验上限,不是批准的奖励额。enabled=false且保留完整发布内容表示paused,不删历史回执。损坏/不完整配置固定POINT_RULE_UNAVAILABLE。

规则版本格式为 2—64 位小写 ASCII,^[a-z][a-z0-9-]{1,63}$;拟首版名 points-checkin-20261001-v1。同一版本永久对应同一奖励额、日期和参与语义;变更必须新版本,不改历史流水。暂停活动不删除成功回执;启用配置编辑后台不属于首阶段,先使用受控部署配置。

每日签到的账本事件固定:

business_type = checkin
business_id   = 服务端上海业务日 YYYY-MM-DD
unique        = user_id + business_type + business_id

事件键不包含规则版本,因此同一天发布新版本也不能重复奖励。新请求只能为当前业务日入账;设备日期和客户端字段不能指定补签。旧已成功请求允许在下一日、规则暂停或新版本发布后返回原回执,但仍要求当前账户有效且拥有该流水。

4. 接口源码契约

统一前缀 /api/v1,全部要求显式合法Bearer及当前正常会员,不接受Cookie/旧Token头回退。沿用v1 Base的code/msg/data与data.errorCode、Cache-Control: no-store,v1框架错误亦补该缓存头。由认证身份取得userId;未知参数、客户端余额/奖励/业务完成状态/其他用户ID均拒绝。五条精确白名单路径已实现并通过HTTP开发检查,原表迁移/规则发布/页面尚未完成,不视为线上能力。

签到要求application/json,可带UTF-8 charset;正文由原生JSON解码及领域校验,必须三个唯一字符串字段,重复键和Unicode等价重复键拒绝。原始QUERY_STRING校验唯一解码键、白名单及PHP解析一致;NUL、坏编码、重复和空片段拒绝。原始/有效HTTP方法须与接口方法一致,覆盖头存在即拒绝;错误方法405,未知路由404。Common既有入口在鉴权前处理允许Origin的匿名OPTIONS,不重复实现CORS。

方法与路径 严格输入 data 主要字段
GET /points 无业务参数 balance:int、serverDate、timezone、signedToday、checkInEnabled、ruleVersion:string/null、checkInReward:int/null、balanceState:normal/anomaly
GET /points/rules 无业务参数 status:draft/published/paused、ruleVersion:string/null、checkInReward:int/null、timezone、publishedRules;未发布规则不显示为已生效
GET /points/ledger limit 默认20/最大50;后续页 beforeId + snapshotId items、snapshotId、nextBeforeId;本人流水,ID 字符串
POST /points/check-in requestId、expectedRuleVersion、expectedDate,必须恰好三个字段 ledgerId、delta:int、balanceAfter:int、signedDate、ruleVersion、replayed:bool
GET /points/tasks 无业务参数 首阶段只返回已发布签到任务;未实施资料/邀请奖励明确 unavailable,reward=null

流水源码采用与现有消息/内容一致的有界游标,替代旧page/pageSize目标草案;没有已发布积分API,不涉及接口兼容迁移。tasks实际返回serverDate/timezone/items,签到任务按已发布规则和今日状态显示,资料/邀请始终unavailable且reward=null。邀请/invitations和/invitations/bind保留后续阶段目标,不返回固定码或假的奖励进度。

4.1 签到请求及回执

requestId 为 pts_ 加 32 位小写十六进制,客户端用安全随机数生成,同一用户命名空间内唯一。expectedRuleVersion 为页面实际读取的已发布版本;expectedDate 为同一次读取返回的 serverDate,是操作预期条件,不是指定奖励日期。日期须为真实有效的 YYYY-MM-DD,拒绝非法日历日期、空值、数组和宽松类型转换。

示例仅描述获批发布后的协议形状,当前无这样的运行规则:

{
  "requestId": "pts_0123456789abcdef0123456789abcdef",
  "expectedRuleVersion": "points-checkin-20261001-v1",
  "expectedDate": "2026-10-01"
}

入账回执与原流水绑定;重放中 replayed=true,其余业务字段保持原值。balanceAfter 是这笔入账后的历史余额,不是当前余额;页面必须随后重新 GET /points,不能用旧回执覆盖新余额。不同请求键同日重复签到返回明确的 POINT_ALREADY_SIGNED,不写第二条零分流水,也不把新的请求键附到原流水。

4.2 余额与流水读取

余额、今日签到状态与规则须来自一次一致的读取:先按当前会员事务读取,固定一个服务端业务日,再完成对应状态查询,避免日界/入账途中混合视图。日期和规则必须一起提供,客户端不能用本机时钟补齐。服务不可读时固定失败,不把错误或未知余额转成 0。

流水DTO为ledgerId/delta/balanceBefore/balanceAfter/businessType/businessDate/ruleVersion/createdAt。时间为ISO8601带时区,历史未知时间保留null;日期为上海YYYY-MM-DD。ID为字符串,支持现有无符号INT至4294967295,服务/迁移要求64位PHP。历史未识别事件统一legacy,业务日/版本可null;不返回原memo、原业务标识或请求键。历史真实0分记录如实展示,不因新服务禁止零分占位而删除。

首次列表取得本会员最大 ID 作为 snapshotId,空为字符串 "0";后续固定上界并按 id<beforeId 倒序读取,nextBeforeId=null 表示末页。上界/游标须属于当前用户;不存在或他人标识统一 POINT_SNAPSHOT_INVALID,不泄露归属。新流水不插入已翻页窗口,刷新重新取得上界。

负余额保留真实读数并标 balanceState=anomaly,停止新签到等待核对,不静默修正。本轮不根据历史流水合计断言账户余额错误;可能存在早期余额或未完整记录的合法调整。

4.3 固定错误

HTTP / errorCode 含义与页面处理
401 / AUTH_REQUIRED 登录或账户状态失效;清当前账户状态,固定登录恢复
503 / AUTH_STORAGE_UNAVAILABLE Token存储不可用;不当作已退出或业务未入账,保留待确认请求
405 / METHOD_NOT_ALLOWED 原始/有效方法不符或使用方法覆盖;不进入账本操作
422 / POINT_INPUT_INVALID 请求字段、类型、ID、版本或日期非法;不入账
409 / POINT_REQUEST_CONFLICT 成功请求键已用于另一日期/版本;保留原账本,不改意图重试
409 / POINT_DATE_CHANGED 未成功的预期日期不是锁后服务端当前日;刷新,用户重新点击才生成新请求
409 / POINT_RULE_CHANGED 预期版本与当前有效版本不同;刷新后重新确认
409 / POINT_ALREADY_SIGNED 当前日已有签到;刷新状态,不再次显示增加积分
409 / POINT_SNAPSHOT_INVALID 流水游标无效;清旧列表并重新查询
503 / POINT_RULE_UNAVAILABLE 规则未发布、暂停、损坏或不可读;关闭新签到,不以 10 分兜底
409 / POINT_BALANCE_INVALID 负余额、非规范余额或本次加法越过 INT 上限;不更新
409 / POINT_WRITE_CONFLICT 确定的1062/1205/1213竞争且本请求事务已回滚;不自动换键,可用原请求重试
503 / POINT_LEDGER_UNAVAILABLE 账本查询/写入暂不可用,或提交/回滚结果无法确认;客户端保留原请求确认结果
409 / POINT_LEDGER_INVALID 成功回执元数据损坏或业务唯一键与预期记录不符;不覆盖、不伪造回执

网络失败和无法确认的 5xx 不表示未入账。异常原文、SQL、内部连接、其他会员信息和凭据均不下发。

5. 事务与重放算法

  1. 验证恰好三个字段,规范化按固定字段顺序处理,拒绝 GET query 或其他正文参数。以当前认证用户确定账户。
  2. 开启事务,锁定 fa_user 当前行,重新核验 status=normal。账户不存在/禁用不能借历史请求重放。
  3. 在用户锁之后以当前读查 (user_id,request_id) 的已成功流水。命中时核验 business_type=checkin、规范日期、规则版本、delta/before/after 算术及请求预期;同意图返回原回执,异意图固定冲突。历史 NULL 元字段不能充当该回执。
  4. 未命中时,在取得用户锁后采样一个上海业务日与时间,校验 expectedDate。锁等待跨午夜必须使用锁后日期;不匹配先返回 POINT_DATE_CHANGED。
  5. 以当前读检查固定 checkin + 当日 事件;已有则返回 POINT_ALREADY_SIGNED,优先于活动暂停或新规则变化。未签到才读取有效发布规则并校验活动启用和 expectedRuleVersion。业务键不因新请求或新规则版本变化。
  6. 校验现有余额及奖励的整数边界,计算 after=before+reward,要求 0≤after≤2147483647。只进行正数签到奖励,负数调整与消费另行设计。
  7. 同事务更新用户 score、写一条完整积分流水(事件键、请求键、规则版本、before/after/delta)。任一步失败全部回滚,不能吞异常或写零分占位。
  8. 提交成功后返回流水冻结回执。确定1062/1205/1213且确认本请求事务已回滚时返回 POINT_WRITE_CONFLICT,不自动换请求键或无界重试;提交/回滚无法确认时返回 POINT_LEDGER_UNAVAILABLE,用原请求键确认。处理框架回滚异常时保留实际业务失败,不暴露原异常。

所有影响入账的数据库读取使用当前读,不能复用在用户锁前形成的 MySQL RR 快照或外层事务旧视图。规则若采用部署配置,必须每次操作读取该进程当前已发布配置;版本与语义不可变。跨实例滚动部署出现版本不一致时,固定规则冲突优先于偷偷改变奖励。

flowchart TD
    A[验证登录和三个预期参数] --> B[锁账户并查成功请求]
    B -->|同意图命中| C[返回原流水回执]
    B -->|未命中| D[锁后日期校验]
    D --> E[检查当日事件 再校验规则与积分边界]
    E --> F[余额和一条流水同事务提交]
    D -->|日期或版本变化| G[固定冲突并刷新]
    E -->|已签到| G

6. 首阶段数据库与写路径

schema.sql及显式迁移脚本已定义可空request_id/rule_version、唯一(user_id,request_id)和ID游标索引(user_id,id);原表未应用,旧流水保持。已有业务唯一和用户时间索引不重复添加;用户时间索引不能直接保证按ID排序。成功流水保存日唯一和请求回执,不建独立签到/请求/连续签到表。具体字段、中文注释与预检见数据库增量方案。

本轮已盘点并封闭通用后台绝对值编辑、批量、User::score和模型score/level更新;仅新增服务加锁不能防其他旧写方覆盖余额。受控管理员积分调整独立排期,当前只读;任意SQL不经过模型守卫,后续插件/导入必须重新审查。会员删除的既有历史策略本轮保持,可能有孤儿流水;已删除/停用账户不能通过新服务读取或重放。

新服务拒绝外层事务,确保成功意味着本次真正提交;不回滚调用方已有事务。数据库break_reconnect必须严格false,防断线自动重连使单条SQL意外自动提交。事务竞争只在确认本人事务回滚后映射409,未知提交返回503保留原键,不进行自动SQL重试。

后续邀请关系需独立表,受邀人唯一绑定、禁止自邀、邀请码唯一且历史冲突先人工处理。资料奖励以永久任务事件去重,不能按每次资料修订发奖。只有服务端可信业务事件可触发奖励;不能用客户端声称“资料已完成/好友已注册”发奖。

7. 页面实施边界

  • 登录后恢复积分页需为现有登录白名单增加固定 resumePoints 目标,不接受任意回跳 URL。
  • 初次/刷新分别处理加载、失败、空流水、未发布规则、已签到;失败清掉不可信旧状态,不显示假的 0 分或成功。
  • 发送中锁定签到按钮;请求结果未知时保留原 requestId/date/version 和账户身份,只允许原请求确认。不能因刷新、关闭提示或跨日自动生成另一签到请求。
  • 原回执仅确认原业务日;回执后重新读取当前余额和 serverDate。要签到新一天,必须由用户在刷新后明确点击。
  • 离页/换账号拒绝迟到响应,未确认请求随账户作用域隔离;未知请求保留范围先按当前页面会话,不跨退出/换账号迁移。
  • 从 API 的 ruleVersion/reward 显示规则与奖励,不再写死 10、AI2680 或本机积分。未接邀请前不显示“真实个人邀请码”或未发生的邀请奖励。
  • 清理体验签到缓存不产生服务器写入;原匿名/本机记录不影响服务器 signedToday。

8. 后续开发检查清单与顺序

以下为完整开发检查范围;已覆盖部分见第0节。HTTP后端隔离检查已完成,页面和原表迁移/正式发布仍未完成:

检查 关键反例
并发入账 同日同键/异键双进程;只有一笔奖励和流水,余额正确
历史重放 第一天成功、第二天原键仍原回执;新日期复用成功键拒绝
日期边界 锁等待跨午夜;昨日未执行请求不能在新一天到账;客户端时区/时钟篡改无效
规则边界 规则变化/暂停/未发布;成功旧回执仍可读,未成功新请求拒绝
原子回滚 流水 insert、余额 update、commit 故障;无“加分成功但无流水”
数据边界 负数、INT 上限、坏元数据、NULL 历史事件、非规范业务键不被误认
所有权与隐私 匿名/禁用/跨账户、流水游标和自由备注;只本人读写
老写方 后台伪造 score/level、legacy 方法/插件调用;不能覆盖新账本
页面状态 实际响应丢失、原键确认、换账号、登录恢复、加载/空/错、迟到回包
迁移和恢复 原表只读预检;新合成库apply4增量/重复0、完整备份/ACL/第二库恢复、旧数据摘要一致及精确清理已通过

当前后端可在规则未定稿时保持默认关闭。下一开发子项可实施积分页面真实读取、登录恢复及原键确认流程;规则未发布时签到不可用,不补造奖励。首阶段业务规则定稿、原表迁移和发布另行安排;邀请资格/奖励规则与关系方案留POINT-003,完整POINT-004后续推进。发布参数在确认前保持空值。

正式迁移、上线、真机/多设备、权益运营和真实业务验收后续按实际范围安排,不复用历史生产授权。本轮为源码、纯测试及显式隔离开发检查,不是生产签到到账或积分页面完成证据。