first commit
This commit is contained in:
564
miniprogram/services/locationTrackingService.ts
Normal file
564
miniprogram/services/locationTrackingService.ts
Normal file
@@ -0,0 +1,564 @@
|
||||
// 位置追踪服务 - 处理用户签到后的位置追踪和状态管理
|
||||
import { UserInfo, LocationData } from '../types';
|
||||
import apiService from './apiService';
|
||||
import { isMockMode } from './apiService';
|
||||
import userService from './userService';
|
||||
|
||||
/**
|
||||
* 在线用户信息接口
|
||||
*/
|
||||
export interface OnlineUserInfo {
|
||||
userId: number;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
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);
|
||||
|
||||
// 启动位置更新定时器(每30秒更新一次)
|
||||
this.startLocationUpdateTimer();
|
||||
|
||||
// 启动状态检查定时器(每60秒检查一次)
|
||||
this.startStatusCheckTimer();
|
||||
|
||||
// 订阅其他用户的位置更新
|
||||
await this.subscribeToOtherUsersLocations();
|
||||
|
||||
console.log('位置追踪服务已启动');
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户签退后停止位置追踪
|
||||
*/
|
||||
public async stopTracking(): Promise<void> {
|
||||
const userInfo = userService.getGlobalUserInfo();
|
||||
if (!userInfo || !userInfo.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSignedIn = false;
|
||||
|
||||
// 从在线列表中移除用户
|
||||
this.removeUserFromOnlineList(userInfo.id);
|
||||
|
||||
// 停止定时器
|
||||
this.stopTimers();
|
||||
|
||||
// 取消订阅
|
||||
await this.unsubscribeFromLocations();
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
if (isMockMode) {
|
||||
// 模拟模式:更新本地在线用户列表
|
||||
console.log('[位置追踪服务] 模拟模式:更新本地位置');
|
||||
this.updateLocalUserLocation(userInfo.id, locationData);
|
||||
} else {
|
||||
// 真实模式:调用API更新位置
|
||||
console.log('[位置追踪服务] 真实模式:调用API更新位置');
|
||||
await apiService.updateDeliveryPersonLocation(userInfo.id, location);
|
||||
|
||||
// 同时更新本地缓存
|
||||
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',
|
||||
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('用户未登录');
|
||||
}
|
||||
|
||||
// 这里应该调用地图服务获取当前位置
|
||||
// 暂时返回一个默认位置
|
||||
return {
|
||||
userId: userInfo.id,
|
||||
longitude: 102.833722,
|
||||
latitude: 24.880095,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅其他用户的位置更新
|
||||
*/
|
||||
private async subscribeToOtherUsersLocations(): Promise<void> {
|
||||
if (isMockMode) {
|
||||
// 模拟模式:定时生成其他用户的位置更新
|
||||
this.startMockOtherUsersUpdates();
|
||||
} else {
|
||||
// 真实模式:通过WebSocket订阅
|
||||
await this.setupRealTimeSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅位置更新
|
||||
*/
|
||||
private async unsubscribeFromLocations(): Promise<void> {
|
||||
if (!isMockMode) {
|
||||
// 真实模式:取消WebSocket订阅
|
||||
await this.teardownRealTimeSubscription();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动模拟的其他用户位置更新
|
||||
*/
|
||||
private startMockOtherUsersUpdates(): void {
|
||||
// 模拟其他用户的位置更新(每45秒一次)
|
||||
setInterval(() => {
|
||||
if (this.isSignedIn) {
|
||||
this.generateMockOtherUsersLocations();
|
||||
this.notifyLocationUpdateCallbacks();
|
||||
}
|
||||
}, 45000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模拟的其他用户位置
|
||||
*/
|
||||
private generateMockOtherUsersLocations(): void {
|
||||
const currentUserInfo = userService.getGlobalUserInfo();
|
||||
if (!currentUserInfo) return;
|
||||
|
||||
// 模拟2个其他在线用户
|
||||
const mockUserIds = [101, 102]; // 模拟用户ID
|
||||
|
||||
mockUserIds.forEach(userId => {
|
||||
if (userId !== currentUserInfo.id) {
|
||||
const existingUser = this.onlineUsers.get(userId);
|
||||
|
||||
if (existingUser) {
|
||||
// 更新现有用户位置
|
||||
existingUser.lastLocation.longitude += (Math.random() - 0.5) * 0.001;
|
||||
existingUser.lastLocation.latitude += (Math.random() - 0.5) * 0.001;
|
||||
existingUser.lastUpdateTime = Date.now();
|
||||
existingUser.status = 'online';
|
||||
|
||||
this.onlineUsers.set(userId, existingUser);
|
||||
} else {
|
||||
// 添加新用户
|
||||
const newUser: OnlineUserInfo = {
|
||||
userId,
|
||||
name: `用户${userId}`,
|
||||
avatarUrl: '/images/user-avatar.png',
|
||||
lastLocation: {
|
||||
userId,
|
||||
longitude: 102.833722 + (Math.random() - 0.5) * 0.01,
|
||||
latitude: 24.880095 + (Math.random() - 0.5) * 0.01,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
lastUpdateTime: Date.now(),
|
||||
status: 'online'
|
||||
};
|
||||
|
||||
this.onlineUsers.set(userId, newUser);
|
||||
this.notifyUserStatusChange(userId, 'online');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实时位置订阅
|
||||
*/
|
||||
private setupRealTimeSubscription(): void {
|
||||
// 获取用户信息
|
||||
const userInfo = userService.getGlobalUserInfo();
|
||||
if (!userInfo || !userInfo.id) {
|
||||
console.warn('用户未登录,无法设置实时位置订阅');
|
||||
return;
|
||||
}
|
||||
|
||||
// 缓存用户信息
|
||||
this.userInfo = userInfo;
|
||||
|
||||
if (isMockMode) {
|
||||
console.log('[MOCK] 设置实时位置订阅');
|
||||
this.startMockLocationGeneration();
|
||||
return;
|
||||
}
|
||||
|
||||
// 真实环境:初始化WebSocket连接并订阅位置更新
|
||||
try {
|
||||
// 使用新的API接口
|
||||
apiService.initLocationWebSocket();
|
||||
|
||||
// 订阅当前用户的位置更新
|
||||
apiService.subscribeToLocationUpdates(this.userInfo.id)
|
||||
.then(() => {
|
||||
console.log('成功订阅位置更新');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('订阅位置更新失败:', error);
|
||||
});
|
||||
|
||||
// 设置位置更新回调
|
||||
this.unsubscribeCallback = apiService.onLocationUpdate((locationUpdate) => {
|
||||
this.handleLocationUpdate(locationUpdate);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('设置实时位置订阅失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭实时位置订阅
|
||||
*/
|
||||
private teardownRealTimeSubscription(): void {
|
||||
if (isMockMode) {
|
||||
console.log('[MOCK] 关闭实时位置订阅');
|
||||
this.stopMockLocationGeneration();
|
||||
return;
|
||||
}
|
||||
|
||||
// 真实环境:取消订阅并关闭WebSocket连接
|
||||
try {
|
||||
if (this.userInfo) {
|
||||
// 使用新的API接口取消订阅
|
||||
apiService.unsubscribeFromLocationUpdates(this.userInfo.id)
|
||||
.then(() => {
|
||||
console.log('成功取消订阅位置更新');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('取消订阅位置更新失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// 移除位置更新回调
|
||||
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 startMockLocationGeneration(): void {
|
||||
console.log('[MOCK] 启动模拟位置生成');
|
||||
// 这里可以添加模拟位置生成的逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止模拟位置生成
|
||||
*/
|
||||
private stopMockLocationGeneration(): void {
|
||||
console.log('[MOCK] 停止模拟位置生成');
|
||||
// 这里可以添加停止模拟位置生成的逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理位置更新
|
||||
*/
|
||||
private handleLocationUpdate(locationUpdate: any): void {
|
||||
console.log('收到位置更新:', locationUpdate);
|
||||
// 这里可以添加处理位置更新的逻辑
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 位置追踪服务单例实例
|
||||
* 导出供应用程序全局使用
|
||||
*/
|
||||
export default new LocationTrackingService();
|
Reference in New Issue
Block a user