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。
This commit is contained in:
@@ -36,7 +36,7 @@ function getActivityDetail(activityId) {
|
||||
* @returns {Promise<{ registrationStatus: 'pending', registrationId: string }>}
|
||||
*/
|
||||
function register(activityId, payload = {}) {
|
||||
const idempotencyKey = `activity-register:${activityId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('activity-register', activityId);
|
||||
return api.post(`/activities/${encodeURIComponent(activityId)}/register`, payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -66,21 +66,17 @@ function getMyActivities(params = {}) {
|
||||
* @param {{ rating: number, content: string }} payload
|
||||
* @returns {Promise<{ submitted: true }>}
|
||||
*/
|
||||
function submitFeedback(activityId, payload) {
|
||||
const idempotencyKey = `activity-feedback:${activityId}:${Date.now()}`;
|
||||
return api.post(`/activities/${encodeURIComponent(activityId)}/feedback`, payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交活动互选意向(API_INVENTORY 行 98,AGENTS 第 6 条:单向意向保密)
|
||||
* @param {string} activityId
|
||||
* @param {{ likedUserIds: string[] }} payload
|
||||
* @returns {Promise<{ submitted: true, matchedUserIds: string[] }>}
|
||||
* matchedUserIds 仅包含双向匹配成功的人;未匹配的人不会泄露是否被对方选择。
|
||||
* 提交活动反馈(含表达意向 via interestTargetUserIds)
|
||||
*
|
||||
* 注意:此前的 `submitInterests()` → `POST /activities/{id}/interests`
|
||||
* **后端不存在该端点**(Phase 16 已把表达意向合并进本流程),调用必然 404。
|
||||
* 全仓确认无页面调用方,属设计残留,已删除。若需要独立的互选提交接口,
|
||||
* 须先由产品定义语义并补后端端点。
|
||||
*/
|
||||
function submitInterests(activityId, payload) {
|
||||
const idempotencyKey = `activity-interests:${activityId}:${Date.now()}`;
|
||||
return api.post(`/activities/${encodeURIComponent(activityId)}/interests`, payload, undefined, idempotencyKey);
|
||||
function submitFeedback(activityId, payload) {
|
||||
const idempotencyKey = api.idempotencyKey('activity-feedback', activityId);
|
||||
return api.post(`/activities/${encodeURIComponent(activityId)}/feedback`, payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -90,5 +86,4 @@ module.exports = {
|
||||
cancelRegistration,
|
||||
getMyActivities,
|
||||
submitFeedback,
|
||||
submitInterests,
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ const api = require('../utils/api');
|
||||
function submitAppeal(payload) {
|
||||
// 幂等键:申诉类型 + 关联处置 ID + 当前秒,防止用户多次点击重复提交
|
||||
const actionKey = payload.relatedActionId || 'no_action';
|
||||
const idempotencyKey = `appeal:${payload.appealType}:${actionKey}:${Math.floor(Date.now() / 1000)}`;
|
||||
const idempotencyKey = api.idempotencyKey('appeal', `${payload.appealType}:${actionKey}`);
|
||||
return api.post('/appeals', payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@ function refresh() {
|
||||
|
||||
/**
|
||||
* 登出(API_INVENTORY 行 11)
|
||||
* 幂等:Idempotency-Key = "logout:" + 当前毫秒时间,便于后端去重。
|
||||
* 幂等:键由 api 层托管,网络异常重试时复用同一把,避免后端重复记账。
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function logout() {
|
||||
return api.post('/auth/logout', {}, undefined, 'logout:' + Date.now());
|
||||
return api.post('/auth/logout', {}, undefined, api.idempotencyKey('logout'));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -26,7 +26,7 @@ const api = require('../utils/api');
|
||||
* @returns {Promise<import('../types').LikeResult>}
|
||||
*/
|
||||
function like(userId) {
|
||||
const idempotencyKey = `like:${userId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('like', userId);
|
||||
return api.post('/likes', { targetUserId: userId }, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ function unlike(userId) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function pass(userId) {
|
||||
const idempotencyKey = `pass:${userId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('pass', userId);
|
||||
return api.post('/passes', { targetUserId: userId }, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ function pass(userId) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function block(userId, payload = {}) {
|
||||
const idempotencyKey = `block:${userId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('block', userId);
|
||||
return api.post('/blocks', { targetUserId: userId, reason: payload.reason }, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ function getMatches(params = {}) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function unmatch(matchId) {
|
||||
const idempotencyKey = `unmatch:${matchId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('unmatch', matchId);
|
||||
return api.post(`/matches/${encodeURIComponent(matchId)}/unmatch`, {}, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ function unmatch(matchId) {
|
||||
* @returns {Promise<{ requestId: string, initiatedBy: string, status: 'pending' }>}
|
||||
*/
|
||||
function requestSuccess(matchId) {
|
||||
const idempotencyKey = `success-req:${matchId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('success-req', matchId);
|
||||
return api.post(`/matches/${encodeURIComponent(matchId)}/success/request`, {}, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ function requestSuccess(matchId) {
|
||||
* @returns {Promise<{ relationshipStatus: string }>}
|
||||
*/
|
||||
function confirmSuccess(matchId) {
|
||||
const idempotencyKey = `success-confirm:${matchId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('success-confirm', matchId);
|
||||
return api.post(`/matches/${encodeURIComponent(matchId)}/success/confirm`, {}, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ function confirmSuccess(matchId) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function declineSuccess(matchId) {
|
||||
const idempotencyKey = `success-decline:${matchId}:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('success-decline', matchId);
|
||||
return api.post(`/matches/${encodeURIComponent(matchId)}/success/decline`, {}, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const api = require('../utils/api');
|
||||
* @returns {Promise<{ serviceId: string, status: 'submitted' }>}
|
||||
*/
|
||||
function applyService(payload) {
|
||||
const idempotencyKey = `matchmaker-apply:${Date.now()}`;
|
||||
const idempotencyKey = api.idempotencyKey('matchmaker-apply');
|
||||
return api.post('/matchmaker/services', payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function getProfile() {
|
||||
*/
|
||||
function updateProfile(payload) {
|
||||
// PUT 默认幂等:用 userId + 字段指纹作为幂等键,避免重复提交时脏写
|
||||
const idempotencyKey = 'profile-put:' + (payload && payload.userId || 'me') + ':' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('profile-put', (payload && payload.userId) || 'me');
|
||||
return api.put('/me/profile', payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ function getPreferences() {
|
||||
* @returns {Promise<import('../types').PartnerPreference>}
|
||||
*/
|
||||
function updatePreferences(payload) {
|
||||
const idempotencyKey = 'preferences-put:' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('preferences-put');
|
||||
return api.put('/me/preferences', payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ function deletePhoto(photoId) {
|
||||
* @returns {Promise<{ avatarUrl: string }>}
|
||||
*/
|
||||
function setMainPhoto(photoId) {
|
||||
const idempotencyKey = 'photo-main:' + photoId + ':' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('photo-main', photoId);
|
||||
return api.put(`/me/photos/${photoId}/main`, {}, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ function getPrivacySettings() {
|
||||
* @returns {Promise<import('../types').PrivacySettings>}
|
||||
*/
|
||||
function updatePrivacySettings(payload) {
|
||||
const idempotencyKey = 'privacy-put:' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('privacy-put');
|
||||
return api.put('/me/privacy-settings', payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ function getReviewStatus() {
|
||||
* @returns {Promise<{ reviewStatus: 'pending', submittedAt: number }>}
|
||||
*/
|
||||
function submitReview() {
|
||||
const idempotencyKey = 'review-submit:' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('review-submit');
|
||||
return api.post('/me/profile/submit-review', {}, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ function getRelationshipStatus() {
|
||||
* @returns {Promise<{ status: 'paused', recommendationEnabled: boolean }>}
|
||||
*/
|
||||
function pauseSearching() {
|
||||
const idempotencyKey = 'rel-pause:' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('rel-pause');
|
||||
return api.patch('/me/relationship-status', { status: 'paused' }, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ function pauseSearching() {
|
||||
* @returns {Promise<{ status: 'searching', recommendationEnabled: boolean }>}
|
||||
*/
|
||||
function resumeSearching() {
|
||||
const idempotencyKey = 'rel-resume:' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('rel-resume');
|
||||
return api.patch('/me/relationship-status', { status: 'searching' }, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ function resumeSearching() {
|
||||
* @returns {Promise<{ status: 'getting_to_know', recommendationEnabled: boolean }>}
|
||||
*/
|
||||
function markGettingToKnow() {
|
||||
const idempotencyKey = 'rel-gtk:' + Date.now();
|
||||
const idempotencyKey = api.idempotencyKey('rel-gtk');
|
||||
return api.patch('/me/relationship-status', { status: 'getting_to_know' }, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const api = require('../utils/api');
|
||||
*/
|
||||
function submitReport(payload) {
|
||||
// 幂等键:目标用户 + 举报类型 + 当前秒,防止用户多次点击重复提交
|
||||
const idempotencyKey = `report:${payload.targetUserId}:${payload.reportType}:${Math.floor(Date.now() / 1000)}`;
|
||||
const idempotencyKey = api.idempotencyKey('report', `${payload.targetUserId}:${payload.reportType}`);
|
||||
return api.post('/reports', payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const api = require('../utils/api');
|
||||
function submit(payload) {
|
||||
// 幂等键:身份证号后4位 + 当前秒,避免用户多次点击
|
||||
const tail = (payload.idCardNumber || '').slice(-4).toUpperCase();
|
||||
const idempotencyKey = 'verification-submit:' + tail + ':' + Math.floor(Date.now() / 1000);
|
||||
const idempotencyKey = api.idempotencyKey('verification-submit', tail);
|
||||
return api.post('/verification/submit', payload, undefined, idempotencyKey);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user