Files
leaudit-platform-frontend/app/services/dify-client.server.ts
T
TanWenyan cf6e9c2421 紧急修复:客户端改为调用Remix API routes,不再直接调用Dify API
根本问题:客户端代码直接调用Dify API(12980端口),绕过了服务端代理

修改内容:
1. app/config/api-config.ts
   - 添加独立的 difyBaseUrl 配置(指向外网 nas.7bm.co:8000)
   - 导出 DIFY_BASE_URL 供服务端使用

2. app/config/chat.ts
   - 移除直接Dify API配置(NEXT_PUBLIC_API_URL, APP_ID, API_KEY)
   - 移除 generateUserId 函数
   - API_URL 改为 '/api'(指向Remix API routes)

3. app/services/api.client.ts
   - 所有fetch调用改为相对路径 /api/*
   - 移除所有 Authorization 头(服务端自动处理JWT)
   - 移除所有 user 参数传递(服务端从JWT提取)
   - credentials 改为 'include' 以携带cookie

4. app/services/dify-client.server.ts
   - 使用 DIFY_BASE_URL 替代 API_BASE_URL

5. app/utils/dify-test.client.ts
   - 测试函数改为调用Remix API routes

调用链路:
客户端 → /api/* → Remix API routes → dify-client.server.ts → FastAPI /dify → Dify

解决问题:
-  不再直接调用 nas.7bm.co:12980(Dify端口)
-  统一通过 nas.7bm.co:8000/dify(FastAPI代理)
-  所有请求都经过JWT认证
-  user字段由后端自动管理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 11:25:37 +08:00

220 lines
7.2 KiB
TypeScript
Raw 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.
import { DIFY_BASE_URL } from '~/config/api-config';
// 获取环境变量的服务端函数
const getServerEnvVar = (name: string, defaultValue: string = '') => {
const value = process.env[name] || defaultValue;
// console.log(`🌐 [DifyClient] 读取环境变量 ${name}:`, {
// hasValue: !!process.env[name],
// value: process.env[name] ? `${process.env[name].substring(0, 20)}...` : 'undefined',
// usingDefault: !process.env[name]
// });
return value;
};
// Dify API 客户端配置
// 注意:现在通过 FastAPI 后端的 /dify 路由代理访问 Dify,使用 JWT 认证
const DIFY_CONFIG = {
// API_URL 指向 FastAPI 后端的 /dify 路由(使用外网地址 nas.7bm.co:8000
API_URL: `${DIFY_BASE_URL}/dify`,
// API_KEY 保留用于配置验证(实际不再使用,改用JWT)
API_KEY: getServerEnvVar('NEXT_PUBLIC_APP_KEY', ''),
APP_ID: (() => {
const rawAppId = getServerEnvVar('NEXT_PUBLIC_APP_ID', '');
// 从完整URL中提取APP ID
const match = rawAppId.match(/\/app\/([a-f0-9-]{36})/);
return match ? match[1] : rawAppId;
})(),
};
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)
});
// 基础请求函数 - 使用 JWT 认证通过 FastAPI 代理访问 Dify
const difyFetch = async (endpoint: string, options: RequestInit = {}, jwt?: string) => {
const url = `${DIFY_CONFIG.API_URL}/${endpoint.replace(/^\//, '')}`;
// 使用 JWT 认证而非 API_KEY
const headers: HeadersInit = {
'Content-Type': 'application/json',
...options.headers,
};
// 如果提供了 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,
headers,
});
if (!response.ok) {
const errorText = await response.text();
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}`);
}
return response;
};
// Dify API 客户端 - 所有方法都需要传入 JWT
// 注意:user 参数已移除,由后端自动从 JWT 中提取 username
export const difyClient = {
// 获取应用参数
async getApplicationParameters(jwt?: string) {
const response = await difyFetch('parameters', {
method: 'GET',
}, jwt);
return response.json();
},
// 获取会话列表
async getConversations(jwt?: string) {
const params = new URLSearchParams({
limit: '100',
first_id: '',
});
const response = await difyFetch(`conversations?${params}`, {
method: 'GET',
}, jwt);
return response.json();
},
// 获取会话消息
async getConversationMessages(conversationId: string, jwt?: string) {
const params = new URLSearchParams({
conversation_id: conversationId,
limit: '20',
last_id: '',
});
const response = await difyFetch(`messages?${params}`, {
method: 'GET',
}, jwt);
return response.json();
},
// 发送聊天消息
async createChatMessage(
inputs: Record<string, any>,
query: string,
responseMode: string = 'streaming',
conversationId?: string,
files?: any[],
jwt?: string
) {
const body = {
inputs,
query,
// user 字段已移除,后端会自动从 JWT 中提取 username
response_mode: responseMode,
conversation_id: conversationId,
files: files || [],
};
console.log('🌐 [DifyClient] 发送聊天消息:', {
queryLength: query.length,
queryPreview: query.substring(0, 100) + (query.length > 100 ? '...' : ''),
responseMode,
conversationId,
hasInputs: !!inputs && Object.keys(inputs).length > 0,
inputsKeys: inputs ? Object.keys(inputs) : [],
hasFiles: !!files && 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,
statusText: response.statusText,
hasBody: !!response.body,
contentType: response.headers.get('Content-Type'),
responseMode
});
// 对于流式响应,直接返回Response对象
if (responseMode === 'streaming') {
console.log('🌊 [DifyClient] 返回流式响应对象');
return response;
}
console.log('📄 [DifyClient] 解析JSON响应');
return response.json();
},
// 重命名会话
async renameConversation(conversationId: string, name: string, autoGenerate: boolean = false, jwt?: string) {
const body = {
name,
auto_generate: autoGenerate,
// user 字段已移除,后端会自动从 JWT 中提取 username
};
const response = await difyFetch(`conversations/${conversationId}/name`, {
method: 'POST',
body: JSON.stringify(body),
}, jwt);
return response.json();
},
// 删除会话
async deleteConversation(conversationId: string, jwt?: string) {
// user 字段已移除,后端会自动从 JWT 中提取 username
const body = {};
const response = await difyFetch(`conversations/${conversationId}`, {
method: 'DELETE',
body: JSON.stringify(body),
}, jwt);
return response.json();
},
// 更新消息反馈
async updateMessageFeedback(messageId: string, rating: 'like' | 'dislike' | null, jwt?: string) {
const body = {
rating,
// user 字段已移除,后端会自动从 JWT 中提取 username
};
const response = await difyFetch(`messages/${messageId}/feedbacks`, {
method: 'POST',
body: JSON.stringify(body),
}, jwt);
return response.json();
},
};
// 工具函数
export const difyUtils = {
getConfig: () => DIFY_CONFIG,
};