excel识别

This commit is contained in:
李鹏宇
2026-06-12 18:10:34 +08:00
parent 09ef1e6277
commit 3194db046b
8 changed files with 256 additions and 72 deletions
+116 -8
View File
@@ -135,21 +135,37 @@ class WecomAiCallback(http.Controller):
db_name = request.env.cr.dbname
threading.Thread(
target=self._async_process_ai_message,
args=(db_name, wecom_userid, "请帮我看看这张图片里有什么", media_id),
args=(db_name, wecom_userid, "请帮我看看这张图片里有什么", media_id, 'image'),
daemon=True,
).start()
return Response("", content_type='text/plain')
# 文件消息(Excel/CSV):下载 → 解析 → 交给 AI
if msg_type == 'file':
media_id = msg.get('media_id', '')
if not wecom_userid or not media_id:
return Response("", content_type='text/plain')
_logger.info("收到企微文件消息: user=%s, media_id=%s", wecom_userid, media_id[:20])
db_name = request.env.cr.dbname
threading.Thread(
target=self._async_process_ai_message,
args=(db_name, wecom_userid, "请帮我分析这个文件的内容", media_id, 'file'),
daemon=True,
).start()
return Response("", content_type='text/plain')
# 其他类型暂不处理
_logger.info("AI回调收到非文本/图片消息类型: %s,暂不处理", msg_type)
_logger.info("AI回调收到非文本/图片/文件消息类型: %s,暂不处理", msg_type)
return Response("", content_type='text/plain')
def _async_process_ai_message(self, db_name, wecom_userid, content, media_id=None):
def _async_process_ai_message(self, db_name, wecom_userid, content, media_id=None, media_type='image'):
"""
后台线程: 处理 AI 对话并推送回复
使用独立的数据库游标和环境
:param media_id: 可选,图片消息的 media_id。不为空时先下载→OCR→拼入消息
:param media_id: 可选,图片/文件消息的 media_id
:param media_type: 'image' | 'file'
"""
try:
registry = api.Registry(db_name)
@@ -158,11 +174,19 @@ class WecomAiCallback(http.Controller):
attachment_ids = None
if media_id:
attachment_ids = self._download_and_ocr_media(env, wecom_userid, media_id)
if media_type == 'file':
attachment_ids = self._download_and_parse_file(env, wecom_userid, media_id)
else:
attachment_ids = self._download_and_ocr_media(env, wecom_userid, media_id)
if not attachment_ids:
# 下载/OCR失败,给用户一个提示,不再调AI
env['wecom.ai.chat']._send_wecom_reply(
wecom_userid, "图片已收到,但未能从中提取到文字信息。请尝试发送更清晰的图片(含印刷文字)。")
# 下载/解析失败,给用户一个提示,不再调AI
if media_type == 'file':
env['wecom.ai.chat']._send_wecom_reply(
wecom_userid, "文件已收到,但解析失败。请确保发送的是 .xlsx/.xls/.csv 格式的 Excel 文件。")
else:
env['wecom.ai.chat']._send_wecom_reply(
wecom_userid, "图片已收到,但未能从中提取到文字信息。请尝试发送更清晰的图片(含印刷文字)。")
cr.commit()
return
@@ -245,3 +269,87 @@ class WecomAiCallback(http.Controller):
except Exception as e:
_logger.exception('[WECOM-OCR] 下载/OCR 异常: %s', e)
return None
def _download_and_parse_file(self, env, wecom_userid, media_id):
"""下载企微文件 → 创建 documents.document → Excel解析 → 返回 [document_id]
支持 .xlsx / .xls / .csv
:return: [int] 或 None
"""
try:
# 1. 下载文件
content, error = env['wecom.apps'].sudo().download_agent_media(media_id)
if content is None:
_logger.error('[WECOM-FILE] 下载失败: %s', error)
return None
_logger.info('[WECOM-FILE] 下载成功: %d bytes', len(content))
# 2. 确定 mimetype(根据魔数)
mimetype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
ext = '.xlsx'
if content[:2] == b'PK':
mimetype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
ext = '.xlsx'
elif content[:2] == b'\xd0\xcf':
mimetype = 'application/vnd.ms-excel'
ext = '.xls'
else:
# 尝试按文本解码 → CSV
try:
_ = content.decode('utf-8-sig')
mimetype = 'text/csv'
ext = '.csv'
except:
_logger.warning('[WECOM-FILE] 无法识别文件类型,降级为 xlsx')
filename = f'wecom_file_{media_id[:8]}{ext}'
# 3. 创建 documents.document
DocFolder = env['documents.folder'].sudo()
folder = DocFolder.search([('name', '=', 'AI 聊天附件')], limit=1)
if not folder:
folder = DocFolder.create({'name': 'AI 聊天附件'})
document = env['documents.document'].sudo().create({
'name': filename,
'folder_id': folder.id,
'mimetype': mimetype,
'raw': content,
})
_logger.info('[WECOM-FILE] 文档已创建: id=%s', document.id)
# 4. Excel 解析
try:
from odoo.addons.yuthon_ai_agent.utils.excel_parser import parse_excel_to_markdown
except ImportError:
_logger.warning('[WECOM-FILE] 无法导入 Excel 解析器')
return [document.id]
markdown_text, meta = parse_excel_to_markdown(content, filename)
if meta.get('error'):
_logger.error('[WECOM-FILE] 解析失败: %s', meta['error'])
return [document.id]
document.write({'ai_ocr_content': markdown_text})
_logger.info('[WECOM-FILE] ✅ 解析成功: %d字符 | sheets=%s | rows=%s',
len(markdown_text), meta.get('sheets'), meta.get('total_rows'))
# 5. 创建导入记录
try:
env['ai.import.record'].sudo().create({
'document_id': document.id,
'filename': filename,
'file_type': 'excel',
'user_id': env.ref('base.user_root').id,
'wecom_userid': wecom_userid,
'ocr_status': 'success' if not meta.get('truncated') else 'success',
'ocr_content': markdown_text,
})
except Exception as e:
_logger.warning('[WECOM-FILE] 创建导入记录失败: %s', e)
return [document.id]
except Exception as e:
_logger.exception('[WECOM-FILE] 下载/解析异常: %s', e)
return None