diff --git a/P0-研发落地清单.md b/P0-研发落地清单.md index 020e0c4..8d460d6 100644 --- a/P0-研发落地清单.md +++ b/P0-研发落地清单.md @@ -54,12 +54,13 @@ | **A7** | **报告前后照片必填后端兜底** | 缺 before 返回 `BEFORE_PHOTO_REQUIRED`;缺 after 返回 `AFTER_PHOTO_REQUIRED` | Report Media Backend | ✅ 已实现(Batch B,含 `ReportControllerTest`) | `mvn -f backend/pom.xml test` | 与前端 B5 同步落地 | | **A8** | **统一鉴权上下文**:HMAC session token + `CurrentUserContext`,protected API 不再信任请求里的身份字段 | 未登录返回 401;A 店用户访问 B 店预约/报告/线索返回 403;customer 不能用任意 `userId` 创建他人宠物/预约 | System Architect / Backend Core | ✅ 已实现(Batch C,含 `SessionTokenServiceTest`) | `mvn -f backend/pom.xml test` + 跨店/跨用户接口验证 | 对应 P0-1;不引入完整 Spring Security | | **A9** | **线索脱敏**:`/report/leads` 默认不返回完整 `wechatOpenid/wechatUnionid` | 回包只有 `wechatBound`、`wechatOpenidMasked`、`wechatUnionidMasked` | Report Media Backend | ✅ 已实现(Batch B) | 手工 curl + `mvn test` | 对应 P0-2 | -| **A10** | **配置安全与启动迁移收口**:`application.yml` 不保留真实敏感值;生产 profile 禁用 `ddl-auto:update`/`show-sql`;移除启动期 DDL/历史 UPDATE | 仓库不含真实 DB/微信密钥;生产启动只读环境变量 | Backend Ops / System Architect | ✅ 已实现(Batch D) | `rg` 扫描 + 启动验证 | 对应 P0-8;已暴露凭据需轮换 | +| **A10** | **配置安全与启动迁移收口**:`application.yml` 不保留真实敏感值;生产 profile 禁用 `ddl-auto:update`/`show-sql`、万能验证码和宽 CORS;生产启动不写默认业务数据 | 仓库不含真实 DB/微信密钥;最终配置不安全时启动失败;有 liveness/readiness、只读 schema/数据预检和上线 smoke | Backend Ops / System Architect | ✅ 代码与脚本已实现;待真实环境留证 | `mvn test` + `deploy/release-preflight.sh` + `deploy/production-smoke.sh` | 对应 P0-8;生产域名/凭据/迁移/线上 smoke 归 H3;已暴露凭据需轮换 | | **A11** | **上传路径与类型保护**:`FileController` 归一化路径 + 扩展名/MIME 校验 | `/api/upload/image/../...`、伪装扩展名、超大文件均被拒绝 | Backend Core / System Architect | ✅ 已实现(Batch D) | 手工 curl + `mvn test` | 对应 P0-9 | | **A12** | **最小测试基线**:补 `SessionTokenServiceTest`、`ReportServiceTest`、`ReportControllerTest`、`AppointmentServiceTest` | `mvn test` 不再输出 `No tests to run`,新增用例全部通过 | Core Flow QA / System Architect | ✅ 已实现(Batch D) | `mvn -f backend/pom.xml test` | 对应 P0-10;持续回归数量以 Surefire 当次统计为准 | | **A13** | **预约/报告身份语义拆分**:客户、创建人、服务技师、报告作者不得复用同一语义字段 | customer 自约身份从 session 派生;门店代约必须提供真实宠主;宠主报告按预约客户归属查询;legacy 字段只做兼容 | System Architect / Backend Core / Report Media Backend | ✅ 已实现(Phase 0 RC1 后) | `mvn -f backend/pom.xml test` + H5/小程序构建 | 迁移见 `backend/db/migrations/20260801_split_service_identity.sql` | | **A14** | **StoreCustomer 门店客户主档**:客户列表不再临时拼成无 ID 投影 | 预约/留资自动建档;同店账号与手机号唯一;API 返回 storeCustomerId 且手机脱敏;历史数据可回填 | Backend Core / Report Media Backend / Data Model | ✅ 已实现(待生产迁移) | `mvn -f backend/pom.xml test` + `npm --prefix admin run build` | 迁移见 `backend/db/migrations/20260801_create_store_customer.sql` | | **A15** | **BusinessEvent 业务事件落库**:经营漏斗不再依赖日志或前端猜测 | 首批 7 类事实统一落库;稳定事件幂等;metadata 不含 token/手机号/IP;工作台已打开可统计;历史可靠事实可回填 | Backend Core / Report Media Backend / Data Model | ✅ 已实现(待生产迁移) | `mvn -f backend/pom.xml test` + `npm --prefix admin run build` + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_business_event.sql` | +| **A16** | **真实预约容量与连续排班占用**:服务时长、门店并发容量和预约时长快照共同决定号源 | 长服务跨半小时桶占号;`walk_in` 消耗一份容量,`blocked` 关闭全部;并发创建不超卖 | Backend Core / Store Admin FE / Data Model | ✅ 已实现(待生产迁移) | backend 全测 + admin/H5/小程序构建 + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_booking_capacity.sql`;不等同排队/准点承诺 | --- @@ -130,8 +131,8 @@ | ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 | |----|------|----------|-------|------|----------|------| -| **H1** | **backend README RC 清单** | 含 `ffmpeg -version`、`ffprobe -version`、`curl -I $APP_BASE_URL/api/upload/image/`、`mvn test`;写明 `APP_BASE_URL`、`HIGHLIGHT_*`、`PETSTORE_SESSION_SECRET`、上传目录权限、Nginx `client_max_body_size` | Backend Ops | ✅ 已实现(Batch D) | 代码审查 | 对应 P0-10 | -| **H2** | **frontend README RC 清单** | 含 `npm install`、`build:h5`、`build:mp-weixin`;写明 `VITE_API_ORIGIN`、`VITE_REPORT_PUBLIC_ORIGIN`、微信 request/download 合法域名、报告二维码链接检查 | Frontend Release Ops | ✅ 已实现(Batch D) | 代码审查 | 对应 P0-10 | +| **H1** | **后端可重复发布门禁** | README + production profile + 配置校验 + schema/15 项只读预检 + liveness/readiness + 只读 smoke + systemd/Nginx 模板 | Backend Ops | ✅ 仓库能力已实现;待真实环境执行 | backend 全测/打包 + `bash -n deploy/*.sh` + Runbook | 不把模板/本地通过写成已上线 | +| **H2** | **前端可重复发布门禁** | admin/H5/小程序真实域名配置先门禁再构建;仓库占位值不能进入正式包;微信合法域名、体验版、静态回滚路径明确 | Frontend Release Ops | ✅ 仓库能力已实现;待真实环境执行 | admin/frontend preflight + 三端构建 + Runbook | 真实配置不得提交 | | **H3** | **P0 通过条件** | 见下方「P0 通过条件」块 | System Architect / Core Flow QA | 🟡 待人工 smoke 复核 | 见下方 | 对应 P0-10 | --- @@ -158,7 +159,9 @@ - 同一 `appointmentId` 第二次提交报告返回 `REPORT_ALREADY_EXISTS`。 - 报告前后照片缺失返回对应业务码。 - A 店 boss/staff 不能查 B 店 leads/list/report;customer 不能用任意 `userId` 创建他人宠物/预约。 -- 生产配置扫描不含真实 DB/微信密钥,无 `ddl-auto:update`,无宽 CORS。 +- 生产配置扫描不含真实 DB/微信密钥,最终 profile 为 `ddl-auto=validate`,CORS 仅允许显式生产 HTTPS 源;只读生产预检通过。 +- 已按顺序完成身份、StoreCustomer、BusinessEvent、预约容量四个生产迁移,并归档备份与验证证据。 +- liveness/readiness、只读 smoke 与 boss/staff/customer 三角色真实环境 smoke 通过;Gitea/GitLab 未受 Petstore 配置影响。 - 文档口径扫描:`rg "到店不排队|会员储值 \| 路线图 P0|宠主通过微信查看服务报告(v2)|无限制,随便选" docs` 无命中。 --- diff --git a/coverage/ontology-coverage-audit.md b/coverage/ontology-coverage-audit.md index 52655be..022ff0d 100644 --- a/coverage/ontology-coverage-audit.md +++ b/coverage/ontology-coverage-audit.md @@ -87,7 +87,8 @@ python3 docs/graph/audit_drift.py | BR-SCH-001 / BR-ST-001 / BR-PET-001 | 对应 ControllerTest | anchored | | BR-USER-001/002 | 间接 | anchored | | BR-USER-003 敏感字段 JsonIgnore | UserJsonIgnoreTest | anchored | -| BR-CONFIG-001/002 | —(配置) | anchored | +| BR-CONFIG-001/002 | ProductionConfigurationValidatorTest(002) | anchored | +| BR-CONFIG-003 生产发布只读门禁 | 配置单测 + release/smoke 脚本;live 证据待发布 | anchored | | BR-TST-001 一报告一寄语 | ReportTestimonialServiceTest | anchored | | BR-UNSUB-001 / BR-INT-001 | ReportLeadControllerTest | anchored | | BR-SC-001 / BR-BE-001 | StoreCustomerServiceTest / BusinessEventServiceTest / MigrationTest | anchored | @@ -104,7 +105,7 @@ python3 docs/graph/audit_drift.py | ~~中~~ | ~~`rule:BR-STORE-003` 公开 Store 字段裁剪~~ | ✅ `toPublicStoreView` | | 中 | `uk_report_appointment` 上线迁移 | 🟡 运维 | | ~~中~~ | ~~预约/履约/报告打开/留资事件可统计落地~~ | ✅ BusinessEvent;成片/播放/保存事件另行接入 | -| 中 | production profile 实测启动 | 🟡 | +| 中 | production profile + 只读预检在真实生产配置实测 | 🟡 发布门禁 | | 低 | 成片真 FFmpeg 端到端集成测(本机有 ffmpeg) | 🟡 单测已覆盖状态机与归类 | | 低 | `action:logout` / `event:session_revoked` 服务端吊销 | 🟡 gap | | 低 | 废弃 `register_store` / wechat OAuth demo 代码路径 | 🟡 deprecated 已标 | diff --git a/graph/ontology.jsonl b/graph/ontology.jsonl index 264dcb5..ec6616d 100644 --- a/graph/ontology.jsonl +++ b/graph/ontology.jsonl @@ -134,7 +134,8 @@ {"id":"rule:BR-USER-001","type":"Rule","name":"员工管理仅 boss + 同店","domain":"user","appliesTo":["action:create_staff","action:list_staff","action:delete_staff"],"code":"backend/src/main/java/com/petstore/controller/UserController.java","evidence":"anchored"} {"id":"rule:BR-USER-002","type":"Rule","name":"用户更新仅本人","domain":"user","appliesTo":["action:update_user","action:get_user_info"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#updateUser; #info","evidence":"anchored"} {"id":"rule:BR-CONFIG-001","type":"Rule","name":"生产配置环境变量","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml; application-example.yml","doc":"backend/README.md 环境变量表","evidence":"anchored"} -{"id":"rule:BR-CONFIG-002","type":"Rule","name":"production profile:ddl=validate + show-sql=false + SMS 禁用","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml production profile","doc":"backend/README.md production profile","evidence":"anchored"} +{"id":"rule:BR-CONFIG-002","type":"Rule","name":"production profile 强制只读 schema 安全配置","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml production profile; backend/src/main/java/com/petstore/config/ProductionConfigurationValidator.java; backend/src/main/java/com/petstore/PetstoreApplication.java","doc":"backend/README.md production profile","test":"ProductionConfigurationValidatorTest","evidence":"anchored","note":"生产禁用自动建表/show-sql/SMS 万能码/默认数据初始化,并拒绝弱密钥、占位域名和宽 CORS"} +{"id":"rule:BR-CONFIG-003","type":"Rule","name":"生产发布只读门禁","domain":"config","appliesTo":[],"code":"backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java; backend/src/main/java/com/petstore/config/PetstoreRuntimeHealthIndicator.java; backend/deploy/release-preflight.sh; backend/deploy/production-smoke.sh","doc":"docs/生产发布与回滚Runbook-2026-08-01.md; docs/生产监控与告警基线-2026-08-01.md","evidence":"anchored","note":"备份和四迁移后运行 schema validate + 15 项只读不变量;readiness 覆盖 DB/磁盘/上传/FFmpeg;自动 smoke 只读"} {"id":"rel:user_owns_pets","type":"Relation","subject":"entity:user","predicate":"OWNS","object":"entity:pet","cardinality":"1—N","evidence":"anchored","doc":"Pet.owner_user_id → User.id","domain":"user"} {"id":"rel:pet_has_appointments","type":"Relation","subject":"entity:pet","predicate":"HAS","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.pet_id(可空)","domain":"pet"} {"id":"rel:store_has_service_types","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:service_type","cardinality":"1—N","evidence":"anchored","doc":"ServiceType.store_id(NULL=系统默认)","domain":"store"} diff --git a/ontology/relations.md b/ontology/relations.md index 24e0dd3..36d5869 100644 --- a/ontology/relations.md +++ b/ontology/relations.md @@ -123,6 +123,7 @@ | `rule:BR-INT-001` | `action:get_report_suggestion` | | `rule:BR-CONFIG-001` | —(全局约束) | | `rule:BR-CONFIG-002` | —(全局约束) | +| `rule:BR-CONFIG-003` | —(发布全局约束) | ## 实体—归属系统(续) diff --git a/ontology/rules.md b/ontology/rules.md index 8fa7541..f5968f6 100644 --- a/ontology/rules.md +++ b/ontology/rules.md @@ -306,12 +306,22 @@ ### rule:BR-CONFIG-002 -**production profile**:`--spring.profiles.active=production` 时 `ddl-auto=validate`(不改表)、`show-sql=false`、`SMS_UNIVERSAL_CODE` 默认空(关闭万能验证码 `rule:BR-AUTH-002`)。 +**production profile**:`--spring.profiles.active=production` 时 `ddl-auto=validate`(不改表)、`show-sql=false`、`SMS_UNIVERSAL_CODE` 默认空(关闭万能验证码 `rule:BR-AUTH-002`),并跳过开发期默认服务初始化。最终配置不满足强密钥、显式生产 HTTPS 域名、绝对上传目录和显式 CORS 源时启动失败。 - **适用动作**:—(全局约束) -- **代码**:`backend/src/main/resources/application.yml`(multi-document production profile) +- **代码**:`backend/src/main/resources/application.yml`(multi-document production profile)、`ProductionConfigurationValidator.java`、`PetstoreApplication.java` +- **测试**:`ProductionConfigurationValidatorTest` - **文档**:`backend/README.md production profile` -- **证据**:`anchored`(配置已加;production 实测启动 `gap`) +- **证据**:`anchored`(真实生产值与启动证据仍属发布门禁) + +### rule:BR-CONFIG-003 + +**生产发布只读门禁**:正式切流前必须按固定顺序完成备份和四个版本化迁移,再以最终生产配置运行 JPA schema `validate` 与 15 项只读数据不变量检查。运行期 readiness 必须同时覆盖数据库、磁盘、上传目录和 FFmpeg/ffprobe;上线自动 smoke 只允许 GET,不创建或修改业务数据。 + +- **适用动作**:—(发布全局约束) +- **代码**:`backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java`、`PetstoreRuntimeHealthIndicator.java`、`backend/deploy/release-preflight.sh`、`production-smoke.sh` +- **文档**:`docs/生产发布与回滚Runbook-2026-08-01.md`、`docs/生产监控与告警基线-2026-08-01.md` +- **证据**:`anchored`(生产备份、迁移和 live smoke 证据待发布时补齐) ## 寄语 / 退订 / 建议周期 diff --git a/qa-reports/phase0-pilot-rc2-release-manifest.md b/qa-reports/phase0-pilot-rc2-release-manifest.md new file mode 100644 index 0000000..1455f4c --- /dev/null +++ b/qa-reports/phase0-pilot-rc2-release-manifest.md @@ -0,0 +1,106 @@ +# 宠小它 Phase 0 Pilot RC2 Release Manifest + +> RC ID:`phase0-pilot-rc2` +> +> 冻结日期:2026-08-01 +> +> 状态:**Ready for Release(未部署)** +> +> RC Steward:PM Assistant +> +> 发布边界:本 manifest 只冻结源码与本地验证证据;真实生产配置、备份、迁移、TLS、live smoke 和三角色验收未完成前不得改为 `Released`。 + +## 1. 冻结提交与回滚点 + +| 仓库 | RC commit | 前一稳定 commit(应用回滚点) | 状态 | +|---|---|---|---| +| backend | `133f61e4ad6152865a76c4e9328d96e689f53635` | `b6138475f073881199abdef8d2681dcb92d1ab66` | clean | +| admin | `3470b124e408501ed824b993bcb34dd40e77c293` | `7e924fa79d6dafbd60643b8e31519737feb92e3b` | clean | +| frontend | `e11ce1aaaa602011718e300e4527a0d971f1339a` | `d29ece3a85e6d137d91afd609f49b10051ad9b7f` | clean | +| docs | 本 manifest 所在 commit | 前一 commit | 提交后确认 clean | + +RC 只能部署以上 commit 或由其生成且校验和一致的产物。后续任何代码改动必须建立新 RC 或 Hotfix manifest。 + +## 2. 纳入范围 + +- 生产 profile 启动配置门禁:`ddl-auto=validate`、关闭 SQL/万能验证码、强 session secret、显式 HTTPS 域名、显式 CORS、绝对上传目录; +- 生产启动跳过开发期默认服务写入; +- JPA schema 校验后执行 15 项只读数据库不变量预检; +- liveness/readiness,readiness 覆盖 DB、磁盘、上传目录、FFmpeg/ffprobe; +- 后端只读 smoke、systemd/EnvironmentFile/Nginx 独立模板; +- admin、H5、小程序发布域名门禁,默认 `.invalid` 占位配置主动失败; +- 小程序构建显式使用 `--mode mp-weixin`,读取独立环境配置; +- 生产 Runbook、试点门店开通清单、监控告警基线和本体 `BR-CONFIG-003`; +- 已冻结的身份拆分、StoreCustomer、BusinessEvent、真实预约容量能力及四个迁移脚本。 + +## 3. 明确排除 + +- 未连接、未迁移、未修改任何生产数据库; +- 未安装 systemd unit,未改服务器 Nginx,未申请或续签 TLS; +- 未重启 Petstore、Gitea、GitLab 或其他服务; +- 未使用真实生产域名、微信账号、AppSecret、密码或 token; +- 未上传微信体验版/正式版,未完成真机或生产三角色 smoke; +- 未接入真实外部监控和告警通知渠道; +- 不包含支付、库存、储值、复杂会员、多门店组织、订单系统或泛 AI。 + +## 4. 本地验证证据 + +环境: + +- OpenJDK `21.0.10`(源码按 Java 17 release 编译) +- Maven `3.9.14` +- Node `v22.22.2` +- npm `10.9.7` + +结果: + +| 门禁 | 结果 | +|---|---| +| `mvn test` | 158 tests,0 failure / 0 error / 0 skipped | +| `mvn -DskipTests package` | BUILD SUCCESS | +| backend shell `bash -n` | PASS | +| backend/admin/frontend `git diff --check` | PASS | +| admin `npm run build` | PASS;存在既有大 chunk warning,不阻塞功能验证 | +| frontend `npm run build:h5` | PASS | +| frontend `npm run build:mp-weixin` | PASS;`verify-mp-app-json` PASS | +| admin/frontend 默认占位配置门禁 | 按预期失败 | +| admin/frontend 受控测试 HTTPS 源门禁 | PASS;输出只含变量名,不含值 | +| `validate_ontology.py` | 238 条引用完整 | +| `audit_drift.py` | 55 endpoints、13 JPA entities,code-only / ontology-only 均为 0 | + +## 5. 本地构建校验和 + +| 产物 | SHA-256 | 可部署性 | +|---|---|---| +| `backend/target/petstore-backend-1.0.0.jar` | `e4ef2477c549c77c7bd0ed392d772c2abd62fb5fdf2e932124cd82726e7d01d2` | 可作为候选 jar;仍须最终生产环境预检 | +| admin `dist` 文件哈希清单的聚合 SHA-256 | `017089e4c7fd236b6efcbdcba10b32b9e423e3ccef9613e7ea66717b65b2e984` | **不可部署**:使用仓库 `.invalid` 占位配置构建,仅证明可编译 | +| H5 `dist/build/h5` 聚合 SHA-256 | `d816be92a12f353b2edb6f19987936f7f25e1b09d0a285c4203da494caeb123e` | **不可部署**:使用仓库 `.invalid` 占位配置构建,仅证明可编译 | +| 小程序 `dist/build/mp-weixin` 聚合 SHA-256 | `97489bb9b4b93f96606a10459b6d73526c8287d598019c1651490be854376084` | **不可上传**:使用仓库 `.invalid` 占位配置构建,仅证明可编译 | + +正式 admin/H5/小程序必须用权限受控的真实域名文件重新运行 `preflight:release` 和构建,并把新产物校验和追加到生产发布记录。 + +## 6. 生产 Go/No-Go 门禁 + +- [ ] Petstore 专用 API/admin/report 域名与 TLS 已就绪,不复用其他产品域名; +- [ ] 最终配置通过后端、admin、frontend release preflight,未输出任何秘密值; +- [ ] 生产数据库完成全量备份、SHA-256 和隔离恢复演练; +- [ ] 按顺序执行身份拆分、StoreCustomer、BusinessEvent、预约容量四个迁移,末尾检查均为 0; +- [ ] 后端 15 项只读不变量预检与 JPA `validate` 通过; +- [ ] Petstore systemd/Nginx 配置独立,`nginx -t` 通过; +- [ ] liveness/readiness 和只读 smoke 通过; +- [ ] boss/staff/customer 三角色人工 smoke 及权限/异常反例通过; +- [ ] 微信合法域名、体验版和真机验证通过; +- [ ] 外部监控、告警联系人和值班窗口真实测试通过; +- [ ] Gitea/GitLab 健康回归通过,配置 diff 证明未被改动; +- [ ] 前一应用/静态/小程序回滚点明确,数据库不做自动 down migration。 + +任一项未勾选,结论均为 `No-Go / Ready for Release`,不得写成已上线。 + +## 7. 风险与回滚 + +- admin 主 bundle 仍有大于 500 kB 的构建 warning;试点流量下不是上线阻断,但应进入后续性能债,不与本次发布安全闭环混排。 +- readiness 会把上传目录或 FFmpeg 依赖异常视为不可接流;服务器权限和二进制路径必须在切流前验证。 +- 四个迁移为前向兼容追加式变更。常规故障只回退应用和静态软链接,不删除列/表/索引;灾难性数据恢复需要独立授权和变更单。 +- 微信小程序必须走平台版本回退,不能依靠服务器软链接。 + +详细步骤见《生产发布与回滚Runbook-2026-08-01.md》;试点运营和指标口径见《试点门店开通清单-2026-08-01.md》;告警见《生产监控与告警基线-2026-08-01.md》。 diff --git a/生产发布与回滚Runbook-2026-08-01.md b/生产发布与回滚Runbook-2026-08-01.md new file mode 100644 index 0000000..bd58f2b --- /dev/null +++ b/生产发布与回滚Runbook-2026-08-01.md @@ -0,0 +1,224 @@ +# 宠小它生产发布与回滚 Runbook + +> 版本:2026-08-01 +> +> 适用范围:3~5 家独立宠物洗护单店试点;backend、admin、H5、微信小程序。 +> +> 当前结论:仓库交付到 `Ready for Release`,未取得真实生产域名、凭据、备份和线上验收证据前,不得标记 `Released`。 + +## 1. 发布原则 + +1. 发布单元必须冻结为四元组:`backend commit + admin commit + frontend commit + docs/RC manifest commit`,同时记录 jar/静态包校验和。 +2. 生产数据库只通过已审核 SQL 迁移;应用固定 `ddl-auto=validate`,禁止启动期改表或修复数据。 +3. 后端预检和自动 smoke 均为只读;创建预约、开始服务、提交报告等写路径只在明确指定的试点门店和人工验收窗口执行。 +4. 应用、静态站点采用版本目录加 `current` 软链接切换;数据库迁移为向前兼容的追加式变更,不做自动向下迁移。 +5. Petstore 使用独立域名、Nginx `server` 块、systemd unit 和发布目录。不得覆盖、重启或复用同机 Gitea/GitLab 的配置与服务。 +6. 任何命令输出都不得包含数据库密码、微信 AppSecret、session secret、完整报告 token、手机号或私密媒体 URL。 + +## 2. 本次发布任务卡 + +| 字段 | 值 | +|---|---| +| Task Name | 建立可重复的生产发布与回滚闭环 | +| Role | Backend Ops / Frontend Release Ops | +| Owner Agent | Backend Ops(后端与 Nginx)/ Frontend Release Ops(admin、H5、小程序) | +| Paired QA | Core Flow QA / Report Share QA | +| Workstream | Release | +| Track | Ops | +| Status | Ready for Release(生产证据待补) | +| Priority | P0 | +| Repo/Path | `backend/deploy/**`、前端发布配置、`docs/**` | +| Service Lock | Petstore backend unit + Petstore 专用 Nginx server blocks + Petstore static roots | +| Depends On | 冻结 RC、真实域名/TLS、备份、四个迁移、微信合法域名 | +| Acceptance | 预检、构建、readiness、只读 smoke、三角色人工 smoke、回滚演练均有证据 | +| RC Included | Yes | +| Due Date | TBD | +| Output Link | 当前 Runbook + RC manifest | +| Blocker Owner | 生产环境负责人(域名/凭据/变更窗口) | +| Commit / RC | Not Frozen,提交后写入 RC manifest | +| Architecture Review Required | Yes | +| Data Model Review Required | Yes(迁移顺序与回滚边界) | +| Access / Privacy Review Required | Yes | + +## 3. 立即停止条件 + +出现任一情况立即停止,不继续切流: + +- 四仓 commit、构建产物或校验和与 RC manifest 不一致; +- 生产域名仍为 localhost、`.invalid`、`api.s-good.com` 或 `www.s-good.com` 等非本项目域名; +- 数据库备份未完成、校验和缺失或未在隔离库验证可恢复; +- 任一迁移末尾验证查询非 0,或预检的 15 项只读不变量非 0; +- `nginx -t` 失败,或 diff 显示将修改 Gitea/GitLab 的 server block; +- readiness 非 `UP`,上传目录/FFmpeg/数据库任一依赖失败; +- 发现凭据、完整 token、手机号或私密媒体地址进入日志/工单/截图; +- 无明确值班人、回滚执行人或生产变更授权。 + +## 4. 发布前冻结 + +RC Steward 在 `docs/qa-reports` 创建当次 manifest,至少记录: + +- 四仓 commit、分支、`git status --short`(必须为空); +- Java/Node/npm/Maven 版本; +- jar、admin `dist`、H5 `dist`、小程序构建目录的 SHA-256; +- 目标环境、变更窗口、操作者、审核人; +- 本次包含/排除项、已知风险、旧版本回滚点; +- 域名只记 host,不记 query/token;配置只记变量名和“已配置”,不记值。 + +冻结后重新运行本地门禁: + +```bash +cd /Users/apple/_src/petstore/backend +mvn test +mvn -DskipTests package +bash -n deploy/release-preflight.sh deploy/production-smoke.sh + +cd /Users/apple/_src/petstore/admin +npm run build + +cd /Users/apple/_src/petstore/frontend +npm run build:h5 +npm run build:mp-weixin + +cd /Users/apple/_src/petstore +python3 docs/graph/validate_ontology.py docs +python3 docs/graph/audit_drift.py +``` + +## 5. 数据库备份与迁移 + +### 5.1 备份证据 + +使用权限受控的 MySQL option file,避免把密码写在命令行。备份必须包含表结构、数据、触发器、事件和存储过程,并记录文件大小、SHA-256、开始/结束时间。示例: + +```bash +mysqldump --defaults-extra-file=/secure/path/petstore-mysql.cnf \ + --single-transaction --routines --triggers --events --hex-blob \ + petstore > /secure/backup/petstore-before--.sql +shasum -a 256 /secure/backup/petstore-before--.sql +``` + +上线前必须把该备份恢复到隔离数据库并完成基础计数核对;“命令退出 0”不能替代恢复演练。备份保留周期由数据负责人决定,本流程不自动删除任何备份。 + +### 5.2 固定迁移顺序 + +在维护窗口中按以下顺序人工执行,每一步都保存执行时间和脚本 SHA-256,并检查脚本末尾验证查询: + +1. `backend/db/migrations/20260801_split_service_identity.sql` +2. `backend/db/migrations/20260801_create_store_customer.sql` +3. `backend/db/migrations/20260801_create_business_event.sql` +4. `backend/db/migrations/20260801_create_booking_capacity.sql` + +禁止跳步、交换顺序或让应用自动补表。任一验证项非 0,停止发布并由 Data Model/业务 owner 判断修复方案;不得猜测性回填客户身份、报告作者或事件。 + +### 5.3 生产只读预检 + +把 `backend/deploy/petstore-backend.env.example` 复制到服务器权限受控的 `/etc/petstore/backend.env` 并填入真实值;文件权限建议 `0600`,所有者为服务账号。不要在交互 shell 中 `source` 该文件,也不要用 `env $(cat ...)`,避免特殊字符被 shell 解释或泄露。通过一次性 systemd unit 以与正式服务相同的 `EnvironmentFile` 和服务账号执行: + +```bash +systemd-run --wait --pipe --collect \ + --unit=petstore-preflight- \ + --property=Type=oneshot \ + --property=User=petstore \ + --property=Group=petstore \ + --property=WorkingDirectory=/opt/petstore/backend/releases/ \ + --property=EnvironmentFile=/etc/petstore/backend.env \ + /usr/bin/env BUILD_FIRST=0 \ + JAR_PATH=/opt/petstore/backend/releases//petstore-backend.jar \ + ./deploy/release-preflight.sh +``` + +通过条件:静态配置检查通过、JPA `validate` 通过、15 项数据不变量全部为 0,并以退出码 0 结束。预检不会执行迁移或修复数据。 + +## 6. 构建发布产物 + +### 6.1 后端 + +将已冻结 jar 放入 `/opt/petstore/backend/releases//petstore-backend.jar`,同时归档 commit、SHA-256 和 `deploy/` 模板。systemd unit 以 `backend/deploy/petstore-backend.service.example` 为基线,由服务器管理员安装为 Petstore 独立 unit。 + +### 6.2 Admin 与 H5 + +真实域名配置保存在权限受控文件,不提交仓库。门禁和构建必须使用同一份内容: + +```bash +cd /opt/build/petstore/admin +PETSTORE_ADMIN_ENV_FILE=/secure/path/petstore-admin.env npm run preflight:release +cp /secure/path/petstore-admin.env .env.production.local +npm run build + +cd /opt/build/petstore/frontend +PETSTORE_FRONTEND_ENV_FILE=/secure/path/petstore-h5.env npm run preflight:release +cp /secure/path/petstore-h5.env .env.production.local +npm run build:h5 +``` + +产物进入版本目录后再切换 `current` 软链接。构建结束应清除工作区内临时 `.env.*.local`,但保留受控配置源和构建证据。 + +### 6.3 微信小程序 + +```bash +cd /opt/build/petstore/frontend +PETSTORE_FRONTEND_ENV_FILE=/secure/path/petstore-mp.env npm run preflight:release +cp /secure/path/petstore-mp.env .env.mp-weixin.local +npm run build:mp-weixin +``` + +微信公众平台的 `request` / `downloadFile` 合法域名必须与门禁通过的实际域名一致。先上传体验版并完成真机三角色验收,再决定提审/发布;后端或 H5 发布不会自动更新用户手机里的小程序。 + +## 7. 切换应用与 Nginx + +1. 将后端 `current` 指向新版本目录,执行 `systemctl daemon-reload`,只重启 Petstore 独立 unit。 +2. 确认进程稳定并通过本机 readiness,再切 admin/H5 静态目录 `current`。 +3. 以 `backend/deploy/nginx-petstore.conf.example` 新建 Petstore 专用配置文件;替换域名、证书路径和静态根目录。 +4. 执行 `nginx -t`。检查配置 diff 仅增加/修改 Petstore 的三个 server block,不改 Gitea/GitLab upstream、端口、证书和 location。 +5. 只执行 Nginx reload,不停止 Nginx;reload 后同时检查 Petstore、Gitea、GitLab 的既有健康入口。 + +证书续签必须沿用服务器现有 ACME/证书管理方式,为 Petstore 新域名单独申请/续签;不要替换其他产品证书。续签后先 `nginx -t` 再 reload,并记录证书到期时间。 + +## 8. 上线验证 + +### 8.1 自动只读 smoke + +```bash +cd /opt/petstore/backend/current +API_ORIGIN=https:// ./deploy/production-smoke.sh +``` + +脚本验证 liveness、readiness、公开门店、服务时长和可约容量契约;提供受控的 `SMOKE_BEARER_TOKEN` 时才增加两个 protected GET。脚本不会创建或修改业务数据。 + +### 8.2 三角色人工 smoke + +仅在预先标记的试点门店和测试宠主上执行,记录匿名化预约 ID/报告 ID,不记录完整 token/手机号: + +1. **boss**:登录 admin,核对门店营业时段、并发容量、服务时长;创建员工;查看工作台、客户、日程。 +2. **staff**:代客预约,开始服务,上传最小合规素材,提交一约一报告,复制报告话术;确认重复提交被拒绝。 +3. **customer**:小程序自助预约并验证长服务跨时段占号;打开匿名报告页;提交提醒留资;再次预约。 +4. **权限反例**:customer 不可进入后台;A 店账号不可读取 B 店预约/报告/线索;公开报告响应不含内部身份字段。 +5. **异常反例**:无容量、非法状态迁移、缺前/后照片、错误 token、FFmpeg 失败均有可理解提示。 + +## 9. 回滚 + +### 9.1 应用与静态资源回滚 + +- 保留前一版本目录和软链接目标。若新版本 readiness、错误率或主链路失败,先停止切流,把 backend/admin/H5 的 `current` 恢复到 RC manifest 中的前一版本,再只重启 Petstore unit、`nginx -t` 并 reload。 +- 微信小程序不能靠服务器软链接回滚。应在公众平台保留上一稳定版本,按微信流程回退;同时确认其 API 契约仍受当前后端兼容。 +- 回滚后重新执行只读 smoke,并记录故障窗口、影响门店、回滚时间和 owner。 + +### 9.2 数据库回滚边界 + +本批四个迁移以新增列/表/索引和兼容回填为主,旧应用应忽略新增结构。因此常规回滚只回应用,不删除列、表、索引,不执行自动 down migration,也不把已产生的新业务数据覆盖回旧备份。 + +只有确认发生灾难性数据破坏、已冻结所有写入、明确接受备份时间点之后的数据丢失,并取得用户/业务负责人、DBA 和隐私责任人的书面授权后,才可执行整库恢复。恢复必须先在隔离库验证,再制定专门变更单;本 Runbook 不授权该操作。 + +## 10. 发布完成定义 + +只有以下证据全部归档,RC 才能由 `Ready for Release` 改为 `Released`: + +- 四仓冻结 commit 与产物校验和; +- 备份及隔离恢复验证; +- 四个迁移执行和末尾验证结果; +- 最终配置门禁与后端只读预检; +- `nginx -t`、readiness、自动只读 smoke; +- boss/staff/customer 三角色人工 smoke; +- Gitea/GitLab 回归未受影响; +- 监控、告警联系人、回滚点和值班窗口; +- 未解决问题清单及 Go/No-Go 决策人。 diff --git a/生产监控与告警基线-2026-08-01.md b/生产监控与告警基线-2026-08-01.md new file mode 100644 index 0000000..eebe956 --- /dev/null +++ b/生产监控与告警基线-2026-08-01.md @@ -0,0 +1,91 @@ +# 宠小它生产监控与告警基线 + +> 适用阶段:3~5 家单店试点。阈值是首版基线,上线两周后按真实流量校准。当前仓库只提供健康探针与运行手册;外部监控、日志平台和通知渠道必须在生产发布前实际接入并留证。 + +## 1. 监控目标 + +按优先级守住四件事: + +1. 预约、开始服务、提交报告、打开报告的主链路可用; +2. 不产生跨店、身份错绑、状态回退、重复报告或容量超卖; +3. 数据库、上传目录、磁盘、FFmpeg 和 TLS 等运行依赖可用; +4. 监控本身不泄露密码、手机号、完整 token、微信标识或私密媒体 URL。 + +## 2. 健康探针 + +| 探针 | 地址 | 含义 | 使用方式 | +|---|---|---|---| +| Liveness | `/actuator/health/liveness` | JVM/Spring 是否存活 | 连续失败才重启,避免业务依赖短抖导致重启风暴 | +| Readiness | `/actuator/health/readiness` | DB、磁盘、上传目录、FFmpeg/ffprobe 是否可用 | 非 UP 立即摘流,不把请求送给未就绪实例 | + +生产 `show-details=never`,公网响应只显示总状态。探针日志不得附环境变量值。`petstoreRuntime` 结果缓存 30 秒,避免每次 readiness 都启动外部进程。 + +## 3. 首版告警阈值 + +| 信号 | Warning | Critical | Owner | +|---|---|---|---| +| Readiness | 1 次失败 | 连续 2 次失败或持续 2 分钟 | Backend Ops | +| HTTP 5xx | 5 分钟 ≥ 2% 或 ≥ 5 次 | 5 分钟 ≥ 5% 或 ≥ 20 次 | Backend Ops + 业务 owner | +| API p95 | 10 分钟 > 1 秒 | 10 分钟 > 2 秒 | Backend Ops | +| DB 连接池 | active/max 持续 5 分钟 > 70% | 持续 5 分钟 > 85% 或获取超时 | Backend Ops / Data Model | +| 磁盘 | 可用 < 20% 或 < 20 GB | 可用 < 10% 或 < 10 GB | Backend Ops | +| 上传失败 | 10 分钟 ≥ 3 次 | 10 分钟 ≥ 10 次或连续失败 | Report Media Backend | +| 成片失败 | 30 分钟失败率 ≥ 10% | ≥ 20% 或连续 3 个任务失败 | Report Media Backend | +| TLS | 距到期 < 21 天 | 距到期 < 7 天 | Backend Ops | +| 主链路人工救援 | 当日 ≥ 1 次 | 同类问题当日 ≥ 3 次 | Core Flow QA / PM | +| 权限或隐私异常 | 不设 Warning | 任意 1 次即 Critical | System Architect / Privacy owner | + +低流量试点同时看比例和绝对数量,避免一个请求造成噪音;权限、隐私和数据串店不做流量豁免。 + +## 4. 应用与业务信号 + +### 技术指标 + +- 按 endpoint family 聚合请求量、2xx/4xx/5xx、p50/p95/p99,不记录 query 中的 token; +- JVM heap、GC pause、线程数、进程重启次数; +- Hikari active/idle/pending/max 和获取连接耗时; +- 上传目录容量、inode、读写失败; +- FFmpeg/ffprobe 可执行性、成片耗时、失败归类(material/service/network/unknown); +- Nginx 499/502/503/504、上游延迟、证书到期; +- 每次发布的 commit、时间、操作者和 readiness 变化标记。 + +### 业务漏斗 + +每天按门店聚合 `appointment_created → service_started → service_completed/report_submitted → report_opened → lead_submitted → rebooked`。试点初期不把业务漏斗波动直接当系统故障,但需检查: + +- 有 done 无报告、报告无客户/作者、重复报告; +- BusinessEvent 缺失或幂等键重复; +- 服务时长/预约快照非法、容量超卖; +- 报告已提交但长期未打开; +- 回访后没有明确状态或再次预约归因。 + +发布前的 `release-preflight.sh` 已覆盖前四类结构/数据不变量;运行期应每天做等价的只读审计或报表,不自动修复。 + +## 5. 日志与隐私 + +- 采用结构化字段:timestamp、level、traceId、endpoint、status、duration、storeId(内部受控); +- 不记录请求/响应全文,不记录密码、验证码、AppSecret、session token、完整 report token、完整手机号、openid/unionid 或媒体签名 URL; +- 公开报告只允许使用不可逆短 hash 做技术关联,业务事件表本身不保存 token/hash; +- 错误对用户返回稳定业务码和可理解文案,堆栈只进入受控日志; +- 生产日志访问采用最小权限,导出/截图前脱敏,并设置保留周期。 + +## 6. 告警处理流程 + +1. 告警必须带:环境、时间、信号名、当前值、受影响 endpoint/门店数量、最近发布版本;不得带敏感值。 +2. 值班人先判断是否为探针、依赖或业务错误;readiness Critical 先摘流,权限/隐私 Critical 先关闭相关入口。 +3. 15 分钟内明确 owner 和下一动作;30 分钟仍未恢复则启动回滚评估。 +4. 若问题与新版本相关且旧应用兼容当前数据库,按 Runbook 切回上一版本;禁止为了快速恢复而删新列/表或直接改业务状态。 +5. 恢复后补时间线、根因、影响、修复、验证和预防项;事故报告不粘贴真实 token/手机号/媒体 URL。 + +## 7. 上线前接入清单 + +- [ ] 外部 HTTPS 探测覆盖 liveness/readiness、admin、H5 报告入口; +- [ ] 5xx、延迟、DB 池、磁盘、证书和进程重启已配置告警; +- [ ] FFmpeg/上传失败有可聚合字段,而非只能搜索自由文本; +- [ ] BusinessEvent 日漏斗和 15 项只读数据审计有每日结果; +- [ ] 通知渠道、Primary/Backup 值班人和升级联系人完成真实测试; +- [ ] 日志脱敏抽查通过,访问权限和保留周期已批准; +- [ ] 发布标记能关联四仓 commit 与 RC; +- [ ] Gitea/GitLab 有独立监控,Petstore 发布后做旁路回归。 + +未完成以上接入,RC 只能保持 `Ready for Release`。 diff --git a/试点门店开通清单-2026-08-01.md b/试点门店开通清单-2026-08-01.md new file mode 100644 index 0000000..d8f3905 --- /dev/null +++ b/试点门店开通清单-2026-08-01.md @@ -0,0 +1,119 @@ +# 宠小它试点门店开通清单 + +> 目标:让 3~5 家独立宠物洗护单店用真实业务完成约 200 次“预约 → 服务 → 报告 → 查看/回访 → 再次预约”闭环,以数据决定 Phase 2 是否进入商业化。本文是开通和验收清单,不代表任何门店已经开通。 + +## 1. 试点准入 + +每家门店必须满足: + +- 有一名可决策的老板和一名日常负责人,接受至少 4 周连续试用; +- 主要业务是宠物洗护/美容,愿意把真实预约和服务报告录入系统; +- 明确同意试点数据用途、照片/视频告知方式、数据保留与退出处理; +- 可配置真实营业时段、同时接待数、服务项目和预计时长; +- 有可用手机/电脑、稳定网络和微信小程序使用条件; +- 接受上线首周每日反馈、之后每周复盘,不要求支付、库存、储值、复杂会员、多门店组织等本期外能力。 + +不满足隐私告知、负责人、真实业务量或反馈承诺的门店,不进入试点。 + +## 2. 门店建档卡 + +每店单独保存,敏感信息进入权限受控系统,不写入公开文档或提交记录: + +| 项目 | 填写要求 | +|---|---| +| 试点门店编号 | 使用内部编号,不用公开昵称代替主键 | +| 老板 / 日常负责人 | 姓名、联系方式、可响应时间(受控保存) | +| 预计周服务量 | 用于判断 200 次样本达成时间 | +| 营业日与营业时段 | 精确到半小时容量桶 | +| 同时接待数 | 1~10,必须与现场工位/人力真实匹配 | +| 服务项目 | 名称、预计时长(30~480 分钟,30 分钟倍数) | +| 员工名单 | 最小必要账号;离职/退出当天停用 | +| 门店 logo/电话/地址 | 宠主公开页允许展示的内容,经门店确认 | +| 素材告知方式 | 何时向宠主说明拍摄、报告链接和转发风险 | +| 支持群与升级联系人 | 产品、技术、隐私各自 owner | +| 上线日期 / 退出日期 | 明确试点窗口和退出处理 | + +## 3. 开通前配置 + +- [ ] 创建门店和 boss,首次登录后更换/确认安全登录方式。 +- [ ] boss 创建最小必要 staff;customer 不能进入 admin。 +- [ ] 核对门店公开字段,不展示邀请码、ownerId 等内部信息。 +- [ ] 配置营业开始、最后容量桶和真实 `bookingCapacity`。 +- [ ] 配置全部在售服务项目及 `durationMinutes`;不使用笼统默认时长掩盖差异。 +- [ ] 建立午休/停约 `blocked` 与到店客 `walk_in` 的操作约定。 +- [ ] 用未来一天验证:长服务覆盖全部时段、超容量不可约、取消后释放容量。 +- [ ] 清除 demo 门店/万能验证码/测试宠主和无效服务项目;测试记录与真实数据可区分。 +- [ ] 确认报告前后照片最少各一张、服务过程素材可选、报告提交后锁定。 +- [ ] 告知员工:完整报告 token、手机号和媒体链接不得贴入群聊、工单或截图。 + +## 4. 上线日三角色验收 + +### Boss + +- 登录 admin,查看工作台、预约、客户、日程、报告、线索和设置; +- 新增/停用一个试点员工;确认跨店数据不可见; +- 调整一个服务时长或容量配置,并由第二人复核; +- 确认当天异常处理和人工回退联系人。 + +### Staff + +- 按手机号关联真实宠主做一次代客预约,员工身份不被写成宠主; +- 开始服务、上传前/过程/后素材、提交报告并发送给宠主; +- 验证重复报告、缺照片、非法状态迁移均被拒绝; +- 在日程中登记一次 `walk_in`,必要时登记一次 `blocked`。 + +### Customer + +- 扫门店入口,选择服务和可约时段,提交预约; +- 打开报告公开页,确认门店与宠物信息准确且没有内部字段; +- 提交提醒留资,重复提交不生成重复线索; +- 从报告页再次预约,验证门店和服务上下文连续。 + +## 5. 试点期支持节奏 + +- 上线前 2 天:完成培训、模拟全链路、回滚演练和联系人确认。 +- 上线第 1~3 天:每日核对预约、完成、报告、报告打开、留资和失败记录。 +- 第 2 周起:每周一次 30 分钟复盘,先解决阻塞履约的问题,不以新增页面代替流程修复。 +- 严重权限/隐私/数据错误:立即停用相关入口并升级;容量或状态脏数据:暂停新增预约直至核对完成。 +- 门店退出:停用账号和入口,按约定导出/保留/删除数据,并记录完成证据。 + +## 6. 200 次完整服务的统计口径 + +“一次完整服务”必须同时满足: + +1. 有真实 `Appointment`,客户身份和创建人语义正确; +2. 合法 `new → doing → done`; +3. 有且仅有一份绑定报告,至少包含服务前/后照片; +4. 报告已产生可发送链接; +5. BusinessEvent 可追溯预约创建、服务开始、服务完成/报告提交。 + +样本按门店、服务类型、创建来源(customer/staff/boss)分层,测试预约、取消预约、重复数据和纯演示数据不计入 200 次。 + +## 7. 每周指标 + +| 指标 | 口径 | 用途 | +|---|---|---| +| 完整服务数 | 符合上方五项的服务 | 判断样本进度 | +| 预约完成率 | done / 有效预约 | 判断主链路稳定性 | +| 报告提交率 | 有报告的 done / done | 判断员工执行成本 | +| 报告打开率 | 首次打开报告 / 已提交报告 | 判断宠主价值 | +| 留资率 | 去重留资 / 首次打开报告 | 判断回访意愿 | +| 再次预约率 | 报告后窗口内产生新预约的 StoreCustomer / 可观察客户 | 判断闭环价值 | +| 容量冲突率 | 因无容量被拒绝的请求 / 可约请求 | 校准容量而非承诺零等待 | +| 人工救援率 | 需技术/运营改数据或绕流程的服务 / 完整服务尝试 | 判断是否可规模化 | +| P0 故障数 | 权限、数据、主链路阻断 | Go/No-Go 硬门槛 | + +指标只使用最小必要业务 ID 和聚合数;周报不展示完整手机号、token 或私密媒体 URL。 + +## 8. 阶段退出标准 + +进入 Phase 2 决策评审前,至少满足: + +- 3~5 家门店真实运行,累计约 200 次完整服务; +- 连续两周无未解决的 P0 权限、隐私或数据一致性事故; +- 核心链路不依赖开发者直接改库或手工补状态; +- 老板与员工能独立完成日常预约、服务、报告和回访; +- 再次预约闭环有可统计证据,而不是仅有页面或口头反馈; +- 各门店的继续使用意愿、付费假设和主要阻碍被记录。 + +未满足时继续修复产品内核,不提前引入支付、库存、储值、复杂会员、多门店组织或泛 AI。