配置好正式环境的nginx配置文件
This commit is contained in:
@@ -1,213 +0,0 @@
|
|||||||
# 动态客户端配置使用指南
|
|
||||||
|
|
||||||
## 🎯 概述
|
|
||||||
|
|
||||||
本项目支持通过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` 并使用对应的配置!
|
|
||||||
@@ -1,429 +0,0 @@
|
|||||||
# 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环境中测试多客户端功能提供了完整的基础设施支持。
|
|
||||||
@@ -1,422 +0,0 @@
|
|||||||
# 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<string, Partial<ApiConfig>> => {
|
|
||||||
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<string, string> = {
|
|
||||||
'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 <PID>
|
|
||||||
```
|
|
||||||
|
|
||||||
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. **健康检查**:每个端口提供独立的健康检查端点
|
|
||||||
|
|
||||||
这个方案为生产环境的多客户端部署提供了完整的测试基础。
|
|
||||||
+26
-44
@@ -1,48 +1,30 @@
|
|||||||
server {
|
# 基于 state 参数端口分发的 OAuth2 回调 Nginx 配置
|
||||||
listen 5174;
|
# 只保留回调分发相关配置,其他内容全部删除
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
location / {
|
# 1. 端口白名单映射(只允许指定端口)
|
||||||
proxy_pass http://172.16.0.34:5173;
|
map $arg_state $target_port {
|
||||||
proxy_set_header Host $host;
|
default "";
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
~^login(51703)_ 51703;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
~^login(51704)_ 51704;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
~^login(51705)_ 51705;
|
||||||
}
|
~^login(51706)_ 51706;
|
||||||
}
|
~^login(51707)_ 51707;
|
||||||
|
~^login(51708)_ 51708;
|
||||||
server {
|
}
|
||||||
listen 5175;
|
|
||||||
server_name localhost;
|
# 2. 统一回调入口,根据 state 分发到对应端口
|
||||||
|
server {
|
||||||
location / {
|
listen 80;
|
||||||
proxy_pass http://172.16.0.34:5173;
|
server_name 10.79.97.17;
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
location /callback {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
# 未匹配到允许端口直接返回 400
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
if ($target_port = "") {
|
||||||
}
|
return 400 "Invalid or unsupported state/port";
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
# 反向代理到本地对应端口的 /callback
|
||||||
listen 5176;
|
proxy_pass http://10.79.97.17:$target_port/callback$is_args$args;
|
||||||
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 {
|
|
||||||
listen 5177;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://172.16.0.34:5173;
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|||||||
@@ -1,342 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Ubuntu环境下Nginx多客户端配置快速部署脚本
|
|
||||||
# 使用方法: chmod +x ubuntu-nginx-setup.sh && ./ubuntu-nginx-setup.sh
|
|
||||||
|
|
||||||
set -e # 遇到错误立即退出
|
|
||||||
|
|
||||||
# 颜色定义
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# 日志函数
|
|
||||||
log_info() {
|
|
||||||
echo -e "${BLUE}[INFO]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success() {
|
|
||||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warning() {
|
|
||||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查是否为root用户
|
|
||||||
check_root() {
|
|
||||||
if [[ $EUID -eq 0 ]]; then
|
|
||||||
log_warning "检测到root用户,建议使用sudo执行此脚本"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查系统要求
|
|
||||||
check_requirements() {
|
|
||||||
log_info "检查系统要求..."
|
|
||||||
|
|
||||||
# 检查操作系统
|
|
||||||
if [[ ! -f /etc/os-release ]]; then
|
|
||||||
log_error "无法检测操作系统版本"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
. /etc/os-release
|
|
||||||
log_info "操作系统: $PRETTY_NAME"
|
|
||||||
|
|
||||||
# 检查网络连接
|
|
||||||
if ! ping -c 1 172.16.0.34 &> /dev/null; then
|
|
||||||
log_warning "无法连接到开发服务器 172.16.0.34,请确保网络连接正常"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "系统要求检查完成"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 安装Nginx
|
|
||||||
install_nginx() {
|
|
||||||
log_info "检查Nginx安装状态..."
|
|
||||||
|
|
||||||
if command -v nginx &> /dev/null; then
|
|
||||||
NGINX_VERSION=$(nginx -v 2>&1 | cut -d' ' -f3 | cut -d'/' -f2)
|
|
||||||
log_info "Nginx已安装,版本: $NGINX_VERSION"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "安装Nginx..."
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y nginx
|
|
||||||
|
|
||||||
# 启动并启用Nginx服务
|
|
||||||
sudo systemctl start nginx
|
|
||||||
sudo systemctl enable nginx
|
|
||||||
|
|
||||||
log_success "Nginx安装完成"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 备份原始配置
|
|
||||||
backup_config() {
|
|
||||||
log_info "备份原始Nginx配置..."
|
|
||||||
|
|
||||||
BACKUP_DIR="/etc/nginx/backup-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
sudo mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
# 备份主要配置文件
|
|
||||||
if [[ -f /etc/nginx/nginx.conf ]]; then
|
|
||||||
sudo cp /etc/nginx/nginx.conf "$BACKUP_DIR/"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f /etc/nginx/sites-available/default ]]; then
|
|
||||||
sudo cp /etc/nginx/sites-available/default "$BACKUP_DIR/"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 备份现有的conf.d配置
|
|
||||||
if [[ -d /etc/nginx/conf.d ]]; then
|
|
||||||
sudo cp -r /etc/nginx/conf.d "$BACKUP_DIR/"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "配置已备份到: $BACKUP_DIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 创建多客户端配置
|
|
||||||
create_multi_client_config() {
|
|
||||||
log_info "创建多客户端Nginx配置..."
|
|
||||||
|
|
||||||
# 确保conf.d目录存在
|
|
||||||
sudo mkdir -p /etc/nginx/conf.d
|
|
||||||
|
|
||||||
# 检查配置文件是否存在
|
|
||||||
if [[ ! -f "nginx-ubuntu-optimized.conf" ]]; then
|
|
||||||
log_error "找不到nginx-ubuntu-optimized.conf文件,请确保文件在当前目录"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 复制配置文件
|
|
||||||
sudo cp nginx-ubuntu-optimized.conf /etc/nginx/conf.d/multi-client.conf
|
|
||||||
|
|
||||||
# 设置正确的权限
|
|
||||||
sudo chown root:root /etc/nginx/conf.d/multi-client.conf
|
|
||||||
sudo chmod 644 /etc/nginx/conf.d/multi-client.conf
|
|
||||||
|
|
||||||
log_success "多客户端配置已创建"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 创建日志目录
|
|
||||||
setup_logging() {
|
|
||||||
log_info "设置日志目录..."
|
|
||||||
|
|
||||||
# 创建客户端日志目录
|
|
||||||
sudo mkdir -p /var/log/nginx/clients
|
|
||||||
|
|
||||||
# 设置权限
|
|
||||||
sudo chown -R www-data:www-data /var/log/nginx
|
|
||||||
sudo chmod -R 755 /var/log/nginx
|
|
||||||
|
|
||||||
# 创建日志轮转配置
|
|
||||||
sudo tee /etc/logrotate.d/nginx-clients > /dev/null <<EOF
|
|
||||||
/var/log/nginx/client-*-access.log {
|
|
||||||
daily
|
|
||||||
missingok
|
|
||||||
rotate 52
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 644 www-data www-data
|
|
||||||
postrotate
|
|
||||||
systemctl reload nginx
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
|
|
||||||
/var/log/nginx/client-*-error.log {
|
|
||||||
daily
|
|
||||||
missingok
|
|
||||||
rotate 52
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 644 www-data www-data
|
|
||||||
postrotate
|
|
||||||
systemctl reload nginx
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
log_success "日志配置完成"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 配置防火墙
|
|
||||||
setup_firewall() {
|
|
||||||
log_info "配置防火墙规则..."
|
|
||||||
|
|
||||||
# 检查ufw是否安装
|
|
||||||
if command -v ufw &> /dev/null; then
|
|
||||||
# 允许Nginx端口
|
|
||||||
sudo ufw allow 5174/tcp comment "Nginx Client A"
|
|
||||||
sudo ufw allow 5175/tcp comment "Nginx Client B"
|
|
||||||
sudo ufw allow 5176/tcp comment "Nginx Client C"
|
|
||||||
sudo ufw allow 5177/tcp comment "Nginx Client D"
|
|
||||||
|
|
||||||
log_success "防火墙规则已配置"
|
|
||||||
else
|
|
||||||
log_warning "ufw未安装,跳过防火墙配置"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 验证配置
|
|
||||||
validate_config() {
|
|
||||||
log_info "验证Nginx配置..."
|
|
||||||
|
|
||||||
# 测试配置语法
|
|
||||||
if sudo nginx -t; then
|
|
||||||
log_success "Nginx配置语法正确"
|
|
||||||
else
|
|
||||||
log_error "Nginx配置语法错误,请检查配置文件"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 重启Nginx服务
|
|
||||||
restart_nginx() {
|
|
||||||
log_info "重启Nginx服务..."
|
|
||||||
|
|
||||||
sudo systemctl reload nginx
|
|
||||||
|
|
||||||
# 检查服务状态
|
|
||||||
if sudo systemctl is-active --quiet nginx; then
|
|
||||||
log_success "Nginx服务运行正常"
|
|
||||||
else
|
|
||||||
log_error "Nginx服务启动失败"
|
|
||||||
sudo systemctl status nginx
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 测试端口监听
|
|
||||||
test_ports() {
|
|
||||||
log_info "测试端口监听状态..."
|
|
||||||
|
|
||||||
PORTS=(5174 5175 5176 5177)
|
|
||||||
|
|
||||||
for port in "${PORTS[@]}"; do
|
|
||||||
if ss -tuln | grep -q ":$port "; then
|
|
||||||
log_success "端口 $port 监听正常"
|
|
||||||
else
|
|
||||||
log_error "端口 $port 未监听"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# 测试健康检查
|
|
||||||
test_health_checks() {
|
|
||||||
log_info "测试健康检查端点..."
|
|
||||||
|
|
||||||
PORTS=(5174 5175 5176 5177)
|
|
||||||
CLIENTS=("Client A" "Client B" "Client C" "Client D")
|
|
||||||
|
|
||||||
for i in "${!PORTS[@]}"; do
|
|
||||||
port=${PORTS[$i]}
|
|
||||||
client=${CLIENTS[$i]}
|
|
||||||
|
|
||||||
if curl -s "http://localhost:$port/health" | grep -q "OK"; then
|
|
||||||
log_success "$client (端口 $port) 健康检查通过"
|
|
||||||
else
|
|
||||||
log_warning "$client (端口 $port) 健康检查失败"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# 显示测试命令
|
|
||||||
show_test_commands() {
|
|
||||||
log_info "测试命令示例:"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}1. 健康检查:${NC}"
|
|
||||||
echo " curl http://localhost:5174/health"
|
|
||||||
echo " curl http://localhost:5175/health"
|
|
||||||
echo " curl http://localhost:5176/health"
|
|
||||||
echo " curl http://localhost:5177/health"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}2. 测试代理功能:${NC}"
|
|
||||||
echo " curl -v http://localhost:5174/ 2>&1 | grep 'X-Client-ID'"
|
|
||||||
echo " curl -v http://localhost:5175/ 2>&1 | grep 'X-Client-ID'"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}3. 监控日志:${NC}"
|
|
||||||
echo " sudo tail -f /var/log/nginx/client-a-access.log"
|
|
||||||
echo " sudo tail -f /var/log/nginx/client-*-error.log"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}4. 浏览器测试:${NC}"
|
|
||||||
echo " http://$(hostname -I | awk '{print $1}'):5174"
|
|
||||||
echo " http://$(hostname -I | awk '{print $1}'):5175"
|
|
||||||
echo " http://$(hostname -I | awk '{print $1}'):5176"
|
|
||||||
echo " http://$(hostname -I | awk '{print $1}'):5177"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}5. 环境变量测试:${NC}"
|
|
||||||
echo " CLIENT_ID=client-a npm run dev"
|
|
||||||
echo " CLIENT_ID=client-b npm run dev"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 显示管理命令
|
|
||||||
show_management_commands() {
|
|
||||||
log_info "管理命令:"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}Nginx服务管理:${NC}"
|
|
||||||
echo " sudo systemctl start nginx # 启动服务"
|
|
||||||
echo " sudo systemctl stop nginx # 停止服务"
|
|
||||||
echo " sudo systemctl restart nginx # 重启服务"
|
|
||||||
echo " sudo systemctl reload nginx # 重新加载配置"
|
|
||||||
echo " sudo systemctl status nginx # 查看状态"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}配置管理:${NC}"
|
|
||||||
echo " sudo nginx -t # 测试配置"
|
|
||||||
echo " sudo nginx -s reload # 重新加载"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}日志查看:${NC}"
|
|
||||||
echo " sudo tail -f /var/log/nginx/error.log"
|
|
||||||
echo " sudo tail -f /var/log/nginx/access.log"
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}端口检查:${NC}"
|
|
||||||
echo " sudo ss -tuln | grep :517"
|
|
||||||
echo " sudo lsof -i :5174"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主函数
|
|
||||||
main() {
|
|
||||||
echo -e "${GREEN}======================================${NC}"
|
|
||||||
echo -e "${GREEN} Ubuntu Nginx多客户端配置部署脚本 ${NC}"
|
|
||||||
echo -e "${GREEN}======================================${NC}"
|
|
||||||
echo
|
|
||||||
|
|
||||||
check_root
|
|
||||||
check_requirements
|
|
||||||
install_nginx
|
|
||||||
backup_config
|
|
||||||
create_multi_client_config
|
|
||||||
setup_logging
|
|
||||||
setup_firewall
|
|
||||||
validate_config
|
|
||||||
restart_nginx
|
|
||||||
|
|
||||||
echo
|
|
||||||
log_success "多客户端Nginx配置部署完成!"
|
|
||||||
echo
|
|
||||||
|
|
||||||
test_ports
|
|
||||||
test_health_checks
|
|
||||||
|
|
||||||
echo
|
|
||||||
show_test_commands
|
|
||||||
show_management_commands
|
|
||||||
|
|
||||||
echo
|
|
||||||
log_info "部署完成!现在可以开始测试多客户端功能。"
|
|
||||||
log_info "请确保开发服务器在 172.16.0.34:5173 上运行。"
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
# 脚本入口
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
||||||
main "$@"
|
|
||||||
fi
|
|
||||||
-634
@@ -1,634 +0,0 @@
|
|||||||
# 交叉评查系统完整文档
|
|
||||||
|
|
||||||
## 📋 目录
|
|
||||||
|
|
||||||
1. [系统概述](#系统概述)
|
|
||||||
2. [核心概念](#核心概念)
|
|
||||||
3. [业务流程](#业务流程)
|
|
||||||
4. [数据模型](#数据模型)
|
|
||||||
5. [API接口文档](#api接口文档)
|
|
||||||
6. [业务逻辑详解](#业务逻辑详解)
|
|
||||||
7. [测试用例](#测试用例)
|
|
||||||
8. [部署说明](#部署说明)
|
|
||||||
|
|
||||||
## 🎯 系统概述
|
|
||||||
|
|
||||||
交叉评查系统是一个基于FastAPI和PostgreSQL的分布式评查协作平台,支持多用户对文档评查结果进行异议提案和投票表决,通过民主化的方式确保评查结果的准确性和公正性。
|
|
||||||
|
|
||||||
### 主要特性
|
|
||||||
|
|
||||||
- ✅ **任务分配管理** - 支持管理员分配评查任务给多个评查员
|
|
||||||
- ✅ **异议提案机制** - 评查员可对系统评分提出修改建议
|
|
||||||
- ✅ **民主投票表决** - 通过投票机制形成共识
|
|
||||||
- ✅ **自动仲裁逻辑** - 基于投票结果自动确定提案状态
|
|
||||||
- ✅ **撤销机制** - 支持提案和投票的撤销操作
|
|
||||||
- ✅ **进度跟踪** - 实时监控任务完成进度
|
|
||||||
- ✅ **软删除设计** - 保证数据完整性和可追溯性
|
|
||||||
|
|
||||||
## 🔑 核心概念
|
|
||||||
|
|
||||||
### 评查任务 (Cross Examination Task)
|
|
||||||
- **定义**: 一次评查工作的容器,包含需要评查的文档和负责评查的评查员
|
|
||||||
- **状态**: `in_progress`(进行中) → `completed`(已完成)
|
|
||||||
- **作用**: 定义评查的范围和参与者
|
|
||||||
|
|
||||||
### 权威参与者 (Authoritative Participants)
|
|
||||||
- **定义**: 针对特定文档被分配参与评查的所有用户集合
|
|
||||||
- **计算**: 通过`cross_task_document_mapping`表确定
|
|
||||||
- **重要性**: 是投票和仲裁逻辑的基础
|
|
||||||
|
|
||||||
### 评分提案 (Scoring Proposal)
|
|
||||||
- **定义**: 评查员对系统自动评分的修改建议
|
|
||||||
- **状态**: `pending`(待处理) → `approved`(已批准) / `rejected`(已拒绝)
|
|
||||||
- **特点**: 创建时自动为提案人投同意票
|
|
||||||
|
|
||||||
### 批准阈值 (Approval Threshold)
|
|
||||||
- **计算公式**: `floor(N / 2) + 1`,其中N是权威参与者总数
|
|
||||||
- **作用**: 确定提案通过所需的最少同意票数
|
|
||||||
- **示例**: 6个参与者的阈值为4票
|
|
||||||
|
|
||||||
## 🔄 业务流程
|
|
||||||
|
|
||||||
### 完整流程图
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
subgraph "任务分配阶段"
|
|
||||||
A["管理员选择文档和评查员"] --> B["调用 POST /tasks/assign"]
|
|
||||||
B --> C["创建 cross_examination_tasks 记录"]
|
|
||||||
C --> D["创建 cross_task_document_mapping 记录"]
|
|
||||||
D --> E["任务分配完成"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "提案创建阶段"
|
|
||||||
E --> F["评查员审查系统评分"]
|
|
||||||
F --> G{"发现异议?"}
|
|
||||||
G -->|是| H["调用 POST /proposals"]
|
|
||||||
G -->|否| I["评查完成"]
|
|
||||||
H --> J["创建 cross_scoring_proposals 记录"]
|
|
||||||
J --> K["自动为提案人投同意票"]
|
|
||||||
K --> L["触发状态检查"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "投票与仲裁阶段"
|
|
||||||
L --> M["其他评查员收到通知"]
|
|
||||||
M --> N["调用 POST /proposals/votes"]
|
|
||||||
N --> O["创建/更新 cross_opinion_votes 记录"]
|
|
||||||
O --> P["触发自动仲裁逻辑"]
|
|
||||||
P --> Q{"计算投票结果"}
|
|
||||||
Q -->|同意票达到阈值| R["提案状态: approved"]
|
|
||||||
Q -->|反对票达到阈值| S["提案状态: rejected"]
|
|
||||||
Q -->|票数不足| T["提案状态: pending"]
|
|
||||||
R --> U["更新评查结果分数"]
|
|
||||||
S --> V["通知所有参与者"]
|
|
||||||
T --> W["等待更多投票"]
|
|
||||||
U --> V
|
|
||||||
W --> N
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "任务完成阶段"
|
|
||||||
V --> X["检查所有提案状态"]
|
|
||||||
X --> Y{"所有提案已处理?"}
|
|
||||||
Y -->|是| Z["任务状态: completed"]
|
|
||||||
Y -->|否| AA["任务继续进行"]
|
|
||||||
Z --> BB["流程结束"]
|
|
||||||
AA --> M
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "撤销机制"
|
|
||||||
H --> CC["调用 DELETE /proposals"]
|
|
||||||
CC --> DD["软删除提案和投票"]
|
|
||||||
N --> EE["调用 POST /votes 撤销投票"]
|
|
||||||
EE --> FF["软删除投票记录"]
|
|
||||||
DD --> P
|
|
||||||
FF --> P
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### 详细流程说明
|
|
||||||
|
|
||||||
#### 阶段1: 任务分配
|
|
||||||
1. **管理员操作**: 选择文档和评查员
|
|
||||||
2. **系统处理**: 创建任务记录和映射关系
|
|
||||||
3. **结果**: 建立文档-评查员的关联关系
|
|
||||||
|
|
||||||
#### 阶段2: 提案创建
|
|
||||||
1. **评查员审查**: 检查系统自动评分结果
|
|
||||||
2. **发现异议**: 对某个评查点的分数有不同意见
|
|
||||||
3. **创建提案**: 提交新的分数和理由
|
|
||||||
4. **自动投票**: 系统为提案人自动投同意票
|
|
||||||
|
|
||||||
#### 阶段3: 投票与仲裁
|
|
||||||
1. **投票参与**: 其他评查员对提案进行投票
|
|
||||||
2. **实时仲裁**: 每次投票后触发状态检查
|
|
||||||
3. **状态确定**: 根据投票结果确定提案状态
|
|
||||||
4. **结果处理**: 更新评查结果或通知参与者
|
|
||||||
|
|
||||||
#### 阶段4: 任务完成
|
|
||||||
1. **状态检查**: 检查所有提案是否已处理
|
|
||||||
2. **任务完成**: 所有提案处理完毕后标记任务完成
|
|
||||||
3. **流程结束**: 整个评查流程结束
|
|
||||||
|
|
||||||
## 🗄️ 数据模型
|
|
||||||
|
|
||||||
### 核心表结构
|
|
||||||
|
|
||||||
#### 1. cross_examination_tasks (评查任务表)
|
|
||||||
```sql
|
|
||||||
CREATE TABLE cross_examination_tasks (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_ids INTEGER[], -- 参与评查的用户ID数组
|
|
||||||
assigner_id INTEGER, -- 分配任务的管理员ID
|
|
||||||
task_status VARCHAR DEFAULT 'in_progress', -- 任务状态
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
deleted_at TIMESTAMP WITH TIME ZONE -- 软删除时间戳
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. cross_task_document_mapping (任务文档映射表)
|
|
||||||
```sql
|
|
||||||
CREATE TABLE cross_task_document_mapping (
|
|
||||||
task_id INTEGER NOT NULL, -- 任务ID
|
|
||||||
document_id INTEGER NOT NULL, -- 文档ID
|
|
||||||
audit_status INTEGER DEFAULT 0, -- 审核状态 (0:待审核, 1:已完成)
|
|
||||||
deleted_at TIMESTAMP WITH TIME ZONE, -- 软删除时间戳
|
|
||||||
PRIMARY KEY (task_id, document_id)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. cross_scoring_proposals (评分提案表)
|
|
||||||
```sql
|
|
||||||
CREATE TABLE cross_scoring_proposals (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
evaluation_result_id INTEGER, -- 评查结果ID
|
|
||||||
document_id INTEGER NOT NULL, -- 文档ID
|
|
||||||
evaluation_point_id INTEGER NOT NULL, -- 评查点ID
|
|
||||||
proposed_score DOUBLE PRECISION, -- 建议分数
|
|
||||||
reason TEXT, -- 提案理由
|
|
||||||
proposer_id INTEGER NOT NULL, -- 提案人ID
|
|
||||||
status VARCHAR DEFAULT 'pending', -- 提案状态
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
deleted_at TIMESTAMP WITH TIME ZONE -- 软删除时间戳
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. cross_opinion_votes (意见投票表)
|
|
||||||
```sql
|
|
||||||
CREATE TABLE cross_opinion_votes (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
proposal_id INTEGER NOT NULL, -- 提案ID
|
|
||||||
voter_id INTEGER NOT NULL, -- 投票人ID
|
|
||||||
vote_type VARCHAR NOT NULL, -- 投票类型 (agree/disagree)
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
deleted_at TIMESTAMP WITH TIME ZONE, -- 软删除时间戳
|
|
||||||
UNIQUE(proposal_id, voter_id) -- 每个用户对每个提案只能投一票
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数据关系图
|
|
||||||
|
|
||||||
```
|
|
||||||
cross_examination_tasks (1) ←→ (N) cross_task_document_mapping
|
|
||||||
↓
|
|
||||||
documents (1) ←→ (N) cross_scoring_proposals
|
|
||||||
↓
|
|
||||||
(1) ←→ (N) cross_opinion_votes
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 API接口文档
|
|
||||||
|
|
||||||
### 基础信息
|
|
||||||
- **Base URL**: `/admin/cross_review`
|
|
||||||
- **认证方式**: 暂时禁用 (测试阶段)
|
|
||||||
- **数据格式**: JSON
|
|
||||||
|
|
||||||
### 1. 分配交叉评查任务
|
|
||||||
|
|
||||||
#### 请求
|
|
||||||
```http
|
|
||||||
POST /admin/cross_review/tasks/assign
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"document_ids": [1205, 1248, 1257],
|
|
||||||
"user_ids": [1, 2, 3, 4, 5, 6],
|
|
||||||
"assigner_id": 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "任务分配成功",
|
|
||||||
"task_id": 123
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 错误响应
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"detail": "文档ID列表和用户ID列表均不能为空"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 发起评分提案
|
|
||||||
|
|
||||||
#### 请求
|
|
||||||
```http
|
|
||||||
POST /admin/cross_review/proposals
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"document_id": 1205,
|
|
||||||
"evaluation_point_id": 123,
|
|
||||||
"proposed_score": -1.0,
|
|
||||||
"reason": "根据相关法规,此项应扣1分",
|
|
||||||
"proposer_id": 2,
|
|
||||||
"evaluation_result_id": 37290
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"proposal": {
|
|
||||||
"id": 25,
|
|
||||||
"document_id": 1205,
|
|
||||||
"evaluation_point_id": 123,
|
|
||||||
"proposed_score": -1.0,
|
|
||||||
"reason": "根据相关法规,此项应扣1分",
|
|
||||||
"proposer_id": 2,
|
|
||||||
"status": "pending",
|
|
||||||
"created_at": "2024-01-01T10:00:00Z"
|
|
||||||
},
|
|
||||||
"message": "评分提案创建成功"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 对提案进行投票
|
|
||||||
|
|
||||||
#### 请求
|
|
||||||
```http
|
|
||||||
POST /admin/cross_review/proposals/{proposal_id}/votes
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"vote_type": "agree",
|
|
||||||
"voter_id": 3
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "投票成功",
|
|
||||||
"proposal_status": "pending"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 投票类型说明
|
|
||||||
- `agree`: 同意提案
|
|
||||||
- `disagree`: 反对提案
|
|
||||||
- `cancel`: 撤销投票
|
|
||||||
|
|
||||||
### 4. 获取提案详情列表
|
|
||||||
|
|
||||||
#### 请求
|
|
||||||
```http
|
|
||||||
POST /admin/cross_review/proposals/details
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"user_id": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"proposal_id": 25,
|
|
||||||
"evaluation_point_name": "事实认定准确性",
|
|
||||||
"proposer": "张三",
|
|
||||||
"proposed_score": -1.0,
|
|
||||||
"reason": "根据相关法规,此项应扣1分",
|
|
||||||
"agree_voters": ["李四", "王五"],
|
|
||||||
"disagree_voters": ["赵六"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 撤销评分提案
|
|
||||||
|
|
||||||
#### 请求
|
|
||||||
```http
|
|
||||||
DELETE /admin/cross_review/proposals/{proposal_id}
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"user_id": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "提案已成功撤销"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 获取任务进度
|
|
||||||
|
|
||||||
#### 请求
|
|
||||||
```http
|
|
||||||
GET /admin/cross_review/tasks/{task_id}/progress
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"task_id": 123,
|
|
||||||
"total_documents": 3,
|
|
||||||
"completed_documents": 1,
|
|
||||||
"progress": 33.33
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. 获取用户参与的所有任务及文档
|
|
||||||
|
|
||||||
#### 请求
|
|
||||||
```http
|
|
||||||
POST /admin/cross_review/tasks/user_documents
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"user_id": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"task_id": 1,
|
|
||||||
"task_status": "in_progress",
|
|
||||||
"documents": [
|
|
||||||
{
|
|
||||||
"document_id": 1001,
|
|
||||||
"document_name": "无烟草专卖品准运证运输烟草专卖品.pdf",
|
|
||||||
"document_type_id": 2,
|
|
||||||
"document_type_name": "行政处罚卷宗"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"document_id": 1002,
|
|
||||||
"document_name": "行政处罚决定书.pdf",
|
|
||||||
"document_type_id": 2,
|
|
||||||
"document_type_name": "行政处罚卷宗"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"task_id": 2,
|
|
||||||
"task_status": "completed",
|
|
||||||
"documents": [
|
|
||||||
{
|
|
||||||
"document_id": 1003,
|
|
||||||
"document_name": "案件调查笔录.pdf",
|
|
||||||
"document_type_id": 3,
|
|
||||||
"document_type_name": "调查笔录"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 功能说明
|
|
||||||
- **用途**: 获取指定用户参与的所有评查任务及其下属文档的详细信息
|
|
||||||
- **数据隔离**: 只返回该用户参与的任务组,未参与的任务不返回
|
|
||||||
- **文档信息**: 包含文档ID、文档名称、文档类型ID、文档类型名称
|
|
||||||
- **任务状态**: 显示每个任务的当前状态(如:in_progress、completed等)
|
|
||||||
|
|
||||||
#### 字段说明
|
|
||||||
| 字段名 | 类型 | 说明 |
|
|
||||||
|--------|------|------|
|
|
||||||
| task_id | integer | 任务ID |
|
|
||||||
| task_status | string | 任务状态(in_progress/completed等) |
|
|
||||||
| documents | array | 该任务下的文档列表 |
|
|
||||||
| document_id | integer | 文档ID |
|
|
||||||
| document_name | string | 文档名称 |
|
|
||||||
| document_type_id | integer | 文档类型ID |
|
|
||||||
| document_type_name | string | 文档类型名称 |
|
|
||||||
|
|
||||||
#### 错误响应
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"detail": "获取用户任务及文档失败: 数据库连接错误"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧠 业务逻辑详解
|
|
||||||
|
|
||||||
### 投票阈值计算
|
|
||||||
|
|
||||||
#### 计算公式
|
|
||||||
```python
|
|
||||||
approval_threshold = (participant_count // 2) + 1
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 示例场景
|
|
||||||
| 参与者数量 | 阈值 | 说明 |
|
|
||||||
|-----------|------|------|
|
|
||||||
| 3 | 2 | 需要2票同意 |
|
|
||||||
| 4 | 3 | 需要3票同意 |
|
|
||||||
| 5 | 3 | 需要3票同意 |
|
|
||||||
| 6 | 4 | 需要4票同意 |
|
|
||||||
|
|
||||||
### 自动仲裁逻辑
|
|
||||||
|
|
||||||
#### 状态判断规则
|
|
||||||
1. **提案通过**: `同意票数 >= 阈值`
|
|
||||||
2. **提案拒绝**: `反对票数 >= 阈值`
|
|
||||||
3. **提前拒绝**: `同意票数 + 剩余票数 < 阈值`
|
|
||||||
4. **继续等待**: 其他情况保持pending状态
|
|
||||||
|
|
||||||
#### 实现代码
|
|
||||||
```python
|
|
||||||
async def _check_and_process_proposal_status(self, proposal_id: int):
|
|
||||||
# 获取参与者总数
|
|
||||||
participant_count_n = len(task_info["user_ids"])
|
|
||||||
|
|
||||||
# 统计票数
|
|
||||||
agree_votes_a = sum(1 for v in votes if v["vote_type"] == "agree")
|
|
||||||
disagree_votes_d = sum(1 for v in votes if v["vote_type"] == "disagree")
|
|
||||||
|
|
||||||
# 计算阈值
|
|
||||||
approval_threshold = (participant_count_n // 2) + 1
|
|
||||||
|
|
||||||
# 判断状态
|
|
||||||
if agree_votes_a >= approval_threshold:
|
|
||||||
new_status = "approved"
|
|
||||||
elif (disagree_votes_d >= approval_threshold or
|
|
||||||
(agree_votes_a + (participant_count_n - len(votes))) < approval_threshold):
|
|
||||||
new_status = "rejected"
|
|
||||||
else:
|
|
||||||
new_status = "pending"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 权限验证机制
|
|
||||||
|
|
||||||
#### 创建提案权限
|
|
||||||
- 用户必须是任务的参与者
|
|
||||||
- 用户不能为同一评查点重复创建提案
|
|
||||||
|
|
||||||
#### 投票权限
|
|
||||||
- 用户必须是任务的参与者
|
|
||||||
- 用户不能对自己的提案投票
|
|
||||||
- 用户不能对已确定状态的提案投票
|
|
||||||
|
|
||||||
#### 撤销权限
|
|
||||||
- 只有提案人可以撤销自己的提案
|
|
||||||
- 只能撤销pending状态的提案
|
|
||||||
|
|
||||||
### 软删除机制
|
|
||||||
|
|
||||||
#### 设计原则
|
|
||||||
- 使用`deleted_at`字段标记删除状态
|
|
||||||
- 保留历史数据以便审计
|
|
||||||
- 查询时自动过滤已删除记录
|
|
||||||
|
|
||||||
#### 实现方式
|
|
||||||
```python
|
|
||||||
# 软删除提案
|
|
||||||
await self.db.update(
|
|
||||||
"cross_scoring_proposals",
|
|
||||||
data={"deleted_at": datetime.utcnow().isoformat()},
|
|
||||||
filters={"id": f"eq.{proposal_id}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 查询时过滤已删除记录
|
|
||||||
filters={"deleted_at": "is.null"}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧪 测试用例
|
|
||||||
|
|
||||||
### 测试数据准备
|
|
||||||
|
|
||||||
#### 文档数据
|
|
||||||
```python
|
|
||||||
DOCUMENT_IDS = [1205, 1248, 1257] # 已评查的文档
|
|
||||||
TEST_USER_IDS = [1, 2, 3, 4, 5, 6] # 测试用户
|
|
||||||
ASSIGNER_ID = 1 # 管理员ID
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 评查结果数据
|
|
||||||
```python
|
|
||||||
DOC_EVAL_RESULTS = {
|
|
||||||
1205: [37290, 37291, 37292, ...], # 55个评查结果ID
|
|
||||||
1248: [38678, 38679, 38680, ...], # 55个评查结果ID
|
|
||||||
1257: [38898, 38899, 38900, ...] # 55个评查结果ID
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 完整测试流程
|
|
||||||
|
|
||||||
#### 1. 任务分配测试
|
|
||||||
```python
|
|
||||||
def test_assign_task():
|
|
||||||
payload = {
|
|
||||||
"document_ids": [1205, 1248, 1257],
|
|
||||||
"user_ids": [1, 2, 3, 4, 5, 6],
|
|
||||||
"assigner_id": 1
|
|
||||||
}
|
|
||||||
response = requests.post(f"{BASE_URL}/admin/cross_review/tasks/assign", json=payload)
|
|
||||||
assert response.status_code == 201
|
|
||||||
assert "task_id" in response.json()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 提案创建测试
|
|
||||||
```python
|
|
||||||
def test_create_proposal():
|
|
||||||
payload = {
|
|
||||||
"document_id": 1205,
|
|
||||||
"evaluation_point_id": 123,
|
|
||||||
"proposed_score": -1.0,
|
|
||||||
"reason": "测试提案理由",
|
|
||||||
"proposer_id": 2,
|
|
||||||
"evaluation_result_id": 37290
|
|
||||||
}
|
|
||||||
response = requests.post(f"{BASE_URL}/admin/cross_review/proposals", json=payload)
|
|
||||||
assert response.status_code == 201
|
|
||||||
assert response.json()["success"] == True
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 投票测试
|
|
||||||
```python
|
|
||||||
def test_vote_on_proposal():
|
|
||||||
payload = {
|
|
||||||
"vote_type": "agree",
|
|
||||||
"voter_id": 3
|
|
||||||
}
|
|
||||||
response = requests.post(f"{BASE_URL}/admin/cross_review/proposals/25/votes", json=payload)
|
|
||||||
assert response.status_code == 201
|
|
||||||
assert response.json()["success"] == True
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. 自动仲裁测试
|
|
||||||
```python
|
|
||||||
def test_auto_arbitration():
|
|
||||||
# 模拟4票同意,达到阈值
|
|
||||||
for user_id in [1, 2, 3, 4]:
|
|
||||||
vote_payload = {"vote_type": "agree", "voter_id": user_id}
|
|
||||||
response = requests.post(f"{BASE_URL}/admin/cross_review/proposals/25/votes", json=vote_payload)
|
|
||||||
|
|
||||||
# 检查提案状态
|
|
||||||
assert final_status == "approved"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 测试结果验证
|
|
||||||
|
|
||||||
#### 成功指标
|
|
||||||
- ✅ 任务分配成功率: 100%
|
|
||||||
- ✅ 提案创建成功率: 100%
|
|
||||||
- ✅ 投票成功率: 100%
|
|
||||||
- ✅ 自动仲裁准确率: 100%
|
|
||||||
- ✅ 权限验证有效性: 100%
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 数据库初始化
|
|
||||||
```sql
|
|
||||||
-- 创建外键约束
|
|
||||||
ALTER TABLE cross_opinion_votes
|
|
||||||
ADD CONSTRAINT fk_cross_opinion_votes_voter_id
|
|
||||||
FOREIGN KEY (voter_id) REFERENCES users(id);
|
|
||||||
|
|
||||||
-- 创建索引
|
|
||||||
CREATE INDEX idx_cross_scoring_proposals_document_id ON cross_scoring_proposals(document_id);
|
|
||||||
CREATE INDEX idx_cross_opinion_votes_proposal_id ON cross_opinion_votes(proposal_id);
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 总结
|
|
||||||
|
|
||||||
交叉评查系统通过完善的业务流程设计和技术实现,实现了:
|
|
||||||
|
|
||||||
1. **高效的任务管理** - 支持批量分配和进度跟踪
|
|
||||||
2. **民主的决策机制** - 通过投票形成共识
|
|
||||||
3. **可靠的数据保护** - 软删除和事务保证
|
|
||||||
4. **灵活的权限控制** - 多层次权限验证
|
|
||||||
5. **完整的API接口** - RESTful设计和标准化响应
|
|
||||||
|
|
||||||
系统已通过完整的回归测试验证,可以稳定运行在生产环境中。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**文档版本**: v1.5
|
|
||||||
**创建日期**: 2025-07-15
|
|
||||||
**最后更新**: 2025-07-17
|
|
||||||
**维护人员**: Wren
|
|
||||||
Reference in New Issue
Block a user