Просмотр исходного кода

feat(ocr): 添加 OCR 渠道和设备统计功能

- 在 OcrChannelController 中添加 getStatistics 和 getChannelNames 方法
- 在 OcrDevicesController 中添加 getStatistics 和 getTodayStatistics 方法
- 更新路由配置,增加相关 API端点
- 优化 API 用户权限验证逻辑
wui 9 месяцев назад
Родитель
Сommit
c6e67a49db

+ 58 - 0
app/Controllers/Http/OcrChannelController.ts

@@ -2,6 +2,7 @@ import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
 import PaginationService from 'App/Services/PaginationService'
 import OcrChannel from 'App/Models/OcrChannel'
 import { schema } from '@ioc:Adonis/Core/Validator'
+import { DateTime } from 'luxon'
 
 export default class OcrChannelController {
     private paginationService = new PaginationService(OcrChannel)
@@ -108,4 +109,61 @@ export default class OcrChannelController {
             })
         }
     }
+
+    public async getStatistics({ request, response }: HttpContextContract) {
+        try {
+            const name = request.input('name')
+            const query = OcrChannel.query()
+
+            if (name) {
+                query.where('name', name)
+            }
+
+            const sevenDaysAgo = DateTime.now().minus({ days: 7 }).startOf('day').toSQL()
+            const data = await query
+                .where('createdAt', '>=', sevenDaysAgo)
+                .orderBy('createdAt', 'asc')
+                .select('createdAt', 'deviceNum', 'recordNum', 'scanNum')
+
+            const result = {
+                dates: data.map((item) => item.createdAt.toFormat('yyyy-MM-dd')),
+                deviceNum: data.map((item) => item.deviceNum),
+                recordNum: data.map((item) => item.recordNum),
+                scanNum: data.map((item) => item.scanNum)
+            }
+
+            return response.ok(result)
+        } catch (error) {
+            return response.internalServerError({
+                message: '获取统计数据时发生错误',
+                error: error.message
+            })
+        }
+    }
+
+    public async getChannelNames({ auth, response }: HttpContextContract) {
+        try {
+            const user = auth.user
+            const role = user?.$attributes?.role
+
+            // 验证管理员或操作员权限
+            if (role !== 'admin' && role !== 'operator') {
+                return response.forbidden({
+                    message: 'unauthorized'
+                })
+            }
+
+            // 获取所有渠道名称
+            const channels = await OcrChannel.query().select('name').orderBy('name', 'asc')
+
+            return response.ok({
+                data: channels.map((channel) => channel.name)
+            })
+        } catch (error) {
+            return response.internalServerError({
+                message: '获取渠道名称列表时发生错误',
+                error: error.message
+            })
+        }
+    }
 }

+ 115 - 0
app/Controllers/Http/OcrDevicesController.ts

@@ -3,6 +3,7 @@ import PaginationService from 'App/Services/PaginationService'
 import { schema } from '@ioc:Adonis/Core/Validator'
 import OcrDevice from 'App/Models/OcrDevice'
 import OcrChannel from 'App/Models/OcrChannel'
+import { DateTime } from 'luxon'
 
 export default class OcrDevicesController {
     private paginationService = new PaginationService(OcrDevice)
@@ -91,4 +92,118 @@ export default class OcrDevicesController {
             })
         }
     }
+
+    public async getStatistics({ request, response, auth }: HttpContextContract) {
+        try {
+            const user = auth.user
+            const isApiUser = user?.$attributes?.role === 'api'
+            const query = OcrDevice.query()
+
+            // 如果是 API 用户,强制使用其 username 作为 channel
+            if (isApiUser) {
+                query.where('channel', user.username)
+            } else {
+                // 如果不是 API 用户,则使用请求中的 channel 参数
+                const channel = request.input('channel')
+                if (channel) {
+                    query.where('channel', channel)
+                }
+            }
+
+            // 获取近7天的数据
+            const sevenDaysAgo = DateTime.now().minus({ days: 7 }).startOf('day').toSQL()
+            const data = await query
+                .where('createdAt', '>=', sevenDaysAgo)
+                .orderBy('createdAt', 'asc')
+                .select('createdAt', 'total', 'scanned')
+
+            // 按日期分组并计算每天的总数
+            const dailyStats = data.reduce(
+                (acc, item) => {
+                    const date = item.createdAt.toFormat('yyyy-MM-dd')
+                    if (!acc[date]) {
+                        acc[date] = {
+                            total: 0,
+                            scanned: 0,
+                            deviceCount: 0
+                        }
+                    }
+                    acc[date].total += item.total
+                    acc[date].scanned += item.scanned
+                    acc[date].deviceCount += 1
+                    return acc
+                },
+                {} as Record<string, { total: number; scanned: number; deviceCount: number }>
+            )
+
+            // 确保所有日期都有数据,没有数据的日期填充0
+            const dates: string[] = []
+            const totals: number[] = []
+            const scanned: number[] = []
+            const deviceCounts: number[] = []
+            for (let i = 0; i < 7; i++) {
+                const date = DateTime.now().minus({ days: i }).toFormat('yyyy-MM-dd')
+                dates.unshift(date)
+                totals.unshift(dailyStats[date]?.total || 0)
+                scanned.unshift(dailyStats[date]?.scanned || 0)
+                deviceCounts.unshift(dailyStats[date]?.deviceCount || 0)
+            }
+
+            return response.ok({
+                dates,
+                total: totals,
+                scanned: scanned,
+                deviceCount: deviceCounts
+            })
+        } catch (error) {
+            return response.internalServerError({
+                message: '获取设备统计数据时发生错误',
+                error: error.message
+            })
+        }
+    }
+
+    public async getTodayStatistics({ request, response, auth }: HttpContextContract) {
+        try {
+            const user = auth.user
+            const isApiUser = user?.$attributes?.role === 'api'
+            const query = OcrDevice.query()
+
+            // 如果是 API 用户,强制使用其 username 作为 channel
+            if (isApiUser) {
+                query.where('channel', user.username)
+            } else {
+                // 如果不是 API 用户,则使用请求中的 channel 参数
+                const channel = request.input('channel')
+                if (channel) {
+                    query.where('channel', channel)
+                }
+            }
+
+            // 获取今天的数据
+            const today = DateTime.now().startOf('day').toSQL()
+            const data = await query.where('createdAt', '>=', today).select('total', 'scanned')
+
+            // 计算今日统计数据
+            const stats = data.reduce(
+                (acc, item) => {
+                    acc.total += item.total
+                    acc.scanned += item.scanned
+                    acc.deviceCount += 1
+                    return acc
+                },
+                { total: 0, scanned: 0, deviceCount: 0 }
+            )
+
+            return response.ok({
+                date: DateTime.now().toFormat('yyyy-MM-dd'),
+                ...stats
+            })
+        } catch (error) {
+            return response.internalServerError({
+                message: '获取今日统计数据时发生错误',
+                error: error.message
+            })
+        }
+    }
 }

+ 16 - 5
start/routes.ts

@@ -108,7 +108,6 @@ Route.group(() => {
         Route.resource('memberships', 'MembershipsController').apiOnly()
         Route.resource('textRecord', 'TextRecordController').apiOnly()
         Route.resource('filesRecord', 'FilesRecordController').apiOnly()
-        Route.resource('ocrChannel', 'OcrChannelController').apiOnly()
     }).middleware('auth:api')
 
     Route.group(() => {
@@ -133,16 +132,28 @@ Route.group(() => {
     }).prefix('/ocrImg')
 
     Route.group(() => {
-        Route.get('/plusDevice/:id', 'OcrChannelController.plusDeviceNum')
-        Route.get('/plusRecord/:id', 'OcrChannelController.plusRecordNum')
-        Route.get('/plusScan/:id/:scanCount', 'OcrChannelController.plusScanNum')
-        Route.post('/findApiChannel', 'OcrChannelController.findApiChannel').middleware('auth:api')
+        Route.group(() => {
+            Route.get('/plusDevice/:id', 'OcrChannelController.plusDeviceNum')
+            Route.get('/plusRecord/:id', 'OcrChannelController.plusRecordNum')
+            Route.get('/plusScan/:id/:scanCount', 'OcrChannelController.plusScanNum')
+        })
+        Route.group(() => {
+            Route.get('/', 'OcrChannelController.index')
+            Route.post('/', 'OcrChannelController.store')
+            Route.get('/:id', 'OcrChannelController.show')
+            Route.post('/findApiChannel', 'OcrChannelController.findApiChannel')
+            Route.post('/getStatistics', 'OcrChannelController.getStatistics')
+            Route.post('/names', 'OcrChannelController.getChannelNames')
+        }).middleware('auth:api')
     }).prefix('/ocrChannel')
 
     Route.group(() => {
         Route.get('/', 'OcrDevicesController.index').middleware('auth:api')
         Route.post('/', 'OcrDevicesController.store')
+        Route.get('/:id', 'OcrDevicesController.show').middleware('auth:api')
         Route.get('/plusTotal/:id', 'OcrDevicesController.plusTotal')
         Route.get('/plusScanned/:id/:scanCount', 'OcrDevicesController.plusScanned')
+        Route.post('/getStatistics', 'OcrDevicesController.getStatistics').middleware('auth:api')
+        Route.post('/today', 'OcrDevicesController.getTodayStatistics').middleware('auth:api')
     }).prefix('/ocrDevice')
 }).prefix('/api')