Files
WXProgram/miniprogram/services/locationTrackingService.ts

595 lines
17 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('用户未登录,无法启动位置追踪');
}
this.isSignedIn = true;
// 将当前用户添加到在线列表
await this.addUserToOnlineList(userInfo);
// 签到消息已通过REST API处理服务器会自动处理订阅逻辑
// 设置实时位置订阅初始化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();
// 启动状态检查定时器每60秒检查一次
this.startStatusCheckTimer();
console.log('位置追踪服务已启动');
}
/**
* 初始化位置追踪服务
*/
public async initialize(): Promise<void> {
try {
// 注册位置更新回调
apiService.onLocationUpdate((locationUpdate) => {
this.handleLocationUpdate(locationUpdate);
});
// 注册在线用户列表回调
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}`);
console.log(`- 时间戳: ${Date.now()}`);
const locationData: LocationData = {
userId: userInfo.id,
longitude: location.longitude,
latitude: location.latitude,
timestamp: Date.now()
};
// 真实模式通过WebSocket发送位置更新
console.log('[位置追踪服务] 真实模式通过WebSocket发送位置更新');
await apiService.sendLocationUpdate(userInfo.id, location.latitude, location.longitude);
// 同时更新本地缓存
this.updateLocalUserLocation(userInfo.id, locationData);
// 通知所有回调
this.notifyLocationUpdateCallbacks();
console.log('[位置追踪服务] 位置更新完成,已通知回调');
}
/**
* 获取所有在线用户信息
*/
public getOnlineUsers(): OnlineUserInfo[] {
return Array.from(this.onlineUsers.values()).filter(user =>
user.status === 'online'
);
}
/**
* 订阅位置更新
*/
public subscribeToLocationUpdates(callback: (locations: OnlineUserInfo[]) => void): void {
this.locationUpdateCallbacks.add(callback);
}
/**
* 取消订阅位置更新
*/
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: Date.now(),
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} 因超时被标记为离线`);
}
});
}
/**
* 获取当前位置
*/
private 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连接已建立服务器将自动处理位置订阅逻辑');
// 设置位置更新回调
this.unsubscribeCallback = apiService.onLocationUpdate((locationUpdate) => {
this.handleLocationUpdate(locationUpdate);
});
} 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();
this.locationUpdateCallbacks.forEach(callback => {
try {
callback(onlineUsers);
} catch (error) {
console.error('位置更新回调执行失败:', error);
}
});
}
/**
* 通知用户状态变化回调
*/
private notifyUserStatusChange(userId: number, status: 'online' | 'offline' | 'timeout'): void {
this.userStatusCallbacks.forEach(callback => {
try {
callback(userId, status);
} catch (error) {
console.error('用户状态变化回调执行失败:', error);
}
});
}
/**
* 处理位置更新
*/
private handleLocationUpdate(locationUpdate: any): void {
console.log('收到位置更新:', locationUpdate);
// 验证位置更新消息格式
if (!locationUpdate || typeof locationUpdate !== 'object') {
console.warn('无效的位置更新消息格式');
return;
}
// 提取关键信息
const { deliveryPersonId, latitude, longitude, timestamp } = locationUpdate;
if (!deliveryPersonId || latitude === undefined || longitude === undefined) {
console.warn('位置更新消息缺少必要字段:', locationUpdate);
return;
}
console.log(`处理员工 ${deliveryPersonId} 的位置更新: (${longitude}, ${latitude})`);
// 更新本地缓存中的员工位置
this.updateLocalUserLocation(deliveryPersonId, {
userId: deliveryPersonId,
longitude: longitude,
latitude: latitude,
timestamp: timestamp || Date.now()
});
// 通知所有位置更新回调UI层会监听这些回调来更新地图标记点
this.notifyLocationUpdateCallbacks();
console.log(`员工 ${deliveryPersonId} 位置更新处理完成`);
}
/**
* 处理在线用户列表消息
* @param userList 在线用户列表消息
*/
private handleOnlineUserList(userList: any): void {
try {
console.log('[LocationTrackingService] 收到在线用户列表:', userList);
if (!userList || !userList.users) {
console.warn('[LocationTrackingService] 无效的在线用户列表消息');
return;
}
// 清空当前在线用户列表
this.onlineUsers.clear();
// 更新在线用户列表
userList.users.forEach((userInfo: any) => {
if (userInfo && userInfo.userId) {
this.onlineUsers.set(userInfo.userId, {
userId: userInfo.userId,
name: userInfo.userName || '未知用户',
avatarUrl: '/images/user-avatar.png',
role: userInfo.role || 'delivery_person', // 用户角色,默认为配送员
lastLocation: {
userId: userInfo.userId,
longitude: userInfo.longitude || 0,
latitude: userInfo.latitude || 0,
timestamp: userInfo.lastUpdateTime || Date.now()
},
lastUpdateTime: userInfo.lastUpdateTime || Date.now(),
status: 'online'
});
}
});
console.log('[LocationTrackingService] 在线用户列表已更新,当前在线用户数:', this.onlineUsers.size);
// 通知位置模块更新地图标记点
this.notifyLocationUpdateCallbacks();
} catch (error) {
console.error('[LocationTrackingService] 处理在线用户列表失败:', error);
}
}
}
/**
* 位置追踪服务单例实例
* 导出供应用程序全局使用
*/
export default new LocationTrackingService();