更新验证程序

This commit is contained in:
wsb1224 2025-10-25 17:49:09 +08:00
parent b83ecf8beb
commit c83ab2dcf5
14 changed files with 970 additions and 1507 deletions

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

574
README.md
View File

@ -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+
- 峰值 QPS50
---
## 🔄 版本历史
### 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*

View File

@ -1,6 +1,6 @@
{
"api_url": "https://129.211.65.73:5100/",
"api_url": "http://localhost:5100/",
"api_key": "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224",
"comment": "请修改上面的配置为你的实际服务器地址和API密钥"
"comment": "使用本地API服务器。如果需要远程验证改为远程服务器地址"
}

View File

@ -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():

View File

@ -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()

View File

@ -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)

View File

@ -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验证器包含持久化缓存、到期提醒等改进功能")

View File

@ -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 限制配置文件权限

View File

@ -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 = 珠海,落马,股票,股市,股民,爆炸,火灾,死亡,抢劫,诈骗,习大大,习近平,政府,官员,扫黑,警察,落网,嫌疑人,通报,暴力执法,执法,暴力,气象,天气,暴雨,大雨

View File

@ -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"
}
}

View File

@ -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"
}
}

155
start_api_server.py Normal file
View File

@ -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)

View File

@ -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('<Return>', 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()

View File

@ -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 文件进行测试。