Преглед изворни кода

新增团队域名统计功能,支持获取每日新增用户数和总收入的接口,同时更新相关服务和路由以处理统计数据的查询和返回。

wuyi пре 3 месеци
родитељ
комит
4856c63b14

+ 21 - 1
src/controllers/team-domain.controller.ts

@@ -25,7 +25,7 @@ export class TeamDomainController {
     try {
       // 检查是否包含多个域名(通过中英文逗号、分号或换行分隔)
       const hasMultipleDomains = /[,;\n\r]/.test(request.body.domain)
-      
+
       if (hasMultipleDomains) {
         // 批量创建
         const result = await this.teamDomainService.createBatch(request.body)
@@ -146,4 +146,24 @@ export class TeamDomainController {
       return reply.code(500).send({ message: '获取团队域名失败' })
     }
   }
+
+  async getDailyStatistics(request: FastifyRequest<{ Querystring: { domain: string } }>, reply: FastifyReply) {
+    try {
+      const { domain } = request.query
+      const result = await this.teamDomainService.getDailyStatistics(domain)
+      return reply.send(result)
+    } catch (error) {
+      return reply.code(500).send({ message: '获取每日统计数据失败' })
+    }
+  }
+
+  async getAllStatistics(request: FastifyRequest<{ Querystring: { domain: string } }>, reply: FastifyReply) {
+    try {
+      const { domain } = request.query
+      const result = await this.teamDomainService.getAllStatistics(domain)
+      return reply.send(result)
+    } catch (error) {
+      return reply.code(500).send({ message: '获取总统计数据失败' })
+    }
+  }
 }

+ 9 - 0
src/entities/member.entity.ts

@@ -21,6 +21,9 @@ export enum MemberStatus {
 @Index('idx_member_user_id', ['userId'])
 @Index('idx_member_vip_status', ['vipLevel', 'status'])
 @Index('idx_member_created_at', ['createdAt'])
+@Index('idx_member_domain_id', ['domainId'])
+@Index('idx_member_domain_created', ['domainId', 'createdAt'])
+@Index('idx_member_team_id', ['teamId'])
 export class Member {
   @PrimaryGeneratedColumn()
   id: number
@@ -28,6 +31,12 @@ export class Member {
   @Column()
   userId: number
 
+  @Column({ nullable: true, default: 1 })
+  teamId: number
+
+  @Column({ nullable: true, default: 0 })
+  domainId: number
+
   @Column({ unique: true, length: 100, nullable: true })
   email: string
 

+ 15 - 0
src/routes/team-domain.routes.ts

@@ -60,4 +60,19 @@ export default async function teamDomainRoutes(fastify: FastifyInstance) {
     { onRequest: [authenticate, hasAnyRole(UserRole.ADMIN, UserRole.TEAM, UserRole.PROMOTER)] },
     teamDomainController.findByTeamId.bind(teamDomainController)
   )
+
+  // 统计各个域名下今日新增用户数以及收入
+  fastify.get<{ Querystring: { domain: string } }>(
+    '/statistics/daily',
+    { onRequest: [authenticate, hasAnyRole(UserRole.ADMIN)] },
+    teamDomainController.getDailyStatistics.bind(teamDomainController)
+  )
+
+  // 统计各个域名下所有新增用户数以及收入
+  fastify.get<{ Querystring: { domain: string } }>(
+    '/statistics/all',
+    { onRequest: [authenticate, hasAnyRole(UserRole.ADMIN)] },
+    teamDomainController.getAllStatistics.bind(teamDomainController)
+  )
+
 }

+ 1 - 0
src/services/income-records.service.ts

@@ -222,6 +222,7 @@ export class IncomeRecordsService {
       .where('record.createdAt >= :start', { start })
       .andWhere('record.createdAt <= :end', { end })
       .andWhere('record.delFlag = :delFlag', { delFlag: false })
+      .andWhere('record.status = :status', { status: true })
       .setParameter('tipType', IncomeType.TIP)
       .setParameter('commissionType', IncomeType.COMMISSION)
       .andWhere(agentId ? 'record.agentId = :agentId' : '1=1', { agentId })

+ 15 - 0
src/services/member.service.ts

@@ -45,12 +45,15 @@ export class MemberService {
       }
 
       let parentId = 1
+      let teamId = 1
+      let domainId = 0
 
       if (code) {
         // 使用 code 查找团队
         const team = await manager.findOne(Team, { where: { affCode: code } })
         if (team) {
           parentId = team.userId
+          teamId = team.id
         }
       } else if (domain) {
         let domainName = domain
@@ -70,7 +73,9 @@ export class MemberService {
           const team = await manager.findOne(Team, { where: { id: teamDomain.teamId } })
           if (team) {
             parentId = team.userId
+            teamId = team.id
           }
+          domainId = teamDomain.id
         }
       }
 
@@ -82,6 +87,8 @@ export class MemberService {
       const savedUser = await manager.save(user)
       const member = manager.create(Member, {
         userId: savedUser.id,
+        teamId,
+        domainId,
         vipLevel: VipLevel.GUEST,
         status: MemberStatus.ACTIVE,
         ip: ip || 'unknown',
@@ -174,6 +181,8 @@ export class MemberService {
 
   async create(data: {
     userId: number
+    teamId?: number
+    domainId?: number
     email?: string
     phone?: string
     vipLevel?: VipLevel
@@ -183,6 +192,8 @@ export class MemberService {
   }): Promise<Member> {
     const member = this.memberRepository.create({
       userId: data.userId,
+      teamId: data.teamId,
+      domainId: data.domainId,
       email: data.email,
       phone: data.phone,
       vipLevel: data.vipLevel || VipLevel.GUEST,
@@ -348,6 +359,8 @@ export class MemberService {
       // 获取推荐团队
       const team = await manager.findOne(Team, { where: { affCode: code } })
       const parentId = team ? team.userId : 1
+      const teamId = team ? team.id : 1
+      const domainId = 0
 
       // 创建用户
       const hashedPassword = await bcrypt.hash(password, 10)
@@ -362,6 +375,8 @@ export class MemberService {
       // 创建会员
       const member = manager.create(Member, {
         userId: savedUser.id,
+        teamId,
+        domainId,
         email,
         phone,
         vipLevel: VipLevel.FREE,

+ 114 - 7
src/services/team-domain.service.ts

@@ -1,4 +1,4 @@
-import { Repository, Like } from 'typeorm'
+import { Repository, Like, Between } from 'typeorm'
 import { FastifyInstance } from 'fastify'
 import { TeamDomain } from '../entities/team-domain.entity'
 import { PaginationResponse } from '../dto/common.dto'
@@ -6,14 +6,20 @@ import { CreateTeamDomainBody, UpdateTeamDomainBody, ListTeamDomainQuery } from
 import { UserService } from './user.service'
 import { TeamService } from './team.service'
 import { TeamMembersService } from './team-members.service'
+import { Member } from '../entities/member.entity'
+import { IncomeRecords } from '../entities/income-records.entity'
 
 export class TeamDomainService {
   private teamDomainRepository: Repository<TeamDomain>
+  private memberRepository: Repository<Member>
+  private incomeRecordsRepository: Repository<IncomeRecords>
   private userService: UserService
   private teamService: TeamService
   private teamMembersService: TeamMembersService
   constructor(app: FastifyInstance) {
     this.teamDomainRepository = app.dataSource.getRepository(TeamDomain)
+    this.memberRepository = app.dataSource.getRepository(Member)
+    this.incomeRecordsRepository = app.dataSource.getRepository(IncomeRecords)
     this.userService = new UserService(app)
     this.teamService = new TeamService(app)
     this.teamMembersService = new TeamMembersService(app)
@@ -33,18 +39,20 @@ export class TeamDomainService {
     return this.teamDomainRepository.save(teamDomain)
   }
 
-  async createBatch(data: CreateTeamDomainBody): Promise<{ success: TeamDomain[], failed: { domain: string, error: string }[] }> {
+  async createBatch(
+    data: CreateTeamDomainBody
+  ): Promise<{ success: TeamDomain[]; failed: { domain: string; error: string }[] }> {
     await this.teamService.findById(data.teamId)
 
     // 解析域名字符串,支持逗号和换行分隔
     const domains = this.parseDomains(data.domain)
-    
+
     if (domains.length === 0) {
       throw new Error('没有有效的域名')
     }
 
     const success: TeamDomain[] = []
-    const failed: { domain: string, error: string }[] = []
+    const failed: { domain: string; error: string }[] = []
 
     // 检查已存在的域名
     const existingDomains = await this.teamDomainRepository.find({
@@ -54,16 +62,16 @@ export class TeamDomainService {
 
     // 批量创建域名
     const domainsToCreate = domains.filter(domain => !existingDomainSet.has(domain))
-    
+
     if (domainsToCreate.length > 0) {
-      const teamDomains = domainsToCreate.map(domain => 
+      const teamDomains = domainsToCreate.map(domain =>
         this.teamDomainRepository.create({
           teamId: data.teamId,
           domain: domain.trim(),
           description: data.description
         })
       )
-      
+
       const savedDomains = await this.teamDomainRepository.save(teamDomains)
       success.push(...savedDomains)
     }
@@ -193,4 +201,103 @@ export class TeamDomainService {
 
     return groupedData
   }
+
+  async getDailyStatistics(domain?: string): Promise<{ id: number; domain: string; todayNewUsers: number; todayIncome: number }[]> {
+    // 获取今天的开始和结束时间
+    const today = new Date()
+    today.setHours(0, 0, 0, 0)
+    const tomorrow = new Date(today)
+    tomorrow.setDate(tomorrow.getDate() + 1)
+
+    // 构建查询条件
+    const whereCondition: any = {}
+    if (domain) {
+      whereCondition.domain = domain
+    }
+
+    // 获取域名
+    const teamDomains = await this.teamDomainRepository.find({
+      where: whereCondition
+    })
+
+    const results: { id: number; domain: string; todayNewUsers: number; todayIncome: number }[] = []
+
+    for (const teamDomain of teamDomains) {
+      // 统计今日新增用户数
+      const todayNewUsers = await this.memberRepository.count({
+        where: {
+          domainId: teamDomain.id,
+          createdAt: Between(today, tomorrow)
+        }
+      })
+
+      // 统计今日收入
+      const todayIncomeRecords = await this.incomeRecordsRepository
+        .createQueryBuilder('record')
+        .innerJoin('member', 'm', 'm.userId = record.userId')
+        .select('SUM(CAST(record.incomeAmount AS DECIMAL(10,5)))', 'totalIncome')
+        .where('m.domainId = :domainId', { domainId: teamDomain.id })
+        .andWhere('record.createdAt >= :startDate', { startDate: today })
+        .andWhere('record.createdAt < :endDate', { endDate: tomorrow })
+        .andWhere('record.delFlag = :delFlag', { delFlag: false })
+        .andWhere('record.status = :status', { status: true })
+        .getRawOne()
+
+      const todayIncome = todayIncomeRecords?.totalIncome ? parseFloat(todayIncomeRecords.totalIncome) : 0
+
+      results.push({
+        id: teamDomain.id,
+        domain: teamDomain.domain,
+        todayNewUsers,
+        todayIncome
+      })
+    }
+
+    return results
+  }
+
+  async getAllStatistics(domain?: string): Promise<{ id: number; domain: string; totalNewUsers: number; totalIncome: number }[]> {
+    // 构建查询条件
+    const whereCondition: any = {}
+    if (domain) {
+      whereCondition.domain = domain
+    }
+
+    // 获取域名
+    const teamDomains = await this.teamDomainRepository.find({
+      where: whereCondition
+    })
+
+    const results: { id: number; domain: string; totalNewUsers: number; totalIncome: number }[] = []
+
+    for (const teamDomain of teamDomains) {
+      // 统计总新增用户数
+      const totalNewUsers = await this.memberRepository.count({
+        where: {
+          domainId: teamDomain.id
+        }
+      })
+
+      // 统计总收入
+      const totalIncomeRecords = await this.incomeRecordsRepository
+        .createQueryBuilder('record')
+        .innerJoin('member', 'm', 'm.userId = record.userId')
+        .select('SUM(CAST(record.incomeAmount AS DECIMAL(10,5)))', 'totalIncome')
+        .where('m.domainId = :domainId', { domainId: teamDomain.id })
+        .andWhere('record.delFlag = :delFlag', { delFlag: false })
+        .andWhere('record.status = :status', { status: true })
+        .getRawOne()
+
+      const totalIncome = totalIncomeRecords?.totalIncome ? parseFloat(totalIncomeRecords.totalIncome) : 0
+
+      results.push({
+        id: teamDomain.id,
+        domain: teamDomain.domain,
+        totalNewUsers,
+        totalIncome
+      })
+    }
+
+    return results
+  }
 }