文件
shanghaipeiwan/pwuniapp/common/expert-heartbeat.js
xcbyy 001f81eaca 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 用例全绿
2026-10-22 18:44:02 +08:00

62 行
1.6 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

// 大神接单心跳:工作台/接单大厅/雷达页在前台时每 60 秒上报一次
// 服务端以 last_active_at 判断是否真实在线(10 分钟无心跳自动移出匹配池)
import { getUserLocation } from './location.js';
import { sendCompanionHeartbeat, normalizeUserId } from './api.js';
let timer = null;
let inFlight = false;
let lastSentAt = 0;
let coords = null;
let coordsAt = 0;
const COORD_TTL_MS = 5 * 60 * 1000;
async function resolveCoords() {
if (coords && Date.now() - coordsAt < COORD_TTL_MS) return coords;
try {
const pos = await getUserLocation();
coords = { lat: pos.latitude, lng: pos.longitude };
coordsAt = Date.now();
return coords;
} catch (_) {
// 用户拒绝定位时仍上报活跃时间,只是没有距离信息
return coords;
}
}
export async function sendHeartbeat(force = false) {
if (inFlight) return;
if (!force && Date.now() - lastSentAt < 45 * 1000) return;
if (!normalizeUserId()) return;
inFlight = true;
try {
const pos = await resolveCoords();
await sendCompanionHeartbeat(pos || {});
lastSentAt = Date.now();
} catch (_) {
} finally {
inFlight = false;
}
}
export function startHeartbeat(intervalMs = 60 * 1000) {
if (timer) return;
void sendHeartbeat(true);
timer = setInterval(() => {
void sendHeartbeat();
}, Math.max(30 * 1000, Number(intervalMs) || 60 * 1000));
if (timer && typeof timer.unref === 'function') timer.unref();
}
export function stopHeartbeat() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
export function resetHeartbeatLocation() {
coords = null;
coordsAt = 0;
}