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