guyue
2 天以前 d3b6555513c6c0e283bd6e891d4e080aefa6003a
更新
6个文件已添加
15个文件已修改
591 ■■■■ 已修改文件
pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/config/Swagger2Config.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/controller/CollectController.java 320 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/controller/KeywordController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/controller/QuestionController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/mapper/KeywordTaskMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/mapper/PlatformMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/mapper/ReferenceMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/model/dto/ServerResourceResponse.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/model/entity/KeywordTask.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/model/entity/Reference.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/service/KeywordTaskService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/service/PlatformService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/service/ReferenceService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/service/TypeService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/service/impl/KeywordTaskServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/service/impl/PlatformServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/linghu/service/impl/ReferenceServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/KeywordTaskMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/PlatformMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/ReferenceMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -84,6 +84,10 @@
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
src/main/java/com/linghu/config/Swagger2Config.java
@@ -32,32 +32,32 @@
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.linghu.controller"))
                .paths(PathSelectors.any())
                .build()
                .build();
                // 重点修改:使用 securitySchemes + securityContext 替代全局参数
                .securitySchemes(Arrays.asList(apiKey()))  // 添加安全方案
                .securityContexts(Arrays.asList(securityContext())); // 应用安全上下文
//                .securitySchemes(Arrays.asList(apiKey()))  // 添加安全方案
//                .securityContexts(Arrays.asList(securityContext())); // 应用安全上下文
    }
    // 1. 定义安全方案(在Swagger UI顶部添加Authorize按钮)
    private ApiKey apiKey() {
        return new ApiKey("BearerToken", "Authorization", "header");
    }
    // 2. 配置安全上下文(全局生效)
    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.any()) // 对所有路径生效
                .build();
    }
    // 3. 设置默认授权范围
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Arrays.asList(new SecurityReference("BearerToken", authorizationScopes));
    }
//    private ApiKey apiKey() {
//        return new ApiKey("BearerToken", "Authorization", "header");
//    }
//
//    // 2. 配置安全上下文(全局生效)
//    private SecurityContext securityContext() {
//        return SecurityContext.builder()
//                .securityReferences(defaultAuth())
//                .forPaths(PathSelectors.any()) // 对所有路径生效
//                .build();
//    }
//
//    // 3. 设置默认授权范围
//    private List<SecurityReference> defaultAuth() {
//        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
//        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
//        authorizationScopes[0] = authorizationScope;
//        return Arrays.asList(new SecurityReference("BearerToken", authorizationScopes));
//    }
//http://localhost:8080/swagger-ui.html
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
src/main/java/com/linghu/controller/CollectController.java
@@ -1,19 +1,23 @@
package com.linghu.controller;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.linghu.mapper.PlatformMapper;
import com.linghu.mapper.TypeMapper;
import com.linghu.model.dto.*;
import com.linghu.model.entity.*;
import com.linghu.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -26,30 +30,28 @@
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.model.entity.Keyword;
import com.linghu.model.entity.Question;
import com.linghu.model.entity.User;
import com.linghu.service.KeywordService;
import com.linghu.service.QuestionService;
import com.linghu.service.ReferenceService;
import com.linghu.utils.JwtUtils;
import io.jsonwebtoken.lang.Collections;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;
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 com.linghu.model.entity.Reference;
import reactor.core.scheduler.Schedulers;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@RestController
@RequestMapping("/collect")
@Api(value = "采集接口", tags = "采集管理")
@Slf4j
public class CollectController {
    @Autowired
@@ -67,26 +69,19 @@
    private KeywordService keywordService;
    @Autowired
    private QuestionService questionService;
    @Autowired
    private KeywordTaskService keywordTaskService;
    @Autowired
    private PlatformService platformService;
    @Autowired
    private TypeService typeService;
    @PostMapping("/search")
 /*   @PostMapping("/search")
    @ApiOperation(value = "开始采集")
    public Mono<SearchTaskResponse> createSearchTask(
            @RequestBody SearchTaskRequest searchTaskRequest,
            HttpServletRequest request) throws JsonProcessingException {
//        String token = request.getHeader("Authorization");
//        User user = jwtUtils.parseToken(token);
//        // 复制到UserDto
//        UserDto userDto = new UserDto();
//        userDto.setName(user.getUser_name());
//        userDto.setEmail(user.getUser_email());
//        userDto.setPassword(user.getPassword());
//
//        List<UserDto> users = new ArrayList<>();
//        users.add(userDto);
//        searchTaskRequest.setUsers(users);
        // json格式
        ObjectMapper objectMapper = new ObjectMapper();
        System.out.println(objectMapper.writeValueAsString(searchTaskRequest));
        return webClient.post()
                .uri(baseUrl + "/api/v1/search")
@@ -104,10 +99,12 @@
                        // 保存任务ID到关键词
                        LambdaUpdateWrapper<Keyword> updateWrapper = new LambdaUpdateWrapper<>();
                        updateWrapper.eq(Keyword::getKeyword_id, searchTaskRequest.getKeyword_id());
                        updateWrapper.set(Keyword::getStatus,"Submitted");
                        updateWrapper.set(Keyword::getTask_id, taskResponse.getTask_id());
                        keywordService.update(updateWrapper);
                        // 可选:更新响应中的其他信息
                        // taskResponse.setMessage("任务已提交并保存,ID: " + taskResponse.getTaskId());
                    }
                    return Mono.just(taskResponse);
                })
@@ -117,8 +114,179 @@
                    task.setMessage("调用失败: " + e.getMessage());
                    return Mono.just(task);
                });
    }*/
//    public SearchTaskController(WebClient.Builder webClientBuilder, KeywordService keywordService) {
//        this.webClient = webClientBuilder.build();
//        this.keywordService = keywordService;
//    }
   /* @PostMapping("/search")
    @ApiOperation(value = "开始采集")
    public Mono<SearchTaskResponse> createSearchTask(
            @RequestBody SearchTaskRequest searchTaskRequest,
            HttpServletRequest request) throws JsonProcessingException {
        int maxConcurrentUsers = searchTaskRequest.getConfig() != null ?
                searchTaskRequest.getConfig().getMax_concurrent_users() : 3;
        List<List<UserDto>> userBatches = splitUsersIntoBatches(searchTaskRequest.getUsers(), maxConcurrentUsers);
        return processBatchesSequentially(userBatches, searchTaskRequest)
                .onErrorResume(e -> {
                    SearchTaskResponse task = new SearchTaskResponse();
                    task.setMessage("调用失败: " + e.getMessage());
                    return Mono.just(task);
                });
    }
    private List<List<UserDto>> splitUsersIntoBatches(List<UserDto> users, int batchSize) {
        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> processBatchesSequentially(List<List<UserDto>> userBatches, SearchTaskRequest originalRequest) {
        Mono<SearchTaskResponse> resultMono = Mono.empty();
        for (List<UserDto> batch : userBatches) {
            SearchTaskRequest batchRequest = new SearchTaskRequest();
            batchRequest.setUsers(batch);
            batchRequest.setQuestions(originalRequest.getQuestions());
            batchRequest.setConfig(originalRequest.getConfig());
            batchRequest.setSave_to_database(originalRequest.getSave_to_database());
            batchRequest.setWebhook_url(originalRequest.getWebhook_url());
            batchRequest.setKeyword_id(originalRequest.getKeyword_id());
            resultMono = resultMono.then(createSingleBatchTask(batchRequest));
        }
        return resultMono;
    }
    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(responseResult -> {
                    SearchTaskResponse taskResponse = responseResult;
                    if (taskResponse != null && taskResponse.getTask_id() != null) {
                        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);
                    }
                    return waitForTaskCompletion(taskResponse.getTask_id())
                            .then(Mono.just(taskResponse));
                });
    }
    private Mono<Void> waitForTaskCompletion(String taskId) {
        return Flux.interval(Duration.ofSeconds(5)) // 每5秒执行一次
                .flatMap(tick -> webClient.get()
                        .uri(baseUrl + "/api/v1/tasks/" + taskId)
                        .retrieve()
                        .bodyToMono(TaskStatusResponse.class)
                )
                .filter(response -> "completed".equals(response.getStatus()))
                .next() // 找到第一个完成的响应后结束流
                .then(); // 转换为Mono<Void>
    }*/
   @PostMapping("/search")
   @ApiOperation(value = "开始采集")
   public Mono<List<SearchTaskResponse>> createSearchTask(
           @RequestBody SearchTaskRequest searchTaskRequest,
           HttpServletRequest request) throws JsonProcessingException {
       int maxConcurrentUsers = searchTaskRequest.getConfig() != null ?
               searchTaskRequest.getConfig().getMax_concurrent_users() : 3;
       List<List<UserDto>> userBatches = splitUsersIntoBatches(searchTaskRequest.getUsers(), maxConcurrentUsers);
       // 获取 keywordId
       Integer keywordId = searchTaskRequest.getKeyword_id();
       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) // 返回原始响应
               );
   }
    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());
                    return keywordTask;
                })
                .collect(Collectors.toList());
        // 将 MyBatis-Plus 的同步方法包装为 Mono<Void>
        return Mono.fromRunnable(() -> {
                    boolean success = keywordTaskService.saveOrUpdateBatch(keywordTasks);
                    if (!success) {
                        throw new RuntimeException("保存关键词任务关联失败");
                    }
                })
                .doFinally(signalType -> log.info("成功保存 {} 个关键词任务关联", keywordTasks.size()))
                .then();
    }
    private List<List<UserDto>> splitUsersIntoBatches(List<UserDto> users, int batchSize) {
        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);
                                }).subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池执行
                                .thenReturn(taskResponse);
                    }
                    return Mono.just(taskResponse);
                });
    }
    // 移除原来的waitForTaskCompletion方法,不再需要同步等待
    @ApiOperation(value = "查询任务状态")
    @GetMapping("/status")
    public Mono<TaskStatusResponse> getTaskStatus(String taskId) {
@@ -126,7 +294,7 @@
                .uri(baseUrl + "/api/v1/tasks/" + taskId)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> response.bodyToMono(TaskStatusResponse.class)
                .onStatus(HttpStatus::isError, response -> response.bodyToMono(TaskStatusResponse.class)
                        .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody.getDetail()))))
                .bodyToMono(TaskStatusResponse.class)
                .flatMap(result -> {
@@ -140,7 +308,12 @@
                                    return question;
                                }).collect(Collectors.toList());
                        questionService.updateBatchById(updateQuestions);
                        // 包装成响应式操作
                        return Mono.fromCallable(() -> {
                            questionService.updateBatchById(updateQuestions);
                            return result;
                        });
                    }
                    return Mono.just(result);
                })
@@ -287,13 +460,20 @@
                keywordService.update(keywordUpdate);
                // 查询关键词ID
                LambdaQueryWrapper<Keyword> keywordQuery = new LambdaQueryWrapper<>();
                keywordQuery.eq(Keyword::getTask_id, result.getTask_id());
                Keyword keyword = keywordService.getOne(keywordQuery);
                LambdaQueryWrapper<KeywordTask> keywordTaskWrapper = new LambdaQueryWrapper<>();
                keywordTaskWrapper.eq(KeywordTask::getTask_id, result.getTask_id());
                KeywordTask keywordTask = keywordTaskService.getOne(keywordTaskWrapper);
//                LambdaQueryWrapper<Keyword> keywordQuery = new LambdaQueryWrapper<>();
//                keywordQuery.eq(Keyword::getTask_id, keywordTask.getTask_id());
                Keyword keyword = keywordService.getById(keywordTask.getKeyword_id());
                if (keyword == null) {
                    System.out.println("未找到关联的关键词,task_id: " + result.getTask_id());
                    return;
                    //报错
                    throw new Exception("未找到关联的关键词,task_id: " + result.getTask_id());
//                    return;
                }
                // 2. 批量查询所有问题
@@ -320,6 +500,7 @@
                                question.setResponse(questionResult.getResponse());
                                question.setExtracted_count(questionResult.getExtracted_count());
                                question.setError(questionResult.getError());
                                question.setKeyword_id(keyword.getKeyword_id());
                                // 解析时间戳
                                if (questionResult.getTimestamp() != null) {
@@ -333,7 +514,17 @@
                                questionsToUpdate.add(question);
                                //如果查询结果不为空查询num
                                Integer maxNumByKeywordId = referenceService.getMaxNumByKeywordId(keyword.getKeyword_id());
                               if (maxNumByKeywordId != null){
                                   maxNumByKeywordId++;
                               }else {
                                   maxNumByKeywordId = 1;
                               }
                                // 收集引用数据,处理空集合情况
                                Integer finalMaxNumByKeywordId = maxNumByKeywordId;
                                List<Reference> references =
                                        Optional.ofNullable(questionResult.getReferences())
                                                .orElse(Collections.emptyList())
@@ -344,7 +535,28 @@
                                                    reference.setTitle(ref.getTitle());
                                                    reference.setUrl(ref.getUrl());
                                                    reference.setDomain(ref.getDomain());
                                                    reference.setNum(finalMaxNumByKeywordId);
                                                    //域名和平台id映射
                                                    reference.setCreate_time(LocalDateTime.now());
                                                    Platform platform = platformService.getPlatformByDomain(reference.getDomain());
//                                                    if (platform == null) {
//                                                        throw new RuntimeException("未找到对应的平台: " + reference.getDomain());
//                                                    }
                                                    if (platform != null){
                                                        reference.setPlatform_id(platform.getPlatform_id());
                                                        Type type = typeService.getById(platform.getType_id());
//                                                    if (type == null) {
//                                                        throw new RuntimeException("未找到对应的类型: " + reference.getDomain());
//                                                    }
                                                        if (type != null){
                                                            reference.setType_id(type.getType_id());
                                                        }
                                                    }
                                                    // 根据 domain 查询类型
                                                    return reference;
                                                })
                                                .collect(Collectors.toList());
@@ -355,6 +567,7 @@
                                }
                            }
                        } catch (Exception e) {
                            log.error(e.getMessage(), e);
                            System.out.println("处理问题结果失败: " + e.getMessage());
                        }
                    }
@@ -366,22 +579,24 @@
                    questionService.updateBatchById(questionsToUpdate);
                    System.out.println("成功批量更新 " + questionsToUpdate.size() + " 个问题");
                }
                referenceService.saveBatch(allReferences);
                // 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() + " 条引用数据");
                }
//                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) {
                System.out.println("更新问题和引用数据失败: " + e.getMessage());
                log.error("更新问题和引用数据失败: " ,e.getMessage(), e);
//                System.out.println("更新问题和引用数据失败: " + e.getMessage());
                throw new RuntimeException("更新问题和引用数据失败", e);
            }
        });
@@ -414,4 +629,17 @@
                .onErrorResume(e -> Mono.just(
                        new HealthResponse("unhealthy", null, "", e.getMessage())));
    }
    /**
     * 查询服务器资源
     */
    @GetMapping("/server/resource")
    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())));
    }
}
src/main/java/com/linghu/controller/KeywordController.java
@@ -421,6 +421,9 @@
    @PostMapping("/updateKeyword")
    @ApiOperation(value = "修改关键词")
    public ResponseResult<String> updateKeyword(@RequestBody Keyword keyword) {
        if(!"notSubmitted".equals(keyword.getStatus())){
            return ResponseResult.error("关键词已开始采集或采集完成不允许修改!");
        }
        keywordService.updateById(keyword);
        return ResponseResult.success("修改成功");
    }
@@ -431,6 +434,10 @@
    @DeleteMapping("/deleteKeyword")
    @ApiOperation(value = "删除关键词")
    public ResponseResult<String> deleteKeyword(@RequestParam("keywordId") Integer keywordId) {
        Keyword keyword = keywordService.getById(keywordId);
        if(!"notSubmitted".equals(keyword.getStatus())){
            return ResponseResult.error("关键词已开始采集或采集完成不允许删除!");
        }
        keywordService.removeById(keywordId);
        return ResponseResult.success("删除成功");
    }
src/main/java/com/linghu/controller/QuestionController.java
@@ -73,7 +73,7 @@
    @ApiOperation(value = "修改提问词")
    @Transactional
    public ResponseResult<List<Question>> update(@RequestBody KeywordDto keywordDto) {
        if (keywordDto.getStatus() != "customPage"){
        if (!"notSubmitted".equals(keywordDto.getStatus() )){
            return ResponseResult.error("该关键词已提交或者已采集完成不允许修改提问词!");
        }
        LambdaQueryWrapper<Question> queryWrapper = new LambdaQueryWrapper<>();
src/main/java/com/linghu/mapper/KeywordTaskMapper.java
New file
@@ -0,0 +1,18 @@
package com.linghu.mapper;
import com.linghu.model.entity.KeywordTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author xy
* @description 针对表【keyword_task】的数据库操作Mapper
* @createDate 2025-07-10 12:59:32
* @Entity com.linghu.model.entity.KeywordTask
*/
public interface KeywordTaskMapper extends BaseMapper<KeywordTask> {
}
src/main/java/com/linghu/mapper/PlatformMapper.java
@@ -10,5 +10,6 @@
 * @Entity com.linghu.model.entity.Platfrom
 */
public interface PlatformMapper extends BaseMapper<Platform> {
    public Platform getPlatformByDomain(String domain);
}
src/main/java/com/linghu/mapper/ReferenceMapper.java
@@ -16,6 +16,7 @@
public interface ReferenceMapper extends BaseMapper<Reference> {
    List<FeedExportExcel> importTemplateList(@Param("keywordId") Integer keywordId, @Param("num")Integer num);
    Integer getMaxNumByKeywordId(@Param("keywordId") Integer keywordId);
}
src/main/java/com/linghu/model/dto/ServerResourceResponse.java
New file
@@ -0,0 +1,23 @@
package com.linghu.model.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ServerResourceResponse {
    private String cpu_usage_percent;
    private String memory_usage_percent;
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone = "GMT+8")
    private LocalDateTime timestamp;
    private String message;
    public ServerResourceResponse(){
        super();
    }
    public ServerResourceResponse(String message) {
        this.message = message;
    }
}
src/main/java/com/linghu/model/entity/KeywordTask.java
New file
@@ -0,0 +1,76 @@
package com.linghu.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
/**
 *
 * @TableName keyword_task
 */
@TableName(value ="keyword_task")
@Data
public class KeywordTask implements Serializable {
    /**
     *
     */
    @TableId(type = IdType.AUTO)
    private Integer id;
    /**
     *
     */
    private Integer keyword_id;
    /**
     *
     */
    private String task_id;
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        KeywordTask other = (KeywordTask) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getKeyword_id() == null ? other.getKeyword_id() == null : this.getKeyword_id().equals(other.getKeyword_id()))
            && (this.getTask_id() == null ? other.getTask_id() == null : this.getTask_id().equals(other.getTask_id()));
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getKeyword_id() == null) ? 0 : getKeyword_id().hashCode());
        result = prime * result + ((getTask_id() == null) ? 0 : getTask_id().hashCode());
        return result;
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", keyword_id=").append(keyword_id);
        sb.append(", task_id=").append(task_id);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}
src/main/java/com/linghu/model/entity/Reference.java
@@ -1,5 +1,6 @@
package com.linghu.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -19,7 +20,7 @@
    /**
     * 结果id
     */
    @TableId
    @TableId(type = IdType.AUTO)
    private Integer reference_id;
    /**
src/main/java/com/linghu/service/KeywordTaskService.java
New file
@@ -0,0 +1,13 @@
package com.linghu.service;
import com.linghu.model.entity.KeywordTask;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author xy
* @description 针对表【keyword_task】的数据库操作Service
* @createDate 2025-07-10 12:59:32
*/
public interface KeywordTaskService extends IService<KeywordTask> {
}
src/main/java/com/linghu/service/PlatformService.java
@@ -9,5 +9,5 @@
 * @createDate 2025-07-04 20:17:33
 */
public interface PlatformService extends IService<Platform> {
    public Platform getPlatformByDomain(String domain);
}
src/main/java/com/linghu/service/ReferenceService.java
@@ -9,5 +9,5 @@
* @createDate 2025-07-04 20:17:33
*/
public interface ReferenceService extends IService<Reference> {
    public Integer getMaxNumByKeywordId( Integer keywordId);
}
src/main/java/com/linghu/service/TypeService.java
@@ -12,4 +12,5 @@
    public Type getTypeByName(String typeName);
}
src/main/java/com/linghu/service/impl/KeywordTaskServiceImpl.java
New file
@@ -0,0 +1,22 @@
package com.linghu.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.linghu.model.entity.KeywordTask;
import com.linghu.service.KeywordTaskService;
import com.linghu.mapper.KeywordTaskMapper;
import org.springframework.stereotype.Service;
/**
* @author xy
* @description 针对表【keyword_task】的数据库操作Service实现
* @createDate 2025-07-10 12:59:32
*/
@Service
public class KeywordTaskServiceImpl extends ServiceImpl<KeywordTaskMapper, KeywordTask>
    implements KeywordTaskService{
}
src/main/java/com/linghu/service/impl/PlatformServiceImpl.java
@@ -4,7 +4,10 @@
import com.linghu.model.entity.Platform;
import com.linghu.service.PlatformService;
import com.linghu.mapper.PlatformMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
 * @author xy
@@ -14,5 +17,11 @@
@Service
public class PlatformServiceImpl extends ServiceImpl<PlatformMapper, Platform>
        implements PlatformService {
@Resource
private PlatformMapper platformMapper;
    @Override
    public Platform getPlatformByDomain(String domain) {
        return platformMapper.getPlatformByDomain(domain);
    }
}
src/main/java/com/linghu/service/impl/ReferenceServiceImpl.java
@@ -4,7 +4,10 @@
import com.linghu.model.entity.Reference;
import com.linghu.service.ReferenceService;
import com.linghu.mapper.ReferenceMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author xy
@@ -14,7 +17,14 @@
@Service
public class ReferenceServiceImpl extends ServiceImpl<ReferenceMapper, Reference>
    implements ReferenceService{
    @Resource
    private ReferenceMapper referenceMapper;
    @Override
    public Integer getMaxNumByKeywordId(Integer keywordId) {
        return referenceMapper.getMaxNumByKeywordId(keywordId);
    }
}
src/main/resources/mapper/KeywordTaskMapper.xml
New file
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.linghu.mapper.KeywordTaskMapper">
    <resultMap id="BaseResultMap" type="com.linghu.model.entity.KeywordTask">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="keyword_id" column="keyword_id" jdbcType="INTEGER"/>
            <result property="task_id" column="task_id" jdbcType="VARCHAR"/>
    </resultMap>
    <sql id="Base_Column_List">
        id,keyword_id,task_id
    </sql>
</mapper>
src/main/resources/mapper/PlatformMapper.xml
@@ -20,4 +20,11 @@
        domain,create_time,create_by,
        update_time,update_by,del_flag
    </sql>
    <select id="getPlatformByDomain"  resultType="com.linghu.model.entity.Platform">
            select
            <include refid="Base_Column_List"/>
            from platform
            where domain = #{domain}
    </select>
</mapper>
src/main/resources/mapper/ReferenceMapper.xml
@@ -38,4 +38,14 @@
            r.num = #{num}
    </select>
<!--    根据keyword查询,如果不为空返回num的最大值-->
    <select id="getMaxNumByKeywordId" resultType="java.lang.Integer">
        SELECT
            max(num)
        FROM
            `reference`
        WHERE
            keyword_id = #{keywordId}
    </select>
</mapper>