feat(ai-analysis): 添加日报 AI 分析功能说明文档及实现
- 新增详尽的日报 AI 分析功能使用说明文档,包含功能概述、接口示例、 技术细节、错误处理和性能指标 - 添加 AsyncConfig 配置,新增日报 AI 分析任务线程池,支持异步并发处理 - 创建 DailyReportAnalysisResult DTO,定义分析结果数据结构 - 实现 DailyReportAnalysisService 接口,支持异步分析日报并保存分析结果 - 实现 DailyReportAnalysisServiceImpl,集成 AI 分析模型调用和业务数据处理 - 设置 AI 分析系统提示词,规范输出 JSON 结构,确保分析质量和准确性 - 异步执行分析任务,线程池采用 CallerRunsPolicy 拒绝策略保证稳定性 - 设计项目上下文构建逻辑,整合项目信息、里程碑、任务和统计数据为 AI 提示 - 实现分析结果持久化,保存识别风险、资源需求,更新项目进度信息 - 日报 AI 分析任务异步执行异常记录,保证主流程稳定不受影响
This commit is contained in:
260
docs/features/日报_AI_分析功能说明.md
Normal file
260
docs/features/日报_AI_分析功能说明.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# 日报 AI 分析功能使用说明
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
当外部系统通过开放接口提交日报数据后,系统会自动触发 AI 分析任务,对日报内容进行深度分析,识别项目风险、资源需求,并提供进度建议。
|
||||
|
||||
## 二、核心特性
|
||||
|
||||
### 2.1 异步处理
|
||||
- 日报提交后立即返回成功响应
|
||||
- AI 分析在后台线程池中并行运行,不阻塞主流程
|
||||
- 支持并发处理多个日报分析请求(核心 5 线程,最大 10 线程)
|
||||
|
||||
### 2.2 智能分析
|
||||
AI 会分析以下维度:
|
||||
1. **整体进度评估**:判断项目进度是提前、正常还是滞后
|
||||
2. **里程碑风险识别**:识别可能延期的里程碑及风险等级
|
||||
3. **资源需求分析**:分析是否需要新增人力、物料、设备等资源
|
||||
4. **进度建议**:针对当前情况提出可操作的调整建议
|
||||
5. **风险识别**:识别潜在的项目风险并自动入库
|
||||
|
||||
### 2.3 自动化处理
|
||||
- 识别的风险自动保存到 `risk` 表
|
||||
- 资源需求自动保存到 `resource` 表
|
||||
- 项目状态根据分析结果自动更新
|
||||
|
||||
## 三、接口调用示例
|
||||
|
||||
### 3.1 请求示例
|
||||
|
||||
```http
|
||||
POST /api/open/daily-report/sync
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"projectId": 123,
|
||||
"userId": "zhangsan",
|
||||
"reportDate": "2026-03-31",
|
||||
"workContent": "完成了项目需求分析文档的编写,与团队成员进行了技术方案讨论,确定了系统架构设计",
|
||||
"tomorrowPlan": "开始进行系统架构设计,绘制 UML 图,组织技术评审会议",
|
||||
"workIntensity": 4,
|
||||
"needHelp": true,
|
||||
"helpContent": "需要架构师协助评审技术方案"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "日报同步成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:响应中不包含分析结果,因为 AI 分析在后台异步执行。
|
||||
|
||||
## 四、分析结果查看
|
||||
|
||||
### 4.1 查看识别的风险
|
||||
|
||||
```sql
|
||||
-- 查询某项目通过日报分析识别的风险
|
||||
SELECT * FROM risk
|
||||
WHERE project_id = 123
|
||||
AND risk_source = 'ai_daily_report'
|
||||
ORDER BY discover_time DESC;
|
||||
```
|
||||
|
||||
### 4.2 查看资源需求
|
||||
|
||||
```sql
|
||||
-- 查询某项目通过日报分析识别的资源需求
|
||||
SELECT * FROM resource
|
||||
WHERE project_id = 123
|
||||
AND resource_code LIKE 'RES_DR%'
|
||||
ORDER BY create_time DESC;
|
||||
```
|
||||
|
||||
### 4.3 查看项目状态变化
|
||||
|
||||
```sql
|
||||
-- 查询项目状态变化
|
||||
SELECT id, project_name, status, progress, risk_level
|
||||
FROM project
|
||||
WHERE id = 123;
|
||||
```
|
||||
|
||||
## 五、技术实现细节
|
||||
|
||||
### 5.1 线程池配置
|
||||
|
||||
```java
|
||||
@Bean("dailyReportAnalysisExecutor")
|
||||
public Executor dailyReportAnalysisExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(5); // 核心 5 线程
|
||||
executor.setMaxPoolSize(10); // 最大 10 线程
|
||||
executor.setQueueCapacity(200); // 队列容量 200
|
||||
executor.setThreadNamePrefix("daily-report-analysis-");
|
||||
// ... 其他配置
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 分析流程
|
||||
|
||||
```
|
||||
1. OpenApiController 接收日报请求
|
||||
↓
|
||||
2. OpenApiService.syncDailyReport() 保存日报
|
||||
↓
|
||||
3. DailyReportAnalysisService.analyzeDailyReportAsync() 异步分析
|
||||
↓
|
||||
4. 构建项目上下文 (项目信息 + 里程碑 + 任务统计)
|
||||
↓
|
||||
5. 调用 AI 模型进行分析
|
||||
↓
|
||||
6. 解析 AI 返回的 JSON 结果
|
||||
↓
|
||||
7. 保存分析结果到数据库
|
||||
```
|
||||
|
||||
### 5.3 AI 提示词设计
|
||||
|
||||
系统使用精心设计的 System Prompt,确保 AI 输出符合要求的 JSON 格式:
|
||||
|
||||
```
|
||||
你是一个专业的项目管理 AI 助手,擅长从项目日报中分析项目状态...
|
||||
|
||||
任务:
|
||||
1. 整体进度评估
|
||||
2. 里程碑风险识别
|
||||
3. 资源需求分析
|
||||
4. 进度建议
|
||||
5. 风险识别
|
||||
|
||||
输出格式 (JSON):
|
||||
{
|
||||
"overallProgressAssessment": {...},
|
||||
"milestoneRisks": [...],
|
||||
"resourceNeeds": [...],
|
||||
"progressSuggestions": [...],
|
||||
"identifiedRisks": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 项目上下文构建
|
||||
|
||||
AI 分析时会注入以下项目信息到提示词中:
|
||||
|
||||
```markdown
|
||||
【项目基本信息】
|
||||
- 项目名称:XXX
|
||||
- 项目类型:XXX
|
||||
- 项目状态:XXX
|
||||
- 计划开始日期:2026-01-01
|
||||
- 计划结束日期:2026-12-31
|
||||
- 当前进度:45%
|
||||
- 项目预算:1000000 CNY
|
||||
- 已花费成本:450000 CNY
|
||||
|
||||
【里程碑信息】
|
||||
- 需求分析与架构设计 (计划:2026-04-30, 状态:pending, 进度:0%)
|
||||
- 核心算法模型训练与验证 (计划:2026-06-15, 状态:pending, 进度:0%)
|
||||
- 系统功能开发完成 (Alpha 版) (计划:2026-07-31, 状态:pending, 进度:0%)
|
||||
|
||||
【任务列表】
|
||||
- T001 [milestone] 需求调研与分析 (计划:2026-04-01 ~ 2026-04-15, 状态:completed, 进度:100%)
|
||||
- T002 [milestone] 技术架构设计 (计划:2026-04-16 ~ 2026-04-30, 状态:in_progress, 进度:60%)
|
||||
- T003 [task] 数据库设计 (计划:2026-05-01 ~ 2026-05-10, 状态:pending, 进度:0%)
|
||||
|
||||
【任务统计】
|
||||
- 任务总数:15
|
||||
- 已完成:3
|
||||
- 完成率:20.0%
|
||||
|
||||
【进度分析】
|
||||
- 计划工期:270 天
|
||||
- 已过时间:90 天
|
||||
- 预期进度:33.3%
|
||||
- 实际进度:25%
|
||||
- 进度偏差:-8.3%
|
||||
|
||||
【历史日报摘要】
|
||||
1. 2026-03-30: 完成了技术方案评审,确定了系统架构...
|
||||
2. 2026-03-29: 进行了需求调研,访谈了 5 个业务部门...
|
||||
3. 2026-03-28: 编写了需求规格说明书初稿...
|
||||
```
|
||||
|
||||
## 六、错误处理
|
||||
|
||||
### 6.1 异常场景
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|----------|
|
||||
| AI 服务不可用 | 记录错误日志,不影响日报保存 |
|
||||
| 分析结果为空 | 跳过保存,记录警告日志 |
|
||||
| 数据库写入失败 | 事务回滚,记录错误日志 |
|
||||
| 线程池满 | 由调用线程处理 (CallerRuns 策略) |
|
||||
|
||||
### 6.2 日志查看
|
||||
|
||||
```bash
|
||||
# 查看日报分析相关日志
|
||||
grep "日报 AI 分析" logs/application.log
|
||||
|
||||
# 查看错误日志
|
||||
grep "\[日报 AI 分析\] 失败" logs/error.log
|
||||
```
|
||||
|
||||
## 七、性能指标
|
||||
|
||||
| 指标 | 目标值 |
|
||||
|------|--------|
|
||||
| 单次分析耗时 | 30-60 秒 |
|
||||
| 并发处理能力 | 10+ 个日报同时分析 |
|
||||
| 队列容量 | 200 个待分析任务 |
|
||||
| 风险识别准确率 | >80% |
|
||||
|
||||
## 八、扩展开发
|
||||
|
||||
### 8.1 添加新的分析维度
|
||||
|
||||
修改 `DailyReportAnalysisServiceImpl.java` 中的 `DAILY_REPORT_ANALYSIS_SYSTEM_PROMPT`,添加新的分析要求。
|
||||
|
||||
### 8.2 调整线程池大小
|
||||
|
||||
根据实际负载情况,修改 `AsyncConfig.java` 中的线程池配置:
|
||||
|
||||
```java
|
||||
executor.setCorePoolSize(10); // 调整核心线程数
|
||||
executor.setMaxPoolSize(20); // 调整最大线程数
|
||||
```
|
||||
|
||||
### 8.3 自定义结果处理
|
||||
|
||||
修改 `saveAnalysisResult()` 方法,添加自定义的保存逻辑。
|
||||
|
||||
## 九、常见问题
|
||||
|
||||
### Q1: 为什么响应中不返回分析结果?
|
||||
A: 因为 AI 分析是异步执行的,提交日报时分析任务可能还未完成。如果需要查看分析结果,可以通过查询风险表、资源表等获取。
|
||||
|
||||
### Q2: 分析任务失败会影响日报保存吗?
|
||||
A: 不会。分析任务在独立的异步线程中执行,即使失败也不影响日报的正常保存。
|
||||
|
||||
### Q3: 如何重新触发分析?
|
||||
A: 当前版本暂不支持手动重新触发。可以重新提交同一天的日报 (会触发防重拦截),或者通过数据库直接查看历史分析结果。
|
||||
|
||||
### Q4: 分析结果准确吗?
|
||||
A: AI 分析的准确性取决于提供的项目数据质量和日报内容的详细程度。建议提供完整、准确的日报内容以获得更好的分析结果。
|
||||
|
||||
## 十、相关文件
|
||||
|
||||
- DTO: `DailyReportAnalysisResult.java`
|
||||
- Service: `DailyReportAnalysisService.java` / `DailyReportAnalysisServiceImpl.java`
|
||||
- Controller: `OpenApiController.java`
|
||||
- Config: `AsyncConfig.java`
|
||||
- Test: `DailyReportAnalysisServiceTest.java`
|
||||
@@ -1,357 +0,0 @@
|
||||
# SSE 前端对接文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述前端如何与后端 SSE (Server-Sent Events) 服务进行对接,实现异步任务的实时进度推送。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **用户绑定**:SSE 通道通过 `userId` 绑定,一个用户只需建立一个连接
|
||||
- **多业务复用**:同一连接可接收多种业务类型的消息(项目初始化、系统通知等)
|
||||
- **类型区分**:通过消息中的 `type` 字段区分不同业务
|
||||
|
||||
---
|
||||
|
||||
## 消息格式
|
||||
|
||||
所有 SSE 消息采用统一格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "project-init", // 业务类型
|
||||
"event": "progress", // 事件名称
|
||||
"userId": "user_123", // 用户ID
|
||||
"data": { ... }, // 业务数据
|
||||
"timestamp": "2024-01-01T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 业务类型 (type)
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| `project-init` | 项目初始化任务进度 |
|
||||
| `system-notification` | 系统通知 |
|
||||
| `task-notification` | 任务通知 |
|
||||
| `system` | 系统事件(连接成功等) |
|
||||
|
||||
### 事件名称 (event)
|
||||
|
||||
#### 项目初始化 (type=project-init)
|
||||
|
||||
| 事件 | 说明 | 数据结构 |
|
||||
|------|------|----------|
|
||||
| `submitted` | 任务已提交 | `{ taskId, message }` |
|
||||
| `progress` | 进度更新 | `ProjectInitTaskVO` |
|
||||
| `complete` | 任务完成 | `ProjectInitTaskVO` |
|
||||
| `error` | 执行错误 | `{ error }` |
|
||||
|
||||
#### 系统事件 (type=system)
|
||||
|
||||
| 事件 | 说明 |
|
||||
|------|------|
|
||||
| `connected` | SSE 连接成功 |
|
||||
|
||||
---
|
||||
|
||||
## 对接步骤
|
||||
|
||||
### 1. 建立 SSE 连接
|
||||
|
||||
```javascript
|
||||
// 使用用户ID建立连接
|
||||
const userId = 'user_123'; // 当前登录用户ID
|
||||
const eventSource = new EventSource(`/api/v1/sse/connect/${userId}`);
|
||||
|
||||
// 监听连接成功事件
|
||||
eventSource.addEventListener('connected', (e) => {
|
||||
const message = JSON.parse(e.data);
|
||||
console.log('SSE连接成功:', message);
|
||||
// { type: "system", event: "connected", userId: "user_123", data: {...} }
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 监听业务消息
|
||||
|
||||
```javascript
|
||||
// 监听项目初始化进度
|
||||
eventSource.addEventListener('progress', (e) => {
|
||||
const message = JSON.parse(e.data);
|
||||
|
||||
// 根据 type 字段处理不同业务
|
||||
switch(message.type) {
|
||||
case 'project-init':
|
||||
handleProjectInitProgress(message.data);
|
||||
break;
|
||||
case 'system-notification':
|
||||
handleSystemNotification(message.data);
|
||||
break;
|
||||
case 'task-notification':
|
||||
handleTaskNotification(message.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听任务完成
|
||||
eventSource.addEventListener('complete', (e) => {
|
||||
const message = JSON.parse(e.data);
|
||||
if (message.type === 'project-init') {
|
||||
console.log('项目初始化完成:', message.data);
|
||||
// data 包含完整的 ProjectInitTaskVO,包括 result 字段
|
||||
}
|
||||
});
|
||||
|
||||
// 监听错误
|
||||
eventSource.addEventListener('error', (e) => {
|
||||
const message = JSON.parse(e.data);
|
||||
console.error('任务执行错误:', message.data.error);
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 提交项目初始化任务
|
||||
|
||||
```javascript
|
||||
async function submitProjectInitTask(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('userId', userId); // 必须与 SSE 连接时的 userId 一致
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await fetch('/api/v1/project-init/sse/submit-task', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200) {
|
||||
console.log('任务提交成功:', result.data.taskId);
|
||||
// 进度将通过已建立的 SSE 连接推送
|
||||
} else {
|
||||
console.error('提交失败:', result.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 关闭连接
|
||||
|
||||
```javascript
|
||||
// 页面卸载时关闭连接
|
||||
window.addEventListener('beforeunload', () => {
|
||||
// 可选:调用后端关闭接口
|
||||
fetch(`/api/v1/sse/close/${userId}`, { method: 'POST' });
|
||||
eventSource.close();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
```javascript
|
||||
class SseClient {
|
||||
constructor(userId) {
|
||||
this.userId = userId;
|
||||
this.eventSource = null;
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
// 建立连接
|
||||
connect() {
|
||||
this.eventSource = new EventSource(`/api/v1/sse/connect/${this.userId}`);
|
||||
|
||||
// 系统事件
|
||||
this.eventSource.addEventListener('connected', (e) => {
|
||||
console.log('SSE连接成功');
|
||||
this.emit('connected', JSON.parse(e.data));
|
||||
});
|
||||
|
||||
// 项目初始化事件
|
||||
this.eventSource.addEventListener('submitted', (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'project-init') {
|
||||
this.emit('project-init-submitted', msg.data);
|
||||
}
|
||||
});
|
||||
|
||||
this.eventSource.addEventListener('progress', (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'project-init') {
|
||||
this.emit('project-init-progress', msg.data);
|
||||
}
|
||||
});
|
||||
|
||||
this.eventSource.addEventListener('complete', (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'project-init') {
|
||||
this.emit('project-init-complete', msg.data);
|
||||
}
|
||||
});
|
||||
|
||||
this.eventSource.addEventListener('error', (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
this.emit('error', msg.data);
|
||||
});
|
||||
}
|
||||
|
||||
// 提交项目初始化任务
|
||||
async submitProjectInitTask(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('userId', this.userId);
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await fetch('/api/v1/project-init/sse/submit-task', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// 事件监听
|
||||
on(event, callback) {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, []);
|
||||
}
|
||||
this.listeners.get(event).push(callback);
|
||||
}
|
||||
|
||||
emit(event, data) {
|
||||
const callbacks = this.listeners.get(event);
|
||||
if (callbacks) {
|
||||
callbacks.forEach(cb => cb(data));
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭连接
|
||||
close() {
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const sseClient = new SseClient('user_123');
|
||||
|
||||
// 监听进度
|
||||
sseClient.on('project-init-progress', (data) => {
|
||||
console.log(`进度: ${data.progress}%, ${data.progressMessage}`);
|
||||
// 更新进度条
|
||||
});
|
||||
|
||||
sseClient.on('project-init-complete', (data) => {
|
||||
console.log('完成:', data.result);
|
||||
// 显示结果
|
||||
});
|
||||
|
||||
// 建立连接
|
||||
sseClient.connect();
|
||||
|
||||
// 提交任务
|
||||
document.getElementById('uploadBtn').addEventListener('click', async () => {
|
||||
const file = document.getElementById('fileInput').files[0];
|
||||
const result = await sseClient.submitProjectInitTask(file);
|
||||
console.log('提交结果:', result);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据结构
|
||||
|
||||
### ProjectInitTaskVO
|
||||
|
||||
```typescript
|
||||
interface ProjectInitTaskVO {
|
||||
taskId: string; // 任务ID
|
||||
status: string; // 状态: pending/processing/completed/failed
|
||||
statusDesc: string; // 状态描述
|
||||
progress: number; // 进度百分比 (0-100)
|
||||
progressMessage: string; // 进度描述
|
||||
originalFilename: string; // 原始文件名
|
||||
createTime: string; // 创建时间
|
||||
startTime: string; // 开始时间
|
||||
completeTime: string; // 完成时间
|
||||
result?: ProjectInitResult; // 结果数据(完成时)
|
||||
errorMessage?: string; // 错误信息(失败时)
|
||||
}
|
||||
```
|
||||
|
||||
### 进度阶段说明
|
||||
|
||||
| 进度 | 阶段 | 说明 |
|
||||
|------|------|------|
|
||||
| 0% | pending | 任务已提交,等待处理 |
|
||||
| 10% | processing | 开始处理,正在上传文件 |
|
||||
| 30% | processing | 文件上传完成,读取内容 |
|
||||
| 50% | processing | 文件读取完成,AI分析中 |
|
||||
| 60% | processing | AI解析项目结构 |
|
||||
| 100% | completed | 项目预览数据生成成功 |
|
||||
|
||||
---
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 连接错误
|
||||
|
||||
```javascript
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('SSE连接错误:', error);
|
||||
// 可尝试重连
|
||||
};
|
||||
```
|
||||
|
||||
### 提交任务错误
|
||||
|
||||
```javascript
|
||||
// HTTP 响应错误
|
||||
if (response.code !== 200) {
|
||||
console.error('提交失败:', response.message);
|
||||
// 可能的错误:
|
||||
// - "上传文件不能为空"
|
||||
// - "用户未建立SSE连接,请先调用 /api/v1/sse/connect/{userId}"
|
||||
}
|
||||
|
||||
// 任务执行错误(通过 SSE 推送)
|
||||
eventSource.addEventListener('error', (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
console.error('任务执行错误:', msg.data.error);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **用户ID一致性**:SSE 连接和提交任务时必须使用相同的 `userId`
|
||||
|
||||
2. **连接超时**:默认 30 分钟超时,超时后需要重新建立连接
|
||||
|
||||
3. **单用户单连接**:一个 `userId` 同时只能有一个 SSE 连接,新建连接会自动关闭旧连接
|
||||
|
||||
4. **文件大小限制**:建议前端先做文件大小校验,避免上传过大文件
|
||||
|
||||
5. **重连机制**:建议实现自动重连机制,当连接断开时自动重新建立连接
|
||||
|
||||
```javascript
|
||||
// 简单重连示例
|
||||
function connectWithRetry(userId, maxRetries = 3) {
|
||||
let retries = 0;
|
||||
|
||||
const connect = () => {
|
||||
const es = new EventSource(`/api/v1/sse/connect/${userId}`);
|
||||
|
||||
es.onerror = (e) => {
|
||||
es.close();
|
||||
retries++;
|
||||
if (retries < maxRetries) {
|
||||
setTimeout(connect, 3000); // 3秒后重试
|
||||
}
|
||||
};
|
||||
|
||||
return es;
|
||||
};
|
||||
|
||||
return connect();
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user