后台管理页面导航栏修改为侧边

This commit is contained in:
rjb
2025-09-07 22:33:16 +08:00
parent 177079221e
commit 78aab17511
14 changed files with 2969 additions and 15 deletions

View File

@@ -0,0 +1,646 @@
# 提示词大师平台 API 接口文档
## 📋 **文档概述**
本文档提供了提示词大师平台的所有API接口供第三方应用或新应用开发使用。
**基础信息:**
- **API版本** v1.0
- **基础URL** `http://localhost:5002/api`
- **数据格式:** JSON
- **认证方式:** API Key / Session Token
---
## 🔐 **认证接口**
### 1. 获取API访问令牌
```http
POST /api/auth/token
Content-Type: application/json
{
"username": "your_username",
"password": "your_password"
}
```
**响应示例:**
```json
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600,
"user_info": {
"id": 1,
"username": "your_username",
"email": "user@example.com"
}
}
```
### 2. 刷新访问令牌
```http
POST /api/auth/refresh
Authorization: Bearer {token}
```
---
## 📝 **模板管理接口**
### 1. 获取模板列表
```http
GET /api/templates?page=1&limit=20&category=all&search=
Authorization: Bearer {token}
```
**查询参数:**
- `page`: 页码默认1
- `limit`: 每页数量默认20最大100
- `category`: 分类筛选
- `search`: 搜索关键词
- `industry`: 行业筛选
- `profession`: 职业筛选
- `sub_category`: 子分类筛选
**响应示例:**
```json
{
"success": true,
"data": {
"templates": [
{
"id": 1,
"name": "通用优化",
"description": "通用提示词优化",
"category": "通用工具",
"industry": "通用",
"profession": "通用",
"sub_category": "优化",
"content": "你是一个专业的AI助手...",
"usage_count": 1250,
"rating": 4.8,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 281,
"pages": 15
}
}
}
```
### 2. 获取单个模板详情
```http
GET /api/templates/{template_id}
Authorization: Bearer {token}
```
### 3. 创建新模板
```http
POST /api/templates
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "",
"description": "",
"category": "",
"industry": "",
"profession": "",
"sub_category": "",
"content": ""
}
```
### 4. 更新模板
```http
PUT /api/templates/{template_id}
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "",
"description": "",
"content": ""
}
```
### 5. 删除模板
```http
DELETE /api/templates/{template_id}
Authorization: Bearer {token}
```
---
## 🎯 **提示词生成接口**
### 1. 生成提示词
```http
POST /api/generate
Authorization: Bearer {token}
Content-Type: application/json
{
"template_id": 1,
"input_text": "",
"options": {
"max_length": 1000,
"style": "professional",
"language": "zh-CN"
}
}
```
**响应示例:**
```json
{
"success": true,
"data": {
"generated_text": "生成的提示词内容...",
"template_used": {
"id": 1,
"name": "通用优化"
},
"generation_time": 1.2,
"word_count": 150
}
}
```
### 2. 批量生成提示词
```http
POST /api/generate/batch
Authorization: Bearer {token}
Content-Type: application/json
{
"requests": [
{
"template_id": 1,
"input_text": "1"
},
{
"template_id": 2,
"input_text": "2"
}
]
}
```
---
## 📊 **分类管理接口**
### 1. 获取所有分类
```http
GET /api/categories
Authorization: Bearer {token}
```
**响应示例:**
```json
{
"success": true,
"data": {
"categories": [
{
"name": "通用工具",
"description": "通用工具类模板",
"icon": "fas fa-tools",
"template_count": 45
}
]
}
}
```
### 2. 获取行业列表
```http
GET /api/industries
Authorization: Bearer {token}
```
### 3. 获取职业列表
```http
GET /api/professions
Authorization: Bearer {token}
```
### 4. 获取子分类列表
```http
GET /api/sub-categories
Authorization: Bearer {token}
```
---
## ❤️ **收藏管理接口**
### 1. 获取用户收藏列表
```http
GET /api/favorites?page=1&limit=20
Authorization: Bearer {token}
```
### 2. 添加收藏
```http
POST /api/favorites
Authorization: Bearer {token}
Content-Type: application/json
{
"template_id": 1,
"note": ""
}
```
### 3. 删除收藏
```http
DELETE /api/favorites/{favorite_id}
Authorization: Bearer {token}
```
### 4. 快速添加收藏(包含生成内容)
```http
POST /api/favorites/quick-add
Authorization: Bearer {token}
Content-Type: application/json
{
"template_id": 1,
"original_text": "",
"generated_prompt": "",
"category": ""
}
```
---
## 📈 **统计接口**
### 1. 获取平台统计信息
```http
GET /api/stats/platform
Authorization: Bearer {token}
```
**响应示例:**
```json
{
"success": true,
"data": {
"total_templates": 281,
"total_categories": 73,
"total_generations": 15420,
"total_users": 1250,
"popular_templates": [
{
"id": 1,
"name": "通用优化",
"usage_count": 1250
}
]
}
}
```
### 2. 获取用户统计信息
```http
GET /api/stats/user
Authorization: Bearer {token}
```
### 3. 获取模板使用统计
```http
GET /api/stats/templates/{template_id}
Authorization: Bearer {token}
```
---
## 🔍 **搜索接口**
### 1. 智能搜索
```http
GET /api/search?q=&type=template&limit=20
Authorization: Bearer {token}
```
**查询参数:**
- `q`: 搜索关键词
- `type`: 搜索类型template, category, all
- `limit`: 结果数量限制
### 2. 获取热门搜索
```http
GET /api/search/hot
Authorization: Bearer {token}
```
### 3. 获取搜索建议
```http
GET /api/search/suggestions?q=
Authorization: Bearer {token}
```
---
## 👤 **用户管理接口**
### 1. 获取用户信息
```http
GET /api/user/profile
Authorization: Bearer {token}
```
### 2. 更新用户信息
```http
PUT /api/user/profile
Authorization: Bearer {token}
Content-Type: application/json
{
"username": "",
"email": "newemail@example.com",
"avatar": "URL"
}
```
### 3. 修改密码
```http
PUT /api/user/password
Authorization: Bearer {token}
Content-Type: application/json
{
"old_password": "",
"new_password": ""
}
```
---
## 📝 **使用示例**
### Python 示例
```python
import requests
import json
class PromptMasterAPI:
def __init__(self, base_url="http://localhost:5002/api", token=None):
self.base_url = base_url
self.token = token
self.headers = {
"Content-Type": "application/json"
}
if token:
self.headers["Authorization"] = f"Bearer {token}"
def login(self, username, password):
"""用户登录获取token"""
response = requests.post(
f"{self.base_url}/auth/token",
json={"username": username, "password": password}
)
data = response.json()
if data["success"]:
self.token = data["token"]
self.headers["Authorization"] = f"Bearer {self.token}"
return data
def get_templates(self, page=1, limit=20, category="all", search=None):
"""获取模板列表"""
params = {
"page": page,
"limit": limit,
"category": category
}
if search:
params["search"] = search
response = requests.get(
f"{self.base_url}/templates",
headers=self.headers,
params=params
)
return response.json()
def generate_prompt(self, template_id, input_text, options=None):
"""生成提示词"""
data = {
"template_id": template_id,
"input_text": input_text
}
if options:
data["options"] = options
response = requests.post(
f"{self.base_url}/generate",
headers=self.headers,
json=data
)
return response.json()
def add_favorite(self, template_id, note=None):
"""添加收藏"""
data = {"template_id": template_id}
if note:
data["note"] = note
response = requests.post(
f"{self.base_url}/favorites",
headers=self.headers,
json=data
)
return response.json()
# 使用示例
api = PromptMasterAPI()
# 登录
result = api.login("your_username", "your_password")
if result["success"]:
print("登录成功!")
# 获取模板列表
templates = api.get_templates(page=1, limit=10)
print(f"找到 {templates['data']['pagination']['total']} 个模板")
# 生成提示词
if templates["data"]["templates"]:
template_id = templates["data"]["templates"][0]["id"]
result = api.generate_prompt(
template_id=template_id,
input_text="请帮我写一篇关于人工智能的文章"
)
if result["success"]:
print("生成的提示词:", result["data"]["generated_text"])
```
### JavaScript 示例
```javascript
class PromptMasterAPI {
constructor(baseUrl = 'http://localhost:5002/api', token = null) {
this.baseUrl = baseUrl;
this.token = token;
this.headers = {
'Content-Type': 'application/json'
};
if (token) {
this.headers['Authorization'] = `Bearer ${token}`;
}
}
async login(username, password) {
const response = await fetch(`${this.baseUrl}/auth/token`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.success) {
this.token = data.token;
this.headers['Authorization'] = `Bearer ${this.token}`;
}
return data;
}
async getTemplates(page = 1, limit = 20, category = 'all', search = null) {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
category: category
});
if (search) params.append('search', search);
const response = await fetch(`${this.baseUrl}/templates?${params}`, {
headers: this.headers
});
return await response.json();
}
async generatePrompt(templateId, inputText, options = null) {
const data = {
template_id: templateId,
input_text: inputText
};
if (options) data.options = options;
const response = await fetch(`${this.baseUrl}/generate`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify(data)
});
return await response.json();
}
}
// 使用示例
const api = new PromptMasterAPI();
// 登录
api.login('your_username', 'your_password').then(result => {
if (result.success) {
console.log('登录成功!');
// 获取模板列表
return api.getTemplates(1, 10);
}
}).then(templates => {
if (templates && templates.success) {
console.log(`找到 ${templates.data.pagination.total} 个模板`);
// 生成提示词
if (templates.data.templates.length > 0) {
const templateId = templates.data.templates[0].id;
return api.generatePrompt(templateId, '请帮我写一篇关于人工智能的文章');
}
}
}).then(result => {
if (result && result.success) {
console.log('生成的提示词:', result.data.generated_text);
}
});
```
---
## 🚨 **错误处理**
### 错误响应格式
```json
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "错误描述信息",
"details": "详细错误信息"
}
}
```
### 常见错误码
- `AUTH_REQUIRED`: 需要认证
- `INVALID_TOKEN`: 无效的访问令牌
- `TOKEN_EXPIRED`: 令牌已过期
- `PERMISSION_DENIED`: 权限不足
- `RESOURCE_NOT_FOUND`: 资源不存在
- `VALIDATION_ERROR`: 参数验证失败
- `RATE_LIMIT_EXCEEDED`: 请求频率超限
- `INTERNAL_ERROR`: 服务器内部错误
---
## 📊 **速率限制**
- **普通用户:** 100次/小时
- **高级用户:** 1000次/小时
- **企业用户:** 10000次/小时
超过限制时返回 `429 Too Many Requests` 状态码。
---
## 🔧 **开发建议**
### 1. 环境准备
- 确保现有服务正常运行在 `http://localhost:5002`
- 准备测试账号和API访问权限
- 安装必要的开发工具和SDK
### 2. 开发流程
1. **接口测试:** 使用Postman或curl测试所有接口
2. **SDK开发** 封装API调用为易用的SDK
3. **错误处理:** 实现完善的错误处理机制
4. **缓存策略:** 对模板列表等数据进行缓存
5. **监控日志:** 记录API调用日志和性能指标
### 3. 安全考虑
- 使用HTTPS传输
- 实现API Key轮换机制
- 添加请求签名验证
- 实现IP白名单限制
### 4. 性能优化
- 实现请求缓存
- 使用连接池
- 添加重试机制
- 实现异步处理
---
## 📞 **技术支持**
如有问题,请联系:
- **邮箱:** support@promptmaster.com
- **文档:** https://docs.promptmaster.com
- **GitHub** https://github.com/promptmaster/api

View File

@@ -0,0 +1,367 @@
# 应用首页模板选择界面现代化设计优化
## 🎯 **设计目标**
根据提供的设计规范文档,对应用首页的模板选择界面进行全面现代化优化,实现简洁现代、用户体验优先的设计风格。
## 📋 **设计规范遵循**
### 1. **网站类型和风格**
-**简洁现代**:主要使用留白,避免过度装饰,突出内容本身
-**用户体验优先**:确保用户能快速找到所需模板,操作路径清晰
-**视觉层次**:使用字体大小、颜色对比、间距区分内容优先级
### 2. **页面布局和结构**
-**顶部导航栏**Logo、主菜单、用户入口
-**搜索栏**:居中突出显示,支持关键词搜索和热门搜索
-**模板分类**:侧边栏布局,图标和描述
-**模板展示区**:网格布局,悬停效果,模态窗口
-**侧边栏**:筛选条件和排序功能
### 3. **响应式适配**
-**桌面端**:多列网格布局,侧边栏固定
-**平板端**:两列网格布局,侧边栏可折叠
-**移动端**:单列布局,汉堡菜单,底部弹出面板
## 🚀 **实现内容**
### 1. **页面结构重新设计**
#### 1.1 现代化页面容器
```html
<div class="modern-template-page">
<!-- 顶部导航栏 -->
<header class="page-header">
<div class="header-container">
<div class="logo-section">
<div class="logo">
<i class="fas fa-magic"></i>
<span>提示词大师</span>
</div>
<p class="brand-slogan">让AI更好地理解您的需求</p>
</div>
<div class="header-actions">
<a href="#" class="btn-expert-mode">
<i class="fas fa-brain"></i>
<span>专家模式</span>
</a>
</div>
</div>
</header>
```
#### 1.2 主要内容区域
```html
<main class="main-content">
<div class="content-container">
<!-- 搜索区域 -->
<section class="search-section">
<div class="search-container">
<div class="search-box">
<div class="search-input-wrapper">
<input type="text" class="search-input"
placeholder="搜索模板名称或描述,如:商业邮件、创意海报...">
<button type="button" class="search-btn">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="search-suggestions">
<div class="popular-keywords">
<span class="keyword-label">热门搜索:</span>
<a href="#" class="keyword-tag">商业邮件模板</a>
<a href="#" class="keyword-tag">创意海报模板</a>
</div>
</div>
</div>
</section>
```
### 2. **快速开始区域优化**
#### 2.1 现代化卡片设计
```html
<section class="quick-start-section">
<div class="section-header">
<div class="section-title">
<i class="fas fa-rocket"></i>
<h2>快速开始</h2>
</div>
<p class="section-subtitle">选择最常用的模板,快速生成提示词</p>
</div>
<div class="quick-templates-grid">
<div class="quick-template-card">
<div class="card-header">
<div class="template-icon">
<i class="fas fa-magic"></i>
</div>
<div class="template-badge">热门</div>
</div>
<div class="card-content">
<h3 class="template-name">通用优化</h3>
<p class="template-desc">通用提示词优化,适用于各种场景</p>
<div class="template-meta">
<span class="usage-count">1.2k 使用</span>
<span class="rating">⭐ 4.8</span>
</div>
</div>
</div>
</div>
</section>
```
### 3. **模板库区域侧边栏设计**
#### 3.1 侧边栏布局
```html
<section class="template-library-section">
<div class="library-layout">
<!-- 侧边栏 - 筛选和分类 -->
<aside class="sidebar">
<div class="sidebar-header">
<h3>模板分类</h3>
<span class="category-count">73 个分类</span>
</div>
<!-- 筛选条件 -->
<div class="filter-section">
<h4>筛选条件</h4>
<div class="filter-controls">
<div class="filter-group">
<label>使用场景</label>
<select class="filter-select">
<option value="all">全部行业</option>
</select>
</div>
</div>
</div>
<!-- 分类列表 -->
<div class="category-list">
<div class="category-item active">
<div class="category-icon">
<i class="fas fa-th-large"></i>
</div>
<div class="category-info">
<span class="category-name">全部模板</span>
<span class="category-desc">所有可用模板</span>
</div>
<div class="category-count-badge">281</div>
</div>
</div>
</aside>
<!-- 主内容区域 -->
<main class="main-content">
<div class="content-header">
<div class="content-title">
<h2>模板库</h2>
<div class="template-stats">
<span class="template-count">显示 281/281 个模板</span>
</div>
</div>
<div class="view-controls">
<button class="view-toggle active" data-view="grid">
<i class="fas fa-th-large"></i>
</button>
<button class="view-toggle" data-view="list">
<i class="fas fa-list"></i>
</button>
</div>
</div>
</main>
</div>
</section>
```
### 4. **输入和操作区域优化**
#### 4.1 现代化输入区域
```html
<section class="input-section">
<div class="section-header">
<div class="section-title">
<h2>输入需求</h2>
</div>
<p class="section-subtitle">描述越详细,生成的提示词效果越好</p>
</div>
<div class="input-container">
<textarea class="modern-textarea" rows="6"
placeholder="请详细描述您的需求,例如:我需要写一篇关于人工智能发展趋势的文章,要求内容专业、逻辑清晰,适合技术从业者阅读..."></textarea>
<div class="input-tips">
<i class="fas fa-lightbulb"></i>
<span>提示描述越具体AI生成的提示词越精准</span>
</div>
</div>
</section>
```
#### 4.2 现代化操作按钮
```html
<section class="action-section">
<div class="action-container">
<button type="submit" class="btn-generate">
<i class="fas fa-magic"></i>
<span>生成专业提示词</span>
</button>
</div>
</section>
```
### 5. **结果展示区域优化**
#### 5.1 现代化结果展示
```html
<section class="result-section">
<div class="result-container">
<div class="result-header">
<div class="result-title">
<i class="fas fa-check-circle"></i>
<h2>生成结果</h2>
</div>
<div class="result-actions">
<button class="btn-action btn-copy">
<i class="fas fa-copy"></i>
<span>复制提示词</span>
</button>
<button class="btn-action btn-favorite">
<i class="fas fa-heart"></i>
<span>收藏</span>
</button>
</div>
</div>
<div class="output-preview">
<div class="text-content">{{ prompt.generated_text }}</div>
</div>
</div>
</section>
```
## 🎨 **视觉设计特点**
### 1. **色彩系统**
- **主色调**:蓝色系 (#4a90e2) 用于按钮、图标、重要文字
- **中性色**:灰色和白色用于背景和辅助色
- **状态色**:成功绿、警告黄、危险红
### 2. **字体设计**
- **标题**:品牌标准字体,较大字号,粗体显示
- **正文**:易读的无衬线字体,适中字号,宽松行距
### 3. **图标和按钮**
- **图标**:线性图标风格,确保简洁一致
- **按钮**:统一圆角设计,主色调按钮用于主要操作
### 4. **阴影和层次**
```css
:root {
--shadow-light: 0 2px 8px rgba(0, 0, 0, 0.05);
--shadow-medium: 0 4px 16px rgba(0, 0, 0, 0.1);
--shadow-heavy: 0 8px 32px rgba(0, 0, 0, 0.15);
--border-radius: 12px;
--border-radius-small: 8px;
--transition: all 0.3s ease;
}
```
## 📱 **响应式设计**
### 1. **桌面端 (≥1200px)**
- 多列网格布局,充分利用屏幕宽度
- 侧边栏固定,模板展示区占据主要空间
- 完整的筛选和排序功能
### 2. **平板端 (768px-1199px)**
- 调整为两列网格布局
- 侧边栏可折叠
- 筛选条件简化,确保内容清晰可读
### 3. **移动端 (≤767px)**
- 单列布局
- 导航栏折叠为汉堡菜单
- 搜索栏置于页面顶部
- 侧边栏筛选功能改为底部弹出面板
- 模板展示区简化,只显示缩略图和标题
## 🔧 **交互设计**
### 1. **搜索功能**
- **自动完成**:输入时显示相关建议
- **热门搜索**:显示常用搜索关键词
- **加载动画**:搜索完成后显示简洁加载动画
### 2. **模板展示**
- **悬停效果**:鼠标悬停时缩略图放大,显示阴影效果
- **模态窗口**:点击弹出模态窗口,背景模糊,模态居中显示
### 3. **筛选功能**
- **动态刷新**:选择筛选条件后页面平滑刷新
- **加载动画**:刷新时显示简洁加载动画
### 4. **视图切换**
- **网格/列表视图**:支持两种展示模式切换
- **平滑过渡**:视图切换时有平滑的过渡动画
## 🎯 **用户体验优化**
### 1. **视觉引导**
- **清晰的层次结构**:使用颜色、大小、间距区分重要程度
- **操作提示**:提供清晰的操作指引和反馈
- **状态指示**:明确显示当前选中状态和操作结果
### 2. **交互反馈**
- **即时响应**:所有操作都有即时的视觉反馈
- **动画效果**使用CSS过渡动画提升交互体验
- **错误处理**:友好的错误提示和恢复建议
### 3. **性能优化**
- **懒加载**:对大量模板进行懒加载
- **防抖处理**:搜索输入使用防抖优化性能
- **缓存机制**:缓存用户操作状态和偏好
## 📊 **设计效果**
### 1. **视觉一致性**
- ✅ 统一的色彩系统和字体规范
- ✅ 一致的组件设计和交互模式
- ✅ 品牌元素的统一应用
### 2. **用户体验**
- ✅ 清晰的信息架构和导航路径
- ✅ 直观的操作流程和反馈机制
- ✅ 响应式设计适配各种设备
### 3. **功能完整性**
- ✅ 完整的搜索和筛选功能
- ✅ 灵活的模板展示和选择
- ✅ 便捷的快速开始功能
## 🚀 **技术实现**
### 1. **CSS架构**
- 使用CSS变量定义设计系统
- 模块化的样式组织
- 响应式设计的最佳实践
### 2. **JavaScript交互**
- 事件驱动的交互处理
- 防抖和节流优化性能
- 状态管理和数据绑定
### 3. **HTML语义化**
- 语义化的HTML结构
- 无障碍访问支持
- SEO友好的标记
## 🎯 **总结**
通过遵循设计规范文档,我们成功实现了:
1. **简洁现代的设计风格**:使用留白、清晰的层次结构和现代化的视觉元素
2. **用户体验优先的布局**:侧边栏设计、突出搜索功能、清晰的导航路径
3. **响应式适配**:完美适配桌面、平板、移动端各种设备
4. **品牌视觉一致性**:统一的色彩、字体、图标和交互模式
5. **功能完整性**:保留所有原有功能,同时优化用户体验
这次现代化设计优化显著提升了用户界面的美观性和可用性,为用户提供了更加专业、高效的模板选择体验。

228
TEMPLATE_DISPLAY_LIMIT.md Normal file
View File

@@ -0,0 +1,228 @@
# 模板显示限制功能实现说明
## 🎯 **功能概述**
为了解决"全部"分类下模板过多导致页面过长的问题实现了模板显示限制功能。默认只显示前8个模板用户可以通过"显示更多"按钮查看全部模板。
## 📋 **实现内容**
### 1. **功能特性**
#### 1.1 默认显示限制
- **显示数量**默认只显示前8个模板
- **隐藏逻辑**超过8个的模板自动隐藏
- **智能判断**只在模板数量超过8个时显示"更多"按钮
#### 1.2 显示更多功能
- **动态按钮**:显示剩余模板数量
- **切换状态**:支持展开和收起
- **动画效果**:按钮图标旋转动画
#### 1.3 筛选联动
- **筛选同步**:筛选条件变化时重新计算显示逻辑
- **状态保持**:筛选后保持展开/收起状态
- **重置功能**:重置筛选时恢复默认状态
### 2. **技术实现**
#### 2.1 HTML结构
```html
<div class="template-grid" id="templateGrid">
{% for template in templates %}
<div class="template-card {% if loop.index > 8 %}template-hidden{% endif %}"
data-category="{{ template.category }}"
data-industry="{{ template.industry }}"
data-profession="{{ template.profession }}"
data-subcategory="{{ template.sub_category }}">
<!-- 模板内容 -->
</div>
{% endfor %}
</div>
<!-- 显示更多按钮 -->
<div class="show-more-section" id="showMoreSection" style="display: none;">
<button class="btn btn-outline-primary show-more-btn" id="showMoreBtn">
<i class="fas fa-chevron-down"></i>
显示更多模板
</button>
</div>
```
#### 2.2 CSS样式
```css
/* 模板显示控制 */
.template-hidden {
display: none !important;
}
.show-more-section {
text-align: center;
margin-top: 2rem;
padding: 1rem;
}
.show-more-btn {
padding: 0.75rem 2rem;
font-size: 1rem;
font-weight: 600;
border-radius: 8px;
transition: all 0.3s ease;
background: white;
border: 2px solid var(--primary-color);
color: var(--primary-color);
}
.show-more-btn:hover {
background: var(--primary-color);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3);
}
.show-more-btn i {
margin-right: 0.5rem;
transition: transform 0.3s ease;
}
.show-more-btn.expanded i {
transform: rotate(180deg);
}
```
#### 2.3 JavaScript逻辑
```javascript
// 更新显示更多按钮
function updateShowMoreButton(visibleCount) {
const showMoreSection = document.getElementById('showMoreSection');
const showMoreBtn = document.getElementById('showMoreBtn');
if (visibleCount > 8) {
showMoreSection.style.display = 'block';
showMoreBtn.innerHTML = `
<i class="fas fa-chevron-down"></i>
显示更多模板 (${visibleCount - 8}个)
`;
} else {
showMoreSection.style.display = 'none';
}
}
// 切换显示更多
function toggleShowMore() {
const showMoreBtn = document.getElementById('showMoreBtn');
const hiddenCards = document.querySelectorAll('.template-card.template-hidden:not(.hidden)');
if (hiddenCards.length > 0) {
// 显示所有隐藏的模板
hiddenCards.forEach(card => {
card.classList.remove('template-hidden');
});
showMoreBtn.innerHTML = `
<i class="fas fa-chevron-up"></i>
收起模板
`;
showMoreBtn.classList.add('expanded');
} else {
// 隐藏多余的模板
const visibleCards = document.querySelectorAll('.template-card:not(.hidden)');
visibleCards.forEach((card, index) => {
if (index >= 8) {
card.classList.add('template-hidden');
}
});
showMoreBtn.innerHTML = `
<i class="fas fa-chevron-down"></i>
显示更多模板
`;
showMoreBtn.classList.remove('expanded');
}
}
```
### 3. **用户体验**
#### 3.1 默认状态
- **页面简洁**默认只显示8个模板页面不会过长
- **快速浏览**:用户可以快速浏览主要模板
- **清晰提示**:显示剩余模板数量
#### 3.2 展开状态
- **完整查看**:点击后可以查看所有模板
- **状态指示**:按钮变为"收起模板"
- **图标变化**:箭头向上,表示可以收起
#### 3.3 交互反馈
- **悬停效果**:按钮有悬停动画效果
- **图标动画**:展开/收起时图标旋转
- **即时响应**:点击后立即显示/隐藏
### 4. **筛选联动**
#### 4.1 筛选变化
- **重新计算**:筛选条件变化时重新计算显示逻辑
- **状态保持**:保持当前的展开/收起状态
- **数量更新**:动态更新剩余模板数量
#### 4.2 重置功能
- **状态重置**:重置筛选时恢复默认收起状态
- **显示重置**重新应用8个模板的限制
- **按钮隐藏**如果模板数量≤8隐藏按钮
### 5. **性能优化**
#### 5.1 渲染优化
- **CSS隐藏**使用CSS隐藏而非JavaScript移除
- **DOM保持**保持DOM结构避免重新渲染
- **事件保持**:保持事件监听器,避免重复绑定
#### 5.2 交互优化
- **防抖处理**:避免频繁的状态切换
- **状态缓存**:缓存当前显示状态
- **平滑动画**使用CSS过渡动画
### 6. **响应式适配**
#### 6.1 移动端
- **触摸友好**:按钮大小适合触摸操作
- **间距优化**:移动端减少按钮间距
- **文字适配**:移动端简化按钮文字
#### 6.2 桌面端
- **悬停效果**:桌面端显示悬停效果
- **键盘支持**支持键盘Tab导航
- **焦点状态**:清晰的焦点指示
### 7. **使用场景**
#### 7.1 全部分类
- **默认限制**:选择"全部"分类时默认显示8个
- **智能显示**:根据筛选结果动态显示按钮
- **用户控制**:用户可以选择是否查看全部
#### 7.2 其他分类
- **无限制**:其他分类下不限制显示数量
- **自动隐藏**如果模板数量≤8自动隐藏按钮
- **一致体验**:保持统一的交互体验
### 8. **后续优化**
#### 8.1 功能增强
- **自定义数量**:允许用户自定义显示数量
- **记住状态**:记住用户的展开/收起偏好
- **分页显示**:支持分页显示大量模板
#### 8.2 性能优化
- **懒加载**:对隐藏的模板进行懒加载
- **虚拟滚动**:大量模板时使用虚拟滚动
- **缓存机制**:缓存模板数据和状态
#### 8.3 用户体验
- **动画优化**:更流畅的展开/收起动画
- **状态提示**:更清晰的状态提示信息
- **快捷键**:支持键盘快捷键操作
## 🎯 **总结**
模板显示限制功能有效解决了页面过长的问题,通过智能的显示控制和用户友好的交互设计,让用户可以在简洁的界面中快速浏览模板,同时保留了查看全部模板的灵活性。这个功能为后续的性能优化和用户体验提升奠定了良好的基础。

View File

@@ -0,0 +1,315 @@
# 界面交互优化实现说明
## 🎨 **优化概述**
界面交互优化旨在重新设计主应用布局,优化视觉层次,增强交互反馈,让用户能够更高效地选择和使用模板。
## 📋 **实现内容**
### 1. **布局重新设计**
#### 1.1 侧边栏布局
- **侧边栏设计**:将分类选择改为左侧固定侧边栏
- **主内容区域**:右侧重点展示模板卡片
- **响应式适配**:移动端自动切换为垂直布局
#### 1.2 布局结构
```html
<!-- 主内容区域 - 采用侧边栏布局 -->
<div class="main-content-layout">
<!-- 侧边栏 - 分类选择 -->
<div class="sidebar">
<div class="sidebar-header">
<h3>场景分类</h3>
<div class="category-count">73 个分类</div>
</div>
<!-- 筛选控件 -->
<div class="filter-section">
<h4>筛选条件</h4>
<div class="filter-controls">
<!-- 筛选下拉框 -->
</div>
</div>
<!-- 分类列表 -->
<div class="category-list">
<!-- 分类项目 -->
</div>
</div>
<!-- 主区域 - 模板展示 -->
<div class="main-content">
<div class="content-header">
<div class="content-title">
<h2>模板库</h2>
<div class="template-stats">
<span class="template-count">显示 281/281 个模板</span>
</div>
</div>
<div class="view-controls">
<!-- 视图切换按钮 -->
</div>
</div>
<div class="template-grid" id="templateGrid">
<!-- 模板卡片 -->
</div>
</div>
</div>
```
### 2. **视觉层次优化**
#### 2.1 侧边栏设计
- **固定定位**:侧边栏使用 `position: sticky` 固定
- **清晰分组**:筛选条件和分类列表分组显示
- **视觉层次**:使用不同的颜色和大小区分重要程度
#### 2.2 分类项目设计
```css
.category-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
margin-bottom: 0.5rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid transparent;
}
.category-item:hover {
background: var(--hover-color);
border-color: var(--primary-color);
}
.category-item.active {
background: linear-gradient(135deg, var(--primary-color) 0%, #4a90e2 100%);
color: white;
border-color: var(--primary-color);
}
```
#### 2.3 模板卡片优化
- **信息层次**:图标、标题、描述、统计信息清晰分层
- **标签系统**:使用彩色标签区分行业、职业、领域
- **统计信息**:显示使用次数、评分、预计时间
### 3. **交互反馈增强**
#### 3.1 悬停预览效果
- **操作按钮**:悬停时显示预览、收藏、删除按钮
- **卡片动画**:悬停时卡片上浮和阴影效果
- **选择状态**:选中时显示蓝色高亮和选择指示器
#### 3.2 模板预览弹窗
```javascript
function showTemplatePreview(templateId) {
const modal = document.createElement('div');
modal.className = 'template-preview-modal';
modal.innerHTML = `
<div class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
<h3>模板预览</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<!-- 模板详情内容 -->
</div>
</div>
</div>
`;
document.body.appendChild(modal);
}
```
#### 3.3 操作成功反馈
- **Toast提示**:操作成功后显示临时提示消息
- **动画效果**:删除时卡片缩放消失动画
- **状态更新**:实时更新模板数量统计
### 4. **功能特性**
#### 4.1 视图切换
- **网格视图**:默认的卡片网格布局
- **列表视图**:紧凑的列表布局
- **切换按钮**:右上角的视图切换控件
#### 4.2 模板操作
- **预览功能**:点击眼睛图标查看模板详情
- **收藏功能**:点击心形图标收藏/取消收藏
- **删除功能**:点击垃圾桶图标删除模板
#### 4.3 筛选优化
- **侧边栏筛选**:筛选条件移到侧边栏
- **实时更新**:筛选条件变化时实时更新结果
- **统计显示**:显示当前筛选结果数量
### 5. **响应式设计**
#### 5.1 桌面端
- **侧边栏固定**左侧300px固定宽度
- **主内容区域**:右侧自适应宽度
- **网格布局**:模板卡片采用响应式网格
#### 5.2 移动端
- **垂直布局**:侧边栏和主内容垂直排列
- **紧凑设计**:减少间距,优化触摸体验
- **操作按钮**:移动端始终显示操作按钮
### 6. **CSS样式特点**
#### 6.1 现代化设计
```css
/* 主内容布局 - 侧边栏设计 */
.main-content-layout {
display: grid;
grid-template-columns: 300px 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
/* 侧边栏样式 */
.sidebar {
background: white;
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
height: fit-content;
position: sticky;
top: 2rem;
}
```
#### 6.2 动画效果
```css
/* 动画效果 */
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
```
### 7. **JavaScript功能**
#### 7.1 事件处理
```javascript
// 侧边栏分类选择事件
const categoryItems = document.querySelectorAll('.category-item');
categoryItems.forEach(item => {
item.addEventListener('click', () => {
categoryItems.forEach(i => i.classList.remove('active'));
item.classList.add('active');
filterTemplates();
});
});
// 视图切换功能
const viewToggles = document.querySelectorAll('.view-toggle');
viewToggles.forEach(toggle => {
toggle.addEventListener('click', () => {
viewToggles.forEach(t => t.classList.remove('active'));
toggle.classList.add('active');
const viewType = toggle.dataset.view;
if (viewType === 'list') {
templateGrid.classList.add('list-view');
} else {
templateGrid.classList.remove('list-view');
}
});
});
```
#### 7.2 模板操作
```javascript
// 模板操作按钮事件
document.addEventListener('click', function(e) {
if (e.target.closest('.btn-preview')) {
const templateId = e.target.closest('.btn-preview').dataset.templateId;
showTemplatePreview(templateId);
}
if (e.target.closest('.btn-favorite')) {
const templateId = e.target.closest('.btn-favorite').dataset.templateId;
toggleFavorite(templateId);
}
if (e.target.closest('.btn-delete')) {
const templateId = e.target.closest('.btn-delete').dataset.templateId;
deleteTemplate(templateId);
}
});
```
### 8. **用户体验提升**
#### 8.1 操作便捷性
- **一键操作**:预览、收藏、删除一键完成
- **视觉反馈**:所有操作都有明确的视觉反馈
- **状态保持**:操作状态在页面刷新后保持
#### 8.2 信息展示
- **层次清晰**:信息按照重要程度分层展示
- **标签系统**:使用颜色编码的标签系统
- **统计信息**:显示使用统计和评分信息
#### 8.3 交互流畅性
- **平滑动画**:所有交互都有平滑的动画效果
- **即时反馈**:操作结果立即显示
- **错误处理**:友好的错误提示和处理
### 9. **性能优化**
#### 9.1 渲染优化
- **CSS Grid**使用现代CSS Grid布局
- **Flexbox**使用Flexbox进行元素对齐
- **硬件加速**使用transform进行动画
#### 9.2 交互优化
- **事件委托**:使用事件委托减少事件监听器
- **防抖处理**:搜索输入使用防抖处理
- **懒加载**:弹窗内容按需加载
### 10. **后续优化方向**
#### 10.1 功能增强
- **拖拽排序**:支持模板卡片拖拽排序
- **批量操作**:支持批量选择和管理
- **高级筛选**:支持多条件组合筛选
#### 10.2 个性化
- **主题切换**:支持深色/浅色主题切换
- **布局定制**:允许用户自定义布局
- **快捷键**:支持键盘快捷键操作
#### 10.3 智能化
- **智能推荐**:基于用户行为推荐模板
- **自动分类**:自动对模板进行分类
- **使用分析**:提供详细的使用分析报告
## 🎯 **总结**
界面交互优化通过重新设计布局、优化视觉层次、增强交互反馈,显著提升了用户的使用体验。新的侧边栏布局让分类选择更加便捷,优化的模板卡片提供了更丰富的信息展示,增强的交互功能让操作更加流畅自然。这些改进为后续的功能扩展和用户体验提升奠定了坚实的基础。

View File

@@ -0,0 +1,306 @@
# 端口管理指南
## 📋 **概述**
本指南介绍如何管理Flask应用的端口占用问题确保服务稳定运行。
## 🛠️ **工具介绍**
### 1. 端口管理脚本 (`scripts/port_manager.sh`)
**功能:**
- 检查端口占用情况
- 清理端口占用
- 启动/停止/重启应用
- 查看应用状态
- 监控模式
**使用方法:**
```bash
# 检查端口占用
./scripts/port_manager.sh check
# 清理端口占用
./scripts/port_manager.sh clean
# 启动应用
./scripts/port_manager.sh start
# 停止应用
./scripts/port_manager.sh stop
# 重启应用
./scripts/port_manager.sh restart
# 查看状态
./scripts/port_manager.sh status
# 启动监控
./scripts/port_manager.sh monitor
# 显示帮助
./scripts/port_manager.sh help
```
### 2. 端口监控脚本 (`scripts/port_monitor.py`)
**功能:**
- 实时监控端口状态
- 自动检测服务异常
- 自动重启服务
- 生成监控报告
- 系统资源监控
**使用方法:**
```bash
# 查看当前状态
python scripts/port_monitor.py status
# 生成详细报告
python scripts/port_monitor.py report
# 重启服务
python scripts/port_monitor.py restart
# 启动监控模式
python scripts/port_monitor.py monitor
# 查看配置
python scripts/port_monitor.py config
```
### 3. Systemd服务配置 (`scripts/flask-app.service`)
**功能:**
- 系统级服务管理
- 自动启动/停止
- 故障自动重启
- 日志管理
## 🔧 **配置说明**
### 端口管理脚本配置
脚本中的主要配置项:
```bash
PORT=5002 # 应用端口
APP_NAME="flask_prompt_master" # 应用名称
PID_FILE="logs/gunicorn.pid" # PID文件路径
LOG_DIR="logs" # 日志目录
```
### 监控脚本配置
配置文件:`logs/monitor_config.json`
```json
{
"check_interval": 30, // 检查间隔(秒)
"max_restart_attempts": 3, // 最大重启尝试次数
"alert_threshold": 2, // 告警阈值
"port_timeout": 5, // 端口检查超时时间
"enable_alerts": true, // 启用告警
"auto_restart": true // 自动重启
}
```
## 🚀 **部署步骤**
### 1. 安装systemd服务可选
```bash
# 复制服务文件
sudo cp scripts/flask-app.service /etc/systemd/system/
# 重新加载systemd
sudo systemctl daemon-reload
# 启用服务
sudo systemctl enable flask-app
# 启动服务
sudo systemctl start flask-app
# 查看状态
sudo systemctl status flask-app
```
### 2. 设置定时任务
```bash
# 编辑crontab
crontab -e
# 添加定时检查任务每5分钟检查一次
*/5 * * * * /home/renjianbo/aitsc/scripts/port_manager.sh status > /dev/null 2>&1
```
### 3. 配置日志轮转
创建日志轮转配置:`/etc/logrotate.d/flask-app`
```
/home/renjianbo/aitsc/logs/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 644 renjianbo renjianbo
postrotate
systemctl reload flask-app > /dev/null 2>&1 || true
endscript
}
```
## 📊 **监控和告警**
### 1. 监控指标
- **端口占用状态**检查5002端口是否被正确占用
- **Gunicorn进程状态**:检查主进程和工作进程
- **服务响应状态**检查HTTP服务是否正常响应
- **系统资源**CPU、内存、磁盘使用率
### 2. 告警机制
- **自动告警**:检测到异常时自动记录告警日志
- **自动重启**:服务异常时自动尝试重启
- **重启限制**:防止频繁重启,设置重启间隔和次数限制
### 3. 日志文件
- `logs/port_monitor.log`:监控脚本日志
- `logs/port_alerts.log`:告警日志
- `logs/gunicorn_error.log`Gunicorn错误日志
- `logs/gunicorn_access.log`Gunicorn访问日志
## 🔍 **故障排查**
### 1. 端口被占用
**症状:** 启动时提示 "Address already in use"
**解决方案:**
```bash
# 检查端口占用
./scripts/port_manager.sh check
# 清理端口占用
./scripts/port_manager.sh clean
# 重新启动
./scripts/port_manager.sh start
```
### 2. 服务无响应
**症状:** 访问网站显示连接被拒绝
**解决方案:**
```bash
# 检查服务状态
./scripts/port_manager.sh status
# 重启服务
./scripts/port_manager.sh restart
# 查看错误日志
tail -f logs/gunicorn_error.log
```
### 3. 进程异常退出
**症状:** PID文件存在但进程不存在
**解决方案:**
```bash
# 清理所有相关进程
pkill -f gunicorn
# 重新启动
./scripts/port_manager.sh start
```
## 📈 **性能优化**
### 1. 监控配置优化
根据服务器性能调整监控参数:
```json
{
"check_interval": 60, // 降低检查频率
"port_timeout": 3, // 减少超时时间
"max_restart_attempts": 5 // 增加重启尝试次数
}
```
### 2. 日志管理
定期清理日志文件:
```bash
# 清理30天前的日志
find logs/ -name "*.log" -mtime +30 -delete
# 压缩旧日志
find logs/ -name "*.log" -mtime +7 -exec gzip {} \;
```
### 3. 系统资源监控
设置资源告警阈值:
- CPU使用率 > 80%
- 内存使用率 > 90%
- 磁盘使用率 > 85%
## 🔒 **安全考虑**
### 1. 权限管理
确保脚本和日志文件权限正确:
```bash
# 设置脚本权限
chmod +x scripts/port_manager.sh
chmod +x scripts/port_monitor.py
# 设置日志目录权限
chmod 755 logs/
chmod 644 logs/*.log
```
### 2. 网络安全
- 限制端口访问只允许必要的IP访问5002端口
- 防火墙配置配置iptables规则
- SSL/TLS使用HTTPS协议
### 3. 监控安全
- 监控脚本权限:只允许特定用户运行
- 日志安全:定期备份和加密敏感日志
- 告警安全:避免在告警中暴露敏感信息
## 📞 **技术支持**
### 1. 常见问题
**Q: 为什么端口会被其他进程占用?**
A: 可能是之前的Gunicorn进程未完全清理或者有其他应用使用了相同端口。
**Q: 如何防止服务意外停止?**
A: 使用systemd服务配置自动重启或启用监控脚本的自动重启功能。
**Q: 监控脚本消耗太多资源怎么办?**
A: 调整检查间隔,或使用更轻量级的监控方式。
### 2. 联系支持
- 查看日志文件获取详细错误信息
- 使用状态检查命令诊断问题
- 参考故障排查章节的解决方案
## 📝 **更新日志**
- **v1.0** - 初始版本,基础端口管理功能
- **v1.1** - 添加监控脚本和自动重启功能
- **v1.2** - 添加systemd服务配置和日志轮转
- **v1.3** - 优化监控性能和告警机制

View File

@@ -0,0 +1,103 @@
好的我来修改端口监控脚本使其使用conda环境中的Python。
现在让我们测试修改后的端口监控脚本:
```bash
eval "$(/home/renjianbo/miniconda3/bin/conda shell.bash hook)" && conda activate myenv && python scripts/port_monitor.py status
```
让我检查并修复Python脚本的语法错误
Read file: scripts/port_monitor.py
我发现了语法错误,让我修复它:
现在让我们再次测试端口监控脚本:
```bash
eval "$(/home/renjianbo/miniconda3/bin/conda shell.bash hook)" && conda activate myenv && python scripts/port_monitor.py status
```
太好了!现在让我们测试端口管理脚本的其他功能:
```bash
./scripts/port_manager.sh check
```
## ✅ **端口管理系统已成功部署!**
### 🎯 **系统功能总结**
我已经为您创建了一套完整的端口管理系统,包含以下组件:
#### **1. 端口管理脚本** (`scripts/port_manager.sh`)
- ✅ **端口检查**检测5002端口占用情况
- ✅ **进程管理**:启动/停止/重启Gunicorn服务
- ✅ **状态监控**:实时查看服务状态
- ✅ **自动清理**:清理端口占用和僵尸进程
#### **2. 端口监控脚本** (`scripts/port_monitor.py`)
- ✅ **实时监控**使用conda环境Python运行
- ✅ **自动重启**:检测到异常时自动重启服务
- ✅ **系统监控**CPU、内存、磁盘使用率监控
- ✅ **告警机制**:异常情况自动告警和记录
#### **3. Systemd服务配置** (`scripts/flask-app.service`)
- ✅ **系统级管理**:支持开机自启动
- ✅ **自动重启**:服务异常时自动重启
- ✅ **日志管理**:集成系统日志
#### **4. 详细文档** (`docs/port_management_guide.md`)
- ✅ **使用指南**:完整的使用说明
- ✅ **故障排查**:常见问题解决方案
- ✅ **性能优化**:监控和优化建议
### 🚀 **当前服务状态**
根据监控结果显示:
- ✅ **端口状态**5002端口正常占用
- ✅ **进程状态**Gunicorn主进程+6个工作进程正常运行
- ✅ **服务响应**HTTP服务正常响应
- ✅ **系统资源**CPU 16.1%、内存 45.85%、磁盘 22.0%(健康状态)
### <20><> **使用方法**
#### **快速检查状态:**
```bash
# 使用端口管理脚本
./scripts/port_manager.sh status
# 使用监控脚本(推荐)
eval "$(/home/renjianbo/miniconda3/bin/conda shell.bash hook)" && conda activate myenv && python scripts/port_monitor.py status
```
#### **服务管理:**
```bash
# 重启服务
./scripts/port_manager.sh restart
# 启动监控模式
eval "$(/home/renjianbo/miniconda3/bin/conda shell.bash hook)" && conda activate myenv && python scripts/port_monitor.py monitor
```
#### **故障处理:**
```bash
# 清理端口占用
./scripts/port_manager.sh clean
# 重新启动
./scripts/port_manager.sh start
```
### 🔒 **安全保障**
1. **端口冲突防护**:自动检测和清理端口占用
2. **进程管理**:完善的进程启动和停止机制
3. **自动恢复**:服务异常时自动重启
4. **监控告警**:实时监控和异常告警
5. **日志记录**:详细的操作和错误日志
### <20><> **监控指标**
- **端口占用状态**确保5002端口被正确占用
- **Gunicorn进程状态**:主进程和工作进程健康检查
- **服务响应状态**HTTP服务可用性检查
- **系统资源监控**CPU、内存、磁盘使用率
**现在您的Flask应用已经具备了完善的端口管理机制可以有效防止端口冲突和服务异常** 🎉

1
logs/gunicorn.pid Normal file
View File

@@ -0,0 +1 @@
2147

View File

@@ -94,3 +94,46 @@
123.139.40.144 - - [29/Aug/2025:00:42:49 +0800] "GET / HTTP/1.1" 500 265 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 508571
123.139.40.144 - - [29/Aug/2025:00:44:27 +0800] "GET / HTTP/1.1" 200 51246 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 279239
123.139.40.144 - - [29/Aug/2025:00:55:57 +0800] "GET / HTTP/1.1" 500 265 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 458593
127.0.0.1 - - [07/Sep/2025:22:32:13 +0800] "GET / HTTP/1.1" 200 826733 "-" "curl/7.29.0" 561305
123.139.41.108 - - [07/Sep/2025:22:32:23 +0800] "GET /admin/ HTTP/1.1" 200 38183 "http://101.43.95.130:5002/admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 424888
123.139.41.108 - - [07/Sep/2025:22:32:26 +0800] "GET /admin/admin_user/ HTTP/1.1" 200 36361 "http://101.43.95.130:5002/admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 613362
123.139.41.108 - - [07/Sep/2025:22:32:26 +0800] "GET /admin/static/vendor/select2/select2.css?v=4.2.1 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 6123
123.139.41.108 - - [07/Sep/2025:22:32:26 +0800] "GET /admin/static/vendor/select2/select2-bootstrap4.css?v=1.4.6 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 2018
123.139.41.108 - - [07/Sep/2025:22:32:26 +0800] "GET /admin/static/vendor/bootstrap-daterangepicker/daterangepicker-bs4.css?v=1.3.22 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 5738
123.139.41.108 - - [07/Sep/2025:22:32:27 +0800] "GET /admin/admin_prompt/ HTTP/1.1" 200 95071 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 325709
123.139.41.108 - - [07/Sep/2025:22:32:27 +0800] "GET /admin/static/vendor/select2/select2.css?v=4.2.1 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1541
123.139.41.108 - - [07/Sep/2025:22:32:27 +0800] "GET /admin/static/vendor/select2/select2-bootstrap4.css?v=1.4.6 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1684
123.139.41.108 - - [07/Sep/2025:22:32:27 +0800] "GET /admin/static/vendor/bootstrap-daterangepicker/daterangepicker-bs4.css?v=1.3.22 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 2021
123.139.41.108 - - [07/Sep/2025:22:32:28 +0800] "GET /admin/analytics_admin/ HTTP/1.1" 200 37312 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 411337
123.139.41.108 - - [07/Sep/2025:22:32:30 +0800] "GET /admin/admin_template/ HTTP/1.1" 200 76118 "http://101.43.95.130:5002/admin/analytics_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 662052
123.139.41.108 - - [07/Sep/2025:22:32:30 +0800] "GET /admin/static/vendor/select2/select2.css?v=4.2.1 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1158
123.139.41.108 - - [07/Sep/2025:22:32:30 +0800] "GET /admin/static/vendor/bootstrap-daterangepicker/daterangepicker-bs4.css?v=1.3.22 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1056
123.139.41.108 - - [07/Sep/2025:22:32:30 +0800] "GET /admin/static/vendor/select2/select2-bootstrap4.css?v=1.4.6 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1201
123.139.41.108 - - [07/Sep/2025:22:32:31 +0800] "GET /admin/batch_admin/ HTTP/1.1" 200 34207 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 123559
123.139.41.108 - - [07/Sep/2025:22:32:33 +0800] "GET /admin/monitor_admin/ HTTP/1.1" 200 38880 "http://101.43.95.130:5002/admin/batch_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1161906
123.139.41.108 - - [07/Sep/2025:22:32:34 +0800] "GET /admin/report_admin/ HTTP/1.1" 200 34773 "http://101.43.95.130:5002/admin/monitor_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 142601
123.139.41.108 - - [07/Sep/2025:22:32:34 +0800] "GET /admin/monitor_admin/api/system-status HTTP/1.1" 200 315 "http://101.43.95.130:5002/admin/monitor_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1106537
123.139.41.108 - - [07/Sep/2025:22:32:35 +0800] "GET /admin/backup_admin/ HTTP/1.1" 200 38122 "http://101.43.95.130:5002/admin/report_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 137440
123.139.41.108 - - [07/Sep/2025:22:32:36 +0800] "GET /admin/api_admin/ HTTP/1.1" 200 40362 "http://101.43.95.130:5002/admin/backup_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 751333
123.139.41.108 - - [07/Sep/2025:22:32:37 +0800] "GET /admin/api_admin/api/calls HTTP/1.1" 200 4207 "http://101.43.95.130:5002/admin/api_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 159215
123.139.41.108 - - [07/Sep/2025:22:32:37 +0800] "GET /admin/admin_system/ HTTP/1.1" 200 31552 "http://101.43.95.130:5002/admin/api_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 319715
123.139.41.108 - - [07/Sep/2025:22:32:40 +0800] "GET /admin/analytics_admin/ HTTP/1.1" 200 37312 "http://101.43.95.130:5002/admin/admin_system/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 418525
123.139.41.108 - - [07/Sep/2025:22:32:41 +0800] "GET /admin/admin_template/ HTTP/1.1" 200 76118 "http://101.43.95.130:5002/admin/analytics_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 320917
123.139.41.108 - - [07/Sep/2025:22:32:41 +0800] "GET /admin/static/vendor/select2/select2.css?v=4.2.1 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1078
123.139.41.108 - - [07/Sep/2025:22:32:41 +0800] "GET /admin/static/vendor/select2/select2-bootstrap4.css?v=1.4.6 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1077
123.139.41.108 - - [07/Sep/2025:22:32:41 +0800] "GET /admin/static/vendor/bootstrap-daterangepicker/daterangepicker-bs4.css?v=1.3.22 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1680
123.139.41.108 - - [07/Sep/2025:22:32:42 +0800] "GET /admin/admin_prompt/ HTTP/1.1" 200 95071 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 355769
123.139.41.108 - - [07/Sep/2025:22:32:42 +0800] "GET /admin/static/vendor/select2/select2.css?v=4.2.1 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1612
123.139.41.108 - - [07/Sep/2025:22:32:42 +0800] "GET /admin/static/vendor/select2/select2-bootstrap4.css?v=1.4.6 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1064
123.139.41.108 - - [07/Sep/2025:22:32:42 +0800] "GET /admin/static/vendor/bootstrap-daterangepicker/daterangepicker-bs4.css?v=1.3.22 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 914
123.139.41.108 - - [07/Sep/2025:22:32:43 +0800] "GET /admin/admin_user/ HTTP/1.1" 200 36361 "http://101.43.95.130:5002/admin/admin_prompt/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 173451
123.139.41.108 - - [07/Sep/2025:22:32:43 +0800] "GET /admin/static/vendor/select2/select2.css?v=4.2.1 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1135
123.139.41.108 - - [07/Sep/2025:22:32:43 +0800] "GET /admin/static/vendor/select2/select2-bootstrap4.css?v=1.4.6 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1294
123.139.41.108 - - [07/Sep/2025:22:32:43 +0800] "GET /admin/static/vendor/bootstrap-daterangepicker/daterangepicker-bs4.css?v=1.3.22 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1292
123.139.41.108 - - [07/Sep/2025:22:32:44 +0800] "GET /admin/ HTTP/1.1" 200 38183 "http://101.43.95.130:5002/admin/admin_user/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 139884
123.139.41.108 - - [07/Sep/2025:22:32:45 +0800] "GET /admin/admin_template/ HTTP/1.1" 200 76118 "http://101.43.95.130:5002/admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 229710
123.139.41.108 - - [07/Sep/2025:22:32:45 +0800] "GET /admin/static/vendor/select2/select2.css?v=4.2.1 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1095
123.139.41.108 - - [07/Sep/2025:22:32:45 +0800] "GET /admin/static/vendor/select2/select2-bootstrap4.css?v=1.4.6 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 1016
123.139.41.108 - - [07/Sep/2025:22:32:45 +0800] "GET /admin/static/vendor/bootstrap-daterangepicker/daterangepicker-bs4.css?v=1.3.22 HTTP/1.1" 304 0 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 847
123.139.41.108 - - [07/Sep/2025:22:32:47 +0800] "GET /admin/batch_admin/ HTTP/1.1" 200 34207 "http://101.43.95.130:5002/admin/admin_template/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 119855
123.139.41.108 - - [07/Sep/2025:22:32:48 +0800] "GET /admin/report_admin/ HTTP/1.1" 200 34773 "http://101.43.95.130:5002/admin/batch_admin/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0" 130296

View File

@@ -1466,3 +1466,30 @@ SystemExit: 1
[2025-08-29 01:01:36 +0800] [22763] [ERROR] Worker (pid:26600) exited with code 120
[2025-08-29 01:01:36 +0800] [22763] [ERROR] Worker (pid:26600) exited with code 120.
[2025-08-29 01:01:36 +0800] [22763] [INFO] Shutting down: Master
[2025-09-07 22:31:51 +0800] [2147] [INFO] Gunicorn服务器重载中...
[2025-09-07 22:31:51 +0800] [2147] [INFO] 工作进程 [booting] 即将启动
[2025-09-07 22:31:51 +0800] [2147] [INFO] 工作进程 [booting] 即将启动
[2025-09-07 22:31:51 +0800] [29548] [INFO] Booting worker with pid: 29548
[2025-09-07 22:31:51 +0800] [29548] [INFO] 工作进程 29548 已启动
[2025-09-07 22:31:51 +0800] [29548] [INFO] 工作进程 29548 初始化完成
[2025-09-07 22:31:51 +0800] [2147] [INFO] 工作进程 [booting] 即将启动
[2025-09-07 22:31:51 +0800] [29550] [INFO] Booting worker with pid: 29550
[2025-09-07 22:31:51 +0800] [29550] [INFO] 工作进程 29550 已启动
[2025-09-07 22:31:51 +0800] [2147] [INFO] 工作进程 [booting] 即将启动
[2025-09-07 22:31:51 +0800] [29550] [INFO] 工作进程 29550 初始化完成
[2025-09-07 22:31:51 +0800] [29551] [INFO] Booting worker with pid: 29551
[2025-09-07 22:31:51 +0800] [29551] [INFO] 工作进程 29551 已启动
[2025-09-07 22:31:51 +0800] [2147] [INFO] 工作进程 [booting] 即将启动
[2025-09-07 22:31:51 +0800] [29551] [INFO] 工作进程 29551 初始化完成
[2025-09-07 22:31:51 +0800] [29552] [INFO] Booting worker with pid: 29552
[2025-09-07 22:31:51 +0800] [29552] [INFO] 工作进程 29552 已启动
[2025-09-07 22:31:51 +0800] [29552] [INFO] 工作进程 29552 初始化完成
[2025-09-07 22:31:51 +0800] [29553] [INFO] Booting worker with pid: 29553
[2025-09-07 22:31:51 +0800] [29553] [INFO] 工作进程 29553 已启动
[2025-09-07 22:31:51 +0800] [29553] [INFO] 工作进程 29553 初始化完成
[2025-09-07 22:31:52 +0800] [2147] [ERROR] Worker (pid:22819) was sent SIGTERM!
[2025-09-07 22:31:52 +0800] [2147] [ERROR] Worker (pid:4293) was sent SIGTERM!
[2025-09-07 22:31:52 +0800] [2147] [ERROR] Worker (pid:22816) was sent SIGTERM!
[2025-09-07 22:31:52 +0800] [2147] [ERROR] Worker (pid:22820) was sent SIGTERM!
[2025-09-07 22:31:52 +0800] [2147] [ERROR] Worker (pid:22818) exited with code 120
[2025-09-07 22:31:52 +0800] [2147] [ERROR] Worker (pid:22818) exited with code 120.

0
logs/port_monitor.log Normal file
View File

34
scripts/flask-app.service Normal file
View File

@@ -0,0 +1,34 @@
[Unit]
Description=Flask Prompt Master Application
After=network.target
Wants=network.target
[Service]
Type=forking
User=renjianbo
Group=renjianbo
WorkingDirectory=/home/renjianbo/aitsc
Environment=PATH=/home/renjianbo/miniconda3/envs/myenv/bin
ExecStart=/home/renjianbo/aitsc/scripts/port_manager.sh start
ExecStop=/home/renjianbo/aitsc/scripts/port_manager.sh stop
ExecReload=/home/renjianbo/aitsc/scripts/port_manager.sh restart
PIDFile=/home/renjianbo/aitsc/logs/gunicorn.pid
Restart=always
RestartSec=10
StartLimitInterval=60
StartLimitBurst=3
# 安全设置
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/home/renjianbo/aitsc/logs /home/renjianbo/aitsc/data
# 日志设置
StandardOutput=journal
StandardError=journal
SyslogIdentifier=flask-app
[Install]
WantedBy=multi-user.target

328
scripts/port_manager.sh Executable file
View File

@@ -0,0 +1,328 @@
#!/bin/bash
# 端口管理脚本
# 用于管理Flask应用的端口占用问题
# 配置
PORT=5002
APP_NAME="flask_prompt_master"
PID_FILE="logs/gunicorn.pid"
LOG_DIR="logs"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
}
# 检查端口是否被占用
check_port() {
log_info "检查端口 $PORT 占用情况..."
if ss -tlnp | grep -q ":$PORT "; then
log_warn "端口 $PORT 已被占用"
ss -tlnp | grep ":$PORT "
return 1
else
log_info "端口 $PORT 未被占用"
return 0
fi
}
# 获取占用端口的进程
get_port_processes() {
log_info "获取占用端口 $PORT 的进程信息..."
local processes=$(ss -tlnp | grep ":$PORT " | awk '{print $7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | sort -u)
if [ -n "$processes" ]; then
log_warn "占用端口 $PORT 的进程:"
for pid in $processes; do
if ps -p $pid > /dev/null 2>&1; then
ps -p $pid -o pid,ppid,cmd --no-headers
fi
done
return 1
else
log_info "没有进程占用端口 $PORT"
return 0
fi
}
# 清理端口占用
clean_port() {
log_info "清理端口 $PORT 占用..."
local processes=$(ss -tlnp | grep ":$PORT " | awk '{print $7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | sort -u)
if [ -n "$processes" ]; then
log_warn "发现占用端口 $PORT 的进程,正在清理..."
for pid in $processes; do
if ps -p $pid > /dev/null 2>&1; then
local cmd=$(ps -p $pid -o cmd --no-headers)
log_info "终止进程 $pid: $cmd"
kill -TERM $pid
# 等待进程终止
local count=0
while ps -p $pid > /dev/null 2>&1 && [ $count -lt 10 ]; do
sleep 1
count=$((count + 1))
done
# 强制终止
if ps -p $pid > /dev/null 2>&1; then
log_warn "强制终止进程 $pid"
kill -KILL $pid
fi
fi
done
# 验证清理结果
sleep 2
if check_port; then
log_info "端口 $PORT 清理成功"
return 0
else
log_error "端口 $PORT 清理失败"
return 1
fi
else
log_info "端口 $PORT 未被占用,无需清理"
return 0
fi
}
# 检查Gunicorn进程
check_gunicorn() {
log_info "检查Gunicorn进程状态..."
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p $pid > /dev/null 2>&1; then
log_info "Gunicorn主进程 $pid 正在运行"
local workers=$(ps aux | grep "gunicorn.*run_dev:app" | grep -v grep | wc -l)
log_info "工作进程数量: $workers"
return 0
else
log_warn "PID文件存在但进程不存在: $pid"
return 1
fi
else
log_warn "PID文件不存在: $PID_FILE"
return 1
fi
}
# 启动应用
start_app() {
log_info "启动Flask应用..."
# 切换到项目目录
cd "$PROJECT_DIR" || {
log_error "无法切换到项目目录: $PROJECT_DIR"
return 1
}
# 激活conda环境
if command -v conda > /dev/null 2>&1; then
log_info "激活conda环境..."
eval "$(/home/renjianbo/miniconda3/bin/conda shell.bash hook)" && conda activate myenv
fi
# 检查端口
if ! check_port; then
log_warn "端口被占用,尝试清理..."
if ! clean_port; then
log_error "无法清理端口占用,启动失败"
return 1
fi
fi
# 启动Gunicorn
log_info "启动Gunicorn服务..."
nohup gunicorn -c gunicorn.conf.py run_dev:app > "$LOG_DIR/gunicorn_startup.log" 2>&1 &
# 等待启动
sleep 5
# 验证启动
if check_gunicorn; then
log_info "应用启动成功"
return 0
else
log_error "应用启动失败"
return 1
fi
}
# 停止应用
stop_app() {
log_info "停止Flask应用..."
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if ps -p $pid > /dev/null 2>&1; then
log_info "停止Gunicorn主进程 $pid"
kill -TERM $pid
# 等待进程终止
local count=0
while ps -p $pid > /dev/null 2>&1 && [ $count -lt 10 ]; do
sleep 1
count=$((count + 1))
done
# 强制终止
if ps -p $pid > /dev/null 2>&1; then
log_warn "强制终止进程 $pid"
kill -KILL $pid
fi
fi
fi
# 清理所有相关进程
local gunicorn_pids=$(ps aux | grep "gunicorn.*run_dev:app" | grep -v grep | awk '{print $2}')
if [ -n "$gunicorn_pids" ]; then
log_info "清理Gunicorn工作进程..."
for pid in $gunicorn_pids; do
kill -TERM $pid 2>/dev/null
done
fi
log_info "应用停止完成"
}
# 重启应用
restart_app() {
log_info "重启Flask应用..."
stop_app
sleep 2
start_app
}
# 状态检查
status() {
log_info "=== Flask应用状态检查 ==="
echo "1. 端口占用检查:"
if check_port; then
echo " ✅ 端口 $PORT 可用"
else
echo " ❌ 端口 $PORT 被占用"
get_port_processes
fi
echo ""
echo "2. Gunicorn进程检查:"
if check_gunicorn; then
echo " ✅ Gunicorn正在运行"
else
echo " ❌ Gunicorn未运行"
fi
echo ""
echo "3. 服务响应检查:"
if curl -s http://localhost:$PORT/ > /dev/null 2>&1; then
echo " ✅ 服务正常响应"
else
echo " ❌ 服务无响应"
fi
}
# 监控模式
monitor() {
log_info "启动端口监控模式..."
while true; do
if ! check_gunicorn; then
log_warn "检测到Gunicorn进程异常尝试重启..."
restart_app
fi
if ! curl -s http://localhost:$PORT/ > /dev/null 2>&1; then
log_warn "检测到服务无响应,尝试重启..."
restart_app
fi
sleep 30
done
}
# 帮助信息
show_help() {
echo "端口管理脚本使用方法:"
echo ""
echo " $0 check - 检查端口占用情况"
echo " $0 clean - 清理端口占用"
echo " $0 start - 启动应用"
echo " $0 stop - 停止应用"
echo " $0 restart - 重启应用"
echo " $0 status - 查看应用状态"
echo " $0 monitor - 启动监控模式"
echo " $0 help - 显示帮助信息"
echo ""
echo "示例:"
echo " $0 status # 检查当前状态"
echo " $0 restart # 重启应用"
echo " $0 monitor # 后台监控"
}
# 主函数
main() {
case "${1:-help}" in
check)
check_port
;;
clean)
clean_port
;;
start)
start_app
;;
stop)
stop_app
;;
restart)
restart_app
;;
status)
status
;;
monitor)
monitor
;;
help|--help|-h)
show_help
;;
*)
log_error "未知命令: $1"
show_help
exit 1
;;
esac
}
# 执行主函数
main "$@"

399
scripts/port_monitor.py Executable file
View File

@@ -0,0 +1,399 @@
#!/home/renjianbo/miniconda3/envs/myenv/bin/python
# -*- coding: utf-8 -*-
"""
端口监控脚本
用于监控Flask应用的端口占用和服务状态
"""
import os
import sys
import time
import json
import subprocess
import logging
from datetime import datetime
from pathlib import Path
# 配置
PORT = 5002
APP_NAME = "flask_prompt_master"
PID_FILE = "logs/gunicorn.pid"
LOG_FILE = "logs/port_monitor.log"
CONFIG_FILE = "logs/monitor_config.json"
ALERT_FILE = "logs/port_alerts.log"
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class PortMonitor:
def __init__(self):
self.project_dir = Path(__file__).parent.parent
self.pid_file = self.project_dir / PID_FILE
self.config_file = self.project_dir / CONFIG_FILE
self.alert_file = self.project_dir / ALERT_FILE
# 确保日志目录存在
self.project_dir.mkdir(exist_ok=True)
(self.project_dir / "logs").mkdir(exist_ok=True)
# 加载配置
self.config = self.load_config()
def load_config(self):
"""加载监控配置"""
default_config = {
"check_interval": 30, # 检查间隔(秒)
"max_restart_attempts": 3, # 最大重启尝试次数
"alert_threshold": 2, # 告警阈值
"port_timeout": 5, # 端口检查超时时间
"enable_alerts": True, # 启用告警
"auto_restart": True, # 自动重启
}
if self.config_file.exists():
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
default_config.update(config)
except Exception as e:
logger.error("加载配置文件失败: {}".format(e))
return default_config
def save_config(self):
"""保存监控配置"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error("保存配置文件失败: {}".format(e))
def run_command(self, cmd, timeout=10):
"""执行命令"""
try:
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
timeout=timeout
)
return result.returncode == 0, result.stdout, result.stderr
except subprocess.TimeoutExpired:
return False, "", "命令执行超时"
except Exception as e:
return False, "", str(e)
def check_port_usage(self):
"""检查端口占用情况"""
cmd = f"ss -tlnp | grep ':{PORT} '"
success, stdout, stderr = self.run_command(cmd)
if success and stdout.strip():
# 解析进程信息
processes = []
for line in stdout.strip().split('\n'):
if line:
parts = line.split()
if len(parts) >= 7:
pid_info = parts[6]
if 'pid=' in pid_info:
pid = pid_info.split('pid=')[1].split(',')[0]
processes.append(pid)
return True, processes
else:
return False, []
def check_gunicorn_process(self):
"""检查Gunicorn进程状态"""
if not self.pid_file.exists():
return False, "PID文件不存在"
try:
with open(self.pid_file, 'r') as f:
pid = f.read().strip()
if not pid:
return False, "PID文件为空"
# 检查进程是否存在
cmd = f"ps -p {pid}"
success, stdout, stderr = self.run_command(cmd)
if success and stdout.strip():
# 检查工作进程数量
cmd = "ps aux | grep 'gunicorn.*run_dev:app' | grep -v grep | wc -l"
success, stdout, stderr = self.run_command(cmd)
if success:
worker_count = int(stdout.strip())
return True, f"主进程 {pid}, 工作进程 {worker_count}"
return False, f"进程 {pid} 不存在"
except Exception as e:
return False, "检查进程失败: {}".format(e)
def check_service_response(self):
"""检查服务响应"""
cmd = f"curl -s -o /dev/null -w '%{{http_code}}' http://localhost:{PORT}/"
success, stdout, stderr = self.run_command(cmd, timeout=self.config['port_timeout'])
if success and stdout.strip() == '200':
return True, "服务正常响应"
else:
return False, "服务无响应 (HTTP: {})".format(stdout.strip())
def get_system_info(self):
"""获取系统信息"""
info = {}
# CPU使用率
cmd = "top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1"
success, stdout, stderr = self.run_command(cmd)
if success:
info['cpu_usage'] = float(stdout.strip())
# 内存使用率
cmd = "free | grep Mem | awk '{printf \"%.2f\", $3/$2 * 100.0}'"
success, stdout, stderr = self.run_command(cmd)
if success:
info['memory_usage'] = float(stdout.strip())
# 磁盘使用率
cmd = "df / | tail -1 | awk '{print $5}' | sed 's/%//'"
success, stdout, stderr = self.run_command(cmd)
if success:
info['disk_usage'] = float(stdout.strip())
return info
def log_alert(self, message, level="WARNING"):
"""记录告警信息"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
alert_msg = f"[{timestamp}] [{level}] {message}"
logger.warning(alert_msg)
if self.config['enable_alerts']:
try:
with open(self.alert_file, 'a', encoding='utf-8') as f:
f.write(alert_msg + '\n')
except Exception as e:
logger.error("写入告警文件失败: {}".format(e))
def restart_service(self):
"""重启服务"""
logger.info("尝试重启服务...")
# 停止服务
cmd = f"cd {self.project_dir} && ./scripts/port_manager.sh stop"
success, stdout, stderr = self.run_command(cmd)
if not success:
logger.error("停止服务失败: {}".format(stderr))
return False
time.sleep(2)
# 启动服务
cmd = f"cd {self.project_dir} && ./scripts/port_manager.sh start"
success, stdout, stderr = self.run_command(cmd)
if not success:
logger.error("启动服务失败: {}".format(stderr))
return False
logger.info("服务重启成功")
return True
def generate_report(self):
"""生成监控报告"""
report = {
"timestamp": datetime.now().isoformat(),
"port_usage": {},
"gunicorn_status": {},
"service_response": {},
"system_info": {},
"recommendations": []
}
# 检查端口占用
port_occupied, processes = self.check_port_usage()
report["port_usage"] = {
"occupied": port_occupied,
"processes": processes
}
# 检查Gunicorn进程
gunicorn_running, status = self.check_gunicorn_process()
report["gunicorn_status"] = {
"running": gunicorn_running,
"status": status
}
# 检查服务响应
service_ok, response = self.check_service_response()
report["service_response"] = {
"ok": service_ok,
"response": response
}
# 获取系统信息
report["system_info"] = self.get_system_info()
# 生成建议
if not port_occupied:
report["recommendations"].append("端口未被占用,可能需要启动服务")
if not gunicorn_running:
report["recommendations"].append("Gunicorn进程未运行需要重启服务")
if not service_ok:
report["recommendations"].append("服务无响应,需要检查服务状态")
# 检查系统资源
sys_info = report["system_info"]
if sys_info.get('cpu_usage', 0) > 80:
report["recommendations"].append("CPU使用率过高建议优化或扩容")
if sys_info.get('memory_usage', 0) > 90:
report["recommendations"].append("内存使用率过高,建议增加内存或优化应用")
if sys_info.get('disk_usage', 0) > 85:
report["recommendations"].append("磁盘使用率过高,建议清理日志或扩容")
return report
def monitor_loop(self):
"""监控主循环"""
logger.info("开始端口监控...")
restart_count = 0
last_restart_time = 0
while True:
try:
# 生成报告
report = self.generate_report()
# 检查是否需要重启
need_restart = False
if not report["gunicorn_status"]["running"]:
logger.warning("Gunicorn进程未运行")
need_restart = True
if not report["service_response"]["ok"]:
logger.warning("服务无响应")
need_restart = True
# 执行重启
if need_restart and self.config['auto_restart']:
current_time = time.time()
# 检查重启限制
if (current_time - last_restart_time > 300 and # 5分钟内不重复重启
restart_count < self.config['max_restart_attempts']):
self.log_alert("检测到服务异常,尝试自动重启")
if self.restart_service():
restart_count += 1
last_restart_time = current_time
else:
self.log_alert("自动重启失败", "ERROR")
else:
self.log_alert("达到重启限制,跳过自动重启", "ERROR")
# 记录状态
status_msg = f"端口占用: {report['port_usage']['occupied']}, " \
f"Gunicorn: {report['gunicorn_status']['running']}, " \
f"服务响应: {report['service_response']['ok']}"
logger.info(status_msg)
# 等待下次检查
time.sleep(self.config['check_interval'])
except KeyboardInterrupt:
logger.info("监控被用户中断")
break
except Exception as e:
logger.error(f"监控过程中发生错误: {e}")
time.sleep(self.config['check_interval'])
def show_status(self):
"""显示当前状态"""
report = self.generate_report()
print("=== Flask应用监控状态 ===")
print(f"检查时间: {report['timestamp']}")
print()
print("1. 端口占用检查:")
if report['port_usage']['occupied']:
print(f" ✅ 端口 {PORT} 被占用")
print(f" 进程: {', '.join(report['port_usage']['processes'])}")
else:
print(f" ❌ 端口 {PORT} 未被占用")
print()
print("2. Gunicorn进程检查:")
if report['gunicorn_status']['running']:
print(f"{report['gunicorn_status']['status']}")
else:
print(f"{report['gunicorn_status']['status']}")
print()
print("3. 服务响应检查:")
if report['service_response']['ok']:
print(f"{report['service_response']['response']}")
else:
print(f"{report['service_response']['response']}")
print()
print("4. 系统资源:")
sys_info = report['system_info']
print(f" CPU使用率: {sys_info.get('cpu_usage', 'N/A')}%")
print(f" 内存使用率: {sys_info.get('memory_usage', 'N/A')}%")
print(f" 磁盘使用率: {sys_info.get('disk_usage', 'N/A')}%")
if report['recommendations']:
print()
print("5. 建议:")
for i, rec in enumerate(report['recommendations'], 1):
print(f" {i}. {rec}")
def main():
monitor = PortMonitor()
if len(sys.argv) > 1:
command = sys.argv[1]
if command == "status":
monitor.show_status()
elif command == "report":
report = monitor.generate_report()
print(json.dumps(report, indent=2, ensure_ascii=False))
elif command == "restart":
monitor.restart_service()
elif command == "monitor":
monitor.monitor_loop()
elif command == "config":
print(json.dumps(monitor.config, indent=2, ensure_ascii=False))
else:
print("用法: python port_monitor.py [status|report|restart|monitor|config]")
else:
monitor.monitor_loop()
if __name__ == "__main__":
main()

View File

@@ -23,6 +23,7 @@
--dark-color: #5a5c69;
--light-color: #f8f9fc;
--sidebar-width: 250px;
--sidebar-collapsed-width: 70px;
--header-height: 70px;
}
@@ -46,6 +47,29 @@
box-shadow: 4px 0 10px rgba(0,0,0,0.1);
z-index: 1000;
transition: all 0.3s ease;
overflow-y: auto;
overflow-x: hidden;
}
.sidebar.collapsed {
width: var(--sidebar-collapsed-width);
}
.sidebar.collapsed .sidebar-brand-text {
display: none;
}
.sidebar.collapsed .sidebar-link-text {
display: none;
}
.sidebar.collapsed .sidebar-link {
justify-content: center;
padding: 0.75rem 0.5rem;
}
.sidebar.collapsed .sidebar-icon {
margin-right: 0;
}
.sidebar-header {
@@ -111,6 +135,10 @@
transition: all 0.3s ease;
}
.main-content.sidebar-collapsed {
margin-left: var(--sidebar-collapsed-width);
}
/* 顶部导航栏 */
.top-navbar {
background: white;
@@ -124,6 +152,67 @@
z-index: 999;
}
.navbar-left {
display: flex;
align-items: center;
gap: 1rem;
}
.sidebar-toggle {
background: none;
border: none;
font-size: 1.2rem;
color: var(--dark-color);
cursor: pointer;
padding: 0.5rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.sidebar-toggle:hover {
background: var(--light-color);
color: var(--primary-color);
}
/* 响应式设计 */
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.mobile-open {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.top-navbar {
padding: 1rem;
}
.page-title {
font-size: 1.2rem;
}
}
@media (max-width: 576px) {
.top-navbar {
padding: 0.75rem;
}
.user-menu {
flex-direction: column;
align-items: flex-end;
gap: 0.5rem;
}
.user-info {
text-align: right;
}
}
.page-title {
font-size: 1.5rem;
font-weight: 600;
@@ -395,6 +484,69 @@
</style>
{% block head %}{% endblock %}
<!-- JavaScript -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.querySelector('.sidebar');
const mainContent = document.querySelector('.main-content');
const sidebarToggle = document.getElementById('sidebarToggle');
const isMobile = window.innerWidth <= 768;
// 初始化侧边栏状态
let sidebarCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
if (sidebarCollapsed && !isMobile) {
sidebar.classList.add('collapsed');
mainContent.classList.add('sidebar-collapsed');
}
// 切换侧边栏
function toggleSidebar() {
if (isMobile) {
// 移动端:显示/隐藏侧边栏
sidebar.classList.toggle('mobile-open');
} else {
// 桌面端:折叠/展开侧边栏
sidebar.classList.toggle('collapsed');
mainContent.classList.toggle('sidebar-collapsed');
sidebarCollapsed = sidebar.classList.contains('collapsed');
localStorage.setItem('sidebarCollapsed', sidebarCollapsed);
}
}
// 绑定点击事件
sidebarToggle.addEventListener('click', toggleSidebar);
// 移动端点击遮罩关闭侧边栏
if (isMobile) {
mainContent.addEventListener('click', function() {
if (sidebar.classList.contains('mobile-open')) {
sidebar.classList.remove('mobile-open');
}
});
}
// 响应式处理
window.addEventListener('resize', function() {
const newIsMobile = window.innerWidth <= 768;
if (newIsMobile !== isMobile) {
// 屏幕尺寸改变时重新加载页面
window.location.reload();
}
});
// 侧边栏链接点击后自动关闭移动端侧边栏
if (isMobile) {
const sidebarLinks = document.querySelectorAll('.sidebar-link');
sidebarLinks.forEach(link => {
link.addEventListener('click', function() {
sidebar.classList.remove('mobile-open');
});
});
}
});
</script>
</head>
<body>
<!-- 侧边栏 -->
@@ -402,7 +554,7 @@
<div class="sidebar-header">
<a href="{{ url_for('admin.index') }}" class="sidebar-brand">
<i class="fas fa-magic me-2"></i>
提示词大师
<span class="sidebar-brand-text">提示词大师</span>
</a>
</div>
@@ -410,77 +562,77 @@
<div class="sidebar-item">
<a href="{{ url_for('admin.index') }}" class="sidebar-link {% if request.endpoint == 'admin.index' %}active{% endif %}">
<i class="fas fa-tachometer-alt sidebar-icon"></i>
仪表板
<span class="sidebar-link-text">仪表板</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('admin_user.index_view') }}" class="sidebar-link {% if 'admin_user' in request.endpoint %}active{% endif %}">
<i class="fas fa-users sidebar-icon"></i>
用户管理
<span class="sidebar-link-text">用户管理</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('admin_prompt.index_view') }}" class="sidebar-link {% if 'admin_prompt' in request.endpoint %}active{% endif %}">
<i class="fas fa-magic sidebar-icon"></i>
提示词管理
<span class="sidebar-link-text">提示词管理</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('admin_template.index_view') }}" class="sidebar-link {% if 'admin_template' in request.endpoint %}active{% endif %}">
<i class="fas fa-clipboard-list sidebar-icon"></i>
模板管理
<span class="sidebar-link-text">模板管理</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('analytics_admin.index') }}" class="sidebar-link {% if 'analytics_admin' in request.endpoint %}active{% endif %}">
<i class="fas fa-chart-line sidebar-icon"></i>
数据分析
<span class="sidebar-link-text">数据分析</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('batch_admin.index') }}" class="sidebar-link {% if 'batch_admin' in request.endpoint %}active{% endif %}">
<i class="fas fa-tasks sidebar-icon"></i>
批量操作
<span class="sidebar-link-text">批量操作</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('monitor_admin.index') }}" class="sidebar-link {% if 'monitor_admin' in request.endpoint %}active{% endif %}">
<i class="fas fa-tachometer-alt sidebar-icon"></i>
系统监控
<span class="sidebar-link-text">系统监控</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('report_admin.index') }}" class="sidebar-link {% if 'report_admin' in request.endpoint %}active{% endif %}">
<i class="fas fa-chart-bar sidebar-icon"></i>
高级报表
<span class="sidebar-link-text">高级报表</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('backup_admin.index') }}" class="sidebar-link {% if 'backup_admin' in request.endpoint %}active{% endif %}">
<i class="fas fa-database sidebar-icon"></i>
数据备份
<span class="sidebar-link-text">数据备份</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('api_admin.index') }}" class="sidebar-link {% if 'api_admin' in request.endpoint %}active{% endif %}">
<i class="fas fa-code sidebar-icon"></i>
API管理
<span class="sidebar-link-text">API管理</span>
</a>
</div>
<div class="sidebar-item">
<a href="{{ url_for('admin_system.index') }}" class="sidebar-link {% if 'admin_system' in request.endpoint %}active{% endif %}">
<i class="fas fa-cogs sidebar-icon"></i>
系统管理
<span class="sidebar-link-text">系统管理</span>
</a>
</div>
</div>
@@ -490,9 +642,14 @@
<div class="main-content">
<!-- 顶部导航栏 -->
<header class="top-navbar">
<h1 class="page-title">
{% block page_title %}{% endblock %}
</h1>
<div class="navbar-left">
<button class="sidebar-toggle" id="sidebarToggle">
<i class="fas fa-bars"></i>
</button>
<h1 class="page-title">
{% block page_title %}{% endblock %}
</h1>
</div>
<div class="user-menu">
<div class="user-info">