92 lines
3.4 KiB
TypeScript
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;
|