UNPKG

dqm-api-client

Version:

A Node.js client library for court booking API services including authentication, SMS verification, and court management

275 lines (249 loc) 7.4 kB
const { encodeOpenId, getOrderParams } = require('../utils/common'); const { get, post, put } = require('../utils/request'); const AppError = require('../errors/AppError'); const { ERROR_CODES } = require('../constants/errorCodes'); class ApiService { #baseURL; #openid; #org; #token; #memberId; #init; constructor(openid) { this.#baseURL = 'https://fdsaas.hulasports.com/api/'; this.#openid = openid; this.#org = '67623479acb2a720bd8e21bc'; // 默认或初始组织ID // 实例状态 this.#token = ''; this.#memberId = ''; this.#init = false; // 标记是否已认证 } /** * 动态生成请求头,确保信息总是最新的 * @private */ _getHeaders() { const headers = { openid: encodeOpenId(this.#openid), appId: 'wx85217acaed0bb9af', _org: this.#org, // 总是包含组织ID }; // 只有在获取到 token 和 memberId 后才添加到请求头 if (this.#token) { headers.token = this.#token; } if (this.#memberId) { headers.memberId = this.#memberId; } return headers; } /** * 统一处理认证成功后的状态更新 * @private * @param {object} authData - 包含 member 信息的响应数据 */ _setAuthenticated(authData) { if (authData && authData.member) { this.#memberId = authData.member._id; this.#token = authData.member.token; // 如果API返回了新的组织ID,则更新 if (authData.member._organization) { this.#org = authData.member._organization; } this.#init = true; console.log('认证成功,状态已更新。'); } else { console.error('认证数据格式不正确', authData); throw new AppError('Invalid authentication data format', { name: 'AuthenticationDataError', code: ERROR_CODES.INVALID_AUTH_DATA, data: { authData }, isOperational: false }); } } /** * 在需要认证的API调用前执行此检查 * @private */ async _ensureAuthenticated() { if (!this.#init) { try { await this.getMemberInfo(); } catch (error) { console.error('获取会员信息时发生错误:', error); throw new AppError('Failed to authenticate user', { name: 'AuthenticationError', code: ERROR_CODES.AUTH_FAILED, data: { originalError: error.message }, isOperational: true }); } } } /** * 使用 openid 获取会员信息并完成登录 */ async getMemberInfo() { const url = `${this.#baseURL}members/wx/${this.#openid}?_org=${this.#org}&state=xiaochengxu`; const response = await get(url, this._getHeaders()); const { code, data } = response.data; if (code === 200) { this._setAuthenticated(data); return data; } else { throw new AppError('Failed to get member info', { name: 'MemberInfoError', code: ERROR_CODES.MEMBER_INFO_FAILED, data: { code, responseData: data }, isOperational: true }); } } /** * 使用手机号和验证码登录/绑定手机 * @param {string} tel - 手机号 * @param {string} smsCode - 短信验证码 * @returns {Object} 登录成功后的会员信息 */ async login(tel, smsCode) { const url = this.#baseURL + 'members/tel/bind'; const body = { state: 'xiaochengxu', tel: tel, code: smsCode, _org: this.#org, wxid: this.#openid, }; console.log('登录参数:', body); // login 方法本身不需要 token,所以直接使用当前生成的 headers const response = await put(url, body, this._getHeaders()); const { code, data } = response.data; if (code === 200) { console.log('登录成功:', data); this._setAuthenticated(data); return data; } else { console.log('登录失败响应:', response.data); throw new AppError('Login failed', { name: 'LoginError', code: ERROR_CODES.LOGIN_FAILED, data: { code, responseData: data, tel }, isOperational: true }); } } /** * 发送验证码(无需登录) * @param {string} tel - 手机号 * @param {string} action - 'login' 或其他 */ async sendSmsCode(tel, action) { const url = this.#baseURL + 'smscode'; let body = { tel: tel, _org: this.#org, // 使用初始的组织ID }; if (action === 'login') { body.state = 'login'; } else { body._venue = '676a26539f1ee424e50fa0af'; // 根据需要添加 } // 此方法不需要token或memberId const response = await post(url, body, this._getHeaders()); const { code, data } = response.data; if (code !== 200) { throw new AppError('Failed to send SMS code', { name: 'SmsCodeError', code: ERROR_CODES.SMS_CODE_FAILED, data: { code, responseData: data, tel, action }, isOperational: true }); } console.log('发送验证码成功:', data); return data; } /** * 根据日期查询场地(需要登录) * @param {number} orderDateNum */ async getCourtByDate(orderDateNum) { await this._ensureAuthenticated(); // 检查是否已登录 const url = this.#baseURL + 'orderlists/get/book'; const body = { _org: this.#org, _member: this.#memberId, orderDateNum: orderDateNum, _venue: '676a26539f1ee424e50fa0af', _item: '676a24637fa48934627cb2f6', passBaseOn: 'start', showLine: 'row', showPassTime: false, delayMins: 0, }; // console.log('查询场地参数:', body); const response = await post(url, body, this._getHeaders()); const { code, data } = response.data; if (code !== 200) { throw new AppError('Failed to query court data', { name: 'CourtQueryError', code: ERROR_CODES.COURT_QUERY_FAILED, data: { code, responseData: data, orderDateNum }, isOperational: true }); } return data; } /** * 创建订单(需要登录) * @param {object} body */ async createOrder(orderDate, orderArr, smscode = '') { await this._ensureAuthenticated(); // 检查是否已登录 const orderParam = getOrderParams( this.#org, this.#memberId, orderDate, orderArr, this.#openid, smscode ); const url = this.#baseURL + 'wxpay/order'; const response = await post(url, orderParam, this._getHeaders()); const { code, data } = response.data; if (code !== 200) { throw new AppError('Failed to create order', { name: 'OrderCreationError', code: ERROR_CODES.ORDER_CREATION_FAILED, data: { code, responseData: data, orderDate, orderArrLength: orderArr.length }, isOperational: true }); } console.log('创建订单成功:', data); return data; } // Getter 方法 - 提供对私有变量的受控访问 get baseURL() { return this.#baseURL; } get org() { return this.#org; } get token() { return this.#token; } get memberId() { return this.#memberId; } get openid() { return this.#openid; } get isAuthenticated() { return this.#init; } // Setter 方法 - 允许外部更新组织ID(如果需要) set org(newOrg) { this.#org = newOrg; } } module.exports = ApiService;