Spaces:
Runtime error
Runtime error
Upload app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
import datetime
|
| 4 |
+
import time # 用于时间转换
|
| 5 |
+
import gradio as gr
|
| 6 |
+
|
| 7 |
+
# --- 抖音配置信息 ---
|
| 8 |
+
DOUYIN_CLIENT_KEY = 'awbeykzyos7kbidv'
|
| 9 |
+
DOUYIN_CLIENT_SECRET = '4575440b156ecbe144284e4f69d284a2' # Keep secure!
|
| 10 |
+
DOUYIN_ACCOUNT_ID = '7241078611527075855'
|
| 11 |
+
DOUYIN_TOKEN_URL = 'https://open.douyin.com/oauth/client_token/'
|
| 12 |
+
DOUYIN_ORDER_QUERY_URL = 'https://open.douyin.com/goodlife/v1/trade/order/query/'
|
| 13 |
+
|
| 14 |
+
# --- 飞书配置信息 ---
|
| 15 |
+
FEISHU_APP_ID = "cli_a6672cae343ad00e"
|
| 16 |
+
FEISHU_APP_SECRET = "0J4SpfBMeIxJEOXDJMNbofMipRgwkMpV" # Keep secure!
|
| 17 |
+
FEISHU_APP_TOKEN = "GcTMbpdDPaAxV5sb8gvcRVC3ndh"
|
| 18 |
+
|
| 19 |
+
# 表格 1 (订单记录)
|
| 20 |
+
FEISHU_TABLE1_ID = "tblaYUUrFT1rKXNB"
|
| 21 |
+
FEISHU_TABLE1_ORDER_FIELD = "订单号" # 文本类型
|
| 22 |
+
FEISHU_TABLE1_STATUS_FIELD = "履约状态" # 文本类型 - 将被更新为 "已兑换"
|
| 23 |
+
TABLE1_EXCHANGED_STATUS_CONTENT = "已兑换" # Table 1 更新后的状态
|
| 24 |
+
|
| 25 |
+
# 表格 2 (激活码/发送记录)
|
| 26 |
+
FEISHU_TABLE2_ID = "tbl0S1kl9FTBX8oT"
|
| 27 |
+
FEISHU_TABLE2_ACTIVATION_CODE_FIELD = "激活码" # Table 2 中激活码字段名 (假设为文本)
|
| 28 |
+
FEISHU_TABLE2_STATUS_FIELD = "发送状态" # Table 2 中发送状态字段名 (文本类型)
|
| 29 |
+
FEISHU_TABLE2_SORT_FIELD = "最后更新时间" # Table 2 中用于排序的字段名
|
| 30 |
+
FEISHU_TABLE2_UPDATE_TIME_FIELD = "最后更新时间" # Table 2 中最后更新日期字段名
|
| 31 |
+
TABLE2_UNSENT_STATUS_CONTENT = "未发送" # Table 2 中查找的状态
|
| 32 |
+
TABLE2_SENT_STATUS_CONTENT = "已发送" # Table 2 更新后的状态
|
| 33 |
+
|
| 34 |
+
# --- 抖音券(item)状态映射字典 ---
|
| 35 |
+
ITEM_STATUS_MAP = {
|
| 36 |
+
0: "初始化", 1: "交易成功", 10: "待支付", 20: "支付成功",
|
| 37 |
+
100: "待使用", 101: "交易关闭", 200: "预约中", 201: "已预约",
|
| 38 |
+
300: "退款中", 301: "已退款", 400: "履约中", 401: "已履约"
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
# =========== 必须包含所有 API 函数定义 ===========
|
| 42 |
+
|
| 43 |
+
def get_douyin_access_token(client_key, client_secret):
|
| 44 |
+
headers = {'Content-Type': 'application/json'}
|
| 45 |
+
payload = {"grant_type": "client_credential", "client_key": client_key, "client_secret": client_secret}
|
| 46 |
+
try:
|
| 47 |
+
response = requests.post(DOUYIN_TOKEN_URL, headers=headers, json=payload, timeout=10); response.raise_for_status(); data = response.json()
|
| 48 |
+
if data.get('data') and data['data'].get('error_code') == 0: return data['data'].get('access_token'), "成功获取抖音 Access Token!"
|
| 49 |
+
else: msg = f"获取抖音 Access Token 失败: Code={data.get('data', {}).get('error_code', 'N/A')}, Desc={data.get('data', {}).get('description', '未知错误')}"; return None, msg
|
| 50 |
+
except Exception as e: msg = f"请求抖音 Access Token 时发生错误: {e}"; return None, msg
|
| 51 |
+
|
| 52 |
+
def query_douyin_order(access_token, account_id, order_id_str):
|
| 53 |
+
if not access_token: return None, "抖音 Token 无效"
|
| 54 |
+
headers = {'Content-Type': 'application/json', 'access-token': access_token}
|
| 55 |
+
params = {'account_id': account_id, 'order_id': order_id_str, 'page_num': 1, 'page_size': 1}
|
| 56 |
+
try:
|
| 57 |
+
response = requests.get(DOUYIN_ORDER_QUERY_URL, headers=headers, params=params, timeout=15); response.raise_for_status(); data = response.json()
|
| 58 |
+
extra_error_code = data.get('extra', {}).get('error_code', 0); data_error_code = data.get('data', {}).get('error_code', 0)
|
| 59 |
+
if extra_error_code == 0 and data_error_code == 0: return data, f"抖音订单 '{order_id_str}' 查询成功!"
|
| 60 |
+
else: error_code = extra_error_code if extra_error_code != 0 else data_error_code; description = data.get('extra', {}).get('description', '') or data.get('data', {}).get('description', '未知错误'); msg = f"抖音订单查询失败: Code={error_code}, Desc={description}"; return None, msg
|
| 61 |
+
except Exception as e: msg = f"查询抖音订单时发生错误: {e}"; return None, msg
|
| 62 |
+
|
| 63 |
+
def parse_douyin_order_status(order_data):
|
| 64 |
+
try:
|
| 65 |
+
if not order_data or 'data' not in order_data or 'orders' not in order_data['data'] or not order_data['data']['orders']: return None, None, "抖音订单数据无效。"
|
| 66 |
+
order = order_data['data']['orders'][0]; certificates = order.get('certificate', [])
|
| 67 |
+
if not certificates: return None, None, "订单中未找到券信息。"
|
| 68 |
+
first_cert_status_code = certificates[0].get('item_status'); certificate_id = certificates[0].get('certificate_id', 'N/A')
|
| 69 |
+
if first_cert_status_code is not None: status_text = ITEM_STATUS_MAP.get(first_cert_status_code, f"未知状态码({first_cert_status_code})"); msg=f"解析到抖音订单状态: {status_text} (状态码: {first_cert_status_code}, 券ID: {certificate_id})"; return status_text, first_cert_status_code, msg
|
| 70 |
+
else: return None, None, "第一个券信息中未找到 'item_status'。"
|
| 71 |
+
except Exception as e: msg = f"解析抖音订单状态时出错: {e}"; return None, None, msg
|
| 72 |
+
|
| 73 |
+
def get_feishu_tenant_access_token(app_id, app_secret):
|
| 74 |
+
auth_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
|
| 75 |
+
payload = json.dumps({"app_id": app_id, "app_secret": app_secret})
|
| 76 |
+
headers = {'Content-Type': 'application/json; charset=utf-8'}
|
| 77 |
+
try:
|
| 78 |
+
response = requests.post(auth_url, headers=headers, data=payload, timeout=10); response.raise_for_status(); data = response.json()
|
| 79 |
+
if data.get("code") == 0: return data.get("tenant_access_token"), "获取飞书 tenant_access_token 成功!"
|
| 80 |
+
else: msg = f"获取飞书 tenant_access_token 失败: {data.get('msg')} (Code: {data.get('code')})"; return None, msg
|
| 81 |
+
except Exception as e: msg = f"请求飞书 tenant_access_token 时发生错误: {e}"; return None, msg
|
| 82 |
+
|
| 83 |
+
def check_order_exists_in_feishu(access_token, app_token, table_id,
|
| 84 |
+
order_field_name, order_id_str_to_check):
|
| 85 |
+
if not access_token: return None, "飞书 Access Token 无效,无法检查记录。"
|
| 86 |
+
search_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/search"
|
| 87 |
+
headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json; charset=utf-8'}
|
| 88 |
+
filter_payload = {"filter": {"conjunction": "and", "conditions": [{"field_name": order_field_name, "operator": "is", "value": [ order_id_str_to_check ]}]}, "page_size": 1}
|
| 89 |
+
msg = f"正在 Table 1 ({table_id}) 中检查订单号 '{order_id_str_to_check}'..."
|
| 90 |
+
try:
|
| 91 |
+
response = requests.post(search_url, headers=headers, json=filter_payload, timeout=15); response.raise_for_status(); data = response.json()
|
| 92 |
+
if data.get("code") == 0:
|
| 93 |
+
items = data.get("data", {}).get("items", [])
|
| 94 |
+
exists = bool(items)
|
| 95 |
+
msg += f" 检查结果: {'已存在' if exists else '不存在'}。"
|
| 96 |
+
return exists, msg # 返回布尔值和消息
|
| 97 |
+
else:
|
| 98 |
+
msg += f" 检查失败: Code={data.get('code')}, Msg={data.get('msg')}"
|
| 99 |
+
return None, msg # 返回 None 表示检查出错
|
| 100 |
+
except Exception as e:
|
| 101 |
+
msg += f" 检查时发生错误: {e}"
|
| 102 |
+
return None, msg
|
| 103 |
+
|
| 104 |
+
def add_bitable_record(access_token, app_token, table_id,
|
| 105 |
+
order_field_name, order_id_str,
|
| 106 |
+
status_field_name, exchange_status):
|
| 107 |
+
if not access_token: return None, "飞书 Access Token 无效,无法写入数据。"
|
| 108 |
+
write_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records"
|
| 109 |
+
headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json; charset=utf-8'}
|
| 110 |
+
payload = {"fields": {order_field_name: order_id_str, status_field_name: exchange_status}}
|
| 111 |
+
msg = f"准备写入 Table 1 ({table_id})..."
|
| 112 |
+
try:
|
| 113 |
+
response = requests.post(write_url, headers=headers, json=payload, timeout=15); response.raise_for_status(); data = response.json()
|
| 114 |
+
if data.get("code") == 0:
|
| 115 |
+
record_info = data.get('data', {}).get('record')
|
| 116 |
+
record_id = record_info.get('record_id') if record_info else None
|
| 117 |
+
msg += f" Table 1 写入成功!Record ID: {record_id}"
|
| 118 |
+
return record_id, msg # 返回 record_id 和消息
|
| 119 |
+
else:
|
| 120 |
+
msg += f" Table 1 写入失败: Code={data.get('code')}, Msg={data.get('msg')}"
|
| 121 |
+
return None, msg
|
| 122 |
+
except Exception as e:
|
| 123 |
+
msg += f" Table 1 写入时发生错误: {e}"
|
| 124 |
+
return None, msg
|
| 125 |
+
|
| 126 |
+
# --- 修改:查找 Table 2 记录,并只提取激活码文本 ---
|
| 127 |
+
def find_first_unsent_record_and_code_text(access_token, app_token, table_id,
|
| 128 |
+
status_field, unsent_status_value,
|
| 129 |
+
activation_code_field, sort_field):
|
| 130 |
+
"""
|
| 131 |
+
在 Table 2 查找第一个状态为 '未发送' 的记录,返回其 record_id 和激活码文本。
|
| 132 |
+
"""
|
| 133 |
+
if not access_token: print("内部错误: 飞书 Token 无效"); return None, None
|
| 134 |
+
search_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/search"
|
| 135 |
+
headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json; charset=utf-8'}
|
| 136 |
+
payload = {
|
| 137 |
+
"filter": {"conjunction": "and", "conditions": [{"field_name": status_field, "operator": "is", "value": [unsent_status_value]}]},
|
| 138 |
+
"sort": [{"field_name": sort_field, "order": "asc"}],
|
| 139 |
+
"page_size": 1,
|
| 140 |
+
# 【修改这里】: 直接传递 Python 列表,不要 json.dumps()
|
| 141 |
+
"field_names": [activation_code_field]
|
| 142 |
+
}
|
| 143 |
+
print(f"飞书API: 查找 Table 2 '{status_field}' 为 '{unsent_status_value}' 的记录...") # 后台日志
|
| 144 |
+
# print("构造的查找 Payload:", json.dumps(payload, indent=2, ensure_ascii=False)) # 调试时可以打印看看
|
| 145 |
+
try:
|
| 146 |
+
response = requests.post(search_url, headers=headers, json=payload, timeout=15); response.raise_for_status(); data = response.json()
|
| 147 |
+
if data.get("code") == 0:
|
| 148 |
+
items = data.get("data", {}).get("items", [])
|
| 149 |
+
if items:
|
| 150 |
+
# ... (后续解析激活码的逻辑不变) ...
|
| 151 |
+
record = items[0]; record_id = record.get('record_id')
|
| 152 |
+
activation_code_raw = record.get('fields', {}).get(activation_code_field)
|
| 153 |
+
activation_code_text = None
|
| 154 |
+
if isinstance(activation_code_raw, list) and len(activation_code_raw) > 0 and isinstance(activation_code_raw[0], dict) and 'text' in activation_code_raw[0]:
|
| 155 |
+
activation_code_text = activation_code_raw[0]['text']
|
| 156 |
+
elif isinstance(activation_code_raw, str):
|
| 157 |
+
activation_code_text = activation_code_raw
|
| 158 |
+
elif activation_code_raw is not None:
|
| 159 |
+
try: activation_code_text = str(activation_code_raw); print(f"警告: 激活码字段值类型为 {type(activation_code_raw)}, 已尝试转为字符串。")
|
| 160 |
+
except: print(f"警告: 无法将激活码字段值 {activation_code_raw} 转为字符串。")
|
| 161 |
+
else: print(f"警告: 激活码字段值为空或格式未知: {activation_code_raw}")
|
| 162 |
+
|
| 163 |
+
if record_id and activation_code_text is not None:
|
| 164 |
+
print(f"飞书API: 找到记录 ID: {record_id}, 激活码文本: {activation_code_text}"); return record_id, activation_code_text
|
| 165 |
+
elif record_id: print(f"飞书API: 找到记录 ID: {record_id}, 但未能提取激活码文本。"); return record_id, None
|
| 166 |
+
else: return None, None
|
| 167 |
+
else:
|
| 168 |
+
print(f"飞书API: 未找到 '{status_field}' 为 '{unsent_status_value}' 的记录。"); return None, None
|
| 169 |
+
else:
|
| 170 |
+
print(f"飞书API: 查找 Table 2 记录失败: Code={data.get('code')}, Msg={data.get('msg')}"); return None, None
|
| 171 |
+
except Exception as e:
|
| 172 |
+
print(f"飞书API: 查找 Table 2 记录时发生错误: {e}"); return None, None
|
| 173 |
+
|
| 174 |
+
def update_table2_record_to_sent(access_token, app_token, table_id, record_id,
|
| 175 |
+
status_field, sent_status_content,
|
| 176 |
+
update_time_field):
|
| 177 |
+
if not access_token or not record_id: return False, "飞书 Token 或 Table 2 Record ID 无效,无法更新。"
|
| 178 |
+
update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
|
| 179 |
+
headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json; charset=utf-8'}
|
| 180 |
+
now = datetime.datetime.now(); formatted_time_str = now.strftime("%Y/%m/%d %H:%M")
|
| 181 |
+
payload_fields = {status_field: sent_status_content, update_time_field: formatted_time_str}
|
| 182 |
+
payload = {"records": [{"record_id": record_id, "fields": payload_fields}]}
|
| 183 |
+
msg = f"准备更新 Table 2 ({table_id}, ID: {record_id}) 为 '{sent_status_content}'..."
|
| 184 |
+
try:
|
| 185 |
+
response = requests.post(update_url, headers=headers, json=payload, timeout=15); response.raise_for_status(); data = response.json()
|
| 186 |
+
if data.get("code") == 0:
|
| 187 |
+
failed_records = data.get("data", {}).get("failed_records", [])
|
| 188 |
+
if not failed_records: msg += " Table 2 更新成功!"; return True, msg
|
| 189 |
+
else: msg += f" Table 2 更新失败: {failed_records}"; return False, msg
|
| 190 |
+
else: msg += f" Table 2 更新失败: Code={data.get('code')}, Msg={data.get('msg')}"; return False, msg
|
| 191 |
+
except Exception as e: msg += f" Table 2 更新时发生错误: {e}"; return False, msg
|
| 192 |
+
|
| 193 |
+
def update_table1_record_to_exchanged(access_token, app_token, table_id, record_id,
|
| 194 |
+
status_field, exchanged_status_content):
|
| 195 |
+
if not access_token or not record_id: return False, "飞书 Token 或 Table 1 Record ID 无效,无法更新。"
|
| 196 |
+
update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update"
|
| 197 |
+
headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json; charset=utf-8'}
|
| 198 |
+
payload = {"records": [{"record_id": record_id, "fields": {status_field: exchanged_status_content}}]}
|
| 199 |
+
msg = f"准备更新 Table 1 ({table_id}, ID: {record_id}) 为 '{exchanged_status_content}'..."
|
| 200 |
+
try:
|
| 201 |
+
response = requests.post(update_url, headers=headers, json=payload, timeout=15); response.raise_for_status(); data = response.json()
|
| 202 |
+
if data.get("code") == 0:
|
| 203 |
+
failed_records = data.get("data", {}).get("failed_records", [])
|
| 204 |
+
if not failed_records: msg += " Table 1 更新成功!"; return True, msg
|
| 205 |
+
else: msg += f" Table 1 更新失败: {failed_records}"; return False, msg
|
| 206 |
+
else: msg += f" Table 1 更新失败: Code={data.get('code')}, Msg={data.get('msg')}"; return False, msg
|
| 207 |
+
except Exception as e: msg += f" Table 1 更新时发生错误: {e}"; return False, msg
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
# --- Gradio 主处理函数 (简化版) ---
|
| 211 |
+
def process_order_simple(douyin_order_id_str):
|
| 212 |
+
final_output = "处理中..."
|
| 213 |
+
# --- 1. 获取抖音 Token ---
|
| 214 |
+
douyin_token, msg = get_douyin_access_token(DOUYIN_CLIENT_KEY, DOUYIN_CLIENT_SECRET)
|
| 215 |
+
if not douyin_token: return f"错误(抖音Token): {msg}"
|
| 216 |
+
# --- 2. 验证输入 ---
|
| 217 |
+
douyin_order_id_str = douyin_order_id_str.strip()
|
| 218 |
+
if not douyin_order_id_str: return "请输入抖音订单号"
|
| 219 |
+
# --- 3. 查询抖音订单 ---
|
| 220 |
+
order_details, msg = query_douyin_order(douyin_token, DOUYIN_ACCOUNT_ID, douyin_order_id_str)
|
| 221 |
+
if not order_details: return f"错误(查询订单): {msg}"
|
| 222 |
+
# --- 4. 解析抖音状态 ---
|
| 223 |
+
fulfillment_status_text, _, msg = parse_douyin_order_status(order_details)
|
| 224 |
+
if fulfillment_status_text is None: return f"错误(解析状态): {msg}"
|
| 225 |
+
# --- 5. 检查抖音状态 ---
|
| 226 |
+
if fulfillment_status_text != "已履约": return f"提示:订单状态为 '{fulfillment_status_text}',不予兑换。"
|
| 227 |
+
# --- 6. 获取飞书 Token ---
|
| 228 |
+
feishu_token, msg = get_feishu_tenant_access_token(FEISHU_APP_ID, FEISHU_APP_SECRET)
|
| 229 |
+
if not feishu_token: return f"错误(飞书Token): {msg}"
|
| 230 |
+
# --- 7. 检查 Table 1 ---
|
| 231 |
+
exists_in_table1, msg = check_order_exists_in_feishu(feishu_token, FEISHU_APP_TOKEN, FEISHU_TABLE1_ID, FEISHU_TABLE1_ORDER_FIELD, douyin_order_id_str)
|
| 232 |
+
if exists_in_table1 is None: return f"错误(检查Table1): {msg}"
|
| 233 |
+
if exists_in_table1 is True: return f"提示:订单 '{douyin_order_id_str}' 已兑换过。"
|
| 234 |
+
# --- 8. 写入 Table 1 ---
|
| 235 |
+
table1_record_id, msg = add_bitable_record(feishu_token, FEISHU_APP_TOKEN, FEISHU_TABLE1_ID, FEISHU_TABLE1_ORDER_FIELD, douyin_order_id_str, FEISHU_TABLE1_STATUS_FIELD, fulfillment_status_text)
|
| 236 |
+
if not table1_record_id: return f"错误(写入Table1): {msg}"
|
| 237 |
+
print(f"后台日志: Table 1 写入成功, Record ID: {table1_record_id}")
|
| 238 |
+
# --- 9. 查找 Table 2 ---
|
| 239 |
+
table2_record_id, activation_code_text = find_first_unsent_record_and_code_text(feishu_token, FEISHU_APP_TOKEN, FEISHU_TABLE2_ID, FEISHU_TABLE2_STATUS_FIELD, TABLE2_UNSENT_STATUS_CONTENT, FEISHU_TABLE2_ACTIVATION_CODE_FIELD, FEISHU_TABLE2_SORT_FIELD)
|
| 240 |
+
if table2_record_id and activation_code_text:
|
| 241 |
+
final_output = f"{activation_code_text}" # 直接显示激活码
|
| 242 |
+
# --- 10. "发送" 激活码 (模拟) ---
|
| 243 |
+
print(f"后台日志: '发送' 激活码: {activation_code_text}")
|
| 244 |
+
sent_success = True # 假设发送成功
|
| 245 |
+
if sent_success:
|
| 246 |
+
# --- 11. 更新 Table 2 ---
|
| 247 |
+
update_t2_success, msg_t2 = update_table2_record_to_sent(feishu_token, FEISHU_APP_TOKEN, FEISHU_TABLE2_ID, table2_record_id, FEISHU_TABLE2_STATUS_FIELD, TABLE2_SENT_STATUS_CONTENT, FEISHU_TABLE2_UPDATE_TIME_FIELD)
|
| 248 |
+
if not update_t2_success: print(f"后台警告: {msg_t2}") # 只在后台打印警告
|
| 249 |
+
if update_t2_success: # 只有 T2 更新成功才更新 T1
|
| 250 |
+
# --- 12. 更新 Table 1 ---
|
| 251 |
+
update_t1_success, msg_t1 = update_table1_record_to_exchanged(feishu_token, FEISHU_APP_TOKEN, FEISHU_TABLE1_ID, table1_record_id, FEISHU_TABLE1_STATUS_FIELD, TABLE1_EXCHANGED_STATUS_CONTENT)
|
| 252 |
+
if not update_t1_success: print(f"后台警告: {msg_t1}") # 只在后台打印警告
|
| 253 |
+
else: final_output = "错误:激活码 '发送' 失败 (模拟)"
|
| 254 |
+
elif table2_record_id and not activation_code_text: final_output = f"错误:未能获取激活码文本,请检查 Table 2 数据。"
|
| 255 |
+
else: final_output = "提示:无可用激活码,请联系管理员。"
|
| 256 |
+
return final_output # 返回最终结果
|
| 257 |
+
|
| 258 |
+
# --- 创建 Gradio Interface (最终简化版) ---
|
| 259 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 260 |
+
gr.Markdown("# 竞潮玩游戏激活码兑换小助手")
|
| 261 |
+
gr.Markdown("请输入抖音订单号以兑换激活码。仅限状态为“已履约”且未兑换过的订单。")
|
| 262 |
+
with gr.Row():
|
| 263 |
+
order_id_input = gr.Textbox(label="输入抖音订单号", placeholder="在此填写订单号...")
|
| 264 |
+
process_button = gr.Button("兑换激活码")
|
| 265 |
+
with gr.Row():
|
| 266 |
+
activation_code_output = gr.Textbox(label="兑换结果 / 激活码", interactive=False)
|
| 267 |
+
process_button.click(
|
| 268 |
+
fn=process_order_simple,
|
| 269 |
+
inputs=order_id_input,
|
| 270 |
+
outputs=activation_code_output
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
# --- 启动 Gradio 应用 ---
|
| 274 |
+
if __name__ == "__main__":
|
| 275 |
+
demo.launch()
|