更新验证程序

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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </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", "api_key": "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224",
"comment": "请修改上面的配置为你的实际服务器地址和API密钥" "comment": "使用本地API服务器。如果需要远程验证改为远程服务器地址"
} }

View File

@ -260,13 +260,20 @@ def validate():
'message': '此激活码已在其他设备上使用' 'message': '此激活码已在其他设备上使用'
}) })
else: 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) cache[cache_key] = (time.time(), result)
log_request('validate_success', '验证成功', { log_request('validate_success', '验证成功', {
'license_key': license_key[:10] + '...', 'license_key': license_key[:10] + '...',
'machine_code': machine_code 'machine_code': machine_code,
'remaining_days': remaining_days
}) })
return jsonify(result) return jsonify(result)
@ -312,14 +319,28 @@ def validate():
'message': '激活失败,请重试' 'message': '激活失败,请重试'
}), 500 }), 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) cache[cache_key] = (time.time(), result)
log_request('activate_success', '首次激活成功', { log_request('activate_success', '首次激活成功', {
'license_key': license_key[:10] + '...', 'license_key': license_key[:10] + '...',
'machine_code': machine_code, 'machine_code': machine_code,
'software_name': software_name 'software_name': software_name,
'remaining_days': remaining_days
}) })
return jsonify(result) return jsonify(result)
@ -355,6 +376,107 @@ def validate():
'message': '服务器内部错误' 'message': '服务器内部错误'
}), 500 }), 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']) @app.route('/api/stats', methods=['GET'])
@require_api_key @require_api_key
def stats(): 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 import base64
class SecureEXEEncryptor: class SecureEXEEncryptor:
"""安全的EXE加密器""" """安全的EXE加密器 V2改进版"""
def __init__(self): def __init__(self):
self.encryption_key = None self.encryption_key = None
@ -24,7 +24,8 @@ class SecureEXEEncryptor:
return Fernet.generate_key() return Fernet.generate_key()
def encrypt_exe(self, source_path: str, output_path: str, 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文件 加密EXE文件
@ -33,6 +34,7 @@ class SecureEXEEncryptor:
output_path: 输出路径 output_path: 输出路径
api_config: API配置不包含数据库密码 api_config: API配置不包含数据库密码
software_name: 软件名称 software_name: 软件名称
use_v2_validator: 是否使用V2版本的验证器推荐
""" """
try: try:
print(f"开始加密: {source_path}") print(f"开始加密: {source_path}")
@ -55,23 +57,25 @@ class SecureEXEEncryptor:
# 4. 计算哈希 # 4. 计算哈希
file_hash = hashlib.sha256(original_content).hexdigest() file_hash = hashlib.sha256(original_content).hexdigest()
# 5. 准备资源数据(只包含加密后的数据和密钥提示) # 5. 准备资源数据
# 实际密钥应该分拆:一部分硬编码,一部分从服务器获取 key_part1 = base64.b64encode(self.encryption_key[:16]).decode()
key_part1 = base64.b64encode(self.encryption_key[:16]).decode() # 硬编码部分 key_part2 = base64.b64encode(self.encryption_key[16:]).decode()
key_part2 = base64.b64encode(self.encryption_key[16:]).decode() # 应该从服务器获取
resource_data = { resource_data = {
'data': base64.b64encode(encrypted_content).decode(), 'data': base64.b64encode(encrypted_content).decode(),
'hash': file_hash, 'hash': file_hash,
'key_hint': key_part1, # 前半部分密钥 'key_hint': key_part1,
'key_part2': key_part2, # 后半部分密钥(应该混淆存储) 'key_part2': key_part2,
} }
# 6. 读取验证器模板 # 6. 选择验证器模板
validator_template_path = "validator_secure.py" validator_template_path = "validator_secure.py" if use_v2_validator else "validator_secure.py"
if not os.path.exists(validator_template_path): if not os.path.exists(validator_template_path):
return False, f"找不到验证器模板: {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: with open(validator_template_path, 'r', encoding='utf-8') as f:
template_code = f.read() template_code = f.read()
@ -81,16 +85,20 @@ class SecureEXEEncryptor:
f'ENCRYPTED_RESOURCE = {json.dumps(resource_data)}' f'ENCRYPTED_RESOURCE = {json.dumps(resource_data)}'
) )
final_code = final_code.replace( # 替换API配置
'API_BASE_URL = "https://your-server.com/api"', if 'api_url' in api_config:
f'API_BASE_URL = "{api_config.get("api_url", "https://your-server.com/api")}"' final_code = final_code.replace(
) 'API_BASE_URL = "http://localhost:5100/"',
f'API_BASE_URL = "{api_config["api_url"]}"'
)
final_code = final_code.replace( if 'api_key' in api_config:
'API_KEY = "your-secure-api-key-here"', final_code = final_code.replace(
f'API_KEY = "{api_config.get("api_key", "your-secure-api-key-here")}"' 'API_KEY = "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224"',
) f'API_KEY = "{api_config["api_key"]}"'
)
# 替换软件名称
final_code = final_code.replace( final_code = final_code.replace(
'SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER"', 'SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER"',
f'SOFTWARE_NAME = "{software_name}"' f'SOFTWARE_NAME = "{software_name}"'
@ -114,13 +122,33 @@ class SecureEXEEncryptor:
print(f"⚠️ 密钥已保存到: {key_file}") print(f"⚠️ 密钥已保存到: {key_file}")
print(f"⚠️ 请妥善保管此文件,建议加密存储!") 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 return success, msg
except Exception as e: except Exception as e:
import traceback
error_detail = traceback.format_exc()
print(f"加密过程出错: {error_detail}")
return False, f"加密过程出错: {str(e)}" return False, f"加密过程出错: {str(e)}"
def _compile_to_exe(self, python_file: str, output_path: str) -> Tuple[bool, str]: def _compile_to_exe(self, python_file: str, output_path: str) -> Tuple[bool, str]:
"""编译Python文件为EXE改进版""" """编译Python文件为EXE"""
build_dir = None build_dir = None
try: try:
# 1. 检查PyInstaller # 1. 检查PyInstaller
@ -133,7 +161,7 @@ class SecureEXEEncryptor:
pyinstaller_version = result.stdout.strip() pyinstaller_version = result.stdout.strip()
print(f"PyInstaller 版本: {pyinstaller_version}") print(f"PyInstaller 版本: {pyinstaller_version}")
# 2. 创建固定的工作目录(避免临时目录权限问题) # 2. 创建工作目录
build_dir = os.path.join(os.path.dirname(os.path.abspath(output_path)), '.build_temp') build_dir = os.path.join(os.path.dirname(os.path.abspath(output_path)), '.build_temp')
os.makedirs(build_dir, exist_ok=True) os.makedirs(build_dir, exist_ok=True)
print(f"工作目录: {build_dir}") print(f"工作目录: {build_dir}")
@ -144,19 +172,19 @@ class SecureEXEEncryptor:
shutil.copy2(python_file, work_py) shutil.copy2(python_file, work_py)
print(f"工作文件: {work_py}") print(f"工作文件: {work_py}")
# 4. 使用简化的命令行方式(更稳定) # 4. 构建PyInstaller命令
exe_name = os.path.splitext(os.path.basename(output_path))[0] exe_name = os.path.splitext(os.path.basename(output_path))[0]
cmd = [ cmd = [
'pyinstaller', 'pyinstaller',
'--clean', # 清理缓存 '--clean',
'--noconfirm', # 不确认覆盖 '--noconfirm',
'--onefile', # 单文件模式 '--onefile',
'--windowed', # 无控制台窗口 '--windowed',
'--name', exe_name, # 输出文件名 '--name', exe_name,
'--distpath', build_dir, # 输出目录 '--distpath', build_dir,
'--workpath', os.path.join(build_dir, 'build'), # 工作目录 '--workpath', os.path.join(build_dir, 'build'),
'--specpath', build_dir, # spec文件目录 '--specpath', build_dir,
# 添加隐藏导入 # 添加隐藏导入
'--hidden-import', 'cryptography.fernet', '--hidden-import', 'cryptography.fernet',
'--hidden-import', 'cryptography.hazmat.primitives', '--hidden-import', 'cryptography.hazmat.primitives',
@ -166,9 +194,10 @@ class SecureEXEEncryptor:
'--hidden-import', 'tkinter', '--hidden-import', 'tkinter',
'--hidden-import', 'tkinter.messagebox', '--hidden-import', 'tkinter.messagebox',
'--hidden-import', '_tkinter', '--hidden-import', '_tkinter',
'--hidden-import', 'sqlite3',
# 收集所有cryptography包 # 收集所有cryptography包
'--collect-all', 'cryptography', '--collect-all', 'cryptography',
# 禁用UPX(避免压缩问题) # 禁用UPX
'--noupx', '--noupx',
work_py work_py
] ]
@ -176,12 +205,12 @@ class SecureEXEEncryptor:
print("开始编译...") print("开始编译...")
print(f"命令: {' '.join(cmd)}") print(f"命令: {' '.join(cmd)}")
# 5. 执行编译(增加超时时间) # 5. 执行编译
result = subprocess.run( result = subprocess.run(
cmd, cmd,
capture_output=True, capture_output=True,
text=True, text=True,
timeout=600, # 10分钟超时 timeout=600,
cwd=build_dir, cwd=build_dir,
encoding='utf-8', encoding='utf-8',
errors='replace' errors='replace'
@ -190,11 +219,11 @@ class SecureEXEEncryptor:
# 6. 输出详细日志 # 6. 输出详细日志
if result.stdout: if result.stdout:
print("=== 编译输出 ===") print("=== 编译输出 ===")
print(result.stdout[-2000:]) # 最后2000字符 print(result.stdout[-2000:])
if result.stderr: if result.stderr:
print("=== 编译错误 ===") print("=== 编译错误 ===")
print(result.stderr[-2000:]) # 最后2000字符 print(result.stderr[-2000:])
# 7. 检查编译结果 # 7. 检查编译结果
if result.returncode == 0: if result.returncode == 0:
@ -240,10 +269,9 @@ class SecureEXEEncryptor:
print(f"编译异常: {error_detail}") print(f"编译异常: {error_detail}")
return False, f"编译过程出错: {str(e)}\n\n详细信息:\n{error_detail[-500:]}" return False, f"编译过程出错: {str(e)}\n\n详细信息:\n{error_detail[-500:]}"
finally: finally:
# 确保清理临时文件(如果编译失败) # 清理临时文件(如果编译失败)
if build_dir and os.path.exists(build_dir): if build_dir and os.path.exists(build_dir):
try: try:
# 只在编译失败时保留,方便调试
if not os.path.exists(output_path): if not os.path.exists(output_path):
print(f"⚠️ 编译失败,工作目录已保留用于调试: {build_dir}") print(f"⚠️ 编译失败,工作目录已保留用于调试: {build_dir}")
except: except:
@ -252,22 +280,32 @@ class SecureEXEEncryptor:
# ========== 测试代码 ========== # ========== 测试代码 ==========
if __name__ == '__main__': if __name__ == '__main__':
print("EXE加密器 V2 - 测试")
print("=" * 60)
# 测试加密 # 测试加密
encryptor = SecureEXEEncryptor() encryptor = SecureEXEEncryptor()
# 配置 # 配置
api_config = { api_config = {
'api_url': 'https://your-server.com/api', 'api_url': 'http://localhost:5100/',
'api_key': 'your-secure-api-key-here' 'api_key': 'taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224'
} }
# 加密 # 示例加密一个测试EXE
success, msg = encryptor.encrypt_exe( # success, msg = encryptor.encrypt_exe(
source_path='test.exe', # source_path='test.exe',
output_path='test_encrypted.exe', # output_path='test_encrypted_v2.exe',
api_config=api_config, # api_config=api_config,
software_name='TestApp' # 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 import os
@ -18,16 +26,18 @@ import base64
from datetime import datetime, timedelta from datetime import datetime, timedelta
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
import requests 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_BASE_URL = "http://localhost:5100/" # 使用本地API服务器
API_KEY = "your-secure-api-key-here" # 从环境变量读取更安全 API_KEY = "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224" # 从环境变量读取更安全
SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER" # 打包时替换 SOFTWARE_NAME = "SOFTWARE_NAME_PLACEHOLDER" # 打包时替换
# 本地缓存配置
CACHE_FILE = os.path.join(tempfile.gettempdir(), '.lic_cache')
CACHE_VALIDITY_HOURS = 24 # 缓存有效期
# ========== 机器码生成 ========== # ========== 机器码生成 ==========
def get_machine_code(): def get_machine_code():
"""生成机器码(简化且稳定的版本)""" """生成机器码(简化且稳定的版本)"""
@ -60,65 +70,148 @@ def get_machine_code():
fallback = f"{platform.node()}{uuid.getnode()}" fallback = f"{platform.node()}{uuid.getnode()}"
return hashlib.md5(fallback.encode()).hexdigest()[:16].upper() return hashlib.md5(fallback.encode()).hexdigest()[:16].upper()
# ========== 加密缓存 ========== # ========== 持久化缓存管理器 ==========
def _get_cache_key(): class LicenseCache:
"""获取缓存加密密钥(基于机器码)""" """持久化许可证缓存管理器使用SQLite"""
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): def __init__(self):
"""保存验证缓存""" # 存储在用户AppData目录不会被系统清理
try: if os.name == 'nt': # Windows
data = { cache_base = Path(os.environ.get('APPDATA', Path.home()))
'license_key': license_key, else: # Linux/Mac
'token': token, cache_base = Path.home() / '.config'
'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(): self.cache_dir = cache_base / '.secure_license'
"""加载验证缓存""" self.cache_dir.mkdir(exist_ok=True, parents=True)
try: self.db_path = self.cache_dir / 'license.db'
if not os.path.exists(CACHE_FILE): 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()
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()
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 return None
with open(CACHE_FILE, 'rb') as f: def should_revalidate(self, license_data):
encrypted = f.read() """判断是否需要重新验证每7天联网验证一次"""
try:
last_validated = datetime.fromisoformat(license_data['last_validated'])
return (datetime.now() - last_validated).days >= 7
except:
return True
fernet = Fernet(_get_cache_key()) def is_expired(self, license_data):
decrypted = fernet.decrypt(encrypted) """检查许可证是否过期"""
data = json.loads(decrypted.decode()) try:
end_time = datetime.fromisoformat(license_data['end_time'])
return datetime.now() > end_time
except:
return True
# 验证机器码 def get_days_remaining(self, license_data):
if data.get('machine_code') != get_machine_code(): """获取剩余天数"""
return None 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):
expires_at = datetime.fromisoformat(data.get('expires_at')) """更新最后验证时间"""
if datetime.now() > expires_at: try:
return None 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
return data def clear_license(self, software_name, machine_code):
"""清除许可证缓存"""
except Exception as e: try:
print(f"加载缓存失败: {e}") conn = sqlite3.connect(str(self.db_path))
return None conn.execute('''
DELETE FROM license_cache
def clear_cache(): WHERE software_name=? AND machine_code=?
"""清除缓存""" ''', (software_name, machine_code))
try: conn.commit()
if os.path.exists(CACHE_FILE): conn.close()
os.remove(CACHE_FILE) return True
except: except Exception as e:
pass print(f"清除许可证缓存失败: {e}")
return False
# ========== API通信 ========== # ========== API通信 ==========
def create_signature(license_key, machine_code, software_name): 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) signature = create_signature(license_key, machine_code, SOFTWARE_NAME)
response = requests.post( response = requests.post(
f"{API_BASE_URL}/validate", f"{API_BASE_URL}api/validate",
json={ json={
'license_key': license_key, 'license_key': license_key,
'machine_code': machine_code, 'machine_code': machine_code,
@ -140,17 +233,26 @@ def validate_license_online(license_key, machine_code):
'signature': signature 'signature': signature
}, },
headers={'X-API-Key': API_KEY}, headers={'X-API-Key': API_KEY},
timeout=10 timeout=10,
verify=False # 跳过SSL证书验证用于自签名证书
) )
if response.status_code == 200: if response.status_code == 200:
data = response.json() data = response.json()
if data.get('success'): 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, license_key,
machine_code,
data.get('token', ''), data.get('token', ''),
data.get('expires_at', (datetime.now() + timedelta(hours=24)).isoformat()) start_time,
end_time
) )
return True, data.get('message', '验证成功') return True, data.get('message', '验证成功')
else: else:
@ -165,21 +267,163 @@ def validate_license_online(license_key, machine_code):
except Exception as e: except Exception as e:
return False, f"验证过程出错: {str(e)}" return False, f"验证过程出错: {str(e)}"
def validate_license_offline(cache_data): def validate_license_offline(license_data):
"""离线验证(使用缓存)""" """增强的离线验证"""
try: try:
# 验证token可选发送到服务器验证 # 1. 验证数据完整性
token = cache_data.get('token', '') required_fields = ['license_key', 'token', 'end_time', 'machine_code']
if not token: if not all(field in license_data for field in required_fields):
return False, "缓存数据不完整" return False, "缓存数据不完整", None
# 这里可以添加本地token验证逻辑 # 2. 验证机器码
# 或者在有网络时向服务器验证token 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: 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 # ENCRYPTED_RESOURCE_PLACEHOLDER
@ -195,7 +439,6 @@ def decrypt_resource():
import base64 import base64
# 重建完整密钥 # 重建完整密钥
# key_hint包含前半部分key_part2包含后半部分
key_part1_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_hint'].encode()) key_part1_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_hint'].encode())
key_part2_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_part2'].encode()) key_part2_bytes = base64.b64decode(ENCRYPTED_RESOURCE['key_part2'].encode())
@ -245,9 +488,113 @@ def extract_and_run():
except Exception as e: except Exception as e:
return False, f"启动失败: {str(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(): def on_activate():
license_key = entry_key.get().strip() license_key = entry_key.get().strip()
@ -256,118 +603,163 @@ def show_activation_dialog():
return return
btn_activate.config(state=tk.DISABLED, text="验证中...") btn_activate.config(state=tk.DISABLED, text="验证中...")
status_label.config(text="正在验证激活码...", fg="blue") status_label.config(text="正在联网验证激活码,请稍候...", fg="blue")
root.update() root.update()
def validate_thread(): def validate_thread():
machine_code = get_machine_code()
success, msg = validate_license_online(license_key, machine_code) success, msg = validate_license_online(license_key, machine_code)
root.after(0, lambda: handle_result(success, msg)) root.after(0, lambda: handle_result(success, msg))
threading.Thread(target=validate_thread, daemon=True).start() threading.Thread(target=validate_thread, daemon=True).start()
def handle_result(success, msg): def handle_result(success, msg):
if success: if success:
messagebox.showinfo("成功", "激活成功!正在启动程序...") messagebox.showinfo("成功", f"激活成功!\n{msg}\n\n正在启动程序...")
root.destroy() root.destroy()
# 启动程序
ok, err = extract_and_run() ok, err = extract_and_run()
if ok: if ok:
sys.exit(0) sys.exit(0)
else: else:
messagebox.showerror("错误", err) messagebox.showerror("错误", f"程序启动失败:\n{err}")
sys.exit(1) sys.exit(1)
else: else:
messagebox.showerror("失败", msg) messagebox.showerror("激活失败", f"{msg}\n\n请检查:\n1. 激活码是否正确\n2. 网络连接是否正常\n3. 激活码是否已被其他设备使用")
btn_activate.config(state=tk.NORMAL, text="验证并启动") btn_activate.config(state=tk.NORMAL, text="验证并激活")
status_label.config(text="", fg="black") status_label.config(text="", fg="black")
machine_code = get_machine_code() btn_activate = tk.Button(frame_buttons, text="验证并激活",
command=on_activate,
root = tk.Tk() bg="#27ae60", fg="white",
root.title("软件激活验证") font=("Microsoft YaHei", 11, "bold"),
root.geometry("500x400") padx=25, pady=10,
root.resizable(False, False) cursor="hand2")
# 居中
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.pack(side=tk.LEFT, padx=10) 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, tk.Button(frame_buttons, text="退出", command=sys.exit,
bg="#e74c3c", fg="white", font=("Microsoft YaHei", 11, "bold"), bg="#e74c3c", fg="white",
padx=20, pady=8).pack(side=tk.LEFT, padx=10) 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.bind('<Return>', lambda e: on_activate())
root.protocol("WM_DELETE_WINDOW", sys.exit) root.protocol("WM_DELETE_WINDOW", sys.exit)
root.mainloop() 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(): def main():
"""主函数""" """主函数"""
print("=" * 60)
print(f"软件许可证验证系统 V2.0")
print(f"软件名称: {SOFTWARE_NAME}")
print(f"机器码: {get_machine_code()}")
print("=" * 60)
# 1. 尝试加载缓存 # 使用智能验证策略
cache_data = load_cache() smart_validate()
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()
if __name__ == '__main__': if __name__ == '__main__':
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 文件进行测试。