From 16242e63d85888f7b6fda768f6c761c9d0f57f73 Mon Sep 17 00:00:00 2001 From: sladro Date: Wed, 4 Feb 2026 15:18:50 +0800 Subject: [PATCH] feat: Implement a comprehensive WebSocket API for file, log, and configuration management, along with new documentation and configuration files. --- CONFIG_MANAGEMENT_FEATURE.md | 194 ++++++++++++++++++++++++ app/api/v1/files.py | 21 ++- app/api/v1/websocket.py | 118 +++++++++++++++ app/config.py | 44 +++++- configs/software_config.yaml | 45 +++--- websocket-file-api-docs.md | 276 +++++++++++++++++++++++++++++++++++ 6 files changed, 670 insertions(+), 28 deletions(-) create mode 100644 CONFIG_MANAGEMENT_FEATURE.md diff --git a/CONFIG_MANAGEMENT_FEATURE.md b/CONFIG_MANAGEMENT_FEATURE.md new file mode 100644 index 0000000..731693d --- /dev/null +++ b/CONFIG_MANAGEMENT_FEATURE.md @@ -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"`消息返回给客户端。 diff --git a/app/api/v1/files.py b/app/api/v1/files.py index 28ba336..758d18b 100644 --- a/app/api/v1/files.py +++ b/app/api/v1/files.py @@ -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) diff --git a/app/api/v1/websocket.py b/app/api/v1/websocket.py index 01eaa51..9262e4a 100644 --- a/app/api/v1/websocket.py +++ b/app/api/v1/websocket.py @@ -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: # 未知消息类型 diff --git a/app/config.py b/app/config.py index 3c44596..9df79a6 100644 --- a/app/config.py +++ b/app/config.py @@ -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}") # 创建全局配置实例 diff --git a/configs/software_config.yaml b/configs/software_config.yaml index b42ecf5..920829f 100644 --- a/configs/software_config.yaml +++ b/configs/software_config.yaml @@ -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 \ No newline at end of file + 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 diff --git a/websocket-file-api-docs.md b/websocket-file-api-docs.md index 4b359e5..fa011b3 100644 --- a/websocket-file-api-docs.md +++ b/websocket-file-api-docs.md @@ -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 ( +
+

CAD配置管理器

+ +
+

当前基础路径

+

{basePath}

+ + setNewBasePath(e.target.value)} + /> + +
+ +
+

当前文件扩展名配置

+
{JSON.stringify(fileExtensions, null, 2)}
+
+
+ ); +} +``` + +--- + +## 配置管理功能注意事项 + +1. **路径验证** + - 设置基础路径前会验证路径是否存在 + - 路径必须是有效的目录,不能是文件 + +2. **扩展名格式** + - 所有扩展名必须以`.`开头 + - 扩展名配置必须是字典格式,值必须是数组 + +3. **配置持久化** + - 所有配置修改会立即写入`configs/software_config.yaml`文件 + - 配置修改后会立即生效,影响文件列表和下载功能 + +4. **权限要求** + - 修改配置需要对配置文件有写入权限 + - 建议在管理员权限下运行 + +5. **配置同步** + - 修改配置后,建议重新获取文件列表以查看变更效果 + - 多个客户端同时修改配置时,以最后一次修改为准 +