152 lines
5.1 KiB
Bash
Executable File
152 lines
5.1 KiB
Bash
Executable File
#!/bin/bash
|
||
# 用户购课完课统计定时任务脚本
|
||
# 执行时间:每天9:00
|
||
# 输出:Excel报表,发送到指定群
|
||
set -euo pipefail
|
||
|
||
# 配置参数
|
||
DB_HOST="bj-postgres-16pob4sg.sql.tencentcdb.com"
|
||
DB_PORT="28591"
|
||
DB_NAME="vala_bi"
|
||
DB_USER="ai_member"
|
||
SQL_PATH="/root/.openclaw/workspace/scripts/用户购课完课统计.sql"
|
||
OUTPUT_CSV="/tmp/user_course_stat_$(date +%Y%m%d).csv"
|
||
OUTPUT_EXCEL="/root/.openclaw/workspace/output/用户购课完课统计_$(date +%Y%m%d).xlsx"
|
||
TARGET_CHAT_ID="oc_af81515caefe26918736ad1941286224"
|
||
LOG_FILE="/var/log/user_course_stat.log"
|
||
|
||
# 确保 PATH 包含必要工具
|
||
export PATH="/usr/bin:/bin:/usr/local/bin:/root/.nvm/versions/node/v24.14.0/bin:$PATH"
|
||
|
||
# 加载数据库密码
|
||
source /root/.openclaw/workspace/secrets.env
|
||
|
||
log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" >&2; }
|
||
|
||
log "========== 开始执行用户购课完课统计任务 =========="
|
||
|
||
# 1. 运行SQL导出CSV
|
||
log "执行SQL查询..."
|
||
export PGPASSWORD="${PG_ONLINE_PASSWORD}"
|
||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" --csv -f "$SQL_PATH" > "$OUTPUT_CSV" 2>> "$LOG_FILE"
|
||
|
||
if [ ! -s "$OUTPUT_CSV" ]; then
|
||
log "ERROR: SQL查询结果为空"
|
||
exit 1
|
||
fi
|
||
log "SQL查询完成,CSV大小: $(wc -c < "$OUTPUT_CSV") bytes"
|
||
|
||
# 2. CSV转Excel
|
||
log "生成Excel..."
|
||
python3 -c "
|
||
import pandas as pd
|
||
import numpy as np
|
||
try:
|
||
np._get_promotion_state = lambda *args, **kwargs: 0
|
||
except:
|
||
pass
|
||
df = pd.read_csv('${OUTPUT_CSV}', low_memory=False)
|
||
with pd.ExcelWriter('${OUTPUT_EXCEL}') as writer:
|
||
df.to_excel(writer, sheet_name='用户统计数据', index=False)
|
||
" 2>> "$LOG_FILE"
|
||
|
||
if [ ! -f "$OUTPUT_EXCEL" ]; then
|
||
log "ERROR: Excel生成失败"
|
||
exit 1
|
||
fi
|
||
log "Excel生成完成,大小: $(wc -c < "$OUTPUT_EXCEL") bytes"
|
||
|
||
# 3. 获取飞书token
|
||
log "获取飞书访问令牌..."
|
||
APP_ID=$(jq -r '.apps[0].appId' /root/.openclaw/credentials/xiaoxi/config.json)
|
||
APP_SECRET=$(jq -r '.apps[0].appSecret' /root/.openclaw/credentials/xiaoxi/config.json)
|
||
TOKEN=$(curl -s -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" \
|
||
| jq -r '.tenant_access_token')
|
||
|
||
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
|
||
log "ERROR: 获取飞书token失败"
|
||
exit 1
|
||
fi
|
||
|
||
# 4. 先发文本消息
|
||
log "发送文本通知..."
|
||
TEXT_PAYLOAD=$(jq -n --arg rid "$TARGET_CHAT_ID" --arg text "【每日定时统计】$(date +%Y年%m月%d日)用户购课与完课情况统计,请查收附件。" \
|
||
'{receive_id: $rid, msg_type: "text", content: ({text: $text} | tostring)}')
|
||
TEXT_RESP=$(curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$TEXT_PAYLOAD")
|
||
TEXT_CODE=$(echo "$TEXT_RESP" | jq -r '.code')
|
||
if [ "$TEXT_CODE" != "0" ]; then
|
||
log "WARN: 文本消息发送失败: $TEXT_RESP"
|
||
fi
|
||
|
||
# 5. 上传Excel文件(带重试)
|
||
upload_file() {
|
||
local attempt=1
|
||
local max_attempts=3
|
||
while [ $attempt -le $max_attempts ]; do
|
||
log "上传文件 (第${attempt}次尝试)..."
|
||
local fk
|
||
fk=$(curl -s --max-time 30 -X POST "https://open.feishu.cn/open-apis/im/v1/files" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-F "file_type=xls" \
|
||
-F "file_name=用户购课完课统计_$(date +%Y%m%d).xlsx" \
|
||
-F "file=@${OUTPUT_EXCEL}" | jq -r '.data.file_key')
|
||
if [ -n "$fk" ] && [ "$fk" != "null" ]; then
|
||
echo "$fk"
|
||
return 0
|
||
fi
|
||
log "WARN: 第${attempt}次文件上传失败,等待3秒后重试..."
|
||
sleep 3
|
||
attempt=$((attempt + 1))
|
||
done
|
||
return 1
|
||
}
|
||
|
||
FILE_KEY=$(upload_file)
|
||
if [ -z "$FILE_KEY" ] || [ "$FILE_KEY" = "null" ]; then
|
||
log "ERROR: 文件上传失败(已重试3次)"
|
||
exit 1
|
||
fi
|
||
log "文件上传成功,file_key: ${FILE_KEY}"
|
||
|
||
# 6. 发送文件消息到群(带重试)
|
||
send_file_msg() {
|
||
local attempt=1
|
||
local max_attempts=3
|
||
local fk="$1"
|
||
while [ $attempt -le $max_attempts ]; do
|
||
log "发送文件消息 (第${attempt}次尝试)..."
|
||
local payload
|
||
payload=$(jq -n --arg rid "$TARGET_CHAT_ID" --arg fk "$fk" \
|
||
'{receive_id: $rid, msg_type: "file", content: ({file_key: $fk} | tostring)}')
|
||
local resp
|
||
resp=$(curl -s --max-time 15 -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$payload")
|
||
local code
|
||
code=$(echo "$resp" | jq -r '.code')
|
||
if [ "$code" = "0" ]; then
|
||
log "文件消息发送成功"
|
||
return 0
|
||
fi
|
||
log "WARN: 第${attempt}次文件消息发送失败 (code=${code}): $resp"
|
||
sleep 3
|
||
attempt=$((attempt + 1))
|
||
done
|
||
return 1
|
||
}
|
||
|
||
if ! send_file_msg "$FILE_KEY"; then
|
||
log "ERROR: 文件消息发送失败(已重试3次)"
|
||
exit 1
|
||
fi
|
||
|
||
# 7. 清理临时CSV文件
|
||
rm -f "$OUTPUT_CSV"
|
||
log "========== 任务执行完成 =========="
|