feat(admin,api,player): 返水流程优化、账单详情与数据库重置

优化返水预览/确认/作废,新增玩家账变详情与后台一键重置为 seed 数据,并修复 dev 启动时 3000 端口占用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 11:14:22 +08:00
parent 24fa1b275c
commit b2216abd0c
24 changed files with 2253 additions and 849 deletions

View File

@@ -16,4 +16,5 @@ export const P = {
cashback: 'cashback.confirm',
content: 'content.manage',
audit: 'audit.view',
resetDatabase: 'settings.reset_database',
} as const;

View File

@@ -32,6 +32,7 @@ import { PrismaService } from '../../shared/prisma/prisma.service';
import { AdminDashboardService } from './admin-dashboard.service';
import { SystemConfigService } from '../../shared/config/system-config.service';
import { P } from './admin-permissions';
import { DatabaseResetService } from '../../infrastructure/database/database-reset.service';
import {
IsString,
IsNumber,
@@ -41,6 +42,7 @@ import {
MinLength,
IsIn,
Min,
Equals,
ValidateIf,
} from 'class-validator';
import type { ZhiboMatchExport, ZhiboMatchesBundleExport } from '../../domains/catalog/zhibo-match.types';
@@ -152,6 +154,12 @@ class PlayerAccountSettingsDto {
allowUsernameChange?: boolean;
}
class ResetDatabaseDto {
@IsString()
@Equals('RESET')
confirmPhrase!: string;
}
class CreateAgentAdminDto {
/** 已有玩家用户 ID升级为一级代理 */
@IsString()
@@ -675,6 +683,7 @@ export class AdminController {
private readonly dashboardService: AdminDashboardService,
private systemConfig: SystemConfigService,
private bettingLimits: BettingLimitsService,
private databaseReset: DatabaseResetService,
) {}
@Get('dashboard')
@@ -732,6 +741,32 @@ export class AdminController {
return jsonResponse(limits);
}
@Get('system/reset-database')
@RequirePermissions(P.resetDatabase)
getResetDatabaseStatus() {
return jsonResponse({ allowed: this.databaseReset.isAllowed() });
}
@Post('system/reset-database')
@RequirePermissions(P.resetDatabase)
async resetDatabase(
@CurrentUser('id') operatorId: bigint,
@Body() dto: ResetDatabaseDto,
) {
if (dto.confirmPhrase !== 'RESET') {
throw new BadRequestException('确认短语不正确,请输入 RESET');
}
const result = await this.databaseReset.resetDatabase();
await this.audit.log({
operatorId,
operatorType: 'ADMIN',
action: 'RESET_DATABASE',
module: 'SYSTEM',
afterData: { demoAccounts: result.demoAccounts },
});
return jsonResponse(result);
}
@Get('users')
@RequirePermissions(P.usersView)
async listUsers(
@@ -1545,6 +1580,28 @@ export class AdminController {
return jsonResponse(preview);
}
@Get('cashbacks')
@RequirePermissions(P.cashback, P.reports)
async listCashbacks(
@Query('page') page = '1',
@Query('pageSize') pageSize = '10',
@Query('status') status?: string,
) {
const result = await this.cashback.listBatches({
page: Number(page) || 1,
pageSize: Number(pageSize) || 10,
status,
});
return jsonResponse(result);
}
@Get('cashbacks/:batchId')
@RequirePermissions(P.cashback, P.reports)
async getCashbackBatch(@Param('batchId') batchId: string) {
const detail = await this.cashback.getBatchDetail(BigInt(batchId));
return jsonResponse(detail);
}
@Post('cashbacks/:batchId/confirm')
@RequirePermissions(P.cashback)
async cashbackConfirm(@CurrentUser('id') operatorId: bigint, @Param('batchId') batchId: string) {
@@ -1559,6 +1616,20 @@ export class AdminController {
return jsonResponse(result);
}
@Post('cashbacks/:batchId/cancel')
@RequirePermissions(P.cashback)
async cashbackCancel(@CurrentUser('id') operatorId: bigint, @Param('batchId') batchId: string) {
const result = await this.cashback.cancelBatch(BigInt(batchId));
await this.audit.log({
operatorId,
operatorType: 'ADMIN',
action: 'CANCEL_CASHBACK',
module: 'CASHBACK',
targetId: batchId,
});
return jsonResponse(result);
}
@Get('contents')
@RequirePermissions(P.content, P.reports)
async listContents(

View File

@@ -12,6 +12,7 @@ import { CashbackModule } from '../../domains/operations/cashback/cashback.modul
import { ContentModule } from '../../domains/operations/content/content.module';
import { I18nModule } from '../../domains/operations/i18n/i18n.module';
import { BetsModule } from '../../domains/betting/bets.module';
import { DatabaseModule } from '../../infrastructure/database/database.module';
@Module({
imports: [
@@ -25,6 +26,7 @@ import { BetsModule } from '../../domains/betting/bets.module';
ContentModule,
I18nModule,
BetsModule,
DatabaseModule,
],
controllers: [AdminController],
providers: [AdminDashboardService, PermissionsGuard],

View File

@@ -255,6 +255,15 @@ export class PlayerController {
return jsonResponse(result);
}
@Get('wallet/transactions/:transactionId')
async transactionDetail(
@CurrentUser('id') userId: bigint,
@Param('transactionId') transactionId: string,
) {
const detail = await this.wallet.getTransactionDetail(userId, transactionId);
return jsonResponse(detail);
}
@Get('cashbacks')
async cashbacks(@CurrentUser('id') userId: bigint) {
const items = await this.cashback.getUserCashbacks(userId);