356 lines
8.7 KiB
Markdown
356 lines
8.7 KiB
Markdown
# 混合架构快速开发模板
|
||
|
||
## 🚀 5 分钟添加新功能
|
||
|
||
### 模板代码
|
||
|
||
复制下面的模板,替换 `YOUR_FEATURE` 为你的功能名称。
|
||
|
||
---
|
||
|
||
## 📝 Step 1: 后端 API(3 层)
|
||
|
||
**文件:** `Server/app/api/v1/jsx_demo.py`
|
||
|
||
```python
|
||
# ==================== 请求/响应模型 ====================
|
||
class YourFeatureRequest(BaseModel):
|
||
"""你的功能请求"""
|
||
param1: str
|
||
param2: int
|
||
|
||
class YourFeatureResult(BaseModel):
|
||
"""你的功能结果"""
|
||
success: bool
|
||
result_data: dict
|
||
message: str
|
||
|
||
# ==================== API 端点 ====================
|
||
@router.post("/your-feature", response_model=YourFeatureResult)
|
||
async def your_feature_endpoint(
|
||
request: YourFeatureRequest,
|
||
x_api_key: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
🔒 服务器端核心计算
|
||
客户端只能拿到结果,看不到算法
|
||
"""
|
||
|
||
# 📝 日志:记录请求
|
||
logger.info("="*60)
|
||
logger.info("📥 收到请求: YOUR_FEATURE")
|
||
logger.info(f" 参数1: {request.param1}")
|
||
logger.info(f" 参数2: {request.param2}")
|
||
logger.info(f" API Key: {x_api_key}")
|
||
logger.info("="*60)
|
||
|
||
# 🔐 API Key 验证
|
||
if not validate_api_key(x_api_key):
|
||
logger.warning(f"❌ API Key 验证失败")
|
||
raise HTTPException(status_code=403, detail="无效的 API Key")
|
||
|
||
key_info = get_key_info(x_api_key)
|
||
logger.info(f"✅ API Key 验证通过 | 名称: {key_info['name']}")
|
||
|
||
try:
|
||
# 🛡️ 输入验证
|
||
logger.info("🛡️ 验证输入参数...")
|
||
if not request.param1 or request.param2 < 0:
|
||
logger.warning("❌ 参数验证失败")
|
||
return YourFeatureResult(
|
||
success=False,
|
||
result_data={},
|
||
message="参数无效"
|
||
)
|
||
logger.info("✅ 参数验证通过")
|
||
|
||
# 🔒 核心算法(客户端看不到)
|
||
logger.info("🔒 开始执行核心算法...")
|
||
|
||
# ===== 在这里写你的核心逻辑 =====
|
||
result = {
|
||
"output1": f"处理结果: {request.param1}",
|
||
"output2": request.param2 * 2
|
||
}
|
||
# ================================
|
||
|
||
logger.info(f"✅ 计算完成: {result}")
|
||
|
||
# 📤 返回结果
|
||
logger.info("="*60)
|
||
return YourFeatureResult(
|
||
success=True,
|
||
result_data=result,
|
||
message="处理成功"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 处理失败: {str(e)}")
|
||
return YourFeatureResult(
|
||
success=False,
|
||
result_data={},
|
||
message=f"处理失败: {str(e)}"
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Step 2: 前端 API(2 层)
|
||
|
||
**文件:** `Designer/src/api/jsxApi/inline/your-feature.ts`
|
||
|
||
```typescript
|
||
/**
|
||
* YOUR_FEATURE 功能
|
||
* 混合方案:本地执行 + 服务器计算
|
||
*/
|
||
|
||
import { evalInlineJSX, JSXResponse } from './utils';
|
||
import { config } from '@/config';
|
||
|
||
export async function yourFeatureFunction(
|
||
param1: string,
|
||
param2: number
|
||
): Promise<JSXResponse> {
|
||
try {
|
||
// 1. 💻 【可选】本地获取 PS 数据
|
||
const getDataJsx = `
|
||
try {
|
||
if (!$.global.JSXUtils.hasDocument()) {
|
||
return $.global.JSXUtils.stringify({ error: '没有打开的文档' });
|
||
}
|
||
|
||
var doc = $.global.JSXUtils.getDocument();
|
||
|
||
// 获取你需要的数据
|
||
var layerName = doc.activeLayer ? doc.activeLayer.name : '';
|
||
|
||
return $.global.JSXUtils.stringify({
|
||
success: true,
|
||
layerName: layerName
|
||
});
|
||
} catch (error) {
|
||
return $.global.JSXUtils.stringify({ error: error.toString() });
|
||
}
|
||
`;
|
||
|
||
const psData = await evalInlineJSX(getDataJsx);
|
||
|
||
if (psData.error) {
|
||
return psData;
|
||
}
|
||
|
||
// 2. 🌐 发送到服务器计算(核心算法)
|
||
const response = await fetch(`${config.apiBaseUrl}/jsx_demo/your-feature`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-API-Key': 'demo_key_123' // 🔐 API Key
|
||
},
|
||
body: JSON.stringify({
|
||
param1: param1,
|
||
param2: param2
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
return { error: '服务器错误' };
|
||
}
|
||
|
||
const serverResult = await response.json();
|
||
|
||
if (!serverResult.success) {
|
||
return { error: serverResult.message };
|
||
}
|
||
|
||
// 3. 💻 【可选】使用服务器结果执行 PS 操作
|
||
const { output1, output2 } = serverResult.result_data;
|
||
|
||
const applyJsx = `
|
||
try {
|
||
var doc = $.global.JSXUtils.getDocument();
|
||
|
||
// 使用服务器计算的结果
|
||
// 示例:创建文本图层显示结果
|
||
var textLayer = doc.artLayers.add();
|
||
textLayer.kind = LayerKind.TEXT;
|
||
textLayer.name = "${output1}";
|
||
|
||
return $.global.JSXUtils.stringify({
|
||
success: true,
|
||
message: '操作完成'
|
||
});
|
||
} catch (error) {
|
||
return $.global.JSXUtils.stringify({ error: error.toString() });
|
||
}
|
||
`;
|
||
|
||
return evalInlineJSX(applyJsx);
|
||
|
||
} catch (error) {
|
||
return { error: String(error) };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Step 3: UI 界面(2 层)
|
||
|
||
**文件:** `Designer/src/view/Home.vue`
|
||
|
||
```vue
|
||
<template>
|
||
<a-button type="primary" @click="handleYourFeature">
|
||
你的功能
|
||
</a-button>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { yourFeatureFunction } from '@/api/jsxApi/inline/your-feature';
|
||
import { Message } from '@arco-design/web-vue';
|
||
|
||
const handleYourFeature = async () => {
|
||
try {
|
||
Message.loading('处理中...');
|
||
|
||
// 调用混合 API
|
||
const res = await yourFeatureFunction('测试参数', 100);
|
||
|
||
if (res && res.success) {
|
||
Message.success(res.message || '操作成功');
|
||
} else {
|
||
Message.error(res?.error || '执行失败');
|
||
}
|
||
} catch (e: any) {
|
||
Message.error('调用失败: ' + e.message);
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 开发流程
|
||
|
||
### 本地测试
|
||
```bash
|
||
# Terminal 1: 启动后端
|
||
cd Server
|
||
python -m uvicorn app.main:app --reload
|
||
|
||
# Terminal 2: 启动前端
|
||
cd Designer
|
||
npm run dev
|
||
```
|
||
|
||
### 发布新版本
|
||
```bash
|
||
# 自动构建、打包、发布
|
||
python auto_deploy_core.py
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 检查清单
|
||
|
||
开发新功能时,确保:
|
||
|
||
- [ ] 后端添加了 API Key 验证
|
||
- [ ] 后端添加了详细日志
|
||
- [ ] 后端添加了输入验证
|
||
- [ ] 前端使用了正确的 API Key
|
||
- [ ] 前端添加了错误处理
|
||
- [ ] UI 有加载提示和错误提示
|
||
- [ ] 测试了成功和失败的情况
|
||
- [ ] 更新了版本号
|
||
|
||
---
|
||
|
||
## 🎯 三种常见模式
|
||
|
||
### 模式 1:纯服务器计算
|
||
```
|
||
前端输入 → 服务器计算 → 前端显示结果
|
||
(不涉及 PS 操作)
|
||
```
|
||
|
||
### 模式 2:服务器计算 + PS 应用
|
||
```
|
||
前端获取 PS 数据 → 服务器计算 → 前端应用到 PS
|
||
(当前示例)
|
||
```
|
||
|
||
### 模式 3:本地执行 + 服务器验证
|
||
```
|
||
前端执行操作 → 服务器验证权限 → 前端继续
|
||
(需要权限控制的操作)
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 快速参考
|
||
|
||
### 后端日志模板
|
||
```python
|
||
logger.info("="*60)
|
||
logger.info("📥 收到请求")
|
||
logger.info("✅ 验证通过")
|
||
logger.info("🔒 开始计算")
|
||
logger.info("✅ 计算完成")
|
||
logger.info("="*60)
|
||
```
|
||
|
||
### 前端错误处理模板
|
||
```typescript
|
||
try {
|
||
Message.loading('处理中...');
|
||
const res = await yourFunction();
|
||
if (res?.success) {
|
||
Message.success(res.message);
|
||
} else {
|
||
Message.error(res?.error || '失败');
|
||
}
|
||
} catch (e: any) {
|
||
Message.error('调用失败: ' + e.message);
|
||
}
|
||
```
|
||
|
||
### JSX 模板
|
||
```typescript
|
||
const jsx = `
|
||
try {
|
||
if (!$.global.JSXUtils.hasDocument()) {
|
||
return $.global.JSXUtils.stringify({ error: '没有打开的文档' });
|
||
}
|
||
|
||
var doc = $.global.JSXUtils.getDocument();
|
||
|
||
// 你的 PS 操作代码
|
||
|
||
return $.global.JSXUtils.stringify({
|
||
success: true,
|
||
message: '成功'
|
||
});
|
||
} catch (error) {
|
||
return $.global.JSXUtils.stringify({ error: error.toString() });
|
||
}
|
||
`;
|
||
|
||
return evalInlineJSX(jsx);
|
||
```
|
||
|
||
---
|
||
|
||
## 🎉 完成!
|
||
|
||
现在你可以:
|
||
1. 复制模板代码
|
||
2. 替换功能名称
|
||
3. 填写核心逻辑
|
||
4. 测试
|
||
5. 发布
|
||
|
||
**预计开发时间:5-15 分钟/功能** ⚡
|
||
|