修复页面搜索功能无法使用

This commit is contained in:
taiyi 2025-11-17 12:56:43 +08:00
parent 8d8c8a9aac
commit 95461a09ab
5 changed files with 1101 additions and 0 deletions

52
DOCUMENTATION_GUIDE.md Normal file
View File

@ -0,0 +1,52 @@
# 项目文档整理指南
本文档介绍了KaMiXiTong软件授权管理系统中各个文档的作用和使用方法帮助用户快速找到所需的文档。
## 文档分类
### 1. 入门指南
- [README.md](README.md) - 项目简介和快速开始指南
- [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) - 项目详细总结和架构说明
### 2. 部署文档
- [DEPLOYMENT.md](DEPLOYMENT.md) - MySQL数据库部署详细指南
- [MYSQL_CONFIG_GUIDE.md](MYSQL_CONFIG_GUIDE.md) - MySQL配置与初始化指南
### 3. 集成文档
- [docs/INTEGRATION.md](docs/INTEGRATION.md) - 软件授权系统集成指南
- [docs/EXAMPLES.md](docs/EXAMPLES.md) - 使用示例和代码模板
### 4. API文档
- [docs/FASTAPI.md](docs/FASTAPI.md) - FastAPI接口配置文档
- [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) - API部署说明
## 文档使用建议
### 新用户入门
1. 首先阅读 [README.md](README.md) 了解项目基本功能
2. 查看 [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) 了解系统架构和技术栈
3. 根据需要选择部署方式,参考相应的部署文档
### 开发者集成
1. 阅读 [docs/INTEGRATION.md](docs/INTEGRATION.md) 了解如何将授权验证器集成到您的软件中
2. 参考 [docs/EXAMPLES.md](docs/EXAMPLES.md) 中的代码示例进行实际开发
3. 如需使用FastAPI接口查看 [docs/FASTAPI.md](docs/FASTAPI.md)
### 系统管理员
1. 根据数据库类型选择相应的部署文档:
- MySQL数据库[DEPLOYMENT.md](DEPLOYMENT.md) 和 [MYSQL_CONFIG_GUIDE.md](MYSQL_CONFIG_GUIDE.md)
2. 参考 [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) 进行生产环境部署
## 已删除文档说明
以下文档已被删除,因为它们是过时的或与当前系统不相关:
- `DEPLOYMENT_GUIDE.md` - 账号管理系统重构部署指南(已过时)
- `SERVER_DEPLOYMENT_GUIDE.md` - 服务器部署指南(内容已整合到其他文档中)
- `REFACTOR_NOTES.md` - 账号管理系统重构说明(已过时)
## 注意事项
1. 所有文档均保持最新状态,与当前代码版本兼容
2. 如在使用过程中发现问题,请及时反馈
3. 建议收藏本文档,方便日后查阅

View File

@ -0,0 +1,203 @@
{% extends "base.html" %}
{% block title %}导出卡密 - 软件授权管理系统{% endblock %}
{% block page_title %}导出卡密{% endblock %}
{% block page_actions %}
<a href="{{ url_for('web.licenses') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>
返回列表
</a>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-body">
<form id="export-form">
<div class="mb-3">
<label for="product_id" class="form-label">选择产品</label>
<select class="form-select" id="product_id" name="product_id">
<option value="">全部产品</option>
{% for product in products %}
<option value="{{ product.product_id }}">{{ product.product_name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="status" class="form-label">卡密状态</label>
<select class="form-select" id="status" name="status">
<option value="">全部状态</option>
<option value="0">未激活</option>
<option value="1">已激活</option>
<option value="2">已过期</option>
<option value="3">已禁用</option>
</select>
</div>
<div class="mb-3">
<label for="type" class="form-label">卡密类型</label>
<select class="form-select" id="type" name="type">
<option value="">全部类型</option>
<option value="1">正式卡密</option>
<option value="0">试用卡密</option>
</select>
</div>
<div class="mb-3">
<label for="format" class="form-label">导出格式 *</label>
<select class="form-select" id="format" name="format" required>
<option value="excel">Excel (.xlsx)</option>
<option value="csv">CSV (.csv)</option>
</select>
</div>
<button type="submit" class="btn btn-success" id="submit-btn">
<i class="fas fa-file-export me-2"></i>
<span id="submit-text">导出卡密</span>
</button>
<a href="{{ url_for('web.licenses') }}" class="btn btn-secondary">取消</a>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow">
<div class="card-header">
<h6 class="mb-0">导出说明</h6>
</div>
<div class="card-body">
<ul class="list-unstyled">
<li class="mb-2">
<i class="fas fa-info-circle text-primary me-2"></i>
<small>可以选择特定产品、状态和类型的卡密进行导出</small>
</li>
<li class="mb-2">
<i class="fas fa-info-circle text-primary me-2"></i>
<small>导出格式支持Excel和CSV两种</small>
</li>
<li class="mb-2">
<i class="fas fa-info-circle text-primary me-2"></i>
<small>导出的文件包含卡密、产品、状态、有效期等详细信息</small>
</li>
<li class="mb-2">
<i class="fas fa-info-circle text-primary me-2"></i>
<small>导出操作可能需要一些时间,请耐心等待</small>
</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initEventListeners();
});
// 初始化事件监听器
function initEventListeners() {
// 表单提交
document.getElementById('export-form').addEventListener('submit', function(e) {
e.preventDefault();
exportLicenses();
});
}
// 导出卡密
function exportLicenses() {
const submitBtn = document.getElementById('submit-btn');
const submitText = document.getElementById('submit-text');
// 获取表单数据
const formData = {
product_id: document.getElementById('product_id').value || null,
status: document.getElementById('status').value ? parseInt(document.getElementById('status').value) : null,
type: document.getElementById('type').value ? parseInt(document.getElementById('type').value) : null,
format: document.getElementById('format').value
};
// 显示加载状态
submitBtn.disabled = true;
submitText.textContent = '导出中...';
// 直接使用fetch API而不是apiRequest函数因为我们需要处理文件下载
const fullUrl = (window.FRONTEND_DOMAIN || window.location.origin) + '/api/v1/licenses/export';
fetch(fullUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify(formData)
})
.then(response => {
// 隐藏加载动画
hideLoading();
if (!response.ok) {
// 处理错误响应
if (response.status === 401) {
showNotification('会话已过期,请重新登录', 'warning');
setTimeout(() => {
window.location.href = '/login';
}, 1500);
throw new Error('未授权访问');
} else if (response.status === 403) {
return response.json().then(errorData => {
showNotification(errorData.message || '权限不足,无法执行此操作', 'error');
throw new Error(`403: ${errorData.message || '权限不足'}`);
});
} else {
return response.json().then(errorData => {
throw new Error(`${response.status}: ${errorData.message || response.statusText}`);
});
}
}
// 获取文件名
const contentDisposition = response.headers.get('Content-Disposition');
let filename = 'licenses.xlsx';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
if (filenameMatch && filenameMatch.length === 2) {
filename = filenameMatch[1];
}
}
// 下载文件
return response.blob().then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showNotification('导出成功', 'success');
});
})
.catch(error => {
// 隐藏加载动画
hideLoading();
console.error('Failed to export licenses:', error);
showNotification(error.message || '导出失败', 'error');
})
.finally(() => {
// 恢复按钮状态
submitBtn.disabled = false;
submitText.textContent = '导出卡密';
});
}
</script>
{% endblock %}

292
deploy.py Normal file
View File

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
KaMiXiTong 部署脚本
用于生产环境的一键部署
"""
import os
import sys
import subprocess
import argparse
from pathlib import Path
def check_python_version():
"""检查Python版本"""
if sys.version_info < (3, 6):
print("❌ 错误: 需要Python 3.6或更高版本")
print(f"当前版本: {sys.version}")
sys.exit(1)
print(f"✅ Python版本检查通过: {sys.version}")
def create_directories():
"""创建必要的目录"""
directories = [
'logs',
'static/uploads',
'static/css',
'static/js'
]
for directory in directories:
Path(directory).mkdir(parents=True, exist_ok=True)
print("✅ 目录结构创建完成")
def check_env_file():
"""检查环境配置文件"""
env_file = Path('.env')
if not env_file.exists():
print("⚠️ 未找到 .env 文件,正在创建默认配置...")
try:
with open('.env.example', 'r', encoding='utf-8') as src:
with open('.env', 'w', encoding='utf-8') as dst:
dst.write(src.read())
print("✅ 已创建默认 .env 文件")
except FileNotFoundError:
print("❌ 未找到 .env.example 文件")
return False
else:
print("✅ 环境配置文件已存在")
return True
def install_dependencies():
"""安装依赖"""
print("📦 正在安装依赖包...")
try:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt'])
# 检查并安装生产环境依赖
try:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'gunicorn'])
print("✅ Gunicorn安装完成")
except subprocess.CalledProcessError:
print("⚠️ Gunicorn安装失败")
print("✅ 依赖安装完成")
return True
except subprocess.CalledProcessError:
print("❌ 依赖安装失败")
return False
def init_database():
"""初始化数据库"""
print("🗄️ 正在初始化数据库...")
# 检查使用哪种数据库
env_file = Path('.env')
if env_file.exists():
with open(env_file, 'r', encoding='utf-8') as f:
content = f.read()
if 'DATABASE_URL=sqlite' in content:
init_script = 'init_db_sqlite.py'
else:
init_script = 'setup_mysql.py'
else:
# 默认使用SQLite
init_script = 'init_db_sqlite.py'
try:
if os.path.exists(init_script):
subprocess.check_call([sys.executable, init_script])
print("✅ 数据库初始化完成")
return True
else:
print(f"⚠️ 数据库初始化脚本 {init_script} 不存在")
return False
except subprocess.CalledProcessError as e:
print(f"❌ 数据库初始化失败: {e}")
return False
def setup_systemd_service():
"""配置Systemd服务"""
print("⚙️ 正在配置Systemd服务...")
# 获取当前路径
app_path = os.path.abspath('.')
venv_path = os.path.join(app_path, 'venv', 'bin', 'gunicorn')
# 如果没有虚拟环境使用系统gunicorn
if not os.path.exists(venv_path):
venv_path = 'gunicorn'
service_content = f"""[Unit]
Description=KaMiXiTong Service
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory={app_path}
Environment="FLASK_ENV=production"
ExecStart={venv_path} -w 4 -b 127.0.0.1:5000 run:app
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
"""
try:
# 写入服务文件
with open('/tmp/kamaxitong.service', 'w') as f:
f.write(service_content)
print("✅ Systemd服务配置文件已生成")
print("💡 请使用以下命令安装服务:")
print(" sudo cp /tmp/kamaxitong.service /etc/systemd/system/")
print(" sudo systemctl daemon-reload")
print(" sudo systemctl enable kamaxitong.service")
print(" sudo systemctl start kamaxitong.service")
return True
except Exception as e:
print(f"❌ Systemd服务配置失败: {e}")
return False
def setup_nginx_config():
"""配置Nginx"""
print("⚙️ 正在生成Nginx配置...")
# 从环境变量获取域名
frontend_domain = os.environ.get('FRONTEND_DOMAIN', '')
if frontend_domain:
# 移除协议部分
if '://' in frontend_domain:
server_name = frontend_domain.split('://', 1)[1]
else:
server_name = frontend_domain
else:
server_name = 'your-domain.com'
nginx_config = f"""server {{
listen 80;
server_name {server_name};
# 重定向到HTTPS可选
return 301 https://$server_name$request_uri;
}}
server {{
listen 443 ssl http2;
server_name {server_name};
# SSL证书配置请替换为实际路径
# ssl_certificate /path/to/your/certificate.crt;
# ssl_certificate_key /path/to/your/private.key;
# 安全头配置
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss;
# 代理到Flask应用
location / {{
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $server_name;
}}
# 静态文件直接由Nginx处理
location /static/ {{
alias {os.path.abspath('static')}/;
expires 1y;
add_header Cache-Control "public, immutable";
}}
# 日志配置
access_log /var/log/nginx/kamaxitong.access.log;
error_log /var/log/nginx/kamaxitong.error.log;
}}
"""
try:
# 写入配置文件
with open('/tmp/kamaxitong_nginx.conf', 'w') as f:
f.write(nginx_config)
print("✅ Nginx配置文件已生成")
print("💡 请使用以下命令安装配置:")
print(" sudo cp /tmp/kamaxitong_nginx.conf /etc/nginx/sites-available/kamaxitong")
print(" sudo ln -s /etc/nginx/sites-available/kamaxitong /etc/nginx/sites-enabled/")
print(" sudo nginx -t")
print(" sudo systemctl restart nginx")
return True
except Exception as e:
print(f"❌ Nginx配置生成失败: {e}")
return False
def main():
"""主函数"""
parser = argparse.ArgumentParser(description='KaMiXiTong 部署脚本')
parser.add_argument('--skip-install', action='store_true', help='跳过依赖安装')
parser.add_argument('--skip-db', action='store_true', help='跳过数据库初始化')
parser.add_argument('--setup-service', action='store_true', help='配置Systemd服务')
parser.add_argument('--setup-nginx', action='store_true', help='生成Nginx配置')
args = parser.parse_args()
print("=" * 50)
print(" KaMiXiTong 生产环境部署脚本")
print("=" * 50)
# 检查Python版本
check_python_version()
# 创建目录
create_directories()
# 检查环境配置
if not check_env_file():
sys.exit(1)
# 安装依赖
if not args.skip_install:
if not install_dependencies():
sys.exit(1)
else:
print("⏭️ 跳过依赖安装")
# 初始化数据库
if not args.skip_db:
if not init_database():
sys.exit(1)
else:
print("⏭️ 跳过数据库初始化")
# 配置Systemd服务
if args.setup_service:
setup_systemd_service()
# 配置Nginx
if args.setup_nginx:
setup_nginx_config()
print("\n" + "=" * 50)
print("✅ 部署准备完成!")
print("=" * 50)
if args.setup_service:
print("💡 已生成Systemd服务配置文件请按提示安装")
if args.setup_nginx:
print("💡 已生成Nginx配置文件请按提示安装")
print("\n📌 下一步操作:")
print("1. 编辑 .env 文件,配置数据库和其他参数")
print("2. 配置SSL证书推荐")
print("3. 启动服务")
print("4. 访问你的域名")
if __name__ == '__main__':
main()

134
docs/DEPLOYMENT.md Normal file
View File

@ -0,0 +1,134 @@
# 部署说明
本文档介绍如何将KaMiXiTong系统部署到生产环境并通过域名访问。
## 域名访问配置
### 问题描述
在本地开发环境中,系统默认通过 `http://localhost:5000` 访问。当部署到服务器并通过域名访问时,前端页面中的接口调用可能会仍然指向 `localhost:5000`,导致接口调用失败。
### 解决方案
系统已通过修改前端JavaScript中的 `apiRequest` 函数来自动检测当前访问的主机并构建正确的API调用地址。
前端JavaScript现在会使用 `window.location.origin` 来获取当前访问的完整域名并自动构建完整的API URL。
### 配置Nginx反向代理
推荐使用Nginx作为反向代理服务器。以下是一个Nginx配置示例
```nginx
server {
listen 80;
server_name your-domain.com;
# 重定向到HTTPS可选
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name your-domain.com;
# SSL证书配置
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
# 代理到Flask应用
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态文件直接由Nginx处理
location /static/ {
alias /path/to/your/app/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
### 环境变量配置
在生产环境中,建议通过环境变量来配置应用:
```bash
# 复制示例配置文件
cp .env.example .env
# 编辑配置文件
nano .env
```
重要的环境变量包括:
- `FLASK_ENV`: 设置为 `production`
- `SECRET_KEY`: 设置为强随机字符串
- `DATABASE_URL`: 数据库连接URL
- `HOST`: 监听地址,生产环境建议设置为 `0.0.0.0`
### 使用Gunicorn启动应用
在生产环境中推荐使用Gunicorn作为WSGI服务器
```bash
# 安装Gunicorn
pip install gunicorn
# 启动应用
gunicorn -w 4 -b 127.0.0.1:5000 run:app
```
### 使用Systemd管理服务Linux
创建Systemd服务文件
```ini
# /etc/systemd/system/kamaxitong.service
[Unit]
Description=KaMiXiTong Service
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/your/app
Environment="FLASK_ENV=production"
ExecStart=/path/to/your/venv/bin/gunicorn -w 4 -b 127.0.0.1:5000 run:app
Restart=always
[Install]
WantedBy=multi-user.target
```
启用并启动服务:
```bash
sudo systemctl enable kamaxitong.service
sudo systemctl start kamaxitong.service
```
## 常见问题
### Q: 前端页面能正常访问但API调用失败怎么办
A: 检查以下几点:
1. 确认Nginx配置正确特别是 `proxy_set_header` 指令
2. 确认Flask应用监听地址设置为 `0.0.0.0`
3. 检查浏览器开发者工具中的网络请求确认API请求的URL是否正确
### Q: 如何确认域名配置是否正确?
A: 可以通过以下方式检查:
1. 在浏览器中打开应用按F12打开开发者工具
2. 切换到Network标签页
3. 刷新页面观察API请求的URL是否为域名地址而非localhost
### Q: 静态文件无法加载怎么办?
A: 确保Nginx配置中正确设置了静态文件的location块或者让Flask应用处理静态文件。

420
init_db_mysql.py Normal file
View File

@ -0,0 +1,420 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MySQL 数据库初始化脚本
KaMiXiTong 软件授权管理系统
使用 MySQL 数据库的完整版本支持多用户和高性能
"""
import os
import sys
from datetime import datetime, timedelta, timezone
from werkzeug.security import generate_password_hash
# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def load_env_config():
"""从.env文件加载配置"""
env_file = '.env'
if os.path.exists(env_file):
print(f"正在从 {env_file} 加载配置...")
with open(env_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
# 跳过注释和空行
if line and not line.startswith('#'):
# 解析键值对
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
os.environ[key] = value
print("配置加载完成")
else:
print(f"警告: 未找到 {env_file} 文件")
def init_mysql_database():
"""初始化 MySQL 数据库"""
print("=" * 50)
print("KaMiXiTong MySQL 数据库初始化开始...")
print("=" * 50)
# 从.env文件加载配置
load_env_config()
# 获取数据库URL
database_url = os.environ.get('DATABASE_URL')
if not database_url:
print("❌ 未设置DATABASE_URL环境变量")
print("💡 请在.env文件中配置DATABASE_URL")
print("示例: DATABASE_URL=mysql+pymysql://用户名:密码@主机:端口/数据库名")
return False
try:
# 设置数据库URL到环境变量
os.environ['DATABASE_URL'] = database_url
# 导入应用
from app import create_app, db
from app.models import Admin, Product, Version, License, Device, Ticket
from app.models.audit_log import AuditLog
# 创建应用实例
app = create_app()
print(f"数据库类型: MySQL")
print(f"数据库URL: {database_url}")
with app.app_context():
# 1. 删除所有表(如果存在)
print("\n1. 清理现有数据库表...")
db.drop_all()
print(" ✓ 已删除所有现有表")
# 2. 创建所有表
print("\n2. 创建数据库表结构...")
db.create_all()
print(" ✓ 已创建所有数据表")
# 显式提交事务以确保表被创建
db.session.commit()
# 2.1 验证表创建
print("\n2.1 验证表创建...")
from sqlalchemy import inspect
inspector = inspect(db.engine)
tables = inspector.get_table_names()
print(f" ✓ 成功创建 {len(tables)} 个表: {', '.join(tables)}")
if not tables:
raise Exception("表创建失败,没有找到任何表")
# 3. 插入初始数据
print("\n3. 插入初始数据...")
insert_mysql_data()
# 显式提交并关闭会话
db.session.commit()
db.session.close()
# 4. 显示数据库信息
print("\n4. MySQL 数据库初始化完成!")
show_mysql_database_info()
return True
except Exception as e:
print(f"\n❌ MySQL 数据库初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def insert_mysql_data():
"""插入 MySQL 初始数据"""
from app import db
from app.models import Admin, Product, Version, License, Device, Ticket
from app.models.audit_log import AuditLog
# 1. 创建默认管理员账号
print(" 创建默认管理员账号...")
admin = Admin(
username='admin',
email='admin@kamaxitong.com',
role=1, # 超级管理员
status=1 # 正常
)
admin.set_password('admin123')
db.session.add(admin)
# 创建测试管理员账号
test_admin = Admin(
username='test_admin',
email='test@kamaxitong.com',
role=0, # 普通管理员
status=1 # 正常
)
test_admin.set_password('test123')
db.session.add(test_admin)
print(" ✓ 已创建管理员账号 (admin/admin123, test_admin/test123)")
# 2. 创建示例产品
print(" 创建示例产品...")
products = [
Product(
product_id='KMX001',
product_name='KaMiXiTong 专业版',
description='专业的软件授权管理系统,支持多种授权模式和完整的设备管理',
status=1
),
Product(
product_id='KMX002',
product_name='KaMiXiTong 企业版',
description='企业级软件授权管理解决方案,支持分布式部署和高并发访问',
status=1
)
]
for product in products:
db.session.add(product)
print(" ✓ 已创建2个示例产品")
# 3. 创建版本信息
print(" 创建版本信息...")
versions = [
Version(
product_id='KMX001',
version_num='1.0.0',
update_log='初始版本发布',
download_url='https://download.kamaxitong.com/v1.0.0/professional.exe',
min_license_version='1.0.0',
force_update=0,
download_status=1,
publish_status=1
),
Version(
product_id='KMX001',
version_num='1.1.0',
update_log='功能增强版本',
download_url='https://download.kamaxitong.com/v1.1.0/professional.exe',
min_license_version='1.0.0',
force_update=0,
download_status=1,
publish_status=1
),
Version(
product_id='KMX002',
version_num='2.0.0',
update_log='企业版初始版本',
download_url='https://download.kamaxitong.com/v2.0.0/enterprise.exe',
min_license_version='2.0.0',
force_update=0,
download_status=1,
publish_status=1
)
]
for version in versions:
db.session.add(version)
print(" ✓ 已创建3个版本信息")
# 4. 创建示例许可证
print(" 创建示例许可证...")
# 使用时区感知的 datetime 对象替换 utcnow()
current_time = datetime.now(timezone.utc)
licenses = [
# 永久许可证
License(
license_key=License.generate_license_key(),
product_id='KMX001',
type=1, # 正式版
status=1, # 已激活
valid_days=-1, # 永久
bind_machine_code='DEMO-MACHINE-CODE-001',
activate_time=current_time - timedelta(days=30),
expire_time=None, # 永久不过期
last_verify_time=current_time - timedelta(days=1),
unbind_count=0
),
# 1年试用期许可证
License(
license_key=License.generate_license_key(),
product_id='KMX001',
type=0, # 试用版
status=1, # 已激活
valid_days=365, # 1年
bind_machine_code='DEMO-MACHINE-CODE-002',
activate_time=current_time - timedelta(days=15),
expire_time=current_time + timedelta(days=350), # 还有350天过期
last_verify_time=current_time - timedelta(hours=6),
unbind_count=1
),
# 未激活许可证
License(
license_key=License.generate_license_key(),
product_id='KMX002',
type=1, # 正式版
status=0, # 未激活
valid_days=365, # 1年
bind_machine_code=None,
activate_time=None,
expire_time=None,
last_verify_time=None,
unbind_count=0
)
]
for license_obj in licenses:
db.session.add(license_obj)
print(" ✓ 已创建3个示例许可证")
# 5. 创建示例设备
print(" 创建示例设备...")
devices = [
Device(
machine_code='DEMO-MACHINE-CODE-001',
license_id=1, # 对应第一个许可证
product_id='KMX001',
software_version='1.1.0',
status=1, # 正常
activate_time=current_time - timedelta(days=30),
last_verify_time=current_time - timedelta(days=1)
),
Device(
machine_code='DEMO-MACHINE-CODE-002',
license_id=2, # 对应第二个许可证
product_id='KMX001',
software_version='1.0.0',
status=1, # 正常
activate_time=current_time - timedelta(days=15),
last_verify_time=current_time - timedelta(hours=6)
)
]
for device in devices:
db.session.add(device)
print(" ✓ 已创建2个示例设备")
# 6. 创建示例工单
print(" 创建示例工单...")
tickets = [
Ticket(
title='许可证激活失败',
product_id='KMX001',
software_version='1.0.0',
machine_code='CUSTOMER-MACHINE-001',
license_key='DEMO-LICENSE-KEY-001',
description='客户反馈无法激活许可证',
priority=2, # 高优先级
status=2, # 已解决
operator='admin',
remark='问题已解决',
create_time=current_time - timedelta(days=5),
update_time=current_time - timedelta(days=4),
resolve_time=current_time - timedelta(days=4)
),
Ticket(
title='功能咨询',
product_id='KMX002',
software_version='2.0.0',
machine_code=None,
license_key=None,
description='客户咨询批量购买事宜',
priority=1, # 中优先级
status=0, # 待处理
operator=None,
remark=None,
create_time=current_time - timedelta(days=2),
update_time=current_time - timedelta(days=2)
)
]
for ticket in tickets:
db.session.add(ticket)
print(" ✓ 已创建2个示例工单")
# 7. 创建审计日志示例数据
print(" 创建示例审计日志...")
audit_logs = [
AuditLog(
admin_id=1,
action='login',
target_type='system',
target_id=None,
details='管理员登录系统',
ip_address='127.0.0.1',
user_agent='Mozilla/5.0...',
create_time=current_time - timedelta(days=30)
),
AuditLog(
admin_id=1,
action='create',
target_type='product',
target_id=1,
details='创建产品 KaMiXiTong 专业版',
ip_address='127.0.0.1',
user_agent='Mozilla/5.0...',
create_time=current_time - timedelta(days=29)
)
]
for log in audit_logs:
db.session.add(log)
print(" ✓ 已创建2条示例审计日志")
# 提交所有更改
db.session.commit()
print(" ✓ 所有初始数据插入完成")
def show_mysql_database_info():
"""显示 MySQL 数据库信息"""
from app import db
from app.models import Admin, Product, Version, License, Device, Ticket
from app.models.audit_log import AuditLog
print("\n" + "=" * 50)
print("MySQL 数据库初始化信息统计")
print("=" * 50)
# 统计各表的记录数
tables_info = [
('管理员账号', Admin.query.count()),
('产品', Product.query.count()),
('版本', Version.query.count()),
('许可证', License.query.count()),
('设备', Device.query.count()),
('工单', Ticket.query.count()),
('审计日志', AuditLog.query.count())
]
for table_name, count in tables_info:
print(f" {table_name:8} : {count:3} 条记录")
print("\n" + "=" * 50)
print("默认登录信息")
print("=" * 50)
print(" 超级管理员: admin / admin123")
print(" 普通管理员: test_admin / test123")
print("\n注意事项:")
print(" 1. 请在生产环境中修改默认密码")
print(" 2. 确保MySQL服务正常运行")
print(" 3. 修改 .env 文件中的 DATABASE_URL 为正确的MySQL配置")
print(" 4. 定期备份数据库")
print("=" * 50)
def main():
"""主函数"""
try:
# 检查是否安装了PyMySQL
try:
import pymysql
except ImportError:
print("❌ 未安装PyMySQL请运行: pip install PyMySQL")
sys.exit(1)
# 初始化MySQL数据库
success = init_mysql_database()
if success:
print("\n🎉 MySQL 数据库初始化成功!")
print("\n启动服务器:")
print(" python run.py")
print("")
print(" python start.py")
print("\n访问: http://localhost:5000")
else:
print("\n❌ MySQL 数据库初始化失败!")
sys.exit(1)
except Exception as e:
print(f"\n❌ 初始化失败: {e}")
sys.exit(1)
if __name__ == '__main__':
main()