8.7
This commit is contained in:
commit
ea4278d1be
3
.idea/.gitignore
vendored
Normal file
3
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
14
.idea/Exeprotector.iml
Normal file
14
.idea/Exeprotector.iml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
94
.idea/inspectionProfiles/Project_Default.xml
Normal file
94
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="81">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="httpx" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="PySimpleGUI" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="greenlet" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="joblib" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="threadpoolctl" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="Babel" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="huggingface-hub" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="scikit-learn" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="nltk" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="PyYAML" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="h11" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="PyQt5-sip" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="QtPy" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="hanziconv" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="docxcompose" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="MarkupSafe" />
|
||||||
|
<item index="16" class="java.lang.String" itemvalue="mkl" />
|
||||||
|
<item index="17" class="java.lang.String" itemvalue="fsspec" />
|
||||||
|
<item index="18" class="java.lang.String" itemvalue="PyQt5-Qt5" />
|
||||||
|
<item index="19" class="java.lang.String" itemvalue="filelock" />
|
||||||
|
<item index="20" class="java.lang.String" itemvalue="playwright" />
|
||||||
|
<item index="21" class="java.lang.String" itemvalue="guiqwt" />
|
||||||
|
<item index="22" class="java.lang.String" itemvalue="PyQt5" />
|
||||||
|
<item index="23" class="java.lang.String" itemvalue="safetensors" />
|
||||||
|
<item index="24" class="java.lang.String" itemvalue="certifi" />
|
||||||
|
<item index="25" class="java.lang.String" itemvalue="anyio" />
|
||||||
|
<item index="26" class="java.lang.String" itemvalue="lxml" />
|
||||||
|
<item index="27" class="java.lang.String" itemvalue="PythonQwt" />
|
||||||
|
<item index="28" class="java.lang.String" itemvalue="soupsieve" />
|
||||||
|
<item index="29" class="java.lang.String" itemvalue="sympy" />
|
||||||
|
<item index="30" class="java.lang.String" itemvalue="xlrd" />
|
||||||
|
<item index="31" class="java.lang.String" itemvalue="beautifulsoup4" />
|
||||||
|
<item index="32" class="java.lang.String" itemvalue="tokenizers" />
|
||||||
|
<item index="33" class="java.lang.String" itemvalue="pydantic" />
|
||||||
|
<item index="34" class="java.lang.String" itemvalue="transformers" />
|
||||||
|
<item index="35" class="java.lang.String" itemvalue="pyperclip" />
|
||||||
|
<item index="36" class="java.lang.String" itemvalue="h5py" />
|
||||||
|
<item index="37" class="java.lang.String" itemvalue="synonyms" />
|
||||||
|
<item index="38" class="java.lang.String" itemvalue="pyqt-tools" />
|
||||||
|
<item index="39" class="java.lang.String" itemvalue="click" />
|
||||||
|
<item index="40" class="java.lang.String" itemvalue="openai" />
|
||||||
|
<item index="41" class="java.lang.String" itemvalue="regex" />
|
||||||
|
<item index="42" class="java.lang.String" itemvalue="pydantic_core" />
|
||||||
|
<item index="43" class="java.lang.String" itemvalue="tbb" />
|
||||||
|
<item index="44" class="java.lang.String" itemvalue="charset-normalizer" />
|
||||||
|
<item index="45" class="java.lang.String" itemvalue="httpcore" />
|
||||||
|
<item index="46" class="java.lang.String" itemvalue="idna" />
|
||||||
|
<item index="47" class="java.lang.String" itemvalue="distro" />
|
||||||
|
<item index="48" class="java.lang.String" itemvalue="jieba" />
|
||||||
|
<item index="49" class="java.lang.String" itemvalue="networkx" />
|
||||||
|
<item index="50" class="java.lang.String" itemvalue="Whoosh" />
|
||||||
|
<item index="51" class="java.lang.String" itemvalue="numpy" />
|
||||||
|
<item index="52" class="java.lang.String" itemvalue="Jinja2" />
|
||||||
|
<item index="53" class="java.lang.String" itemvalue="sniffio" />
|
||||||
|
<item index="54" class="java.lang.String" itemvalue="exceptiongroup" />
|
||||||
|
<item index="55" class="java.lang.String" itemvalue="tomli" />
|
||||||
|
<item index="56" class="java.lang.String" itemvalue="guidata" />
|
||||||
|
<item index="57" class="java.lang.String" itemvalue="cnsyn" />
|
||||||
|
<item index="58" class="java.lang.String" itemvalue="urllib3" />
|
||||||
|
<item index="59" class="java.lang.String" itemvalue="pyee" />
|
||||||
|
<item index="60" class="java.lang.String" itemvalue="volcengine-python-sdk" />
|
||||||
|
<item index="61" class="java.lang.String" itemvalue="annotated-types" />
|
||||||
|
<item index="62" class="java.lang.String" itemvalue="chatoperastore" />
|
||||||
|
<item index="63" class="java.lang.String" itemvalue="scipy" />
|
||||||
|
<item index="64" class="java.lang.String" itemvalue="python-docx" />
|
||||||
|
<item index="65" class="java.lang.String" itemvalue="OpenCC" />
|
||||||
|
<item index="66" class="java.lang.String" itemvalue="six" />
|
||||||
|
<item index="67" class="java.lang.String" itemvalue="intel-openmp" />
|
||||||
|
<item index="68" class="java.lang.String" itemvalue="tzdata" />
|
||||||
|
<item index="69" class="java.lang.String" itemvalue="packaging" />
|
||||||
|
<item index="70" class="java.lang.String" itemvalue="torch" />
|
||||||
|
<item index="71" class="java.lang.String" itemvalue="et-xmlfile" />
|
||||||
|
<item index="72" class="java.lang.String" itemvalue="chardet" />
|
||||||
|
<item index="73" class="java.lang.String" itemvalue="pandas" />
|
||||||
|
<item index="74" class="java.lang.String" itemvalue="tqdm" />
|
||||||
|
<item index="75" class="java.lang.String" itemvalue="colorama" />
|
||||||
|
<item index="76" class="java.lang.String" itemvalue="mpmath" />
|
||||||
|
<item index="77" class="java.lang.String" itemvalue="typing_extensions" />
|
||||||
|
<item index="78" class="java.lang.String" itemvalue="pillow" />
|
||||||
|
<item index="79" class="java.lang.String" itemvalue="pytz" />
|
||||||
|
<item index="80" class="java.lang.String" itemvalue="openpyxl" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.10 (Exeprotector)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/Exeprotector.iml" filepath="$PROJECT_DIR$/.idea/Exeprotector.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/vcs.xml
Normal file
7
.idea/vcs.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?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>
|
||||||
106
config.py
Normal file
106
config.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"""
|
||||||
|
Configuration file for the EXE encryption system
|
||||||
|
Contains security settings and system parameters
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
作者:太一
|
||||||
|
微信:taiyi1224
|
||||||
|
邮箱:shoubo1224@qq.com
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# System configuration
|
||||||
|
SYSTEM_CONFIG = {
|
||||||
|
'app_name': 'EXE Secure Wrapper',
|
||||||
|
'version': '2.0.0',
|
||||||
|
'company': 'Secure Software Solutions',
|
||||||
|
'contact': 'support@securesoft.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
SECURITY_CONFIG = {
|
||||||
|
# Encryption settings
|
||||||
|
'key_length': 32, # bytes
|
||||||
|
'salt_length': 32, # bytes
|
||||||
|
'iterations': 100000, # PBKDF2 iterations
|
||||||
|
'hash_algorithm': 'sha256',
|
||||||
|
'xor_key': os.environ.get('EXE_WRAPPER_KEY', 'EXEWrapper#2024').encode(),
|
||||||
|
|
||||||
|
# File validation
|
||||||
|
'min_file_size': 1024, # bytes
|
||||||
|
'magic_header': b'ENC_MAGIC',
|
||||||
|
'header_size': 512, # 配置头固定长度(字节)
|
||||||
|
|
||||||
|
# License settings
|
||||||
|
'trial_days': 7,
|
||||||
|
'license_key_length': 20,
|
||||||
|
'license_format': 'XXXXX-XXXXX-XXXXX-XXXXX',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Temporary file settings
|
||||||
|
TEMP_CONFIG = {
|
||||||
|
'temp_prefix': 'sec_wrap_',
|
||||||
|
'max_temp_age': 3600, # seconds (1 hour)
|
||||||
|
'auto_cleanup': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Database settings (会被嵌入到包装文件中)
|
||||||
|
DATABASE_CONFIG = {
|
||||||
|
'mysql': {
|
||||||
|
'host': os.environ.get('DB_HOST', 'localhost'),
|
||||||
|
'port': int(os.environ.get('DB_PORT', 3306)),
|
||||||
|
'database': os.environ.get('DB_NAME', 'license_system'),
|
||||||
|
'user': os.environ.get('DB_USER', 'root'),
|
||||||
|
'password': os.environ.get('DB_PASSWORD', ''),
|
||||||
|
'charset': 'utf8mb4',
|
||||||
|
'connection_timeout': 30,
|
||||||
|
'ssl_disabled': True,
|
||||||
|
},
|
||||||
|
'sqlite': {
|
||||||
|
'filename': 'licenses_local.db',
|
||||||
|
'check_same_thread': False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
LOGGING_CONFIG = {
|
||||||
|
'level': 'INFO',
|
||||||
|
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
'file_max_size': 10 * 1024 * 1024, # 10MB
|
||||||
|
'backup_count': 5,
|
||||||
|
'log_dir': 'logs',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validation settings
|
||||||
|
VALIDATION_CONFIG = {
|
||||||
|
'check_internet': True,
|
||||||
|
'max_retries': 3,
|
||||||
|
'retry_delay': 1, # seconds
|
||||||
|
'timeout': 10, # seconds
|
||||||
|
'heartbeat_interval': 300, # seconds (5 minutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build paths
|
||||||
|
BASE_DIR = Path(__file__).parent.absolute()
|
||||||
|
CONFIG_DIR = BASE_DIR / "config"
|
||||||
|
LOG_DIR = BASE_DIR / LOGGING_CONFIG['log_dir']
|
||||||
|
TEMP_DIR = BASE_DIR / "temp"
|
||||||
|
|
||||||
|
# Ensure directories exist
|
||||||
|
for directory in [LOG_DIR, TEMP_DIR, CONFIG_DIR]:
|
||||||
|
directory.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
def get_config_path(filename):
|
||||||
|
"""Get full path to configuration file"""
|
||||||
|
return CONFIG_DIR / filename
|
||||||
|
|
||||||
|
def get_temp_path(filename=None):
|
||||||
|
"""Get temporary file path"""
|
||||||
|
if filename:
|
||||||
|
return TEMP_DIR / filename
|
||||||
|
return TEMP_DIR
|
||||||
|
|
||||||
|
def get_log_path(filename):
|
||||||
|
"""Get log file path"""
|
||||||
|
return LOG_DIR / filename
|
||||||
630
database.py
Normal file
630
database.py
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
"""
|
||||||
|
作者:太一
|
||||||
|
微信:taiyi1224
|
||||||
|
邮箱:shoubo1224@qq.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Tuple, List, Dict, Optional
|
||||||
|
|
||||||
|
import mysql.connector
|
||||||
|
from mysql.connector import Error
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 导入文件管理器
|
||||||
|
try:
|
||||||
|
from file_manager import FileManager
|
||||||
|
except ImportError:
|
||||||
|
FileManager = None
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseDatabase:
|
||||||
|
def __init__(self, host=None, database=None, user=None, password=None):
|
||||||
|
self.host = host or os.environ.get('DB_HOST', 'taiyiagi.xyz')
|
||||||
|
self.database = database or os.environ.get('DB_NAME', 'filesend_db')
|
||||||
|
self.user = user or os.environ.get('DB_USER', 'taiyi')
|
||||||
|
self.password = password or os.environ.get('DB_PASSWORD', 'taiyi1224')
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
# 初始化文件管理器
|
||||||
|
if FileManager:
|
||||||
|
self.file_manager = FileManager()
|
||||||
|
else:
|
||||||
|
self.file_manager = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""连接到数据库"""
|
||||||
|
try:
|
||||||
|
self.connection = mysql.connector.connect(
|
||||||
|
host=self.host,
|
||||||
|
database=self.database,
|
||||||
|
user=self.user,
|
||||||
|
password=self.password
|
||||||
|
)
|
||||||
|
if self.connection.is_connected():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Error as e:
|
||||||
|
print(f"数据库连接错误: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_tables(self):
|
||||||
|
"""创建必要的数据库表"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
# 创建软件产品表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS software_products (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
version VARCHAR(50),
|
||||||
|
exe_path VARCHAR(500),
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 检查并添加缺失的字段
|
||||||
|
try:
|
||||||
|
# 检查version字段是否存在
|
||||||
|
cursor.execute("SHOW COLUMNS FROM software_products LIKE 'version'")
|
||||||
|
if not cursor.fetchone():
|
||||||
|
cursor.execute("ALTER TABLE software_products ADD COLUMN version VARCHAR(50) AFTER description")
|
||||||
|
print("成功添加version字段")
|
||||||
|
except Error as e:
|
||||||
|
print(f"检查version字段时出错: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 检查exe_path字段是否存在
|
||||||
|
cursor.execute("SHOW COLUMNS FROM software_products LIKE 'exe_path'")
|
||||||
|
if not cursor.fetchone():
|
||||||
|
cursor.execute("ALTER TABLE software_products ADD COLUMN exe_path VARCHAR(500) AFTER version")
|
||||||
|
print("成功添加exe_path字段")
|
||||||
|
except Error as e:
|
||||||
|
print(f"检查exe_path字段时出错: {e}")
|
||||||
|
|
||||||
|
# 创建卡密表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS license_keys (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
key_code VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
software_id INT NOT NULL,
|
||||||
|
machine_code VARCHAR(100) DEFAULT NULL,
|
||||||
|
start_time DATETIME DEFAULT NULL,
|
||||||
|
end_time DATETIME NOT NULL,
|
||||||
|
status ENUM('unused', 'active', 'expired', 'banned') DEFAULT 'unused',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (software_id) REFERENCES software_products(id)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
print(f"创建表错误: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def generate_key(self, days_valid, software_id):
|
||||||
|
"""生成一个新的卡密并保存到数据库"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 生成UUID作为基础,然后进行哈希处理
|
||||||
|
key_uuid = uuid.uuid4().hex
|
||||||
|
hash_obj = hashlib.sha256(key_uuid.encode())
|
||||||
|
# 取前20个字符作为卡密
|
||||||
|
key_code = hash_obj.hexdigest()[:20].upper()
|
||||||
|
|
||||||
|
# 格式化卡密,每5个字符一组
|
||||||
|
formatted_key = '-'.join([key_code[i:i + 5] for i in range(0, len(key_code), 5)])
|
||||||
|
|
||||||
|
# 计算过期时间
|
||||||
|
end_time = datetime.now() + timedelta(days=days_valid)
|
||||||
|
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
query = """
|
||||||
|
INSERT INTO license_keys (key_code, software_id, end_time)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
"""
|
||||||
|
cursor.execute(query, (formatted_key, software_id, end_time))
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return formatted_key
|
||||||
|
except Error as e:
|
||||||
|
print(f"生成卡密错误: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def validate_key(self, key_code, machine_code, software_id):
|
||||||
|
"""验证卡密是否有效,并绑定机器码 - 严格一机一码"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False, "数据库连接失败"
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor(dictionary=True)
|
||||||
|
|
||||||
|
# 查询卡密信息
|
||||||
|
query = "SELECT * FROM license_keys WHERE key_code = %s AND software_id = %s"
|
||||||
|
cursor.execute(query, (key_code, software_id))
|
||||||
|
key_info = cursor.fetchone()
|
||||||
|
|
||||||
|
if not key_info:
|
||||||
|
cursor.close()
|
||||||
|
return False, "无效的激活码"
|
||||||
|
|
||||||
|
# 检查卡密状态
|
||||||
|
if key_info['status'] == 'banned':
|
||||||
|
cursor.close()
|
||||||
|
return False, "激活码已被封禁"
|
||||||
|
|
||||||
|
if key_info['status'] == 'expired':
|
||||||
|
cursor.close()
|
||||||
|
return False, "激活码已过期"
|
||||||
|
|
||||||
|
# 一机一码严格检查:每个激活码只能在一台机器上使用
|
||||||
|
if key_info['status'] == 'active':
|
||||||
|
if key_info['machine_code'] != machine_code:
|
||||||
|
# 这个码已经用过了,不能再次使用
|
||||||
|
cursor.close()
|
||||||
|
return False, f"此激活码已在设备{key_info['machine_code'][:8]}...上使用,一个激活码只能在一台设备上使用一次"
|
||||||
|
else:
|
||||||
|
# 已经激活过这台机器,验证是否过期
|
||||||
|
if datetime.now() > key_info['end_time']:
|
||||||
|
update_query = "UPDATE license_keys SET status = 'expired' WHERE key_code = %s"
|
||||||
|
cursor.execute(update_query, (key_code,))
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
return False, "激活码已过期"
|
||||||
|
else:
|
||||||
|
cursor.close()
|
||||||
|
return True, "此设备已激活,继续使用"
|
||||||
|
|
||||||
|
# 首次激活:验证通过后绑定到机器
|
||||||
|
if key_info['status'] == 'unused':
|
||||||
|
update_query = """
|
||||||
|
UPDATE license_keys
|
||||||
|
SET status = 'active', machine_code = %s, start_time = %s
|
||||||
|
WHERE key_code = %s AND status = 'unused'
|
||||||
|
"""
|
||||||
|
cursor.execute(update_query, (machine_code, datetime.now(), key_code))
|
||||||
|
rows_affected = cursor.rowcount
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
if rows_affected == 0:
|
||||||
|
# 可能已经被其他并发操作激活
|
||||||
|
cursor.close()
|
||||||
|
return False, "激活码已被使用,请使用新的激活码"
|
||||||
|
|
||||||
|
# 再次检查是否过期(防止并发问题)
|
||||||
|
final_check_query = "SELECT * FROM license_keys WHERE key_code = %s"
|
||||||
|
cursor.execute(final_check_query, (key_code,))
|
||||||
|
final_info = cursor.fetchone()
|
||||||
|
|
||||||
|
if final_info and datetime.now() > final_info['end_time']:
|
||||||
|
update_query = "UPDATE license_keys SET status = 'expired' WHERE key_code = %s"
|
||||||
|
cursor.execute(update_query, (key_code,))
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
return False, "激活码已过期"
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
return True, "激活成功"
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
print(f"验证激活码错误: {e}")
|
||||||
|
return False, f"验证过程出错: {str(e)}"
|
||||||
|
|
||||||
|
def get_all_keys(self, software_id=None):
|
||||||
|
"""获取所有卡密信息,可按软件ID筛选"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
print("警告: 数据库未连接,尝试重新连接...")
|
||||||
|
if not self.connect():
|
||||||
|
print("错误: 数据库重连失败")
|
||||||
|
return []
|
||||||
|
|
||||||
|
cursor = None
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor(dictionary=True)
|
||||||
|
|
||||||
|
# 先检查license_keys表是否存在
|
||||||
|
try:
|
||||||
|
cursor.execute("SHOW TABLES LIKE 'license_keys'")
|
||||||
|
if not cursor.fetchone():
|
||||||
|
print("警告: license_keys表不存在,请先创建数据库表")
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
return []
|
||||||
|
except Error as e:
|
||||||
|
print(f"检查表存在性时出错: {e}")
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 检查software_products表是否存在以及字段是否完整
|
||||||
|
use_join = False
|
||||||
|
try:
|
||||||
|
cursor.execute("SHOW TABLES LIKE 'software_products'")
|
||||||
|
if cursor.fetchone():
|
||||||
|
# 表存在,检查字段
|
||||||
|
cursor.execute("SHOW COLUMNS FROM software_products")
|
||||||
|
columns = [row['Field'] for row in cursor.fetchall()]
|
||||||
|
if 'name' in columns: # 如果有name字段,就使用JOIN查询
|
||||||
|
use_join = True
|
||||||
|
except Error as e:
|
||||||
|
print(f"检查software_products表时出错: {e}")
|
||||||
|
use_join = False
|
||||||
|
|
||||||
|
# 构建并执行查询
|
||||||
|
try:
|
||||||
|
if use_join:
|
||||||
|
if software_id:
|
||||||
|
query = """
|
||||||
|
SELECT lk.*, COALESCE(sp.name, 'Unknown') as software_name
|
||||||
|
FROM license_keys lk
|
||||||
|
LEFT JOIN software_products sp ON lk.software_id = sp.id
|
||||||
|
WHERE lk.software_id = %s
|
||||||
|
ORDER BY lk.created_at DESC
|
||||||
|
"""
|
||||||
|
cursor.execute(query, (software_id,))
|
||||||
|
else:
|
||||||
|
query = """
|
||||||
|
SELECT lk.*, COALESCE(sp.name, 'Unknown') as software_name
|
||||||
|
FROM license_keys lk
|
||||||
|
LEFT JOIN software_products sp ON lk.software_id = sp.id
|
||||||
|
ORDER BY lk.created_at DESC
|
||||||
|
"""
|
||||||
|
cursor.execute(query)
|
||||||
|
else:
|
||||||
|
# 不使用JOIN,只查询license_keys表
|
||||||
|
if software_id:
|
||||||
|
query = "SELECT *, 'Unknown' as software_name FROM license_keys WHERE software_id = %s ORDER BY created_at DESC"
|
||||||
|
cursor.execute(query, (software_id,))
|
||||||
|
else:
|
||||||
|
query = "SELECT *, 'Unknown' as software_name FROM license_keys ORDER BY created_at DESC"
|
||||||
|
cursor.execute(query)
|
||||||
|
|
||||||
|
keys = cursor.fetchall()
|
||||||
|
|
||||||
|
# 确保返回的是列表
|
||||||
|
if keys is None:
|
||||||
|
keys = []
|
||||||
|
|
||||||
|
if len(keys) == 0:
|
||||||
|
print("提示: 数据库中暂无卡密记录")
|
||||||
|
else:
|
||||||
|
print(f"成功从数据库获取 {len(keys)} 个卡密记录")
|
||||||
|
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
return keys
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
print(f"执行查询时出错: {e}")
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
return []
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
print(f"获取卡密列表错误: {type(e).__name__}: {e}")
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取卡密列表时发生未知错误: {type(e).__name__}: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
return []
|
||||||
|
|
||||||
|
def update_key_status(self, key_code, status):
|
||||||
|
"""更新卡密状态"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
query = "UPDATE license_keys SET status = %s WHERE key_code = %s"
|
||||||
|
cursor.execute(query, (status, key_code))
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
print(f"更新卡密状态错误: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def release_key(self, key_code):
|
||||||
|
"""释放已使用的激活码 - 将其重置为未使用状态,清空机器码"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False, "数据库连接失败"
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor(dictionary=True)
|
||||||
|
|
||||||
|
# 检查卡密是否存在且处于已激活状态
|
||||||
|
check_query = "SELECT * FROM license_keys WHERE key_code = %s"
|
||||||
|
cursor.execute(check_query, (key_code,))
|
||||||
|
key_info = cursor.fetchone()
|
||||||
|
|
||||||
|
if not key_info:
|
||||||
|
cursor.close()
|
||||||
|
return False, "激活码不存在"
|
||||||
|
|
||||||
|
if key_info['status'] != 'active':
|
||||||
|
cursor.close()
|
||||||
|
return False, f"激活码处于 {key_info['status']} 状态,只能释放已使用的激活码"
|
||||||
|
|
||||||
|
# 释放激活码:重置为未使用状态,清空机器码和开始时间
|
||||||
|
release_query = """
|
||||||
|
UPDATE license_keys
|
||||||
|
SET status = 'unused', machine_code = NULL, start_time = NULL
|
||||||
|
WHERE key_code = %s
|
||||||
|
"""
|
||||||
|
cursor.execute(release_query, (key_code,))
|
||||||
|
rows_affected = cursor.rowcount
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
if rows_affected > 0:
|
||||||
|
return True, "激活码已释放,可以重新使用"
|
||||||
|
else:
|
||||||
|
return False, "释放激活码失败"
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
print(f"释放激活码错误: {e}")
|
||||||
|
return False, f"释放过程出错: {str(e)}"
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭数据库连接"""
|
||||||
|
if self.connection and self.connection.is_connected():
|
||||||
|
self.connection.close()
|
||||||
|
def unbind_key(self, key_code: str) -> Tuple[bool, str]:
|
||||||
|
"""强制解除卡密与机器码的绑定"""
|
||||||
|
if not self.connection:
|
||||||
|
return False, "数据库未连接"
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor(dictionary=True)
|
||||||
|
cursor.execute(
|
||||||
|
"UPDATE license_keys SET status='unused', machine_code=NULL, start_time=NULL WHERE key_code=%s",
|
||||||
|
(key_code,))
|
||||||
|
self.connection.commit()
|
||||||
|
rows = cursor.rowcount
|
||||||
|
cursor.close()
|
||||||
|
return (True, "已解绑并释放") if rows else (False, "卡密不存在或状态异常")
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
def add_software_product_with_file(self, name: str, description: str = "", version: str = "",
|
||||||
|
exe_source_path: str = "") -> Tuple[bool, str]:
|
||||||
|
"""添加软件产品并上传exe文件"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False, "数据库连接失败"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 如果提供了exe文件路径,尝试上传
|
||||||
|
local_exe_path = ""
|
||||||
|
if exe_source_path and self.file_manager:
|
||||||
|
success, msg, relative_path = self.file_manager.upload_exe_file(
|
||||||
|
exe_source_path, name
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
local_exe_path = relative_path
|
||||||
|
else:
|
||||||
|
return False, f"文件上传失败: {msg}"
|
||||||
|
elif exe_source_path:
|
||||||
|
# 没有文件管理器,使用原来的逻辑
|
||||||
|
local_exe_path = exe_source_path
|
||||||
|
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
query = """
|
||||||
|
INSERT INTO software_products (name, description, version, exe_path)
|
||||||
|
VALUES (%s, %s, %s, %s)
|
||||||
|
"""
|
||||||
|
cursor.execute(query, (name, description, version, local_exe_path))
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
if exe_source_path and self.file_manager:
|
||||||
|
return True, f"软件产品添加成功!\n\n✅ 文件已自动保存到本地目录\n目录: {self.file_manager.exe_dir}\n文件名: {local_exe_path}\n\n现在你的EXE文件已经被安全地存储在程序专属目录中,不用担心文件移动问题了!"
|
||||||
|
else:
|
||||||
|
return True, "软件产品添加成功"
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
if "Duplicate entry" in str(e):
|
||||||
|
return False, "软件产品已存在"
|
||||||
|
return False, f"添加软件产品失败: {str(e)}"
|
||||||
|
|
||||||
|
def get_exe_full_path(self, relative_path: str) -> str:
|
||||||
|
"""获取exe文件的完整路径"""
|
||||||
|
if not relative_path:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 如果是绝对路径,直接返回
|
||||||
|
if os.path.isabs(relative_path):
|
||||||
|
return relative_path
|
||||||
|
|
||||||
|
# 如果有文件管理器,使用它获取路径
|
||||||
|
if self.file_manager:
|
||||||
|
return self.file_manager.get_exe_full_path(relative_path)
|
||||||
|
else:
|
||||||
|
# 退回到相对路径逻辑
|
||||||
|
base_dir = os.path.dirname(__file__)
|
||||||
|
return os.path.join(base_dir, "files", "executables", relative_path)
|
||||||
|
|
||||||
|
def delete_software_product_with_file(self, product_id: int) -> Tuple[bool, str]:
|
||||||
|
"""删除软件产品并删除关联的exe文件"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False, "数据库连接失败"
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor(dictionary=True)
|
||||||
|
|
||||||
|
# 获取软件信息
|
||||||
|
cursor.execute("SELECT * FROM software_products WHERE id = %s", (product_id,))
|
||||||
|
product = cursor.fetchone()
|
||||||
|
|
||||||
|
if not product:
|
||||||
|
cursor.close()
|
||||||
|
return False, "软件产品不存在"
|
||||||
|
|
||||||
|
# 检查是否有关联的卡密
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM license_keys WHERE software_id = %s", (product_id,))
|
||||||
|
key_count = cursor.fetchone()['COUNT(*)']
|
||||||
|
|
||||||
|
if key_count > 0:
|
||||||
|
cursor.close()
|
||||||
|
return False, f"无法删除,还有 {key_count} 个关联的卡密"
|
||||||
|
|
||||||
|
# 删除数据库记录
|
||||||
|
cursor.execute("DELETE FROM software_products WHERE id = %s", (product_id,))
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
# 删除关联的exe文件
|
||||||
|
exe_path = product.get('exe_path', '')
|
||||||
|
if exe_path and self.file_manager:
|
||||||
|
# 如果是相对路径,删除文件
|
||||||
|
if not os.path.isabs(exe_path):
|
||||||
|
file_success, file_msg = self.file_manager.delete_exe_file(exe_path)
|
||||||
|
if not file_success:
|
||||||
|
return True, f"软件产品删除成功,但文件删除失败: {file_msg}"
|
||||||
|
else:
|
||||||
|
return True, f"软件产品和文件删除成功: {file_msg}"
|
||||||
|
|
||||||
|
return True, "软件产品删除成功"
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
return False, f"删除软件产品失败: {str(e)}"
|
||||||
|
|
||||||
|
def get_file_storage_info(self) -> Dict:
|
||||||
|
"""获取文件存储信息"""
|
||||||
|
if self.file_manager:
|
||||||
|
return self.file_manager.get_storage_info()
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
'total_files': 0,
|
||||||
|
'total_size': 0,
|
||||||
|
'existing_files': 0,
|
||||||
|
'missing_files': 0,
|
||||||
|
'base_dir': '文件管理器未初始化',
|
||||||
|
'exe_dir': '',
|
||||||
|
'backup_dir': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_software_products(self) -> List[Dict]:
|
||||||
|
"""获取所有软件产品列表"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor(dictionary=True)
|
||||||
|
query = "SELECT * FROM software_products ORDER BY name"
|
||||||
|
cursor.execute(query)
|
||||||
|
products = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
return products
|
||||||
|
except Error as e:
|
||||||
|
print(f"获取软件产品列表错误: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_software_by_name(self, name: str) -> Optional[Dict]:
|
||||||
|
"""根据名称获取软件产品信息"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor(dictionary=True)
|
||||||
|
query = "SELECT * FROM software_products WHERE name = %s"
|
||||||
|
cursor.execute(query, (name,))
|
||||||
|
product = cursor.fetchone()
|
||||||
|
cursor.close()
|
||||||
|
return product
|
||||||
|
except Error as e:
|
||||||
|
print(f"获取软件产品信息错误: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_software_product(self, product_id: int, name: str = None, description: str = None,
|
||||||
|
version: str = None, exe_path: str = None) -> Tuple[bool, str]:
|
||||||
|
"""更新软件产品信息"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False, "数据库连接失败"
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
# 构建更新语句
|
||||||
|
updates = []
|
||||||
|
params = []
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
updates.append("name = %s")
|
||||||
|
params.append(name)
|
||||||
|
if description is not None:
|
||||||
|
updates.append("description = %s")
|
||||||
|
params.append(description)
|
||||||
|
if version is not None:
|
||||||
|
updates.append("version = %s")
|
||||||
|
params.append(version)
|
||||||
|
if exe_path is not None:
|
||||||
|
updates.append("exe_path = %s")
|
||||||
|
params.append(exe_path)
|
||||||
|
|
||||||
|
if not updates:
|
||||||
|
return False, "没有提供更新内容"
|
||||||
|
|
||||||
|
query = f"UPDATE software_products SET {', '.join(updates)} WHERE id = %s"
|
||||||
|
params.append(product_id)
|
||||||
|
|
||||||
|
cursor.execute(query, params)
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return True, "软件产品更新成功"
|
||||||
|
except Error as e:
|
||||||
|
if "Duplicate entry" in str(e):
|
||||||
|
return False, "软件名称已存在"
|
||||||
|
return False, f"更新软件产品失败: {str(e)}"
|
||||||
|
|
||||||
|
def delete_software_product(self, product_id: int) -> Tuple[bool, str]:
|
||||||
|
"""删除软件产品"""
|
||||||
|
if not self.connection or not self.connection.is_connected():
|
||||||
|
if not self.connect():
|
||||||
|
return False, "数据库连接失败"
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
# 检查是否有关联的卡密
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM license_keys WHERE software_id = %s", (product_id,))
|
||||||
|
key_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
if key_count > 0:
|
||||||
|
cursor.close()
|
||||||
|
return False, f"无法删除,还有 {key_count} 个关联的卡密"
|
||||||
|
|
||||||
|
cursor.execute("DELETE FROM software_products WHERE id = %s", (product_id,))
|
||||||
|
self.connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return True, "软件产品删除成功"
|
||||||
|
except Error as e:
|
||||||
|
return False, f"删除软件产品失败: {str(e)}"
|
||||||
1
db_config.json
Normal file
1
db_config.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"host": "129.211.65.73", "database": "exeprotector", "user": "root", "password": "taiyi1224"}
|
||||||
281
machine_code.py
Normal file
281
machine_code.py
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
"""
|
||||||
|
作者:太一
|
||||||
|
微信:taiyi1224
|
||||||
|
邮箱:shoubo1224@qq.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def get_windows_machine_code():
|
||||||
|
"""获取Windows系统的机器码"""
|
||||||
|
try:
|
||||||
|
# 尝试获取主板序列号
|
||||||
|
try:
|
||||||
|
wmic_output = subprocess.check_output(
|
||||||
|
'wmic baseboard get serialnumber',
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
timeout=10
|
||||||
|
).decode().strip()
|
||||||
|
|
||||||
|
if "SerialNumber" in wmic_output:
|
||||||
|
lines = [line.strip() for line in wmic_output.split("\n") if line.strip()]
|
||||||
|
if len(lines) > 1:
|
||||||
|
serial = lines[1]
|
||||||
|
if serial and serial not in ["To Be Filled By O.E.M.", "Default string", "N/A", ""]:
|
||||||
|
return hashlib.md5(serial.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试获取硬盘序列号
|
||||||
|
try:
|
||||||
|
wmic_output = subprocess.check_output(
|
||||||
|
'wmic diskdrive get serialnumber',
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
timeout=10
|
||||||
|
).decode().strip()
|
||||||
|
|
||||||
|
if "SerialNumber" in wmic_output:
|
||||||
|
lines = [line.strip() for line in wmic_output.split("\n") if line.strip()]
|
||||||
|
for line in lines[1:]: # 跳过标题行
|
||||||
|
if line and line not in ["", "N/A"]:
|
||||||
|
return hashlib.md5(line.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试获取CPU序列号
|
||||||
|
try:
|
||||||
|
wmic_output = subprocess.check_output(
|
||||||
|
'wmic cpu get processorid',
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
timeout=10
|
||||||
|
).decode().strip()
|
||||||
|
|
||||||
|
if "ProcessorId" in wmic_output:
|
||||||
|
lines = [line.strip() for line in wmic_output.split("\n") if line.strip()]
|
||||||
|
if len(lines) > 1:
|
||||||
|
cpu_id = lines[1]
|
||||||
|
if cpu_id and cpu_id != "":
|
||||||
|
return hashlib.md5(cpu_id.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试获取BIOS序列号
|
||||||
|
try:
|
||||||
|
wmic_output = subprocess.check_output(
|
||||||
|
'wmic bios get serialnumber',
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
timeout=10
|
||||||
|
).decode().strip()
|
||||||
|
|
||||||
|
if "SerialNumber" in wmic_output:
|
||||||
|
lines = [line.strip() for line in wmic_output.split("\n") if line.strip()]
|
||||||
|
if len(lines) > 1:
|
||||||
|
bios_serial = lines[1]
|
||||||
|
if bios_serial and bios_serial not in ["To Be Filled By O.E.M.", "Default string", "N/A", ""]:
|
||||||
|
return hashlib.md5(bios_serial.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 组合多个系统信息生成唯一标识
|
||||||
|
try:
|
||||||
|
# 获取网卡MAC地址
|
||||||
|
mac = uuid.getnode()
|
||||||
|
mac_str = ':'.join(['{:02x}'.format((mac >> elements) & 0xff) for elements in range(0, 2 * 6, 2)][::-1])
|
||||||
|
|
||||||
|
# 获取计算机名
|
||||||
|
computer_name = platform.node()
|
||||||
|
|
||||||
|
# 获取系统信息
|
||||||
|
system_info = f"{platform.system()}-{platform.release()}-{platform.version()}"
|
||||||
|
|
||||||
|
# 组合信息
|
||||||
|
combined_info = f"{mac_str}-{computer_name}-{system_info}"
|
||||||
|
return hashlib.md5(combined_info.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 最后的备用方案
|
||||||
|
fallback = f"{platform.uname()}-{uuid.getnode()}"
|
||||||
|
return hashlib.md5(fallback.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取Windows机器码错误: {e}")
|
||||||
|
# 生成一个基于系统信息的备用哈希
|
||||||
|
try:
|
||||||
|
fallback = f"{platform.node()}-{uuid.getnode()}-{platform.processor()}"
|
||||||
|
return hashlib.md5(fallback.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
# 终极备用方案
|
||||||
|
import time
|
||||||
|
fallback = f"FALLBACK-{int(time.time())}-{platform.system()}"
|
||||||
|
return hashlib.md5(fallback.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
|
||||||
|
def get_linux_machine_code():
|
||||||
|
"""获取Linux系统的机器码"""
|
||||||
|
try:
|
||||||
|
# 尝试读取machine-id
|
||||||
|
try:
|
||||||
|
with open('/etc/machine-id', 'r') as f:
|
||||||
|
machine_id = f.read().strip()
|
||||||
|
if machine_id and len(machine_id) > 10:
|
||||||
|
return machine_id[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试读取/var/lib/dbus/machine-id
|
||||||
|
try:
|
||||||
|
with open('/var/lib/dbus/machine-id', 'r') as f:
|
||||||
|
machine_id = f.read().strip()
|
||||||
|
if machine_id and len(machine_id) > 10:
|
||||||
|
return machine_id[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试获取主板信息
|
||||||
|
try:
|
||||||
|
with open('/sys/class/dmi/id/board_serial', 'r') as f:
|
||||||
|
board_serial = f.read().strip()
|
||||||
|
if board_serial and board_serial not in ["", "N/A", "To be filled by O.E.M."]:
|
||||||
|
return hashlib.md5(board_serial.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试获取产品UUID
|
||||||
|
try:
|
||||||
|
with open('/sys/class/dmi/id/product_uuid', 'r') as f:
|
||||||
|
product_uuid = f.read().strip()
|
||||||
|
if product_uuid and product_uuid != "":
|
||||||
|
return hashlib.md5(product_uuid.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 获取MAC地址和主机名组合
|
||||||
|
mac = uuid.getnode()
|
||||||
|
mac_str = ':'.join(['{:02x}'.format((mac >> elements) & 0xff) for elements in range(0, 2 * 6, 2)][::-1])
|
||||||
|
hostname = platform.node()
|
||||||
|
combined = f"{mac_str}-{hostname}"
|
||||||
|
return hashlib.md5(combined.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取Linux机器码错误: {e}")
|
||||||
|
# 备用方案
|
||||||
|
system_info = f"{platform.node()}-{uuid.getnode()}-{platform.machine()}"
|
||||||
|
return hashlib.md5(system_info.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
|
||||||
|
def get_mac_machine_code():
|
||||||
|
"""获取macOS系统的机器码"""
|
||||||
|
try:
|
||||||
|
# 尝试获取硬件UUID
|
||||||
|
try:
|
||||||
|
result = subprocess.check_output(
|
||||||
|
['system_profiler', 'SPHardwareDataType'],
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
timeout=10
|
||||||
|
).decode().strip()
|
||||||
|
|
||||||
|
for line in result.split('\n'):
|
||||||
|
if 'Hardware UUID' in line:
|
||||||
|
uuid_part = line.split(':')[1].strip()
|
||||||
|
return hashlib.md5(uuid_part.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 尝试获取序列号
|
||||||
|
try:
|
||||||
|
serial = subprocess.check_output(
|
||||||
|
['system_profiler', 'SPHardwareDataType', '|', 'grep', 'Serial'],
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
timeout=10
|
||||||
|
).decode().strip()
|
||||||
|
|
||||||
|
if serial:
|
||||||
|
serial_number = serial.split(':')[1].strip()
|
||||||
|
return hashlib.md5(serial_number.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 备用方案
|
||||||
|
mac = uuid.getnode()
|
||||||
|
hostname = platform.node()
|
||||||
|
combined = f"{mac}-{hostname}-{platform.machine()}"
|
||||||
|
return hashlib.md5(combined.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取macOS机器码错误: {e}")
|
||||||
|
# 备用方案
|
||||||
|
system_info = f"{platform.node()}-{uuid.getnode()}-{platform.machine()}"
|
||||||
|
return hashlib.md5(system_info.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
|
||||||
|
def get_machine_code():
|
||||||
|
"""获取当前系统的机器码"""
|
||||||
|
try:
|
||||||
|
system = platform.system()
|
||||||
|
|
||||||
|
if system == "Windows":
|
||||||
|
return get_windows_machine_code()
|
||||||
|
elif system == "Linux":
|
||||||
|
return get_linux_machine_code()
|
||||||
|
elif system == "Darwin": # macOS
|
||||||
|
return get_mac_machine_code()
|
||||||
|
else:
|
||||||
|
# 未知系统,使用通用方法
|
||||||
|
system_info = f"{platform.node()}-{uuid.getnode()}-{platform.processor()}-{platform.machine()}"
|
||||||
|
return hashlib.md5(system_info.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取机器码失败: {e}")
|
||||||
|
# 终极备用方案
|
||||||
|
try:
|
||||||
|
fallback = f"{platform.system()}-{uuid.getnode()}-{platform.node()}"
|
||||||
|
return hashlib.md5(fallback.encode()).hexdigest()[:16].upper()
|
||||||
|
except:
|
||||||
|
import time
|
||||||
|
ultimate_fallback = f"MACHINE-{int(time.time())}"
|
||||||
|
return hashlib.md5(ultimate_fallback.encode()).hexdigest()[:16].upper()
|
||||||
|
|
||||||
|
|
||||||
|
def format_machine_code(machine_code):
|
||||||
|
"""格式化机器码为更易读的格式"""
|
||||||
|
if len(machine_code) >= 16:
|
||||||
|
# 将16位机器码格式化为 XXXX-XXXX-XXXX-XXXX
|
||||||
|
return f"{machine_code[:4]}-{machine_code[4:8]}-{machine_code[8:12]}-{machine_code[12:16]}"
|
||||||
|
else:
|
||||||
|
return machine_code
|
||||||
|
|
||||||
|
|
||||||
|
def verify_machine_code(stored_code, current_code):
|
||||||
|
"""验证机器码是否匹配"""
|
||||||
|
# 移除格式化字符进行比较
|
||||||
|
stored_clean = stored_code.replace('-', '').upper()
|
||||||
|
current_clean = current_code.replace('-', '').upper()
|
||||||
|
return stored_clean == current_clean
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("机器码生成器测试")
|
||||||
|
print("-" * 30)
|
||||||
|
|
||||||
|
machine_code = get_machine_code()
|
||||||
|
formatted_code = format_machine_code(machine_code)
|
||||||
|
|
||||||
|
print(f"系统: {platform.system()} {platform.release()}")
|
||||||
|
print(f"原始机器码: {machine_code}")
|
||||||
|
print(f"格式化机器码: {formatted_code}")
|
||||||
|
print(f"机器码长度: {len(machine_code)}")
|
||||||
|
|
||||||
|
# 测试验证功能
|
||||||
|
print(f"\n验证测试: {verify_machine_code(formatted_code, machine_code)}")
|
||||||
|
|
||||||
|
input("\n按回车键退出...")
|
||||||
Loading…
Reference in New Issue
Block a user