城市提示与实时位置共享
城市贴纸推荐、一次性地点消息和实时位置共享是不同功能。城市识别只为推荐素材;实时共享会把精确坐标和时间持续写入聊天服务,必须由用户显式开始,并在停止、权限变化、断网和退出账号时分别处理本机采集与远端记录。
三条位置路径
| 路径 | 数据与用途 | 是否自动开始实时共享 |
|---|---|---|
城市提示GET /location/city |
请求来源公网地址映射到city、countryCode、source=ip;可返回null | 否 |
| 城市贴纸设备定位 | 前台取一次定位并经系统地理编码得到城市,再匹配本地目录 | 否 |
/conversations/{id}/live-locations或/groups/{id}/live-locations |
当前范围用户的经纬度、精度、开始和更新时间 | 只有POST显式开始 |
城市接口不返回街道或坐标。服务端仅在连接对端属于可信代理allowlist时接受代理覆盖的单个X-Real-IP,不读取X-Forwarded-For;非公网来源、目录缺失、校验失败或查不到城市时返回null而非伪造结果。依据:chat-service/src/city-location.mjs:99-124。
iOS城市定位期望约三公里精度、10秒取值超时,不启用后台更新;iOS选择器并行尝试设备与IP,设备结果优先,10分钟内可复用结果,手动选择会使旧自动任务失效。Android城市定位只做一次前台fix,地理编码前把经纬度量化到0.02度网格。系统地理编码仍有其系统服务的数据边界,不应把“坐标不传聊天城市接口”扩大成“整个定位过程不接触外部服务”。依据:ios-app/Sources/Features/Chat/CityStickerDeviceLocator.swift:18-108、ios-app/Sources/Features/Chat/CityStickerSelection.swift:36-113、android-app/app/src/main/java/cn/thestarline/workbench/CityStickerLocation.kt:44-90。
实时共享的数据与权限
| 字段 | 类型与约束 |
|---|---|
| scopeKind、scopeId | conversation/group与对应聊天ID;用不同路径区分 |
| userId、displayName | 由已登录会话决定;请求不能替别人上传 |
| latitude、longitude | 有限数字;纬度-90至90,经度-180至180 |
| accuracy | 有限数字,0–100000,单位米;不是“误差越大越可信” |
| startedAt、updatedAt | 服务端Unix毫秒;前者首次开始时间,后者最近成功写入时间 |
所有操作都要求Bearer与当前范围成员资格。单聊POST/PUT还要求仍是好友且任一方均未屏蔽;DELETE只停止自己的共享。群GET/POST/PUT/DELETE要求当前成员,不要求每两位群成员互为好友。服务端存储可读坐标,不是MLS密文;同范围有权成员能读取全部当前共享者。
依据:chat-service/src/server.mjs:101-109,582-610、chat-service/src/database.mjs:1037-1085。
从开始到恢复的操作次序
- 用户明确选择共享并确认,客户端请求系统位置权限;Android当前界面还要求通知权限,用于持续共享通知。
- 获得有效坐标后POST范围路径,请求只包含latitude、longitude、accuracy。成功201
{location}后才登记本机共享范围并启动持续定位。 - 后续位置更新使用PUT,同一用户、范围只更新一条记录;首次startedAt不重置,updatedAt更新为本次服务端时间。
- 页面单独GET范围位置,当前两端聊天面板约每4秒刷新;位置写入没有普通/events通知,不应等待message事件才刷新地图。
- 登录或恢复时GET
/live-locations/mine取得本人的共享范围,然后与权限和本机定位状态协调。 - 用户结束该范围时DELETE,成功200
{status:"stopped"}后从本机集合和界面移除;没有其他共享范围才停止持续采集。
POST会upsert既有自己的记录,PUT不会创建新共享:未POST就PUT返回409 NOT_SHARING。客户端不能在PUT失败时自动POST恢复,因为这可能在用户已结束共享后重新开启。依据:chat-service/src/database.mjs:1063-1085、ios-app/Sources/Features/Chat/ChatStore.swift:577-645。
合成正常请求:{"latitude":0,"longitude":0,"accuracy":25}是合法测试坐标值,不代表用户真实位置。POST后GET应出现当前用户并有两个时间字段;DELETE后GET不再出现。合成失败请求:{"latitude":91,"longitude":0,"accuracy":25}返回400 INVALID_LOCATION;不应因缺少定位结果而把用户默认为零坐标发送。
同一个账号可同时向多个聊天范围共享,同一条本机定位会依次PUT到ownLiveScopes中的各范围。停止一个聊天不会停止其他范围;只有本机剩余范围为空才释放持续监听。确认“全部结束”时应重新读取mine,而不是只看当前聊天面板为空。范围内没有自己的记录,也不表示同账号在另一个群没有共享。依据:ios-app/Sources/Features/Chat/ChatStore.swift:607-645、android-app/app/src/main/java/cn/thestarline/workbench/AppState.kt:387-449。
持续定位的双端实现
| 场景 | iOS | Android |
|---|---|---|
| 采集设置 | best accuracy、distanceFilter=10米,禁止系统自动暂停,允许后台更新 | GPS/NETWORK provider更新,最小间隔5秒、距离10米,前台定位服务 |
| 上传节流 | 至少间隔5秒,同一时刻仅一轮上传 | 单调时钟间隔至少5秒、AtomicBoolean避免并行上传 |
| 过旧fix | 接收精度非负且时间不旧于约60秒的定位 | 精度0–100000、时间不旧于60秒 |
| 范围恢复 | 按mine重建ownLiveScopes | 服务约30秒同步范围,持续通知显示状态 |
| 普通页面停止 | 先等DELETE成功,再改本机范围集合 | 同样先等DELETE成功,再停止相应本机状态 |
这些是程序配置与调度目标,不保证操作系统每5秒交付一次定位,也不代表锁屏、后台权限和省电模式已完成设备验收。iOS仅在至少一个共享范围存在时持续更新;Android服务返回START_NOT_STICKY,不保证被系统终止后自动复活。依据:ios-app/Sources/Features/Chat/ChatLiveLocation.swift:41-107、android-app/app/src/main/java/cn/thestarline/workbench/LiveLocationService.kt:54-171。
时间、可信程度与页面一致性
updatedAt由服务器在成功接收POST/PUT时生成,协议没有单独的定位fix时间字段。因此“刚刚更新”仅表示刚收到坐标,不能单独证明坐标刚由卫星测得、用户仍在原地或后台采集持续正常。当前官方客户端会过滤部分过旧定位,但服务端并不验证设备传感器来源。展示accuracy和最后更新时间比只显示一个地图标记更有依据。
Android刷新当前范围时会保存请求前的scope与session,响应返回后再次核对,避免原聊天的结果覆盖用户新打开的聊天。iOS当前refreshLiveLocations方法没有同等响应后scope/token核对,需要把换账号、切换聊天时旧请求晚到加入验收,不能直接承诺两端异步隔离行为完全一致。依据:android-app/app/src/main/java/cn/thestarline/workbench/AppState.kt:374-384、ios-app/Sources/Features/Chat/ChatStore.swift:577-586。
停止失败、权限关闭与数据保留
停止本机传感器不会自动删除服务器最后坐标,服务器删除成功也不等于系统定位监听必定已释放。两端普通页面的结束动作若DELETE失败,会保留共享状态并展示或传播错误,不应显示“已经结束”。Android持续通知的“结束全部”是另一个分支:先停止本机采集,随后DELETE所有范围,网络失败约每10秒重试,通知显示正在重试。
系统权限关闭后,本机停止或无法继续采集,但远端记录不会因此自动消失;Android明确提示打开App结束原有共享。网络暂时不可用时保留最后成功位置和updatedAt,地图应标明最后更新时间,而不是宣称仍在实时移动。依据:android-app/app/src/main/java/cn/thestarline/workbench/LiveLocationService.kt:54-65,91-130、ios-app/Sources/Features/Chat/ChatStore.swift:607-645。
当前live_locations没有expiresAt或自动过期清理字段,也不保存一张连续轨迹历史表:同一范围用户更新覆盖该行的当前坐标。DELETE停止会删除该行;屏蔽或删除好友清理对应单聊双方的位置,退出/移除群成员清理该成员的位置,解散群清理该范围全部位置。备份、日志和系统缓存的生命周期不等同于该表行删除,不能据此承诺所有副本立即销毁。依据:chat-service/src/database.mjs:693-725,928-934,990-1033,1063-1085。
退出账号也需区分:Android先停止本机,再尽力清理服务器共享;iOS本机退出先停止manager,但其signOut路径没有同等逐范围DELETE共享流程。聊天session到期不会自动清掉live_locations,仍有权的其他成员可能读到旧位置,直至显式停止或关系清理。此为源码边界,未据此执行真实定位请求。详见客户端生命周期、限制与错误与安全和隐私。
源码核对范围:2026-10-23 的当前实现。本轮未执行生产业务请求或原生设备验收。