guyue
13 小时以前 286cc1963d6d5a26f26c78d542974691b80a86f7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
package com.linghu.controller;
 
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
 
import javax.servlet.http.HttpServletRequest;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.linghu.model.dto.*;
import com.linghu.model.entity.*;
import com.linghu.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.web.reactive.function.client.WebClient;
 
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.linghu.model.common.ResponseResult;
import com.linghu.utils.JwtUtils;
 
import io.jsonwebtoken.lang.Collections;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import reactor.core.publisher.Mono;
 
import org.springframework.web.bind.annotation.* ;
import org.springframework.http.HttpStatus;
import com.linghu.model.dto.TaskResultResponse.QuestionResult;
import com.linghu.model.dto.TaskResultResponse.UserResult;
import reactor.core.scheduler.Schedulers;
 
@RestController
@RequestMapping("/collect")
@Api(value = "采集接口", tags = "采集管理")
@Slf4j
public class CollectController {
 
    @Autowired
    private ReferenceService referenceService;
 
    @Value("${linghu.url}")
    private String baseUrl;
 
    @Autowired
    private WebClient webClient;
 
    @Autowired
    private JwtUtils jwtUtils;
    @Autowired
    private KeywordService keywordService;
    @Autowired
    private QuestionService questionService;
    @Autowired
    private KeywordTaskService keywordTaskService;
    @Autowired
    private PlatformService platformService;
    @Autowired
    private TypeService typeService;
    @Autowired
    private UserService userService;
    @Autowired
    private OrderService orderService;
    private static final Queue<SearchTaskRequest> taskQueue = new LinkedList<>();
    private static boolean isProcessing = false;
    @PostMapping("/search")
    @ApiOperation(value = "开始采集")
    public Mono<ResponseResult<?>> createSearchTask(
            @RequestBody SearchTaskRequest searchTaskRequest,
            HttpServletRequest request) throws JsonProcessingException {
 
        // 首先检查服务器资源
        return getServerResource()
                .flatMap(resourceResponse -> {
                    double cpuUsage = parseUsage(resourceResponse.getCpu_usage_percent());
                    double memoryUsage = parseUsage(resourceResponse.getMemory_usage_percent());
 
                    if (cpuUsage >= 90.0 || memoryUsage >= 90.0) {
 
 
                            String errorMsg = String.format("服务器资源不足,请稍后再试");
 
                        log.warn(errorMsg);
                        return Mono.just(ResponseResult.error(503, errorMsg));
                    }
 
                    // 将新的任务请求加入队列
                    taskQueue.add(searchTaskRequest);
 
                    // 如果当前没有任务在处理中,则启动任务队列的处理
                    if (!isProcessing) {
                        processNextTaskInQueue();
                    }
 
                    // 返回响应,通知用户任务已开始
                    return Mono.just(ResponseResult.success("任务已加入队列,正在处理..."));
                })
                .onErrorResume(e -> {
                    log.error("检查服务器资源失败: {}", e.getMessage(), e);
                    return Mono.just(ResponseResult.error("检查服务器资源失败: " + e.getMessage()));
                });
    }
 
    private void processNextTaskInQueue() {
        // 设置为正在处理
        isProcessing = true;
 
        // 从队列中取出下一个任务
        SearchTaskRequest nextTaskRequest = taskQueue.poll();
 
        if (nextTaskRequest != null) {
            // 处理任务
            executeBatchTask(nextTaskRequest)
                    .doFinally(signal -> {
                        // 完成后,继续处理下一个任务
                        isProcessing = false;
                        if (!taskQueue.isEmpty()) {
                            processNextTaskInQueue();  // 继续处理队列中的下一个任务
                        }
                    })
                    .subscribe();
        }
    }
 
    private Mono<ResponseResult<String>> executeBatchTask(SearchTaskRequest searchTaskRequest) {
        log.info("开始处理任务:{}", searchTaskRequest);
        log.info("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        Integer keywordId = searchTaskRequest.getKeyword_id();
        int maxConcurrentUsers = searchTaskRequest.getConfig() != null ?
                searchTaskRequest.getConfig().getMax_concurrent_users() : 3;
        List<List<UserDto>> userBatches = splitUsersIntoBatches(searchTaskRequest.getUsers(), maxConcurrentUsers, keywordId);
 
        Queue<List<UserDto>> batchQueue = new LinkedList<>(userBatches); // 用队列存储批次
 
        return Mono.just(ResponseResult.success("第一个批次已开始"))
                .doOnTerminate(() -> {
                    // 启动后台任务,继续处理批次
                    executeBatchTask(batchQueue, searchTaskRequest, keywordId)
                            .subscribe();  // 使用subscribe()启动后台任务
                });
    }
 
    private Mono<ResponseResult<?>> executeBatchTask(Queue<List<UserDto>> batchQueue, SearchTaskRequest searchTaskRequest, Integer keywordId) {
        // 如果队列为空,说明所有批次已经完成
        if (batchQueue.isEmpty()) {
            return Mono.just(ResponseResult.success("所有批次已完成"));
        }
 
        List<UserDto> currentBatch = batchQueue.poll(); // 从队列中获取当前批次
        SearchTaskRequest batchRequest = new SearchTaskRequest();
        batchRequest.setUsers(currentBatch);
        batchRequest.setQuestions(searchTaskRequest.getQuestions());
        batchRequest.setConfig(searchTaskRequest.getConfig());
        batchRequest.setSave_to_database(searchTaskRequest.getSave_to_database());
        batchRequest.setWebhook_url(searchTaskRequest.getWebhook_url());
        batchRequest.setKeyword_id(keywordId);
 
        return createSingleBatchTask(batchRequest)
                .flatMap(taskResponse -> {
                    if (taskResponse != null && taskResponse.getTask_id() != null) {
                        // 保存任务关联到数据库
                        return saveKeywordTasks(keywordId, taskResponse)
                                .then(waitForTaskCompletion(taskResponse.getTask_id(), batchQueue, searchTaskRequest, keywordId));
                    } else {
                        return Mono.just(ResponseResult.error("创建批次任务失败"));
                    }
                });
    }
 
    private Mono<Void> saveKeywordTasks(Integer keywordId, SearchTaskResponse taskResponse) {
        if (taskResponse == null || taskResponse.getTask_id() == null) {
            return Mono.error(new RuntimeException("任务响应无效或任务ID为空"));
        }
 
        KeywordTask keywordTask = new KeywordTask();
        keywordTask.setKeyword_id(keywordId);
        keywordTask.setTask_id(taskResponse.getTask_id());
        keywordTask.setStatus("pending");
 
        // 将 MyBatis-Plus 的同步方法包装为 Mono<Void>
        return Mono.fromRunnable(() -> {
                    boolean success = keywordTaskService.saveOrUpdate(keywordTask);
                    if (!success) {
                        throw new RuntimeException("保存关键词任务关联失败");
                    }
                })
                .doFinally(signalType -> log.info("成功保存关键词任务关联: Task ID {}", taskResponse.getTask_id()))
                .then();
    }
    private Mono<ResponseResult<?>> waitForTaskCompletion(String taskId, Queue<List<UserDto>> batchQueue, SearchTaskRequest searchTaskRequest, Integer keywordId) {
        // 查询任务状态
        return getTaskStatus(taskId)
                .flatMap(statusResponse -> {
                    // 如果任务状态是"submitted"或"running",继续轮询
                    if ("submitted".equalsIgnoreCase(statusResponse.getStatus()) || "running".equalsIgnoreCase(statusResponse.getStatus())) {
                        return Mono.delay(Duration.ofSeconds(5))  // 延迟 5 秒后再次查询
                                .flatMap(aLong -> waitForTaskCompletion(taskId, batchQueue, searchTaskRequest, keywordId));  // 递归调用继续等待
                    } else {
                        // 如果状态为其他状态,则继续处理下一个批次
                        return executeBatchTask(batchQueue, searchTaskRequest, keywordId);
                    }
                })
                .onErrorResume(e -> {
                    // 处理查询任务状态时的错误
                    return Mono.just(ResponseResult.error("查询任务状态失败: " + e.getMessage()));
                });
    }
    @ApiOperation(value = "查询任务状态")
    @GetMapping("/status")
    public Mono<TaskStatusResponse> getTaskStatus(String taskId) {
        return webClient.get()
                .uri(baseUrl + "/api/v1/tasks/" + taskId)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .onStatus(HttpStatus::isError, response -> response.bodyToMono(TaskStatusResponse.class)
                        .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody.getDetail()))))
                .bodyToMono(TaskStatusResponse.class)
                .onErrorResume(e -> {
                    // 处理错误,创建一个自定义的错误响应对象
                    TaskStatusResponse errorResponse = new TaskStatusResponse();
                    errorResponse.setStatus("ERROR");
                    errorResponse.setMessage(e.getMessage());
                    errorResponse.setDetail(e.getMessage());
                    return Mono.just(errorResponse);
                });
    }
 
 
 
   // 添加一个辅助方法来安全地将字符串转换为double
   private double parseUsage(String usageStr) {
       try {
           if (usageStr != null) {
               // 移除可能存在的百分号
               usageStr = usageStr.replace("%", "").trim();
               return Double.parseDouble(usageStr);
           }
           return 0.0;
       } catch (NumberFormatException e) {
           log.error("解析资源使用率失败: {}", e.getMessage());
           return 0.0;
       }
   }
   /*@PostMapping("/search")
   @ApiOperation(value = "开始采集")
   public Mono<ResponseResult<?>> createSearchTask(
           @RequestBody SearchTaskRequest searchTaskRequest,
           HttpServletRequest request) throws JsonProcessingException {
 
       // 首先检查服务器资源
       return getServerResource()
               .flatMap(resourceResponse -> {
                   // 将字符串类型的使用率转换为double类型
                   double cpuUsage = parseUsage(resourceResponse.getCpu_usage_percent());
                   double memoryUsage = parseUsage(resourceResponse.getMemory_usage_percent());
                   // 检查CPU和内存使用率
                   if (cpuUsage >= 90.0 || memoryUsage >= 90.0) {
                       String errorMsg = String.format("服务器资源不足:CPU使用率 %.1f%%,内存使用率 %.1f%%",
                               resourceResponse.getCpu_usage_percent(), resourceResponse.getMemory_usage_percent());
                       log.warn(errorMsg);
                       return Mono.just(ResponseResult.error(503, errorMsg));
                   }
                   Integer keywordId = searchTaskRequest.getKeyword_id();
 
                   int maxConcurrentUsers = searchTaskRequest.getConfig() != null ?
                           searchTaskRequest.getConfig().getMax_concurrent_users() : 3;
                   List<List<UserDto>> userBatches = splitUsersIntoBatches(searchTaskRequest.getUsers(), maxConcurrentUsers,keywordId);
 
                   return Flux.fromIterable(userBatches)
                           .flatMap(batch -> {
                               SearchTaskRequest batchRequest = new SearchTaskRequest();
                               batchRequest.setUsers(batch);
                               batchRequest.setQuestions(searchTaskRequest.getQuestions());
                               batchRequest.setConfig(searchTaskRequest.getConfig());
                               batchRequest.setSave_to_database(searchTaskRequest.getSave_to_database());
                               batchRequest.setWebhook_url(searchTaskRequest.getWebhook_url());
                               batchRequest.setKeyword_id(keywordId);
 
                               return createSingleBatchTask(batchRequest)
                                       .delaySubscription(Duration.ofSeconds(2)); // 批次之间添加延迟
                           }, 1) // 限制并发数为1,确保顺序执行
                           .collectList() // 收集所有批次的响应
                           .flatMap(responses ->
                                   saveKeywordTasks(keywordId, responses) // 保存关联关系
                                           .thenReturn(responses) // 返回原始响应
                           )
                           .map(responses -> ResponseResult.success(responses)) // 使用ResponseResult包装结果
                           .onErrorResume(e -> {
                               log.error("创建搜索任务失败: {}", e.getMessage(), e);
                               return Mono.just(ResponseResult.error("创建搜索任务失败: " + e.getMessage()));
                           });
               })
               .onErrorResume(e -> {
                   log.error("检查服务器资源失败: {}", e.getMessage(), e);
                   return Mono.just(ResponseResult.error("检查服务器资源失败: " + e.getMessage()));
               });
   }*/
 
//    private Mono<Void> saveKeywordTasks(Integer keywordId, List<SearchTaskResponse> taskResponses) {
//        List<KeywordTask> keywordTasks = taskResponses.stream()
//                .filter(response -> response.getTask_id() != null)
//                .map(response -> {
//                    KeywordTask keywordTask = new KeywordTask();
//                    keywordTask.setKeyword_id(keywordId);
//                    keywordTask.setTask_id(response.getTask_id());
//                    keywordTask.setStatus("pending");
//                    return keywordTask;
//                })
//                .collect(Collectors.toList());
//
//        // 将 MyBatis-Plus 的同步方法包装为 Mono<Void>
//        return Mono.fromRunnable(() -> {
//                    boolean success = keywordTaskService.saveOrUpdateBatch(keywordTasks);
//                    if (!success) {
////                        throw new RuntimeException("保存关键词任务关联失败");
//                        // 添加异常处理
//                        Mono.error( new RuntimeException("保存关键词任务关联失败"));
//                    }
//                })
//                .doFinally(signalType -> log.info("成功保存 {} 个关键词任务关联", keywordTasks.size()))
//                .then();
//    }
 
    private List<List<UserDto>> splitUsersIntoBatches(List<UserDto> users, int batchSize,Integer keywordId) {
 
        Keyword keyword = keywordService.getById(keywordId);
        if (null==keyword.getNum()){
            keyword.setNum(0);
        }
        keyword.setNum(keyword.getNum()+1);
        keywordService.updateById(keyword);
 
        List<List<UserDto>> batches = new ArrayList<>();
        for (int i = 0; i < users.size(); i += batchSize) {
            batches.add(users.subList(i, Math.min(i + batchSize, users.size())));
        }
        return batches;
    }
 
    private Mono<SearchTaskResponse> createSingleBatchTask(SearchTaskRequest batchRequest) {
        return webClient.post()
                .uri(baseUrl + "/api/v1/search")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(batchRequest)
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> response.bodyToMono(String.class)
                        .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody))))
                .bodyToMono(new ParameterizedTypeReference<SearchTaskResponse>() {})
                .flatMap(taskResponse -> {
                    if (taskResponse != null && taskResponse.getTask_id() != null) {
 
                        // 使用 Reactor 的方式更新数据库
                        return Mono.fromRunnable(() -> {
                                    LambdaUpdateWrapper<Keyword> updateWrapper = new LambdaUpdateWrapper<>();
                                    updateWrapper.eq(Keyword::getKeyword_id, batchRequest.getKeyword_id());
                                    updateWrapper.set(Keyword::getStatus, "submitted");
                                    updateWrapper.set(Keyword::getTask_id, taskResponse.getTask_id());
                                    keywordService.update(updateWrapper);
                                    //设置轮数
                                    Keyword keyword = keywordService.getById(batchRequest.getKeyword_id());
 
                                    //设置订单进入采集状态
                                    LambdaUpdateWrapper<Orders> updateOrderWrapper = new LambdaUpdateWrapper<>();
                                    updateOrderWrapper.eq(Orders::getOrder_id, keyword.getOrder_id()) // 确保字段名正确
                                            .set(Orders::getStatus, 2); // 直接设置状态值
 
                                    boolean success = orderService.update(updateOrderWrapper);
                                    log.info("订单状态更新结果: {}", success ? "成功" : "失败");
 
                                }).subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池执行
                                .thenReturn(taskResponse);
                    }
                    return Mono.just(taskResponse);
                });
    }
 
    // 移除原来的waitForTaskCompletion方法,不再需要同步等待
//    @ApiOperation(value = "查询任务状态")
//    @GetMapping("/status")
//    public Mono<TaskStatusResponse> getTaskStatus(String taskId) {
//        return webClient.get()
//                .uri(baseUrl + "/api/v1/tasks/" + taskId)
//                .accept(MediaType.APPLICATION_JSON)
//                .retrieve()
//                .onStatus(HttpStatus::isError, response -> response.bodyToMono(TaskStatusResponse.class)
//                        .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody.getDetail()))))
//                .bodyToMono(TaskStatusResponse.class)
//                .flatMap(result -> {
//                    TaskStatusResponse taskStatusResponse = result;
//                    if (taskStatusResponse != null && taskStatusResponse.getStatus() != null) {
//                        List<Question> updateQuestions = taskStatusResponse.getQuestions_status().stream()
//                                .map(qs -> {
//                                    Question question = new Question();
//                                    question.setQuestion_id(qs.getQuestion_id());
//                                    question.setStatus(qs.getStatus());
//                                    return question;
//                                }).collect(Collectors.toList());
//
//                        // 包装成响应式操作
//                        return Mono.fromCallable(() -> {
//                            questionService.updateBatchById(updateQuestions);
//                            return result;
//                        });
//
//                    }
//                    return Mono.just(result);
//                })
//                .onErrorResume(e -> {
//                    // 创建一个自定义的错误响应对象
//                    TaskStatusResponse errorResponse = new TaskStatusResponse();
//                    errorResponse.setStatus("ERROR");
//                    errorResponse.setMessage(e.getMessage());
//                    errorResponse.setDetail(e.getMessage());
//
//                    return Mono.just(errorResponse);
//                });
//    }
 
    @PostMapping("/cancel/{taskId}")
    @ApiOperation(value = "取消任务")
    public Mono<ResponseResult<TaskCancelResponse>> cancelTask(@PathVariable String taskId) {
        return webClient.post()
                .uri(baseUrl + "/api/v1/tasks/" + taskId + "/cancel")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(Collections.emptyMap()) // 添加空请求体
                .retrieve()
                .onStatus(HttpStatus::isError, response -> response.bodyToMono(TaskCancelResponse.class)
                        .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody.getDetail()))))
                .bodyToMono(TaskCancelResponse.class)
                .flatMap(cancelResponse -> {
                    // 更新关键词状态
                    Mono<Void> updateKeyword = Mono.fromRunnable(() -> {
                                LambdaUpdateWrapper<Keyword> updateWrapper = new LambdaUpdateWrapper<>();
                                updateWrapper.eq(Keyword::getTask_id, taskId);
                                updateWrapper.set(Keyword::getStatus, "canceled"); // 统一使用"canceled"
                                keywordService.update(updateWrapper);
                            })
                            .subscribeOn(Schedulers.boundedElastic())
                            .then();
 
                    // 更新关键词任务状态
                    Mono<Void> updateKeywordTask = Mono.fromRunnable(() -> {
                                LambdaUpdateWrapper<KeywordTask> updateWrapper = new LambdaUpdateWrapper<>();
                                updateWrapper.eq(KeywordTask::getTask_id, taskId);
                                updateWrapper.set(KeywordTask::getStatus, "canceled"); // 统一使用"canceled"
                                keywordTaskService.update(updateWrapper);
                            })
                            .subscribeOn(Schedulers.boundedElastic())
                            .then();
 
                    // 并行执行两个更新操作,并在完成后返回cancelResponse
                    return Mono.when(updateKeyword, updateKeywordTask)
                            .thenReturn(cancelResponse);
                })
                .map(data -> ResponseResult.success(data))
                .onErrorResume(e -> {
                    if (e.getMessage().contains("任务不存在")) {
                        return Mono.just(ResponseResult.error(200, e.getMessage()));
                    } else if (e.getMessage().contains("无法取消")) {
                        return Mono.just(ResponseResult.error(200, e.getMessage()));
                    }
                    return Mono.just(ResponseResult.error(500,  e.getMessage()));
                });
    }
 
    @ApiOperation(value = "获取任务结果")
    @GetMapping("/tasks/{taskId}")
    public Mono<TaskResultResponse> getTaskResult(@PathVariable String taskId) {
        return webClient.get()
                .uri(baseUrl + "/api/v1/tasks/" + taskId + "/result")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> {
                    if (response.statusCode() == HttpStatus.NOT_FOUND) {
                        return response.bodyToMono(String.class)
                                .flatMap(errorBody -> Mono.error(new RuntimeException("任务不存在")));
                    } else if (response.statusCode() == HttpStatus.BAD_REQUEST) {
                        return response.bodyToMono(String.class)
                                .flatMap(errorBody -> Mono.error(new RuntimeException("任务未完成,无法获取结果")));
                    }
                    return response.createException().flatMap(Mono::error);
                })
                .bodyToMono(TaskResultResponse.class)
                .flatMap(responseResult -> {
                    TaskResultResponse result = responseResult;
                    if (result != null && result.getResults() != null) {
                        return updateQuestionAndReference(result)
                                .thenReturn(responseResult);
                    }
                    return Mono.just(responseResult);
                })
                .onErrorResume(e -> {
                    System.out.println("获取任务结果失败");
                        TaskResultResponse result = new TaskResultResponse();
                    result.setDetail("获取任务结果失败: " + e.getMessage());
                    return Mono.just(result);
                });
    }
 
//    private Mono<Void> updateQuestionAndReference(TaskResultResponse result) {
//        return Mono.fromRunnable(() -> {
//            // 1. 更新关键词状态
//            LambdaUpdateWrapper<Keyword> keywordUpdate = new LambdaUpdateWrapper<>();
//            keywordUpdate.eq(Keyword::getTask_id, result.getTask_id())
//                    .set(Keyword::getStatus, "completed");
//            keywordService.update(keywordUpdate);
//
//            // 查询关键词ID
//            LambdaQueryWrapper<Keyword> keywordQuery = new LambdaQueryWrapper<>();
//            keywordQuery.eq(Keyword::getTask_id, result.getTask_id());
//            Keyword keyword = keywordService.getOne(keywordQuery);
//
//            if (keyword == null) {
//                System.out.println("未找到关联的关键词,task_id: " + result.getTask_id());
//                return;
//            }
//
//            // 2. 处理每个用户的问题结果
//            for (UserResult userResult : result.getResults()) {
//                for (QuestionResult questionResult : userResult.getQuestions_results()) {
//                    // 2.1 查询问题ID
//                    LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
//                    queryWrapper.eq(Question::getQuestion, questionResult.getQuestion())
//                            .eq(Question::getKeyword_id, keyword.getKeyword_id());
//                    Question question = questionService.getOne(queryWrapper);
//
//                    if (question != null) {
//                        // 更新问题状态
//                        LambdaUpdateWrapper<Question> updateWrapper = new LambdaUpdateWrapper<>();
//                        updateWrapper.eq(Question::getQuestion_id, question.getQuestion_id())
//                                .set(Question::getStatus, questionResult.getStatus())
//                                .set(Question::getResponse, questionResult.getResponse())
//                                .set(Question::getExtracted_count, questionResult.getExtracted_count())
//                                .set(Question::getError, questionResult.getError())
//                                .set(Question::getTimestamp, LocalDateTime.parse(
//                                        questionResult.getTimestamp(),
//                                        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS")
//                                ));
//                        questionService.update(updateWrapper);
//
//                        // 2.2 保存引用数据
//                        List<Reference> references = questionResult.getReferences().stream()
//                                .map(ref -> {
//                                    Reference reference = new Reference();
//                                    reference.setQuestion_id(question.getQuestion_id());
//                                    reference.setTitle(ref.getTitle());
//                                    reference.setUrl(ref.getUrl());
//                                    reference.setDomain(ref.getDomain());
//                                    reference.setCreate_time(LocalDateTime.now());
//                                    return reference;
//                                })
//                                .collect(Collectors.toList());
//
//                        if (!references.isEmpty()) {
//                            referenceService.saveBatch(references);
//                        }
//                    } else {
//                        System.out.println("未找到匹配的问题,question " + question.getQuestion());
//
//                    }
//                }
//            }
//        });
//    }
 
    private Mono<Void> updateQuestionAndReference(TaskResultResponse result) {
        return Mono.fromRunnable(() -> {
            try {
                //查看每个账号信息的status是否正常
 
                // 1. 根据KeywordTask更新关键词状态
                // 查询关键词ID
                LambdaQueryWrapper<KeywordTask> keywordTaskWrapper = new LambdaQueryWrapper<>();
                keywordTaskWrapper.eq(KeywordTask::getTask_id, result.getTask_id());
                KeywordTask keywordTask = keywordTaskService.getOne(keywordTaskWrapper);
 
                Keyword keyword = keywordService.getById(keywordTask.getKeyword_id());
 
                if (keyword == null) {
                    System.out.println("未找到关联的关键词,task_id: " + result.getTask_id());
                    //报错
                    throw new Exception("未找到关联的关键词,task_id: " + result.getTask_id());
//                    return;
                }
                LambdaQueryWrapper<KeywordTask> keywordTaskWrapper2 = new LambdaQueryWrapper<>();
                keywordTaskWrapper2.eq(KeywordTask::getKeyword_id, keyword.getKeyword_id());
                List<KeywordTask> keywordTasks = keywordTaskService.list(keywordTaskWrapper2);
 
// 定义状态优先级:canceled > false > completed
                String finalStatus = "completed"; // 默认状态为 completed
 
                for (KeywordTask task : keywordTasks) {
                    String status = task.getStatus();
                    if ("canceled".equals(status)) {
                        finalStatus = "canceled";
                        break; // 遇到 canceled 直接跳出循环,因为优先级最高
                    } else if ("false".equals(status)) {
                        finalStatus = "false";
                        // 不跳出循环,继续检查是否存在 canceled
                    }
                }
// 更新关键词状态
                if (!finalStatus.equals(keyword.getStatus())) {
                    keyword.setStatus(finalStatus);
                    keywordService.updateById(keyword);
                    String orderId = keyword.getOrder_id();
                    if (orderId == null || orderId.isEmpty()) {
                        System.out.println("关键词[" + keyword.getKeyword_id() + "]未关联订单,跳过订单状态更新");
                        return;
                    }
 
                    // 2. 查询该订单下的所有关键词
                    LambdaQueryWrapper<Keyword> orderKeywordsWrapper = new LambdaQueryWrapper<>();
                    orderKeywordsWrapper.eq(Keyword::getOrder_id, orderId);
                    List<Keyword> orderKeywords = keywordService.list(orderKeywordsWrapper);
 
                    if (orderKeywords.isEmpty()) {
                        System.out.println("订单[" + orderId + "]下无关键词,跳过状态更新");
                        return;
                    }
 
                    // 3. 检查所有关键词的状态是否均为 completed 或 false
                    boolean allValid = orderKeywords.stream()
                            .allMatch(k -> "completed".equals(k.getStatus()) || "false".equals(k.getStatus()));
 
                    // 4. 若所有关键词状态均有效,更新订单状态为3
                    if (allValid) {
                        Orders orders = orderService.getById(orderId);
                        if (orders != null) {
                            orders.setStatus(3); // 假设Orders有Integer类型的status字段
                            orderService.updateById(orders);
                            System.out.println("订单[" + orderId + "]所有关键词状态符合条件,已更新状态为3");
                        } else {
                            System.out.println("未找到订单[" + orderId + "],无法更新状态");
                        }
                    }
                }
                Orders orders = orderService.getById(keyword.getOrder_id());
 
                // 2. 批量查询所有问题
                LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(Question::getKeyword_id, keyword.getKeyword_id());
                List<Question> questions = questionService.list(queryWrapper);
 
                // 构建问题映射表,用于快速查找
                Map<String, Question> questionMap = questions.stream()
                        .collect(Collectors.toMap(Question::getQuestion, q -> q));
 
                // 3. 收集所有需要更新的问题和引用
                List<Question> questionsToUpdate = new ArrayList<>();
                List<Reference> allReferences = new ArrayList<>();
                List<Reference> resultList = new ArrayList<>();
                // 遍历结果
                for (UserResult userResult : result.getResults()) {
                    for (QuestionResult questionResult : userResult.getQuestions_results()) {
                        try {
                            Question question = questionMap.get(questionResult.getQuestion());
                            if (question != null) {
                                // 更新问题对象
                                question.setStatus(questionResult.getStatus());
                                question.setResponse(questionResult.getResponse());
                                question.setExtracted_count(questionResult.getExtracted_count());
                                question.setError(questionResult.getError());
                                question.setKeyword_id(keyword.getKeyword_id());
 
                                // 解析时间戳
                                if (questionResult.getTimestamp() != null) {
                                    DateTimeFormatter formatter = DateTimeFormatter
                                            .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");
                                    question.setTimestamp(
                                            LocalDateTime.parse(questionResult.getTimestamp(), formatter));
                                }
                                //更新
//                                questionService.updateById(question);
 
                                questionsToUpdate.add(question);
 
                               /* List<Reference> references =
                                        Optional.ofNullable(questionResult.getReferences())
                                                .orElse(Collections.emptyList())
                                                .stream()
                                                .map(ref -> {
                                                    Reference reference = new Reference();
                                                    reference.setQuestion_id(question.getQuestion_id());
                                                    reference.setTitle(ref.getTitle());
                                                    reference.setUrl(ref.getUrl());
                                                    reference.setDomain(ref.getDomain());
                                                    reference.setNum(keyword.getNum());
                                                    reference.setTask_id(result.getTask_id());
                                                    reference.setKeyword_id(keyword.getKeyword_id());
                                                    //域名和平台id映射
                                                    reference.setCreate_time(LocalDateTime.now());
                                                    Platform platform = platformService.getPlatformByDomain(reference.getDomain());
                                                    if (platform == null) {
                                                        //平台为空 创建平台 类型为“默认”
                                                        Type type = typeService.getOne(new LambdaQueryWrapper<Type>().eq(Type::getType_name,"默认"));
                                                        if (type == null) {
                                                            Type newType = new Type();
                                                            newType.setType_name("默认");
                                                            typeService.save(newType);
                                                            type = newType;
                                                        }
                                                        Platform platform1 = new Platform();
                                                        platform1.setDomain(reference.getDomain());
                                                        platform1.setPlatform_name(reference.getDomain());
                                                        platform1.setType_id(type.getType_id());
                                                        platformService.save(platform1);
 
                                                        reference.setType_id(type.getType_id());
                                                        reference.setPlatform_id(platform1.getPlatform_id());
 
                                                    }
                                                    else {
                                                        reference.setPlatform_id(platform.getPlatform_id());
                                                        Type type = typeService.getById(platform.getType_id());
                                                        if (type != null){
                                                            reference.setType_id(type.getType_id());
                                                        }
                                                    }
                                                    return reference;
                                                })
                                                .collect(Collectors.toList());*/
// 初始化引用列表(避免null)
                                List<Reference> references = new ArrayList<>();
                                List<TaskResultResponse.Reference> originalReferences = questionResult.getReferences();
                                if (originalReferences == null) {
                                    originalReferences = Collections.emptyList();
                                }
 
// 遍历原始引用列表,转换为Reference对象
                                for (TaskResultResponse.Reference ref : originalReferences) { // 注意:需将“原引用类型”替换为实际类型(如QuestionResult中的引用类型)
                                    Reference reference = new Reference();
                                    // 设置基本字段
                                    reference.setQuestion_id(question.getQuestion_id());
                                    reference.setTitle(ref.getTitle());
                                    reference.setUrl(ref.getUrl());
                                    reference.setDomain(ref.getDomain());
                                    reference.setNum(keyword.getNum());
                                    reference.setTask_id(result.getTask_id());
                                    reference.setKeyword_id(keyword.getKeyword_id());
                                    reference.setCreate_time(LocalDateTime.now());
 
                                    // 处理平台和类型关联
                                    Platform platform = platformService.getPlatformByDomain(reference.getDomain());
                                    if (platform == null) {
                                        // 平台不存在,创建新平台(类型默认为“默认”)
                                        Type type = typeService.getOne(new LambdaQueryWrapper<Type>().eq(Type::getType_name, "默认"));
                                        if (type == null) {
                                            Type newType = new Type();
                                            newType.setType_name("默认");
                                            typeService.save(newType);
                                            type = newType;
                                        }
                                        Platform platform1 = new Platform();
                                        platform1.setDomain(reference.getDomain());
                                        platform1.setPlatform_name(reference.getDomain());
                                        platform1.setType_id(type.getType_id());
                                        platform1.setCreate_time(LocalDateTime.now());
                                        platformService.save(platform1);
 
                                        // 关联新平台和类型
                                        reference.setType_id(type.getType_id());
                                        reference.setPlatform_id(platform1.getPlatform_id());
                                    } else {
                                        // 平台已存在,直接关联
                                        reference.setPlatform_id(platform.getPlatform_id());
                                        Type type = typeService.getById(platform.getType_id());
                                        if (type != null) {
                                            reference.setType_id(type.getType_id());
                                        }
                                    }
 
                                    // 添加到结果列表
                                    references.add(reference);
                                }
                                // 添加到总引用列表
                                if (!references.isEmpty()) {
                                    allReferences.addAll(references);
                                }
 
                                //取数据库中当前关键词的当前轮次的当前问题id结果拿出来
                                List<Reference> dbList = referenceService.list(new LambdaQueryWrapper<Reference>().eq(Reference::getKeyword_id, keyword.getKeyword_id())
                                        .eq(Reference::getNum, keyword.getNum())
                                        .eq(Reference::getQuestion_id, question.getQuestion_id())
                                );
 
                                // 1. 合并两个列表
                                List<Reference> combinedList = new ArrayList<>();
                                combinedList.addAll(allReferences);
                                combinedList.addAll(dbList);
 
                                // 2. 创建复合键的Map,用于统计完全匹配的记录
                                Map<String, List<Reference>> compositeKeyMap = combinedList.stream()
                                        .collect(Collectors.groupingBy(
                                                ref -> ref.getTitle() + "|" + ref.getUrl() + "|" + ref.getDomain()
                                        ));
 
                                // 3. 处理每组重复记录
 
                                compositeKeyMap.forEach((key, refGroup) -> {
                                    // 3.1 找出组内有ID的记录(优先从dbList中获取)
                                    Optional<Reference> existingRecord = refGroup.stream()
                                            .filter(ref -> ref.getReference_id() != null)
                                            .findFirst();
 
                                    // 3.2 统计该组的重复次数(总数-1)
                                    int repetitionCount = refGroup.size() - 1;
 
                                    // 3.3 决定最终保留的记录
                                    Reference recordToSave = new Reference();
                                    if (existingRecord.isPresent()) {
                                        // 使用已有ID的记录并更新重复次数
                                        recordToSave = existingRecord.get();
                                        recordToSave.setRepetition_num(
                                                (recordToSave.getRepetition_num() == null ? 1 : recordToSave.getRepetition_num())
                                                        + repetitionCount
                                        );
                                    } else {
                                        // 没有ID记录则取第一条并设置重复次数
                                        recordToSave = refGroup.get(0);
                                        recordToSave.setRepetition_num(1+repetitionCount);
                                    }
 
                                    resultList.add(recordToSave);
                                });
                                referenceService.saveOrUpdateBatch(resultList);
                            }
                        } catch (Exception e) {
                            log.error(e.getMessage(), e);
                            System.out.println("处理问题结果失败: " + e.getMessage());
                        }
                    }
                    //更新账号状态
                    if ( "failed".equals(userResult.getStatus())){
                        if (userResult.getError().contains("登录失败")){
                            LambdaUpdateWrapper<User> userWrapper =  new LambdaUpdateWrapper<>();
                            userWrapper.eq(User::getUser_email, userResult.getUser_email());
                            userWrapper.set(User::getStatus, "无法登录");
                            userService.update(userWrapper);
 
                        }else if (userResult.getError().contains("信息错误")){
                            LambdaUpdateWrapper<User> userWrapper =  new LambdaUpdateWrapper<>();
                            userWrapper.eq(User::getUser_email, userResult.getUser_email());
                            userWrapper.set(User::getStatus, "信息错误");
                            userService.update(userWrapper);
                        }
                    }
 
                }
 
                // 4. 批量更新问题
                System.out.println(questionsToUpdate);
                if (!questionsToUpdate.isEmpty()) {
                    questionService.updateBatchById(questionsToUpdate);
                    System.out.println("成功批量更新 " + questionsToUpdate.size() + " 个问题");
                }
 
                // 5. 批量插入引用,使用流式分批处理
//                if (!allReferences.isEmpty()) {
//                    int batchSize = 1000;
//                    IntStream.iterate(0, i -> i + batchSize)
//                            .limit((allReferences.size() + batchSize - 1) / batchSize)
//                            .forEach(i -> {
//                                List<Reference> batch = allReferences.subList(
//                                        i, Math.min(i + batchSize, allReferences.size()));
//                                referenceService.saveBatch(batch);
//                            });
//                    System.out.println("成功批量插入 " + allReferences.size() + " 条引用数据");
//                }
 
 
            } catch (Exception e) {
                log.error("更新问题和引用数据失败: " ,e.getMessage(), e);
//                System.out.println("更新问题和引用数据失败: " + e.getMessage());
                throw new RuntimeException("更新问题和引用数据失败", e);
            }
        });
    }
 
    @GetMapping("/tasks/all")
    @ApiOperation(value = "获取所有任务列表")
    public Mono<TaskListResponse> getAllTasks() {
        return webClient.get()
                .uri(baseUrl + "/api/v1/tasks")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<TaskListResponse>() {
                })
                .onErrorResume(e -> {
                    TaskListResponse response = new TaskListResponse();
                    response.setDetail("获取任务列表失败: " + e.getMessage());
                    return Mono.just(response);
 
                    // return Mono.just(ResponseResult.error("获取任务列表失败: " + e.getMessage()));
                });
    }
 
    @GetMapping("/health")
    @ApiOperation("健康检查")
    public Mono<HealthResponse> checkThirdPartyHealth() {
        return webClient.get()
                .uri(baseUrl + "/health") // 假设第三方健康检查接口路径为/health
                .retrieve()
                .bodyToMono(HealthResponse.class)
                .onErrorResume(e -> Mono.just(
                        new HealthResponse("unhealthy", null, "", e.getMessage())));
    }
 
    /**
     * 查询服务器资源
     */
    @GetMapping("/server/resource")
    @ApiOperation(value = "查询服务器资源")
    public Mono<ServerResourceResponse> getServerResource() {
        return webClient.get()
                .uri(baseUrl + "/api/v1/system/resources")
                .retrieve()
                .bodyToMono(ServerResourceResponse.class)
                .onErrorResume(e -> Mono.just(
                        new ServerResourceResponse( e.getMessage())));
    }
    /**
     * 传入orderid查所有关键词id以及关键词下面的所有任务id,轮询所有任务状态,如果状态为completed,则循环调用获取结果接口,处理结果
     */
 
 
}