Browse Source

设备采集exe,自定义备份目录

master
han\hanst 1 week ago
parent
commit
882e81b47b
  1. 6
      client-file-collector/README.md
  2. 80
      client-file-collector/client_file_collector.py
  3. BIN
      client-file-collector/dist/QMSFileCollector.exe
  4. 5
      client-file-collector/dist/collector_config.json

6
client-file-collector/README.md

@ -9,7 +9,7 @@
- 客户端只需录入:
- 系统地址
- 轮询秒
- 最多3行配置(`site + buNo + equipmentNo + 本地目录`)
- 最多3行配置(`site + buNo + equipmentNo + 本地目录` 必填,`备份目录` 可选
- 双击打开 EXE 后,录入并保存配置即可按轮询秒自动同步。
- 服务端收到每行配置上传的文件后,会自动写入 `D:\qms-dataCollection\equipmentNo`
@ -26,7 +26,9 @@
- 每轮轮询会扫描每行配置的本地目录。
- 仅同步新增或修改过的文件(避免重复上传同一文件)。
- 单个文件上传成功后,会将该文件从本地目录转移至备份目录(源目录名 + `_bak`,例如 `D:\sbFile\DRFID02004` -> `D:\sbFile\DRFID02004_bak`),源目录中对应文件即被移除。
- 单个文件上传成功后,会将该文件从本地目录转移至该行配置的备份目录,源目录中对应文件即被移除。
- 备份目录留空时,会自动使用源目录同级的 `_bak` 目录(例如 `D:\sbFile\DRFID02004` -> `D:\sbFile\DRFID02004_bak`)。
- 旧版配置若未设置备份目录,加载时也会自动补成上述默认路径。
- 若备份目录中已存在同名文件,会自动在文件名后追加时间戳避免覆盖。
- Windows 下首次启动 `QMSFileCollector.exe` 时,会自动写入当前用户自启动项(优先写入 `Startup` 启动文件夹,失败时回退注册表 `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`)。

80
client-file-collector/client_file_collector.py

@ -127,7 +127,7 @@ class CollectorApp:
def __init__(self, root):
self.root = root
self.root.title("文件采集客户端")
self.root.geometry("1180x720")
self.root.geometry("1280x720")
self.root.resizable(True, True)
self.config_path = get_runtime_dir() / "collector_config.json"
@ -148,6 +148,7 @@ class CollectorApp:
"bu_no": StringVar(),
"equipment_no": StringVar(),
"source_path": StringVar(),
"backup_path": StringVar(),
})
self._build_ui()
@ -175,22 +176,25 @@ class CollectorApp:
Button(self.root, text="开始同步", command=self.start_collect).place(x=180, y=54)
Button(self.root, text="停止同步", command=self.stop_collect).place(x=265, y=54)
Label(self.root, text="配置说明:最多3行,每行需填写 site + buNo + equipmentNo + 本地目录").place(x=360, y=58)
Label(self.root, text="配置说明:最多3行,每行需填写 site + buNo + equipmentNo + 本地目录;备份目录可选(为空则自动使用本地目录_bak)").place(x=360, y=58)
Label(self.root, text="").place(x=20, y=100)
Label(self.root, text="Site").place(x=80, y=100)
Label(self.root, text="BuNo").place(x=230, y=100)
Label(self.root, text="EquipmentNo").place(x=380, y=100)
Label(self.root, text="本地目录").place(x=560, y=100)
Label(self.root, text="BuNo").place(x=220, y=100)
Label(self.root, text="EquipmentNo").place(x=360, y=100)
Label(self.root, text="本地目录").place(x=530, y=100)
Label(self.root, text="备份目录").place(x=890, y=100)
for idx, row in enumerate(self.row_vars):
y = 130 + idx * 40
Label(self.root, text=str(idx + 1)).place(x=26, y=y)
Entry(self.root, textvariable=row["site"], width=16).place(x=80, y=y)
Entry(self.root, textvariable=row["bu_no"], width=16).place(x=230, y=y)
Entry(self.root, textvariable=row["equipment_no"], width=18).place(x=380, y=y)
Entry(self.root, textvariable=row["source_path"], width=56).place(x=560, y=y)
Button(self.root, text="选择目录", command=lambda x=idx: self.choose_source_dir(x)).place(x=1010, y=y - 4)
Entry(self.root, textvariable=row["bu_no"], width=16).place(x=220, y=y)
Entry(self.root, textvariable=row["equipment_no"], width=18).place(x=360, y=y)
Entry(self.root, textvariable=row["source_path"], width=40).place(x=530, y=y)
Button(self.root, text="选择目录", command=lambda x=idx: self.choose_source_dir(x)).place(x=815, y=y - 4)
Entry(self.root, textvariable=row["backup_path"], width=30).place(x=890, y=y)
Button(self.root, text="选择目录", command=lambda x=idx: self.choose_backup_dir(x)).place(x=1110, y=y - 4)
self.log_text = ScrolledText(self.root, width=165, height=23)
self.log_text.place(x=20, y=280)
@ -209,7 +213,22 @@ class CollectorApp:
def choose_source_dir(self, row_index):
path = filedialog.askdirectory(title="选择第%d行本地目录" % (row_index + 1))
if path:
self.row_vars[row_index]["source_path"].set(path)
row = self.row_vars[row_index]
row["source_path"].set(path)
if not row["backup_path"].get().strip():
row["backup_path"].set(self._default_backup_path(path))
def choose_backup_dir(self, row_index):
path = filedialog.askdirectory(title="选择第%d行备份目录" % (row_index + 1))
if path:
self.row_vars[row_index]["backup_path"].set(path)
def _default_backup_path(self, source_path):
source_text = str(source_path).strip()
if not source_text:
return ""
source = Path(source_text)
return str(source.parent / (source.name + "_bak"))
def _load_config(self):
if not self.config_path.exists():
@ -228,14 +247,21 @@ class CollectorApp:
"bu_no": config.get("bu_no", ""),
"equipment_no": config.get("equipment_no", ""),
"source_path": config.get("source_path", ""),
"backup_path": config.get("backup_path", ""),
}]
for idx in range(min(3, len(rows))):
item = rows[idx] if isinstance(rows[idx], dict) else {}
source_path = str(item.get("source_path", "")).strip()
backup_path = str(item.get("backup_path", "")).strip()
if source_path and not backup_path:
backup_path = self._default_backup_path(source_path)
self.row_vars[idx]["site"].set(str(item.get("site", "")).strip())
self.row_vars[idx]["bu_no"].set(str(item.get("bu_no", "")).strip())
self.row_vars[idx]["equipment_no"].set(str(item.get("equipment_no", "")).strip())
self.row_vars[idx]["source_path"].set(str(item.get("source_path", "")).strip())
self.row_vars[idx]["source_path"].set(source_path)
self.row_vars[idx]["backup_path"].set(backup_path)
self.log("配置加载完成。")
if self._can_auto_start():
@ -295,25 +321,37 @@ class CollectorApp:
bu_no = row["bu_no"].get().strip()
equipment_no = row["equipment_no"].get().strip()
source_path = row["source_path"].get().strip()
backup_path = row["backup_path"].get().strip()
filled_count = 0
for value in (site, bu_no, equipment_no, source_path):
for value in (site, bu_no, equipment_no, source_path, backup_path):
if value:
filled_count += 1
if filled_count == 0:
continue
if require_complete and filled_count < 4:
required_count = 0
for value in (site, bu_no, equipment_no, source_path):
if value:
required_count += 1
if require_complete and required_count < 4:
raise ValueError("%d行配置未填写完整,请补全site、buNo、equipmentNo、本地目录。" % (idx + 1))
if filled_count == 4:
if required_count == 4:
if not backup_path:
backup_path = self._default_backup_path(source_path)
if os.path.normcase(os.path.normpath(source_path)) == os.path.normcase(os.path.normpath(backup_path)):
raise ValueError("%d行配置错误:本地目录和备份目录不能相同。" % (idx + 1))
rows.append({
"row_index": idx + 1,
"site": site,
"bu_no": bu_no,
"equipment_no": equipment_no,
"source_path": source_path,
"backup_path": backup_path,
})
if raise_on_empty and len(rows) == 0:
@ -434,14 +472,20 @@ class CollectorApp:
self.log("读取文件状态失败: %s, 原因: %s" % (file_path, e))
return None
def _get_backup_dir(self, source_path):
source = Path(source_path)
return source.parent / (source.name + "_bak")
def _get_backup_dir(self, row):
backup_path = str(row.get("backup_path", "")).strip()
if backup_path:
return Path(backup_path)
return Path(self._default_backup_path(row["source_path"]))
def _backup_and_remove_file(self, row, file_path):
source_path = row["source_path"]
row_index = row["row_index"]
backup_dir = self._get_backup_dir(source_path)
backup_dir = self._get_backup_dir(row)
if os.path.normcase(os.path.normpath(str(source_path))) == os.path.normcase(os.path.normpath(str(backup_dir))):
self.log("%d行备份目录不能与本地目录相同: %s" % (row_index, backup_dir))
return False
try:
backup_dir.mkdir(parents=True, exist_ok=True)

BIN
client-file-collector/dist/QMSFileCollector.exe

5
client-file-collector/dist/collector_config.json

@ -1,13 +1,14 @@
{
"base_url": "http://192.168.1.162:9097",
"poll_seconds": "10",
"poll_seconds": "1",
"rows": [
{
"row_index": 1,
"site": "2",
"bu_no": "03-RFID",
"equipment_no": "RFID02004",
"source_path": "D:/sbFile/DRFID02004"
"source_path": "D:/sbFile/DRFID02004",
"backup_path": "D:/sbFile/DRFID02004_bak"
}
]
}
Loading…
Cancel
Save