|
| 1 | +package com.yupi.codertestbackend.service.ai; |
| 2 | + |
| 3 | +import dev.langchain4j.agent.tool.Tool; |
| 4 | +import lombok.extern.slf4j.Slf4j; |
| 5 | +import org.jsoup.Jsoup; |
| 6 | +import org.jsoup.nodes.Document; |
| 7 | +import org.jsoup.nodes.Element; |
| 8 | +import org.jsoup.select.Elements; |
| 9 | +import org.springframework.stereotype.Component; |
| 10 | + |
| 11 | +import java.net.URLEncoder; |
| 12 | +import java.nio.charset.StandardCharsets; |
| 13 | +import java.util.ArrayList; |
| 14 | +import java.util.List; |
| 15 | + |
| 16 | +/** |
| 17 | + * 面试题搜索工具 |
| 18 | + * 从面试鸭网站搜索相关面试题 |
| 19 | + */ |
| 20 | +@Component |
| 21 | +@Slf4j |
| 22 | +public class InterviewQuestionSearchTool { |
| 23 | + |
| 24 | + private static final String SEARCH_URL_TEMPLATE = "https://www.mianshiya.com/search/all?searchText=%s"; |
| 25 | + |
| 26 | + /** |
| 27 | + * 搜索面试题 |
| 28 | + * |
| 29 | + * @param query 搜索关键词,应该是技术相关的关键词,比如"Java"、"Spring Boot"、"Vue"等 |
| 30 | + * @return 搜索到的面试题列表,格式为:题目标题 - 题目链接 |
| 31 | + */ |
| 32 | + @Tool("根据技术关键词搜索相关的面试题,帮助用户获得针对性的面试题推荐") |
| 33 | + public String searchInterviewQuestions(String query) { |
| 34 | + try { |
| 35 | + log.info("开始搜索面试题,关键词:{}", query); |
| 36 | + |
| 37 | + // URL编码搜索关键词 |
| 38 | + String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8); |
| 39 | + String searchUrl = String.format(SEARCH_URL_TEMPLATE, encodedQuery); |
| 40 | + |
| 41 | + // 使用Jsoup获取网页内容 |
| 42 | + Document doc = Jsoup.connect(searchUrl) |
| 43 | + .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") |
| 44 | + .timeout(10000) |
| 45 | + .get(); |
| 46 | + |
| 47 | + // 查找题目列表容器 |
| 48 | + Element questionTableView = doc.selectFirst(".question-table-view"); |
| 49 | + if (questionTableView == null) { |
| 50 | + log.warn("未找到题目列表容器,关键词:{}", query); |
| 51 | + return "未找到相关面试题,建议尝试其他关键词。"; |
| 52 | + } |
| 53 | + |
| 54 | + // 提取题目信息 |
| 55 | + Elements questionItems = questionTableView.select(".ant-table-row"); |
| 56 | + List<String> questions = new ArrayList<>(); |
| 57 | + |
| 58 | + int count = 0; |
| 59 | + for (Element row : questionItems) { |
| 60 | + if (count >= 3) { // 限制返回数量 |
| 61 | + break; |
| 62 | + } |
| 63 | + |
| 64 | + try { |
| 65 | + // 获取所有列 |
| 66 | + Elements cells = row.select(".ant-table-cell"); |
| 67 | + |
| 68 | + // 确保列数足够(至少4列:空列、标题列、难度列、标签列) |
| 69 | + if (cells.size() < 4) { |
| 70 | + continue; |
| 71 | + } |
| 72 | + |
| 73 | + // 第二列:题目标题和链接(索引为1) |
| 74 | + Element titleCell = cells.get(1); |
| 75 | + String title = titleCell.text().trim(); |
| 76 | + if (title.isEmpty()) { |
| 77 | + continue; |
| 78 | + } |
| 79 | + |
| 80 | + // 提取题目链接 |
| 81 | + Element linkElement = titleCell.selectFirst("a"); |
| 82 | + String link = ""; |
| 83 | + if (linkElement != null) { |
| 84 | + String href = linkElement.attr("href"); |
| 85 | + if (!href.isEmpty()) { |
| 86 | + link = href.startsWith("http") ? href : "https://www.mianshiya.com" + href; |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + // 第三列:题目难度(索引为2) |
| 91 | + String difficulty = cells.get(2).text().trim(); |
| 92 | + |
| 93 | + // 第四列:题目标签(索引为3) |
| 94 | + String tags = cells.get(3).text().trim(); |
| 95 | + |
| 96 | + // 构建题目信息 |
| 97 | + StringBuilder questionInfo = new StringBuilder(); |
| 98 | + questionInfo.append(title); |
| 99 | + |
| 100 | + // 添加难度信息 |
| 101 | + if (!difficulty.isEmpty()) { |
| 102 | + questionInfo.append(" [难度: ").append(difficulty).append("]"); |
| 103 | + } |
| 104 | + |
| 105 | + // 添加标签信息 |
| 106 | + if (!tags.isEmpty()) { |
| 107 | + questionInfo.append(" [标签: ").append(tags).append("]"); |
| 108 | + } |
| 109 | + |
| 110 | + // 添加链接 |
| 111 | + if (!link.isEmpty()) { |
| 112 | + questionInfo.append(" - ").append(link); |
| 113 | + } |
| 114 | + |
| 115 | + questions.add(questionInfo.toString()); |
| 116 | + count++; |
| 117 | + |
| 118 | + } catch (Exception e) { |
| 119 | + log.warn("解析题目信息时出错:{}", e.getMessage()); |
| 120 | + continue; |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + if (questions.isEmpty()) { |
| 125 | + log.warn("未解析到有效的面试题,关键词:{}", query); |
| 126 | + return "未找到相关面试题,建议尝试其他关键词。"; |
| 127 | + } |
| 128 | + |
| 129 | + log.info("成功搜索到{}道面试题,关键词:{}", questions.size(), query); |
| 130 | + |
| 131 | + // 构建返回结果 |
| 132 | + StringBuilder result = new StringBuilder(); |
| 133 | + result.append("找到以下相关面试题:\n\n"); |
| 134 | + for (int i = 0; i < questions.size(); i++) { |
| 135 | + result.append(i + 1).append(". ").append(questions.get(i)).append("\n"); |
| 136 | + } |
| 137 | + |
| 138 | + return result.toString(); |
| 139 | + |
| 140 | + } catch (Exception e) { |
| 141 | + log.error("搜索面试题时发生错误,关键词:{},错误:{}", query, e.getMessage()); |
| 142 | + return "搜索面试题时发生错误,请稍后再试。"; |
| 143 | + } |
| 144 | + } |
| 145 | +} |
0 commit comments