Files
eu_active_script/hang/Rewrite/car_active_seek_UK.py

348 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import pandas as pd
import requests
import logging
import time
import json
from sqlalchemy import create_engine
from urllib.parse import quote_plus
from datetime import datetime
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s %(name)s %(message)s'
)
# ====================== 全局配置 ======================
# 数据库连接配置
DB_CONFIG = {
'host': 'eutsp-prod.mysql.germany.rds.aliyuncs.com',
'port': 3306,
'user': 'international_tsp_eu_r',
'password': 'ZXhBgo1TB2XbF3kP',
'database': 'chery_international_tsp_eu'
}
# 飞书多维表格配置 (多市场表格配置)
FEISHU_APP_TOKEN = "T3QLbmpqma014ussjy9cgitqnSh" # 应用Token (恢复此关键变量)
MARKET_TABLES = [
{"name": "英国", "table_id": "tblbfEHrsojQ4D5w", "view_id": "vewBvcYDAa"}
]
# 通用字段映射
VIN_FIELD_NAME = "车架号"
ACTIVE_FIELD_NAME = "激活状态"
VEHICLE_NAME_FIELD = "车型"
# 飞书应用凭证
APP_ID = "cli_a8b50ec0eed1500d"
APP_SECRET = "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK"
# 飞书机器人配置
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/3bc45247-c469-47a0-bfe9-c62e46c5aca0"
MULTI_TABLE_URL = "https://my-ichery.feishu.cn/base/T3QLbmpqma014ussjy9cgitqnSh?from=from_copylink"
# ====================== 通用函数 ======================
def get_access_token():
"""获取飞书访问令牌"""
api_token_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
api_post_data = {"app_id": APP_ID, "app_secret": APP_SECRET}
try:
response = requests.post(api_token_url, json=api_post_data, timeout=10)
response.raise_for_status()
resp_data = response.json()
if resp_data.get("code") == 0:
return resp_data["tenant_access_token"]
else:
raise Exception(f"获取Token失败: {resp_data.get('msg')}")
except Exception as e:
logging.error(f"获取飞书Token异常: {e}")
raise
def send_feishu_message(message):
"""发送飞书通知"""
headers = {'Content-Type': 'application/json'}
payload = {
"msg_type": "interactive",
"card": {
"config": {"wide_screen_mode": True},
"elements": [
{
"tag": "div",
"text": {"content": message, "tag": "lark_md"}
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {"content": "查看多维表格", "tag": "plain_text"},
"type": "primary",
"url": MULTI_TABLE_URL
}
]
}
],
"header": {
"title": {"content": "车辆状态同步报告", "tag": "plain_text"},
"template": "blue"
}
}
}
try:
response = requests.post(WEBHOOK_URL, headers=headers, json=payload, timeout=10)
response.raise_for_status()
return response.json()
except Exception as e:
logging.error(f"飞书消息发送失败: {str(e)}")
return None
def read_feishu_records(app_token, table_id, view_id, retry=3):
"""从飞书多维表格读取车辆记录"""
access_token = get_access_token()
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records"
params = {
"page_size": 500,
"view_id": view_id # 添加视图ID参数
}
all_records = []
page_token = None
while True:
if page_token:
params["page_token"] = page_token
for attempt in range(retry):
try:
response = requests.get(url, headers=headers, params=params, timeout=30)
response.raise_for_status()
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"API错误: {resp_data.get('msg')}")
records = resp_data["data"]["items"]
all_records.extend(records)
has_more = resp_data["data"].get("has_more")
page_token = resp_data["data"].get("page_token") if has_more else None
break
except Exception as e:
logging.warning(f"读取多维表格失败(尝试 {attempt + 1}/{retry}): {e}")
time.sleep(2 ** attempt)
else:
raise Exception("无法读取多维表格数据")
if not page_token:
break
return all_records
def fetch_activation_data():
"""从数据库查询车辆激活状态和车型"""
try:
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
)
sql_query = """
SELECT
v.VIN AS car_vins,
CASE s.STATE
WHEN 2 THEN 'active'
ELSE 'no_active'
END AS active_status,
iv.`NAME` AS `车型`
FROM v_vehicle_info v
JOIN v_tbox_info t USING (VIN)
JOIN v_sim_info s USING (SN)
LEFT JOIN v_material_info m ON m.MATERIAL_ID = v.MATERIAL_ID
LEFT JOIN v_outside_vehicle_type ov ON ov.TYPE_CODE = m.OUTSIDE_TYPE_CODE
LEFT JOIN v_inside_vehicle_type iv ON iv.TYPE_CODE = ov.INSIDE_TYPE_CODE
"""
df = pd.read_sql(sql_query, engine)
df = df.drop_duplicates(subset=['car_vins'], keep='first')
expected = ['car_vins', 'active_status', VEHICLE_NAME_FIELD]
missing = [c for c in expected if c not in df.columns]
if missing:
logging.error(f"查询结果缺少列: {missing}")
raise KeyError(f"Missing columns: {missing}")
df2 = df.set_index('car_vins')[['active_status', VEHICLE_NAME_FIELD]]
return df2.to_dict(orient='index')
except Exception as e:
logging.error(f"数据库查询失败: {e}")
raise
def compare_and_update_records(app_token, table_id, view_id, activation_data):
"""比较并更新飞书多维表格记录"""
access_token = get_access_token()
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
feishu_records = read_feishu_records(app_token, table_id, view_id)
logging.info(f"读取 {len(feishu_records)} 条多维表格记录完成")
# 初始化计数器和待更新列表
records_to_update = []
activated_count = 0
not_activated_count = 0
# Streamline record processing and validation upfront
valid_records = [
record for record in feishu_records
if record.get("fields", {}).get(VIN_FIELD_NAME)
]
logging.info(f"有效记录数: {len(valid_records)} (过滤无效记录)")
for record in valid_records:
record_id = record["record_id"]
fields = record["fields"]
vin = fields[VIN_FIELD_NAME]
# 数据库信息匹配
db_data = activation_data.get(vin)
if not db_data:
continue # 如果VIN不存在于数据库则跳过
db_status = db_data['active_status']
db_name = db_data[VEHICLE_NAME_FIELD]
current_status = fields.get(ACTIVE_FIELD_NAME, "").lower()
current_name = fields.get(VEHICLE_NAME_FIELD, "")
# 需要更新的记录
if db_status != current_status or db_name != current_name:
records_to_update.append({
"record_id": record_id,
"fields": {
ACTIVE_FIELD_NAME: db_status,
VEHICLE_NAME_FIELD: db_name
}
})
if db_status == "active":
activated_count += 1
else:
not_activated_count += 1
total_updated = len(records_to_update)
logging.info(f"准备更新 {total_updated} 条记录 (激活: {activated_count}, 未激活: {not_activated_count})")
# 批量更新记录
batch_size = 100
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
def batch_update(batch):
payload = {"records": batch}
for attempt in range(3): # 网络重试机制
try:
response = requests.post(url, headers=headers, json=payload, timeout=30)
if response.status_code == 200 and response.json().get("code") == 0:
return True # 更新成功
else:
logging.warning(f"更新失败,第{attempt + 1}次尝试: {response.json().get('msg')}")
except Exception as e:
logging.warning(f"网络异常,第{attempt + 1}次尝试: {e}")
time.sleep(2 ** attempt) # 指数级等待
return False # 所有尝试均失败
# 批次处理记录更新
for i in range(0, total_updated, batch_size):
batch = records_to_update[i:i + batch_size]
if batch_update(batch):
logging.info(f"批次 {i // batch_size + 1} 更新成功 ({len(batch)} 条记录)")
else:
logging.error(f"批次 {i // batch_size + 1} 更新失败 ({len(batch)} 条记录)")
return total_updated, activated_count, not_activated_count
# ====================== 多表格处理流程 ======================
def sync_all_markets():
"""执行所有市场的车辆激活状态同步"""
start_time = time.time()
all_results = []
try:
# 一次性获取所有激活数据(避免多次查询数据库)
logging.info("开始获取数据库激活数据...")
activation_data = fetch_activation_data()
logging.info(f"获取到 {len(activation_data)} 条车辆激活数据")
# 按市场依次处理
for market in MARKET_TABLES:
market_name = market["name"]
table_id = market["table_id"]
view_id = market["view_id"]
logging.info(f"开始同步市场: {market_name} (表格ID: {table_id})")
try:
# 使用FEISHU_APP_TOKEN修复NameError问题 ⭐⭐关键修复
updated, activated, not_activated = compare_and_update_records(
FEISHU_APP_TOKEN,
table_id,
view_id,
activation_data
)
result = {
"market": market_name,
"table_id": table_id,
"total_updated": updated,
"activated_count": activated,
"not_activated_count": not_activated,
"success": True
}
logging.info(f"{market_name}同步完成: 更新{updated}台 (✅激活{activated}台,❌未激活{not_activated}台)")
except Exception as e:
result = {
"market": market_name,
"table_id": table_id,
"error": str(e),
"success": False
}
logging.error(f"{market_name}同步失败: {str(e)}", exc_info=True)
all_results.append(result)
time.sleep(1) # 每个市场处理间隔
# 统计总体数据
total_elapsed = time.time() - start_time
return {
"success": True,
"results": all_results,
"elapsed_time": total_elapsed,
"start_time": start_time
}
except Exception as e:
logging.error(f"整体同步流程失败: {str(e)}", exc_info=True)
return {
"success": False,
"error": str(e),
"elapsed_time": time.time() - start_time
}
if __name__ == '__main__':
# 执行多市场同步
sync_result = sync_all_markets()
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")