@@ -64,4 +64,51 @@
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- 管理员规则:可查看所有用户的数据 -->
|
||||
<!-- ============================================ -->
|
||||
<record id="rule_ai_conversation_admin" model="ir.rule">
|
||||
<field name="name">AI 对话:管理员可见全部</field>
|
||||
<field name="model_id" ref="model_ai_conversation"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="groups" eval="[(4, ref('yuthon_ai_agent.group_ai_agent_admin'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_ai_profile_admin" model="ir.rule">
|
||||
<field name="name">AI 画像:管理员可见全部</field>
|
||||
<field name="model_id" ref="model_ai_user_profile"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="groups" eval="[(4, ref('yuthon_ai_agent.group_ai_agent_admin'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_ai_memory_admin" model="ir.rule">
|
||||
<field name="name">AI 记忆:管理员可见全部</field>
|
||||
<field name="model_id" ref="model_ai_user_memory"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="groups" eval="[(4, ref('yuthon_ai_agent.group_ai_agent_admin'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_ai_token_usage_admin" model="ir.rule">
|
||||
<field name="name">Token 用量:管理员可见全部</field>
|
||||
<field name="model_id" ref="model_ai_token_usage"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="groups" eval="[(4, ref('yuthon_ai_agent.group_ai_agent_admin'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -8,7 +8,7 @@ import logging
|
||||
import threading
|
||||
|
||||
from odoo import http, api, SUPERUSER_ID
|
||||
from odoo.http import request
|
||||
from odoo.http import request, Response
|
||||
|
||||
from ..utils.wx_biz_msg_crypt import WXBizMsgCrypt, WXBizMsgCryptError, parse_wecom_message
|
||||
|
||||
@@ -53,11 +53,11 @@ class WecomAiCallback(http.Controller):
|
||||
wecom_app = self._get_ai_app_config()
|
||||
if not wecom_app:
|
||||
_logger.error("未找到 AI 回调应用配置(wecom_apps_type=ai_callback, category=agent)")
|
||||
return "config error"
|
||||
return Response("config error", content_type='text/plain')
|
||||
|
||||
crypt = self._get_crypt_instance(wecom_app)
|
||||
if not crypt:
|
||||
return "config error"
|
||||
return Response("config error", content_type='text/plain')
|
||||
|
||||
# === GET: URL验证 ===
|
||||
if request.httprequest.method == 'GET':
|
||||
@@ -69,26 +69,27 @@ class WecomAiCallback(http.Controller):
|
||||
def _handle_verify(self, crypt, msg_signature, timestamp, nonce, echostr):
|
||||
"""处理URL验证请求"""
|
||||
if not all([msg_signature, timestamp, nonce, echostr]):
|
||||
_logger.warning("AI回调URL验证缺少参数")
|
||||
return "param error"
|
||||
_logger.warning("AI回调URL验证缺少参数: sig=%s, ts=%s, nonce=%s, echo=%s",
|
||||
msg_signature, timestamp, nonce, bool(echostr))
|
||||
return Response("param error", content_type='text/plain')
|
||||
try:
|
||||
echo_plain = crypt.verify_url(msg_signature, timestamp, nonce, echostr)
|
||||
_logger.info("企业微信 AI 回调URL验证成功")
|
||||
return echo_plain
|
||||
return Response(echo_plain, content_type='text/plain')
|
||||
except WXBizMsgCryptError as e:
|
||||
_logger.error("AI回调URL验证失败: %s", e)
|
||||
return "verify error"
|
||||
return Response("verify error", content_type='text/plain')
|
||||
|
||||
def _handle_message(self, crypt, msg_signature, timestamp, nonce):
|
||||
"""处理接收消息请求"""
|
||||
if not all([msg_signature, timestamp, nonce]):
|
||||
_logger.warning("AI回调消息缺少签名参数")
|
||||
return ""
|
||||
return Response("", content_type='text/plain')
|
||||
|
||||
post_data = request.httprequest.get_data(as_text=True)
|
||||
if not post_data:
|
||||
_logger.warning("AI回调消息体为空")
|
||||
return ""
|
||||
return Response("", content_type='text/plain')
|
||||
|
||||
# 解密消息
|
||||
try:
|
||||
@@ -110,12 +111,12 @@ class WecomAiCallback(http.Controller):
|
||||
# 只处理文本消息
|
||||
if msg.get('msg_type') != 'text':
|
||||
_logger.info("AI回调收到非文本消息类型: %s,暂不处理", msg.get('msg_type'))
|
||||
return ""
|
||||
return Response("", content_type='text/plain')
|
||||
|
||||
wecom_userid = msg.get('from_user')
|
||||
content = msg.get('content', '').strip()
|
||||
if not wecom_userid or not content:
|
||||
return ""
|
||||
return Response("", content_type='text/plain')
|
||||
|
||||
# 异步处理: 立即返回空响应,后台线程处理AI对话并推送回复
|
||||
db_name = request.env.cr.dbname
|
||||
@@ -126,7 +127,7 @@ class WecomAiCallback(http.Controller):
|
||||
).start()
|
||||
|
||||
# 立即返回空字符串,表示成功接收
|
||||
return ""
|
||||
return Response("", content_type='text/plain')
|
||||
|
||||
def _async_process_ai_message(self, db_name, wecom_userid, content):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user