重构Dify客户端:改为通过FastAPI代理并使用JWT认证
主要变更: - 修改 dify-client.server.ts 使用 JWT 认证通过 FastAPI 后端代理访问 Dify API - 所有 Dify API 路由(chat-messages, parameters, conversations, messages)添加 JWT 获取和传递逻辑 - API_URL 从直连 Dify 改为 FastAPI 后端的 /dify 路由 - 增强 JWT 认证失败的错误处理(返回401状态码) - 添加详细的日志输出,便于调试 安全提升: - DIFY_API_KEY 从前端移至后端,不再暴露在客户端代码 - 使用统一的 JWT 认证体系,提高系统安全性 文档: - 新增 dify-proxy-backend-integration.md - 后端对接文档(包含完整 FastAPI 实现示例) - 新增 dify-frontend-modification-summary.md - 前端修改总结 - 新增 CLAUDE.md - 项目架构说明文档 影响范围: - app/services/dify-client.server.ts - 核心服务层 - app/routes/api.chat-messages.tsx - 聊天消息 - app/routes/api.parameters.tsx - 应用参数 - app/routes/api.conversations.tsx - 会话列表 - app/routes/api.messages.tsx - 消息历史 - app/routes/api.conversations.$id.tsx - 删除会话 - app/routes/api.conversations.$id.name.tsx - 重命名会话 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
# Dify 客户端 JWT 认证改造 - 前端修改总结
|
||||
|
||||
## 📅 修改信息
|
||||
- **修改日期**: 2025-01-XX
|
||||
- **版本**: v2.0 - JWT 认证版本
|
||||
- **修改类型**: 架构升级 - 从直连改为代理模式
|
||||
|
||||
---
|
||||
|
||||
## 🎯 修改目标
|
||||
|
||||
将 Dify AI 服务调用从 **前端直连** 改为 **通过 FastAPI 后端代理**,并使用 **JWT 认证**替代原有的 API KEY。
|
||||
|
||||
---
|
||||
|
||||
## 📋 核心变更
|
||||
|
||||
### **架构变更**
|
||||
```
|
||||
旧: 前端 → Dify API (使用 API_KEY)
|
||||
新: 前端 → FastAPI 后端 (使用 JWT) → Dify API (使用 API_KEY)
|
||||
```
|
||||
|
||||
### **认证方式变更**
|
||||
```
|
||||
旧: Authorization: Bearer {DIFY_API_KEY}
|
||||
新: Authorization: Bearer {frontendJWT}
|
||||
```
|
||||
|
||||
### **API 端点变更**
|
||||
```
|
||||
旧: https://api.dify.ai/v1/chat-messages
|
||||
新: http://172.16.0.55:8000/dify/chat-messages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 修改的文件清单
|
||||
|
||||
### **1. 核心服务层** (1 个文件)
|
||||
- ✅ `app/services/dify-client.server.ts` - Dify 客户端核心
|
||||
|
||||
**主要修改**:
|
||||
- 导入 `API_BASE_URL` 从配置文件
|
||||
- `DIFY_CONFIG.API_URL` 改为 `${API_BASE_URL}/dify`
|
||||
- `difyFetch` 函数添加 `jwt` 参数
|
||||
- 所有 client 方法添加 `jwt?` 参数
|
||||
- 添加 401 错误处理(JWT 认证失败)
|
||||
|
||||
### **2. API 路由层** (6 个文件)
|
||||
所有路由都添加了 JWT 获取、验证和传递逻辑:
|
||||
|
||||
- ✅ `app/routes/api.chat-messages.tsx` - 聊天消息发送
|
||||
- ✅ `app/routes/api.parameters.tsx` - 应用参数获取
|
||||
- ✅ `app/routes/api.conversations.tsx` - 会话列表获取
|
||||
- ✅ `app/routes/api.messages.tsx` - 会话消息历史
|
||||
- ✅ `app/routes/api.conversations.$id.tsx` - 会话删除
|
||||
- ✅ `app/routes/api.conversations.$id.name.tsx` - 会话重命名
|
||||
|
||||
**统一修改模式**:
|
||||
```typescript
|
||||
// 1. 获取 JWT
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 2. JWT 验证
|
||||
if (!frontendJWT) {
|
||||
return json({ error: 'JWT认证失败,请重新登录' }, { status: 401 });
|
||||
}
|
||||
|
||||
// 3. 传递给 difyClient
|
||||
await difyClient.method(..., frontendJWT);
|
||||
|
||||
// 4. 错误处理
|
||||
const status = error.message?.includes('JWT认证失败') ? 401 : 500;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 配置迁移说明
|
||||
|
||||
### **前端配置(已废弃)**
|
||||
```bash
|
||||
# .env - 这些配置不再使用
|
||||
NEXT_PUBLIC_APP_ID=http://nas.7bm.co:12980/app/46539478-3281-4e98-a445-6da9dc078e95/configuration
|
||||
NEXT_PUBLIC_APP_KEY=app-N3su9tKyMMnqxt2EMgOkVof7
|
||||
```
|
||||
|
||||
### **后端配置(需要添加)**
|
||||
**这些配置应该移到 FastAPI 后端**:
|
||||
```python
|
||||
# FastAPI 环境变量配置
|
||||
DIFY_API_URL = "http://nas.7bm.co:12980/v1"
|
||||
DIFY_API_KEY = "app-N3su9tKyMMnqxt2EMgOkVof7"
|
||||
DIFY_APP_ID = "46539478-3281-4e98-a445-6da9dc078e95"
|
||||
```
|
||||
|
||||
### **前端新配置**
|
||||
```bash
|
||||
# .env - 前端只需要配置 FastAPI 地址
|
||||
API_BASE_URL=http://172.16.0.55:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛣️ 后端需要实现的路由
|
||||
|
||||
| 路由 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/dify/parameters` | GET | 获取应用参数 |
|
||||
| `/dify/conversations` | GET | 获取会话列表 |
|
||||
| `/dify/messages` | GET | 获取会话消息历史 |
|
||||
| `/dify/chat-messages` | POST | 发送聊天消息(支持流式) |
|
||||
| `/dify/conversations/{id}/name` | POST | 重命名会话 |
|
||||
| `/dify/conversations/{id}` | DELETE | 删除会话 |
|
||||
| `/dify/messages/{id}/feedbacks` | POST | 消息反馈 |
|
||||
|
||||
**详细对接文档**: `docs/dify-proxy-backend-integration.md`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键注意事项
|
||||
|
||||
### **1. JWT 认证必须实现**
|
||||
- 所有请求都携带 `Authorization: Bearer {JWT}`
|
||||
- JWT 验证失败必须返回 **401 状态码**
|
||||
- 错误格式:`{"error": "JWT认证失败,请重新登录"}`
|
||||
|
||||
### **2. 流式响应必须支持**
|
||||
- `/dify/chat-messages` 接口支持流式响应(SSE)
|
||||
- 响应头:`Content-Type: text/event-stream`
|
||||
- 不能缓冲,必须实时转发
|
||||
|
||||
### **3. 配置安全**
|
||||
- `DIFY_API_KEY` 只能存在后端
|
||||
- 前端代码中不再包含任何 Dify 凭据
|
||||
- 所有敏感配置通过环境变量管理
|
||||
|
||||
### **4. 错误处理**
|
||||
- JWT 不存在: **401**
|
||||
- JWT 过期/无效: **401**
|
||||
- 其他错误: **500**
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试清单
|
||||
|
||||
### **前端测试**
|
||||
- [ ] 聊天消息发送(流式)
|
||||
- [ ] 聊天消息发送(非流式)
|
||||
- [ ] 会话列表加载
|
||||
- [ ] 会话切换和消息历史
|
||||
- [ ] 会话重命名
|
||||
- [ ] 会话删除
|
||||
- [ ] JWT 过期后跳转登录
|
||||
|
||||
### **后端测试**
|
||||
- [ ] JWT 验证逻辑
|
||||
- [ ] 所有 API 路由返回正确
|
||||
- [ ] 流式响应正常工作
|
||||
- [ ] 错误返回正确状态码
|
||||
- [ ] 日志记录完整
|
||||
|
||||
---
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
- **修改文件数**: 7 个
|
||||
- **新增代码行数**: ~150 行
|
||||
- **修改代码行数**: ~200 行
|
||||
- **核心逻辑变更**: 认证方式 + API 端点
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署步骤
|
||||
|
||||
### **1. 后端准备**
|
||||
```bash
|
||||
# 1. 在 FastAPI 中实现 /dify/* 路由
|
||||
# 2. 配置 Dify API 凭据
|
||||
# 3. 集成 JWT 验证逻辑
|
||||
# 4. 测试所有接口
|
||||
```
|
||||
|
||||
### **2. 前端部署**
|
||||
```bash
|
||||
# 1. 确认后端已就绪
|
||||
npm run build
|
||||
npm run start
|
||||
```
|
||||
|
||||
### **3. 联调测试**
|
||||
```bash
|
||||
# 1. 测试聊天功能
|
||||
# 2. 测试会话管理
|
||||
# 3. 测试 JWT 过期处理
|
||||
# 4. 检查日志输出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 代码示例
|
||||
|
||||
### **前端调用示例**
|
||||
```typescript
|
||||
// app/routes/api.chat-messages.tsx
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
const response = await difyClient.createChatMessage(
|
||||
inputs,
|
||||
query,
|
||||
user,
|
||||
responseMode,
|
||||
conversationId,
|
||||
files,
|
||||
frontendJWT // 传递 JWT
|
||||
);
|
||||
```
|
||||
|
||||
### **后端实现示例**
|
||||
```python
|
||||
# FastAPI 路由
|
||||
@app.post("/dify/chat-messages")
|
||||
async def create_chat_message(
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
# 1. 验证 JWT
|
||||
jwt_token = authorization.replace("Bearer ", "")
|
||||
user_info = verify_jwt(jwt_token)
|
||||
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="JWT认证失败")
|
||||
|
||||
# 2. 调用 Dify API
|
||||
headers = {"Authorization": f"Bearer {DIFY_API_KEY}"}
|
||||
body = await request.json()
|
||||
|
||||
# 3. 转发流式响应
|
||||
if body.get("response_mode") == "streaming":
|
||||
return StreamingResponse(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 支持与文档
|
||||
|
||||
### **详细文档**
|
||||
- **后端对接文档**: `docs/dify-proxy-backend-integration.md`
|
||||
- **JWT 实现文档**: `docs/JWT_IMPLEMENTATION.md`
|
||||
- **CLAUDE.md**: 项目总体架构说明
|
||||
|
||||
### **相关链接**
|
||||
- [Dify 官方文档](https://docs.dify.ai/)
|
||||
- [FastAPI 文档](https://fastapi.tiangolo.com/)
|
||||
- [JWT 规范](https://jwt.io/)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 检查清单
|
||||
|
||||
### **前端(已完成)**
|
||||
- [x] dify-client.server.ts 修改
|
||||
- [x] 所有 API 路由添加 JWT
|
||||
- [x] 错误处理完善
|
||||
- [x] 日志输出优化
|
||||
- [x] 配置迁移说明
|
||||
|
||||
### **后端(待实现)**
|
||||
- [ ] /dify/* 路由实现
|
||||
- [ ] JWT 验证集成
|
||||
- [ ] 流式响应支持
|
||||
- [ ] 错误处理规范
|
||||
- [ ] 日志记录完善
|
||||
- [ ] CORS 配置(如需)
|
||||
|
||||
### **联调测试(待完成)**
|
||||
- [ ] 基础功能测试
|
||||
- [ ] JWT 认证测试
|
||||
- [ ] 流式响应测试
|
||||
- [ ] 错误处理测试
|
||||
- [ ] 性能测试
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
本次修改完成了 Dify 服务调用的架构升级:
|
||||
- ✅ **安全性提升**: API KEY 不再暴露在前端
|
||||
- ✅ **统一认证**: 使用项目统一的 JWT 认证体系
|
||||
- ✅ **便于管理**: 所有 Dify 配置集中在后端
|
||||
- ✅ **向后兼容**: 保留了原有的 API 接口设计
|
||||
|
||||
**下一步**: 等待后端实现完成后进行联调测试。
|
||||
|
||||
---
|
||||
|
||||
**修改完成日期**: 2025-01-XX
|
||||
**文档版本**: v1.0
|
||||
@@ -0,0 +1,821 @@
|
||||
# Dify 代理服务后端对接文档
|
||||
|
||||
## 📋 文档说明
|
||||
|
||||
本文档描述了前端 Remix 应用如何通过 FastAPI 后端代理访问 Dify AI 服务,以及后端需要实现的接口规范。
|
||||
|
||||
**更新时间**: 2025-01-XX
|
||||
**前端修改版本**: v2.0 - JWT 认证版本
|
||||
|
||||
---
|
||||
|
||||
## 🎯 架构变更
|
||||
|
||||
### **旧架构**
|
||||
```
|
||||
前端 Remix App → 直接调用 Dify API (使用 API KEY)
|
||||
```
|
||||
|
||||
### **新架构**
|
||||
```
|
||||
前端 Remix App → FastAPI 后端 /dify 路由 (使用 JWT) → Dify API (使用 API KEY)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 认证流程说明
|
||||
|
||||
### **前端侧**
|
||||
1. 用户登录后获得 `frontendJWT` (前端JWT)
|
||||
2. 所有 Dify 相关请求携带 `Authorization: Bearer {frontendJWT}`
|
||||
3. JWT 由 OAuth2.0 + 自定义签名生成,包含用户信息
|
||||
|
||||
### **后端侧**
|
||||
1. 接收前端请求,验证 `frontendJWT` 是否有效
|
||||
2. JWT 验证通过后,使用配置的 `DIFY_API_KEY` 调用真正的 Dify API
|
||||
3. 将 Dify API 的响应原样返回给前端
|
||||
|
||||
### **关键配置迁移**
|
||||
|
||||
前端原本使用的配置:
|
||||
```bash
|
||||
# .env (前端配置 - 已不再使用)
|
||||
NEXT_PUBLIC_APP_ID=http://nas.7bm.co:12980/app/46539478-3281-4e98-a445-6da9dc078e95/configuration
|
||||
NEXT_PUBLIC_APP_KEY=app-N3su9tKyMMnqxt2EMgOkVof7
|
||||
```
|
||||
|
||||
**现在这两个配置应该移到 FastAPI 后端**:
|
||||
```python
|
||||
# FastAPI 配置 (后端配置)
|
||||
DIFY_API_URL = "http://nas.7bm.co:12980/v1" # Dify API 基础URL
|
||||
DIFY_API_KEY = "app-N3su9tKyMMnqxt2EMgOkVof7" # Dify API Key
|
||||
DIFY_APP_ID = "46539478-3281-4e98-a445-6da9dc078e95" # Dify App ID
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛣️ API 路由规范
|
||||
|
||||
### **基础路由前缀**
|
||||
```
|
||||
http://{FASTAPI_HOST}:{PORT}/dify
|
||||
```
|
||||
|
||||
### **需要实现的路由列表**
|
||||
|
||||
| 路由 | 方法 | 说明 | 前端调用频率 |
|
||||
|------|------|------|--------------|
|
||||
| `/dify/parameters` | GET | 获取应用参数 | 初始化时 |
|
||||
| `/dify/conversations` | GET | 获取会话列表 | 每次打开聊天 |
|
||||
| `/dify/messages` | GET | 获取会话消息历史 | 切换会话时 |
|
||||
| `/dify/chat-messages` | POST | 发送聊天消息 | 用户发送消息时 |
|
||||
| `/dify/conversations/{id}/name` | POST | 重命名会话 | 用户重命名时 |
|
||||
| `/dify/conversations/{id}` | DELETE | 删除会话 | 用户删除时 |
|
||||
| `/dify/messages/{id}/feedbacks` | POST | 消息反馈(点赞/点踩) | 用户反馈时 |
|
||||
|
||||
---
|
||||
|
||||
## 📡 详细接口规范
|
||||
|
||||
### **1. 获取应用参数**
|
||||
|
||||
#### 前端请求
|
||||
```http
|
||||
GET http://{FASTAPI_HOST}/dify/parameters
|
||||
Authorization: Bearer {frontendJWT}
|
||||
```
|
||||
|
||||
#### 后端处理逻辑
|
||||
```python
|
||||
@app.get("/dify/parameters")
|
||||
async def get_parameters(
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
# 1. 验证 JWT
|
||||
jwt_token = authorization.replace("Bearer ", "")
|
||||
user_info = verify_jwt(jwt_token) # 你们的JWT验证函数
|
||||
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录")
|
||||
|
||||
# 2. 调用 Dify API
|
||||
headers = {
|
||||
"Authorization": f"Bearer {DIFY_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
f"{DIFY_API_URL}/parameters",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
# 3. 返回 Dify 响应
|
||||
return response.json()
|
||||
```
|
||||
|
||||
#### Dify API 响应示例
|
||||
```json
|
||||
{
|
||||
"opening_statement": "你好!我是AI助手...",
|
||||
"suggested_questions": ["问题1", "问题2"],
|
||||
"speech_to_text": { "enabled": false },
|
||||
"retriever_resource": { "enabled": false },
|
||||
"annotation_reply": { "enabled": false },
|
||||
"user_input_form": [],
|
||||
"file_upload": {
|
||||
"image": {
|
||||
"enabled": false,
|
||||
"number_limits": 3,
|
||||
"transfer_methods": ["remote_url", "local_file"]
|
||||
}
|
||||
},
|
||||
"system_parameters": {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2. 获取会话列表**
|
||||
|
||||
#### 前端请求
|
||||
```http
|
||||
GET http://{FASTAPI_HOST}/dify/conversations?user={user_id}&limit=100&first_id=
|
||||
Authorization: Bearer {frontendJWT}
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
- `user`: 用户标识(从 JWT 中可提取)
|
||||
- `limit`: 返回数量限制(默认100)
|
||||
- `first_id`: 分页游标(可选)
|
||||
|
||||
#### 后端处理逻辑
|
||||
```python
|
||||
@app.get("/dify/conversations")
|
||||
async def get_conversations(
|
||||
user: str,
|
||||
limit: int = 100,
|
||||
first_id: str = "",
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
# 1. 验证 JWT
|
||||
jwt_token = authorization.replace("Bearer ", "")
|
||||
user_info = verify_jwt(jwt_token)
|
||||
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录")
|
||||
|
||||
# 2. 调用 Dify API
|
||||
headers = {
|
||||
"Authorization": f"Bearer {DIFY_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
params = {
|
||||
"user": user,
|
||||
"limit": limit,
|
||||
"first_id": first_id
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
f"{DIFY_API_URL}/conversations",
|
||||
headers=headers,
|
||||
params=params
|
||||
)
|
||||
|
||||
# 3. 返回 Dify 响应
|
||||
return response.json()
|
||||
```
|
||||
|
||||
#### Dify API 响应示例
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "conv-123456",
|
||||
"name": "会话标题",
|
||||
"inputs": {},
|
||||
"status": "normal",
|
||||
"introduction": "",
|
||||
"created_at": 1234567890,
|
||||
"updated_at": 1234567890
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"limit": 100
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. 获取会话消息历史**
|
||||
|
||||
#### 前端请求
|
||||
```http
|
||||
GET http://{FASTAPI_HOST}/dify/messages?user={user_id}&conversation_id={conv_id}&limit=20&last_id=
|
||||
Authorization: Bearer {frontendJWT}
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
- `user`: 用户标识
|
||||
- `conversation_id`: 会话ID(必填)
|
||||
- `limit`: 返回数量限制(默认20)
|
||||
- `last_id`: 分页游标(可选)
|
||||
|
||||
#### 后端处理逻辑
|
||||
```python
|
||||
@app.get("/dify/messages")
|
||||
async def get_messages(
|
||||
user: str,
|
||||
conversation_id: str,
|
||||
limit: int = 20,
|
||||
last_id: str = "",
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
# 验证 JWT 和调用 Dify API(同上)
|
||||
# ...
|
||||
|
||||
params = {
|
||||
"user": user,
|
||||
"conversation_id": conversation_id,
|
||||
"limit": limit,
|
||||
"last_id": last_id
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
f"{DIFY_API_URL}/messages",
|
||||
headers=headers,
|
||||
params=params
|
||||
)
|
||||
|
||||
return response.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **4. 发送聊天消息(流式响应)⭐**
|
||||
|
||||
#### 前端请求
|
||||
```http
|
||||
POST http://{FASTAPI_HOST}/dify/chat-messages
|
||||
Authorization: Bearer {frontendJWT}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"inputs": {},
|
||||
"query": "用户的问题",
|
||||
"user": "user_app-id:session-id",
|
||||
"response_mode": "streaming",
|
||||
"conversation_id": "conv-123456",
|
||||
"files": []
|
||||
}
|
||||
```
|
||||
|
||||
**请求体参数**:
|
||||
- `inputs`: 输入变量(对象)
|
||||
- `query`: 用户消息内容(必填)
|
||||
- `user`: 用户标识(必填)
|
||||
- `response_mode`: 响应模式,`"streaming"` 或 `"blocking"`
|
||||
- `conversation_id`: 会话ID(可选,不传则创建新会话)
|
||||
- `files`: 上传的文件列表(可选)
|
||||
|
||||
#### 后端处理逻辑(重要!)
|
||||
|
||||
```python
|
||||
from fastapi import StreamingResponse
|
||||
import httpx
|
||||
|
||||
@app.post("/dify/chat-messages")
|
||||
async def create_chat_message(
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
# 1. 验证 JWT
|
||||
jwt_token = authorization.replace("Bearer ", "")
|
||||
user_info = verify_jwt(jwt_token)
|
||||
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录")
|
||||
|
||||
# 2. 获取请求体
|
||||
body = await request.json()
|
||||
response_mode = body.get("response_mode", "streaming")
|
||||
|
||||
# 3. 调用 Dify API
|
||||
headers = {
|
||||
"Authorization": f"Bearer {DIFY_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 4. 根据响应模式处理
|
||||
if response_mode == "streaming":
|
||||
# 流式响应 - 直接转发流
|
||||
async def stream_response():
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with client.stream(
|
||||
"POST",
|
||||
f"{DIFY_API_URL}/chat-messages",
|
||||
headers=headers,
|
||||
json=body,
|
||||
timeout=60.0
|
||||
) as response:
|
||||
async for chunk in response.aiter_bytes():
|
||||
yield chunk
|
||||
|
||||
return StreamingResponse(
|
||||
stream_response(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
)
|
||||
else:
|
||||
# 非流式响应
|
||||
response = requests.post(
|
||||
f"{DIFY_API_URL}/chat-messages",
|
||||
headers=headers,
|
||||
json=body
|
||||
)
|
||||
return response.json()
|
||||
```
|
||||
|
||||
#### Dify API 流式响应格式
|
||||
```
|
||||
data: {"event": "message", "message_id": "msg-123", "conversation_id": "conv-123", "answer": "你好"}
|
||||
|
||||
data: {"event": "message", "message_id": "msg-123", "conversation_id": "conv-123", "answer": "!"}
|
||||
|
||||
data: {"event": "message_end", "id": "msg-123", "metadata": {...}}
|
||||
|
||||
data: {"event": "workflow_finished", "data": {...}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **5. 重命名会话**
|
||||
|
||||
#### 前端请求
|
||||
```http
|
||||
POST http://{FASTAPI_HOST}/dify/conversations/{conversation_id}/name
|
||||
Authorization: Bearer {frontendJWT}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "新的会话名称",
|
||||
"auto_generate": false,
|
||||
"user": "user_app-id:session-id"
|
||||
}
|
||||
```
|
||||
|
||||
#### 后端处理逻辑
|
||||
```python
|
||||
@app.post("/dify/conversations/{conversation_id}/name")
|
||||
async def rename_conversation(
|
||||
conversation_id: str,
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
# 验证 JWT
|
||||
jwt_token = authorization.replace("Bearer ", "")
|
||||
user_info = verify_jwt(jwt_token)
|
||||
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录")
|
||||
|
||||
# 获取请求体
|
||||
body = await request.json()
|
||||
|
||||
# 调用 Dify API
|
||||
headers = {
|
||||
"Authorization": f"Bearer {DIFY_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{DIFY_API_URL}/conversations/{conversation_id}/name",
|
||||
headers=headers,
|
||||
json=body
|
||||
)
|
||||
|
||||
return response.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. 删除会话**
|
||||
|
||||
#### 前端请求
|
||||
```http
|
||||
DELETE http://{FASTAPI_HOST}/dify/conversations/{conversation_id}
|
||||
Authorization: Bearer {frontendJWT}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"user": "user_app-id:session-id"
|
||||
}
|
||||
```
|
||||
|
||||
#### 后端处理逻辑
|
||||
```python
|
||||
@app.delete("/dify/conversations/{conversation_id}")
|
||||
async def delete_conversation(
|
||||
conversation_id: str,
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
# 验证 JWT
|
||||
jwt_token = authorization.replace("Bearer ", "")
|
||||
user_info = verify_jwt(jwt_token)
|
||||
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录")
|
||||
|
||||
# 获取请求体
|
||||
body = await request.json()
|
||||
|
||||
# 调用 Dify API
|
||||
headers = {
|
||||
"Authorization": f"Bearer {DIFY_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.delete(
|
||||
f"{DIFY_API_URL}/conversations/{conversation_id}",
|
||||
headers=headers,
|
||||
json=body
|
||||
)
|
||||
|
||||
return response.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **7. 消息反馈(点赞/点踩)**
|
||||
|
||||
#### 前端请求
|
||||
```http
|
||||
POST http://{FASTAPI_HOST}/dify/messages/{message_id}/feedbacks
|
||||
Authorization: Bearer {frontendJWT}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"rating": "like",
|
||||
"user": "user_app-id:session-id"
|
||||
}
|
||||
```
|
||||
|
||||
**请求体参数**:
|
||||
- `rating`: 评价类型,`"like"` | `"dislike"` | `null`
|
||||
- `user`: 用户标识
|
||||
|
||||
---
|
||||
|
||||
## 🔧 完整后端实现示例(FastAPI)
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, Header, HTTPException, Request
|
||||
from fastapi.responses import StreamingResponse
|
||||
import httpx
|
||||
import requests
|
||||
from typing import Optional
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# 配置
|
||||
DIFY_API_URL = "http://nas.7bm.co:12980/v1"
|
||||
DIFY_API_KEY = "app-N3su9tKyMMnqxt2EMgOkVof7"
|
||||
DIFY_APP_ID = "46539478-3281-4e98-a445-6da9dc078e95"
|
||||
|
||||
# JWT 验证函数(使用你们现有的JWT验证逻辑)
|
||||
def verify_jwt(token: str) -> Optional[dict]:
|
||||
"""
|
||||
验证 JWT Token
|
||||
返回: 用户信息字典,如果验证失败返回 None
|
||||
"""
|
||||
try:
|
||||
# 这里使用你们现有的 JWT 验证逻辑
|
||||
# payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
|
||||
# return payload
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"JWT 验证失败: {e}")
|
||||
return None
|
||||
|
||||
# 通用 Dify 请求包装器
|
||||
async def dify_request(
|
||||
method: str,
|
||||
endpoint: str,
|
||||
authorization: str,
|
||||
params: dict = None,
|
||||
json_data: dict = None,
|
||||
stream: bool = False
|
||||
):
|
||||
"""通用 Dify API 请求处理"""
|
||||
# 验证 JWT
|
||||
jwt_token = authorization.replace("Bearer ", "") if authorization else None
|
||||
if not jwt_token:
|
||||
raise HTTPException(status_code=401, detail="缺少认证令牌")
|
||||
|
||||
user_info = verify_jwt(jwt_token)
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录")
|
||||
|
||||
# 构建 Dify 请求头
|
||||
headers = {
|
||||
"Authorization": f"Bearer {DIFY_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
url = f"{DIFY_API_URL}/{endpoint.lstrip('/')}"
|
||||
|
||||
if stream:
|
||||
# 流式响应
|
||||
async def stream_response():
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with client.stream(
|
||||
method,
|
||||
url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
json=json_data,
|
||||
timeout=60.0
|
||||
) as response:
|
||||
async for chunk in response.aiter_bytes():
|
||||
yield chunk
|
||||
|
||||
return StreamingResponse(
|
||||
stream_response(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive"
|
||||
}
|
||||
)
|
||||
else:
|
||||
# 普通响应
|
||||
response = requests.request(
|
||||
method,
|
||||
url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
json=json_data
|
||||
)
|
||||
|
||||
if response.status_code >= 400:
|
||||
raise HTTPException(
|
||||
status_code=response.status_code,
|
||||
detail=response.text
|
||||
)
|
||||
|
||||
return response.json()
|
||||
|
||||
# 路由实现
|
||||
@app.get("/dify/parameters")
|
||||
async def get_parameters(authorization: str = Header(None)):
|
||||
return await dify_request("GET", "/parameters", authorization)
|
||||
|
||||
@app.get("/dify/conversations")
|
||||
async def get_conversations(
|
||||
user: str,
|
||||
limit: int = 100,
|
||||
first_id: str = "",
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
params = {"user": user, "limit": limit, "first_id": first_id}
|
||||
return await dify_request("GET", "/conversations", authorization, params=params)
|
||||
|
||||
@app.get("/dify/messages")
|
||||
async def get_messages(
|
||||
user: str,
|
||||
conversation_id: str,
|
||||
limit: int = 20,
|
||||
last_id: str = "",
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
params = {
|
||||
"user": user,
|
||||
"conversation_id": conversation_id,
|
||||
"limit": limit,
|
||||
"last_id": last_id
|
||||
}
|
||||
return await dify_request("GET", "/messages", authorization, params=params)
|
||||
|
||||
@app.post("/dify/chat-messages")
|
||||
async def create_chat_message(
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
body = await request.json()
|
||||
response_mode = body.get("response_mode", "streaming")
|
||||
stream = (response_mode == "streaming")
|
||||
|
||||
return await dify_request(
|
||||
"POST",
|
||||
"/chat-messages",
|
||||
authorization,
|
||||
json_data=body,
|
||||
stream=stream
|
||||
)
|
||||
|
||||
@app.post("/dify/conversations/{conversation_id}/name")
|
||||
async def rename_conversation(
|
||||
conversation_id: str,
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
body = await request.json()
|
||||
return await dify_request(
|
||||
"POST",
|
||||
f"/conversations/{conversation_id}/name",
|
||||
authorization,
|
||||
json_data=body
|
||||
)
|
||||
|
||||
@app.delete("/dify/conversations/{conversation_id}")
|
||||
async def delete_conversation(
|
||||
conversation_id: str,
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
body = await request.json()
|
||||
return await dify_request(
|
||||
"DELETE",
|
||||
f"/conversations/{conversation_id}",
|
||||
authorization,
|
||||
json_data=body
|
||||
)
|
||||
|
||||
@app.post("/dify/messages/{message_id}/feedbacks")
|
||||
async def message_feedback(
|
||||
message_id: str,
|
||||
request: Request,
|
||||
authorization: str = Header(None)
|
||||
):
|
||||
body = await request.json()
|
||||
return await dify_request(
|
||||
"POST",
|
||||
f"/messages/{message_id}/feedbacks",
|
||||
authorization,
|
||||
json_data=body
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要注意事项
|
||||
|
||||
### **1. JWT 验证**
|
||||
- 必须验证 JWT 的签名和过期时间
|
||||
- JWT 验证失败时返回 **401 状态码**
|
||||
- 错误信息格式:`{"error": "JWT认证失败,请重新登录"}`
|
||||
|
||||
### **2. 流式响应处理**
|
||||
- `/chat-messages` 接口必须支持流式响应(SSE)
|
||||
- 响应头必须包含:
|
||||
```
|
||||
Content-Type: text/event-stream
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
```
|
||||
- 不要缓冲流式响应,直接转发给前端
|
||||
|
||||
### **3. 错误处理**
|
||||
所有错误响应应遵循统一格式:
|
||||
```json
|
||||
{
|
||||
"error": "错误描述信息"
|
||||
}
|
||||
```
|
||||
|
||||
常见错误码:
|
||||
- `400`: 请求参数错误
|
||||
- `401`: JWT 认证失败
|
||||
- `403`: 权限不足
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
### **4. CORS 配置**
|
||||
如果前后端分离部署,需要配置 CORS:
|
||||
```python
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:5173", "http://your-frontend-domain"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
```
|
||||
|
||||
### **5. 日志记录**
|
||||
建议记录以下信息:
|
||||
- 请求时间、用户ID、接口路径
|
||||
- JWT 验证结果
|
||||
- Dify API 调用结果
|
||||
- 错误信息和堆栈
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试验证
|
||||
|
||||
### **测试工具**
|
||||
推荐使用以下工具测试:
|
||||
- **Postman** - API 接口测试
|
||||
- **curl** - 命令行测试
|
||||
- **httpie** - 友好的命令行工具
|
||||
|
||||
### **测试步骤**
|
||||
|
||||
#### 1. 获取 JWT Token
|
||||
从前端登录后,在浏览器开发者工具中获取 `frontendJWT`:
|
||||
```javascript
|
||||
// 在浏览器控制台执行
|
||||
console.log(document.cookie);
|
||||
```
|
||||
|
||||
#### 2. 测试基础接口
|
||||
```bash
|
||||
# 测试获取参数
|
||||
curl -X GET "http://localhost:8000/dify/parameters" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
|
||||
# 测试获取会话列表
|
||||
curl -X GET "http://localhost:8000/dify/conversations?user=test_user&limit=10" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
#### 3. 测试流式聊天
|
||||
```bash
|
||||
# 测试发送消息(流式)
|
||||
curl -X POST "http://localhost:8000/dify/chat-messages" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"inputs": {},
|
||||
"query": "你好",
|
||||
"user": "test_user",
|
||||
"response_mode": "streaming"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
如有疑问或需要协助,请联系:
|
||||
- **前端开发**: [你的联系方式]
|
||||
- **文档位置**: `E:\A_Wrok\Porject\docreview\docs\dify-proxy-backend-integration.md`
|
||||
- **前端代码仓库**: [仓库地址]
|
||||
|
||||
---
|
||||
|
||||
## 📚 附录
|
||||
|
||||
### **相关文档**
|
||||
- [Dify 官方 API 文档](https://docs.dify.ai/v/zh-hans/guides/application-publishing/developing-with-apis)
|
||||
- [JWT 认证实现文档](./JWT_IMPLEMENTATION.md)
|
||||
- [FastAPI 官方文档](https://fastapi.tiangolo.com/zh/)
|
||||
|
||||
### **配置清单**
|
||||
|
||||
#### 后端需要的环境变量
|
||||
```bash
|
||||
# Dify API 配置
|
||||
DIFY_API_URL=http://nas.7bm.co:12980/v1
|
||||
DIFY_API_KEY=app-N3su9tKyMMnqxt2EMgOkVof7
|
||||
DIFY_APP_ID=46539478-3281-4e98-a445-6da9dc078e95
|
||||
|
||||
# JWT 验证配置(使用现有的)
|
||||
JWT_SECRET=your-jwt-secret
|
||||
JWT_ALGORITHM=HS256
|
||||
```
|
||||
|
||||
#### 前端配置(已修改)
|
||||
```bash
|
||||
# API Base URL - 指向 FastAPI 后端
|
||||
API_BASE_URL=http://172.16.0.55:8000
|
||||
|
||||
# Dify 配置已移除,不再使用
|
||||
# NEXT_PUBLIC_APP_ID=... (已废弃)
|
||||
# NEXT_PUBLIC_APP_KEY=... (已废弃)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 检查清单
|
||||
|
||||
后端实现完成后,请确认:
|
||||
|
||||
- [ ] 所有 7 个 Dify API 路由已实现
|
||||
- [ ] JWT 验证逻辑已集成
|
||||
- [ ] 流式响应(SSE)正常工作
|
||||
- [ ] 错误处理返回正确的状态码
|
||||
- [ ] CORS 配置已启用(如需要)
|
||||
- [ ] 日志记录已完善
|
||||
- [ ] 已使用 Postman/curl 测试所有接口
|
||||
- [ ] 与前端联调测试通过
|
||||
|
||||
---
|
||||
|
||||
**祝开发顺利!** 🎉
|
||||
Reference in New Issue
Block a user