auto backup 2026-05-10 08:10:01
This commit is contained in:
parent
c3c8dbbab2
commit
d0b44f5a8c
@ -14,4 +14,4 @@ user-feedback-collector c0320451bf7ea0ce3d8ceaa603ae0a7b55c373c048363a5142258a4c
|
||||
user-feedback-data-source a95eb9142f3019fd193c46f89147dc7e0bf01dfe250202565a86f8bc52f37b13
|
||||
feishu-group-msg-sync 085f95a5b89fec3b6a627da25d66ffeeb0be430098387739a64f7903f0ee88d4
|
||||
user-feedback-processor 61783a8e9f03a973c187b359a87749ad1993dc71f8364b0a853d8b3ff64c75e8
|
||||
feishu-feedback-sync 878cad61ebd9d480eac6d1146a556031f02580c75841d4173f58afb076af7c34
|
||||
feishu-feedback-sync 7bd0ccd7112156e8fcfca7512b669f1c956cc085c6de9530e02838fa27d583cc
|
||||
|
||||
96
memory/2026-05-09.md
Normal file
96
memory/2026-05-09.md
Normal file
@ -0,0 +1,96 @@
|
||||
## 2026-05-09 工作日志
|
||||
|
||||
### 飞书反馈同步 - 三个文档问题修复
|
||||
|
||||
**问题一:图片无法点击查看**
|
||||
- 根因:Markdown表格中 `|` 被 `dialogue_info` 内部的 ` | ` 分隔符破坏 + `![图片]()` 格式在飞书导入时不可靠
|
||||
- 修复:`info_parts` 分隔符从 `" | "` 改为 `<br>`;图片格式从 `` 改为 `📎 [图片](url)` 可点击链接
|
||||
|
||||
**问题二:子文档作者显示"小研"**
|
||||
- 根因:现有子文档由 xiaoyan bot (`ou_3e97d43a66639a457f0020a0d7f2bd74`) 创建,xiaokui 无法直接覆盖
|
||||
- 修复:在 `update_summary_doc_as_children` 中添加 creator 校验,非 xiaokui 创建则先通过 xiaoyan 凭证删除再重建
|
||||
- 关键常量:`XIAOKUI_BOT_OPEN_ID = "ou_fdbf5fdafd91670db34b6ac887f30fb7"`
|
||||
- 注意:xiaokui 无法删除 xiaoyan 创建的文档(跨应用权限隔离),需回退到 xiaoyan 凭证删除
|
||||
|
||||
**问题三:子文档排序不规则**
|
||||
- 根因:旧 `sort_tag = dt.timestamp()` 升序导致旧日期在前
|
||||
- 修复:改为 `sort_tag = 9999999999 - int(dt.timestamp())` 实现日期降序
|
||||
- ⚠️ 不足:飞书 Wiki V2 API 创建节点时 `sort_tag` 参数可能被忽略(API 返回均为 null)
|
||||
- 兜底方案:按日期由近到远的顺序依次创建子文档,利用 `node_create_time` 自然排序
|
||||
- 所有旧文档已删除并按正确顺序重建(5月8日→5月7日→4月28日),5月6日需手动创建
|
||||
|
||||
### 飞书分发消息 `<at>` 标签修复
|
||||
- 根因:`dispatch_summary_to_chat` 中两步打架——第一步 `re.sub` 注入 HTML `<at>` 文本,第二步 `content_parts` 用正确 `{"tag":"at"}` 格式插入
|
||||
- 修复:删除 `re.sub` 注入原始 HTML 标签的代码,仅保留富文本 at tag
|
||||
|
||||
### 废弃定时任务的 crontab 清理
|
||||
- 已删除 xiaokui crontab 中 `*/5 * * * *` 的「飞书问题反馈同步每分钟」任务(含 wrapper 脚本调用)
|
||||
- 该任务每分钟执行一次打开 MySQL 连接/查询/返回存在潜在连接泄漏风险
|
||||
|
||||
### 5月9日补跑问题
|
||||
- 5月9日10:00定时任务因 `IndentationError` 失败(凌晨08:10自动备份 `c3c8dbb` 损坏了脚本)
|
||||
- 修复:从上游版本恢复被清空的步骤4-7逻辑 + 模块常量
|
||||
- 手动补跑5月8日数据(8条反馈,1个P0)成功
|
||||
## 2026-05-09 工作日志
|
||||
|
||||
### 飞书反馈同步 - 三个文档问题修复
|
||||
|
||||
**问题一:图片无法点击查看**
|
||||
- 根因:Markdown表格中 `|` 被 `dialogue_info` 内部的 ` | ` 分隔符破坏 + `![图片]()` 格式在飞书导入时不可靠
|
||||
- 修复:`info_parts` 分隔符从 `" | "` 改为 `<br>`;图片格式从 `` 改为 `📎 [图片](url)` 可点击链接
|
||||
|
||||
**问题二:子文档作者显示"小研"**
|
||||
- 根因:现有子文档由 xiaoyan bot (`ou_3e97d43a66639a457f0020a0d7f2bd74`) 创建,xiaokui 无法直接覆盖
|
||||
- 修复:在 `update_summary_doc_as_children` 中添加 creator 校验,非 xiaokui 创建则先通过 xiaoyan 凭证删除再重建
|
||||
- 关键常量:`XIAOKUI_BOT_OPEN_ID = "ou_fdbf5fdafd91670db34b6ac887f30fb7"`
|
||||
- 注意:xiaokui 无法删除 xiaoyan 创建的文档(跨应用权限隔离),需回退到 xiaoyan 凭证删除
|
||||
|
||||
**问题三:子文档排序不规则**
|
||||
- 根因:旧 `sort_tag = dt.timestamp()` 升序导致旧日期在前
|
||||
- 修复:改为 `sort_tag = 9999999999 - int(dt.timestamp())` 实现日期降序
|
||||
- ⚠️ 不足:飞书 Wiki V2 API 创建节点时 `sort_tag` 参数可能被忽略(API 返回均为 null)
|
||||
- 兜底方案:按日期由近到远的顺序依次创建子文档,利用 `node_create_time` 自然排序
|
||||
- 所有旧文档已删除并按正确顺序重建(5月8日→5月7日→4月28日),5月6日需手动创建
|
||||
|
||||
### 飞书分发消息 `<at>` 标签修复
|
||||
- 根因:`dispatch_summary_to_chat` 中两步打架——第一步 `re.sub` 注入 HTML `<at>` 文本,第二步 `content_parts` 用正确 `{"tag":"at"}` 格式插入
|
||||
- 修复:删除 `re.sub` 注入原始 HTML 标签的代码,仅保留富文本 at tag
|
||||
|
||||
### 5月9日补跑问题
|
||||
- 5月9日10:00定时任务因 `IndentationError` 失败(凌晨08:10自动备份 `c3c8dbb` 损坏了脚本)
|
||||
- 修复:从上游版本恢复被清空的步骤4-7逻辑 + 模块常量
|
||||
- 手动补跑5月8日数据(8条反馈,1个P0)成功
|
||||
|
||||
### 待验证
|
||||
- [ ] 确认「用户反馈问题汇总」下子文档排序是否为:5月8日→5月7日→4月28日(由近及远)
|
||||
- [ ] 5月6日文档需手动创建(重建过程中因无有效问题簇被跳过)
|
||||
- [ ] 下次定时任务执行时验证 sort_tag 创建-删除-重建流程是否完全正常
|
||||
## 2026-05-09 工作日志
|
||||
|
||||
### 飞书反馈同步 - 三个文档问题修复
|
||||
|
||||
**问题一:图片无法点击查看**
|
||||
- 根因:Markdown表格中 `|` 被 `dialogue_info` 内部的 ` | ` 分隔符破坏 + `![图片]()` 格式在飞书导入时不可靠
|
||||
- 修复:`info_parts` 分隔符从 `" | "` 改为 `<br>`;图片格式从 `` 改为 `📎 [图片](url)` 可点击链接
|
||||
|
||||
**问题二:子文档作者显示"小研"**
|
||||
- 根因:现有子文档由 xiaoyan bot (`ou_3e97d43a66639a457f0020a0d7f2bd74`) 创建,xiaokui 无法直接覆盖
|
||||
- 修复:在 `update_summary_doc_as_children` 中添加 creator 校验,非 xiaokui 创建则先通过 xiaoyan 凭证删除再重建
|
||||
- 关键常量:`XIAOKUI_BOT_OPEN_ID = "ou_fdbf5fdafd91670db34b6ac887f30fb7"`
|
||||
- 注意:xiaokui 无法删除 xiaoyan 创建的文档(跨应用权限隔离),需回退到 xiaoyan 凭证删除
|
||||
|
||||
**问题三:子文档排序不规则**
|
||||
- 根因:旧 `sort_tag = dt.timestamp()` 升序导致旧日期在前
|
||||
- 修复:改为 `sort_tag = 9999999999 - int(dt.timestamp())` 实现日期降序
|
||||
- ⚠️ 不足:飞书 Wiki V2 API 创建节点时 `sort_tag` 参数可能被忽略(API 返回均为 null)
|
||||
- 兜底方案:按日期由近到远的顺序依次创建子文档,利用 `node_create_time` 自然排序
|
||||
- 所有旧文档已删除并按正确顺序重建(5月8日→5月7日→4月28日),5月6日需手动创建
|
||||
|
||||
### 飞书分发消息 `<at>` 标签修复
|
||||
- 根因:`dispatch_summary_to_chat` 中两步打架——第一步 `re.sub` 注入 HTML `<at>` 文本,第二步 `content_parts` 用正确 `{"tag":"at"}` 格式插入
|
||||
- 修复:删除 `re.sub` 注入原始 HTML 标签的代码,仅保留富文本 at tag
|
||||
|
||||
### 5月9日补跑问题
|
||||
- 5月9日10:00定时任务因 `IndentationError` 失败(凌晨08:10自动备份 `c3c8dbb` 损坏了脚本)
|
||||
- 修复:从上游版本恢复被清空的步骤4-7逻辑 + 模块常量
|
||||
- 手动补跑5月8日数据(8条反馈,1个P0)成功
|
||||
Binary file not shown.
@ -36,6 +36,16 @@ CLI = "/root/.nvm/versions/node/v24.14.0/bin/lark-cli"
|
||||
|
||||
HEADER = ["消息ID", "发送者", "消息类型", "内容", "媒体URL", "引用消息ID", "消息时间", "消息时间戳"]
|
||||
|
||||
# === 分发配置 ===
|
||||
DISPATCH_CHAT_ID = "oc_4171a2188f2554522a4309f2d7c27753" # 「小葵小葵」问题反馈群
|
||||
DISPATCH_CRED_DIR = "/root/.openclaw/credentials/xiaokui"
|
||||
XIAOKUI_BOT_OPEN_ID = "ou_fdbf5fdafd91670db34b6ac887f30fb7" # 小葵Bot的open_id,用于校验文档归属
|
||||
|
||||
# P0问题默认@的人(user_id)
|
||||
P0_NOTIFY_USERS = [
|
||||
"eggbg21g", # 毋益飞
|
||||
]
|
||||
|
||||
# 推断引用策略参数
|
||||
TIME_WINDOW_MIN = 2 # 同发送者聚类时间窗口(分钟)
|
||||
GAP_THRESHOLD_MIN = 30 # 大时间跨度视为新话题(分钟)
|
||||
@ -725,14 +735,12 @@ def summarize_cluster(cluster_msgs, idx, priority_info=None):
|
||||
text = text[:77] + "..."
|
||||
info_parts.append(text)
|
||||
if media_url:
|
||||
# 图片格式直接用图片标签,飞书可点击预览;其他文件保留链接格式
|
||||
if media_url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')):
|
||||
info_parts.append(f"")
|
||||
else:
|
||||
info_parts.append(f"📎 [文件]({media_url})")
|
||||
# 图片和文件统一使用可点击链接,避免 Markdown 表格 | 分隔符冲突
|
||||
label = "图片" if media_url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')) else "文件"
|
||||
info_parts.append(f"📎 [{label}]({media_url})")
|
||||
if not info_parts:
|
||||
info_parts.append("[图片]")
|
||||
dialogue_info = " | ".join(info_parts)
|
||||
dialogue_info = "<br>".join(info_parts) if len(info_parts) > 1 else info_parts[0]
|
||||
|
||||
role_tag = ""
|
||||
if name == first_speaker and name not in seen_speakers:
|
||||
@ -906,9 +914,9 @@ def get_tenant_token(cred_dir=None):
|
||||
|
||||
|
||||
def list_child_nodes():
|
||||
"""列出「用户反馈问题汇总」下的所有子节点,返回 {title: {node_token, obj_token}}。同名节点取 obj_edit_time 最新的。"""
|
||||
"""列出「用户反馈问题汇总」下的所有子节点,返回 {title: {node_token, obj_token, creator}}。同名节点取 obj_edit_time 最新的。"""
|
||||
import urllib.request
|
||||
token = get_tenant_token()
|
||||
token = get_tenant_token(cred_dir="/root/.openclaw/credentials/xiaokui")
|
||||
url = f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{SUMMARY_SPACE_ID}/nodes?parent_node_token={SUMMARY_PARENT_NODE}"
|
||||
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
||||
resp = urllib.request.urlopen(req, timeout=10)
|
||||
@ -923,6 +931,7 @@ def list_child_nodes():
|
||||
nodes[title] = {
|
||||
"node_token": item["node_token"],
|
||||
"obj_token": item["obj_token"],
|
||||
"creator": item.get("creator", ""),
|
||||
"_edit_time": edit_time
|
||||
}
|
||||
return nodes
|
||||
@ -931,16 +940,18 @@ def list_child_nodes():
|
||||
def create_child_doc(title):
|
||||
"""在「用户反馈问题汇总」下创建子文档,返回 obj_token"""
|
||||
import urllib.request, time
|
||||
token = get_tenant_token()
|
||||
token = get_tenant_token(cred_dir="/root/.openclaw/credentials/xiaokui")
|
||||
url = f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{SUMMARY_SPACE_ID}/nodes"
|
||||
|
||||
# 提取标题中的日期,生成sort_tag(时间戳越大排序越靠前)
|
||||
# 提取标题中的日期,生成sort_tag
|
||||
# 飞书Wiki按sort_tag升序排列子节点,因此日期越新sort_tag越小(排在前面)
|
||||
sort_tag = int(time.time()) # 默认当前时间
|
||||
try:
|
||||
# 标题格式如:2026-05-08 问题反馈
|
||||
date_str = title.split(" ")[0]
|
||||
dt = datetime.strptime(date_str, "%Y-%m-%d")
|
||||
sort_tag = int(dt.timestamp())
|
||||
# 用大基数减去时间戳:日期越新时间戳越大,减后值越小 → 排在前面
|
||||
sort_tag = 9999999999 - int(dt.timestamp())
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -963,6 +974,37 @@ def create_child_doc(title):
|
||||
return None
|
||||
|
||||
|
||||
def _delete_child_node(obj_token):
|
||||
"""删除指定子文档(使用 obj_token)。先尝试 xiaokui 凭证,失败则回退到 xiaoyan(处理旧文档归属问题)"""
|
||||
import urllib.request
|
||||
last_error = None
|
||||
for cred_dir in ["/root/.openclaw/credentials/xiaokui", "/root/.openclaw/credentials/xiaoyan"]:
|
||||
try:
|
||||
token = get_tenant_token(cred_dir=cred_dir)
|
||||
body = json.dumps({"obj_type": "docx"}).encode()
|
||||
url = f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{SUMMARY_SPACE_ID}/nodes/{obj_token}"
|
||||
req = urllib.request.Request(url, data=body, headers={
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}, method="DELETE")
|
||||
resp = urllib.request.urlopen(req, timeout=10)
|
||||
data = json.loads(resp.read())
|
||||
if data.get("code") == 0:
|
||||
print(f" 🗑️ 已删除旧文档")
|
||||
return True
|
||||
# 权限拒绝 / 服务暂不可用时尝试下一个凭证
|
||||
if data.get("code") in (131006, 131001):
|
||||
last_error = data
|
||||
continue
|
||||
last_error = data
|
||||
break
|
||||
except Exception as e:
|
||||
last_error = {"error": str(e)}
|
||||
continue
|
||||
print(f" ⚠️ 删除旧文档失败: {last_error}")
|
||||
return False
|
||||
|
||||
|
||||
def update_summary_doc_as_children(day_summaries):
|
||||
"""
|
||||
将各日期的归纳结果写入「用户反馈问题汇总」的子文档中。
|
||||
@ -988,7 +1030,16 @@ def update_summary_doc_as_children(day_summaries):
|
||||
fcntl.flock(lock_f, fcntl.LOCK_EX)
|
||||
existing_nodes = list_child_nodes()
|
||||
if title in existing_nodes:
|
||||
obj_token = existing_nodes[title]["obj_token"]
|
||||
node = existing_nodes[title]
|
||||
# 检查文档是否由小葵创建,若不是则删除重建(修复 author 归属)
|
||||
if node.get("creator") != XIAOKUI_BOT_OPEN_ID:
|
||||
print(f" 🔄 文档归属非小葵,重建: {title}")
|
||||
_delete_child_node(node["obj_token"])
|
||||
obj_token = create_child_doc(title)
|
||||
if not obj_token:
|
||||
continue
|
||||
else:
|
||||
obj_token = node["obj_token"]
|
||||
print(f" 📝 更新子文档: {title}")
|
||||
else:
|
||||
obj_token = create_child_doc(title)
|
||||
@ -1112,19 +1163,8 @@ def dispatch_summary_to_chat(day_label, summary_text, p0_only=False):
|
||||
# P0 @跟在标题行后面,不放单独段落
|
||||
title = f"📋 {day_label} 用户反馈问题归纳"
|
||||
|
||||
# 将归纳内容中的 P0 标题行注入 @人
|
||||
# 格式:⚠️ P0级核心问题(需优先处理) → ⚠️ P0级核心问题(需优先处理) @某某
|
||||
if has_p0 and P0_NOTIFY_USERS:
|
||||
at_str = "".join(f"<at user_id=\"{uid}\" />" for uid in P0_NOTIFY_USERS)
|
||||
# 在 P0 标题行后面插入 @
|
||||
归纳_content = re.sub(
|
||||
r'(⚠️ P0级核心问题(需优先处理))',
|
||||
rf'\1 {at_str}',
|
||||
归纳_content
|
||||
)
|
||||
|
||||
# 飞书 post 消息不支持 HTML 标签的 at,需要用富文本 tag
|
||||
# 所以改为:把归纳内容按 P0/P1/P2/P3 段落拆分,P0 段落后追加 at tag
|
||||
# 把归纳内容按 P0/P1/P2/P3 段落拆分,P0 标题行后追加 at tag
|
||||
content_parts = []
|
||||
|
||||
if has_p0 and P0_NOTIFY_USERS:
|
||||
@ -1264,6 +1304,45 @@ def main():
|
||||
|
||||
if do_summary:
|
||||
# 步骤 4:问题归纳
|
||||
# 步骤 5:优先级判定(默认启用,--skip-priority 可跳过)
|
||||
summary, has_content = generate_summary(clusters, cluster_order, skip_priority=args.skip_priority)
|
||||
if has_content:
|
||||
priority_count = summary.count('优先级:') if not args.skip_priority else 0
|
||||
print(f" 归纳完成:{summary.count('### 问题')} 个问题")
|
||||
if not args.skip_priority:
|
||||
print(f" 步骤5 优先级判定完成:{priority_count} 个问题已评定优先级并排序")
|
||||
print(f" (归纳内容见下方)")
|
||||
print(summary)
|
||||
day_summaries[day] = summary
|
||||
else:
|
||||
print(" ⚠️ 无有效问题簇可归纳")
|
||||
|
||||
# 写入表格(步骤2)
|
||||
success = write_sheet(sheet_id, sorted_msgs)
|
||||
if success:
|
||||
total_written += len(sorted_msgs)
|
||||
print(f" ✅ 写入 {len(sorted_msgs)} 条")
|
||||
else:
|
||||
print(f" ❌ 写入失败")
|
||||
|
||||
# 统一写入「用户反馈问题汇总」文档(增量更新,保留已有日期内容)— 步骤4-6
|
||||
if day_summaries and do_summary:
|
||||
update_summary_doc_as_children(day_summaries)
|
||||
|
||||
# 步骤7:问题分发(发送到群聊 + @相关人)
|
||||
if do_summary and not args.skip_dispatch:
|
||||
dispatch_mode = args.dispatch_mode
|
||||
print(f"\n📨 步骤7:问题分发(模式: {dispatch_mode})...")
|
||||
for day, summary_text in day_summaries.items():
|
||||
# 检查当天归纳中是否有 P0 问题
|
||||
has_p0 = "⚠️ P0级" in summary_text
|
||||
if dispatch_mode == "p0" and not has_p0:
|
||||
print(f" [{day}] 无P0问题,跳过分发")
|
||||
continue
|
||||
dispatch_summary_to_chat(day, summary_text, p0_only=(dispatch_mode == "p0"))
|
||||
|
||||
print(f"\n🎉 同步完成,总计写入 {total_written} 条")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user