重构 API 为 8 领域 + 应用层架构

将后端模块拆分为 domains、applications、shared 三层,结算计算器移入 domain 纯函数目录,API 路径与测试保持不变。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-02 14:48:41 +08:00
parent 14e49374ac
commit 4c92157299
47 changed files with 169 additions and 138 deletions

View File

@@ -61,8 +61,27 @@ API 文档http://localhost:3000/api/docs
## 项目结构
```
apps/api/src/
├── domains/ # 8 个业务领域
│ ├── identity/ # 身份与权限auth + users
│ ├── agent/ # 代理网络
│ ├── ledger/ # 账务账本wallet
│ ├── catalog/ # 赛事目录matches
│ ├── odds/ # 盘口赔率markets
│ ├── betting/ # 注单引擎
│ ├── settlement/ # 结算引擎(含 domain/ 纯函数)
│ └── operations/ # 运营支撑audit/cashback/content/i18n
├── applications/ # 三端应用层(编排,不含领域规则)
│ ├── player/
│ ├── admin/
│ └── agent/
├── shared/ # 基础设施
│ ├── prisma/
│ └── common/
├── app.module.ts
└── main.ts
apps/
api/ NestJS 单体后端
player/ 玩家 H5 前台
admin/ 平台后台
agent/ 代理后台

View File

@@ -1,29 +0,0 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { UsersModule } from '../users/users.module';
import { AgentsModule } from '../agents/agents.module';
import { WalletModule } from '../wallet/wallet.module';
import { MatchesModule } from '../matches/matches.module';
import { MarketsModule } from '../markets/markets.module';
import { SettlementModule } from '../settlement/settlement.module';
import { CashbackModule } from '../cashback/cashback.module';
import { ContentModule } from '../content/content.module';
import { I18nModule } from '../i18n/i18n.module';
import { BetsModule } from '../bets/bets.module';
@Module({
imports: [
UsersModule,
AgentsModule,
WalletModule,
MatchesModule,
MarketsModule,
SettlementModule,
CashbackModule,
ContentModule,
I18nModule,
BetsModule,
],
controllers: [AdminController],
})
export class AdminModule {}

View File

@@ -2,41 +2,33 @@ import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './auth/guards';
import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { AgentsModule } from './agents/agents.module';
import { WalletModule } from './wallet/wallet.module';
import { MatchesModule } from './matches/matches.module';
import { MarketsModule } from './markets/markets.module';
import { BetsModule } from './bets/bets.module';
import { SettlementModule } from './settlement/settlement.module';
import { CashbackModule } from './cashback/cashback.module';
import { ContentModule } from './content/content.module';
import { I18nModule } from './i18n/i18n.module';
import { AuditModule } from './audit/audit.module';
import { AdminModule } from './admin/admin.module';
import { PlayerModule } from './player/player.module';
import { AgentPortalModule } from './agent-portal/agent-portal.module';
import { JwtAuthGuard } from './domains/identity/guards';
import { PrismaModule } from './shared/prisma/prisma.module';
import { IdentityModule } from './domains/identity/identity.module';
import { AgentsModule } from './domains/agent/agents.module';
import { WalletModule } from './domains/ledger/wallet.module';
import { MatchesModule } from './domains/catalog/matches.module';
import { MarketsModule } from './domains/odds/markets.module';
import { BetsModule } from './domains/betting/bets.module';
import { SettlementModule } from './domains/settlement/settlement.module';
import { OperationsModule } from './domains/operations/operations.module';
import { AdminModule } from './applications/admin/admin.module';
import { PlayerModule } from './applications/player/player.module';
import { AgentPortalModule } from './applications/agent/agent-portal.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
ScheduleModule.forRoot(),
PrismaModule,
AuthModule,
UsersModule,
IdentityModule,
AgentsModule,
WalletModule,
MatchesModule,
MarketsModule,
BetsModule,
SettlementModule,
CashbackModule,
ContentModule,
I18nModule,
AuditModule,
OperationsModule,
AdminModule,
PlayerModule,
AgentPortalModule,

View File

@@ -9,21 +9,21 @@ import {
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard, AdminGuard } from '../auth/guards';
import { ContentService } from '../content/content.service';
import { CurrentUser } from '../common/decorators';
import { jsonResponse } from '../common/filters';
import { UsersService } from '../users/users.service';
import { AgentsService } from '../agents/agents.service';
import { WalletService } from '../wallet/wallet.service';
import { MatchesService } from '../matches/matches.service';
import { MarketsService } from '../markets/markets.service';
import { SettlementService } from '../settlement/settlement.service';
import { CashbackService } from '../cashback/cashback.service';
import { I18nService } from '../i18n/i18n.service';
import { AuditService } from '../audit/audit.service';
import { BetsService } from '../bets/bets.service';
import { PrismaService } from '../prisma/prisma.service';
import { JwtAuthGuard, AdminGuard } from '../../domains/identity/guards';
import { ContentService } from '../../domains/operations/content/content.service';
import { CurrentUser } from '../../shared/common/decorators';
import { jsonResponse } from '../../shared/common/filters';
import { UsersService } from '../../domains/identity/users.service';
import { AgentsService } from '../../domains/agent/agents.service';
import { WalletService } from '../../domains/ledger/wallet.service';
import { MatchesService } from '../../domains/catalog/matches.service';
import { MarketsService } from '../../domains/odds/markets.service';
import { SettlementService } from '../../domains/settlement/settlement.service';
import { CashbackService } from '../../domains/operations/cashback/cashback.service';
import { I18nService } from '../../domains/operations/i18n/i18n.service';
import { AuditService } from '../../domains/operations/audit/audit.service';
import { BetsService } from '../../domains/betting/bets.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { IsString, IsNumber, IsOptional, IsArray, IsBoolean, MinLength } from 'class-validator';
class CreateUserDto {

View File

@@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { UsersModule } from '../../domains/identity/users.module';
import { AgentsModule } from '../../domains/agent/agents.module';
import { WalletModule } from '../../domains/ledger/wallet.module';
import { MatchesModule } from '../../domains/catalog/matches.module';
import { MarketsModule } from '../../domains/odds/markets.module';
import { SettlementModule } from '../../domains/settlement/settlement.module';
import { CashbackModule } from '../../domains/operations/cashback/cashback.module';
import { ContentModule } from '../../domains/operations/content/content.module';
import { I18nModule } from '../../domains/operations/i18n/i18n.module';
import { BetsModule } from '../../domains/betting/bets.module';
@Module({
imports: [
UsersModule,
AgentsModule,
WalletModule,
MatchesModule,
MarketsModule,
SettlementModule,
CashbackModule,
ContentModule,
I18nModule,
BetsModule,
],
controllers: [AdminController],
})
export class AdminModule {}

View File

@@ -8,13 +8,13 @@ import {
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard, AgentGuard } from '../auth/guards';
import { CurrentUser } from '../common/decorators';
import { jsonResponse } from '../common/filters';
import { AgentsService } from '../agents/agents.service';
import { WalletService } from '../wallet/wallet.service';
import { BetsService } from '../bets/bets.service';
import { PrismaService } from '../prisma/prisma.service';
import { JwtAuthGuard, AgentGuard } from '../../domains/identity/guards';
import { CurrentUser } from '../../shared/common/decorators';
import { jsonResponse } from '../../shared/common/filters';
import { AgentsService } from '../../domains/agent/agents.service';
import { WalletService } from '../../domains/ledger/wallet.service';
import { BetsService } from '../../domains/betting/bets.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { IsString, IsNumber, MinLength, IsOptional } from 'class-validator';
class CreatePlayerDto {

View File

@@ -1,8 +1,8 @@
import { Module } from '@nestjs/common';
import { AgentPortalController } from './agent-portal.controller';
import { AgentsModule } from '../agents/agents.module';
import { WalletModule } from '../wallet/wallet.module';
import { BetsModule } from '../bets/bets.module';
import { AgentsModule } from '../../domains/agent/agents.module';
import { WalletModule } from '../../domains/ledger/wallet.module';
import { BetsModule } from '../../domains/betting/bets.module';
@Module({
imports: [AgentsModule, WalletModule, BetsModule],

View File

@@ -8,15 +8,15 @@ import {
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard, PlayerGuard } from '../auth/guards';
import { CurrentUser } from '../common/decorators';
import { jsonResponse } from '../common/filters';
import { UsersService } from '../users/users.service';
import { WalletService } from '../wallet/wallet.service';
import { MatchesService } from '../matches/matches.service';
import { BetsService } from '../bets/bets.service';
import { ContentService } from '../content/content.service';
import { CashbackService } from '../cashback/cashback.service';
import { JwtAuthGuard, PlayerGuard } from '../../domains/identity/guards';
import { CurrentUser } from '../../shared/common/decorators';
import { jsonResponse } from '../../shared/common/filters';
import { UsersService } from '../../domains/identity/users.service';
import { WalletService } from '../../domains/ledger/wallet.service';
import { MatchesService } from '../../domains/catalog/matches.service';
import { BetsService } from '../../domains/betting/bets.service';
import { ContentService } from '../../domains/operations/content/content.service';
import { CashbackService } from '../../domains/operations/cashback/cashback.service';
import { IsString, IsNumber, IsArray, ValidateNested, Min, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';

View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { PlayerController } from './player.controller';
import { UsersModule } from '../../domains/identity/users.module';
import { WalletModule } from '../../domains/ledger/wallet.module';
import { MatchesModule } from '../../domains/catalog/matches.module';
import { BetsModule } from '../../domains/betting/bets.module';
import { ContentModule } from '../../domains/operations/content/content.module';
import { CashbackModule } from '../../domains/operations/cashback/cashback.module';
@Module({
imports: [UsersModule, WalletModule, MatchesModule, BetsModule, ContentModule, CashbackModule],
controllers: [PlayerController],
})
export class PlayerModule {}

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { AgentsService } from './agents.service';
import { WalletModule } from '../wallet/wallet.module';
import { AuthModule } from '../auth/auth.module';
import { WalletModule } from '../ledger/wallet.module';
import { AuthModule } from '../identity/auth.module';
@Module({
imports: [WalletModule, AuthModule],

View File

@@ -1,9 +1,9 @@
import { Injectable, BadRequestException, ForbiddenException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { WalletService } from '../wallet/wallet.service';
import { AuthService } from '../auth/auth.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { WalletService } from '../ledger/wallet.service';
import { AuthService } from '../identity/auth.service';
import { Decimal } from '@prisma/client/runtime/library';
import { generateBatchNo } from '../common/decorators';
import { generateBatchNo } from '../../shared/common/decorators';
@Injectable()
export class AgentsService {

View File

@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { BetsService } from './bets.service';
import { WalletModule } from '../wallet/wallet.module';
import { WalletModule } from '../ledger/wallet.module';
@Module({
imports: [WalletModule],

View File

@@ -1,9 +1,9 @@
import { Injectable, BadRequestException, ConflictException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { WalletService } from '../wallet/wallet.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { WalletService } from '../ledger/wallet.service';
import { Decimal } from '@prisma/client/runtime/library';
import { generateBetNo } from '../common/decorators';
import { isQuarterHandicapOrTotal } from '../settlement/settlement-calculator';
import { generateBetNo } from '../../shared/common/decorators';
import { isQuarterHandicapOrTotal } from '../settlement/domain/settlement-calculator';
import { PARLAY_MIN_LEGS, PARLAY_MAX_LEGS } from '@thebet365/shared';
interface BetSelectionInput {

View File

@@ -1,6 +1,6 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
@Injectable()
export class MatchesService {

View File

@@ -2,9 +2,9 @@ import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import { LoginDto, ChangePasswordDto } from './auth.dto';
import { Public, CurrentUser } from '../common/decorators';
import { Public, CurrentUser } from '../../shared/common/decorators';
import { JwtAuthGuard } from './guards';
import { jsonResponse } from '../common/filters';
import { jsonResponse } from '../../shared/common/filters';
@ApiTags('Auth')
@Controller()

View File

@@ -2,7 +2,7 @@ import { Injectable, UnauthorizedException, ForbiddenException } from '@nestjs/c
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcryptjs';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
const MAX_LOGIN_FAILS = 5;
const LOCK_DURATION_MS = 15 * 60 * 1000;

View File

@@ -1,7 +1,7 @@
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY, PERMISSIONS_KEY } from '../common/decorators';
import { IS_PUBLIC_KEY, PERMISSIONS_KEY } from '../../shared/common/decorators';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { AuthModule } from './auth.module';
import { UsersModule } from './users.module';
@Module({
imports: [AuthModule, UsersModule],
exports: [AuthModule, UsersModule],
})
export class IdentityModule {}

View File

@@ -2,7 +2,7 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { JwtPayload } from './auth.service';
@Injectable()

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
@Injectable()
export class UsersService {

View File

@@ -1,7 +1,7 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { Decimal } from '@prisma/client/runtime/library';
import { generateTransactionId } from '../common/decorators';
import { generateTransactionId } from '../../shared/common/decorators';
@Injectable()
export class WalletService {

View File

@@ -1,10 +1,10 @@
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { Decimal } from '@prisma/client/runtime/library';
import {
FT_CORRECT_SCORE_TEMPLATE,
HT_CORRECT_SCORE_TEMPLATE,
} from '../settlement/settlement-calculator';
} from '../settlement/domain/settlement-calculator';
@Injectable()
export class MarketsService {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../../shared/prisma/prisma.service';
@Injectable()
export class AuditService {

View File

@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { CashbackService } from './cashback.service';
import { WalletModule } from '../wallet/wallet.module';
import { WalletModule } from '../../ledger/wallet.module';
@Module({
imports: [WalletModule],

View File

@@ -1,8 +1,8 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { WalletService } from '../wallet/wallet.service';
import { PrismaService } from '../../../shared/prisma/prisma.service';
import { WalletService } from '../../ledger/wallet.service';
import { Decimal } from '@prisma/client/runtime/library';
import { generateBatchNo } from '../common/decorators';
import { generateBatchNo } from '../../../shared/common/decorators';
@Injectable()
export class CashbackService {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../../shared/prisma/prisma.service';
@Injectable()
export class ContentService {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PrismaService } from '../../../shared/prisma/prisma.service';
import { DEFAULT_LOCALE } from '@thebet365/shared';
const FALLBACK_ORDER = ['en-US', 'zh-CN', 'ms-MY'];

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AuditModule } from './audit/audit.module';
import { CashbackModule } from './cashback/cashback.module';
import { ContentModule } from './content/content.module';
import { I18nModule } from './i18n/i18n.module';
@Module({
imports: [AuditModule, CashbackModule, ContentModule, I18nModule],
exports: [AuditModule, CashbackModule, ContentModule, I18nModule],
})
export class OperationsModule {}

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { SettlementService } from './settlement.service';
import { WalletModule } from '../wallet/wallet.module';
import { AgentsModule } from '../agents/agents.module';
import { WalletModule } from '../ledger/wallet.module';
import { AgentsModule } from '../agent/agents.module';
@Module({
imports: [WalletModule, AgentsModule],

View File

@@ -1,9 +1,9 @@
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { WalletService } from '../wallet/wallet.service';
import { AgentsService } from '../agents/agents.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { WalletService } from '../ledger/wallet.service';
import { AgentsService } from '../agent/agents.service';
import { Decimal } from '@prisma/client/runtime/library';
import { generateBatchNo } from '../common/decorators';
import { generateBatchNo } from '../../shared/common/decorators';
import {
settleSelection,
calculatePayout,
@@ -11,7 +11,7 @@ import {
ScoreInput,
FT_CORRECT_SCORE_TEMPLATE,
HT_CORRECT_SCORE_TEMPLATE,
} from './settlement-calculator';
} from './domain/settlement-calculator';
@Injectable()
export class SettlementService {

View File

@@ -2,7 +2,7 @@ import {
settleSelection,
calculatePayout,
isQuarterHandicapOrTotal,
} from './settlement/settlement-calculator';
} from './domains/settlement/domain/settlement-calculator';
/**
* Agent credit & wallet integration scenarios (A001-A007)

View File

@@ -1,14 +0,0 @@
import { Module } from '@nestjs/common';
import { PlayerController } from './player.controller';
import { UsersModule } from '../users/users.module';
import { WalletModule } from '../wallet/wallet.module';
import { MatchesModule } from '../matches/matches.module';
import { BetsModule } from '../bets/bets.module';
import { ContentModule } from '../content/content.module';
import { CashbackModule } from '../cashback/cashback.module';
@Module({
imports: [UsersModule, WalletModule, MatchesModule, BetsModule, ContentModule, CashbackModule],
controllers: [PlayerController],
})
export class PlayerModule {}