feat: Implement a comprehensive WebSocket API for file, log, and configuration management, along with new documentation and configuration files.

This commit is contained in:
sladro 2026-02-04 15:18:50 +08:00
parent 23f88858a5
commit 16242e63d8
6 changed files with 670 additions and 28 deletions

View File

@ -0,0 +1,194 @@
# 配置管理功能实现说明
## 功能概述
本次更新为WebSocket API添加了配置管理功能允许前端动态修改CAD文件的基础路径和文件扩展名配置所有修改都会持久化到配置文件中。
## 新增功能
### 1. 获取文件配置 (get_file_config)
获取当前的文件基础路径和扩展名配置。
**WebSocket消息**:
```json
{
"type": "get_file_config"
}
```
**响应**:
```json
{
"type": "info",
"message": "获取文件配置成功",
"data": {
"base_path": "C:\\Users\\Public\\Documents",
"file_extensions": {
"creo": [".prt", ".asm", ".drw"],
"pdms": [".rvm", ".dri"],
"revit": [".rvt", ".rfa", ".rte"]
}
}
}
```
### 2. 设置基础路径 (set_base_path)
修改CAD文件的基础存储路径并持久化。
**WebSocket消息**:
```json
{
"type": "set_base_path",
"base_path": "D:\\CAD_Files"
}
```
**验证规则**:
- 路径必须存在
- 路径必须是有效的目录
- 修改会立即写入配置文件
### 3. 设置文件扩展名 (set_file_extensions)
修改支持的文件扩展名配置并持久化。
**WebSocket消息**:
```json
{
"type": "set_file_extensions",
"file_extensions": {
"creo": [".prt", ".asm", ".drw", ".sec"],
"pdms": [".rvm", ".dri"],
"revit": [".rvt", ".rfa", ".rte"],
"autocad": [".dwg", ".dxf"]
}
}
```
**验证规则**:
- 扩展名配置必须是字典格式
- 每个软件的扩展名必须是数组
- 所有扩展名必须以"."开头
- 修改会立即写入配置文件
## 代码修改说明
### 1. 配置文件修改 (`configs/software_config.yaml`)
添加了`file_extensions`配置项:
```yaml
file_storage:
cad_files_path: "C:\\Users\\Public\\Documents"
# 文件扩展名配置
file_extensions:
creo: [".prt", ".asm", ".drw"]
pdms: [".rvm", ".dri"]
revit: [".rvt", ".rfa", ".rte"]
```
### 2. 配置类扩展 (`app/config.py`)
在`SoftwareConfig`类中新增方法:
- `get_file_extensions()`: 获取文件扩展名配置
- `set_file_extensions(extensions)`: 设置文件扩展名并持久化
- `set_cad_files_path(path)`: 设置CAD文件路径并持久化
- `save_config()`: 保存配置到YAML文件
### 3. 文件管理模块修改 (`app/api/v1/files.py`)
- 将硬编码的`CAD_EXTENSIONS`改为动态获取:`get_cad_extensions()`
- 更新`is_cad_file()`函数,从配置动态读取扩展名
- 更新`get_file_extension()`函数,从配置动态读取扩展名
### 4. WebSocket API扩展 (`app/api/v1/websocket.py`)
在`WSMessageType`类中新增消息类型:
- `GET_FILE_CONFIG = "get_file_config"`
- `SET_BASE_PATH = "set_base_path"`
- `SET_FILE_EXTENSIONS = "set_file_extensions"`
在`handle_client_message()`函数中新增对应的消息处理逻辑。
### 5. API文档更新 (`websocket-file-api-docs.md`)
添加了完整的配置管理功能文档,包括:
- 接口说明
- 请求/响应示例
- JavaScript和React使用示例
- 注意事项
## 使用示例
### JavaScript示例
```javascript
const ws = new WebSocket('ws://localhost:8000/api/v1/ws/connect?user_id=user123');
ws.onopen = () => {
// 获取当前配置
ws.send(JSON.stringify({ type: 'get_file_config' }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log(message);
};
// 修改基础路径
setTimeout(() => {
ws.send(JSON.stringify({
type: 'set_base_path',
base_path: 'D:\\CAD_Files'
}));
}, 1000);
// 修改文件扩展名
setTimeout(() => {
ws.send(JSON.stringify({
type: 'set_file_extensions',
file_extensions: {
creo: ['.prt', '.asm', '.drw'],
pdms: ['.rvm', '.dri'],
revit: ['.rvt', '.rfa', '.rte'],
autocad: ['.dwg', '.dxf']
}
}));
}, 2000);
```
## 注意事项
1. **权限要求**: 修改配置需要对`configs/software_config.yaml`文件有写入权限
2. **配置立即生效**: 修改后立即生效,影响文件列表查询和下载功能
3. **路径验证**: 设置基础路径时会验证路径是否存在且为有效目录
4. **扩展名格式**: 所有扩展名必须以"."开头
5. **配置同步**: 修改配置后,建议调用`get_file_list`重新获取文件列表以查看变更效果
6. **并发修改**: 多个客户端同时修改配置时,以最后一次修改为准
## 测试建议
1. 启动WebSocket服务器
2. 使用WebSocket客户端连接
3. 测试获取配置功能
4. 测试修改基础路径(使用有效和无效路径)
5. 测试修改文件扩展名(使用有效和无效格式)
6. 验证配置文件是否正确更新
7. 验证文件列表是否使用新配置
## 错误处理
所有接口都包含完善的错误处理:
- 参数验证
- 路径验证
- 扩展名格式验证
- 文件写入异常处理
错误会通过WebSocket以`type: "error"`消息返回给客户端。

View File

@ -11,14 +11,13 @@ import zipfile
import io
from app.config import settings, software_config
router = APIRouter()
# CAD文件扩展名映射
CAD_EXTENSIONS = {
'creo': ['.prt', '.asm', '.drw'], # Creo文件包括.prt.1等版本文件
'pdms': ['.rvm', '.dri'], # PDMS文件
'revit': ['.rvt', '.rfa', '.rte'] # Revit文件
}
def get_cad_extensions() -> dict:
"""获取CAD文件扩展名配置"""
return software_config.get_file_extensions()
def get_cad_files_path() -> Path:
@ -36,8 +35,11 @@ def is_cad_file(filename: str) -> bool:
"""判断文件是否为CAD文件包括版本号后缀"""
filename_lower = filename.lower()
# 获取配置的扩展名
cad_extensions = get_cad_extensions()
# 检查所有CAD扩展名
for software, extensions in CAD_EXTENSIONS.items():
for software, extensions in cad_extensions.items():
for ext in extensions:
# 检查标准扩展名
if filename_lower.endswith(ext):
@ -84,8 +86,11 @@ def get_file_extension(filename: str) -> str:
"""获取文件扩展名(包括版本号)"""
filename_lower = filename.lower()
# 获取配置的扩展名
cad_extensions = get_cad_extensions()
# 检查是否有版本号
for software, extensions in CAD_EXTENSIONS.items():
for software, extensions in cad_extensions.items():
for ext in extensions:
if ext in filename_lower:
idx = filename_lower.find(ext)

View File

@ -33,6 +33,10 @@ class WSMessageType:
GET_RENAME_STRATEGIES = "get_rename_strategies"
PREVIEW_RENAME = "preview_rename"
RENAME_FILES = "rename_files"
# 配置管理相关消息类型
SET_BASE_PATH = "set_base_path"
SET_FILE_EXTENSIONS = "set_file_extensions"
GET_FILE_CONFIG = "get_file_config"
router = APIRouter()
@ -130,6 +134,9 @@ async def handle_client_message(message: dict, client_id: str, user_id: str):
- get_rename_strategies: 获取可用的重命名策略列表
- preview_rename: 预览重命名结果
- rename_files: 执行批量重命名
- get_file_config: 获取文件配置基础路径和扩展名
- set_base_path: 设置CAD文件基础路径
- set_file_extensions: 设置文件扩展名配置
"""
from app.core.software_manager import software_manager
from app.core.log_manager import log_manager
@ -762,6 +769,117 @@ async def handle_client_message(message: dict, client_id: str, user_id: str):
"message": f"批量重命名失败: {str(e)}",
"timestamp": websocket_manager._get_timestamp()
}, client_id)
elif message_type == WSMessageType.GET_FILE_CONFIG:
# 获取文件配置(基础路径和扩展名)
try:
from app.config import software_config
base_path = software_config.get_cad_files_path()
file_extensions = software_config.get_file_extensions()
await websocket_manager.send_personal_message({
"type": MessageType.INFO,
"message": "获取文件配置成功",
"data": {
"base_path": base_path,
"file_extensions": file_extensions
},
"timestamp": websocket_manager._get_timestamp()
}, client_id)
except Exception as e:
await websocket_manager.send_personal_message({
"type": MessageType.ERROR,
"message": f"获取文件配置失败: {str(e)}",
"timestamp": websocket_manager._get_timestamp()
}, client_id)
elif message_type == WSMessageType.SET_BASE_PATH:
# 设置CAD文件基础路径
base_path = message.get("base_path")
if not base_path:
await websocket_manager.send_personal_message({
"type": MessageType.ERROR,
"message": "缺少参数: base_path",
"timestamp": websocket_manager._get_timestamp()
}, client_id)
return
try:
from app.config import software_config
from pathlib import Path
# 验证路径是否存在
path = Path(base_path)
if not path.exists():
raise ValueError(f"路径不存在: {base_path}")
if not path.is_dir():
raise ValueError(f"路径不是有效的目录: {base_path}")
# 保存到配置
software_config.set_cad_files_path(base_path)
await websocket_manager.send_personal_message({
"type": MessageType.INFO,
"message": "基础路径设置成功",
"data": {
"base_path": base_path
},
"timestamp": websocket_manager._get_timestamp()
}, client_id)
except Exception as e:
await websocket_manager.send_personal_message({
"type": MessageType.ERROR,
"message": f"设置基础路径失败: {str(e)}",
"timestamp": websocket_manager._get_timestamp()
}, client_id)
elif message_type == WSMessageType.SET_FILE_EXTENSIONS:
# 设置文件扩展名配置
file_extensions = message.get("file_extensions")
if not file_extensions or not isinstance(file_extensions, dict):
await websocket_manager.send_personal_message({
"type": MessageType.ERROR,
"message": "缺少参数: file_extensions (必须是字典对象)",
"timestamp": websocket_manager._get_timestamp()
}, client_id)
return
try:
from app.config import software_config
# 验证扩展名格式
for software, extensions in file_extensions.items():
if not isinstance(extensions, list):
raise ValueError(f"软件 {software} 的扩展名必须是数组")
for ext in extensions:
if not isinstance(ext, str) or not ext.startswith('.'):
raise ValueError(f"扩展名格式错误: {ext},必须以'.'开头")
# 保存到配置
software_config.set_file_extensions(file_extensions)
await websocket_manager.send_personal_message({
"type": MessageType.INFO,
"message": "文件扩展名配置已更新",
"data": {
"file_extensions": file_extensions
},
"timestamp": websocket_manager._get_timestamp()
}, client_id)
except Exception as e:
await websocket_manager.send_personal_message({
"type": MessageType.ERROR,
"message": f"设置文件扩展名失败: {str(e)}",
"timestamp": websocket_manager._get_timestamp()
}, client_id)
else:
# 未知消息类型

View File

@ -93,7 +93,49 @@ class SoftwareConfig:
file_storage = self._config.get('file_storage', {})
return file_storage.get('cad_files_path', r'C:\Users\Public\Documents')
def set_cad_files_path(self, path: str) -> None:
"""设置CAD文件存储路径并持久化"""
if not self._config:
self.load_config()
if 'file_storage' not in self._config:
self._config['file_storage'] = {}
self._config['file_storage']['cad_files_path'] = path
self.save_config()
def get_file_extensions(self) -> dict:
"""获取文件扩展名配置"""
if not self._config:
self.load_config()
file_storage = self._config.get('file_storage', {})
default_extensions = {
'creo': ['.prt', '.asm', '.drw'],
'pdms': ['.rvm', '.dri'],
'revit': ['.rvt', '.rfa', '.rte']
}
return file_storage.get('file_extensions', default_extensions)
def set_file_extensions(self, extensions: dict) -> None:
"""设置文件扩展名配置并持久化"""
if not self._config:
self.load_config()
if 'file_storage' not in self._config:
self._config['file_storage'] = {}
self._config['file_storage']['file_extensions'] = extensions
self.save_config()
def save_config(self) -> None:
"""保存配置到文件"""
try:
with open(self.config_path, 'w', encoding='utf-8') as f:
yaml.dump(self._config, f, allow_unicode=True, default_flow_style=False)
except Exception as e:
raise IOError(f"保存配置文件失败: {e}")
# 创建全局配置实例

View File

@ -1,27 +1,34 @@
# 文件存储路径配置
file_storage:
cad_files_path: "C:\\Users\\Public\\Documents"
cad_files_path: D:\App\vue\Github\serena
file_extensions:
MD:
- .md
software:
creo:
name: "PTC Creo"
executable_path: "C:\\Program Files\\PTC\\Creo 5.0.0.0\\Parametric\\bin\\parametric.exe"
check_process_name:
- xtop.exe
- pro_comm_msg.exe
executable_path: C:\Program Files\PTC\Creo 5.0.0.0\Parametric\bin\parametric.exe
name: PTC Creo
startup_args: []
startup_timeout: 60
check_process_name: ["xtop.exe", "pro_comm_msg.exe"]
stop_timeout: 15
revit:
name: "Autodesk Revit 2017"
executable_path: "C:\\Program Files\\Autodesk\\Revit 2017\\Revit.exe"
startup_args: ["/language", "CHS"]
startup_timeout: 90
check_process_name: "Revit.exe"
pdms:
name: "AVEVA PDMS 12.1 SP4"
executable_path: "C:\\AVEVA\\Plant\\PDMS12.1.SP4\\pdms.bat"
startup_args: ["Design", "noconsole"]
check_process_name:
- des.exe
- PDMSConsole.exe
executable_path: C:\AVEVA\Plant\PDMS12.1.SP4\pdms.bat
name: AVEVA PDMS 12.1 SP4
startup_args:
- Design
- noconsole
startup_timeout: 120
check_process_name: ["des.exe", "PDMSConsole.exe"]
stop_timeout: 20
stop_timeout: 20
revit:
check_process_name: Revit.exe
executable_path: C:\Program Files\Autodesk\Revit 2017\Revit.exe
name: Autodesk Revit 2017
startup_args:
- /language
- CHS
startup_timeout: 90

View File

@ -1005,3 +1005,279 @@ function CADFileRenamer() {
5. **安全性**
- 只能重命名配置目录内的CAD文件
- 路径安全检查防止目录遍历攻击
---
## 配置管理功能
### 7. 获取文件配置
**发送消息**:
```json
{
"type": "get_file_config"
}
```
**响应消息**:
```json
{
"type": "info",
"message": "获取文件配置成功",
"data": {
"base_path": "C:\\Users\\Public\\Documents",
"file_extensions": {
"creo": [".prt", ".asm", ".drw"],
"pdms": [".rvm", ".dri"],
"revit": [".rvt", ".rfa", ".rte"]
}
},
"timestamp": "2026-02-04T14:30:00"
}
```
---
### 8. 设置文件基础路径
**发送消息**:
```json
{
"type": "set_base_path",
"base_path": "D:\\CAD_Files"
}
```
**响应消息(成功)**:
```json
{
"type": "info",
"message": "基础路径设置成功",
"data": {
"base_path": "D:\\CAD_Files"
},
"timestamp": "2026-02-04T14:30:00"
}
```
**响应消息(失败)**:
```json
{
"type": "error",
"message": "设置基础路径失败: 路径不存在: D:\\Invalid_Path",
"timestamp": "2026-02-04T14:30:00"
}
```
---
### 9. 设置文件扩展名
**发送消息**:
```json
{
"type": "set_file_extensions",
"file_extensions": {
"creo": [".prt", ".asm", ".drw", ".sec"],
"pdms": [".rvm", ".dri"],
"revit": [".rvt", ".rfa", ".rte"],
"autocad": [".dwg", ".dxf"]
}
}
```
**响应消息(成功)**:
```json
{
"type": "info",
"message": "文件扩展名配置已更新",
"data": {
"file_extensions": {
"creo": [".prt", ".asm", ".drw", ".sec"],
"pdms": [".rvm", ".dri"],
"revit": [".rvt", ".rfa", ".rte"],
"autocad": [".dwg", ".dxf"]
}
},
"timestamp": "2026-02-04T14:30:00"
}
```
**响应消息(失败)**:
```json
{
"type": "error",
"message": "设置文件扩展名失败: 扩展名格式错误: dwg必须以'.'开头",
"timestamp": "2026-02-04T14:30:00"
}
```
---
## 配置管理功能使用示例
### JavaScript示例
```javascript
// 扩展之前的 CADFileManager 类
class CADFileManager {
// ... 之前的代码 ...
// 获取文件配置
getFileConfig() {
this.ws.send(JSON.stringify({
type: 'get_file_config'
}));
}
// 设置基础路径
setBasePath(basePath) {
this.ws.send(JSON.stringify({
type: 'set_base_path',
base_path: basePath
}));
}
// 设置文件扩展名
setFileExtensions(extensions) {
this.ws.send(JSON.stringify({
type: 'set_file_extensions',
file_extensions: extensions
}));
}
}
// 使用示例
async function configExample() {
const manager = new CADFileManager();
await manager.connect('user123');
// 1. 获取当前配置
manager.getFileConfig();
// 2. 修改基础路径
setTimeout(() => {
manager.setBasePath('D:\\CAD_Files');
}, 1000);
// 3. 修改文件扩展名添加AutoCAD支持
setTimeout(() => {
manager.setFileExtensions({
creo: ['.prt', '.asm', '.drw'],
pdms: ['.rvm', '.dri'],
revit: ['.rvt', '.rfa', '.rte'],
autocad: ['.dwg', '.dxf']
});
}, 2000);
}
```
### React示例
```jsx
function CADConfigManager() {
const [basePath, setBasePath] = useState('');
const [fileExtensions, setFileExtensions] = useState({});
const [newBasePath, setNewBasePath] = useState('');
const wsRef = useRef(null);
useEffect(() => {
const ws = new WebSocket('ws://localhost:8000/api/v1/ws/connect?user_id=user123');
ws.onopen = () => {
// 连接成功后获取当前配置
ws.send(JSON.stringify({ type: 'get_file_config' }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'info' && message.data) {
if (message.data.base_path) {
setBasePath(message.data.base_path);
}
if (message.data.file_extensions) {
setFileExtensions(message.data.file_extensions);
}
}
};
wsRef.current = ws;
return () => ws.close();
}, []);
// 修改基础路径
const handleSetBasePath = () => {
if (wsRef.current && newBasePath) {
wsRef.current.send(JSON.stringify({
type: 'set_base_path',
base_path: newBasePath
}));
}
};
// 修改文件扩展名
const handleSetExtensions = (software, extensions) => {
const newExtensions = {
...fileExtensions,
[software]: extensions
};
if (wsRef.current) {
wsRef.current.send(JSON.stringify({
type: 'set_file_extensions',
file_extensions: newExtensions
}));
}
};
return (
<div style={{ padding: '20px' }}>
<h1>CAD配置管理器</h1>
<div style={{ marginBottom: '20px' }}>
<h2>当前基础路径</h2>
<p>{basePath}</p>
<input
type="text"
placeholder="输入新路径"
value={newBasePath}
onChange={(e) => setNewBasePath(e.target.value)}
/>
<button onClick={handleSetBasePath}>修改路径</button>
</div>
<div>
<h2>当前文件扩展名配置</h2>
<pre>{JSON.stringify(fileExtensions, null, 2)}</pre>
</div>
</div>
);
}
```
---
## 配置管理功能注意事项
1. **路径验证**
- 设置基础路径前会验证路径是否存在
- 路径必须是有效的目录,不能是文件
2. **扩展名格式**
- 所有扩展名必须以`.`开头
- 扩展名配置必须是字典格式,值必须是数组
3. **配置持久化**
- 所有配置修改会立即写入`configs/software_config.yaml`文件
- 配置修改后会立即生效,影响文件列表和下载功能
4. **权限要求**
- 修改配置需要对配置文件有写入权限
- 建议在管理员权限下运行
5. **配置同步**
- 修改配置后,建议重新获取文件列表以查看变更效果
- 多个客户端同时修改配置时,以最后一次修改为准