OcrDevicesController.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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. import { UserRoles } from 'App/Models/User'
  11. import UserService from 'App/Services/UserService'
  12. export default class OcrDevicesController {
  13. private paginationService = new PaginationService(OcrDevice)
  14. public async index({ request, response, auth }: HttpContextContract) {
  15. try {
  16. const { page = 1, size = 20, id, deviceId, channel } = request.qs()
  17. const user = auth.user
  18. const role = user?.$attributes?.role
  19. if (!role) {
  20. return response.forbidden({
  21. error: 'Forbidden',
  22. message: 'Unauthorized access'
  23. })
  24. }
  25. let userChannel: string | undefined
  26. if (role === UserRoles.Api) {
  27. userChannel = user!.username
  28. } else if (['admin', 'operator'].includes(role)) {
  29. const apiUsers = await UserService.findReferredUsers(user!.id)
  30. const allowedChannels = apiUsers.map((user) => user.username as string)
  31. // 如果请求中指定了channel,检查是否在允许的channel列表中
  32. if (channel) {
  33. if (!allowedChannels.includes(channel)) {
  34. return response.ok({
  35. data: [],
  36. meta: {
  37. total: 0,
  38. per_page: Number(size),
  39. current_page: Number(page),
  40. last_page: 0,
  41. first_page: 1,
  42. first_page_url: '/?page=1',
  43. last_page_url: '/?page=0',
  44. next_page_url: null,
  45. previous_page_url: null
  46. }
  47. })
  48. }
  49. userChannel = channel
  50. } else {
  51. userChannel = allowedChannels.join(',')
  52. }
  53. } else {
  54. return response.forbidden({
  55. error: 'Forbidden',
  56. message: 'You are not authorized to access this resource'
  57. })
  58. }
  59. // 构建查询条件
  60. const query = OcrDevice.query()
  61. if (id) {
  62. query.where('id', id)
  63. }
  64. if (deviceId) {
  65. query.where('id', deviceId)
  66. }
  67. if (userChannel) {
  68. query.whereIn('channel', userChannel.split(','))
  69. }
  70. // 执行分页查询
  71. const result = await query.orderBy('created_at', 'desc').paginate(page, size)
  72. return response.ok(result)
  73. } catch (error) {
  74. return response.internalServerError({
  75. message: '获取OCR设备列表时发生错误',
  76. error: error.message
  77. })
  78. }
  79. }
  80. public async store({ request, bouncer }: HttpContextContract) {
  81. // await bouncer.authorize('admin')
  82. const data = await request.validate({
  83. schema: schema.create({
  84. id: schema.string(),
  85. platform: schema.string(),
  86. channel: schema.string(),
  87. deviceInfo: schema.string.optional(),
  88. total: schema.number(),
  89. scanned: schema.number(),
  90. ipAddress: schema.string.optional()
  91. })
  92. })
  93. const clientIp = request.ip()
  94. if (!data.ipAddress) {
  95. data.ipAddress = clientIp
  96. }
  97. const device = await OcrDevice.findBy('id', data.id)
  98. let ocrDevice: OcrDevice
  99. if (device) {
  100. device.merge(data)
  101. ocrDevice = await device.save()
  102. } else {
  103. ocrDevice = await OcrDevice.create(data)
  104. }
  105. // 更新渠道统计数据
  106. await this.updateNums(data.channel)
  107. return ocrDevice
  108. }
  109. private async updateNums(channel: string) {
  110. if (channel) {
  111. const ocrChannel = await OcrChannel.findBy('name', channel)
  112. if (ocrChannel) {
  113. const deviceCount = await OcrDevice.query()
  114. .where('channel', channel)
  115. .count('* as total')
  116. const recordCount = await OcrRecord.query()
  117. .where('channel', channel)
  118. .count('* as total')
  119. const scanSum = await OcrDevice.query()
  120. .where('channel', channel)
  121. .sum('scanned as total')
  122. ocrChannel.deviceNum = Number(deviceCount[0].$extras.total || 0)
  123. ocrChannel.recordNum = Number(recordCount[0].$extras.total || 0)
  124. ocrChannel.scanNum = Number(scanSum[0].$extras.total || 0)
  125. await ocrChannel.save()
  126. }
  127. }
  128. }
  129. public async show({ params, bouncer }: HttpContextContract) {
  130. await bouncer.authorize('admin')
  131. return await OcrDevice.findOrFail(params.id)
  132. }
  133. public async plusTotal({ request, response }: HttpContextContract) {
  134. try {
  135. const device = await OcrDevice.findBy('id', request.param('id'))
  136. if (!device) {
  137. return response.notFound({ message: `未找到ID为 ${request.param('id')} 的OCR设备` })
  138. }
  139. device.total += 1
  140. await device.save()
  141. return response.ok(device)
  142. } catch (error) {
  143. return response.internalServerError({
  144. message: '更新设备记录数量时发生错误',
  145. error: error.message
  146. })
  147. }
  148. }
  149. public async plusScanned({ request, response }: HttpContextContract) {
  150. const scanCount = Number(request.param('scanCount'))
  151. if (isNaN(scanCount)) {
  152. return response.badRequest({ message: 'scanCount 参数必须是有效数字' })
  153. }
  154. try {
  155. const device = await OcrDevice.findBy('id', request.param('id'))
  156. if (!device) {
  157. return response.notFound({
  158. message: `未找到 ID 为 ${request.param('id')} 的 OCR 设备`
  159. })
  160. }
  161. device.scanned += scanCount
  162. await device.save()
  163. return response.ok(device)
  164. } catch (error) {
  165. return response.internalServerError({
  166. message: '更新设备扫描数量时发生错误',
  167. error: error.message
  168. })
  169. }
  170. }
  171. public async getStatistics({ request, response, auth }: HttpContextContract) {
  172. try {
  173. const user = auth.user
  174. const role = user?.$attributes?.role
  175. if (!role) {
  176. return response.forbidden({
  177. error: 'Forbidden',
  178. message: 'Unauthorized access'
  179. })
  180. }
  181. let userChannel: string | undefined
  182. if (role === UserRoles.Api) {
  183. userChannel = user!.username
  184. } else if (['admin', 'operator'].includes(role)) {
  185. const apiUsers = await UserService.findReferredUsers(user!.id)
  186. const allowedChannels = apiUsers.map((user) => user.username as string)
  187. // 如果请求中指定了channel,检查是否在允许的channel列表中
  188. const requestChannel = request.input('channel')
  189. if (requestChannel) {
  190. if (!allowedChannels.includes(requestChannel)) {
  191. return response.ok({
  192. dates: [],
  193. total: [],
  194. scanned: [],
  195. deviceCount: []
  196. })
  197. }
  198. userChannel = requestChannel
  199. } else {
  200. userChannel = allowedChannels.join(',')
  201. }
  202. } else {
  203. return response.forbidden({
  204. error: 'Forbidden',
  205. message: 'You are not authorized to access this resource'
  206. })
  207. }
  208. // 获取开始日期和结束日期,默认为不包括今天的最近七天
  209. let startDate = request.input(
  210. 'startDate',
  211. DateTime.now().minus({ days: 7 }).startOf('day').toFormat('yyyy-MM-dd HH:mm:ss')
  212. )
  213. let endDate = request.input(
  214. 'endDate',
  215. DateTime.now().minus({ days: 1 }).endOf('day').toFormat('yyyy-MM-dd HH:mm:ss')
  216. )
  217. // 处理可能的不同日期格式
  218. let startDateTime: DateTime
  219. let endDateTime: DateTime
  220. // 尝试解析开始日期
  221. if (startDate.includes(' ')) {
  222. // 如果包含空格,假设是完整的日期时间格式
  223. startDateTime = DateTime.fromFormat(startDate, 'yyyy-MM-dd HH:mm:ss')
  224. if (!startDateTime.isValid) {
  225. // 尝试其他可能的格式
  226. startDateTime = DateTime.fromISO(startDate)
  227. }
  228. } else {
  229. // 只有日期部分
  230. startDateTime = DateTime.fromFormat(startDate, 'yyyy-MM-dd').startOf('day')
  231. startDate = startDateTime.toFormat('yyyy-MM-dd HH:mm:ss')
  232. }
  233. // 尝试解析结束日期
  234. if (endDate.includes(' ')) {
  235. // 如果包含空格,假设是完整的日期时间格式
  236. endDateTime = DateTime.fromFormat(endDate, 'yyyy-MM-dd HH:mm:ss')
  237. if (!endDateTime.isValid) {
  238. // 尝试其他可能的格式
  239. endDateTime = DateTime.fromISO(endDate)
  240. }
  241. } else {
  242. // 只有日期部分,设置为当天结束
  243. endDateTime = DateTime.fromFormat(endDate, 'yyyy-MM-dd').endOf('day')
  244. endDate = endDateTime.toFormat('yyyy-MM-dd HH:mm:ss')
  245. }
  246. // 验证日期是否有效
  247. if (!startDateTime.isValid || !endDateTime.isValid) {
  248. return response.badRequest({
  249. message: '无效的日期格式',
  250. details: {
  251. startDate: startDateTime.isValid ? '有效' : '无效',
  252. endDate: endDateTime.isValid ? '有效' : '无效'
  253. }
  254. })
  255. }
  256. // 计算日期之间的天数差
  257. const diffInDays = Math.max(1, Math.ceil(endDateTime.diff(startDateTime, 'days').days))
  258. console.log('日期调试信息:', {
  259. startDate,
  260. endDate,
  261. startDateTime: startDateTime.toISO(),
  262. endDateTime: endDateTime.toISO(),
  263. diffInDays
  264. })
  265. // 生成日期数组
  266. const dates: string[] = []
  267. for (let i = 0; i < diffInDays; i++) {
  268. const date = startDateTime.plus({ days: i }).toFormat('yyyy-MM-dd')
  269. dates.push(date)
  270. }
  271. // 准备查询条件
  272. const deviceStatsQuery = Database.from('ocr_devices')
  273. .select(
  274. Database.raw("DATE_FORMAT(created_at, '%Y-%m-%d') as date"),
  275. Database.raw('COUNT(id) as device_count'),
  276. Database.raw('SUM(scanned) as scanned_count')
  277. )
  278. .whereBetween('created_at', [startDate, endDate])
  279. .whereIn('channel', userChannel!.split(','))
  280. .groupBy('date')
  281. .orderBy('date', 'asc')
  282. const deviceStats = await deviceStatsQuery
  283. // 使用SQL直接获取每日记录统计数据
  284. const recordStatsQuery = Database.from('ocr_records')
  285. .select(
  286. Database.raw("DATE_FORMAT(created_at, '%Y-%m-%d') as date"),
  287. Database.raw('COUNT(id) as record_count')
  288. )
  289. .whereBetween('created_at', [startDate, endDate])
  290. .whereIn('channel', userChannel!.split(','))
  291. .groupBy('date')
  292. .orderBy('date', 'asc')
  293. const recordStats = await recordStatsQuery
  294. // 合并结果
  295. const dailyStats = {}
  296. // 初始化所有日期的统计数据
  297. dates.forEach((date) => {
  298. dailyStats[date] = {
  299. total: 0,
  300. scanned: 0,
  301. deviceCount: 0
  302. }
  303. })
  304. // 填充设备统计数据
  305. deviceStats.forEach((stat) => {
  306. if (dailyStats[stat.date]) {
  307. dailyStats[stat.date].scanned = Number(stat.scanned_count) || 0
  308. dailyStats[stat.date].deviceCount = Number(stat.device_count) || 0
  309. }
  310. })
  311. // 填充记录统计数据
  312. recordStats.forEach((stat) => {
  313. if (dailyStats[stat.date]) {
  314. dailyStats[stat.date].total = Number(stat.record_count) || 0
  315. }
  316. })
  317. // 转换为前端需要的数组格式
  318. const totals = dates.map((date) => dailyStats[date].total)
  319. const scanned = dates.map((date) => dailyStats[date].scanned)
  320. const deviceCounts = dates.map((date) => dailyStats[date].deviceCount)
  321. return response.ok({
  322. dates,
  323. total: totals,
  324. scanned: scanned,
  325. deviceCount: deviceCounts
  326. })
  327. } catch (error) {
  328. return response.internalServerError({
  329. message: '获取设备统计数据时发生错误',
  330. error: error.message
  331. })
  332. }
  333. }
  334. public async getTodayStatistics({ request, response, auth }: HttpContextContract) {
  335. try {
  336. const user = auth.user
  337. const role = user?.$attributes?.role
  338. if (!role) {
  339. return response.forbidden({
  340. error: 'Forbidden',
  341. message: 'Unauthorized access'
  342. })
  343. }
  344. let userChannel: string | undefined
  345. if (role === UserRoles.Api) {
  346. userChannel = user!.username
  347. } else if (['admin', 'operator'].includes(role)) {
  348. const apiUsers = await UserService.findReferredUsers(user!.id)
  349. const allowedChannels = apiUsers.map((user) => user.username as string)
  350. // 如果请求中指定了channel,检查是否在允许的channel列表中
  351. const requestChannel = request.input('channel')
  352. if (requestChannel) {
  353. if (!allowedChannels.includes(requestChannel)) {
  354. return response.ok({
  355. date: request.input('date', DateTime.now().toFormat('yyyy-MM-dd')),
  356. total: 0,
  357. scanned: 0,
  358. deviceCount: 0
  359. })
  360. }
  361. userChannel = requestChannel
  362. } else {
  363. userChannel = allowedChannels.join(',')
  364. }
  365. } else {
  366. return response.forbidden({
  367. error: 'Forbidden',
  368. message: 'You are not authorized to access this resource'
  369. })
  370. }
  371. // 获取指定日期的数据,默认为今天
  372. const targetDate = request.input('date', DateTime.now().toFormat('yyyy-MM-dd'))
  373. const dayStart = DateTime.fromFormat(targetDate, 'yyyy-MM-dd').startOf('day').toSQL()
  374. const dayEnd = DateTime.fromFormat(targetDate, 'yyyy-MM-dd').endOf('day').toSQL()
  375. // 获取设备数据
  376. const deviceData = await Database.from('ocr_devices')
  377. .where('created_at', '>=', dayStart)
  378. .where('created_at', '<=', dayEnd)
  379. .whereIn('channel', userChannel!.split(','))
  380. .select('scanned')
  381. // 获取OcrRecord数据
  382. const recordCount = await Database.from('ocr_records')
  383. .where('created_at', '>=', dayStart)
  384. .where('created_at', '<=', dayEnd)
  385. .whereIn('channel', userChannel!.split(','))
  386. .count('* as total')
  387. // 计算统计数据
  388. const scanned = deviceData.reduce((acc, item) => acc + Number(item.scanned || 0), 0)
  389. const total = Number(recordCount[0].total) || 0
  390. const deviceCount = deviceData.length
  391. return response.ok({
  392. date: targetDate,
  393. total,
  394. scanned,
  395. deviceCount
  396. })
  397. } catch (error) {
  398. return response.internalServerError({
  399. message: '获取统计数据时发生错误',
  400. error: error.message
  401. })
  402. }
  403. }
  404. }