auto backup 2026-05-10 08:10:01

This commit is contained in:
--git_token 2026-05-10 08:10:01 +08:00
parent c3c8dbbab2
commit d0b44f5a8c
4 changed files with 204 additions and 29 deletions

View File

@ -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
View File

@ -0,0 +1,96 @@
## 2026-05-09 工作日志
### 飞书反馈同步 - 三个文档问题修复
**问题一:图片无法点击查看**
- 根因Markdown表格中 `|``dialogue_info` 内部的 ` | ` 分隔符破坏 + `![图片]()` 格式在飞书导入时不可靠
- 修复:`info_parts` 分隔符从 `" | "` 改为 `<br>`;图片格式从 `![图片](url)` 改为 `📎 [图片](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)` 改为 `📎 [图片](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)` 改为 `📎 [图片](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成功

View File

@ -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"![图片]({media_url})")
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,8 +1030,17 @@ 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"]
print(f" 📝 更新子文档: {title}")
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)
if obj_token:
@ -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:
@ -1262,8 +1302,47 @@ def main():
run_steps = args.steps
do_summary = "4" in run_steps or "5" in run_steps or "6" in run_steps or "7" in run_steps
if do_summary:
# 步骤 4问题归纳
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()