1000 lines
53 KiB
Python
1000 lines
53 KiB
Python
import tkinter as tk
|
||
from tkinter import ttk, scrolledtext, filedialog, messagebox
|
||
import hashlib
|
||
import base64
|
||
import json
|
||
import urllib.parse
|
||
import requests
|
||
import re
|
||
import time
|
||
from datetime import datetime
|
||
import openpyxl
|
||
from openpyxl.styles import Font, PatternFill
|
||
from openpyxl.worksheet.datavalidation import DataValidation
|
||
import os
|
||
from pathlib import Path
|
||
|
||
class TrackPushApp:
|
||
def __init__(self, root):
|
||
self.root = root
|
||
self.root.title("轨迹推送")
|
||
self.root.geometry("1100x1100")
|
||
|
||
# 主容器
|
||
main_frame = tk.Frame(root, padx=20, pady=20)
|
||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
row = 0
|
||
|
||
# SecretKey
|
||
tk.Label(main_frame, text="SecretKey:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
self.secret_key_entry = tk.Entry(main_frame, width=100)
|
||
self.secret_key_entry.insert(0, "y299lC8N2d8ebm4001MHFq507kPDT458")
|
||
self.secret_key_entry.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# url(支持下拉选择)
|
||
tk.Label(main_frame, text="url:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
url_values = [
|
||
"https://link.cainiao.com/gateway/link.do",
|
||
"https://prelink.cainiao.com/gateway/link.do",
|
||
]
|
||
self.url_var = tk.StringVar(value=url_values[1])
|
||
self.url_entry = ttk.Combobox(main_frame, textvariable=self.url_var, values=url_values, width=96, state='readonly')
|
||
self.url_entry.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# 分隔线
|
||
tk.Label(main_frame, text="公共参数", font=("Arial", 10, "bold")).grid(row=row, column=0, columnspan=2, padx=5, pady=(15, 5), sticky='w')
|
||
row += 1
|
||
|
||
# logistic_provider_id
|
||
tk.Label(main_frame, text="资源code:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
logistic_provider_id_values = [
|
||
"带电渠道-干线[TRUNK_31726544]",
|
||
"带电渠道-关务[GATE_31726757]",
|
||
"带电渠道-配送[DISTRIBUTOR_31724076]",
|
||
"普货渠道-干线[TRUNK_31724331]",
|
||
"普货渠道-关务[GATE_31724116]",
|
||
"普货渠道-配送[DISTRIBUTOR_31724341]",
|
||
]
|
||
self.logistic_provider_id_var = tk.StringVar(value=logistic_provider_id_values[0])
|
||
logistic_provider_id_combo = ttk.Combobox(main_frame, textvariable=self.logistic_provider_id_var, width=94, state='readonly')
|
||
logistic_provider_id_combo['values'] = logistic_provider_id_values
|
||
logistic_provider_id_combo.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# msg_type
|
||
tk.Label(main_frame, text="接口名称:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
# msg_type映射:显示中文,传值用code
|
||
self.msg_type_map = {
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOIN_CALLBACK": "收货",
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK": "交航",
|
||
"CAINIAO_GLOBAL_LINEHAUL_DEPARTURE_CALLBACK": "起飞",
|
||
"CAINIAO_GLOBAL_LINEHAUL_ARRIVAL_CALLBACK": "落地",
|
||
"CAINIAO_GLOBAL_CCIM_CALLBACK": "清关完成",
|
||
"CAINIAO_GLOBAL_CCIM_ABNORMAL_CALLBACK": "清关异常",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSACCEPT_CALLBACK": "配送接收",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK": "签收",
|
||
"CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_CALLBACK": "干线退回发起",
|
||
"CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURNPROCESS_CALLBACK": "干线退回处理",
|
||
"CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_HANDOVEROUT_CALLBACK": "干线退回交接",
|
||
"CAINIAO_GLOBAL_CUSTOMS_UNREACHABLERETURN_CALLBACK": "清关失败"
|
||
}
|
||
# msg_type到opCode的映射(根据中文描述)
|
||
self.msg_type_to_opcode = {
|
||
"包裹信息不全": "5247",
|
||
"申报信息错误/缺失": "5562",
|
||
"消费者拒收": "5792",
|
||
"收货地址未知或错误": "5525"
|
||
}
|
||
# 添加需要自动填充opCode的msg_type选项(包含完整列表)
|
||
msg_type_list = [
|
||
("CAINIAO_GLOBAL_LINEHAUL_HOIN_CALLBACK", "收货"),
|
||
("CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK", "交航"),
|
||
("CAINIAO_GLOBAL_LINEHAUL_DEPARTURE_CALLBACK", "起飞"),
|
||
("CAINIAO_GLOBAL_LINEHAUL_ARRIVAL_CALLBACK", "落地"),
|
||
("CAINIAO_GLOBAL_CCIM_CALLBACK", "清关完成"),
|
||
("CAINIAO_GLOBAL_CCIM_ABNORMAL_CALLBACK", "清关异常"),
|
||
("CAINIAO_GLOBAL_LASTMILE_GTMSACCEPT_CALLBACK", "配送接收"),
|
||
("CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK", "签收"),
|
||
("CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_CALLBACK", "干线退回发起"),
|
||
("CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURNPROCESS_CALLBACK", "干线退回处理"),
|
||
("CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_HANDOVEROUT_CALLBACK", "干线退回交接"),
|
||
("CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK", "包裹信息不全"),
|
||
("CAINIAO_GLOBAL_CCIM_CALLBACK", "申报信息错误/缺失"),
|
||
("CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK", "消费者拒收"),
|
||
("CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK", "收货地址未知或错误"),
|
||
("CAINIAO_GLOBAL_CUSTOMS_UNREACHABLERETURN_CALLBACK", "清关失败")
|
||
]
|
||
msg_type_values = [f"{code} - {desc}" for code, desc in msg_type_list]
|
||
self.msg_type_var = tk.StringVar(value=msg_type_values[1]) # 默认选择"交航"
|
||
msg_type_combo = ttk.Combobox(main_frame, textvariable=self.msg_type_var, width=94, state='readonly')
|
||
msg_type_combo['values'] = msg_type_values
|
||
msg_type_combo.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
msg_type_combo.bind('<<ComboboxSelected>>', self.on_msg_type_changed)
|
||
row += 1
|
||
|
||
# 分隔线
|
||
tk.Label(main_frame, text="logistics_interface 参数", font=("Arial", 10, "bold")).grid(row=row, column=0, columnspan=2, padx=5, pady=(15, 5), sticky='w')
|
||
row += 1
|
||
|
||
# logisticsOrderCode
|
||
tk.Label(main_frame, text="AP单号:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
self.logistics_order_code_entry = tk.Entry(main_frame, width=100)
|
||
self.logistics_order_code_entry.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# opCode
|
||
tk.Label(main_frame, text="作业结果:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
op_code_frame = tk.Frame(main_frame)
|
||
op_code_frame.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
self.op_code_entry = tk.Entry(op_code_frame, width=80)
|
||
self.op_code_entry.pack(side=tk.LEFT)
|
||
tk.Label(op_code_frame, text="(0=成功, 1=失败, 其他=异常code)", font=("Arial", 8), fg="gray").pack(side=tk.LEFT, padx=(5, 0))
|
||
row += 1
|
||
|
||
# opLocation
|
||
tk.Label(main_frame, text="操作地:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
self.op_location_entry = tk.Entry(main_frame, width=100)
|
||
self.op_location_entry.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# opRemark
|
||
tk.Label(main_frame, text="备注:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
self.op_remark_entry = tk.Entry(main_frame, width=100)
|
||
self.op_remark_entry.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# opTime
|
||
tk.Label(main_frame, text="操作时间:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
op_time_frame = tk.Frame(main_frame)
|
||
op_time_frame.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
self.op_time_entry = tk.Entry(op_time_frame, width=80)
|
||
self.op_time_entry.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||
self.op_time_entry.pack(side=tk.LEFT)
|
||
update_time_btn = tk.Button(op_time_frame, text="更新为当前时间", command=self.update_op_time)
|
||
update_time_btn.pack(side=tk.LEFT, padx=(5, 0))
|
||
row += 1
|
||
|
||
# relabelTrackingNumber
|
||
tk.Label(main_frame, text="转单号:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
self.relabel_tracking_number_entry = tk.Entry(main_frame, width=100)
|
||
self.relabel_tracking_number_entry.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# timeZone
|
||
tk.Label(main_frame, text="时区:", width=15, anchor='w').grid(row=row, column=0, padx=5, pady=5, sticky='w')
|
||
self.time_zone_entry = tk.Entry(main_frame, width=100)
|
||
self.time_zone_entry.insert(0, "8")
|
||
self.time_zone_entry.grid(row=row, column=1, padx=5, pady=5, sticky='w')
|
||
row += 1
|
||
|
||
# transportType (固定为5,不显示输入框)
|
||
# self.transport_type_entry 在代码中固定使用 "5"
|
||
row += 1
|
||
|
||
# parcel字段(默认隐藏,只有选择"交航"时显示)
|
||
self.parcel_frame = tk.Frame(main_frame)
|
||
self.parcel_frame.grid(row=row, column=0, columnspan=2, padx=5, pady=5, sticky='w')
|
||
self.parcel_frame.grid_remove() # 默认隐藏
|
||
|
||
# weight
|
||
tk.Label(self.parcel_frame, text="重量(weight):", width=15, anchor='w').grid(row=0, column=0, padx=5, pady=5, sticky='w')
|
||
self.parcel_weight_entry = tk.Entry(self.parcel_frame, width=30)
|
||
self.parcel_weight_entry.insert(0, "1450")
|
||
self.parcel_weight_entry.grid(row=0, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# weightUnit
|
||
tk.Label(self.parcel_frame, text="重量单位(weightUnit):", width=20, anchor='w').grid(row=0, column=2, padx=5, pady=5, sticky='w')
|
||
weight_unit_values = ["g", "kg"]
|
||
self.parcel_weight_unit_var = tk.StringVar(value="g")
|
||
weight_unit_combo = ttk.Combobox(self.parcel_frame, textvariable=self.parcel_weight_unit_var, values=weight_unit_values, width=10, state='readonly')
|
||
weight_unit_combo.grid(row=0, column=3, padx=5, pady=5, sticky='w')
|
||
|
||
# length
|
||
tk.Label(self.parcel_frame, text="长度(length):", width=15, anchor='w').grid(row=1, column=0, padx=5, pady=5, sticky='w')
|
||
self.parcel_length_entry = tk.Entry(self.parcel_frame, width=30)
|
||
self.parcel_length_entry.insert(0, "100")
|
||
self.parcel_length_entry.grid(row=1, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# width
|
||
tk.Label(self.parcel_frame, text="宽度(width):", width=15, anchor='w').grid(row=1, column=2, padx=5, pady=5, sticky='w')
|
||
self.parcel_width_entry = tk.Entry(self.parcel_frame, width=30)
|
||
self.parcel_width_entry.insert(0, "100")
|
||
self.parcel_width_entry.grid(row=1, column=3, padx=5, pady=5, sticky='w')
|
||
|
||
# height
|
||
tk.Label(self.parcel_frame, text="高度(height):", width=15, anchor='w').grid(row=2, column=0, padx=5, pady=5, sticky='w')
|
||
self.parcel_height_entry = tk.Entry(self.parcel_frame, width=30)
|
||
self.parcel_height_entry.insert(0, "100")
|
||
self.parcel_height_entry.grid(row=2, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# dimensionUnit
|
||
tk.Label(self.parcel_frame, text="尺寸单位(dimensionUnit):", width=20, anchor='w').grid(row=2, column=2, padx=5, pady=5, sticky='w')
|
||
dimension_unit_values = ["cm", "m"]
|
||
self.parcel_dimension_unit_var = tk.StringVar(value="cm")
|
||
dimension_unit_combo = ttk.Combobox(self.parcel_frame, textvariable=self.parcel_dimension_unit_var, values=dimension_unit_values, width=10, state='readonly')
|
||
dimension_unit_combo.grid(row=2, column=3, padx=5, pady=5, sticky='w')
|
||
|
||
row += 1
|
||
|
||
# undeliverableOption和returnTo(默认隐藏,只有选择"干线退回发起"时显示)
|
||
self.undeliverable_return_frame = tk.Frame(main_frame)
|
||
self.undeliverable_return_frame.grid(row=row, column=0, columnspan=2, padx=5, pady=5, sticky='w')
|
||
self.undeliverable_return_frame.grid_remove() # 默认隐藏
|
||
|
||
# undeliverableOption
|
||
tk.Label(self.undeliverable_return_frame, text="处理方式:", width=15, anchor='w').grid(row=0, column=0, padx=5, pady=5, sticky='w')
|
||
undeliverable_option_values = [
|
||
"不选",
|
||
"退回 Return",
|
||
"销毁 Discard"
|
||
]
|
||
self.undeliverable_option_var = tk.StringVar(value=undeliverable_option_values[0])
|
||
undeliverable_option_combo = ttk.Combobox(self.undeliverable_return_frame, textvariable=self.undeliverable_option_var, width=94, state='readonly')
|
||
undeliverable_option_combo['values'] = undeliverable_option_values
|
||
undeliverable_option_combo.grid(row=0, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# returnTo
|
||
tk.Label(self.undeliverable_return_frame, text="退回方式:", width=15, anchor='w').grid(row=1, column=0, padx=5, pady=5, sticky='w')
|
||
return_to_values = [
|
||
"不选",
|
||
"首公里 FirstMile",
|
||
"快递 Express"
|
||
]
|
||
self.return_to_var = tk.StringVar(value=return_to_values[0])
|
||
return_to_combo = ttk.Combobox(self.undeliverable_return_frame, textvariable=self.return_to_var, width=94, state='readonly')
|
||
return_to_combo['values'] = return_to_values
|
||
return_to_combo.grid(row=1, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
row += 1
|
||
|
||
# 干线退回处理专用字段(默认隐藏)
|
||
self.return_process_frame = tk.Frame(main_frame)
|
||
self.return_process_frame.grid(row=row, column=0, columnspan=2, padx=5, pady=5, sticky='w')
|
||
self.return_process_frame.grid_remove() # 默认隐藏
|
||
|
||
# processType
|
||
tk.Label(self.return_process_frame, text="退回方式(processType):", width=20, anchor='w').grid(row=0, column=0, padx=5, pady=5, sticky='w')
|
||
process_type_values = [
|
||
"销毁 00",
|
||
"退回首公里 01",
|
||
"寄送 02",
|
||
"菜鸟快递寄送 03",
|
||
"退集运仓(非快递)04"
|
||
]
|
||
self.process_type_var = tk.StringVar(value="菜鸟快递寄送 03")
|
||
process_type_combo = ttk.Combobox(self.return_process_frame, textvariable=self.process_type_var, width=70, state='readonly')
|
||
process_type_combo['values'] = process_type_values
|
||
process_type_combo.grid(row=0, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# returnPickupAdressProvince
|
||
tk.Label(self.return_process_frame, text="退回省份:", width=20, anchor='w').grid(row=1, column=0, padx=5, pady=5, sticky='w')
|
||
self.return_pickup_address_province_entry = tk.Entry(self.return_process_frame, width=70)
|
||
self.return_pickup_address_province_entry.insert(0, "广东")
|
||
self.return_pickup_address_province_entry.grid(row=1, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# returnPickupAdressCity
|
||
tk.Label(self.return_process_frame, text="退回城市:", width=20, anchor='w').grid(row=2, column=0, padx=5, pady=5, sticky='w')
|
||
self.return_pickup_address_city_entry = tk.Entry(self.return_process_frame, width=70)
|
||
self.return_pickup_address_city_entry.insert(0, "深圳")
|
||
self.return_pickup_address_city_entry.grid(row=2, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# returnPickupAdressDistrict
|
||
tk.Label(self.return_process_frame, text="退回区县:", width=20, anchor='w').grid(row=3, column=0, padx=5, pady=5, sticky='w')
|
||
self.return_pickup_address_district_entry = tk.Entry(self.return_process_frame, width=70)
|
||
self.return_pickup_address_district_entry.insert(0, "宝安区")
|
||
self.return_pickup_address_district_entry.grid(row=3, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# returnPickupAdressDetail
|
||
tk.Label(self.return_process_frame, text="退回详细地址:", width=20, anchor='w').grid(row=4, column=0, padx=5, pady=5, sticky='w')
|
||
self.return_pickup_address_detail_entry = tk.Entry(self.return_process_frame, width=70)
|
||
self.return_pickup_address_detail_entry.insert(0, "怀德翠岗工业区五区36栋二楼")
|
||
self.return_pickup_address_detail_entry.grid(row=4, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# returnPickupPhone
|
||
tk.Label(self.return_process_frame, text="退回电话:", width=20, anchor='w').grid(row=5, column=0, padx=5, pady=5, sticky='w')
|
||
self.return_pickup_phone_entry = tk.Entry(self.return_process_frame, width=70)
|
||
self.return_pickup_phone_entry.insert(0, "19513356304")
|
||
self.return_pickup_phone_entry.grid(row=5, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
# returnPickupSender
|
||
tk.Label(self.return_process_frame, text="退回发件人:", width=20, anchor='w').grid(row=6, column=0, padx=5, pady=5, sticky='w')
|
||
self.return_pickup_sender_entry = tk.Entry(self.return_process_frame, width=70)
|
||
self.return_pickup_sender_entry.insert(0, "蒋彬")
|
||
self.return_pickup_sender_entry.grid(row=6, column=1, padx=5, pady=5, sticky='w')
|
||
|
||
row += 1
|
||
|
||
# 按钮区域
|
||
button_frame = tk.Frame(main_frame)
|
||
button_frame.grid(row=row, column=0, columnspan=2, padx=5, pady=20)
|
||
|
||
push_button = tk.Button(button_frame, text="推送", bg="#4CAF50", fg="white",
|
||
width=12, height=2, command=self.on_push_click)
|
||
push_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
export_button = tk.Button(button_frame, text="导出模板", bg="#2196F3", fg="white",
|
||
width=12, height=2, command=self.export_template)
|
||
export_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
import_button = tk.Button(button_frame, text="导入轨迹", bg="#FF9800", fg="white",
|
||
width=12, height=2, command=self.import_and_push)
|
||
import_button.pack(side=tk.LEFT, padx=5)
|
||
|
||
row += 1
|
||
|
||
# 推送日志
|
||
log_label = tk.Label(main_frame, text="推送日志:", anchor='w', font=("Arial", 10, "bold"))
|
||
log_label.grid(row=row, column=0, columnspan=2, padx=5, pady=(10, 5), sticky='w')
|
||
row += 1
|
||
|
||
self.log_text = scrolledtext.ScrolledText(main_frame, height=25, width=140, wrap=tk.WORD)
|
||
self.log_text.grid(row=row, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
|
||
|
||
# 配置网格权重
|
||
main_frame.grid_columnconfigure(1, weight=1)
|
||
|
||
def do_sign(self, content, charset, keys):
|
||
"""签名算法:MD5(content + keys) 然后转base64"""
|
||
try:
|
||
sign_content = content + keys
|
||
md5_hash = hashlib.md5(sign_content.encode(charset)).digest()
|
||
sign = base64.b64encode(md5_hash).decode(charset)
|
||
return sign
|
||
except Exception as e:
|
||
raise Exception(f"签名失败: {str(e)}")
|
||
|
||
def update_op_time(self):
|
||
"""更新opTime为当前时间"""
|
||
self.op_time_entry.delete(0, tk.END)
|
||
self.op_time_entry.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||
|
||
def on_msg_type_changed(self, event=None):
|
||
"""msg_type选择改变时,自动填充opCode并显示/隐藏相关字段"""
|
||
selected = self.msg_type_var.get()
|
||
if " - " in selected:
|
||
code = selected.split(" - ")[0]
|
||
desc = selected.split(" - ")[1]
|
||
|
||
# 如果选择交航,显示parcel字段
|
||
if code == "CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK":
|
||
self.parcel_frame.grid()
|
||
else:
|
||
self.parcel_frame.grid_remove()
|
||
|
||
# 如果选择干线退回发起,显示处理方式和退回方式
|
||
if code == "CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_CALLBACK":
|
||
self.undeliverable_return_frame.grid()
|
||
else:
|
||
self.undeliverable_return_frame.grid_remove()
|
||
|
||
# 如果选择干线退回处理,显示相关字段
|
||
if code == "CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURNPROCESS_CALLBACK":
|
||
self.return_process_frame.grid()
|
||
else:
|
||
self.return_process_frame.grid_remove()
|
||
|
||
# 如果选择特定的msg_type,opCode默认为0
|
||
default_opcode_0_types = [
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOIN_CALLBACK",
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK",
|
||
"CAINIAO_GLOBAL_LINEHAUL_DEPARTURE_CALLBACK",
|
||
"CAINIAO_GLOBAL_LINEHAUL_ARRIVAL_CALLBACK",
|
||
"CAINIAO_GLOBAL_CCIM_CALLBACK",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSACCEPT_CALLBACK"
|
||
]
|
||
|
||
if code in default_opcode_0_types:
|
||
self.op_code_entry.delete(0, tk.END)
|
||
self.op_code_entry.insert(0, "0")
|
||
elif desc in self.msg_type_to_opcode:
|
||
# 如果有特殊映射,使用特殊映射的值
|
||
self.op_code_entry.delete(0, tk.END)
|
||
self.op_code_entry.insert(0, self.msg_type_to_opcode[desc])
|
||
|
||
def log(self, message):
|
||
"""添加日志"""
|
||
self.log_text.insert(tk.END, f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}\n")
|
||
self.log_text.see(tk.END)
|
||
self.root.update()
|
||
|
||
def export_template(self):
|
||
"""导出Excel模板"""
|
||
try:
|
||
# 获取桌面路径
|
||
desktop_path = Path.home() / "Desktop"
|
||
if not desktop_path.exists():
|
||
# 如果Desktop不存在,尝试使用中文桌面
|
||
desktop_path = Path.home() / "桌面"
|
||
|
||
# 生成文件名:菜鸟轨迹模板+时间戳
|
||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
default_filename = f"菜鸟轨迹模板_{timestamp}.xlsx"
|
||
default_path = desktop_path / default_filename
|
||
|
||
file_path = filedialog.asksaveasfilename(
|
||
defaultextension=".xlsx",
|
||
filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
|
||
title="保存模板文件",
|
||
initialdir=str(desktop_path),
|
||
initialfile=default_filename
|
||
)
|
||
if not file_path:
|
||
return
|
||
|
||
wb = openpyxl.Workbook()
|
||
ws = wb.active
|
||
ws.title = "轨迹推送模板"
|
||
|
||
# 设置表头样式
|
||
header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
|
||
header_font = Font(bold=True, color="FFFFFF")
|
||
|
||
# 公共参数表头(移除SecretKey和url)
|
||
headers = [
|
||
"资源code", "接口名称",
|
||
"AP单号", "作业结果", "操作地", "备注", "操作时间",
|
||
"转单号", "时区", "处理方式", "退回方式",
|
||
"重量(weight)", "重量单位(weightUnit)", "长度(length)", "宽度(width)", "高度(height)", "尺寸单位(dimensionUnit)"
|
||
]
|
||
|
||
# 写入表头
|
||
for col, header in enumerate(headers, 1):
|
||
cell = ws.cell(row=1, column=col, value=header)
|
||
cell.fill = header_fill
|
||
cell.font = header_font
|
||
|
||
# 资源code下拉选项
|
||
logistic_provider_id_options = [
|
||
"带电渠道-干线[TRUNK_31726544]",
|
||
"带电渠道-关务[GATE_31726757]",
|
||
"带电渠道-配送[DISTRIBUTOR_31724076]",
|
||
"普货渠道-干线[TRUNK_31724331]",
|
||
"普货渠道-关务[GATE_31724116]",
|
||
"普货渠道-配送[DISTRIBUTOR_31724341]"
|
||
]
|
||
|
||
# 接口名称下拉选项
|
||
msg_type_options = [
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOIN_CALLBACK - 收货",
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK - 交航",
|
||
"CAINIAO_GLOBAL_LINEHAUL_DEPARTURE_CALLBACK - 起飞",
|
||
"CAINIAO_GLOBAL_LINEHAUL_ARRIVAL_CALLBACK - 落地",
|
||
"CAINIAO_GLOBAL_CCIM_CALLBACK - 清关完成",
|
||
"CAINIAO_GLOBAL_CCIM_ABNORMAL_CALLBACK - 清关异常",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSACCEPT_CALLBACK - 配送接收",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK - 签收",
|
||
"CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_CALLBACK - 干线退回发起",
|
||
"CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURNPROCESS_CALLBACK - 干线退回处理",
|
||
"CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_HANDOVEROUT_CALLBACK - 干线退回交接",
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK - 包裹信息不全",
|
||
"CAINIAO_GLOBAL_CCIM_CALLBACK - 申报信息错误/缺失",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK - 消费者拒收",
|
||
"CAINIAO_GLOBAL_LASTMILE_GTMSSIGN_CALLBACK - 收货地址未知或错误",
|
||
"CAINIAO_GLOBAL_CUSTOMS_UNREACHABLERETURN_CALLBACK - 清关失败"
|
||
]
|
||
|
||
# 处理方式下拉选项
|
||
undeliverable_option_options = ["不选", "退回 Return", "销毁 Discard"]
|
||
|
||
# 退回方式下拉选项
|
||
return_to_options = ["不选", "首公里 FirstMile", "快递 Express"]
|
||
|
||
# 在“下拉选项”工作表中导出所有下拉值,方便在Excel中查看和下拉选择
|
||
options_ws = wb.create_sheet("下拉选项")
|
||
options_ws.cell(row=1, column=1, value="资源code")
|
||
options_ws.cell(row=1, column=2, value="接口名称")
|
||
|
||
# 写入资源code选项
|
||
for idx, value in enumerate(logistic_provider_id_options, start=2):
|
||
options_ws.cell(row=idx, column=1, value=value)
|
||
|
||
# 写入接口名称选项
|
||
for idx, value in enumerate(msg_type_options, start=2):
|
||
options_ws.cell(row=idx, column=2, value=value)
|
||
|
||
# 为资源code列添加数据验证(下拉列表),引用“下拉选项”工作表
|
||
dv1 = DataValidation(
|
||
type="list",
|
||
formula1=f"=下拉选项!$A$2:$A${len(logistic_provider_id_options) + 1}",
|
||
allow_blank=True,
|
||
)
|
||
dv1.error = '请从下拉列表中选择'
|
||
dv1.errorTitle = '输入错误'
|
||
ws.add_data_validation(dv1)
|
||
dv1.add("A2:A1000") # 资源code列(第1列)
|
||
|
||
# 为接口名称列添加数据验证(下拉列表)
|
||
dv2 = DataValidation(
|
||
type="list",
|
||
formula1=f"=下拉选项!$B$2:$B${len(msg_type_options) + 1}",
|
||
allow_blank=True,
|
||
)
|
||
dv2.error = '请从下拉列表中选择'
|
||
dv2.errorTitle = '输入错误'
|
||
ws.add_data_validation(dv2)
|
||
dv2.add("B2:B1000") # 接口名称列(第2列)
|
||
|
||
# 为处理方式列添加数据验证(仍使用内联列表)
|
||
dv3 = DataValidation(
|
||
type="list",
|
||
formula1=f'"{";".join(undeliverable_option_options)}"',
|
||
allow_blank=True,
|
||
)
|
||
dv3.error = '请从下拉列表中选择'
|
||
dv3.errorTitle = '输入错误'
|
||
ws.add_data_validation(dv3)
|
||
dv3.add("K2:K1000") # 处理方式列(第11列)
|
||
|
||
# 为退回方式列添加数据验证
|
||
dv4 = DataValidation(
|
||
type="list",
|
||
formula1=f'"{";".join(return_to_options)}"',
|
||
allow_blank=True,
|
||
)
|
||
dv4.error = '请从下拉列表中选择'
|
||
dv4.errorTitle = '输入错误'
|
||
ws.add_data_validation(dv4)
|
||
dv4.add("L2:L1000") # 退回方式列(第12列)
|
||
|
||
# 设置列宽
|
||
column_widths = [30, 50, 20, 15, 20, 20, 20, 20, 10, 20, 20, 15, 15, 15, 15, 15, 15]
|
||
for col, width in enumerate(column_widths, 1):
|
||
ws.column_dimensions[openpyxl.utils.get_column_letter(col)].width = width
|
||
|
||
# 添加示例数据(第二行)
|
||
example_data = [
|
||
"带电渠道-干线[TRUNK_31726544]", # 资源code
|
||
"CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK - 交航", # 接口名称
|
||
"AP00790279362887", # AP单号
|
||
"0", # 作业结果
|
||
"US", # 操作地
|
||
"交航", # 备注
|
||
datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 操作时间
|
||
"20201080021", # 转单号
|
||
"8", # 时区
|
||
"不选", # 处理方式
|
||
"不选", # 退回方式
|
||
"1450", # 重量(weight)
|
||
"g", # 重量单位(weightUnit)
|
||
"100", # 长度(length)
|
||
"100", # 宽度(width)
|
||
"100", # 高度(height)
|
||
"cm" # 尺寸单位(dimensionUnit)
|
||
]
|
||
|
||
for col, value in enumerate(example_data, 1):
|
||
ws.cell(row=2, column=col, value=value)
|
||
|
||
wb.save(file_path)
|
||
messagebox.showinfo("成功", f"模板已导出到:\n{file_path}")
|
||
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"导出模板失败:\n{str(e)}")
|
||
|
||
def import_and_push(self):
|
||
"""导入Excel并批量推送"""
|
||
try:
|
||
file_path = filedialog.askopenfilename(
|
||
filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")],
|
||
title="选择要导入的Excel文件"
|
||
)
|
||
if not file_path:
|
||
return
|
||
|
||
self.log_text.delete(1.0, tk.END)
|
||
self.log("开始导入文件...")
|
||
|
||
# 读取Excel
|
||
wb = openpyxl.load_workbook(file_path)
|
||
# 优先选择名称为“轨迹推送模板”的工作表,避免当前激活的是“下拉选项”导致表头不一致
|
||
try:
|
||
ws = wb["轨迹推送模板"]
|
||
except KeyError:
|
||
ws = wb.active
|
||
|
||
# 读取表头
|
||
headers = [cell.value for cell in ws[1]]
|
||
# 为兼容可能存在的空格/隐藏字符,预先定位各字段的列索引
|
||
header_index_map = {}
|
||
for idx, h in enumerate(headers):
|
||
name = str(h).strip() if h is not None else ""
|
||
if not name:
|
||
continue
|
||
header_index_map[name] = idx
|
||
# 关键字段的索引(如果找不到就为None)
|
||
ap_col_index = next((i for n, i in header_index_map.items() if "AP单号" in n), None)
|
||
resource_col_index = next((i for n, i in header_index_map.items() if "资源code" in n), None)
|
||
msg_type_col_index = next((i for n, i in header_index_map.items() if "接口名称" in n), None)
|
||
op_result_col_index = next((i for n, i in header_index_map.items() if "作业结果" in n), None)
|
||
op_loc_col_index = next((i for n, i in header_index_map.items() if "操作地" in n), None)
|
||
remark_col_index = next((i for n, i in header_index_map.items() if "备注" in n), None)
|
||
op_time_col_index = next((i for n, i in header_index_map.items() if "操作时间" in n), None)
|
||
relabel_col_index = next((i for n, i in header_index_map.items() if "转单号" in n), None)
|
||
tz_col_index = next((i for n, i in header_index_map.items() if "时区" in n), None)
|
||
parcel_weight_col_index = next((i for n, i in header_index_map.items() if "重量" in n and "weight" in n.lower()), None)
|
||
parcel_weight_unit_col_index = next((i for n, i in header_index_map.items() if "重量单位" in n and "weightunit" in n.lower()), None)
|
||
parcel_length_col_index = next((i for n, i in header_index_map.items() if "长度" in n and "length" in n.lower()), None)
|
||
parcel_width_col_index = next((i for n, i in header_index_map.items() if "宽度" in n and "width" in n.lower()), None)
|
||
parcel_height_col_index = next((i for n, i in header_index_map.items() if "高度" in n and "height" in n.lower()), None)
|
||
parcel_dimension_unit_col_index = next((i for n, i in header_index_map.items() if "尺寸单位" in n and "dimensionunit" in n.lower()), None)
|
||
|
||
# 从界面读取SecretKey和url
|
||
secret_key = self.secret_key_entry.get().strip()
|
||
url = self.url_entry.get().strip()
|
||
|
||
if not secret_key:
|
||
messagebox.showerror("错误", "请先在界面上填写SecretKey!")
|
||
return
|
||
|
||
if not url:
|
||
url = "https://prelink.cainiao.com/gateway/link.do"
|
||
|
||
# 读取数据(从第2行开始,第1行是表头)
|
||
data_rows = []
|
||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||
if any(cell for cell in row): # 跳过空行
|
||
data_rows.append(row)
|
||
|
||
if not data_rows:
|
||
messagebox.showwarning("警告", "Excel文件中没有数据!")
|
||
return
|
||
|
||
self.log(f"共读取到 {len(data_rows)} 条数据")
|
||
self.log(f"使用SecretKey:{secret_key[:20]}...")
|
||
self.log(f"使用URL:{url}")
|
||
self.log("=" * 80)
|
||
|
||
# 批量推送
|
||
success_count = 0
|
||
fail_count = 0
|
||
|
||
for idx, row_data in enumerate(data_rows, 1):
|
||
try:
|
||
# 构建数据字典(用于除AP单号外的其它字段)
|
||
row_dict = dict(zip(headers, row_data))
|
||
|
||
# 获取AP单号:优先按列索引读取,避免表头名字有细微差异
|
||
ap_number = ""
|
||
if ap_col_index is not None and ap_col_index < len(row_data):
|
||
cell_value = row_data[ap_col_index]
|
||
if cell_value is not None:
|
||
ap_number = str(cell_value).strip()
|
||
else:
|
||
# 兜底:按照键名尝试
|
||
ap_number = str(row_dict.get("AP单号", "")).strip()
|
||
if not ap_number:
|
||
self.log(f"第{idx+1}行:AP单号为空,跳过")
|
||
fail_count += 1
|
||
continue
|
||
|
||
self.log(f"\n【第{idx}条】AP单号:{ap_number}")
|
||
|
||
# 解析资源code
|
||
if resource_col_index is not None and resource_col_index < len(row_data):
|
||
logistic_provider_id_display = str(row_data[resource_col_index] or "").strip()
|
||
else:
|
||
logistic_provider_id_display = str(row_dict.get("资源code", "")).strip()
|
||
match = re.search(r'\[([^\]]+)\]', logistic_provider_id_display)
|
||
logistic_provider_id = match.group(1) if match else logistic_provider_id_display
|
||
|
||
# 解析接口名称
|
||
if msg_type_col_index is not None and msg_type_col_index < len(row_data):
|
||
msg_type_display = str(row_data[msg_type_col_index] or "").strip()
|
||
else:
|
||
msg_type_display = str(row_dict.get("接口名称", "")).strip()
|
||
msg_type = msg_type_display.split(" - ")[0] if " - " in msg_type_display else msg_type_display
|
||
|
||
# 构建logistics_interface
|
||
logistics_interface = {
|
||
"logisticsOrderCode": ap_number,
|
||
"opCode": str((row_data[op_result_col_index] if op_result_col_index is not None and op_result_col_index < len(row_data) else row_dict.get("作业结果", "0")) or "0").strip(),
|
||
"opLocation": str((row_data[op_loc_col_index] if op_loc_col_index is not None and op_loc_col_index < len(row_data) else row_dict.get("操作地", "")) or "").strip(),
|
||
"opRemark": str((row_data[remark_col_index] if remark_col_index is not None and remark_col_index < len(row_data) else row_dict.get("备注", "")) or "").strip(),
|
||
"opTime": str((row_data[op_time_col_index] if op_time_col_index is not None and op_time_col_index < len(row_data) else row_dict.get("操作时间", datetime.now().strftime("%Y-%m-%d %Y-%m-%d %H:%M:%S"))) or datetime.now().strftime("%Y-%m-%d %H:%M:%S")).strip(),
|
||
"operator": logistic_provider_id,
|
||
"relabelTrackingNumber": str((row_data[relabel_col_index] if relabel_col_index is not None and relabel_col_index < len(row_data) else row_dict.get("转单号", "")) or "").strip(),
|
||
"timeZone": int((row_data[tz_col_index] if tz_col_index is not None and tz_col_index < len(row_data) else row_dict.get("时区", 8)) or 8),
|
||
"trackingNumber": ap_number,
|
||
"transportType": "5"
|
||
}
|
||
|
||
# 如果是干线退回发起,添加处理方式和退回方式
|
||
if msg_type == "CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_CALLBACK":
|
||
# 处理方式
|
||
undeliverable_option_display = str(row_dict.get("处理方式", "")).strip()
|
||
if undeliverable_option_display and undeliverable_option_display != "不选":
|
||
undeliverable_option = undeliverable_option_display.split(" ")[-1] if " " in undeliverable_option_display else undeliverable_option_display
|
||
logistics_interface["undeliverableOption"] = undeliverable_option
|
||
|
||
# 退回方式
|
||
return_to_display = str(row_dict.get("退回方式", "")).strip()
|
||
if return_to_display and return_to_display != "不选":
|
||
return_to = return_to_display.split(" ")[-1] if " " in return_to_display else return_to_display
|
||
logistics_interface["returnTo"] = return_to
|
||
|
||
# 如果是交航,添加parcel字段(注意:只有交航类型才回传parcel,其他类型即使有数据也不回传)
|
||
if msg_type == "CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK":
|
||
parcel = {}
|
||
weight = ""
|
||
if parcel_weight_col_index is not None and parcel_weight_col_index < len(row_data):
|
||
weight = str((row_data[parcel_weight_col_index] or "")).strip()
|
||
else:
|
||
weight = str(row_dict.get("重量(weight)", "")).strip()
|
||
if weight:
|
||
parcel["weight"] = weight
|
||
|
||
weight_unit = ""
|
||
if parcel_weight_unit_col_index is not None and parcel_weight_unit_col_index < len(row_data):
|
||
weight_unit = str((row_data[parcel_weight_unit_col_index] or "")).strip()
|
||
else:
|
||
weight_unit = str(row_dict.get("重量单位(weightUnit)", "")).strip()
|
||
if weight_unit:
|
||
parcel["weightUnit"] = weight_unit
|
||
|
||
length = ""
|
||
if parcel_length_col_index is not None and parcel_length_col_index < len(row_data):
|
||
length = str((row_data[parcel_length_col_index] or "")).strip()
|
||
else:
|
||
length = str(row_dict.get("长度(length)", "")).strip()
|
||
if length:
|
||
parcel["length"] = length
|
||
|
||
width = ""
|
||
if parcel_width_col_index is not None and parcel_width_col_index < len(row_data):
|
||
width = str((row_data[parcel_width_col_index] or "")).strip()
|
||
else:
|
||
width = str(row_dict.get("宽度(width)", "")).strip()
|
||
if width:
|
||
parcel["width"] = width
|
||
|
||
height = ""
|
||
if parcel_height_col_index is not None and parcel_height_col_index < len(row_data):
|
||
height = str((row_data[parcel_height_col_index] or "")).strip()
|
||
else:
|
||
height = str(row_dict.get("高度(height)", "")).strip()
|
||
if height:
|
||
parcel["height"] = height
|
||
|
||
dimension_unit = ""
|
||
if parcel_dimension_unit_col_index is not None and parcel_dimension_unit_col_index < len(row_data):
|
||
dimension_unit = str((row_data[parcel_dimension_unit_col_index] or "")).strip()
|
||
else:
|
||
dimension_unit = str(row_dict.get("尺寸单位(dimensionUnit)", "")).strip()
|
||
if dimension_unit:
|
||
parcel["dimensionUnit"] = dimension_unit
|
||
|
||
if parcel:
|
||
logistics_interface["parcel"] = parcel
|
||
|
||
# 如果是干线退回处理,添加相关字段
|
||
if msg_type == "CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURNPROCESS_CALLBACK":
|
||
# processType
|
||
process_type_display = str(row_dict.get("退回方式(processType)", "")).strip() or "菜鸟快递寄送 03"
|
||
process_type = process_type_display.split(" ")[-1] if " " in process_type_display else process_type_display
|
||
logistics_interface["processType"] = process_type
|
||
|
||
# 退回地址相关字段
|
||
logistics_interface["returnPickupAdressProvince"] = str(row_dict.get("退回省份", "广东")).strip()
|
||
logistics_interface["returnPickupAdressCity"] = str(row_dict.get("退回城市", "深圳")).strip()
|
||
logistics_interface["returnPickupAdressDistrict"] = str(row_dict.get("退回区县", "宝安区")).strip()
|
||
logistics_interface["returnPickupAdressDetail"] = str(row_dict.get("退回详细地址", "怀德翠岗工业区五区36栋二楼")).strip()
|
||
logistics_interface["returnPickupPhone"] = str(row_dict.get("退回电话", "19513356304")).strip()
|
||
logistics_interface["returnPickupSender"] = str(row_dict.get("退回发件人", "蒋彬")).strip()
|
||
|
||
# 转换为JSON字符串
|
||
logistics_interface_str = json.dumps(logistics_interface, ensure_ascii=False)
|
||
|
||
# 生成签名
|
||
charset = "UTF-8"
|
||
data_digest = self.do_sign(logistics_interface_str, charset, secret_key)
|
||
|
||
# 构建请求参数
|
||
params = {
|
||
"logistic_provider_id": logistic_provider_id,
|
||
"data_digest": data_digest,
|
||
"msg_type": msg_type,
|
||
"logistics_interface": logistics_interface_str
|
||
}
|
||
|
||
# 发送请求
|
||
headers = {"Accept": "application/json"}
|
||
response = requests.post(url, data=params, headers=headers, timeout=30)
|
||
|
||
# 只打印必要的三行日志:公共参数、请求报文、返回报文
|
||
self.log(f"公共参数:{params}")
|
||
self.log(f"请求报文:{logistics_interface_str}")
|
||
self.log(f"返回报文:{response.text}")
|
||
|
||
# 内部根据success字段和HTTP状态码统计成功/失败,但不再逐条打印 ✓ / ✗
|
||
success_raw = None
|
||
try:
|
||
resp_json = response.json()
|
||
success_raw = resp_json.get("success")
|
||
except Exception:
|
||
pass
|
||
|
||
is_success = False
|
||
if response.status_code == 200:
|
||
if isinstance(success_raw, bool):
|
||
is_success = success_raw
|
||
elif isinstance(success_raw, str):
|
||
is_success = success_raw.lower() == "true"
|
||
elif success_raw is None:
|
||
# 某些接口可能没有success字段,HTTP 200 即认为成功
|
||
is_success = True
|
||
|
||
if is_success:
|
||
success_count += 1
|
||
else:
|
||
fail_count += 1
|
||
|
||
# 批量请求时,每条请求间隔1秒(最后一条不需要延时)
|
||
if idx < len(data_rows):
|
||
time.sleep(1)
|
||
|
||
except Exception as e:
|
||
self.log(f"✗ 错误:{str(e)}")
|
||
fail_count += 1
|
||
|
||
# 汇总
|
||
self.log("\n" + "=" * 80)
|
||
self.log(f"批量推送完成!")
|
||
self.log(f"总计:{len(data_rows)} 条")
|
||
self.log(f"成功:{success_count} 条")
|
||
self.log(f"失败:{fail_count} 条")
|
||
|
||
messagebox.showinfo("完成", f"批量推送完成!\n总计:{len(data_rows)} 条\n成功:{success_count} 条\n失败:{fail_count} 条")
|
||
|
||
except Exception as e:
|
||
error_msg = f"导入或推送失败:\n{str(e)}"
|
||
self.log(error_msg)
|
||
messagebox.showerror("错误", error_msg)
|
||
|
||
def on_push_click(self):
|
||
"""推送按钮点击事件"""
|
||
try:
|
||
self.log_text.delete(1.0, tk.END)
|
||
self.log("开始推送...")
|
||
|
||
# 获取基本参数
|
||
secret_key = self.secret_key_entry.get().strip()
|
||
url = self.url_entry.get().strip()
|
||
charset = "UTF-8" # 固定使用UTF-8编码
|
||
|
||
if not secret_key:
|
||
self.log("错误: SecretKey不能为空")
|
||
return
|
||
|
||
if not url:
|
||
self.log("错误: url不能为空")
|
||
return
|
||
|
||
# 获取公共参数
|
||
# 从下拉框获取logistic_provider_id,提取[]内的CODE部分
|
||
logistic_provider_id_display = self.logistic_provider_id_var.get()
|
||
match = re.search(r'\[([^\]]+)\]', logistic_provider_id_display)
|
||
logistic_provider_id = match.group(1) if match else logistic_provider_id_display
|
||
|
||
# 从下拉框获取msg_type,提取code部分(去掉中文描述)
|
||
msg_type_display = self.msg_type_var.get()
|
||
msg_type = msg_type_display.split(" - ")[0] if " - " in msg_type_display else msg_type_display
|
||
|
||
# 获取logisticsOrderCode(用于trackingNumber)
|
||
logistics_order_code = self.logistics_order_code_entry.get().strip()
|
||
|
||
# 更新opTime为当前时间
|
||
self.update_op_time()
|
||
op_time = self.op_time_entry.get().strip()
|
||
|
||
# 构建logistics_interface JSON
|
||
logistics_interface = {
|
||
"logisticsOrderCode": logistics_order_code,
|
||
"opCode": self.op_code_entry.get().strip(),
|
||
"opLocation": self.op_location_entry.get().strip(),
|
||
"opRemark": self.op_remark_entry.get().strip(),
|
||
"opTime": op_time,
|
||
"operator": logistic_provider_id, # 使用logistic_provider_id的值
|
||
"relabelTrackingNumber": self.relabel_tracking_number_entry.get().strip(),
|
||
"timeZone": int(self.time_zone_entry.get().strip()) if self.time_zone_entry.get().strip() else 8,
|
||
"trackingNumber": logistics_order_code, # 使用logisticsOrderCode的值
|
||
"transportType": "5" # 固定为5(空运)
|
||
}
|
||
|
||
# 如果是干线退回发起,添加处理方式和退回方式
|
||
if msg_type == "CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURN_CALLBACK":
|
||
# 获取undeliverableOption的值(只取英文部分),如果不选则不添加
|
||
undeliverable_option_display = self.undeliverable_option_var.get()
|
||
if undeliverable_option_display != "不选":
|
||
undeliverable_option = undeliverable_option_display.split(" ")[-1] if " " in undeliverable_option_display else undeliverable_option_display
|
||
logistics_interface["undeliverableOption"] = undeliverable_option
|
||
|
||
# 获取returnTo的值(只取英文部分),如果不选则不添加
|
||
return_to_display = self.return_to_var.get()
|
||
if return_to_display != "不选":
|
||
return_to = return_to_display.split(" ")[-1] if " " in return_to_display else return_to_display
|
||
logistics_interface["returnTo"] = return_to
|
||
|
||
# 如果是交航,添加parcel字段(注意:只有交航类型才回传parcel,其他类型即使有数据也不回传)
|
||
if msg_type == "CAINIAO_GLOBAL_LINEHAUL_HOAIRLINE_CALLBACK":
|
||
parcel = {}
|
||
weight = self.parcel_weight_entry.get().strip()
|
||
if weight:
|
||
parcel["weight"] = weight
|
||
weight_unit = self.parcel_weight_unit_var.get().strip()
|
||
if weight_unit:
|
||
parcel["weightUnit"] = weight_unit
|
||
length = self.parcel_length_entry.get().strip()
|
||
if length:
|
||
parcel["length"] = length
|
||
width = self.parcel_width_entry.get().strip()
|
||
if width:
|
||
parcel["width"] = width
|
||
height = self.parcel_height_entry.get().strip()
|
||
if height:
|
||
parcel["height"] = height
|
||
dimension_unit = self.parcel_dimension_unit_var.get().strip()
|
||
if dimension_unit:
|
||
parcel["dimensionUnit"] = dimension_unit
|
||
if parcel:
|
||
logistics_interface["parcel"] = parcel
|
||
|
||
# 如果是干线退回处理,添加相关字段
|
||
if msg_type == "CAINIAO_GLOBAL_LINEHAUL_UNREACHABLERETURNPROCESS_CALLBACK":
|
||
# processType(只取数字部分)
|
||
process_type_display = self.process_type_var.get()
|
||
process_type = process_type_display.split(" ")[-1] if " " in process_type_display else process_type_display
|
||
logistics_interface["processType"] = process_type
|
||
|
||
# 退回地址相关字段
|
||
logistics_interface["returnPickupAdressProvince"] = self.return_pickup_address_province_entry.get().strip()
|
||
logistics_interface["returnPickupAdressCity"] = self.return_pickup_address_city_entry.get().strip()
|
||
logistics_interface["returnPickupAdressDistrict"] = self.return_pickup_address_district_entry.get().strip()
|
||
logistics_interface["returnPickupAdressDetail"] = self.return_pickup_address_detail_entry.get().strip()
|
||
logistics_interface["returnPickupPhone"] = self.return_pickup_phone_entry.get().strip()
|
||
logistics_interface["returnPickupSender"] = self.return_pickup_sender_entry.get().strip()
|
||
|
||
# 转换为JSON字符串
|
||
logistics_interface_str = json.dumps(logistics_interface, ensure_ascii=False)
|
||
self.log(f"logistics_interface: {logistics_interface_str}")
|
||
|
||
# 生成签名
|
||
data_digest = self.do_sign(logistics_interface_str, charset, secret_key)
|
||
self.log(f"生成的签名(data_digest): {data_digest}")
|
||
|
||
# 构建请求参数
|
||
params = {
|
||
"logistic_provider_id": logistic_provider_id,
|
||
"data_digest": data_digest,
|
||
"msg_type": msg_type,
|
||
"logistics_interface": logistics_interface_str
|
||
}
|
||
|
||
self.log(f"请求URL: {url}")
|
||
self.log(f"请求参数: {params}")
|
||
|
||
# 发送POST请求
|
||
headers = {
|
||
"Accept": "application/json"
|
||
}
|
||
|
||
self.log("发送POST请求...")
|
||
response = requests.post(url, data=params, headers=headers, timeout=30)
|
||
|
||
# 只打印必要的三行日志:公共参数、请求报文、返回报文
|
||
self.log(f"响应状态码: {response.status_code}")
|
||
self.log(f"响应内容: {response.text}")
|
||
|
||
except Exception as e:
|
||
self.log(f"错误: {str(e)}")
|
||
import traceback
|
||
self.log(traceback.format_exc())
|
||
|
||
if __name__ == "__main__":
|
||
root = tk.Tk()
|
||
app = TrackPushApp(root)
|
||
root.mainloop()
|