OcrDevicesController.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
  2. import PaginationService from 'App/Services/PaginationService'
  3. import { schema } from '@ioc:Adonis/Core/Validator'
  4. import OcrDevice from 'App/Models/OcrDevice'
  5. import OcrRecord from 'App/Models/OcrRecord'
  6. import OcrChannel from 'App/Models/OcrChannel'
  7. import { DateTime } from 'luxon'
  8. import * as console from 'node:console'
  9. import Database from '@ioc:Adonis/Lucid/Database'
  10. export default class OcrDevicesController {
  11. private paginationService = new PaginationService(OcrDevice)
  12. public async index({ request, auth }: HttpContextContract) {
  13. const user = auth.user
  14. const isApiUser = user?.$attributes?.role === 'api'
  15. const requestData = request.all()
  16. if (isApiUser) {
  17. requestData.channel = user.username
  18. }
  19. return await this.paginationService.paginate(request.all())
  20. }
  21. public async store({ request, bouncer }: HttpContextContract) {
  22. // await bouncer.authorize('admin')
  23. const data = await request.validate({
  24. schema: schema.create({
  25. id: schema.string(),
  26. platform: schema.string(),
  27. channel: schema.string(),
  28. deviceInfo: schema.string.optional(),
  29. total: schema.number(),
  30. scanned: schema.number(),
  31. ipAddress: schema.string.optional()
  32. })
  33. })
  34. const clientIp = request.ip()
  35. if (!data.ipAddress) {
  36. data.ipAddress = clientIp
  37. }
  38. const device = await OcrDevice.findBy('id', data.id)
  39. let ocrDevice: OcrDevice
  40. if (device) {
  41. device.merge(data)
  42. ocrDevice = await device.save()
  43. } else {
  44. ocrDevice = await OcrDevice.create(data)
  45. }
  46. // 更新渠道统计数据
  47. await this.updateNums(data.channel)
  48. return ocrDevice
  49. }
  50. private async updateNums(channel: string) {
  51. if (channel) {
  52. const ocrChannel = await OcrChannel.findBy('name', channel)
  53. if (ocrChannel) {
  54. const deviceCount = await OcrDevice.query()
  55. .where('channel', channel)
  56. .count('* as total')
  57. const recordCount = await OcrRecord.query()
  58. .where('channel', channel)
  59. .count('* as total')
  60. const scanSum = await OcrDevice.query()
  61. .where('channel', channel)
  62. .sum('scanned as total')
  63. ocrChannel.deviceNum = Number(deviceCount[0].$extras.total || 0)
  64. ocrChannel.recordNum = Number(recordCount[0].$extras.total || 0)
  65. ocrChannel.scanNum = Number(scanSum[0].$extras.total || 0)
  66. await ocrChannel.save()
  67. }
  68. }
  69. }
  70. public async show({ params, bouncer }: HttpContextContract) {
  71. await bouncer.authorize('admin')
  72. return await OcrDevice.findOrFail(params.id)
  73. }
  74. public async plusTotal({ request, response }: HttpContextContract) {
  75. try {
  76. const device = await OcrDevice.findBy('id', request.param('id'))
  77. if (!device) {
  78. return response.notFound({ message: `未找到ID为 ${request.param('id')} 的OCR设备` })
  79. }
  80. device.total += 1
  81. await device.save()
  82. return response.ok(device)
  83. } catch (error) {
  84. return response.internalServerError({
  85. message: '更新设备记录数量时发生错误',
  86. error: error.message
  87. })
  88. }
  89. }
  90. public async plusScanned({ request, response }: HttpContextContract) {
  91. const scanCount = Number(request.param('scanCount'))
  92. if (isNaN(scanCount)) {
  93. return response.badRequest({ message: 'scanCount 参数必须是有效数字' })
  94. }
  95. try {
  96. const device = await OcrDevice.findBy('id', request.param('id'))
  97. if (!device) {
  98. return response.notFound({
  99. message: `未找到 ID 为 ${request.param('id')} 的 OCR 设备`
  100. })
  101. }
  102. device.scanned += scanCount
  103. await device.save()
  104. return response.ok(device)
  105. } catch (error) {
  106. return response.internalServerError({
  107. message: '更新设备扫描数量时发生错误',
  108. error: error.message
  109. })
  110. }
  111. }
  112. public async getStatistics({ request, response, auth }: HttpContextContract) {
  113. try {
  114. const user = auth.user
  115. const isApiUser = user?.$attributes?.role === 'api'
  116. // 获取开始日期和结束日期,默认为不包括今天的最近七天
  117. let startDate = request.input(
  118. 'startDate',
  119. DateTime.now().minus({ days: 7 }).startOf('day').toFormat('yyyy-MM-dd HH:mm:ss')
  120. )
  121. let endDate = request.input(
  122. 'endDate',
  123. DateTime.now().minus({ days: 1 }).endOf('day').toFormat('yyyy-MM-dd HH:mm:ss')
  124. )
  125. // 处理可能的不同日期格式
  126. let startDateTime: DateTime
  127. let endDateTime: DateTime
  128. // 尝试解析开始日期
  129. if (startDate.includes(' ')) {
  130. // 如果包含空格,假设是完整的日期时间格式
  131. startDateTime = DateTime.fromFormat(startDate, 'yyyy-MM-dd HH:mm:ss')
  132. if (!startDateTime.isValid) {
  133. // 尝试其他可能的格式
  134. startDateTime = DateTime.fromISO(startDate)
  135. }
  136. } else {
  137. // 只有日期部分
  138. startDateTime = DateTime.fromFormat(startDate, 'yyyy-MM-dd').startOf('day')
  139. startDate = startDateTime.toFormat('yyyy-MM-dd HH:mm:ss')
  140. }
  141. // 尝试解析结束日期
  142. if (endDate.includes(' ')) {
  143. // 如果包含空格,假设是完整的日期时间格式
  144. endDateTime = DateTime.fromFormat(endDate, 'yyyy-MM-dd HH:mm:ss')
  145. if (!endDateTime.isValid) {
  146. // 尝试其他可能的格式
  147. endDateTime = DateTime.fromISO(endDate)
  148. }
  149. } else {
  150. // 只有日期部分,设置为当天结束
  151. endDateTime = DateTime.fromFormat(endDate, 'yyyy-MM-dd').endOf('day')
  152. endDate = endDateTime.toFormat('yyyy-MM-dd HH:mm:ss')
  153. }
  154. // 验证日期是否有效
  155. if (!startDateTime.isValid || !endDateTime.isValid) {
  156. return response.badRequest({
  157. message: '无效的日期格式',
  158. details: {
  159. startDate: startDateTime.isValid ? '有效' : '无效',
  160. endDate: endDateTime.isValid ? '有效' : '无效'
  161. }
  162. })
  163. }
  164. // 计算日期之间的天数差
  165. const diffInDays = Math.max(1, Math.ceil(endDateTime.diff(startDateTime, 'days').days))
  166. console.log('日期调试信息:', {
  167. startDate,
  168. endDate,
  169. startDateTime: startDateTime.toISO(),
  170. endDateTime: endDateTime.toISO(),
  171. diffInDays
  172. })
  173. // 生成日期数组
  174. const dates: string[] = []
  175. for (let i = 0; i < diffInDays; i++) {
  176. const date = startDateTime.plus({ days: i }).toFormat('yyyy-MM-dd')
  177. dates.push(date)
  178. }
  179. // 准备查询条件
  180. let channelCondition = {}
  181. if (isApiUser) {
  182. channelCondition = { channel: user.username }
  183. } else if (request.input('channel')) {
  184. channelCondition = { channel: request.input('channel') }
  185. }
  186. // 使用SQL直接获取每日设备统计数据
  187. const deviceStatsQuery = Database.from('ocr_devices')
  188. .select(
  189. Database.raw("DATE_FORMAT(created_at, '%Y-%m-%d') as date"),
  190. Database.raw('COUNT(id) as device_count'),
  191. Database.raw('SUM(scanned) as scanned_count')
  192. )
  193. .whereBetween('created_at', [startDate, endDate])
  194. .groupBy('date')
  195. .orderBy('date', 'asc')
  196. // 添加渠道条件
  197. if (Object.keys(channelCondition).length > 0) {
  198. deviceStatsQuery.where(channelCondition)
  199. }
  200. const deviceStats = await deviceStatsQuery
  201. // 使用SQL直接获取每日记录统计数据
  202. const recordStatsQuery = Database.from('ocr_records')
  203. .select(
  204. Database.raw("DATE_FORMAT(created_at, '%Y-%m-%d') as date"),
  205. Database.raw('COUNT(id) as record_count')
  206. )
  207. .whereBetween('created_at', [startDate, endDate])
  208. .groupBy('date')
  209. .orderBy('date', 'asc')
  210. // 添加渠道条件
  211. if (Object.keys(channelCondition).length > 0) {
  212. recordStatsQuery.where(channelCondition)
  213. }
  214. const recordStats = await recordStatsQuery
  215. // 合并结果
  216. const dailyStats = {}
  217. // 初始化所有日期的统计数据
  218. dates.forEach((date) => {
  219. dailyStats[date] = {
  220. total: 0,
  221. scanned: 0,
  222. deviceCount: 0
  223. }
  224. })
  225. // 填充设备统计数据
  226. deviceStats.forEach((stat) => {
  227. if (dailyStats[stat.date]) {
  228. dailyStats[stat.date].scanned = Number(stat.scanned_count) || 0
  229. dailyStats[stat.date].deviceCount = Number(stat.device_count) || 0
  230. }
  231. })
  232. // 填充记录统计数据
  233. recordStats.forEach((stat) => {
  234. if (dailyStats[stat.date]) {
  235. dailyStats[stat.date].total = Number(stat.record_count) || 0
  236. }
  237. })
  238. // 转换为前端需要的数组格式
  239. const totals = dates.map((date) => dailyStats[date].total)
  240. const scanned = dates.map((date) => dailyStats[date].scanned)
  241. const deviceCounts = dates.map((date) => dailyStats[date].deviceCount)
  242. return response.ok({
  243. dates,
  244. total: totals,
  245. scanned: scanned,
  246. deviceCount: deviceCounts
  247. })
  248. } catch (error) {
  249. return response.internalServerError({
  250. message: '获取设备统计数据时发生错误',
  251. error: error.message
  252. })
  253. }
  254. }
  255. public async getTodayStatistics({ request, response, auth }: HttpContextContract) {
  256. try {
  257. const user = auth.user
  258. const isApiUser = user?.$attributes?.role === 'api'
  259. const deviceQuery = OcrDevice.query()
  260. // 如果是API用户,强制使用其username作为channel
  261. if (isApiUser) {
  262. deviceQuery.where('channel', user.username)
  263. } else {
  264. // 如果不是API用户,则使用请求中的channel参数
  265. const channel = request.input('channel')
  266. if (channel) {
  267. deviceQuery.where('channel', channel)
  268. }
  269. }
  270. // 获取指定日期的数据,默认为今天
  271. const targetDate = request.input('date', DateTime.now().toFormat('yyyy-MM-dd'))
  272. const dayStart = DateTime.fromFormat(targetDate, 'yyyy-MM-dd').startOf('day').toSQL()
  273. const dayEnd = DateTime.fromFormat(targetDate, 'yyyy-MM-dd').endOf('day').toSQL()
  274. // 获取设备数据
  275. const deviceData = await deviceQuery
  276. .where('createdAt', '>=', dayStart)
  277. .where('createdAt', '<=', dayEnd)
  278. .select('scanned')
  279. // 获取OcrRecord数据
  280. const recordCount = await Database.from('ocr_records')
  281. .where('created_at', '>=', dayStart)
  282. .where('created_at', '<=', dayEnd)
  283. .where(function (query) {
  284. if (isApiUser) {
  285. query.where('channel', user.username)
  286. } else {
  287. const channel = request.input('channel')
  288. if (channel) {
  289. query.where('channel', channel)
  290. }
  291. }
  292. })
  293. .count('* as total')
  294. // 计算统计数据
  295. const scanned = deviceData.reduce((acc, item) => acc + item.scanned, 0)
  296. const total = Number(recordCount[0].total) || 0
  297. const deviceCount = deviceData.length
  298. return response.ok({
  299. date: targetDate,
  300. total,
  301. scanned,
  302. deviceCount
  303. })
  304. } catch (error) {
  305. return response.internalServerError({
  306. message: '获取统计数据时发生错误',
  307. error: error.message
  308. })
  309. }
  310. }
  311. }