更新验证程序
This commit is contained in:
parent
b83ecf8beb
commit
c83ab2dcf5
@ -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
574
README.md
@ -1,574 +0,0 @@
|
||||
# ExeProtector - 个人管理版
|
||||
|
||||
> **简单、安全、高效的软件加密授权系统**
|
||||
> **适合个人开发者和小团队使用**
|
||||
|
||||
[](https://github.com/yourusername/exeprotector)
|
||||
[](https://www.python.org/)
|
||||
[](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
## 📖 项目简介
|
||||
|
||||
ExeProtector 是一个专为个人开发者设计的软件加密授权系统,帮助你保护自己的软件产品,防止盗版和未授权使用。
|
||||
|
||||
### ✨ 核心特点
|
||||
|
||||
- 🔐 **真正的加密**:采用 AES-256 加密算法,不是简单的编码
|
||||
- 🌐 **云端验证**:授权验证在云服务器进行,客户端无法绕过
|
||||
- 🎨 **友好界面**:Tkinter 图形界面,操作简单直观
|
||||
- 💾 **数据库管理**:MySQL 存储授权数据,稳定可靠
|
||||
- 🚀 **轻量部署**:最低 30 元/月,30 分钟完成部署
|
||||
- 🔒 **安全隔离**:数据库密码不会暴露给客户端
|
||||
|
||||
### 🎯 适用场景
|
||||
|
||||
- ✅ 个人开发者想要保护自己的软件
|
||||
- ✅ 小团队需要简单的授权管理系统
|
||||
- ✅ 软件售价在 100-1000 元之间
|
||||
- ✅ 对安全性有一定要求,但预算有限
|
||||
- ✅ 希望保持现有的管理方式
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 系统架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 你的电脑(管理端) │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ main.py 管理界面 │ │
|
||||
│ │ • 生成卡密 │ │
|
||||
│ │ • 管理授权 │ │
|
||||
│ │ • 加密软件 │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ ↓ 直连 │
|
||||
└─────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ 云服务器(验证端) │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ api_server_lite.py │ │
|
||||
│ │ • 验证卡密 │ │
|
||||
│ │ • 激活授权 │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ MySQL 数据库 │ │
|
||||
│ └─────────────────────────┘ │
|
||||
└─────────────────────────────────┘
|
||||
↑
|
||||
┌─────────────────────────────────┐
|
||||
│ 用户电脑(客户端) │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ 加密的 EXE 程序 │ │
|
||||
│ │ • 输入卡密激活 │ │
|
||||
│ │ • 联网验证 │ │
|
||||
│ │ • 运行受保护的程序 │ │
|
||||
│ └─────────────────────────┘ │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 文件结构
|
||||
|
||||
```
|
||||
Exeprotector/
|
||||
├── 📄 核心代码文件
|
||||
│ ├── main.py # 管理界面(Tkinter)
|
||||
│ ├── database.py # 数据库操作
|
||||
│ ├── config.py # 配置文件
|
||||
│ ├── machine_code.py # 机器码生成
|
||||
│ ├── api_server_lite.py # 轻量级API服务器(部署到云端)
|
||||
│ ├── validator_secure.py # 安全验证器(嵌入到加密EXE)
|
||||
│ └── encryptor_secure.py # AES加密器
|
||||
│
|
||||
├── 📁 数据目录
|
||||
│ ├── licenses_local.db # 本地SQLite数据库(可选)
|
||||
│ ├── db_config.json # 数据库配置
|
||||
│ └── files/ # 文件存储目录
|
||||
│ ├── executables/ # 加密后的EXE文件
|
||||
│ ├── backups/ # 备份文件
|
||||
│ └── file_metadata.json # 文件元数据
|
||||
│
|
||||
└── 📚 文档文件
|
||||
├── README.md # 本文件
|
||||
├── 个人版_开始这里.md # ⭐ 快速入门指南
|
||||
├── 个人版_快速部署指南.md # 详细部署教程
|
||||
├── 个人版_文件清单.md # 文件说明
|
||||
├── 方案_个人管理版.md # 核心方案文档
|
||||
└── env_config_example.txt # 环境变量配置示例
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 准备工作
|
||||
|
||||
在开始之前,请确保你有:
|
||||
|
||||
- [x] 一台云服务器(1核1G 即可,约 30 元/月)
|
||||
- [x] Python 3.7+ 环境
|
||||
- [x] MySQL 数据库
|
||||
- [x] 30-40 分钟的时间
|
||||
|
||||
### 第一步:阅读文档(5分钟)
|
||||
|
||||
**⭐ 强烈推荐先阅读:**
|
||||
|
||||
```bash
|
||||
个人版_开始这里.md
|
||||
```
|
||||
|
||||
这个文档会告诉你:
|
||||
- 为什么选择这个方案
|
||||
- 系统是如何工作的
|
||||
- 成本和效果如何
|
||||
- 是否适合你的需求
|
||||
|
||||
### 第二步:部署 API 服务器(15分钟)
|
||||
|
||||
**详细教程请查看:**
|
||||
|
||||
```bash
|
||||
个人版_快速部署指南.md
|
||||
```
|
||||
|
||||
简要步骤:
|
||||
|
||||
1. 上传 `api_server_lite.py` 到云服务器
|
||||
2. 安装依赖:
|
||||
```bash
|
||||
pip3 install flask mysql-connector-python
|
||||
```
|
||||
3. 配置环境变量
|
||||
4. 启动服务
|
||||
|
||||
### 第三步:配置本地管理程序(5分钟)
|
||||
|
||||
1. 安装依赖:
|
||||
```bash
|
||||
pip install mysql-connector-python cryptography requests
|
||||
```
|
||||
|
||||
2. 修改 `main.py` 中的加密配置(约第 1365 行):
|
||||
|
||||
```python
|
||||
# 在 encrypt_software_by_name 方法中
|
||||
from encryptor_secure import SecureEXEEncryptor
|
||||
|
||||
api_config = {
|
||||
'api_url': 'https://your-domain.com/api', # 改成你的服务器地址
|
||||
'api_key': 'your-api-key-here' # 改成你的API密钥
|
||||
}
|
||||
|
||||
encryptor = SecureEXEEncryptor()
|
||||
```
|
||||
|
||||
3. 配置数据库连接(`db_config.json`)
|
||||
|
||||
### 第四步:测试运行(10分钟)
|
||||
|
||||
1. 启动管理程序:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
2. 测试流程:
|
||||
- 连接数据库
|
||||
- 添加软件产品
|
||||
- 生成卡密
|
||||
- 加密测试EXE
|
||||
- 在另一台电脑测试激活
|
||||
|
||||
**完成!** 🎉
|
||||
|
||||
---
|
||||
|
||||
## 💡 使用指南
|
||||
|
||||
### 管理卡密
|
||||
|
||||
1. 运行 `main.py`
|
||||
2. 连接数据库
|
||||
3. 在"卡密管理"标签页:
|
||||
- 生成卡密
|
||||
- 查看卡密状态
|
||||
- 管理到期时间
|
||||
- 封禁/解封卡密
|
||||
|
||||
### 加密软件
|
||||
|
||||
1. 在"软件管理"标签页添加软件信息
|
||||
2. 选择要加密的 EXE 文件
|
||||
3. 点击"加密软件"
|
||||
4. 等待加密完成
|
||||
5. 在 `files/executables/` 目录获取加密后的 EXE
|
||||
|
||||
### 分发给用户
|
||||
|
||||
1. 将加密后的 EXE 发送给用户
|
||||
2. 用户运行 EXE 后会显示机器码
|
||||
3. 用户提供机器码给你
|
||||
4. 你在管理界面生成对应的卡密
|
||||
5. 用户输入卡密完成激活
|
||||
|
||||
---
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 数据库配置
|
||||
|
||||
编辑 `db_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "your-mysql-host",
|
||||
"port": 3306,
|
||||
"user": "your-username",
|
||||
"password": "your-password",
|
||||
"database": "filesend_db"
|
||||
}
|
||||
```
|
||||
|
||||
### API 服务器配置
|
||||
|
||||
在云服务器上设置环境变量:
|
||||
|
||||
```bash
|
||||
export DB_HOST=localhost
|
||||
export DB_USER=your-username
|
||||
export DB_PASSWORD=your-password
|
||||
export DB_NAME=filesend_db
|
||||
export API_KEY=$(python3 -c "import os; print(os.urandom(32).hex())")
|
||||
```
|
||||
|
||||
### 安全建议
|
||||
|
||||
1. **必须修改 API 密钥**
|
||||
- 不要使用默认值
|
||||
- 使用强随机密钥
|
||||
|
||||
2. **启用 HTTPS**
|
||||
- 申请免费 SSL 证书(Let's Encrypt)
|
||||
- 通过 Nginx 配置反向代理
|
||||
|
||||
3. **定期备份数据库**
|
||||
```bash
|
||||
mysqldump -u username -p database_name > backup.sql
|
||||
```
|
||||
|
||||
4. **限制 SSH 访问**
|
||||
- 使用密钥认证
|
||||
- 禁用密码登录
|
||||
- 配置 fail2ban
|
||||
|
||||
---
|
||||
|
||||
## 💰 成本分析
|
||||
|
||||
| 项目 | 费用 | 说明 |
|
||||
|------|------|------|
|
||||
| 云服务器 | ¥30-50/月 | 1核1G 已足够 |
|
||||
| 域名 | ¥50-100/年 | 可选但推荐 |
|
||||
| SSL 证书 | 免费 | Let's Encrypt |
|
||||
| **总计** | **¥50/月** | 约 **¥600/年** |
|
||||
|
||||
### 投资回报率(ROI)
|
||||
|
||||
假设:
|
||||
- 软件售价:¥100
|
||||
- 用户数:1000
|
||||
- 盗版率:从 50% 降到 20%
|
||||
|
||||
**额外收入**:1000 × ¥100 × 30% = **¥30,000/年**
|
||||
|
||||
**投资回报率**:(30,000 - 600) / 600 × 100% = **4900%**
|
||||
|
||||
---
|
||||
|
||||
## 🔒 安全性说明
|
||||
|
||||
### 修复前的问题
|
||||
|
||||
- ❌ 数据库密码硬编码在客户端
|
||||
- ❌ 使用 hex 编码(伪加密)
|
||||
- ❌ 验证逻辑在客户端
|
||||
- ❌ PyInstaller 容易反编译
|
||||
- ❌ 破解难度:5 分钟
|
||||
|
||||
### 修复后的改进
|
||||
|
||||
- ✅ 数据库密码只在云服务器
|
||||
- ✅ 使用 AES-256 真加密
|
||||
- ✅ 验证逻辑在云端
|
||||
- ✅ API 密钥保护
|
||||
- ✅ 破解难度:2-5 天
|
||||
|
||||
**安全评分:从 2/10 提升到 6.5/10**
|
||||
|
||||
### 安全等级说明
|
||||
|
||||
| 等级 | 破解时间 | 适用场景 | 成本 |
|
||||
|------|---------|---------|------|
|
||||
| 2/10 | 5分钟 | ❌ 不适用 | - |
|
||||
| 6.5/10 | 2-5天 | ✅ 个人/小团队 | ¥600/年 |
|
||||
| 8/10 | 数周 | 企业级 | ¥5,000+/年 |
|
||||
| 9.5/10 | 数月 | 军工级 | ¥50,000+/年 |
|
||||
|
||||
**本方案适合软件售价在 100-1000 元的个人开发者。**
|
||||
|
||||
---
|
||||
|
||||
## 🐛 故障排查
|
||||
|
||||
### API 服务器无法访问
|
||||
|
||||
```bash
|
||||
# 检查服务状态
|
||||
systemctl status license-api
|
||||
|
||||
# 检查端口
|
||||
netstat -tulpn | grep 5000
|
||||
|
||||
# 检查防火墙
|
||||
ufw status
|
||||
|
||||
# 查看日志
|
||||
journalctl -u license-api -f
|
||||
```
|
||||
|
||||
### 数据库连接失败
|
||||
|
||||
```bash
|
||||
# 测试连接
|
||||
mysql -h host -u user -p
|
||||
|
||||
# 检查环境变量
|
||||
echo $DB_HOST
|
||||
echo $DB_USER
|
||||
```
|
||||
|
||||
### 加密失败
|
||||
|
||||
1. 检查 `validator_secure.py` 是否存在
|
||||
2. 检查 `encryptor_secure.py` 是否存在
|
||||
3. 检查依赖是否安装:
|
||||
```bash
|
||||
pip list | grep cryptography
|
||||
```
|
||||
|
||||
### 激活失败
|
||||
|
||||
1. 检查 API 服务器日志
|
||||
2. 确认 API 密钥配置一致
|
||||
3. 测试网络连接
|
||||
4. 检查数据库中的软件名称
|
||||
|
||||
更多问题请查看:`个人版_快速部署指南.md`
|
||||
|
||||
---
|
||||
|
||||
## 📊 性能指标
|
||||
|
||||
### API 服务器(1核1G)
|
||||
|
||||
- 并发请求:50-100
|
||||
- 响应时间:< 50ms
|
||||
- 内存占用:< 100MB
|
||||
- CPU 占用:< 10%
|
||||
|
||||
### 支持规模
|
||||
|
||||
- 日活用户:500+
|
||||
- 月活用户:2000+
|
||||
- 峰值 QPS:50
|
||||
|
||||
---
|
||||
|
||||
## 🔄 版本历史
|
||||
|
||||
### v2.0(当前版本)
|
||||
|
||||
- ✅ 采用 AES-256 真加密
|
||||
- ✅ 云端 API 验证
|
||||
- ✅ 数据库密码隔离
|
||||
- ✅ 请求签名保护
|
||||
- ✅ 完整的文档
|
||||
|
||||
### v1.0(已废弃)
|
||||
|
||||
- ❌ hex 编码(不安全)
|
||||
- ❌ 客户端验证
|
||||
- ❌ 数据库密码硬编码
|
||||
|
||||
---
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
### 免费支持
|
||||
|
||||
- 📖 查看文档(推荐先阅读文档)
|
||||
- 💬 邮件咨询:shoubo1224@qq.com
|
||||
- 📱 微信:taiyi1224
|
||||
|
||||
### 付费服务
|
||||
|
||||
- 🔧 远程部署:¥200/次(包成功)
|
||||
- 📞 技术支持:¥500/月
|
||||
- 🎓 一对一培训:¥500/小时
|
||||
- 🛠️ 定制开发:¥1000+ 起
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q1: 必须要有域名吗?
|
||||
|
||||
**A:** 不必须,但强烈推荐。
|
||||
|
||||
- 没有域名:只能用 IP,无法使用 HTTPS,不够安全
|
||||
- 有域名:支持 HTTPS,更稳定、更专业
|
||||
|
||||
域名很便宜(¥50-100/年),值得投资。
|
||||
|
||||
### Q2: 数据库会被改动吗?
|
||||
|
||||
**A:** 完全不会!
|
||||
|
||||
- 不需要迁移数据
|
||||
- 不需要修改表结构
|
||||
- 现有功能完全正常
|
||||
- 只是加了一个 API 层
|
||||
|
||||
### Q3: 部署很难吗?
|
||||
|
||||
**A:** 非常简单!
|
||||
|
||||
如果你会:
|
||||
- SSH 登录服务器
|
||||
- 复制粘贴命令
|
||||
- 修改几行代码
|
||||
|
||||
那就完全没问题!预计 30-40 分钟。
|
||||
|
||||
### Q4: 维护麻烦吗?
|
||||
|
||||
**A:** 几乎不需要维护!
|
||||
|
||||
日常操作:
|
||||
- 管理卡密 - 用现有界面(不变)
|
||||
- 加密软件 - 用现有界面(不变)
|
||||
- 查看统计 - 用现有界面(不变)
|
||||
|
||||
偶尔需要:
|
||||
- 重启 API 服务器(很少)
|
||||
- 查看日志(有问题时)
|
||||
- 备份数据库(建议每周)
|
||||
|
||||
### Q5: 可以破解吗?
|
||||
|
||||
**A:** 任何加密都可能被破解,但:
|
||||
|
||||
- 本方案破解时间:2-5 天
|
||||
- 对于普通用户:难度太高
|
||||
- 对于专业黑客:成本太高
|
||||
|
||||
**破解成本 > 购买成本 = 有效保护**
|
||||
|
||||
如果软件售价很高(>¥5000),建议使用更高级的方案。
|
||||
|
||||
### Q6: 适合我吗?
|
||||
|
||||
**适合:**
|
||||
- ✅ 个人开发者
|
||||
- ✅ 小团队(< 10人)
|
||||
- ✅ 软件售价 ¥100-1000
|
||||
- ✅ 预算有限
|
||||
- ✅ 需要基本保护
|
||||
|
||||
**不适合:**
|
||||
- ❌ 大型企业
|
||||
- ❌ 软件售价 > ¥5000
|
||||
- ❌ 需要军工级安全
|
||||
- ❌ 有专业安全团队
|
||||
|
||||
---
|
||||
|
||||
## 🎓 学习路径
|
||||
|
||||
### 第1天:理解方案
|
||||
|
||||
1. 阅读 `README.md`(本文件)
|
||||
2. 阅读 `个人版_开始这里.md`
|
||||
3. 阅读 `方案_个人管理版.md`
|
||||
|
||||
### 第2天:部署系统
|
||||
|
||||
1. 按照 `个人版_快速部署指南.md` 部署
|
||||
2. 配置 API 服务器
|
||||
3. 修改本地程序
|
||||
|
||||
### 第3天:测试优化
|
||||
|
||||
1. 测试加密功能
|
||||
2. 测试激活流程
|
||||
3. 优化配置
|
||||
|
||||
### 第4天:了解安全原理(可选)
|
||||
|
||||
1. 学习 AES 加密原理
|
||||
2. 学习 API 认证机制
|
||||
3. 学习安全最佳实践
|
||||
|
||||
---
|
||||
|
||||
## 📝 许可证
|
||||
|
||||
本项目采用 MIT 许可证。
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 ExeProtector
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
感谢所有使用和支持本项目的开发者!
|
||||
|
||||
特别感谢:
|
||||
- Python 社区
|
||||
- Flask 框架
|
||||
- MySQL 数据库
|
||||
- Let's Encrypt
|
||||
|
||||
---
|
||||
|
||||
## 🚀 立即开始
|
||||
|
||||
**准备好了吗?现在就开始部署吧!**
|
||||
|
||||
👉 **下一步:打开 `个人版_开始这里.md`**
|
||||
|
||||
预计时间:30-40 分钟
|
||||
预计成本:¥50/月
|
||||
|
||||
**你绝对可以做到!** 💪
|
||||
|
||||
---
|
||||
|
||||
*最后更新:2025-10-23*
|
||||
*作者:太一*
|
||||
*微信:taiyi1224*
|
||||
*邮箱:shoubo1224@qq.com*
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"api_url": "https://129.211.65.73:5100/",
|
||||
"api_url": "http://localhost:5100/",
|
||||
"api_key": "taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224taiyi1224",
|
||||
"comment": "请修改上面的配置为你的实际服务器地址和API密钥"
|
||||
"comment": "使用本地API服务器。如果需要远程验证,改为远程服务器地址"
|
||||
}
|
||||
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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验证器包含持久化缓存、到期提醒等改进功能")
|
||||
|
||||
|
||||
@ -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 限制配置文件权限
|
||||
|
||||
43
files/executables/config.ini
Normal file
43
files/executables/config.ini
Normal 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 = 珠海,落马,股票,股市,股民,爆炸,火灾,死亡,抢劫,诈骗,习大大,习近平,政府,官员,扫黑,警察,落网,嫌疑人,通报,暴力执法,执法,暴力,气象,天气,暴雨,大雨
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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
155
start_api_server.py
Normal 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)
|
||||
|
||||
@ -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()
|
||||
|
||||
288
编译问题解决指南.md
288
编译问题解决指南.md
@ -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 文件进行测试。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user