| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- import { FastifyInstance } from 'fastify'
- import { Like, Not, Repository } from 'typeorm'
- import Decimal from 'decimal.js'
- import { ConfigType, SysConfig } from '../entities/sys-config.entity'
- import {
- CreateSysConfigBody,
- CreateTeamConfigBody,
- ListTeamConfigQuery,
- UpdateSysConfigBody,
- UpdateTeamConfigBody
- } from '../dto/sys-config.dto'
- import { User, UserRole } from '../entities/user.entity'
- import { Team } from '../entities/team.entity'
- import { TeamMembers } from '../entities/team-members.entity'
- import { Member } from '../entities/member.entity'
- export class SysConfigService {
- private app: FastifyInstance
- private sysConfigRepository: Repository<SysConfig>
- private userRepository: Repository<User>
- private teamRepository: Repository<Team>
- private teamMembersRepository: Repository<TeamMembers>
- private memberRepository: Repository<Member>
- constructor(app: FastifyInstance) {
- this.app = app
- this.sysConfigRepository = app.dataSource.getRepository(SysConfig)
- this.userRepository = app.dataSource.getRepository(User)
- this.teamRepository = app.dataSource.getRepository(Team)
- this.teamMembersRepository = app.dataSource.getRepository(TeamMembers)
- this.memberRepository = app.dataSource.getRepository(Member)
- }
- async getSysConfig(name: string, teamId?: number) {
- const where: any = { name }
- if (teamId !== undefined) {
- where.teamId = teamId
- }
- const sysConfig = await this.sysConfigRepository.findOneOrFail({ where })
- return sysConfig
- }
- async maxTransferAmount(defaultAmount: Decimal) {
- const sysConfig = await this.sysConfigRepository.findOne({ where: { name: 'max_transfer_amount' } })
- if (sysConfig) {
- return new Decimal(sysConfig.value)
- }
- await this.sysConfigRepository.save({ name: 'max_transfer_amount', value: defaultAmount.toString() })
- return defaultAmount
- }
- async replaceType(defaultValue: number) {
- try {
- const sysConfig = await this.sysConfigRepository.findOne({ where: { name: 'replace_type' } })
- if (sysConfig) {
- return Number(sysConfig.value)
- }
- await this.sysConfigRepository.save({ name: 'replace_type', value: defaultValue.toString() })
- } catch (e) {
- this.app.log.error(e, 'get replaceType error')
- }
- return defaultValue
- }
- async getSensitiveWords() {
- try {
- const config = await this.sysConfigRepository.findOne({ where: { name: 'sensitive_words' } })
- if (config) {
- return { value: config.value }
- }
- } catch (e) {
- this.app.log.error(e, 'get sensitiveWords error')
- }
- return null
- }
- async updateSensitiveWords(words: string) {
- try {
- const config = await this.sysConfigRepository.findOne({ where: { name: 'sensitive_words' } })
- if (config) {
- config.value = words
- const sysConfig = await this.sysConfigRepository.save(config)
- return {
- value: sysConfig.value
- }
- }
- await this.sysConfigRepository.save({ name: 'sensitive_words', value: words })
- } catch (e) {
- this.app.log.error(e, 'update sensitiveWords error')
- }
- }
- /**
- * 从 sysconfig 读取 domain_redirect 和 domain_land 配置
- * 将逗号分隔的字符串转换为列表返回
- * @param teamId 可选的团队ID,如果不提供则读取全局配置(teamId=0),如果为0则返回所有团队的配置(不筛选)
- * @returns 包含 domainRedirect 和 domainLand 两个列表的对象
- */
- async getDomainLists(teamId?: number): Promise<{ domainRedirect: string[]; domainLand: string[] }> {
- const targetTeamId = teamId !== undefined ? teamId : 0
-
- // 读取 domain_redirect 配置
- let domainRedirect: string[] = []
- try {
- if (targetTeamId === 0) {
- // teamId 为 0 时,查询所有团队的配置并合并
- const redirectConfigs = await this.sysConfigRepository.find({
- where: { name: 'domain_redirect' }
- })
- this.app.log.info(`查询 domain_redirect 配置,找到 ${redirectConfigs.length} 条记录`)
- const allDomains = new Set<string>()
- redirectConfigs.forEach(config => {
- this.app.log.info(`处理配置: teamId=${config.teamId}, name=${config.name}, value=${config.value}`)
- if (config && config.value) {
- const domains = config.value
- .split(',')
- .map(item => item.trim())
- .filter(item => item.length > 0)
- this.app.log.info(`解析出域名: ${domains.join(', ')}`)
- domains.forEach(domain => allDomains.add(domain))
- }
- })
- domainRedirect = Array.from(allDomains)
- this.app.log.info(`最终 domainRedirect 列表: ${domainRedirect.join(', ')}`)
- } else {
- // 查询指定团队的配置
- const redirectConfig = await this.sysConfigRepository.findOne({
- where: { name: 'domain_redirect', teamId: targetTeamId }
- })
- this.app.log.info(`查询指定团队配置: teamId=${targetTeamId}, 找到配置: ${redirectConfig ? '是' : '否'}`)
- if (redirectConfig && redirectConfig.value) {
- this.app.log.info(`配置值: ${redirectConfig.value}`)
- domainRedirect = redirectConfig.value
- .split(',')
- .map(item => item.trim())
- .filter(item => item.length > 0)
- }
- }
- } catch (e) {
- this.app.log.error(e, '读取 domain_redirect 配置失败')
- }
- // 读取 domain_land 配置
- let domainLand: string[] = []
- try {
- if (targetTeamId === 0) {
- // teamId 为 0 时,查询所有团队的配置并合并
- const landConfigs = await this.sysConfigRepository.find({
- where: { name: 'domain_land' }
- })
- this.app.log.info(`查询 domain_land 配置,找到 ${landConfigs.length} 条记录`)
- const allDomains = new Set<string>()
- landConfigs.forEach(config => {
- this.app.log.info(`处理配置: teamId=${config.teamId}, name=${config.name}, value=${config.value}`)
- if (config && config.value) {
- const domains = config.value
- .split(',')
- .map(item => item.trim())
- .filter(item => item.length > 0)
- this.app.log.info(`解析出域名: ${domains.join(', ')}`)
- domains.forEach(domain => allDomains.add(domain))
- }
- })
- domainLand = Array.from(allDomains)
- this.app.log.info(`最终 domainLand 列表: ${domainLand.join(', ')}`)
- } else {
- // 查询指定团队的配置
- const landConfig = await this.sysConfigRepository.findOne({
- where: { name: 'domain_land', teamId: targetTeamId }
- })
- this.app.log.info(`查询指定团队配置: teamId=${targetTeamId}, 找到配置: ${landConfig ? '是' : '否'}`)
- if (landConfig && landConfig.value) {
- this.app.log.info(`配置值: ${landConfig.value}`)
- domainLand = landConfig.value
- .split(',')
- .map(item => item.trim())
- .filter(item => item.length > 0)
- }
- }
- } catch (e) {
- this.app.log.error(e, '读取 domain_land 配置失败')
- }
- return {
- domainRedirect,
- domainLand
- }
- }
- async create(data: CreateSysConfigBody) {
- const where: any = { name: data.name }
- if (data.teamId !== undefined) {
- where.teamId = data.teamId
- }
- const existingConfig = await this.sysConfigRepository.findOne({ where })
- if (existingConfig) {
- throw new Error('操作失败')
- }
- const config = this.sysConfigRepository.create(data)
- return await this.sysConfigRepository.save(config)
- }
- async update(name: string, data: UpdateSysConfigBody, teamId?: number) {
- const config = await this.getSysConfig(name, teamId)
- Object.assign(config, data)
- return await this.sysConfigRepository.save(config)
- }
- async delete(name: string, teamId?: number) {
- const config = await this.getSysConfig(name, teamId)
- return await this.sysConfigRepository.remove(config)
- }
- async list(page: number = 0, size: number = 20, name?: string, type?: ConfigType, teamId?: number) {
- const queryBuilder = this.sysConfigRepository.createQueryBuilder('config')
- .where('config.name != :excludedName', { excludedName: 'sensitive_words' })
- if (name) {
- queryBuilder.andWhere('config.name LIKE :name', { name: `%${name}%` })
- }
- if (type) {
- queryBuilder.andWhere('config.type = :type', { type })
- }
- if (teamId !== undefined) {
- queryBuilder.andWhere('config.teamId = :teamId', { teamId })
- }
- // 排序:teamId为0的展示在最上方,其他的按照teamId排序
- queryBuilder
- .orderBy('CASE WHEN config.teamId = 0 THEN 0 ELSE 1 END', 'ASC')
- .addOrderBy('config.teamId', 'ASC')
- .addOrderBy('config.id', 'ASC')
- const total = await queryBuilder.getCount()
- const data = await queryBuilder
- .skip(page * size)
- .take(size)
- .getMany()
- return {
- data,
- meta: {
- page,
- size,
- total,
- totalPages: Math.ceil(total / size)
- }
- }
- }
- async getConfigTypes() {
- return Object.values(ConfigType)
- }
- // 获取用户的团队ID
- async getUserTeamId(userId: number, userRole: UserRole): Promise<number> {
- if (userRole === UserRole.ADMIN) {
- throw new Error('管理员不需要团队ID')
- }
- if (userRole === UserRole.USER) {
- throw new Error('普通用户无权限访问团队配置')
- }
- if (userRole === UserRole.TEAM) {
- // 队长从team表中获取teamId
- const team = await this.teamRepository.findOne({ where: { userId } })
- if (!team) {
- throw new Error('未找到该用户的团队信息')
- }
- return team.id
- } else {
- // 队员从team-members表中获取teamId
- const teamMember = await this.teamMembersRepository.findOne({ where: { userId } })
- if (!teamMember) {
- throw new Error('未找到该用户的团队成员信息')
- }
- return teamMember.teamId
- }
- }
- // 团队配置相关方法
- async createTeamConfig(data: CreateTeamConfigBody, userId: number, userRole: UserRole, adminTeamId?: number) {
- let teamId: number
- if (userRole === UserRole.ADMIN) {
- if (adminTeamId === undefined) {
- throw new Error('管理员操作团队配置时必须指定teamId')
- }
- teamId = adminTeamId
- } else {
- teamId = await this.getUserTeamId(userId, userRole)
- }
- const existingConfig = await this.sysConfigRepository.findOne({
- where: { name: data.name, teamId }
- })
- if (existingConfig) {
- throw new Error('该团队配置名称已存在')
- }
- const config = this.sysConfigRepository.create({ ...data, teamId })
- return await this.sysConfigRepository.save(config)
- }
- async updateTeamConfig(
- name: string,
- data: UpdateTeamConfigBody,
- userId: number,
- userRole: UserRole,
- adminTeamId?: number
- ) {
- let teamId: number
- if (userRole === UserRole.ADMIN) {
- if (adminTeamId === undefined) {
- throw new Error('管理员操作团队配置时必须指定teamId')
- }
- teamId = adminTeamId
- } else {
- teamId = await this.getUserTeamId(userId, userRole)
- }
- const config = await this.getSysConfig(name, teamId)
- Object.assign(config, data)
- return await this.sysConfigRepository.save(config)
- }
- async deleteTeamConfig(name: string, userId: number, userRole: UserRole, adminTeamId?: number) {
- let teamId: number
- if (userRole === UserRole.ADMIN) {
- if (adminTeamId === undefined) {
- throw new Error('管理员操作团队配置时必须指定teamId')
- }
- teamId = adminTeamId
- } else {
- teamId = await this.getUserTeamId(userId, userRole)
- }
- const config = await this.getSysConfig(name, teamId)
- return await this.sysConfigRepository.remove(config)
- }
- async getTeamConfig(name: string, userId: number, userRole: UserRole, adminTeamId?: number) {
- if (userRole === UserRole.ADMIN) {
- return await this.getSysConfig(name, adminTeamId)
- } else {
- const teamId = await this.getUserTeamId(userId, userRole)
- return await this.getSysConfig(name, teamId)
- }
- }
- async listTeamConfigs(query: ListTeamConfigQuery, userId: number, userRole: UserRole, adminTeamId?: number) {
- if (userRole === UserRole.ADMIN) {
- const { page = 0, size = 20, name, type } = query
- return await this.list(page, size, name, type, adminTeamId)
- } else {
- const teamId = await this.getUserTeamId(userId, userRole)
- const { page = 0, size = 20, name, type } = query
- return await this.list(page, size, name, type, teamId)
- }
- }
- /**
- * 从 parentId 递归查找 team 用户,直到找到 team 或到达顶层
- * @param parentId 父用户ID
- * @param maxDepth 最大递归深度,防止无限循环
- * @returns teamId,如果找不到则返回 0
- */
- private async findTeamIdByParentId(parentId: number, maxDepth: number = 10): Promise<number> {
- if (maxDepth <= 0 || !parentId || parentId <= 0) {
- return 0
- }
- try {
- const parentUser = await this.userRepository.findOne({ where: { id: parentId } })
- if (!parentUser) {
- return 0
- }
- // 如果父用户是 team 用户,直接查找对应的 team
- if (parentUser.role === UserRole.TEAM) {
- const team = await this.teamRepository.findOne({ where: { userId: parentId } })
- if (team) {
- return team.id
- }
- }
- // 如果父用户是推广员(promoter),可能是二级代理,继续向上查找
- if (parentUser.role === UserRole.PROMOTER && parentUser.parentId && parentUser.parentId > 0) {
- return await this.findTeamIdByParentId(parentUser.parentId, maxDepth - 1)
- }
- // 如果父用户有 parentId,继续向上查找
- if (parentUser.parentId && parentUser.parentId > 0) {
- return await this.findTeamIdByParentId(parentUser.parentId, maxDepth - 1)
- }
- } catch (error) {
- this.app.log.warn({ err: error, parentId }, 'Failed to find team by parentId')
- }
- return 0
- }
- async getUserTeamConfigs(userId?: number) {
- let teamId = 0
-
- if (userId) {
- // 方案1:优先从 member 表读取 teamId
- try {
- const member = await this.memberRepository.findOne({ where: { userId } })
- if (member && member.teamId && member.teamId > 0) {
- teamId = member.teamId
- }
- } catch (error) {
- this.app.log.warn({ err: error, userId }, 'Failed to get teamId from member table')
- }
- // 方案2:如果 member 表中没有有效的 teamId,从 parentId 递归查找 team
- if (teamId === 0) {
- try {
- const user = await this.userRepository.findOne({ where: { id: userId } })
- if (user && user.parentId && user.parentId > 0) {
- teamId = await this.findTeamIdByParentId(user.parentId)
- }
- } catch (error) {
- this.app.log.warn({ err: error, userId }, 'Failed to get teamId from parentId')
- }
- }
- }
- return await this.sysConfigRepository.find({
- where: { teamId },
- select: ['name', 'value'],
- order: { id: 'ASC' }
- })
- }
- // 创建团队默认配置
- async createDefaultTeamConfigs(teamId: number) {
- const defaultConfigs = [
- { name: 'hourly', value: '15', remark: '包时会员', type: ConfigType.Number },
- { name: 'daily', value: '28', remark: '包天会员', type: ConfigType.Number },
- { name: 'weekly', value: '58', remark: '包周会员', type: ConfigType.Number },
- { name: 'monthly', value: '88', remark: '包月会员', type: ConfigType.Number },
- { name: 'quarterly', value: '128', remark: '包季会员', type: ConfigType.Number },
- { name: 'yearly', value: '198', remark: '包年会员', type: ConfigType.Number },
- { name: 'lifetime', value: '299', remark: '终生会员', type: ConfigType.Number },
- { name: 'single', value: '6', remark: '单片价格', type: ConfigType.Number },
- { name: 'preview_duration', value: '30', remark: '试看时间(秒)', type: ConfigType.Number },
- { name: 'invite_reward_count', value: '3', remark: '邀请奖励所需次数', type: ConfigType.Number },
- { name: 'invite_ip_limit', value: '1', remark: '同一IP每日邀请注册限制次数', type: ConfigType.Number }
- ]
- const configs = defaultConfigs.map(config =>
- this.sysConfigRepository.create({
- ...config,
- teamId
- })
- )
- return await this.sysConfigRepository.save(configs)
- }
- }
|