diff --git a/.vala_skill_hashes b/.vala_skill_hashes index 1a68556..0f30b62 100644 --- a/.vala_skill_hashes +++ b/.vala_skill_hashes @@ -11,4 +11,4 @@ smart-auto-model-switch bfb3547dcd6029622c7062b49ae7922614a366b6dfe88c7d0fae9dcd vala_git_workspace_backup.vala 4cf352bec88fe84af065ba1ffcbb06647b77df0e01860faaf0bca9fd64b968ec feishu-group-msg-sync 338e821c4dd0b2f532b7f3ca31aea6d1881e16e0a76a6b7a0ecf2b723907690f tencent-cos-upload 172517ed41d06c48425cd961ec5972a48495cfd62ec588bc1c2912ddf31b3a06 -user-feedback-collector 7a2c9fc9b2b32f8dd0be52c1ac3cf6d33bc992c1ec200f061a26acaa7c637f77 +user-feedback-collector 487eaa8446495d891c72d0f1171eec822b0877ab9dae4111eb477dc706e1ae71 diff --git a/data/last_feedback_sync_time b/data/last_feedback_sync_time index 562620f..4dfd4e8 100644 --- a/data/last_feedback_sync_time +++ b/data/last_feedback_sync_time @@ -1 +1 @@ -2026-04-17T06:00:01.887056+08:00 \ No newline at end of file +2026-04-17T12:00:01.691097+08:00 \ No newline at end of file diff --git a/data/last_lark_group_sync_time b/data/last_lark_group_sync_time new file mode 100644 index 0000000..4cad3e0 --- /dev/null +++ b/data/last_lark_group_sync_time @@ -0,0 +1 @@ +2026-04-18T08:00:01.304881+08:00 \ No newline at end of file diff --git a/data/last_wechat_sync_id b/data/last_wechat_sync_id new file mode 100644 index 0000000..b6e2760 --- /dev/null +++ b/data/last_wechat_sync_id @@ -0,0 +1 @@ +242 \ No newline at end of file diff --git a/memory/2026-04-17-cron-check.md b/memory/2026-04-17-cron-check.md new file mode 100644 index 0000000..7ced783 --- /dev/null +++ b/memory/2026-04-17-cron-check.md @@ -0,0 +1,45 @@ +# Session: 2026-04-17 07:25:23 UTC + +- **Session Key**: agent:xiaokui:feishu:direct:ou_088ee79216826be4a24af44f7268f880 +- **Session ID**: cbba7f09-869f-45a9-baf8-ff35f64ec7b2 +- **Source**: feishu + +## Conversation Summary + +assistant: ✅ New session started · model: volcengine/doubao-seed-2-0-pro-260215 +user: System: [2026-04-15 19:11:21 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b52c36b4460bcb314283a8b23394] + +A new session was started via /new or /reset. Run your Session Startup sequence - read the required files before responding to the user. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning. +Current time: Wednesday, April 15th, 2026 — 7:11 PM (Asia/Shanghai) / 2026-04-15 11:11 UTC +assistant: 若松好,我已准备就绪,有什么需求可以随时告诉我。 +user: System: [2026-04-15 19:11:45 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b52c368abd0b0b4c78753a17ec00] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b52c368abd0b0b4c78753a17ec00", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Wed 2026-04-15 19:11 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +查看你的定时任务 +assistant: 当前小葵的定时任务如下: + +### 系统crontab任务: +1. **VALA技能自动推送**:每天凌晨2:00执行 `/root/.openclaw/workspace-xiaokui/scripts/vala_skill_auto_push.sh`,日志路径:`/root/.openclaw/workspace-xiaokui/logs/vala_skill_push.log` +2. **用户反馈群同步**:每6小时执行一次 `/root/.openclaw/workspace-xiaokui/scripts/sync_feedback_group.py`,日志路径:`/var/log/feedback_sync.log` +3. **Git工作区备份**:每天早上8:10执行 `/root/.openclaw/workspace-xiaokui/skills/vala_git_workspace_backup/scripts/backup.sh`,日志路径:`/root/.openclaw/workspace-xiaokui/logs/git_backup.log` + +### openclaw cron任务: +暂无创建的定时任务。 diff --git a/memory/2026-04-17-feishu-message-stats.md b/memory/2026-04-17-feishu-message-stats.md new file mode 100644 index 0000000..b3fe197 --- /dev/null +++ b/memory/2026-04-17-feishu-message-stats.md @@ -0,0 +1,161 @@ +# Session: 2026-04-17 16:11:04 UTC + +- **Session Key**: agent:xiaokui:feishu:direct:ou_088ee79216826be4a24af44f7268f880 +- **Session ID**: 756e2dcf-7477-4385-a081-e7148a04e59a +- **Source**: feishu + +## Conversation Summary + +assistant: ✅ New session started · model: volcengine/doubao-seed-2-0-pro-260215 +user: System: [2026-04-17 16:10:44 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b512afef0948cb2c3d0b78cebed9] +System: [2026-04-17 16:10:49 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b512afe48dcb0b215c867411ab87] + +A new session was started via /new or /reset. Run your Session Startup sequence - read the required files before responding to the user. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning. +Current time: Friday, April 17th, 2026 — 4:11 PM (Asia/Shanghai) / 2026-04-17 08:11 UTC +assistant: 你好若松,我已就位,请问今天需要我处理什么工作? +user: System: [2026-04-17 16:11:33 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b512afd8e482cb282f22d314c588] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b512afd8e482cb282f22d314c588", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Fri 2026-04-17 16:11 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +查看一下 你的飞书消息数据库中 记录了多少条消息 最近一条是什么时候 +assistant: 请提供一下MySQL数据库的连接信息(主机地址、端口、用户名、密码),我来查询飞书消息表的记录。 +user: System: [2026-04-17 16:13:15 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b512af77d5090b2592e0af891675] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b512af77d5090b2592e0af891675", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Fri 2026-04-17 16:13 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +你查看下你的用户反馈 skill! +assistant: 我已查看用户反馈收集skill的配置,目前反馈数据的存储情况如下: +1. **主动上报的用户反馈**:实时存储在飞书知识库文档《2026年 用户反馈收集》(Wiki Token: SB3dwaSshie7ifklKlLc2GswnqX) +2. **飞书内部测试反馈群消息**:每4小时同步到MySQL数据库 `vala_test.lark_group_message` 表 +3. **微信用户反馈群消息**:实时存储到MySQL数据库 `vala_test.wechat_group_message` 表 + +当前本地MySQL服务未启动,无法查询数据库中的消息统计,如需查询具体数据需要先启动本地MySQL服务或者提供远程数据库连接信息。 +user: System: [2026-04-17 23:25:09 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b5111438994acb10ee4cd657ae45] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b5111438994acb10ee4cd657ae45", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Fri 2026-04-17 23:25 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +我给过你 mysql的用户名和密码啊 chatbot 账号 +assistant: 当前我没有找到保存的MySQL chatbot账号凭证,请你提供一下chatbot账号的密码,我会将连接信息保存在secrets.env中,后续查询就不需要再提供了。 +user: System: [2026-04-17 23:34:02 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b5111606c28a4b3c6b5b9d603411] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b5111606c28a4b3c6b5b9d603411", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Fri 2026-04-17 23:34 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +user:chatbot +pass:xhuBx7d@uT2gUVv +assistant: 请提供一下MySQL数据库的主机地址(IP或域名),我来配置连接并查询数据。 + +user: System: [2026-04-18 00:06:17 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b5111e9445d30b4b65bcfa860db5] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b5111e9445d30b4b65bcfa860db5", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Sat 2026-04-18 00:06 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +MYSQL_HOST=bj-cdb-8frbdwju.sql.tencentcdb.com +MYSQL_PORT=25413 +assistant: 飞书消息数据库目前共记录了 **175条** 群消息,最近一条消息的时间是 **2026-04-17 18:40:00**。 +user: System: [2026-04-18 00:09:06 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b5111fcd67ca0b4b587a67295205] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b5111fcd67ca0b4b587a67295205", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Sat 2026-04-18 00:09 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +好 记住以上 test mysql 环境 的 访问凭证 +assistant: 已将test MySQL环境的访问凭证永久保存在本地secrets.env文件中,后续查询相关数据无需再提供配置信息。 diff --git a/memory/2026-04-17-request-timed-out-before-a-res.md b/memory/2026-04-17-request-timed-out-before-a-res.md new file mode 100644 index 0000000..3b1750e --- /dev/null +++ b/memory/2026-04-17-request-timed-out-before-a-res.md @@ -0,0 +1,60 @@ +# Session: 2026-04-17 16:14:21 UTC + +- **Session Key**: agent:xiaokui:feishu:direct:ou_088ee79216826be4a24af44f7268f880 +- **Session ID**: a3993289-1785-4f14-b91f-768a86c6560a +- **Source**: feishu + +## Conversation Summary + +assistant: ✅ New session started · model: volcengine/doubao-seed-2-0-pro-260215 +user: System: [2026-04-18 00:11:03 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b5111f7b9b938b32dce6b4ab6d27] + +A new session was started via /new or /reset. Run your Session Startup sequence - read the required files before responding to the user. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning. +Current time: Saturday, April 18th, 2026 — 12:11 AM (Asia/Shanghai) / 2026-04-17 16:11 UTC +assistant: Hi 若松,新会话已启动,请问你需要我处理什么任务? +user: System: [2026-04-18 00:11:32 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b5111f5f2dc6cb2601197d6654cd] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b5111f5f2dc6cb2601197d6654cd", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Sat 2026-04-18 00:11 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +查看一下 你的飞书反馈消息数据库中 记录了多少条消息 最近一条是什么时候 +assistant: 目前飞书反馈消息数据库中共记录了 **175条** 消息,最近一条的反馈时间为:**2026-04-17 18:40:00**(北京时间)。 +user: System: [2026-04-18 00:13:00 GMT+8] Feishu[xiaokui] DM | 李若松 (ou_088ee79216826be4a24af44f7268f880) [msg:om_x100b51118e9d94a0b4b5831c86bfa9a] + +Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b51118e9d94a0b4b5831c86bfa9a", + "sender_id": "ou_088ee79216826be4a24af44f7268f880", + "sender": "李若松", + "timestamp": "Sat 2026-04-18 00:12 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "李若松 (ou_088ee79216826be4a24af44f7268f880)", + "id": "ou_088ee79216826be4a24af44f7268f880", + "name": "李若松" +} +``` + +飞书反馈消息数据库 默认指的就是 用户反馈技能中的 飞书 mysql 数据表 +assistant: 好的,已记录该术语共识,后续查询将默认使用该数据表。 diff --git a/memory/2026-04-17.md b/memory/2026-04-17.md new file mode 100644 index 0000000..aaac9c0 --- /dev/null +++ b/memory/2026-04-17.md @@ -0,0 +1,22 @@ +# 2026-04-17 工作日志 + +## 飞书群消息同步改造(李若松要求) + +### 变更内容 +- **存储从飞书表格改为 MySQL**:新建 `vala_test.lark_group_message` 表,结构参考 `wechat_group_message` +- **同步频率**:从每6小时改为每4小时 +- **数据范围**:2026.4.1 起的「内容测试问题反馈」群消息 +- **数据库账户**:chatbot(test环境,仅对 lark_group_message 有写入权限) + +### 完成事项 +1. ✅ 创建 `lark_group_message` 表(唯一键 message_id 防重复) +2. ✅ 编写新同步脚本 `scripts/sync_lark_group_to_mysql.py`(基于原有 sync_group_to_sheet.py 改造) +3. ✅ 首次全量同步完成:172 条记录(2026-04-01 ~ 2026-04-17),含文本134条、图片17条、视频10条、富文本9条、表情2条 +4. ✅ crontab 定时任务已替换:旧的每6小时飞书表格同步 → 新的每4小时MySQL同步 +5. ✅ 更新 secrets.md 记录 chatbot 账户 +6. ✅ 更新 user-feedback-collector SKILL.md 反馈数据源信息 + +### 文件变更 +- 新增:`scripts/sync_lark_group_to_mysql.py`(核心同步脚本) +- 新增:`scripts/run_lark_group_sync.sh`(crontab 入口脚本) +- 旧脚本保留:`scripts/sync_feedback_group.py`(飞书表格版本,已从crontab移除,可后续清理) diff --git a/memory/2026-04-18.md b/memory/2026-04-18.md new file mode 100644 index 0000000..20f220e --- /dev/null +++ b/memory/2026-04-18.md @@ -0,0 +1,4 @@ +# 2026-04-18 工作日志 + +## 术语共识 +[李若松确认] 术语「飞书反馈消息数据库」默认指代用户反馈收集技能中的飞书内部测试反馈MySQL数据表 `vala_test.lark_group_message`,存储「内容测试问题反馈」群(oc_fabff7672e62a9ced7b326ee4a286c26)的同步消息数据。 diff --git a/scripts/run_lark_group_sync.sh b/scripts/run_lark_group_sync.sh new file mode 100755 index 0000000..d28a268 --- /dev/null +++ b/scripts/run_lark_group_sync.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# 飞书群消息同步到MySQL - 定时执行脚本 +# crontab: 每4小时执行一次 +# 0 */4 * * * /root/.openclaw/workspace-xiaokui/scripts/run_lark_group_sync.sh >> /var/log/lark_group_sync.log 2>&1 + +export PATH="/root/.nvm/versions/node/v24.14.0/bin:$PATH" +export LARK_SYNC_DB_PASS='xhuBx7d@uT2gUVv' + +cd /root/.openclaw/workspace-xiaokui +python3 scripts/sync_lark_group_to_mysql.py diff --git a/scripts/sync_feedback_minutely.sh b/scripts/sync_feedback_minutely.sh new file mode 100755 index 0000000..d3e6a51 --- /dev/null +++ b/scripts/sync_feedback_minutely.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# 任务名称:飞书用户反馈每分钟同步(时间倒序,全量覆盖) +# 执行时间:* * * * * 每分钟执行 +# 归属 Agent:小葵 xiaokui + +set +e + +# 环境配置 +export PATH=/root/.nvm/versions/node/v24.14.0/bin:$PATH +LOG_FILE="/var/log/sync_feedback_minutely.log" +WORKSPACE="/root/.openclaw/workspace-xiaokui" +TMP_DATA="$WORKSPACE/tmp/all_feedback_sorted.json" + +# 源表格配置 +SOURCE_SPREADSHEET_TOKEN="E8vFsCmPBhT4SCtNmnJchqeJnJe" +SOURCE_SHEET_ID="7bce8f" + +# 目标表格配置 +TARGET_SPREADSHEET_TOKEN="AOxbsifk3hybRZteGowcMxNnnqc" +TARGET_SHEET_ID="f17380" + +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" +} + +log "=== 同步任务开始 ===" + +# 1. 读取源表格所有数据 +log "读取源表格数据..." +SOURCE_RAW=$(LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +read --spreadsheet-token "$SOURCE_SPREADSHEET_TOKEN" --range "$SOURCE_SHEET_ID!A:D" --as bot) +SOURCE_DATA=$(echo "$SOURCE_RAW" | jq -r '.data.valueRange.values | .[1:] | map(select(.[0] != null and .[0] != ""))') + +if [ -z "$SOURCE_DATA" ] || [ "$SOURCE_DATA" == "null" ]; then + log "源表格无有效数据,退出" + exit 0 +fi + +# 2. 转换格式并按时间倒序排列 +SORTED_DATA=$(echo "$SOURCE_DATA" | jq ' + sort_by(.[0]) | reverse | to_entries | map( + .key as $idx | + .value as $row | + { + id: ($idx + 1), + time: $row[0], + submitter: $row[1], + type: $row[2], + content: ($row[3] | if type == "array" then .[0].link else . end) + } | + [ + (.id | tostring), + "", + "", + "", + .time, + "飞书群", + .submitter, + "", + "", + (if .type == "文本" then .content else "" end), + (if .type == "图片" then .content else "" end), + (if .type | test("视频") then .content else "" end), + "", + "待确认", + "" + ] + )') + +ROW_COUNT=$(echo "$SORTED_DATA" | jq length) +log "共获取到 $ROW_COUNT 条反馈记录,按时间倒序排列完成" + +# 3. 清空目标表格原有数据(保留表头) +log "清空目标表格原有数据..." +EMPTY_DATA=$(printf '[]%.0s' {1..200} | jq -s '.') +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ + --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ + --range "$TARGET_SHEET_ID!A2:O201" \ + --values "$EMPTY_DATA" \ + --as bot >/dev/null 2>&1 + +# 4. 写入排序后的数据 +echo "$SORTED_DATA" > "$TMP_DATA" +END_ROW=$((ROW_COUNT + 1)) + +log "写入数据到目标表格..." +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ + --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ + --range "$TARGET_SHEET_ID!A2:O$END_ROW" \ + --values "$(cat $TMP_DATA)" \ + --as bot + +log "✅ 同步完成,共写入 $ROW_COUNT 条记录,最新反馈已排在最上方" + +# 清理临时文件 +rm -f "$TMP_DATA" + +log "=== 同步任务完成 ===" +exit 0 diff --git a/scripts/sync_lark_group_to_mysql.py b/scripts/sync_lark_group_to_mysql.py new file mode 100644 index 0000000..f55c2be --- /dev/null +++ b/scripts/sync_lark_group_to_mysql.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python3 +""" +飞书群聊消息同步到 MySQL (lark_group_message 表) +- Bot身份拉取群消息(增量) +- 非文本媒体上传到腾讯COS +- 记录写入 MySQL vala_test.lark_group_message +""" +import os +import sys +import json +import subprocess +import logging +import re +import pymysql +from datetime import datetime, timezone, timedelta +from pathlib import Path + +# ============ 配置 ============ + +# 飞书群 +CHAT_ID = "oc_fabff7672e62a9ced7b326ee4a286c26" +CHAT_NAME = "内容测试问题反馈" + +# Bot凭证 +LARK_CLI_CONFIG = "/root/.openclaw/credentials/xiaokui" + +# 同步状态 +LAST_SYNC_FILE = "/root/.openclaw/workspace-xiaokui/data/last_lark_group_sync_time" +WORK_DIR = "/tmp/lark_group_sync_workdir" + +# COS +COS_BASE_PATH = "vala_llm/user_feedback" + +# MySQL(从环境变量或默认值) +DB_HOST = os.environ.get("LARK_SYNC_DB_HOST", "bj-cdb-8frbdwju.sql.tencentcdb.com") +DB_PORT = int(os.environ.get("LARK_SYNC_DB_PORT", "25413")) +DB_USER = os.environ.get("LARK_SYNC_DB_USER", "chatbot") +DB_PASS = os.environ.get("LARK_SYNC_DB_PASS", "") +DB_NAME = os.environ.get("LARK_SYNC_DB_NAME", "vala_test") + +# 时区 +TZ = timezone(timedelta(hours=8)) + +# ============ 配置结束 ============ + +# 确保 lark-cli 在 PATH 中 +_NODE_BIN = "/root/.nvm/versions/node/v24.14.0/bin" +if _NODE_BIN not in os.environ.get("PATH", ""): + os.environ["PATH"] = _NODE_BIN + ":" + os.environ.get("PATH", "") + +logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') +log = logging.getLogger(__name__) + +# 引入COS上传器 +sys.path.insert(0, '/root/.openclaw/workspace-xiaokui/skills/tencent-cos-upload/scripts') +from cos_upload import CosUploader + + +def get_db_connection(): + """获取MySQL连接""" + return pymysql.connect( + host=DB_HOST, + port=DB_PORT, + user=DB_USER, + password=DB_PASS, + database=DB_NAME, + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor, + autocommit=False + ) + + +def lark_cli(*args, cwd=None): + """调用lark-cli并返回解析后的JSON""" + env = os.environ.copy() + env['LARKSUITE_CLI_CONFIG_DIR'] = LARK_CLI_CONFIG + cmd = ['lark-cli'] + list(args) + result = subprocess.run(cmd, capture_output=True, text=True, env=env, cwd=cwd or WORK_DIR) + if result.returncode != 0 and not result.stdout: + log.error(f"lark-cli error: {result.stderr}") + return None + try: + return json.loads(result.stdout) + except json.JSONDecodeError: + log.error(f"lark-cli JSON parse error: {result.stdout[:200]}") + return None + + +def fetch_messages(start_iso: str, end_iso: str) -> list: + """拉取群消息,支持分页""" + all_messages = [] + page_token = None + + while True: + args = [ + 'im', '+chat-messages-list', + '--chat-id', CHAT_ID, + '--start', start_iso, + '--end', end_iso, + '--sort', 'asc', + '--page-size', '50', + '--as', 'bot' + ] + if page_token: + args.extend(['--page-token', page_token]) + + result = lark_cli(*args) + if not result or not result.get('ok'): + err = result.get('error', {}).get('message', 'unknown') if result else 'no response' + log.error(f"拉取消息失败: {err}") + break + + messages = result.get('data', {}).get('messages', []) + all_messages.extend(messages) + + page_token = result.get('data', {}).get('page_token') + has_more = result.get('data', {}).get('has_more', False) + if not has_more or not page_token: + break + + return all_messages + + +def download_resource(msg_id: str, file_key: str, res_type: str, filename: str) -> str: + """下载飞书消息中的资源,返回本地路径""" + result = lark_cli( + 'im', '+messages-resources-download', + '--message-id', msg_id, + '--file-key', file_key, + '--type', res_type, + '--output', filename, + '--as', 'bot', + cwd=WORK_DIR + ) + local_path = os.path.join(WORK_DIR, filename) + if result and result.get('ok') and os.path.exists(local_path) and os.path.getsize(local_path) > 0: + return local_path + return None + + +def extract_file_key(msg_type: str, content: str) -> tuple: + """从消息内容中提取file_key和资源类型""" + if msg_type == 'image': + match = re.search(r'img_[a-zA-Z0-9_-]+', content) + return (match.group(0), 'image') if match else (None, None) + elif msg_type in ('media', 'audio'): + match = re.search(r'file_[a-zA-Z0-9_-]+', content) + return (match.group(0), 'file') if match else (None, None) + elif msg_type == 'file': + try: + c = json.loads(content) + return (c.get('file_key'), 'file') + except Exception: + match = re.search(r'file_[a-zA-Z0-9_-]+', content) + return (match.group(0), 'file') if match else (None, None) + return None, None + + +def get_media_info(msg_type: str, content: str) -> tuple: + """返回 (cos子目录, 文件扩展名, content_type)""" + if msg_type == 'image': + return 'image', '.png', 'image/png' + elif msg_type == 'media': + name_match = re.search(r'name="([^"]*)"', content) + ext = os.path.splitext(name_match.group(1))[1] if name_match else '.mp4' + return 'video', ext or '.mp4', 'video/mp4' + elif msg_type == 'audio': + return 'audio', '.ogg', 'audio/ogg' + elif msg_type == 'file': + try: + c = json.loads(content) + ext = os.path.splitext(c.get('file_name', ''))[1] + except Exception: + ext = '' + return 'file', ext or '.bin', 'application/octet-stream' + return 'other', '', 'application/octet-stream' + + +def process_message(msg: dict, cos_uploader, date_str: str) -> dict: + """ + 处理单条消息,返回字典用于插入数据库,或 None(跳过) + """ + msg_id = msg.get('message_id', '') + sender = msg.get('sender', {}) + sender_id = sender.get('id', '') + sender_name = sender.get('name', '未知') + create_time_str = msg.get('create_time', '') + msg_type = msg.get('msg_type', '') + content_raw = msg.get('content', '') + deleted = msg.get('deleted', False) + + if deleted or msg_type == 'system': + return None + + # 解析消息时间 + try: + # create_time 可能是毫秒时间戳字符串或ISO格式 + if create_time_str.isdigit(): + ts_ms = int(create_time_str) + msg_dt = datetime.fromtimestamp(ts_ms / 1000, tz=TZ) + else: + msg_dt = datetime.fromisoformat(create_time_str) + ts_ms = int(msg_dt.timestamp() * 1000) + except Exception: + msg_dt = datetime.now(TZ) + ts_ms = int(msg_dt.timestamp() * 1000) + + record = { + 'chat_id': CHAT_ID, + 'chat_name': CHAT_NAME, + 'sender_id': sender_id, + 'sender_name': sender_name, + 'message_id': msg_id, + 'msg_type': msg_type, + 'content': None, + 'media_url': None, + 'msg_time': msg_dt.strftime('%Y-%m-%d %H:%M:%S'), + 'msg_timestamp': ts_ms, + } + + # 文本消息 + if msg_type in ('text', 'post'): + text = re.sub(r'<[^>]*>', '', content_raw).strip() + record['content'] = text + return record + + # 表情包 + if msg_type == 'sticker': + record['content'] = '(表情包)' + return record + + # 媒体消息 + cos_subdir, ext, content_type = get_media_info(msg_type, content_raw) + file_key, res_type = extract_file_key(msg_type, content_raw) + + if not file_key: + record['content'] = f'(无法提取资源key)' + return record + + short_id = msg_id[-12:] + if msg_type == 'media': + name_match = re.search(r'name="([^"]*)"', content_raw) + orig_ext = os.path.splitext(name_match.group(1))[1] if name_match else ext + filename = f'{short_id}{orig_ext or ext}' + elif msg_type == 'file': + try: + c = json.loads(content_raw) + orig_ext = os.path.splitext(c.get('file_name', ''))[1] + except Exception: + orig_ext = ext + filename = f'{short_id}{orig_ext or ext}' + else: + filename = f'{short_id}{ext}' + + cos_key = f'{COS_BASE_PATH}/{cos_subdir}/{date_str}/{filename}' + + # 下载 + local_path = download_resource(msg_id, file_key, res_type, filename) + if not local_path: + log.warning(f"资源下载失败: {msg_id} ({msg_type})") + record['content'] = '(下载失败)' + return record + + # 上传COS + try: + url = cos_uploader.upload(local_path, cos_key, content_type) + log.info(f"COS上传成功: {cos_key}") + record['media_url'] = url + except Exception as e: + log.error(f"COS上传失败: {cos_key} - {e}") + record['content'] = '(上传失败)' + finally: + try: + os.remove(local_path) + except Exception: + pass + + return record + + +def insert_records(conn, records: list): + """批量插入记录到MySQL,使用 INSERT IGNORE 避免重复""" + if not records: + return 0 + + sql = """ + INSERT IGNORE INTO lark_group_message + (chat_id, chat_name, sender_id, sender_name, message_id, msg_type, content, media_url, msg_time, msg_timestamp) + VALUES + (%(chat_id)s, %(chat_name)s, %(sender_id)s, %(sender_name)s, %(message_id)s, + %(msg_type)s, %(content)s, %(media_url)s, %(msg_time)s, %(msg_timestamp)s) + """ + inserted = 0 + with conn.cursor() as cursor: + for record in records: + cursor.execute(sql, record) + inserted += cursor.rowcount + conn.commit() + return inserted + + +def main(): + os.makedirs(WORK_DIR, exist_ok=True) + os.makedirs(os.path.dirname(LAST_SYNC_FILE), exist_ok=True) + + # 读取上次同步时间 + if os.path.exists(LAST_SYNC_FILE): + with open(LAST_SYNC_FILE) as f: + last_sync_iso = f.read().strip() + else: + # 首次运行:从 2026-04-01 开始 + last_sync_iso = "2026-04-01T00:00:00+08:00" + + current_iso = datetime.now(TZ).isoformat() + log.info(f"开始同步: {last_sync_iso} -> {current_iso}") + + # 拉取消息 + messages = fetch_messages(last_sync_iso, current_iso) + if not messages: + log.info("没有新消息,同步结束") + with open(LAST_SYNC_FILE, 'w') as f: + f.write(current_iso) + return + + log.info(f"发现 {len(messages)} 条新消息") + + # 初始化COS + cos_uploader = CosUploader() + date_str = datetime.now(TZ).strftime('%Y-%m-%d') + + # 处理消息 + records = [] + for msg in messages: + record = process_message(msg, cos_uploader, date_str) + if record: + records.append(record) + + # 写入MySQL + if records: + conn = get_db_connection() + try: + inserted = insert_records(conn, records) + log.info(f"同步完成: 处理 {len(records)} 条, 新增 {inserted} 条 (重复跳过 {len(records) - inserted} 条)") + finally: + conn.close() + else: + log.info("无有效消息需要写入") + + # 更新同步时间 + with open(LAST_SYNC_FILE, 'w') as f: + f.write(current_iso) + + +if __name__ == '__main__': + main() diff --git a/scripts/sync_wechat_feedback_minutely.py b/scripts/sync_wechat_feedback_minutely.py new file mode 100644 index 0000000..de53076 --- /dev/null +++ b/scripts/sync_wechat_feedback_minutely.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +微信用户反馈每分钟同步到飞书收集表格 +""" +import os +import json +import pymysql +import subprocess +from datetime import datetime + +# 配置 +LOG_FILE = "/var/log/sync_wechat_feedback.log" +LAST_SYNC_ID_FILE = "/root/.openclaw/workspace-xiaokui/data/last_wechat_sync_id" +TARGET_SPREADSHEET_TOKEN = "AOxbsifk3hybRZteGowcMxNnnqc" +TARGET_SHEET_ID = "f17380" +LARK_CLI_CONFIG = "/root/.openclaw/credentials/xiaoyan" + +# 数据库配置 +DB_CONFIG = { + "host": "bj-cdb-8frbdwju.sql.tencentcdb.com", + "port": 25413, + "user": "read_only", + "password": "fdsfiidier^$*hjfdijjd232", + "database": "vala_test", + "charset": "utf8mb4" +} + +def log(message): + """写日志""" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + with open(LOG_FILE, "a", encoding="utf-8") as f: + f.write(f"[{timestamp}] {message}\n") + print(f"[{timestamp}] {message}") + +def get_last_sync_id(): + """获取上次同步的最大ID""" + if not os.path.exists(LAST_SYNC_ID_FILE): + return 0 + with open(LAST_SYNC_ID_FILE, "r") as f: + return int(f.read().strip()) + +def save_last_sync_id(last_id): + """保存上次同步的最大ID""" + with open(LAST_SYNC_ID_FILE, "w") as f: + f.write(str(last_id)) + +def fetch_new_wechat_data(last_id): + """从MySQL读取新增的微信反馈数据""" + conn = pymysql.connect(**DB_CONFIG) + cursor = conn.cursor() + sql = """ + SELECT id, msg_time, sender_name, msg_type, content + FROM wechat_group_message + WHERE id > %s + ORDER BY msg_time DESC + """ + cursor.execute(sql, (last_id,)) + rows = cursor.fetchall() + cursor.close() + conn.close() + return rows + +def convert_to_sheet_format(rows): + """转换为飞书表格需要的格式""" + sheet_rows = [] + max_id = 0 + for row in rows: + id, msg_time, sender_name, msg_type, content = row + if id > max_id: + max_id = id + + # 整理反馈内容 + if msg_type == "text": + feedback_content = content + elif msg_type == "image": + feedback_content = f"[图片] {content}" + elif msg_type == "video": + feedback_content = f"[视频] {content}" + else: + feedback_content = f"[{msg_type}] {content}" + + # 清理特殊字符 + feedback_content = feedback_content.replace("\n", " ").replace("\r", "").replace('"', '\\"') + + sheet_rows.append([ + "", # A 序号后续统一生成 + "微信-用户火线救火", # B 反馈渠道 + msg_time.strftime("%Y-%m-%d %H:%M:%S"), # C 反馈时间 + sender_name, # D 反馈人 + feedback_content, # E 反馈内容 + "", # F 备注 留空 + "" # G 回复 留空 + ]) + return sheet_rows, max_id + +def get_existing_sheet_data(): + """读取目标表格现有数据""" + cmd = [ + "lark-cli", "sheets", "+read", + "--spreadsheet-token", TARGET_SPREADSHEET_TOKEN, + "--range", f"{TARGET_SHEET_ID}!A2:O", + "--as", "bot" + ] + env = os.environ.copy() + env["LARKSUITE_CLI_CONFIG_DIR"] = LARK_CLI_CONFIG + env["PATH"] = "/root/.nvm/versions/node/v24.14.0/bin:" + env.get("PATH", "") + + result = subprocess.run(cmd, env=env, capture_output=True, text=True) + if result.returncode != 0: + log(f"读取表格失败: {result.stderr}") + return [] + + try: + data = json.loads(result.stdout) + values = data.get("data", {}).get("valueRange", {}).get("values", []) + # 过滤空行 + valid_values = [row for row in values if row and len(row) >= 1 and row[0] and row[0].strip()] + return valid_values + except Exception as e: + log(f"解析表格数据失败: {str(e)}") + return [] + +def merge_and_sort_data(new_rows, existing_rows): + """合并新老数据,按时间倒序排列,重新生成序号,去重""" + seen = set() + all_rows = [] + + # 合并新老数据 + for row in new_rows + existing_rows: + if not row or len(row) < 5: + continue + # 去重:时间+反馈人+内容 + key = f'{row[2]}_{row[3]}_{row[4]}' + if key in seen: + continue + seen.add(key) + all_rows.append(row) + + # 按反馈时间倒序排列 + all_rows.sort(key=lambda x: x[2], reverse=True) + # 重新生成序号 + for i, row in enumerate(all_rows): + row[0] = str(i + 1) + return all_rows + +def write_to_sheet(all_rows): + """写入数据到飞书表格""" + if not all_rows: + log("无数据需要写入") + return + + # 清空原有数据 + empty_data = [["" for _ in range(15)] for _ in range(200)] + cmd_clear = [ + "lark-cli", "sheets", "+write", + "--spreadsheet-token", TARGET_SPREADSHEET_TOKEN, + "--range", f"{TARGET_SHEET_ID}!A2:O201", + "--values", json.dumps(empty_data, ensure_ascii=False), + "--as", "bot" + ] + env = os.environ.copy() + env["LARKSUITE_CLI_CONFIG_DIR"] = LARK_CLI_CONFIG + env["PATH"] = "/root/.nvm/versions/node/v24.14.0/bin:" + env.get("PATH", "") + + subprocess.run(cmd_clear, env=env, capture_output=True) + + # 写入新数据 + end_row = len(all_rows) + 1 + cmd_write = [ + "lark-cli", "sheets", "+write", + "--spreadsheet-token", TARGET_SPREADSHEET_TOKEN, + "--range", f"{TARGET_SHEET_ID}!A2:O{end_row}", + "--values", json.dumps(all_rows, ensure_ascii=False), + "--as", "bot" + ] + + result = subprocess.run(cmd_write, env=env, capture_output=True, text=True) + if result.returncode != 0: + log(f"写入表格失败: {result.stderr}") + return False + return True + +def main(): + log("=== 微信反馈同步任务开始 ===") + last_sync_id = get_last_sync_id() + log(f"上次同步最大ID: {last_sync_id}") + + # 获取新数据 + new_rows = fetch_new_wechat_data(last_sync_id) + if not new_rows: + log("无新增微信反馈数据,退出") + return + + new_count = len(new_rows) + log(f"发现 {new_count} 条新增微信反馈数据") + + # 转换格式 + sheet_rows, new_max_id = convert_to_sheet_format(new_rows) + + # 获取现有数据 + existing_rows = get_existing_sheet_data() + log(f"现有表格数据量: {len(existing_rows)} 条") + + # 合并排序 + all_rows = merge_and_sort_data(sheet_rows, existing_rows) + log(f"合并后总数据量: {len(all_rows)} 条") + + # 写入表格 + success = write_to_sheet(all_rows) + if success: + save_last_sync_id(new_max_id) + log(f"✅ 同步完成,新增 {new_count} 条,总 {len(all_rows)} 条,已按时间倒序排列") + else: + log("❌ 同步失败") + + log("=== 同步任务完成 ===") + +if __name__ == "__main__": + main() diff --git a/scripts/sync_wechat_feedback_minutely.sh b/scripts/sync_wechat_feedback_minutely.sh new file mode 100755 index 0000000..3bfce25 --- /dev/null +++ b/scripts/sync_wechat_feedback_minutely.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# 任务名称:微信用户反馈每分钟同步到收集表格 +# 执行时间:* * * * * 每分钟执行 +# 归属 Agent:小葵 xiaokui + +set +e + +# 环境配置 +export PATH=/root/.nvm/versions/node/v24.14.0/bin:$PATH +LOG_FILE="/var/log/sync_wechat_feedback.log" +WORKSPACE="/root/.openclaw/workspace-xiaokui" +LAST_SYNC_ID_FILE="$WORKSPACE/data/last_wechat_sync_id" +TMP_DATA="$WORKSPACE/tmp/wechat_feedback.json" + +# 数据库配置 +DB_HOST="bj-cdb-8frbdwju.sql.tencentcdb.com" +DB_PORT="25413" +DB_USER="read_only" +DB_PASS="fdsfiidier^\$*hjfdijjd232" +DB_NAME="vala_test" + +# 目标表格配置 +TARGET_SPREADSHEET_TOKEN="AOxbsifk3hybRZteGowcMxNnnqc" +TARGET_SHEET_ID="f17380" + +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" +} + +# 初始化上次同步ID +if [ ! -f "$LAST_SYNC_ID_FILE" ]; then + echo "0" > "$LAST_SYNC_ID_FILE" +fi +LAST_SYNC_ID=$(cat "$LAST_SYNC_ID_FILE") + +log "=== 微信反馈同步任务开始 ===" +log "上次同步最大ID:$LAST_SYNC_ID" + +# 1. 读取微信新增反馈数据 +log "读取微信新增反馈数据..." +mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -N -e " +SELECT id, msg_time, sender_name, msg_type, content +FROM wechat_group_message +WHERE id > $LAST_SYNC_ID +ORDER BY msg_time DESC;" > /tmp/wechat_raw.txt + +if [ ! -s /tmp/wechat_raw.txt ]; then + log "无新增微信反馈数据,退出" + exit 0 +fi + +# 2. 转换为表格格式 +log "转换数据格式..." +NEW_DATA=$(awk ' +BEGIN { + print "[" + first = 1 +} +{ + if (first == 0) print "," + first = 0 + + id = $1 + time = $2 " " $3 + sender = $4 + type = $5 + content = substr($0, index($0,$6)) + + # 处理字段 + text = "" + image = "" + video = "" + if (type == "text") { + text = content + } else if (type == "image") { + image = content + } else if (type == "video") { + video = content + } else { + text = "[" type "] " content + } + + gsub(/"/, "\\\"", text) + gsub(/"/, "\\\"", image) + gsub(/"/, "\\\"", video) + + printf "[\"\", \"\", \"\", \"\", \"%s\", \"微信\", \"%s\", \"\", \"\", \"%s\", \"%s\", \"%s\", \"\", \"待确认\", \"\"]", time, sender, text, image, video +} +END { + print "\n]" +}' /tmp/wechat_raw.txt) + +NEW_COUNT=$(echo "$NEW_DATA" | jq length) +NEW_MAX_ID=$(tail -1 /tmp/wechat_raw.txt | awk '{print $1}') +log "发现 $NEW_COUNT 条新增微信反馈数据,最大ID:$NEW_MAX_ID" + +# 3. 读取目标表格现有数据(飞书+微信) +log "读取目标表格现有数据..." +TARGET_RAW=$(LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +read --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" --range "$TARGET_SHEET_ID!A2:O" --as bot) +TARGET_DATA=$(echo "$TARGET_RAW" | jq '.data.valueRange.values | map(select(.[0] != null and .[0] != ""))') + +if [ "$TARGET_DATA" == "null" ] || [ -z "$TARGET_DATA" ]; then + TARGET_DATA="[]" +fi + +# 4. 合并数据并按时间倒序排列 +echo "$NEW_DATA" > /tmp/new.json +echo "$TARGET_DATA" > /tmp/existing.json + +ALL_DATA=$(jq -s 'add | sort_by(.[4]) | reverse' /tmp/new.json /tmp/existing.json) + +# 5. 重新生成序号 +ALL_DATA=$(echo "$ALL_DATA" | jq 'to_entries | map(.value[0] = (.key + 1 | tostring) | .value)') + +echo "$ALL_DATA" > "$TMP_DATA" +ROW_COUNT=$(echo "$ALL_DATA" | jq length) +END_ROW=$((ROW_COUNT + 1)) + +# 6. 清空目标表格并写入合并后的数据 +log "写入合并后的数据,共 $ROW_COUNT 条..." +# 先清空原有数据 +EMPTY_DATA=$(printf '[]%.0s' {1..200} | jq -s '.') +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ + --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ + --range "$TARGET_SHEET_ID!A2:O201" \ + --values "$EMPTY_DATA" \ + --as bot >/dev/null 2>&1 + +# 写入新数据 +LARKSUITE_CLI_CONFIG_DIR=/root/.openclaw/credentials/xiaoyan lark-cli sheets +write \ + --spreadsheet-token "$TARGET_SPREADSHEET_TOKEN" \ + --range "$TARGET_SHEET_ID!A2:O$END_ROW" \ + --values "$(cat $TMP_DATA)" \ + --as bot + +# 7. 更新上次同步ID +echo "$NEW_MAX_ID" > "$LAST_SYNC_ID_FILE" + +log "✅ 同步完成:新增 $NEW_COUNT 条微信反馈,当前总记录数 $ROW_COUNT 条,已按时间倒序排列" + +# 清理临时文件 +rm -f /tmp/wechat_raw.txt /tmp/new.json /tmp/existing.json "$TMP_DATA" + +log "=== 同步任务完成 ===" +exit 0 diff --git a/skills/user-feedback-collector/SKILL.md b/skills/user-feedback-collector/SKILL.md index c5be7dd..8479518 100644 --- a/skills/user-feedback-collector/SKILL.md +++ b/skills/user-feedback-collector/SKILL.md @@ -48,7 +48,7 @@ description: | | 反馈渠道 | 来源 | 存储位置 | 更新频率 | |---------|------|----------|----------| | 主动上报 | 用户主动发送 `用户反馈:` 前缀消息 | 飞书知识库文档 | 实时 | -| 飞书内部测试反馈 | 「内容测试问题反馈」群(`oc_fabff7672e62a9ced7b326ee4a286c26`) | 飞书表格:`E8vFsCmPBhT4SCtNmnJchqeJnJe` | 每6小时自动同步 | +| 飞书内部测试反馈 | 「内容测试问题反馈」群(`oc_fabff7672e62a9ced7b326ee4a286c26`) | MySQL:`vala_test.lark_group_message` 表 | 每4小时自动同步 | | 微信用户反馈 | 微信用户反馈群 | MySQL:`vala_test.wechat_group_message` 表 | 实时更新 | ## 目标存储位置