Files
YGChatCS/src/utils/index.js
duanshuwen 34711330bd feat(utils, payment): add execution lock utils and prevent duplicate payment requests
Add two new throttle utility methods: createAsyncThrottle and createExecutionLock for safe async operation locking. Update the existing throttle method's comment to clarify traditional timestamp usage. Replace DebounceUtils usage with ThrottleUtils.createExecutionLock in order footer and booking page payment handlers to prevent duplicate payments. Rewrite uni.requestPayment calls to use Promise wrapping for proper async handling, and add try/catch/finally blocks to ensure loading state is always cleaned up correctly. Fix template formatting and trailing commas across modified files.
2026-07-23 21:10:34 +08:00

420 lines
10 KiB
JavaScript

/**
* 工具函数集合
* 包含打字机效果、ID生成、回调安全调用等通用工具函数
*/
/**
* ID生成工具类
* 提供各种ID生成功能
*/
export class IdUtils {
/**
* 生成消息ID
* @returns {string} 消息ID
*/
static generateMessageId() {
const timestamp = new Date().getTime();
const chars = "abcdefghijklmnopqrstuvwxyz";
const randomStr = Array.from({ length: 4 }, () =>
chars.charAt(Math.floor(Math.random() * chars.length)),
).join("");
return "mid" + randomStr + timestamp;
}
}
/**
* 回调函数安全调用工具类
* 提供安全的回调函数调用机制
*/
export class CallbackUtils {
/**
* 安全调用回调函数
* @param {Object} callbacks - 回调函数对象
* @param {string} callbackName - 回调函数名称
* @param {...any} args - 传递给回调函数的参数
*/
static safeCall(callbacks, callbackName, ...args) {
if (callbacks && typeof callbacks[callbackName] === "function") {
try {
callbacks[callbackName](...args);
} catch (error) {
console.error(`回调函数 ${callbackName} 执行出错:`, error);
}
} else {
console.warn(`回调函数 ${callbackName} 不可用`);
}
}
/**
* 批量安全调用回调函数
* @param {Object} callbacks - 回调函数对象
* @param {Array} callbackConfigs - 回调配置数组 [{name, args}, ...]
*/
static safeBatchCall(callbacks, callbackConfigs) {
callbackConfigs.forEach((config) => {
const { name, args = [] } = config;
this.safeCall(callbacks, name, ...args);
});
}
}
/**
* 消息处理工具类
* 提供消息相关的工具函数
*/
export class MessageUtils {
/**
* 验证消息格式
* @param {any} message - 消息对象
* @returns {boolean} 是否为有效消息格式
*/
static validateMessage(message) {
return message && typeof message === "object" && message.type;
}
/**
* 格式化消息
* @param {string} type - 消息类型
* @param {any} content - 消息内容
* @param {Object} options - 额外选项
* @returns {Object} 格式化后的消息对象
*/
static formatMessage(type, content, options = {}) {
return {
type,
content,
timestamp: Date.now(),
...options,
};
}
/**
* 检查是否为完整消息
* @param {any} message - 消息对象
* @returns {boolean} 是否为完整消息
*/
static isCompleteMessage(message) {
return message && message.isComplete === true;
}
/**
* 检查消息是否为心跳响应
* @param {any} messageData - 消息数据
* @returns {boolean} 是否为心跳响应
*/
static isPongMessage(messageData) {
if (typeof messageData === "string") {
return (
messageData === "pong" || messageData.toLowerCase().includes("pong")
);
}
if (typeof messageData === "object" && messageData !== null) {
return messageData.type === "pong";
}
return false;
}
/**
* 安全解析JSON消息
* @param {string} messageStr - 消息字符串
* @returns {Object|null} 解析后的对象或null
*/
static safeParseJSON(messageStr) {
try {
return JSON.parse(messageStr);
} catch (error) {
console.warn("JSON解析失败:", messageStr);
return null;
}
}
/**
* 创建打字机消息对象
* @param {string} content - 消息内容
* @param {boolean} isComplete - 是否完成
* @param {string} type - 消息类型
* @returns {Object} 消息对象
*/
static createTypewriterMessage(
content,
isComplete = false,
type = "typewriter",
) {
return {
type,
content,
isComplete,
timestamp: Date.now(),
};
}
/**
* 创建加载消息对象
* @param {string} content - 加载内容
* @returns {Object} 加载消息对象
*/
static createLoadingMessage(content = "加载中...") {
return {
type: "loading",
content,
timestamp: Date.now(),
};
}
/**
* 创建错误消息对象
* @param {string} error - 错误信息
* @returns {Object} 错误消息对象
*/
static createErrorMessage(error) {
return {
type: "error",
content: error.message || error || "未知错误",
timestamp: Date.now(),
};
}
}
/**
* 定时器管理工具类
* 提供定时器的统一管理
*/
export class TimerUtils {
/**
* 安全清除定时器
* @param {number|null} timerId - 定时器ID
* @param {string} type - 定时器类型 'timeout' | 'interval'
* @returns {null} 返回null便于重置变量
*/
static safeClear(timerId, type = "timeout") {
if (timerId) {
if (type === "interval") {
clearInterval(timerId);
} else {
clearTimeout(timerId);
}
}
return null;
}
/**
* 清除定时器(别名方法)
* @param {number|null} timerId - 定时器ID
* @param {string} type - 定时器类型 'timeout' | 'interval'
* @returns {null} 返回null便于重置变量
*/
static clearTimer(timerId, type = "timeout") {
return this.safeClear(timerId, type);
}
/**
* 创建可取消的延时执行
* @param {Function} callback - 回调函数
* @param {number} delay - 延时时间
* @returns {Object} 包含cancel方法的对象
*/
static createCancelableTimeout(callback, delay) {
let timerId = setTimeout(callback, delay);
return {
cancel() {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
},
isActive() {
return timerId !== null;
},
};
}
/**
* 创建可取消的定时执行
* @param {Function} callback - 回调函数
* @param {number} interval - 间隔时间
* @returns {Object} 包含cancel方法的对象
*/
static createCancelableInterval(callback, interval) {
let timerId = setInterval(callback, interval);
return {
cancel() {
if (timerId) {
clearInterval(timerId);
timerId = null;
}
},
isActive() {
return timerId !== null;
},
};
}
}
/**
* 防抖工具类
* 提供防抖功能,防止函数在短时间内被重复调用
*/
export class DebounceUtils {
/**
* 创建防抖函数
* @param {Function} func - 要防抖的函数
* @param {number} delay - 防抖延迟时间
* @returns {Function} 防抖后的函数
*/
static createDebounce(func, delay) {
let timerId = null;
return function (...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => func.apply(this, args), delay);
};
}
}
/**
* 节流工具类
* 提供节流功能,防止函数在短时间内被重复调用
*/
export class ThrottleUtils {
/**
* 创建节流函数(传统时间戳方式)
* @param {Function} func - 要节流的函数
* @param {number} delay - 节流延迟时间
* @returns {Function} 节流后的函数
*/
static createThrottle(func, delay) {
let prev = Date.now();
return function (...args) {
let now = Date.now();
if (now - prev >= delay) {
func.apply(this, args);
prev = now;
}
};
}
/**
* 创建异步安全的节流函数(带执行状态锁)
* 适用于支付等需要等待完成的异步操作
* @param {Function} func - 要节流的异步函数
* @param {number} delay - 节流延迟时间(毫秒)
* @returns {Function} 节流后的函数
*/
static createAsyncThrottle(func, delay = 1000) {
let isExecuting = false; // 执行状态锁
let lastExecTime = 0; // 上次执行时间
return async function (...args) {
const now = Date.now();
// 如果正在执行中,直接返回
if (isExecuting) {
console.warn("函数正在执行中,跳过此次调用");
return;
}
// 如果还在节流时间内,直接返回
if (now - lastExecTime < delay) {
console.warn("节流时间内,跳过此次调用");
return;
}
// 加锁
isExecuting = true;
lastExecTime = now;
try {
// 执行异步函数
return await func.apply(this, args);
} catch (error) {
console.error("异步节流函数执行出错:", error);
throw error;
} finally {
// 无论成功失败都解锁
isExecuting = false;
}
};
}
/**
* 创建执行锁函数(最简单的防重复方式,只锁执行状态)
* 适用于支付等场景,上一次没完成前绝对不执行第二次
* @param {Function} func - 要加锁的函数
* @returns {Function} 加锁后的函数
*/
static createExecutionLock(func) {
let isExecuting = false;
return async function (...args) {
// 如果正在执行中,直接返回
if (isExecuting) {
console.warn("函数正在执行中,跳过此次调用");
return;
}
// 加锁
isExecuting = true;
try {
// 执行函数
return await func.apply(this, args);
} catch (error) {
console.error("执行锁函数执行出错:", error);
throw error;
} finally {
// 无论成功失败都解锁
isExecuting = false;
}
};
}
}
/**
* 日期工具类
* 提供日期格式化功能
* */
export class DateUtils {
/**
* 格式化日期为指定格式
* @param {Date} date - 日期对象
* @param {string} format - 格式化字符串,默认值为 "yyyy-MM-dd"
* @returns {string} 格式化后的日期字符串
*/
static formatDate(date = new Date(), format = "yyyy-MM-dd") {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return format.replace("yyyy", year).replace("MM", month).replace("dd", day);
}
}
/**
* 验证手机号工具类
* @param {string} phone - 手机号字符串
* @returns {boolean} 是否为有效手机号
*/
export class PhoneUtils {
static validatePhone(phone) {
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(phone);
}
}
// 默认导出所有工具类
export default {
IdUtils,
CallbackUtils,
MessageUtils,
TimerUtils,
DateUtils,
DebounceUtils,
ThrottleUtils,
PhoneUtils,
};