| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- import axios, { AxiosInstance } from 'axios';
- import { createHash } from 'crypto';
- import { FeieApiConfig, FeiePrinterInfo, FeiePrintRequest, FeiePrintResponse, FeiePrinterStatusResponse, FeieOrderStatusResponse, FeieAddPrinterResponse, FeieDeletePrinterResponse } from '../types/feie.types';
- export class FeieApiService {
- private client: AxiosInstance;
- private config: FeieApiConfig;
- private maxRetries: number;
- constructor(config: FeieApiConfig) {
- // 确保baseUrl有默认值
- const defaultBaseUrl = 'https://api.feieyun.cn/Api/Open/';
- this.config = {
- baseUrl: defaultBaseUrl,
- timeout: 30000, // 增加到30秒,飞鹅API有时响应较慢
- maxRetries: 3,
- ...config
- };
- this.maxRetries = this.config.maxRetries || 3;
- this.client = axios.create({
- baseURL: this.config.baseUrl,
- timeout: this.config.timeout,
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- }
- });
- }
- /**
- * 生成飞鹅API签名
- */
- private generateSignature(timestamp: number): string {
- const content = `${this.config.user}${this.config.ukey}${timestamp}`;
- return createHash('sha1').update(content).digest('hex');
- }
- /**
- * 解析飞鹅API返回的响应(支持JSON和PHP var_dump格式)
- */
- private parseFeieResponse(responseString: string): any {
- try {
- console.debug('开始解析飞鹅API响应');
- // 移除多余的空白字符和换行
- const cleaned = responseString.trim();
- console.debug('清理后的响应:', cleaned.substring(0, 200) + '...');
- // 首先尝试解析为JSON
- try {
- const jsonResult = JSON.parse(cleaned);
- console.debug('成功解析为JSON格式');
- return jsonResult;
- } catch (jsonError) {
- console.debug('不是JSON格式,尝试解析PHP var_dump格式');
- }
- // 检查是否是var_dump格式
- if (!cleaned.startsWith('array(')) {
- console.debug('不是PHP var_dump格式');
- return null;
- }
- // 解析PHP var_dump格式
- const result: any = {};
- // 提取ret字段 - 支持多种格式
- let retMatch = cleaned.match(/\["ret"\]\s*=>\s*int\((\d+)\)/);
- if (!retMatch) {
- retMatch = cleaned.match(/\["ret"\]\s*=>\s*string\((\d+)\)\s*"(\d+)"/);
- if (retMatch) {
- result.ret = parseInt(retMatch[2], 10);
- }
- } else {
- result.ret = parseInt(retMatch[1], 10);
- }
- // 提取msg字段
- const msgMatch = cleaned.match(/\["msg"\]\s*=>\s*string\((\d+)\)\s*"([^"]*)"/);
- if (msgMatch) {
- result.msg = msgMatch[2];
- }
- // 提取data字段中的信息
- const dataMatch = cleaned.match(/\["data"\]\s*=>\s*array\(\d+\)\s*{([\s\S]*?)}\s*}/);
- if (dataMatch) {
- const dataContent = dataMatch[1];
- result.data = {};
- // 提取ok数组
- const okMatch = dataContent.match(/\["ok"\]\s*=>\s*array\(\d+\)\s*{([\s\S]*?)}/);
- if (okMatch) {
- result.data.ok = [];
- const okContent = okMatch[1];
- const okItems = okContent.match(/\[(\d+)\]\s*=>\s*string\(\d+\)\s*"([^"]*)"/g);
- if (okItems) {
- okItems.forEach(item => {
- const itemMatch = item.match(/string\(\d+\)\s*"([^"]*)"/);
- if (itemMatch) {
- result.data.ok.push(itemMatch[1]);
- }
- });
- }
- }
- // 提取no数组
- const noMatch = dataContent.match(/\["no"\]\s*=>\s*array\(\d+\)\s*{([\s\S]*?)}/);
- if (noMatch) {
- result.data.no = [];
- const noContent = noMatch[1];
- const noItems = noContent.match(/\[(\d+)\]\s*=>\s*string\(\d+\)\s*"([^"]*)"/g);
- if (noItems) {
- noItems.forEach(item => {
- const itemMatch = item.match(/string\(\d+\)\s*"([^"]*)"/);
- if (itemMatch) {
- result.data.no.push(itemMatch[1]);
- }
- });
- }
- }
- }
- // 如果没有解析到ret字段,尝试其他方式
- if (result.ret === undefined) {
- console.debug('未解析到ret字段,尝试其他方式');
- // 检查是否有错误信息
- if (cleaned.includes('错误:')) {
- result.ret = -1; // 假设是参数错误
- result.msg = '打印机添加失败';
- } else if (cleaned.includes('"ok"')) {
- result.ret = 0;
- result.msg = 'ok';
- }
- }
- console.debug('解析结果:', JSON.stringify(result, null, 2));
- return result;
- } catch (error) {
- console.debug('解析飞鹅API响应失败:', error);
- return null;
- }
- }
- /**
- * 执行API请求,支持重试
- */
- private async executeRequest<T>(endpoint: string, params: Record<string, any>): Promise<T> {
- const timestamp = Math.floor(Date.now() / 1000);
- const signature = this.generateSignature(timestamp);
- const requestParams = {
- user: this.config.user,
- stime: timestamp,
- sig: signature,
- apiname: endpoint, // 飞鹅API要求将接口名作为apiname参数传递
- debug: 1, // 添加debug参数以便查看更多错误信息
- ...params
- };
- // 调试日志:记录请求信息(不记录敏感信息)
- console.debug(`飞鹅API请求: ${endpoint}, 用户: ${this.config.user}, 时间戳: ${timestamp}`);
- let lastError: Error | null = null;
- for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
- try {
- // 飞鹅API所有请求都发送到根路径,接口名通过apiname参数指定
- const response = await this.client.post('', requestParams);
- // 调试日志:记录完整的API响应
- console.debug(`飞鹅API响应:`, {
- status: response.status,
- statusText: response.statusText,
- data: response.data,
- headers: response.headers
- });
- // 检查响应数据结构
- if (!response.data) {
- throw new Error(`飞鹅API响应格式错误: 响应数据为空`);
- }
- // 尝试解析响应数据,可能是字符串或对象
- let responseData = response.data;
- // 如果是字符串,使用统一的解析函数
- if (typeof responseData === 'string') {
- const parsedResponse = this.parseFeieResponse(responseData);
- if (parsedResponse) {
- responseData = parsedResponse;
- } else {
- // 检查是否是HTML错误页面
- if (responseData.includes('<!DOCTYPE') || responseData.includes('<html')) {
- throw new Error(`飞鹅API返回HTML错误页面,可能是网络或配置问题: ${responseData.substring(0, 200)}...`);
- }
- throw new Error(`飞鹅API响应格式错误: 无法解析响应数据: ${responseData.substring(0, 200)}...`);
- }
- }
- // 检查是否为对象
- if (typeof responseData !== 'object' || responseData === null) {
- throw new Error(`飞鹅API响应格式错误: 响应数据不是有效的对象: ${JSON.stringify(responseData)}`);
- }
- // 检查ret字段是否存在
- if (responseData.ret === undefined) {
- // 飞鹅API有时会返回不同的字段名,检查常见字段
- if (responseData.code !== undefined) {
- // 如果有code字段,将其映射到ret
- responseData.ret = responseData.code;
- } else if (responseData.error !== undefined) {
- // 如果有error字段,将其作为错误信息
- throw new Error(`飞鹅API错误: ${responseData.error}`);
- } else if (responseData.msg !== undefined) {
- // 如果只有msg字段,将其作为错误信息,并设置ret为-1(参数错误)
- responseData.ret = -1;
- console.debug('飞鹅API响应缺少ret字段,但包含msg字段,设置为参数错误:', responseData.msg);
- } else {
- throw new Error(`飞鹅API响应格式错误: 缺少ret字段,响应数据: ${JSON.stringify(responseData)}`);
- }
- }
- if (responseData.ret !== 0) {
- const errorMsg = responseData.msg || '未知错误';
- const errorCode = responseData.ret;
- // 根据错误代码提供更详细的错误信息
- let detailedMessage = `飞鹅API错误: ${errorMsg} (错误代码: ${errorCode})`;
- // 常见的飞鹅API错误代码
- switch (errorCode) {
- case -1:
- detailedMessage += ' - 参数错误,请检查请求参数';
- break;
- case -2:
- detailedMessage += ' - 签名错误,请检查用户和密钥配置';
- break;
- case -3:
- detailedMessage += ' - 用户或密钥错误';
- break;
- case -4:
- detailedMessage += ' - 打印机不存在或未授权';
- break;
- case -5:
- detailedMessage += ' - 打印机离线';
- break;
- case -6:
- detailedMessage += ' - 订单号重复';
- break;
- case -7:
- detailedMessage += ' - 打印内容过长';
- break;
- case -8:
- detailedMessage += ' - 请求频率过高';
- break;
- case -9:
- detailedMessage += ' - 打印机忙';
- break;
- case -10:
- detailedMessage += ' - 打印机缺纸';
- break;
- case -11:
- detailedMessage += ' - 打印机过热';
- break;
- case -12:
- detailedMessage += ' - 打印机故障';
- break;
- default:
- detailedMessage += ' - 未知API错误';
- }
- throw new Error(detailedMessage);
- }
- return responseData;
- } catch (error) {
- lastError = error as Error;
- // 如果是 Axios 错误,提供更详细的信息
- if (axios.isAxiosError(error)) {
- const status = error.response?.status;
- const data = error.response?.data;
- let axiosErrorMsg = `HTTP错误: ${status || '未知'}`;
- if (status === 400) {
- axiosErrorMsg += ' - 请求参数错误';
- if (data && typeof data === 'object') {
- axiosErrorMsg += `, 响应: ${JSON.stringify(data)}`;
- }
- } else if (status === 401) {
- axiosErrorMsg += ' - 认证失败';
- } else if (status === 403) {
- axiosErrorMsg += ' - 权限不足';
- } else if (status === 404) {
- axiosErrorMsg += ' - API端点不存在';
- } else if (status === 429) {
- axiosErrorMsg += ' - 请求频率过高';
- } else if (status && status >= 500) {
- axiosErrorMsg += ' - 服务器内部错误';
- }
- lastError = new Error(`飞鹅API请求失败: ${axiosErrorMsg}`);
- }
- if (attempt < this.maxRetries) {
- // 等待指数退避
- const delay = Math.pow(2, attempt) * 1000;
- await new Promise(resolve => setTimeout(resolve, delay));
- continue;
- }
- }
- }
- throw lastError || new Error('飞鹅API请求失败');
- }
- /**
- * 添加打印机
- */
- async addPrinter(printerInfo: FeiePrinterInfo): Promise<FeieAddPrinterResponse> {
- const { sn, key, name = '' } = printerInfo;
- // 飞鹅API要求格式:sn#key#remark,其中remark是备注名称
- const snlist = `${sn}#${key}#${name}`;
- return this.executeRequest<FeieAddPrinterResponse>('Open_printerAddlist', {
- printerContent: snlist
- });
- }
- /**
- * 删除打印机
- */
- async deletePrinter(sn: string): Promise<FeieDeletePrinterResponse> {
- const snlist = sn;
- return this.executeRequest<FeieDeletePrinterResponse>('Open_printerDelList', {
- snlist
- });
- }
- /**
- * 查询打印机状态
- */
- async queryPrinterStatus(sn: string): Promise<FeiePrinterStatusResponse> {
- return this.executeRequest<FeiePrinterStatusResponse>('Open_queryPrinterStatus', {
- sn
- });
- }
- /**
- * 打印小票
- */
- async printReceipt(printRequest: FeiePrintRequest): Promise<FeiePrintResponse> {
- const { sn, content, times = 1 } = printRequest;
- return this.executeRequest<FeiePrintResponse>('Open_printMsg', {
- sn,
- content,
- times
- });
- }
- /**
- * 查询订单打印状态
- */
- async queryOrderStatus(orderId: string): Promise<FeieOrderStatusResponse> {
- return this.executeRequest<FeieOrderStatusResponse>('Open_queryOrderState', {
- orderid: orderId
- });
- }
- /**
- * 根据时间查询订单
- */
- async queryOrdersByDate(date: string, page: number = 1): Promise<any> {
- return this.executeRequest<any>('Open_queryOrderInfoByDate', {
- date,
- page
- });
- }
- /**
- * 批量查询打印机状态
- */
- async batchQueryPrinterStatus(snList: string[]): Promise<FeiePrinterStatusResponse> {
- const snlist = snList.join('-');
- return this.executeRequest<FeiePrinterStatusResponse>('Open_queryPrinterStatus', {
- snlist
- });
- }
- /**
- * 获取打印机在线状态
- */
- async getPrinterOnlineStatus(sn: string): Promise<boolean> {
- try {
- const response = await this.queryPrinterStatus(sn);
- if (response.data && response.data.length > 0) {
- const printerStatus = response.data[0];
- return printerStatus.online === 1;
- }
- return false;
- } catch (error) {
- console.error('获取打印机在线状态失败:', error);
- return false;
- }
- }
- /**
- * 验证打印机配置
- */
- async validatePrinterConfig(sn: string, key: string): Promise<boolean> {
- try {
- console.debug(`开始验证打印机配置,SN: ${sn}, 用户: ${this.config.user}`);
- // 首先尝试查询打印机状态,检查打印机是否已存在
- try {
- await this.executeRequest('Open_queryPrinterStatus', {
- sn
- });
- console.debug(`打印机 ${sn} 已存在,配置验证通过`);
- return true;
- } catch (queryError: any) {
- console.debug(`查询打印机状态失败:`, queryError.message);
- // 如果错误代码是-4(打印机不存在),尝试添加打印机来验证密钥
- // 或者错误代码是undefined(可能是响应格式错误),也尝试添加验证
- // 或者错误信息包含"响应格式错误",也尝试添加验证
- if (queryError.message.includes('错误代码: -4') ||
- queryError.message.includes('错误代码: undefined') ||
- queryError.message.includes('响应格式错误')) {
- console.debug(`打印机 ${sn} 不存在或响应格式错误,尝试添加验证。错误信息: ${queryError.message}`);
- try {
- // 尝试添加打印机
- const addResponse = await this.executeRequest<any>('Open_printerAddlist', {
- printerContent: `${sn}#${key}#验证配置`
- });
- console.debug(`打印机添加API响应:`, JSON.stringify(addResponse, null, 2));
- // 检查响应结果
- if (addResponse.ret === 0) {
- // ret为0表示API调用成功
- // 检查data.no数组中是否包含当前打印机
- const isInNoArray = addResponse.data?.no?.some((item: string) =>
- item.includes(sn) && item.includes(key)
- );
- if (isInNoArray) {
- // 打印机在no数组中,检查具体错误
- const errorItem = addResponse.data.no.find((item: string) =>
- item.includes(sn) && item.includes(key)
- );
- if (errorItem && errorItem.includes('已被添加过')) {
- console.debug(`打印机 ${sn} 已被添加过,配置验证通过`);
- return true; // 打印机已存在,配置正确
- } else if (errorItem && errorItem.includes('设备编号和KEY不正确')) {
- console.debug(`打印机 ${sn} 设备编号和KEY不正确,配置验证失败`);
- return false; // 配置错误
- } else {
- console.debug(`打印机 ${sn} 添加失败,未知错误: ${errorItem}`);
- return false;
- }
- } else {
- // 打印机添加成功或在ok数组中
- console.debug(`打印机 ${sn} 添加成功,配置验证通过,开始删除测试打印机`);
- // 删除刚刚添加的测试打印机
- try {
- await this.executeRequest('Open_printerDelList', {
- snlist: sn
- });
- console.debug('删除测试打印机成功');
- } catch (deleteError: any) {
- console.debug('删除测试打印机失败,但配置验证已通过:', deleteError.message);
- }
- return true;
- }
- } else {
- // ret不为0,API调用失败
- console.debug(`打印机 ${sn} 添加失败,ret: ${addResponse.ret}, msg: ${addResponse.msg}`);
- return false;
- }
- } catch (addError: any) {
- console.debug(`打印机 ${sn} 添加失败:`, addError.message);
- // 如果返回-1(参数错误)、-2(签名错误)或-3(用户或密钥错误),说明配置有问题
- // 其他错误(如格式错误等)也认为配置有问题
- const isParamError = addError.message.includes('错误代码: -1') ||
- addError.message.includes('错误代码: -2') ||
- addError.message.includes('错误代码: -3');
- // 检查是否是响应格式错误
- const isFormatError = addError.message.includes('响应格式错误') ||
- addError.message.includes('无法解析响应数据') ||
- addError.message.includes('HTML错误页面');
- // 检查是否是超时错误
- const isTimeoutError = addError.message.includes('timeout') ||
- addError.message.includes('超时') ||
- addError.message.includes('Timeout');
- const isValid = !isParamError && !isFormatError && !isTimeoutError;
- console.debug(`配置验证结果: ${isValid ? '通过' : '失败'}, 参数错误: ${isParamError}, 格式错误: ${isFormatError}, 超时错误: ${isTimeoutError}`);
- // 如果是超时错误,可能是网络问题,建议用户检查网络连接
- if (isTimeoutError) {
- console.debug('飞鹅API请求超时,请检查网络连接或联系管理员检查飞鹅API服务状态');
- }
- return isValid;
- }
- }
- // 其他错误情况
- console.debug(`查询打印机状态失败:`, queryError.message);
- return false;
- }
- } catch (error) {
- console.error('验证打印机配置失败:', error);
- if (error instanceof Error) {
- console.error('错误详情:', error.message);
- }
- return false;
- }
- }
- }
|