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

@@ -6,11 +6,12 @@ import {
Body,
Param,
Query,
Headers,
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard, PlayerGuard } from '../../domains/identity/guards';
import { CurrentUser } from '../../shared/common/decorators';
import { CurrentUser, Public } from '../../shared/common/decorators';
import { jsonResponse } from '../../shared/common/filters';
import { UsersService } from '../../domains/identity/users.service';
import { SystemConfigService } from '../../shared/config/system-config.service';
@@ -144,8 +145,13 @@ export class PlayerController {
return jsonResponse(await this.formatPlayerProfile(user));
}
@Public()
@Get('home')
async home(@CurrentUser('locale') locale: string) {
async home(
@CurrentUser('locale') userLocale: string | undefined,
@Headers('x-locale') headerLocale?: string,
) {
const locale = userLocale || headerLocale || 'zh-CN';
const [banners, announcements, hotMatches, todayMatches] = await Promise.all([
this.content.listActive('BANNER', locale),
this.content.listActiveAnnouncements(locale),
@@ -164,23 +170,37 @@ export class PlayerController {
});
}
@Public()
@Get('matches')
async listMatches(
@CurrentUser('locale') locale: string,
@CurrentUser('locale') userLocale: string | undefined,
@Headers('x-locale') headerLocale: string | undefined,
@Query('leagueId') leagueId?: string,
) {
const locale = userLocale || headerLocale || 'zh-CN';
const items = await this.matches.listPublished(locale, leagueId ? BigInt(leagueId) : undefined);
return jsonResponse(items);
}
@Public()
@Get('outrights')
async listOutrights(@CurrentUser('locale') locale: string) {
async listOutrights(
@CurrentUser('locale') userLocale: string | undefined,
@Headers('x-locale') headerLocale: string | undefined,
) {
const locale = userLocale || headerLocale || 'zh-CN';
const items = await this.outright.listForPlayer(locale);
return jsonResponse(items);
}
@Public()
@Get('matches/:id')
async matchDetail(@Param('id') id: string, @CurrentUser('locale') locale: string) {
async matchDetail(
@Param('id') id: string,
@CurrentUser('locale') userLocale: string | undefined,
@Headers('x-locale') headerLocale: string | undefined,
) {
const locale = userLocale || headerLocale || 'zh-CN';
const match = await this.matches.getMatchDetail(BigInt(id), locale);
return jsonResponse(match);
}