d605c04707dee5038cd8f159751d611483a3ded5
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8dee67040c |
fix(miniapp): 幂等键改为按操作稳定;删除指向 404 的互选死代码
1) 幂等键(配合后端机制)
原写法把 Date.now() 拼进幂等键,每次调用都生成新键,即使后端实现了幂等
也永远不会命中 —— 声明端与实现端各自都坏。
api.idempotencyKey(scope, subject):键缓存在 _pendingIdempotency(TTL 10min),
同一逻辑操作在成功之前复用同一把键,收到 200+success 才释放;失败(含网络错误)
不释放,下次重试沿用同一把键。25 处调用点(8 个 service + 2 个 TS 页面)全部替换,
Date.now 幂等键写法残留经 grep 为 0。
2) 删除死代码 submitInterests
它请求 POST /activities/{id}/interests —— 后端从未实现该端点
(Phase 16 已把表达意向合并进 /activities/{id}/feedback 的
interestTargetUserIds)。全仓确认无任何页面/组件调用方。
两个文件各留注释说明删除原因与恢复条件:若产品确认需要独立的
「提交互选」端点,应先定义语义并补后端实现,再恢复前端方法。
store 头部关于 AGENTS 第 6 条(单向意向保密)的注释同步改为引用
submitFeedback 返回的 newMutuals。
|
||
|
|
72b4e9b846 |
fix(ops-backend/service-station): close Phase 19 KEEP-VERIFY — 25 defects (unreachable announcements + station write privilege escalation; banner 500 + soft-delete leak; content publish dead-end; program approval dead-end/draft leak/zero counts/missing withdraw; zero-coverage test schema) ; add V042 announcement chain + 34 guard tests; full regression 602 green
Backend - Service Station: expose the 「临时公告」 chain end-to-end (V042 table/service existed but had ZERO endpoints -> both admin and miniapp could never reach it): 5 operator endpoints (list/create/update/publish/archive) + 1 user-facing read endpoint. - Fix privilege escalation: create/update were guarded only by the class-level service_location.view, making V015's service_location.manage a never-used dead permission -> read-only roles could edit stations. Permissions moved to method level. - Add ServiceLocationOperatorController to OperatorEndpointPermissionCoverageTest (it was NOT scanned, which is why the escalation stayed invisible). - Add create/update validation (address NOT NULL + status CHECK only blew up on PG). - Banner: validation failures threw bare RuntimeException -> 500; now VALIDATION_ERROR -> 400. Add deleted_at IS NULL to both home queries (soft-deleted banners stayed live). - Content: operator list/detail split (admin «publish» was a dead end because it reused the published-only public API); category deactivate/reactivate; article body on edit. - Service Program: operator member list now includes pending (approval reachable); active-only public visibility (draft leak); memberCount/pendingCount aggregation; withdraw/removeMember; reuse row on re-apply (UNIQUE(program_id,user_id) crash). - MeController: add the missing GET /me/service-history route. Schema / guards - schema-h2.sql: add service_location_announcements (+ chk_announcement_status) and content_categories (+ uk_content_category_code, chk_category_status); move both into REQUIRED_IN_TEST_SCHEMA. MigrationValidationTest baseline 41 -> 42. - V11PermissionMatrixContractTest now scans ALL migrations instead of hardcoding V015+V029. - New: ServiceLocationAnnouncementPhase19IntegrationTest (24), ServiceLocationControllerPermissionContractTest (5), ServiceLocationAnnouncementConstraintParityTest (5, prod DDL <-> test schema <-> backend whitelist <-> admin labels). Frontend - admin-web: ServiceStationView gains announcement management (statuses, create/edit, publish/archive with confirm, expiry); write buttons gated by service_location.manage. - miniapp: U50 renders the 临时公告 block via new serviceStationService; page no longer talks to utils/api directly; announcement failure degrades to [] instead of erroring. - ui-tests: U37 four-group + U50 announcement contracts; /home assertion retargeted to the service layer. Verification - Negative verification: 3 injected defects (drop soft-delete filter / drop test-schema CHECK / downgrade write endpoint to view) -> 4 named tests fail precisely, no collateral failures; restored, NEG-VAL residue = 0. - Backend full regression: 602 tests, 0 failures, 0 errors, 3 skipped (baseline 420). - admin-web: vue-tsc --noEmit EXIT=0, vite build EXIT=0. Miniapp: tsc --noEmit EXIT=0. - ui-tests 14/14 PASS. |
||
|
|
29112eed6e |
fix(activity): close Phase 15 KEEP-VERIFY/EXTEND — 2 critical + 14 other defects, 3 test-fidelity fixes
Phase 15 (activity registration / review / checkin) verification closure. Two CRITICAL defects, both "production always had it, test schema never did": - D1: generateCheckinToken wrote the token to Redis with value "used", while scanCheckin rejects any token marked "used" -> every freshly issued check-in code was dead on arrival, staff scanning failed 100% of the time. Issuing now writes "issued"; "used" is set only after a successful scan. - D9: UNIQUE(activity_id, user_id) exists in production, but re-registering after cancel/reject went through INSERT -> users who cancelled once could never register for that activity again (duplicate key 500). The H2 test schema lacked the UNIQUE, so the path had zero coverage. Now the original row is reused via an atomic conditional UPDATE. Checkin (D2-D8): - D2: duplicate check-in was read-then-write; now atomic checkinIfNotCheckedIn and repeat scans are idempotent (alreadyCheckedIn) without duplicate audit. - D3: no data scope at all -> added activity staff assignment (V040) and assertCheckinScope; INV-020 (org relation grants nothing). - D4: manualCheckin did not validate activity existence/cancellation. - D5: getCheckinStats NPE on missing activity; remainingCount was semantically "free seats" -> replaced with notCheckedInCount. - D6: malformed timestamp caused 500 -> 400. D7: constant-time signature compare. Registration / review / cancel (D10-D13): - D10: backend registration list only queried status='approved' -> the review screen never saw pending rows (review unusable). - D11: registration_start_at / registration_end_at were never enforced. - D12: cancel had no audit log (the only write without one). - D13: reject/cancel were read-then-full-update -> rejectIfPending/cancelIfActive. Capacity groups (D14, plan item 2): groups were stored and read back but never participated in capacity -> incrementIfAvailable / decrementIfPositive now make approved_count real; activities without groups are unaffected. Frontend contract (F1-F2): - F1: admin-web had no check-in surface at all -> ActivityCheckinView.vue (paste/scan token, manual check-in, stats) + /activities/checkin route & nav. - F2: miniapp had no cancel-registration wrapper although the endpoint existed. Test fidelity (T1-T3): - T1: test schema gained UNIQUE(activity_id,user_id) + chk_reg_status + chk_checkin_status; two new guards parse the real V010 DDL and compare. - T2: 3 assertTrue(true) placeholders in CheckinServiceTest (covering duplicate check-in, expiry, no-sensitive-plaintext) replaced with 5 real guards that parse the real source and real production DDL. - T3: activity_registrations + activity_staff_assignments moved into REQUIRED_IN_TEST_SCHEMA; MigrationValidationTest baseline 39 -> 40. Note: ActivityStaffAssignment must NOT extend BaseEntity - the table is a composite PK (activity_id, operator_id); BaseEntity would add id/updated_at/ deleted_at to every INSERT and break on PostgreSQL (caught by EntityMigrationParityTest immediately). Tests: ActivityPhase15Test (21), CheckinServiceTest (5), 2 new constraint guards. Negative verification: 3 injected defects -> 8 assertions turned red, all restored. Regression: 474 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS (baseline 450). admin-web vue-tsc 0 error, vite build OK. |
||
|
|
5b3bad326e |
fix(matchmaker,referral): close Phase 13 KEEP-VERIFY/EXTEND — 7 real defects
KEEP-VERIFY/EXTEND 复验(历史 ✅ 不可信;测试覆盖函数 ≠ 覆盖运行时路径)。
逐条核对运行时路径与生产约束后修复 7 处缺陷,其中 3 处「只在真实 PostgreSQL 上炸」。
D1 (Critical, 生产故障)
matchmaker_recommendations.status 代码写 liked/passed/confirmed_mutual,
而 V009 chk_rec_status 只允许 pending_view/viewed/responded 且从未扩展
→ 真实 PG 上用户接受/跳过推荐必崩。测试库未复刻该 CHECK,故长期静默。
D2 (Critical, 生产故障)
控制器缺省 serviceType="recommendation" 不在 chk_matchmaker_service_type
白名单 → 提交红娘服务必崩。
D1+D2 同修于新增 V038__align_matchmaker_status_constraints.sql
(沿用 V018/V020/V025 的 DROP CONSTRAINT IF EXISTS + 重加白名单惯例)。
D3 (High, 越权写)
MatchmakerServiceService.recommendPartner 无 assignedOperatorId 归属校验,
且可把用户推荐给自己 → 补校验 + 禁止自我推荐。
D4 (High, 越权读)
getServiceDetail 无归属校验且返回完整用户资料(userId/description/
userProfile/followups)→ 签名加 requesterId,按调用者返回完整/脱敏视图。
D5 (High, 越权写)
ReferralService 的 userId 取自请求体且零授权校验 → 新增
assertMatchmakerServesUser,在 referToServiceStation/referToActivity 入口
强制校验存在进行中的服务关系。
D6 (High, 生产故障)
MatchmakerFollowupMapper.findByServiceId 只有声明无 @Select
→ Invalid bound statement (not found),getServiceDetail 必崩。
该缺陷由本轮新增测试首次暴露(历史测试从未调用该路径)→ 补 @Select。
D7 (Medium, 契约缺口)
admin-web 5 处 API 缺口(4 条 404 + C 端端点被运营端误用)+ 小程序缺转介服务层
→ 后端补 GET /operator/matchmaker/recommendations 与
GET /referrals/operator/history 两个运营端端点;前端改路径、
assign 改 POST + body operatorId;新增 miniapp/services/referralService.js。
测试保真度
- 测试库补 matchmaker_services/recommendations/followups 三表(此前该模块零真实覆盖),
约束与 V038 对齐;EntityMigrationParityTest 三表移入 REQUIRED_IN_TEST_SCHEMA;
MigrationValidationTest LATEST_MIGRATION 37 → 38。
- 新增 MatchmakerReferralPhase13Test(17 用例,@SpringBootTest 走真实方法路径 + 真实落库)。
- 重写 MatchmakerServiceTest(6 用例):直接解析 db/migration/V*.sql,按文件名顺序应用
DROP CONSTRAINT IF EXISTS + ADD CONSTRAINT 取"最终有效"白名单,并内置解析器自检。
【关键】首版守卫硬编码白名单副本,注入 D1 后仍全绿(对漂移零反应)→ 必须解析真实 DDL。
- 删除 3 处假测试占位(MatchmakerServiceTest.testMatchmakerCannotLike 的 assertTrue(true);
CoreInvariantsTest INV-007 断言的是状态常量、INV-010 亦为 assertTrue(true)),
已如实改名并注明行为验证位置。
负向验证(两轮)
注入 D1(回退 chk_rec_status 白名单)→ 行为守卫 DataIntegrityViolation
+ 常量守卫 Multiple Failures (3 failures) → Tests run: 23, Failures: 1, Errors: 2
/ BUILD FAILURE,守卫有效性得证。缺陷已还原。
全量回归(离线容器,实测)
[WARNING] Tests run: 391, Failures: 0, Errors: 0, Skipped: 3
[INFO] BUILD SUCCESS / Total time: 04:59 min
Phase 12 基线 371 → 391(净 +20)。
报告:docs/Phase-13-V11-Recovery-Completion-Report-2026-09-16.md
Phase 13A 未自动进入,等待用户确认。
|
||
|
|
3244e02e7e |
refactor(api): 关系/匹配域采用 2026-09-14 canonical 路径,消除三代并存
## 问题:同一能力存在三代路径,前端指向的是已废止的一代
按 mtime 排查后发现"最新"并非上一轮以为的 /relations/*,而是 2026-09-14 的 match 模块:
| 代际 | 位置 | 日期 | 路径 |
|---|---|---|---|
| canonical | platform.match: Like/Block/MatchController | 09-14 12:13~12:14 | /likes /blocks /matches |
| 二代 | platform.relationship: Relationship(Status)Controller | 09-10 10:59 | /relations/* /relationship/* |
| 一代(规范) | REST API 接口详细设计规范.md | 08-28 | /relationships/*(多一个 hips) |
| 前端 | miniapp/services/matchService.js | 08-29 | 混用二代 + 一代 |
后果:缘分三榜、黑名单页、成功牵手流程全部 404;另有两份重复 success 实现 + 一份桩实现。
## 裁决:采用 2026-09-14 最新路径,前端与二代让位(用户指令)
### 补齐 canonical 模块缺口(此前只有 javadoc 声明或服务方法,无控制器)
- RelationService.unlike() + LikeController `DELETE /likes/{targetUserId}`
- 新增 PassController `POST /passes`(RelationService.pass 在 09-14 模块内却无控制器,补齐与 /likes、/blocks 对称)
- BlockController `GET /blocks`(javadoc 早声明"我的 Block 列表"但未实现,mapper findBlockedByUser 已就绪)
- MatchController `POST /matches/{id}/success/request|confirm|decline`(javadoc 标"Phase 12 预留"),
委派真实实现 RelationshipStatusService;authorizedMatch 对"不存在"与"无权"统一回 404,避免探测他人 Match
- LikeResponse 对齐前端 LikeResult:{ relationStatus, mutualMatch, matchedConversationId, matchId }
Mutual 时调 ChatService.getOrCreateConversation 取会话 id → "双向喜欢→跳聊天"链路真正打通
- LikeController/PassController/BlockController 补 targetUserId 空值校验
### 移除被取代的路由与桩实现
- RelationshipController 移除 like/cancelLike/pass/block/unblock/matches/unmatch/success,仅保留 /fate/*
- RelationshipStatusController 移除 /success/*(已由 /matches/{id}/success/* 取代),保留关系状态生命周期
- RelationshipService 删除 requestSuccess/confirmSuccess 桩 ——
前者自述 "TODO: 查询 relationship_success_requests 表" 却返回硬编码 status="pending",
等于绕过 AGENTS 硬规则 6「成功牵手必须双确认」
- 注意:RelationshipService.like/block/pass 被 P0FixVerificationTest /
RelationshipBlockPriorityPhase08Test 直接测试,方法体保留
### 列表信封统一
- CursorPage 从 chat.dto 提升到 shared.dto(消除 match 模块反向依赖 chat DTO)
- /relations/fate/mutual|liked|likers 与 GET /matches 改返回 { items, nextCursor }
### 前端对齐
- matchService.js 全部路径改到 canonical;补 declineSuccess
- matchStore 补 _decorateBlockedList,修复"模板渲染 blockedAtText 但全仓从未计算"的缺陷
(与上一轮 lastMessageAtText 同源,同类字段一律在 store 装饰一次)
### 文档
- docs/api/API_INVENTORY.md 关系/缘分/匹配三节重写为 canonical 路径;
顶部加 /me/* 路径族显式警告(小程序按规范写 /me/*,后端实为 /profile/*、/photos、/relationship/*,
约 15 个端点会 404,收敛方向未裁决,防止后续照抄踩坑)
- docs/STATUS.md:Backend 回归债务由「待清(277 tests 4F/8E)」更新为「已清偿(297 tests 全绿)」
## 验证
- 新增 CanonicalRouteContractTest(3 例):用 RequestMappingHandlerMapping 断言
canonical 路径已注册、被取代的旧路径不再注册、关系状态生命周期保留 —— 路由再漂移即红
- 后端 297 tests, 0 failures, 0 errors, 3 skipped, BUILD SUCCESS
- 小程序 tsc --noEmit 通过
|
||
|
|
b46b48eb33 | chore: establish V1.1 recovery baseline |