Exeprotector/main.py
2025-10-28 13:18:45 +08:00

1598 lines
67 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
作者:太一
微信taiyi1224
邮箱shoubo1224@qq.com
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os
import json
import pyperclip
from database import LicenseDatabase
# from encryptor import EXEEncryptor # 已弃用,使用 encryptor_secure.SecureEXEEncryptor
def set_dark_theme(root):
style = ttk.Style(root)
# 仅 Windows 支持 'vista',其它系统可改为 'clam'
style.theme_use('clam')
style.configure('.', background='#2e2e2e', foreground='#ffffff',
fieldbackground='#3c3f41', selectbackground='#0078d4',
insertbackground='#ffffff', borderwidth=1,
focuscolor='none')
style.map('.', background=[('active', '#0078d4')])
style.configure('TButton', padding=6, relief='flat',
background='#0078d4', foreground='#ffffff')
style.map('TButton', background=[('active', '#106ebe')])
style.configure('TLabel', background='#2e2e2e', foreground='#ffffff')
style.configure('TEntry', fieldbackground='#3c3f41', foreground='#ffffff',
insertbackground='#ffffff', relief='flat', padding=5)
style.configure('Treeview', background='#252526', foreground='#ffffff',
fieldbackground='#252526')
style.configure('Treeview.Heading', background='#3c3f41', foreground='#ffffff')
class EXEEncryptionTool(tk.Tk):
def __init__(self):
super().__init__()
self.title("EXE文件加密保护系统")
self.geometry("800x600")
self.minsize(800, 600)
# set_dark_theme(self)
# 数据库配置
self.db_config = {
'host': '',
'database': 'license_system',
'user': '',
'password': ''
}
# 初始化数据库连接
self.db = None
# 创建界面
self.create_widgets()
# 加载保存的配置
self.load_config()
def create_widgets(self):
"""创建界面组件"""
# 创建标签页
tab_control = ttk.Notebook(self)
# 数据库配置标签页
self.tab_db_config = ttk.Frame(tab_control)
tab_control.add(self.tab_db_config, text="数据库配置")
# 卡密生成标签页
self.tab_key_gen = ttk.Frame(tab_control)
tab_control.add(self.tab_key_gen, text="卡密生成")
# 卡密管理标签页
self.tab_key_manage = ttk.Frame(tab_control)
tab_control.add(self.tab_key_manage, text="卡密管理")
# 软件管理标签页融合了EXE加密功能
self.tab_software_manage = ttk.Frame(tab_control)
tab_control.add(self.tab_software_manage, text="软件管理与加密")
tab_control.pack(expand=1, fill="both")
# 初始化各个标签页
self.init_db_config_tab()
self.init_key_gen_tab()
self.init_key_manage_tab()
self.init_software_manage_tab()
# 状态栏
self.status_var = tk.StringVar(value="就绪")
status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# def unbind_selected_key(self):
# key_code = self.get_selected_key_code()
# if not key_code:
# return
# if messagebox.askyesno("确认", f"确定解除卡密 {key_code} 与当前机器的绑定?"):
# ok, msg = self.db.unbind_key(key_code)
# messagebox.showinfo("结果", msg)
# self.load_all_keys()
def unbind_selected_key(self):
"""后台解除卡密与当前机器的绑定"""
key_code = self.get_selected_key_code()
if not key_code:
return
if not self.db or not self.db.connection.is_connected():
messagebox.showerror("错误", "请先连接数据库")
return
if messagebox.askyesno("确认", f"确定解除卡密 {key_code} 与当前机器的绑定?"):
ok, msg = self.db.unbind_key(key_code)
messagebox.showinfo("结果", msg)
self.load_all_keys() # 刷新列表
def init_db_config_tab(self):
"""初始化数据库配置标签页"""
frame = ttk.LabelFrame(self.tab_db_config, text="数据库连接设置")
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 表单布局
grid_frame = ttk.Frame(frame)
grid_frame.pack(padx=10, pady=10, fill=tk.X)
# 主机
ttk.Label(grid_frame, text="主机:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.entry_db_host = ttk.Entry(grid_frame)
self.entry_db_host.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
# 数据库名
ttk.Label(grid_frame, text="数据库:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.entry_db_name = ttk.Entry(grid_frame)
self.entry_db_name.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
self.entry_db_name.insert(0, "")
# 用户名
ttk.Label(grid_frame, text="用户名:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
self.entry_db_user = ttk.Entry(grid_frame)
self.entry_db_user.grid(row=2, column=1, padx=5, pady=5, sticky=tk.EW)
# 密码
ttk.Label(grid_frame, text="密码:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
self.entry_db_password = ttk.Entry(grid_frame, show="*")
self.entry_db_password.grid(row=3, column=1, padx=5, pady=5, sticky=tk.EW)
# 按钮
button_frame = ttk.Frame(frame)
button_frame.pack(padx=10, pady=10, fill=tk.X)
self.btn_connect_db = ttk.Button(button_frame, text="连接数据库", command=self.connect_db)
self.btn_connect_db.pack(side=tk.LEFT, padx=5)
self.btn_create_tables = ttk.Button(button_frame, text="创建数据库表", command=self.create_db_tables)
self.btn_create_tables.pack(side=tk.LEFT, padx=5)
self.btn_save_db_config = ttk.Button(button_frame, text="保存配置", command=self.save_db_config)
self.btn_save_db_config.pack(side=tk.RIGHT, padx=5)
# 连接状态
self.label_db_status = ttk.Label(frame, text="未连接数据库", foreground="red")
self.label_db_status.pack(anchor=tk.W, padx=10, pady=5)
# 日志区域
log_frame = ttk.LabelFrame(frame, text="操作日志")
log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.text_db_log = scrolledtext.ScrolledText(log_frame, height=10)
self.text_db_log.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.text_db_log.config(state=tk.DISABLED)
# 设置网格权重
grid_frame.columnconfigure(1, weight=1)
def init_key_gen_tab(self):
"""初始化卡密生成标签页"""
frame = ttk.LabelFrame(self.tab_key_gen, text="卡密生成设置")
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 配置区域
config_frame = ttk.Frame(frame)
config_frame.pack(fill=tk.X, padx=10, pady=10)
# 软件选择
ttk.Label(config_frame, text="选择软件:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.software_combo = ttk.Combobox(config_frame, width=25, state="readonly")
self.software_combo.grid(row=0, column=1, padx=5, pady=5)
self.load_software_for_combo()
# 有效期
ttk.Label(config_frame, text="有效期(天):").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
self.entry_valid_days = ttk.Entry(config_frame, width=10)
self.entry_valid_days.grid(row=0, column=3, padx=5, pady=5)
self.entry_valid_days.insert(0, "30")
# 生成数量
ttk.Label(config_frame, text="生成数量:").grid(row=0, column=4, padx=5, pady=5, sticky=tk.W)
self.entry_key_count = ttk.Entry(config_frame, width=10)
self.entry_key_count.grid(row=0, column=5, padx=5, pady=5)
self.entry_key_count.insert(0, "1")
# 生成按钮
self.btn_generate_keys = ttk.Button(config_frame, text="生成卡密", command=self.generate_keys)
self.btn_generate_keys.grid(row=0, column=6, padx=20, pady=5)
# 卡密列表
list_frame = ttk.LabelFrame(frame, text="生成的卡密")
list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.text_keys = scrolledtext.ScrolledText(list_frame)
self.text_keys.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 操作按钮
button_frame = ttk.Frame(frame)
button_frame.pack(fill=tk.X, padx=10, pady=10)
self.btn_copy_keys = ttk.Button(button_frame, text="复制所有卡密", command=self.copy_keys)
self.btn_copy_keys.pack(side=tk.LEFT, padx=5)
self.btn_export_keys = ttk.Button(button_frame, text="导出卡密到文件", command=self.export_keys)
self.btn_export_keys.pack(side=tk.LEFT, padx=5)
# 设置网格权重
config_frame.columnconfigure(5, weight=1)
def init_key_manage_tab(self):
"""初始化卡密管理标签页"""
frame = ttk.LabelFrame(self.tab_key_manage, text="卡密管理")
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 搜索区域
search_frame = ttk.Frame(frame)
search_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(search_frame, text="搜索卡密:").pack(side=tk.LEFT, padx=5)
self.entry_key_search = ttk.Entry(search_frame)
self.entry_key_search.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.btn_search_keys = ttk.Button(search_frame, text="搜索", command=self.search_keys)
self.btn_search_keys.pack(side=tk.LEFT, padx=5)
self.btn_refresh_keys = ttk.Button(search_frame, text="刷新", command=self.load_all_keys)
self.btn_refresh_keys.pack(side=tk.LEFT, padx=5)
# 卡密列表
columns = ("id", "key_code", "machine_code", "start_time", "end_time", "status", "created_at")
self.tree_keys = ttk.Treeview(frame, columns=columns, show="headings")
# 设置列标题
self.tree_keys.heading("id", text="ID")
self.tree_keys.heading("key_code", text="卡密")
self.tree_keys.heading("machine_code", text="机器码")
self.tree_keys.heading("start_time", text="开始时间")
self.tree_keys.heading("end_time", text="结束时间")
self.tree_keys.heading("status", text="状态")
self.tree_keys.heading("created_at", text="创建时间")
# 设置列宽
self.tree_keys.column("id", width=50)
self.tree_keys.column("key_code", width=150)
self.tree_keys.column("machine_code", width=120)
self.tree_keys.column("start_time", width=120)
self.tree_keys.column("end_time", width=120)
self.tree_keys.column("status", width=80)
self.tree_keys.column("created_at", width=120)
# 绑定右键菜单事件
self.tree_keys.bind("<Button-3>", self.show_key_context_menu)
# 添加滚动条
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.tree_keys.yview)
self.tree_keys.configure(yscroll=scrollbar.set)
self.tree_keys.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=5)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=5)
# 软件筛选
filter_frame = ttk.Frame(frame)
filter_frame.pack(fill=tk.X, padx=10, pady=5)
ttk.Label(filter_frame, text="筛选软件:").pack(side=tk.LEFT, padx=5)
self.software_filter_combo = ttk.Combobox(filter_frame, width=30)
self.software_filter_combo.pack(side=tk.LEFT, padx=5)
self.software_filter_combo.bind("<<ComboboxSelected>>", self.on_software_filter_change)
ttk.Button(filter_frame, text="刷新", command=self.refresh_with_filter).pack(side=tk.LEFT, padx=5)
# 操作按钮
button_frame = ttk.Frame(frame)
button_frame.pack(fill=tk.X, padx=10, pady=10)
self.btn_ban_key = ttk.Button(button_frame, text="封禁选中卡密", command=self.ban_selected_key)
self.btn_ban_key.pack(side=tk.LEFT, padx=5)
self.btn_unban_key = ttk.Button(button_frame, text="解封选中卡密", command=self.unban_selected_key)
self.btn_unban_key.pack(side=tk.LEFT, padx=5)
self.btn_release_key = ttk.Button(button_frame, text="释放选中卡密", command=self.release_selected_key)
self.btn_release_key.pack(side=tk.LEFT, padx=5)
self.btn_delete_key = ttk.Button(button_frame, text="删除选中卡密", command=self.delete_selected_key)
self.btn_delete_key.pack(side=tk.LEFT, padx=5)
# ✅ 新增:解绑卡密按钮
self.btn_unbind = ttk.Button(button_frame, text="解绑卡密", command=self.unbind_selected_key)
self.btn_unbind.pack(side=tk.LEFT, padx=5)
# 数据库相关方法
def log(self, text, widget=None):
"""添加日志信息"""
if not widget:
widget = self.text_db_log
widget.config(state=tk.NORMAL)
widget.insert(tk.END, text + "\n")
widget.see(tk.END)
widget.config(state=tk.DISABLED)
self.update_idletasks()
def connect_db(self):
"""连接到数据库"""
self.db_config['host'] = self.entry_db_host.get()
self.db_config['database'] = self.entry_db_name.get()
self.db_config['user'] = self.entry_db_user.get()
self.db_config['password'] = self.entry_db_password.get()
self.log(f"尝试连接到数据库: {self.db_config['host']}/{self.db_config['database']}")
self.db = LicenseDatabase(
self.db_config['host'],
self.db_config['database'],
self.db_config['user'],
self.db_config['password']
)
# 检查文件管理器是否正常初始化
if hasattr(self.db, 'file_manager') and self.db.file_manager:
print("✅ 文件管理器初始化成功")
else:
print("⚠️ 文件管理器初始化失败,将使用传统模式")
if self.db.connect():
self.label_db_status.config(text="已连接到数据库", foreground="green")
self.log("数据库连接成功")
messagebox.showinfo("成功", "数据库连接成功")
# 连接成功后先检查表结构,再加载数据
try:
# 先加载软件列表,然后再加载卡密列表
print("开始加载软件列表...")
self.load_software_for_combo()
# 同时刷新软件管理页面
self.load_software_products()
print("软件列表加载完成")
print("开始加载卡密列表...")
self.load_all_keys()
print("卡密列表加载完成")
except Exception as e:
self.log(f"加载数据时出错: {e},可能需要先创建或修复数据库表")
print(f"加载数据时出错: {e}")
else:
self.label_db_status.config(text="数据库连接失败", foreground="red")
self.log("数据库连接失败")
messagebox.showerror("错误", "无法连接到数据库,请检查配置")
return self.db and self.db.connection and self.db.connection.is_connected()
def create_db_tables(self):
"""创建数据库表"""
if not self.db or not self.db.connection.is_connected():
messagebox.showerror("错误", "请先连接数据库")
return
self.log("尝试创建数据库表...")
if self.db.create_tables():
self.log("数据库表创建成功")
messagebox.showinfo("成功", "数据库表创建成功")
else:
self.log("数据库表创建失败")
messagebox.showerror("错误", "数据库表创建失败")
def save_db_config(self):
"""保存数据库配置"""
self.db_config['host'] = self.entry_db_host.get()
self.db_config['database'] = self.entry_db_name.get()
self.db_config['user'] = self.entry_db_user.get()
self.db_config['password'] = self.entry_db_password.get()
try:
with open('db_config.json', 'w') as f:
json.dump(self.db_config, f)
self.log("数据库配置保存成功")
messagebox.showinfo("成功", "数据库配置已保存")
except Exception as e:
self.log(f"保存配置失败: {str(e)}")
messagebox.showerror("错误", f"保存配置失败: {str(e)}")
def load_config(self):
"""加载保存的配置"""
try:
if os.path.exists('db_config.json'):
with open('db_config.json', 'r') as f:
config = json.load(f)
self.entry_db_host.insert(0, config.get('host', ''))
self.entry_db_name.insert(0, config.get('database', 'license_system'))
self.entry_db_user.insert(0, config.get('user', ''))
self.entry_db_password.insert(0, config.get('password', ''))
except Exception as e:
print(f"加载配置失败: {e}")
# 卡密生成相关方法
def generate_keys(self):
"""生成卡密"""
if not self.db or not self.db.connection.is_connected():
messagebox.showerror("错误", "请先连接数据库")
return
try:
# 获取选中的软件
selected_software = self.software_combo.get()
if not selected_software:
messagebox.showerror("错误", "请先选择软件")
return
# 从选中的软件文本中提取软件名称
software_name = selected_software.split(' v')[0]
# 获取软件ID
software = self.db.get_software_by_name(software_name)
if not software:
messagebox.showerror("错误", "未找到选中的软件")
return
software_id = software['id']
days = int(self.entry_valid_days.get())
count = int(self.entry_key_count.get())
if days <= 0 or count <= 0:
messagebox.showerror("错误", "有效期和数量必须为正数")
return
self.text_keys.delete(1.0, tk.END)
# self.text_keys.insert(tk.END, f"开始生成 {count} 个有效期为 {days} 天的卡密...\n\n")
keys = []
for i in range(count):
key = self.db.generate_key(days, software_id)
if key:
keys.append(key)
self.text_keys.insert(tk.END, key + "\n")
self.text_keys.see(tk.END)
self.update_idletasks()
# self.text_keys.insert(tk.END, f"\n成功生成 {len(keys)} 个卡密")
messagebox.showinfo("成功", f"成功生成 {len(keys)} 个卡密")
# 刷新卡密列表
self.load_all_keys()
except ValueError:
messagebox.showerror("错误", "请输入有效的数字")
except Exception as e:
messagebox.showerror("错误", f"生成卡密失败: {str(e)}")
def copy_keys(self):
"""复制卡密到剪贴板"""
keys = self.text_keys.get(1.0, tk.END).strip()
if keys:
pyperclip.copy(keys)
messagebox.showinfo("成功", "卡密已复制到剪贴板")
else:
messagebox.showinfo("提示", "没有可复制的卡密")
def export_keys(self):
"""导出卡密到文件"""
keys = self.text_keys.get(1.0, tk.END).strip()
if not keys:
messagebox.showinfo("提示", "没有可导出的卡密")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, 'w') as f:
f.write(keys)
messagebox.showinfo("成功", f"卡密已导出到 {file_path}")
except Exception as e:
messagebox.showerror("错误", f"导出失败: {str(e)}")
# 卡密管理相关方法
def refresh_with_filter(self):
"""根据当前筛选条件刷新卡密列表"""
selected_software = self.software_filter_combo.get()
if not selected_software:
self.load_all_keys()
return
# 获取软件ID
software = self.db.get_software_by_name(selected_software)
if software:
self.load_all_keys(software['id'])
else:
self.load_all_keys()
def on_software_filter_change(self, event):
"""软件筛选变化事件"""
selected_software = self.software_filter_combo.get()
if not selected_software:
self.load_all_keys()
return
# 获取软件ID
software = self.db.get_software_by_name(selected_software)
if software:
self.load_all_keys(software['id'])
else:
self.load_all_keys()
def load_all_keys(self, software_id=None):
"""加载所有卡密,可按软件筛选"""
# 检查数据库连接
if not self.db or not self.db.connection.is_connected():
print("警告: 数据库未连接,无法加载卡密列表")
return
# 检查UI组件是否已初始化
if not hasattr(self, 'tree_keys'):
print("警告: 卡密列表组件尚未初始化")
return
try:
# 清空现有列表
for item in self.tree_keys.get_children():
self.tree_keys.delete(item)
# 获取卡密列表
keys = self.db.get_all_keys(software_id)
if not keys:
print("提示: 当前没有卡密记录")
return
# 遍历并显示卡密
for key in keys:
try:
# 格式化日期时间
start_time = key['start_time'].strftime("%Y-%m-%d") if key['start_time'] else ""
end_time = key['end_time'].strftime("%Y-%m-%d") if key['end_time'] else ""
created_at = key['created_at'].strftime("%Y-%m-%d") if key['created_at'] else ""
self.tree_keys.insert("", tk.END, values=(
key['id'],
key['key_code'],
key['machine_code'] or "",
start_time,
end_time,
key['status'],
created_at
))
# 根据状态设置行颜色
item = self.tree_keys.get_children()[-1]
if key['status'] == 'active':
self.tree_keys.item(item, tags=('active',))
elif key['status'] == 'expired':
self.tree_keys.item(item, tags=('expired',))
elif key['status'] == 'banned':
self.tree_keys.item(item, tags=('banned',))
except Exception as key_error:
print(f"处理卡密记录时出错: {key_error}, 卡密ID: {key.get('id', 'unknown')}")
continue
# 设置标签样式
self.tree_keys.tag_configure('active', foreground='green')
self.tree_keys.tag_configure('expired', foreground='gray')
self.tree_keys.tag_configure('banned', foreground='red')
print(f"成功加载 {len(keys)} 个卡密记录")
except Exception as e:
print(f"加载卡密列表失败,错误详情: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
def search_keys(self):
"""搜索卡密"""
if not self.db or not self.db.connection.is_connected():
return
search_text = self.entry_key_search.get().strip().lower()
if not search_text:
self.load_all_keys()
return
# 清空现有列表
for item in self.tree_keys.get_children():
self.tree_keys.delete(item)
try:
keys = self.db.get_all_keys()
for key in keys:
# 检查是否匹配搜索文本
if (search_text in key['key_code'].lower() or
search_text in key['status'].lower() or
(key['machine_code'] and search_text in key['machine_code'].lower())):
# 格式化日期时间
start_time = key['start_time'].strftime("%Y-%m-%d") if key['start_time'] else ""
end_time = key['end_time'].strftime("%Y-%m-%d") if key['end_time'] else ""
created_at = key['created_at'].strftime("%Y-%m-%d") if key['created_at'] else ""
self.tree_keys.insert("", tk.END, values=(
key['id'],
key['key_code'],
key['machine_code'] or "",
start_time,
end_time,
key['status'],
created_at
))
except Exception as e:
print(f"搜索卡密失败: {e}")
def get_selected_key_code(self):
"""获取选中的卡密"""
selected_items = self.tree_keys.selection()
if not selected_items:
messagebox.showinfo("提示", "请先选择一个卡密")
return None
item = selected_items[0]
key_code = self.tree_keys.item(item, "values")[1]
return key_code
def show_key_context_menu(self, event):
"""显示右键菜单"""
# 选择鼠标点击的行
item = self.tree_keys.identify_row(event.y)
if item:
self.tree_keys.selection_set(item)
# 创建右键菜单
menu = tk.Menu(self, tearoff=0)
menu.add_command(label="复制卡密", command=self.copy_selected_key)
menu.add_command(label="复制机器码", command=self.copy_selected_machine_code)
menu.add_separator()
menu.add_command(label="复制整行信息", command=self.copy_selected_row_info)
# 显示菜单
menu.post(event.x_root, event.y_root)
def copy_selected_key(self):
"""复制选中的卡密"""
key_code = self.get_selected_key_code()
if key_code:
pyperclip.copy(key_code)
messagebox.showinfo("成功", f"卡密已复制到剪贴板: {key_code}")
def copy_selected_machine_code(self):
"""复制选中的机器码"""
selected_items = self.tree_keys.selection()
if not selected_items:
messagebox.showinfo("提示", "请先选择一个卡密")
return
item = selected_items[0]
machine_code = self.tree_keys.item(item, "values")[2]
if machine_code:
pyperclip.copy(machine_code)
messagebox.showinfo("成功", f"机器码已复制到剪贴板: {machine_code}")
else:
messagebox.showinfo("提示", "该卡密没有绑定机器码")
def copy_selected_row_info(self):
"""复制整行信息"""
selected_items = self.tree_keys.selection()
if not selected_items:
messagebox.showinfo("提示", "请先选择一个卡密")
return
item = selected_items[0]
values = self.tree_keys.item(item, "values")
info = f"ID: {values[0]}\n卡密: {values[1]}\n机器码: {values[2]}\n开始时间: {values[3]}\n结束时间: {values[4]}\n状态: {values[5]}\n创建时间: {values[6]}"
pyperclip.copy(info)
messagebox.showinfo("成功", "整行信息已复制到剪贴板")
def ban_selected_key(self):
"""封禁选中的卡密"""
key_code = self.get_selected_key_code()
if not key_code:
return
if messagebox.askyesno("确认", f"确定要封禁卡密 {key_code} 吗?"):
if self.db.update_key_status(key_code, 'banned'):
messagebox.showinfo("成功", "卡密已封禁")
self.load_all_keys()
else:
messagebox.showerror("错误", "封禁卡密失败")
def unban_selected_key(self):
"""解封选中的卡密"""
key_code = self.get_selected_key_code()
if not key_code:
return
if messagebox.askyesno("确认", f"确定要解封卡密 {key_code} 吗?"):
# 检查原状态是未使用还是已激活
try:
selected_items = self.tree_keys.selection()
item = selected_items[0]
original_status = self.tree_keys.item(item, "values")[5]
new_status = 'unused' if original_status == 'banned' and not self.tree_keys.item(item, "values")[
2] else 'active'
if self.db.update_key_status(key_code, new_status):
messagebox.showinfo("成功", "卡密已解封")
self.load_all_keys()
else:
messagebox.showerror("错误", "解封卡密失败")
except Exception as e:
messagebox.showerror("错误", f"操作失败: {str(e)}")
def release_selected_key(self):
"""释放选中的已使用激活码"""
key_code = self.get_selected_key_code()
if not key_code:
return
# 获取选择项的状态信息
selected_items = self.tree_keys.selection()
if selected_items:
item = selected_items[0]
status = self.tree_keys.item(item, "values")[5]
if status != 'active':
messagebox.showwarning("警告", "只能释放处于'激活'状态的激活码")
return
if messagebox.askyesno("确认释放",
f"确定要释放激活码 '{key_code}' 吗?\n\n释放后:\n1. 该激活码将变为未使用状态\n2. 机器码将被清空\n3. 可以重新在任何机器上使用\n\n此操作不可撤销!"):
if not self.db or not self.db.connection.is_connected():
if not self.connect_db():
return
success, msg = self.db.release_key(key_code)
if success:
messagebox.showinfo("成功", f"激活码 '{key_code}' 已释放成功\n{msg}")
self.load_all_keys() # 刷新列表
else:
messagebox.showerror("失败", msg)
def delete_selected_key(self):
"""删除选中的卡密"""
key_code = self.get_selected_key_code()
if not key_code:
return
if messagebox.askyesno("确认", f"确定要删除卡密 {key_code} 吗?\n此操作不可恢复!"):
# 实际项目中应该实现delete_key方法
messagebox.showinfo("提示", "为安全起见,当前版本不允许删除激活码,建议使用封禁或释放功能")
def init_software_manage_tab(self):
"""初始化软件管理标签页"""
# 创建框架
frame_top = ttk.Frame(self.tab_software_manage)
frame_top.pack(fill="x", padx=10, pady=5)
frame_middle = ttk.Frame(self.tab_software_manage)
frame_middle.pack(fill="both", expand=True, padx=10, pady=5)
frame_bottom = ttk.Frame(self.tab_software_manage)
frame_bottom.pack(fill="x", padx=10, pady=5)
# 顶部:添加软件表单
ttk.Label(frame_top, text="软件名称:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.software_name_var = tk.StringVar()
ttk.Entry(frame_top, textvariable=self.software_name_var, width=20).grid(row=0, column=1, padx=5, pady=5)
ttk.Label(frame_top, text="版本:").grid(row=0, column=2, padx=5, pady=5, sticky="e")
self.software_version_var = tk.StringVar()
ttk.Entry(frame_top, textvariable=self.software_version_var, width=15).grid(row=0, column=3, padx=5, pady=5)
ttk.Label(frame_top, text="描述:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
self.software_desc_var = tk.StringVar()
ttk.Entry(frame_top, textvariable=self.software_desc_var, width=40).grid(row=1, column=1, columnspan=2, padx=5, pady=5, sticky="w")
# EXE文件选择
ttk.Label(frame_top, text="EXE文件:").grid(row=2, column=0, padx=5, pady=5, sticky="e")
self.software_exe_var = tk.StringVar()
exe_entry = ttk.Entry(frame_top, textvariable=self.software_exe_var, width=45)
exe_entry.grid(row=2, column=1, columnspan=2, padx=5, pady=5, sticky="ew")
ttk.Button(frame_top, text="浏览...", command=self.browse_exe_file).grid(row=2, column=3, padx=5, pady=5)
# 提示信息
ttk.Label(frame_top, text="提示:添加软件时将自动加密", foreground="blue").grid(row=3, column=1, columnspan=2, padx=5, pady=5, sticky="w")
ttk.Button(frame_top, text="添加软件", command=self.add_software_product).grid(row=0, column=4, rowspan=4, padx=10, pady=5, sticky="ns")
# 中部:软件列表
columns = ("ID", "软件名称", "版本", "描述", "EXE路径", "创建时间")
self.tree_software = ttk.Treeview(frame_middle, columns=columns, show="headings", height=12)
# 设置列宽和对齐方式
self.tree_software.heading("ID", text="ID")
self.tree_software.column("ID", width=50, anchor="center")
self.tree_software.heading("软件名称", text="软件名称")
self.tree_software.column("软件名称", width=150, anchor="w")
self.tree_software.heading("版本", text="版本")
self.tree_software.column("版本", width=80, anchor="center")
self.tree_software.heading("描述", text="描述")
self.tree_software.column("描述", width=200, anchor="w")
self.tree_software.heading("EXE路径", text="EXE路径")
self.tree_software.column("EXE路径", width=250, anchor="w")
self.tree_software.heading("创建时间", text="创建时间")
self.tree_software.column("创建时间", width=120, anchor="center")
# 添加滚动条
scrollbar = ttk.Scrollbar(frame_middle, orient="vertical", command=self.tree_software.yview)
self.tree_software.configure(yscrollcommand=scrollbar.set)
self.tree_software.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 绑定右键菜单
self.tree_software.bind("<Button-3>", self.show_software_context_menu)
# 底部:操作按钮
ttk.Button(frame_bottom, text="刷新列表", command=self.load_software_products).pack(side="left", padx=5)
ttk.Button(frame_bottom, text="编辑软件", command=self.edit_software_product).pack(side="left", padx=5)
ttk.Button(frame_bottom, text="删除软件", command=self.delete_software_product).pack(side="left", padx=5)
ttk.Button(frame_bottom, text="🔐 加密软件", command=self.encrypt_selected_software).pack(side="left", padx=5)
ttk.Button(frame_bottom, text="打开EXE目录", command=self.open_exe_directory).pack(side="left", padx=5)
ttk.Button(frame_bottom, text="文件管理", command=self.show_file_manager).pack(side="left", padx=5)
ttk.Button(frame_bottom, text="验证存储", command=self.verify_storage).pack(side="left", padx=5)
# 加载软件列表
self.load_software_products()
self.load_software_for_combo()
def add_software_product(self):
"""添加软件产品(自动加密)"""
name = self.software_name_var.get().strip()
version = self.software_version_var.get().strip()
description = self.software_desc_var.get().strip()
source_exe_path = self.software_exe_var.get().strip()
if not name:
messagebox.showwarning("警告", "软件名称不能为空")
return
if not source_exe_path:
messagebox.showwarning("警告", "请选择EXE文件")
return
if not os.path.exists(source_exe_path):
messagebox.showerror("错误", f"EXE文件不存在{source_exe_path}")
return
if not self.db or not self.db.connection.is_connected():
if not self.connect_db():
return
try:
# 1. 先加密EXE文件
# 加载API配置
api_config = self._load_api_config()
if not api_config:
return
# 生成加密后的文件名(使用版本号)
version_suffix = f"_v{version}" if version else ""
encrypted_filename = f"{name}{version_suffix}.exe"
# 如果有文件管理器,使用文件管理器的路径
if hasattr(self.db, 'file_manager') and self.db.file_manager:
encrypted_path = os.path.join(self.db.file_manager.executables_dir, encrypted_filename)
else:
# 否则使用当前目录
encrypted_path = os.path.join(os.getcwd(), "files", "executables", encrypted_filename)
os.makedirs(os.path.dirname(encrypted_path), exist_ok=True)
# 创建进度窗口
progress_window = tk.Toplevel(self)
progress_window.title("加密并添加软件")
progress_window.geometry("400x150")
progress_window.resizable(False, False)
# 居中窗口
progress_window.update_idletasks()
x = (progress_window.winfo_screenwidth() - progress_window.winfo_width()) // 2
y = (progress_window.winfo_screenheight() - progress_window.winfo_height()) // 2
progress_window.geometry(f"400x150+{x}+{y}")
ttk.Label(progress_window, text=f"正在加密: {name}",
font=("Microsoft YaHei", 10)).pack(pady=15)
progress_label = ttk.Label(progress_window, text="正在加密EXE文件...",
foreground="blue")
progress_label.pack(pady=5)
progress_bar = ttk.Progressbar(progress_window, mode='indeterminate', length=350)
progress_bar.pack(pady=10)
progress_bar.start(10)
progress_window.update()
# 执行加密
from encryptor_secure import SecureEXEEncryptor
encryptor = SecureEXEEncryptor()
success, msg = encryptor.encrypt_exe(
source_path=source_exe_path,
output_path=encrypted_path,
api_config=api_config,
software_name=name
)
progress_bar.stop()
progress_window.destroy()
if not success:
messagebox.showerror("错误", f"加密失败: {msg}")
return
# 2. 加密成功后,将软件添加到数据库
# 使用相对路径相对于executables目录
relative_path = encrypted_filename
if hasattr(self.db, 'add_software_product_with_file'):
success, msg = self.db.add_software_product_with_file(name, description, version, encrypted_path)
else:
success, msg = self.db.add_software_product(name, description, version, relative_path)
if success:
messagebox.showinfo("成功", f"软件已加密并添加成功!\n\n加密文件: {encrypted_filename}")
# 清空表单
self.software_name_var.set("")
self.software_version_var.set("")
self.software_desc_var.set("")
self.software_exe_var.set("")
# 刷新列表
self.load_software_products()
self.load_software_for_combo()
else:
messagebox.showerror("错误", f"添加软件失败: {msg}")
# 如果添加失败,删除加密文件
if os.path.exists(encrypted_path):
try:
os.remove(encrypted_path)
except:
pass
except Exception as e:
messagebox.showerror("错误", f"添加软件时出错: {str(e)}")
def load_software_products(self):
"""加载软件产品列表"""
if not self.db or not self.db.connection.is_connected():
messagebox.showwarning("提示", "请先连接数据库")
return
# 清空现有数据
for item in self.tree_software.get_children():
self.tree_software.delete(item)
products = self.db.get_software_products()
for product in products:
# 检查EXE文件是否存在
exe_path = product.get('exe_path', '') or ''
exe_status = ''
if exe_path:
# 获取完整路径
if hasattr(self.db, 'get_exe_full_path'):
full_path = self.db.get_exe_full_path(exe_path)
else:
full_path = exe_path
if os.path.exists(full_path):
# 判断是相对路径还是绝对路径
if os.path.isabs(exe_path):
exe_status = '✓ [绝对] ' + exe_path
else:
exe_status = '✓ [本地] ' + exe_path
else:
if os.path.isabs(exe_path):
exe_status = '✗ [绝对] ' + exe_path
else:
exe_status = '✗ [本地] ' + exe_path
else:
exe_status = '未设置'
self.tree_software.insert("", "end", values=(
product['id'],
product['name'],
product['version'] or "",
product['description'] or "",
exe_status,
product['created_at'].strftime('%Y-%m-%d %H:%M:%S') if product['created_at'] else ""
))
def load_software_for_combo(self):
"""加载软件列表到下拉框"""
if not self.db or not self.db.connection.is_connected():
return
products = self.db.get_software_products()
software_list = [f"{p['name']} v{p['version']}" if p['version'] else p['name'] for p in products]
# 更新卡密生成页面的下拉框
if hasattr(self, 'software_combo'):
self.software_combo['values'] = software_list
if software_list:
self.software_combo.current(0)
# 更新卡密管理页面的筛选下拉框
if hasattr(self, 'software_filter_combo'):
filter_list = [''] + software_list # 添加空选项用于显示全部
self.software_filter_combo['values'] = filter_list
self.software_filter_combo.set('') # 默认显示全部
def show_software_context_menu(self, event):
"""显示软件右键菜单"""
item = self.tree_software.identify_row(event.y)
if item:
self.tree_software.selection_set(item)
menu = tk.Menu(self, tearoff=0)
menu.add_command(label="🔐 加密软件", command=self.encrypt_selected_software)
menu.add_separator()
menu.add_command(label="编辑软件", command=self.edit_software_product)
menu.add_command(label="删除软件", command=self.delete_software_product)
menu.add_separator()
menu.add_command(label="打开EXE目录", command=self.open_exe_directory)
menu.post(event.x_root, event.y_root)
def browse_exe_file(self):
"""浏览选择EXE文件"""
file_path = filedialog.askopenfilename(
title="选择EXE文件",
filetypes=[("EXE文件", "*.exe"), ("所有文件", "*.*")]
)
if file_path:
self.software_exe_var.set(file_path)
def edit_software_product(self):
"""编辑软件产品"""
selected_item = self.tree_software.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择要编辑的软件")
return
item_values = self.tree_software.item(selected_item[0], 'values')
product_id = int(item_values[0])
# 创建编辑窗口
edit_window = tk.Toplevel(self)
edit_window.title("编辑软件")
edit_window.geometry("500x300")
edit_window.resizable(False, False)
# 获取原数据
original_name = item_values[1]
original_version = item_values[2]
original_desc = item_values[3]
# 解析exe路径移除状态符号
original_exe = item_values[4]
if original_exe.startswith('') or original_exe.startswith(''):
original_exe = original_exe[2:]
# 软件名称
ttk.Label(edit_window, text="软件名称:").grid(row=0, column=0, padx=10, pady=10, sticky="e")
name_var = tk.StringVar(value=original_name)
ttk.Entry(edit_window, textvariable=name_var, width=30).grid(row=0, column=1, padx=10, pady=10)
# 版本
ttk.Label(edit_window, text="版本:").grid(row=1, column=0, padx=10, pady=10, sticky="e")
version_var = tk.StringVar(value=original_version)
ttk.Entry(edit_window, textvariable=version_var, width=30).grid(row=1, column=1, padx=10, pady=10)
# 描述
ttk.Label(edit_window, text="描述:").grid(row=2, column=0, padx=10, pady=10, sticky="e")
desc_var = tk.StringVar(value=original_desc)
ttk.Entry(edit_window, textvariable=desc_var, width=30).grid(row=2, column=1, padx=10, pady=10)
# EXE文件
ttk.Label(edit_window, text="EXE文件:").grid(row=3, column=0, padx=10, pady=10, sticky="e")
exe_var = tk.StringVar(value=original_exe)
exe_frame = ttk.Frame(edit_window)
exe_frame.grid(row=3, column=1, padx=10, pady=10, sticky="ew")
ttk.Entry(exe_frame, textvariable=exe_var, width=25).pack(side="left", padx=(0, 5))
ttk.Button(exe_frame, text="浏览...",
command=lambda: self._browse_exe_for_edit(exe_var)).pack(side="left")
# 按钮
button_frame = ttk.Frame(edit_window)
button_frame.grid(row=4, column=0, columnspan=2, pady=20)
def save_changes():
new_name = name_var.get().strip()
new_version = version_var.get().strip()
new_desc = desc_var.get().strip()
new_exe = exe_var.get().strip()
if not new_name:
messagebox.showwarning("警告", "软件名称不能为空")
return
# 验证EXE文件
if new_exe and not os.path.exists(new_exe):
if not messagebox.askyesno("警告", f"EXE文件不存在{new_exe}\n是否继续保存?"):
return
success, msg = self.db.update_software_product(
product_id, new_name, new_desc, new_version, new_exe
)
if success:
messagebox.showinfo("成功", msg)
edit_window.destroy()
self.load_software_products()
self.load_software_for_combo()
else:
messagebox.showerror("错误", msg)
ttk.Button(button_frame, text="保存", command=save_changes).pack(side="left", padx=5)
ttk.Button(button_frame, text="取消", command=edit_window.destroy).pack(side="left", padx=5)
def _browse_exe_for_edit(self, exe_var):
"""为编辑窗口浏览EXE文件"""
file_path = filedialog.askopenfilename(
title="选择EXE文件",
filetypes=[("EXE文件", "*.exe"), ("所有文件", "*.*")]
)
if file_path:
exe_var.set(file_path)
def open_exe_directory(self):
"""打开选中软件的EXE目录"""
selected_item = self.tree_software.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择软件")
return
item_values = self.tree_software.item(selected_item[0], 'values')
exe_path = item_values[4]
# 解析exe路径移除状态符号
if exe_path.startswith('') or exe_path.startswith(''):
exe_path = exe_path[2:]
# 去除类型标识
if exe_path.startswith('[绝对] '):
exe_path = exe_path[5:]
elif exe_path.startswith('[本地] '):
exe_path = exe_path[5:]
if not exe_path:
messagebox.showwarning("警告", "该软件没有设置EXE文件路径")
return
# 使用数据库的路径解析功能获取完整路径
if self.db and hasattr(self.db, 'get_exe_full_path'):
full_path = self.db.get_exe_full_path(exe_path)
else:
full_path = exe_path
if not os.path.exists(full_path):
messagebox.showerror("错误", f"文件不存在:{full_path}")
return
exe_dir = os.path.dirname(full_path)
if os.path.exists(exe_dir):
os.startfile(exe_dir)
else:
messagebox.showerror("错误", f"目录不存在:{exe_dir}")
def delete_software_product(self):
"""删除软件产品"""
selection = self.tree_software.selection()
if not selection:
messagebox.showwarning("警告", "请先选择要删除的软件")
return
item = selection[0]
software_id = self.tree_software.item(item, "values")[0]
software_name = self.tree_software.item(item, "values")[1]
if messagebox.askyesno("确认", f"确定要删除软件 '{software_name}' 吗?\n注意:删除软件将同时删除其所有关联的卡密!"):
try:
cursor = self.db.connection.cursor()
# 先删除关联的卡密
cursor.execute("DELETE FROM license_keys WHERE software_id = %s", (software_id,))
# 再删除软件
cursor.execute("DELETE FROM software_products WHERE id = %s", (software_id,))
self.db.connection.commit()
cursor.close()
messagebox.showinfo("成功", f"软件 '{software_name}' 及其关联卡密已删除")
self.load_software_products()
except Exception as e:
messagebox.showerror("错误", f"删除软件失败: {str(e)}")
def show_file_manager(self):
"""显示文件管理窗口"""
if not self.db or not hasattr(self.db, 'file_manager') or not self.db.file_manager:
messagebox.showwarning("警告", "文件管理器未初始化")
return
# 创建文件管理窗口
file_window = tk.Toplevel(self)
file_window.title("文件管理")
file_window.geometry("800x600")
file_window.resizable(True, True)
# 创建主框架
main_frame = ttk.Frame(file_window)
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# 存储信息框架
info_frame = ttk.LabelFrame(main_frame, text="存储信息")
info_frame.pack(fill="x", pady=(0, 10))
# 获取存储信息
try:
storage_info = self.db.get_file_storage_info()
except:
storage_info = {}
info_text = f"""
存储目录: {storage_info.get('base_dir', '')}
EXE目录: {storage_info.get('exe_dir', '')}
备份目录: {storage_info.get('backup_dir', '')}
总文件数: {storage_info.get('total_files', 0)}
存在文件: {storage_info.get('existing_files', 0)}
丢失文件: {storage_info.get('missing_files', 0)}
总大小: {storage_info.get('total_size', 0) / 1024 / 1024:.2f} MB
""".strip()
info_label = ttk.Label(info_frame, text=info_text, justify="left")
info_label.pack(padx=10, pady=10, anchor="w")
# 文件列表框架
list_frame = ttk.LabelFrame(main_frame, text="文件列表")
list_frame.pack(fill="both", expand=True, pady=(0, 10))
# 文件列表
columns = ("文件ID", "软件名称", "原文件名", "本地文件名", "大小", "上传时间", "状态")
file_tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=15)
# 设置列标题和宽度
file_tree.heading("文件ID", text="文件ID")
file_tree.column("文件ID", width=80, anchor="center")
file_tree.heading("软件名称", text="软件名称")
file_tree.column("软件名称", width=120, anchor="w")
file_tree.heading("原文件名", text="原文件名")
file_tree.column("原文件名", width=150, anchor="w")
file_tree.heading("本地文件名", text="本地文件名")
file_tree.column("本地文件名", width=180, anchor="w")
file_tree.heading("大小", text="大小")
file_tree.column("大小", width=80, anchor="center")
file_tree.heading("上传时间", text="上传时间")
file_tree.column("上传时间", width=140, anchor="center")
file_tree.heading("状态", text="状态")
file_tree.column("状态", width=80, anchor="center")
# 添加滚动条
file_scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=file_tree.yview)
file_tree.configure(yscrollcommand=file_scrollbar.set)
file_tree.pack(side="left", fill="both", expand=True)
file_scrollbar.pack(side="right", fill="y")
# 加载文件列表
def load_file_list():
# 清空现有数据
for item in file_tree.get_children():
file_tree.delete(item)
try:
files = self.db.file_manager.list_exe_files()
for file_info in files:
file_id = file_info.get('file_id', '')[:8] + '...' if len(file_info.get('file_id', '')) > 8 else file_info.get('file_id', '')
software_name = file_info.get('software_name', '')
original_name = file_info.get('original_name', '')
local_path = file_info.get('local_path', '')
size = f"{file_info.get('size', 0) / 1024 / 1024:.2f} MB"
upload_time = file_info.get('upload_time', '').split('T')[0] if file_info.get('upload_time') else ''
status = "✓ 存在" if file_info.get('exists', False) else "✗ 丢失"
file_tree.insert("", "end", values=(
file_id, software_name, original_name, local_path,
size, upload_time, status
))
except Exception as e:
messagebox.showerror("错误", f"加载文件列表失败: {str(e)}")
load_file_list()
# 操作按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill="x")
ttk.Button(button_frame, text="刷新列表", command=load_file_list).pack(side="left", padx=5)
ttk.Button(button_frame, text="清理孤立文件",
command=lambda: self._cleanup_orphaned_files(load_file_list)).pack(side="left", padx=5)
ttk.Button(button_frame, text="打开存储目录",
command=lambda: os.startfile(storage_info.get('base_dir', '')) if storage_info.get('base_dir') and os.path.exists(storage_info.get('base_dir', '')) else messagebox.showerror("错误", "目录不存在")).pack(side="left", padx=5)
def verify_storage(self):
"""验证文件存储状态"""
if not self.db or not hasattr(self.db, 'file_manager') or not self.db.file_manager:
messagebox.showwarning("警告", "文件管理器未初始化")
return
try:
storage_info = self.db.get_file_storage_info()
products = self.db.get_software_products()
# 统计信息
total_products = len(products)
local_stored = 0
absolute_path = 0
missing_files = 0
details = []
for p in products:
exe_path = p.get('exe_path', '')
if not exe_path:
details.append(f"\u26a0\ufe0f {p['name']}: 未设置EXE路径")
continue
if os.path.isabs(exe_path):
absolute_path += 1
if os.path.exists(exe_path):
details.append(f"\u2705 {p['name']}: [绝对路径] {exe_path}")
else:
missing_files += 1
details.append(f"\u274c {p['name']}: [绝对路径-丢失] {exe_path}")
else:
local_stored += 1
full_path = self.db.get_exe_full_path(exe_path)
if os.path.exists(full_path):
details.append(f"\u2705 {p['name']}: [本地存储] {exe_path}")
else:
missing_files += 1
details.append(f"\u274c {p['name']}: [本地存储-丢失] {exe_path}")
# 生成报告
report = f"""【EXE文件存储验证报告】
📈 统计信息:
• 总软件数量: {total_products}
• 本地存储: {local_stored}
• 绝对路径: {absolute_path}
• 丢失文件: {missing_files}
📁 存储目录:
{storage_info.get('base_dir', '')}
📝 详细信息:
{chr(10).join(details) if details else '无软件记录'}
💡 建议:
"""
if missing_files > 0:
report += f"• 有 {missing_files} 个文件丢失,请检查文件状态\n"
if absolute_path > 0:
report += f"• 有 {absolute_path} 个软件使用绝对路径,建议重新添加以使用本地存储\n"
if local_stored > 0:
report += f"• 有 {local_stored} 个文件已正确保存到本地目录✅\n"
if missing_files == 0 and local_stored > 0:
report += "• 所有本地文件都存在,存储状态良好!\ud83c\udf89"
# 显示报告
report_window = tk.Toplevel(self)
report_window.title("存储验证报告")
report_window.geometry("600x500")
report_window.resizable(True, True)
text_widget = scrolledtext.ScrolledText(report_window, wrap=tk.WORD)
text_widget.pack(fill="both", expand=True, padx=10, pady=10)
text_widget.insert("1.0", report)
text_widget.config(state="disabled")
# 关闭按钮
ttk.Button(report_window, text="关闭", command=report_window.destroy).pack(pady=10)
except Exception as e:
messagebox.showerror("错误", f"验证存储状态时出错: {str(e)}")
def _cleanup_orphaned_files(self, refresh_callback):
"""清理孤立文件"""
if not self.db or not hasattr(self.db, 'file_manager') or not self.db.file_manager:
return
try:
count, files = self.db.file_manager.cleanup_orphaned_files()
if count > 0:
messagebox.showinfo("成功", f"清理了 {count} 个孤立文件:\n" + "\n".join(files))
else:
messagebox.showinfo("信息", "没有发现孤立文件")
refresh_callback()
except Exception as e:
messagebox.showerror("错误", f"清理孤立文件时出错: {str(e)}")
def _load_api_config(self):
"""加载API配置"""
config_file = 'api_config.json'
# 检查配置文件是否存在
if not os.path.exists(config_file):
if messagebox.askyesno("配置缺失",
f"未找到API配置文件 '{config_file}'\n\n"
"是否创建默认配置文件?"):
try:
default_config = {
"api_url": "https://your-domain.com/api",
"api_key": "your-api-key-here",
"comment": "请修改上面的配置为你的实际服务器地址和API密钥"
}
with open(config_file, 'w', encoding='utf-8') as f:
json.dump(default_config, f, indent=4, ensure_ascii=False)
messagebox.showinfo("成功",
f"已创建默认配置文件:{config_file}\n\n"
"请编辑此文件填写你的API地址和密钥")
except Exception as e:
messagebox.showerror("错误", f"创建配置文件失败: {str(e)}")
return None
# 加载配置
try:
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
api_url = config.get('api_url', '')
api_key = config.get('api_key', '')
# 验证配置
if not api_url or api_url == 'https://your-domain.com/api':
messagebox.showerror("配置错误",
f"请先编辑 '{config_file}' 文件\n\n"
"将 api_url 修改为你的实际服务器地址")
return None
if not api_key or api_key == 'your-api-key-here':
messagebox.showerror("配置错误",
f"请先编辑 '{config_file}' 文件\n\n"
"将 api_key 修改为你的实际API密钥")
return None
return {
'api_url': api_url,
'api_key': api_key
}
except json.JSONDecodeError as e:
messagebox.showerror("配置错误",
f"配置文件格式错误: {str(e)}\n\n"
f"请检查 '{config_file}' 文件格式")
return None
except Exception as e:
messagebox.showerror("错误",
f"读取配置文件失败: {str(e)}")
return None
def encrypt_selected_software(self):
"""加密选中的软件"""
selected_item = self.tree_software.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择要加密的软件")
return
item_values = self.tree_software.item(selected_item[0], 'values')
software_name = item_values[1]
self.encrypt_software_by_name(software_name)
def encrypt_software_by_name(self, software_name):
"""根据软件名称加密软件"""
if not self.db or not self.db.connection.is_connected():
if not self.connect_db():
return
# 获取软件信息
software = self.db.get_software_by_name(software_name)
if not software:
messagebox.showerror("错误", f"未找到软件: {software_name}")
return
exe_path = software.get('exe_path', '')
if not exe_path:
messagebox.showerror("错误", f"软件 '{software_name}' 没有设置EXE文件路径")
return
# 获取完整路径
if hasattr(self.db, 'get_exe_full_path'):
source_path = self.db.get_exe_full_path(exe_path)
else:
source_path = exe_path
if not os.path.exists(source_path):
messagebox.showerror("错误", f"EXE文件不存在: {source_path}")
return
# 询问加密输出路径(使用版本号)
version = software.get('version', '')
version_suffix = f"_v{version}" if version else "_encrypted"
default_name = f"{software_name}{version_suffix}.exe"
dest_path = filedialog.asksaveasfilename(
title="选择加密后的文件保存位置",
defaultextension=".exe",
initialfile=default_name,
filetypes=[("EXE文件", "*.exe"), ("所有文件", "*.*")]
)
if not dest_path:
return
# 加载API配置
api_config = self._load_api_config()
if not api_config:
return
# 创建进度窗口
progress_window = tk.Toplevel(self)
progress_window.title("加密进度")
progress_window.geometry("400x150")
progress_window.resizable(False, False)
# 居中窗口
progress_window.update_idletasks()
x = (progress_window.winfo_screenwidth() - progress_window.winfo_width()) // 2
y = (progress_window.winfo_screenheight() - progress_window.winfo_height()) // 2
progress_window.geometry(f"400x150+{x}+{y}")
ttk.Label(progress_window, text=f"正在加密: {software_name}",
font=("Microsoft YaHei", 10)).pack(pady=15)
progress_label = ttk.Label(progress_window, text="正在准备...",
foreground="blue")
progress_label.pack(pady=5)
progress_bar = ttk.Progressbar(progress_window, mode='indeterminate', length=350)
progress_bar.pack(pady=10)
progress_bar.start(10)
progress_window.update()
# 执行加密
try:
# 使用新的安全加密器
from encryptor_secure import SecureEXEEncryptor
encryptor = SecureEXEEncryptor()
# 获取软件ID和版本如果有的话
software_id = str(software.get('id', '')) # 使用数据库ID作为软件ID
version = software.get('version', '1.0.0')
success, msg = encryptor.encrypt_exe(
source_path=source_path,
output_path=dest_path,
api_config=api_config,
software_name=software_name,
software_id=software_id if software_id else None,
version=version
)
progress_bar.stop()
progress_window.destroy()
if success:
messagebox.showinfo("成功", f"软件加密成功!\n\n加密文件: {dest_path}")
else:
messagebox.showerror("错误", f"加密失败: {msg}")
except Exception as e:
progress_bar.stop()
progress_window.destroy()
messagebox.showerror("错误", f"加密过程出错: {str(e)}")
if __name__ == "__main__":
app = EXEEncryptionTool()
app.mainloop()