API文档搜索引擎设计文档


一、项目简介

1.项目背景

目前搜索引擎技术已经是非常成熟了,很多网站,应用等都有属于自己搜索引擎。但是哪一个的性能好,哪一个用户用着舒服,就说不定了。搜索引擎虽然只是做搜索的,但是在各个地方都有用到,是许多系统必不可少的功能。而且搜索时间短,匹配度高,满足用户心意的搜索引擎才是最重要的。鉴于此,我也想做一个搜索引擎,锻炼自己的业务能力,加深自己对这方面技术的掌握程度。

2.项目描述

本项目主要实现了在前端输入框内输入需要搜索的Java API文档的关键字,对后端发出请求,后端将处理后的结果返回给前端,按照一定的权重排序展示若干个搜索结果,每个搜索结果包含了标题,描述,展示URL,可点击标题跳转,查看文档的详细内容。

3.项目条件

  • 开发环境:IDEA、Tomcat 9、Maven、JDK1.8
  • 相关技术:正排索引、倒排索引、分词技术、过滤器、Servlet、Json、Ajax
  • 文档资源:我用的是jdk源码文件包解压之后的Java API文档,下载地址:点击这里

二、项目设计

1.数据库设计

(1)创建数据库“searcher”,在该数据库下创建正排索引表,包括文档id(docid)、标题(title)、url、文档内容(content),用于保存项目构建的正排索引;

CREATE TABLE `searcher`.`forward_indexes` (
    `docid` int(11) NOT NULL AUTO_INCREMENT,
    `title` varchar(100) NOT NULL,
    `url` varchar(200) NOT NULL,
    `content` longtext NOT NULL,
    PRIMARY KEY (`docid`)
) COMMENT='正排索引';

(2)创建倒排索引表,包括字段id、关键词(word)、文档id(docid)、单词权重(weight),用于保存构建的倒排索引;

CREATE TABLE `searcher`.`inverted_indexes` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `word` varchar(100) NOT NULL,
    `docid` int(11) NOT NULL,
    `weight` int(11) NOT NULL,
    PRIMARY KEY (`id`)
) COMMENT='倒排索引';

2.保存数据的实体类

(1)Document:
每一个 api 文档的 html 文件都对应一个该类,在该类中主要有四个属性字段,分别是:
docId:类似于数据库的主键可以对应单独一个文档
title:文档的文件名
url:Oracle 官网上的 api 文档下 html 的 url 地址
content:文档的正文部分

public class Document {
   
    @Getter @Setter
    private Integer docId;
    @Getter
    private final String title;
    @Getter
    private final String url;
    @Getter
    private final String content;

(2)InvertedRecord:
该类表示的是某个关键词在某个文档中的权值,在该类中主要有三个属性字段,分别是:
word:关键词
docId:该关键词对应的文档的id
weight:该关键词在该文档中的权值

public class InvertedRecord {
   
    private final String word;
    private final int docId;
    private final int weight;
}

(3)Result:
该类表示的是将搜索内容进行分词后,会得到多个关键词,每个关键词会对应多个文档,而其中不乏出现重复的文档,这时就需要对重复文档进行合并,用文档 ID 作为唯一标识,将 ID 相同的文档的权值根据关键字先后顺序不同进行加权操作,最终所有会匹配到的文档都是唯一的,根据权值对其进行排序后返回前端展示。在该类中主要有三个属性字段,分别是:
title:该文档的标题
url:该文档的 url
decs:该文档的描述

public class Result {
   
    private final String title;
    private final String url;
    private final String desc;
}

3.构建索引

索引构建程序原则上只执行一次即可,所以我们单独创建一个项目包indexer,存放构建索引的业务代码。下面是具体实现:
(1)遍历 api 文档存储的目录,对每个 html 文件进行读取解析,去掉多余的标签,并且将需要的信息提取出来并且封装到实体类 Document中,然后将所有提取到的信息持久化到本地的 文件中。具体实现:

public class DocumentBuilder {
   
    private static final String SUFFIX = ".html";

    private final IndexProperties properties;

    @Autowired
    public DocumentBuilder(IndexProperties properties) {
   
        this.properties = properties;
    }

    public Document build(File rootFile, File docFile) {
   
        String title = parseTitle(docFile);
        String url = parseUrl(rootFile, docFile);
        String content = parseContent(docFile);

        return new Document(title, url, content);
    }

    @SneakyThrows
    private String parseTitle(File file) {
   
        String name = file.getName();
        return name.substring(0, name.length() - SUFFIX.length());
    }

    @SneakyThrows
    private String parseUrl(File rootFile, File docFile) {
   
        String rootPath = rootFile.getCanonicalPath().replace('\\', '/');
        String docPath = docFile.getCanonicalPath().replace('\\', '/');
        String relativePath = docPath.substring(rootPath.length());

        if (properties.getUrlPrefix().endsWith("/")) {
   
            return properties.getUrlPrefix() + relativePath.substring(1);
        } else {
   
            return properties.getUrlPrefix() + relativePath;
        }
    }

    @SneakyThrows
    private String parseContent(File file) {
   
        StringBuilder contentBuilder = new StringBuilder();
        try (InputStream is = new FileInputStream(file)) {
   
            try (Scanner scanner = new Scanner(is, "ISO-8859-1")) {
   
                while (scanner.hasNextLine()) {
   
                    String line = scanner.nextLine();

                    contentBuilder.append(line).append(" ");
                }
            }
        }

        return contentBuilder.toString()
                .replaceAll("<script.*?>.*?</script>", " ") // 去掉 <script ...>...</script>
                .replaceAll("<[^>]*>", " ")                     // 去掉所有标签 <...>
                .replaceAll("&.*?;", " ")                        // 去掉 HTML 转义符
                .replaceAll("\\s+", " ")                        // 合并空白字符
                .trim();                                                          // 去掉首尾空白字符
    }
}

(2)首先加载本地文件内容,加载到正排索引的集合中,根据正排索引构建倒排索引(标题权重10,内容权重1),具体实现如下:
首先有一个 Map<String,Integer> 集合表示一个关键词对应多个 api 文档,然后遍历存储所有 DocInfo 类的 List,对于每一个 Doc都分别对标题和内容进行分词。
所以这里引入分词技术,分词技术使用的是一个开源的分词工具 Ansj,可以很高效的将句子进行分词处理。我们将分词之后的关键词加入 Map 集合,关键词作为键,Weight类作为值,用来保存每个关键词在对应的每个 api 文档中的权值。对于权值的计算,我们自定义的认为如果出现在标题中那么权值 乘10,如果出现在文章中,那么权值 +1,从而构建好倒排索引。具体代码实现如下:

 public class docunment{
   
    @Getter @Setter
    private Integer docId;
    @Getter
    private final String title;
    @Getter
    private final String url
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值