1598 lines
67 KiB
Python
1598 lines
67 KiB
Python
"""
|
||
作者:太一
|
||
微信: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() |