重构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:
@@ -1,4 +1,4 @@
|
||||
|
||||
import { API_BASE_URL } from '~/config/api-config';
|
||||
|
||||
// 获取环境变量的服务端函数
|
||||
const getServerEnvVar = (name: string, defaultValue: string = '') => {
|
||||
@@ -12,8 +12,11 @@ const getServerEnvVar = (name: string, defaultValue: string = '') => {
|
||||
};
|
||||
|
||||
// Dify API 客户端配置
|
||||
// 注意:现在通过 FastAPI 后端的 /dify 路由代理访问 Dify,使用 JWT 认证
|
||||
const DIFY_CONFIG = {
|
||||
API_URL: getServerEnvVar('NEXT_PUBLIC_API_URL', 'https://api.dify.ai/v1'),
|
||||
// API_URL 指向 FastAPI 后端的 /dify 路由
|
||||
API_URL: `${API_BASE_URL}/dify`,
|
||||
// API_KEY 保留用于配置验证(实际不再使用,改用JWT)
|
||||
API_KEY: getServerEnvVar('NEXT_PUBLIC_APP_KEY', ''),
|
||||
APP_ID: (() => {
|
||||
const rawAppId = getServerEnvVar('NEXT_PUBLIC_APP_ID', '');
|
||||
@@ -27,24 +30,32 @@ console.log('🔧 Dify Client Config:', {
|
||||
apiUrl: DIFY_CONFIG.API_URL,
|
||||
appId: DIFY_CONFIG.APP_ID,
|
||||
hasApiKey: !!DIFY_CONFIG.API_KEY,
|
||||
configComplete: !!(DIFY_CONFIG.API_URL && DIFY_CONFIG.APP_ID && DIFY_CONFIG.API_KEY)
|
||||
configComplete: !!(DIFY_CONFIG.API_URL && DIFY_CONFIG.APP_ID)
|
||||
});
|
||||
|
||||
// 基础请求函数
|
||||
const difyFetch = async (endpoint: string, options: RequestInit = {}) => {
|
||||
// 基础请求函数 - 使用 JWT 认证通过 FastAPI 代理访问 Dify
|
||||
const difyFetch = async (endpoint: string, options: RequestInit = {}, jwt?: string) => {
|
||||
const url = `${DIFY_CONFIG.API_URL}/${endpoint.replace(/^\//, '')}`;
|
||||
|
||||
const headers = {
|
||||
// 使用 JWT 认证而非 API_KEY
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${DIFY_CONFIG.API_KEY}`,
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
// console.log('🌐 Dify API Request:', {
|
||||
// url,
|
||||
// method: options.method || 'GET',
|
||||
// hasAuth: !!DIFY_CONFIG.API_KEY
|
||||
// });
|
||||
// 如果提供了 JWT,添加到请求头
|
||||
if (jwt) {
|
||||
(headers as Record<string, string>)['Authorization'] = `Bearer ${jwt}`;
|
||||
} else {
|
||||
console.warn('⚠️ [DifyClient] 没有提供 JWT,请求可能失败');
|
||||
}
|
||||
|
||||
console.log('🌐 [DifyClient] Dify API Request:', {
|
||||
url,
|
||||
method: options.method || 'GET',
|
||||
hasJWT: !!jwt,
|
||||
jwtPreview: jwt ? `${jwt.substring(0, 20)}...` : 'none'
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
@@ -53,11 +64,17 @@ const difyFetch = async (endpoint: string, options: RequestInit = {}) => {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('❌ Dify API Error:', {
|
||||
console.error('❌ [DifyClient] Dify API Error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText
|
||||
});
|
||||
|
||||
// 如果是401错误,说明JWT过期或无效
|
||||
if (response.status === 401) {
|
||||
throw new Error('JWT认证失败,请重新登录');
|
||||
}
|
||||
|
||||
throw new Error(`Dify API Error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
@@ -69,21 +86,18 @@ const generateUserId = (sessionId: string) => {
|
||||
return `user_${DIFY_CONFIG.APP_ID}:${sessionId}`;
|
||||
};
|
||||
|
||||
// Dify API 客户端
|
||||
// Dify API 客户端 - 所有方法都需要传入 JWT
|
||||
export const difyClient = {
|
||||
// 获取应用参数
|
||||
async getApplicationParameters(user: string) {
|
||||
async getApplicationParameters(user: string, jwt?: string) {
|
||||
const response = await difyFetch('parameters', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${DIFY_CONFIG.API_KEY}`,
|
||||
},
|
||||
});
|
||||
}, jwt);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
// 获取会话列表
|
||||
async getConversations(user: string) {
|
||||
async getConversations(user: string, jwt?: string) {
|
||||
const params = new URLSearchParams({
|
||||
user,
|
||||
limit: '100',
|
||||
@@ -92,12 +106,12 @@ export const difyClient = {
|
||||
|
||||
const response = await difyFetch(`conversations?${params}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}, jwt);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
// 获取会话消息
|
||||
async getConversationMessages(user: string, conversationId: string) {
|
||||
async getConversationMessages(user: string, conversationId: string, jwt?: string) {
|
||||
const params = new URLSearchParams({
|
||||
user,
|
||||
conversation_id: conversationId,
|
||||
@@ -107,7 +121,7 @@ export const difyClient = {
|
||||
|
||||
const response = await difyFetch(`messages?${params}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}, jwt);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
@@ -118,7 +132,8 @@ export const difyClient = {
|
||||
user: string,
|
||||
responseMode: string = 'streaming',
|
||||
conversationId?: string,
|
||||
files?: any[]
|
||||
files?: any[],
|
||||
jwt?: string
|
||||
) {
|
||||
const body = {
|
||||
inputs,
|
||||
@@ -138,13 +153,14 @@ export const difyClient = {
|
||||
hasInputs: !!inputs && Object.keys(inputs).length > 0,
|
||||
inputsKeys: inputs ? Object.keys(inputs) : [],
|
||||
hasFiles: !!files && files.length > 0,
|
||||
filesCount: files?.length || 0
|
||||
filesCount: files?.length || 0,
|
||||
hasJWT: !!jwt
|
||||
});
|
||||
|
||||
const response = await difyFetch('chat-messages', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}, jwt);
|
||||
|
||||
console.log('📡 [DifyClient] Dify API响应:', {
|
||||
status: response.status,
|
||||
@@ -165,7 +181,7 @@ export const difyClient = {
|
||||
},
|
||||
|
||||
// 重命名会话
|
||||
async renameConversation(conversationId: string, name: string, user: string, autoGenerate: boolean = false) {
|
||||
async renameConversation(conversationId: string, name: string, user: string, autoGenerate: boolean = false, jwt?: string) {
|
||||
const body = {
|
||||
name,
|
||||
auto_generate: autoGenerate,
|
||||
@@ -175,12 +191,12 @@ export const difyClient = {
|
||||
const response = await difyFetch(`conversations/${conversationId}/name`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}, jwt);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
// 删除会话
|
||||
async deleteConversation(conversationId: string, user: string) {
|
||||
async deleteConversation(conversationId: string, user: string, jwt?: string) {
|
||||
const body = {
|
||||
user,
|
||||
};
|
||||
@@ -188,12 +204,12 @@ export const difyClient = {
|
||||
const response = await difyFetch(`conversations/${conversationId}`, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}, jwt);
|
||||
return response.json();
|
||||
},
|
||||
|
||||
// 更新消息反馈
|
||||
async updateMessageFeedback(messageId: string, rating: 'like' | 'dislike' | null, user: string) {
|
||||
async updateMessageFeedback(messageId: string, rating: 'like' | 'dislike' | null, user: string, jwt?: string) {
|
||||
const body = {
|
||||
rating,
|
||||
user,
|
||||
@@ -202,7 +218,7 @@ export const difyClient = {
|
||||
const response = await difyFetch(`messages/${messageId}/feedbacks`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}, jwt);
|
||||
return response.json();
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user