Functions-d 1.34__tar.gz → 1.36__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {functions_d-1.34 → functions_d-1.36}/Functions_d/Functions_d.py +234 -130
- {functions_d-1.34 → functions_d-1.36}/Functions_d.egg-info/PKG-INFO +1 -1
- {functions_d-1.34 → functions_d-1.36}/PKG-INFO +1 -1
- {functions_d-1.34 → functions_d-1.36}/setup.cfg +1 -1
- {functions_d-1.34 → functions_d-1.36}/Functions_d/__init__.py +0 -0
- {functions_d-1.34 → functions_d-1.36}/Functions_d.egg-info/SOURCES.txt +0 -0
- {functions_d-1.34 → functions_d-1.36}/Functions_d.egg-info/dependency_links.txt +0 -0
- {functions_d-1.34 → functions_d-1.36}/Functions_d.egg-info/requires.txt +0 -0
- {functions_d-1.34 → functions_d-1.36}/Functions_d.egg-info/top_level.txt +0 -0
- {functions_d-1.34 → functions_d-1.36}/README.md +0 -0
- {functions_d-1.34 → functions_d-1.36}/pyproject.toml +0 -0
- {functions_d-1.34 → functions_d-1.36}/setup.py +0 -0
|
@@ -9,10 +9,6 @@ import urllib.request
|
|
|
9
9
|
import WeComMsg
|
|
10
10
|
import xlwings as xw
|
|
11
11
|
import yagmail
|
|
12
|
-
import smtplib
|
|
13
|
-
from email.mime.multipart import MIMEMultipart
|
|
14
|
-
from email.mime.text import MIMEText
|
|
15
|
-
from email.mime.application import MIMEApplication
|
|
16
12
|
import HiveClient
|
|
17
13
|
from PIL import ImageGrab, Image
|
|
18
14
|
from bs4 import BeautifulSoup
|
|
@@ -29,9 +25,12 @@ from tenacity import retry, stop_after_attempt, wait_fixed
|
|
|
29
25
|
import requests
|
|
30
26
|
import pandas as pd
|
|
31
27
|
import os
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
from
|
|
28
|
+
import time
|
|
29
|
+
import smtplib
|
|
30
|
+
from email.mime.multipart import MIMEMultipart
|
|
31
|
+
from email.mime.text import MIMEText
|
|
32
|
+
from email.mime.application import MIMEApplication
|
|
33
|
+
from email.mime.image import MIMEImage
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
class DataProcessingAndMessaging:
|
|
@@ -79,6 +78,15 @@ class DataProcessingAndMessaging:
|
|
|
79
78
|
self.wechat_doc_access_token = None
|
|
80
79
|
self._wechat_doc_logger = None # 企微文档独立日志器(延迟初始化)
|
|
81
80
|
|
|
81
|
+
# -------------------------- 邮件SMTP全局配置 --------------------------
|
|
82
|
+
self.SMTP_SENDER = "cc_yingxiao@xin.com"
|
|
83
|
+
self.SMTP_PWD = "cw46pfeznNQx"
|
|
84
|
+
self.SMTP_HOST = "mail.xin.com"
|
|
85
|
+
self.SMTP_PORT = 587
|
|
86
|
+
self.SMTP_TIMEOUT = 300 # 超时延长至5分钟
|
|
87
|
+
self.MAIL_RETRY_TIMES = 3 # 失败重试3次
|
|
88
|
+
self.MAIL_RETRY_INTERVAL = 5 # 重试间隔5秒
|
|
89
|
+
|
|
82
90
|
|
|
83
91
|
# -------------------------- 企微文档日志:延迟初始化独立日志器 --------------------------
|
|
84
92
|
def _init_wechat_doc_logger(self):
|
|
@@ -130,6 +138,8 @@ class DataProcessingAndMessaging:
|
|
|
130
138
|
# -------------------------- 脚本路径和文件名初始化 --------------------------
|
|
131
139
|
def Start_Get_filepath_and_filename(self):
|
|
132
140
|
self.start_time = time.time()
|
|
141
|
+
# ========== 新增:运行开始时追加10行空行分隔 ==========
|
|
142
|
+
self.logger.info('\n' * 5)
|
|
133
143
|
caller_frame = inspect.stack()[1]
|
|
134
144
|
self.current_script_name = caller_frame.filename
|
|
135
145
|
self.log_filename = os.path.splitext(self.current_script_name)[0] + ".log"
|
|
@@ -148,7 +158,7 @@ class DataProcessingAndMessaging:
|
|
|
148
158
|
elapsed_time = round(end_time - self.start_time, 0)
|
|
149
159
|
print(f"运行时间:{elapsed_time} 秒")
|
|
150
160
|
self.logger.info(f"运行时间:{elapsed_time} 秒")
|
|
151
|
-
self.logger.info('\n' * 10)
|
|
161
|
+
# self.logger.info('\n' * 10)
|
|
152
162
|
|
|
153
163
|
# -------------------------- 企业微信消息发送 --------------------------
|
|
154
164
|
# 仅展示修改后的 uxin_wx 方法,其余代码不变
|
|
@@ -815,16 +825,14 @@ class DataProcessingAndMessaging:
|
|
|
815
825
|
self.screen(filename, sheet_name,
|
|
816
826
|
f"{start_range}:%s" % (self.column_label(len(data.columns)) + str(len(data) + 3)), image_path)
|
|
817
827
|
|
|
818
|
-
# --------------------------
|
|
828
|
+
# -------------------------- 发送邮件(新版-优化增强版+重试+防超时) --------------------------
|
|
819
829
|
def send_email_new(self, recipient_emails, cc_emails=None, bcc_emails=None, subject="", html_body="",
|
|
820
830
|
attachments=None):
|
|
821
831
|
# 1. 内部工具:格式化邮箱(和旧版完全对齐,转列表+过滤无效值)
|
|
822
832
|
def format_email(emails):
|
|
823
833
|
if not emails:
|
|
824
834
|
return []
|
|
825
|
-
# 单个邮箱转列表,列表直接使用
|
|
826
835
|
email_list = [emails] if isinstance(emails, str) else emails
|
|
827
|
-
# 过滤:非空字符串 + 简单格式校验(含@和.)
|
|
828
836
|
valid_emails = [
|
|
829
837
|
e.strip() for e in email_list
|
|
830
838
|
if isinstance(e, str) and e.strip() and '@' in e.strip() and '.' in e.strip()
|
|
@@ -853,17 +861,16 @@ class DataProcessingAndMessaging:
|
|
|
853
861
|
}
|
|
854
862
|
return mime_map.get(suffix, 'application/octet-stream')
|
|
855
863
|
|
|
856
|
-
# 3.
|
|
864
|
+
# 3. 参数标准化处理
|
|
857
865
|
subject = subject.strip()
|
|
858
866
|
to_emails = format_email(recipient_emails)
|
|
859
867
|
cc_emails = format_email(cc_emails)
|
|
860
868
|
bcc_emails = format_email(bcc_emails)
|
|
861
869
|
attachments = attachments or []
|
|
862
|
-
# 附件标准化:单个字符串转列表
|
|
863
870
|
if isinstance(attachments, str):
|
|
864
871
|
attachments = [attachments.strip()]
|
|
865
872
|
|
|
866
|
-
# 4.
|
|
873
|
+
# 4. 核心参数校验
|
|
867
874
|
if not to_emails:
|
|
868
875
|
err_msg = "❌ 邮件发送失败:无有效收件人邮箱"
|
|
869
876
|
self.logger.error(err_msg)
|
|
@@ -875,7 +882,7 @@ class DataProcessingAndMessaging:
|
|
|
875
882
|
print(err_msg)
|
|
876
883
|
raise ValueError(err_msg)
|
|
877
884
|
|
|
878
|
-
# 5.
|
|
885
|
+
# 5. 控制台打印
|
|
879
886
|
print(f"📧 开始发送邮件 - 主题: {subject}")
|
|
880
887
|
print(f"📥 收件人: {', '.join(to_emails)}")
|
|
881
888
|
if cc_emails:
|
|
@@ -886,7 +893,7 @@ class DataProcessingAndMessaging:
|
|
|
886
893
|
attach_names = [os.path.basename(att) for att in attachments]
|
|
887
894
|
print(f"📎 附件: {', '.join(attach_names)}")
|
|
888
895
|
|
|
889
|
-
# 6.
|
|
896
|
+
# 6. 日志记录
|
|
890
897
|
self.logger.info(f"开始发送邮件 - 主题: {subject}")
|
|
891
898
|
self.logger.info(f"收件人: {', '.join(to_emails)}")
|
|
892
899
|
if cc_emails:
|
|
@@ -897,106 +904,147 @@ class DataProcessingAndMessaging:
|
|
|
897
904
|
attach_names = [os.path.basename(att) for att in attachments]
|
|
898
905
|
self.logger.info(f"附件: {', '.join(attach_names)}")
|
|
899
906
|
|
|
900
|
-
#
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
smtp_host = 'mail.xin.com'
|
|
904
|
-
smtp_port = 587
|
|
907
|
+
# 所有收件人提前合并去重
|
|
908
|
+
all_recipients = list(set(to_emails + cc_emails + bcc_emails))
|
|
909
|
+
last_exception = None
|
|
905
910
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
msg['
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
911
|
+
# 7. 循环重试发送(解决网络超时/抖动)
|
|
912
|
+
for retry_idx in range(1, self.MAIL_RETRY_TIMES + 1):
|
|
913
|
+
try:
|
|
914
|
+
# 8. 构建邮件对象
|
|
915
|
+
msg = MIMEMultipart()
|
|
916
|
+
msg['From'] = self.SMTP_SENDER
|
|
917
|
+
msg['To'] = ', '.join(to_emails)
|
|
918
|
+
if cc_emails:
|
|
919
|
+
msg['Cc'] = ', '.join(cc_emails)
|
|
920
|
+
if bcc_emails:
|
|
921
|
+
msg['Bcc'] = ', '.join(bcc_emails)
|
|
922
|
+
msg['Subject'] = subject
|
|
923
|
+
|
|
924
|
+
# 9. 邮件正文
|
|
925
|
+
body = MIMEText(html_body, 'html', 'utf-8')
|
|
926
|
+
msg.attach(body)
|
|
927
|
+
|
|
928
|
+
# 10. 附件处理 + 安全解码 + 中文文件名修复
|
|
929
|
+
valid_attachments = []
|
|
930
|
+
for attachment in attachments:
|
|
931
|
+
attachment = attachment.strip()
|
|
932
|
+
if not os.path.exists(attachment):
|
|
933
|
+
error_msg = f"❌ 邮件发送失败:附件不存在 -> {attachment}"
|
|
934
|
+
self.logger.error(error_msg)
|
|
935
|
+
print(error_msg)
|
|
936
|
+
raise FileNotFoundError(error_msg)
|
|
937
|
+
|
|
938
|
+
file_name = os.path.basename(attachment)
|
|
939
|
+
mime_type = get_mime_type(attachment)
|
|
940
|
+
|
|
941
|
+
# 二进制读取,避免大文件/编码报错
|
|
942
|
+
try:
|
|
943
|
+
with open(attachment, 'rb') as file:
|
|
944
|
+
file_content = file.read()
|
|
945
|
+
except Exception as e:
|
|
946
|
+
err = f"❌ 读取附件失败 {attachment}:{str(e)}"
|
|
947
|
+
self.logger.error(err, exc_info=True)
|
|
948
|
+
print(err)
|
|
949
|
+
raise RuntimeError(err)
|
|
950
|
+
|
|
951
|
+
# 构造附件
|
|
952
|
+
if mime_type.startswith('image/'):
|
|
953
|
+
part = MIMEImage(file_content, _subtype=mime_type.split('/')[1])
|
|
954
|
+
elif mime_type in ('text/plain', 'text/csv'):
|
|
955
|
+
# 文本文件容错解码
|
|
956
|
+
text = file_content.decode("utf-8", errors="replace")
|
|
957
|
+
part = MIMEText(text, _subtype=mime_type.split('/')[1], _charset='utf-8')
|
|
958
|
+
else:
|
|
959
|
+
part = MIMEApplication(file_content, _subtype=mime_type.split('/')[1])
|
|
960
|
+
|
|
961
|
+
# 修复中文附件名乱码(RFC2231标准)
|
|
962
|
+
part.add_header('Content-Disposition', 'attachment', filename=file_name)
|
|
963
|
+
part.set_param("filename*", f"utf-8''{file_name}", header="Content-Disposition")
|
|
964
|
+
part['Content-Type'] = f'{mime_type}'
|
|
965
|
+
msg.attach(part)
|
|
966
|
+
|
|
967
|
+
valid_attachments.append(file_name)
|
|
968
|
+
self.logger.debug(f"已添加附件: {file_name}")
|
|
969
|
+
print(f"✅ 已添加附件: {file_name}")
|
|
970
|
+
|
|
971
|
+
# 11. 连接SMTP服务器(延长超时 + 双重ehlo提升兼容性)
|
|
972
|
+
print(f"🔌 第{retry_idx}次尝试连接 SMTP {self.SMTP_HOST}:{self.SMTP_PORT}")
|
|
973
|
+
self.logger.info(f"第{retry_idx}次连接 SMTP 服务器")
|
|
974
|
+
|
|
975
|
+
with smtplib.SMTP(self.SMTP_HOST, self.SMTP_PORT, timeout=self.SMTP_TIMEOUT) as smtp:
|
|
976
|
+
smtp.ehlo()
|
|
977
|
+
smtp.starttls()
|
|
978
|
+
smtp.ehlo()
|
|
979
|
+
smtp.login(self.SMTP_SENDER, self.SMTP_PWD)
|
|
980
|
+
print(f"🔐 SMTP服务器登录成功")
|
|
981
|
+
|
|
982
|
+
smtp.sendmail(self.SMTP_SENDER, all_recipients, msg.as_string())
|
|
983
|
+
|
|
984
|
+
# 发送成功
|
|
985
|
+
success_msg = f"✅ 邮件发送成功 | 主题:{subject} | 收件人:{len(to_emails)}人"
|
|
986
|
+
if cc_emails:
|
|
987
|
+
success_msg += f" | 抄送:{len(cc_emails)}人"
|
|
988
|
+
if bcc_emails:
|
|
989
|
+
success_msg += f" | 密送:{len(bcc_emails)}人"
|
|
990
|
+
if valid_attachments:
|
|
991
|
+
success_msg += f" | 附件:{len(valid_attachments)}个"
|
|
992
|
+
|
|
993
|
+
self.logger.info(success_msg)
|
|
994
|
+
print(success_msg)
|
|
995
|
+
return True
|
|
972
996
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
997
|
+
# 可重试异常:超时、连接断开
|
|
998
|
+
except (TimeoutError, smtplib.SMTPServerDisconnected) as e:
|
|
999
|
+
last_exception = e
|
|
1000
|
+
err = f"⚠️ 第{retry_idx}次发送超时/连接断开:{str(e)}"
|
|
1001
|
+
self.logger.warning(err)
|
|
1002
|
+
print(err)
|
|
1003
|
+
if retry_idx < self.MAIL_RETRY_TIMES:
|
|
1004
|
+
print(f"⏳ {self.MAIL_RETRY_INTERVAL} 秒后进行重试...")
|
|
1005
|
+
time.sleep(self.MAIL_RETRY_INTERVAL)
|
|
1006
|
+
continue
|
|
1007
|
+
|
|
1008
|
+
# 账号密码错误:致命异常,直接终止重试
|
|
1009
|
+
except smtplib.SMTPAuthenticationError:
|
|
1010
|
+
err_msg = "❌ 邮件发送失败:SMTP账号/密码错误,终止重试"
|
|
1011
|
+
self.logger.error(err_msg, exc_info=True)
|
|
1012
|
+
print(err_msg)
|
|
1013
|
+
raise ValueError(err_msg)
|
|
976
1014
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1015
|
+
# 连接失败
|
|
1016
|
+
except smtplib.SMTPConnectError:
|
|
1017
|
+
err_msg = "❌ 邮件发送失败:SMTP服务器连接失败(检查网络/防火墙)"
|
|
1018
|
+
self.logger.error(err_msg, exc_info=True)
|
|
1019
|
+
print(err_msg)
|
|
1020
|
+
raise ConnectionError(err_msg)
|
|
1021
|
+
|
|
1022
|
+
# 其他SMTP协议异常
|
|
1023
|
+
except smtplib.SMTPException as e:
|
|
1024
|
+
err_msg = f"❌ 邮件发送失败:SMTP协议错误 - {str(e)}"
|
|
1025
|
+
self.logger.error(err_msg, exc_info=True)
|
|
1026
|
+
print(err_msg)
|
|
1027
|
+
raise RuntimeError(err_msg)
|
|
1028
|
+
|
|
1029
|
+
# 文件类异常,直接抛出
|
|
1030
|
+
except (FileNotFoundError, RuntimeError):
|
|
1031
|
+
raise
|
|
1032
|
+
|
|
1033
|
+
# 通用未知异常
|
|
1034
|
+
except Exception as e:
|
|
1035
|
+
last_exception = e
|
|
1036
|
+
err = f"⚠️ 第{retry_idx}次发送未知异常:{str(e)}"
|
|
1037
|
+
self.logger.error(err, exc_info=True)
|
|
1038
|
+
print(err)
|
|
1039
|
+
if retry_idx < self.MAIL_RETRY_TIMES:
|
|
1040
|
+
time.sleep(self.MAIL_RETRY_INTERVAL)
|
|
1041
|
+
continue
|
|
1042
|
+
|
|
1043
|
+
# 所有重试全部失败
|
|
1044
|
+
final_err = f"❌ 邮件{self.MAIL_RETRY_TIMES}次重试全部失败,最终异常:{str(last_exception)}"
|
|
1045
|
+
self.logger.error(final_err, exc_info=True)
|
|
1046
|
+
print(final_err)
|
|
1047
|
+
raise RuntimeError(final_err)
|
|
1000
1048
|
|
|
1001
1049
|
# -------------------------- Excel格式化 --------------------------
|
|
1002
1050
|
def format_excel_worksheet(self, worksheet, df, workbook):
|
|
@@ -1687,28 +1735,38 @@ class DataProcessingAndMessaging:
|
|
|
1687
1735
|
self.logger.warning(f"关闭Excel进程时发生异常: {str(e)}")
|
|
1688
1736
|
|
|
1689
1737
|
# -------------------------- 上传文件/DF到内网文件服务器(最终完整版) --------------------------
|
|
1690
|
-
|
|
1738
|
+
# -------------------------- 上传文件/DF到内网文件服务器(带可控自动下载最终版) --------------------------
|
|
1739
|
+
def upload_to_internal_server(self, input_data, filename_in_server: str = "dataframe_result.xlsx",
|
|
1740
|
+
auto_download: bool = False, download_save_path: str = None):
|
|
1691
1741
|
"""
|
|
1692
|
-
上传数据到内网 http://fs-cc.xin.com
|
|
1693
|
-
:param input_data: 支持 3 种输入 →
|
|
1694
|
-
1) 单个 DataFrame
|
|
1695
|
-
2) 字典 {sheet名: df} → 自动生成多Sheet Excel
|
|
1696
|
-
3) 本地Excel文件路径(str)
|
|
1742
|
+
上传数据到内网 http://fs-cc.xin.com(带可控自动下载功能)
|
|
1743
|
+
:param input_data: 支持 3 种输入 → 1) 单个 DataFrame 2) 字典 {sheet名: df} 3) 本地Excel文件路径(str)
|
|
1697
1744
|
:param filename_in_server: 服务器上显示的文件名
|
|
1745
|
+
:param auto_download: 上传成功后是否自动下载,【默认False=关闭下载】,仅传入True时启动下载
|
|
1746
|
+
:param download_save_path: 下载文件的本地保存路径(可选),默认与日志文件同目录
|
|
1698
1747
|
:return: 下载链接(失败返回None)
|
|
1699
1748
|
"""
|
|
1749
|
+
import requests
|
|
1750
|
+
import pandas as pd
|
|
1751
|
+
import os
|
|
1752
|
+
import datetime
|
|
1753
|
+
from io import BytesIO
|
|
1754
|
+
from requests.adapters import HTTPAdapter
|
|
1755
|
+
from urllib3.util.retry import Retry
|
|
1756
|
+
|
|
1700
1757
|
upload_url = "http://fs-cc.xin.com"
|
|
1701
1758
|
self.logger.info(f"开始上传数据到内网服务器,服务器文件名:{filename_in_server}")
|
|
1702
1759
|
print(f"📤 正在上传 → 服务器文件名:{filename_in_server}")
|
|
1703
1760
|
|
|
1704
|
-
# ======================
|
|
1705
|
-
def create_retry_session(retries=3):
|
|
1761
|
+
# ====================== 【完全保留原有配置:重试3次、读超时5分钟】 ======================
|
|
1762
|
+
def create_retry_session(retries=3, allowed_methods=None):
|
|
1763
|
+
allowed_methods = allowed_methods or ["POST"]
|
|
1706
1764
|
session = requests.Session()
|
|
1707
1765
|
retry_strategy = Retry(
|
|
1708
1766
|
total=retries,
|
|
1709
1767
|
backoff_factor=2,
|
|
1710
1768
|
status_forcelist=[500, 502, 503, 504, 408],
|
|
1711
|
-
allowed_methods=
|
|
1769
|
+
allowed_methods=allowed_methods,
|
|
1712
1770
|
raise_on_status=False
|
|
1713
1771
|
)
|
|
1714
1772
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
@@ -1718,9 +1776,48 @@ class DataProcessingAndMessaging:
|
|
|
1718
1776
|
|
|
1719
1777
|
# ======================================================================
|
|
1720
1778
|
|
|
1779
|
+
# ====================== 【新增:自动下载核心函数(与上传逻辑完全对齐)】 ======================
|
|
1780
|
+
def _auto_download_file(download_url, save_path, file_name):
|
|
1781
|
+
"""内部自动下载函数,带重试保障、流式下载、日志同步"""
|
|
1782
|
+
try:
|
|
1783
|
+
# 自动创建保存目录,不存在则新建
|
|
1784
|
+
os.makedirs(save_path, exist_ok=True)
|
|
1785
|
+
full_save_path = os.path.join(save_path, file_name)
|
|
1786
|
+
|
|
1787
|
+
self.logger.info(f"📥 启动自动下载,文件保存路径:{full_save_path}")
|
|
1788
|
+
print(f"📥 启动自动下载,文件保存路径:{full_save_path}")
|
|
1789
|
+
|
|
1790
|
+
# 流式下载(支持大文件,避免内存溢出),带3次重试、5分钟超时
|
|
1791
|
+
session = create_retry_session(retries=3, allowed_methods=["GET"])
|
|
1792
|
+
response = session.get(download_url, timeout=(10, 300), stream=True)
|
|
1793
|
+
|
|
1794
|
+
if response.status_code == 200:
|
|
1795
|
+
# 分块写入文件
|
|
1796
|
+
with open(full_save_path, 'wb') as f:
|
|
1797
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
1798
|
+
if chunk:
|
|
1799
|
+
f.write(chunk)
|
|
1800
|
+
|
|
1801
|
+
self.logger.info(f"✅ 自动下载成功,文件已保存至:{full_save_path}")
|
|
1802
|
+
print(f"✅ 自动下载成功,文件已保存至:{full_save_path}")
|
|
1803
|
+
return full_save_path
|
|
1804
|
+
else:
|
|
1805
|
+
err_msg = f"❌ 自动下载失败,服务器响应状态码:{response.status_code}"
|
|
1806
|
+
self.logger.error(err_msg)
|
|
1807
|
+
print(err_msg)
|
|
1808
|
+
return None
|
|
1809
|
+
|
|
1810
|
+
except Exception as e:
|
|
1811
|
+
err_msg = f"❌ 自动下载异常:{str(e)}"
|
|
1812
|
+
self.logger.error(err_msg, exc_info=True)
|
|
1813
|
+
print(err_msg)
|
|
1814
|
+
return None
|
|
1815
|
+
|
|
1816
|
+
# ======================================================================
|
|
1817
|
+
|
|
1721
1818
|
try:
|
|
1722
1819
|
# ==============================================
|
|
1723
|
-
#
|
|
1820
|
+
# 【100%完全保留原有上传逻辑:无Sheet拆分、无ZIP压缩】
|
|
1724
1821
|
# ==============================================
|
|
1725
1822
|
if isinstance(input_data, dict):
|
|
1726
1823
|
# 字典 → 多Sheet Excel
|
|
@@ -1759,25 +1856,23 @@ class DataProcessingAndMessaging:
|
|
|
1759
1856
|
else:
|
|
1760
1857
|
raise ValueError(f"不支持输入类型:{type(input_data)}")
|
|
1761
1858
|
|
|
1762
|
-
#
|
|
1859
|
+
# 上传执行
|
|
1763
1860
|
session = create_retry_session()
|
|
1764
1861
|
files = {"file": (filename_in_server, file_obj, file_type)}
|
|
1765
|
-
# ====================== 【按要求修改:读超时改为5分钟(300秒)】 ======================
|
|
1766
1862
|
response = session.post(upload_url, files=files, timeout=(10, 600))
|
|
1767
|
-
# ======================================================================
|
|
1768
1863
|
|
|
1769
1864
|
# 关闭文件
|
|
1770
1865
|
if isinstance(input_data, str):
|
|
1771
1866
|
file_obj.close()
|
|
1772
1867
|
|
|
1773
|
-
#
|
|
1868
|
+
# 上传结果处理
|
|
1774
1869
|
if response.status_code == 200:
|
|
1775
1870
|
download_url = response.text.strip()
|
|
1776
|
-
self.logger.info(f"✅
|
|
1871
|
+
self.logger.info(f"✅ 上传成功,下载链接:{download_url}")
|
|
1777
1872
|
print("✅ 上传成功!")
|
|
1778
1873
|
print("🔗 下载地址:", download_url)
|
|
1779
1874
|
|
|
1780
|
-
#
|
|
1875
|
+
# 生成同名链接文件(完全保留原有逻辑)
|
|
1781
1876
|
file_base_name = os.path.splitext(filename_in_server)[0]
|
|
1782
1877
|
link_file_name = f"{file_base_name}.log"
|
|
1783
1878
|
link_path = os.path.join(os.path.dirname(self.main_log_file), link_file_name)
|
|
@@ -1789,10 +1884,19 @@ class DataProcessingAndMessaging:
|
|
|
1789
1884
|
f.write(file_content)
|
|
1790
1885
|
|
|
1791
1886
|
self.logger.info(f"上传链接已保存到:{link_path}")
|
|
1887
|
+
|
|
1888
|
+
# ====================== 【核心:仅当auto_download=True时,才启动自动下载】 ======================
|
|
1889
|
+
if auto_download:
|
|
1890
|
+
# 下载路径优先级:用户自定义路径 > 默认日志文件同目录
|
|
1891
|
+
final_save_path = download_save_path if download_save_path else os.path.dirname(self.main_log_file)
|
|
1892
|
+
# 执行自动下载
|
|
1893
|
+
_auto_download_file(download_url, final_save_path, filename_in_server)
|
|
1894
|
+
# ======================================================================
|
|
1895
|
+
|
|
1792
1896
|
return download_url
|
|
1793
1897
|
|
|
1794
1898
|
else:
|
|
1795
|
-
err = f"❌
|
|
1899
|
+
err = f"❌ 上传失败,服务器响应状态码:{response.status_code}"
|
|
1796
1900
|
self.logger.error(err)
|
|
1797
1901
|
print(err)
|
|
1798
1902
|
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|