1284 lines
29 KiB
Markdown
1284 lines
29 KiB
Markdown
# WebSocket 文件管理API文档
|
||
|
||
## 概述
|
||
通过WebSocket连接实现CAD文件的列表查询和下载功能。支持Creo、PDMS、Revit三种CAD软件的文件格式。
|
||
|
||
## WebSocket连接
|
||
|
||
**连接地址**: `ws://localhost:8000/api/v1/ws/connect`
|
||
|
||
**连接参数**:
|
||
- `client_id`: 客户端ID(可选,不提供会自动生成)
|
||
- `user_id`: 用户ID(可选)
|
||
|
||
**连接示例**:
|
||
```javascript
|
||
const ws = new WebSocket('ws://localhost:8000/api/v1/ws/connect?user_id=user123');
|
||
```
|
||
|
||
---
|
||
|
||
## 支持的文件格式
|
||
|
||
### 📐 Creo 文件
|
||
- `.prt` - 零件文件(Part)
|
||
- `.asm` - 装配文件(Assembly)
|
||
- `.drw` - 工程图文件(Drawing)
|
||
- **特别支持版本号后缀**:`.prt.1`, `.prt.2`, `.asm.1` 等
|
||
|
||
### 🏭 PDMS 文件
|
||
- `.rvm` - 3D模型文件
|
||
- `.dri` - 图纸文件
|
||
|
||
### 🏗️ Revit 文件
|
||
- `.rvt` - Revit项目文件
|
||
- `.rfa` - Revit族文件(Family)
|
||
- `.rte` - Revit模板文件
|
||
|
||
---
|
||
|
||
## WebSocket消息格式
|
||
|
||
### 1. 获取CAD文件列表
|
||
|
||
**发送消息**:
|
||
```json
|
||
{
|
||
"type": "get_file_list"
|
||
}
|
||
```
|
||
|
||
**响应消息**:
|
||
```json
|
||
{
|
||
"type": "info",
|
||
"message": "获取文件列表成功",
|
||
"data": {
|
||
"base_path": "C:\\Users\\Public\\Documents",
|
||
"total_count": 15,
|
||
"files": [
|
||
{
|
||
"filename": "part1.prt.1",
|
||
"relative_path": "creo/part1.prt.1",
|
||
"absolute_path": "C:\\Users\\Public\\Documents\\creo\\part1.prt.1",
|
||
"size": 1024000,
|
||
"modified_time": 1706950800.0,
|
||
"extension": ".prt.1"
|
||
},
|
||
{
|
||
"filename": "building.rvt",
|
||
"relative_path": "revit/building.rvt",
|
||
"absolute_path": "C:\\Users\\Public\\Documents\\revit\\building.rvt",
|
||
"size": 5120000,
|
||
"modified_time": 1706951000.0,
|
||
"extension": ".rvt"
|
||
}
|
||
]
|
||
},
|
||
"timestamp": "2026-02-04T10:30:00"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 获取单个文件下载链接
|
||
|
||
**发送消息**:
|
||
```json
|
||
{
|
||
"type": "download_file",
|
||
"file_path": "creo/part1.prt.1"
|
||
}
|
||
```
|
||
|
||
**响应消息**:
|
||
```json
|
||
{
|
||
"type": "info",
|
||
"message": "文件下载链接已生成",
|
||
"data": {
|
||
"file_path": "creo/part1.prt.1",
|
||
"filename": "part1.prt.1",
|
||
"download_url": "/api/v1/files/download/creo/part1.prt.1",
|
||
"file_size": 1024000
|
||
},
|
||
"timestamp": "2026-02-04T10:30:00"
|
||
}
|
||
```
|
||
|
||
**使用下载链接**:
|
||
```javascript
|
||
// 获取到download_url后,使用HTTP GET请求下载
|
||
const downloadUrl = `http://localhost:8000${data.download_url}`;
|
||
window.open(downloadUrl, '_blank');
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 获取批量下载链接
|
||
|
||
**发送消息**:
|
||
```json
|
||
{
|
||
"type": "download_batch",
|
||
"file_paths": [
|
||
"creo/part1.prt.1",
|
||
"creo/assembly.asm",
|
||
"revit/building.rvt"
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应消息**:
|
||
```json
|
||
{
|
||
"type": "info",
|
||
"message": "批量下载链接已生成",
|
||
"data": {
|
||
"file_count": 3,
|
||
"file_paths": [
|
||
"creo/part1.prt.1",
|
||
"creo/assembly.asm",
|
||
"revit/building.rvt"
|
||
],
|
||
"download_url": "/api/v1/files/download/batch",
|
||
"method": "POST",
|
||
"note": "请使用POST方法,将file_paths作为JSON数组发送到此URL"
|
||
},
|
||
"timestamp": "2026-02-04T10:30:00"
|
||
}
|
||
```
|
||
|
||
**使用批量下载**:
|
||
```javascript
|
||
// 使用fetch API进行POST请求
|
||
const response = await fetch('http://localhost:8000/api/v1/files/download/batch', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(data.file_paths)
|
||
});
|
||
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'cad_files.zip';
|
||
a.click();
|
||
```
|
||
|
||
---
|
||
|
||
## 完整前端示例
|
||
|
||
### JavaScript/WebSocket
|
||
|
||
```javascript
|
||
class CADFileManager {
|
||
constructor(serverUrl = 'ws://localhost:8000/api/v1/ws/connect') {
|
||
this.ws = null;
|
||
this.serverUrl = serverUrl;
|
||
this.httpBaseUrl = 'http://localhost:8000';
|
||
}
|
||
|
||
// 连接WebSocket
|
||
connect(userId = 'user123') {
|
||
return new Promise((resolve, reject) => {
|
||
this.ws = new WebSocket(`${this.serverUrl}?user_id=${userId}`);
|
||
|
||
this.ws.onopen = () => {
|
||
console.log('WebSocket连接成功');
|
||
resolve();
|
||
};
|
||
|
||
this.ws.onerror = (error) => {
|
||
console.error('WebSocket错误:', error);
|
||
reject(error);
|
||
};
|
||
|
||
this.ws.onmessage = (event) => {
|
||
const message = JSON.parse(event.data);
|
||
this.handleMessage(message);
|
||
};
|
||
});
|
||
}
|
||
|
||
// 处理服务器消息
|
||
handleMessage(message) {
|
||
console.log('收到消息:', message);
|
||
|
||
if (message.type === 'info' && message.data) {
|
||
// 处理不同类型的响应
|
||
if (message.data.files) {
|
||
this.onFileListReceived(message.data);
|
||
} else if (message.data.download_url) {
|
||
this.onDownloadUrlReceived(message.data);
|
||
}
|
||
} else if (message.type === 'error') {
|
||
console.error('错误:', message.message);
|
||
}
|
||
}
|
||
|
||
// 获取文件列表
|
||
getFileList() {
|
||
this.ws.send(JSON.stringify({
|
||
type: 'get_file_list'
|
||
}));
|
||
}
|
||
|
||
// 文件列表回调(可以被覆盖)
|
||
onFileListReceived(data) {
|
||
console.log('文件列表:', data.files);
|
||
console.log('总数:', data.total_count);
|
||
}
|
||
|
||
// 请求单个文件下载
|
||
requestDownload(filePath) {
|
||
this.ws.send(JSON.stringify({
|
||
type: 'download_file',
|
||
file_path: filePath
|
||
}));
|
||
}
|
||
|
||
// 下载URL回调
|
||
onDownloadUrlReceived(data) {
|
||
const fullUrl = `${this.httpBaseUrl}${data.download_url}`;
|
||
console.log('下载链接:', fullUrl);
|
||
|
||
// 自动触发下载
|
||
window.open(fullUrl, '_blank');
|
||
}
|
||
|
||
// 请求批量下载
|
||
async requestBatchDownload(filePaths) {
|
||
// 批量下载直接使用HTTP POST
|
||
const response = await fetch(`${this.httpBaseUrl}/api/v1/files/download/batch`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(filePaths)
|
||
});
|
||
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'cad_files.zip';
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
}
|
||
|
||
// 断开连接
|
||
disconnect() {
|
||
if (this.ws) {
|
||
this.ws.close();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
async function main() {
|
||
const manager = new CADFileManager();
|
||
|
||
// 连接
|
||
await manager.connect('user123');
|
||
|
||
// 自定义文件列表回调
|
||
manager.onFileListReceived = (data) => {
|
||
console.log(`找到 ${data.total_count} 个文件`);
|
||
data.files.forEach(file => {
|
||
console.log(`- ${file.filename} (${(file.size / 1024).toFixed(2)} KB)`);
|
||
});
|
||
};
|
||
|
||
// 获取文件列表
|
||
manager.getFileList();
|
||
|
||
// 下载单个文件
|
||
setTimeout(() => {
|
||
manager.requestDownload('creo/part1.prt.1');
|
||
}, 2000);
|
||
|
||
// 批量下载
|
||
setTimeout(async () => {
|
||
await manager.requestBatchDownload([
|
||
'creo/part1.prt.1',
|
||
'revit/building.rvt'
|
||
]);
|
||
}, 4000);
|
||
}
|
||
|
||
main();
|
||
```
|
||
|
||
---
|
||
|
||
### React示例
|
||
|
||
```jsx
|
||
import React, { useState, useEffect, useRef } from 'react';
|
||
|
||
function CADFileManager() {
|
||
const [files, setFiles] = useState([]);
|
||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||
const [connected, setConnected] = useState(false);
|
||
const wsRef = useRef(null);
|
||
|
||
useEffect(() => {
|
||
// 连接WebSocket
|
||
const ws = new WebSocket('ws://localhost:8000/api/v1/ws/connect?user_id=user123');
|
||
|
||
ws.onopen = () => {
|
||
console.log('WebSocket连接成功');
|
||
setConnected(true);
|
||
|
||
// 连接成功后立即获取文件列表
|
||
ws.send(JSON.stringify({ type: 'get_file_list' }));
|
||
};
|
||
|
||
ws.onmessage = (event) => {
|
||
const message = JSON.parse(event.data);
|
||
|
||
if (message.type === 'info' && message.data?.files) {
|
||
setFiles(message.data.files);
|
||
} else if (message.type === 'info' && message.data?.download_url) {
|
||
// 自动打开下载链接
|
||
window.open(`http://localhost:8000${message.data.download_url}`, '_blank');
|
||
}
|
||
};
|
||
|
||
wsRef.current = ws;
|
||
|
||
return () => {
|
||
ws.close();
|
||
};
|
||
}, []);
|
||
|
||
// 刷新文件列表
|
||
const refreshFileList = () => {
|
||
if (wsRef.current && connected) {
|
||
wsRef.current.send(JSON.stringify({ type: 'get_file_list' }));
|
||
}
|
||
};
|
||
|
||
// 下载单个文件
|
||
const downloadFile = (filePath) => {
|
||
if (wsRef.current && connected) {
|
||
wsRef.current.send(JSON.stringify({
|
||
type: 'download_file',
|
||
file_path: filePath
|
||
}));
|
||
}
|
||
};
|
||
|
||
// 批量下载
|
||
const downloadBatch = async () => {
|
||
const response = await fetch('http://localhost:8000/api/v1/files/download/batch', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(selectedFiles)
|
||
});
|
||
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'cad_files.zip';
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
};
|
||
|
||
// 切换文件选择
|
||
const toggleFileSelection = (filePath) => {
|
||
setSelectedFiles(prev =>
|
||
prev.includes(filePath)
|
||
? prev.filter(f => f !== filePath)
|
||
: [...prev, filePath]
|
||
);
|
||
};
|
||
|
||
return (
|
||
<div style={{ padding: '20px' }}>
|
||
<h1>CAD文件管理器</h1>
|
||
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<span>连接状态: {connected ? '✅ 已连接' : '❌ 未连接'}</span>
|
||
<button onClick={refreshFileList} style={{ marginLeft: '10px' }}>
|
||
刷新列表
|
||
</button>
|
||
<button
|
||
onClick={downloadBatch}
|
||
disabled={selectedFiles.length === 0}
|
||
style={{ marginLeft: '10px' }}
|
||
>
|
||
批量下载 ({selectedFiles.length})
|
||
</button>
|
||
</div>
|
||
|
||
<div>
|
||
<h2>文件列表 (共 {files.length} 个)</h2>
|
||
<table border="1" cellPadding="8" style={{ width: '100%' }}>
|
||
<thead>
|
||
<tr>
|
||
<th>选择</th>
|
||
<th>文件名</th>
|
||
<th>路径</th>
|
||
<th>大小</th>
|
||
<th>扩展名</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{files.map(file => (
|
||
<tr key={file.absolute_path}>
|
||
<td>
|
||
<input
|
||
type="checkbox"
|
||
checked={selectedFiles.includes(file.relative_path)}
|
||
onChange={() => toggleFileSelection(file.relative_path)}
|
||
/>
|
||
</td>
|
||
<td>{file.filename}</td>
|
||
<td>{file.relative_path}</td>
|
||
<td>{(file.size / 1024).toFixed(2)} KB</td>
|
||
<td>{file.extension}</td>
|
||
<td>
|
||
<button onClick={() => downloadFile(file.relative_path)}>
|
||
下载
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default CADFileManager;
|
||
```
|
||
|
||
---
|
||
|
||
## 错误处理
|
||
|
||
**错误响应格式**:
|
||
```json
|
||
{
|
||
"type": "error",
|
||
"message": "错误描述",
|
||
"timestamp": "2026-02-04T10:30:00"
|
||
}
|
||
```
|
||
|
||
**常见错误**:
|
||
- `"缺少参数: file_path"` - 未提供文件路径
|
||
- `"缺少参数: file_paths (必须是数组)"` - 批量下载参数格式错误
|
||
- `"访问被拒绝"` - 文件路径不安全
|
||
- `"文件不存在"` - 请求的文件不存在
|
||
- `"只能下载CAD文件"` - 尝试下载非CAD格式文件
|
||
- `"获取文件列表失败"` - 扫描文件系统失败
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
1. **WebSocket用于控制,HTTP用于下载**
|
||
- 文件列表通过WebSocket返回
|
||
- 文件下载使用HTTP接口(因为WebSocket不适合传输大文件)
|
||
|
||
2. **路径安全**
|
||
- 所有文件路径都会进行安全检查
|
||
- 只能访问配置目录内的文件
|
||
|
||
3. **文件格式限制**
|
||
- 只能下载Creo、PDMS、Revit格式的文件
|
||
- 自动识别带版本号的Creo文件
|
||
|
||
4. **批量下载**
|
||
- 文件打包为ZIP格式
|
||
- 保持原有目录结构
|
||
- 自动跳过不存在或不安全的文件
|
||
|
||
5. **配置路径**
|
||
- 默认路径:`C:\Users\Public\Documents`
|
||
- 可在 `configs/software_config.yaml` 中修改
|
||
|
||
---
|
||
|
||
## 文件批量重命名功能
|
||
|
||
### 4. 获取重命名策略列表
|
||
|
||
**发送消息**:
|
||
```json
|
||
{
|
||
"type": "get_rename_strategies"
|
||
}
|
||
```
|
||
|
||
**响应消息**:
|
||
```json
|
||
{
|
||
"type": "info",
|
||
"message": "获取重命名策略成功",
|
||
"data": {
|
||
"strategies": [
|
||
{
|
||
"name": "add_prefix",
|
||
"description": "在文件名前添加前缀"
|
||
},
|
||
{
|
||
"name": "add_suffix",
|
||
"description": "在扩展名前添加后缀"
|
||
},
|
||
{
|
||
"name": "sequence",
|
||
"description": "使用序号重命名文件"
|
||
},
|
||
{
|
||
"name": "replace_text",
|
||
"description": "替换文件名中的文本"
|
||
},
|
||
{
|
||
"name": "add_datetime",
|
||
"description": "添加日期时间戳"
|
||
},
|
||
{
|
||
"name": "change_case",
|
||
"description": "转换文件名大小写"
|
||
}
|
||
],
|
||
"total_count": 6
|
||
},
|
||
"timestamp": "2026-02-04T11:10:00"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 预览重命名结果
|
||
|
||
**发送消息**:
|
||
```json
|
||
{
|
||
"type": "preview_rename",
|
||
"file_paths": [
|
||
"creo/part1.prt.1",
|
||
"creo/assembly.asm",
|
||
"revit/building.rvt"
|
||
],
|
||
"strategy": "add_prefix",
|
||
"params": {
|
||
"prefix": "NEW_"
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应消息**:
|
||
```json
|
||
{
|
||
"type": "info",
|
||
"message": "重命名预览生成成功",
|
||
"data": {
|
||
"strategy": "add_prefix",
|
||
"params": {
|
||
"prefix": "NEW_"
|
||
},
|
||
"preview": [
|
||
{
|
||
"original": "part1.prt.1",
|
||
"new": "NEW_part1.prt.1",
|
||
"success": true
|
||
},
|
||
{
|
||
"original": "assembly.asm",
|
||
"new": "NEW_assembly.asm",
|
||
"success": true
|
||
},
|
||
{
|
||
"original": "building.rvt",
|
||
"new": "NEW_building.rvt",
|
||
"success": true
|
||
}
|
||
],
|
||
"total_count": 3
|
||
},
|
||
"timestamp": "2026-02-04T11:10:00"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6. 执行批量重命名
|
||
|
||
**发送消息**:
|
||
```json
|
||
{
|
||
"type": "rename_files",
|
||
"file_paths": [
|
||
"creo/part1.prt.1",
|
||
"creo/assembly.asm"
|
||
],
|
||
"strategy": "sequence",
|
||
"params": {
|
||
"base_name": "component",
|
||
"start_number": 1,
|
||
"digits": 3,
|
||
"separator": "_"
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应消息**:
|
||
```json
|
||
{
|
||
"type": "info",
|
||
"message": "批量重命名完成: 成功 2 个, 失败 0 个",
|
||
"data": {
|
||
"strategy": "sequence",
|
||
"params": {
|
||
"base_name": "component",
|
||
"start_number": 1,
|
||
"digits": 3,
|
||
"separator": "_"
|
||
},
|
||
"success_count": 2,
|
||
"failed_count": 0,
|
||
"results": [
|
||
{
|
||
"original_path": "creo/part1.prt.1",
|
||
"original_name": "part1.prt.1",
|
||
"new_name": "component_001.prt.1",
|
||
"new_path": "creo/component_001.prt.1",
|
||
"success": true
|
||
},
|
||
{
|
||
"original_path": "creo/assembly.asm",
|
||
"original_name": "assembly.asm",
|
||
"new_name": "component_002.asm",
|
||
"new_path": "creo/component_002.asm",
|
||
"success": true
|
||
}
|
||
]
|
||
},
|
||
"timestamp": "2026-02-04T11:10:00"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 重命名策略参数说明
|
||
|
||
### 1. add_prefix(添加前缀)
|
||
```json
|
||
{
|
||
"strategy": "add_prefix",
|
||
"params": {
|
||
"prefix": "NEW_"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. add_suffix(添加后缀)
|
||
```json
|
||
{
|
||
"strategy": "add_suffix",
|
||
"params": {
|
||
"suffix": "_backup"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. sequence(序号重命名)
|
||
```json
|
||
{
|
||
"strategy": "sequence",
|
||
"params": {
|
||
"base_name": "file",
|
||
"start_number": 1,
|
||
"digits": 3,
|
||
"separator": "_"
|
||
}
|
||
}
|
||
```
|
||
- `base_name`: 基础文件名
|
||
- `start_number`: 起始序号(默认1)
|
||
- `digits`: 序号位数(默认3,如001)
|
||
- `separator`: 分隔符(默认下划线)
|
||
|
||
### 4. replace_text(文本替换)
|
||
```json
|
||
{
|
||
"strategy": "replace_text",
|
||
"params": {
|
||
"search": "old",
|
||
"replace": "new",
|
||
"case_sensitive": true
|
||
}
|
||
}
|
||
```
|
||
- `search`: 要搜索的文本
|
||
- `replace`: 替换为的文本
|
||
- `case_sensitive`: 是否区分大小写(默认true)
|
||
|
||
### 5. add_datetime(添加日期时间)
|
||
```json
|
||
{
|
||
"strategy": "add_datetime",
|
||
"params": {
|
||
"format": "%Y%m%d_%H%M%S",
|
||
"position": "suffix",
|
||
"separator": "_"
|
||
}
|
||
}
|
||
```
|
||
- `format`: 日期时间格式(默认:`%Y%m%d_%H%M%S`)
|
||
- `position`: 位置,`prefix`或`suffix`(默认suffix)
|
||
- `separator`: 分隔符(默认下划线)
|
||
|
||
### 6. change_case(大小写转换)
|
||
```json
|
||
{
|
||
"strategy": "change_case",
|
||
"params": {
|
||
"case_type": "lower"
|
||
}
|
||
}
|
||
```
|
||
- `case_type`: 转换类型,可选值:`lower`(小写)、`upper`(大写)、`title`(标题格式)
|
||
|
||
---
|
||
|
||
## 重命名功能使用示例
|
||
|
||
### JavaScript示例
|
||
|
||
```javascript
|
||
// 扩展之前的 CADFileManager 类
|
||
class CADFileManager {
|
||
// ... 之前的代码 ...
|
||
|
||
// 获取重命名策略列表
|
||
getRenameStrategies() {
|
||
this.ws.send(JSON.stringify({
|
||
type: 'get_rename_strategies'
|
||
}));
|
||
}
|
||
|
||
// 预览重命名
|
||
previewRename(filePaths, strategy, params) {
|
||
this.ws.send(JSON.stringify({
|
||
type: 'preview_rename',
|
||
file_paths: filePaths,
|
||
strategy: strategy,
|
||
params: params
|
||
}));
|
||
}
|
||
|
||
// 执行批量重命名
|
||
renameFiles(filePaths, strategy, params) {
|
||
this.ws.send(JSON.stringify({
|
||
type: 'rename_files',
|
||
file_paths: filePaths,
|
||
strategy: strategy,
|
||
params: params
|
||
}));
|
||
}
|
||
}
|
||
|
||
// 使用示例
|
||
async function renameExample() {
|
||
const manager = new CADFileManager();
|
||
await manager.connect('user123');
|
||
|
||
// 1. 获取策略列表
|
||
manager.getRenameStrategies();
|
||
|
||
// 2. 预览重命名(添加前缀)
|
||
setTimeout(() => {
|
||
manager.previewRename(
|
||
['creo/part1.prt.1', 'creo/assembly.asm'],
|
||
'add_prefix',
|
||
{ prefix: 'V2_' }
|
||
);
|
||
}, 1000);
|
||
|
||
// 3. 执行重命名(序号重命名)
|
||
setTimeout(() => {
|
||
manager.renameFiles(
|
||
['creo/part1.prt.1', 'creo/part2.prt.1'],
|
||
'sequence',
|
||
{
|
||
base_name: 'component',
|
||
start_number: 1,
|
||
digits: 3,
|
||
separator: '_'
|
||
}
|
||
);
|
||
}, 3000);
|
||
}
|
||
```
|
||
|
||
### React示例
|
||
|
||
```jsx
|
||
function CADFileRenamer() {
|
||
const [files, setFiles] = useState([]);
|
||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||
const [strategies, setStrategies] = useState([]);
|
||
const [selectedStrategy, setSelectedStrategy] = useState('');
|
||
const [renameParams, setRenameParams] = useState({});
|
||
const [previewResults, setPreviewResults] = 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_list' }));
|
||
ws.send(JSON.stringify({ type: 'get_rename_strategies' }));
|
||
};
|
||
|
||
ws.onmessage = (event) => {
|
||
const message = JSON.parse(event.data);
|
||
|
||
if (message.type === 'info') {
|
||
if (message.data?.files) {
|
||
setFiles(message.data.files);
|
||
} else if (message.data?.strategies) {
|
||
setStrategies(message.data.strategies);
|
||
} else if (message.data?.preview) {
|
||
setPreviewResults(message.data.preview);
|
||
} else if (message.data?.results) {
|
||
// 重命名完成,刷新文件列表
|
||
ws.send(JSON.stringify({ type: 'get_file_list' }));
|
||
setPreviewResults([]);
|
||
setSelectedFiles([]);
|
||
}
|
||
}
|
||
};
|
||
|
||
wsRef.current = ws;
|
||
return () => ws.close();
|
||
}, []);
|
||
|
||
// 预览重命名
|
||
const handlePreview = () => {
|
||
if (wsRef.current && selectedFiles.length > 0 && selectedStrategy) {
|
||
wsRef.current.send(JSON.stringify({
|
||
type: 'preview_rename',
|
||
file_paths: selectedFiles,
|
||
strategy: selectedStrategy,
|
||
params: renameParams
|
||
}));
|
||
}
|
||
};
|
||
|
||
// 执行重命名
|
||
const handleRename = () => {
|
||
if (wsRef.current && selectedFiles.length > 0 && selectedStrategy) {
|
||
if (confirm(`确定要重命名 ${selectedFiles.length} 个文件吗?`)) {
|
||
wsRef.current.send(JSON.stringify({
|
||
type: 'rename_files',
|
||
file_paths: selectedFiles,
|
||
strategy: selectedStrategy,
|
||
params: renameParams
|
||
}));
|
||
}
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div style={{ padding: '20px' }}>
|
||
<h1>CAD文件批量重命名</h1>
|
||
|
||
{/* 策略选择 */}
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<label>重命名策略: </label>
|
||
<select
|
||
value={selectedStrategy}
|
||
onChange={(e) => setSelectedStrategy(e.target.value)}
|
||
>
|
||
<option value="">请选择策略</option>
|
||
{strategies.map(s => (
|
||
<option key={s.name} value={s.name}>{s.description}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
{/* 参数输入(根据策略动态显示) */}
|
||
{selectedStrategy === 'add_prefix' && (
|
||
<div>
|
||
<label>前缀: </label>
|
||
<input
|
||
type="text"
|
||
value={renameParams.prefix || ''}
|
||
onChange={(e) => setRenameParams({...renameParams, prefix: e.target.value})}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* 操作按钮 */}
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<button onClick={handlePreview} disabled={selectedFiles.length === 0}>
|
||
预览重命名
|
||
</button>
|
||
<button onClick={handleRename} disabled={selectedFiles.length === 0} style={{ marginLeft: '10px' }}>
|
||
执行重命名
|
||
</button>
|
||
</div>
|
||
|
||
{/* 预览结果 */}
|
||
{previewResults.length > 0 && (
|
||
<div style={{ marginBottom: '20px', padding: '10px', background: '#f0f0f0' }}>
|
||
<h3>预览结果</h3>
|
||
{previewResults.map((result, idx) => (
|
||
<div key={idx}>
|
||
{result.original} → {result.new}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* 文件列表 */}
|
||
<h2>文件列表</h2>
|
||
<table border="1" cellPadding="8">
|
||
<thead>
|
||
<tr>
|
||
<th>选择</th>
|
||
<th>文件名</th>
|
||
<th>路径</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{files.map(file => (
|
||
<tr key={file.absolute_path}>
|
||
<td>
|
||
<input
|
||
type="checkbox"
|
||
checked={selectedFiles.includes(file.relative_path)}
|
||
onChange={(e) => {
|
||
if (e.target.checked) {
|
||
setSelectedFiles([...selectedFiles, file.relative_path]);
|
||
} else {
|
||
setSelectedFiles(selectedFiles.filter(f => f !== file.relative_path));
|
||
}
|
||
}}
|
||
/>
|
||
</td>
|
||
<td>{file.filename}</td>
|
||
<td>{file.relative_path}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 重命名功能注意事项
|
||
|
||
1. **文件名冲突处理**
|
||
- 如果重命名后的文件名已存在,系统会自动添加数字后缀(如 `file_1`, `file_2`)
|
||
- 最多尝试1000次,避免无限循环
|
||
|
||
2. **扩展名保留**
|
||
- 所有重命名策略都会保留原有的文件扩展名
|
||
- 支持带版本号的扩展名(如 `.prt.1`, `.prt.2`)
|
||
|
||
3. **预览功能**
|
||
- 建议在执行重命名前先使用预览功能
|
||
- 预览不会修改实际文件,只返回重命名结果
|
||
|
||
4. **错误处理**
|
||
- 批量重命名时,单个文件失败不会影响其他文件
|
||
- 返回结果中会详细说明每个文件的成功/失败状态
|
||
|
||
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. **配置同步**
|
||
- 修改配置后,建议重新获取文件列表以查看变更效果
|
||
- 多个客户端同时修改配置时,以最后一次修改为准
|
||
|