feat: 前台匿名浏览、登录引导、客服入口与返水增强

前台:
- 未登录可浏览首页/赛事/赔率,下注等操作弹出登录引导(去登录/继续浏览)
- 顶部新增客服入口与 iframe 弹窗
- 登录页支持暂不登录返回浏览

API:
- 首页/赛事/冠军盘接口改为公开访问,支持 X-Locale 头
- JWT 守卫支持可选认证

返水:
- 注单新增 is_cashbacked 字段,发放时自动标记
- 预览展示玩家余额,明确平台直发不从代理扣款
- 后台注单列表与玩家历史展示回水状态

其他:
- 串关禁止同场重复选号(SAME_MATCH)
- 补充结算资金流分析文档

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 09:36:44 +08:00
parent 785fa4416d
commit 844727c82e
35 changed files with 1007 additions and 49 deletions

View File

@@ -18,6 +18,7 @@ type AggregatedItem = {
amount: Decimal;
username: string;
agentUsername: string | null;
availableBalance: Decimal;
};
type BetCashbackLine = {
@@ -158,12 +159,25 @@ export class CashbackService {
: [];
const userById = new Map(users.map((u) => [u.id.toString(), u]));
const wallets =
userIds.length > 0
? await this.prisma.wallet.findMany({
where: { userId: { in: userIds } },
select: { userId: true, availableBalance: true },
})
: [];
const balanceByUserId = new Map(
wallets.map((w) => [w.userId.toString(), w.availableBalance]),
);
const items: AggregatedItem[] = rawItems.map((item) => {
const user = userById.get(item.userId.toString());
return {
...item,
username: user?.username ?? '',
agentUsername: user?.parent?.username ?? null,
availableBalance:
balanceByUserId.get(item.userId.toString()) ?? new Decimal(0),
};
});
@@ -362,6 +376,17 @@ export class CashbackService {
: [];
const userById = new Map(users.map((u) => [u.id.toString(), u]));
const wallets =
userIds.length > 0
? await this.prisma.wallet.findMany({
where: { userId: { in: userIds } },
select: { userId: true, availableBalance: true },
})
: [];
const balanceByUserId = new Map(
wallets.map((w) => [w.userId.toString(), w.availableBalance]),
);
let operatorUsername: string | null = null;
if (batch.operatorId) {
const op = await this.prisma.user.findUnique({
@@ -378,6 +403,8 @@ export class CashbackService {
userId: item.userId,
username: user?.username ?? '',
agentUsername: user?.parent?.username ?? null,
availableBalance:
balanceByUserId.get(item.userId.toString()) ?? new Decimal(0),
effectiveStake: item.effectiveStake,
betCount: item.betCount,
rate: item.rate,
@@ -448,6 +475,13 @@ export class CashbackService {
}
}
if (betIds.length > 0) {
await this.prisma.bet.updateMany({
where: { id: { in: betIds } },
data: { isCashbacked: true },
});
}
await this.prisma.cashbackBatch.update({
where: { id: batchId },
data: { status: 'CONFIRMED', confirmedAt: new Date(), operatorId },