import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; export const PLAYER_ALLOW_PASSWORD_CHANGE = 'player.allow_password_change'; export const PLAYER_ALLOW_USERNAME_CHANGE = 'player.allow_username_change'; export const AGENT_SUSPEND_FREEZE_DIRECT_PLAYERS = 'agent.suspend_freeze_direct_players'; export const AGENT_SUSPEND_BLOCK_PLAYER_LOGIN = 'agent.suspend_block_player_login'; export const AGENT_MAX_LEVEL = 'agent.max_level'; export const AGENT_DEFAULT_SUB_CREDIT_RATIO = 'agent.default_sub_credit_ratio'; export const CASHBACK_PLATFORM_DIRECT_RATE = 'cashback.platform_direct_rate'; export const CASHBACK_ADMIN_INVITE_RATE = 'cashback.admin_invite_rate'; export const INBOX_NOTIFY_DEPOSIT = 'inbox.notify.deposit'; export const INBOX_FEATURE_ENABLED = 'inbox.feature_enabled'; export const INBOX_NOTIFY_BANNER = 'inbox.notify.banner'; export const INBOX_NOTIFY_ANNOUNCEMENT = 'inbox.notify.announcement'; export type InboxNotifySettings = { /** 玩家端是否展示站内邮箱(关闭后入口直达客服) */ inboxEnabled: boolean; /** 充值审核通过/拒绝时发送站内信 */ deposit: boolean; }; export type PlatformDirectCashbackSettings = { /** 平台直属玩家默认返水比例(小数,0.01 = 1%) */ platformDirectRate: number; /** 管理员邀请注册玩家返水比例;未配置时与 platformDirectRate 相同 */ adminInviteRate: number; }; export type AgentHierarchySettings = { /** 最大代理层级;0 = 不限制 */ maxAgentLevel: number; /** 创建下级代理时默认授信占上级可用授信的比例(1–100) */ defaultSubAgentCreditRatio: number; }; export type PlayerAccountSettings = { allowPasswordChange: boolean; allowUsernameChange: boolean; }; export type AgentSuspendSettings = { /** 停用代理时是否允许级联冻结其直属玩家(需管理员显式勾选) */ suspendFreezeDirectPlayers: boolean; /** 上级代理停用时是否禁止其直属玩家登录 */ suspendBlockPlayerLogin: boolean; }; @Injectable() export class SystemConfigService { constructor(private prisma: PrismaService) {} async getBoolean(key: string, defaultValue: boolean): Promise { const row = await this.prisma.systemConfig.findUnique({ where: { configKey: key } }); if (!row) return defaultValue; return row.configValue === 'true' || row.configValue === '1'; } async setBoolean(key: string, value: boolean, description?: string) { await this.prisma.systemConfig.upsert({ where: { configKey: key }, create: { configKey: key, configValue: value ? 'true' : 'false', description, }, update: { configValue: value ? 'true' : 'false' }, }); } async getInt(key: string, defaultValue: number): Promise { const row = await this.prisma.systemConfig.findUnique({ where: { configKey: key } }); if (!row) return defaultValue; const parsed = parseInt(row.configValue, 10); return Number.isFinite(parsed) ? parsed : defaultValue; } async setInt(key: string, value: number, description?: string) { await this.prisma.systemConfig.upsert({ where: { configKey: key }, create: { configKey: key, configValue: String(value), description, }, update: { configValue: String(value) }, }); } async getPlayerAccountSettings(): Promise { const [allowPasswordChange, allowUsernameChange] = await Promise.all([ this.getBoolean(PLAYER_ALLOW_PASSWORD_CHANGE, true), this.getBoolean(PLAYER_ALLOW_USERNAME_CHANGE, false), ]); return { allowPasswordChange, allowUsernameChange }; } async updatePlayerAccountSettings(data: Partial) { if (data.allowPasswordChange !== undefined) { await this.setBoolean( PLAYER_ALLOW_PASSWORD_CHANGE, data.allowPasswordChange, '玩家是否可在客户端修改密码', ); } if (data.allowUsernameChange !== undefined) { await this.setBoolean( PLAYER_ALLOW_USERNAME_CHANGE, data.allowUsernameChange, '玩家是否可在客户端修改登录账号名', ); } return this.getPlayerAccountSettings(); } async getAgentSuspendSettings(): Promise { const [suspendFreezeDirectPlayers, suspendBlockPlayerLogin] = await Promise.all([ this.getBoolean(AGENT_SUSPEND_FREEZE_DIRECT_PLAYERS, false), this.getBoolean(AGENT_SUSPEND_BLOCK_PLAYER_LOGIN, false), ]); return { suspendFreezeDirectPlayers, suspendBlockPlayerLogin }; } async updateAgentSuspendSettings(data: Partial) { if (data.suspendFreezeDirectPlayers !== undefined) { await this.setBoolean( AGENT_SUSPEND_FREEZE_DIRECT_PLAYERS, data.suspendFreezeDirectPlayers, '停用代理时是否允许级联冻结直属玩家', ); } if (data.suspendBlockPlayerLogin !== undefined) { await this.setBoolean( AGENT_SUSPEND_BLOCK_PLAYER_LOGIN, data.suspendBlockPlayerLogin, '上级代理停用时是否禁止直属玩家登录', ); } return this.getAgentSuspendSettings(); } async getAgentHierarchySettings(): Promise { const [maxAgentLevel, defaultSubAgentCreditRatio] = await Promise.all([ this.getInt(AGENT_MAX_LEVEL, 0), this.getInt(AGENT_DEFAULT_SUB_CREDIT_RATIO, 50), ]); return { maxAgentLevel, defaultSubAgentCreditRatio }; } async updateAgentHierarchySettings(data: Partial) { if (data.maxAgentLevel !== undefined) { if (!Number.isInteger(data.maxAgentLevel) || data.maxAgentLevel < 0) { throw new Error('maxAgentLevel must be a non-negative integer'); } await this.setInt( AGENT_MAX_LEVEL, data.maxAgentLevel, '最大代理层级;0 表示不限制', ); } if (data.defaultSubAgentCreditRatio !== undefined) { if ( !Number.isInteger(data.defaultSubAgentCreditRatio) || data.defaultSubAgentCreditRatio < 1 || data.defaultSubAgentCreditRatio > 100 ) { throw new Error('defaultSubAgentCreditRatio must be an integer between 1 and 100'); } await this.setInt( AGENT_DEFAULT_SUB_CREDIT_RATIO, data.defaultSubAgentCreditRatio, '创建下级代理时默认授信占上级可用授信的比例(%)', ); } return this.getAgentHierarchySettings(); } async getDecimalRate(key: string, defaultValue = 0): Promise { const row = await this.prisma.systemConfig.findUnique({ where: { configKey: key } }); if (!row) return defaultValue; const parsed = parseFloat(row.configValue); return Number.isFinite(parsed) && parsed >= 0 ? parsed : defaultValue; } async setDecimalRate(key: string, value: number, description?: string) { const safe = Math.max(0, value); await this.prisma.systemConfig.upsert({ where: { configKey: key }, create: { configKey: key, configValue: String(safe), description, }, update: { configValue: String(safe) }, }); } async getPlatformDirectCashbackSettings(): Promise { const platformDirectRate = await this.getDecimalRate(CASHBACK_PLATFORM_DIRECT_RATE, 0); const adminInviteConfigured = await this.prisma.systemConfig.findUnique({ where: { configKey: CASHBACK_ADMIN_INVITE_RATE }, }); const adminInviteRate = adminInviteConfigured ? await this.getDecimalRate(CASHBACK_ADMIN_INVITE_RATE, platformDirectRate) : platformDirectRate; return { platformDirectRate, adminInviteRate }; } async updatePlatformDirectCashbackSettings(data: { platformDirectRate?: number; adminInviteRate?: number; }) { if (data.platformDirectRate !== undefined) { if (!Number.isFinite(data.platformDirectRate) || data.platformDirectRate < 0) { throw new Error('platformDirectRate must be a non-negative number'); } await this.setDecimalRate( CASHBACK_PLATFORM_DIRECT_RATE, data.platformDirectRate, '平台直属玩家默认返水比例(小数,如 0.01 = 1%)', ); } if (data.adminInviteRate !== undefined) { if (!Number.isFinite(data.adminInviteRate) || data.adminInviteRate < 0) { throw new Error('adminInviteRate must be a non-negative number'); } await this.setDecimalRate( CASHBACK_ADMIN_INVITE_RATE, data.adminInviteRate, '管理员邀请注册玩家返水比例(小数,如 0.01 = 1%)', ); } return this.getPlatformDirectCashbackSettings(); } async getInboxFeatureEnabled(): Promise { return this.getBoolean(INBOX_FEATURE_ENABLED, true); } async getInboxNotifySettings(): Promise { const [inboxEnabled, deposit] = await Promise.all([ this.getBoolean(INBOX_FEATURE_ENABLED, true), this.getBoolean(INBOX_NOTIFY_DEPOSIT, true), ]); return { inboxEnabled, deposit }; } async updateInboxNotifySettings(data: Partial) { if (data.inboxEnabled !== undefined) { await this.setBoolean( INBOX_FEATURE_ENABLED, data.inboxEnabled, '玩家端是否开启站内邮箱功能', ); } if (data.deposit !== undefined) { await this.setBoolean( INBOX_NOTIFY_DEPOSIT, data.deposit, '充值审核结果是否通过站内邮箱通知玩家', ); } return this.getInboxNotifySettings(); } async getDepositScreenshotCleanupConfig(): Promise<{ enabled: boolean; keepDays: number }> { const enabled = await this.getBoolean('deposit.cleanup.enabled', false); const keepDays = await this.getInt('deposit.cleanup.keep_days', 180); return { enabled, keepDays }; } async updateDepositScreenshotCleanupConfig(data: { enabled?: boolean; keepDays?: number }) { if (data.enabled !== undefined) { await this.setBoolean( 'deposit.cleanup.enabled', data.enabled, '是否开启定时清理充值截图', ); } if (data.keepDays !== undefined) { if (!Number.isInteger(data.keepDays) || data.keepDays <= 0) { throw new Error('keepDays must be a positive integer'); } await this.setInt( 'deposit.cleanup.keep_days', data.keepDays, '充值截图保留天数', ); } return this.getDepositScreenshotCleanupConfig(); } }