Files
danyang-match/miniapp/services/activityService.js
T
WorkBuddy 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。
2026-09-18 16:37:33 +08:00

90 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 活动服务(API_INVENTORY 行 92-96, 98)
*
* 封装:活动列表 / 详情 / 报名 / 我的活动 / 反馈 / 互选意向提交。
* 关键不变量(AGENTS.md):
* - 第 6 条:活动单向意向保密——A 选 B 但 B 没选 → B 不知道。
* 前端调用 interests 提交后,结果接口只返回"匹配成功"的双向交集,
* 不返回对方是否单独选了我。
* - 第 1 条:用户永久免费——feeType=paid 仅作为后端字段,前端展示以"公益免费"为主标识。
* 报名与互选均走幂等键防重(AGENTS 第 12 条:参数配置化)。
*/
const api = require('../utils/api');
/**
* 活动列表(API_INVENTORY 行 92)
* @param {{ status?: string, cursor?: string, pageSize?: number }} params
* @returns {Promise<{ items: Array<import('../types').Activity>, nextCursor: string|null }>}
*/
function getActivities(params = {}) {
return api.get('/activities', params);
}
/**
* 活动详情(API_INVENTORY 行 93)
* @param {string} activityId
* @returns {Promise<import('../types').Activity & { registrationStatus: import('../types').RegistrationStatus }>}
*/
function getActivityDetail(activityId) {
return api.get(`/activities/${encodeURIComponent(activityId)}`);
}
/**
* 报名活动(API_INVENTORY 行 94)
* @param {string} activityId
* @param {{ remark?: string }} [payload]
* @returns {Promise<{ registrationStatus: 'pending', registrationId: string }>}
*/
function register(activityId, payload = {}) {
const idempotencyKey = api.idempotencyKey('activity-register', activityId);
return api.post(`/activities/${encodeURIComponent(activityId)}/register`, payload, undefined, idempotencyKey);
}
/**
* 取消报名(Phase 15)
* 后端:DELETE /activities/{activityId}/register
*
* 说明:后端为 UNIQUE(activity_id, user_id) 设计,取消后原记录保留为 cancelled,
* 再次报名会**复用**该记录(不会撞唯一键),因此取消不是不可逆操作。
*/
function cancelRegistration(activityId) {
return api.delete(`/activities/${encodeURIComponent(activityId)}/register`);
}
/**
* 我的活动(API_INVENTORY 行 95)
* @param {{ status?: string, cursor?: string, pageSize?: number }} params
* @returns {Promise<{ items: Array<import('../types').Activity & { registrationStatus: import('../types').RegistrationStatus }>, nextCursor: string|null }>}
*/
function getMyActivities(params = {}) {
return api.get('/my-activities', params);
}
/**
* 提交活动反馈(API_INVENTORY 行 96)
* @param {string} activityId
* @param {{ rating: number, content: string }} payload
* @returns {Promise<{ submitted: true }>}
*/
/**
* 提交活动反馈(含表达意向 via interestTargetUserIds)
*
* 注意:此前的 `submitInterests()` → `POST /activities/{id}/interests`
* **后端不存在该端点**(Phase 16 已把表达意向合并进本流程),调用必然 404。
* 全仓确认无页面调用方,属设计残留,已删除。若需要独立的互选提交接口,
* 须先由产品定义语义并补后端端点。
*/
function submitFeedback(activityId, payload) {
const idempotencyKey = api.idempotencyKey('activity-feedback', activityId);
return api.post(`/activities/${encodeURIComponent(activityId)}/feedback`, payload, undefined, idempotencyKey);
}
module.exports = {
getActivities,
getActivityDetail,
register,
cancelRegistration,
getMyActivities,
submitFeedback,
};