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