feat(lbs): 大神真实 5km 即时匹配与接单心跳
- companions 新增 latitude/longitude/last_active_at 与 idx_comp_active;切换接单中立即刷新活跃时间 - 新增 /api/companions/heartbeat(鉴权)与 /api/companions/match(真实注册+审核通过+接单中+10分钟心跳,Haversine 5km 过滤与距离排序,不返回精确坐标) - 工作台/接单大厅/雷达前台每 60s 心跳,离开停止;首页即时匹配改为服务端 LBS 匹配并展示真实距离 - 线上实测:2010 匹配排除自己,仅命中 280m 的 8 号;匿名命中 9 号 10m、8 号 280m;北京 5km 为 0;118 套件/771 用例全绿
这个提交包含在:
@@ -249,6 +249,59 @@ export async function getCompanions(keyword) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 即时匹配在线大神:服务端按真实注册、审核通过、10 分钟心跳在线过滤;
|
||||
* 传入定位时按 5km 距离过滤并升序返回(只返回距离,不返回精确坐标)。
|
||||
*/
|
||||
export async function matchCompanions(options = {}) {
|
||||
try {
|
||||
const qs = [];
|
||||
const lat = Number(options.lat);
|
||||
const lng = Number(options.lng);
|
||||
if (Number.isFinite(lat) && Number.isFinite(lng) && !(lat === 0 && lng === 0)) {
|
||||
qs.push(`lat=${lat}`, `lng=${lng}`);
|
||||
}
|
||||
qs.push(`radius=${Math.min(50, Math.max(1, Number(options.radius) || 5))}`);
|
||||
qs.push(`limit=${Math.min(20, Math.max(1, Number(options.limit) || 10))}`);
|
||||
const res = await http.get(`/companions/match?${qs.join('&')}`);
|
||||
const list = Array.isArray(res) ? res : (res && Array.isArray(res.list) ? res.list : []);
|
||||
return list.map((c) => ({
|
||||
id: String(c.id),
|
||||
userId: Number(c.userId) || 0,
|
||||
nickname: c.nickname || '',
|
||||
avatar: c.avatar || '',
|
||||
gender: c.gender === '男' ? 'male' : (c.gender === '女' ? 'female' : ''),
|
||||
age: Number(c.age) || 0,
|
||||
game: c.track === 'OFFLINE_MEAL' ? '美食陪吃'
|
||||
: (c.track === 'OFFLINE_SING' ? '同城陪唱'
|
||||
: (c.track === 'OFFLINE_DRINK' ? '下午茶陪喝' : (c.track || ''))),
|
||||
track: c.track || '',
|
||||
rankTag: c.rankTag || '',
|
||||
price: Number(c.price) || 0,
|
||||
rating: Number(c.rating) || 0,
|
||||
orderCount: Number(c.orderCount) || 0,
|
||||
isOnline: Boolean(c.isOnline),
|
||||
distanceKm: c.distanceKm === null || c.distanceKm === undefined ? null : Number(c.distanceKm),
|
||||
distanceText: c.distanceText || '同城',
|
||||
}));
|
||||
} catch (err) {
|
||||
console.warn('[API] 即时匹配大神异常:', err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** 大神接单心跳:刷新在线状态;定位失败时仍上报活跃时间 */
|
||||
export async function sendCompanionHeartbeat({ lat, lng } = {}) {
|
||||
const payload = {};
|
||||
const nLat = Number(lat);
|
||||
const nLng = Number(lng);
|
||||
if (Number.isFinite(nLat) && Number.isFinite(nLng) && !(nLat === 0 && nLng === 0)) {
|
||||
payload.lat = nLat;
|
||||
payload.lng = nLng;
|
||||
}
|
||||
return http.post('/companions/heartbeat', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个大神详情 (真实从 MySQL 读取)
|
||||
*/
|
||||
|
||||
在新工单中引用
屏蔽一个用户