// 位置追踪服务 - 处理用户签到后的位置追踪和状态管理 import { LocationData } from '../types'; import apiService from './apiService'; import userService from './userService'; import mapService from './mapService'; /** * 在线用户信息接口 */ export interface OnlineUserInfo { userId: number; name: string; avatarUrl: string; role: string; // 用户角色:'admin' | 'delivery_person' lastLocation: LocationData; lastUpdateTime: number; status: 'online' | 'offline' | 'timeout'; } /** * 位置追踪服务类 * 提供用户签到后的位置追踪、状态管理和位置分发功能 */ class LocationTrackingService { // 在线用户列表(仅通过服务器WebSocket推送更新) private onlineUsers: Map; // 位置更新定时器(仅用于向服务器发送位置信息) private locationUpdateTimer: number | null; // 用户状态变化回调函数(单一方法赋值) private userStatusCallback: ((userId: number, status: 'online' | 'offline' | 'timeout') => void) | null; /** * 位置更新回调函数类型 */ private locationUpdateCallback: ((onlineUsers: OnlineUserInfo[]) => void) | null; // 当前用户是否已签到 private isSignedIn: boolean; // 取消订阅回调函数 private unsubscribeCallback: (() => void) | null = null; /** * 构造函数 */ constructor() { this.onlineUsers = new Map(); this.locationUpdateTimer = null; this.userStatusCallback = null; this.locationUpdateCallback = null; this.isSignedIn = false; this.unsubscribeCallback = null; } /** * 启动位置追踪服务(WebSocket连接) */ public async startTracking(): Promise { const userInfo = userService.getGlobalUserInfo(); if (!userInfo || !userInfo.id) { throw new Error('用户未登录,无法启动位置追踪'); } this.isSignedIn = true; // 设置实时位置订阅(初始化WebSocket连接) await this.setupRealTimeSubscription(); // 启动位置更新定时器(每30秒向服务器发送位置信息) this.startLocationUpdateTimer(); console.log('位置追踪服务已启动'); } /** * 初始化位置追踪服务 */ public async initialize(): Promise { } /** * 停止位置追踪服务(WebSocket断开时调用) */ public async stopTracking(): Promise { const userInfo = userService.getGlobalUserInfo(); if (!userInfo || !userInfo.id) { return; } this.isSignedIn = false; // 停止位置更新定时器 this.stopLocationUpdateTimer(); // 关闭WebSocket连接 try { await this.teardownRealTimeSubscription(); console.log('调试信息 - WebSocket连接已关闭'); } catch (error) { console.warn('调试信息 - 关闭WebSocket连接失败:', error); } // 清空在线用户列表(WebSocket断开时清空所有数据) this.onlineUsers.clear(); // 通知所有位置更新回调,清空地图标记点 this.notifyLocationUpdateCallbacks(); console.log('位置追踪服务已停止,在线用户列表已清空'); } /** * 向服务器发送位置更新(仅发送,不更新本地缓存) */ public async updateUserLocation(location: { longitude: number; latitude: number }): Promise { const userInfo = userService.getGlobalUserInfo(); if (!userInfo || !userInfo.id || !this.isSignedIn) { console.warn('[位置追踪服务] 用户未登录或未签到,无法更新位置'); return; } console.log('[位置追踪服务] 向服务器发送位置信息:'); console.log(`- 用户ID: ${userInfo.id}`); console.log(`- 经度: ${location.longitude}`); console.log(`- 纬度: ${location.latitude}`); // 通过WebSocket发送位置更新给服务器 await apiService.sendLocationUpdate(userInfo.id, location.latitude, location.longitude); console.log('[位置追踪服务] 位置信息已发送到服务器'); } /** * 获取所有在线用户信息 */ public getOnlineUsers(): OnlineUserInfo[] { return Array.from(this.onlineUsers.values()).filter(user => user.status === 'online' ); } /** * 订阅用户状态变化(单一方法赋值) * @param callback 用户状态变化回调函数 * @returns 取消订阅函数 */ public subscribeToUserStatusChanges(callback: (userId: number, status: 'online' | 'offline' | 'timeout') => void): () => void { console.log('📝 [LocationTrackingService] 注册用户状态变化回调函数'); // 直接赋值,覆盖之前的回调 this.userStatusCallback = callback; console.log('✅ [LocationTrackingService] 用户状态变化回调注册完成'); // 返回取消订阅函数 return () => { if (this.userStatusCallback === callback) { this.userStatusCallback = null; console.log('✅ [LocationTrackingService] 用户状态变化回调已取消'); } }; } /** * 检查用户是否在线 */ public isUserOnline(userId: number): boolean { const userInfo = this.onlineUsers.get(userId); return userInfo ? userInfo.status === 'online' : false; } // ===== 私有方法 ===== /** * 启动位置更新定时器 */ private startLocationUpdateTimer(): void { this.stopLocationUpdateTimer(); this.locationUpdateTimer = setInterval(async () => { if (this.isSignedIn) { try { const location = await this.getCurrentLocation(); await this.updateUserLocation(location); } catch (error) { console.error('自动位置更新失败:', error); } } }, 30000); // 每30秒更新一次 } /** * 获取当前位置 */ public async getCurrentLocation(): Promise { const userInfo = userService.getGlobalUserInfo(); if (!userInfo || !userInfo.id) { throw new Error('用户未登录'); } try { // 使用mapService获取真实位置,保持与项目中其他地方的一致性 const location = await mapService.getLocation(); console.log('[位置追踪服务] 获取真实位置成功:', location); return { userId: userInfo.id, longitude: location.longitude, latitude: location.latitude, timestamp: Date.now() }; } catch (error) { console.error('[位置追踪服务] 获取真实位置失败,使用默认位置:', error); // 定位失败时返回默认位置作为降级方案 return { userId: userInfo.id, longitude: 102.833722, latitude: 24.880095, timestamp: Date.now() }; } } /** * 设置实时位置订阅 */ private async setupRealTimeSubscription(): Promise { // 获取用户信息 const userInfo = userService.getGlobalUserInfo(); if (!userInfo || !userInfo.id) { console.warn('用户未登录,无法设置实时位置订阅'); return; } // 缓存用户信息(已移除) // 真实环境:初始化WebSocket连接(服务器会自动处理订阅逻辑) try { // 初始化WebSocket连接(如果尚未连接) await apiService.initLocationWebSocket(); // 等待WebSocket连接建立(最多等待5秒) await new Promise((resolve, reject) => { let attempts = 0; const maxAttempts = 50; // 5秒超时 const checkConnection = () => { if (apiService.isWebSocketConnected()) { resolve(); } else if (attempts < maxAttempts) { attempts++; setTimeout(checkConnection, 100); } else { reject(new Error('WebSocket连接建立超时')); } }; checkConnection(); }); // 服务器会自动处理订阅逻辑,无需发送订阅消息 console.log('WebSocket连接已建立,服务器将自动处理位置订阅逻辑'); } catch (error) { console.error('设置实时位置订阅失败:', error); } } /** * 关闭实时位置订阅 */ private teardownRealTimeSubscription(): void { // 真实环境:关闭WebSocket连接(服务器会自动处理取消订阅逻辑) try { // 服务器会自动处理取消订阅逻辑,无需发送取消订阅消息 console.log('关闭WebSocket连接,服务器将自动处理取消订阅逻辑'); // 移除位置更新回调 if (this.unsubscribeCallback) { this.unsubscribeCallback(); this.unsubscribeCallback = null; } // 关闭WebSocket连接 apiService.closeLocationWebSocket(); } catch (error) { console.error('关闭实时位置订阅失败:', error); } } /** * 停止位置更新定时器 */ private stopLocationUpdateTimer(): void { if (this.locationUpdateTimer) { clearInterval(this.locationUpdateTimer); this.locationUpdateTimer = null; } } /** * 通知位置更新回调 */ private notifyLocationUpdateCallbacks(): void { const onlineUsers = this.getOnlineUsers(); console.log('🔔 [LocationTrackingService] 开始通知位置更新回调'); console.log('👥 [LocationTrackingService] 在线用户数量:', onlineUsers.length); // 触发位置更新回调 if (this.locationUpdateCallback) { try { console.log('🔄 [LocationTrackingService] 执行位置更新回调'); this.locationUpdateCallback(onlineUsers); console.log('✅ [LocationTrackingService] 位置更新回调执行成功'); } catch (error) { console.error('❌ [LocationTrackingService] 位置更新回调执行失败:', error); } } else { console.log('⚠️ [LocationTrackingService] 没有注册位置更新回调'); } console.log('🏁 [LocationTrackingService] 位置更新回调通知完成'); } /** * 手动触发位置更新(公共方法,可从外部调用) * 用于手动刷新地图上的用户标记 */ public triggerLocationUpdate(): void { console.log('🔔 [LocationTrackingService] 手动触发位置更新'); this.notifyLocationUpdateCallbacks(); } /** * 订阅位置更新(用于locationModule注册回调) * @param callback 位置更新回调函数 * @returns 取消订阅函数 */ public subscribeToLocationUpdates(callback: (onlineUsers: OnlineUserInfo[]) => void): () => void { console.log('📝 [LocationTrackingService] 注册位置更新回调函数'); // 直接赋值,覆盖之前的回调 this.locationUpdateCallback = callback; console.log('✅ [LocationTrackingService] 位置更新回调注册完成'); // 返回取消订阅函数 return () => { if (this.locationUpdateCallback === callback) { this.locationUpdateCallback = null; console.log('✅ [LocationTrackingService] 位置更新回调已取消'); } }; } /** * 通知用户状态变化回调 */ private notifyUserStatusChange(userId: number, status: 'online' | 'offline' | 'timeout'): void { console.log(`🔔 [LocationTrackingService] 开始通知用户状态变化回调: 用户 ${userId} 状态变为 ${status}`); if (this.userStatusCallback) { try { console.log('🔄 [LocationTrackingService] 执行用户状态变化回调'); this.userStatusCallback(userId, status); console.log('✅ [LocationTrackingService] 用户状态变化回调执行成功'); } catch (error) { console.error('❌ [LocationTrackingService] 用户状态变化回调执行失败:', error); } } else { console.log('⚠️ [LocationTrackingService] 没有注册用户状态变化回调'); } console.log('🏁 [LocationTrackingService] 用户状态变化回调通知完成'); } /** * 处理WebSocket消息 * @param message 服务器下发的消息 */ public handleWebSocketMessage(message: any){ console.log(`📡 收到WebSocket消息类型: ${message.type}`, message); switch (message.type) { case 'onlineUserList': // 处理在线用户列表消息(服务器发送当前在线用户列表) console.log('👥 处理在线用户列表,用户数量:', message.users ? message.users.length : 0); if (message.users && Array.isArray(message.users)) { this.triggerOnlineUserListCallbacks({ type: 'onlineUserList', users: message.users.map((user: any) => ({ userId: user.userId, name: user.name, role: user.role, userStatus: user.userStatus, lastUpdateTime: user.lastUpdateTime, latitude: user.locationData?.latitude || user.latitude, longitude: user.locationData?.longitude || user.longitude, timestamp: user.locationData?.timestamp || user.timestamp })) }); }else { console.warn('❌ onlineUserList消息格式错误,users字段不存在或不是数组'); } break; case 'userLocationList': // 处理用户位置列表消息(服务器每30秒发送所有在线用户的位置列表) console.log('👥 处理用户位置列表,用户数量:', message.users ? message.users.length : 0); // 确保用户列表存在且是数组 if (message.users && Array.isArray(message.users)) { // 转换用户数据格式,确保与位置追踪服务兼容 const formattedUsers = message.users.map((user: any) => { // 支持多种数据格式:locationData字段或直接字段 const locationData = user.locationData || user; return { userId: user.userId || locationData.userId, name: user.name || user.userName || `用户${user.userId || locationData.userId}`, role: user.role || 'employee', userStatus: user.userStatus !== false, // 转换为布尔值 lastUpdateTime: user.lastUpdateTime || locationData.timestamp || Date.now(), latitude: locationData.latitude, longitude: locationData.longitude, timestamp: locationData.timestamp || user.timestamp || Date.now() }; }); console.log('📊 转换后的用户位置数据:', formattedUsers); this.triggerOnlineUserListCallbacks({ type: 'userLocationList', users: formattedUsers }); } else { console.warn('❌ userLocationList消息格式错误,users字段不存在或不是数组'); } break; default: console.warn('收到未知类型的WebSocket消息:', message); break; } } /** * 触发在线用户列表回调函数 * @param userList 在线用户列表数据 */ private triggerOnlineUserListCallbacks(userList: any): void { console.log('🔔 [apiService] 开始触发在线用户列表回调,消息类型:', userList.type); console.log('📊 [apiService] 回调数据内容:', JSON.stringify(userList, null, 2)); try { console.log('🔄 [apiService] 执行在线用户列表回调函数'); // 传递正确的数据格式:只传递users数组,而不是整个消息对象 this.handleOnlineUserList(userList.users); console.log('✅ [apiService] 在线用户列表回调函数执行成功'); } catch (error) { console.error('❌ [apiService] 在线用户列表回调函数执行失败:', error); } console.log('🏁 [apiService] 在线用户列表回调触发完成'); } /** * 处理在线用户列表 * @param userList 服务器下发的在线用户列表 */ private handleOnlineUserList(userList: any[]): void { try { // 验证数据格式 if (!Array.isArray(userList)) { console.warn('[LocationTrackingService] 无效的在线用户列表数据格式'); return; } console.log(`[LocationTrackingService] 开始处理在线用户列表,用户数量: ${userList.length}`); // 获取当前本地用户列表的userId集合 const currentUserIds = new Set(this.onlineUsers.keys()); // 处理服务器下发的用户列表 const newUserIds = new Set(); userList.forEach(user => { if (user && user.userId && user.latitude !== undefined && user.longitude !== undefined) { newUserIds.add(user.userId); // 提取位置数据(支持多种数据格式) const latitude = user.latitude; const longitude = user.longitude; const timestamp = user.timestamp || user.lastUpdateTime || Date.now(); // 更新或添加用户信息 this.onlineUsers.set(user.userId, { userId: user.userId, name: user.name || user.userName || `用户${user.userId}`, avatarUrl: '/images/user-avatar.png', role: user.role || 'employee', lastLocation: { userId: user.userId, longitude: longitude, latitude: latitude, timestamp: timestamp }, lastUpdateTime: timestamp, status: user.userStatus === false ? 'offline' : 'online' }); console.log(`[LocationTrackingService] 更新用户 ${user.userId} 位置: (${latitude}, ${longitude})`); // 如果用户是新上线的,触发用户状态回调 if (!currentUserIds.has(user.userId)) { this.notifyUserStatusChange(user.userId, 'online'); console.log(`[LocationTrackingService] 新用户上线: ${user.userId}`); } } else { console.warn(`[LocationTrackingService] 跳过无效用户数据:`, user); } }); // 删除本地列表中不在服务器列表中的用户 let removedCount = 0; currentUserIds.forEach(userId => { if (!newUserIds.has(userId)) { // 删除离线用户 this.onlineUsers.delete(userId); removedCount++; // 触发用户状态回调(离线) this.notifyUserStatusChange(userId, 'offline'); console.log(`[LocationTrackingService] 用户离线: ${userId}`); } }); console.log(`[LocationTrackingService] 在线用户列表更新完成: 新增 ${newUserIds.size - (currentUserIds.size - removedCount)} 个用户,删除 ${removedCount} 个用户`); // 通知位置模块更新地图标记点(无论是否有变化都通知,确保地图标记点同步) this.notifyLocationUpdateCallbacks(); console.log('[LocationTrackingService] 已通知位置模块更新地图标记点'); } catch (error) { console.error('[LocationTrackingService] 处理在线用户列表失败:', error); } } } /** * 位置追踪服务单例实例 * 导出供应用程序全局使用 */ export default new LocationTrackingService();