""" 作者:太一 微信: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("", 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("<>", 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("", 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()