Files
WXProgram/miniprogram/services/apiService.ts
2025-10-19 23:38:54 +08:00

949 lines
26 KiB
TypeScript
Raw Permalink 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 { WarehouseInfo, UserInfo, DeliveryPerson, Order, EmployeeInfo } from '../types';
import locationTrackingService from './locationTrackingService';
/**
* API服务基类
* 提供与后端API交互的核心功能
*/
// API基础URL配置
const IS_LOCAL_DEV = false; // true: 本地开发环境, false: 生产环境
const API_BASE_URL = IS_LOCAL_DEV ? 'http://localhost:8080' : 'https://www.doubleyin.cn';
console.log(`当前API地址: ${API_BASE_URL} (${IS_LOCAL_DEV ? '本地开发环境' : '生产环境'})`);
/**
* 是否启用模拟模式
* 该配置控制是否使用模拟数据进行开发测试
*/
/**
* API服务类
* 封装了所有与后端API交互的方法并按照功能模块进行组织
*/
class ApiService {
/**
* 位置更新WebSocket连接
*/
private locationWebSocket: any | null;
/**
* 构造函数
*/
constructor() {
this.locationWebSocket = null;
}
/**
* 基础请求方法
* @param endpoint API端点路径
* @param options 请求配置选项
* @returns Promise<T> 返回泛型类型的响应数据
*/
private async request<T>(endpoint: string, options: {
method?: string;
data?: any;
headers?: Record<string, string>
} = {}): Promise<T> {
const requestUrl = `${API_BASE_URL}${endpoint}`;
// 获取全局token
const app = getApp<any>();
const token = app.globalData.token;
// 构建请求头自动添加Authorization头如果存在token
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options.headers,
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const requestOptions = {
method: options.method || 'GET',
data: options.data,
header: headers,
};
// 打印请求信息
console.log('\n========== API Request ==========');
console.log(`Method: ${requestOptions.method}`);
console.log(`URL: ${requestUrl}`);
console.log(`Headers:`, requestOptions.header);
console.log(`Data:`, requestOptions.data);
console.log('================================');
return new Promise((resolve, reject) => {
wx.request({
url: requestUrl,
method: requestOptions.method as any,
data: requestOptions.data,
header: requestOptions.header,
success: (res: any) => {
// 打印响应信息
console.log('\n========== API Response ==========');
console.log(`URL: ${requestUrl}`);
console.log(`Status: ${res.statusCode}`);
console.log(`Response Data:`, res.data);
console.log('==================================');
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data);
} else {
console.error(`API Error: HTTP ${res.statusCode}: ${res.errMsg}`);
reject(new Error(`HTTP ${res.statusCode}: ${res.errMsg}`));
}
},
fail: (err: any) => {
console.error('\n========== API Request Failed ==========');
console.error(`URL: ${requestUrl}`);
console.error(`Error: ${err.errMsg}`);
console.error('=======================================');
reject(new Error(`Request failed: ${err.errMsg}`));
}
});
});
}
// ==================== 用户模块 ====================
// ====== 认证相关 ======
/**
* 微信登录接口
* @param code 微信登录授权码
* @returns 用户信息及认证令牌
*/
async ServerLogin(code: string) {
console.log('API wxLogin调用参数code:', code);
return await this.request<{ user: UserInfo; token: string; openid: string; session_key: string }>('/user/wxlogin', {
method: 'POST',
data: { code: code },
});
}
/**
* 签到接口
* @param userId 用户ID
* @param initialLocation 初始位置数据(必须)
* @returns 签到结果和员工信息
*/
async userSignIn(userId: number, initialLocation: { latitude: number; longitude: number; timestamp: number }): Promise<{ success: boolean; employeeInfo: EmployeeInfo; message?: string }> {
console.log('API userSignIn调用参数userId:', userId, 'initialLocation:', initialLocation);
// 构建请求数据,必须包含位置数据
const requestData: any = {
latitude: initialLocation.latitude,
longitude: initialLocation.longitude,
timestamp: initialLocation.timestamp
};
// 服务器现在返回统一的DTO格式直接使用
const response = await this.request<any>('/user/signin', {
method: 'POST',
data: requestData,
});
// 服务器返回格式:{ success: boolean, userInfo: UserInfoResponse }
return {
success: response.success,
employeeInfo: {
id: response.userInfo.id,
name: response.userInfo.name,
phone: response.userInfo.phone,
role: response.userInfo.role || 'DELIVERY_PERSON'
},
message: response.message
};
}
/**
* 注册接口
* @param userInfo 注册用户信息
* @returns 注册结果和员工信息
*/
async userRegister(userInfo: { name: string; phone: string }): Promise<{ success: boolean; employeeInfo: EmployeeInfo; message?: string }> {
console.log('API userRegister调用参数userInfo:', userInfo);
// 服务器现在返回统一的DTO格式{ success: boolean, userInfo: UserInfoResponse }
const response = await this.request<any>('/user/register', {
method: 'POST',
data: userInfo,
});
return {
success: response.success,
employeeInfo: {
id: response.userInfo.id,
name: response.userInfo.name || userInfo.name, // 如果服务器返回null使用用户输入的值
phone: response.userInfo.phone || userInfo.phone, // 如果服务器返回null使用用户输入的值
role: response.userInfo.role || 'DELIVERY_PERSON'
},
message: response.message
};
}
/**
* 用户登出
*/
async logout(): Promise<void> {
return await this.request('/user/logout', { method: 'POST' });
}
/**
* 签退接口
* @param userId 用户ID
* @returns 签退结果
*/
async userSignOut(userId: number): Promise<{ success: boolean; message?: string }> {
console.log('API userSignOut调用参数userId:', userId);
// 服务器现在返回统一的DTO格式{ success: boolean, message: string }
const response = await this.request<any>('/user/signout', {
method: 'POST',
data: { userId },
});
return {
success: response.success,
message: response.message
};
}
/**
* 获取用户签到状态
* @returns 用户状态信息
*/
async getUserStatus(): Promise<{ status: 'signed_in' | 'signed_out' | 'registered' | 'unregistered'; lastSignInTime?: string; lastSignOutTime?: string }> {
return await this.request('/user/status', { method: 'GET' });
}
// ====== 信息相关 ======
/**
* 获取当前用户信息
* @returns 用户详细信息
*/
async getUserInfo() {
return await this.request<UserInfo>('/user/info');
}
/**
* 更新用户信息
* @param userInfo 待更新的用户信息
* @returns 更新后的用户信息
*/
async updateUserInfo(userInfo: Partial<UserInfo>): Promise<UserInfo> {
return await this.request<UserInfo>('/user/update', {
method: 'PUT',
data: userInfo,
});
}
/**
* 获取用户权限列表
* @returns 权限字符串数组
*/
async getUserPermissions(): Promise<string[]> {
return await this.request<string[]>('/user/permissions');
}
/**
* 检查用户是否在线
* @returns 在线状态
*/
async checkUserOnline(): Promise<boolean> {
return await this.request<boolean>('/user/online');
}
/**
* 获取用户会话信息
* @returns 会话详细信息
*/
async getSessionInfo(): Promise<{
userId: number;
sessionId: string;
expiresAt: number;
permissions: string[];
}> {
return await this.request<{
userId: number;
sessionId: string;
expiresAt: number;
permissions: string[];
}>('/user/session');
}
/**
* 刷新用户令牌
* @param oldToken 旧令牌
* @returns 新令牌及过期时间
*/
async refreshToken(oldToken: string): Promise<{ token: string; expiresAt: number }> {
return await this.request<{ token: string; expiresAt: number }>('/user/refresh-token', {
method: 'POST',
data: { token: oldToken },
});
}
/**
* 验证用户权限
* @param permission 待验证的权限
* @returns 是否拥有该权限
*/
async verifyPermission(permission: string): Promise<boolean> {
return await this.request<boolean>(`/user/verify-permission?permission=${permission}`);
}
// ====== 管理相关 ======
/**
* 搜索用户
* @param query 搜索关键词
* @param page 页码
* @param pageSize 每页数量
* @returns 用户列表及分页信息
*/
async searchUsers(query: string, page: number = 1, pageSize: number = 20): Promise<{
users: UserInfo[];
total: number;
page: number;
pageSize: number;
}> {
return await this.request<{ users: UserInfo[]; total: number; page: number; pageSize: number }>(
`/user/search?query=${query}&page=${page}&pageSize=${pageSize}`
);
}
/**
* 批量操作用户状态
* @param userIds 用户ID列表
* @param status 目标状态
*/
async batchUpdateUserStatus(userIds: number[], status: 'active' | 'inactive' | 'suspended'): Promise<void> {
return await this.request('/user/batch-update-status', {
method: 'POST',
data: { userIds, status },
});
}
// ====== 员工管理接口 ======
/**
* 获取所有员工列表
* @returns 员工信息数组
*/
async getEmployees(): Promise<EmployeeInfo[]> {
return await this.request<EmployeeInfo[]>('/employees');
}
/**
* 添加新员工
* @param employeeInfo 员工信息
* @returns 添加结果
*/
async addEmployee(employeeInfo: { name: string; phone: string; role: string }): Promise<EmployeeInfo> {
return await this.request<EmployeeInfo>('/employees', {
method: 'POST',
data: employeeInfo,
});
}
/**
* 删除员工
* @param employeeId 员工ID
* @returns 删除结果
*/
async deleteEmployee(employeeId: number): Promise<{ success: boolean; message?: string }> {
return await this.request<{ success: boolean; message?: string }>(`/employees/${employeeId}`, {
method: 'DELETE',
});
}
/**
* 更新员工信息
* @param employeeId 员工ID
* @param employeeInfo 员工信息
* @returns 更新结果
*/
async updateEmployee(employeeId: number, employeeInfo: { name?: string; phone?: string; role?: string }): Promise<EmployeeInfo> {
return await this.request<EmployeeInfo>(`/employees/${employeeId}`, {
method: 'PUT',
data: employeeInfo,
});
}
// ==================== 仓库相关接口 ====================
/**
* 获取所有仓库列表
* @returns 仓库信息数组
*/
async getWarehouses(): Promise<WarehouseInfo[]> {
return await this.request<WarehouseInfo[]>('/warehouses');
}
/**
* 获取指定仓库详情
* @param id 仓库ID
* @returns 仓库详细信息
*/
async getWarehouseById(id: number): Promise<WarehouseInfo> {
return await this.request<WarehouseInfo>(`/warehouses/${id}`);
}
/**
* 获取仓库相关订单
* @param warehouseId 仓库ID
* @returns 订单列表
*/
async getWarehouseOrders(warehouseId: number): Promise<Order[]> {
return await this.request<Order[]>(`/warehouses/${warehouseId}/orders`);
}
// ==================== 货运人员接口 ====================
/**
* 获取所有货运人员
* @returns 货运人员列表
*/
async getDeliveryPersons(): Promise<DeliveryPerson[]> {
return await this.request<DeliveryPerson[]>('/delivery-persons');
}
/**
* 获取所有配送员实时位置
* @returns 所有配送员实时位置信息
*/
async getAllDeliveryPersonLocations(): Promise<Array<{
deliveryPersonId: number;
name: string;
latitude: number;
longitude: number;
status: string;
}>> {
return await this.request<Array<{
deliveryPersonId: number;
name: string;
latitude: number;
longitude: number;
status: string;
}>>('/location-sync/delivery-persons/locations');
}
/**
* 获取指定货运人员详情
* @param id 货运人员ID
* @returns 货运人员详细信息
*/
async getDeliveryPersonById(id: number): Promise<DeliveryPerson> {
return await this.request<DeliveryPerson>(`/delivery-persons/${id}`);
}
/**
* 更新货运人员位置
* @param id 货运人员ID
* @param location 位置信息
*/
async updateDeliveryPersonLocation(id: number, location: { longitude: number; latitude: number }): Promise<void> {
return await this.request(`/delivery-persons/${id}/location`, {
method: 'PUT',
data: location,
});
}
// 地图相关接口支持
/**
* 批量更新货运人员位置兼容mapService参数
* @param locations 位置信息数组
*/
async batchUpdateDeliveryPersonLocations(locations: Array<{
userId: number;
latitude: number;
longitude: number;
timestamp: number;
}>): Promise<void> {
// 转换参数格式以匹配原有API
const formattedLocations = locations.map(loc => ({
deliveryPersonId: loc.userId,
longitude: loc.longitude,
latitude: loc.latitude,
timestamp: loc.timestamp
}));
await this.request('/delivery-persons/locations/batch', {
method: 'POST',
data: { locations: formattedLocations }
});
}
/**
* 获取空闲的货运人员
* @returns 空闲货运人员列表
*/
async getIdleDeliveryPersons(): Promise<DeliveryPerson[]> {
return await this.request<DeliveryPerson[]>('/delivery-persons/idle');
}
/**
* 获取忙碌的货运人员
* @returns 忙碌货运人员列表
*/
async getBusyDeliveryPersons(): Promise<DeliveryPerson[]> {
return await this.request<DeliveryPerson[]>('/delivery-persons/busy');
}
/**
* 获取位置历史记录
* @param deliveryPersonId 货运人员ID
* @param limit 记录数量
* @returns 位置历史记录
*/
async getLocationHistory(deliveryPersonId: number, limit: number = 50): Promise<Array<{
timestamp: number;
longitude: number;
latitude: number;
speed?: number;
accuracy?: number;
}>> {
return await this.request<Array<{
timestamp: number;
longitude: number;
latitude: number;
speed?: number;
accuracy?: number;
}>>(`/delivery-persons/${deliveryPersonId}/location-history?limit=${limit}`);
}
/**
* 订阅实时位置更新接收货运人员ID列表
* @param deliveryPersonIds 货运人员ID列表
* @returns 订阅信息
*/
async subscribeToRealTimeLocations(deliveryPersonIds: number[]): Promise<{
subscriptionId: string;
expiresAt: number;
}> {
// 真实环境中调用API
try {
return await this.request<{
subscriptionId: string;
expiresAt: number;
}>('/locations/subscribe', {
method: 'POST',
data: { deliveryPersonIds }
});
} catch (error) {
console.error('订阅实时位置更新失败:', error);
throw error;
}
}
/**
* 取消订阅实时位置更新
* @param subscriptionId 订阅ID
*/
async unsubscribeFromRealTimeLocations(subscriptionId: string): Promise<void> {
// 真实环境中调用API
try {
await this.request<void>('/locations/unsubscribe', {
method: 'POST',
data: { subscriptionId }
});
} catch (error) {
console.error('取消订阅实时位置更新失败:', error);
throw error;
}
}
// ===== 私有属性和方法 =====
/**
* 初始化位置更新WebSocket连接
*/
public async initLocationWebSocket(): Promise<void> {
if (this.locationWebSocket) {
console.log('WebSocket连接已存在');
return;
}
try {
if (typeof wx !== 'undefined' && typeof wx.connectSocket === 'function') {
console.log('初始化位置更新WebSocket连接小程序环境');
// 连接到服务端的WebSocket地址使用配置的服务器地址
const serverUrl = API_BASE_URL.replace('http', 'ws');
const wsUrl = `${serverUrl}/ws/location`;
// 获取当前用户ID从全局应用数据中获取
const app = getApp<any>();
const userInfo = app.globalData.userInfo;
const userId = userInfo?.id;
// 构建带用户ID参数的WebSocket URL
const finalWsUrl = userId ? `${wsUrl}?userId=${userId}` : wsUrl;
console.log(`WebSocket连接URL: ${finalWsUrl}`);
this.locationWebSocket = wx.connectSocket({
url: finalWsUrl,
header: {
'content-type': 'application/json'
}
});
// WebSocket连接打开事件
wx.onSocketOpen(() => {
console.log('WebSocket连接已打开');
});
// WebSocket消息接收事件
wx.onSocketMessage((res) => {
try {
const message = JSON.parse(typeof res.data === 'string' ? res.data : String(res.data));
this.handleWebSocketMessage(message);
} catch (error) {
console.error('解析WebSocket消息失败:', error);
}
});
// WebSocket连接关闭事件
wx.onSocketClose((res) => {
console.log('WebSocket连接已关闭', res);
this.locationWebSocket = null;
// 如果是非主动关闭,尝试重连
if (res.code !== 1000) { // 1000表示正常关闭
console.log('WebSocket连接异常关闭将在3秒后尝试重连');
setTimeout(() => {
if (!this.locationWebSocket) {
this.initLocationWebSocket().catch(err => {
console.error('WebSocket重连失败:', err);
});
}
}, 3000);
}
});
// WebSocket错误事件
wx.onSocketError((error) => {
console.error('WebSocket连接错误:', error);
this.locationWebSocket = null;
});
} else {
console.warn('当前环境不支持WebSocket连接');
}
} catch (error) {
console.error('初始化位置更新WebSocket连接失败:', error);
this.locationWebSocket = null;
}
}
/**
* 检查WebSocket连接是否已建立
*/
public isWebSocketConnected(): boolean {
return this.locationWebSocket !== null && this.locationWebSocket !== undefined;
}
/**
* 关闭位置更新WebSocket连接
*/
public async closeLocationWebSocket(): Promise<void> {
if (this.locationWebSocket && typeof wx !== 'undefined' && typeof wx.closeSocket === 'function') {
try {
// 先检查WebSocket状态避免在连接过程中关闭
wx.closeSocket();
this.locationWebSocket = null;
console.log('WebSocket连接已关闭');
} catch (error) {
console.warn('关闭WebSocket连接时出现异常:', error);
// 即使关闭失败,也重置连接状态
this.locationWebSocket = null;
}
}
}
/**
* 处理WebSocket消息
* @param message 接收到的消息
*/
private handleWebSocketMessage(message: any): void {
if (!message || typeof message.type !== 'string') {
console.warn('收到无效的WebSocket消息:', message);
return;
}
locationTrackingService.handleWebSocketMessage(message);
}
/**
* 发送WebSocket消息
* @param message 要发送的消息对象
*/
private sendWebSocketMessage(message: any): void {
if (!this.locationWebSocket) {
console.warn('WebSocket连接未建立无法发送消息');
return;
}
try {
const messageStr = JSON.stringify(message);
if (typeof wx !== 'undefined' && typeof wx.sendSocketMessage === 'function') {
wx.sendSocketMessage({
data: messageStr
});
}
} catch (error) {
console.error('发送WebSocket消息失败:', error);
}
}
/**
* 订阅位置更新(服务器自动处理,无需发送消息)
* @param userId 用户ID
*/
async subscribeToLocationUpdates(): Promise<void> {
// 确保WebSocket连接已建立
if (!this.locationWebSocket) {
this.initLocationWebSocket();
}
// 服务器会自动处理订阅逻辑,无需发送订阅消息
console.log('位置订阅已初始化,服务器将自动处理订阅逻辑');
}
/**
* 取消订阅位置更新(服务器自动处理,无需发送消息)
* @param userId 用户ID
*/
async unsubscribeFromLocationUpdates(_userId: number): Promise<void> {
// 服务器会自动处理取消订阅逻辑,无需发送取消订阅消息
console.log('位置取消订阅已初始化,服务器将自动处理取消订阅逻辑');
}
/**
* 发送位置更新
* @param userId 用户ID
* @param latitude 纬度
* @param longitude 经度
*/
async sendLocationUpdate(userId: number, latitude: number, longitude: number): Promise<void> {
// 确保WebSocket连接已建立
if (!this.locationWebSocket) {
this.initLocationWebSocket();
}
// 发送位置更新消息
const locationMessage = {
type: 'updateLocation',
userId: userId,
latitude: latitude,
longitude: longitude,
timestamp: Date.now()
};
this.sendWebSocketMessage(locationMessage);
}
// ==================== 订单接口 ====================
/**
* 获取待指派订单
* @returns 待指派订单列表
*/
async getPendingOrders(): Promise<Order[]> {
return await this.request<Order[]>('/orders/pending');
}
/**
* 指派订单给货运人员
* @param orderId 订单ID
* @param deliveryPersonId 货运人员ID
*/
async assignOrder(orderId: number, deliveryPersonId: number): Promise<{ success: boolean; message?: string }> {
return await this.request<{ success: boolean; message?: string }>(`/orders/${orderId}/assign`, {
method: 'POST',
data: { deliveryPersonId },
});
}
/**
* 获取货运人员当前订单
* @param deliveryPersonId 货运人员ID
* @returns 订单列表
*/
async getDeliveryPersonOrders(deliveryPersonId: number): Promise<Order[]> {
return await this.request<Order[]>(`/delivery-persons/${deliveryPersonId}/orders`);
}
/**
* 根据ID获取订单
* @param id 订单ID
* @returns 订单详细信息
*/
async getOrderById(id: number): Promise<Order> {
return await this.request<Order>(`/orders/${id}`);
}
/**
* 更新订单状态
* @param orderId 订单ID
* @param status 目标状态
*/
async updateOrderStatus(orderId: number, status: Order['status']): Promise<{ success: boolean; message?: string }> {
return await this.request<{ success: boolean; message?: string }>(`/orders/${orderId}/status`, {
method: 'PUT',
data: { status },
});
}
/**
* 创建新订单
* @param orderData 订单数据
* @returns 创建的订单信息
*/
async createOrder(orderData: Omit<Order, 'id' | 'createTime'>): Promise<Order> {
return await this.request<Order>('/orders', {
method: 'POST',
data: orderData,
});
}
/**
* 删除订单
* @param orderId 订单ID
*/
async deleteOrder(orderId: number): Promise<void> {
return await this.request(`/orders/${orderId}`, {
method: 'DELETE',
});
}
// ==================== 统计和日志接口 ====================
/**
* 获取系统统计信息
* @returns 系统统计数据
*/
async getSystemStats(): Promise<{
totalWarehouses: number;
totalDeliveryPersons: number;
pendingOrders: number;
activeOrders: number;
}> {
return await this.request<{
totalWarehouses: number;
totalDeliveryPersons: number;
pendingOrders: number;
activeOrders: number;
}>('/stats/system');
}
/**
* 获取订单统计信息
* @param timeRange 时间范围
* @returns 订单统计数据
*/
async getOrderStats(timeRange: 'today' | 'week' | 'month'): Promise<{
totalOrders: number;
completedOrders: number;
inProgressOrders: number;
averageDeliveryTime: number;
}> {
return await this.request<{
totalOrders: number;
completedOrders: number;
inProgressOrders: number;
averageDeliveryTime: number;
}>(`/stats/orders?timeRange=${timeRange}`);
}
/**
* 获取用户统计信息
* @returns 用户统计数据
*/
async getUserStats(): Promise<{
totalUsers: number;
activeUsers: number;
onlineUsers: number;
newUsersToday: number;
}> {
return await this.request<{
totalUsers: number;
activeUsers: number;
onlineUsers: number;
newUsersToday: number;
}>('/user/stats');
}
/**
* 获取用户活动日志
* @param userId 用户ID
* @param limit 日志数量
* @returns 活动日志列表
*/
async getUserActivityLogs(userId: number, limit: number = 20): Promise<Array<{
id: number;
action: string;
timestamp: number;
details?: any;
}>> {
return await this.request<Array<{ id: number; action: string; timestamp: number; details?: any }>>(
`/user/${userId}/activity-logs?limit=${limit}`
);
}
/**
* 上传错误日志
* @param error 错误信息
*/
async uploadErrorLog(error: any): Promise<void> {
await this.request('/logs/error', {
method: 'POST',
data: {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
timestamp: Date.now(),
userAgent: 'miniprogram/' + (typeof wx !== 'undefined' ? wx.getSystemInfoSync().platform : 'unknown')
}
});
}
// ==================== 系统接口 ====================
/**
* 健康检查
* @returns 系统健康状态
*/
async healthCheck(): Promise<{
status: 'ok' | 'degraded' | 'down';
message: string;
timestamp: number;
}> {
return await this.request<{
status: 'ok' | 'degraded' | 'down';
message: string;
timestamp: number;
}>('/health');
}
}
/**
* API服务单例实例
* 导出供应用程序全局使用
*/
export default new ApiService();