207 lines
7.7 KiB
Python
207 lines
7.7 KiB
Python
|
|
import pandas as pd
|
|||
|
|
import requests
|
|||
|
|
import json
|
|||
|
|
from datetime import datetime
|
|||
|
|
from sqlalchemy import create_engine
|
|||
|
|
from urllib.parse import quote_plus
|
|||
|
|
import logging
|
|||
|
|
|
|||
|
|
# 配置日志
|
|||
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_access_token():
|
|||
|
|
"""获取飞书访问令牌[1,7](@ref)"""
|
|||
|
|
api_token_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
|
|||
|
|
api_post_data = {"app_id": "cli_a8b50ec0eed1500d", "app_secret": "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK"}
|
|||
|
|
try:
|
|||
|
|
response = requests.post(api_token_url, json=api_post_data, timeout=10)
|
|||
|
|
response.raise_for_status()
|
|||
|
|
if response.json().get("code") == 0:
|
|||
|
|
return response.json()["tenant_access_token"]
|
|||
|
|
else:
|
|||
|
|
raise Exception(f"获取Token失败: {response.text}")
|
|||
|
|
except Exception as e:
|
|||
|
|
logging.error(f"获取飞书Token异常: {str(e)}")
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
|
|||
|
|
def overwrite_data(spreadsheet_token, values, access_token=None):
|
|||
|
|
"""覆盖写入数据到飞书多维表格[1,6](@ref)"""
|
|||
|
|
if not access_token:
|
|||
|
|
access_token = get_access_token()
|
|||
|
|
|
|||
|
|
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values"
|
|||
|
|
headers = {
|
|||
|
|
"Authorization": f"Bearer {access_token}",
|
|||
|
|
"Content-Type": "application/json; charset=utf-8",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
row_count = len(values) + 1
|
|||
|
|
range_str = f"46ea20!A1:B{row_count}"
|
|||
|
|
|
|||
|
|
post_data = {
|
|||
|
|
"valueRange": {
|
|||
|
|
"range": range_str,
|
|||
|
|
"values": values
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
response = requests.put(url, json=post_data, headers=headers, timeout=15)
|
|||
|
|
response.raise_for_status()
|
|||
|
|
return response
|
|||
|
|
except Exception as e:
|
|||
|
|
logging.error(f"写入飞书表格失败: {str(e)}")
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 数据库连接配置
|
|||
|
|
DB_CONFIG = {
|
|||
|
|
'host': 'eutsp-prod.mysql.germany.rds.aliyuncs.com',
|
|||
|
|
'port': 3306,
|
|||
|
|
'user': 'international_tsp_eu_r',
|
|||
|
|
'password': 'ZXhBgo1TB2XbF3kP',
|
|||
|
|
'database': 'chery_international_tsp_eu'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 创建SQLAlchemy引擎(解决pandas警告问题)
|
|||
|
|
safe_password = quote_plus(DB_CONFIG['password'])
|
|||
|
|
engine = create_engine(
|
|||
|
|
f"mysql+pymysql://{DB_CONFIG['user']}:{safe_password}@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}",
|
|||
|
|
pool_recycle=3600,
|
|||
|
|
pool_pre_ping=True,
|
|||
|
|
pool_size=5,
|
|||
|
|
max_overflow=10
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def fetch_data_from_db():
|
|||
|
|
"""从数据库执行SQL查询获取数据(使用SQLAlchemy引擎)"""
|
|||
|
|
try:
|
|||
|
|
sql_query = """
|
|||
|
|
SELECT
|
|||
|
|
v_vehicle_info.VIN AS car_vins,
|
|||
|
|
CASE v_sim_info.STATE
|
|||
|
|
WHEN 2 THEN 'active'
|
|||
|
|
ELSE 'no_active'
|
|||
|
|
END AS active_status,
|
|||
|
|
v_vehicle_info.MATERIAL_ID as MATERIAL,
|
|||
|
|
v_vehicle_info.SALES_TERRITORY as Area,
|
|||
|
|
v_sim_info.ACTIVATION_DATE as active_date
|
|||
|
|
FROM v_vehicle_info
|
|||
|
|
JOIN v_tbox_info USING (VIN)
|
|||
|
|
JOIN v_sim_info USING (SN);
|
|||
|
|
"""
|
|||
|
|
return pd.read_sql(sql_query, engine)
|
|||
|
|
except Exception as e:
|
|||
|
|
logging.error(f"数据库查询失败: {str(e)}")
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
|
|||
|
|
def send_feishu_message(webhook_url, message, spreadsheet_token=None, retry=2):
|
|||
|
|
"""
|
|||
|
|
通过Webhook发送飞书消息,支持添加多维表格链接[2,8](@ref)
|
|||
|
|
:param webhook_url: 机器人Webhook地址
|
|||
|
|
:param message: 要发送的文本内容
|
|||
|
|
:param spreadsheet_token: 多维表格ID(可选)
|
|||
|
|
:param retry: 重试次数
|
|||
|
|
"""
|
|||
|
|
headers = {'Content-Type': 'application/json'}
|
|||
|
|
|
|||
|
|
# 如果有提供多维表格ID,添加表格链接按钮
|
|||
|
|
if spreadsheet_token:
|
|||
|
|
spreadsheet_url = f"https://my-ichery.feishu.cn/sheets/{spreadsheet_token}"
|
|||
|
|
payload = {
|
|||
|
|
"msg_type": "interactive",
|
|||
|
|
"card": {
|
|||
|
|
"config": {"wide_screen_mode": True},
|
|||
|
|
"elements": [
|
|||
|
|
{
|
|||
|
|
"tag": "div",
|
|||
|
|
"text": {"content": message, "tag": "lark_md"}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"actions": [{
|
|||
|
|
"tag": "button",
|
|||
|
|
"text": {"content": "查看多维表格", "tag": "plain_text"},
|
|||
|
|
"type": "primary",
|
|||
|
|
"url": spreadsheet_url
|
|||
|
|
}],
|
|||
|
|
"tag": "action"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"header": {
|
|||
|
|
"title": {"content": "数据同步完成通知", "tag": "plain_text"},
|
|||
|
|
"template": "green"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else:
|
|||
|
|
payload = {"msg_type": "text", "content": {"text": message}}
|
|||
|
|
|
|||
|
|
for attempt in range(retry):
|
|||
|
|
try:
|
|||
|
|
response = requests.post(webhook_url, headers=headers, data=json.dumps(payload), timeout=10)
|
|||
|
|
response.raise_for_status()
|
|||
|
|
return response.json()
|
|||
|
|
except Exception as e:
|
|||
|
|
logging.warning(f"飞书消息发送失败(尝试 {attempt + 1}/{retry}): {str(e)}")
|
|||
|
|
if attempt < retry - 1:
|
|||
|
|
time.sleep(2) # 等待后重试
|
|||
|
|
raise Exception(f"飞书消息发送失败,重试{retry}次后仍不成功")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
# 配置参数
|
|||
|
|
SPREADSHEET_ID = "MIqssvuB3h3XmgtkI26cBX7Angc"
|
|||
|
|
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/d82e4ada-8f78-40ae-b98e-75af0c448c95"
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
logging.info("开始数据同步流程...")
|
|||
|
|
|
|||
|
|
# 1. 从数据库获取数据
|
|||
|
|
df = fetch_data_from_db()
|
|||
|
|
logging.info(f"成功获取{len(df)}条车辆数据")
|
|||
|
|
|
|||
|
|
# 2. 处理数据
|
|||
|
|
df['active_date'] = pd.to_datetime(df['active_date'], errors='coerce')
|
|||
|
|
active_df = df[df['active_status'] == 'active']
|
|||
|
|
logging.info(f"有效激活数据: {len(active_df)}条")
|
|||
|
|
|
|||
|
|
# 3. 按天统计激活数
|
|||
|
|
daily_count = active_df.groupby(active_df['active_date'].dt.date).size().reset_index(name='active_count')
|
|||
|
|
values = [[str(d), str(c)] for d, c in daily_count.values.tolist()] # 确保值为字符串类型
|
|||
|
|
logging.info(f"生成{len(values)}条日期统计记录")
|
|||
|
|
|
|||
|
|
# 4. 覆盖写入多维表格
|
|||
|
|
response = overwrite_data(SPREADSHEET_ID, values)
|
|||
|
|
if response.status_code == 200:
|
|||
|
|
logging.info("数据成功写入飞书多维表格")
|
|||
|
|
|
|||
|
|
# 5. 构建通知消息
|
|||
|
|
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|||
|
|
today_count = daily_count[daily_count['active_date'] == datetime.now().date()]
|
|||
|
|
weekly_count = daily_count[daily_count['active_date'] >= (datetime.now() - pd.Timedelta(days=7)).date()]
|
|||
|
|
|
|||
|
|
stats_summary = f"• 今日激活车辆: {today_count['active_count'].values[0] if not today_count.empty else 0}\n"
|
|||
|
|
stats_summary += f"• 本周激活车辆: {weekly_count['active_count'].sum()}\n"
|
|||
|
|
stats_summary += f"• 累计激活车辆: {len(active_df)}"
|
|||
|
|
|
|||
|
|
message = f"🚗 车辆数据同步完成 ✅\n"
|
|||
|
|
message += f"⏰ 时间: {current_time}\n"
|
|||
|
|
message += f"📊 统计摘要:\n{stats_summary}\n"
|
|||
|
|
|
|||
|
|
# 6. 发送包含多维表格链接的通知
|
|||
|
|
result = send_feishu_message(WEBHOOK_URL, message, SPREADSHEET_ID)
|
|||
|
|
logging.info(f"飞书消息发送成功: {result}")
|
|||
|
|
else:
|
|||
|
|
error_msg = f"表格写入失败: {response.status_code} - {response.text}"
|
|||
|
|
logging.error(error_msg)
|
|||
|
|
error_message = f"❌ 数据同步失败\n{error_msg}"
|
|||
|
|
send_feishu_message(WEBHOOK_URL, error_message)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
error_message = f"❌ 数据同步异常: {str(e)}"
|
|||
|
|
logging.exception("程序执行异常")
|
|||
|
|
send_feishu_message(WEBHOOK_URL, error_message)
|