Explorar el Código

更新鱼类统计接口,支持按所有者 ID 获取统计数据,并添加最近 7 天的详细统计信息。同时,优化相关 DTO 和服务逻辑。

wuyi hace 4 meses
padre
commit
e10740c96d
Se han modificado 4 ficheros con 126 adiciones y 26 borrados
  1. 13 10
      src/controllers/fish.controller.ts
  2. 4 0
      src/dto/fish.dto.ts
  3. 2 2
      src/routes/fish.routes.ts
  4. 107 14
      src/services/fish.service.ts

+ 13 - 10
src/controllers/fish.controller.ts

@@ -1,12 +1,8 @@
 import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
 import { FishService } from '../services/fish.service'
 import { ResultEnum } from '../entities/fish.entity'
-import {
-  ListFishQuery,
-  CreateFishBody,
-  UpdateFishBody,
-  DeleteFishBody
-} from '../dto/fish.dto'
+import { UserRole } from '../entities/user.entity'
+import { ListFishQuery, CreateFishBody, UpdateFishBody, DeleteFishBody, StatisticsQuery } from '../dto/fish.dto'
 
 export class FishController {
   private fishService: FishService
@@ -166,9 +162,16 @@ export class FishController {
     }
   }
 
-  async getStatistics(request: FastifyRequest, reply: FastifyReply) {
+  async getStatistics(request: FastifyRequest<{ Querystring: StatisticsQuery }>, reply: FastifyReply) {
     try {
-      const statistics = await this.fishService.getStatistics()
+      const { ownerId } = request.query
+
+      let currentOwnerId = ownerId
+
+      if (request.user.role !== UserRole.ADMIN) {
+        currentOwnerId = request.user.id
+      }
+      const statistics = await this.fishService.getStatistics(currentOwnerId)
 
       return reply.send({
         statistics
@@ -196,12 +199,12 @@ export class FishController {
   async findByResult(request: FastifyRequest<{ Querystring: { result: string } }>, reply: FastifyReply) {
     try {
       const { result } = request.query
-      
+
       // 验证 result 参数是否为有效的枚举值
       if (!Object.values(ResultEnum).includes(result as ResultEnum)) {
         return reply.code(400).send({ message: '无效的结果状态值' })
       }
-      
+
       const fishList = await this.fishService.findByResult(result as ResultEnum)
 
       return reply.send({

+ 4 - 0
src/dto/fish.dto.ts

@@ -14,6 +14,10 @@ export interface ListFishQuery extends Pagination {
   loginTime?: string // 日期格式: '2025-09-02'
 }
 
+export interface StatisticsQuery {
+  ownerId?: number
+}
+
 export interface CreateFishBody {
   id: string
   name?: string

+ 2 - 2
src/routes/fish.routes.ts

@@ -2,7 +2,7 @@ import { FastifyInstance } from 'fastify'
 import { FishController } from '../controllers/fish.controller'
 import { authenticate, hasRole } from '../middlewares/auth.middleware'
 import { UserRole } from '../entities/user.entity'
-import { ListFishQuery, CreateFishBody, UpdateFishBody, DeleteFishBody } from '../dto/fish.dto'
+import { ListFishQuery, CreateFishBody, UpdateFishBody, DeleteFishBody, StatisticsQuery } from '../dto/fish.dto'
 
 export default async function fishRoutes(fastify: FastifyInstance) {
   const fishController = new FishController(fastify)
@@ -50,7 +50,7 @@ export default async function fishRoutes(fastify: FastifyInstance) {
   )
 
   // 获取统计信息
-  fastify.get(
+  fastify.get<{ Querystring: StatisticsQuery }>(
     '/statistics',
     { onRequest: [authenticate] },
     fishController.getStatistics.bind(fishController)

+ 107 - 14
src/services/fish.service.ts

@@ -140,33 +140,126 @@ export class FishService {
     return this.fishRepository.count({ where: { ownerId } })
   }
 
-  async getStatistics(): Promise<{
+  async getStatistics(ownerId?: number): Promise<{
     total: number
     noTag: number
     tagged: number
     success: number
+    dailyStats: Array<{
+      date: string
+      newCount: number
+      taggedCount: number
+      untaggedCount: number
+    }>
   }> {
-    const [total, noTag, tagged, success] = await Promise.all([
-      this.fishRepository.count(),
-      this.countByResult(ResultEnum.NoTag),
-      this.countByResult(ResultEnum.Tagged),
-      this.countByResult(ResultEnum.Success)
+    const days = 7
+    const today = new Date()
+    today.setHours(23, 59, 59, 999)
+
+    const startDate = new Date()
+    startDate.setDate(startDate.getDate() - 6)
+    startDate.setHours(0, 0, 0, 0)
+
+    // 提取日期格式化函数
+    const formatDate = (date: Date): string => {
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    }
+
+    // 构建查询条件 - 统一处理
+    const buildWhereClause = (includeTimeRange = false) => {
+      const conditions: string[] = []
+      const parameters: any = {}
+
+      if (ownerId) {
+        conditions.push('fish.ownerId = :ownerId')
+        parameters.ownerId = ownerId
+      }
+
+      if (includeTimeRange) {
+        conditions.push('fish.createdAt >= :startDate', 'fish.createdAt <= :today')
+        parameters.startDate = startDate
+        parameters.today = today
+      }
+
+      return {
+        whereClause: conditions.length ? conditions.join(' AND ') : '1=1',
+        parameters
+      }
+    }
+
+    const baseQuery = this.fishRepository.createQueryBuilder('fish')
+
+    const [totalStats, recentData] = await Promise.all([
+      // 总体统计
+      (() => {
+        const { whereClause, parameters } = buildWhereClause()
+        return baseQuery
+          .clone()
+          .where(whereClause, parameters)
+          .select([
+            'COUNT(*) as total',
+            `SUM(CASE WHEN fish.result = '${ResultEnum.NoTag}' THEN 1 ELSE 0 END) as noTag`,
+            `SUM(CASE WHEN fish.result = '${ResultEnum.Tagged}' THEN 1 ELSE 0 END) as tagged`,
+            `SUM(CASE WHEN fish.result = '${ResultEnum.Success}' THEN 1 ELSE 0 END) as success`
+          ])
+          .getRawOne()
+      })(),
+
+      // 最近7天详细数据
+      (() => {
+        const { whereClause, parameters } = buildWhereClause(true)
+        return baseQuery.clone().where(whereClause, parameters).select(['fish.createdAt', 'fish.result']).getMany()
+      })()
     ])
+    
+    const dateRange = Array.from({ length: days }, (_, i) => {
+      const date = new Date(startDate)
+      date.setDate(startDate.getDate() + i)
+      return formatDate(date)
+    })
+
+    const dailyStatsMap = new Map(dateRange.map(date => [date, { newCount: 0, taggedCount: 0, untaggedCount: 0 }]))
+
+    recentData.forEach(item => {
+      const dateKey = formatDate(new Date(item.createdAt))
+      const stats = dailyStatsMap.get(dateKey)
+
+      if (stats) {
+        stats.newCount++
+        switch (item.result) {
+          case ResultEnum.Tagged:
+            stats.taggedCount++
+            break
+          case ResultEnum.NoTag:
+            stats.untaggedCount++
+            break
+        }
+      }
+    })
+
+    const dailyStats = dateRange.map(date => ({
+      date,
+      ...dailyStatsMap.get(date)!
+    }))
 
     return {
-      total,
-      noTag,
-      tagged,
-      success
+      total: parseInt(totalStats.total),
+      noTag: parseInt(totalStats.noTag),
+      tagged: parseInt(totalStats.tagged),
+      success: parseInt(totalStats.success),
+      dailyStats
     }
   }
 
   async exportToExcel(query?: ListFishQuery): Promise<Buffer> {
     const whereConditions: any = {}
-    
+
     if (query) {
       const { id, name, username, phone, result, ownerName, remark, createdAt, loginTime } = query
-      
+
       if (id) {
         whereConditions.id = id
       }
@@ -248,10 +341,10 @@ export class FishService {
       { wch: 20 }
     ]
     worksheet['!cols'] = colWidths
-    
+
     XLSX.utils.book_append_sheet(workbook, worksheet, 'Fish数据')
     const excelBuffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' })
-    
+
     return excelBuffer
   }
 }