新增积分服务端接口封装、积分中心页面,支持登录回跳、签到恢复、积分明细等功能。清理缓存时保留未确认签到键,新增ApiRequestError错误类型区分业务状态,更新开发文档记录积分页面联调完成情况。
25 KiB
积分规则与开发契约
记录日期:2026-10-02。版本:points-page-20261002-v3。状态:后端已随5fee3cf部署、积分页面读取与恢复隔离联调通过;业务规则未定稿,正式配置关闭,原表未迁移、前端未发行。归属既有 POINT-001—004,不新增任务编号。POINT-002/004保持开发中;DEC-POINT/POINT-001仍待确认,POINT-003未实施。
本文件作为积分实施入口;数据库具体增量见积分数据库方案,最新页面状态与证据见开发进度0.51。未获规则确认时不填真实奖励,配置默认false/null;合成联调不代表正式积分可用。
0. 最新页面恢复与隔离联调
页面主流程98项/127次积分请求和补充81项/81次积分请求通过,共201次进入本机真实PHP/MySQL、7次受控故障/网络中断。覆盖真实丢响应后原键确认、跨接口一致性、读取失败时确认入口、确定拒绝弃键、坏回执保键、账户切换及迟到401;登录响应只为合成认证,真机、生产迁移与正式发布仍未完成。
未知请求由页面会话范围调整为原userId作用域的恢复命令上下文:只含requestId/expectedDate/expectedRuleVersion,不含Token、余额、流水或填写资料;401、会话过期、退出及账户切换仍清资料缓存和会话,但该键保留至确认或明确拒绝。恢复前核验当前账户,POST仍重新鉴权;不会把原请求迁移到别的会员,也不会在登录/刷新后自动发奖。此恢复例外不改变DEC-POINT业务参数。
共享清理专项35项通过(15真实积分HTTP、2合成密码登录):真实401清掉会话与该账户的其它资料缓存、保留原命令,同账户重新登录后实际重放且无重复到账。专项未实测本机过期/主动退出/完整账户切换交互;它们共用的清理函数已按上述规则实现。三批共214项/223积分请求,临时环境及私密配置已清理,正式发布/原表迁移状态保持。
0.1 前序:协议与隔离开发检查
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.2 历史: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. 已核实的源码现状
| 位置 | 当前能力或风险 | 实施处理 |
|---|---|---|
| 积分页面 | 已读取服务端余额、规则、任务和本人流水;按服务端日期/规则恢复签到请求 | 关闭配置时继续显示未发布状态,不伪造奖励;原键确认后重新读取当前余额 |
| 账户服务 | 已有登录、账户变化防护;资料 DTO 无 score/share_code | 积分服务复用会话,不扩充无关资料字段;登录恢复只接受固定 resumePoints 白名单 |
| 请求基础 | 已保留 Token 变化、401 清理、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均拒绝。五条精确白名单路径、原键恢复和页面隔离联调已通过开发检查;原表迁移和规则发布仍未完成,不视为线上积分已启用。
签到要求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. 事务与重放算法
- 验证恰好三个字段,规范化按固定字段顺序处理,拒绝 GET query 或其他正文参数。以当前认证用户确定账户。
- 开启事务,锁定 fa_user 当前行,重新核验 status=normal。账户不存在/禁用不能借历史请求重放。
- 在用户锁之后以当前读查
(user_id,request_id)的已成功流水。命中时核验business_type=checkin、规范日期、规则版本、delta/before/after 算术及请求预期;同意图返回原回执,异意图固定冲突。历史 NULL 元字段不能充当该回执。 - 未命中时,在取得用户锁后采样一个上海业务日与时间,校验 expectedDate。锁等待跨午夜必须使用锁后日期;不匹配先返回 POINT_DATE_CHANGED。
- 以当前读检查固定
checkin + 当日事件;已有则返回 POINT_ALREADY_SIGNED,优先于活动暂停或新规则变化。未签到才读取有效发布规则并校验活动启用和 expectedRuleVersion。业务键不因新请求或新规则版本变化。 - 校验现有余额及奖励的整数边界,计算 after=before+reward,要求 0≤after≤2147483647。只进行正数签到奖励,负数调整与消费另行设计。
- 同事务更新用户 score、写一条完整积分流水(事件键、请求键、规则版本、before/after/delta)。任一步失败全部回滚,不能吞异常或写零分占位。
- 提交成功后返回流水冻结回执。确定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。要签到新一天,必须由用户在刷新后明确点击。
- 离页/换账号拒绝迟到响应;未知请求在原userId作用域保留,离页、401、过期、退出和切换账户不会弃键,不能跨账户迁移。只保留三字段恢复命令,其它账户缓存照常清除;重新登录该会员后才能显式确认,仍须服务端鉴权。
- 从 API 的 ruleVersion/reward 显示规则与奖励,不再写死 10、AI2680 或本机积分。未接邀请前不显示“真实个人邀请码”或未发生的邀请奖励。
- 清理体验签到缓存不产生服务器写入;原匿名/本机记录不影响服务器 signedToday。
8. 后续开发检查清单与顺序
以下为完整开发检查范围;已覆盖部分见第0节和开发进度0.51。积分读取/签到恢复隔离页面检查已通过,邀请与资料奖励、原表迁移、正式发布及真机仍未完成:
| 检查 | 关键反例 |
|---|---|
| 并发入账 | 同日同键/异键双进程;只有一笔奖励和流水,余额正确 |
| 历史重放 | 第一天成功、第二天原键仍原回执;新日期复用成功键拒绝 |
| 日期边界 | 锁等待跨午夜;昨日未执行请求不能在新一天到账;客户端时区/时钟篡改无效 |
| 规则边界 | 规则变化/暂停/未发布;成功旧回执仍可读,未成功新请求拒绝 |
| 原子回滚 | 流水 insert、余额 update、commit 故障;无“加分成功但无流水” |
| 数据边界 | 负数、INT 上限、坏元数据、NULL 历史事件、非规范业务键不被误认 |
| 所有权与隐私 | 匿名/禁用/跨账户、流水游标和自由备注;只本人读写 |
| 老写方 | 后台伪造 score/level、legacy 方法/插件调用;不能覆盖新账本 |
| 页面状态 | 实际响应丢失、原键确认、换账号、登录恢复、加载/空/错、迟到回包 |
| 迁移和恢复 | 原表只读预检;新合成库apply4增量/重复0、完整备份/ACL/第二库恢复、旧数据摘要一致及精确清理已通过 |
当前后端可在规则未定稿时保持默认关闭。页面真实读取、登录恢复及原键确认已完成隔离联调;下一步是确认首阶段业务规则,随后执行正式库只读预检、完整备份、4项增量迁移和线上关闭状态烟测。邀请资格/奖励规则与关系方案留POINT-003,完整POINT-004后续推进。发布参数在确认前保持空值。
正式迁移、上线、真机/多设备、权益运营和真实业务验收后续按实际范围安排,不复用历史生产授权。本轮为源码、纯测试及显式隔离开发检查,不是生产签到到账或积分页面完成证据。