更新测试一版

This commit is contained in:
wsb1224 2025-11-16 19:06:49 +08:00
parent 73800eeaa7
commit 0cd42f3401
19 changed files with 433 additions and 480 deletions

3
.env
View File

@ -23,6 +23,9 @@ MAX_UNBIND_TIMES=3
LICENSE_KEY_LENGTH=32
LICENSE_KEY_PREFIX=
FRONTEND_DOMAIN=http://km.taisan.online
# API配置
API_VERSION=v1
ITEMS_PER_PAGE=20

View File

@ -12,6 +12,7 @@ FLASK_DEBUG=True
# 安全配置
SECRET_KEY=your-super-secret-key-change-this-in-production
AUTH_SECRET_KEY=your-auth-validator-secret-key
FRONTEND_DOMAIN=
# 验证器配置
OFFLINE_CACHE_DAYS=7
@ -30,6 +31,8 @@ ITEMS_PER_PAGE=20
# 服务器配置
HOST=0.0.0.0
PORT=5000
FASTAPI_HOST=127.0.0.1
FASTAPI_PORT=9000
# 文件上传配置
MAX_CONTENT_LENGTH=524288000

View File

@ -11,6 +11,7 @@
5. [启动服务](#启动服务)
6. [常见问题解决](#常见问题解决)
7. [维护与管理](#维护与管理)
8. [前端域名配置](#前端域名配置-1)
---
@ -184,6 +185,27 @@ pip install PyMySQL==1.1.0
- `localhost`:数据库地址(本地部署就用 localhost
- `kamaxitong`:数据库名称
### 第二步:配置前端域名(可选但推荐)
1. **为什么要配置前端域名**
在本地开发环境中,系统默认通过 `http://localhost:5000` 访问。当部署到服务器并通过域名访问时,如果不配置前端域名,前端页面中的接口调用可能会仍然指向 `localhost:5000`,导致接口调用失败。
2. **配置方法**
`.env` 文件中添加或修改以下配置:
```bash
# 前端域名配置
FRONTEND_DOMAIN=https://your-domain.com
```
或者通过管理后台配置:
- 登录系统管理后台
- 进入"系统设置"页面
- 在"基本设置"中找到"前端域名"配置项
- 输入你的域名,例如:`https://your-domain.com`
- 点击"保存设置"
> 注意:如果两种方式都配置了,环境变量的优先级更高。
### 第二步:创建数据库
#### 方法一:使用命令行(推荐)
@ -510,6 +532,39 @@ tail -f logs/kamaxitong.log
---
## 前端域名配置
### 问题描述
在本地开发环境中,系统默认通过 `http://localhost:5000` 访问。当部署到服务器并通过域名访问时,前端页面中的接口调用可能会仍然指向 `localhost:5000`,导致接口调用失败。
### 解决方案
系统已通过修改前端JavaScript中的 `apiRequest` 函数来自动检测当前访问的主机并构建正确的API调用地址。现在还支持通过环境变量或管理后台配置前端域名。
前端JavaScript现在会使用配置的域名或 `window.location.origin` 来获取当前访问的完整域名并自动构建完整的API URL。
### 配置方法
1. **通过环境变量配置(推荐)**
`.env` 文件中添加:
```bash
FRONTEND_DOMAIN=https://your-domain.com
```
2. **通过管理后台配置**
- 登录系统管理后台
- 进入"系统设置"页面
- 在"基本设置"中找到"前端域名"配置项
- 输入你的域名,例如:`https://your-domain.com`
- 点击"保存设置"
### 验证配置
1. 在浏览器中打开应用按F12打开开发者工具
2. 切换到Network标签页
3. 刷新页面观察API请求的URL是否为域名地址而非localhost
## 🎉 部署完成!
恭喜你!如果以上步骤都成功执行,你的 KaMiXiTong 系统已经成功部署了!

View File

@ -1,262 +1,96 @@
# MySQL数据库配置指南
# MySQL 数据库配置与初始化指南
## 概述
## 目录
1. [环境要求](#环境要求)
2. [安装依赖](#安装依赖)
3. [配置数据库连接](#配置数据库连接)
4. [创建数据库](#创建数据库)
5. [初始化数据库](#初始化数据库)
6. [启动应用](#启动应用)
系统已经配置为使用MySQL数据库并从`.env`文件读取配置。
## 环境要求
## 配置步骤
- MySQL 5.7 或更高版本
- Python 3.8 或更高版本
- PyMySQL 驱动
### 1. 确保安装MySQL客户端依赖
安装PyMySQLMySQL Python驱动
## 安装依赖
```bash
pip install PyMySQL
# 或者
pip install pymysql
```
### 2. 配置.env文件
## 配置数据库连接
编辑项目根目录的`.env`文件:
复制 [.env.example](.env.example) 文件为 .env 并修改数据库配置:
```bash
cp .env.example .env
```
在 .env 文件中修改数据库连接配置:
```env
# 数据库配置
# 格式: mysql+pymysql://用户名:密码@主机:端口/数据库名
DATABASE_URL=mysql+pymysql://root:你的密码@localhost/kamaxitong
# 示例:
DATABASE_URL=mysql+pymysql://root:taiyi1224@localhost/kamaxitong
DATABASE_URL=mysql+pymysql://用户名:密码@主机:端口/数据库名
```
### 3. 创建数据库
示例:
```env
DATABASE_URL=mysql+pymysql://root:password@localhost:3306/kamaxitong
```
在MySQL中创建数据库
## 创建数据库
在 MySQL 中手动创建数据库:
```sql
-- 登录MySQL
mysql -u root -p
-- 创建数据库
CREATE DATABASE kamaxitong CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 授权(可选)
GRANT ALL PRIVILEGES ON kamaxitong.* TO 'root'@'localhost';
FLUSH PRIVILEGES;
```
### 4. 更新.env文件中的数据库URL
`.env`文件中的`DATABASE_URL`格式:
```env
# 标准格式
DATABASE_URL=mysql+pymysql://用户名:密码@主机:端口/数据库名
# 本地示例
DATABASE_URL=mysql+pymysql://root:taiyi1224@localhost/kamaxitong
# 远程示例
DATABASE_URL=mysql+pymysql://root:password@192.168.1.100:3306/kamaxitong
```
### 5. 初始化数据库
#### 方式1: 使用Flask-Migrate推荐
或者使用提供的脚本自动创建:
```bash
# 初始化迁移(如果还没有)
flask db init
# 生成迁移文件
flask db migrate -m "Initial migration"
# 应用迁移
flask db upgrade
python setup_mysql.py
```
#### 方式2: 使用快速修复脚本
## 初始化数据库
有两种方式初始化数据库:
### 方式一:使用专用初始化脚本(推荐)
```bash
python quick_fix.py
python init_db_mysql.py
```
### 6. 启动应用
该脚本将:
1. 从 .env 文件读取数据库配置
2. 清理现有表结构
3. 创建所有数据表
4. 插入初始数据(管理员账号、示例产品等)
### 方式二:使用通用配置脚本
```bash
python setup_mysql.py
```
## 启动应用
```bash
python run.py
```
## 数据库配置参数说明
| 参数 | 说明 | 示例 |
|------|------|------|
| 数据库类型 | mysql+pymysql | mysql+pymysql |
| 用户名 | MySQL用户名 | root |
| 密码 | MySQL密码 | taiyi1224 |
| 主机 | 数据库主机 | localhost 或 192.168.1.100 |
| 端口 | MySQL端口可选 | 3306默认 |
| 数据库名 | 要使用的数据库名 | kamaxitong |
## 完整的.env文件示例
```env
# 环境配置
FLASK_ENV=development
FLASK_DEBUG=True
# 数据库配置 - MySQL
DATABASE_URL=mysql+pymysql://root:taiyi1224@localhost/kamaxitong
# 安全配置
SECRET_KEY=taiyi1224
AUTH_SECRET_KEY=taiyi1224
# 验证器配置
OFFLINE_CACHE_DAYS=7
MAX_FAILED_ATTEMPTS=5
LOCKOUT_MINUTES=10
MAX_UNBIND_TIMES=3
# 卡密配置
LICENSE_KEY_LENGTH=32
LICENSE_KEY_PREFIX=
# API配置
API_VERSION=v1
ITEMS_PER_PAGE=20
# 服务器配置
HOST=0.0.0.0
PORT=5000
# 文件上传配置
MAX_CONTENT_LENGTH=16777216
UPLOAD_FOLDER=static/uploads
# 日志配置
LOG_LEVEL=INFO
LOG_FILE=logs/kamaxitong.log
```
## 验证配置
### 1. 检查.env文件是否被加载
启动应用时查看控制台输出:
```
成功加载.env文件
```
### 2. 验证数据库连接
在Python中测试
```python
from app import create_app
from app import db
app = create_app()
with app.app_context():
print("数据库URI:", app.config['SQLALCHEMY_DATABASE_URI'])
db.create_all() # 测试连接
print("数据库连接成功!")
```
## 常见问题
### Q1: ImportError: No module named 'pymysql'
**解决方案:**
```bash
pip install pymysql
```
### Q2: 1049 (42000): Unknown database 'kamaxitong'
**解决方案:**
```sql
CREATE DATABASE kamaxitong CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
### Q3: 1045 (28000): Access denied for user 'root'@'localhost'
**解决方案:**
1. 检查.env文件中的用户名和密码是否正确
2. 确保MySQL用户有权限访问数据库
3. 重置MySQL root密码
### Q4: 2003 (HY000): Can't connect to MySQL server
**解决方案:**
1. 确保MySQL服务已启动
2. 检查主机和端口是否正确
3. 检查防火墙设置
### Q5: .env文件没有被加载
**解决方案:**
1. 确保python-dotenv已安装`pip install python-dotenv`
2. 确保.env文件在项目根目录
3. 重启应用
## 性能优化建议
### 1. 配置MySQL连接池
在config.py中已经配置
```python
SQLALCHEMY_ENGINE_OPTIONS = {
"future": True,
"pool_pre_ping": True,
"pool_size": 10,
"pool_recycle": 3600,
"max_overflow": 20
}
```
### 2. 启用MySQL查询缓存
在MySQL配置文件中my.cnf
```ini
[mysqld]
query_cache_type = 1
query_cache_size = 256M
```
### 3. 设置合适的字符集
确保数据库、表、列都使用utf8mb4字符集
```sql
CREATE DATABASE kamaxitong
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
```
## 备份和恢复
### 备份数据库
```bash
mysqldump -u root -p kamaxitong > backup.sql
python start.py
```
### 恢复数据库
访问地址http://localhost:5000
```bash
mysql -u root -p kamaxitong < backup.sql
```
默认管理员账号:
- 超级管理员: admin / admin123
- 普通管理员: test_admin / test123
## 总结
系统已经配置为使用MySQL和.env文件
- ✅ .env文件支持
- ✅ MySQL配置已就绪
- ✅ 从环境变量读取配置
- ✅ 支持开发/生产/测试环境
只需确保MySQL服务运行、依赖安装正确、.env文件配置正确即可
⚠️ 请在生产环境中修改默认密码!

View File

@ -183,13 +183,20 @@ python start.py
### 生产环境
```bash
# 使用Gunicorn
# 使用一键部署脚本(推荐)
python deploy.py --setup-service --setup-nginx
# 或者手动部署
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 run:app
# 使用Nginx反向代理
# 配置SSL证书
# 设置防火墙规则
# 前端域名配置
# 在.env文件中设置FRONTEND_DOMAIN=https://your-domain.com
# 或通过管理后台配置
```
### Docker部署

View File

@ -38,7 +38,7 @@ KaMiXiTong/
## 使用方式
### 快速开始
### 快速开始 (SQLite)
```bash
# 克隆项目
@ -50,7 +50,7 @@ cd kamaxitong
# 安装依赖
pip install -r requirements.txt
# 初始化数据库
# 初始化SQLite数据库
python init_db_sqlite.py
# 启动服务
@ -60,6 +60,36 @@ python run.py
访问地址: http://localhost:5000
默认账号: admin / admin123
### MySQL数据库支持
系统也支持使用MySQL数据库提供更好的性能和并发支持。
#### 初始化MySQL数据库
```bash
# 安装MySQL依赖
pip install PyMySQL
# 配置数据库连接
# 复制 .env.example 为 .env 并修改 DATABASE_URL 配置
cp .env.example .env
# 编辑 .env 文件设置MySQL连接信息
# DATABASE_URL=mysql+pymysql://用户名:密码@主机:端口/数据库名
# 初始化MySQL数据库
python init_db_mysql.py
```
或者使用配置脚本:
```bash
# 使用配置脚本自动创建数据库和表
python setup_mysql.py
```
详细配置说明请参考 [MySQL配置指南](MYSQL_CONFIG_GUIDE.md)
### FastAPI接口
系统还提供了现代化的FastAPI接口具有自动生成的交互式文档
@ -85,7 +115,10 @@ python start.py
### 生产环境
```bash
# 使用Gunicorn
# 使用一键部署脚本(推荐)
python deploy.py --setup-service --setup-nginx
# 或者手动部署
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 run:app
@ -107,6 +140,8 @@ gunicorn -w 4 -b 0.0.0.0:5000 run:app
- [集成指南](docs/INTEGRATION.md)
- [使用示例](docs/EXAMPLES.md)
- [FastAPI接口文档](docs/FASTAPI.md)
- [部署说明](docs/DEPLOYMENT.md)
- [MySQL配置指南](MYSQL_CONFIG_GUIDE.md)
## 许可证

View File

@ -8,6 +8,7 @@ CONFIG_MAPPING = {
# 基本设置
'site_name': 'SITE_NAME',
'admin_email': 'ADMIN_EMAIL',
'frontend_domain': 'FRONTEND_DOMAIN',
'max_failed_attempts': 'MAX_FAILED_ATTEMPTS',
'lockout_minutes': 'LOCKOUT_MINUTES',
'max_unbind_times': 'MAX_UNBIND_TIMES',

View File

@ -228,34 +228,20 @@ class AuthValidator:
return ""
def _validate_license_format(self, license_key: str) -> bool:
"""验证卡密格式(与服务端保持一致)"""
"""验证卡密格式"""
if not license_key:
return False
# 去除空格和制表符,并转为大写
license_key = license_key.strip().replace(' ', '').replace('\t', '').upper()
# 检查是否为XXXX-XXXX-XXXX-XXXX格式
if '-' in license_key:
parts = license_key.split('-')
# 应该有4部分每部分8个字符
if len(parts) == 4 and all(len(part) == 8 for part in parts):
# 检查所有字符是否为大写字母或数字
combined = ''.join(parts)
if len(combined) == 32:
pattern = r'^[A-Z0-9]+$'
import re
return bool(re.match(pattern, combined))
# 检查长度16-32位
if len(license_key) < 16 or len(license_key) > 32:
return False
else:
# 兼容旧格式检查长度16-32位
if len(license_key) < 16 or len(license_key) > 32:
return False
# 检查字符(只允许大写字母、数字和下划线)
pattern = r'^[A-Z0-9_]+$'
import re
return bool(re.match(pattern, license_key))
# 检查字符(只允许大写字母、数字和下划线)
import re
pattern = r'^[A-Z0-9_]+$'
return bool(re.match(pattern, license_key))
def _input_license_key(self) -> str:
"""输入卡密"""
@ -407,55 +393,117 @@ class AuthValidator:
max_attempts = 3 # 最多尝试3次
for attempt in range(max_attempts):
# 获取卡密
# 输入卡密
license_key = self._input_license_key()
if not license_key:
self._show_message("验证取消", "用户取消了验证操作")
self._show_message("验证取消", "未输入卡密,程序退出", True)
return False
# 验证卡密格式
if not self._validate_license_format(license_key):
self._show_message("格式错误", "卡密格式不正确,请重新输入", True)
self._show_message("格式错误", "卡密格式错误,请检查后重新输入", True)
continue
# 在线验证
success, message, auth_info = self._online_verify(license_key)
if success:
# 缓存授权信息
if auth_info:
self._cache_auth_info(auth_info)
self._show_message("验证成功", message)
if success and auth_info:
# 验证成功,缓存授权信息
self._cache_auth_info(auth_info)
# 检查是否需要更新
force_update = auth_info.get('force_update', False)
download_url = auth_info.get('download_url')
new_version = auth_info.get('new_version')
if force_update and download_url:
self._show_message("需要更新", f"发现新版本 {new_version}\n请下载更新后重新启动程序", True)
# 尝试打开下载链接
try:
import webbrowser
webbrowser.open(download_url)
except:
pass
return False
self._show_message("验证成功", f"授权验证成功!\n卡密: {license_key}\n有效期至: {auth_info.get('expire_time', '永久')}")
return True
else:
# 记录失败尝试
# 验证失败
self.failed_attempts += 1
if self.failed_attempts >= 3:
self._lock_account(10) # 锁定10分钟
self.last_attempt_time = datetime.utcnow()
if self.failed_attempts >= 5: # 失败5次锁定
self._lock_account()
self._show_message("验证失败", self._get_lock_message(), True)
return False
else:
remaining_attempts = 3 - self.failed_attempts
self._show_message(
"验证失败",
f"{message}\n\n剩余尝试次数: {remaining_attempts}",
True
)
continue
# 所有尝试都失败
self._show_message("验证失败", "已达到最大尝试次数", True)
self._show_message("验证失败", f"验证失败: {message}\n剩余尝试次数: {5 - self.failed_attempts}", True)
# 如果不是最后一次尝试,询问是否继续
if attempt < max_attempts - 1:
if self.gui_mode:
try:
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
root.withdraw()
result = messagebox.askyesno("继续验证", "是否继续输入卡密验证?")
root.destroy()
if not result:
return False
except ImportError:
continue
else:
continue
else:
return False
return False
def get_auth_info(self) -> Optional[Dict[str, Any]]:
"""
获取当前授权信息
Returns:
Optional[Dict[str, Any]]: 授权信息
"""
return self.cache.get_auth_info(self.software_id)
def get_software_info(self) -> Optional[Dict[str, Any]]:
"""获取软件信息"""
try:
url = f"{self.api_url}/software/info"
params = {"software_id": self.software_id}
def clear_auth_cache(self):
"""清除授权缓存"""
self.cache.clear_cache(self.software_id)
response = requests.get(url, params=params, timeout=self.timeout)
if response.status_code == 200:
result = response.json()
if result.get('success'):
return result.get('data')
except Exception:
pass
return None
def clear_cache(self):
"""清除本地缓存"""
self.cache.clear_cache(self.software_id)
# 删除机器码缓存文件
try:
if os.path.exists(".machine_code"):
os.remove(".machine_code")
except Exception:
pass
# 便捷函数
def validate_license(software_id: str, **kwargs) -> bool:
"""
便捷的验证函数
Args:
software_id: 软件ID
**kwargs: 其他参数api_url, secret_key, cache_days, timeout, gui_mode
Returns:
bool: 验证是否成功
"""
validator = AuthValidator(software_id, **kwargs)
return validator.validate()
def get_machine_code() -> str:
"""获取当前机器码"""
return MachineCodeGenerator.generate()

View File

@ -19,48 +19,55 @@ def login():
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
if not username or not password:
print("DEBUG: Missing username or password")
# 对于AJAX请求返回JSON错误
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
'success': False,
'message': '请输入用户名和密码'
}), 400
# 对于普通表单提交使用flash消息
flash('请输入用户名和密码', 'error')
return render_template('login.html')
# 查找用户
admin = Admin.query.filter_by(username=username).first()
if admin:
if admin and admin.check_password(password) and admin.is_active:
# 登录成功
login_user(admin, remember=True)
if admin and admin.check_password(password) and admin.is_active:
# 登录成功
login_user(admin, remember=True)
# 更新最后登录信息
admin.update_last_login(request.remote_addr)
# 更新最后登录信息
admin.update_last_login(request.remote_addr)
# 生成简单的token实际项目中应使用JWT
import secrets
token = secrets.token_urlsafe(32)
# 生成简单的token实际项目中应使用JWT
import secrets
token = secrets.token_urlsafe(32)
# 如果是AJAX请求返回JSON
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
'success': True,
'token': token,
'user': {
'username': admin.username,
'role': admin.role,
'is_super_admin': admin.is_super_admin()
},
'redirect': url_for('web.dashboard')
})
# 如果是AJAX请求返回JSON
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
'success': True,
'token': token,
'user': {
'username': admin.username,
'role': admin.role,
'is_super_admin': admin.is_super_admin()
},
'redirect': url_for('web.dashboard')
})
# 获取next参数
next_page = request.args.get('next')
if next_page:
return redirect(next_page)
return redirect(url_for('web.dashboard'))
else:
flash('用户名或密码错误', 'error')
# 获取next参数
next_page = request.args.get('next')
if next_page:
return redirect(next_page)
return redirect(url_for('web.dashboard'))
else:
# 登录失败
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
'success': False,
'message': '用户名或密码错误'
}), 401
flash('用户名或密码错误', 'error')
return render_template('login.html')
@ -86,6 +93,4 @@ def favicon():
return Response(status=204) # No Content
# 导入视图函数
from . import views
from . import views

View File

@ -53,6 +53,14 @@
left: 50%;
transform: translate(-50%, -50%);
}
/* 消息通知样式 */
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
width: 300px;
}
</style>
{% block extra_css %}{% endblock %}
@ -66,6 +74,9 @@
</div>
</div>
</div>
<!-- Notification Container -->
<div class="notification-container" id="notification-container"></div>
{% if current_user.is_authenticated %}
<!-- Main Container -->
@ -201,6 +212,9 @@
<!-- Common Functions -->
<script>
// 设置前端域名全局变量
window.FRONTEND_DOMAIN = '{{ config.FRONTEND_DOMAIN or "" }}';
// 显示加载动画
function showLoading() {
document.getElementById('loading').style.display = 'block';
@ -213,6 +227,20 @@
// AJAX请求封装
function apiRequest(url, options = {}) {
// 自动构建完整的API URL
let fullUrl = url;
// 检查是否配置了前端域名
const frontendDomain = window.FRONTEND_DOMAIN || '';
if (url.startsWith('/')) {
// 如果是相对路径,则使用配置的域名或当前主机
fullUrl = (frontendDomain || window.location.origin) + url;
} else if (!url.startsWith('http')) {
// 如果不是完整URL且不以http开头则添加API前缀
fullUrl = (frontendDomain || window.location.origin) + '/api/v1/' + url;
}
showLoading();
const defaultOptions = {
headers: {
@ -221,7 +249,7 @@
credentials: 'same-origin' // 重要: 使用session cookies
};
return fetch(url, { ...defaultOptions, ...options })
return fetch(fullUrl, { ...defaultOptions, ...options })
.then(response => {
hideLoading();
if (!response.ok) {
@ -278,19 +306,23 @@
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 显示通知
// 显示通知 - 统一使用静态JS中的函数
function showNotification(message, type = 'info') {
// 创建通知元素
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
const alertType = type === 'error' ? 'danger' : type;
alertDiv.className = `alert alert-${alertType} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('main');
alertDiv.style.marginBottom = '10px';
// 插入到通知容器中
const container = document.getElementById('notification-container') || document.querySelector('main');
if (container) {
container.insertBefore(alertDiv, container.firstChild);
// 自动隐藏
setTimeout(() => {
if (alertDiv.parentNode) {

View File

@ -73,7 +73,6 @@ document.addEventListener('DOMContentLoaded', function() {
// 基础验证
if (!username || !password) {
// 使用基础模板中的showNotification函数
showNotification('请填写用户名和密码', 'warning');
return;
}
@ -83,8 +82,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (loginBtnText) loginBtnText.textContent = '登录中...';
try {
console.log('尝试登录:', username);
// 尝试使用Web表单登录传统Flask登录
const formData = new FormData();
formData.append('username', username);
@ -107,9 +104,6 @@ document.addEventListener('DOMContentLoaded', function() {
credentials: 'same-origin' // 重要发送cookies
});
console.log('登录响应状态:', response.status);
console.log('登录响应URL:', response.url);
// 检查响应状态码
if (response.status === 200) {
// 检查是否真正登录成功重定向到dashboard
@ -119,23 +113,30 @@ document.addEventListener('DOMContentLoaded', function() {
} else {
// 尝试解析响应内容
const responseData = await response.text();
// 检查响应内容中是否包含成功信息
if (responseData.includes('dashboard') || responseData.includes('仪表板')) {
// 登录成功
window.location.href = '/dashboard';
} else if (responseData.includes('用户名或密码错误')) {
// 检查是否包含Flask flash消息
if (responseData.includes('用户名或密码错误')) {
showNotification('用户名或密码错误,请重试', 'error');
} else if (responseData.includes('请输入用户名和密码')) {
showNotification('请输入用户名和密码', 'warning');
} else {
// 尝试解析为JSON
try {
const jsonData = JSON.parse(responseData);
if (jsonData.success) {
window.location.href = jsonData.redirect || '/dashboard';
} else {
showNotification(jsonData.message || '登录失败,请检查用户名和密码', 'error');
// 检查响应内容中是否包含成功信息
if (responseData.includes('dashboard') || responseData.includes('仪表板')) {
// 登录成功
window.location.href = '/dashboard';
} else {
// 尝试解析为JSON
try {
const jsonData = JSON.parse(responseData);
if (jsonData.success) {
window.location.href = jsonData.redirect || '/dashboard';
} else {
showNotification(jsonData.message || '登录失败,请检查用户名和密码', 'error');
}
} catch (e) {
// 如果无法解析为JSON显示通用错误消息
showNotification('登录失败,请检查用户名和密码', 'error');
}
} catch (e) {
showNotification('登录失败,请检查用户名和密码', 'error');
}
}
}
@ -147,7 +148,14 @@ document.addEventListener('DOMContentLoaded', function() {
} else {
showNotification('请求参数错误,请刷新页面后重试', 'error');
}
} else if (response.status === 401) {
// 认证失败
showNotification('用户名或密码错误,请重试', 'error');
} else if (response.status >= 500) {
// 服务器错误
showNotification('服务器内部错误,请稍后重试', 'error');
} else {
// 其他错误
showNotification('登录失败,请检查用户名和密码', 'error');
}
@ -163,4 +171,4 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
</script>
{% endblock %}
{% endblock %}

View File

@ -23,6 +23,13 @@
<input type="email" class="form-control" id="admin_email" name="admin_email" value="{{ config.ADMIN_EMAIL or '' }}">
</div>
<div class="mb-3">
<label for="frontend_domain" class="form-label">前端域名</label>
<input type="text" class="form-control" id="frontend_domain" name="frontend_domain"
value="{{ config.FRONTEND_DOMAIN or '' }}">
<div class="form-text">前端API请求使用的域名留空则使用当前访问域名</div>
</div>
<div class="mb-3">
<label for="max_failed_attempts" class="form-label">最大失败尝试次数</label>
<input type="number" class="form-control" id="max_failed_attempts" name="max_failed_attempts"
@ -366,6 +373,7 @@ function saveBasicSettings() {
const formData = {
site_name: document.getElementById('site_name').value,
admin_email: document.getElementById('admin_email').value,
frontend_domain: document.getElementById('frontend_domain').value,
max_failed_attempts: parseInt(document.getElementById('max_failed_attempts').value),
lockout_minutes: parseInt(document.getElementById('lockout_minutes').value),
max_unbind_times: parseInt(document.getElementById('max_unbind_times').value),

View File

@ -1,6 +1,7 @@
import os
from datetime import timedelta
import logging
from logging.handlers import TimedRotatingFileHandler
class Config:
"""基础配置类"""
@ -24,6 +25,7 @@ class Config:
# 系统基本配置
SITE_NAME = os.environ.get('SITE_NAME') or '软件授权管理系统'
ADMIN_EMAIL = os.environ.get('ADMIN_EMAIL') or ''
FRONTEND_DOMAIN = os.environ.get('FRONTEND_DOMAIN') or '' # 前端域名配置
# 验证器配置
AUTH_SECRET_KEY = os.environ.get('AUTH_SECRET_KEY') or 'auth-validator-secret-key'
@ -56,11 +58,12 @@ class Config:
if not os.path.exists('logs'):
os.mkdir('logs')
# 配置日志
# 配置日志 - 使用时间轮转而不是大小轮转来避免Windows文件锁定问题
import logging
from logging.handlers import RotatingFileHandler
from logging.handlers import TimedRotatingFileHandler
file_handler = RotatingFileHandler('logs/kamaxitong.log', maxBytes=10240, backupCount=10)
# 使用时间轮转日志处理器,每天轮转一次
file_handler = TimedRotatingFileHandler('logs/kamaxitong.log', when='midnight', interval=1, backupCount=10, encoding='utf-8')
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
@ -86,7 +89,7 @@ class ProductionConfig(Config):
# 生产环境日志配置
import logging
from logging.handlers import RotatingFileHandler
from logging.handlers import TimedRotatingFileHandler
if not app.debug:
# 确保日志目录存在
@ -94,7 +97,8 @@ class ProductionConfig(Config):
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/kamaxitong.log', maxBytes=10240, backupCount=10)
# 使用时间轮转日志处理器,每天轮转一次
file_handler = TimedRotatingFileHandler('logs/kamaxitong.log', when='midnight', interval=1, backupCount=10, encoding='utf-8')
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))

View File

@ -29,23 +29,12 @@
[parameters: {'admin_id': 1, 'action': 'GENERATE_LICENSES', 'target_type': 'LICENSE', 'target_id': None, 'details': {'product_id': 'ArticleReplace', 'count': 1, 'license_type': 1, 'license_keys': ['4SGGNAPF-HPGNQC1Z-6D7OH879-9BGW32PI']}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 5, 7, 9, 662553)}]
(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
2025-11-16 13:07:44,918 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:10:12,691 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:11:09,687 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:14:50,942 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:15:22,908 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:15:40,597 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:17:33,606 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:18:26,492 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:18:38,007 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:18:49,532 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:21:42,076 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:21:54,735 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:24:17,919 ERROR: 更新卡密失败: 'License' object has no attribute 'remark' [in D:\work\code\python\KaMiXiTong\master\app\api\license.py:270]
2025-11-16 13:24:20,319 ERROR: 记录审计日志失败: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'license_key\': "\'4SGGNAPF-HPGNQC1Z-6D7OH879-9BGW32PI\'"}, \'127.0.0.1\', \'Mozilla/5\' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
[parameters: {'admin_id': 1, 'action': 'DELETE_LICENSE', 'target_type': 'LICENSE', 'target_id': None, 'details': {'license_key': '4SGGNAPF-HPGNQC1Z-6D7OH879-9BGW32PI'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 5, 24, 20, 318782)}]
(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
2025-11-16 13:24:53,054 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:26:10,998 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:32:27,257 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 13:32:51,116 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 17:40:11,814 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 17:40:13,787 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 18:01:08,089 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 18:02:10,913 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 18:06:15,046 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 18:11:51,110 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 19:05:00,160 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:74]
2025-11-16 19:05:02,504 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:74]
2025-11-16 19:05:57,986 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:74]

View File

@ -1,71 +0,0 @@
2025-11-15 23:58:20,986 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-15 23:58:22,596 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 11:34:44,087 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 11:34:48,148 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 11:36:10,246 ERROR: 记录审计日志失败: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_name\': "\'测试产品B\'"}, \'127.0.0.1\', \'Mozilla/5.0 (Windows NT 10.0; \' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
[parameters: {'admin_id': 1, 'action': 'DELETE_PRODUCT', 'target_type': 'PRODUCT', 'target_id': 'PROD_23EF726D', 'details': {'product_name': '测试产品B'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 10, 245687)}]
(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
2025-11-16 11:36:15,609 ERROR: 记录审计日志失败: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_name\': "\'测试产品A\'"}, \'127.0.0.1\', \'Mozilla/5.0 (Windows NT 10.0; \' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
[parameters: {'admin_id': 1, 'action': 'DELETE_PRODUCT', 'target_type': 'PRODUCT', 'target_id': 'PROD_897EF967', 'details': {'product_name': '测试产品A'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 15, 608798)}]
(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
2025-11-16 11:36:28,535 ERROR: 记录审计日志失败: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'license_key\': "\'W7XGEZU0-MFLWWKUK-XWPWFW3V-0LE77N2K\'"}, \'127.0.0.1\', \'Mozilla/5\' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
[parameters: {'admin_id': 1, 'action': 'DELETE_LICENSE', 'target_type': 'LICENSE', 'target_id': None, 'details': {'license_key': 'W7XGEZU0-MFLWWKUK-XWPWFW3V-0LE77N2K'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 28, 535307)}]
(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
2025-11-16 11:36:34,617 ERROR: 记录审计日志失败: (pymysql.err.ProgrammingError) (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'\'product_name\': "\'测试产品C\'"}, \'127.0.0.1\', \'Mozilla/5.0 (Windows NT 10.0; \' at line 1')
[SQL: INSERT INTO audit_log (admin_id, action, target_type, target_id, details, ip_address, user_agent, create_time) VALUES (%(admin_id)s, %(action)s, %(target_type)s, %(target_id)s, %(details)s, %(ip_address)s, %(user_agent)s, %(create_time)s)]
[parameters: {'admin_id': 1, 'action': 'DELETE_PRODUCT', 'target_type': 'PRODUCT', 'target_id': 'PROD_A24B55D2', 'details': {'product_name': '测试产品C'}, 'ip_address': '127.0.0.1', 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0', 'create_time': datetime.datetime(2025, 11, 16, 3, 36, 34, 616101)}]
(Background on this error at: https://sqlalche.me/e/20/f405) [in D:\work\code\python\KaMiXiTong\master\app\models\audit_log.py:59]
2025-11-16 12:12:41,268 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:13:10,962 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:13:13,050 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:14:26,744 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:14:34,528 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:14:44,691 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:56,754 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,016 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,016 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,024 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,039 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,092 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,343 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:57,372 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:16:59,517 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:17:24,098 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:21:22,890 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:21:27,735 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:21:31,439 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:21:40,107 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:21:43,784 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:21:53,199 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:22:01,231 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:22:03,158 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:22:05,098 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:22:07,063 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:22:11,957 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:22:59,514 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:23:01,359 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:32:48,638 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]
2025-11-16 12:33:31,339 INFO: KaMiXiTong startup [in D:\work\code\python\KaMiXiTong\master\config.py:71]

4
run.py
View File

@ -32,7 +32,7 @@ if __name__ == '__main__':
# 运行应用
app.run(
host='0.0.0.0',
port=5000,
host=os.environ.get('HOST', '0.0.0.0'),
port=int(os.environ.get('PORT', 5000)),
debug=app.config.get('DEBUG', False)
)

View File

@ -102,8 +102,8 @@ def start_flask_app():
from run import app
print("🚀 正在启动Flask应用...")
app.run(
host='127.0.0.1',
port=5000,
host=os.environ.get('HOST', '127.0.0.1'),
port=int(os.environ.get('PORT', 5000)),
debug=True,
use_reloader=False # 禁用重载器以避免子进程问题
)
@ -120,11 +120,14 @@ def start_fastapi_app():
print("🚀 正在启动FastAPI应用...")
# 使用uvicorn启动FastAPI应用
fastapi_host = os.environ.get('FASTAPI_HOST', '127.0.0.1')
fastapi_port = os.environ.get('FASTAPI_PORT', '9000')
subprocess.run([
sys.executable, '-m', 'uvicorn',
'fastapi_app:app',
'--host', '127.0.0.1',
'--port', '9000'
'--host', fastapi_host,
'--port', fastapi_port
])
except Exception as e:
print(f"❌ FastAPI应用启动失败: {e}")
@ -157,9 +160,13 @@ def main():
print("\n" + "=" * 50)
print("🚀 启动开发服务器...")
print("=" * 50)
print("📍 Flask访问地址: http://localhost:5000")
print("📍 FastAPI访问地址: http://localhost:9000")
print("📍 FastAPI文档: http://localhost:9000/docs")
host = os.environ.get('HOST', '127.0.0.1')
port = os.environ.get('PORT', '5000')
fastapi_port = os.environ.get('FASTAPI_PORT', '9000')
print(f"📍 Flask访问地址: http://{host}:{port}")
print(f"📍 FastAPI访问地址: http://{host}:{fastapi_port}")
print(f"📍 FastAPI文档: http://{host}:{fastapi_port}/docs")
print("👤 管理员账号: admin")
print("🔑 管理员密码: admin123")
print("⏹️ 按 Ctrl+C 停止服务器")

View File

@ -1,5 +1,10 @@
// 自定义JavaScript函数
// 设置前端域名全局变量
if (typeof FRONTEND_DOMAIN !== 'undefined') {
window.FRONTEND_DOMAIN = FRONTEND_DOMAIN;
}
// 显示加载动画
function showLoading() {
const loadingElement = document.getElementById('loading');
@ -16,28 +21,6 @@ function hideLoading() {
}
}
// 显示通知
function showNotification(message, type = 'info') {
// 创建通知元素
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// 插入到页面中
const container = document.querySelector('main') || document.body;
container.insertBefore(alertDiv, container.firstChild);
// 5秒后自动隐藏
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '-';
@ -56,16 +39,18 @@ function formatFileSize(bytes) {
// API请求函数 - 添加认证支持
function apiRequest(url, options = {}) {
// 确保URL是完整的路径
// 自动构建完整的API URL
let fullUrl = url;
if (!url.startsWith('/')) {
fullUrl = '/' + url;
}
if (!url.startsWith('/api/')) {
fullUrl = '/api/v1' + fullUrl;
}
if (!url.startsWith('/')) {
fullUrl = '/' + fullUrl;
// 检查是否配置了前端域名
const frontendDomain = window.FRONTEND_DOMAIN || '';
if (url.startsWith('/')) {
// 如果是相对路径,则使用配置的域名或当前主机
fullUrl = (frontendDomain || window.location.origin) + url;
} else if (!url.startsWith('http')) {
// 如果不是完整URL且不以http开头则添加API前缀
fullUrl = (frontendDomain || window.location.origin) + '/api/v1/' + url;
}
// 设置默认选项

View File

@ -74,7 +74,7 @@ class TestAuthValidator(unittest.TestCase):
self.software_id = "TEST_SOFTWARE_ID"
self.validator = AuthValidator(
software_id=self.software_id,
api_url="http://localhost:5000/api/v1",
api_url="http://km.taisan.online/api/v1",
timeout=1 # 快速超时
)