添加总人数显示功能
This commit is contained in:
Binary file not shown.
+279
@@ -0,0 +1,279 @@
|
||||
# 自动控制理论AI+数智平台 - 学生使用指南
|
||||
|
||||
## 一、平台访问
|
||||
|
||||
**平台地址**: http://182.148.54.29:7860/
|
||||
|
||||
**推荐浏览器**: Chrome、Edge 或 Firefox 最新版本
|
||||
|
||||
---
|
||||
|
||||
## 二、平台功能简介
|
||||
|
||||
本平台是《自动控制理论》课程的配套学习工具,提供四大核心功能:
|
||||
|
||||
1. **时域分析** - 分析系统的阶跃响应和脉冲响应
|
||||
2. **频域分析** - 绘制Bode图和Nyquist图,分析系统稳定性
|
||||
3. **根轨迹分析** - 观察增益变化对系统极点的影响
|
||||
4. **AI智能问答** - 24小时在线的自动控制理论助教
|
||||
|
||||
---
|
||||
|
||||
## 三、详细使用教程
|
||||
|
||||
### 3.1 时域分析
|
||||
|
||||
#### 3.1.1 功能说明
|
||||
|
||||
输入系统的传递函数,查看系统的时域响应特性。
|
||||
|
||||
#### 3.1.2 使用步骤
|
||||
|
||||
1. 点击顶部"时域分析"标签页
|
||||
2. 在左侧输入框中输入:
|
||||
- **分子系数**:例如 `1` 或 `1,2,3`(用逗号分隔)
|
||||
- **分母系数**:例如 `1,6,11,6`(从最高次项到常数项)
|
||||
3. 点击"显示传递函数"按钮查看数学公式
|
||||
4. 点击"开始分析"按钮生成响应曲线
|
||||
|
||||
#### 3.1.3 输出结果
|
||||
|
||||
- **左图**:单位阶跃响应曲线
|
||||
- **右图**:单位脉冲响应曲线
|
||||
- **性能指标**:上升时间、峰值时间、超调量、调节时间等
|
||||
|
||||
#### 3.1.4 示例输入
|
||||
|
||||
```text
|
||||
分子系数: 1
|
||||
分母系数: 1,2,1
|
||||
```
|
||||
|
||||
这是一个典型的二阶系统:$G(s) = \frac{1}{s^2+2s+1}$
|
||||
|
||||
---
|
||||
|
||||
### 3.2 频域分析
|
||||
|
||||
#### 3.2.1 功能说明
|
||||
|
||||
通过Bode图和Nyquist图分析系统的频率特性和稳定性。
|
||||
|
||||
#### 3.2.2 使用步骤
|
||||
|
||||
1. 点击顶部"频域分析"标签页
|
||||
2. 输入传递函数的分子和分母系数(同时域分析)
|
||||
3. 拖动"对数增益 log₁₀(K)"滑块,调整系统增益
|
||||
4. 观察右侧的Bode图和Nyquist图实时变化
|
||||
|
||||
#### 3.2.3 输出结果
|
||||
|
||||
- **Bode图**:幅频特性和相频特性曲线
|
||||
- **Nyquist图**:极坐标表示的频率响应
|
||||
- **稳定裕度**:增益裕度(GM)和相角裕度(PM)
|
||||
- **稳定性评估**:系统是否稳定的判断结果
|
||||
|
||||
#### 3.2.4 稳定性判断标准
|
||||
|
||||
- GM > 0 dB 且 PM > 0° → **系统稳定**
|
||||
- GM < 0 dB 或 PM < 0° → **系统不稳定**
|
||||
|
||||
---
|
||||
|
||||
### 3.3 根轨迹分析
|
||||
|
||||
#### 3.3.1 功能说明
|
||||
|
||||
观察增益K从0到∞变化时,闭环极点在s平面上的移动轨迹。
|
||||
|
||||
#### 3.3.2 使用步骤
|
||||
|
||||
1. 点击顶部"根轨迹"标签页
|
||||
2. 输入开环传递函数的分子和分母系数
|
||||
3. 拖动"对数增益 log₁₀(K)"滑块
|
||||
4. 观察极点位置的实时变化
|
||||
|
||||
#### 3.3.3 输出结果
|
||||
|
||||
- **根轨迹图**:蓝色曲线表示极点运动轨迹
|
||||
- **当前极点**:红色×标记表示当前增益K下的闭环极点位置
|
||||
- **极点坐标**:实部和虚部的具体数值
|
||||
|
||||
#### 3.3.4 稳定性判断标准
|
||||
|
||||
- 极点在左半平面(实部 < 0)→ **稳定**
|
||||
- 极点在虚轴上(实部 = 0)→ **临界稳定**
|
||||
- 极点在右半平面(实部 > 0)→ **不稳定**
|
||||
|
||||
---
|
||||
|
||||
### 3.4 AI智能问答
|
||||
|
||||
#### 3.4.1 功能说明
|
||||
|
||||
随时向AI助教提问自动控制理论相关的问题。
|
||||
|
||||
#### 3.4.2 使用步骤
|
||||
|
||||
1. 点击顶部"智能问答"标签页
|
||||
2. 在底部输入框输入你的问题
|
||||
3. 点击"发送"按钮或按Enter键
|
||||
4. 等待AI助教回复(支持LaTeX公式显示)
|
||||
|
||||
#### 3.4.3 可提问的内容
|
||||
|
||||
- **概念解释**:"什么是传递函数?"
|
||||
- **公式推导**:"如何计算二阶系统的超调量?"
|
||||
- **例题讲解**:"如何用劳斯判据判断稳定性?"
|
||||
- **知识点讨论**:"PID控制器的三个参数分别有什么作用?"
|
||||
|
||||
#### 3.4.4 示例问题
|
||||
|
||||
```text
|
||||
1. 什么是增益裕度和相角裕度?
|
||||
2. 如何从根轨迹图判断系统的稳定性?
|
||||
3. 解释一下Nyquist稳定判据
|
||||
4. 二阶系统的阻尼比对响应有什么影响?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、常用传递函数示例
|
||||
|
||||
### 4.1 一阶系统
|
||||
|
||||
```text
|
||||
分子: 1
|
||||
分母: 1,1
|
||||
```
|
||||
|
||||
传递函数:$G(s) = \frac{1}{s+1}$
|
||||
|
||||
### 4.2 二阶欠阻尼系统
|
||||
|
||||
```text
|
||||
分子: 1
|
||||
分母: 1,2,1
|
||||
```
|
||||
|
||||
传递函数:$G(s) = \frac{1}{s^2+2s+1}$
|
||||
|
||||
### 4.3 二阶无阻尼系统
|
||||
|
||||
```text
|
||||
分子: 4
|
||||
分母: 1,0,4
|
||||
```
|
||||
|
||||
传递函数:$G(s) = \frac{4}{s^2+4}$
|
||||
|
||||
### 4.4 三阶系统
|
||||
|
||||
```text
|
||||
分子: 1
|
||||
分母: 1,6,11,6
|
||||
```
|
||||
|
||||
传递函数:$G(s) = \frac{1}{s^3+6s^2+11s+6}$
|
||||
|
||||
---
|
||||
|
||||
## 五、使用技巧与建议
|
||||
|
||||
### 5.1 推荐做法
|
||||
|
||||
1. **先显示传递函数** - 确认输入正确再进行分析
|
||||
2. **对比不同参数** - 修改系数观察系统特性变化
|
||||
3. **结合AI问答** - 不理解的地方随时提问
|
||||
4. **多尝试示例** - 从简单系统开始,逐步深入
|
||||
|
||||
### 5.2 常见问题及解决方法
|
||||
|
||||
**问题1:输入后没有反应**
|
||||
|
||||
- 检查输入格式:系数之间用英文逗号分隔
|
||||
- 确保分子和分母都已输入
|
||||
|
||||
**问题2:图表显示异常**
|
||||
|
||||
- 刷新页面重试
|
||||
- 检查传递函数的阶数是否合理(分母阶数≥分子阶数)
|
||||
|
||||
**问题3:AI回答不准确**
|
||||
|
||||
- 尝试更具体地描述问题
|
||||
- 问题中包含关键词和背景信息
|
||||
|
||||
**问题4:在线人数显示说明**
|
||||
|
||||
- 页面顶部显示当前有多少同学在线使用平台
|
||||
- 每10秒自动更新
|
||||
|
||||
---
|
||||
|
||||
## 六、学习建议
|
||||
|
||||
### 6.1 时域分析练习方向
|
||||
|
||||
1. 对比一阶和二阶系统的响应差异
|
||||
2. 观察阻尼比对超调量的影响
|
||||
3. 分析零点对系统响应的作用
|
||||
|
||||
### 6.2 频域分析练习方向
|
||||
|
||||
1. 调整增益K,观察稳定裕度变化
|
||||
2. 找出系统临界稳定的增益值
|
||||
3. 理解Bode图和Nyquist图的关系
|
||||
|
||||
### 6.3 根轨迹分析练习方向
|
||||
|
||||
1. 找出根轨迹的分离点
|
||||
2. 确定系统稳定的增益范围
|
||||
3. 观察主导极点的移动规律
|
||||
|
||||
---
|
||||
|
||||
## 七、系统要求
|
||||
|
||||
### 7.1 设备要求
|
||||
|
||||
- **浏览器**: Chrome 90+、Edge 90+、Firefox 88+
|
||||
- **网络**: 稳定的互联网连接
|
||||
- **屏幕分辨率**: 建议 1366×768 及以上
|
||||
|
||||
### 7.2 网络要求
|
||||
|
||||
- 可访问校园网或互联网
|
||||
- 建议带宽不低于2Mbps
|
||||
|
||||
---
|
||||
|
||||
## 八、技术支持
|
||||
|
||||
**联系方式**:
|
||||
|
||||
- 邮箱: pengfeiwei@nwpu.edu.cn
|
||||
- 机构: 西北工业大学
|
||||
- 负责人: 魏鹏飞
|
||||
|
||||
**说明**:
|
||||
|
||||
- 课程相关问题:请联系任课教师或助教
|
||||
- 平台技术问题:请通过上述邮箱反馈
|
||||
|
||||
---
|
||||
|
||||
## 九、附录
|
||||
|
||||
### 9.1 平台特色功能
|
||||
|
||||
1. **实时在线人数统计** - 页面顶部显示当前在线用户数
|
||||
2. **自动保存会话** - AI问答记录自动保存
|
||||
3. **LaTeX公式支持** - 完美显示数学公式
|
||||
4. **响应式设计** - 适配不同屏幕尺寸
|
||||
|
||||
### 9.2 更新说明
|
||||
|
||||
- 版本: v1.1.0
|
||||
- 更新日期: 2025年10月19日
|
||||
- 主要更新: 新增实时在线人数统计功能
|
||||
Binary file not shown.
@@ -1,284 +0,0 @@
|
||||
# 自动控制原理AI+数智平台 - 模块化架构说明
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
AutoControl/
|
||||
├── app.py # 原始完整程序(已废弃,保留作参考)
|
||||
├── app_main.py # 新的主程序入口 ⭐ 使用这个启动
|
||||
├── config.py # 配置文件:API密钥、系统参数
|
||||
├── utils.py # 工具函数:解析、验证、格式化
|
||||
├── analysis.py # 分析功能:时域、频域、根轨迹
|
||||
├── ai_chat.py # AI聊天:DeepSeek/Gemini接口
|
||||
├── ui_styles.py # CSS样式:480+行现代化样式
|
||||
├── ui_components.py # UI组件:横幅、知识卡片HTML
|
||||
└── README_MODULE.md # 本文件
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 方法1:使用新的模块化主程序(推荐)
|
||||
|
||||
```bash
|
||||
python app_main.py
|
||||
```
|
||||
|
||||
### 方法2:使用原始完整程序
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
## 📦 模块说明
|
||||
|
||||
### 1. `config.py` - 配置管理
|
||||
**功能:** 集中管理所有配置常量
|
||||
|
||||
**内容:**
|
||||
- `API_KEY`: DeepSeek API密钥
|
||||
- `API_BASE_URL`: API基础URL
|
||||
- `API_MODEL`: 使用的模型名称
|
||||
- `DEFAULT_NUM_COEFFS`: 默认分子系数
|
||||
- `DEFAULT_DEN_COEFFS`: 默认分母系数
|
||||
- `TIME_SPAN`: 时间范围设置
|
||||
- `SERVER_PORT`: 服务器端口
|
||||
- `PROJECT_INFO`: 项目信息字典
|
||||
|
||||
**修改配置:**
|
||||
```python
|
||||
# 在 config.py 中修改
|
||||
API_KEY = "你的API密钥"
|
||||
SERVER_PORT = 8080 # 更改端口
|
||||
```
|
||||
|
||||
### 2. `utils.py` - 工具函数库
|
||||
**功能:** 提供数据处理和格式化工具
|
||||
|
||||
**主要函数:**
|
||||
- `coeffs_to_latex(coeffs, var='s')`: 系数转LaTeX多项式
|
||||
- `parse_coefficients(coeffs_str)`: 解析系数字符串
|
||||
- `validate_transfer_function(num, den)`: 验证传递函数
|
||||
- `format_complex_number(c)`: 格式化复数显示
|
||||
|
||||
**使用示例:**
|
||||
```python
|
||||
from utils import parse_coefficients, validate_transfer_function
|
||||
|
||||
num = parse_coefficients("1,2,3")
|
||||
den = parse_coefficients("1,4,5,2")
|
||||
is_valid, msg = validate_transfer_function(num, den)
|
||||
```
|
||||
|
||||
### 3. `analysis.py` - 核心分析功能
|
||||
**功能:** 控制系统的时域、频域、根轨迹分析
|
||||
|
||||
**主要函数:**
|
||||
- `display_transfer_function(num_str, den_str)`: 显示传递函数
|
||||
- `time_domain_analysis(num_str, den_str)`: 时域分析(阶跃/脉冲响应)
|
||||
- `frequency_domain_analysis(num_str, den_str, K)`: 频域分析(Bode/Nyquist图)
|
||||
- `root_locus_analysis(num_str, den_str, log_k)`: 根轨迹分析
|
||||
|
||||
**返回值:**
|
||||
- 图表:matplotlib Figure对象
|
||||
- 文本:性能指标、极点位置等
|
||||
|
||||
### 4. `ai_chat.py` - AI聊天功能
|
||||
**功能:** 与DeepSeek/Gemini大模型交互
|
||||
|
||||
**主要函数:**
|
||||
- `chat_with_ai(message, history)`: 异步聊天函数(支持流式输出)
|
||||
|
||||
**特点:**
|
||||
- 支持对话历史记录
|
||||
- 流式响应(实时显示)
|
||||
- 自动错误处理
|
||||
- Emoji状态指示
|
||||
|
||||
**系统提示词:**
|
||||
```
|
||||
你是一位精通自动控制原理的专家教授,擅长用通俗易懂的方式解释复杂概念。
|
||||
回答要准确、专业,适当使用LaTeX公式,并举例说明。
|
||||
```
|
||||
|
||||
### 5. `ui_styles.py` - CSS样式定义
|
||||
**功能:** 提供480+行现代化CSS样式
|
||||
|
||||
**函数:**
|
||||
- `get_custom_css()`: 返回完整CSS字符串
|
||||
|
||||
**样式特点:**
|
||||
- 渐变背景和阴影效果
|
||||
- 动画过渡效果
|
||||
- 响应式设计(移动端适配)
|
||||
- 暗色主题支持
|
||||
- Emoji彩色显示修复
|
||||
|
||||
### 6. `ui_components.py` - UI组件库
|
||||
**功能:** 提供HTML组件和知识卡片
|
||||
|
||||
**主要函数:**
|
||||
- `get_main_title()`: 主标题
|
||||
- `get_subtitle()`: 副标题
|
||||
- `get_project_info_banner()`: 项目信息横幅
|
||||
- `get_tab_tip(tab_name)`: 标签页提示
|
||||
- `get_time_domain_knowledge_card()`: 时域知识卡片
|
||||
- `get_frequency_domain_knowledge_card()`: 频域知识卡片
|
||||
- `get_root_locus_knowledge_card()`: 根轨迹知识卡片
|
||||
- `get_ai_example_questions()`: AI示例问题
|
||||
|
||||
### 7. `app_main.py` - 主程序入口
|
||||
**功能:** 简化的主程序,组装所有模块
|
||||
|
||||
**代码行数:** ~350行(相比原来的2134行减少84%)
|
||||
|
||||
**结构:**
|
||||
```python
|
||||
# 导入所有模块
|
||||
from config import *
|
||||
from utils import *
|
||||
from analysis import *
|
||||
from ai_chat import *
|
||||
from ui_styles import *
|
||||
from ui_components import *
|
||||
|
||||
# 使用Gradio构建UI
|
||||
with gr.Blocks(css=get_custom_css()) as demo:
|
||||
# UI布局
|
||||
# 事件绑定
|
||||
pass
|
||||
|
||||
# 启动服务器
|
||||
demo.launch()
|
||||
```
|
||||
|
||||
## 🎯 模块化的优势
|
||||
|
||||
### 1. 易于维护
|
||||
- 每个模块职责单一,代码清晰
|
||||
- 修改某个功能只需编辑对应模块
|
||||
- 减少代码重复,提高复用性
|
||||
|
||||
### 2. 易于扩展
|
||||
- 添加新功能:创建新模块或在现有模块添加函数
|
||||
- 添加新UI:在`ui_components.py`添加新函数
|
||||
- 添加新样式:在`ui_styles.py`修改CSS
|
||||
|
||||
### 3. 易于调试
|
||||
- 模块独立,可单独测试
|
||||
- 错误定位更快速
|
||||
- 日志和异常处理更精确
|
||||
|
||||
### 4. 团队协作友好
|
||||
- 不同开发者可并行工作在不同模块
|
||||
- 代码冲突减少
|
||||
- 代码审查更高效
|
||||
|
||||
## 📝 常见修改场景
|
||||
|
||||
### 场景1:更改API密钥
|
||||
**文件:** `config.py`
|
||||
```python
|
||||
API_KEY = "sk-新的密钥"
|
||||
```
|
||||
|
||||
### 场景2:添加新的分析功能
|
||||
**文件:** `analysis.py`
|
||||
```python
|
||||
def new_analysis_function(num_str, den_str):
|
||||
"""新的分析功能"""
|
||||
# 实现代码
|
||||
return figure, metrics
|
||||
```
|
||||
|
||||
**文件:** `app_main.py`(添加UI和事件绑定)
|
||||
```python
|
||||
from analysis import new_analysis_function
|
||||
|
||||
# 添加UI组件
|
||||
new_button = gr.Button("新分析")
|
||||
new_output = gr.Plot()
|
||||
|
||||
# 绑定事件
|
||||
new_button.click(fn=new_analysis_function, inputs=[...], outputs=[...])
|
||||
```
|
||||
|
||||
### 场景3:修改知识卡片内容
|
||||
**文件:** `ui_components.py`
|
||||
```python
|
||||
def get_time_domain_knowledge_card():
|
||||
return """
|
||||
<div class='knowledge-card'>
|
||||
<!-- 修改HTML内容 -->
|
||||
</div>
|
||||
"""
|
||||
```
|
||||
|
||||
### 场景4:调整样式颜色
|
||||
**文件:** `ui_styles.py`
|
||||
```python
|
||||
def get_custom_css():
|
||||
return """
|
||||
.main-title {
|
||||
background: linear-gradient(135deg, #新颜色1, #新颜色2);
|
||||
}
|
||||
"""
|
||||
```
|
||||
|
||||
### 场景5:更改默认参数
|
||||
**文件:** `config.py`
|
||||
```python
|
||||
DEFAULT_DEN_COEFFS = "1,8,15,8" # 更改默认分母系数
|
||||
SERVER_PORT = 8080 # 更改服务器端口
|
||||
```
|
||||
|
||||
## 🔧 依赖库
|
||||
|
||||
```txt
|
||||
gradio>=5.0.0
|
||||
numpy>=1.21.0
|
||||
matplotlib>=3.5.0
|
||||
scipy>=1.7.0
|
||||
control>=0.9.0
|
||||
aiohttp>=3.8.0 # AI聊天功能
|
||||
```
|
||||
|
||||
**安装命令:**
|
||||
```bash
|
||||
pip install gradio numpy matplotlib scipy control aiohttp
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **首次运行:** 需要在`config.py`中配置DeepSeek API密钥才能使用AI聊天功能
|
||||
2. **端口冲突:** 如果7860端口被占用,修改`config.py`中的`SERVER_PORT`
|
||||
3. **模块导入:** 所有模块必须在同一目录下
|
||||
4. **Python版本:** 建议使用Python 3.8+
|
||||
|
||||
## 📊 代码对比
|
||||
|
||||
| 指标 | 原版 (app.py) | 模块化版本 |
|
||||
|-----|--------------|-----------|
|
||||
| 总代码行数 | 2134行 | 分散在7个文件 |
|
||||
| 主程序行数 | 2134行 | 350行 (↓84%) |
|
||||
| 可维护性 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| 可扩展性 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| 团队协作 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
|
||||
## 🎓 学习建议
|
||||
|
||||
1. **初学者:** 先运行`app_main.py`,熟悉整体功能
|
||||
2. **进阶学习:** 阅读各模块代码,理解功能实现
|
||||
3. **高级应用:** 尝试添加新功能或修改现有模块
|
||||
4. **问题排查:** 参考模块注释和本文档
|
||||
|
||||
## 📮 联系方式
|
||||
|
||||
**项目负责人:** 魏鹏飞 教授
|
||||
**邮箱:** pengfeiwei@nwpu.edu.cn
|
||||
**单位:** 西北工业大学
|
||||
**资助:** 2025年校级本科生建设项目
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2025年1月
|
||||
**版本:** 2.0 (模块化架构)
|
||||
@@ -68,13 +68,79 @@ import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from threading import Lock
|
||||
import atexit
|
||||
|
||||
# ==================== 在线人数统计 ====================
|
||||
# 全局变量:追踪活跃用户
|
||||
active_users = {} # 存储用户最后活跃时间 {session_id: timestamp}
|
||||
users_lock = Lock() # 线程锁,保证并发安全
|
||||
"""
|
||||
==================== 在线/总人数统计与持久化 ====================
|
||||
新增:在项目根目录下自动创建数据文件夹,持久化累计人数,
|
||||
防止容器重启后总人数从 0 开始。
|
||||
保存策略:每 SAVE_INTERVAL_SECONDS 秒写盘一次(由在线人数刷新触发)。
|
||||
"""
|
||||
# 当前活跃用户(在线)
|
||||
active_users = {} # {session_id: last_active_timestamp}
|
||||
users_lock = Lock()
|
||||
TIMEOUT_SECONDS = 300 # 5分钟无活动视为离线
|
||||
|
||||
# 累计人数(总人数)持久化
|
||||
stats_lock = Lock()
|
||||
STATS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data_usage")
|
||||
STATS_FILE = os.path.join(STATS_DIR, "usage_stats.json")
|
||||
SAVE_INTERVAL_SECONDS = int(os.environ.get("USAGE_SAVE_INTERVAL", "60")) # 默认60秒写盘一次
|
||||
total_users = 0 # 累计会话人数(近似代表累计访问人数)
|
||||
seen_sessions = set() # 仅在本次进程内去重,避免同一session重复计数
|
||||
_LAST_SAVE_TS = 0.0
|
||||
|
||||
def _ensure_stats_dir():
|
||||
try:
|
||||
os.makedirs(STATS_DIR, exist_ok=True)
|
||||
except Exception:
|
||||
# 即便目录创建失败,不影响主流程,仅导致不持久化
|
||||
pass
|
||||
|
||||
def _load_usage_stats():
|
||||
"""从磁盘加载累计人数,若无文件则从0开始。"""
|
||||
global total_users, _LAST_SAVE_TS
|
||||
try:
|
||||
if os.path.exists(STATS_FILE):
|
||||
with open(STATS_FILE, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
total_users = int(data.get("total_users", 0))
|
||||
_LAST_SAVE_TS = float(data.get("last_saved_at", time.time()))
|
||||
else:
|
||||
total_users = 0
|
||||
_LAST_SAVE_TS = time.time()
|
||||
except Exception:
|
||||
# 文件损坏或解析失败时,安全回退为0
|
||||
total_users = 0
|
||||
_LAST_SAVE_TS = time.time()
|
||||
|
||||
def _save_usage_stats():
|
||||
"""将累计人数持久化到磁盘。"""
|
||||
global _LAST_SAVE_TS
|
||||
try:
|
||||
tmp_path = STATS_FILE + ".tmp"
|
||||
with open(tmp_path, "w", encoding="utf-8") as f:
|
||||
json.dump({"total_users": total_users, "last_saved_at": time.time()}, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp_path, STATS_FILE)
|
||||
_LAST_SAVE_TS = time.time()
|
||||
except Exception:
|
||||
# 写盘失败不阻塞主流程
|
||||
pass
|
||||
|
||||
def _maybe_save_usage_stats():
|
||||
"""按间隔条件触发写盘。调用点:在线人数UI刷新时。"""
|
||||
try:
|
||||
with stats_lock:
|
||||
if time.time() - _LAST_SAVE_TS >= SAVE_INTERVAL_SECONDS:
|
||||
_save_usage_stats()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 初始化目录与读取历史累计值
|
||||
_ensure_stats_dir()
|
||||
_load_usage_stats()
|
||||
atexit.register(_save_usage_stats)
|
||||
|
||||
def get_active_users_count():
|
||||
"""获取当前活跃用户数量"""
|
||||
current_time = time.time()
|
||||
@@ -87,15 +153,34 @@ def get_active_users_count():
|
||||
return len(active_users)
|
||||
|
||||
def update_user_activity(session_id):
|
||||
"""更新用户活跃时间"""
|
||||
"""更新用户活跃时间并在首次出现时累计总人数。"""
|
||||
# 更新在线用户心跳
|
||||
with users_lock:
|
||||
active_users[session_id] = time.time()
|
||||
# 首次见到该 session,累计 +1(仅在本进程内去重),并等待定时写盘
|
||||
try:
|
||||
with stats_lock:
|
||||
global total_users
|
||||
if session_id not in seen_sessions:
|
||||
seen_sessions.add(session_id)
|
||||
total_users += 1
|
||||
except Exception:
|
||||
# 统计异常不影响主流程
|
||||
pass
|
||||
|
||||
def get_online_status_html():
|
||||
"""生成在线人数显示的HTML"""
|
||||
"""生成在线人数与总人数的显示HTML,并按需触发持久化。"""
|
||||
count = get_active_users_count()
|
||||
# 读取累计人数(读锁即可)
|
||||
try:
|
||||
with stats_lock:
|
||||
total = int(total_users)
|
||||
except Exception:
|
||||
total = 0
|
||||
# 触发间隔写盘
|
||||
_maybe_save_usage_stats()
|
||||
return f"""
|
||||
<div style='display: inline-flex; align-items: center; gap: 8px;
|
||||
<div style='display: inline-flex; align-items: center; gap: 10px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 8px 16px; border-radius: 20px;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);'>
|
||||
@@ -104,6 +189,12 @@ def get_online_status_html():
|
||||
<span style='color: #fff; font-weight: 700; font-size: 1.2em;
|
||||
background: rgba(255,255,255,0.2); padding: 2px 12px;
|
||||
border-radius: 12px; min-width: 30px; text-align: center;'>{count}</span>
|
||||
<span style='opacity: 0.4; color: #fff;'> | </span>
|
||||
<span style='font-size: 1.2em;'>👥</span>
|
||||
<span style='color: white; font-weight: 600; font-size: 1em;'>总人数:</span>
|
||||
<span style='color: #fff; font-weight: 700; font-size: 1.2em;
|
||||
background: rgba(255,255,255,0.2); padding: 2px 12px;
|
||||
border-radius: 12px; min-width: 30px; text-align: center;'>{total}</span>
|
||||
</div>
|
||||
"""
|
||||
# ==================================================
|
||||
@@ -2210,9 +2301,15 @@ with gr.Blocks(title="自动控制理论学习网站 - AI+数智平台", css=cus
|
||||
outputs=[freq_plot_output, freq_metrics_display, freq_tf_display, freq_stability_display, k_number_display_freq, rl_plot_output, rl_poles_display, k_number_display, online_counter]
|
||||
)
|
||||
|
||||
# 页面加载时初始化在线人数
|
||||
# 页面加载时:记录一次活跃,计入总人数,并初始化在线人数
|
||||
def _on_page_load(sid):
|
||||
try:
|
||||
update_user_activity(sid)
|
||||
finally:
|
||||
return (get_online_status_html(),)
|
||||
|
||||
demo.load(
|
||||
fn=lambda sid: (get_online_status_html(),),
|
||||
fn=_on_page_load,
|
||||
inputs=[session_id],
|
||||
outputs=[online_counter]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"total_users": 1,
|
||||
"last_saved_at": 1760886050.8138287
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
autocontrol:
|
||||
build: .
|
||||
container_name: autocontrol-app
|
||||
ports:
|
||||
- "7860:7860"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- PYTHONUNBUFFERED=1
|
||||
# 可选:资源限制
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
# 可选:日志持久化
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
# 健康检查
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:7860"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
Reference in New Issue
Block a user