1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| // src/utils/credits.ts
// 实际函数名:consumeUserCredits,参数为对象
export async function consumeUserCredits({
userId,
amount,
description,
}: {
userId: string;
amount: number;
description: string;
}) {
const db = getDB();
const currentTime = new Date();
// 先处理过期积分
await processExpiredCredits(userId, currentTime);
// 查询可用积分桶,排序:先按过期时间(最快过期的先消费),再按创建时间
const sourceTransactions = await db.query.creditTransactionTable.findMany({
where: and(
eq(creditTransactionTable.userId, userId),
inArray(creditTransactionTable.type, [
CREDIT_TRANSACTION_TYPE.PURCHASE,
CREDIT_TRANSACTION_TYPE.MONTHLY_REFRESH,
]),
gt(creditTransactionTable.remainingAmount, 0),
isNull(creditTransactionTable.expirationDateProcessedAt),
or(
isNull(creditTransactionTable.expirationDate),
gte(creditTransactionTable.expirationDate, currentTime),
),
),
orderBy: [
asc(creditTransactionTable.expirationDate), // 最快过期的先消费
asc(creditTransactionTable.createdAt),
],
columns: { id: true, remainingAmount: true },
});
const availableFromBuckets = sourceTransactions.reduce(
(total, t) => total + t.remainingAmount, 0
);
if (availableFromBuckets < amount) {
throw new InsufficientCreditsError({
requiredCredits: amount,
availableCredits: availableFromBuckets,
});
}
// 从桶中扣减(FIFO,按过期时间优先)
// ...(逐桶扣减逻辑)
// 更新用户总积分
await db
.update(userTable)
.set({ currentCredits: sql`${userTable.currentCredits} - ${amount}` })
.where(and(
eq(userTable.id, userId),
gte(userTable.currentCredits, amount),
));
}
|