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 用例全绿
这个提交包含在:
xcbyy
2026-10-22 18:44:02 +08:00
父节点 b573c1ed05
当前提交 001f81eaca
共有 11 个文件被更改,包括 425 次插入 和 19 次删除

查看文件

@@ -349,14 +349,14 @@ describe('ClientFeatures (TDD 测试套件: 用户端功能闭环)', () => {
const content = fs.readFileSync(indexVuePath, 'utf-8');
const apiContent = fs.readFileSync(path.join(rootDir, 'pwuniapp', 'common', 'api.js'), 'utf-8');
expect(content).toContain('await getCompanions()');
expect(content).toContain('Number(e.userId) > 0');
expect(content).toContain('Number(e.userId) !== uid');
expect(content).toContain('matchCompanions({ ...(pos || {}), radius: 5, limit: 20 })');
expect(content).toContain('lastMatchedId');
expect(content).toContain('matchedMeta');
expect(content).toContain('换一位');
expect(content).toContain('在线大神即时匹配');
expect(content).not.toContain('首位接单大神命中');
expect(apiContent).toContain('userId: Number(c.userId) || 0');
expect(apiContent).toContain('export async function matchCompanions');
});
});