Files
WXProgram/miniprogram/services/locationTrackingService.ts
2025-10-19 13:40:20 +08:00

623 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 位置追踪服务 - 处理用户签到后的位置追踪和状态管理
import { UserInfo, 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 {
// 在线用户列表
private onlineUsers: Map<number, OnlineUserInfo>;
// 位置更新定时器
private locationUpdateTimer: number | null;
// 状态检查定时器
private statusCheckTimer: number | null;
// 位置更新回调集合
private locationUpdateCallbacks: Set<(locations: OnlineUserInfo[]) => void>;
// 用户状态变化回调集合
private userStatusCallbacks: Set<(userId: number, status: 'online' | 'offline' | 'timeout') => void>;
// 当前用户是否已签到
private isSignedIn: boolean;
// 用户信息缓存
private userInfo: UserInfo | null;
// 取消订阅回调函数
private unsubscribeCallback: (() => void) | null;
/**
* 构造函数
*/
constructor() {
this.onlineUsers = new Map();
this.locationUpdateTimer = null;
this.statusCheckTimer = null;
this.locationUpdateCallbacks = new Set();
this.userStatusCallbacks = new Set();
this.isSignedIn = false;
this.userInfo = null;
this.unsubscribeCallback = null;
}
/**
* 用户签到后启动位置追踪
*/
public async startTrackingAfterSignIn(): Promise<void> {
const userInfo = userService.getGlobalUserInfo();
if (!userInfo || !userInfo.id) {
throw new Error('用户未登录,无法启动位置追踪');
}
// 先获取当前位置数据
let initialLocation: { latitude: number; longitude: number; timestamp: number } ;
const location = await this.getCurrentLocation();
initialLocation = {
latitude: location.latitude,
longitude: location.longitude,
timestamp: Math.floor(Date.now() / 1000)
}
console.log('获取到初始位置数据:', initialLocation);
// 调用签到接口,传递位置数据
try {
await userService.signIn(initialLocation);
console.log('签到成功,位置数据已发送到服务器');
} catch (error) {
console.error('签到失败:', error);
throw error;
}
this.isSignedIn = true;
// 将当前用户添加到在线列表
await this.addUserToOnlineList(userInfo);
// 设置实时位置订阅初始化WebSocket连接
await this.setupRealTimeSubscription();
// WebSocket连接成功后立即发送第一次位置信息
try {
const location = await this.getCurrentLocation();
await this.updateUserLocation(location);
console.log('WebSocket连接成功后立即发送第一次位置信息');
} catch (error) {
console.error('立即发送位置信息失败:', error);
}
// 启动位置更新定时器每30秒更新一次
this.startLocationUpdateTimer();
console.log('位置追踪服务已启动');
}
/**
* 初始化位置追踪服务
*/
public async initialize(): Promise<void> {
try {
// 注册在线用户列表回调
apiService.onOnlineUserList((userList) => {
this.handleOnlineUserList(userList);
});
console.log('[LocationTrackingService] 位置追踪服务初始化完成');
} catch (error) {
console.error('[LocationTrackingService] 初始化失败:', error);
throw error;
}
}
/**
* 用户签退后停止位置追踪
*/
public async stopTracking(): Promise<void> {
const userInfo = userService.getGlobalUserInfo();
if (!userInfo || !userInfo.id) {
return;
}
this.isSignedIn = false;
// 签退消息已通过REST API处理服务器会自动处理取消订阅逻辑
// 从在线列表中移除用户
this.removeUserFromOnlineList(userInfo.id);
// 停止定时器
this.stopTimers();
// 关闭实时位置订阅(服务器会自动处理取消订阅)
this.teardownRealTimeSubscription();
console.log('位置追踪服务已停止');
}
/**
* 手动更新用户位置
*/
public async updateUserLocation(location: { longitude: number; latitude: number }): Promise<void> {
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}`);
const timestamp = Math.floor(Date.now() / 1000);
console.log(`- 时间戳: ${timestamp}`);
const locationData: LocationData = {
userId: userInfo.id,
longitude: location.longitude,
latitude: location.latitude,
timestamp: timestamp
};
// 真实模式通过WebSocket发送位置更新
console.log('[位置追踪服务] 真实模式通过WebSocket发送位置更新');
await apiService.sendLocationUpdate(userInfo.id, location.latitude, location.longitude);
// 同时更新本地缓存
this.updateLocalUserLocation(userInfo.id, locationData);
console.log('[位置追踪服务] 位置更新完成');
}
/**
* 获取所有在线用户信息
*/
public getOnlineUsers(): OnlineUserInfo[] {
return Array.from(this.onlineUsers.values()).filter(user =>
user.status === 'online'
);
}
/**
* 订阅位置更新
*/
public subscribeToLocationUpdates(callback: (locations: OnlineUserInfo[]) => void): void {
console.log('📝 [LocationTrackingService] 注册位置更新回调函数');
console.log('📍 [LocationTrackingService] 当前已注册回调数量:', this.locationUpdateCallbacks.size);
this.locationUpdateCallbacks.add(callback);
console.log('✅ [LocationTrackingService] 位置更新回调注册完成,当前回调数量:', this.locationUpdateCallbacks.size);
}
/**
* 取消订阅位置更新
*/
public unsubscribeFromLocationUpdates(callback: (locations: OnlineUserInfo[]) => void): void {
this.locationUpdateCallbacks.delete(callback);
}
/**
* 订阅用户状态变化
*/
public subscribeToUserStatusChanges(callback: (userId: number, status: 'online' | 'offline' | 'timeout') => void): void {
this.userStatusCallbacks.add(callback);
}
/**
* 取消订阅用户状态变化
*/
public unsubscribeFromUserStatusChanges(callback: (userId: number, status: 'online' | 'offline' | 'timeout') => void): void {
this.userStatusCallbacks.delete(callback);
}
/**
* 检查用户是否在线
*/
public isUserOnline(userId: number): boolean {
const userInfo = this.onlineUsers.get(userId);
return userInfo ? userInfo.status === 'online' : false;
}
// ===== 私有方法 =====
/**
* 添加用户到在线列表
*/
private async addUserToOnlineList(userInfo: UserInfo): Promise<void> {
const currentLocation = await this.getCurrentLocation();
const onlineUser: OnlineUserInfo = {
userId: userInfo.id,
name: userInfo.name || '未知用户',
avatarUrl: '/images/user-avatar.png',
role: userInfo.role || 'delivery_person', // 用户角色
lastLocation: currentLocation,
lastUpdateTime: Math.floor(Date.now() / 1000),
status: 'online'
};
this.onlineUsers.set(userInfo.id, onlineUser);
console.log(`用户 ${userInfo.id} 已添加到在线列表`);
}
/**
* 从在线列表中移除用户
*/
private removeUserFromOnlineList(userId: number): void {
if (this.onlineUsers.has(userId)) {
this.onlineUsers.delete(userId);
// 通知状态变化回调
this.notifyUserStatusChange(userId, 'offline');
console.log(`用户 ${userId} 已从在线列表移除`);
}
}
/**
* 更新本地用户位置
*/
private updateLocalUserLocation(userId: number, location: LocationData): void {
const userInfo = this.onlineUsers.get(userId);
if (userInfo) {
userInfo.lastLocation = location;
userInfo.lastUpdateTime = Date.now();
userInfo.status = 'online';
this.onlineUsers.set(userId, userInfo);
}
}
/**
* 启动位置更新定时器
*/
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秒更新一次
}
/**
* 启动状态检查定时器
*/
private startStatusCheckTimer(): void {
this.stopStatusCheckTimer();
this.statusCheckTimer = setInterval(() => {
this.checkUserStatuses();
}, 60000); // 每60秒检查一次
}
/**
* 检查用户状态
*/
private checkUserStatuses(): void {
const now = Date.now();
const timeoutThreshold = 120000; // 2分钟无更新视为超时
this.onlineUsers.forEach((userInfo, userId) => {
if (now - userInfo.lastUpdateTime > timeoutThreshold) {
// 用户超时
userInfo.status = 'timeout';
this.onlineUsers.set(userId, userInfo);
this.notifyUserStatusChange(userId, 'timeout');
console.log(`用户 ${userId} 因超时被标记为离线`);
}
});
}
/**
* 获取当前位置
*/
public async getCurrentLocation(): Promise<LocationData> {
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 unsubscribeFromLocations(): Promise<void> {
// 真实模式取消WebSocket订阅
await this.teardownRealTimeSubscription();
}
/**
* 设置实时位置订阅
*/
private async setupRealTimeSubscription(): Promise<void> {
// 获取用户信息
const userInfo = userService.getGlobalUserInfo();
if (!userInfo || !userInfo.id) {
console.warn('用户未登录,无法设置实时位置订阅');
return;
}
// 缓存用户信息
this.userInfo = userInfo;
// 真实环境初始化WebSocket连接服务器会自动处理订阅逻辑
try {
// 初始化WebSocket连接如果尚未连接
await apiService.initLocationWebSocket();
// 等待WebSocket连接建立最多等待5秒
await new Promise<void>((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 stopTimers(): void {
this.stopLocationUpdateTimer();
this.stopStatusCheckTimer();
}
/**
* 停止位置更新定时器
*/
private stopLocationUpdateTimer(): void {
if (this.locationUpdateTimer) {
clearInterval(this.locationUpdateTimer);
this.locationUpdateTimer = null;
}
}
/**
* 停止状态检查定时器
*/
private stopStatusCheckTimer(): void {
if (this.statusCheckTimer) {
clearInterval(this.statusCheckTimer);
this.statusCheckTimer = null;
}
}
/**
* 通知位置更新回调
*/
private notifyLocationUpdateCallbacks(): void {
const onlineUsers = this.getOnlineUsers();
console.log('🔔 [LocationTrackingService] 开始通知位置更新回调');
console.log('👥 [LocationTrackingService] 在线用户数量:', onlineUsers.length);
console.log('📞 [LocationTrackingService] 准备执行', this.locationUpdateCallbacks.size, '个回调函数');
this.locationUpdateCallbacks.forEach((callback, index) => {
try {
console.log(`🔄 [LocationTrackingService] 执行第 ${index + 1} 个位置更新回调`);
callback(onlineUsers);
console.log(`✅ [LocationTrackingService] 第 ${index + 1} 个位置更新回调执行成功`);
} catch (error) {
console.error(`❌ [LocationTrackingService] 第 ${index + 1} 个位置更新回调执行失败:`, error);
}
});
console.log('🏁 [LocationTrackingService] 位置更新回调通知完成');
}
/**
* 通知用户状态变化回调
*/
private notifyUserStatusChange(userId: number, status: 'online' | 'offline' | 'timeout'): void {
this.userStatusCallbacks.forEach(callback => {
try {
callback(userId, status);
} catch (error) {
console.error('用户状态变化回调执行失败:', error);
}
});
}
/**
* 处理在线用户列表
* @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();