欧洲分国家车辆激活数查询脚本

This commit is contained in:
2026-03-18 15:13:48 +08:00
commit 1438117ba7
32 changed files with 5102 additions and 0 deletions

95
aa Normal file
View File

@@ -0,0 +1,95 @@
config:
printCql: true
httpTimeout: 10000
server:
port: 80
servlet:
context-path: /scls
spring:
main:
allow-circular-references: true
application:
name: tsp-statistics-control-log-service
jackson:
default-property-inclusion: non_null
mvc:
throw-exception-if-no-handler-found: true
pathmatch:
matching-strategy: ant_path_matcher
datasource:
url: jdbc:mysql://eutsp-uat.mysql.germany.rds.aliyuncs.com:3306/CHERY_INTERNATIONAL_TSP_EU?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&allowMultiQueries=true
username: international_tsp_eu
password: p%geZgf$$n26Qpcn
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
minimum-idle: 5
idle-timeout: 180000
maximum-pool-size: 10
auto-commit: true
connection-timeout: 30000
connection-test-query: select 1
kafka:
bootstrap-servers: 10.95.3.204:9092,10.95.3.205:9092,10.95.3.206:9092
producer:
bootstrap-servers: 10.95.3.204:9092,10.95.3.205:9092,10.95.3.206:9092
# 以下序列方式请根据项目实际情况进行配置,如果和下面相同则项目中可以不用配置
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.ByteArraySerializer
#发送失败,重试次数
retries: 3
# acks=0 生产者在成功写入消息之前不会等待任何来自服务器的响应。
# acks=1 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
acks: 1
properties:
linger:
ms: 1
batch-size: 16384
buffer-memory: 33554432
consumer:
bootstrap-servers: 10.95.3.204:9092,10.95.3.205:9092,10.95.3.206:9092
auto-offset-reset: earliest
group-id: ${spring.application.name}
enable-auto-commit: true
auto-commit-interval: 1000
# 以下序列方式请根据项目实际情况进行配置,如果和下面相同则项目中可以不用配置
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.ByteArrayDeserializer
listener:
ack-mode:
cache:
type: redis
data:
cassandra:
keyspace-name: tsp_eu
contact-points:
- 10.95.3.201
- 10.95.3.202
- 10.95.3.203
port: 9042
username: tsp_eu_rw
password: awXKeq6cJIytuS4y
session-name: Test Cluster
local-datacenter: dc1 #默认的数据中心
request:
timeout: 30s
mybatis-plus:
global-config:
sql-parser-cache: true
logging:
level:
root: INFO
remote-control-log-kafka-topic: tsp-remote-control-flow-data-eu
threadPool:
main:
corePoolSize: 10
maximumPoolSize: 20
queueCapacity: 50
rejectedPolicy: 1

411
hang/0630.py Normal file
View File

@@ -0,0 +1,411 @@
import requests
import logging
import time
from datetime import datetime, timedelta, date
# ====================== 配置区域 ======================
# 飞书应用凭证
APP_ID = "cli_a8b50ec0eed1500d"
APP_SECRET = "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK"
# 日统计表格配置
DAILY_SPREADSHEET_ID = "MIqssvuB3h3XmgtkI26cBX7Angc" # 日统计电子表格ID
DAILY_SHEET_ID = "46ea20" # 日统计工作表ID
DAILY_URL = "https://my-ichery.feishu.cn/sheets/MIqssvuB3h3XmgtkI26cBX7Angc?sheet=46ea20" # 日统计表格链接
# 周统计表格配置
WEEKLY_BASE_TOKEN = "T3QLbmpqma014ussjy9cgitqnSh" # 周统计多维表格ID
WEEKLY_TABLE_ID = "tbloB11YuVOSdi9e" # 周统计表ID
WEEKLY_URL = "https://my-ichery.feishu.cn/base/T3QLbmpqma014ussjy9cgitqnSh?table=tbloB11YuVOSdi9e" # 周统计表格链接
# 飞书机器人配置
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/b9c810bd-a9b6-4b5a-8ff9-943b93cadaee"
# ====================== 日志配置 ======================
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def get_access_token():
"""获取飞书访问令牌[1,3](@ref)"""
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异常: {str(e)}")
raise
def read_daily_data(retry=3):
"""从飞书电子表格读取日统计数据[1,3](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{DAILY_SPREADSHEET_ID}/values/{DAILY_SHEET_ID}"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
for attempt in range(retry):
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"API错误: {resp_data.get('msg')}")
# 解析数据:跳过标题行,只取日期和激活数量
values = resp_data['data']['valueRange']['values'][1:]
daily_data = []
for row in values:
try:
date_str = row[0]
count = int(row[1])
date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
daily_data.append({"date": date_obj, "count": count})
except (IndexError, ValueError) as e:
logging.warning(f"数据格式错误: {row} - {str(e)}")
return daily_data
except Exception as e:
logging.warning(f"读取飞书表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt) # 指数退避重试
raise Exception(f"读取飞书表格失败,重试{retry}次后仍不成功")
def clear_weekly_table(retry=3):
"""清空周统计表(覆盖写入前的准备)[3,5](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 获取所有记录ID
record_ids = []
page_token = ""
while True:
params = {"page_size": 100}
if page_token:
params["page_token"] = page_token
response = requests.get(url, headers=headers, params=params, timeout=30)
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"获取记录失败: {resp_data.get('msg')}")
# 收集所有记录的ID
for record in resp_data.get("data", {}).get("items", []):
record_ids.append(record["record_id"])
# 检查是否有更多数据
if resp_data["data"].get("has_more"):
page_token = resp_data["data"].get("page_token", "")
else:
break
if not record_ids:
logging.info("周统计表为空,无需清除")
return True
logging.info(f"获取到{len(record_ids)}条记录待删除")
# 批量删除所有记录
delete_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records/batch_delete"
# 分批删除每次最多100条[3](@ref)
batch_size = 100
for i in range(0, len(record_ids), batch_size):
batch_ids = record_ids[i:i + batch_size]
payload = {"records": batch_ids}
for attempt in range(retry):
try:
response = requests.post(delete_url, json=payload, headers=headers, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
logging.info(f"成功删除批次 {i // batch_size + 1}: {len(batch_ids)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
logging.warning(f"删除记录失败(尝试 {attempt + 1}/{retry}): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"删除记录网络错误(尝试 {attempt + 1}/{retry}): {str(e)}")
time.sleep(2 ** attempt)
return True
def write_weekly_data(records, retry=3):
"""覆盖写入数据到飞书多维表格(周统计)[3,5](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records/batch_create"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 构建API请求体
payload = {"records": []}
for row in records:
try:
# 使用毫秒时间戳格式(飞书官方要求)[3](@ref)
start_dt = datetime.combine(row['week_start'], datetime.min.time())
end_dt = datetime.combine(row['week_end'], datetime.min.time())
start_timestamp = int(start_dt.timestamp() * 1000)
end_timestamp = int(end_dt.timestamp() * 1000)
payload["records"].append({
"fields": {
"周数": str(row['week_number']),
"周开始日期": start_timestamp,
"周结束日期": end_timestamp,
"激活数量": int(row['active_count'])
}
})
except Exception as e:
logging.error(f"记录格式错误: {row} - {str(e)}")
# 处理空记录情况
if not payload["records"]:
logging.warning("没有有效记录可写入多维表格")
return 0
# 分批写入每次最多100条[3](@ref)
batch_size = 100
success_count = 0
for i in range(0, len(payload["records"]), batch_size):
batch = payload["records"][i:i + batch_size]
batch_payload = {"records": batch}
batch_success = False
for attempt in range(retry):
try:
response = requests.post(url, json=batch_payload, headers=headers, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
if not batch_success:
success_count += len(batch)
batch_success = True
logging.info(f"成功写入批次 {i // batch_size + 1}: {len(batch)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
error_code = resp_data.get("code", "")
# 错误诊断[3](@ref)
if error_code == "DatetimeFieldConvFail":
logging.error("日期字段转换失败: 请检查多维表格中日期字段类型配置")
elif error_code == "TextFieldConvFail":
logging.error("文本字段转换失败: 请检查数字字段是否包含非数字字符")
logging.warning(f"飞书API错误(尝试 {attempt + 1}/{retry}): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/{retry}): {str(e)}")
time.sleep(2 ** attempt)
if not batch_success:
logging.error(f"批次 {i // batch_size + 1} 写入失败")
return success_count
def calculate_iso_week(date):
"""计算ISO周数处理跨年"""
iso_year, iso_week, iso_day = date.isocalendar()
return f"{iso_year}-W{iso_week:02d}"
def calculate_week_range(date):
"""计算所在周的周一和周日日期ISO标准"""
start_date = date - timedelta(days=date.weekday())
end_date = start_date + timedelta(days=6)
return start_date, end_date
def send_feishu_message(message, retry=2):
"""发送飞书通知[6,7,8](@ref)"""
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": "default",
"url": DAILY_URL
},
{
"tag": "button",
"text": {"content": "查看周统计", "tag": "plain_text"},
"type": "primary",
"url": WEEKLY_URL
}
]
}
],
"header": {
"title": {"content": "数据同步完成通知", "tag": "plain_text"},
"template": "green"
}
}
}
for attempt in range(retry):
try:
response = requests.post(WEBHOOK_URL, headers=headers, json=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__':
try:
logging.info("开始周统计计算流程...")
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
# 1. 从飞书日统计表读取数据
daily_data = read_daily_data()
logging.info(f"成功读取{len(daily_data)}条日统计记录")
# 2. 计算周统计数据(处理跨年)
weekly_stats = {}
for entry in daily_data:
date = entry['date']
count = entry['count']
week_number = calculate_iso_week(date)
week_start, week_end = calculate_week_range(date)
if week_number not in weekly_stats:
weekly_stats[week_number] = {
'week_number': week_number,
'week_start': week_start,
'week_end': week_end,
'active_count': 0
}
weekly_stats[week_number]['active_count'] += count
# 转换为列表并排序
weekly_list = sorted(weekly_stats.values(), key=lambda x: x['week_start'])
logging.info(f"生成{len(weekly_list)}条周统计记录")
# 3. 覆盖写入周统计表
# 先清空表
if not clear_weekly_table():
raise Exception("清空周统计表失败")
logging.info("成功清空周统计表")
# 再写入新数据
success_count = write_weekly_data(weekly_list)
if success_count != len(weekly_list):
raise Exception(f"周统计写入失败: 成功写入{success_count}条,总记录{len(weekly_list)}")
logging.info(f"成功覆盖写入{success_count}条周统计记录")
# 4. 构建通知消息
today = datetime.now().date()
current_week_number = calculate_iso_week(today)
stats_summary = f"• 本周({current_week_number})激活车辆: {weekly_stats.get(current_week_number, {}).get('active_count', 0)}\n"
stats_summary += f"• 累计激活车辆: {sum(week['active_count'] for week in weekly_list)}\n"
if weekly_list:
latest_week = weekly_list[-1]
stats_summary += f"• 最新一周({latest_week['week_number']}): {latest_week['week_start'].strftime('%Y-%m-%d')}{latest_week['week_end'].strftime('%Y-%m-%d')}, 激活{latest_week['active_count']}"
message = f"📅 周统计计算完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += f"📝 处理结果: 成功处理{len(daily_data)}条日记录 → 生成{len(weekly_list)}条周记录"
# 5. 构建通知消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
# 新增统计维度计算
today = datetime.now().date()
yesterday = today - timedelta(days=1)
current_week_start = today - timedelta(days=today.weekday())
current_week_end = current_week_start + timedelta(days=6)
last_week_start = current_week_start - timedelta(days=7)
last_week_end = last_week_start + timedelta(days=6)
three_months_ago = today - timedelta(days=90)
# 计算昨日数据
yesterday_count = next((d['count'] for d in daily_data if d['date'] == yesterday), 0)
# 计算今日数据
today_count = next((d['count'] for d in daily_data if d['date'] == today), 0)
# 计算本周数据(当前周)
current_week_number = calculate_iso_week(today)
current_week_total = weekly_stats.get(current_week_number, {}).get('active_count', 0)
# 计算上周数据
last_week_number = calculate_iso_week(last_week_start)
last_week_total = weekly_stats.get(last_week_number, {}).get('active_count', 0)
# 计算最近三个月数据
recent_three_months = sum(
d['count'] for d in daily_data
if three_months_ago <= d['date'] <= today
)
# 计算累计数据
total_count = sum(d['count'] for d in daily_data)
# 构建统计摘要
stats_summary = f"• 今日激活: {today_count}\n"
stats_summary += f"• 昨日激活: {yesterday_count}\n"
stats_summary += f"• 本周激活: {current_week_total}\n"
stats_summary += f"• 上周激活: {last_week_total}\n"
stats_summary += f"• 近三月激活: {recent_three_months}\n"
stats_summary += f"• 累计激活: {total_count}\n"
# # 添加最新周统计摘要
# if weekly_list:
# latest_week = weekly_list[-1]
# stats_summary += f"• 最新一周({latest_week['week_number']}): {latest_week['active_count']}辆"
message = f"📅欧盟O&J统计计算完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += f"📝 处理结果: 成功处理{len(daily_data)}条日记录 → 生成{len(weekly_list)}条周记录"
# 5. 发送通知
result = send_feishu_message(message)
logging.info(f"飞书消息发送成功: {result}")
except Exception as e:
error_message = f"❌ 周统计计算异常: {str(e)}"
logging.exception("程序执行异常")
send_feishu_message(error_message)

View File

@@ -0,0 +1,411 @@
import requests
import logging
import time
from datetime import datetime, timedelta, date
# ====================== 配置区域 ======================
# 飞书应用凭证
APP_ID = "cli_a8b50ec0eed1500d"
APP_SECRET = "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK"
# 日统计表格配置
DAILY_SPREADSHEET_ID = "MIqssvuB3h3XmgtkI26cBX7Angc" # 日统计电子表格ID
DAILY_SHEET_ID = "46ea20" # 日统计工作表ID
DAILY_URL = "https://my-ichery.feishu.cn/sheets/MIqssvuB3h3XmgtkI26cBX7Angc?sheet=46ea20" # 日统计表格链接
# 周统计表格配置
WEEKLY_BASE_TOKEN = "T3QLbmpqma014ussjy9cgitqnSh" # 周统计多维表格ID
WEEKLY_TABLE_ID = "tbloB11YuVOSdi9e" # 周统计表ID
WEEKLY_URL = "https://my-ichery.feishu.cn/base/T3QLbmpqma014ussjy9cgitqnSh?table=tbloB11YuVOSdi9e" # 周统计表格链接
# 飞书机器人配置
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/3bc45247-c469-47a0-bfe9-c62e46c5aca0"
# ====================== 日志配置 ======================
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def get_access_token():
"""获取飞书访问令牌[1,3](@ref)"""
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异常: {str(e)}")
raise
def read_daily_data(retry=3):
"""从飞书电子表格读取日统计数据[1,3](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{DAILY_SPREADSHEET_ID}/values/{DAILY_SHEET_ID}"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
for attempt in range(retry):
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"API错误: {resp_data.get('msg')}")
# 解析数据:跳过标题行,只取日期和激活数量
values = resp_data['data']['valueRange']['values'][1:]
daily_data = []
for row in values:
try:
date_str = row[0]
count = int(row[1])
date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
daily_data.append({"date": date_obj, "count": count})
except (IndexError, ValueError) as e:
logging.warning(f"数据格式错误: {row} - {str(e)}")
return daily_data
except Exception as e:
logging.warning(f"读取飞书表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt) # 指数退避重试
raise Exception(f"读取飞书表格失败,重试{retry}次后仍不成功")
def clear_weekly_table(retry=3):
"""清空周统计表(覆盖写入前的准备)[3,5](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 获取所有记录ID
record_ids = []
page_token = ""
while True:
params = {"page_size": 100}
if page_token:
params["page_token"] = page_token
response = requests.get(url, headers=headers, params=params, timeout=30)
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"获取记录失败: {resp_data.get('msg')}")
# 收集所有记录的ID
for record in resp_data.get("data", {}).get("items", []):
record_ids.append(record["record_id"])
# 检查是否有更多数据
if resp_data["data"].get("has_more"):
page_token = resp_data["data"].get("page_token", "")
else:
break
if not record_ids:
logging.info("周统计表为空,无需清除")
return True
logging.info(f"获取到{len(record_ids)}条记录待删除")
# 批量删除所有记录
delete_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records/batch_delete"
# 分批删除每次最多100条[3](@ref)
batch_size = 100
for i in range(0, len(record_ids), batch_size):
batch_ids = record_ids[i:i + batch_size]
payload = {"records": batch_ids}
for attempt in range(retry):
try:
response = requests.post(delete_url, json=payload, headers=headers, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
logging.info(f"成功删除批次 {i // batch_size + 1}: {len(batch_ids)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
logging.warning(f"删除记录失败(尝试 {attempt + 1}/{retry}): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"删除记录网络错误(尝试 {attempt + 1}/{retry}): {str(e)}")
time.sleep(2 ** attempt)
return True
def write_weekly_data(records, retry=3):
"""覆盖写入数据到飞书多维表格(周统计)[3,5](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records/batch_create"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 构建API请求体
payload = {"records": []}
for row in records:
try:
# 使用毫秒时间戳格式(飞书官方要求)[3](@ref)
start_dt = datetime.combine(row['week_start'], datetime.min.time())
end_dt = datetime.combine(row['week_end'], datetime.min.time())
start_timestamp = int(start_dt.timestamp() * 1000)
end_timestamp = int(end_dt.timestamp() * 1000)
payload["records"].append({
"fields": {
"周数": str(row['week_number']),
"周开始日期": start_timestamp,
"周结束日期": end_timestamp,
"激活数量": int(row['active_count'])
}
})
except Exception as e:
logging.error(f"记录格式错误: {row} - {str(e)}")
# 处理空记录情况
if not payload["records"]:
logging.warning("没有有效记录可写入多维表格")
return 0
# 分批写入每次最多100条[3](@ref)
batch_size = 100
success_count = 0
for i in range(0, len(payload["records"]), batch_size):
batch = payload["records"][i:i + batch_size]
batch_payload = {"records": batch}
batch_success = False
for attempt in range(retry):
try:
response = requests.post(url, json=batch_payload, headers=headers, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
if not batch_success:
success_count += len(batch)
batch_success = True
logging.info(f"成功写入批次 {i // batch_size + 1}: {len(batch)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
error_code = resp_data.get("code", "")
# 错误诊断[3](@ref)
if error_code == "DatetimeFieldConvFail":
logging.error("日期字段转换失败: 请检查多维表格中日期字段类型配置")
elif error_code == "TextFieldConvFail":
logging.error("文本字段转换失败: 请检查数字字段是否包含非数字字符")
logging.warning(f"飞书API错误(尝试 {attempt + 1}/{retry}): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/{retry}): {str(e)}")
time.sleep(2 ** attempt)
if not batch_success:
logging.error(f"批次 {i // batch_size + 1} 写入失败")
return success_count
def calculate_iso_week(date):
"""计算ISO周数处理跨年"""
iso_year, iso_week, iso_day = date.isocalendar()
return f"{iso_year}-W{iso_week:02d}"
def calculate_week_range(date):
"""计算所在周的周一和周日日期ISO标准"""
start_date = date - timedelta(days=date.weekday())
end_date = start_date + timedelta(days=6)
return start_date, end_date
def send_feishu_message(message, retry=2):
"""发送飞书通知[6,7,8](@ref)"""
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": "default",
"url": DAILY_URL
},
{
"tag": "button",
"text": {"content": "查看周统计", "tag": "plain_text"},
"type": "primary",
"url": WEEKLY_URL
}
]
}
],
"header": {
"title": {"content": "数据同步完成通知", "tag": "plain_text"},
"template": "green"
}
}
}
for attempt in range(retry):
try:
response = requests.post(WEBHOOK_URL, headers=headers, json=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__':
try:
logging.info("开始周统计计算流程...")
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
# 1. 从飞书日统计表读取数据
daily_data = read_daily_data()
logging.info(f"成功读取{len(daily_data)}条日统计记录")
# 2. 计算周统计数据(处理跨年)
weekly_stats = {}
for entry in daily_data:
date = entry['date']
count = entry['count']
week_number = calculate_iso_week(date)
week_start, week_end = calculate_week_range(date)
if week_number not in weekly_stats:
weekly_stats[week_number] = {
'week_number': week_number,
'week_start': week_start,
'week_end': week_end,
'active_count': 0
}
weekly_stats[week_number]['active_count'] += count
# 转换为列表并排序
weekly_list = sorted(weekly_stats.values(), key=lambda x: x['week_start'])
logging.info(f"生成{len(weekly_list)}条周统计记录")
# 3. 覆盖写入周统计表
# 先清空表
if not clear_weekly_table():
raise Exception("清空周统计表失败")
logging.info("成功清空周统计表")
# 再写入新数据
success_count = write_weekly_data(weekly_list)
if success_count != len(weekly_list):
raise Exception(f"周统计写入失败: 成功写入{success_count}条,总记录{len(weekly_list)}")
logging.info(f"成功覆盖写入{success_count}条周统计记录")
# 4. 构建通知消息
today = datetime.now().date()
current_week_number = calculate_iso_week(today)
stats_summary = f"• 本周({current_week_number})激活车辆: {weekly_stats.get(current_week_number, {}).get('active_count', 0)}\n"
stats_summary += f"• 累计激活车辆: {sum(week['active_count'] for week in weekly_list)}\n"
if weekly_list:
latest_week = weekly_list[-1]
stats_summary += f"• 最新一周({latest_week['week_number']}): {latest_week['week_start'].strftime('%Y-%m-%d')}{latest_week['week_end'].strftime('%Y-%m-%d')}, 激活{latest_week['active_count']}"
message = f"📅 周统计计算完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += f"📝 处理结果: 成功处理{len(daily_data)}条日记录 → 生成{len(weekly_list)}条周记录"
# 5. 构建通知消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
# 新增统计维度计算
today = datetime.now().date()
yesterday = today - timedelta(days=1)
current_week_start = today - timedelta(days=today.weekday())
current_week_end = current_week_start + timedelta(days=6)
last_week_start = current_week_start - timedelta(days=7)
last_week_end = last_week_start + timedelta(days=6)
three_months_ago = today - timedelta(days=90)
# 计算昨日数据
yesterday_count = next((d['count'] for d in daily_data if d['date'] == yesterday), 0)
# 计算今日数据
today_count = next((d['count'] for d in daily_data if d['date'] == today), 0)
# 计算本周数据(当前周)
current_week_number = calculate_iso_week(today)
current_week_total = weekly_stats.get(current_week_number, {}).get('active_count', 0)
# 计算上周数据
last_week_number = calculate_iso_week(last_week_start)
last_week_total = weekly_stats.get(last_week_number, {}).get('active_count', 0)
# 计算最近三个月数据
recent_three_months = sum(
d['count'] for d in daily_data
if three_months_ago <= d['date'] <= today
)
# 计算累计数据
total_count = sum(d['count'] for d in daily_data)
# 构建统计摘要
stats_summary = f"• 今日激活: {today_count}\n"
stats_summary += f"• 昨日激活: {yesterday_count}\n"
stats_summary += f"• 本周激活: {current_week_total}\n"
stats_summary += f"• 上周激活: {last_week_total}\n"
stats_summary += f"• 近三月激活: {recent_three_months}\n"
stats_summary += f"• 累计激活: {total_count}\n"
# # 添加最新周统计摘要
# if weekly_list:
# latest_week = weekly_list[-1]
# stats_summary += f"• 最新一周({latest_week['week_number']}): {latest_week['active_count']}辆"
message = f"📅欧盟O&J统计计算完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += f"📝 处理结果: 成功处理{len(daily_data)}条日记录 → 生成{len(weekly_list)}条周记录"
# 5. 发送通知
result = send_feishu_message(message)
logging.info(f"飞书消息发送成功: {result}")
except Exception as e:
error_message = f"❌ 周统计计算异常: {str(e)}"
logging.exception("程序执行异常")
send_feishu_message(error_message)

View File

@@ -0,0 +1,362 @@
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 - %(essage)s',
datefmt='%Y-%m-%d %H:%M:%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
FEISHU_TABLE_ID = "tbloPJHLc05MHIAY" # 表格ID
VIN_FIELD_NAME = "车架号" # VIN字段名称
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异常: {str(e)}")
raise
def send_feishu_message(message):
"""发送飞书通知[9,10](@ref)"""
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, 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} # 单次请求最大记录数
all_records = []
page_token = None
while True:
if page_token:
params["page_token"] = page_token
records_fetched = False
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)
# 检查是否有下一页
if "page_token" in resp_data["data"] and resp_data["data"]["has_more"]:
page_token = resp_data["data"]["page_token"]
else:
page_token = None
records_fetched = True
break
except Exception as e:
logging.warning(f"读取多维表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt)
if not records_fetched:
raise Exception("无法读取多维表格数据")
if not page_token:
break
return all_records
def fetch_activation_data():
"""从数据库查询车辆激活状态"""
try:
# 创建SQLAlchemy引擎
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 vehicle_name
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
"""
# 执行查询并转换为DataFrame
df = pd.read_sql(sql_query, engine)
return df.set_index('car_vins')['active_status'].to_dict()
except Exception as e:
logging.error(f"数据库查询失败: {str(e)}")
raise
def compare_and_update_records(app_token, table_id, activation_data):
"""比较并更新飞书多维表格记录"""
access_token = get_access_token()
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 1. 读取多维表格所有记录
feishu_records = read_feishu_records(app_token, table_id)
logging.info(f"成功读取{len(feishu_records)}条多维表格记录")
# 统计变量
total_updated = 0
activated_count = 0
not_activated_count = 0
# 2. 准备需要更新的记录
records_to_update = []
for record in feishu_records:
try:
record_id = record["record_id"]
fields = record.get("fields", {})
# 获取车架号(VIN)
vin = fields.get(VIN_FIELD_NAME)
if not vin:
logging.warning(f"记录 {record_id} 缺少VIN字段")
continue
# 获取多维表格中的激活状态
current_status = fields.get(ACTIVE_FIELD_NAME, "").lower()
# 获取数据库中的激活状态
db_status = activation_data.get(vin)
if not db_status:
logging.warning(f"VIN {vin} 在数据库中未找到")
continue
# 比较状态是否需要更新
if db_status != current_status:
records_to_update.append({
"record_id": record_id,
"fields": {
ACTIVE_FIELD_NAME: db_status
}
})
logging.info(f"VIN {vin} 状态需更新: {current_status}{db_status}")
# 统计更新状态
if db_status == 'active':
activated_count += 1
else:
not_activated_count += 1
except Exception as e:
logging.error(f"处理记录异常: {record} - {str(e)}")
total_updated = len(records_to_update)
logging.info(f"需要更新{total_updated}条记录")
logging.info(f"更新统计: 激活车辆: {activated_count}台, 未激活车辆: {not_activated_count}")
# 3. 批量更新记录 (每批次50条)
if not records_to_update:
logging.info("没有需要更新的记录")
return total_updated, activated_count, not_activated_count
batch_size = 50
success_count = 0
for i in range(0, len(records_to_update), batch_size):
batch = records_to_update[i:i + batch_size]
batch_payload = {"records": batch}
update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
for attempt in range(3): # 最多重试3次
try:
response = requests.post(update_url, headers=headers, json=batch_payload, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
success_count += len(batch)
logging.info(f"成功更新批次 {i // batch_size + 1}: {len(batch)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
logging.warning(f"更新失败(尝试 {attempt + 1}/3): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/3): {str(e)}")
time.sleep(2 ** attempt)
logging.info(f"总计更新成功{success_count}条记录")
return total_updated, activated_count, not_activated_count
def sync_vehicle_activation():
"""执行车辆激活状态同步流程"""
try:
start_time = time.time()
logging.info("开始车辆激活状态同步流程...")
# 1. 从数据库获取激活状态数据
activation_data = fetch_activation_data()
logging.info(f"从数据库获取{len(activation_data)}条激活状态记录")
# 统计数据库中的激活状态
db_activated = sum(1 for status in activation_data.values() if status == 'active')
db_not_activated = sum(1 for status in activation_data.values() if status == 'no_active')
logging.info(f"数据库统计: 激活车辆: {db_activated}台, 未激活车辆: {db_not_activated}")
# 2. 与飞书多维表格数据比对并更新
total_updated, activated_count, not_activated_count = compare_and_update_records(
FEISHU_APP_TOKEN, FEISHU_TABLE_ID, activation_data
)
# 3. 输出最终统计结果
logging.info("=" * 50)
logging.info(f"本次更新统计结果:")
logging.info(f" - 更新车辆总数: {total_updated}")
logging.info(f" - 激活车辆数量: {activated_count}")
logging.info(f" - 未激活车辆数量: {not_activated_count}")
logging.info("=" * 50)
# 4. 完成日志
elapsed_time = time.time() - start_time
logging.info(f"流程完成! 耗时: {elapsed_time:.2f}秒, 更新记录数: {total_updated}")
return {
"total_updated": total_updated,
"activated_count": activated_count,
"not_activated_count": not_activated_count,
"elapsed_time": elapsed_time
}
except Exception as e:
logging.error(f"流程执行异常: {str(e)}")
logging.exception("详细错误信息")
return None
# ====================== 主执行流程 ======================
if __name__ == '__main__':
# 执行车辆激活状态同步
sync_result = sync_vehicle_activation()
if sync_result:
# 构建飞书消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
message = (
f"🚗 **车辆激活状态同步完成** ✅\n"
f"⏰ 同步时间: {current_time}\n"
f"⏱️ 处理耗时: {sync_result['elapsed_time']:.2f}\n\n"
f"📊 **同步统计结果:**\n"
f"• 更新车辆总数: {sync_result['total_updated']}\n"
f"• ✅ 激活车辆数量: {sync_result['activated_count']}\n"
f"• ❌ 未激活车辆数量: {sync_result['not_activated_count']}\n\n"
f"🔗 查看多维表格: [点击访问]({MULTI_TABLE_URL})"
)
# 发送飞书通知
send_result = send_feishu_message(message)
if send_result:
logging.info(f"飞书消息发送成功: {send_result}")
else:
logging.error("飞书消息发送失败")
else:
error_message = "❌ 车辆激活状态同步失败,请检查日志"
send_feishu_message(error_message)
logging.error(error_message)

View File

@@ -0,0 +1,322 @@
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
FEISHU_TABLE_ID = "tbloPJHLc05MHIAY" # 表格ID
VIN_FIELD_NAME = "车架号" # VIN字段名称
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):
"""发送飞书通知[9,10](@ref)"""
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, 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}
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)
# --- 新增:按 VIN 去重,保留第一条记录 ---
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, 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)
logging.info(f"成功读取{len(feishu_records)}条多维表格记录")
records_to_update = []
activated_count = not_activated_count = 0
for record in feishu_records:
record_id = record.get("record_id")
fields = record.get("fields", {})
vin = fields.get(VIN_FIELD_NAME)
if not vin:
logging.warning(f"记录 {record_id} 缺少VIN字段")
continue
db = activation_data.get(vin)
if not db:
logging.warning(f"VIN {vin} 在数据库中未找到")
continue
db_status = db['active_status']
db_name = db[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:
update_fields = {
ACTIVE_FIELD_NAME: db_status,
VEHICLE_NAME_FIELD: db_name
}
records_to_update.append({
"record_id": record_id,
"fields": update_fields
})
logging.debug(f"VIN {vin} 需要更新: 状态 {current_status}{db_status}, 车型 {current_name}{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 = 50
for i in range(0, total_updated, batch_size):
batch = records_to_update[i:i+batch_size]
payload = {"records": batch}
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
for attempt in range(3):
try:
r = requests.post(url, headers=headers, json=payload, timeout=30)
data = r.json()
if r.status_code == 200 and data.get("code") == 0:
logging.info(f"{i//batch_size+1}批更新成功,{len(batch)}")
break
else:
logging.warning(f"{i//batch_size+1}批更新失败(尝试{attempt+1}/3){data.get('msg')}")
except Exception as e:
logging.warning(f"网络异常(尝试{attempt+1}/3){e}")
time.sleep(2 ** attempt)
return total_updated, activated_count, not_activated_count
def sync_vehicle_activation():
"""执行车辆激活状态同步流程"""
try:
start_time = time.time()
logging.info("开始车辆激活状态同步流程...")
activation_data = fetch_activation_data()
logging.info(f"从数据库获取{len(activation_data)}条激活状态记录")
db_activated = sum(1 for v in activation_data.values() if v['active_status']=='active')
db_not_activated = sum(1 for v in activation_data.values() if v['active_status']=='no_active')
logging.info(f"数据库统计: 激活车辆 {db_activated}台, 未激活车辆 {db_not_activated}")
total_updated, activated_count, not_activated_count = compare_and_update_records(
FEISHU_APP_TOKEN, FEISHU_TABLE_ID, activation_data
)
logging.info("="*50)
logging.info(f"本次更新统计: 总更新 {total_updated}台, 激活 {activated_count}台, 未激活 {not_activated_count}")
logging.info("="*50)
elapsed = time.time()-start_time
logging.info(f"流程完成! 耗时 {elapsed:.2f}")
return {
"total_updated": total_updated,
"activated_count": activated_count,
"not_activated_count": not_activated_count,
"elapsed_time": elapsed
}
except Exception as e:
logging.error(f"流程执行异常: {e}")
logging.exception("详细错误信息")
return None
if __name__ == '__main__':
sync_result=sync_vehicle_activation()
if sync_result:
# 构建飞书消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
message = (
f"🚗 **车辆激活状态同步完成** ✅\n"
f"⏰ 同步时间: {current_time}\n"
f"⏱️ 处理耗时: {sync_result['elapsed_time']:.2f}\n\n"
f"📊 **同步统计结果:**\n"
f"• 更新车辆总数: {sync_result['total_updated']}\n"
f"• ✅ 激活车辆数量: {sync_result['activated_count']}\n"
f"• ❌ 未激活车辆数量: {sync_result['not_activated_count']}\n\n"
f"🔗 查看多维表格: [点击访问]({MULTI_TABLE_URL})"
)
# 发送飞书通知
send_result = send_feishu_message(message)
if send_result:
logging.info(f"飞书消息发送成功: {send_result}")
else:
logging.error("飞书消息发送失败")
else:
error_message = "❌ 车辆激活状态同步失败,请检查日志"
send_feishu_message(error_message)
logging.error(error_message)

View File

@@ -0,0 +1,347 @@
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")

View File

@@ -0,0 +1,387 @@
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": "tbloPJHLc05MHIAY", "view_id": "vewBvcYDAa"},
{"name": "意大利", "table_id": "tbl4ZXTTgcPvdZUD", "view_id": "vewBvcYDAa"},
{"name": "西班牙", "table_id": "tbl0KBWQT1ZqiT2g", "view_id": "vewBvcYDAa"},
{"name": "英国", "table_id": "tblbfEHrsojQ4D5w", "view_id": "vewBvcYDAa"},
{"name": "比利时", "table_id": "tblYN3eEEumBMlgB", "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 = not_activated_count = 0
for record in feishu_records:
record_id = record.get("record_id")
fields = record.get("fields", {})
vin = fields.get(VIN_FIELD_NAME)
if not vin:
logging.warning(f"记录 {record_id} 缺少VIN字段")
continue
db = activation_data.get(vin)
if not db:
logging.warning(f"VIN {vin} 在数据库中未找到")
continue
db_status = db['active_status']
db_name = db[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:
update_fields = {
ACTIVE_FIELD_NAME: db_status,
VEHICLE_NAME_FIELD: db_name
}
records_to_update.append({
"record_id": record_id,
"fields": update_fields
})
logging.debug(f"VIN {vin} 需要更新: 状态 {current_status}{db_status}, 车型 {current_name}{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 = 50
for i in range(0, total_updated, batch_size):
batch = records_to_update[i:i + batch_size]
payload = {"records": batch}
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
for attempt in range(3):
try:
r = requests.post(url, headers=headers, json=payload, timeout=30)
data = r.json()
if r.status_code == 200 and data.get("code") == 0:
logging.info(f"{i // batch_size + 1}批更新成功,{len(batch)}")
break
else:
logging.warning(f"{i // batch_size + 1}批更新失败(尝试{attempt + 1}/3){data.get('msg')}")
except Exception as e:
logging.warning(f"网络异常(尝试{attempt + 1}/3){e}")
time.sleep(2 ** attempt)
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")
if sync_result.get("success"):
# 构建详细的飞书消息
message_header = f"🚗 **车辆激活状态同步完成** ✅\n" \
f"⏰ 同步时间: {current_time}\n" \
f"⏱️ 总耗时: {sync_result['elapsed_time']:.2f}\n\n" \
f"📊 **各市场同步详情:**\n"
message_details = []
success_count = 0
for result in sync_result["results"]:
if result["success"]:
success_count += 1
detail = (
f"• **{result['market']}**: "
f"更新 {result['total_updated']}台 | "
f"✅激活 {result['activated_count']}台 | "
f"❌未激活 {result['not_activated_count']}"
)
else:
detail = (
f"• **{result['market']}**: ❌同步失败!错误信息: `{result['error']}`"
)
message_details.append(detail)
message_summary = f"\n✅ 成功处理 {success_count}/{len(MARKET_TABLES)} 个市场\n" \
f"🔗 查看多维表格: [点击访问]({MULTI_TABLE_URL})"
full_message = message_header + "\n".join(message_details) + message_summary
# 发送飞书通知
send_result = send_feishu_message(full_message)
if send_result:
logging.info("飞书消息发送成功")
else:
logging.error("飞书消息发送失败")
else:
error_message = f"❌ **车辆激活状态同步失败**\n" \
f"⏰ 时间: {current_time}\n" \
f"⏱️ 耗时: {sync_result['elapsed_time']:.2f}\n" \
f"错误信息: `{sync_result['error']}`\n\n" \
f"请检查系统日志获取详细信息"
send_feishu_message(error_message)
logging.error(f"整体同步失败: {sync_result['error']}")

View File

@@ -0,0 +1,54 @@
import pandas as pd
from datetime import datetime
import os
# 配置参数
CONFIG = {
'input_csv': r'C:\Users\Administrator\Desktop\European Local O&M\all-active-0522.csv.csv',
'output_dir': './output',
'required_columns': ['car_vins', 'active_status', 'MATERIAL', 'Area','active_date']
}
df=pd.read_csv(CONFIG['input_csv'])
df['active_date'] = pd.to_datetime(df['active_date'], errors='coerce')
active_df = df[df['active_status'] == 'active']
daily_count = active_df.groupby(active_df['active_date'].dt.date).size().reset_index(name='active_count')
print(daily_count)
daily_count['active_date'] = pd.to_datetime(daily_count['active_date'])
daily_count.set_index('active_date', inplace=True)
weekly_count = daily_count.resample('W-MON').sum().reset_index() # 每周从周一开始
weekly_count.columns = ['week_start', 'weekly_active_count']
weekly_count['week_number'] = pd.to_datetime(weekly_count['week_start']).dt.isocalendar().week
weekly_count['year'] = pd.to_datetime(weekly_count['week_start']).dt.isocalendar().year
# 转换为 datetime 类型
weekly_count['week_start'] = pd.to_datetime(weekly_count['week_start'])
# 提取年份后两位、月份
weekly_count['year'] = weekly_count['week_start'].dt.year % 100
weekly_count['month'] = weekly_count['week_start'].dt.month
# 分组内生成「每月第几周」编号(用 cumcount
weekly_count['week_in_month'] = weekly_count.groupby(['year', 'month']).cumcount() + 1
# 拼接成格式25-1-4
weekly_count['year_month_week'] = (
weekly_count['year'].astype(str) + '-' +
weekly_count['month'].astype(str) + '-' +
weekly_count['week_in_month'].astype(str)
)
print(weekly_count)
# 获取输出目录路径
output_dir = CONFIG['output_dir']
os.makedirs(output_dir, exist_ok=True) # 自动创建目录(如果不存在)
# 拼接 Excel 文件路径
output_path = os.path.join(output_dir, 'active_car_number.xlsx')
# 导出
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
daily_count.reset_index().to_excel(writer, sheet_name='Daily', index=False)
weekly_count.to_excel(writer, sheet_name='Weekly', index=False)
print(f"已成功导出至:{output_path}")

97
hang/chery_active_car.py Normal file
View File

@@ -0,0 +1,97 @@
import pandas as pd
from datetime import datetime
import os
# 配置参数
CONFIG = {
'input_csv': r'C:\Users\Administrator\Desktop\European Local O&M\all-active-0519.csv.csv',
'output_dir': './reports',
'required_columns': ['car_vins', 'active_status', 'MATERIAL', 'Area']
}
def validate_data(df):
"""校验数据完整性"""
missing_cols = [col for col in CONFIG['required_columns'] if col not in df.columns]
if missing_cols:
raise ValueError(f"CSV文件中缺少必要字段: {','.join(missing_cols)}")
def generate_report():
now = datetime.now()
today_str = now.strftime("%Y-%m-%d %H:%M:%S")
filename_date = now.strftime("%Y-%m-%d")
try:
print(f"\n📅 当前时间:{today_str}")
print("正在读取原始数据...")
df = pd.read_csv(CONFIG['input_csv'], encoding='utf-8-sig')
print("校验数据结构...")
validate_data(df)
# 拆分激活与未激活
active_df = df[df['active_status'] == 'active'].copy()
inactive_df = df[df['active_status'] != 'active'].copy()
print(f"✅ 已激活车辆总数:{len(active_df)}")
print(f"❗ 未激活车辆总数:{len(inactive_df)}")
if inactive_df.empty:
print("⚠️ 所有车辆均为激活状态,无需生成未激活清单")
else:
# 处理未激活车辆数据
inactive_df.insert(0, 'ReportDate', filename_date)
inactive_df.fillna({'Area': '未分配', 'MATERIAL': '未知物料'}, inplace=True)
# 保存未激活车辆清单
os.makedirs(CONFIG['output_dir'], exist_ok=True)
output_path = f"{CONFIG['output_dir']}/Inactive_Vehicles_{filename_date}.xlsx"
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
inactive_df.to_excel(writer, index=False, sheet_name='InactiveVehicles')
print(f"\n📁 未激活车辆清单已生成:{os.path.abspath(output_path)}")
if active_df.empty:
print("⚠️ 无激活车辆,跳过分析")
return
# 分析激活车辆数据
active_df.fillna({'Area': '未分配', 'MATERIAL': '未知物料'}, inplace=True)
area_summary = (
active_df.groupby('Area')['car_vins']
.count()
.reset_index()
.rename(columns={'car_vins': 'ActiveCount'})
.sort_values(by='ActiveCount', ascending=False)
)
material_summary = (
active_df.groupby('MATERIAL')['car_vins']
.count()
.reset_index()
.rename(columns={'car_vins': 'ActiveCount'})
.sort_values(by='ActiveCount', ascending=False)
)
# 控制台输出分析结果
print("\n📊 区域分布统计(已激活车辆):")
print(area_summary.to_string(index=False))
print("\n📦 销量前10的物料号已激活车辆")
top10_materials = material_summary.head(10)
print(top10_materials.to_string(index=False))
except FileNotFoundError:
print(f"❌ 错误:未找到输入文件 {CONFIG['input_csv']}")
except Exception as e:
print(f"❌ 发生异常:{str(e)}")
if __name__ == "__main__":
print("\n===== 奇瑞车辆日报生成器 =====")
input("请将最新csv放入daily_data文件夹后按回车键开始...")
generate_report()
input("\n处理完成!按任意键退出...")

View File

@@ -0,0 +1,183 @@
import pandas as pd
import pymysql
from sqlalchemy import create_engine
import requests
import json
import pandas as pd
from datetime import datetime
import os
def get_access_token():
"""获取飞书访问令牌"""
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"}
response = requests.post(api_token_url, json=api_post_data)
if response.status_code == 200 and response.json().get("code") == 0:
return response.json()["tenant_access_token"]
else:
raise Exception(f"获取 Token 失败: {response.text}")
def overwrite_data(spreadsheet_token, values, access_token=None):
"""覆盖写入数据到飞书多维表格"""
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
}
}
response = requests.put(url, json=post_data, headers=headers)
return response # 返回响应对象以便检查状态
# 数据库连接配置
DB_CONFIG = {
'host': 'eutsp-prod.mysql.germany.rds.aliyuncs.com',
'port': 3306,
'user': 'international_tsp_eu_r',
'password': 'ZXhBgo1TB2XbF3kP',
'database': 'chery_international_tsp_eu'
}
def fetch_data_from_db():
"""从数据库执行SQL查询获取数据"""
connection = pymysql.connect(**DB_CONFIG)
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, connection)
finally:
connection.close()
def send_feishu_message(webhook_url, message, spreadsheet_token=None):
"""
通过Webhook发送飞书消息支持添加多维表格链接
:param webhook_url: 机器人Webhook地址
:param message: 要发送的文本内容
:param spreadsheet_token: 多维表格ID可选
"""
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}
}
response = requests.post(webhook_url, headers=headers, data=json.dumps(payload))
return response.json()
if __name__ == '__main__':
# 配置参数
SPREADSHEET_ID = "MIqssvuB3h3XmgtkI26cBX7Angc"
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/d82e4ada-8f78-40ae-b98e-75af0c448c95"
try:
# 1. 从数据库获取数据
df = fetch_data_from_db()
# 2. 处理数据
df['active_date'] = pd.to_datetime(df['active_date'], errors='coerce')
active_df = df[df['active_status'] == 'active']
# 3. 按天统计活跃数
daily_count = active_df.groupby(active_df['active_date'].dt.date).size().reset_index(name='active_count')
values = [[str(d), c] for d, c in daily_count.values.tolist()]
# 4. 覆盖写入多维表格
response = overwrite_data(SPREADSHEET_ID, values)
if response.status_code == 200:
# 5. 构建通知消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
stats_summary = f"• 今日活跃车辆: {daily_count['active_count'].iloc[-1] if len(daily_count) > 0 else 0}\n"
stats_summary += f"• 本周活跃车辆: {daily_count[daily_count['active_date'] >= (datetime.now() - pd.Timedelta(days=7)).date()]['active_count'].sum()}\n"
stats_summary += f"• 累计活跃车辆: {len(active_df)}"
message = f"🚗 车辆数据同步完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += "[安全关键词:告警]"
# 6. 发送包含多维表格链接的通知
result = send_feishu_message(WEBHOOK_URL, message, SPREADSHEET_ID)
print("飞书消息发送成功:", result)
else:
error_msg = f"表格写入失败: {response.status_code} - {response.text}"
error_message = f"❌ 数据同步失败\n{error_msg}\n[安全关键词:告警]"
send_feishu_message(WEBHOOK_URL, error_message)
print(error_msg)
except Exception as e:
error_message = f"❌ 数据同步异常: {str(e)}\n[安全关键词:告警]"
send_feishu_message(WEBHOOK_URL, error_message)
print("发生异常:", str(e))

241
hang/demo060904.py Normal file
View File

@@ -0,0 +1,241 @@
import pandas as pd
import requests
import json
import logging
import time
from datetime import datetime
from sqlalchemy import create_engine
from urllib.parse import quote_plus
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%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
FEISHU_TABLE_ID = "tbloPJHLc05MHIAY" # 表格ID
VIN_FIELD_NAME = "车架号" # VIN字段名称
ACTIVE_FIELD_NAME = "激活状态" # 激活状态字段名称
def get_access_token():
"""获取飞书访问令牌"""
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()
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异常: {str(e)}")
raise
def read_feishu_records(app_token, table_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} # 单次请求最大记录数
all_records = []
page_token = None
while True:
if page_token:
params["page_token"] = page_token
records_fetched = False
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)
# 检查是否有下一页
if "page_token" in resp_data["data"] and resp_data["data"]["has_more"]:
page_token = resp_data["data"]["page_token"]
else:
page_token = None
records_fetched = True
break
except Exception as e:
logging.warning(f"读取多维表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt)
if not records_fetched:
raise Exception("无法读取多维表格数据")
if not page_token:
break
return all_records
def fetch_activation_data():
"""从数据库查询车辆激活状态"""
try:
# 创建SQLAlchemy引擎
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_vehicle_info.VIN AS car_vins,
CASE v_sim_info.STATE
WHEN 2 THEN 'active'
ELSE 'no_active'
END AS active_status
FROM v_vehicle_info
JOIN v_tbox_info USING (VIN)
JOIN v_sim_info USING (SN);
"""
# 执行查询并转换为DataFrame
df = pd.read_sql(sql_query, engine)
return df.set_index('car_vins')['active_status'].to_dict()
except Exception as e:
logging.error(f"数据库查询失败: {str(e)}")
raise
def compare_and_update_records(app_token, table_id, activation_data):
"""比较并更新飞书多维表格记录"""
access_token = get_access_token()
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 1. 读取多维表格所有记录
feishu_records = read_feishu_records(app_token, table_id)
logging.info(f"成功读取{len(feishu_records)}条多维表格记录")
# 2. 准备需要更新的记录
records_to_update = []
for record in feishu_records:
try:
record_id = record["record_id"]
fields = record.get("fields", {})
# 获取车架号(VIN)
vin = fields.get(VIN_FIELD_NAME)
if not vin:
logging.warning(f"记录 {record_id} 缺少VIN字段")
continue
# 获取多维表格中的激活状态
current_status = fields.get(ACTIVE_FIELD_NAME, "").lower()
# 获取数据库中的激活状态
db_status = activation_data.get(vin)
if not db_status:
logging.warning(f"VIN {vin} 在数据库中未找到")
continue
# 比较状态是否需要更新
if db_status != current_status:
records_to_update.append({
"record_id": record_id,
"fields": {
ACTIVE_FIELD_NAME: db_status
}
})
logging.info(f"VIN {vin} 状态需更新: {current_status}{db_status}")
except Exception as e:
logging.error(f"处理记录异常: {record} - {str(e)}")
logging.info(f"需要更新{len(records_to_update)}条记录")
# 3. 批量更新记录 (每批次50条)
if not records_to_update:
logging.info("没有需要更新的记录")
return
batch_size = 50
success_count = 0
for i in range(0, len(records_to_update), batch_size):
batch = records_to_update[i:i + batch_size]
batch_payload = {"records": batch}
update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
for attempt in range(3): # 最多重试3次
try:
response = requests.post(update_url, headers=headers, json=batch_payload, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
success_count += len(batch)
logging.info(f"成功更新批次 {i // batch_size + 1}: {len(batch)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
logging.warning(f"更新失败(尝试 {attempt + 1}/3): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/3): {str(e)}")
time.sleep(2 ** attempt)
logging.info(f"总计更新成功{success_count}条记录")
return success_count
def main():
try:
start_time = time.time()
logging.info("开始车辆激活状态同步流程...")
# 1. 从数据库获取激活状态数据
activation_data = fetch_activation_data()
logging.info(f"从数据库获取{len(activation_data)}条激活状态记录")
# 2. 与飞书多维表格数据比对并更新
update_count = compare_and_update_records(FEISHU_APP_TOKEN, FEISHU_TABLE_ID, activation_data)
# 3. 完成日志
elapsed_time = time.time() - start_time
logging.info(f"流程完成! 耗时: {elapsed_time:.2f}秒, 更新记录数: {update_count}")
except Exception as e:
logging.error(f"流程执行异常: {str(e)}")
logging.exception("详细错误信息")
if __name__ == '__main__':
main()

207
hang/demo06092.py Normal file
View File

@@ -0,0 +1,207 @@
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)

336
hang/demo06093.py Normal file
View File

@@ -0,0 +1,336 @@
import pandas as pd
import requests
import json
import logging
import time
from datetime import datetime, timedelta, date
from sqlalchemy import create_engine
from urllib.parse import quote_plus
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def get_access_token():
"""获取飞书访问令牌"""
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()
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异常: {str(e)}")
raise
def read_daily_data(spreadsheet_token, sheet_id, retry=3):
"""从飞书电子表格读取日统计数据"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values/{sheet_id}"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
for attempt in range(retry):
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"API错误: {resp_data.get('msg')}")
# 解析数据:跳过标题行,只取日期和激活数量
values = resp_data['data']['valueRange']['values'][1:]
daily_data = []
for row in values:
try:
date_str = row[0]
count = int(row[1])
# 转换日期格式
date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
daily_data.append({"date": date_obj, "count": count})
except (IndexError, ValueError) as e:
logging.warning(f"数据格式错误: {row} - {str(e)}")
return daily_data
except Exception as e:
logging.warning(f"读取飞书表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt) # 指数退避重试
raise Exception(f"读取飞书表格失败,重试{retry}次后仍不成功")
def write_weekly_data(base_token, table_id, records, retry=3):
"""写入数据到飞书多维表格(周统计)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{base_token}/tables/{table_id}/records/batch_create"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 构建API请求体
payload = {"records": []}
for row in records:
try:
# === 关键修复:使用毫秒时间戳格式(飞书官方要求)[3,8](@ref) ===
# 注意需要将date对象转换为datetime对象设置时间为00:00:00
start_dt = datetime.combine(row['week_start'], datetime.min.time())
end_dt = datetime.combine(row['week_end'], datetime.min.time())
start_timestamp = int(start_dt.timestamp() * 1000)
end_timestamp = int(end_dt.timestamp() * 1000)
payload["records"].append({
"fields": {
"周数": str(row['week_number']),
"周开始日期": start_timestamp, # 时间戳格式[3,8](@ref)
"周结束日期": end_timestamp, # 时间戳格式[3,8](@ref)
"激活数量": int(row['active_count'])
}
})
except Exception as e:
logging.error(f"记录格式错误: {row} - {str(e)}")
# 处理空记录情况
if not payload["records"]:
logging.warning("没有有效记录可写入多维表格")
return None
# 分批写入每次最多100条
batch_size = 100
success_count = 0
for i in range(0, len(payload["records"]), batch_size):
batch = payload["records"][i:i + batch_size]
batch_payload = {"records": batch}
batch_success = False # 标记批次是否成功
for attempt in range(retry):
try:
response = requests.post(url, json=batch_payload, headers=headers, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
if not batch_success: # 仅首次成功时计数
success_count += len(batch)
batch_success = True
logging.info(f"成功写入批次 {i // batch_size + 1}: {len(batch)}条记录")
break # 成功则跳出重试循环
else:
error_msg = resp_data.get('msg', '未知错误')
error_code = resp_data.get("code", "")
# 添加详细的错误诊断[3,8](@ref)
if error_code == "DatetimeFieldConvFail":
logging.error(f"日期字段转换失败: 请检查多维表格中日期字段类型配置[3,8](@ref)")
# 记录导致错误的字段值
logging.error(f"问题记录示例: 周开始日期={start_timestamp}, 周结束日期={end_timestamp}")
elif error_code == "TextFieldConvFail":
logging.error(f"文本字段转换失败: 请检查数字字段是否包含非数字字符")
elif error_code == "FieldNameNotFound":
logging.error(f"字段未找到: 请检查字段名称是否与多维表格一致")
# 添加详细错误日志
if 'data' in resp_data and 'errors' in resp_data['data']:
logging.error(f"详细错误信息: {resp_data['data']['errors']}")
logging.warning(f"飞书多维表格API错误(尝试 {attempt + 1}/{retry}): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/{retry}): {str(e)}")
time.sleep(2 ** attempt)
if not batch_success:
logging.error(f"批次 {i // batch_size + 1} 写入失败")
return success_count
def calculate_iso_week(date):
"""计算ISO周数处理跨年"""
# 获取ISO年份和周数
iso_year, iso_week, iso_day = date.isocalendar()
# 处理跨年情况
if iso_week == 1 and date.month == 12:
return f"{iso_year}-W{iso_week:02d}"
elif iso_week >= 52 and date.month == 1:
return f"{date.year - 1}-W{iso_week:02d}"
else:
return f"{date.year}-W{iso_week:02d}"
def calculate_week_range(date):
"""计算所在周的周一和周日日期ISO标准"""
# 计算周一 (0=Monday, 6=Sunday)
start_date = date - timedelta(days=date.weekday())
end_date = start_date + timedelta(days=6)
return start_date, end_date
def send_feishu_message(webhook_url, message, daily_url=None, weekly_url=None, retry=2):
"""发送飞书通知"""
headers = {'Content-Type': 'application/json'}
# 构建交互式卡片
elements = [
{
"tag": "div",
"text": {"content": message, "tag": "lark_md"}
}
]
actions = []
# 添加日统计表格按钮
if daily_url:
actions.append({
"tag": "button",
"text": {"content": "查看日统计", "tag": "plain_text"},
"type": "default",
"url": daily_url
})
# 添加周统计表格按钮
if weekly_url:
actions.append({
"tag": "button",
"text": {"content": "查看周统计", "tag": "plain_text"},
"type": "primary",
"url": weekly_url
})
if actions:
elements.append({
"tag": "action",
"actions": actions
})
payload = {
"msg_type": "interactive",
"card": {
"config": {"wide_screen_mode": True},
"elements": elements,
"header": {
"title": {"content": "数据同步完成通知", "tag": "plain_text"},
"template": "green"
}
}
}
for attempt in range(retry):
try:
response = requests.post(webhook_url, headers=headers, json=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__':
# 配置参数
DAILY_SPREADSHEET_ID = "MIqssvuB3h3XmgtkI26cBX7Angc" # 日统计电子表格ID
DAILY_SHEET_ID = "46ea20" # 日统计工作表ID
WEEKLY_BASE_TOKEN = "T3QLbmpqma014ussjy9cgitqnSh" # 周统计多维表格ID
WEEKLY_TABLE_ID = "tbloB11YuVOSdi9e" # 周统计表ID
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/d82e4ada-8f78-40ae-b98e-75af0c448c95"
# 表格链接
DAILY_URL = "https://my-ichery.feishu.cn/sheets/MIqssvuB3h3XmgtkI26cBX7Angc?sheet=46ea20&table=tblKYOLYYl1CFwn9&view=vews5Ash0A"
WEEKLY_URL = "https://my-ichery.feishu.cn/base/T3QLbmpqma014ussjy9cgitqnSh?table=tbloB11YuVOSdi9e&view=vewFl6lbpB"
try:
logging.info("开始周统计计算流程...")
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
# 1. 从飞书日统计表读取数据
daily_data = read_daily_data(DAILY_SPREADSHEET_ID, DAILY_SHEET_ID)
logging.info(f"成功读取{len(daily_data)}条日统计记录")
# 2. 计算周统计数据(处理跨年)
weekly_stats = {}
for entry in daily_data:
date = entry['date']
count = entry['count']
# 计算ISO周数处理跨年
week_number = calculate_iso_week(date)
# 计算周范围
week_start, week_end = calculate_week_range(date)
# 初始化周统计
if week_number not in weekly_stats:
weekly_stats[week_number] = {
'week_number': week_number,
'week_start': week_start,
'week_end': week_end,
'active_count': 0
}
# 累加激活数量
weekly_stats[week_number]['active_count'] += count
# 转换为列表并排序
weekly_list = sorted(weekly_stats.values(), key=lambda x: x['week_start'])
logging.info(f"生成{len(weekly_list)}条周统计记录(含跨年处理)")
# 3. 准备写入数据
weekly_values = []
for week in weekly_list:
weekly_values.append({
'week_number': week['week_number'],
'week_start': week['week_start'],
'week_end': week['week_end'],
'active_count': week['active_count']
})
# 4. 写入周统计表(分批处理)
success_count = write_weekly_data(WEEKLY_BASE_TOKEN, WEEKLY_TABLE_ID, weekly_values)
if success_count != len(weekly_values):
raise Exception(f"周统计写入失败: 成功写入{success_count}条,总记录{len(weekly_values)}")
logging.info(f"成功写入{success_count}条周统计记录到飞书多维表格")
# 5. 构建通知消息
today = datetime.now().date()
current_week_number = calculate_iso_week(today)
stats_summary = f"• 本周({current_week_number})激活车辆: {weekly_stats.get(current_week_number, {}).get('active_count', 0)}\n"
stats_summary += f"• 累计激活车辆: {sum(week['active_count'] for week in weekly_list)}\n"
# 添加最新周统计摘要
if weekly_list:
latest_week = weekly_list[-1]
stats_summary += f"• 最新一周({latest_week['week_number']}): {latest_week['week_start'].strftime('%Y-%m-%d')}{latest_week['week_end'].strftime('%Y-%m-%d')}, 激活{latest_week['active_count']}"
message = f"📅 周统计计算完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += f"📝 处理结果: 成功处理{len(daily_data)}条日记录 → 生成{len(weekly_list)}条周记录"
# 6. 发送通知
result = send_feishu_message(
WEBHOOK_URL,
message,
daily_url=DAILY_URL,
weekly_url=WEEKLY_URL
)
logging.info(f"飞书消息发送成功: {result}")
except Exception as e:
error_message = f"❌ 周统计计算异常: {str(e)}"
logging.exception("程序执行异常")
send_feishu_message(WEBHOOK_URL, error_message)

213
hang/demo0610.py Normal file
View File

@@ -0,0 +1,213 @@
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
import time # 新增用于消息重试延迟
# ================== 飞书配置 ==================
# 飞书应用凭证
APP_ID = "cli_a8b50ec0eed1500d"
APP_SECRET = "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK"
# 飞书多维表格配置
SPREADSHEET_ID = "MIqssvuB3h3XmgtkI26cBX7Angc" # 表格ID
SHEET_NAME = "46ea20" # 工作表名称
# 飞书机器人Webhook
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/3bc45247-c469-47a0-bfe9-c62e46c5aca0"
# ================== 数据库配置 ==================
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
)
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# ================== 功能函数 ==================
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()
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):
"""覆盖写入数据到飞书多维表格"""
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"{SHEET_NAME}!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
def fetch_data_from_db():
"""从数据库执行SQL查询获取数据"""
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发送飞书消息支持添加多维表格链接
: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__':
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)

411
hang/demo061002.py Normal file
View File

@@ -0,0 +1,411 @@
import requests
import logging
import time
from datetime import datetime, timedelta, date
# ====================== 配置区域 ======================
# 飞书应用凭证
APP_ID = "cli_a8b50ec0eed1500d"
APP_SECRET = "ccxkE7ZCFQZcwkkM1rLy0ccZRXYsT2xK"
# 日统计表格配置
DAILY_SPREADSHEET_ID = "MIqssvuB3h3XmgtkI26cBX7Angc" # 日统计电子表格ID
DAILY_SHEET_ID = "46ea20" # 日统计工作表ID
DAILY_URL = "https://my-ichery.feishu.cn/sheets/MIqssvuB3h3XmgtkI26cBX7Angc?sheet=46ea20" # 日统计表格链接
# 周统计表格配置
WEEKLY_BASE_TOKEN = "T3QLbmpqma014ussjy9cgitqnSh" # 周统计多维表格ID
WEEKLY_TABLE_ID = "tbloB11YuVOSdi9e" # 周统计表ID
WEEKLY_URL = "https://my-ichery.feishu.cn/base/T3QLbmpqma014ussjy9cgitqnSh?table=tbloB11YuVOSdi9e" # 周统计表格链接
# 飞书机器人配置
WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/3bc45247-c469-47a0-bfe9-c62e46c5aca0"
# ====================== 日志配置 ======================
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def get_access_token():
"""获取飞书访问令牌[1,3](@ref)"""
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异常: {str(e)}")
raise
def read_daily_data(retry=3):
"""从飞书电子表格读取日统计数据[1,3](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{DAILY_SPREADSHEET_ID}/values/{DAILY_SHEET_ID}"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
for attempt in range(retry):
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"API错误: {resp_data.get('msg')}")
# 解析数据:跳过标题行,只取日期和激活数量
values = resp_data['data']['valueRange']['values'][1:]
daily_data = []
for row in values:
try:
date_str = row[0]
count = int(row[1])
date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
daily_data.append({"date": date_obj, "count": count})
except (IndexError, ValueError) as e:
logging.warning(f"数据格式错误: {row} - {str(e)}")
return daily_data
except Exception as e:
logging.warning(f"读取飞书表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt) # 指数退避重试
raise Exception(f"读取飞书表格失败,重试{retry}次后仍不成功")
def clear_weekly_table(retry=3):
"""清空周统计表(覆盖写入前的准备)[3,5](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 获取所有记录ID
record_ids = []
page_token = ""
while True:
params = {"page_size": 100}
if page_token:
params["page_token"] = page_token
response = requests.get(url, headers=headers, params=params, timeout=30)
resp_data = response.json()
if resp_data.get("code") != 0:
raise Exception(f"获取记录失败: {resp_data.get('msg')}")
# 收集所有记录的ID
for record in resp_data.get("data", {}).get("items", []):
record_ids.append(record["record_id"])
# 检查是否有更多数据
if resp_data["data"].get("has_more"):
page_token = resp_data["data"].get("page_token", "")
else:
break
if not record_ids:
logging.info("周统计表为空,无需清除")
return True
logging.info(f"获取到{len(record_ids)}条记录待删除")
# 批量删除所有记录
delete_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records/batch_delete"
# 分批删除每次最多100条[3](@ref)
batch_size = 100
for i in range(0, len(record_ids), batch_size):
batch_ids = record_ids[i:i + batch_size]
payload = {"records": batch_ids}
for attempt in range(retry):
try:
response = requests.post(delete_url, json=payload, headers=headers, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
logging.info(f"成功删除批次 {i // batch_size + 1}: {len(batch_ids)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
logging.warning(f"删除记录失败(尝试 {attempt + 1}/{retry}): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"删除记录网络错误(尝试 {attempt + 1}/{retry}): {str(e)}")
time.sleep(2 ** attempt)
return True
def write_weekly_data(records, retry=3):
"""覆盖写入数据到飞书多维表格(周统计)[3,5](@ref)"""
access_token = get_access_token()
url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{WEEKLY_BASE_TOKEN}/tables/{WEEKLY_TABLE_ID}/records/batch_create"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 构建API请求体
payload = {"records": []}
for row in records:
try:
# 使用毫秒时间戳格式(飞书官方要求)[3](@ref)
start_dt = datetime.combine(row['week_start'], datetime.min.time())
end_dt = datetime.combine(row['week_end'], datetime.min.time())
start_timestamp = int(start_dt.timestamp() * 1000)
end_timestamp = int(end_dt.timestamp() * 1000)
payload["records"].append({
"fields": {
"周数": str(row['week_number']),
"周开始日期": start_timestamp,
"周结束日期": end_timestamp,
"激活数量": int(row['active_count'])
}
})
except Exception as e:
logging.error(f"记录格式错误: {row} - {str(e)}")
# 处理空记录情况
if not payload["records"]:
logging.warning("没有有效记录可写入多维表格")
return 0
# 分批写入每次最多100条[3](@ref)
batch_size = 100
success_count = 0
for i in range(0, len(payload["records"]), batch_size):
batch = payload["records"][i:i + batch_size]
batch_payload = {"records": batch}
batch_success = False
for attempt in range(retry):
try:
response = requests.post(url, json=batch_payload, headers=headers, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
if not batch_success:
success_count += len(batch)
batch_success = True
logging.info(f"成功写入批次 {i // batch_size + 1}: {len(batch)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
error_code = resp_data.get("code", "")
# 错误诊断[3](@ref)
if error_code == "DatetimeFieldConvFail":
logging.error("日期字段转换失败: 请检查多维表格中日期字段类型配置")
elif error_code == "TextFieldConvFail":
logging.error("文本字段转换失败: 请检查数字字段是否包含非数字字符")
logging.warning(f"飞书API错误(尝试 {attempt + 1}/{retry}): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/{retry}): {str(e)}")
time.sleep(2 ** attempt)
if not batch_success:
logging.error(f"批次 {i // batch_size + 1} 写入失败")
return success_count
def calculate_iso_week(date):
"""计算ISO周数处理跨年"""
iso_year, iso_week, iso_day = date.isocalendar()
return f"{iso_year}-W{iso_week:02d}"
def calculate_week_range(date):
"""计算所在周的周一和周日日期ISO标准"""
start_date = date - timedelta(days=date.weekday())
end_date = start_date + timedelta(days=6)
return start_date, end_date
def send_feishu_message(message, retry=2):
"""发送飞书通知[6,7,8](@ref)"""
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": "default",
"url": DAILY_URL
},
{
"tag": "button",
"text": {"content": "查看周统计", "tag": "plain_text"},
"type": "primary",
"url": WEEKLY_URL
}
]
}
],
"header": {
"title": {"content": "数据同步完成通知", "tag": "plain_text"},
"template": "green"
}
}
}
for attempt in range(retry):
try:
response = requests.post(WEBHOOK_URL, headers=headers, json=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__':
try:
logging.info("开始周统计计算流程...")
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
# 1. 从飞书日统计表读取数据
daily_data = read_daily_data()
logging.info(f"成功读取{len(daily_data)}条日统计记录")
# 2. 计算周统计数据(处理跨年)
weekly_stats = {}
for entry in daily_data:
date = entry['date']
count = entry['count']
week_number = calculate_iso_week(date)
week_start, week_end = calculate_week_range(date)
if week_number not in weekly_stats:
weekly_stats[week_number] = {
'week_number': week_number,
'week_start': week_start,
'week_end': week_end,
'active_count': 0
}
weekly_stats[week_number]['active_count'] += count
# 转换为列表并排序
weekly_list = sorted(weekly_stats.values(), key=lambda x: x['week_start'])
logging.info(f"生成{len(weekly_list)}条周统计记录")
# 3. 覆盖写入周统计表
# 先清空表
if not clear_weekly_table():
raise Exception("清空周统计表失败")
logging.info("成功清空周统计表")
# 再写入新数据
success_count = write_weekly_data(weekly_list)
if success_count != len(weekly_list):
raise Exception(f"周统计写入失败: 成功写入{success_count}条,总记录{len(weekly_list)}")
logging.info(f"成功覆盖写入{success_count}条周统计记录")
# 4. 构建通知消息
today = datetime.now().date()
current_week_number = calculate_iso_week(today)
stats_summary = f"• 本周({current_week_number})激活车辆: {weekly_stats.get(current_week_number, {}).get('active_count', 0)}\n"
stats_summary += f"• 累计激活车辆: {sum(week['active_count'] for week in weekly_list)}\n"
if weekly_list:
latest_week = weekly_list[-1]
stats_summary += f"• 最新一周({latest_week['week_number']}): {latest_week['week_start'].strftime('%Y-%m-%d')}{latest_week['week_end'].strftime('%Y-%m-%d')}, 激活{latest_week['active_count']}"
message = f"📅 周统计计算完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += f"📝 处理结果: 成功处理{len(daily_data)}条日记录 → 生成{len(weekly_list)}条周记录"
# 5. 构建通知消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
# 新增统计维度计算
today = datetime.now().date()
yesterday = today - timedelta(days=1)
current_week_start = today - timedelta(days=today.weekday())
current_week_end = current_week_start + timedelta(days=6)
last_week_start = current_week_start - timedelta(days=7)
last_week_end = last_week_start + timedelta(days=6)
three_months_ago = today - timedelta(days=90)
# 计算昨日数据
yesterday_count = next((d['count'] for d in daily_data if d['date'] == yesterday), 0)
# 计算今日数据
today_count = next((d['count'] for d in daily_data if d['date'] == today), 0)
# 计算本周数据(当前周)
current_week_number = calculate_iso_week(today)
current_week_total = weekly_stats.get(current_week_number, {}).get('active_count', 0)
# 计算上周数据
last_week_number = calculate_iso_week(last_week_start)
last_week_total = weekly_stats.get(last_week_number, {}).get('active_count', 0)
# 计算最近三个月数据
recent_three_months = sum(
d['count'] for d in daily_data
if three_months_ago <= d['date'] <= today
)
# 计算累计数据
total_count = sum(d['count'] for d in daily_data)
# 构建统计摘要
stats_summary = f"• 今日激活: {today_count}\n"
stats_summary += f"• 昨日激活: {yesterday_count}\n"
stats_summary += f"• 本周激活: {current_week_total}\n"
stats_summary += f"• 上周激活: {last_week_total}\n"
stats_summary += f"• 近三月激活: {recent_three_months}\n"
stats_summary += f"• 累计激活: {total_count}\n"
# # 添加最新周统计摘要
# if weekly_list:
# latest_week = weekly_list[-1]
# stats_summary += f"• 最新一周({latest_week['week_number']}): {latest_week['active_count']}辆"
message = f"📅欧盟O&J统计计算完成 ✅\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 统计摘要:\n{stats_summary}\n"
message += f"📝 处理结果: 成功处理{len(daily_data)}条日记录 → 生成{len(weekly_list)}条周记录"
# 5. 发送通知
result = send_feishu_message(message)
logging.info(f"飞书消息发送成功: {result}")
except Exception as e:
error_message = f"❌ 周统计计算异常: {str(e)}"
logging.exception("程序执行异常")
send_feishu_message(error_message)

358
hang/demo061003.py Normal file
View File

@@ -0,0 +1,358 @@
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 - %(essage)s',
datefmt='%Y-%m-%d %H:%M:%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
FEISHU_TABLE_ID = "tbloPJHLc05MHIAY" # 表格ID
VIN_FIELD_NAME = "车架号" # VIN字段名称
ACTIVE_FIELD_NAME = "激活状态" # 激活状态字段名称
# 飞书应用凭证
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异常: {str(e)}")
raise
def send_feishu_message(message):
"""发送飞书通知[9,10](@ref)"""
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, 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} # 单次请求最大记录数
all_records = []
page_token = None
while True:
if page_token:
params["page_token"] = page_token
records_fetched = False
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)
# 检查是否有下一页
if "page_token" in resp_data["data"] and resp_data["data"]["has_more"]:
page_token = resp_data["data"]["page_token"]
else:
page_token = None
records_fetched = True
break
except Exception as e:
logging.warning(f"读取多维表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt)
if not records_fetched:
raise Exception("无法读取多维表格数据")
if not page_token:
break
return all_records
def fetch_activation_data():
"""从数据库查询车辆激活状态"""
try:
# 创建SQLAlchemy引擎
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_vehicle_info.VIN AS car_vins,
CASE v_sim_info.STATE
WHEN 2 THEN 'active'
ELSE 'no_active'
END AS active_status
FROM v_vehicle_info
JOIN v_tbox_info USING (VIN)
JOIN v_sim_info USING (SN);
"""
# 执行查询并转换为DataFrame
df = pd.read_sql(sql_query, engine)
return df.set_index('car_vins')['active_status'].to_dict()
except Exception as e:
logging.error(f"数据库查询失败: {str(e)}")
raise
def compare_and_update_records(app_token, table_id, activation_data):
"""比较并更新飞书多维表格记录"""
access_token = get_access_token()
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 1. 读取多维表格所有记录
feishu_records = read_feishu_records(app_token, table_id)
logging.info(f"成功读取{len(feishu_records)}条多维表格记录")
# 统计变量
total_updated = 0
activated_count = 0
not_activated_count = 0
# 2. 准备需要更新的记录
records_to_update = []
for record in feishu_records:
try:
record_id = record["record_id"]
fields = record.get("fields", {})
# 获取车架号(VIN)
vin = fields.get(VIN_FIELD_NAME)
if not vin:
logging.warning(f"记录 {record_id} 缺少VIN字段")
continue
# 获取多维表格中的激活状态
current_status = fields.get(ACTIVE_FIELD_NAME, "").lower()
# 获取数据库中的激活状态
db_status = activation_data.get(vin)
if not db_status:
logging.warning(f"VIN {vin} 在数据库中未找到")
continue
# 比较状态是否需要更新
if db_status != current_status:
records_to_update.append({
"record_id": record_id,
"fields": {
ACTIVE_FIELD_NAME: db_status
}
})
logging.info(f"VIN {vin} 状态需更新: {current_status}{db_status}")
# 统计更新状态
if db_status == 'active':
activated_count += 1
else:
not_activated_count += 1
except Exception as e:
logging.error(f"处理记录异常: {record} - {str(e)}")
total_updated = len(records_to_update)
logging.info(f"需要更新{total_updated}条记录")
logging.info(f"更新统计: 激活车辆: {activated_count}台, 未激活车辆: {not_activated_count}")
# 3. 批量更新记录 (每批次50条)
if not records_to_update:
logging.info("没有需要更新的记录")
return total_updated, activated_count, not_activated_count
batch_size = 50
success_count = 0
for i in range(0, len(records_to_update), batch_size):
batch = records_to_update[i:i + batch_size]
batch_payload = {"records": batch}
update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
for attempt in range(3): # 最多重试3次
try:
response = requests.post(update_url, headers=headers, json=batch_payload, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
success_count += len(batch)
logging.info(f"成功更新批次 {i // batch_size + 1}: {len(batch)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
logging.warning(f"更新失败(尝试 {attempt + 1}/3): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/3): {str(e)}")
time.sleep(2 ** attempt)
logging.info(f"总计更新成功{success_count}条记录")
return total_updated, activated_count, not_activated_count
def sync_vehicle_activation():
"""执行车辆激活状态同步流程"""
try:
start_time = time.time()
logging.info("开始车辆激活状态同步流程...")
# 1. 从数据库获取激活状态数据
activation_data = fetch_activation_data()
logging.info(f"从数据库获取{len(activation_data)}条激活状态记录")
# 统计数据库中的激活状态
db_activated = sum(1 for status in activation_data.values() if status == 'active')
db_not_activated = sum(1 for status in activation_data.values() if status == 'no_active')
logging.info(f"数据库统计: 激活车辆: {db_activated}台, 未激活车辆: {db_not_activated}")
# 2. 与飞书多维表格数据比对并更新
total_updated, activated_count, not_activated_count = compare_and_update_records(
FEISHU_APP_TOKEN, FEISHU_TABLE_ID, activation_data
)
# 3. 输出最终统计结果
logging.info("=" * 50)
logging.info(f"本次更新统计结果:")
logging.info(f" - 更新车辆总数: {total_updated}")
logging.info(f" - 激活车辆数量: {activated_count}")
logging.info(f" - 未激活车辆数量: {not_activated_count}")
logging.info("=" * 50)
# 4. 完成日志
elapsed_time = time.time() - start_time
logging.info(f"流程完成! 耗时: {elapsed_time:.2f}秒, 更新记录数: {total_updated}")
return {
"total_updated": total_updated,
"activated_count": activated_count,
"not_activated_count": not_activated_count,
"elapsed_time": elapsed_time
}
except Exception as e:
logging.error(f"流程执行异常: {str(e)}")
logging.exception("详细错误信息")
return None
# ====================== 主执行流程 ======================
if __name__ == '__main__':
# 执行车辆激活状态同步
sync_result = sync_vehicle_activation()
if sync_result:
# 构建飞书消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
message = (
f"🚗 **车辆激活状态同步完成** ✅\n"
f"⏰ 同步时间: {current_time}\n"
f"⏱️ 处理耗时: {sync_result['elapsed_time']:.2f}\n\n"
f"📊 **同步统计结果:**\n"
f"• 更新车辆总数: {sync_result['total_updated']}\n"
f"• ✅ 激活车辆数量: {sync_result['activated_count']}\n"
f"• ❌ 未激活车辆数量: {sync_result['not_activated_count']}\n\n"
f"🔗 查看多维表格: [点击访问]({MULTI_TABLE_URL})"
)
# 发送飞书通知
send_result = send_feishu_message(message)
if send_result:
logging.info(f"飞书消息发送成功: {send_result}")
else:
logging.error("飞书消息发送失败")
else:
error_message = "❌ 车辆激活状态同步失败,请检查日志"
send_feishu_message(error_message)
logging.error(error_message)

91
hang/demo2.py Normal file
View File

@@ -0,0 +1,91 @@
from datetime import datetime, timedelta
import pandas as pd
import pymysql
from sqlalchemy import create_engine
import requests
import json
# 1. 获取当前系统时间[1,3,6](@ref)
def get_current_time():
"""获取格式化的当前系统时间"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 2. 数据库查询(核心函数)
def fetch_activation_data():
"""获取激活车辆数据"""
connection_uri = f"mysql+pymysql://{DB_CONFIG['user']}:{DB_CONFIG['password']}@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}"
engine = create_engine(connection_uri)
sql_query = """
SELECT
v_sim_info.ACTIVATION_DATE as active_date
FROM v_vehicle_info
JOIN v_tbox_info USING (VIN)
JOIN v_sim_info USING (SN)
WHERE v_sim_info.STATE = 2; -- 只查询激活状态车辆
"""
return pd.read_sql(sql_query, engine)
# 3. 统计计算
def calculate_stats(df):
"""计算多维统计指标"""
# 基础数据处理[1](@ref)
df['active_date'] = pd.to_datetime(df['active_date']).dt.date
# 本周统计06-02至06-05
start_week = datetime(2025, 6, 2).date()
end_week = datetime(2025, 6, 5).date()
weekly_data = df[(df['active_date'] >= start_week) & (df['active_date'] <= end_week)]
# 近三月统计03-05至06-05
start_3month = (datetime.now() - timedelta(days=90)).date()
monthly_data = df[df['active_date'] >= start_3month]
return {
"本周激活总数": len(weekly_data),
"本周每日激活": weekly_data.groupby('active_date').size().to_dict(),
"近三月激活总数": len(monthly_data),
"累计激活总数": len(df)
}
# 4. 飞书机器人消息发送
def send_feishu_report(stats):
"""发送统计报告"""
message = f"🚗 **车辆激活统计报告**{get_current_time()}\n\n"
message += f"🔹 **本周激活总数**: {stats['本周激活总数']}\n"
message += "🔹 **本周每日激活**:\n"
for date, count in stats['本周每日激活'].items():
message += f" ▸ {date}: {count}\n"
message += f"\n🔹 **近三月激活总数**: {stats['近三月激活总数']}\n"
message += f"🔹 **累计激活总数**: {stats['累计激活总数']}"
requests.post(FEISHU_WEBHOOK_URL, json={
"msg_type": "text",
"content": {"text": message}
})
# 主流程
if __name__ == "__main__":
# 配置参数
FEISHU_WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/ff3726f6-d4e3-455b-ae65-bca471305733"
# 数据库连接
DB_CONFIG = {
'host': 'eutsp-prod.mysql.germany.rds.aliyuncs.com',
'port': 3306, # MySQL默认端口
'user': 'international_tsp_eu_r',
'password': 'ZXhBgo1TB2XbF3kP',
'database': 'chery_international_tsp_eu'
}
# 执行统计
df = fetch_activation_data()
stats = calculate_stats(df)
# 发送报告
send_feishu_report(stats)
print("✅ 统计报告已发送至飞书")

74
hang/italy-seek.py Normal file
View File

@@ -0,0 +1,74 @@
from datetime import datetime, timezone
import os
import csv
# 设置路径参数
base_dir = r'C:\Users\Administrator\Desktop\European Local O&M'
result_dir = os.path.join(base_dir, 'italy_result')
os.makedirs(result_dir, exist_ok=True) # 自动创建目录
# 获取日期
current_date = datetime.now().strftime("%m%d")
# 加载激活状态数据
active_file = os.path.join(base_dir, f'all-active-{current_date}.csv.csv') # 确保文件名正确
active_status = {}
with open(active_file, encoding='utf-8') as f:
csv_reader = csv.reader(f)
next(csv_reader)
for row in csv_reader:
vin = row[0].strip().upper()
status = row[1].strip().lower()
active_status[vin] = 'active' if status == 'active' else 'NO'
# 加载需要核实的VIN列表
vin_list = []
with open(os.path.join(base_dir, 'eu516.csv'), 'r', encoding='utf-8') as f:
csv_reader = csv.reader(f)
next(csv_reader)
for row in csv_reader:
vin_list.append(row[0].strip().upper())
# 输出文件路径
output_all = os.path.join(result_dir, f'italy-result{current_date}.csv')
output_no = os.path.join(result_dir, f'no-italy-result{current_date}.csv')
# 写入意大利所有VIN的状态
with open(output_all, 'w', newline='', encoding='utf-8') as f:
csv_writer = csv.writer(f)
csv_writer.writerow(['VIN', 'active-status'])
for vin in vin_list:
status = active_status.get(vin, 'NO')
csv_writer.writerow([vin, status])
# 统计信息并生成未激活车辆清单
activated_count = 0
deactivated_count = 0
# 当前UTC时间
current_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
with open(output_all, encoding='utf-8') as f_in, \
open(output_no, 'w', newline='', encoding='utf-8') as f_out:
reader = csv.DictReader(f_in)
writer = csv.DictWriter(f_out, fieldnames=reader.fieldnames)
writer.writeheader()
for row in reader:
status = row['active-status'].strip().lower()
if status == 'active':
activated_count += 1
elif status == 'no':
deactivated_count += 1
writer.writerow(row)
# 控制台输出统计信息
print(f'''
统计时间:{current_utc}
激活车辆数:{activated_count}
未激活车辆数:{deactivated_count}
📁 所有结果已保存至:{result_dir}
''')

Binary file not shown.

358
hang/output/demo0611.py Normal file
View File

@@ -0,0 +1,358 @@
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 - %(essage)s',
datefmt='%Y-%m-%d %H:%M:%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
FEISHU_TABLE_ID = "tbloPJHLc05MHIAY" # 表格ID
VIN_FIELD_NAME = "车架号" # VIN字段名称
ACTIVE_FIELD_NAME = "激活状态" # 激活状态字段名称
# 飞书应用凭证
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异常: {str(e)}")
raise
def send_feishu_message(message):
"""发送飞书通知[9,10](@ref)"""
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, 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} # 单次请求最大记录数
all_records = []
page_token = None
while True:
if page_token:
params["page_token"] = page_token
records_fetched = False
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)
# 检查是否有下一页
if "page_token" in resp_data["data"] and resp_data["data"]["has_more"]:
page_token = resp_data["data"]["page_token"]
else:
page_token = None
records_fetched = True
break
except Exception as e:
logging.warning(f"读取多维表格失败(尝试 {attempt + 1}/{retry}): {str(e)}")
if attempt < retry - 1:
time.sleep(2 ** attempt)
if not records_fetched:
raise Exception("无法读取多维表格数据")
if not page_token:
break
return all_records
def fetch_activation_data():
"""从数据库查询车辆激活状态"""
try:
# 创建SQLAlchemy引擎
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_vehicle_info.VIN AS car_vins,
CASE v_sim_info.STATE
WHEN 2 THEN 'active'
ELSE 'no_active'
END AS active_status
FROM v_vehicle_info
JOIN v_tbox_info USING (VIN)
JOIN v_sim_info USING (SN);
"""
# 执行查询并转换为DataFrame
df = pd.read_sql(sql_query, engine)
return df.set_index('car_vins')['active_status'].to_dict()
except Exception as e:
logging.error(f"数据库查询失败: {str(e)}")
raise
def compare_and_update_records(app_token, table_id, activation_data):
"""比较并更新飞书多维表格记录"""
access_token = get_access_token()
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json; charset=utf-8",
}
# 1. 读取多维表格所有记录
feishu_records = read_feishu_records(app_token, table_id)
logging.info(f"成功读取{len(feishu_records)}条多维表格记录")
# 统计变量
total_updated = 0
activated_count = 0
not_activated_count = 0
# 2. 准备需要更新的记录
records_to_update = []
for record in feishu_records:
try:
record_id = record["record_id"]
fields = record.get("fields", {})
# 获取车架号(VIN)
vin = fields.get(VIN_FIELD_NAME)
if not vin:
logging.warning(f"记录 {record_id} 缺少VIN字段")
continue
# 获取多维表格中的激活状态
current_status = fields.get(ACTIVE_FIELD_NAME, "").lower()
# 获取数据库中的激活状态
db_status = activation_data.get(vin)
if not db_status:
logging.warning(f"VIN {vin} 在数据库中未找到")
continue
# 比较状态是否需要更新
if db_status != current_status:
records_to_update.append({
"record_id": record_id,
"fields": {
ACTIVE_FIELD_NAME: db_status
}
})
logging.info(f"VIN {vin} 状态需更新: {current_status}{db_status}")
# 统计更新状态
if db_status == 'active':
activated_count += 1
else:
not_activated_count += 1
except Exception as e:
logging.error(f"处理记录异常: {record} - {str(e)}")
total_updated = len(records_to_update)
logging.info(f"需要更新{total_updated}条记录")
logging.info(f"更新统计: 激活车辆: {activated_count}台, 未激活车辆: {not_activated_count}")
# 3. 批量更新记录 (每批次50条)
if not records_to_update:
logging.info("没有需要更新的记录")
return total_updated, activated_count, not_activated_count
batch_size = 50
success_count = 0
for i in range(0, len(records_to_update), batch_size):
batch = records_to_update[i:i + batch_size]
batch_payload = {"records": batch}
update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
for attempt in range(3): # 最多重试3次
try:
response = requests.post(update_url, headers=headers, json=batch_payload, timeout=30)
resp_data = response.json()
if response.status_code == 200 and resp_data.get("code") == 0:
success_count += len(batch)
logging.info(f"成功更新批次 {i // batch_size + 1}: {len(batch)}条记录")
break
else:
error_msg = resp_data.get('msg', '未知错误')
logging.warning(f"更新失败(尝试 {attempt + 1}/3): {error_msg}")
time.sleep(2 ** attempt)
except Exception as e:
logging.warning(f"网络错误(尝试 {attempt + 1}/3): {str(e)}")
time.sleep(2 ** attempt)
logging.info(f"总计更新成功{success_count}条记录")
return total_updated, activated_count, not_activated_count
def sync_vehicle_activation():
"""执行车辆激活状态同步流程"""
try:
start_time = time.time()
logging.info("开始车辆激活状态同步流程...")
# 1. 从数据库获取激活状态数据
activation_data = fetch_activation_data()
logging.info(f"从数据库获取{len(activation_data)}条激活状态记录")
# 统计数据库中的激活状态
db_activated = sum(1 for status in activation_data.values() if status == 'active')
db_not_activated = sum(1 for status in activation_data.values() if status == 'no_active')
logging.info(f"数据库统计: 激活车辆: {db_activated}台, 未激活车辆: {db_not_activated}")
# 2. 与飞书多维表格数据比对并更新
total_updated, activated_count, not_activated_count = compare_and_update_records(
FEISHU_APP_TOKEN, FEISHU_TABLE_ID, activation_data
)
# 3. 输出最终统计结果
logging.info("=" * 50)
logging.info(f"本次更新统计结果:")
logging.info(f" - 更新车辆总数: {total_updated}")
logging.info(f" - 激活车辆数量: {activated_count}")
logging.info(f" - 未激活车辆数量: {not_activated_count}")
logging.info("=" * 50)
# 4. 完成日志
elapsed_time = time.time() - start_time
logging.info(f"流程完成! 耗时: {elapsed_time:.2f}秒, 更新记录数: {total_updated}")
return {
"total_updated": total_updated,
"activated_count": activated_count,
"not_activated_count": not_activated_count,
"elapsed_time": elapsed_time
}
except Exception as e:
logging.error(f"流程执行异常: {str(e)}")
logging.exception("详细错误信息")
return None
# ====================== 主执行流程 ======================
if __name__ == '__main__':
# 执行车辆激活状态同步
sync_result = sync_vehicle_activation()
if sync_result:
# 构建飞书消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
message = (
f"🚗 **车辆激活状态同步完成** ✅\n"
f"⏰ 同步时间: {current_time}\n"
f"⏱️ 处理耗时: {sync_result['elapsed_time']:.2f}\n\n"
f"📊 **同步统计结果:**\n"
f"• 更新车辆总数: {sync_result['total_updated']}\n"
f"• ✅ 激活车辆数量: {sync_result['activated_count']}\n"
f"• ❌ 未激活车辆数量: {sync_result['not_activated_count']}\n\n"
f"🔗 查看多维表格: [点击访问]({MULTI_TABLE_URL})"
)
# 发送飞书通知
send_result = send_feishu_message(message)
if send_result:
logging.info(f"飞书消息发送成功: {send_result}")
else:
logging.error("飞书消息发送失败")
else:
error_message = "❌ 车辆激活状态同步失败,请检查日志"
send_feishu_message(error_message)
logging.error(error_message)

123
lion-ououmeng9.py Normal file
View File

@@ -0,0 +1,123 @@
from cassandra.cluster import Cluster
from cassandra.query import BatchStatement
import time
import hashlib
from cassandra.auth import PlainTextAuthProvider
import base64
from multiprocessing import Process
from multiprocessing import Pool
def sha256(data):
sha256 = hashlib.sha256()
sha256.update(data.encode('utf-8'))
return sha256.hexdigest()
def execute():
# 定义用户名和密码
username = 'tsp_eu_stress'
password = 'UaiKeq6cKsm2S4y'
# 创建身份验证提供程序
auth_provider = PlainTextAuthProvider(username=username, password=password)
# 连接到 Cassandra 集群
cluster = Cluster(['10.95.0.81'], auth_provider=auth_provider)
session = cluster.connect()
# # 创建Cassandra集群连接
# cluster = Cluster(['172.25.116.188'])
session = cluster.connect('tsp_eu')
# 创建批量操作语句
# batch = BatchStatement()
query2 = "insert into tsp_eu.realtime_his(v,ct,data,st,type) values(%s, %s,%s, %s,%s)"
query3 = "insert into tsp_eu.realtime_hf(v,ct,data,st,type) values(%s, %s,%s, %s,%s)"
#j为每个vin插入条数i为插入的vin数
for j in range(1):
for i in range(1,1000001):
# time.sleep(1)
# 创建插入语句
# tbox登陆登出插入语句
# query1 = "INSERT INTO tbox_login_logout (v,st,ct,reason,result,tbox_sn,type) VALUES (%s, %s,%s, %s,%s, %s,%s)"
# batch = BatchStatement()
# batch1 = BatchStatement()
# batch.add(query1, (
# sha256('TESTVIN' + str(i).zfill(10)), int(time.time() * 1000), int(time.time() * 1000), "1", "1", "1111",
# "2"))
# print('TESTVIN' + str(i).zfill(10))
# session.execute(batch)
#
# # # IHU车机登陆登出插入语句
# query2 = "insert into tsp_eu.hu_login_logout(v,st,ct,hu_sn,status,status_msg,type) values(%s, %s,%s, %s,%s, %s,%s)"
# batch1.add(query2, (sha256('TESTVIN' + str(i).zfill(10)), int(time.time() * 1000), int(time.time() * 1000),
# 'TESTVIN' + str(i).zfill(10), '1', 'ddd', '1'))
# session.execute(batch1)
# 低频数据插入语句
# batch2 = BatchStatement()
# print('TESTVIN' + str(i).zfill(10))
# batch2.add(query2, (
# sha256('TESTVIN' + str(i).zfill(10)), int(time.time() * 1000), "{\"202F\":255,\"2030\":255,\"2031\":255,\"2032\":255,\"2035\":255,\"2036\":255,\"2039\":255,\"203A\":255,\"203B\":255,\"203C\":255}",int(time.time() * 1000), '0'))
# session.execute(batch2)
#
# # 高频数据插入语句
# batch3 = BatchStatement()
# query3 = "insert into tsp_eu.realtime_hf(v,ct,data,st,type) values(%s, %s,%s, %s,%s)"
# batch2.add(query3, (
# sha256('TESTVIN' + str(i).zfill(10)), int(time.time() * 1000), "{\"0001\":46720,\"2005\":0,\"2076\":0,\"2077\":38912,\"2082\":2958360575,\"2084\":111428784,\"2085\":46720,\"2086\":0,\"2087\":2908749824,\"208A\":176,\"208C\":68}",
# int(time.time() * 1000), '0'))
# session.execute(batch2)
# 实时数据插入语句-每个vin只能有一条数据
batch4 = BatchStatement()
query4 = "insert into tsp_eu.realtime_new(v,ct,data,st) values(%s, %s,%s, %s)"
print('HESTVIN' + str(i).zfill(10))
batch4.add(query4, (
sha256('HESTVIN' + str(i).zfill(10)), int(time.time() * 1000), {'2001': '350', '2002': '1', '2003': '2000', '2004': '600', '2005': '60', '2006': '83', '2007': '350', '2008': '0', '2009': '10000', '200A': '600', '200B': '1000', '200C': '3000', '200D': '0', '200E': '1', '200F': '3', '2010': '1', '2011': '0', '2012': '0', '2013': '2', '2014': '1', '2015': '0', '2016': '0', '2017': '3', '2018': '0', '2019': '0', '201A': '0', '201B': '0', '201C': '0', '201D': '0', '201E': '0', '201F': '0', '2020': '0', '2021': '0', '2022': '0', '2023': '0', '2024': '0', '2025': '0', '2026': '230', '2027': '230', '2028': '230', '2029': '0', '202A': '6', '202B': '6', '202C': '1', '202D': '0', '202E': '0', '202F': '0', '2030': '1', '2031': '2', '2032': '0', '2033': '1', '2034': '1', '2035': '1', '2036': '1', '2037': '3', '2038': '1', '2039': '1', '203A': '0', '203B': '0', '203C': '0', '203D': '0', '203E': '150', '203F': '0', '2040': '0', '2041': '3', '2042': '17000', '2043': '1000', '2044': '85', '2045': '0', '2046': '23', '2047': '23', '2048': '23', '2049': '23', '204A': '70', '204B': '70', '204C': '70', '204D': '70', '204E': '0', '204F': '0', '2050': '0', '2051': '0', '2052': '0', '2053': '0', '2054': '0', '2055': '0', '2056': '0', '2057': '0', '2058': '0', '2059': '0', '205A': '0', '205B': '0', '205C': '0', '205D': '0', '205E': '1687590135166', '205F': '1687590135166', '2060': '5000', '2061': '180', '2062': '3000', '2063': '0', '2064': '0', '2065': '0', '2066': '0', '2067': '0', '2068': '0', '2069': '0', '206A': '0', '206B': '0', '206C': '0', '206D': '5', '206E': '0', '206F': '5', '2070': '0', '2071': '0', '2072': '0', '2073': '0', '2076': '3', '2077': '350', '2078': '0', '2080': '255', '2081': '255', '2082': '4294967295', '2083': '255', '2084': '4294967295', '2085': '4294967295', '2086': '4294967295', '2087': '4294967295', '2088': '0', '2089': '255', '208A': '255', '208B': '255', '208C': '1800', '208D': '4294967295', '208E': '4294967295', '208F': '4294967295', '2090': '4294967295', '2091': '255', '2092': '255', '2093': '255', '2094': '255', '2095': '65535'}, int(time.time() * 1000)))
session.execute(batch4)
# # 最新诊断
# batch5 = BatchStatement()
# query5 = "insert into tsp_eu.diagnosis_new(v,ct,data,out_vehicle_type,st) values(%s,%s, %s,%s, %s)"
# batch5.add(query5, (
# base64.urlsafe_b64encode(('TESTVIN' + str(i).zfill(10)).encode())[0:23].decode(), int(time.time() * 1000),
# {'10': 'null', '11': 'null', '12': 'null', '13': 'null', '15': 'null', '19': 'null',
# '2': '[9717033,9717267]', '20': 'null', '21': 'null', '22': 'null', '24': 'null', '32': 'null',
# '35': 'null', '36': 'null', '5': 'null', '8': 'null'}, 'T19CEV_OUTSIDE_001_TEST_KB51',
# int(time.time() * 1000)))
# session.execute(batch5)
# # 历史诊断
# batch6 = BatchStatement()
# query6 = "insert into tsp_eu.diagnosis_his(v,ct,data,st) values(%s, %s,%s, %s)"
# batch6.add(query6, (
# sha256('TESTVIN' + str(i).zfill(10)), int(time.time() * 1000),
# "{\"5010\":0,\"5011\":1,\"5001\":4,\"5A01\":[]}",
# int(time.time() * 1000)))
# session.execute(batch6)
# 执行批量操作
# session.execute(batch)
# 关闭连接
session.shutdown()
cluster.shutdown()
if __name__ == '__main__':
# poo1=Pool(30)
# for i in range(1000):
# poo1.apply_async(execute)
# poo1.close()
# poo1.join()
# for i in range(10):
# p=Process(target=execute)
# p.start()
execute()

16
main.py Normal file
View File

@@ -0,0 +1,16 @@
# This is a sample Python script.
# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
def print_hi(name):
# Use a breakpoint in the code line below to debug your script.
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
print_hi('PyCharm')
# See PyCharm help at https://www.jetbrains.com/help/pycharm/

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
pandas~=2.2.3
requests~=2.32.3
sqlalchemy~=2.0.41
PyMySQL~=1.1.1
cassandra-driver~=3.28.0