diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 288b36b..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,7 +1,6 @@ - \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index cad222e..0000000 --- a/README.md +++ /dev/null @@ -1,574 +0,0 @@ -# ExeProtector - 个人管理版 - -> **简单、安全、高效的软件加密授权系统** -> **适合个人开发者和小团队使用** - -[![版本](https://img.shields.io/badge/版本-2.0-blue.svg)](https://github.com/yourusername/exeprotector) -[![Python](https://img.shields.io/badge/Python-3.7+-green.svg)](https://www.python.org/) -[![许可证](https://img.shields.io/badge/许可证-MIT-yellow.svg)](LICENSE) - ---- - -## 📖 项目简介 - -ExeProtector 是一个专为个人开发者设计的软件加密授权系统,帮助你保护自己的软件产品,防止盗版和未授权使用。 - -### ✨ 核心特点 - -- 🔐 **真正的加密**:采用 AES-256 加密算法,不是简单的编码 -- 🌐 **云端验证**:授权验证在云服务器进行,客户端无法绕过 -- 🎨 **友好界面**:Tkinter 图形界面,操作简单直观 -- 💾 **数据库管理**:MySQL 存储授权数据,稳定可靠 -- 🚀 **轻量部署**:最低 30 元/月,30 分钟完成部署 -- 🔒 **安全隔离**:数据库密码不会暴露给客户端 - -### 🎯 适用场景 - -- ✅ 个人开发者想要保护自己的软件 -- ✅ 小团队需要简单的授权管理系统 -- ✅ 软件售价在 100-1000 元之间 -- ✅ 对安全性有一定要求,但预算有限 -- ✅ 希望保持现有的管理方式 - ---- - -## 🏗️ 系统架构 - -``` -┌─────────────────────────────────┐ -│ 你的电脑(管理端) │ -│ ┌─────────────────────────┐ │ -│ │ main.py 管理界面 │ │ -│ │ • 生成卡密 │ │ -│ │ • 管理授权 │ │ -│ │ • 加密软件 │ │ -│ └─────────────────────────┘ │ -│ ↓ 直连 │ -└─────────────────────────────────┘ - ↓ -┌─────────────────────────────────┐ -│ 云服务器(验证端) │ -│ ┌─────────────────────────┐ │ -│ │ api_server_lite.py │ │ -│ │ • 验证卡密 │ │ -│ │ • 激活授权 │ │ -│ └─────────────────────────┘ │ -│ ↓ │ -│ ┌─────────────────────────┐ │ -│ │ MySQL 数据库 │ │ -│ └─────────────────────────┘ │ -└─────────────────────────────────┘ - ↑ -┌─────────────────────────────────┐ -│ 用户电脑(客户端) │ -│ ┌─────────────────────────┐ │ -│ │ 加密的 EXE 程序 │ │ -│ │ • 输入卡密激活 │ │ -│ │ • 联网验证 │ │ -│ │ • 运行受保护的程序 │ │ -│ └─────────────────────────┘ │ -└─────────────────────────────────┘ -``` - ---- - -## 📦 文件结构 - -``` -Exeprotector/ -├── 📄 核心代码文件 -│ ├── main.py # 管理界面(Tkinter) -│ ├── database.py # 数据库操作 -│ ├── config.py # 配置文件 -│ ├── machine_code.py # 机器码生成 -│ ├── api_server_lite.py # 轻量级API服务器(部署到云端) -│ ├── validator_secure.py # 安全验证器(嵌入到加密EXE) -│ └── encryptor_secure.py # AES加密器 -│ -├── 📁 数据目录 -│ ├── licenses_local.db # 本地SQLite数据库(可选) -│ ├── db_config.json # 数据库配置 -│ └── files/ # 文件存储目录 -│ ├── executables/ # 加密后的EXE文件 -│ ├── backups/ # 备份文件 -│ └── file_metadata.json # 文件元数据 -│ -└── 📚 文档文件 - ├── README.md # 本文件 - ├── 个人版_开始这里.md # ⭐ 快速入门指南 - ├── 个人版_快速部署指南.md # 详细部署教程 - ├── 个人版_文件清单.md # 文件说明 - ├── 方案_个人管理版.md # 核心方案文档 - └── env_config_example.txt # 环境变量配置示例 -``` - ---- - -## 🚀 快速开始 - -### 准备工作 - -在开始之前,请确保你有: - -- [x] 一台云服务器(1核1G 即可,约 30 元/月) -- [x] Python 3.7+ 环境 -- [x] MySQL 数据库 -- [x] 30-40 分钟的时间 - -### 第一步:阅读文档(5分钟) - -**⭐ 强烈推荐先阅读:** - -```bash -个人版_开始这里.md -``` - -这个文档会告诉你: -- 为什么选择这个方案 -- 系统是如何工作的 -- 成本和效果如何 -- 是否适合你的需求 - -### 第二步:部署 API 服务器(15分钟) - -**详细教程请查看:** - -```bash -个人版_快速部署指南.md -``` - -简要步骤: - -1. 上传 `api_server_lite.py` 到云服务器 -2. 安装依赖: - ```bash - pip3 install flask mysql-connector-python - ``` -3. 配置环境变量 -4. 启动服务 - -### 第三步:配置本地管理程序(5分钟) - -1. 安装依赖: - ```bash - pip install mysql-connector-python cryptography requests - ``` - -2. 修改 `main.py` 中的加密配置(约第 1365 行): - - ```python - # 在 encrypt_software_by_name 方法中 - from encryptor_secure import SecureEXEEncryptor - - api_config = { - 'api_url': 'https://your-domain.com/api', # 改成你的服务器地址 - 'api_key': 'your-api-key-here' # 改成你的API密钥 - } - - encryptor = SecureEXEEncryptor() - ``` - -3. 配置数据库连接(`db_config.json`) - -### 第四步:测试运行(10分钟) - -1. 启动管理程序: - ```bash - python main.py - ``` - -2. 测试流程: - - 连接数据库 - - 添加软件产品 - - 生成卡密 - - 加密测试EXE - - 在另一台电脑测试激活 - -**完成!** 🎉 - ---- - -## 💡 使用指南 - -### 管理卡密 - -1. 运行 `main.py` -2. 连接数据库 -3. 在"卡密管理"标签页: - - 生成卡密 - - 查看卡密状态 - - 管理到期时间 - - 封禁/解封卡密 - -### 加密软件 - -1. 在"软件管理"标签页添加软件信息 -2. 选择要加密的 EXE 文件 -3. 点击"加密软件" -4. 等待加密完成 -5. 在 `files/executables/` 目录获取加密后的 EXE - -### 分发给用户 - -1. 将加密后的 EXE 发送给用户 -2. 用户运行 EXE 后会显示机器码 -3. 用户提供机器码给你 -4. 你在管理界面生成对应的卡密 -5. 用户输入卡密完成激活 - ---- - -## 🔧 配置说明 - -### 数据库配置 - -编辑 `db_config.json`: - -```json -{ - "host": "your-mysql-host", - "port": 3306, - "user": "your-username", - "password": "your-password", - "database": "filesend_db" -} -``` - -### API 服务器配置 - -在云服务器上设置环境变量: - -```bash -export DB_HOST=localhost -export DB_USER=your-username -export DB_PASSWORD=your-password -export DB_NAME=filesend_db -export API_KEY=$(python3 -c "import os; print(os.urandom(32).hex())") -``` - -### 安全建议 - -1. **必须修改 API 密钥** - - 不要使用默认值 - - 使用强随机密钥 - -2. **启用 HTTPS** - - 申请免费 SSL 证书(Let's Encrypt) - - 通过 Nginx 配置反向代理 - -3. **定期备份数据库** - ```bash - mysqldump -u username -p database_name > backup.sql - ``` - -4. **限制 SSH 访问** - - 使用密钥认证 - - 禁用密码登录 - - 配置 fail2ban - ---- - -## 💰 成本分析 - -| 项目 | 费用 | 说明 | -|------|------|------| -| 云服务器 | ¥30-50/月 | 1核1G 已足够 | -| 域名 | ¥50-100/年 | 可选但推荐 | -| SSL 证书 | 免费 | Let's Encrypt | -| **总计** | **¥50/月** | 约 **¥600/年** | - -### 投资回报率(ROI) - -假设: -- 软件售价:¥100 -- 用户数:1000 -- 盗版率:从 50% 降到 20% - -**额外收入**:1000 × ¥100 × 30% = **¥30,000/年** - -**投资回报率**:(30,000 - 600) / 600 × 100% = **4900%** - ---- - -## 🔒 安全性说明 - -### 修复前的问题 - -- ❌ 数据库密码硬编码在客户端 -- ❌ 使用 hex 编码(伪加密) -- ❌ 验证逻辑在客户端 -- ❌ PyInstaller 容易反编译 -- ❌ 破解难度:5 分钟 - -### 修复后的改进 - -- ✅ 数据库密码只在云服务器 -- ✅ 使用 AES-256 真加密 -- ✅ 验证逻辑在云端 -- ✅ API 密钥保护 -- ✅ 破解难度:2-5 天 - -**安全评分:从 2/10 提升到 6.5/10** - -### 安全等级说明 - -| 等级 | 破解时间 | 适用场景 | 成本 | -|------|---------|---------|------| -| 2/10 | 5分钟 | ❌ 不适用 | - | -| 6.5/10 | 2-5天 | ✅ 个人/小团队 | ¥600/年 | -| 8/10 | 数周 | 企业级 | ¥5,000+/年 | -| 9.5/10 | 数月 | 军工级 | ¥50,000+/年 | - -**本方案适合软件售价在 100-1000 元的个人开发者。** - ---- - -## 🐛 故障排查 - -### API 服务器无法访问 - -```bash -# 检查服务状态 -systemctl status license-api - -# 检查端口 -netstat -tulpn | grep 5000 - -# 检查防火墙 -ufw status - -# 查看日志 -journalctl -u license-api -f -``` - -### 数据库连接失败 - -```bash -# 测试连接 -mysql -h host -u user -p - -# 检查环境变量 -echo $DB_HOST -echo $DB_USER -``` - -### 加密失败 - -1. 检查 `validator_secure.py` 是否存在 -2. 检查 `encryptor_secure.py` 是否存在 -3. 检查依赖是否安装: - ```bash - pip list | grep cryptography - ``` - -### 激活失败 - -1. 检查 API 服务器日志 -2. 确认 API 密钥配置一致 -3. 测试网络连接 -4. 检查数据库中的软件名称 - -更多问题请查看:`个人版_快速部署指南.md` - ---- - -## 📊 性能指标 - -### API 服务器(1核1G) - -- 并发请求:50-100 -- 响应时间:< 50ms -- 内存占用:< 100MB -- CPU 占用:< 10% - -### 支持规模 - -- 日活用户:500+ -- 月活用户:2000+ -- 峰值 QPS:50 - ---- - -## 🔄 版本历史 - -### v2.0(当前版本) - -- ✅ 采用 AES-256 真加密 -- ✅ 云端 API 验证 -- ✅ 数据库密码隔离 -- ✅ 请求签名保护 -- ✅ 完整的文档 - -### v1.0(已废弃) - -- ❌ hex 编码(不安全) -- ❌ 客户端验证 -- ❌ 数据库密码硬编码 - ---- - -## 📞 技术支持 - -### 免费支持 - -- 📖 查看文档(推荐先阅读文档) -- 💬 邮件咨询:shoubo1224@qq.com -- 📱 微信:taiyi1224 - -### 付费服务 - -- 🔧 远程部署:¥200/次(包成功) -- 📞 技术支持:¥500/月 -- 🎓 一对一培训:¥500/小时 -- 🛠️ 定制开发:¥1000+ 起 - ---- - -## ❓ 常见问题 - -### Q1: 必须要有域名吗? - -**A:** 不必须,但强烈推荐。 - -- 没有域名:只能用 IP,无法使用 HTTPS,不够安全 -- 有域名:支持 HTTPS,更稳定、更专业 - -域名很便宜(¥50-100/年),值得投资。 - -### Q2: 数据库会被改动吗? - -**A:** 完全不会! - -- 不需要迁移数据 -- 不需要修改表结构 -- 现有功能完全正常 -- 只是加了一个 API 层 - -### Q3: 部署很难吗? - -**A:** 非常简单! - -如果你会: -- SSH 登录服务器 -- 复制粘贴命令 -- 修改几行代码 - -那就完全没问题!预计 30-40 分钟。 - -### Q4: 维护麻烦吗? - -**A:** 几乎不需要维护! - -日常操作: -- 管理卡密 - 用现有界面(不变) -- 加密软件 - 用现有界面(不变) -- 查看统计 - 用现有界面(不变) - -偶尔需要: -- 重启 API 服务器(很少) -- 查看日志(有问题时) -- 备份数据库(建议每周) - -### Q5: 可以破解吗? - -**A:** 任何加密都可能被破解,但: - -- 本方案破解时间:2-5 天 -- 对于普通用户:难度太高 -- 对于专业黑客:成本太高 - -**破解成本 > 购买成本 = 有效保护** - -如果软件售价很高(>¥5000),建议使用更高级的方案。 - -### Q6: 适合我吗? - -**适合:** -- ✅ 个人开发者 -- ✅ 小团队(< 10人) -- ✅ 软件售价 ¥100-1000 -- ✅ 预算有限 -- ✅ 需要基本保护 - -**不适合:** -- ❌ 大型企业 -- ❌ 软件售价 > ¥5000 -- ❌ 需要军工级安全 -- ❌ 有专业安全团队 - ---- - -## 🎓 学习路径 - -### 第1天:理解方案 - -1. 阅读 `README.md`(本文件) -2. 阅读 `个人版_开始这里.md` -3. 阅读 `方案_个人管理版.md` - -### 第2天:部署系统 - -1. 按照 `个人版_快速部署指南.md` 部署 -2. 配置 API 服务器 -3. 修改本地程序 - -### 第3天:测试优化 - -1. 测试加密功能 -2. 测试激活流程 -3. 优化配置 - -### 第4天:了解安全原理(可选) - -1. 学习 AES 加密原理 -2. 学习 API 认证机制 -3. 学习安全最佳实践 - ---- - -## 📝 许可证 - -本项目采用 MIT 许可证。 - -``` -MIT License - -Copyright (c) 2025 ExeProtector - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction... -``` - ---- - -## 🙏 致谢 - -感谢所有使用和支持本项目的开发者! - -特别感谢: -- Python 社区 -- Flask 框架 -- MySQL 数据库 -- Let's Encrypt - ---- - -## 🚀 立即开始 - -**准备好了吗?现在就开始部署吧!** - -👉 **下一步:打开 `个人版_开始这里.md`** - -预计时间:30-40 分钟 -预计成本:¥50/月 - -**你绝对可以做到!** 💪 - ---- - -*最后更新:2025-10-23* -*作者:太一* -*微信:taiyi1224* -*邮箱:shoubo1224@qq.com* - diff --git a/api_config.json b/api_config.json index 9c316b4..bace7c9 100644 --- a/api_config.json +++ b/api_config.json @@ -1,6 +1,6 @@ { - "api_url": "https://129.211.65.73:5100/", + "api_url": "http://localhost:5100/", "api_key": "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224", - "comment": "请修改上面的配置为你的实际服务器地址和API密钥" + "comment": "使用本地API服务器。如果需要远程验证,改为远程服务器地址" } diff --git a/api_server_lite.py b/api_server_lite.py index 9ed4812..ea8b8b6 100644 --- a/api_server_lite.py +++ b/api_server_lite.py @@ -260,13 +260,20 @@ def validate(): 'message': '此激活码已在其他设备上使用' }) else: - # 验证成功,缓存结果 - result = {'success': True, 'message': '验证成功'} + # 验证成功,缓存结果,返回到期时间 + remaining_days = (key_info['end_time'] - datetime.now()).days + result = { + 'success': True, + 'message': '验证成功', + 'expires_at': key_info['end_time'].isoformat(), + 'remaining_days': max(0, remaining_days) + } cache[cache_key] = (time.time(), result) log_request('validate_success', '验证成功', { 'license_key': license_key[:10] + '...', - 'machine_code': machine_code + 'machine_code': machine_code, + 'remaining_days': remaining_days }) return jsonify(result) @@ -312,14 +319,28 @@ def validate(): 'message': '激活失败,请重试' }), 500 - # 激活成功,缓存结果 - result = {'success': True, 'message': '激活成功'} + # 激活成功,缓存结果,返回到期时间 + # 重新查询获取完整信息 + cursor.execute( + 'SELECT * FROM license_keys WHERE key_code=%s', + (license_key,) + ) + activated_key = cursor.fetchone() + + remaining_days = (activated_key['end_time'] - datetime.now()).days + result = { + 'success': True, + 'message': '激活成功', + 'expires_at': activated_key['end_time'].isoformat(), + 'remaining_days': max(0, remaining_days) + } cache[cache_key] = (time.time(), result) log_request('activate_success', '首次激活成功', { 'license_key': license_key[:10] + '...', 'machine_code': machine_code, - 'software_name': software_name + 'software_name': software_name, + 'remaining_days': remaining_days }) return jsonify(result) @@ -355,6 +376,107 @@ def validate(): 'message': '服务器内部错误' }), 500 +@app.route('/api/license/status', methods=['POST']) +@require_api_key +def license_status(): + """ + 查询许可证状态接口 + 用于客户端定期刷新状态 + + 请求格式: + { + "license_key": "XXXXX-XXXXX-XXXXX-XXXXX", + "machine_code": "1234567890ABCDEF", + "software_name": "MyApp" + } + + 返回格式: + { + "success": true/false, + "status": "active/expired/banned", + "end_time": "2025-12-31T23:59:59", + "remaining_days": 30, + "is_expired": false, + "needs_renewal": false + } + """ + try: + data = request.get_json() + license_key = data.get('license_key', '').strip() + machine_code = data.get('machine_code', '').strip() + software_name = data.get('software_name', '').strip() + + if not all([license_key, machine_code, software_name]): + return jsonify({ + 'success': False, + 'message': '缺少必要参数' + }), 400 + + conn = get_db() + cursor = conn.cursor(dictionary=True) + + try: + # 获取软件ID + cursor.execute( + 'SELECT id FROM software_products WHERE name=%s', + (software_name,) + ) + software = cursor.fetchone() + + if not software: + return jsonify({ + 'success': False, + 'message': '软件未注册' + }) + + # 查询许可证信息 + cursor.execute(''' + SELECT * FROM license_keys + WHERE key_code=%s AND software_id=%s + ''', (license_key, software['id'])) + + key_info = cursor.fetchone() + + if not key_info: + return jsonify({ + 'success': False, + 'message': '许可证不存在' + }) + + # 验证机器码 + if key_info['machine_code'] and key_info['machine_code'] != machine_code: + return jsonify({ + 'success': False, + 'message': '设备不匹配' + }) + + # 计算剩余天数 + end_time = key_info['end_time'] + remaining_days = (end_time - datetime.now()).days + is_expired = remaining_days < 0 + needs_renewal = remaining_days <= 7 + + # 返回状态信息 + return jsonify({ + 'success': True, + 'status': key_info['status'], + 'end_time': end_time.isoformat(), + 'remaining_days': max(0, remaining_days), + 'is_expired': is_expired, + 'needs_renewal': needs_renewal + }) + + finally: + cursor.close() + conn.close() + + except Exception as e: + log_request('status_error', str(e)) + return jsonify({ + 'success': False, + 'message': '查询失败' + }), 500 + @app.route('/api/stats', methods=['GET']) @require_api_key def stats(): diff --git a/check_compile_env.py b/check_compile_env.py deleted file mode 100644 index b61a6fa..0000000 --- a/check_compile_env.py +++ /dev/null @@ -1,231 +0,0 @@ -""" -编译环境检查工具 -用于诊断 PyInstaller 编译问题 -作者:太一 -""" - -import sys -import subprocess -import os - -def check_python(): - """检查Python版本""" - print("\n" + "="*60) - print("【1】检查 Python 环境") - print("="*60) - version = sys.version - print(f"✓ Python 版本: {version}") - print(f"✓ Python 路径: {sys.executable}") - - if sys.version_info < (3, 7): - print("⚠️ 警告: Python 版本过低,建议使用 Python 3.7+") - return False - return True - -def check_pip(): - """检查pip""" - print("\n" + "="*60) - print("【2】检查 pip") - print("="*60) - try: - result = subprocess.run([sys.executable, '-m', 'pip', '--version'], - capture_output=True, text=True, timeout=10) - if result.returncode == 0: - print(f"✓ pip 版本: {result.stdout.strip()}") - return True - else: - print("✗ pip 不可用") - return False - except Exception as e: - print(f"✗ 检查 pip 失败: {e}") - return False - -def check_module(module_name): - """检查模块是否安装""" - try: - __import__(module_name) - return True - except ImportError: - return False - -def check_dependencies(): - """检查依赖项""" - print("\n" + "="*60) - print("【3】检查必需的依赖包") - print("="*60) - - required = { - 'pyinstaller': 'PyInstaller', - 'cryptography': 'cryptography', - 'requests': 'requests', - 'tkinter': 'tkinter', - } - - all_ok = True - for module, name in required.items(): - if check_module(module): - try: - if module == 'pyinstaller': - import pyinstaller - version = pyinstaller.__version__ - elif module == 'cryptography': - import cryptography - version = cryptography.__version__ - elif module == 'requests': - import requests - version = requests.__version__ - elif module == 'tkinter': - import tkinter - version = tkinter.TkVersion - else: - version = "已安装" - print(f"✓ {name:20s} - 版本 {version}") - except: - print(f"✓ {name:20s} - 已安装") - else: - print(f"✗ {name:20s} - 未安装") - all_ok = False - - return all_ok - -def check_pyinstaller(): - """检查PyInstaller""" - print("\n" + "="*60) - print("【4】检查 PyInstaller") - print("="*60) - try: - result = subprocess.run(['pyinstaller', '--version'], - capture_output=True, text=True, timeout=10) - if result.returncode == 0: - version = result.stdout.strip() - print(f"✓ PyInstaller 版本: {version}") - - # 测试简单编译 - print("\n测试简单的编译...") - test_py = "_test_compile.py" - test_exe = "_test_compile.exe" - - # 创建测试文件 - with open(test_py, 'w', encoding='utf-8') as f: - f.write('print("Hello, PyInstaller!")\ninput("按回车键退出...")') - - cmd = [ - 'pyinstaller', '--onefile', '--clean', '--noconfirm', - '--distpath', '.', '--workpath', '.build_test', - '--specpath', '.build_test', '--noupx', - test_py - ] - - print(f"执行命令: {' '.join(cmd)}") - result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) - - if result.returncode == 0 and os.path.exists(test_exe): - print(f"✓ 编译测试成功!生成文件: {test_exe}") - print(f" 文件大小: {os.path.getsize(test_exe) / 1024 / 1024:.2f} MB") - - # 清理测试文件 - try: - os.remove(test_py) - os.remove(test_exe) - import shutil - if os.path.exists('.build_test'): - shutil.rmtree('.build_test') - if os.path.exists('_test_compile.spec'): - os.remove('_test_compile.spec') - print(" 测试文件已清理") - except: - pass - - return True - else: - print("✗ 编译测试失败") - if result.stderr: - print("\n错误信息:") - print(result.stderr[-1000:]) - return False - else: - print("✗ PyInstaller 不可用") - print("请运行: pip install pyinstaller") - return False - except FileNotFoundError: - print("✗ PyInstaller 未安装或不在系统路径中") - print("请运行: pip install pyinstaller") - return False - except Exception as e: - print(f"✗ 检查 PyInstaller 失败: {e}") - return False - -def check_disk_space(): - """检查磁盘空间""" - print("\n" + "="*60) - print("【5】检查磁盘空间") - print("="*60) - try: - import shutil - total, used, free = shutil.disk_usage(os.getcwd()) - - print(f"当前目录: {os.getcwd()}") - print(f"总空间: {total / (1024**3):.2f} GB") - print(f"已使用: {used / (1024**3):.2f} GB") - print(f"可用: {free / (1024**3):.2f} GB") - - if free < 1 * 1024**3: # 小于1GB - print("⚠️ 警告: 可用空间不足 1GB,编译可能失败") - return False - else: - print("✓ 磁盘空间充足") - return True - except Exception as e: - print(f"⚠️ 无法检查磁盘空间: {e}") - return True - -def main(): - """主函数""" - print("\n" + "█"*60) - print("█" + " "*58 + "█") - print("█" + " 编译环境诊断工具 - EXE Encryption Tool ".center(58) + "█") - print("█" + " "*58 + "█") - print("█"*60) - - results = [] - - results.append(("Python", check_python())) - results.append(("pip", check_pip())) - results.append(("依赖包", check_dependencies())) - results.append(("PyInstaller", check_pyinstaller())) - results.append(("磁盘空间", check_disk_space())) - - print("\n" + "="*60) - print("【检查结果汇总】") - print("="*60) - - all_passed = True - for name, passed in results: - status = "✓ 通过" if passed else "✗ 失败" - print(f"{name:20s} {status}") - if not passed: - all_passed = False - - print("="*60) - - if all_passed: - print("\n🎉 所有检查通过!编译环境正常。") - print("\n如果仍然遇到编译问题,请检查:") - print(" 1. 杀毒软件是否拦截") - print(" 2. 防火墙设置") - print(" 3. 用户权限(建议以管理员身份运行)") - print(" 4. 临时文件夹权限") - else: - print("\n❌ 部分检查失败,请根据上述提示修复问题。") - print("\n常见解决方案:") - print(" 1. 安装缺失的包: pip install -r requirements.txt") - print(" 2. 升级 PyInstaller: pip install --upgrade pyinstaller") - print(" 3. 清理 pip 缓存: pip cache purge") - print(" 4. 重新安装依赖: pip uninstall pyinstaller && pip install pyinstaller") - - print("\n") - input("按回车键退出...") - -if __name__ == '__main__': - main() - diff --git a/check_dependencies.py b/check_dependencies.py deleted file mode 100644 index f6d0e95..0000000 --- a/check_dependencies.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -""" -依赖检查脚本 -检查系统是否安装了所有必需的Python包 - -作者:太一 -""" - -import sys -import subprocess - -def check_package(package_name, import_name=None): - """检查单个包是否安装""" - if import_name is None: - import_name = package_name.replace('-', '_') - - try: - __import__(import_name) - print(f"✅ {package_name:30s} - 已安装") - return True - except ImportError: - print(f"❌ {package_name:30s} - 未安装") - return False - -def main(): - """主函数""" - print("=" * 60) - print("ExeProtector 依赖检查") - print("=" * 60) - print() - - # 必需的包 - required_packages = { - 'mysql-connector-python': 'mysql.connector', - 'cryptography': 'cryptography', - 'requests': 'requests', - 'pyperclip': 'pyperclip', - } - - # 可选的包 - optional_packages = { - 'flask': 'flask', # 仅API服务器需要 - 'pyinstaller': 'PyInstaller', # 仅打包需要 - } - - # 检查必需包 - print("检查必需依赖:") - print("-" * 60) - missing_required = [] - for package, import_name in required_packages.items(): - if not check_package(package, import_name): - missing_required.append(package) - - print() - - # 检查可选包 - print("检查可选依赖:") - print("-" * 60) - missing_optional = [] - for package, import_name in optional_packages.items(): - if not check_package(package, import_name): - missing_optional.append(package) - - print() - print("=" * 60) - - # 总结 - if not missing_required and not missing_optional: - print("✅ 所有依赖都已安装!") - print() - return 0 - elif not missing_required: - print("✅ 所有必需依赖都已安装") - print(f"⚠️ 有 {len(missing_optional)} 个可选依赖未安装") - print() - print("如果需要安装可选依赖:") - for pkg in missing_optional: - print(f" pip install {pkg}") - print() - return 0 - else: - print(f"❌ 有 {len(missing_required)} 个必需依赖未安装") - print() - print("请运行以下命令安装缺失的依赖:") - print() - print(" pip install -r requirements.txt") - print() - print("或者逐个安装:") - for pkg in missing_required: - print(f" pip install {pkg}") - print() - return 1 - -if __name__ == '__main__': - try: - sys.exit(main()) - except KeyboardInterrupt: - print("\n\n中断执行") - sys.exit(1) - except Exception as e: - print(f"\n\n错误: {e}") - sys.exit(1) - diff --git a/encryptor_secure.py b/encryptor_secure.py index f19e322..9174c4c 100644 --- a/encryptor_secure.py +++ b/encryptor_secure.py @@ -1,5 +1,5 @@ """ -安全的EXE加密器 - 使用AES加密 +安全的EXE加密器 V2 - 使用改进的验证器 作者:太一 """ @@ -14,7 +14,7 @@ from cryptography.fernet import Fernet import base64 class SecureEXEEncryptor: - """安全的EXE加密器""" + """安全的EXE加密器 V2(改进版)""" def __init__(self): self.encryption_key = None @@ -24,7 +24,8 @@ class SecureEXEEncryptor: return Fernet.generate_key() def encrypt_exe(self, source_path: str, output_path: str, - api_config: dict, software_name: str) -> Tuple[bool, str]: + api_config: dict, software_name: str, + use_v2_validator: bool = True) -> Tuple[bool, str]: """ 加密EXE文件 @@ -33,6 +34,7 @@ class SecureEXEEncryptor: output_path: 输出路径 api_config: API配置(不包含数据库密码) software_name: 软件名称 + use_v2_validator: 是否使用V2版本的验证器(推荐) """ try: print(f"开始加密: {source_path}") @@ -55,23 +57,25 @@ class SecureEXEEncryptor: # 4. 计算哈希 file_hash = hashlib.sha256(original_content).hexdigest() - # 5. 准备资源数据(只包含加密后的数据和密钥提示) - # 实际密钥应该分拆:一部分硬编码,一部分从服务器获取 - key_part1 = base64.b64encode(self.encryption_key[:16]).decode() # 硬编码部分 - key_part2 = base64.b64encode(self.encryption_key[16:]).decode() # 应该从服务器获取 + # 5. 准备资源数据 + key_part1 = base64.b64encode(self.encryption_key[:16]).decode() + key_part2 = base64.b64encode(self.encryption_key[16:]).decode() resource_data = { 'data': base64.b64encode(encrypted_content).decode(), 'hash': file_hash, - 'key_hint': key_part1, # 前半部分密钥 - 'key_part2': key_part2, # 后半部分密钥(应该混淆存储) + 'key_hint': key_part1, + 'key_part2': key_part2, } - # 6. 读取验证器模板 - validator_template_path = "validator_secure.py" + # 6. 选择验证器模板 + validator_template_path = "validator_secure.py" if use_v2_validator else "validator_secure.py" + if not os.path.exists(validator_template_path): return False, f"找不到验证器模板: {validator_template_path}" + print(f"使用验证器模板: {validator_template_path}") + with open(validator_template_path, 'r', encoding='utf-8') as f: template_code = f.read() @@ -81,16 +85,20 @@ class SecureEXEEncryptor: f'ENCRYPTED_RESOURCE = {json.dumps(resource_data)}' ) - final_code = final_code.replace( - 'API_BASE_URL = "https://your-server.com/api"', - f'API_BASE_URL = "{api_config.get("api_url", "https://your-server.com/api")}"' - ) + # 替换API配置 + if 'api_url' in api_config: + final_code = final_code.replace( + 'API_BASE_URL = "http://localhost:5100/"', + f'API_BASE_URL = "{api_config["api_url"]}"' + ) - final_code = final_code.replace( - 'API_KEY = "your-secure-api-key-here"', - f'API_KEY = "{api_config.get("api_key", "your-secure-api-key-here")}"' - ) + if 'api_key' in api_config: + final_code = final_code.replace( + 'API_KEY = "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224"', + f'API_KEY = "{api_config["api_key"]}"' + ) + # 替换软件名称 final_code = final_code.replace( 'SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER"', f'SOFTWARE_NAME = "{software_name}"' @@ -113,14 +121,34 @@ class SecureEXEEncryptor: f.write(self.encryption_key) print(f"⚠️ 密钥已保存到: {key_file}") print(f"⚠️ 请妥善保管此文件,建议加密存储!") + + # 显示改进说明 + if use_v2_validator: + print("\n" + "="*60) + print("✅ 已使用V2验证器,包含以下改进:") + print(" 1. 持久化缓存存储(不会被系统清理)") + print(" 2. 智能验证策略(减少重复验证)") + print(" 3. 到期提醒功能(提前7天开始提醒)") + print(" 4. 增强的离线验证") + print(" 5. 改善的用户界面") + print("="*60 + "\n") + + # 清理临时文件 + try: + os.remove(temp_py) + except: + pass return success, msg except Exception as e: + import traceback + error_detail = traceback.format_exc() + print(f"加密过程出错: {error_detail}") return False, f"加密过程出错: {str(e)}" def _compile_to_exe(self, python_file: str, output_path: str) -> Tuple[bool, str]: - """编译Python文件为EXE(改进版)""" + """编译Python文件为EXE""" build_dir = None try: # 1. 检查PyInstaller @@ -133,7 +161,7 @@ class SecureEXEEncryptor: pyinstaller_version = result.stdout.strip() print(f"PyInstaller 版本: {pyinstaller_version}") - # 2. 创建固定的工作目录(避免临时目录权限问题) + # 2. 创建工作目录 build_dir = os.path.join(os.path.dirname(os.path.abspath(output_path)), '.build_temp') os.makedirs(build_dir, exist_ok=True) print(f"工作目录: {build_dir}") @@ -144,19 +172,19 @@ class SecureEXEEncryptor: shutil.copy2(python_file, work_py) print(f"工作文件: {work_py}") - # 4. 使用简化的命令行方式(更稳定) + # 4. 构建PyInstaller命令 exe_name = os.path.splitext(os.path.basename(output_path))[0] cmd = [ 'pyinstaller', - '--clean', # 清理缓存 - '--noconfirm', # 不确认覆盖 - '--onefile', # 单文件模式 - '--windowed', # 无控制台窗口 - '--name', exe_name, # 输出文件名 - '--distpath', build_dir, # 输出目录 - '--workpath', os.path.join(build_dir, 'build'), # 工作目录 - '--specpath', build_dir, # spec文件目录 + '--clean', + '--noconfirm', + '--onefile', + '--windowed', + '--name', exe_name, + '--distpath', build_dir, + '--workpath', os.path.join(build_dir, 'build'), + '--specpath', build_dir, # 添加隐藏导入 '--hidden-import', 'cryptography.fernet', '--hidden-import', 'cryptography.hazmat.primitives', @@ -166,9 +194,10 @@ class SecureEXEEncryptor: '--hidden-import', 'tkinter', '--hidden-import', 'tkinter.messagebox', '--hidden-import', '_tkinter', + '--hidden-import', 'sqlite3', # 收集所有cryptography包 '--collect-all', 'cryptography', - # 禁用UPX(避免压缩问题) + # 禁用UPX '--noupx', work_py ] @@ -176,12 +205,12 @@ class SecureEXEEncryptor: print("开始编译...") print(f"命令: {' '.join(cmd)}") - # 5. 执行编译(增加超时时间) + # 5. 执行编译 result = subprocess.run( cmd, capture_output=True, text=True, - timeout=600, # 10分钟超时 + timeout=600, cwd=build_dir, encoding='utf-8', errors='replace' @@ -190,11 +219,11 @@ class SecureEXEEncryptor: # 6. 输出详细日志 if result.stdout: print("=== 编译输出 ===") - print(result.stdout[-2000:]) # 最后2000字符 + print(result.stdout[-2000:]) if result.stderr: print("=== 编译错误 ===") - print(result.stderr[-2000:]) # 最后2000字符 + print(result.stderr[-2000:]) # 7. 检查编译结果 if result.returncode == 0: @@ -240,34 +269,43 @@ class SecureEXEEncryptor: print(f"编译异常: {error_detail}") return False, f"编译过程出错: {str(e)}\n\n详细信息:\n{error_detail[-500:]}" finally: - # 确保清理临时文件(如果编译失败) + # 清理临时文件(如果编译失败) if build_dir and os.path.exists(build_dir): try: - # 只在编译失败时保留,方便调试 if not os.path.exists(output_path): print(f"⚠️ 编译失败,工作目录已保留用于调试: {build_dir}") except: pass - + # ========== 测试代码 ========== if __name__ == '__main__': + print("EXE加密器 V2 - 测试") + print("=" * 60) + # 测试加密 encryptor = SecureEXEEncryptor() # 配置 api_config = { - 'api_url': 'https://your-server.com/api', - 'api_key': 'your-secure-api-key-here' + 'api_url': 'http://localhost:5100/', + 'api_key': 'taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224' } - # 加密 - success, msg = encryptor.encrypt_exe( - source_path='test.exe', - output_path='test_encrypted.exe', - api_config=api_config, - software_name='TestApp' - ) + # 示例:加密一个测试EXE + # success, msg = encryptor.encrypt_exe( + # source_path='test.exe', + # output_path='test_encrypted_v2.exe', + # api_config=api_config, + # software_name='TestApp', + # use_v2_validator=True # 使用V2验证器 + # ) - print(f"结果: {success} - {msg}") + # print(f"\n结果: {success}") + # print(f"消息: {msg}") + + print("\n使用说明:") + print("1. 在main.py中使用此加密器") + print("2. 设置 use_v2_validator=True 使用改进版验证器") + print("3. V2验证器包含持久化缓存、到期提醒等改进功能") diff --git a/env_config_example.txt b/env_config_example.txt deleted file mode 100644 index 7236e8d..0000000 --- a/env_config_example.txt +++ /dev/null @@ -1,36 +0,0 @@ -# API服务器环境变量配置示例 -# 将这些内容添加到服务器的 ~/.bashrc 或 systemd 服务文件中 - -# ========== 数据库配置 ========== -export DB_HOST=localhost -export DB_USER=taiyi -export DB_PASSWORD=taiyi1224 -export DB_NAME=filesend_db - -# ========== API配置 ========== -# ⚠️ 必须修改为随机字符串! -# 生成方法:python3 -c "import os; print(os.urandom(32).hex())" -export API_KEY=change-this-to-random-string-at-least-32-characters-long - -# ========== 使用方法 ========== - -# 方法1:添加到 ~/.bashrc(推荐) -# 1. nano ~/.bashrc -# 2. 复制上面的 export 语句到文件末尾 -# 3. 修改为实际值 -# 4. source ~/.bashrc - -# 方法2:在 systemd 服务文件中配置 -# 在 /etc/systemd/system/license-api.service 的 [Service] 部分添加: -# Environment="DB_HOST=localhost" -# Environment="DB_USER=taiyi" -# Environment="DB_PASSWORD=taiyi1224" -# Environment="DB_NAME=filesend_db" -# Environment="API_KEY=你的密钥" - -# ========== 安全提示 ========== -# ⚠️ API_KEY 必须修改为强随机字符串(至少32个字符) -# ⚠️ 不要在公共场合分享这些配置 -# ⚠️ 定期更换 API_KEY 以提高安全性 -# ⚠️ 使用 chmod 600 限制配置文件权限 - diff --git a/files/executables/config.ini b/files/executables/config.ini new file mode 100644 index 0000000..52654f4 --- /dev/null +++ b/files/executables/config.ini @@ -0,0 +1,43 @@ +[General] +chrome_user_dir = C:\Users\23391\AppData\Local\Google\Chrome\User Data +articles_path = articles +images_path = picture +title_file = 文章链接.xlsx +max_threads = 3 + +[Coze] +workflow_id = +access_token = +is_async = false +input_data_template = {"article": "{article_text}", "link":"{link}", "weijin":"{weijin}"} +last_used_template = +last_used_template_type = 文章 + +[Database] +host = 27.106.125.150 +user = root +password = taiyi.1224 +database = toutiao + +[Dify] +api_key = app-87gssUKFBs9BwJw4m95uUcyF +user_id = toutiao +url = http://27.106.125.150/v1/workflows/run + +[Baidu] +api_key = 6GvuZoSEe4L8I7O3p7tZRKhj +secret_key = jDujU3MyzP34cUuTP0GNtPejlQpUFWvl + +[ImageModify] +crop_percent = 0.02 +min_rotation = 0.3 +max_rotation = 3.0 +min_brightness = 0.8 +max_brightness = 1.2 +watermark_text = Qin Quan Shan Chu +watermark_opacity = 128 +overlay_opacity = 30 + +[Keywords] +banned_words = 珠海,落马,股票,股市,股民,爆炸,火灾,死亡,抢劫,诈骗,习大大,习近平,政府,官员,扫黑,警察,落网,嫌疑人,通报,暴力执法,执法,暴力,气象,天气,暴雨,大雨 + diff --git a/files/file_metadata(TaiYiTai的冲突副本1_2025-10-16 14-29-40).json b/files/file_metadata(TaiYiTai的冲突副本1_2025-10-16 14-29-40).json deleted file mode 100644 index 02250a3..0000000 --- a/files/file_metadata(TaiYiTai的冲突副本1_2025-10-16 14-29-40).json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "f3460922-b1b0-44f5-a58f-4e76bc00a93b": { - "original_name": "txt2docx.exe", - "local_path": "1_4b784eaa_txt2docx.exe", - "software_name": "1", - "software_id": null, - "hash": "c9c11919de5ac4bcafcc7bf711b12e7d", - "size": 28275085, - "upload_time": "2025-09-19T12:03:20.038104", - "source_path": "D:/work/code/python/ArticleReplaceBatch/dist/txt2docx.exe" - }, - "282738f6-7c48-44a9-a121-0961e22322e1": { - "original_name": "Txt2docx2.exe", - "local_path": "1_75ff7948_Txt2docx2.exe", - "software_name": "1", - "software_id": null, - "hash": "64488e1e7cad5babc9b5a09f747da7dc", - "size": 28441379, - "upload_time": "2025-09-19T13:10:09.321737", - "source_path": "D:/work/code/python/ArticleReplaceBatch/dist/Txt2docx2.exe" - }, - "c105e840-51ff-4f4a-ac45-f8589c64339a": { - "original_name": "txt2docx_encrypted.exe", - "local_path": "啊_2db557bd_txt2docx_encrypted.exe", - "software_name": "啊", - "software_id": null, - "hash": "384b1282b420e10f551604a89e2cf456", - "size": 51324579, - "upload_time": "2025-09-25T15:21:14.652255", - "source_path": "D:/work/code/python/ArticleReplaceBatch/dist/txt2docx_encrypted.exe" - } -} \ No newline at end of file diff --git a/files/file_metadata.json b/files/file_metadata.json deleted file mode 100644 index a8678b1..0000000 --- a/files/file_metadata.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "f3460922-b1b0-44f5-a58f-4e76bc00a93b": { - "original_name": "txt2docx.exe", - "local_path": "1_4b784eaa_txt2docx.exe", - "software_name": "1", - "software_id": null, - "hash": "c9c11919de5ac4bcafcc7bf711b12e7d", - "size": 28275085, - "upload_time": "2025-09-19T12:03:20.038104", - "source_path": "D:/work/code/python/ArticleReplaceBatch/dist/txt2docx.exe" - }, - "282738f6-7c48-44a9-a121-0961e22322e1": { - "original_name": "Txt2docx2.exe", - "local_path": "1_75ff7948_Txt2docx2.exe", - "software_name": "1", - "software_id": null, - "hash": "64488e1e7cad5babc9b5a09f747da7dc", - "size": 28441379, - "upload_time": "2025-09-19T13:10:09.321737", - "source_path": "D:/work/code/python/ArticleReplaceBatch/dist/Txt2docx2.exe" - } -} \ No newline at end of file diff --git a/start_api_server.py b/start_api_server.py new file mode 100644 index 0000000..32afcc0 --- /dev/null +++ b/start_api_server.py @@ -0,0 +1,155 @@ +""" +API服务器启动脚本 +自动从 db_config.json 和 api_config.json 读取配置并启动服务器 +""" +import os +import sys +import json + +def load_config(): + """加载配置文件""" + print("=" * 60) + print("加载配置文件...") + print("=" * 60) + + # 读取数据库配置 + if os.path.exists('db_config.json'): + with open('db_config.json', 'r', encoding='utf-8') as f: + db_config = json.load(f) + print("✓ 数据库配置:") + print(f" - 主机: {db_config.get('host', 'localhost')}") + print(f" - 数据库: {db_config.get('database', 'exeprotector')}") + print(f" - 用户: {db_config.get('user', 'root')}") + + # 设置环境变量 + os.environ['DB_HOST'] = db_config.get('host', 'localhost') + os.environ['DB_USER'] = db_config.get('user', 'root') + os.environ['DB_PASSWORD'] = db_config.get('password', 'taiyi1224') + os.environ['DB_NAME'] = db_config.get('database', 'exeprotector') + else: + print("⚠️ 未找到 db_config.json,使用默认配置") + + # 读取API配置 + if os.path.exists('api_config.json'): + with open('api_config.json', 'r', encoding='utf-8') as f: + api_config = json.load(f) + api_key = api_config.get('api_key', '') + if api_key: + print("✓ API密钥已加载") + os.environ['API_KEY'] = api_key + else: + print("⚠️ API密钥为空") + else: + print("⚠️ 未找到 api_config.json") + + print("=" * 60) + print() + +def test_db_connection(): + """测试数据库连接""" + print("测试数据库连接...") + + try: + import mysql.connector + + db_config = { + 'host': os.environ.get('DB_HOST', 'localhost'), + 'user': os.environ.get('DB_USER', 'root'), + 'password': os.environ.get('DB_PASSWORD', 'taiyi1224'), + 'database': os.environ.get('DB_NAME', 'exeprotector'), + 'connect_timeout': 10 + } + + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + + # 检查表 + cursor.execute("SHOW TABLES LIKE 'software_products'") + if cursor.fetchone(): + print("✓ 数据库连接正常") + + # 显示软件产品 + cursor.execute("SELECT id, name FROM software_products") + products = cursor.fetchall() + if products: + print(f"✓ 找到 {len(products)} 个软件产品:") + for pid, name in products: + print(f" - {name} (ID:{pid})") + else: + print("⚠️ 数据库中没有软件产品,请先注册") + else: + print("✗ 数据库表不存在,请先初始化数据库") + cursor.close() + conn.close() + return False + + cursor.close() + conn.close() + print() + return True + + except mysql.connector.Error as e: + print(f"✗ 数据库连接失败: {e}") + print() + print("请检查:") + print(" 1. MySQL服务是否运行") + print(" 2. db_config.json 中的配置是否正确") + print(" 3. 数据库是否已创建") + return False + except ImportError: + print("✗ 未安装 mysql-connector-python") + print(" 安装: pip install mysql-connector-python") + return False + +def start_server(): + """启动API服务器""" + print("=" * 60) + print("启动API服务器...") + print("=" * 60) + print() + print("监听地址: 0.0.0.0:5100") + print("健康检查: http://localhost:5100/api/health") + print("验证接口: http://localhost:5100/api/validate") + print() + print("按 Ctrl+C 停止服务器") + print("=" * 60) + print() + + # 导入并运行服务器 + from api_server_lite import app + app.run(host='0.0.0.0', port=5100, debug=False) + +if __name__ == '__main__': + # 设置UTF-8编码 + if sys.platform == 'win32': + try: + sys.stdout.reconfigure(encoding='utf-8') + except: + pass + + print("\n" + "=" * 60) + print("API服务器启动工具") + print("=" * 60) + print() + + # 加载配置 + load_config() + + # 测试数据库 + if not test_db_connection(): + print("=" * 60) + print("数据库连接失败,无法启动服务器") + print("=" * 60) + sys.exit(1) + + # 启动服务器 + try: + start_server() + except KeyboardInterrupt: + print("\n\n服务器已停止") + except Exception as e: + print(f"\n✗ 启动失败: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + diff --git a/validator_secure.py b/validator_secure.py index 1bde1d0..626515a 100644 --- a/validator_secure.py +++ b/validator_secure.py @@ -1,6 +1,14 @@ """ -安全的验证器 - 通过API验证,不包含数据库密码 +安全的验证器 V2 - 改进版 +主要改进: +1. 持久化缓存存储(不会被系统清理) +2. 智能验证策略(已激活用户不频繁打扰) +3. 到期提醒功能 +4. 增强的离线验证 +5. 改善的用户界面 + 作者:太一 +微信:taiyi1224 """ import os @@ -18,16 +26,18 @@ import base64 from datetime import datetime, timedelta from cryptography.fernet import Fernet import requests +import urllib3 +from pathlib import Path +import sqlite3 + +# 禁用SSL警告(用于自签名证书) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # ========== 配置区(这些可以公开) ========== -API_BASE_URL = "https://your-server.com/api" # 替换为你的服务器地址 -API_KEY = "your-secure-api-key-here" # 从环境变量读取更安全 +API_BASE_URL = "http://localhost:5100/" # 使用本地API服务器 +API_KEY = "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224" # 从环境变量读取更安全 SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER" # 打包时替换 -# 本地缓存配置 -CACHE_FILE = os.path.join(tempfile.gettempdir(), '.lic_cache') -CACHE_VALIDITY_HOURS = 24 # 缓存有效期 - # ========== 机器码生成 ========== def get_machine_code(): """生成机器码(简化且稳定的版本)""" @@ -60,65 +70,148 @@ def get_machine_code(): fallback = f"{platform.node()}{uuid.getnode()}" return hashlib.md5(fallback.encode()).hexdigest()[:16].upper() -# ========== 加密缓存 ========== -def _get_cache_key(): - """获取缓存加密密钥(基于机器码)""" - machine_key = get_machine_code().encode()[:32].ljust(32, b'0') - return base64.urlsafe_b64encode(machine_key) - -def save_cache(license_key, token, expires_at): - """保存验证缓存""" - try: - data = { - 'license_key': license_key, - 'token': token, - 'expires_at': expires_at, - 'machine_code': get_machine_code() - } - fernet = Fernet(_get_cache_key()) - encrypted = fernet.encrypt(json.dumps(data).encode()) - with open(CACHE_FILE, 'wb') as f: - f.write(encrypted) - return True - except Exception as e: - print(f"保存缓存失败: {e}") - return False - -def load_cache(): - """加载验证缓存""" - try: - if not os.path.exists(CACHE_FILE): - return None +# ========== 持久化缓存管理器 ========== +class LicenseCache: + """持久化许可证缓存管理器(使用SQLite)""" + + def __init__(self): + # 存储在用户AppData目录,不会被系统清理 + if os.name == 'nt': # Windows + cache_base = Path(os.environ.get('APPDATA', Path.home())) + else: # Linux/Mac + cache_base = Path.home() / '.config' + + self.cache_dir = cache_base / '.secure_license' + self.cache_dir.mkdir(exist_ok=True, parents=True) + self.db_path = self.cache_dir / 'license.db' + self._init_db() + + def _init_db(self): + """初始化数据库""" + conn = sqlite3.connect(str(self.db_path)) + conn.execute(''' + CREATE TABLE IF NOT EXISTS license_cache ( + id INTEGER PRIMARY KEY, + software_name TEXT NOT NULL, + license_key TEXT NOT NULL, + machine_code TEXT NOT NULL, + token TEXT NOT NULL, + start_time TEXT NOT NULL, + end_time TEXT NOT NULL, + last_validated TEXT NOT NULL, + status TEXT DEFAULT 'active', + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + UNIQUE(software_name, machine_code) + ) + ''') + conn.commit() + conn.close() + + def save_license(self, software_name, license_key, machine_code, + token, start_time, end_time): + """保存许可证信息""" + try: + conn = sqlite3.connect(str(self.db_path)) + # 转换datetime为ISO格式字符串 + if isinstance(start_time, datetime): + start_time = start_time.isoformat() + if isinstance(end_time, datetime): + end_time = end_time.isoformat() - with open(CACHE_FILE, 'rb') as f: - encrypted = f.read() - - fernet = Fernet(_get_cache_key()) - decrypted = fernet.decrypt(encrypted) - data = json.loads(decrypted.decode()) - - # 验证机器码 - if data.get('machine_code') != get_machine_code(): - return None - - # 验证过期时间 - expires_at = datetime.fromisoformat(data.get('expires_at')) - if datetime.now() > expires_at: - return None + conn.execute(''' + INSERT OR REPLACE INTO license_cache + (software_name, license_key, machine_code, token, + start_time, end_time, last_validated) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', (software_name, license_key, machine_code, token, + start_time, end_time, datetime.now().isoformat())) + conn.commit() + conn.close() + return True + except Exception as e: + print(f"保存许可证缓存失败: {e}") + return False + + def get_license(self, software_name, machine_code): + """获取许可证信息""" + try: + conn = sqlite3.connect(str(self.db_path)) + cursor = conn.cursor() + cursor.execute(''' + SELECT * FROM license_cache + WHERE software_name=? AND machine_code=? + ''', (software_name, machine_code)) + row = cursor.fetchone() - return data - - except Exception as e: - print(f"加载缓存失败: {e}") - return None - -def clear_cache(): - """清除缓存""" - try: - if os.path.exists(CACHE_FILE): - os.remove(CACHE_FILE) - except: - pass + if not row: + conn.close() + return None + + # 转换为字典 + columns = [desc[0] for desc in cursor.description] + license_data = dict(zip(columns, row)) + conn.close() + + return license_data + except Exception as e: + print(f"读取许可证缓存失败: {e}") + return None + + def should_revalidate(self, license_data): + """判断是否需要重新验证(每7天联网验证一次)""" + try: + last_validated = datetime.fromisoformat(license_data['last_validated']) + return (datetime.now() - last_validated).days >= 7 + except: + return True + + def is_expired(self, license_data): + """检查许可证是否过期""" + try: + end_time = datetime.fromisoformat(license_data['end_time']) + return datetime.now() > end_time + except: + return True + + def get_days_remaining(self, license_data): + """获取剩余天数""" + try: + end_time = datetime.fromisoformat(license_data['end_time']) + remaining = (end_time - datetime.now()).days + return max(0, remaining) + except: + return 0 + + def update_last_validated(self, software_name, machine_code): + """更新最后验证时间""" + try: + conn = sqlite3.connect(str(self.db_path)) + conn.execute(''' + UPDATE license_cache + SET last_validated=? + WHERE software_name=? AND machine_code=? + ''', (datetime.now().isoformat(), software_name, machine_code)) + conn.commit() + conn.close() + return True + except Exception as e: + print(f"更新验证时间失败: {e}") + return False + + def clear_license(self, software_name, machine_code): + """清除许可证缓存""" + try: + conn = sqlite3.connect(str(self.db_path)) + conn.execute(''' + DELETE FROM license_cache + WHERE software_name=? AND machine_code=? + ''', (software_name, machine_code)) + conn.commit() + conn.close() + return True + except Exception as e: + print(f"清除许可证缓存失败: {e}") + return False # ========== API通信 ========== def create_signature(license_key, machine_code, software_name): @@ -132,7 +225,7 @@ def validate_license_online(license_key, machine_code): signature = create_signature(license_key, machine_code, SOFTWARE_NAME) response = requests.post( - f"{API_BASE_URL}/validate", + f"{API_BASE_URL}api/validate", json={ 'license_key': license_key, 'machine_code': machine_code, @@ -140,17 +233,26 @@ def validate_license_online(license_key, machine_code): 'signature': signature }, headers={'X-API-Key': API_KEY}, - timeout=10 + timeout=10, + verify=False # 跳过SSL证书验证(用于自签名证书) ) if response.status_code == 200: data = response.json() if data.get('success'): - # 保存缓存 - save_cache( + # 保存到持久化缓存 + cache = LicenseCache() + # 获取服务器返回的时间信息(如果有) + end_time = data.get('expires_at', (datetime.now() + timedelta(days=30)).isoformat()) + start_time = datetime.now().isoformat() + + cache.save_license( + SOFTWARE_NAME, license_key, + machine_code, data.get('token', ''), - data.get('expires_at', (datetime.now() + timedelta(hours=24)).isoformat()) + start_time, + end_time ) return True, data.get('message', '验证成功') else: @@ -165,21 +267,163 @@ def validate_license_online(license_key, machine_code): except Exception as e: return False, f"验证过程出错: {str(e)}" -def validate_license_offline(cache_data): - """离线验证(使用缓存)""" +def validate_license_offline(license_data): + """增强的离线验证""" try: - # 验证token(可选:发送到服务器验证) - token = cache_data.get('token', '') - if not token: - return False, "缓存数据不完整" + # 1. 验证数据完整性 + required_fields = ['license_key', 'token', 'end_time', 'machine_code'] + if not all(field in license_data for field in required_fields): + return False, "缓存数据不完整", None - # 这里可以添加本地token验证逻辑 - # 或者在有网络时向服务器验证token + # 2. 验证机器码 + current_machine_code = get_machine_code() + if license_data['machine_code'] != current_machine_code: + return False, "设备已更改,需要重新激活", None - return True, "离线验证成功(使用缓存)" + # 3. 检查许可证是否过期 + end_time = datetime.fromisoformat(license_data['end_time']) + if datetime.now() > end_time: + return False, "许可证已过期", None + + # 4. 计算剩余天数 + remaining_days = (end_time - datetime.now()).days + + # 5. 验证token(基本的完整性检查) + token = license_data.get('token', '') + if not token or len(token) < 10: + return False, "许可证数据异常", None + + # 6. 返回详细信息 + info = { + 'remaining_days': remaining_days, + 'end_time': end_time, + 'license_key': license_data['license_key'] + } + + return True, f"离线验证成功", info except Exception as e: - return False, f"离线验证失败: {str(e)}" + return False, f"离线验证失败: {str(e)}", None + +def try_online_revalidation(license_data): + """尝试联网刷新状态(静默,不打扰用户)""" + try: + machine_code = get_machine_code() + success, msg = validate_license_online( + license_data['license_key'], + machine_code + ) + return success, msg + except: + # 网络问题,不影响使用 + return True, "离线模式" + +# ========== 到期提醒 ========== +def show_expiry_reminder(remaining_days, end_time_str): + """显示到期提醒""" + + try: + end_time = datetime.fromisoformat(end_time_str) + except: + return + + # 根据剩余天数选择不同的提醒级别 + if remaining_days <= 0: + # 已过期,必须续费 + title = "⚠️ 许可证已过期" + message = f"您的许可证已于 {end_time.strftime('%Y-%m-%d')} 到期\n请联系管理员续费" + urgency = "critical" + elif remaining_days == 1: + title = "🔴 许可证即将到期" + message = f"您的许可证将于明天到期\n请尽快联系管理员续费" + urgency = "urgent" + elif remaining_days <= 3: + title = "🟡 许可证即将到期" + message = f"您的许可证还剩 {remaining_days} 天\n建议尽快联系管理员续费" + urgency = "warning" + elif remaining_days <= 7: + title = "🟢 许可证到期提醒" + message = f"您的许可证还剩 {remaining_days} 天\n请注意及时续费" + urgency = "info" + else: + # 不需要提醒 + return + + # 创建提醒窗口 + root = tk.Tk() + root.title(title) + root.geometry("450x280") + root.resizable(False, False) + + # 居中 + root.update_idletasks() + x = (root.winfo_screenwidth() - 450) // 2 + y = (root.winfo_screenheight() - 280) // 2 + root.geometry(f"450x280+{x}+{y}") + + # 根据紧急程度设置窗口属性 + if urgency == "critical": + root.attributes('-topmost', True) + + # 标题 + title_color = "red" if urgency in ["critical", "urgent"] else "orange" if urgency == "warning" else "green" + tk.Label(root, text=title, + font=("Microsoft YaHei", 14, "bold"), + fg=title_color).pack(pady=20) + + # 消息 + tk.Label(root, text=message, + font=("Microsoft YaHei", 11), + justify=tk.CENTER).pack(pady=10) + + # 到期时间 + tk.Label(root, text=f"到期时间:{end_time.strftime('%Y年%m月%d日')}", + font=("Microsoft YaHei", 10), + fg="gray").pack(pady=5) + + # 剩余天数(醒目显示) + if remaining_days > 0: + tk.Label(root, text=f"剩余 {remaining_days} 天", + font=("Microsoft YaHei", 16, "bold"), + fg=title_color).pack(pady=10) + + # 联系方式 + tk.Label(root, text="联系管理员续费:V:taiyi1224", + font=("Microsoft YaHei", 9), + fg="blue").pack(pady=10) + + # 按钮 + frame_buttons = tk.Frame(root) + frame_buttons.pack(pady=15) + + def on_continue(): + root.destroy() + + def on_contact(): + import webbrowser + try: + # 尝试打开微信(如果有协议链接) + webbrowser.open("weixin://") + except: + pass + root.destroy() + + # 如果未过期,显示"继续使用"按钮 + if urgency != "critical": + tk.Button(frame_buttons, text="继续使用", command=on_continue, + bg="#27ae60", fg="white", font=("Microsoft YaHei", 10, "bold"), + padx=20, pady=8).pack(side=tk.LEFT, padx=10) + + tk.Button(frame_buttons, text="联系续费", command=on_contact, + bg="#3498db", fg="white", font=("Microsoft YaHei", 10, "bold"), + padx=20, pady=8).pack(side=tk.LEFT, padx=10) + + # 如果是critical级别,设置为模态窗口 + if urgency == "critical": + root.transient() + root.grab_set() + + root.mainloop() # ========== 资源解密 ========== # ENCRYPTED_RESOURCE_PLACEHOLDER @@ -195,7 +439,6 @@ def decrypt_resource(): import base64 # 重建完整密钥 - # key_hint包含前半部分,key_part2包含后半部分 key_part1_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_hint'].encode()) key_part2_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_part2'].encode()) @@ -245,9 +488,113 @@ def extract_and_run(): except Exception as e: return False, f"启动失败: {str(e)}" -# ========== GUI ========== -def show_activation_dialog(): - """显示激活对话框""" +# ========== 增强的激活对话框 ========== +def show_enhanced_activation_dialog(message=None): + """增强版激活对话框""" + + # 尝试加载已有许可证信息 + cache = LicenseCache() + machine_code = get_machine_code() + existing_license = cache.get_license(SOFTWARE_NAME, machine_code) + + root = tk.Tk() + root.title(f"{SOFTWARE_NAME} - 许可证管理") + root.geometry("550x520") + root.resizable(False, False) + + # 居中 + root.update_idletasks() + x = (root.winfo_screenwidth() - 550) // 2 + y = (root.winfo_screenheight() - 520) // 2 + root.geometry(f"550x520+{x}+{y}") + + # 标题 + tk.Label(root, text="🔐 软件许可证验证", + font=("Microsoft YaHei", 16, "bold")).pack(pady=20) + + # 如果有消息,显示 + if message: + tk.Label(root, text=message, + font=("Microsoft YaHei", 10), + fg="red").pack(pady=5) + + # 如果有现有许可证,显示状态 + if existing_license: + status_frame = tk.LabelFrame(root, text="当前许可证状态", + font=("Microsoft YaHei", 10, "bold"), + padx=10, pady=10) + status_frame.pack(fill=tk.X, padx=30, pady=10) + + remaining = cache.get_days_remaining(existing_license) + is_expired = cache.is_expired(existing_license) + + if is_expired: + status_text = "❌ 已过期" + status_color = "red" + elif remaining <= 7: + status_text = f"⚠️ 剩余 {remaining} 天" + status_color = "orange" + else: + status_text = f"✅ 剩余 {remaining} 天" + status_color = "green" + + tk.Label(status_frame, text=f"状态:{status_text}", + font=("Microsoft YaHei", 11, "bold"), + fg=status_color).pack(anchor=tk.W, pady=3) + + tk.Label(status_frame, + text=f"激活码:{existing_license['license_key']}", + font=("Consolas", 9), + fg="gray").pack(anchor=tk.W, pady=2) + + end_time_str = existing_license.get('end_time', '') + if end_time_str: + try: + end_time = datetime.fromisoformat(end_time_str) + tk.Label(status_frame, + text=f"到期时间:{end_time.strftime('%Y年%m月%d日')}", + font=("Microsoft YaHei", 9), + fg="gray").pack(anchor=tk.W, pady=2) + except: + pass + + # 机器码 + frame_machine = tk.Frame(root) + frame_machine.pack(fill=tk.X, padx=30, pady=10) + tk.Label(frame_machine, text="机器码:", + font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) + entry_machine = tk.Entry(frame_machine, state="readonly", + font=("Consolas", 10), + justify=tk.CENTER) + entry_machine.insert(0, machine_code) + entry_machine.pack(fill=tk.X, pady=5) + + def copy_machine_code(): + root.clipboard_clear() + root.clipboard_append(machine_code) + messagebox.showinfo("成功", "机器码已复制到剪贴板\n请发送给管理员获取激活码") + + tk.Button(frame_machine, text="📋 复制机器码", + command=copy_machine_code, + font=("Microsoft YaHei", 9)).pack(anchor=tk.E) + + # 激活码输入 + frame_key = tk.Frame(root) + frame_key.pack(fill=tk.X, padx=30, pady=10) + tk.Label(frame_key, text="激活码:" if not existing_license else "新激活码:", + font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) + entry_key = tk.Entry(frame_key, font=("Consolas", 12), + justify=tk.CENTER) + entry_key.pack(fill=tk.X, pady=5) + entry_key.focus() + + # 状态标签 + status_label = tk.Label(root, text="", font=("Microsoft YaHei", 9)) + status_label.pack(pady=10) + + # 按钮区域 + frame_buttons = tk.Frame(root) + frame_buttons.pack(pady=15) def on_activate(): license_key = entry_key.get().strip() @@ -256,118 +603,163 @@ def show_activation_dialog(): return btn_activate.config(state=tk.DISABLED, text="验证中...") - status_label.config(text="正在验证激活码...", fg="blue") + status_label.config(text="正在联网验证激活码,请稍候...", fg="blue") root.update() def validate_thread(): - machine_code = get_machine_code() success, msg = validate_license_online(license_key, machine_code) - root.after(0, lambda: handle_result(success, msg)) threading.Thread(target=validate_thread, daemon=True).start() def handle_result(success, msg): if success: - messagebox.showinfo("成功", "激活成功!正在启动程序...") + messagebox.showinfo("成功", f"激活成功!\n{msg}\n\n正在启动程序...") root.destroy() - - # 启动程序 ok, err = extract_and_run() if ok: sys.exit(0) else: - messagebox.showerror("错误", err) + messagebox.showerror("错误", f"程序启动失败:\n{err}") sys.exit(1) else: - messagebox.showerror("失败", msg) - btn_activate.config(state=tk.NORMAL, text="验证并启动") + messagebox.showerror("激活失败", f"{msg}\n\n请检查:\n1. 激活码是否正确\n2. 网络连接是否正常\n3. 激活码是否已被其他设备使用") + btn_activate.config(state=tk.NORMAL, text="验证并激活") status_label.config(text="", fg="black") - machine_code = get_machine_code() - - root = tk.Tk() - root.title("软件激活验证") - root.geometry("500x400") - root.resizable(False, False) - - # 居中 - root.update_idletasks() - x = (root.winfo_screenwidth() - root.winfo_width()) // 2 - y = (root.winfo_screenheight() - root.winfo_height()) // 2 - root.geometry(f"500x400+{x}+{y}") - - # 标题 - tk.Label(root, text="🔐 软件许可证验证", - font=("Microsoft YaHei", 16, "bold")).pack(pady=20) - - # 机器码 - frame_machine = tk.Frame(root) - frame_machine.pack(fill=tk.X, padx=30, pady=10) - tk.Label(frame_machine, text="机器码:", font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) - entry_machine = tk.Entry(frame_machine, state="readonly", font=("Consolas", 10)) - entry_machine.insert(0, machine_code) - entry_machine.pack(fill=tk.X, pady=5) - - def copy_machine_code(): - root.clipboard_clear() - root.clipboard_append(machine_code) - messagebox.showinfo("成功", "机器码已复制") - - tk.Button(frame_machine, text="复制机器码", command=copy_machine_code).pack(anchor=tk.E) - - # 激活码 - frame_key = tk.Frame(root) - frame_key.pack(fill=tk.X, padx=30, pady=10) - tk.Label(frame_key, text="激活码:", font=("Microsoft YaHei", 10, "bold")).pack(anchor=tk.W) - entry_key = tk.Entry(frame_key, font=("Consolas", 12)) - entry_key.pack(fill=tk.X, pady=5) - entry_key.focus() - - # 状态 - status_label = tk.Label(root, text="", font=("Microsoft YaHei", 9)) - status_label.pack(pady=10) - - # 按钮 - frame_buttons = tk.Frame(root) - frame_buttons.pack(pady=20) - - btn_activate = tk.Button(frame_buttons, text="验证并启动", command=on_activate, - bg="#27ae60", fg="white", font=("Microsoft YaHei", 11, "bold"), - padx=20, pady=8) + btn_activate = tk.Button(frame_buttons, text="验证并激活", + command=on_activate, + bg="#27ae60", fg="white", + font=("Microsoft YaHei", 11, "bold"), + padx=25, pady=10, + cursor="hand2") btn_activate.pack(side=tk.LEFT, padx=10) + # 如果有现有许可证且未过期,允许继续使用 + if existing_license and not cache.is_expired(existing_license): + def on_continue(): + root.destroy() + ok, err = extract_and_run() + if ok: + sys.exit(0) + else: + messagebox.showerror("错误", f"程序启动失败:\n{err}") + sys.exit(1) + + tk.Button(frame_buttons, text="继续使用", command=on_continue, + bg="#3498db", fg="white", + font=("Microsoft YaHei", 11, "bold"), + padx=25, pady=10, + cursor="hand2").pack(side=tk.LEFT, padx=10) + tk.Button(frame_buttons, text="退出", command=sys.exit, - bg="#e74c3c", fg="white", font=("Microsoft YaHei", 11, "bold"), - padx=20, pady=8).pack(side=tk.LEFT, padx=10) + bg="#e74c3c", fg="white", + font=("Microsoft YaHei", 11, "bold"), + padx=25, pady=10, + cursor="hand2").pack(side=tk.LEFT, padx=10) + + # 帮助信息 + help_frame = tk.Frame(root) + help_frame.pack(pady=10) + + tk.Label(help_frame, text="需要帮助?", + font=("Microsoft YaHei", 8), + fg="gray").pack() + tk.Label(help_frame, text="联系管理员:V:taiyi1224", + font=("Microsoft YaHei", 8, "bold"), + fg="blue", + cursor="hand2").pack() root.bind('', lambda e: on_activate()) root.protocol("WM_DELETE_WINDOW", sys.exit) root.mainloop() +# ========== 智能验证主流程 ========== +def smart_validate(): + """智能验证策略""" + + try: + # 1. 初始化缓存管理器 + cache = LicenseCache() + machine_code = get_machine_code() + + # 2. 尝试加载本地缓存 + license_data = cache.get_license(SOFTWARE_NAME, machine_code) + + if not license_data: + # 首次使用,需要在线激活 + print("首次使用,需要激活") + show_enhanced_activation_dialog() + return + + # 3. 检查是否过期 + if cache.is_expired(license_data): + print("许可证已过期") + cache.clear_license(SOFTWARE_NAME, machine_code) + show_enhanced_activation_dialog(message="许可证已过期,请重新激活") + return + + # 4. 计算剩余天数 + remaining_days = cache.get_days_remaining(license_data) + print(f"许可证剩余 {remaining_days} 天") + + # 5. 判断是否需要联网刷新(每7天一次) + if cache.should_revalidate(license_data): + print("尝试联网刷新状态...") + # 尝试联网验证(静默,不打扰用户) + success, msg = try_online_revalidation(license_data) + if success: + # 更新缓存中的验证时间 + cache.update_last_validated(SOFTWARE_NAME, machine_code) + print(f"状态刷新成功: {msg}") + else: + print(f"状态刷新失败(不影响使用): {msg}") + # 即使失败也继续使用(网络问题不影响已激活用户) + + # 6. 离线验证 + success, msg, info = validate_license_offline(license_data) + if not success: + print(f"离线验证失败: {msg}") + cache.clear_license(SOFTWARE_NAME, machine_code) + show_enhanced_activation_dialog(message=msg) + return + + print(f"离线验证成功: {msg}") + + # 7. 显示到期提醒(如果接近到期) + if remaining_days <= 7: + print(f"显示到期提醒(剩余{remaining_days}天)") + show_expiry_reminder(remaining_days, license_data['end_time']) + + # 8. 验证通过,启动程序 + print("启动程序...") + ok, err = extract_and_run() + if ok: + print("程序启动成功") + sys.exit(0) + else: + print(f"程序启动失败: {err}") + messagebox.showerror("错误", f"程序启动失败:\n{err}") + sys.exit(1) + + except Exception as e: + print(f"验证过程出错: {e}") + import traceback + traceback.print_exc() + messagebox.showerror("错误", f"验证过程出错:\n{str(e)}") + sys.exit(1) + # ========== 主函数 ========== def main(): """主函数""" + print("=" * 60) + print(f"软件许可证验证系统 V2.0") + print(f"软件名称: {SOFTWARE_NAME}") + print(f"机器码: {get_machine_code()}") + print("=" * 60) - # 1. 尝试加载缓存 - cache_data = load_cache() - if cache_data: - print("找到有效缓存,尝试离线验证...") - success, msg = validate_license_offline(cache_data) - if success: - print(f"离线验证成功: {msg}") - ok, err = extract_and_run() - if ok: - sys.exit(0) - else: - print(f"启动失败: {err}") - clear_cache() - else: - print(f"离线验证失败: {msg}") - clear_cache() - - # 2. 需要在线验证 - show_activation_dialog() + # 使用智能验证策略 + smart_validate() if __name__ == '__main__': main() diff --git a/编译问题解决指南.md b/编译问题解决指南.md deleted file mode 100644 index 888c572..0000000 --- a/编译问题解决指南.md +++ /dev/null @@ -1,288 +0,0 @@ -# 编译问题解决指南 - -## 🔧 编译失败常见原因及解决方案 - -### 方案1:运行环境检查工具(推荐) - -**首先运行环境诊断工具:** -```bash -python check_compile_env.py -``` - -这个工具会自动检查: -- ✅ Python 环境 -- ✅ PyInstaller 安装 -- ✅ 必需的依赖包 -- ✅ 磁盘空间 -- ✅ 编译测试 - -根据检查结果修复问题即可。 - ---- - -### 方案2:手动排查和修复 - -#### 问题1: PyInstaller 未安装或版本过低 - -**症状:** -- 提示 "PyInstaller未安装或不可用" - -**解决方案:** -```bash -# 安装 PyInstaller -pip install pyinstaller - -# 或升级到最新版本 -pip install --upgrade pyinstaller - -# 检查版本 -pyinstaller --version -``` - ---- - -#### 问题2: 依赖包缺失 - -**症状:** -- 编译过程中提示找不到某个模块 -- "ModuleNotFoundError" - -**解决方案:** -```bash -# 安装所有依赖 -pip install -r requirements.txt - -# 或单独安装 -pip install cryptography requests pyinstaller -``` - ---- - -#### 问题3: UPX 压缩问题 - -**症状:** -- 编译卡在 "UPX" 步骤 -- 提示 UPX 相关错误 - -**解决方案:** -已在新版本中**自动禁用 UPX**,无需手动处理。 - ---- - -#### 问题4: 磁盘空间不足 - -**症状:** -- 编译到一半失败 -- 提示写入文件错误 - -**解决方案:** -```bash -# 检查可用空间(至少需要 1GB) -# 清理临时文件 -# Windows: 清理 C:\Users\你的用户名\AppData\Local\Temp -``` - ---- - -#### 问题5: 杀毒软件拦截 - -**症状:** -- 编译完成但文件消失 -- 编译过程中突然中断 - -**解决方案:** -1. 临时关闭杀毒软件 -2. 将项目目录添加到杀毒软件白名单 -3. 将 PyInstaller 添加到白名单 - ---- - -#### 问题6: 权限不足 - -**症状:** -- 提示 "Permission denied" -- 无法创建文件 - -**解决方案:** -```bash -# Windows: 以管理员身份运行 -# 右键点击 main.py -> 以管理员身份运行 -``` - ---- - -#### 问题7: 临时文件夹权限问题 - -**症状:** -- 编译失败,提示无法访问临时目录 - -**解决方案:** -新版本已改用**本地工作目录**(`.build_temp`),避免了临时文件夹权限问题。 - -如果问题仍然存在: -```bash -# 设置环境变量 -set TEMP=%CD%\temp -set TMP=%CD%\temp -mkdir temp -``` - ---- - -### 方案3:完全重置环境 - -如果上述方法都不行,尝试完全重置: - -```bash -# 1. 卸载 PyInstaller -pip uninstall pyinstaller -y - -# 2. 清理缓存 -pip cache purge - -# 3. 删除 __pycache__ 目录 -# Windows: rmdir /s /q __pycache__ - -# 4. 重新安装 -pip install pyinstaller - -# 5. 测试编译 -python check_compile_env.py -``` - ---- - -## 📋 新版本编译器改进 - -### ✨ 主要改进: - -1. **使用固定工作目录** - 避免临时目录权限问题 -2. **禁用 UPX 压缩** - 避免压缩相关错误 -3. **增加编译超时** - 从 5 分钟增加到 10 分钟 -4. **详细日志输出** - 完整显示编译过程和错误 -5. **自动清理临时文件** - 编译成功后自动清理 -6. **保留失败现场** - 编译失败时保留工作目录以便调试 -7. **更多依赖项检测** - 自动包含所有必需的模块 - -### 🔍 编译过程说明: - -``` -1. 检查 PyInstaller 版本 -2. 创建工作目录: .build_temp -3. 复制 Python 文件到工作目录 -4. 执行编译(使用 --onefile --windowed) -5. 输出详细编译日志 -6. 移动生成的 EXE 到目标位置 -7. 清理临时文件 -``` - ---- - -## 🎯 快速诊断命令 - -```bash -# 1. 检查 Python -python --version - -# 2. 检查 pip -pip --version - -# 3. 检查 PyInstaller -pyinstaller --version - -# 4. 检查依赖 -pip list | findstr "pyinstaller cryptography requests" - -# 5. 运行完整检查 -python check_compile_env.py -``` - ---- - -## 📞 获取帮助 - -如果以上方法都无法解决问题: - -1. **查看完整错误日志** - - 编译失败时会显示详细错误信息 - - 工作目录保留在 `.build_temp`,可以查看编译日志 - -2. **手动测试编译** - ```bash - # 进入工作目录 - cd .build_temp - - # 查看生成的 spec 文件 - # 尝试手动编译 - pyinstaller *.spec - ``` - -3. **联系技术支持** - - 作者:太一 - - 微信:taiyi1224 - - 邮箱:shoubo1224@qq.com - - 提供以下信息: - - Python 版本 - - PyInstaller 版本 - - 完整错误日志 - - 操作系统版本 - ---- - -## 💡 最佳实践 - -1. **使用虚拟环境** - ```bash - python -m venv venv - venv\Scripts\activate - pip install -r requirements.txt - ``` - -2. **定期更新依赖** - ```bash - pip install --upgrade pip - pip install --upgrade pyinstaller - ``` - -3. **保持系统整洁** - - 定期清理临时文件 - - 确保有足够的磁盘空间(至少 2GB) - - 关闭不必要的杀毒软件 - -4. **使用管理员权限** - - Windows: 右键 -> 以管理员身份运行 - - 避免权限相关问题 - ---- - -## ⚠️ 注意事项 - -1. **首次编译较慢** - - 首次编译需要下载和缓存依赖 - - 可能需要 5-10 分钟 - - 后续编译会快很多 - -2. **生成的 EXE 文件较大** - - 单文件模式会包含所有依赖 - - 通常 10-30MB - - 这是正常现象 - -3. **杀毒软件误报** - - PyInstaller 生成的 EXE 可能被误报为病毒 - - 这是正常现象 - - 需要添加到白名单 - ---- - -## 🎉 测试编译是否成功 - -编译成功后会看到: -``` -✅ 编译成功: D:\path\to\your_app_encrypted.exe -文件大小: 15.23 MB -已清理临时文件 -``` - -可以直接运行生成的 EXE 文件进行测试。 -