修复接口访问问题
This commit is contained in:
parent
2a132259fb
commit
0e35be59f6
2
.env
2
.env
@ -23,7 +23,7 @@ MAX_UNBIND_TIMES=3
|
|||||||
LICENSE_KEY_LENGTH=32
|
LICENSE_KEY_LENGTH=32
|
||||||
LICENSE_KEY_PREFIX=
|
LICENSE_KEY_PREFIX=
|
||||||
|
|
||||||
FRONTEND_DOMAIN=http://km.taisan.online
|
FRONTEND_DOMAIN=http://127.0.0.1:5000
|
||||||
|
|
||||||
|
|
||||||
# API配置
|
# API配置
|
||||||
|
|||||||
@ -1,329 +0,0 @@
|
|||||||
# 账号管理系统重构部署指南
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
### 1. 备份现有系统
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 备份数据库
|
|
||||||
mysqldump -u root -p your_database > backup_$(date +%Y%m%d_%H%M%S).sql
|
|
||||||
|
|
||||||
# 备份应用文件
|
|
||||||
tar -czf app_backup_$(date +%Y%m%d_%H%M%S).tar.gz app/ migrations/
|
|
||||||
|
|
||||||
# 备份配置文件
|
|
||||||
cp config.py config.py.bak
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 应用迁移
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入项目目录
|
|
||||||
cd /path/to/KaMiXiTong
|
|
||||||
|
|
||||||
# 应用数据库迁移
|
|
||||||
flask db upgrade
|
|
||||||
|
|
||||||
# 或手动执行
|
|
||||||
alembic upgrade head
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 重启应用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 如果使用systemd
|
|
||||||
sudo systemctl restart kamixitong
|
|
||||||
|
|
||||||
# 如果使用gunicorn
|
|
||||||
sudo killall gunicorn
|
|
||||||
# 然后重新启动
|
|
||||||
gunicorn -w 4 -b 0.0.0.0:5000 run:app &
|
|
||||||
|
|
||||||
# 如果直接运行
|
|
||||||
python run.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 验证部署
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 运行测试脚本
|
|
||||||
python test_refactored_admin.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 详细部署步骤
|
|
||||||
|
|
||||||
### 步骤1: 准备工作
|
|
||||||
|
|
||||||
确认以下环境:
|
|
||||||
- Python 3.7+
|
|
||||||
- MySQL 5.7+ 或 PostgreSQL
|
|
||||||
- Flask-Migrate 已安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip list | grep -E "Flask|flask-migrate|sqlalchemy"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤2: 检查代码文件
|
|
||||||
|
|
||||||
确认以下文件已存在:
|
|
||||||
- `app/api/admin.py` (重构后)
|
|
||||||
- `app/models/audit_log.py` (新建)
|
|
||||||
- `app/web/templates/admin/list.html` (重构后)
|
|
||||||
- `migrations/versions/20241111_add_soft_delete_to_admin.py`
|
|
||||||
- `migrations/versions/20241111_create_audit_log.py`
|
|
||||||
|
|
||||||
### 步骤3: 停止应用
|
|
||||||
|
|
||||||
确保没有活跃的用户连接:
|
|
||||||
```bash
|
|
||||||
# 查看活跃连接
|
|
||||||
lsof -i :5000
|
|
||||||
|
|
||||||
# 停止应用
|
|
||||||
sudo systemctl stop kamixitong
|
|
||||||
# 或
|
|
||||||
pkill -f "gunicorn.*run:app"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤4: 应用迁移
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 初始化迁移仓库(如果还没有)
|
|
||||||
flask db init
|
|
||||||
|
|
||||||
# 生成迁移文件(可选,文件已存在)
|
|
||||||
# flask db migrate -m "Add soft delete fields to admin"
|
|
||||||
# flask db migrate -m "Create audit_log table"
|
|
||||||
|
|
||||||
# 升级数据库
|
|
||||||
flask db upgrade
|
|
||||||
|
|
||||||
# 验证迁移
|
|
||||||
flask db current
|
|
||||||
# 应该看到: add_soft_delete_admin 和 create_audit_log
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤5: 验证表结构
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 连接到数据库
|
|
||||||
mysql -u root -p
|
|
||||||
|
|
||||||
-- 检查admin表
|
|
||||||
DESCRIBE admin;
|
|
||||||
-- 应该看到: is_deleted, delete_time 字段
|
|
||||||
|
|
||||||
-- 检查audit_log表
|
|
||||||
DESCRIBE audit_log;
|
|
||||||
-- 应该存在audit_log表
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤6: 启动应用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动应用
|
|
||||||
python run.py
|
|
||||||
# 或
|
|
||||||
gunicorn -w 4 -b 0.0.0.0:5000 run:app &
|
|
||||||
|
|
||||||
# 检查日志
|
|
||||||
tail -f app.log
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤7: 功能测试
|
|
||||||
|
|
||||||
#### 测试1: 登录系统
|
|
||||||
1. 访问登录页面
|
|
||||||
2. 使用超级管理员账号登录
|
|
||||||
3. 确认可以访问管理后台
|
|
||||||
|
|
||||||
#### 测试2: 账号管理
|
|
||||||
1. 进入"账号管理"页面
|
|
||||||
2. 创建新账号
|
|
||||||
3. 编辑账号
|
|
||||||
4. 启用/禁用账号
|
|
||||||
5. 删除账号(验证软删除)
|
|
||||||
6. 查看审计日志
|
|
||||||
|
|
||||||
#### 测试3: 验证新功能
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 访问审计日志表
|
|
||||||
mysql -u root -p -e "SELECT * FROM audit_log ORDER BY create_time DESC LIMIT 10;"
|
|
||||||
|
|
||||||
# 查看已删除的账号
|
|
||||||
mysql -u root -p -e "SELECT admin_id, username, is_deleted, delete_time FROM admin WHERE is_deleted = 1;"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 回滚方案
|
|
||||||
|
|
||||||
如果遇到问题,可以回滚到原版本:
|
|
||||||
|
|
||||||
### 方案1: 回滚数据库
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看迁移历史
|
|
||||||
flask db history
|
|
||||||
|
|
||||||
# 回滚到特定版本
|
|
||||||
flask db downgrade add_soft_delete_admin
|
|
||||||
|
|
||||||
# 或完全回滚
|
|
||||||
flask db downgrade base
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案2: 恢复备份
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 恢复数据库
|
|
||||||
mysql -u root -p your_database < backup_YYYYMMDD_HHMMSS.sql
|
|
||||||
|
|
||||||
# 恢复应用文件
|
|
||||||
tar -xzf app_backup_YYYYMMDD_HHMMSS.tar.gz
|
|
||||||
|
|
||||||
# 重启应用
|
|
||||||
sudo systemctl restart kamixitong
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案3: 手动回滚
|
|
||||||
|
|
||||||
如果自动回滚失败,手动执行:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 删除audit_log表
|
|
||||||
DROP TABLE IF EXISTS audit_log;
|
|
||||||
|
|
||||||
-- 删除admin表字段
|
|
||||||
ALTER TABLE admin DROP COLUMN is_deleted;
|
|
||||||
ALTER TABLE admin DROP COLUMN delete_time;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能优化建议
|
|
||||||
|
|
||||||
### 1. 审计日志归档
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 归档6个月前的日志
|
|
||||||
CREATE TABLE audit_log_archive AS
|
|
||||||
SELECT * FROM audit_log
|
|
||||||
WHERE create_time < DATE_SUB(NOW(), INTERVAL 6 MONTH);
|
|
||||||
|
|
||||||
-- 清理旧日志
|
|
||||||
DELETE FROM audit_log
|
|
||||||
WHERE create_time < DATE_SUB(NOW(), INTERVAL 6 MONTH);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 添加索引
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 为常用查询添加索引
|
|
||||||
CREATE INDEX idx_admin_status ON admin(status);
|
|
||||||
CREATE INDEX idx_admin_username ON admin(username);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 监控查询性能
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 慢查询监控
|
|
||||||
SET long_query_time = 2;
|
|
||||||
SET slow_query_log = 1;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题解决
|
|
||||||
|
|
||||||
### Q1: 迁移失败
|
|
||||||
|
|
||||||
**错误:** `Target database is not up to date`
|
|
||||||
|
|
||||||
**解决:**
|
|
||||||
```bash
|
|
||||||
# 标记当前版本
|
|
||||||
flask db stamp head
|
|
||||||
|
|
||||||
# 重新迁移
|
|
||||||
flask db upgrade
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q2: 权限错误
|
|
||||||
|
|
||||||
**错误:** `Access denied for user`
|
|
||||||
|
|
||||||
**解决:**
|
|
||||||
```sql
|
|
||||||
-- 授权
|
|
||||||
GRANT ALL PRIVILEGES ON your_database.* TO 'your_user'@'localhost';
|
|
||||||
FLUSH PRIVILEGES;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q3: 审计日志记录失败
|
|
||||||
|
|
||||||
**错误:** `Table 'audit_log' doesn't exist`
|
|
||||||
|
|
||||||
**解决:**
|
|
||||||
```bash
|
|
||||||
# 手动应用迁移
|
|
||||||
alembic upgrade create_audit_log
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q4: 登录失败
|
|
||||||
|
|
||||||
**错误:** `管理员不存在`
|
|
||||||
|
|
||||||
**解决:**
|
|
||||||
检查数据库中是否有超级管理员账号:
|
|
||||||
```sql
|
|
||||||
SELECT admin_id, username, role, status, is_deleted FROM admin WHERE role = 1 AND status = 1 AND is_deleted = 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
如果没有,创建一个:
|
|
||||||
```sql
|
|
||||||
INSERT INTO admin (username, password_hash, email, role, status, is_deleted)
|
|
||||||
VALUES ('admin', '<hashed_password>', 'admin@example.com', 1, 1, 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 监控和维护
|
|
||||||
|
|
||||||
### 日常检查
|
|
||||||
|
|
||||||
1. **查看应用日志**
|
|
||||||
```bash
|
|
||||||
tail -f app.log | grep -E "ERROR|CRITICAL"
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **检查数据库状态**
|
|
||||||
```sql
|
|
||||||
SHOW PROCESSLIST;
|
|
||||||
SHOW TABLE STATUS;
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **监控审计日志增长**
|
|
||||||
```sql
|
|
||||||
SELECT COUNT(*) FROM audit_log WHERE create_time > CURDATE();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 定期维护
|
|
||||||
|
|
||||||
1. **每月归档审计日志**
|
|
||||||
2. **清理过期的已删除账号** (可选)
|
|
||||||
3. **优化数据库表** (`OPTIMIZE TABLE`)
|
|
||||||
4. **备份数据库**
|
|
||||||
|
|
||||||
## 联系支持
|
|
||||||
|
|
||||||
如果遇到问题:
|
|
||||||
1. 查看 `REFACTOR_NOTES.md` 了解详细变更
|
|
||||||
2. 运行 `test_refactored_admin.py` 进行诊断
|
|
||||||
3. 检查应用日志和数据库日志
|
|
||||||
4. 如需帮助,请提供:
|
|
||||||
- 错误日志
|
|
||||||
- 数据库版本
|
|
||||||
- Python版本
|
|
||||||
- 操作系统版本
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**重要提示:**
|
|
||||||
- 部署前务必备份数据
|
|
||||||
- 在测试环境先进行验证
|
|
||||||
- 保留原有的admin_old.py和list_old.html作为备份
|
|
||||||
- 建议在低峰期进行部署
|
|
||||||
@ -27,7 +27,6 @@ KaMiXiTong/
|
|||||||
│ ├── test_api.py # API测试
|
│ ├── test_api.py # API测试
|
||||||
│ └── test_models.py # 模型测试
|
│ └── test_models.py # 模型测试
|
||||||
├── docs/ # 文档
|
├── docs/ # 文档
|
||||||
│ ├── API.md # API文档
|
|
||||||
│ ├── INTEGRATION.md # 集成文档
|
│ ├── INTEGRATION.md # 集成文档
|
||||||
│ ├── EXAMPLES.md # 使用示例
|
│ ├── EXAMPLES.md # 使用示例
|
||||||
│ └── FASTAPI.md # FastAPI接口文档
|
│ └── FASTAPI.md # FastAPI接口文档
|
||||||
@ -136,7 +135,8 @@ gunicorn -w 4 -b 0.0.0.0:5000 run:app
|
|||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
- [API文档](docs/API.md)
|
为了更好地管理和使用项目文档,请参考 [文档整理指南](DOCUMENTATION_GUIDE.md)。
|
||||||
|
|
||||||
- [集成指南](docs/INTEGRATION.md)
|
- [集成指南](docs/INTEGRATION.md)
|
||||||
- [使用示例](docs/EXAMPLES.md)
|
- [使用示例](docs/EXAMPLES.md)
|
||||||
- [FastAPI接口文档](docs/FASTAPI.md)
|
- [FastAPI接口文档](docs/FASTAPI.md)
|
||||||
|
|||||||
@ -1,298 +0,0 @@
|
|||||||
# 账号管理系统重构说明
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
本次重构彻底改进了账号管理系统,从后端API到前端页面都进行了全面优化。重构基于第一性原理思考,采用更现代、更安全的架构。
|
|
||||||
|
|
||||||
## 重构前后对比
|
|
||||||
|
|
||||||
### 后端改进
|
|
||||||
|
|
||||||
| 方面 | 重构前 | 重构后 |
|
|
||||||
|------|--------|--------|
|
|
||||||
| 错误处理 | 基础try-catch | 装饰器统一异常处理,分类错误码 |
|
|
||||||
| 响应格式 | 不统一 | 统一响应结构: `{success, data, message, code}` |
|
|
||||||
| 数据验证 | 分散在多处 | 统一的验证函数,前后端一致 |
|
|
||||||
| 删除方式 | 硬删除(物理删除) | 软删除(标记删除) |
|
|
||||||
| 审计日志 | 无 | 完整的审计日志系统 |
|
|
||||||
| 日志记录 | 只记录错误 | 记录所有操作详情 |
|
|
||||||
| 安全性 | 基础验证 | 强验证+权限控制 |
|
|
||||||
| 代码复用 | 重复代码较多 | 模块化,函数复用 |
|
|
||||||
|
|
||||||
### 前端改进
|
|
||||||
|
|
||||||
| 方面 | 重构前 | 重构后 |
|
|
||||||
|------|--------|--------|
|
|
||||||
| 状态管理 | 散乱 | IIFE模式,状态统一管理 |
|
|
||||||
| DOM操作 | 频繁使用innerHTML | DOM创建+事件委托 |
|
|
||||||
| 错误处理 | 简单alert | 优雅的通知系统 |
|
|
||||||
| 事件绑定 | 重复绑定 | 事件委托避免重复 |
|
|
||||||
| 代码结构 | 函数式 | 模块化(IIFE) |
|
|
||||||
| 用户体验 | 基础 | 加载状态、实时反馈 |
|
|
||||||
| 表单验证 | 基础 | 前后端双重验证 |
|
|
||||||
|
|
||||||
## 核心改进
|
|
||||||
|
|
||||||
### 1. 统一响应格式
|
|
||||||
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"success": bool, # 操作是否成功
|
|
||||||
"data": any, # 返回数据
|
|
||||||
"message": str, # 提示信息
|
|
||||||
"code": int # 状态码
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**优点:**
|
|
||||||
- 前端处理更简单
|
|
||||||
- 错误信息更清晰
|
|
||||||
- 便于调试和日志分析
|
|
||||||
|
|
||||||
### 2. 软删除机制
|
|
||||||
|
|
||||||
- **添加字段:** `is_deleted`, `delete_time`
|
|
||||||
- **查询过滤:** 自动过滤已删除记录
|
|
||||||
- **安全保护:** 防止数据丢失
|
|
||||||
|
|
||||||
### 3. 审计日志
|
|
||||||
|
|
||||||
记录所有管理员操作:
|
|
||||||
- 操作类型: CREATE, UPDATE, DELETE, TOGGLE_STATUS
|
|
||||||
- 操作人: admin_id
|
|
||||||
- 目标: target_type, target_id
|
|
||||||
- 详情: details (JSON格式)
|
|
||||||
- 环境: IP地址, User-Agent
|
|
||||||
|
|
||||||
### 4. 统一验证
|
|
||||||
|
|
||||||
- `validate_username()`: 用户名验证
|
|
||||||
- `validate_password()`: 密码强度验证
|
|
||||||
- `validate_email()`: 邮箱格式验证
|
|
||||||
- `validate_admin_data()`: 管理员数据综合验证
|
|
||||||
|
|
||||||
### 5. 异常处理装饰器
|
|
||||||
|
|
||||||
```python
|
|
||||||
@handle_exceptions
|
|
||||||
def api_function():
|
|
||||||
# 异常自动处理
|
|
||||||
# 统一错误响应
|
|
||||||
# 自动回滚事务
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 权限验证装饰器
|
|
||||||
|
|
||||||
```python
|
|
||||||
@require_admin
|
|
||||||
def protected_function():
|
|
||||||
# 自动检查登录状态
|
|
||||||
# 自动检查超级管理员权限
|
|
||||||
# 统一权限错误响应
|
|
||||||
```
|
|
||||||
|
|
||||||
## 文件变更
|
|
||||||
|
|
||||||
### 新增文件
|
|
||||||
|
|
||||||
1. **app/models/audit_log.py** - 审计日志模型
|
|
||||||
2. **app/api/admin_refactored.py** - 重构后的API(已替换原文件)
|
|
||||||
3. **app/web/templates/admin/list_refactored.html** - 重构后的前端页面(已替换原文件)
|
|
||||||
4. **migrations/versions/20241111_add_soft_delete_to_admin.py** - 添加软删除字段迁移
|
|
||||||
5. **migrations/versions/20241111_create_audit_log.py** - 创建审计日志表迁移
|
|
||||||
6. **test_refactored_admin.py** - 测试脚本
|
|
||||||
|
|
||||||
### 备份文件
|
|
||||||
|
|
||||||
1. `app/api/admin_old.py` - 原API文件备份
|
|
||||||
2. `app/web/templates/admin/list_old.html` - 原前端页面备份
|
|
||||||
|
|
||||||
## 数据库迁移
|
|
||||||
|
|
||||||
### 迁移1: 添加软删除字段
|
|
||||||
|
|
||||||
```bash
|
|
||||||
flask db upgrade
|
|
||||||
# 或
|
|
||||||
alembic upgrade head
|
|
||||||
```
|
|
||||||
|
|
||||||
**变更内容:**
|
|
||||||
- 添加 `is_deleted` 字段 (默认0)
|
|
||||||
- 添加 `delete_time` 字段
|
|
||||||
- 创建索引 `ix_admin_is_deleted`
|
|
||||||
|
|
||||||
### 迁移2: 创建审计日志表
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 迁移1完成后自动执行
|
|
||||||
# 或手动执行
|
|
||||||
flask db stamp add_soft_delete_admin
|
|
||||||
flask db upgrade
|
|
||||||
```
|
|
||||||
|
|
||||||
**变更内容:**
|
|
||||||
- 创建 `audit_log` 表
|
|
||||||
- 添加外键关联
|
|
||||||
- 创建索引
|
|
||||||
|
|
||||||
## API变更
|
|
||||||
|
|
||||||
### 响应结构统一
|
|
||||||
|
|
||||||
所有API现在返回统一格式:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true/false,
|
|
||||||
"data": {...}, // 成功时返回数据
|
|
||||||
"message": "提示信息",
|
|
||||||
"code": 0 // 状态码
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 状态码定义
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ResponseCode:
|
|
||||||
SUCCESS = 0 # 成功
|
|
||||||
VALIDATION_ERROR = 1001 # 验证错误
|
|
||||||
AUTHENTICATION_FAILED = 1002 # 认证失败
|
|
||||||
PERMISSION_DENIED = 1003 # 权限不足
|
|
||||||
NOT_FOUND = 1004 # 资源不存在
|
|
||||||
DUPLICATE_USERNAME = 2001 # 用户名重复
|
|
||||||
INVALID_DATA = 2002 # 无效数据
|
|
||||||
CANNOT_DELETE_SELF = 2003 # 不能删除自己
|
|
||||||
CANNOT_DISABLE_SELF = 2004 # 不能禁用自己
|
|
||||||
SERVER_ERROR = 5000 # 服务器错误
|
|
||||||
```
|
|
||||||
|
|
||||||
## 前端改进
|
|
||||||
|
|
||||||
### 1. 模块化代码结构
|
|
||||||
|
|
||||||
使用IIFE模式封装:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// 状态管理
|
|
||||||
const state = {
|
|
||||||
currentPage: 1,
|
|
||||||
searchParams: {...},
|
|
||||||
isLoading: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// 事件处理
|
|
||||||
function bindEvents() {...}
|
|
||||||
|
|
||||||
// 数据加载
|
|
||||||
function loadAdmins() {...}
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
|
||||||
})();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 事件委托
|
|
||||||
|
|
||||||
使用事件委托减少内存泄漏:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
elements.adminList.addEventListener('click', function(e) {
|
|
||||||
const target = e.target.closest('button');
|
|
||||||
if (!target) return;
|
|
||||||
|
|
||||||
if (target.classList.contains('edit-btn')) {
|
|
||||||
// 编辑逻辑
|
|
||||||
} else if (target.classList.contains('delete-btn')) {
|
|
||||||
// 删除逻辑
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. DOM安全操作
|
|
||||||
|
|
||||||
使用createElement替代innerHTML:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const tr = document.createElement('tr');
|
|
||||||
const td = document.createElement('td');
|
|
||||||
td.textContent = admin.username; // 防止XSS
|
|
||||||
tr.appendChild(td);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 用户体验优化
|
|
||||||
|
|
||||||
- 加载状态指示
|
|
||||||
- 优雅的错误通知
|
|
||||||
- 实时数据更新
|
|
||||||
- 表单验证反馈
|
|
||||||
|
|
||||||
## 测试
|
|
||||||
|
|
||||||
运行测试脚本:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python test_refactored_admin.py
|
|
||||||
```
|
|
||||||
|
|
||||||
测试覆盖:
|
|
||||||
1. Admin模型基本功能
|
|
||||||
2. 软删除功能
|
|
||||||
3. 审计日志记录
|
|
||||||
4. 数据验证函数
|
|
||||||
5. 统一响应格式
|
|
||||||
|
|
||||||
## 部署步骤
|
|
||||||
|
|
||||||
1. **备份数据库**
|
|
||||||
```bash
|
|
||||||
# 备份当前数据库
|
|
||||||
mysqldump -u user -p database > backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **应用迁移**
|
|
||||||
```bash
|
|
||||||
flask db upgrade
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **重启应用**
|
|
||||||
```bash
|
|
||||||
# 如果使用gunicorn
|
|
||||||
sudo systemctl restart kamixitong
|
|
||||||
|
|
||||||
# 或直接运行
|
|
||||||
python run.py
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **验证部署**
|
|
||||||
- 访问账号管理页面
|
|
||||||
- 创建测试账号
|
|
||||||
- 查看审计日志
|
|
||||||
|
|
||||||
## 已知问题和限制
|
|
||||||
|
|
||||||
1. **兼容性**: 现有已删除的硬删除记录需要手动处理
|
|
||||||
2. **性能**: 审计日志会逐渐增大,建议定期归档
|
|
||||||
3. **权限**: 当前只有超级管理员可以管理账号
|
|
||||||
|
|
||||||
## 未来改进计划
|
|
||||||
|
|
||||||
1. **批量操作**: 支持批量启用/禁用/删除
|
|
||||||
2. **角色细分**: 支持更多角色类型
|
|
||||||
3. **操作日志查看**: 在管理界面查看审计日志
|
|
||||||
4. **数据导出**: 支持导出账号数据
|
|
||||||
5. **API限流**: 添加API访问频率限制
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
本次重构显著提升了系统的:
|
|
||||||
- **安全性**: 软删除+审计日志
|
|
||||||
- **可维护性**: 模块化+统一规范
|
|
||||||
- **可观测性**: 完整的日志记录
|
|
||||||
- **用户体验**: 实时反馈+优雅交互
|
|
||||||
- **代码质量**: 减少重复+提高复用
|
|
||||||
|
|
||||||
通过这次重构,账号管理系统达到了生产级别的标准,具备了更好的扩展性和维护性。
|
|
||||||
@ -5,6 +5,7 @@ from flask_login import LoginManager
|
|||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from config import config
|
from config import config
|
||||||
from flask_wtf.csrf import CSRFProtect
|
from flask_wtf.csrf import CSRFProtect
|
||||||
|
from flask_cors import CORS
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ db = SQLAlchemy()
|
|||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
csrf = CSRFProtect()
|
csrf = CSRFProtect()
|
||||||
|
cors = CORS()
|
||||||
|
|
||||||
def create_app(config_name='default'):
|
def create_app(config_name='default'):
|
||||||
"""应用工厂函数"""
|
"""应用工厂函数"""
|
||||||
@ -48,6 +50,7 @@ def create_app(config_name='default'):
|
|||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
migrate.init_app(app, db)
|
migrate.init_app(app, db)
|
||||||
csrf.init_app(app)
|
csrf.init_app(app)
|
||||||
|
cors.init_app(app, resources={r"/api/*": {"origins": "*"}})
|
||||||
|
|
||||||
# 配置登录管理器
|
# 配置登录管理器
|
||||||
login_manager.login_view = 'web.login'
|
login_manager.login_view = 'web.login'
|
||||||
|
|||||||
@ -169,6 +169,18 @@ def create_version():
|
|||||||
current_app.logger.warning(f"版本号已存在: product_id={product_id}, version_num={version_num}")
|
current_app.logger.warning(f"版本号已存在: product_id={product_id}, version_num={version_num}")
|
||||||
return jsonify({'success': False, 'message': '版本号已存在'}), 400
|
return jsonify({'success': False, 'message': '版本号已存在'}), 400
|
||||||
|
|
||||||
|
# 验证文件链接格式(如果提供了链接)
|
||||||
|
if download_url:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
try:
|
||||||
|
result = urlparse(download_url)
|
||||||
|
if not all([result.scheme, result.netloc]):
|
||||||
|
current_app.logger.warning(f"无效的文件链接格式: {download_url}")
|
||||||
|
return jsonify({'success': False, 'message': '文件链接格式无效'}), 400
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.warning(f"文件链接验证失败: {str(e)}")
|
||||||
|
return jsonify({'success': False, 'message': '文件链接格式无效'}), 400
|
||||||
|
|
||||||
current_app.logger.info(f"创建版本对象: product_id={product_id}, version_num={version_num}")
|
current_app.logger.info(f"创建版本对象: product_id={product_id}, version_num={version_num}")
|
||||||
version = Version(
|
version = Version(
|
||||||
product_id=product_id,
|
product_id=product_id,
|
||||||
@ -198,7 +210,8 @@ def create_version():
|
|||||||
log_operation('CREATE_VERSION', 'VERSION', version.version_id, {
|
log_operation('CREATE_VERSION', 'VERSION', version.version_id, {
|
||||||
'product_id': version.product_id,
|
'product_id': version.product_id,
|
||||||
'version_num': version.version_num,
|
'version_num': version.version_num,
|
||||||
'publish_now': publish_now
|
'publish_now': publish_now,
|
||||||
|
'has_download_url': bool(download_url)
|
||||||
})
|
})
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
@ -336,7 +349,10 @@ def update_version_status(version_id):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"更新版本状态失败: {str(e)}")
|
current_app.logger.error(f"更新版本状态失败: {str(e)}")
|
||||||
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': '服务器内部错误'
|
||||||
|
}), 500
|
||||||
|
|
||||||
@api_bp.route('/versions/upload', methods=['POST'])
|
@api_bp.route('/versions/upload', methods=['POST'])
|
||||||
@require_login
|
@require_login
|
||||||
@ -630,3 +646,13 @@ def batch_update_version_status():
|
|||||||
'message': '服务器内部错误'
|
'message': '服务器内部错误'
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -50,10 +50,20 @@
|
|||||||
<textarea class="form-control" id="update_notes" name="update_notes" rows="5"></textarea>
|
<textarea class="form-control" id="update_notes" name="update_notes" rows="5"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 文件上传区域 -->
|
<!-- 文件提供方式选择 -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">版本文件上传</label>
|
<label class="form-label">文件提供方式 *</label>
|
||||||
<div class="border rounded p-3 mb-2">
|
<ul class="nav nav-tabs" id="fileSourceTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload-content" type="button" role="tab">上传文件</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="link-tab" data-bs-toggle="tab" data-bs-target="#link-content" type="button" role="tab">文件链接</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content border border-top-0 p-3" id="fileSourceTabsContent">
|
||||||
|
<!-- 文件上传区域 -->
|
||||||
|
<div class="tab-pane fade show active" id="upload-content" role="tabpanel">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm" id="upload-file-btn">
|
<button type="button" class="btn btn-outline-primary btn-sm" id="upload-file-btn">
|
||||||
<i class="fas fa-upload me-1"></i>选择文件
|
<i class="fas fa-upload me-1"></i>选择文件
|
||||||
@ -68,8 +78,22 @@
|
|||||||
<input type="hidden" id="file_url" name="file_url">
|
<input type="hidden" id="file_url" name="file_url">
|
||||||
<input type="hidden" id="file_hash" name="file_hash">
|
<input type="hidden" id="file_hash" name="file_hash">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-text">支持常见的压缩包和安装包格式</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件链接区域 -->
|
||||||
|
<div class="tab-pane fade" id="link-content" role="tabpanel">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="file_link" class="form-label">文件下载链接</label>
|
||||||
|
<input type="url" class="form-control" id="file_link" name="file_link" placeholder="https://example.com/software.zip">
|
||||||
|
<div class="form-text">请输入完整的文件下载链接</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="link_file_name" class="form-label">文件名(可选)</label>
|
||||||
|
<input type="text" class="form-control" id="link_file_name" name="link_file_name" placeholder="software.zip">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">支持常见的压缩包和安装包格式,文件将单独上传</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 form-check">
|
<div class="mb-3 form-check">
|
||||||
@ -241,7 +265,11 @@ function createVersion() {
|
|||||||
formData.append('update_notes', updateNotes);
|
formData.append('update_notes', updateNotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加文件信息(如果已上传)
|
// 检查当前选择的文件提供方式
|
||||||
|
const activeTab = document.querySelector('.nav-link.active').id;
|
||||||
|
|
||||||
|
if (activeTab === 'upload-tab') {
|
||||||
|
// 上传文件方式
|
||||||
const fileUrl = document.getElementById('file_url').value;
|
const fileUrl = document.getElementById('file_url').value;
|
||||||
if (fileUrl) {
|
if (fileUrl) {
|
||||||
formData.append('download_url', fileUrl);
|
formData.append('download_url', fileUrl);
|
||||||
@ -251,6 +279,21 @@ function createVersion() {
|
|||||||
if (fileHash) {
|
if (fileHash) {
|
||||||
formData.append('file_hash', fileHash);
|
formData.append('file_hash', fileHash);
|
||||||
}
|
}
|
||||||
|
} else if (activeTab === 'link-tab') {
|
||||||
|
// 文件链接方式
|
||||||
|
const fileLink = document.getElementById('file_link').value.trim();
|
||||||
|
if (fileLink) {
|
||||||
|
formData.append('download_url', fileLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果提供了文件名,则使用提供的文件名
|
||||||
|
const linkFileName = document.getElementById('link_file_name').value.trim();
|
||||||
|
if (linkFileName) {
|
||||||
|
// 生成一个虚拟的文件哈希(基于文件名)
|
||||||
|
const fileHash = generateSimpleHash(linkFileName);
|
||||||
|
formData.append('file_hash', fileHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const publishNow = document.getElementById('publish_now').checked;
|
const publishNow = document.getElementById('publish_now').checked;
|
||||||
formData.append('publish_now', publishNow);
|
formData.append('publish_now', publishNow);
|
||||||
@ -266,6 +309,17 @@ function createVersion() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证文件提供方式
|
||||||
|
if (activeTab === 'upload-tab') {
|
||||||
|
// 上传方式不需要强制要求文件,但如果有文件则使用
|
||||||
|
} else if (activeTab === 'link-tab') {
|
||||||
|
const fileLink = document.getElementById('file_link').value.trim();
|
||||||
|
if (fileLink && !isValidUrl(fileLink)) {
|
||||||
|
showNotification('请输入有效的文件链接', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 显示加载状态
|
// 显示加载状态
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
submitText.textContent = '创建中...';
|
submitText.textContent = '创建中...';
|
||||||
@ -307,5 +361,26 @@ function createVersion() {
|
|||||||
submitText.textContent = '创建版本';
|
submitText.textContent = '创建版本';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 简单的哈希生成函数
|
||||||
|
function generateSimpleHash(str) {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const char = str.charCodeAt(i);
|
||||||
|
hash = ((hash << 5) - hash) + char;
|
||||||
|
hash = hash & hash; // 转换为32位整数
|
||||||
|
}
|
||||||
|
return Math.abs(hash).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证URL格式
|
||||||
|
function isValidUrl(string) {
|
||||||
|
try {
|
||||||
|
new URL(string);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Binary file not shown.
@ -74,7 +74,7 @@ class TestAuthValidator(unittest.TestCase):
|
|||||||
self.software_id = "TEST_SOFTWARE_ID"
|
self.software_id = "TEST_SOFTWARE_ID"
|
||||||
self.validator = AuthValidator(
|
self.validator = AuthValidator(
|
||||||
software_id=self.software_id,
|
software_id=self.software_id,
|
||||||
api_url="http://km.taisan.online/api/v1",
|
api_url="http://127.0.0.1:5000/api/v1", # 使用本地测试URL
|
||||||
timeout=1 # 快速超时
|
timeout=1 # 快速超时
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user