|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { getUserById, updateUserPoints, logEvent } from './database.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const POINTS_CONFIG = { |
|
|
|
|
|
FREE: { |
|
|
YEARLY_FORTUNE: 0, |
|
|
MONTHLY_FORTUNE: 0, |
|
|
YEARLY_KLINE: 0, |
|
|
BASIC_DAILY_FORTUNE: 0, |
|
|
FULL_ANALYSIS_GUEST: 0, |
|
|
}, |
|
|
|
|
|
|
|
|
PAID: { |
|
|
DAILY_FORTUNE_AI: 20, |
|
|
DAILY_KLINE: 20, |
|
|
MONTHLY_KLINE: 30, |
|
|
FULL_ANALYSIS: 50, |
|
|
}, |
|
|
|
|
|
|
|
|
REWARDS: { |
|
|
SHARE_REWARD: 300, |
|
|
INITIAL_POINTS: 1000, |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const getFeatureCost = (featureKey) => { |
|
|
if (POINTS_CONFIG.PAID[featureKey] !== undefined) { |
|
|
return POINTS_CONFIG.PAID[featureKey]; |
|
|
} |
|
|
if (POINTS_CONFIG.FREE[featureKey] !== undefined) { |
|
|
return POINTS_CONFIG.FREE[featureKey]; |
|
|
} |
|
|
return 0; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const checkUserPoints = (userId, featureKey) => { |
|
|
const user = getUserById(userId); |
|
|
if (!user) { |
|
|
return { sufficient: false, required: getFeatureCost(featureKey), current: 0, error: 'USER_NOT_FOUND' }; |
|
|
} |
|
|
|
|
|
const cost = getFeatureCost(featureKey); |
|
|
return { |
|
|
sufficient: user.points >= cost, |
|
|
required: cost, |
|
|
current: user.points, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const deductUserPoints = (userId, featureKey, ipAddress = null) => { |
|
|
const user = getUserById(userId); |
|
|
if (!user) { |
|
|
return { success: false, error: 'USER_NOT_FOUND' }; |
|
|
} |
|
|
|
|
|
const cost = getFeatureCost(featureKey); |
|
|
if (cost === 0) { |
|
|
return { success: true, newPoints: user.points, cost: 0 }; |
|
|
} |
|
|
|
|
|
if (user.points < cost) { |
|
|
return { success: false, error: 'INSUFFICIENT_POINTS', required: cost, current: user.points }; |
|
|
} |
|
|
|
|
|
const newPoints = Math.max(0, user.points - cost); |
|
|
updateUserPoints(userId, newPoints); |
|
|
|
|
|
|
|
|
try { |
|
|
logEvent('info', '积分消耗', { |
|
|
feature: featureKey, |
|
|
cost, |
|
|
before: user.points, |
|
|
after: newPoints, |
|
|
}, userId, ipAddress); |
|
|
} catch (e) { |
|
|
|
|
|
console.warn('积分消耗日志记录失败:', e.message); |
|
|
} |
|
|
|
|
|
return { success: true, newPoints, cost }; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const requirePoints = (featureKey) => { |
|
|
return (req, res, next) => { |
|
|
const cost = getFeatureCost(featureKey); |
|
|
|
|
|
|
|
|
if (cost === 0) { |
|
|
req.pointsInfo = { cost: 0, isFree: true }; |
|
|
return next(); |
|
|
} |
|
|
|
|
|
|
|
|
if (!req.auth || !req.auth.sub) { |
|
|
return res.status(401).json({ |
|
|
error: 'AUTH_REQUIRED', |
|
|
message: '此功能需要登录', |
|
|
feature: featureKey, |
|
|
cost, |
|
|
}); |
|
|
} |
|
|
|
|
|
const userId = req.auth.sub; |
|
|
const user = getUserById(userId); |
|
|
|
|
|
if (!user) { |
|
|
return res.status(401).json({ |
|
|
error: 'USER_NOT_FOUND', |
|
|
message: '用户不存在', |
|
|
}); |
|
|
} |
|
|
|
|
|
if (user.points < cost) { |
|
|
return res.status(402).json({ |
|
|
error: 'INSUFFICIENT_POINTS', |
|
|
message: '积分不足', |
|
|
required: cost, |
|
|
current: user.points, |
|
|
feature: featureKey, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
req.pointsInfo = { |
|
|
cost, |
|
|
userId, |
|
|
currentPoints: user.points, |
|
|
feature: featureKey, |
|
|
isFree: false, |
|
|
}; |
|
|
|
|
|
next(); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const executePointsDeduction = (req) => { |
|
|
if (!req.pointsInfo || req.pointsInfo.isFree || req.pointsInfo.cost === 0) { |
|
|
return { success: true, newPoints: req.pointsInfo?.currentPoints, cost: 0 }; |
|
|
} |
|
|
|
|
|
const { userId, cost, feature, currentPoints } = req.pointsInfo; |
|
|
const newPoints = Math.max(0, currentPoints - cost); |
|
|
|
|
|
updateUserPoints(userId, newPoints); |
|
|
|
|
|
|
|
|
try { |
|
|
logEvent('info', '积分消耗', { |
|
|
feature, |
|
|
cost, |
|
|
before: currentPoints, |
|
|
after: newPoints, |
|
|
}, userId, req.ip); |
|
|
} catch (e) { |
|
|
console.warn('积分消耗日志记录失败:', e.message); |
|
|
} |
|
|
|
|
|
return { success: true, newPoints, cost }; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const getFeatureDisplayName = (featureKey) => { |
|
|
const names = { |
|
|
DAILY_FORTUNE_AI: '每日运势AI增强版', |
|
|
DAILY_KLINE: '日K线分析', |
|
|
MONTHLY_KLINE: '月度K线分析', |
|
|
FULL_ANALYSIS: '完整命理分析', |
|
|
YEARLY_FORTUNE: '今年运势', |
|
|
MONTHLY_FORTUNE: '本月运势', |
|
|
YEARLY_KLINE: '本年度K线', |
|
|
BASIC_DAILY_FORTUNE: '每日运势基础版', |
|
|
}; |
|
|
return names[featureKey] || featureKey; |
|
|
}; |
|
|
|
|
|
export default { |
|
|
POINTS_CONFIG, |
|
|
getFeatureCost, |
|
|
checkUserPoints, |
|
|
deductUserPoints, |
|
|
requirePoints, |
|
|
executePointsDeduction, |
|
|
getFeatureDisplayName, |
|
|
}; |
|
|
|