493 lines
11 KiB
JavaScript
493 lines
11 KiB
JavaScript
/**
|
||
* 积分系统服务
|
||
* 管理用户积分账户、交易、奖励等
|
||
*/
|
||
|
||
// ==================== 常量配置 ====================
|
||
|
||
export const CREDIT_CONFIG = {
|
||
INITIAL_BALANCE: 10000, // 初始积分
|
||
MIN_BALANCE: 100, // 最低保留余额(破产保护)
|
||
MAX_SINGLE_BET: 1000, // 单次下注上限
|
||
DAILY_BONUS: 100, // 每日签到奖励
|
||
CREATE_TOPIC_COST: 100, // 创建话题费用
|
||
};
|
||
|
||
// 积分账户存储(生产环境应使用数据库)
|
||
const userAccounts = new Map();
|
||
|
||
// 交易记录存储
|
||
const transactions = [];
|
||
|
||
// ==================== 账户管理 ====================
|
||
|
||
/**
|
||
* 获取用户账户
|
||
* @param {string} userId - 用户ID
|
||
* @returns {Object} 用户账户信息
|
||
*/
|
||
export const getUserAccount = (userId) => {
|
||
if (!userAccounts.has(userId)) {
|
||
// 首次访问,创建新账户
|
||
const newAccount = {
|
||
user_id: userId,
|
||
balance: CREDIT_CONFIG.INITIAL_BALANCE,
|
||
frozen: 0,
|
||
total: CREDIT_CONFIG.INITIAL_BALANCE,
|
||
total_earned: CREDIT_CONFIG.INITIAL_BALANCE,
|
||
total_spent: 0,
|
||
total_profit: 0,
|
||
active_positions: [],
|
||
stats: {
|
||
total_topics: 0,
|
||
win_count: 0,
|
||
loss_count: 0,
|
||
win_rate: 0,
|
||
best_profit: 0,
|
||
},
|
||
last_daily_bonus: null,
|
||
};
|
||
userAccounts.set(userId, newAccount);
|
||
}
|
||
|
||
return userAccounts.get(userId);
|
||
};
|
||
|
||
/**
|
||
* 更新用户账户
|
||
* @param {string} userId - 用户ID
|
||
* @param {Object} updates - 更新内容
|
||
*/
|
||
export const updateUserAccount = (userId, updates) => {
|
||
const account = getUserAccount(userId);
|
||
const updated = { ...account, ...updates };
|
||
userAccounts.set(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 获取用户积分余额
|
||
* @param {string} userId - 用户ID
|
||
* @returns {number} 可用余额
|
||
*/
|
||
export const getBalance = (userId) => {
|
||
const account = getUserAccount(userId);
|
||
return account.balance;
|
||
};
|
||
|
||
/**
|
||
* 检查用户是否能支付
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} amount - 金额
|
||
* @returns {boolean} 是否能支付
|
||
*/
|
||
export const canAfford = (userId, amount) => {
|
||
const account = getUserAccount(userId);
|
||
const afterBalance = account.balance - amount;
|
||
|
||
// 必须保留最低余额
|
||
return afterBalance >= CREDIT_CONFIG.MIN_BALANCE;
|
||
};
|
||
|
||
// ==================== 积分操作 ====================
|
||
|
||
/**
|
||
* 增加积分
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} amount - 金额
|
||
* @param {string} reason - 原因
|
||
*/
|
||
export const addCredits = (userId, amount, reason = '系统增加') => {
|
||
const account = getUserAccount(userId);
|
||
|
||
const updated = {
|
||
balance: account.balance + amount,
|
||
total: account.total + amount,
|
||
total_earned: account.total_earned + amount,
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
|
||
// 记录交易
|
||
logTransaction({
|
||
user_id: userId,
|
||
type: 'earn',
|
||
amount,
|
||
reason,
|
||
balance_after: updated.balance,
|
||
});
|
||
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 扣除积分
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} amount - 金额
|
||
* @param {string} reason - 原因
|
||
* @throws {Error} 如果余额不足
|
||
*/
|
||
export const deductCredits = (userId, amount, reason = '系统扣除') => {
|
||
if (!canAfford(userId, amount)) {
|
||
throw new Error(`积分不足,需要${amount}积分,但只有${getBalance(userId)}积分`);
|
||
}
|
||
|
||
const account = getUserAccount(userId);
|
||
|
||
const updated = {
|
||
balance: account.balance - amount,
|
||
total_spent: account.total_spent + amount,
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
|
||
// 记录交易
|
||
logTransaction({
|
||
user_id: userId,
|
||
type: 'spend',
|
||
amount: -amount,
|
||
reason,
|
||
balance_after: updated.balance,
|
||
});
|
||
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 冻结积分(席位占用)
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} amount - 金额
|
||
*/
|
||
export const freezeCredits = (userId, amount) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
if (account.balance < amount) {
|
||
throw new Error('可用余额不足');
|
||
}
|
||
|
||
const updated = {
|
||
balance: account.balance - amount,
|
||
frozen: account.frozen + amount,
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 解冻积分
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} amount - 金额
|
||
*/
|
||
export const unfreezeCredits = (userId, amount) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
const updated = {
|
||
balance: account.balance + amount,
|
||
frozen: account.frozen - amount,
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
// ==================== 每日奖励 ====================
|
||
|
||
/**
|
||
* 领取每日签到奖励
|
||
* @param {string} userId - 用户ID
|
||
* @returns {Object} 奖励信息
|
||
*/
|
||
export const claimDailyBonus = (userId) => {
|
||
const account = getUserAccount(userId);
|
||
const today = new Date().toDateString();
|
||
|
||
// 检查是否已领取
|
||
if (account.last_daily_bonus === today) {
|
||
return {
|
||
success: false,
|
||
message: '今日已领取',
|
||
};
|
||
}
|
||
|
||
// 发放奖励
|
||
addCredits(userId, CREDIT_CONFIG.DAILY_BONUS, '每日签到');
|
||
|
||
// 更新领取时间
|
||
updateUserAccount(userId, { last_daily_bonus: today });
|
||
|
||
return {
|
||
success: true,
|
||
amount: CREDIT_CONFIG.DAILY_BONUS,
|
||
message: `获得${CREDIT_CONFIG.DAILY_BONUS}积分`,
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 检查今天是否已签到
|
||
* @param {string} userId - 用户ID
|
||
* @returns {boolean}
|
||
*/
|
||
export const hasClaimedToday = (userId) => {
|
||
const account = getUserAccount(userId);
|
||
const today = new Date().toDateString();
|
||
return account.last_daily_bonus === today;
|
||
};
|
||
|
||
// ==================== 持仓管理 ====================
|
||
|
||
/**
|
||
* 添加持仓
|
||
* @param {string} userId - 用户ID
|
||
* @param {Object} position - 持仓信息
|
||
*/
|
||
export const addPosition = (userId, position) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
const updated = {
|
||
active_positions: [...account.active_positions, position],
|
||
stats: {
|
||
...account.stats,
|
||
total_topics: account.stats.total_topics + 1,
|
||
},
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 移除持仓
|
||
* @param {string} userId - 用户ID
|
||
* @param {string} positionId - 持仓ID
|
||
*/
|
||
export const removePosition = (userId, positionId) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
const updated = {
|
||
active_positions: account.active_positions.filter((p) => p.id !== positionId),
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 更新持仓
|
||
* @param {string} userId - 用户ID
|
||
* @param {string} positionId - 持仓ID
|
||
* @param {Object} updates - 更新内容
|
||
*/
|
||
export const updatePosition = (userId, positionId, updates) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
const updated = {
|
||
active_positions: account.active_positions.map((p) =>
|
||
p.id === positionId ? { ...p, ...updates } : p
|
||
),
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 获取用户持仓
|
||
* @param {string} userId - 用户ID
|
||
* @param {string} topicId - 话题ID(可选)
|
||
* @returns {Array} 持仓列表
|
||
*/
|
||
export const getUserPositions = (userId, topicId = null) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
if (topicId) {
|
||
return account.active_positions.filter((p) => p.topic_id === topicId);
|
||
}
|
||
|
||
return account.active_positions;
|
||
};
|
||
|
||
// ==================== 统计更新 ====================
|
||
|
||
/**
|
||
* 记录胜利
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} profit - 盈利金额
|
||
*/
|
||
export const recordWin = (userId, profit) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
const newWinCount = account.stats.win_count + 1;
|
||
const totalGames = newWinCount + account.stats.loss_count;
|
||
const winRate = (newWinCount / totalGames) * 100;
|
||
|
||
const updated = {
|
||
total_profit: account.total_profit + profit,
|
||
stats: {
|
||
...account.stats,
|
||
win_count: newWinCount,
|
||
win_rate: winRate,
|
||
best_profit: Math.max(account.stats.best_profit, profit),
|
||
},
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
/**
|
||
* 记录失败
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} loss - 损失金额
|
||
*/
|
||
export const recordLoss = (userId, loss) => {
|
||
const account = getUserAccount(userId);
|
||
|
||
const newLossCount = account.stats.loss_count + 1;
|
||
const totalGames = account.stats.win_count + newLossCount;
|
||
const winRate = (account.stats.win_count / totalGames) * 100;
|
||
|
||
const updated = {
|
||
total_profit: account.total_profit - loss,
|
||
stats: {
|
||
...account.stats,
|
||
loss_count: newLossCount,
|
||
win_rate: winRate,
|
||
},
|
||
};
|
||
|
||
updateUserAccount(userId, updated);
|
||
return updated;
|
||
};
|
||
|
||
// ==================== 排行榜 ====================
|
||
|
||
/**
|
||
* 获取积分排行榜
|
||
* @param {number} limit - 返回数量
|
||
* @returns {Array} 排行榜数据
|
||
*/
|
||
export const getLeaderboard = (limit = 100) => {
|
||
const accounts = Array.from(userAccounts.values());
|
||
|
||
return accounts
|
||
.sort((a, b) => b.total - a.total)
|
||
.slice(0, limit)
|
||
.map((account, index) => ({
|
||
rank: index + 1,
|
||
user_id: account.user_id,
|
||
total: account.total,
|
||
total_profit: account.total_profit,
|
||
win_rate: account.stats.win_rate,
|
||
}));
|
||
};
|
||
|
||
/**
|
||
* 获取用户排名
|
||
* @param {string} userId - 用户ID
|
||
* @returns {number} 排名
|
||
*/
|
||
export const getUserRank = (userId) => {
|
||
const leaderboard = getLeaderboard(1000);
|
||
const index = leaderboard.findIndex((item) => item.user_id === userId);
|
||
return index >= 0 ? index + 1 : -1;
|
||
};
|
||
|
||
// ==================== 交易记录 ====================
|
||
|
||
/**
|
||
* 记录交易
|
||
* @param {Object} transaction - 交易信息
|
||
*/
|
||
const logTransaction = (transaction) => {
|
||
const record = {
|
||
id: `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||
timestamp: new Date().toISOString(),
|
||
...transaction,
|
||
};
|
||
|
||
transactions.push(record);
|
||
return record;
|
||
};
|
||
|
||
/**
|
||
* 获取用户交易记录
|
||
* @param {string} userId - 用户ID
|
||
* @param {number} limit - 返回数量
|
||
* @returns {Array} 交易记录
|
||
*/
|
||
export const getUserTransactions = (userId, limit = 50) => {
|
||
return transactions
|
||
.filter((tx) => tx.user_id === userId)
|
||
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
|
||
.slice(0, limit);
|
||
};
|
||
|
||
// ==================== 批量操作 ====================
|
||
|
||
/**
|
||
* 批量发放积分(如活动奖励)
|
||
* @param {Array} recipients - [{user_id, amount, reason}]
|
||
*/
|
||
export const batchAddCredits = (recipients) => {
|
||
const results = recipients.map(({ user_id, amount, reason }) => {
|
||
try {
|
||
return {
|
||
user_id,
|
||
success: true,
|
||
account: addCredits(user_id, amount, reason),
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
user_id,
|
||
success: false,
|
||
error: error.message,
|
||
};
|
||
}
|
||
});
|
||
|
||
return results;
|
||
};
|
||
|
||
// ==================== 导出所有功能 ====================
|
||
|
||
export default {
|
||
CREDIT_CONFIG,
|
||
|
||
// 账户管理
|
||
getUserAccount,
|
||
updateUserAccount,
|
||
getBalance,
|
||
canAfford,
|
||
|
||
// 积分操作
|
||
addCredits,
|
||
deductCredits,
|
||
freezeCredits,
|
||
unfreezeCredits,
|
||
|
||
// 每日奖励
|
||
claimDailyBonus,
|
||
hasClaimedToday,
|
||
|
||
// 持仓管理
|
||
addPosition,
|
||
removePosition,
|
||
updatePosition,
|
||
getUserPositions,
|
||
|
||
// 统计更新
|
||
recordWin,
|
||
recordLoss,
|
||
|
||
// 排行榜
|
||
getLeaderboard,
|
||
getUserRank,
|
||
|
||
// 交易记录
|
||
getUserTransactions,
|
||
|
||
// 批量操作
|
||
batchAddCredits,
|
||
};
|