紧急修复:客户端改为调用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>
This commit is contained in:
2025-10-30 11:25:37 +08:00
parent 63acabccc9
commit cf6e9c2421
5 changed files with 62 additions and 122 deletions
+29 -59
View File
@@ -3,13 +3,15 @@ import type { Feedbacktype, ThoughtItem, VisionFile, MessageEnd, MessageReplace
import { unicodeToChar } from '../utils/chat-utils';
// 基础请求选项
// 注意:客户端调用Remix API routes,不需要手动添加Authorization
// Remix会通过session自动处理JWT认证
const baseOptions = {
method: 'GET',
mode: 'cors' as RequestMode,
credentials: 'omit' as RequestCredentials,
credentials: 'include' as RequestCredentials, // 改为include以携带cookie
headers: new Headers({
'Content-Type': ContentType.json,
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
// 移除Authorization头,由服务端自动处理
}),
redirect: 'follow' as RequestRedirect,
};
@@ -322,22 +324,14 @@ const handleStream = (
const baseFetch = (url: string, fetchOptions: any, needAllResponseContent: boolean = false) => {
const options = Object.assign({}, baseOptions, fetchOptions);
// 直接构建Dify API URL
// 调用Remix API routes(如 /api/conversations
// 服务端会通过session获取JWT并调用FastAPI代理
const urlWithPrefix = `${CHAT_CONFIG.API_URL}/${url.replace(/^\//, '')}`;
// 确保Authorization头存在
if (CHAT_CONFIG.API_KEY && options.headers) {
options.headers['Authorization'] = `Bearer ${CHAT_CONFIG.API_KEY}`;
}
const { body } = options;
if (body && typeof body === 'object') {
// 为所有请求添加user参数
const bodyWithUser = {
...body,
user: CHAT_CONFIG.generateUserId(),
};
options.body = JSON.stringify(bodyWithUser);
// 不再添加user参数,服务端会从JWT自动提取
options.body = JSON.stringify(body);
}
return fetch(urlWithPrefix, options)
@@ -453,7 +447,7 @@ export const ssePost = (
method: 'POST',
}, fetchOptions);
// 直接构建Dify API URL
// 调用Remix API routes(如 /api/chat-messages
const urlWithPrefix = `${CHAT_CONFIG.API_URL}/${url.replace(/^\//, '')}`;
const controller = new AbortController();
@@ -464,19 +458,15 @@ export const ssePost = (
...options.headers,
'Content-Type': 'application/json',
'Accept': ContentType.stream,
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
// 移除Authorization头,由服务端自动处理
};
options.signal = controller.signal;
const { body } = options;
if (body && typeof body === 'object') {
// 为SSE请求添加user参数
const bodyWithUser = {
...body,
user: CHAT_CONFIG.generateUserId(),
};
options.body = JSON.stringify(bodyWithUser);
// 不再添加user参数,服务端会从JWT自动提取
options.body = JSON.stringify(body);
}
return fetch(urlWithPrefix, options)
@@ -534,18 +524,14 @@ export const ssePost = (
* ```
*/
export const fetchConversations = async () => {
const user = CHAT_CONFIG.generateUserId();
const params = new URLSearchParams({
user,
limit: '100',
first_id: '',
// 不再传递user参数,服务端会从JWT自动提取
});
return fetch(`${CHAT_CONFIG.API_URL}/conversations?${params}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
},
credentials: 'include', // 携带cookie
}).then(res => {
if (!res.ok) {
throw new Error(`Failed to fetch conversations: ${res.status}`);
@@ -578,19 +564,14 @@ export const fetchConversations = async () => {
* ```
*/
export const fetchChatList = async (conversationId: string) => {
const user = CHAT_CONFIG.generateUserId();
const params = new URLSearchParams({
user,
conversation_id: conversationId,
limit: '20',
last_id: '',
// 不再传递user参数,服务端会从JWT自动提取
});
return fetch(`${CHAT_CONFIG.API_URL}/messages?${params}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
},
credentials: 'include', // 携带cookie
}).then(res => {
if (!res.ok) {
throw new Error(`Failed to fetch chat list: ${res.status}`);
@@ -625,9 +606,7 @@ export const fetchChatList = async (conversationId: string) => {
export const fetchAppParams = async () => {
return fetch(`${CHAT_CONFIG.API_URL}/parameters`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
},
credentials: 'include', // 携带cookie
}).then(res => {
if (!res.ok) {
throw new Error(`Failed to fetch app params: ${res.status}`);
@@ -668,12 +647,9 @@ export const updateFeedback = async ({ url, body }: { url: string; body: Feedbac
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
},
body: JSON.stringify({
...body,
user: CHAT_CONFIG.generateUserId(),
}),
credentials: 'include', // 携带cookie
body: JSON.stringify(body), // 不再添加user参数
}).then(res => {
if (!res.ok) {
throw new Error(`Failed to update feedback: ${res.status}`);
@@ -707,11 +683,11 @@ export const generateConversationName = async (id: string) => {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
},
credentials: 'include', // 携带cookie
body: JSON.stringify({
auto_generate: true,
user: CHAT_CONFIG.generateUserId(),
// 不再添加user参数
}),
}).then(res => {
if (!res.ok) {
@@ -751,12 +727,12 @@ export const renameConversation = async (id: string, name: string, autoGenerate:
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
},
credentials: 'include', // 携带cookie
body: JSON.stringify({
name: autoGenerate ? undefined : name,
auto_generate: autoGenerate,
user: CHAT_CONFIG.generateUserId(),
// 不再添加user参数
}),
}).then(res => {
if (!res.ok) {
@@ -790,11 +766,9 @@ export const deleteConversation = async (id: string) => {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`,
},
body: JSON.stringify({
user: CHAT_CONFIG.generateUserId(),
}),
credentials: 'include', // 携带cookie
// 不再发送body和user参数
}).then(res => {
if (!res.ok) {
throw new Error(`Failed to delete conversation: ${res.status}`);
@@ -861,16 +835,12 @@ export const upload = (fetchOptions: any): Promise<any> => {
for (const key in options.headers)
xhr.setRequestHeader(key, options.headers[key]);
if (CHAT_CONFIG.API_KEY) {
xhr.setRequestHeader('Authorization', `Bearer ${CHAT_CONFIG.API_KEY}`);
}
// 不再手动添加Authorization头,由服务端处理
// 添加user参数到formData
if (options.data instanceof FormData) {
options.data.append('user', CHAT_CONFIG.generateUserId());
}
// 不再添加user参数到formData
// 服务端会从JWT自动提取
xhr.withCredentials = false; // 改为false,因为直接调用Dify API
xhr.withCredentials = true; // 改为true以携带cookie
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200)