早些时间领导一直想弄个内部网盘,用纯web形式做了一个,但是文件上传,还是的手动操作上传到文件夹。这段时间无意中发现python有个watchdog库,可以用于监控文件系统的变化。它可以检测文件的新增、修改、删除等事件。找AI写个简单DEMO试了一下,发现可行,占用CPU也是极低的,鉴于python编译成exe很方便在桌面端运行,资源库也很丰富,决定用python做个我想要的个人电脑网盘程序,而且可以用于服务器文件自动备份。各种功能实现模块代码块全部找AI问,然后再自己拼。经历了一段时间拼接,终于基本满意了。实现了以下效果:
1、实时监控指定目录的文件变化信息,新增、修改、删除。(后来升级成多目录监控)
2、多线程文件传输。
3、大文件分片传输,然后在服务器端自动合成。(试验了传输单个60G大文件压缩包,服务器端合成后,可正常打开)
4、一次性传输目录下所有文件。用于初始化监控时同步全部文件。
5、用户验证。(访问web接口验证用户权限)
6、文件历史版本保存。(还在考虑需不需要,需要时再做吧,升级服务器端代码就行了)
7、程序启动时,自动最小化到系统盘图标。

通过这个程序也进一步熟悉了python的编码方式,各种语法,数组,配置文件管理等等,发现还是蛮简洁的。
下面本程序的桌面端代码:
1、主程序:_dog_main.py (想取名文件监控狗*-*)
import tkinter as tk
from tkinter import ttk, scrolledtext ,filedialog , messagebox
import threading
from threading import Thread
import time
import base64
import os
import configparser
import pystray
from PIL import Image, ImageDraw
from pathlib import Path
import sys
import webbrowser
import psutil
import subprocess
import json
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from collections import defaultdict
from GlobalVariable import globalVariables as gbv
import check_act_list
import sendAct
import sendAll
import fodSet # 导入fodCfg模块
#736 INFO: PyInstaller: 6.13.0, contrib hooks: 2025.4
#736 INFO: Python: 3.11.0rc2
#762 INFO: Platform: Windows-10-10.0.19045-SP0
#762 INFO: Python environment: D:\Python311
# dir /B /s d:\temp #完整路径显示目录和子目录下的文件
class FileMonitorHandler(FileSystemEventHandler):
def __init__(self, log_callback , conf ): #init接收2个参数
self.log_callback = log_callback
self.confg = conf
self.event_cache = defaultdict(lambda: {'count': 0, 'last_time': 0})
self.debounce_time = 2.1 # 防抖时间阈值(秒)
self.no_send_ext = ".cache.part.tmp"
self.on_send_ext = ".doc.xls.ppt"
self.create_list = ""
self.act_time = time.time()
self.act_log = ""
print(f'{self.confg}')
def add_act(self,act):
self.act_time = time.time
self.no_send_ext = self.confg['nosend'].strip()
self.on_send_ext = self.confg['onsend'].strip()
a_path = act.split("|")[1].replace("\\","/")
if a_path.rfind(".") > 0:
ext = a_path[a_path.rfind("."):] #截取扩展名
#print(f"{ext}")
if self.no_send_ext.find(ext) >= 0: #不传送的扩展名
self.log_callback(f"指定不传送【{ext}】: {a_path} ", "info")
return
if len(self.on_send_ext)>1 and (act.find("create|")==0 or act.find("change|")==0): #仅传送的扩展名
if self.on_send_ext.find(ext) >=0:
pass #仅传送的扩展名,则通过
else:
self.log_callback(f"配置不传送【{ext}】: {a_path} ", "info")
return
if a_path.find("/.") > 0: #.开头的文件名或目录不传
return
print(f"{self.act_time} : {act}")
self.act_log = self.act_log + ">" + act + "\n"
try:
self.timerr.cancel() #如果有连续动作,则不触发线程。
except Exception:
pass
finally:
self.timerr = threading.Timer(6.0, self.act_fun) #6秒后解析近期动作,执行文件传送
self.timerr.start()
def act_fun(self):
#print(f"近期动作:{self.act_log}")
#print(f"近期动作:******************************")
check_list_def = getattr(check_act_list, 'check_list') #调用历史动作解析函数,在模块check_act_list.py
check_list_def(self, self.act_log , self.confg )
self.act_log = "" #将近期文件变化历史传送给线程执行后,清空历史动作记录
def should_process_event(self, event_type, src_path):
"""判断是否应该处理该事件,避免重复"""
current_time = time.time()
#event_key = f"{event_type}_{src_path}"
event_key = f"{src_path}"
# 检查是否在防抖时间范围内
if (current_time - self.event_cache[event_key]['last_time']) < self.debounce_time:
self.event_cache[event_key]['count'] += 1
return False
# 更新事件记录
self.event_cache[event_key] = {
'count': 1,
'last_time': current_time
}
return True
#剪贴:1删除,2创建。
#粘贴:1创建,2修改。
#改名:1移动。
#删除:1删除。
def on_created(self, event):
if event.is_directory:
self.log_callback(f"目录创建: {event.src_path}", "create")
else:
src_path = event.src_path
#if self.should_process_event('created', src_path):
file_stat = os.stat(event.src_path)
if (src_path.find(".lnk") < 1
and src_path.find(".part") < 1
):
self.create_list = self.create_list + "<" + src_path + ">"
#self.add_act(f"create|{event.src_path}|{file_stat.st_size}")
#self.log_callback(f"文件创建: {event.src_path} 文件大小: {file_stat.st_size} bytes", "create")
#创建一个0.1秒后开始运行的线程,2个参数。
#通过检测文件最后保存时间是否一直在变化,来判断文件是否保存完成。(粘贴大文件这个过程比较长)
self.xc = threading.Timer(0.1, lambda: self.check_progss(event.src_path, file_stat.st_size))
self.xc.start()
def on_modified(self, event):
if event.is_directory:
return
src_path = event.src_path
# 如果文件正在创建中,则忽略修改
index = self.create_list.find("<"+src_path+">")
#print(f"前index={index}")
if (index) >-1:
#print(f"前{self.create_list}")
#self.create_list = self.create_list.replace("<"+src_path+">" , "")
#print(f"后{self.create_list}")
return
if (self.should_process_event('modified', src_path)
and src_path.find(".lnk") < 1 #非链接文件
and self.create_list.find("<"+src_path+">") < 0 #文件未在创建中
):
file_stat = os.stat(event.src_path)
self.add_act(f"change|{event.src_path}|{file_stat.st_size}")
self.log_callback(f"文件修改: {event.src_path} 文件大小: {file_stat.st_size} bytes", "modify")
def on_deleted(self, event):
self.add_act(f"delete|{event.src_path}|")
if event.is_directory:
self.log_callback(f"目录删除: {event.src_path}", "delete")
else:
self.log_callback(f"文件删除: {event.src_path}", "delete")
def on_moved(self, event):
src_type = "目录" if event.is_directory else "文件"
if (self.should_process_event('move', event.dest_path)):
self.add_act(f"move|{event.src_path}|{event.dest_path}")
self.log_callback(f"移动{src_type}: {event.src_path} -> {event.dest_path}", "move")
def check_progss(self,src_path,src_size):
print(f"创建文件:开始检测是否保存完成 {src_path} byte {src_size}")
self.log_callback(f"正在创建文件: {src_path} 文件大小: {src_size} bytes", "info")
while True:
time.sleep(0.5) #等待秒
try:
tag_size = os.path.getsize(src_path) # 不会变化
tag_time = os.path.getmtime(src_path) # 最后修改时间会变化
print(f" 文件创建中: {src_path} byte {tag_size} lastChangetime {tag_time}")
if time.time() - tag_time > 2 : # 如果2秒无变化
self.create_list = self.create_list.replace("<"+src_path+">" , "")
self.add_act(f"create|{src_path}|{src_size}")
self.log_callback(f"文件创建完成: {src_path} 文件大小: {src_size} bytes", "create")
print(f" 【文件保存完成】 {src_path} ")
break
except Exception as e:
break
# end while
class FileMonitorApp:
def __init__(self, root):
self.root = root
self.root.title("文件备份系统")
self.root.geometry("900x630")
self.root.protocol('WM_DELETE_WINDOW', self.minimize_to_tray)
self.root.resizable(True,False) # 设置窗口为不可垂直拉伸,但可水平拉伸
self.xcaPlay = 0
self.config = None
self.conf_srv_ip = "127.0.0.1"
self.conf_srv_pt = "8886"
self.conf_srv_ur = "/mycmf3/baklist/upload/"
self.conf_srv_ls = "/mycmf3/baklist/my/"
self.conf_runone = "0"
self.path_cfg = ""
self.nosendext = ""
self.onsendext = ""
self.conf_uid = "60120"
self.conf_upwd = "123456"
configs = configparser.ConfigParser()
configs.read('config_srv.ini' , encoding='utf-8')
self.conf_srv_ip = configs.get('main', 'srv_ip')
self.conf_srv_pt = configs.get('main', 'srv_port')
self.conf_srv_ur = configs.get('main', 'srv_url')
self.conf_srv_ls = configs.get('main', 'srv_list')
self.conf_runone = configs.get('main', 'runone')
# 监控状态
self.monitoring = False
self.observer = None
self.event_handler = None
self.setup_ui()
self.setup_tray_icon()
# 启动时最小化到托盘
self.root.withdraw()
self.timer2 = threading.Timer(1000, lambda:self.relogin()) #每隔1000秒重新启动监控
self.timer2.start()
#self.timer2.cancel()
self.check_runone()
# 开始监控文件变化
self.start_monitoring()
self.dog_name = sys.argv[0] #当前程序名称
print(f"运行程序:{self.dog_name}")
# 监控状态栏自定义内容
#self.listen_tips()
#self.xcls = threading.Timer(0.1, lambda: self.listen_tips())
#self.xcls.start()
def relogin(self):
self.clear_log()
self.stop_monitoring()
self.start_monitoring()
self.xcaPlay = 1 #防止全部传送任务还没结束
self.timer2 = threading.Timer(1000, lambda:self.relogin()) #每隔1000秒重新启动监控
self.timer2.start()
def select_directory(self,event): #弹出目录选择框
directory = filedialog.askdirectory()
if directory:
self.dir_var.set(directory)
self.stop_monitoring()
self.start_monitoring()
def setup_ui(self):
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(1, weight=1)
# 标题 #行0
title_label = ttk.Label(main_frame, text="文件备份系统", font=("Arial", 16, "bold"))
title_label.grid(row=0, column=0, columnspan=4, pady=(0, 10))
# 监控目录输入 #行1
ttk.Label(main_frame, text="本地监控目录:").grid(row=1, column=0, sticky=tk.E, pady=5)
self.dir_var = tk.StringVar(value=r"d:\temp")
dir_entry = ttk.Entry(main_frame, textvariable=self.dir_var, width=30)
dir_entry.grid(row=1, column=1, sticky=tk.W, padx=5)
dir_entry.bind("<ButtonRelease-1>", self.select_directory) # 当鼠标左键被释放时触发。
# 保存目录输入 #行1
ttk.Label(main_frame, text="远程保存目录:").grid(row=1, column=2, sticky=tk.E, pady=5)
self.dis_var = tk.StringVar(value=r"/我的网盘")
dis_entry = ttk.Entry(main_frame, textvariable=self.dis_var, width=30)
dis_entry.grid(row=1, column=3, sticky=tk.W, padx=5)
# 输入用户ID #行2
ttk.Label(main_frame, text="用户ID或工号:").grid(row=2, column=0, sticky=tk.E, pady=5)
self.my_uid = tk.StringVar(value=r"")
dir_uid = ttk.Entry(main_frame, textvariable=self.my_uid, width=30)
dir_uid.grid(row=2, column=1, sticky=tk.W, padx=5)
# 输入用户密码 #行2
ttk.Label(main_frame, text="用户密码:").grid(row=2, column=2, sticky=tk.E, pady=5)
self.my_pwd = tk.StringVar(value=r"")
dir_pwd = ttk.Entry(main_frame, textvariable=self.my_pwd, width=30 , show="*")
dir_pwd.grid(row=2, column=3, sticky=tk.W, padx=5)
#读取配置
#self.dir_var.set("11111111")
config = configparser.ConfigParser()
config.read('config_user.ini' , encoding='utf-8')
decodepwd = config.get('main', 'password')
decodepwd = base64.b64decode(decodepwd).decode() #密码解密
self.dir_var.set(config.get('main', 'locdir'))
self.dis_var.set(config.get('main', 'rmtdir'))
self.nosendext = config.get('main', 'nosendext')
self.onsendext = config.get('main', 'onsendext')
self.my_uid.set( config.get('main', 'userid'))
self.my_pwd.set( decodepwd )
#print(f'{decodepwd}')
# 控制按钮框架 #行3
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=3, column=0, columnspan=4, pady=10)
self.start_btn = ttk.Button(button_frame, text="开始监控", command=self.start_monitoring)
self.start_btn.grid(row=0, column=0, padx=5)
self.stop_btn = ttk.Button(button_frame, text="停止监控", command=self.stop_monitoring, state=tk.DISABLED)
self.stop_btn.grid(row=0, column=1, padx=5)
self.fset_btn = ttk.Button(button_frame, text="多路径监控", command=self.fodSet_run)
self.fset_btn.grid(row=0, column=2, padx=5)
self.clear_btn = ttk.Button(button_frame, text="清空日志", command=self.clear_log)
self.clear_btn.grid(row=0, column=3, padx=5)
self.upall_btn = ttk.Button(button_frame, text="全部传送", command=self.upload_all , state=tk.DISABLED)
self.upall_btn.grid(row=0, column=4, padx=5)
self.upstp_btn = ttk.Button(button_frame, text="停止", command=self.upload_all_stop , state=tk.DISABLED)
self.upstp_btn.grid(row=0, column=5, padx=5)
self.vall_btn = ttk.Button(button_frame, text="查看文件", command=self.view_all )
self.vall_btn.grid(row=0, column=6, padx=5)
# 状态显示 #行4
self.status_var = tk.StringVar(value="状态: 未监控")
status_label = ttk.Label(main_frame, textvariable=self.status_var)
status_label.grid(row=4, column=0, columnspan=4, sticky=tk.W, pady=5)
# 日志显示区域
ttk.Label(main_frame, text="监控记录:").grid(row=4, column=0, sticky=(tk.W, tk.N), pady=5)
# 创建带滚动条的文本框 # 行5
log_frame = ttk.Frame(main_frame)
log_frame.grid(row=5, column=1, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
log_frame.columnconfigure(0, weight=1)
log_frame.rowconfigure(0, weight=1)
self.log_text = scrolledtext.ScrolledText(log_frame, width=80, height=25, state=tk.DISABLED)
self.log_text.grid(row=0, column=0, columnspan=4,sticky=(tk.W, tk.E, tk.N, tk.S))
# 统计信息 #行6
self.stats_frame = ttk.LabelFrame(main_frame, text="统计信息", padding="5")
self.stats_frame.grid(row=6, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)
self.stats_var = tk.StringVar(value="创建: 0 修改: 0 删除: 0 移动: 0")
stats_label = ttk.Label(self.stats_frame, textvariable=self.stats_var)
stats_label.grid(row=0, column=0, sticky=tk.W)
# 初始化统计
self.stats = {"create": 0, "modify": 0, "delete": 0, "move": 0}
def setup_tray_icon(self):
# 创建托盘图标
image = Image.new('RGB', (64, 64), color='white')
draw = ImageDraw.Draw(image)
draw.rectangle([16, 16, 48, 48], fill='blue')
# 创建右键菜单
tray_menu = pystray.Menu(
pystray.MenuItem('显示窗口', self.show_window, default=True),
pystray.Menu.SEPARATOR,
pystray.MenuItem('退出', self.quit_app)
)
self.tray_icon = pystray.Icon(
"file_monitor",
image,
"文件监控系统",
tray_menu
)
# 在单独线程中运行托盘图标
self.tray_thread = threading.Thread(target=self.tray_icon.run, daemon=True)
self.tray_thread.start()
def fodSet_run(self):
fodSet.main()
def log_message(self, message, event_type):
"""添加日志消息并更新统计"""
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
formatted_message = f"[{timestamp}] {message}"
# 在主线程中更新UI
self.root.after(0, self._update_log, formatted_message, event_type)
def _update_log(self, message, event_type):
"""在主线程中更新日志和统计"""
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, message + "\n")
self.log_text.config(state=tk.DISABLED)
self.log_text.see(tk.END)
# 更新统计
if event_type in self.stats:
self.stats[event_type] += 1
stats_text = f"创建: {self.stats['create']} 修改: {self.stats['modify']} 删除: {self.stats['delete']} 移动: {self.stats['move']}"
self.stats_var.set(stats_text)
def _update_status(self , status_text):
self.stats_var.set(status_text)
def listen_tips(self):
while True:
tips = ""
try:
tips = str(os.environ['MY_GLOBAL_FOOTTIPS']).strip()
except Exception as e:
pass
if len(tips)>0:
#print(f"获取到提示:{tips}")
self.stats_var.set(f"{tips}")
#self.root.after(0, lambda: self._update_status(tips))
time.sleep(0.2) # 稍微等待一下,避免CPU占用过高
def view_all(self):
# 指定要打开的网址
url = "https://www.baidu.com"
url = "http://"+self.conf_srv_ip + ":" + self.conf_srv_pt + self.conf_srv_ls + "?uid=" + self.my_uid.get().strip() + "&pwd=" + self.my_pwd.get().strip()
# 使用webbrowser打开网址
webbrowser.open(url)
def upload_all_stop(self):
self.xcaPlay = 0
self.upstp_btn.config(state=tk.DISABLED)
self.upall_btn.config(state=tk.NORMAL)
def upload_all(self):
response = messagebox.askquestion("询问", f"你确认要继续吗?\n\n全部传送:\n{self.dir_var.get().strip()}")
if response == 'yes':
self.xcaPlay = 1
self.upload_all_play()
else:
pass
def upload_all_play(self):
#print(f"{self.config}")
self.upall_btn.config(state=tk.DISABLED)
self.upstp_btn.config(state=tk.NORMAL)
self.log_message(f"开始传送全部文件", "info")
#sendAll.upload_file_all(self, self.config)
#upAll = getattr(sendAll , 'upload_file_all') #调用模块sendAll.py中的upload_file_all函数
#upAll(self, self.config )
#在后台线程中运行耗时函数
#threadd = threading.Thread(target=sendAll.upload_file_all(self, self.config))
#threadd.daemon = True # 设置为守护线程
#threadd.start()
#在后台线程中运行耗时函数*****(此方法最好:在线程中可以访问主程序)
self.xca = threading.Timer(0.1, lambda: sendAll.upload_file_all(self, self.config))
self.xca.start()
#self.xca.cancel()
#self.upall_btn.config(state=tk.NORMAL)
def start_monitoring(self):
"""开始监控"""
monitor_dir = self.dir_var.get().strip()
rmt_dir = self.dis_var.get().strip()
self.conf_uid = self.my_uid.get().strip()
self.conf_upwd = self.my_pwd.get().strip()
gbv.dog_root_fod = monitor_dir #保存为全局变量
if not monitor_dir:
self.log_message("错误: 请先输入要监控的主目录路径", "error")
return
if not os.path.exists(monitor_dir):
try:
#os.makedirs(monitor_dir)
self.log_message(f"目录不存在,无法开始监控:{monitor_dir}", "info")
except Exception as e:
self.log_message(f"错误: 无法创建目录 - {str(e)}", "error")
return
if len(self.conf_uid)<1 or len(self.conf_upwd)<1:
self.log_message(f"请输入网盘用户ID和密码,验证通过才能开始监控。", "info")
return
#保存配置
config = configparser.ConfigParser()
#密码简单加密
encodepwd = base64.b64encode(self.conf_upwd.encode()).decode()
#解密 base64.b64decode(encodepwd).decode()
config['main'] = {
'locdir': f"{monitor_dir}",
'rmtdir': f"{rmt_dir}",
'nosendext': f"{self.nosendext}",
'onsendext': f"{self.onsendext}",
'userid': f"{self.conf_uid}",
'password': f"{encodepwd}"
}
try: #保存用户和密码
with open('config_user.ini', 'w', encoding='utf-8') as configfile:
config.write(configfile)
except Exception as e:
pass
self.config = { #创建字典列表
"userid" : f"{self.conf_uid}",
"userpwd": f"{self.conf_upwd}",
"srv_ip" : f"{self.conf_srv_ip}",
"srv_port":f"{self.conf_srv_pt}",
"srv_url": f"{self.conf_srv_ur}",
"nosend": f"{self.nosendext}",
"onsend": f"{self.onsendext}",
"loc_dir": f"{monitor_dir}",
"rmt_dir": f"{rmt_dir}"
}
#读取v=urllib.parse.quote(self.config['loc_dir']) #处理中文
if len(self.conf_uid)>0 and len(self.conf_upwd)>0:
act_File = getattr(sendAct , 'send_file_act') #调用模块sendAct.py中的send_file_act函数
ree = act_File("", self.config , "login" , "" ).strip()
self.log_message(f"{ree}", "info")
if ree.find("{password}{pass}")>0:
self.log_message(f"用户【{self.conf_uid}】登录{self.conf_srv_ip}成功!", "info")
if ree.find("{password}{pass}")<1:
self.log_message(f"用户【{self.conf_uid}】登录{self.conf_srv_ip}失败!", "info")
messagebox.showerror("错误", "上传服务器连接失败,或用户密码错误")
return
if self.monitoring:
self.log_message("监控已在运行中", "info")
return
self.json_file = "path_config.json"
try:
self.event_handler = FileMonitorHandler(self.log_message , self.config ) #传递2个参数
self.observer = Observer()
self.observer.schedule(self.event_handler, monitor_dir, recursive=True)
cfph = ""
if os.path.exists(self.json_file): #监控多个目录
with open(self.json_file, "r", encoding="utf-8") as f:
datas = json.load(f)
self.path_cfg = datas
for item_data in datas:
print(f"{item_data.get('local_path')} {item_data.get('remote_directory')}")
phh = item_data.get('local_path')
rhh = item_data.get('remote_directory')
if Path(phh).is_dir():
cfph = cfph + phh + ">" + rhh + "|"
self.observer.schedule(self.event_handler, phh, recursive=True)
gbv.FILE_DOG_PATH_CFG = ">|"+cfph
#os.putenv('FILE_DOG_PATH_CFG', cfph) #设置保存到环境变量中
#env_var = os.getenv('FILE_DOG_PATH_CFG') #读取环境变量(不能垮文件访问:弃用)
self.observer.start()
self.monitoring = True
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.upall_btn.config(state=tk.NORMAL)
self.status_var.set(f"状态: 正在监控 {monitor_dir}")
self.root.title(f"正在监控备份:{monitor_dir}")
self.tray_icon.title = f"正在监控备份:{monitor_dir}"
self.log_message(f"开始监控目录: {monitor_dir}", "info")
except Exception as e:
self.log_message(f"启动监控失败: {str(e)}", "error")
print(f"启动监控失败: {str(e)}")
def stop_monitoring(self):
"""停止监控"""
if not self.monitoring or not self.observer:
return
try:
self.observer.stop()
self.observer.join()
self.monitoring = False
self.upload_all_stop()
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
self.upall_btn.config(state=tk.DISABLED)
self.status_var.set("状态: 未监控")
self.root.title(f"文件监控备份系统")
self.tray_icon.title = f"文件监控备份系统: 未监控"
self.log_message("监控已停止", "info")
except Exception as e:
self.log_message(f"停止监控失败: {str(e)}", "error")
def clear_log(self):
"""清空日志"""
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
self.log_text.config(state=tk.DISABLED)
# 重置统计
for key in self.stats:
self.stats[key] = 0
self.stats_var.set("创建: 0 修改: 0 删除: 0 移动: 0")
self.log_message("日志已清空", "info")
def kill_app(self):
subprocess.run(['taskkill', '/F', '/IM', self.dog_name], check=False)
try:
print(f"正在退出:{dog_name}")
#import subprocess
subprocess.run(['taskkill', '/F', '/IM', self.dog_name], check=False)
except Exception as e:
pass
'''
#import psutil
# 遍历所有进程,查找名为'aaa.exe'的进程并结束它
for proc in psutil.process_iter(['pid', 'name']):
if proc.info['name'] == self.dog_name:
proc.kill() # 发送SIGKILL信号来强制结束进程
#break
'''
def check_runone(self):
if self.conf_runone == "1":
#self.quit_app()
#tasklist | findstr dog_main
rcc = 0
process_name = os.path.basename(sys.argv[0]) # 获取当前脚本名称(不带路径)编译exe后才能识别
for proc in psutil.process_iter(['pid', 'name']):
if proc.info['name'] == process_name: # 检查进程名称是否匹配
#print("程序已经在运行。")
rcc = rcc + 1
if rcc > 2:
break
if rcc > 2:
messagebox.showerror("错误", f"程序{process_name}正在运行,请不要重复执行!")
self.quit_app()
def minimize_to_tray(self):
"""最小化到系统托盘"""
self.root.withdraw()
def show_window(self):
"""显示主窗口"""
self.root.deiconify()
self.root.lift()
self.root.focus_force()
def quit_app(self):
"""退出应用程序"""
#print(self.dog_name)
if self.monitoring:
self.stop_monitoring()
self.xcaPlay = 0 #停止传送全部任务线程
self.timer2.cancel() #退出程序时,必须关闭所有线程,才能正常退出
self.tray_icon.stop()
#self.kill_app()
#self.xckill = threading.Timer(0.5, lambda: self.kill_app() )
#self.xckill.start()
#sys.exit()
try:
self.root.quit()
self.root.destroy()
except Exception as e:
sys.exit()
def main():
root = tk.Tk()
app = FileMonitorApp(root)
root.mainloop()
if __name__ == "__main__":
main()
2、文件变化历史:check_act_list.py 用于记录近期变化记录,生成传输任务。
import copy
import sendFile
import sendAct
from GlobalVariable import globalVariables as gbv
def check_list(self, logg, conf):
PATH_CFG = gbv.get('FILE_DOG_PATH_CFG') #自定义的监控目录配置
#print(f"****************************************")
#print(f"配置信息:\n{conf}")
print(f"****************************************")
print(f"调用check_act_list.py函数check_list(logg)")
print(f"获得近期动作:\n{logg}")
up_File = getattr(sendFile, 'upload_file') #调用模块sendFile.py中的upload_file函数
act_File = getattr(sendAct , 'send_file_act') #调用模块sendAct.py中的send_file_act函数
actt = (logg+">").split(">")
print(f"》》》》动作数:{len(actt)-2}")
for i in range(len(actt)):
acts = actt[i]+"||"
#confcur = conf #不能直接赋值字典列表给变量,会直接指向原字典列表。
confcur = copy.deepcopy(conf) #复制字典列表
if acts.find("|") > -1 :
#print(f"》》》》{acts}")
actv = acts.split("|")
act = actv[0]
filePath = actv[1].replace("\\","/")
fileSize = actv[2]
if len(act)>0 and len(filePath)>0:
confcur = dealLRMT(confcur , PATH_CFG , filePath )
#如果删除的下一个是创建,且文件名相同,则为移动
#------------------------------------------
if str(act) == "delete":
acts2 = actt[i+1] + "||" #下一个动作
tv2 = acts2.split("|")
ac = tv2[0]
ph = tv2[1].replace("\\","/")
if ac == "create": #如果是创建
fname1 = getName(filePath)
fname2 = getName(ph)
print(f"》del_name》{fname1}")
print(f"》cre_name》{fname2}")
if fname1 == fname2:
act = "move"
fileSize = ph
actt[i+1] = "" #清除下一个动作
#------------------------------------------
if str(act) == "create" or str(act) == "change":
up_File(self, confcur , filePath)
if str(act) == "delete" or str(act) == "move":
act_File(filePath , confcur , str(act) , str(fileSize) )
#获取路径中的文件名
def getName(path):
path = path.replace("\\","/")
ss = str(path).rfind("/")+1 #lastindexof
path = path[ss:] #字符串截取
return path
#更具触发文件路径,查找自定义监控路径配置的远程保存目录。
def dealLRMT(conff , CFGG , filePath):
#print(f"触发路径:{filePath}")
#print(f"扩展监控路径配置:{CFGG}")
#print(f"原路径:{conff}")
dd = CFGG.split("|")
for i in range(len(dd)):
ee = dd[i]+">"
lp = ee.split(">")[0].strip().replace("\\","/")
rp = ee.split(">")[1].strip()
if len(lp) > 1 and filePath.find(lp)==0:
conff['loc_dir'] = lp
conff['rmt_dir'] = rp
#print(f"新路径:{conff}")
return conff
#FOOT
3、文件传输:sendFile.py (用于单文件传输,自动识别是否需要分片传输)
import http.client
import urllib.parse
import os
import time
def upload_file_part(self, file_path , conf , part , part_size , offset , end ):
urlip = "127.0.0.1"
urlport = "8886"
urldir = "/mycmf3/baklist/upload/"
localhost_root_path = "1111"
remote_root_path = "2222"
x_user_name = "33333"
x_user_pwd = "44444"
if conf != "":
#print(f" 配置:{conf}")
x_user_name = urllib.parse.quote(conf['userid']) #处理中文
x_user_pwd = urllib.parse.quote(conf['userpwd']) #处理中文
localhost_root_path = urllib.parse.quote(conf['loc_dir']) #处理中文
remote_root_path = urllib.parse.quote(conf['rmt_dir']) #处理中文
urlip = conf['srv_ip']
urlport = conf['srv_port']
urldir = conf['srv_url']
#print(f"{x_user_name}")
#print(f"{urlip}:{urlport}{urldir}")
if not os.path.exists(file_path):
return "发送失败:文件不存在 {error}"
file_size = os.path.getsize(file_path)
# 1. 建立连接
#conn = http.client.HTTPConnection("127.0.0.1", 8886,timeout=30)
conn = http.client.HTTPConnection( urlip , urlport , timeout=30)
encoded_path = urllib.parse.quote(file_path, safe='')
file_name = urllib.parse.quote(os.path.basename(file_path)) #处理中文编码
file_dir = urllib.parse.quote(file_path) #处理中文编码
try:
# 2. 准备请求
with open(file_path, 'rb') as f:
f.seek(offset) # 从第几字节开始
chunk = f.read(end - offset) #读取多少字节
# 发送请求(示例为POST请求,需根据实际API调整)
conn.request("POST", urldir , body=chunk,
headers={
"X-fileName": f"{file_name}",
"X-filePath": f"{file_dir}",
"X-fileSize": f"{file_size}",
"X-userName": f"{x_user_name}",
"X-userPwd" : f"{x_user_pwd}",
"X-locRoot" : f"{localhost_root_path}",
"X-rmtRoot" : f"{remote_root_path}",
"Content-Range": f"bytes {offset}-{end}/{file_size}",
"Content-Part": f"{part}/{part_size}",
"Content-Type": "application/octet-stream"
})
print(f" 发送了块:Sent chunk {offset} of {end}")
print(f" ******************************************* ")
# 5. 获取响应
response = conn.getresponse()
redata = response.read().decode('utf-8') #获取URL返回内容
#print(f"Status: {response.status} {response.reason}")
#print(f"{redata}")
if redata.find("{pass}") < 0:
redata = redata + "\n\n发送失败{error}"
# 6. 关闭连接
conn.close()
return redata
except Exception as e:
print(f'An error occurred: {e}')
conn.close()
redata = "发送失败{error}"
return redata
def send_log(self, msg , acttype ):
try:
self.log_callback(msg , acttype)
except Exception as e:
print(f'An error log_callback(): {e}')
pass
try:
self.log_message(msg , acttype)
except Exception as e:
print(f'An error og_message(): {e}')
pass
def upload_file(self, conf , file_path ):
#upload_file_part( file_path , 0 , 1024 )
chunk_size = 1024*1024*10 #10mb
passc = 0 #上传成功的块数
ret = ""
file_path = file_path.replace("\\","/")
print(f"准备发送文件: {file_path}")
# 验证文件是否存在
if not os.path.exists(file_path):
#raise FileNotFoundError(f"文件不存在不存在: {file_path}")
print(f"文件不存在不存在: {file_path}")
return
if file_path.find("/.")>0:
send_log(self, f"未传送临时文件:{file_path}", "info")
return
file_size = os.path.getsize(file_path)
part_size = file_size//chunk_size #相除取整
if (chunk_size * part_size) < file_size:
part_size = part_size + 1 ;
print(f"文件总分块数: {part_size} : {file_size} / {chunk_size}")
part = 1
if part_size > 1:
send_log(self, f"准备发送大文件:共分块:{part_size} {file_path} byte {file_size}", "info")
while part <= part_size:
time.sleep(0.5) #缓冲0.5秒
if not os.path.exists(file_path):
send_log(self, f"停止发送,文件不存在了:{file_path}", "info")
break
top = (part - 1) * chunk_size
end = (part * chunk_size)
if part == part_size:
end = file_size
print(f"文件总分块计数: {part}/{part_size} :byte {top} - {end}")
send_log(self, f"发送文件块{part}/{part_size} {file_path}", "info")
ree = upload_file_part(self, file_path , conf , part , part_size , top , end )
part = part + 1
ret = ree
if "{pass}" in ree:
passc = passc + 1 #记录成功发送的文件块数
print(f"{ree}")
print(f" ******************************************* ")
print(f"全部发送完成,共发送了文件块:{passc}")
print(f" ******************************************* ")
if os.path.exists(file_path):
send_log(self, f"发送完成文件块{part_size} {file_path}", "info")
return ret
#upload_file('127.0.0.1','8886','/mycmf3/baklist/upload/','F:/bzyy/python/demo/fodDog/sendFile_httpClient_part/doc_his_文件.MYD')
#upload_file('127.0.0.1','8886','/mycmf3/baklist/upload/','F:/bzyy/python/demo/fodDog/sendFile_httpClient_part/111.doc')
#upload_file('127.0.0.1','8886','/mycmf3/baklist/upload/','F:/bzyy/python/demo/fodDog/sendFile_httpClient_part/222.doc')
#upload_file('127.0.0.1','8886','/mycmf3/baklist/upload/','F:/bzyy/python/demo/fodDog/sendFile_httpClient_part/333.pdf')
#upload_file( "" , 'D:/temp/测试/444测试.pdf')
#upload_file( "" ,"F:/bzyy/11111111111111111/1111222.txt")
4、全部传输:sendAll.py (用于初始化全部文件同步)
import urllib.parse
import os
import subprocess
from pathlib import Path
import time
import sendFile
def upload_file_all(self, conf ):
print(f"配置:::{conf}")
currdir = os.path.dirname(os.path.abspath(__file__)) #得到当前程序运行的路径
#print(currdir)
tmee = time.time()
txtu = f"{currdir}\\.{tmee}.temp.txt"
print(f"任务列表:{txtu}")
self.log_message(f"任务列表:{txtu}", "info")
filelist = ""
ccc = 0
fcc = 0
erc = 0
if conf != "":
#print(f" 配置:{conf}")
locpath = conf['loc_dir'].strip().replace("/","\\")
command = "dir /b /s " + locpath + " > " + txtu
print(command)
# 执行命令
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 打印输出结果
#print(result.stdout)
with open( txtu, 'r') as file:
filelist = file.read() + "\n"
#print(filelist)
if len(filelist)>3:
ss = filelist.split("\n")
fcc = len(ss) - 2
print(f"准备传送文件个数:{fcc}")
for i in range(fcc):
ccc = ccc + 1
self.status_var.set(f"正在传送:>>> {fcc} - {ccc} = {fcc-ccc}")
filePath = ss[i].strip().replace("/","\\")
if Path(filePath).is_dir(): #is_dir(),不会触发文件change事件。
#os.path.isdir(path) ,会触发文件change事件,不使用。
filePath = "" #目录不传送
if len(filePath)>3:
#print(filePath)
self.log_message(f"准备传送文件:{filePath}", "info")
ree = sendFile.upload_file(self, conf , filePath).strip()
time.sleep(0.3) #休息0.3秒,稍微等待一下,避免CPU占用过高
#self.stats_var.set(f"正在传送文件:{filePath}")
if ree.find("{error}")>0:
erc = erc + 1
self.log_message(f"{ree} {filePath}", "info")
if self.xcaPlay == 0: #中断传送
self.log_message(f"您停止传送任务!", "info")
break
if erc > 10: #中断传送
self.log_message(f"传送失败超过10次,已停止传送任务!", "info")
break
if os.path.exists(txtu):
time.sleep(10) # 停5秒,等待文件关闭
try:
os.remove(txtu) # 删除文件
except Exception as e:
pass
self.log_message(f"文件传送任务结束!", "info")
self.upload_all_stop()
5、多路径监控配置管理GUI界面:fodSet.py
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox, filedialog
import os
import sys
import json
from datetime import datetime
from GlobalVariable import globalVariables as gbv
class PathTableEditor:
def __init__(self, root):
self.root = root
self.root.title("路径表格编辑器")
self.root.geometry("800x500")
self.max_rows = 5
# 设置关闭事件处理
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
# 设置保存文件路径
self.save_file = "path_config.json"
# 创建主框架
main_frame = ttk.Frame(root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建表格框架
table_frame = ttk.Frame(main_frame)
table_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# 创建表格
self.create_table(table_frame)
# 创建按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X)
# 创建按钮
self.create_buttons(button_frame)
# 绑定事件
self.tree.bind("<Button-1>", self.on_click)
self.tree.bind("<Double-1>", self.on_double_click)
# 加载上次保存的数据
self.load_data()
def on_closing(self):
"""处理窗口关闭事件"""
#if messagebox.askyesno("确认退出", "确定要退出程序吗?"):
# self.destroyed = True
# self.root.destroy()
self.save_data()
def create_table(self, parent):
"""创建表格"""
# 创建滚动条
scrollbar = ttk.Scrollbar(parent)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 创建一个Style对象,并配置Treeview的样式
style = ttk.Style()
style.configure('Treeview',
rowheight=30, # 行高
background="#f0f0f0", # 背景色
foreground="black", # 文字颜色
fieldbackground="white") # 字段背景色
# 创建Treeview表格
self.tree = ttk.Treeview(
parent,
columns=("local_path", "remote_directory"),
show="headings",
yscrollcommand=scrollbar.set
)
# 配置列
self.tree.column("local_path", width=120, anchor="w")
self.tree.column("remote_directory", width=350, anchor="w")
# 设置表头
self.tree.heading("local_path", text="本地监控路径(单击选择文件夹)")
self.tree.heading("remote_directory", text="远程同步保存目录(双击编辑)")
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.tree.yview)
# 添加示例数据(仅在首次运行时)
if not self.tree.get_children():
self.add_sample_data()
def create_buttons(self, parent):
"""创建功能按钮"""
# 添加按钮
add_btn = ttk.Button(
parent,
text="添加新行",
command=self.add_new_row
)
add_btn.pack(side=tk.LEFT, padx=(0, 10))
# 删除按钮
delete_btn = ttk.Button(
parent,
text="删除选中行",
command=self.delete_selected_row
)
delete_btn.pack(side=tk.LEFT, padx=(0, 10))
# 清空按钮
'''
clear_btn = ttk.Button(
parent,
text="清空所有行",
command=self.clear_all_rows
)
clear_btn.pack(side=tk.LEFT, padx=(0, 10))
'''
# 保存按钮
save_btn = ttk.Button(
parent,
text="保存配置",
command=self.save_data
)
save_btn.pack(side=tk.LEFT, padx=(0, 10))
# 另存为按钮
'''
save_as_btn = ttk.Button(
parent,
text="另存为",
command=self.save_as_data
)
save_as_btn.pack(side=tk.LEFT)
'''
def add_sample_data(self):
"""添加示例数据"""
sample_data = [
("C:/Users/Documents", "/home/user/documents"),
("D:/Projects", "/var/www/projects"),
("E:/Backups", "/backup/storage")
]
sample_data = [ ]
for local_path, remote_dir in sample_data:
self.tree.insert("", tk.END, values=(local_path, remote_dir))
def add_new_row(self):
current_rows = len(self.tree.get_children())
# 检查是否达到最大行数限制
if current_rows >= self.max_rows:
messagebox.showwarning("行数限制", f"表格最多允许添加{self.max_rows}行,无法继续添加新行。")
return
"""添加新行"""
self.tree.insert("", tk.END, values=("点击选择文件夹", ""))
def delete_selected_row(self):
"""删除选中行"""
selected_items = self.tree.selection()
if not selected_items:
messagebox.showwarning("警告", "请先选择要删除的行")
return
for item in selected_items:
self.tree.delete(item)
def clear_all_rows(self):
"""清空所有行"""
if messagebox.askyesno("确认", "确定要清空所有数据吗?"):
for item in self.tree.get_children():
self.tree.delete(item)
def on_click(self, event):
"""处理单击事件"""
self.root.attributes('-topmost', False) #取消窗口置顶
item = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
if not item or not column:
return
column_index = int(column.replace("#", "")) - 1
# 只在第一列(本地路径)单击时打开文件夹选择对话框
if column_index == 0:
self.select_local_folder(item)
self.root.attributes('-topmost', True) #窗口置顶
def on_double_click(self, event):
"""处理双击事件"""
item = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
if not item or not column:
return
column_index = int(column.replace("#", "")) - 1
# 在第二列(远程目录)双击时编辑
if column_index == 1:
self.edit_remote_directory(item)
def select_local_folder(self, item):
"""打开文件夹选择对话框"""
folder_path = filedialog.askdirectory(
title="选择本地文件夹",
initialdir="/"
)
if self.checkfodin(folder_path)==False:
return
if folder_path: # 用户选择了文件夹
current_values = list(self.tree.item(item, "values"))
current_values[0] = folder_path
self.tree.item(item, values=current_values)
#检测新目录是否为其他目录的子目录
def checkfodin(self , folder_path):
rootFod = ""
if len(folder_path)<1:
return False
try:
rootFod = gbv.get('dog_root_fod').strip()
except Exception:
pass
if len(rootFod) >1 and len(folder_path)>1:
if folder_path.find(rootFod)==0 or rootFod.find(folder_path)==0:
messagebox.showerror("错误", f"选择的目录{folder_path},不能与主监控目录{rootFod},相同或互为子目录")
return False
for item in self.tree.get_children():
values = self.tree.item(item, "values")
ph = values[0]
if folder_path.find(ph)==0 or ph.find(folder_path)==0:
messagebox.showerror("错误", f"选择的目录{folder_path},不能与已有监控目录{ph},相同或互为子目录")
return False
return True
def edit_remote_directory(self, item):
"""编辑远程目录"""
current_value = self.tree.set(item, 1) # 第二列
# 获取单元格位置
bbox = self.tree.bbox(item, 1)
if not bbox:
return
# 创建编辑框架
edit_frame = ttk.Frame(self.tree)
edit_frame.place(x=bbox[0], y=bbox[1], width=bbox[2], height=bbox[3])
# 创建输入框
entry = ttk.Entry(edit_frame)
entry.insert(0, current_value)
entry.pack(fill=tk.BOTH, expand=True)
entry.focus()
entry.select_range(0, tk.END)
def save_edit(event=None):
"""保存编辑"""
new_value = entry.get()
values = list(self.tree.item(item, "values"))
values[1] = new_value
self.tree.item(item, values=values)
edit_frame.destroy()
def cancel_edit(event=None):
"""取消编辑"""
edit_frame.destroy()
# 绑定事件
entry.bind("<Return>", save_edit)
entry.bind("<Escape>", cancel_edit)
entry.bind("<FocusOut>", lambda e: save_edit())
def save_data(self):
"""保存数据到JSON文件"""
try:
# 收集所有行数据
data = []
for item in self.tree.get_children():
values = self.tree.item(item, "values")
data.append({
"local_path": values[0],
"remote_directory": values[1]
})
# 使用UTF-8编码保存JSON文件
with open(self.save_file, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
#messagebox.showinfo("保存成功", f"数据已保存到:{self.save_file}")
self.root.destroy() #保存后退出
except Exception as e:
messagebox.showerror("保存失败", f"保存数据时出错:{str(e)}")
def save_as_data(self):
"""另存为数据"""
try:
# 打开文件保存对话框
filename = filedialog.asksaveasfilename(
title="保存数据文件",
defaultextension=".json",
filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")],
initialfile="path_config.json"
)
if filename:
# 收集所有行数据
data = []
for item in self.tree.get_children():
values = self.tree.item(item, "values")
data.append({
"local_path": values[0],
"remote_directory": values[1]
})
# 使用UTF-8编码保存JSON文件
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
# 更新默认保存文件路径
self.save_file = filename
messagebox.showinfo("保存成功", f"数据已保存到:{filename}")
except Exception as e:
messagebox.showerror("保存失败", f"保存数据时出错:{str(e)}")
def load_data(self):
"""加载上次保存的数据"""
try:
# 检查保存文件是否存在
if os.path.exists(self.save_file):
# 使用UTF-8编码读取JSON文件
with open(self.save_file, "r", encoding="utf-8") as f:
data = json.load(f)
# 清空现有数据
for item in self.tree.get_children():
self.tree.delete(item)
# 加载数据到表格
for item_data in data:
self.tree.insert("", tk.END, values=(
item_data.get("local_path", ""),
item_data.get("remote_directory", "")
))
#messagebox.showinfo("加载成功", f"已加载上次保存的数据")
except Exception as e:
# 如果文件不存在或格式错误,不显示错误信息,仅使用示例数据
pass
def main():
root = tk.Tk()
app = PathTableEditor(root)
root.mainloop()
if __name__ == "__main__":
main()
6、事件传输:sendAct.py(不传送文件,只传送动作,如删除,移动,改名)
import http.client
import urllib.parse
import os
#发送登录、删除、改名指令
def send_file_act(file_path , conf , act , new_file_path):
urlip = "127.0.0.1"
urlport = "8886"
urldir = "/mycmf3/baklist/upload/"
localhost_root_path = "1111"
remote_root_path = "2222"
x_user_name = "33333"
x_user_pwd = "44444"
if conf != "":
#print(f" 配置:{conf}")
x_user_name = urllib.parse.quote(conf['userid']) #处理中文
x_user_pwd = urllib.parse.quote(conf['userpwd']) #处理中文
localhost_root_path = urllib.parse.quote(conf['loc_dir']) #处理中文
remote_root_path = urllib.parse.quote(conf['rmt_dir']) #处理中文
urlip = conf['srv_ip']
urlport = conf['srv_port']
urldir = conf['srv_url']
# 1. 建立连接
conn = http.client.HTTPConnection( urlip , urlport , timeout=30)
encoded_path = urllib.parse.quote(file_path, safe='')
file_name = urllib.parse.quote(os.path.basename(file_path)) #处理中文编码
file_dir = urllib.parse.quote(file_path) #处理中文编码
new_path = urllib.parse.quote(new_file_path) #处理中文编码
file_size = 0
try:
conn.request("POST", urldir , "" ,
headers={
"X-fileName": f"{file_name}",
"X-filePath": f"{file_dir}",
"X-fileSize": f"{file_size}",
"X-userName": f"{x_user_name}",
"X-userPwd" : f"{x_user_pwd}",
"X-locRoot" : f"{localhost_root_path}",
"X-rmtRoot" : f"{remote_root_path}",
"X-act" : f"{act}",
"X-new-path": f"{new_path}",
"Content-Type": "text/html"
})
print(f" 发送了{act}:{file_path} > {new_file_path} ")
print(f" ******************************************* ")
# 5. 获取响应
response = conn.getresponse()
redata = response.read().decode('utf-8') #获取URL返回内容
#print(f"Status: {response.status} {response.reason}")
#print(f"{redata}")
if redata.find("{pass}") < 0:
redata = redata + "\n\n发送失败{error}"
# 6. 关闭连接
conn.close()
return redata
except Exception as e:
print(f'An error occurred: {e}')
conn.close()
redata = "连接失败{error}"
return redata
#send_file_act('F:/bzyy/python/demo/fodDog/sendFile_httpClient_part/doc_his_文件.MYD' , "" , "move" , "----------" )
完工。
服务器端代码,就不贴了。主要是接收文件,接收事件,用户登录验证,查看个人网盘备份文件等功能。