Files
leaudit-platform-frontend/docs/dify-proxy-backend-integration.md
TanWenyan c4c08cb59b 重构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>
2025-10-30 09:47:48 +08:00

822 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 测试所有接口
- [ ] 与前端联调测试通过
---
**祝开发顺利!** 🎉