Files
18xchat/assets/Scripts/chat18x/payment/PaymentPoller.ts
T
2025-09-21 20:18:30 +08:00

92 lines
3.4 KiB
TypeScript

/**
* 轮询器(指数退避+超时+可取消)
*
*/
import { PaymentApi } from "./PaymentApi";
import proto from 'db://assets/Scripts/proto/proto.pb.js';
import { logger } from "db://assets/Scripts/Main/Common/Logger";
export interface PollOptions {
maxDurationMs: number; // 最大轮询时长(超过就自动退出)(例如 2分钟)
startIntervalMs: number; // 第一次轮询等待的间隔(例如 2s)
maxIntervalMs: number; // 最大允许的间隔,防止指数增长过大(例如 10s)
jitter?: boolean; // 是否启用抖动(让等待时间带点随机性,避免所有客户端同一时间请求服务器)
}
export class PaymentPoller {
private cancelled = false;
private currentPollCount = 0; // 轮询次数
// 取消轮询
cancel() {
this.cancelled = true;
}
// 获取当前轮询次数
public getCurrentPollCount(): number {
return this.currentPollCount;
}
/**
* 轮询订单
* @param orderId 要查询的订单 ID
* @param opt 轮询配置(间隔、最大时长、是否抖动)
* @param onTick 可选回调函数,每次轮询到结果时调用
* @returns 最终查询到的订单结果 ICSQueryOrderRes,或者 null(超时/被取消)
*/
async poll(orderId: string, opt: PollOptions, onTick?: (d: proto.cs.ICSQueryOrderRes) => void): Promise<proto.cs.ICSQueryOrderRes> {
const t0 = Date.now();
let interval = opt.startIntervalMs;
while (!this.cancelled) {
// 增加轮询次数
this.currentPollCount++;
// 查询
let reqData = {orderId: orderId};
logger.log("订单:", orderId, " ,第 ", this.currentPollCount, " 次轮询订单的请求数据:", reqData);
const r = await PaymentApi.I.queryOrder(reqData);
logger.log("订单:", orderId, " ,第 ", this.currentPollCount, " 次轮询订单的响应数据:", r);
if (r.code === proto.cs.EnmRetCode.SUCCESS && r.data) {
// 回调
onTick?.(r.data);
// 订单状态,-1:失败,0:成功,1:处理中
if ([-1, 0].indexOf(r.data.status) !== -1) {
return r.data;
}
}
// 轮询超时
if (Date.now() - t0 > opt.maxDurationMs) {
let resultData: proto.cs.ICSQueryOrderRes = {
status: 92, // 自定义的状态,代表客户端轮询超时
retCode: 0,
balance: 0,
vipExpire: 0,
}
return resultData;
}
// 等待
await this.sleep(this.jitter(interval, opt));
// 每次轮询后,interval 会按照 1.5 倍增长,直到达到最大间隔 maxIntervalMs,避免在短时间内重复请求
interval = Math.min(interval * 1.5, opt.maxIntervalMs);
}
// 至此,代表取消了轮询订单
let resultData: proto.cs.ICSQueryOrderRes = {
status: 90, // 自定义的状态,代表取消
retCode: 0,
balance: 0,
vipExpire: 0,
}
return resultData;
}
// 用来暂停一定的时间(ms 毫秒),并让轮询进入等待状态
private sleep(ms: number) { return new Promise(res => setTimeout(res, ms)); }
// 用于实现抖动(随机化轮询间隔)
private jitter(base: number, opt: PollOptions) {
if (!opt.jitter) return base;
const delta = Math.min(500, Math.max(100, base * 0.1));
return base + (Math.random() * 2 - 1) * delta;
}
}
export default PaymentPoller;