From ccd5cdf71e6550e687f4299e29ab885e240e17f9 Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Fri, 25 Jul 2025 09:49:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=B3=BB=E7=BB=9F=E6=A6=82?= =?UTF-8?q?=E8=A7=88=E6=95=B0=E6=8D=AE=E4=B8=8D=E5=87=86=E7=A1=AE=E7=9A=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E3=80=82=E4=BF=AE=E5=A4=8D=E4=BA=A4=E5=8F=89?= =?UTF-8?q?=E8=AF=84=E6=9F=A5=E6=84=8F=E8=A7=81=E5=88=97=E8=A1=A8=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E6=9F=A5=E8=AF=A2=E3=80=82=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E6=B6=88=E6=81=AF=E6=8F=90=E7=A4=BA=E7=9A=84?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=E3=80=82=E4=BC=98=E5=8C=96=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E8=BF=9B=E8=A1=8C=E5=B1=80=E9=83=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DYNAMIC-CLIENT-CONFIG.md | 213 +++++++++ MULTI-CLIENT-DEPLOYMENT.md | 279 ------------ NGINX-CONFIG-ANALYSIS.md | 429 ++++++++++++++++++ UBUNTU-NGINX-TEST-GUIDE.md | 422 +++++++++++++++++ app/api/axios-client.ts | 24 +- app/api/cross-checking/cross-file-result.ts | 21 +- app/api/cross-checking/cross-files.ts | 4 +- app/api/evaluation_points/reviews.ts | 3 +- app/api/home/home.ts | 19 +- app/api/login/token-manager.server.ts | 4 +- app/api/user/user-management.ts | 2 +- .../cross-checking/ReviewPointsList.tsx | 93 ++-- app/components/ui/Toast.tsx | 2 +- app/config/api-config.ts | 204 +++++++-- app/routes/callback.tsx | 4 +- app/routes/cross-checking.result.tsx | 45 +- app/routes/cross-checking.upload.tsx | 14 +- app/routes/login.tsx | 28 +- app/routes/logout.tsx | 4 +- app/routes/test.client-config.tsx | 249 ++++++++++ app/styles/components/toast.css | 2 +- app/utils/client-detection.ts | 54 +++ deploy-multi-client.sh | 255 ----------- nginx-local-dev.conf | 169 ------- nginx-multi-client.conf | 162 ------- nginx-ubuntu-optimized.conf | 377 +++++++++++++++ nginx.conf | 51 +++ ubuntu-nginx-setup.sh | 342 ++++++++++++++ vite.config.ts | 4 +- 29 files changed, 2444 insertions(+), 1035 deletions(-) create mode 100644 DYNAMIC-CLIENT-CONFIG.md delete mode 100644 MULTI-CLIENT-DEPLOYMENT.md create mode 100644 NGINX-CONFIG-ANALYSIS.md create mode 100644 UBUNTU-NGINX-TEST-GUIDE.md create mode 100644 app/routes/test.client-config.tsx create mode 100644 app/utils/client-detection.ts delete mode 100644 deploy-multi-client.sh delete mode 100644 nginx-local-dev.conf delete mode 100644 nginx-multi-client.conf create mode 100644 nginx-ubuntu-optimized.conf create mode 100644 nginx.conf create mode 100644 ubuntu-nginx-setup.sh diff --git a/DYNAMIC-CLIENT-CONFIG.md b/DYNAMIC-CLIENT-CONFIG.md new file mode 100644 index 0000000..f2c49ca --- /dev/null +++ b/DYNAMIC-CLIENT-CONFIG.md @@ -0,0 +1,213 @@ +# 动态客户端配置使用指南 + +## 🎯 概述 + +本项目支持通过Nginx代理自动识别客户端,无需手动设置环境变量。Nginx会在请求头中传递 `X-Client-ID`,前端应用会自动读取并使用对应的配置。 + +## 🔧 工作原理 + +``` +用户访问 → Nginx代理 → 添加X-Client-ID头部 → 前端应用 → 动态获取配置 +``` + +### 端口映射 + +| 端口 | 客户端ID | 说明 | +|------|----------|------| +| 5174 | client-a | 客户端A | +| 5175 | client-b | 客户端B | +| 5176 | client-c | 客户端C | +| 5177 | client-d | 客户端D | + +## 📁 相关文件 + +### 核心配置文件 +- `app/config/api-config.ts` - API配置管理(已更新支持动态配置) +- `nginx-ubuntu-optimized.conf` - Nginx配置文件 + +### 新增工具文件 +- `app/utils/client-detection.ts` - 客户端检测工具函数 +- `app/examples/dynamic-config-usage.ts` - 使用示例 +- `app/routes/test.client-config.tsx` - 配置测试页面 + +## 🚀 使用方法 + +### 1. 在Remix Loader中使用 + +```typescript +import type { LoaderFunctionArgs } from '@remix-run/node'; +import { getApiConfig, getApiBaseUrl } from '~/config/api-config'; + +export async function loader({ request }: LoaderFunctionArgs) { + // 方法1: 获取完整配置 + const config = getApiConfig(request); + + // 方法2: 获取特定配置项 + const baseUrl = getApiBaseUrl(request); + + // 使用动态配置进行API调用 + const response = await fetch(`${baseUrl}/api/data`); + const data = await response.json(); + + return { data }; +} +``` + +### 2. 在Action中使用 + +```typescript +import type { ActionFunctionArgs } from '@remix-run/node'; +import { getUploadUrl } from '~/config/api-config'; + +export async function action({ request }: ActionFunctionArgs) { + const uploadUrl = getUploadUrl(request); + const formData = await request.formData(); + + const response = await fetch(uploadUrl, { + method: 'POST', + body: formData + }); + + return await response.json(); +} +``` + +### 3. OAuth重定向URL生成 + +```typescript +import { getOAuthConfig } from '~/config/api-config'; + +export function generateOAuthUrl(request: Request): string { + const oauthConfig = getOAuthConfig(request); + + const params = new URLSearchParams({ + client_id: oauthConfig.clientId, + redirect_uri: oauthConfig.redirectUri, + response_type: 'code' + }); + + return `${oauthConfig.serverUrl}/oauth/authorize?${params.toString()}`; +} +``` + +## 🧪 测试验证 + +### 1. 访问测试页面 + +通过不同端口访问测试页面来验证配置: + +```bash +# 客户端A +http://localhost:5174/test/client-config + +# 客户端B +http://localhost:5175/test/client-config + +# 客户端C +http://localhost:5176/test/client-config + +# 客户端D +http://localhost:5177/test/client-config +``` + +### 2. 检查配置状态 + +测试页面会显示: +- ✅ Nginx代理是否正常工作 +- ✅ 客户端识别是否成功 +- ✅ 配置匹配是否正确 + +### 3. 验证请求头 + +在浏览器开发者工具中检查网络请求,确认包含: +- `X-Client-ID`: 对应的客户端标识 +- `X-Original-Port`: 原始端口号 +- `X-Forwarded-Port`: 转发端口号 + +## 🔍 调试信息 + +### 查看Nginx日志 + +```bash +# 查看客户端A的访问日志 +sudo tail -f /var/log/nginx/client-a-access.log + +# 查看客户端D的访问日志 +sudo tail -f /var/log/nginx/client-d-access.log +``` + +### 使用调试工具 + +```typescript +import { detectClientFromRequest, getRequestDebugInfo } from '~/utils/client-detection'; + +export async function loader({ request }: LoaderFunctionArgs) { + const clientId = detectClientFromRequest(request); + const debugInfo = getRequestDebugInfo(request); + + console.log('🔧 调试信息:', { clientId, debugInfo }); + + // ... 其他逻辑 +} +``` + +## ⚠️ 注意事项 + +### 1. 兼容性 +- 保持了原有的静态配置导出,确保现有代码不受影响 +- 新的动态配置函数是可选的,可以逐步迁移 + +### 2. 环境要求 +- 需要Nginx正确配置并运行 +- 确保 `nginx-ubuntu-optimized.conf` 配置文件已应用 + +### 3. 错误处理 +- 如果无法检测到客户端ID,会回退到默认值 'main' +- 如果Nginx未正确配置,仍可通过环境变量设置客户端ID + +## 🔄 迁移指南 + +### 从环境变量迁移到动态配置 + +**之前(需要设置环境变量):** +```bash +# .env文件 +NEXT_PUBLIC_CLIENT_ID=client-d +``` + +**现在(自动检测):** +```typescript +// 在loader中 +const config = getApiConfig(request); // 自动从请求头获取客户端ID +``` + +### 更新现有代码 + +1. **替换静态配置调用:** + ```typescript + // 旧方式 + import { API_BASE_URL } from '~/config/api-config'; + + // 新方式 + import { getApiBaseUrl } from '~/config/api-config'; + const baseUrl = getApiBaseUrl(request); + ``` + +2. **在loader/action中传递request:** + ```typescript + // 确保所有需要配置的地方都能访问到request对象 + export async function loader({ request }: LoaderFunctionArgs) { + const config = getApiConfig(request); + // ... + } + ``` + +## 🎉 优势 + +1. **无需手动配置** - 不再需要为每个客户端设置环境变量 +2. **自动识别** - 通过访问端口自动识别客户端 +3. **动态切换** - 同一个应用实例支持多个客户端 +4. **向后兼容** - 现有代码无需修改即可继续工作 +5. **易于调试** - 提供完整的调试信息和测试页面 + +现在你可以直接通过 `http://localhost:5177` 访问应用,系统会自动识别为 `client-d` 并使用对应的配置! \ No newline at end of file diff --git a/MULTI-CLIENT-DEPLOYMENT.md b/MULTI-CLIENT-DEPLOYMENT.md deleted file mode 100644 index a60c26a..0000000 --- a/MULTI-CLIENT-DEPLOYMENT.md +++ /dev/null @@ -1,279 +0,0 @@ -# 多客户端部署方案说明 - -## 概述 - -本方案实现了基于PM2和Nginx的多客户端部署架构,允许不同地区的客户通过不同端口访问同一个应用服务。 - -## 架构设计 - -``` -客户端A (51701) ──┐ -客户端B (51702) ──┼── Nginx反向代理 ──→ 主服务 (51703) -客户端C (51704) ──┘ -``` - -### 端口分配 - -- **主服务**: `10.79.97.17:51703` - 核心应用服务 -- **客户端A**: `10.79.97.17:51701` - 地区A客户访问 -- **客户端B**: `10.79.97.17:51702` - 地区B客户访问 -- **客户端C**: `10.79.97.17:51704` - 地区C客户访问 - -## 文件说明 - -### 1. ecosystem.config.cjs -PM2部署配置文件,定义了4个应用实例: -- `docreview-main`: 主服务 (端口51703) -- `docreview-client-a`: 客户端A代理 (端口51701) -- `docreview-client-b`: 客户端B代理 (端口51702) -- `docreview-client-c`: 客户端C代理 (端口51704) - -### 2. api-config.ts -应用配置文件,支持根据`CLIENT_ID`环境变量加载不同客户端配置: -- 默认配置 (main) -- 客户端A配置 (client-a) -- 客户端B配置 (client-b) -- 客户端C配置 (client-c) - -### 3. nginx-multi-client.conf -Nginx反向代理配置文件,为每个客户端端口配置独立的代理规则。 - -### 4. 部署脚本 -- `deploy-multi-client.sh`: Linux/macOS部署脚本 -- `deploy-multi-client.bat`: Windows部署脚本 - -## 部署步骤 - -### Windows环境部署 - -1. **检查环境依赖** - ```bash - node --version - npm --version - pm2 --version - ``` - -2. **使用部署脚本** - ```bash - # 完整部署 - deploy-multi-client.bat deploy - - # 仅构建项目 - deploy-multi-client.bat build - - # 仅部署PM2 - deploy-multi-client.bat pm2 - - # 检查状态 - deploy-multi-client.bat status - ``` - -3. **手动配置Nginx** (Windows) - - 安装Nginx for Windows - - 将`nginx-multi-client.conf`内容添加到nginx配置中 - - 重启Nginx服务 - -### Linux/macOS环境部署 - -1. **使用部署脚本** - ```bash - chmod +x deploy-multi-client.sh - - # 完整部署(包含Nginx配置) - ./deploy-multi-client.sh deploy - - # 检查状态 - ./deploy-multi-client.sh status - ``` - -### 手动部署步骤 - -1. **构建项目** - ```bash - npm install - npm run build - ``` - -2. **启动PM2应用** - ```bash - pm2 start ecosystem.config.cjs - pm2 save - pm2 startup - ``` - -3. **配置Nginx** - ```bash - # 复制配置文件 - sudo cp nginx-multi-client.conf /etc/nginx/sites-available/docreview-multi-client - sudo ln -s /etc/nginx/sites-available/docreview-multi-client /etc/nginx/sites-enabled/ - - # 测试配置 - sudo nginx -t - - # 重载配置 - sudo systemctl reload nginx - ``` - -## 配置说明 - -### 客户端特定配置 - -每个客户端可以有独立的配置,在`api-config.ts`中定义: - -```typescript -const clientConfigs = { - 'client-a': { - baseUrl: 'http://10.79.97.17:51701/api', - uploadUrl: 'http://10.79.97.17:51701/api/upload', - oauth: { - serverUrl: 'http://10.79.97.17:51701/oauth', - clientId: 'client-a-id', - // ... 其他配置 - } - }, - // ... 其他客户端配置 -}; -``` - -### 环境变量 - -每个PM2应用实例都有独立的环境变量: - -- `CLIENT_ID`: 客户端标识 (main, client-a, client-b, client-c) -- `PROXY_TARGET`: 代理目标地址 (仅客户端实例) -- `PORT`: 监听端口 - -## 监控和管理 - -### PM2管理命令 - -```bash -# 查看所有应用状态 -pm2 status - -# 查看特定应用日志 -pm2 logs docreview-main -pm2 logs docreview-client-a - -# 重启应用 -pm2 restart docreview-main -pm2 restart all - -# 停止应用 -pm2 stop docreview-main -pm2 stop all - -# 删除应用 -pm2 delete docreview-main -pm2 delete all -``` - -### 日志文件位置 - -**PM2日志**: -- 主服务: `logs/main-out.log`, `logs/main-error.log` -- 客户端A: `logs/client-a-out.log`, `logs/client-a-error.log` -- 客户端B: `logs/client-b-out.log`, `logs/client-b-error.log` -- 客户端C: `logs/client-c-out.log`, `logs/client-c-error.log` - -**Nginx日志**: -- 客户端A: `/var/log/nginx/client-a-access.log`, `/var/log/nginx/client-a-error.log` -- 客户端B: `/var/log/nginx/client-b-access.log`, `/var/log/nginx/client-b-error.log` -- 客户端C: `/var/log/nginx/client-c-access.log`, `/var/log/nginx/client-c-error.log` - -### 健康检查 - -每个客户端端口都提供健康检查接口: - -```bash -# 检查各端口状态 -curl http://10.79.97.17:51701/health # 客户端A -curl http://10.79.97.17:51702/health # 客户端B -curl http://10.79.97.17:51703/health # 主服务 -curl http://10.79.97.17:51704/health # 客户端C -``` - -## 故障排除 - -### 常见问题 - -1. **端口被占用** - ```bash - # 查看端口占用 - netstat -tlnp | grep :51703 - - # 杀死占用进程 - kill -9 - ``` - -2. **PM2应用启动失败** - ```bash - # 查看详细错误日志 - pm2 logs docreview-main --lines 50 - - # 重新加载配置 - pm2 reload ecosystem.config.cjs - ``` - -3. **Nginx代理失败** - ```bash - # 检查nginx配置 - sudo nginx -t - - # 查看nginx错误日志 - sudo tail -f /var/log/nginx/error.log - ``` - -4. **客户端配置不生效** - - 检查`CLIENT_ID`环境变量是否正确设置 - - 确认`api-config.ts`中的客户端配置是否正确 - - 重启相关PM2应用 - -### 调试模式 - -启用调试模式查看详细日志: - -```bash -# 设置调试环境变量 -export DEBUG=* - -# 重启应用 -pm2 restart all -``` - -## 扩展和优化 - -### 添加新客户端 - -1. 在`ecosystem.config.cjs`中添加新的应用配置 -2. 在`api-config.ts`中添加客户端特定配置 -3. 在`nginx-multi-client.conf`中添加新的server块 -4. 重新部署应用 - -### 性能优化 - -1. **启用Nginx缓存** -2. **配置负载均衡** -3. **启用Gzip压缩** -4. **配置SSL/TLS** - -### 安全加固 - -1. **配置防火墙规则** -2. **启用访问控制** -3. **配置SSL证书** -4. **设置访问频率限制** - -## 联系支持 - -如果在部署过程中遇到问题,请检查: -1. 系统依赖是否完整安装 -2. 端口是否被其他服务占用 -3. 配置文件语法是否正确 -4. 日志文件中的错误信息 - -部署完成后,可以通过以下地址访问不同客户端: -- 客户端A: http://10.79.97.17:51701 -- 客户端B: http://10.79.97.17:51702 -- 客户端C: http://10.79.97.17:51704 -- 主服务: http://10.79.97.17:51703 \ No newline at end of file diff --git a/NGINX-CONFIG-ANALYSIS.md b/NGINX-CONFIG-ANALYSIS.md new file mode 100644 index 0000000..e9cb872 --- /dev/null +++ b/NGINX-CONFIG-ANALYSIS.md @@ -0,0 +1,429 @@ +# Nginx配置分析与优化建议 + +## 原始配置分析 + +### 当前配置文件:`nginx.conf` + +```nginx +server { + listen 5174; + server_name localhost; + + location / { + proxy_pass http://172.16.0.34:5173; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +# ... 其他三个相同的server块 +``` + +## 问题识别与分析 + +### 🔴 严重问题 + +#### 1. **缺少客户端标识机制** +**问题**:所有端口代理到同一个后端,但没有传递客户端标识信息 +**影响**:应用无法区分来自不同端口的请求,无法实现多客户端配置切换 +**解决方案**: +```nginx +# 添加客户端标识变量和头部 +set $client_id "client-a"; # 每个server块设置不同的client_id +proxy_set_header X-Client-ID $client_id; +proxy_set_header X-Original-Port $server_port; +``` + +#### 2. **缺少上游服务器配置** +**问题**:直接在proxy_pass中硬编码后端地址 +**影响**:无法实现负载均衡、健康检查、连接池等高级功能 +**解决方案**: +```nginx +upstream vite_dev_server { + server 172.16.0.34:5173; + keepalive 32; # 连接池 +} +``` + +#### 3. **缺少开发环境特殊配置** +**问题**:没有配置WebSocket支持,影响Vite热重载功能 +**影响**:开发时热重载可能不工作 +**解决方案**: +```nginx +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection "upgrade"; +proxy_http_version 1.1; +``` + +### 🟡 重要问题 + +#### 4. **缺少日志配置** +**问题**:没有配置访问日志和错误日志 +**影响**:无法监控和调试请求 +**解决方案**: +```nginx +access_log /var/log/nginx/client-a-access.log; +error_log /var/log/nginx/client-a-error.log warn; +``` + +#### 5. **缺少超时配置** +**问题**:使用默认超时设置,可能导致请求超时 +**影响**:长时间请求可能被意外中断 +**解决方案**: +```nginx +proxy_connect_timeout 30s; +proxy_send_timeout 30s; +proxy_read_timeout 30s; +``` + +#### 6. **缺少缓冲控制** +**问题**:默认启用代理缓冲,影响实时性 +**影响**:开发环境下可能影响实时更新 +**解决方案**: +```nginx +proxy_buffering off; +proxy_cache off; +proxy_request_buffering off; +``` + +### 🟢 改进建议 + +#### 7. **缺少健康检查端点** +**建议**:添加健康检查端点便于监控 +**好处**:可以快速检测服务状态 +**实现**: +```nginx +location /health { + access_log off; + return 200 "Client A (Port 5174) - OK\n"; + add_header Content-Type text/plain; +} +``` + +#### 8. **缺少CORS配置** +**建议**:添加CORS头部支持跨域请求 +**好处**:支持前端开发时的跨域请求 +**实现**: +```nginx +add_header Access-Control-Allow-Origin * always; +add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; +``` + +#### 9. **缺少文件上传大小限制** +**建议**:设置合理的文件上传大小限制 +**好处**:防止大文件攻击,提高安全性 +**实现**: +```nginx +client_max_body_size 100M; +``` + +#### 10. **缺少安全头部** +**建议**:添加基本的安全头部 +**好处**:提高应用安全性 +**实现**: +```nginx +add_header X-Frame-Options SAMEORIGIN always; +add_header X-Content-Type-Options nosniff always; +add_header X-XSS-Protection "1; mode=block" always; +``` + +## 详细优化方案 + +### 1. 客户端标识传递机制 + +**原理**:通过Nginx变量和请求头传递客户端信息 + +```nginx +server { + listen 5174; + set $client_id "client-a"; # 设置客户端标识 + + location / { + proxy_pass http://vite_dev_server; + # 传递客户端标识 + proxy_set_header X-Client-ID $client_id; + proxy_set_header X-Original-Port $server_port; + proxy_set_header X-Forwarded-Port $server_port; + } +} +``` + +**应用端接收**: +```typescript +// 在Remix loader或action中 +export const loader: LoaderFunction = async ({ request }) => { + const clientId = request.headers.get('X-Client-ID') || 'main'; + const config = getConfigForClient(clientId); + return json({ config }); +}; +``` + +### 2. 上游服务器配置优化 + +**连接池配置**: +```nginx +upstream vite_dev_server { + server 172.16.0.34:5173; + keepalive 32; # 保持32个长连接 + keepalive_requests 100; # 每个连接最多处理100个请求 + keepalive_timeout 60s; # 连接空闲60秒后关闭 +} +``` + +**健康检查**(需要nginx-plus或第三方模块): +```nginx +upstream vite_dev_server { + server 172.16.0.34:5173 max_fails=3 fail_timeout=30s; + # 3次失败后标记为不可用,30秒后重试 +} +``` + +### 3. 开发环境特殊配置 + +**WebSocket支持**: +```nginx +location / { + proxy_pass http://vite_dev_server; + + # WebSocket支持(Vite HMR必需) + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + + # 禁用缓冲以支持实时更新 + proxy_buffering off; + proxy_cache off; + proxy_request_buffering off; +} +``` + +**API请求特殊处理**: +```nginx +location /api/ { + proxy_pass http://vite_dev_server; + + # API请求不需要WebSocket支持 + proxy_set_header X-Client-ID $client_id; + + # API请求超时配置 + proxy_connect_timeout 10s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; +} +``` + +### 4. 日志配置优化 + +**自定义日志格式**: +```nginx +log_format client_access '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'client_id="$client_id" original_port="$server_port" ' + 'response_time=$request_time'; + +access_log /var/log/nginx/client-a-access.log client_access; +``` + +**日志轮转配置**: +```bash +# /etc/logrotate.d/nginx-clients +/var/log/nginx/client-*-access.log { + daily + missingok + rotate 52 + compress + delaycompress + notifempty + create 644 www-data www-data + postrotate + systemctl reload nginx + endscript +} +``` + +### 5. 性能优化配置 + +**缓存配置**: +```nginx +# 静态资源缓存 +location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + proxy_pass http://vite_dev_server; + proxy_cache_valid 200 1h; + add_header X-Cache-Status $upstream_cache_status; +} +``` + +**压缩配置**: +```nginx +# 在http块中 +gzip on; +gzip_vary on; +gzip_min_length 1024; +gzip_types + text/plain + text/css + application/json + application/javascript + text/xml + application/xml + application/xml+rss + text/javascript; +``` + +### 6. 安全配置 + +**基础安全头部**: +```nginx +# 开发环境相对宽松的安全配置 +add_header X-Frame-Options SAMEORIGIN always; +add_header X-Content-Type-Options nosniff always; +add_header X-XSS-Protection "1; mode=block" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +``` + +**请求限制**: +```nginx +# 限制请求频率(可选,开发环境通常不需要) +limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + +location /api/ { + limit_req zone=api burst=20 nodelay; + proxy_pass http://vite_dev_server; +} +``` + +## 配置模板对比 + +### 原始配置(简化版) +```nginx +server { + listen 5174; + server_name localhost; + location / { + proxy_pass http://172.16.0.34:5173; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +### 优化后配置(完整版) +```nginx +upstream vite_dev_server { + server 172.16.0.34:5173; + keepalive 32; +} + +log_format client_access '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + 'client_id="$client_id" port="$server_port"'; + +server { + listen 5174; + server_name localhost 127.0.0.1; + + set $client_id "client-a"; + + access_log /var/log/nginx/client-a-access.log client_access; + error_log /var/log/nginx/client-a-error.log warn; + + location / { + proxy_pass http://vite_dev_server; + + # 基础代理头部 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 客户端标识头部 + proxy_set_header X-Client-ID $client_id; + proxy_set_header X-Original-Port $server_port; + + # WebSocket支持 + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + + # 超时配置 + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + + # 缓冲控制 + proxy_buffering off; + proxy_cache off; + + # 文件上传限制 + client_max_body_size 100M; + } + + location /health { + access_log off; + return 200 "Client A - OK"; + add_header Content-Type text/plain; + } +} +``` + +## 实施优先级 + +### 🔴 高优先级(必须实施) +1. **客户端标识传递** - 核心功能需求 +2. **WebSocket支持** - 开发体验必需 +3. **上游服务器配置** - 性能和可维护性 + +### 🟡 中优先级(建议实施) +4. **日志配置** - 监控和调试 +5. **超时配置** - 稳定性 +6. **健康检查** - 运维便利 + +### 🟢 低优先级(可选实施) +7. **CORS配置** - 开发便利 +8. **安全头部** - 安全加固 +9. **缓存配置** - 性能优化 + +## 测试验证 + +### 功能测试 +```bash +# 1. 测试客户端标识传递 +curl -H "Accept: application/json" http://localhost:5174/api/config + +# 2. 测试健康检查 +curl http://localhost:5174/health + +# 3. 测试WebSocket(需要浏览器) +# 访问 http://localhost:5174 并检查开发者工具中的WebSocket连接 +``` + +### 性能测试 +```bash +# 使用ab进行简单压力测试 +ab -n 1000 -c 10 http://localhost:5174/ + +# 使用wrk进行更详细的测试 +wrk -t12 -c400 -d30s http://localhost:5174/ +``` + +### 日志验证 +```bash +# 检查日志是否正确记录客户端信息 +sudo tail -f /var/log/nginx/client-a-access.log | grep client_id +``` + +## 总结 + +原始nginx配置虽然能够实现基本的反向代理功能,但缺少多客户端支持的关键特性。通过以上优化,可以实现: + +1. **完整的多客户端支持** - 通过客户端标识传递 +2. **更好的开发体验** - WebSocket和热重载支持 +3. **增强的监控能力** - 详细的日志记录 +4. **提升的性能表现** - 连接池和缓存优化 +5. **基础的安全保障** - 安全头部和请求限制 + +这些改进为在Ubuntu环境中测试多客户端功能提供了完整的基础设施支持。 \ No newline at end of file diff --git a/UBUNTU-NGINX-TEST-GUIDE.md b/UBUNTU-NGINX-TEST-GUIDE.md new file mode 100644 index 0000000..b3fcf2d --- /dev/null +++ b/UBUNTU-NGINX-TEST-GUIDE.md @@ -0,0 +1,422 @@ +# Ubuntu环境下Nginx多客户端测试指南 + +## 概述 + +本指南详细说明如何在Ubuntu环境中配置和测试Nginx反向代理多客户端功能,实现根据不同端口动态切换API配置的能力。 + +## 架构说明 + +``` +客户端访问端口 Nginx代理 开发服务器 +5174 (client-a) ──→ 反向代理 ──→ 172.16.0.34:5173 +5175 (client-b) ──→ 反向代理 ──→ 172.16.0.34:5173 +5176 (client-c) ──→ 反向代理 ──→ 172.16.0.34:5173 +5177 (client-d) ──→ 反向代理 ──→ 172.16.0.34:5173 +``` + +每个端口通过 `X-Client-ID` 头部传递客户端标识,应用根据此标识动态选择对应的API配置。 + +## 环境要求 + +### 系统要求 +- Ubuntu 18.04+ 或其他Linux发行版 +- Nginx 1.18+ +- Node.js 18+ +- 网络访问权限到 172.16.0.34:5173 + +### 端口要求 +- 5174-5177:Nginx监听端口 +- 5173:开发服务器端口(需要在172.16.0.34上运行) + +## 安装和配置步骤 + +### 1. 安装Nginx + +```bash +# 更新包管理器 +sudo apt update + +# 安装Nginx +sudo apt install nginx -y + +# 检查Nginx版本 +nginx -v + +# 启动Nginx服务 +sudo systemctl start nginx +sudo systemctl enable nginx +``` + +### 2. 配置Nginx + +#### 2.1 备份原配置 +```bash +# 备份默认配置 +sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup +sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.backup +``` + +#### 2.2 创建多客户端配置 +```bash +# 创建配置目录 +sudo mkdir -p /etc/nginx/conf.d + +# 复制优化后的配置文件 +sudo cp nginx-ubuntu-optimized.conf /etc/nginx/conf.d/multi-client.conf + +# 或者直接创建配置文件 +sudo nano /etc/nginx/conf.d/multi-client.conf +# 然后粘贴 nginx-ubuntu-optimized.conf 的内容 +``` + +#### 2.3 创建日志目录 +```bash +# 创建客户端专用日志目录 +sudo mkdir -p /var/log/nginx/clients + +# 设置权限 +sudo chown -R www-data:www-data /var/log/nginx/clients +sudo chmod -R 755 /var/log/nginx/clients +``` + +#### 2.4 修改主配置文件 +```bash +# 编辑主配置文件 +sudo nano /etc/nginx/nginx.conf +``` + +确保包含以下配置: +```nginx +http { + # 包含多客户端配置 + include /etc/nginx/conf.d/*.conf; + + # 日志格式(如果主配置中没有) + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + # 其他配置... +} +``` + +### 3. 验证配置 + +```bash +# 测试Nginx配置语法 +sudo nginx -t + +# 如果配置正确,重新加载Nginx +sudo systemctl reload nginx + +# 检查Nginx状态 +sudo systemctl status nginx +``` + +### 4. 防火墙配置 + +```bash +# 允许Nginx端口通过防火墙 +sudo ufw allow 5174 +sudo ufw allow 5175 +sudo ufw allow 5176 +sudo ufw allow 5177 + +# 或者允许端口范围 +sudo ufw allow 5174:5177/tcp + +# 检查防火墙状态 +sudo ufw status +``` + +## 应用配置修改 + +### 1. 修改api-config.ts + +需要在开发环境配置中添加第四个客户端: + +```typescript +const getClientConfigs = (env: string): Record> => { + if (env === 'development') { + return { + 'client-a': { + baseUrl: 'http://172.16.0.34:5174', + uploadUrl: 'http://172.16.0.34:5174/admin/documents', + // ... oauth配置 + }, + 'client-b': { + baseUrl: 'http://172.16.0.34:5175', + uploadUrl: 'http://172.16.0.34:5175/admin/documents', + // ... oauth配置 + }, + 'client-c': { + baseUrl: 'http://172.16.0.34:5176', + uploadUrl: 'http://172.16.0.34:5176/admin/documents', + // ... oauth配置 + }, + 'client-d': { + baseUrl: 'http://172.16.0.34:5177', + uploadUrl: 'http://172.16.0.34:5177/admin/documents', + // ... oauth配置 + } + }; + } + // ... +}; +``` + +### 2. 添加客户端检测逻辑 + +在应用中添加根据请求头自动检测客户端的逻辑: + +```typescript +// 在服务器端或中间件中 +const detectClientFromHeaders = (request: Request): string => { + // 从Nginx传递的头部获取客户端ID + const clientId = request.headers.get('X-Client-ID'); + const originalPort = request.headers.get('X-Original-Port'); + + if (clientId) { + return clientId; + } + + // 根据端口映射客户端ID + const portToClient: Record = { + '5174': 'client-a', + '5175': 'client-b', + '5176': 'client-c', + '5177': 'client-d' + }; + + return portToClient[originalPort || ''] || 'main'; +}; +``` + +## 测试步骤 + +### 1. 启动开发服务器 + +确保在172.16.0.34机器上启动开发服务器: +```bash +# 在项目目录中 +npm run dev +# 或 +pnpm dev +# 确保服务运行在5173端口 +``` + +### 2. 测试Nginx代理 + +```bash +# 测试各个端口的健康检查 +curl http://localhost:5174/health +curl http://localhost:5175/health +curl http://localhost:5176/health +curl http://localhost:5177/health + +# 测试代理功能 +curl -H "Accept: text/html" http://localhost:5174/ +curl -H "Accept: text/html" http://localhost:5175/ +``` + +### 3. 验证客户端标识传递 + +```bash +# 检查请求头传递 +curl -v http://localhost:5174/api/test 2>&1 | grep "X-Client-ID" + +# 查看Nginx访问日志 +sudo tail -f /var/log/nginx/client-a-access.log +sudo tail -f /var/log/nginx/client-b-access.log +``` + +### 4. 浏览器测试 + +在浏览器中访问: +- http://172.16.0.34:5174 (Client A) +- http://172.16.0.34:5175 (Client B) +- http://172.16.0.34:5176 (Client C) +- http://172.16.0.34:5177 (Client D) + +### 5. 环境变量测试 + +```bash +# 设置客户端ID环境变量测试 +export CLIENT_ID=client-a +npm run dev + +# 或在启动时指定 +CLIENT_ID=client-b npm run dev +``` + +## 监控和调试 + +### 1. 日志监控 + +```bash +# 实时监控所有客户端日志 +sudo tail -f /var/log/nginx/client-*-access.log + +# 监控错误日志 +sudo tail -f /var/log/nginx/client-*-error.log + +# 监控Nginx主错误日志 +sudo tail -f /var/log/nginx/error.log +``` + +### 2. 性能监控 + +```bash +# 检查Nginx进程状态 +sudo systemctl status nginx + +# 查看端口监听状态 +sudo netstat -tlnp | grep nginx + +# 检查连接数 +sudo ss -tuln | grep :517 +``` + +### 3. 调试工具 + +```bash +# 使用curl测试详细信息 +curl -v -H "X-Test: true" http://localhost:5174/api/config + +# 使用httpie(需要安装) +sudo apt install httpie +http GET localhost:5174/health X-Test:debug +``` + +## 故障排除 + +### 常见问题 + +1. **端口被占用** +```bash +# 检查端口占用 +sudo lsof -i :5174 +# 杀死占用进程 +sudo kill -9 +``` + +2. **权限问题** +```bash +# 检查Nginx用户权限 +sudo chown -R www-data:www-data /var/log/nginx/ +sudo chmod -R 755 /var/log/nginx/ +``` + +3. **配置语法错误** +```bash +# 详细检查配置 +sudo nginx -t -c /etc/nginx/nginx.conf +``` + +4. **网络连接问题** +```bash +# 测试到开发服务器的连接 +telnet 172.16.0.34 5173 +# 或使用nc +nc -zv 172.16.0.34 5173 +``` + +### 日志分析 + +```bash +# 分析访问模式 +sudo awk '{print $1, $7, $9}' /var/log/nginx/client-a-access.log | sort | uniq -c + +# 查找错误请求 +sudo grep "50[0-9]" /var/log/nginx/client-*-access.log + +# 统计客户端访问量 +sudo grep -o 'client_id="[^"]*"' /var/log/nginx/client-*-access.log | sort | uniq -c +``` + +## 性能优化建议 + +### 1. Nginx优化 + +```nginx +# 在http块中添加 +worker_processes auto; +worker_connections 1024; + +# 启用gzip压缩 +gzip on; +gzip_vary on; +gzip_min_length 1024; +gzip_types text/plain text/css application/json application/javascript; + +# 缓存配置 +proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g + inactive=60m use_temp_path=off; +``` + +### 2. 系统优化 + +```bash +# 增加文件描述符限制 +echo "* soft nofile 65535" | sudo tee -a /etc/security/limits.conf +echo "* hard nofile 65535" | sudo tee -a /etc/security/limits.conf + +# 优化内核参数 +echo "net.core.somaxconn = 65535" | sudo tee -a /etc/sysctl.conf +sudo sysctl -p +``` + +## 部署到生产环境 + +### 1. 安全加固 + +```nginx +# 隐藏Nginx版本 +server_tokens off; + +# 限制请求大小 +client_max_body_size 10M; + +# 添加安全头部 +add_header X-Frame-Options DENY; +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; +``` + +### 2. SSL配置 + +```nginx +# HTTPS配置示例 +server { + listen 443 ssl http2; + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + # SSL优化配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512; +} +``` + +### 3. 监控集成 + +```bash +# 集成Prometheus监控 +sudo apt install nginx-module-prometheus + +# 或使用日志分析工具 +sudo apt install goaccess +goaccess /var/log/nginx/client-a-access.log -o report.html --log-format=COMBINED +``` + +## 总结 + +通过以上配置,你可以在Ubuntu环境中成功测试Nginx多客户端反向代理功能。关键点包括: + +1. **客户端标识传递**:通过 `X-Client-ID` 头部 +2. **端口映射**:5174-5177映射到不同客户端 +3. **配置动态切换**:应用根据客户端ID选择对应配置 +4. **日志分离**:每个客户端独立的访问和错误日志 +5. **健康检查**:每个端口提供独立的健康检查端点 + +这个方案为生产环境的多客户端部署提供了完整的测试基础。 \ No newline at end of file diff --git a/app/api/axios-client.ts b/app/api/axios-client.ts index 919f7b6..860547d 100644 --- a/app/api/axios-client.ts +++ b/app/api/axios-client.ts @@ -20,6 +20,9 @@ export type QueryParams = Record; // const API_BASE_URL = 'http://172.18.0.100:3000'; // const API_BASE_URL = 'http://172.16.0.119:9000/admin'; +// 调试:打印当前API_BASE_URL的值 +console.log('🔍 axios-client.ts - API_BASE_URL.value:', API_BASE_URL.value); + // 文档URL前缀 (从配置文件导入) // export const DOCUMENT_URL = 'http://nas.7bm.co:9000/docauditai/'; export { DOCUMENT_URL }; @@ -32,7 +35,7 @@ const DEFAULT_TIMEOUT = 30000; // 增加到30秒 // 创建 axios 实例 const axiosInstance = axios.create({ - baseURL: API_BASE_URL, + baseURL: API_BASE_URL.value === '/api' ? '' : API_BASE_URL.value, // 如果是相对路径,则不设置baseURL timeout: DEFAULT_TIMEOUT, // 增加超时时间 headers: { 'Content-Type': 'application/json', @@ -97,10 +100,17 @@ function buildUrl(endpoint: string, params?: QueryParams): string { if (endpoint.startsWith('http')) { fullUrl = endpoint; } else { - // 确保API_BASE_URL格式正确 - const baseUrl = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; - const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; - fullUrl = `${baseUrl}${path}`; + // 处理相对路径的情况 + if (API_BASE_URL.value === '/api') { + // 如果是相对路径,直接使用endpoint + const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + fullUrl = path; + } else { + // 确保API_BASE_URL格式正确 + const baseUrl = API_BASE_URL.value.endsWith('/') ? API_BASE_URL.value.slice(0, -1) : API_BASE_URL.value; + const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + fullUrl = `${baseUrl}${path}`; + } } try { @@ -189,6 +199,8 @@ export async function apiRequest( if (USE_MOCK_DATA) { return getMockResponse(endpoint); } + + console.log('api-base-url-----------',API_BASE_URL.value) try { // 构建 URL @@ -387,4 +399,4 @@ export async function downloadFile(path: string): Promise { console.error('下载文件失败:', error); throw error; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/app/api/cross-checking/cross-file-result.ts b/app/api/cross-checking/cross-file-result.ts index 76b26d2..b96d175 100644 --- a/app/api/cross-checking/cross-file-result.ts +++ b/app/api/cross-checking/cross-file-result.ts @@ -26,7 +26,7 @@ function extractApiData(responseData: unknown): T | null { export interface SubmitOpinionRequest { reviewPointResultId: string | number; documentId: string | number; - evaluationPointId: number; // 必须是数字ID + evaluationPointId: number | null; // 必须是数字ID auditOpinion: string; deductionScore: number; } @@ -60,6 +60,7 @@ export interface CrossCheckingOpinion { problem_message: string; proposer_id: number; created_at: string; + status: string; } /** @@ -132,7 +133,7 @@ export async function submitCrossCheckingOpinion( evaluation_result_id: opinionData.reviewPointResultId }; - const response = await fetch(`${API_BASE_URL}/admin/cross_review/proposals`, { + const response = await fetch(`${API_BASE_URL.value}/admin/cross_review/proposals`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -188,7 +189,7 @@ export async function getCrossCheckingOpinions( // 如果没传userId,默认用1 const realUserId = userId ?? 1; // 实际后端API调用,拼接API_BASE_URL - const response = await fetch(`${API_BASE_URL}/admin/cross_review/proposals/document`, { + const response = await fetch(`${API_BASE_URL.value}/admin/cross_review/proposals/document`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -225,6 +226,7 @@ export async function getCrossCheckingOpinions( problem_message?: string; proposer_id: number; created_at: string; + status: string; } // 适配后端返回结构,使用新字段 @@ -241,7 +243,8 @@ export async function getCrossCheckingOpinions( can_vote: item.can_vote ?? false, problem_message: item.problem_message || '', proposer_id: item.proposer_id, - created_at: item.created_at + created_at: item.created_at, + status: item.status })) : []; return { @@ -300,24 +303,24 @@ export async function performOpinionAction( switch (actionData.action) { case 'agree': message = '已赞同该意见'; - endpoint = `${API_BASE_URL}/admin/cross_review/proposals/${actionData.opinionId}/votes`; + endpoint = `${API_BASE_URL.value}/admin/cross_review/proposals/${actionData.opinionId}/votes`; requestBody = { vote_type: 'agree', voter_id: userInfo?.user_id }; break; case 'disagree': message = '已反对该意见'; - endpoint = `${API_BASE_URL}/admin/cross_review/proposals/${actionData.opinionId}/votes`; + endpoint = `${API_BASE_URL.value}/admin/cross_review/proposals/${actionData.opinionId}/votes`; requestBody = { vote_type: 'disagree', voter_id: userInfo?.user_id }; break; case 'withdraw_vote': message = '已撤销投票'; // 撤销投票的接口,根据实际API调整 - endpoint = `${API_BASE_URL}/admin/cross_review/proposals/${actionData.opinionId}/votes`; + endpoint = `${API_BASE_URL.value}/admin/cross_review/proposals/${actionData.opinionId}/votes`; requestBody = { vote_type: 'cancel', voter_id: userInfo?.user_id }; break; case 'withdraw_opinion': message = '已撤销意见'; // 撤销意见的接口,根据实际API调整 - endpoint = `${API_BASE_URL}/admin/cross_review/proposals/${actionData.opinionId}`; + endpoint = `${API_BASE_URL.value}/admin/cross_review/proposals/${actionData.opinionId}`; requestBody = {}; break; default: @@ -412,7 +415,7 @@ export async function checkProposalVotes( document_id: documentId }; - const response = await fetch(`${API_BASE_URL}/admin/cross_review/proposals/document/check_pending_votes`, { + const response = await fetch(`${API_BASE_URL.value}/admin/cross_review/proposals/document/check_pending_votes`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/api/cross-checking/cross-files.ts b/app/api/cross-checking/cross-files.ts index 435e248..b51acac 100644 --- a/app/api/cross-checking/cross-files.ts +++ b/app/api/cross-checking/cross-files.ts @@ -389,7 +389,7 @@ export async function getCrossCheckingStats(userInfo?: { user_id?: number; [key: export async function getUserTaskDocuments(page: number = 1, pageSize: number = 10, jwtToken?: string): Promise> { try { // 拼接绝对路径,去除多余斜杠 - const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; + const base = API_BASE_URL.value.endsWith('/') ? API_BASE_URL.value.slice(0, -1) : API_BASE_URL.value; const url = `${base}/admin/cross_review/tasks/user_tasks`; const response = await fetch(url, { @@ -436,7 +436,7 @@ export async function getUserTaskDocuments(page: number = 1, pageSize: number = export async function getTaskDocuments(taskId: number, page: number = 1, pageSize: number = 10, jwtToken?: string): Promise> { try { // 拼接绝对路径,去除多余斜杠 - const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; + const base = API_BASE_URL.value.endsWith('/') ? API_BASE_URL.value.slice(0, -1) : API_BASE_URL.value; const url = `${base}/admin/cross_review/tasks/${taskId}/documents`; // console.log('最终请求URL:', url); diff --git a/app/api/evaluation_points/reviews.ts b/app/api/evaluation_points/reviews.ts index 6a403ac..a596140 100644 --- a/app/api/evaluation_points/reviews.ts +++ b/app/api/evaluation_points/reviews.ts @@ -324,7 +324,8 @@ export async function getReviewPoints(fileId: string, request: Request) { const scoringProposalsParams: PostgrestParams = { select: '*', filter: { - 'document_id': `eq.${fileId}` + 'document_id': `eq.${fileId}`, + 'deleted_at': `is.null` } }; const scoringProposalsResponse = await postgrestGet('cross_scoring_proposals', scoringProposalsParams); diff --git a/app/api/home/home.ts b/app/api/home/home.ts index 3fd3ef4..615a5b0 100644 --- a/app/api/home/home.ts +++ b/app/api/home/home.ts @@ -105,8 +105,8 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n const startOfLastMonth = dayjs().subtract(1, 'month').startOf('month').format('YYYY-MM-DD HH:mm:ss'); const endOfLastMonth = dayjs().subtract(1, 'month').endOf('month').format('YYYY-MM-DD HH:mm:ss'); - console.log('传入的 reviewType', reviewType); - console.log('传入的 userId', userId); + // console.log('传入的 reviewType', reviewType); + // console.log('传入的 userId', userId); // 基于 reviewType 构建类型过滤条件 const typeFilter = buildTypeFilter(reviewType || null); @@ -181,7 +181,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n select: 'count', filter: { and: `(audit_status.neq.0,audit_status.neq.2)`, - updated_at: `gte.${startOfThisMonth}`, + upload_time: `gte.${startOfThisMonth}`, is_test_document: `eq.false`, user_id: `eq.${userId}` } @@ -212,8 +212,8 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n const lastMonthReviewedParams: PostgrestParams = { select: 'count', filter: { - or: `(audit_status.eq.1,audit_status.eq.-1)`, - and: `(updated_at.gte.${startOfLastMonth},updated_at.lte.${endOfLastMonth})`, + // or: `(audit_status.eq.1,audit_status.eq.-1)`, + and: `(upload_time.gte.${startOfLastMonth},upload_time.lte.${endOfLastMonth},audit_status.neq.0,audit_status.neq.2)`, is_test_document: `eq.false`, user_id: `eq.${userId}` } @@ -226,7 +226,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n if (!lastMonthReviewedParams.filter) { lastMonthReviewedParams.filter = {}; } - lastMonthReviewedParams.filter.or = lastMonthReviewedParams.filter.or + ',' + typeFilter; + lastMonthReviewedParams.filter.or = typeFilter; } else { const [field, op, value] = typeFilter.split('.'); if (!lastMonthReviewedParams.filter) { @@ -243,6 +243,8 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n ); // 上月已审核文件数量 const lastMonthReviewed = lastMonthReviewedCount[0]?.count || 0; + // console.log('上月已审核文件查询参数', lastMonthReviewedParams); + // console.log('上月已审核文件数量', lastMonthReviewed); // 计算同比增长 let reviewGrowthValue = 0; @@ -285,8 +287,11 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n '获取本月审核通过数量失败', [] ); + // console.log('本月审核通过数量查询参数', thisMonthTotalParams); // 本月审核通过数量 const thisMonthPassTotal = thisMonthTotalCount[0]?.count || 0; + // console.log('本月审核通过数量', thisMonthPassTotal); + // console.log('本月已审核文件数量', monthlyReviewedFiles); // 本月审核通过率 const monthlyPassRate = (thisMonthPassTotal > 0 && monthlyReviewedFiles > 0) @@ -298,7 +303,7 @@ export async function getHomeData(reviewType?: string | null,userId?: string | n select: 'count', filter: { audit_status: `eq.1`, - and: `(updated_at.gte.${startOfLastMonth},updated_at.lte.${endOfLastMonth})`, + and: `(upload_time.gte.${startOfLastMonth},upload_time.lte.${endOfLastMonth})`, is_test_document: `eq.false`, user_id: `eq.${userId}` } diff --git a/app/api/login/token-manager.server.ts b/app/api/login/token-manager.server.ts index cfc7506..350169e 100644 --- a/app/api/login/token-manager.server.ts +++ b/app/api/login/token-manager.server.ts @@ -29,7 +29,7 @@ export class TokenManager { private oauthClient: OAuthClient; constructor() { - this.oauthClient = new OAuthClient(OAUTH_CONFIG); + this.oauthClient = new OAuthClient(OAUTH_CONFIG.value); } /** @@ -151,4 +151,4 @@ export class TokenManager { } // 导出单例实例 -export const tokenManager = new TokenManager(); \ No newline at end of file +export const tokenManager = new TokenManager(); \ No newline at end of file diff --git a/app/api/user/user-management.ts b/app/api/user/user-management.ts index 450f132..af8017b 100644 --- a/app/api/user/user-management.ts +++ b/app/api/user/user-management.ts @@ -57,7 +57,7 @@ export async function getOrganizationTree(includeUsers: boolean = true, jwtToken if (jwtToken) { // 如果提供了JWT Token,则使用fetch并携带Authorization头 - const url = `${API_BASE_URL}/admin/users/organizations?include_users=${includeUsers}`; + const url = `${API_BASE_URL.value}/admin/users/organizations?include_users=${includeUsers}`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${jwtToken}`, diff --git a/app/components/cross-checking/ReviewPointsList.tsx b/app/components/cross-checking/ReviewPointsList.tsx index a8187e2..b739b0e 100644 --- a/app/components/cross-checking/ReviewPointsList.tsx +++ b/app/components/cross-checking/ReviewPointsList.tsx @@ -175,6 +175,7 @@ interface ReviewPointsListProps { scoringProposals?: ScoringProposal[]; jwtToken?: string; // 添加JWT token参数 userInfo?: UserInfo; // 添加用户信息参数 + onOpinionSubmitted?: (newProposal: ScoringProposal) => void; // 新增:意见提交成功后的回调 } /** @@ -433,12 +434,14 @@ export function ReviewPointsList({ onReviewPointSelect, scoringProposals = [], jwtToken, - userInfo + userInfo, + onOpinionSubmitted }: ReviewPointsListProps) { // 状态管理 const [searchText, setSearchText] = useState(''); // 搜索文本 const [statusFilter, setStatusFilter] = useState(null); // 状态过滤 const [evaluationResultIds, setEvaluationResultIds] = useState([]); // 评分提案的evaluation_result_id + const [localScoringProposals, setLocalScoringProposals] = useState(scoringProposals); // 本地状态管理scoringProposals const fetcher = useFetcher(); // 归一化 reviewPoints,确保每个点都有 id 字段 @@ -452,17 +455,21 @@ export function ReviewPointsList({ setNormalizedReviewPoints(norm); }, [reviewPoints]); - // 在组件中使用scoringProposals(这里只是简单使用以避免linter警告) - // 将来可以用于显示相关的评分提案信息 + // 同步外部scoringProposals到本地状态 useEffect(() => { - if (scoringProposals && scoringProposals.length > 0) { - // console.log('收到评分提案数据:', scoringProposals.length, '个提案'); + setLocalScoringProposals(scoringProposals); + }, [scoringProposals]); + + // 在组件中使用localScoringProposals + useEffect(() => { + if (localScoringProposals && localScoringProposals.length > 0) { + // console.log('收到评分提案数据:', localScoringProposals.length, '个提案'); // 获取提案的evaluation_result_id - const evaluationResultIds = scoringProposals.map(proposal => Number(proposal.evaluation_result_id)); + const evaluationResultIds = localScoringProposals.map(proposal => Number(proposal.evaluation_result_id)); setEvaluationResultIds(evaluationResultIds); // console.log('提案的evaluation_result_id:', evaluationResultIds); } - }, [scoringProposals]); + }, [localScoringProposals]); // 提出意见模态框相关状态 const [isOpinionModalOpen, setIsOpinionModalOpen] = useState(false); @@ -618,14 +625,14 @@ export function ReviewPointsList({ * 打开意见列表模态框 */ const handleOpenOpinionListModal = (reviewPoint: ReviewPoint) => { - console.log('查看reviewPoint', reviewPoint); - if (scoringProposals.length === 0) { + // console.log('查看reviewPoint', reviewPoint); + if (localScoringProposals.length === 0) { toastService.warning('当前文件尚未有人提出过意见'); return; } setSelectedReviewPoint(reviewPoint); setIsOpinionListModalOpen(true); - console.log('打开意见列表模态框'); + // console.log('打开意见列表模态框'); // 直接传递reviewPoint的documentId,避免依赖状态更新 loadOpinionListData(1, 10, reviewPoint.documentId); }; @@ -714,14 +721,14 @@ export function ReviewPointsList({ } // 新增:详细打印每个校验条件 - console.log('校验前 selectedReviewPoint:', selectedReviewPoint); - console.log('校验前 opinionForm:', opinionForm); - console.log('校验前 userInfo:', userInfo); - console.log('documentId:', selectedReviewPoint.documentId, 'isNaN:', isNaN(Number(selectedReviewPoint.documentId)), 'typeof:', typeof selectedReviewPoint.documentId); - console.log('pointId:', selectedReviewPoint.pointId, 'isNaN:', isNaN(Number(selectedReviewPoint.pointId)), 'typeof:', typeof selectedReviewPoint.pointId); - console.log('deductionScore:', opinionForm.deductionScore, 'typeof:', typeof opinionForm.deductionScore, 'isNaN:', isNaN(Number(opinionForm.deductionScore))); - console.log('auditOpinion:', opinionForm.auditOpinion, 'trim:', String(opinionForm.auditOpinion).trim(), 'typeof:', typeof opinionForm.auditOpinion); - console.log('user_id:', userInfo?.user_id, 'typeof:', typeof userInfo?.user_id); + // console.log('校验前 selectedReviewPoint:', selectedReviewPoint); + // console.log('校验前 opinionForm:', opinionForm); + // console.log('校验前 userInfo:', userInfo); + // console.log('documentId:', selectedReviewPoint.documentId, 'isNaN:', isNaN(Number(selectedReviewPoint.documentId)), 'typeof:', typeof selectedReviewPoint.documentId); + // console.log('pointId:', selectedReviewPoint.pointId, 'isNaN:', isNaN(Number(selectedReviewPoint.pointId)), 'typeof:', typeof selectedReviewPoint.pointId); + // console.log('deductionScore:', opinionForm.deductionScore, 'typeof:', typeof opinionForm.deductionScore, 'isNaN:', isNaN(Number(opinionForm.deductionScore))); + // console.log('auditOpinion:', opinionForm.auditOpinion, 'trim:', String(opinionForm.auditOpinion).trim(), 'typeof:', typeof opinionForm.auditOpinion); + // console.log('user_id:', userInfo?.user_id, 'typeof:', typeof userInfo?.user_id); // 更严谨的校验逻辑 if ( @@ -741,12 +748,12 @@ export function ReviewPointsList({ } // 打印所有关键数据 - console.log('selectedReviewPoint:', selectedReviewPoint); - console.log('opinionForm:', opinionForm); - console.log('userInfo:', userInfo); + // console.log('selectedReviewPoint:', selectedReviewPoint); + // console.log('opinionForm:', opinionForm); + // console.log('userInfo:', userInfo); // 组装后端要求的字段名和内容 - const data: Record = { + const data = { document_id: Number(selectedReviewPoint.documentId), evaluation_point_id: Number(selectedReviewPoint.pointId), proposed_score: Number(opinionForm.deductionScore), @@ -759,10 +766,10 @@ export function ReviewPointsList({ data.evaluation_result_id = Number(selectedReviewPoint.evaluationPointId); } // 打印最终请求体 - console.log('最终请求体:', data); + // console.log('最终请求体:', data); // 用原生 fetch + application/json 提交 try { - const response = await fetch(`${API_BASE_URL.replace(/\/$/, '')}/admin/cross_review/proposals`, { + const response = await fetch(`${API_BASE_URL.value.replace(/\/$/, '')}/admin/cross_review/proposals`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -773,6 +780,28 @@ export function ReviewPointsList({ const result = await response.json(); if (response.ok) { toastService.success('意见提交成功'); + + // 创建新的提案对象 + const newProposal: ScoringProposal = { + id: result.id || Date.now(), // 使用返回的ID或时间戳作为临时ID + evaluation_result_id: data.evaluation_result_id, + proposer_id: data.proposer_id as number, + proposed_score: data.proposed_score, + reason: data.reason, + status: 'pending', // 默认状态 + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + document_id: data.document_id + }; + + // 更新本地状态 + setLocalScoringProposals(prev => [...prev, newProposal]); + + // 调用父组件回调(如果提供) + if (onOpinionSubmitted) { + onOpinionSubmitted(newProposal); + } + handleCloseOpinionModal(); } else { toastService.error(result.detail || '提交意见失败'); @@ -2487,7 +2516,7 @@ export function ReviewPointsList({