更新测试一版
This commit is contained in:
parent
73800eeaa7
commit
0cd42f3401
3
.env
3
.env
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 系统已经成功部署了!
|
||||
|
||||
@ -1,262 +1,96 @@
|
||||
# MySQL数据库配置指南
|
||||
# MySQL 数据库配置与初始化指南
|
||||
|
||||
## 概述
|
||||
## 目录
|
||||
1. [环境要求](#环境要求)
|
||||
2. [安装依赖](#安装依赖)
|
||||
3. [配置数据库连接](#配置数据库连接)
|
||||
4. [创建数据库](#创建数据库)
|
||||
5. [初始化数据库](#初始化数据库)
|
||||
6. [启动应用](#启动应用)
|
||||
|
||||
系统已经配置为使用MySQL数据库,并从`.env`文件读取配置。
|
||||
## 环境要求
|
||||
|
||||
## 配置步骤
|
||||
- MySQL 5.7 或更高版本
|
||||
- Python 3.8 或更高版本
|
||||
- PyMySQL 驱动
|
||||
|
||||
### 1. 确保安装MySQL客户端依赖
|
||||
|
||||
安装PyMySQL(MySQL 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文件配置正确即可!
|
||||
⚠️ 请在生产环境中修改默认密码!
|
||||
@ -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部署
|
||||
|
||||
41
README.md
41
README.md
@ -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)
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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()
|
||||
@ -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
|
||||
@ -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) {
|
||||
|
||||
@ -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 %}
|
||||
@ -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),
|
||||
|
||||
16
config.py
16
config.py
@ -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]'
|
||||
))
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
4
run.py
@ -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)
|
||||
)
|
||||
|
||||
21
start.py
21
start.py
@ -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 停止服务器")
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
// 设置默认选项
|
||||
|
||||
@ -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 # 快速超时
|
||||
)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user