Files
danyang-match/miniapp/services/matchService.js
T

205 lines
7.0 KiB
JavaScript
Raw Normal View History

2026-09-09 22:05:00 +08:00
/**
* 关系与匹配服务
*
* 封装:Like/Pass/Block/取消/解除 + 缘分三榜 + 黑名单 + Unmatch + 成功牵手双确认。
*
* 路径基线:2026-09-14 的 match 模块(LikeController/PassController/BlockController/MatchController)
* 为 canonical 路径 —— /likes、/passes、/blocks、/matches;缘分三榜保留 /relations/fate/*。
* 旧的 /relations/{id}/like|pass|block、/relations/mutual|liked|likers、/me/blacklist 已废止。
2026-09-09 22:05:00 +08:00
*
* 关键不变量(AGENTS.md):
* - 第 4 条:Block 即时禁止互动——前端调用 block 后必须立即更新本地会话与列表。
* - 第 5 条:红娘不可替用户 Like——本层仅供当前用户调用,不接受 matchmakerId 参数。
* - 第 6 条:成功牵手必须双确认——success/request 与 success/confirm 分两次调用。
* 单向意向不泄露:A Like B 但未 Mutual,本层不提供"谁喜欢我"的可识别列表,
* 仅返回计数 / 是否被 liked 的布尔,详情走 /relations/fate/likers(仅返回脱敏字段)。
2026-09-09 22:05:00 +08:00
*/
const api = require('../utils/api');
/**
* 表达喜欢
* POST /likes { targetUserId }
*
* 响应对齐 types.LikeResult:{ relationStatus, mutualMatch, matchedConversationId }。
* mutualMatch=true 且 matchedConversationId 非空时,调用方可直接跳转聊天页。
2026-09-09 22:05:00 +08:00
* @param {string} userId
* @returns {Promise<import('../types').LikeResult>}
*/
function like(userId) {
const idempotencyKey = api.idempotencyKey('like', userId);
return api.post('/likes', { targetUserId: userId }, undefined, idempotencyKey);
2026-09-09 22:05:00 +08:00
}
/**
* 取消喜欢
* DELETE /likes/{userId}
*
* 注意:Mutual 后不可直接取消(后端返回状态冲突,提示先解除匹配),前端不应阻断调用。
2026-09-09 22:05:00 +08:00
* @param {string} userId
* @returns {Promise<void>}
2026-09-09 22:05:00 +08:00
*/
function unlike(userId) {
return api.delete(`/likes/${encodeURIComponent(userId)}`, {}, undefined, `unlike:${userId}:${Date.now()}`);
2026-09-09 22:05:00 +08:00
}
/**
* 表示不合适
* POST /passes { targetUserId }
*
2026-09-09 22:05:00 +08:00
* Pass 是单向意向,不会通知对方(AGENTS 第 6 条)。
* @param {string} userId
* @returns {Promise<void>}
2026-09-09 22:05:00 +08:00
*/
function pass(userId) {
const idempotencyKey = api.idempotencyKey('pass', userId);
return api.post('/passes', { targetUserId: userId }, undefined, idempotencyKey);
2026-09-09 22:05:00 +08:00
}
/**
* 拉黑用户(AGENTS 第 4 条:Block 优先级最高)
* POST /blocks { targetUserId, reason? }
*
2026-09-09 22:05:00 +08:00
* 前端立即停止与该用户的所有互动,从推荐/搜索/会话列表移除。
* @param {string} userId
* @param {{ reason?: string }} [payload]
* @returns {Promise<void>}
2026-09-09 22:05:00 +08:00
*/
function block(userId, payload = {}) {
const idempotencyKey = api.idempotencyKey('block', userId);
return api.post('/blocks', { targetUserId: userId, reason: payload.reason }, undefined, idempotencyKey);
2026-09-09 22:05:00 +08:00
}
/**
* 解除拉黑
* DELETE /blocks/{userId}
2026-09-09 22:05:00 +08:00
* @param {string} userId
* @returns {Promise<void>}
2026-09-09 22:05:00 +08:00
*/
function unblock(userId) {
return api.delete(`/blocks/${encodeURIComponent(userId)}`, {}, undefined, `unblock:${userId}:${Date.now()}`);
2026-09-09 22:05:00 +08:00
}
/**
* 获取互相喜欢列表(Mutual Matches)
* GET /relations/fate/mutual
2026-09-09 22:05:00 +08:00
* @param {{ cursor?: string, pageSize?: number }} params
* @returns {Promise<{ items: Array<import('../types').User>, nextCursor: string|null }>}
2026-09-09 22:05:00 +08:00
*/
function getMutualList(params = {}) {
return api.get('/relations/fate/mutual', params);
2026-09-09 22:05:00 +08:00
}
/**
* 获取我喜欢的人列表
* GET /relations/fate/liked
2026-09-09 22:05:00 +08:00
* @param {{ cursor?: string, pageSize?: number }} params
* @returns {Promise<{ items: Array<import('../types').User & { relationTime?: number }>, nextCursor: string|null }>}
2026-09-09 22:05:00 +08:00
*/
function getLikedByMe(params = {}) {
return api.get('/relations/fate/liked', params);
2026-09-09 22:05:00 +08:00
}
/**
* 获取喜欢我的人列表
* GET /relations/fate/likers
*
2026-09-09 22:05:00 +08:00
* 仅返回脱敏字段(昵称/年龄/地区/是否已 Mutual),不泄露对方明示身份。
* @param {{ cursor?: string, pageSize?: number }} params
* @returns {Promise<{ items: Array<import('../types').FateItem>, nextCursor: string|null }>}
2026-09-09 22:05:00 +08:00
*/
function getLikers(params = {}) {
return api.get('/relations/fate/likers', params);
2026-09-09 22:05:00 +08:00
}
/**
* 获取黑名单列表(黑名单管理页)
* GET /blocks
*
* 拉黑/解除拉黑走 blocks 的 POST/DELETE,本接口仅查询。
2026-09-09 22:05:00 +08:00
* 返回字段已脱敏(不返回对方手机号/身份证等敏感信息)。
* @param {{ cursor?: string, pageSize?: number }} params
* @returns {Promise<{ items: Array<{ userId: string, userName: string, avatarUrl: string|null, blockedAt: number|null, reason: string|null }>, nextCursor: string|null }>}
2026-09-09 22:05:00 +08:00
*/
function getBlockedList(params = {}) {
return api.get('/blocks', params);
2026-09-09 22:05:00 +08:00
}
/**
* 获取我的匹配列表
* GET /matches
*
* 与缘分 mutual 区别:matches 含 Match 状态、episode、服务跟进字段。
2026-09-09 22:05:00 +08:00
* @param {{ cursor?: string, pageSize?: number }} params
* @returns {Promise<{ items: Array<{ id: string, source: string, sourceId: string|null, status: string, peerUserId: string, matchedAt: number|null, lastActivityAt: number|null, episode: number }>, nextCursor: string|null }>}
2026-09-09 22:05:00 +08:00
*/
function getMatches(params = {}) {
return api.get('/matches', params);
}
/**
* 解除匹配
* POST /matches/{matchId}/unmatch
*
* 解除后双方会话立即停用,影响范围仅次于 Block。
2026-09-09 22:05:00 +08:00
* @param {string} matchId
* @returns {Promise<void>}
2026-09-09 22:05:00 +08:00
*/
function unmatch(matchId) {
const idempotencyKey = api.idempotencyKey('unmatch', matchId);
2026-09-09 22:05:00 +08:00
return api.post(`/matches/${encodeURIComponent(matchId)}/unmatch`, {}, undefined, idempotencyKey);
}
/**
* 发起成功牵手请求(AGENTS 第 6 条:成功牵手必须双确认)
* POST /matches/{matchId}/success/request
*
2026-09-09 22:05:00 +08:00
* 仅发起方调用一次,需对方在有效期内 confirm。
* @param {string} matchId
* @returns {Promise<{ requestId: string, initiatedBy: string, status: 'pending' }>}
2026-09-09 22:05:00 +08:00
*/
function requestSuccess(matchId) {
const idempotencyKey = api.idempotencyKey('success-req', matchId);
2026-09-09 22:05:00 +08:00
return api.post(`/matches/${encodeURIComponent(matchId)}/success/request`, {}, undefined, idempotencyKey);
}
/**
* 确认成功牵手
* POST /matches/{matchId}/success/confirm
*
* 仅被请求方调用,双方都确认后关系置为 matched_successfully。
2026-09-09 22:05:00 +08:00
* @param {string} matchId
* @returns {Promise<{ relationshipStatus: string }>}
2026-09-09 22:05:00 +08:00
*/
function confirmSuccess(matchId) {
const idempotencyKey = api.idempotencyKey('success-confirm', matchId);
2026-09-09 22:05:00 +08:00
return api.post(`/matches/${encodeURIComponent(matchId)}/success/confirm`, {}, undefined, idempotencyKey);
}
/**
* 拒绝成功牵手
* POST /matches/{matchId}/success/decline
* @param {string} matchId
* @returns {Promise<void>}
*/
function declineSuccess(matchId) {
const idempotencyKey = api.idempotencyKey('success-decline', matchId);
return api.post(`/matches/${encodeURIComponent(matchId)}/success/decline`, {}, undefined, idempotencyKey);
}
2026-09-09 22:05:00 +08:00
module.exports = {
like,
unlike,
pass,
block,
unblock,
getMutualList,
getLikedByMe,
getLikers,
getBlockedList,
getMatches,
unmatch,
requestSuccess,
confirmSuccess,
declineSuccess,
2026-09-09 22:05:00 +08:00
};