napgram-plugin-slave-market 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -0
- package/dist/commands/admin.commands.d.ts +11 -0
- package/dist/commands/admin.commands.d.ts.map +1 -0
- package/dist/commands/admin.commands.js +268 -0
- package/dist/commands/bank.commands.d.ts +13 -0
- package/dist/commands/bank.commands.d.ts.map +1 -0
- package/dist/commands/bank.commands.js +211 -0
- package/dist/commands/base.commands.d.ts +12 -0
- package/dist/commands/base.commands.d.ts.map +1 -0
- package/dist/commands/base.commands.js +149 -0
- package/dist/commands/economy.commands.d.ts +13 -0
- package/dist/commands/economy.commands.d.ts.map +1 -0
- package/dist/commands/economy.commands.js +221 -0
- package/dist/commands/gameplay.commands.d.ts +15 -0
- package/dist/commands/gameplay.commands.d.ts.map +1 -0
- package/dist/commands/gameplay.commands.js +461 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +47 -0
- package/dist/commands/social.commands.d.ts +13 -0
- package/dist/commands/social.commands.d.ts.map +1 -0
- package/dist/commands/social.commands.js +305 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +35 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.mjs +57 -0
- package/dist/models/index.d.ts +10 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +40 -0
- package/dist/services/admin.service.d.ts +59 -0
- package/dist/services/admin.service.d.ts.map +1 -0
- package/dist/services/admin.service.js +164 -0
- package/dist/services/bank.service.d.ts +74 -0
- package/dist/services/bank.service.d.ts.map +1 -0
- package/dist/services/bank.service.js +354 -0
- package/dist/services/bodyguard.service.d.ts +36 -0
- package/dist/services/bodyguard.service.d.ts.map +1 -0
- package/dist/services/bodyguard.service.js +102 -0
- package/dist/services/cooldown.service.d.ts +33 -0
- package/dist/services/cooldown.service.d.ts.map +1 -0
- package/dist/services/cooldown.service.js +104 -0
- package/dist/services/farm.service.d.ts +61 -0
- package/dist/services/farm.service.d.ts.map +1 -0
- package/dist/services/farm.service.js +255 -0
- package/dist/services/index.d.ts +16 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +15 -0
- package/dist/services/market.service.d.ts +58 -0
- package/dist/services/market.service.d.ts.map +1 -0
- package/dist/services/market.service.js +286 -0
- package/dist/services/player.service.d.ts +56 -0
- package/dist/services/player.service.d.ts.map +1 -0
- package/dist/services/player.service.js +201 -0
- package/dist/services/ranking.service.d.ts +28 -0
- package/dist/services/ranking.service.d.ts.map +1 -0
- package/dist/services/ranking.service.js +71 -0
- package/dist/services/redpacket.service.d.ts +63 -0
- package/dist/services/redpacket.service.d.ts.map +1 -0
- package/dist/services/redpacket.service.js +207 -0
- package/dist/services/transaction.service.d.ts +48 -0
- package/dist/services/transaction.service.d.ts.map +1 -0
- package/dist/services/transaction.service.js +102 -0
- package/dist/services/vip.service.d.ts +41 -0
- package/dist/services/vip.service.d.ts.map +1 -0
- package/dist/services/vip.service.js +167 -0
- package/dist/services/work.service.d.ts +49 -0
- package/dist/services/work.service.d.ts.map +1 -0
- package/dist/services/work.service.js +258 -0
- package/dist/types/index.d.ts +62 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/helpers.d.ts +40 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +88 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/napgram-plugin.json +15 -0
- package/package.json +56 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 红包服务 - 发红包、抢红包
|
|
3
|
+
*/
|
|
4
|
+
import type { PluginContext } from '@napgram/sdk';
|
|
5
|
+
import type { SlaveMarketConfig } from '../config';
|
|
6
|
+
import { TransactionService } from './transaction.service';
|
|
7
|
+
export declare class RedPacketService {
|
|
8
|
+
private ctx;
|
|
9
|
+
private config;
|
|
10
|
+
private transactionService;
|
|
11
|
+
constructor(ctx: PluginContext, config: SlaveMarketConfig, transactionService: TransactionService);
|
|
12
|
+
/**
|
|
13
|
+
* 发红包
|
|
14
|
+
*/
|
|
15
|
+
sendRedPacket(senderId: string, senderName: string, totalAmount: number, totalCount: number, scopeKey: string, isAdmin?: boolean): Promise<{
|
|
16
|
+
packetId: string;
|
|
17
|
+
fee: number;
|
|
18
|
+
cost: number;
|
|
19
|
+
newBalance: number;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* 抢红包
|
|
23
|
+
*/
|
|
24
|
+
grabRedPacket(userId: string, userName: string, packetId: string, scopeKey: string): Promise<{
|
|
25
|
+
amount: number;
|
|
26
|
+
newBalance: number;
|
|
27
|
+
remaining: number;
|
|
28
|
+
lucky: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* 查询红包详情
|
|
32
|
+
*/
|
|
33
|
+
getRedPacket(packetId: string): Promise<({
|
|
34
|
+
grabs: {
|
|
35
|
+
id: number;
|
|
36
|
+
userId: string;
|
|
37
|
+
amount: number;
|
|
38
|
+
packetId: string;
|
|
39
|
+
userName: string;
|
|
40
|
+
grabbedAt: Date;
|
|
41
|
+
}[];
|
|
42
|
+
} & {
|
|
43
|
+
id: number;
|
|
44
|
+
createdAt: Date;
|
|
45
|
+
packetId: string;
|
|
46
|
+
senderId: string;
|
|
47
|
+
senderName: string;
|
|
48
|
+
totalAmount: number;
|
|
49
|
+
totalCount: number;
|
|
50
|
+
remaining: number;
|
|
51
|
+
scopeKey: string;
|
|
52
|
+
expiresAt: Date;
|
|
53
|
+
}) | null>;
|
|
54
|
+
/**
|
|
55
|
+
* 生成红包ID
|
|
56
|
+
*/
|
|
57
|
+
private generatePacketId;
|
|
58
|
+
/**
|
|
59
|
+
* 清理过期红包
|
|
60
|
+
*/
|
|
61
|
+
cleanupExpiredPackets(): Promise<number>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=redpacket.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redpacket.service.d.ts","sourceRoot":"","sources":["../../src/services/redpacket.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,qBAAa,gBAAgB;IAErB,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,kBAAkB;gBAFlB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,EAAE,kBAAkB;IAGlD;;OAEG;IACG,aAAa,CACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,OAAe,GACzB,OAAO,CAAC;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC;IAuEF;;OAEG;IACG,aAAa,CACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC;QACP,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,OAAO,CAAC;KAClB,CAAC;IA6HF;;OAEG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;IAQnC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC;CAcjD"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 红包服务 - 发红包、抢红包
|
|
3
|
+
*/
|
|
4
|
+
import { getDatabase } from '../models';
|
|
5
|
+
export class RedPacketService {
|
|
6
|
+
ctx;
|
|
7
|
+
config;
|
|
8
|
+
transactionService;
|
|
9
|
+
constructor(ctx, config, transactionService) {
|
|
10
|
+
this.ctx = ctx;
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.transactionService = transactionService;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 发红包
|
|
16
|
+
*/
|
|
17
|
+
async sendRedPacket(senderId, senderName, totalAmount, totalCount, scopeKey, isAdmin = false) {
|
|
18
|
+
const db = getDatabase();
|
|
19
|
+
if (totalAmount <= 0 || totalCount <= 0) {
|
|
20
|
+
throw new Error('金额和份数必须大于0');
|
|
21
|
+
}
|
|
22
|
+
if (totalAmount < totalCount) {
|
|
23
|
+
throw new Error('总金额必须大于份数');
|
|
24
|
+
}
|
|
25
|
+
const player = await db.slaveMarketPlayer.findUnique({
|
|
26
|
+
where: { userId: senderId },
|
|
27
|
+
});
|
|
28
|
+
if (!player) {
|
|
29
|
+
throw new Error('玩家不存在');
|
|
30
|
+
}
|
|
31
|
+
// 计算手续费(管理员免费)
|
|
32
|
+
const fee = isAdmin ? 0 : Math.ceil(totalAmount * 0.05);
|
|
33
|
+
const cost = totalAmount + fee;
|
|
34
|
+
if (player.balance < cost) {
|
|
35
|
+
throw new Error(`余额不足,需要 ${cost} (含手续费 ${fee})`);
|
|
36
|
+
}
|
|
37
|
+
// 生成红包ID
|
|
38
|
+
const packetId = this.generatePacketId();
|
|
39
|
+
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时过期
|
|
40
|
+
await db.$transaction([
|
|
41
|
+
// 扣除发送者余额
|
|
42
|
+
db.slaveMarketPlayer.update({
|
|
43
|
+
where: { userId: senderId },
|
|
44
|
+
data: {
|
|
45
|
+
balance: player.balance - cost,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
// 创建红包
|
|
49
|
+
db.slaveMarketRedPacket.create({
|
|
50
|
+
data: {
|
|
51
|
+
packetId,
|
|
52
|
+
senderId,
|
|
53
|
+
senderName,
|
|
54
|
+
totalAmount,
|
|
55
|
+
totalCount,
|
|
56
|
+
remaining: totalCount,
|
|
57
|
+
scopeKey,
|
|
58
|
+
expiresAt,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
]);
|
|
62
|
+
// 记录交易
|
|
63
|
+
await this.transactionService.createTransaction({
|
|
64
|
+
userId: senderId,
|
|
65
|
+
type: 'red_packet',
|
|
66
|
+
amount: -cost,
|
|
67
|
+
balance: player.balance - cost,
|
|
68
|
+
description: `发红包(${totalCount}个)`,
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
packetId,
|
|
72
|
+
fee,
|
|
73
|
+
cost,
|
|
74
|
+
newBalance: player.balance - cost,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 抢红包
|
|
79
|
+
*/
|
|
80
|
+
async grabRedPacket(userId, userName, packetId, scopeKey) {
|
|
81
|
+
const db = getDatabase();
|
|
82
|
+
const result = await db.$transaction(async (tx) => {
|
|
83
|
+
const packets = await tx.$queryRaw `
|
|
84
|
+
SELECT * FROM "slave_market_red_packets"
|
|
85
|
+
WHERE "packetId" = ${packetId}
|
|
86
|
+
FOR UPDATE
|
|
87
|
+
`;
|
|
88
|
+
const packet = packets[0];
|
|
89
|
+
if (!packet) {
|
|
90
|
+
throw new Error('红包不存在');
|
|
91
|
+
}
|
|
92
|
+
if (packet.scopeKey !== scopeKey) {
|
|
93
|
+
throw new Error('红包不在当前群聊/私聊');
|
|
94
|
+
}
|
|
95
|
+
if (packet.expiresAt < new Date()) {
|
|
96
|
+
throw new Error('红包已过期');
|
|
97
|
+
}
|
|
98
|
+
if (packet.remaining <= 0) {
|
|
99
|
+
throw new Error('红包已被抢完');
|
|
100
|
+
}
|
|
101
|
+
const alreadyGrabbed = await tx.slaveMarketRedPacketGrab.findUnique({
|
|
102
|
+
where: {
|
|
103
|
+
packetId_userId: {
|
|
104
|
+
packetId,
|
|
105
|
+
userId,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
if (alreadyGrabbed) {
|
|
110
|
+
throw new Error('你已经抢过这个红包了');
|
|
111
|
+
}
|
|
112
|
+
const grabs = await tx.slaveMarketRedPacketGrab.findMany({
|
|
113
|
+
where: { packetId },
|
|
114
|
+
select: { amount: true },
|
|
115
|
+
});
|
|
116
|
+
const grabbedTotal = grabs.reduce((sum, g) => sum + g.amount, 0);
|
|
117
|
+
const remainingCount = packet.remaining;
|
|
118
|
+
let amount;
|
|
119
|
+
if (remainingCount === 1) {
|
|
120
|
+
amount = packet.totalAmount - grabbedTotal;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const remainingAmount = packet.totalAmount - grabbedTotal;
|
|
124
|
+
const avg = Math.floor(remainingAmount / remainingCount);
|
|
125
|
+
const min = 1;
|
|
126
|
+
const max = Math.min(avg * 2, remainingAmount - (remainingCount - 1));
|
|
127
|
+
amount = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
128
|
+
}
|
|
129
|
+
const player = await tx.slaveMarketPlayer.findUnique({
|
|
130
|
+
where: { userId },
|
|
131
|
+
});
|
|
132
|
+
if (!player) {
|
|
133
|
+
throw new Error('玩家不存在');
|
|
134
|
+
}
|
|
135
|
+
const updatedPlayer = await tx.slaveMarketPlayer.update({
|
|
136
|
+
where: { userId },
|
|
137
|
+
data: {
|
|
138
|
+
balance: { increment: amount },
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
const updatedPacket = await tx.slaveMarketRedPacket.update({
|
|
142
|
+
where: { packetId },
|
|
143
|
+
data: {
|
|
144
|
+
remaining: { decrement: 1 },
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
await tx.slaveMarketRedPacketGrab.create({
|
|
148
|
+
data: {
|
|
149
|
+
packetId,
|
|
150
|
+
userId,
|
|
151
|
+
userName,
|
|
152
|
+
amount,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const allGrabs = [...grabs, { amount }];
|
|
156
|
+
const maxAmount = Math.max(...allGrabs.map((g) => g.amount));
|
|
157
|
+
const lucky = amount === maxAmount && remainingCount === 1;
|
|
158
|
+
await tx.slaveMarketTransaction.create({
|
|
159
|
+
data: {
|
|
160
|
+
userId,
|
|
161
|
+
type: 'red_packet',
|
|
162
|
+
amount,
|
|
163
|
+
balance: updatedPlayer.balance,
|
|
164
|
+
description: `抢红包(${packet.senderName})`,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
amount,
|
|
169
|
+
newBalance: updatedPlayer.balance,
|
|
170
|
+
remaining: updatedPacket.remaining,
|
|
171
|
+
lucky,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 查询红包详情
|
|
178
|
+
*/
|
|
179
|
+
async getRedPacket(packetId) {
|
|
180
|
+
const db = getDatabase();
|
|
181
|
+
return db.slaveMarketRedPacket.findUnique({
|
|
182
|
+
where: { packetId },
|
|
183
|
+
include: { grabs: true },
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 生成红包ID
|
|
188
|
+
*/
|
|
189
|
+
generatePacketId() {
|
|
190
|
+
return `RP${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 清理过期红包
|
|
194
|
+
*/
|
|
195
|
+
async cleanupExpiredPackets() {
|
|
196
|
+
const db = getDatabase();
|
|
197
|
+
const now = new Date();
|
|
198
|
+
const result = await db.slaveMarketRedPacket.deleteMany({
|
|
199
|
+
where: {
|
|
200
|
+
expiresAt: {
|
|
201
|
+
lt: now,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return result.count;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交易服务 - 记录和查询交易历史
|
|
3
|
+
*/
|
|
4
|
+
import type { PluginContext } from '@napgram/sdk';
|
|
5
|
+
import { type SlaveMarketTransaction } from '../models';
|
|
6
|
+
import type { TransactionType } from '../types';
|
|
7
|
+
export interface CreateTransactionParams {
|
|
8
|
+
userId: string;
|
|
9
|
+
type: TransactionType;
|
|
10
|
+
amount: number;
|
|
11
|
+
balance: number;
|
|
12
|
+
targetId?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
metadata?: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
export declare class TransactionService {
|
|
17
|
+
private ctx;
|
|
18
|
+
constructor(ctx: PluginContext);
|
|
19
|
+
/**
|
|
20
|
+
* 创建交易记录
|
|
21
|
+
*/
|
|
22
|
+
createTransaction(params: CreateTransactionParams): Promise<SlaveMarketTransaction>;
|
|
23
|
+
/**
|
|
24
|
+
* 获取用户交易历史
|
|
25
|
+
*/
|
|
26
|
+
getTransactions(userId: string, options?: {
|
|
27
|
+
limit?: number;
|
|
28
|
+
offset?: number;
|
|
29
|
+
type?: TransactionType;
|
|
30
|
+
}): Promise<SlaveMarketTransaction[]>;
|
|
31
|
+
/**
|
|
32
|
+
* 获取交易统计
|
|
33
|
+
*/
|
|
34
|
+
getStatistics(userId: string): Promise<{
|
|
35
|
+
totalIncome: number;
|
|
36
|
+
totalExpense: number;
|
|
37
|
+
transactionCount: number;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* 获取最近的交易
|
|
41
|
+
*/
|
|
42
|
+
getRecentTransaction(userId: string, type: TransactionType): Promise<SlaveMarketTransaction | null>;
|
|
43
|
+
/**
|
|
44
|
+
* 清除旧交易记录(保留最近N天)
|
|
45
|
+
*/
|
|
46
|
+
cleanOldTransactions(days?: number): Promise<number>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=transaction.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction.service.d.ts","sourceRoot":"","sources":["../../src/services/transaction.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAe,KAAK,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,MAAM,WAAW,uBAAuB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,qBAAa,kBAAkB;IACf,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,aAAa;IAEtC;;OAEG;IACG,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAgBzF;;OAEG;IACG,eAAe,CACjB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,eAAe,CAAC;KACrB,GACP,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAiBpC;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QACzC,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;KAC5B,CAAC;IA0BF;;OAEG;IACG,oBAAoB,CACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,GACtB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC;IAczC;;OAEG;IACG,oBAAoB,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC;CAgBjE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交易服务 - 记录和查询交易历史
|
|
3
|
+
*/
|
|
4
|
+
import { getDatabase } from '../models';
|
|
5
|
+
export class TransactionService {
|
|
6
|
+
ctx;
|
|
7
|
+
constructor(ctx) {
|
|
8
|
+
this.ctx = ctx;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 创建交易记录
|
|
12
|
+
*/
|
|
13
|
+
async createTransaction(params) {
|
|
14
|
+
const db = getDatabase();
|
|
15
|
+
return db.slaveMarketTransaction.create({
|
|
16
|
+
data: {
|
|
17
|
+
userId: params.userId,
|
|
18
|
+
type: params.type,
|
|
19
|
+
amount: params.amount,
|
|
20
|
+
balance: params.balance,
|
|
21
|
+
targetId: params.targetId,
|
|
22
|
+
description: params.description,
|
|
23
|
+
metadata: params.metadata,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 获取用户交易历史
|
|
29
|
+
*/
|
|
30
|
+
async getTransactions(userId, options = {}) {
|
|
31
|
+
const db = getDatabase();
|
|
32
|
+
const { limit = 20, offset = 0, type } = options;
|
|
33
|
+
return db.slaveMarketTransaction.findMany({
|
|
34
|
+
where: {
|
|
35
|
+
userId,
|
|
36
|
+
...(type && { type }),
|
|
37
|
+
},
|
|
38
|
+
orderBy: {
|
|
39
|
+
createdAt: 'desc',
|
|
40
|
+
},
|
|
41
|
+
take: limit,
|
|
42
|
+
skip: offset,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 获取交易统计
|
|
47
|
+
*/
|
|
48
|
+
async getStatistics(userId) {
|
|
49
|
+
const db = getDatabase();
|
|
50
|
+
const transactions = await db.slaveMarketTransaction.findMany({
|
|
51
|
+
where: { userId },
|
|
52
|
+
select: { amount: true },
|
|
53
|
+
});
|
|
54
|
+
let totalIncome = 0;
|
|
55
|
+
let totalExpense = 0;
|
|
56
|
+
for (const tx of transactions) {
|
|
57
|
+
if (tx.amount > 0) {
|
|
58
|
+
totalIncome += tx.amount;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
totalExpense += Math.abs(tx.amount);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
totalIncome,
|
|
66
|
+
totalExpense,
|
|
67
|
+
transactionCount: transactions.length,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 获取最近的交易
|
|
72
|
+
*/
|
|
73
|
+
async getRecentTransaction(userId, type) {
|
|
74
|
+
const db = getDatabase();
|
|
75
|
+
return db.slaveMarketTransaction.findFirst({
|
|
76
|
+
where: {
|
|
77
|
+
userId,
|
|
78
|
+
type,
|
|
79
|
+
},
|
|
80
|
+
orderBy: {
|
|
81
|
+
createdAt: 'desc',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 清除旧交易记录(保留最近N天)
|
|
87
|
+
*/
|
|
88
|
+
async cleanOldTransactions(days = 30) {
|
|
89
|
+
const db = getDatabase();
|
|
90
|
+
const cutoffDate = new Date();
|
|
91
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
92
|
+
const result = await db.slaveMarketTransaction.deleteMany({
|
|
93
|
+
where: {
|
|
94
|
+
createdAt: {
|
|
95
|
+
lt: cutoffDate,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
this.ctx.logger.info(`[slave-market] Cleaned ${result.count} old transactions`);
|
|
100
|
+
return result.count;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIP服务 - 卡密管理、特权系统
|
|
3
|
+
*/
|
|
4
|
+
import type { PluginContext } from '@napgram/sdk';
|
|
5
|
+
import type { SlaveMarketConfig } from '../config';
|
|
6
|
+
import { TransactionService } from './transaction.service';
|
|
7
|
+
export declare class VipService {
|
|
8
|
+
private ctx;
|
|
9
|
+
private config;
|
|
10
|
+
private transactionService;
|
|
11
|
+
constructor(ctx: PluginContext, config: SlaveMarketConfig, transactionService: TransactionService);
|
|
12
|
+
/**
|
|
13
|
+
* 生成VIP卡密
|
|
14
|
+
*/
|
|
15
|
+
generateVipCards(creatorId: string, cardType: '日卡' | '周卡' | '月卡' | '小时卡', count: number, customHours?: number): Promise<string[]>;
|
|
16
|
+
/**
|
|
17
|
+
* 兑换VIP卡
|
|
18
|
+
*/
|
|
19
|
+
redeemVipCard(userId: string, cardCode: string): Promise<{
|
|
20
|
+
cardType: string;
|
|
21
|
+
duration: number;
|
|
22
|
+
newEndTime: number;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* 检查VIP状态
|
|
26
|
+
*/
|
|
27
|
+
checkVipStatus(userId: string): Promise<{
|
|
28
|
+
isVip: boolean;
|
|
29
|
+
remaining: number;
|
|
30
|
+
isPermanent: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* 生成卡密码
|
|
34
|
+
*/
|
|
35
|
+
private generateCardCode;
|
|
36
|
+
/**
|
|
37
|
+
* 清理过期卡密
|
|
38
|
+
*/
|
|
39
|
+
cleanupExpiredCards(): Promise<number>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=vip.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vip.service.d.ts","sourceRoot":"","sources":["../../src/services/vip.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAK3D,qBAAa,UAAU;IAEf,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,kBAAkB;gBAFlB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,EAAE,kBAAkB;IAGlD;;OAEG;IACG,gBAAgB,CAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EACpC,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC;IA6CpB;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAC3D,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC;IA4DF;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAC1C,KAAK,EAAE,OAAO,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,OAAO,CAAC;KACxB,CAAC;IAgCF;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;CAe/C"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIP服务 - 卡密管理、特权系统
|
|
3
|
+
*/
|
|
4
|
+
import { getDatabase } from '../models';
|
|
5
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
7
|
+
export class VipService {
|
|
8
|
+
ctx;
|
|
9
|
+
config;
|
|
10
|
+
transactionService;
|
|
11
|
+
constructor(ctx, config, transactionService) {
|
|
12
|
+
this.ctx = ctx;
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.transactionService = transactionService;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 生成VIP卡密
|
|
18
|
+
*/
|
|
19
|
+
async generateVipCards(creatorId, cardType, count, customHours) {
|
|
20
|
+
const db = getDatabase();
|
|
21
|
+
let duration;
|
|
22
|
+
switch (cardType) {
|
|
23
|
+
case '日卡':
|
|
24
|
+
duration = DAY_MS;
|
|
25
|
+
break;
|
|
26
|
+
case '周卡':
|
|
27
|
+
duration = 7 * DAY_MS;
|
|
28
|
+
break;
|
|
29
|
+
case '月卡':
|
|
30
|
+
duration = 30 * DAY_MS;
|
|
31
|
+
break;
|
|
32
|
+
case '小时卡':
|
|
33
|
+
if (!customHours || customHours <= 0) {
|
|
34
|
+
throw new Error('小时卡需要指定时长');
|
|
35
|
+
}
|
|
36
|
+
duration = customHours * HOUR_MS;
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`未知的卡类型: ${cardType}`);
|
|
40
|
+
}
|
|
41
|
+
const cards = [];
|
|
42
|
+
for (let i = 0; i < count; i++) {
|
|
43
|
+
const cardCode = this.generateCardCode();
|
|
44
|
+
await db.slaveMarketVipCard.create({
|
|
45
|
+
data: {
|
|
46
|
+
cardCode,
|
|
47
|
+
cardType,
|
|
48
|
+
duration,
|
|
49
|
+
createdBy: creatorId,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
cards.push(cardCode);
|
|
53
|
+
}
|
|
54
|
+
return cards;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 兑换VIP卡
|
|
58
|
+
*/
|
|
59
|
+
async redeemVipCard(userId, cardCode) {
|
|
60
|
+
const db = getDatabase();
|
|
61
|
+
const card = await db.slaveMarketVipCard.findUnique({
|
|
62
|
+
where: { cardCode },
|
|
63
|
+
});
|
|
64
|
+
if (!card) {
|
|
65
|
+
throw new Error('卡密不存在');
|
|
66
|
+
}
|
|
67
|
+
if (card.used) {
|
|
68
|
+
throw new Error('该卡密已被使用');
|
|
69
|
+
}
|
|
70
|
+
const player = await db.slaveMarketPlayer.findUnique({
|
|
71
|
+
where: { userId },
|
|
72
|
+
});
|
|
73
|
+
if (!player) {
|
|
74
|
+
throw new Error('玩家不存在');
|
|
75
|
+
}
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
let newEndTime;
|
|
78
|
+
if (player.vipEndTime && Number(player.vipEndTime) > now) {
|
|
79
|
+
// 续期
|
|
80
|
+
newEndTime = Number(player.vipEndTime) + card.duration;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// 新开
|
|
84
|
+
newEndTime = now + card.duration;
|
|
85
|
+
}
|
|
86
|
+
await db.$transaction([
|
|
87
|
+
// 更新玩家VIP状态
|
|
88
|
+
db.slaveMarketPlayer.update({
|
|
89
|
+
where: { userId },
|
|
90
|
+
data: {
|
|
91
|
+
vipEndTime: BigInt(newEndTime),
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
// 标记卡密已使用
|
|
95
|
+
db.slaveMarketVipCard.update({
|
|
96
|
+
where: { cardCode },
|
|
97
|
+
data: {
|
|
98
|
+
used: true,
|
|
99
|
+
usedBy: userId,
|
|
100
|
+
usedAt: new Date(),
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
return {
|
|
105
|
+
cardType: card.cardType,
|
|
106
|
+
duration: Math.floor(card.duration / HOUR_MS),
|
|
107
|
+
newEndTime,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 检查VIP状态
|
|
112
|
+
*/
|
|
113
|
+
async checkVipStatus(userId) {
|
|
114
|
+
const db = getDatabase();
|
|
115
|
+
const player = await db.slaveMarketPlayer.findUnique({
|
|
116
|
+
where: { userId },
|
|
117
|
+
});
|
|
118
|
+
if (!player) {
|
|
119
|
+
return { isVip: false, remaining: 0, isPermanent: false };
|
|
120
|
+
}
|
|
121
|
+
// 检查管理员永久VIP
|
|
122
|
+
if (this.config.VIP配置.管理员永久VIP && player.isAdmin) {
|
|
123
|
+
return { isVip: true, remaining: 0, isPermanent: true };
|
|
124
|
+
}
|
|
125
|
+
const admin = await db.slaveMarketAdmin.findUnique({
|
|
126
|
+
where: { userId },
|
|
127
|
+
});
|
|
128
|
+
if (admin && this.config.VIP配置.管理员永久VIP) {
|
|
129
|
+
return { isVip: true, remaining: 0, isPermanent: true };
|
|
130
|
+
}
|
|
131
|
+
// 检查VIP到期时间
|
|
132
|
+
if (player.vipEndTime && Number(player.vipEndTime) > Date.now()) {
|
|
133
|
+
const remaining = Math.ceil((Number(player.vipEndTime) - Date.now()) / HOUR_MS);
|
|
134
|
+
return { isVip: true, remaining, isPermanent: false };
|
|
135
|
+
}
|
|
136
|
+
return { isVip: false, remaining: 0, isPermanent: false };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 生成卡密码
|
|
140
|
+
*/
|
|
141
|
+
generateCardCode() {
|
|
142
|
+
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
143
|
+
let code = '';
|
|
144
|
+
for (let i = 0; i < 16; i++) {
|
|
145
|
+
if (i > 0 && i % 4 === 0)
|
|
146
|
+
code += '-';
|
|
147
|
+
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
148
|
+
}
|
|
149
|
+
return code;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 清理过期卡密
|
|
153
|
+
*/
|
|
154
|
+
async cleanupExpiredCards() {
|
|
155
|
+
const db = getDatabase();
|
|
156
|
+
const thirtyDaysAgo = new Date(Date.now() - 30 * DAY_MS);
|
|
157
|
+
const result = await db.slaveMarketVipCard.deleteMany({
|
|
158
|
+
where: {
|
|
159
|
+
used: true,
|
|
160
|
+
usedAt: {
|
|
161
|
+
lt: thirtyDaysAgo,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
return result.count;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作服务 - 打工、抢劫等收入相关功能
|
|
3
|
+
*/
|
|
4
|
+
import type { PluginContext } from '@napgram/sdk';
|
|
5
|
+
import type { SlaveMarketConfig } from '../config';
|
|
6
|
+
import { TransactionService } from './transaction.service';
|
|
7
|
+
import { PlayerService } from './player.service';
|
|
8
|
+
export type RobStrategy = '稳健' | '激进' | '平衡';
|
|
9
|
+
export declare class WorkService {
|
|
10
|
+
private ctx;
|
|
11
|
+
private config;
|
|
12
|
+
private transactionService;
|
|
13
|
+
private playerService;
|
|
14
|
+
constructor(ctx: PluginContext, config: SlaveMarketConfig, transactionService: TransactionService, playerService: PlayerService);
|
|
15
|
+
/**
|
|
16
|
+
* 打工
|
|
17
|
+
*/
|
|
18
|
+
work(userId: string): Promise<{
|
|
19
|
+
income: number;
|
|
20
|
+
ownerShare: number;
|
|
21
|
+
newBalance: number;
|
|
22
|
+
ownerName?: string;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* 抢劫
|
|
26
|
+
*/
|
|
27
|
+
rob(robberId: string, targetId: string, strategy?: RobStrategy): Promise<{
|
|
28
|
+
success: boolean;
|
|
29
|
+
amount: number;
|
|
30
|
+
newBalance: number;
|
|
31
|
+
targetBalance: number;
|
|
32
|
+
penalty?: number;
|
|
33
|
+
jailTime?: number;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* 转账
|
|
37
|
+
*/
|
|
38
|
+
transfer(senderId: string, receiverId: string, amount: number, isAdmin?: boolean): Promise<{
|
|
39
|
+
fee: number;
|
|
40
|
+
actualAmount: number;
|
|
41
|
+
newBalance: number;
|
|
42
|
+
receiverBalance: number;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* 获取抢劫策略配置
|
|
46
|
+
*/
|
|
47
|
+
private getStrategyConfig;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=work.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work.service.d.ts","sourceRoot":"","sources":["../../src/services/work.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7C,qBAAa,WAAW;IAEhB,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,kBAAkB;IAC1B,OAAO,CAAC,aAAa;gBAHb,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,EAAE,kBAAkB,EACtC,aAAa,EAAE,aAAa;IAGxC;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAChC,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IAqEF;;OAEG;IACG,GAAG,CACL,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,WAAkB,GAC7B,OAAO,CAAC;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IAmHF;;OAEG;IACG,QAAQ,CACV,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,OAAe,GACzB,OAAO,CAAC;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;KAC3B,CAAC;IAqEF;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAc5B"}
|