diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..f2e9eaf
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,293 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+This is a **Chinese Tobacco AI Contract and Case Audit System** (中国烟草AI合同及卷宗审核系统) - a Remix-based full-stack application for intelligent document review and evaluation. The system provides AI-powered contract auditing, risk assessment, and compliance checking.
+
+**Tech Stack:**
+- Frontend: Remix (React) + TypeScript + Vite
+- Styling: Tailwind CSS + custom CSS
+- Icons: Remixicon (locally hosted)
+- Document Processing: react-pdf, mammoth, docx-preview
+- Backend API: PostgreSQL + PostgREST
+- Authentication: OAuth2.0 + JWT + cookie-based sessions
+- Deployment: Docker + PM2 (multi-instance setup)
+
+## Core Commands
+
+### Development
+```bash
+npm run dev # Start dev server (port 5173)
+npm run typecheck # Run TypeScript type checking
+npm run lint # Run ESLint
+```
+
+### Building
+```bash
+npm run build # Production build
+npm run build:production:multi # Build for production multi-instance
+npm run build:test:multi # Build for testing multi-instance
+npm run build:dev # Build for development
+```
+
+### Production Deployment
+```bash
+npm start # Start production server (single instance)
+npm run start:pm2:production:multi # Build and start PM2 multi-instance (production)
+npm run start:pm2:multi # Build and start PM2 multi-instance (testing)
+```
+
+### JWT Secret Generation
+```bash
+npm run generate:jwt-secret # Generate a secure JWT secret for production
+```
+
+### Docker Deployment
+```bash
+# Build image
+docker build -t docreview-app .
+
+# Run with docker-compose
+docker-compose up -d
+
+# The system exposes 6 ports for different regional clients:
+# 51703: Meizhou (main)
+# 51704: Yunfu
+# 51705: Jieyang
+# 51706: Chaozhou
+# 51707: Province
+# 51708: Test instance (limited to /cross-checking routes)
+```
+
+## Architecture
+
+### Multi-Instance Deployment Strategy
+
+The system uses a **port-based multi-client architecture** where:
+- One codebase serves multiple regional clients
+- Each client runs on a different port with isolated configurations
+- Port-specific API configurations are defined in `app/config/api-config.ts`
+- PM2 manages multiple instances via `ecosystem.config.cjs`
+- Each instance has its own environment variables (PORT, CLIENT_ID, API_PORT_CONFIG)
+
+### Authentication Flow
+
+1. **OAuth2.0 Integration** (IDaaS-based):
+ - OAuth client configuration in `app/api/login/oauth-client.ts`
+ - Callback handling in `app/routes/callback.tsx`
+ - User info sync with local PostgreSQL database
+
+2. **JWT Token Management**:
+ - Frontend JWT generated after OAuth success (`app/api/jwt-helper.server.ts`)
+ - JWT contains user info, role, and permissions
+ - Stored in encrypted cookie session
+ - Auto-refresh when OAuth token refreshes
+ - **CRITICAL**: JWT_SECRET must be set in `.env` (use `npm run generate:jwt-secret`)
+
+3. **Session Management**:
+ - Cookie-based sessions via `createCookieSessionStorage`
+ - Global authentication check in `app/root.tsx` loader
+ - Role-based access control (common vs developer roles)
+ - Developer-only paths: /settings, /config-lists, /document-types, /prompts
+
+### API Configuration System
+
+**Port-Based Configuration** (`app/config/api-config.ts`):
+- Automatically detects current port and applies correct API config
+- Each port maps to specific backend services (PostgreSQL, MinIO, etc.)
+- Environment variables can override port configs:
+ - `NEXT_PUBLIC_API_BASE_URL` - PostgreSQL API base URL
+ - `NEXT_PUBLIC_DOCUMENT_URL` - MinIO document storage URL
+ - `NEXT_PUBLIC_UPLOAD_URL` - File upload endpoint
+
+**Configuration Priority**: Port-specific config > Environment variables > Default environment config
+
+### Route Structure
+
+- `app/routes/` - Remix file-based routing
+ - `_index.tsx` - Home page (redirects to /documents or /cross-checking based on port)
+ - `login.tsx` - Login page with OAuth flow
+ - `callback.tsx` - OAuth callback handler
+ - `documents.tsx` / `documents._index.tsx` - Document management
+ - `cross-checking.tsx` - Cross-examination system
+ - `contract-template.tsx` - Contract template search
+ - `chat-with-llm._index.tsx` - AI chat interface
+ - `api.*.tsx` - API routes for backend proxy
+
+### API Layer Organization
+
+- `app/api/` - API client modules
+ - `axios-client.ts` - Configured axios instance with auth headers
+ - `postgrest-client.ts` - PostgREST API client
+ - `db-client.server.ts` - Server-side database client
+ - `jwt-helper.server.ts` - JWT generation and validation
+ - `login/auth.server.ts` - Authentication service (getUserSession, logout)
+ - `login/oauth-client.ts` - OAuth2.0 client implementation
+ - `login/token-manager.server.ts` - Token refresh and management
+
+**Important**: Always use `axios-client.ts` for API calls. It automatically:
+- Adds JWT authentication headers
+- Handles API base URL configuration
+- Includes proper error handling
+
+### Component Architecture
+
+**Design System** (`app/components/ui/`):
+- All components follow BEM naming convention
+- Primary color: `#00684a` (tobacco corporate green)
+- Components are self-contained with co-located styles
+- Import styles in `app/styles/main.css`
+
+**Key Components**:
+- `Layout.tsx` - Main layout with sidebar and header
+- `Sidebar.tsx` - Navigation sidebar (role-based menu filtering)
+- `MessageModal.tsx` - Confirmation/alert modal system
+- `Toast.tsx` - Toast notification provider
+- `LoadingBar.tsx` - Top loading bar for route transitions
+- `FilePreview.tsx` - PDF/Word document preview
+
+**RemixIcon Usage**:
+- Icons are locally hosted in `public/fonts/`
+- Use class syntax: ``
+- Preloaded via link tag for instant display
+- **CRITICAL**: When using CSS isolation (like `all: unset`), add exception rules for RemixIcon classes (see `docs/docreview-development-standards.md` for examples)
+
+### Cross-Checking System
+
+**Overview**: Democratic proposal and voting system for document evaluation disputes.
+
+**Key Concepts**:
+- **Tasks**: Evaluation assignments with multiple reviewers
+- **Proposals**: Suggestions to modify evaluation scores
+- **Voting**: Democratic decision-making with approval threshold
+- **Arbitration**: Automatic status determination based on vote counts
+
+**Database Tables**:
+- `cross_examination_tasks` - Task assignments
+- `cross_task_document_mapping` - Document-task relationships
+- `cross_scoring_proposals` - Score modification proposals
+- `cross_opinion_votes` - Vote records
+
+**API Routes** (via `app/routes/api.*.tsx` proxies):
+- `POST /admin/cross_review/tasks/assign` - Assign cross-checking task
+- `POST /admin/cross_review/proposals` - Create proposal
+- `POST /admin/cross_review/proposals/{id}/votes` - Vote on proposal
+- `DELETE /admin/cross_review/proposals/{id}` - Withdraw proposal
+
+See `docs/交叉评查系统完整文档.md` for complete documentation.
+
+## Environment Variables
+
+**Required** (create `.env` file):
+```bash
+# JWT Secret - MUST be set for production
+JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
+
+# OAuth2.0 Configuration (set in PM2 config or env)
+OAUTH_CLIENT_SECRET=your-oauth-client-secret
+
+# API Configuration (optional, overrides port-based config)
+NEXT_PUBLIC_API_BASE_URL=http://10.79.97.17:8000
+NEXT_PUBLIC_DOCUMENT_URL=http://10.76.244.156:9000/docauditai/
+NEXT_PUBLIC_UPLOAD_URL=http://10.79.97.17:8000/admin/documents
+```
+
+**PM2 Instance Variables** (set in `ecosystem.config.cjs`):
+- `NODE_ENV` - Environment (production/testing/development)
+- `PORT` - Server port
+- `CLIENT_ID` - Regional client identifier
+- `API_PORT_CONFIG` - Port-based API configuration selector
+
+## Development Guidelines
+
+### Code Style
+
+**TypeScript**:
+- Use explicit types for function parameters and return values
+- Interface naming: PascalCase (e.g., `DocumentUI`, `ApiConfig`)
+- Use string literal unions for status types
+- Avoid `any` - use `unknown` if type is truly unknown
+
+**React Components**:
+- Use function declarations, not arrow functions: `export function Component() {}`
+- Group hooks at top: useState first, then useEffect
+- Event handlers: `handleClick`, `handleSubmit` naming
+- Props interface: `ComponentNameProps`
+
+**File Naming**:
+- Components: `PascalCase.tsx` (Button.tsx)
+- Routes: `kebab-case.tsx` (user-profile.tsx)
+- Utilities: `camelCase.ts` (utils.ts)
+
+**Import Order**:
+1. React and Remix imports
+2. Third-party libraries
+3. Internal components with `~/` alias
+4. Type imports
+5. Styles
+
+### Styling
+
+**Color Variables** (defined in `app/root.tsx`):
+```css
+--color-primary: #00684a; /* Tobacco green */
+--color-primary-hover: #005a3f;
+--color-primary-light: rgba(0, 104, 74, 0.1);
+--color-success: #52c41a;
+--color-warning: #faad14;
+--color-error: #f5222d;
+```
+
+**Spacing**: Use Tailwind defaults (4px base: p-4, mt-6, etc.)
+**Border Radius**: `rounded-md` (6px) for buttons/cards
+**Shadows**: `shadow-sm` default, `shadow-md` on hover
+
+### Error Handling
+
+- Use `ErrorBoundary` component for route-level errors
+- API errors: Log and return appropriate Response.json with status
+- Display user-friendly messages via MessageModal or Toast
+- Never expose sensitive error details to users
+
+### Performance
+
+- Use `React.memo` for pure components that re-render frequently
+- Lazy load heavy components (PDF viewers, code editors)
+- Debounce search inputs (300ms standard)
+- Optimize images and use appropriate formats
+
+## Security Considerations
+
+1. **JWT Secret**: NEVER commit JWT_SECRET to version control
+2. **OAuth Client Secret**: Store in environment variables, not code
+3. **API Keys**: Use server-side only (no NEXT_PUBLIC_ prefix)
+4. **Session Cookies**: httpOnly, secure in production
+5. **Port 51708 Restrictions**: Limited to /cross-checking routes only
+
+## Testing
+
+- Test OAuth flow with IDaaS server at `http://10.79.112.85`
+- Verify JWT generation and validation
+- Check port-based API configuration switching
+- Test role-based access control (common vs developer)
+- Validate cross-checking voting logic and thresholds
+
+## Common Pitfalls
+
+1. **RemixIcon not showing**: Check for CSS rules overriding `font-family: 'remixicon'`
+2. **API config not updating**: Port detection may need window.location.port on client
+3. **JWT expired**: Implement token refresh or re-authenticate
+4. **PM2 env vars not working**: Ensure NEXT_PUBLIC_ prefix for client-side vars
+5. **File upload fails**: Check UPLOAD_URL configuration for current port
+
+## Additional Documentation
+
+Detailed documentation is available in `docs/`:
+- `docreview-development-standards.md` - Comprehensive coding standards
+- `交叉评查系统完整文档.md` - Cross-checking system specification
+- `JWT_IMPLEMENTATION.md` - JWT authentication implementation
+- `OAuth2.0认证协议集成指南.md` - OAuth integration guide
+- `docker-deployment.md` - Docker deployment instructions
+- `deployment-config.md` - Multi-instance deployment configuration
diff --git a/app/config/api-config-b.ts b/app/config/api-config-b.ts
new file mode 100644
index 0000000..a9f13ca
--- /dev/null
+++ b/app/config/api-config-b.ts
@@ -0,0 +1,399 @@
+/**
+ * API配置文件
+ * 统一管理所有API地址,方便部署时修改
+ * 支持环境变量覆盖配置
+ */
+// 环境配置类型
+interface ApiConfig {
+ // 主API基础URL
+ baseUrl: string;
+ // 文档服务URL
+ documentUrl: string;
+ // 文档上传API URL
+ uploadUrl: string;
+ // OAuth2.0配置
+ oauth: {
+ // IDaaS服务器地址
+ serverUrl: string;
+ // OAuth2应用Client ID
+ clientId: string;
+ // OAuth2应用Client Secret
+ clientSecret: string;
+ // 回调地址
+ redirectUri: string;
+ // 应用ID(用于登出)
+ appId: string;
+ };
+}
+
+// 端口特定配置映射
+// 根据不同端口提供不同的API配置
+const portConfigs: Record> = {
+
+ // 测试主要服务实例
+ '5173': {
+ baseUrl: 'http://172.16.0.55:8000',
+ documentUrl: 'http://172.16.0.55:8000/docauditai/',
+ uploadUrl: 'http://172.16.0.55:8000/admin/documents'
+ },
+ // 测试客户端实例
+ '5174': {
+ baseUrl: 'http://172.16.0.55:5174',
+ documentUrl: 'http://172.16.0.55:5174/docauditai/',
+ uploadUrl: 'http://172.16.0.55:5174/admin/documents'
+ },
+ // 测试客户端实例
+ '5175': {
+ baseUrl: 'http://172.16.0.55:5175',
+ documentUrl: 'http://172.16.0.55:5175/docauditai/',
+ uploadUrl: 'http://172.16.0.55:5175/admin/documents'
+ },
+ // 测试客户端实例
+ '5176': {
+ baseUrl: 'http://172.16.0.55:5176',
+ documentUrl: 'http://172.16.0.55:5176/docauditai/',
+ uploadUrl: 'http://172.16.0.55:5176/admin/documents'
+ },
+ // 测试客户端实例
+ '5177': {
+ baseUrl: 'http://172.16.0.55:5177',
+ documentUrl: 'http://172.16.0.55:5177/docauditai/',
+ uploadUrl: 'http://172.16.0.55:5177/admin/documents'
+ },
+ // 测试客户端实例
+ '5178': {
+ baseUrl: 'http://172.16.0.55:8008',
+ documentUrl: 'http://172.16.0.55:8008/docauditai/',
+ uploadUrl: 'http://172.16.0.55:8008/admin/documents'
+ },
+
+
+
+ // 主要
+ // 梅州
+ '51703': {
+ baseUrl: 'http://172.16.0.55:8073',
+ documentUrl: 'http://172.16.0.55:8073/docauditai/',
+ uploadUrl: 'http://172.16.0.55:8073/admin/documents'
+ // baseUrl: 'http://nas.7bm.co:8873',
+ // documentUrl: 'http://nas.7bm.co:8873/docauditai/',
+ // uploadUrl: 'http://nas.7bm.co:8873/admin/documents'
+ },
+
+
+ // 云浮
+ '51704': {
+ baseUrl: 'http://10.79.97.17:8001',
+ documentUrl: 'http://10.79.97.17:8001/docauditai/',
+ uploadUrl: 'http://10.79.97.17:8001/admin/documents'
+ },
+
+ // 揭阳
+ '51705': {
+ baseUrl: 'http://10.79.97.17:8002',
+ documentUrl: 'http://10.79.97.17:8002/docauditai/',
+ uploadUrl: 'http://10.79.97.17:8002/admin/documents'
+ },
+
+ // 潮州
+ '51706': {
+ baseUrl: 'http://10.79.97.17:8003',
+ documentUrl: 'http://10.79.97.17:8003/docauditai/',
+ uploadUrl: 'http://10.79.97.17:8003/admin/documents'
+ },
+
+ // 省局
+ '51707': {
+ baseUrl: 'http://10.79.97.17:8004',
+ documentUrl: 'http://10.79.97.17:8004/docauditai/',
+ uploadUrl: 'http://10.79.97.17:8004/admin/documents'
+ },
+ //test
+ '51708': {
+ baseUrl: 'http://10.79.97.17:8005',
+ documentUrl: 'http://10.79.97.17:8005/docauditai/',
+ uploadUrl: 'http://10.79.97.17:8005/admin/documents'
+ },
+};
+
+// 不同环境的默认配置
+// 由于合同模板的上传,后续的的uploadUrl都不需要/upload,直接写/admin/documents,由程序自动添加/upload或/upload_contract_template
+const configs: Record = {
+ // 开发环境
+ development: {
+ baseUrl: 'http://172.16.0.55:8000',
+ documentUrl: 'http://172.16.0.55:8000/docauditai/',
+ uploadUrl: 'http://172.16.0.55:8000/admin/documents',
+ oauth: {
+ serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
+ clientId: 'none',
+ clientSecret: 'none', // 需要替换为实际的Client Secret
+ redirectUri: 'http://10.79.97.17/', // 回调地址
+ appId: 'idaasoauth2' // 应用ID,用于登出
+ }
+ },
+
+ // 测试环境
+ testing: {
+ baseUrl: 'http://nas.7bm.co:8873',
+ documentUrl: 'http://nas.7bm.co:8873/docauditai/',
+ uploadUrl: 'http://nas.7bm.co:8873/admin/documents',
+ oauth: {
+ serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
+ clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
+ clientSecret: 'placeholder', // 需要替换为实际的Client Secret
+ redirectUri: 'http://10.79.97.17/', // 回调地址
+ appId: 'idaasoauth2' // 应用ID,用于登出
+ }
+ },
+
+ // 生产环境
+ production: {
+ // postgrest
+ baseUrl: 'http://10.79.97.17:8000',
+ // minio
+ documentUrl: 'http://10.76.244.156:9000/docauditai/',
+ // 文件上传
+ uploadUrl: 'http://10.79.97.17:8000/admin/documents',
+ oauth: {
+ serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
+ clientId: '54d2a619fe5c81ae1250434c441fccccqMtKwh7H4fO',
+ // clientSecret: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb', // 需要替换为实际的Client Secret
+ // ⚠️ 安全警告:clientSecret 不应该硬编码在代码中
+ // 请在生产环境使用环境变量 OAUTH_CLIENT_SECRET
+ clientSecret: 'placeholder', // 占位符,实际值从环境变量获取
+ redirectUri: 'http://10.79.97.17/', // 回调地址
+ appId: 'idaasoauth2' // 应用ID,用于登出
+ }
+ },
+
+ // 备用配置 (可以根据需要添加更多环境)
+ staging: {
+ baseUrl: 'http://172.16.0.119:9000/admin',
+ documentUrl: 'http://nas.7bm.co:9000/docauditai/',
+ uploadUrl: 'http://172.16.0.119:8000/admin/documents/upload',
+ oauth: {
+ serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
+ clientId: 'none', // 需要替换为实际的Client ID
+ clientSecret: 'your_client_secret', // 需要替换为实际的Client Secret
+ redirectUri: 'http://172.16.0.119:3000/callback', // 回调地址
+ appId: 'idaasoauth2' // 应用ID,用于登出
+ }
+ }
+};
+
+// 获取当前环境,默认为development
+const getCurrentEnvironment = (): string => {
+ // 在服务器端,优先使用PM2设置的环境变量
+ if (typeof window === 'undefined') {
+ // 服务器端:直接使用process.env.NODE_ENV
+ const nodeEnv = process.env.NODE_ENV;
+ console.log('🔧 服务器端环境检测:', {
+ NODE_ENV: nodeEnv,
+ result: nodeEnv || 'development'
+ });
+ return nodeEnv || 'development';
+ }
+
+ // 客户端:优先使用NEXT_PUBLIC_前缀的环境变量
+ const nextPublicNodeEnv = process.env.NEXT_PUBLIC_NODE_ENV;
+ const nodeEnv = process.env.NODE_ENV;
+ const result = nextPublicNodeEnv || nodeEnv || 'development';
+
+ console.log('🔧 客户端环境检测:', {
+ NEXT_PUBLIC_NODE_ENV: nextPublicNodeEnv,
+ NODE_ENV: nodeEnv,
+ result: result
+ });
+
+ return result;
+};
+
+// 从环境变量获取配置,如果环境变量不存在则使用默认配置
+const getConfigFromEnv = (defaultConfig: ApiConfig): ApiConfig => {
+ return {
+ baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || defaultConfig.baseUrl,
+ documentUrl: process.env.NEXT_PUBLIC_DOCUMENT_URL || defaultConfig.documentUrl,
+ uploadUrl: process.env.NEXT_PUBLIC_UPLOAD_URL || defaultConfig.uploadUrl,
+ oauth: {
+ serverUrl: process.env.NEXT_PUBLIC_OAUTH_SERVER_URL || defaultConfig.oauth.serverUrl,
+ clientId: process.env.NEXT_PUBLIC_OAUTH_CLIENT_ID || defaultConfig.oauth.clientId,
+ // ⚠️ 注意:clientSecret 不应该使用 NEXT_PUBLIC_ 前缀
+ // 应该只在服务器端通过 process.env.OAUTH_CLIENT_SECRET 访问
+ clientSecret: process.env.OAUTH_CLIENT_SECRET || defaultConfig.oauth.clientSecret,
+ redirectUri: process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI || defaultConfig.oauth.redirectUri,
+ appId: process.env.NEXT_PUBLIC_OAUTH_APP_ID || defaultConfig.oauth.appId
+ }
+ };
+};
+
+/**
+ * 获取当前端口号
+ * 优先从浏览器location获取,然后从环境变量获取
+ */
+const getCurrentPort = (): string => {
+ // 在客户端,优先从浏览器location获取端口
+ let windowPort = '';
+ if (typeof window !== 'undefined') {
+ windowPort = window.location.port || '';
+ }
+
+ // 在服务器端,优先使用运行时端口检测
+ if (typeof window === 'undefined') {
+ const runtimePort = getRuntimePort();
+ if (runtimePort) {
+ console.log('🔧 服务器端运行时端口检测:', runtimePort);
+ return runtimePort;
+ }
+ }
+
+ // 优先使用环境变量中的端口配置
+ const nextPublicApiPortConfig = process.env.NEXT_PUBLIC_API_PORT_CONFIG;
+ const nextPublicPort = process.env.NEXT_PUBLIC_PORT;
+ const apiPortConfig = process.env.API_PORT_CONFIG;
+ const portEnv = process.env.PORT;
+
+ // 优先级:windowPort > NEXT_PUBLIC_API_PORT_CONFIG > NEXT_PUBLIC_PORT > API_PORT_CONFIG > PORT环境变量
+ const result = windowPort || nextPublicApiPortConfig || nextPublicPort || apiPortConfig || portEnv || '';
+
+ console.log('🔧 端口检测:', {
+ windowPort: windowPort,
+ NEXT_PUBLIC_API_PORT_CONFIG: nextPublicApiPortConfig,
+ NEXT_PUBLIC_PORT: nextPublicPort,
+ API_PORT_CONFIG: apiPortConfig,
+ PORT: portEnv,
+ result: result
+ });
+
+ return result;
+};
+
+/**
+ * 运行时端口检测 - 从服务器启动参数或环境变量获取实际端口
+ * 这个方法只在服务器端运行,用于动态获取实际运行端口
+ */
+const getRuntimePort = (): string => {
+ if (typeof window !== 'undefined') {
+ return ''; // 客户端不执行此逻辑
+ }
+
+ // 尝试从进程参数中获取端口
+ const args = process.argv;
+ for (let i = 0; i < args.length; i++) {
+ if (args[i] === '--port' && i + 1 < args.length) {
+ return args[i + 1];
+ }
+ if (args[i].startsWith('--port=')) {
+ return args[i].split('=')[1];
+ }
+ }
+
+ // 从环境变量获取
+ return process.env.PORT || '';
+};
+
+/**
+ * 获取当前配置
+ * 支持根据端口动态切换API配置
+ */
+const getCurrentConfig = (): ApiConfig => {
+ const env = getCurrentEnvironment();
+ const port = getCurrentPort();
+
+ console.log('🔧 配置调试信息:', {
+ environment: env,
+ port: port,
+ hasPortConfig: !!(port && portConfigs[port]),
+ portConfig: port ? portConfigs[port] : null
+ });
+
+ // 获取基础配置
+ let defaultConfig = configs[env] || configs.development;
+
+ // 如果有端口特定配置,则合并配置
+ if (port && portConfigs[port]) {
+ console.log(`🔧 使用端口特定配置: ${port}`, portConfigs[port]);
+ defaultConfig = {
+ ...defaultConfig,
+ ...portConfigs[port],
+ // 保持oauth配置不变,只覆盖API相关配置
+ oauth: defaultConfig.oauth
+ };
+ } else {
+ console.log(`🔧 使用环境配置: ${env}`, defaultConfig);
+ }
+
+ // 只有在明确设置了环境变量的情况下才覆盖配置
+ const hasEnvOverrides = process.env.NEXT_PUBLIC_API_BASE_URL ||
+ process.env.NEXT_PUBLIC_DOCUMENT_URL ||
+ process.env.NEXT_PUBLIC_UPLOAD_URL;
+
+ if (hasEnvOverrides) {
+ console.log('🔧 检测到环境变量覆盖,使用环境变量配置');
+ return getConfigFromEnv(defaultConfig);
+ }
+
+ console.log('🔧 最终配置:', defaultConfig);
+ return defaultConfig;
+};
+
+// 导出当前环境的配置
+export const apiConfig = getCurrentConfig();
+
+// 导出具体的配置项,方便使用
+export const {
+ baseUrl: API_BASE_URL,
+ documentUrl: DOCUMENT_URL,
+ uploadUrl: UPLOAD_URL,
+ oauth: OAUTH_CONFIG
+} = apiConfig;
+
+/**
+ * 🔓 客户端安全的 OAuth 配置(不包含 clientSecret)
+ * 可以安全地在客户端代码中使用
+ */
+export const CLIENT_OAUTH_CONFIG = {
+ serverUrl: OAUTH_CONFIG.serverUrl,
+ clientId: OAUTH_CONFIG.clientId,
+ redirectUri: OAUTH_CONFIG.redirectUri,
+ appId: OAUTH_CONFIG.appId,
+ // 客户端不需要 clientSecret
+};
+
+// 导出所有配置,供调试使用
+export { configs };
+
+// 工具函数:设置环境(主要用于测试)
+export const setEnvironment = (env: string): ApiConfig => {
+ return configs[env] || configs.development;
+};
+
+/**
+ * 工具函数:获取当前端口配置信息(用于调试)
+ */
+export const getCurrentPortConfig = () => {
+ const port = getCurrentPort();
+ const env = getCurrentEnvironment();
+ return {
+ currentPort: port,
+ currentEnvironment: env,
+ hasPortConfig: !!(port && portConfigs[port]),
+ portConfig: port ? portConfigs[port] : null
+ };
+};
+
+// 调试信息(仅在开发环境显示)
+if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'testing') {
+ console.log('📦 API配置信息:', {
+ environment: getCurrentEnvironment(),
+ currentEnv: process.env.NODE_ENV,
+ port: getCurrentPort(),
+ config: {
+ ...apiConfig,
+ oauth: {
+ ...apiConfig.oauth,
+ clientSecret: '***' // 隐藏敏感信息
+ }
+ },
+ });
+}
\ No newline at end of file
diff --git a/app/routes/api.chat-messages.tsx b/app/routes/api.chat-messages.tsx
index 3e41563..e1debb9 100644
--- a/app/routes/api.chat-messages.tsx
+++ b/app/routes/api.chat-messages.tsx
@@ -8,7 +8,23 @@ export async function action({ request }: ActionFunctionArgs) {
}
try {
+ // 获取用户会话信息和 JWT
+ const { getUserSession } = await import("~/api/login/auth.server");
+ const { frontendJWT } = await getUserSession(request);
const { user } = await getSessionInfo(request);
+
+ // 检查 JWT 是否存在
+ if (!frontendJWT) {
+ console.error('❌ [API] Chat Messages API - JWT不存在');
+ return new Response(
+ JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
+ {
+ status: 401,
+ headers: { 'Content-Type': 'application/json' },
+ }
+ );
+ }
+
const body = await request.json();
const {
@@ -27,7 +43,8 @@ export async function action({ request }: ActionFunctionArgs) {
responseMode,
hasInputs: !!inputs,
hasFiles: !!files && files.length > 0,
- filesCount: files?.length || 0
+ filesCount: files?.length || 0,
+ hasJWT: !!frontendJWT
});
const response = await difyClient.createChatMessage(
@@ -36,7 +53,8 @@ export async function action({ request }: ActionFunctionArgs) {
user,
responseMode,
conversationId,
- files
+ files,
+ frontendJWT // 传递 JWT
);
console.log('📡 [API] Dify响应状态:', {
@@ -77,10 +95,14 @@ export async function action({ request }: ActionFunctionArgs) {
stack: error.stack,
name: error.name
});
+
+ // 检查是否是JWT认证失败
+ const status = error.message?.includes('JWT认证失败') ? 401 : 500;
+
return new Response(
JSON.stringify({ error: error.message || 'Failed to send message' }),
{
- status: 500,
+ status,
headers: {
'Content-Type': 'application/json',
},
diff --git a/app/routes/api.conversations.$id.name.tsx b/app/routes/api.conversations.$id.name.tsx
index ffdd450..48f8d12 100644
--- a/app/routes/api.conversations.$id.name.tsx
+++ b/app/routes/api.conversations.$id.name.tsx
@@ -4,6 +4,9 @@ import { getSessionInfo, commitSession } from '../utils/session.server';
export async function action({ request, params }: ActionFunctionArgs) {
try {
+ // 获取用户会话信息和 JWT
+ const { getUserSession } = await import("~/api/login/auth.server");
+ const { frontendJWT } = await getUserSession(request);
const { user, session } = await getSessionInfo(request);
const { id } = params;
@@ -11,15 +14,35 @@ export async function action({ request, params }: ActionFunctionArgs) {
return json({ error: '会话ID不能为空' }, { status: 400 });
}
+ // 检查 JWT 是否存在
+ if (!frontendJWT) {
+ console.error('❌ [API] Rename Conversation API - JWT不存在');
+ return json(
+ { error: 'JWT认证失败,请重新登录' },
+ {
+ status: 401,
+ headers: {
+ 'Set-Cookie': await commitSession(session),
+ },
+ }
+ );
+ }
+
const body = await request.json();
const { auto_generate, name } = body;
- // console.log('💬 Rename Conversation API - User:', user, 'ID:', id, 'Auto Generate:', auto_generate, 'Name:', name);
+ console.log('💬 [API] Rename Conversation API - 重命名会话:', {
+ user,
+ id,
+ autoGenerate: auto_generate,
+ name,
+ hasJWT: !!frontendJWT
+ });
// 调用服务端API重命名会话
- const data = await difyClient.renameConversation(id, name, user, auto_generate);
+ const data = await difyClient.renameConversation(id, name, user, auto_generate, frontendJWT);
- // console.log('✅ Rename Conversation API - Success:', data);
+ console.log('✅ [API] Rename Conversation API - Success');
return json(data, {
headers: {
@@ -27,13 +50,17 @@ export async function action({ request, params }: ActionFunctionArgs) {
},
});
} catch (error: any) {
- console.error('❌ Rename Conversation API - Error:', error);
+ console.error('❌ [API] Rename Conversation API - Error:', error);
+
+ // 检查是否是JWT认证失败
+ const status = error.message?.includes('JWT认证失败') ? 401 : 500;
+
return json(
{
error: error.message || '重命名会话失败'
},
{
- status: 500,
+ status,
headers: {
'Set-Cookie': await commitSession((await getSessionInfo(request)).session),
},
diff --git a/app/routes/api.conversations.$id.tsx b/app/routes/api.conversations.$id.tsx
index d7a0451..3eb6cb6 100644
--- a/app/routes/api.conversations.$id.tsx
+++ b/app/routes/api.conversations.$id.tsx
@@ -4,6 +4,9 @@ import { getSessionInfo, commitSession } from '../utils/session.server';
export async function action({ request, params }: ActionFunctionArgs) {
try {
+ // 获取用户会话信息和 JWT
+ const { getUserSession } = await import("~/api/login/auth.server");
+ const { frontendJWT } = await getUserSession(request);
const { user, session } = await getSessionInfo(request);
const { id } = params;
@@ -11,15 +14,33 @@ export async function action({ request, params }: ActionFunctionArgs) {
return json({ error: '会话ID不能为空' }, { status: 400 });
}
+ // 检查 JWT 是否存在
+ if (!frontendJWT) {
+ console.error('❌ [API] Delete Conversation API - JWT不存在');
+ return json(
+ { error: 'JWT认证失败,请重新登录' },
+ {
+ status: 401,
+ headers: {
+ 'Set-Cookie': await commitSession(session),
+ },
+ }
+ );
+ }
+
const method = request.method;
if (method === 'DELETE') {
- // console.log('🗑️ Delete Conversation API - User:', user, 'ID:', id);
+ console.log('🗑️ [API] Delete Conversation API - 删除会话:', {
+ user,
+ id,
+ hasJWT: !!frontendJWT
+ });
// 调用服务端API删除会话
- const data = await difyClient.deleteConversation(id, user);
+ const data = await difyClient.deleteConversation(id, user, frontendJWT);
- // console.log('✅ Delete Conversation API - Success:', data);
+ console.log('✅ [API] Delete Conversation API - Success');
return json(data, {
headers: {
@@ -30,13 +51,17 @@ export async function action({ request, params }: ActionFunctionArgs) {
return json({ error: '不支持的请求方法' }, { status: 405 });
} catch (error: any) {
- console.error('❌ Delete Conversation API - Error:', error);
+ console.error('❌ [API] Delete Conversation API - Error:', error);
+
+ // 检查是否是JWT认证失败
+ const status = error.message?.includes('JWT认证失败') ? 401 : 500;
+
return json(
{
error: error.message || '删除会话失败'
},
{
- status: 500,
+ status,
headers: {
'Set-Cookie': await commitSession((await getSessionInfo(request)).session),
},
diff --git a/app/routes/api.conversations.tsx b/app/routes/api.conversations.tsx
index d0be5f5..5eb3032 100644
--- a/app/routes/api.conversations.tsx
+++ b/app/routes/api.conversations.tsx
@@ -4,13 +4,33 @@ import { getSessionInfo, commitSession } from '../utils/session.server';
export async function loader({ request }: LoaderFunctionArgs) {
try {
+ // 获取用户会话信息和 JWT
+ const { getUserSession } = await import("~/api/login/auth.server");
+ const { frontendJWT } = await getUserSession(request);
const { user, session } = await getSessionInfo(request);
- // ('💬 Conversations API - User:', user);
+ // 检查 JWT 是否存在
+ if (!frontendJWT) {
+ console.error('❌ [API] Conversations API - JWT不存在');
+ return json(
+ { data: [], error: 'JWT认证失败,请重新登录' },
+ {
+ status: 401,
+ headers: {
+ 'Set-Cookie': await commitSession(session),
+ },
+ }
+ );
+ }
- const data = await difyClient.getConversations(user);
+ console.log('💬 [API] Conversations API - 获取会话列表:', {
+ user,
+ hasJWT: !!frontendJWT
+ });
- // ('✅ Conversations API - Success:', data);
+ const data = await difyClient.getConversations(user, frontendJWT);
+
+ console.log('✅ [API] Conversations API - Success');
return json(data, {
headers: {
@@ -18,14 +38,18 @@ export async function loader({ request }: LoaderFunctionArgs) {
},
});
} catch (error: any) {
- console.error('❌ Conversations API - Error:', error);
+ console.error('❌ [API] Conversations API - Error:', error);
+
+ // 检查是否是JWT认证失败
+ const status = error.message?.includes('JWT认证失败') ? 401 : 500;
+
return json(
{
data: [],
error: error.message || 'Failed to fetch conversations'
},
{
- status: 500,
+ status,
headers: {
'Set-Cookie': await commitSession((await getSessionInfo(request)).session),
},
diff --git a/app/routes/api.messages.tsx b/app/routes/api.messages.tsx
index c32a624..6a6dc38 100644
--- a/app/routes/api.messages.tsx
+++ b/app/routes/api.messages.tsx
@@ -4,6 +4,9 @@ import { getSessionInfo, commitSession } from '../utils/session.server';
export async function loader({ request }: LoaderFunctionArgs) {
try {
+ // 获取用户会话信息和 JWT
+ const { getUserSession } = await import("~/api/login/auth.server");
+ const { frontendJWT } = await getUserSession(request);
const { user, session } = await getSessionInfo(request);
const url = new URL(request.url);
const conversationId = url.searchParams.get('conversation_id');
@@ -15,11 +18,29 @@ export async function loader({ request }: LoaderFunctionArgs) {
);
}
- // ('📨 Messages API - User:', user, 'ConversationId:', conversationId);
+ // 检查 JWT 是否存在
+ if (!frontendJWT) {
+ console.error('❌ [API] Messages API - JWT不存在');
+ return json(
+ { error: 'JWT认证失败,请重新登录' },
+ {
+ status: 401,
+ headers: {
+ 'Set-Cookie': await commitSession(session),
+ },
+ }
+ );
+ }
- const data = await difyClient.getConversationMessages(user, conversationId);
+ console.log('📨 [API] Messages API - 获取会话消息:', {
+ user,
+ conversationId,
+ hasJWT: !!frontendJWT
+ });
- // ('✅ Messages API - Success:', data);
+ const data = await difyClient.getConversationMessages(user, conversationId, frontendJWT);
+
+ console.log('✅ [API] Messages API - Success');
return json(data, {
headers: {
@@ -27,11 +48,15 @@ export async function loader({ request }: LoaderFunctionArgs) {
},
});
} catch (error: any) {
- console.error('❌ Messages API - Error:', error);
+ console.error('❌ [API] Messages API - Error:', error);
+
+ // 检查是否是JWT认证失败
+ const status = error.message?.includes('JWT认证失败') ? 401 : 500;
+
return json(
{ error: error.message || 'Failed to fetch messages' },
{
- status: 500,
+ status,
headers: {
'Set-Cookie': await commitSession((await getSessionInfo(request)).session),
},
diff --git a/app/routes/api.parameters.tsx b/app/routes/api.parameters.tsx
index 181712b..cea8995 100644
--- a/app/routes/api.parameters.tsx
+++ b/app/routes/api.parameters.tsx
@@ -4,13 +4,33 @@ import { getSessionInfo, commitSession } from '../utils/session.server';
export async function loader({ request }: LoaderFunctionArgs) {
try {
+ // 获取用户会话信息和 JWT
+ const { getUserSession } = await import("~/api/login/auth.server");
+ const { frontendJWT } = await getUserSession(request);
const { user, session } = await getSessionInfo(request);
- // ('📋 Parameters API - User:', user);
+ // 检查 JWT 是否存在
+ if (!frontendJWT) {
+ console.error('❌ [API] Parameters API - JWT不存在');
+ return json(
+ { error: 'JWT认证失败,请重新登录' },
+ {
+ status: 401,
+ headers: {
+ 'Set-Cookie': await commitSession(session),
+ },
+ }
+ );
+ }
- const data = await difyClient.getApplicationParameters(user);
+ console.log('📋 [API] Parameters API - 获取应用参数:', {
+ user,
+ hasJWT: !!frontendJWT
+ });
- // ('✅ Parameters API - Success:', data);
+ const data = await difyClient.getApplicationParameters(user, frontendJWT);
+
+ console.log('✅ [API] Parameters API - Success');
return json(data, {
headers: {
@@ -18,11 +38,15 @@ export async function loader({ request }: LoaderFunctionArgs) {
},
});
} catch (error: any) {
- console.error('❌ Parameters API - Error:', error);
+ console.error('❌ [API] Parameters API - Error:', error);
+
+ // 检查是否是JWT认证失败
+ const status = error.message?.includes('JWT认证失败') ? 401 : 500;
+
return json(
{ error: error.message || 'Failed to fetch parameters' },
{
- status: 500,
+ status,
headers: {
'Set-Cookie': await commitSession((await getSessionInfo(request)).session),
},
diff --git a/app/services/dify-client.server.ts b/app/services/dify-client.server.ts
index b40d4a6..c9c09c1 100644
--- a/app/services/dify-client.server.ts
+++ b/app/services/dify-client.server.ts
@@ -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)['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();
},
};
diff --git a/docs/dify-frontend-modification-summary.md b/docs/dify-frontend-modification-summary.md
new file mode 100644
index 0000000..5439a16
--- /dev/null
+++ b/docs/dify-frontend-modification-summary.md
@@ -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
diff --git a/docs/dify-proxy-backend-integration.md b/docs/dify-proxy-backend-integration.md
new file mode 100644
index 0000000..0ff813a
--- /dev/null
+++ b/docs/dify-proxy-backend-integration.md
@@ -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 测试所有接口
+- [ ] 与前端联调测试通过
+
+---
+
+**祝开发顺利!** 🎉