【C++项目】boost搜索引擎


一、项目的相关背景

1.1 boost基本介绍

boost官网

Boost库是为C++语言标准库提供扩展的一些C++程序库的总称。

  • Boost库由Boost社区组织开发、维护。其目的是为C++程序员提供免费、同行审查的、可移植的程序库。Boost库可以与C++标准库完美共同工作,并且为其提供扩展功能。Boost库使用Boost License来授权使用,根据该协议,商业的非商业的使用都是允许并鼓励的。
  • Boost社区建立的初衷之一就是为C++的标准化工作提供可供参考的实现,Boost社区的发起人Dawes本人就是C++标准委员会的成员之一。在Boost库的开发中,Boost社区也在这个方向上取得了丰硕的成果。在送审的C++标准库TR1中,有十个Boost库成为标准库的候选方案。在更新的TR2中,有更多的Boost库被加入到其中。从某种意义上来讲,Boost库成为具有实践意义的准标准库。
  • 大部分boost库功能的使用只需包括相应头文件即可,少数(如正则表达式库,文件系统库等)需要链接库。里面有许多具有工业强度的库,如graph库。
  • 很多Boost中的库功能堪称对语言功能的扩展,其构造用尽精巧的手法,不要贸然的花费时间研读。Boost另外一面,比如Graph这样的库则是具有工业强度,结构良好,非常值得研读的精品代码,并且也可以放心的在产品代码中多多利用

1.2 为什么要自主实现boost搜索引擎

  • 百度、搜狗、360搜索、头条新闻客户端 - 我们自己实现是不可能的!(全网搜索)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • boost的官网是没有站内搜索的,需要我们自己做一个
    在这里插入图片描述
    站内搜索:搜索的数据更垂直,数据量其实更小

二、搜索引擎的相关宏观原理和项目演示

在这里插入图片描述
用户输入:关键字 -> 倒排索引中查找 -> 提取出文档ID -> 根据正排索引 -> 找到文档的内容 ->title+conent(desc)+url 文档结果进行摘要->构建响应结果

2.1 项目演示:

在这里插入图片描述

三、搜索引擎技术栈和项目环境

  • 技术栈: C/C++ C++11, STL, 准标准库Boost,Jsoncpp,cppjieba,cpp-httplib , 选学: html5,css,js、jQuery、Ajax
  • 项目环境: Centos 7云服务器,vim/gcc(g++)/Makefile , vs2019 or vs code

四、正排索引 vs 倒排索引 - 搜索引擎具体原理

  • 文档1: 雷军买了四斤小米
  • 文档2: 雷军发布了小米手机

正排索引:就是从文档ID找到文档内容(文档内的关键字)

文档ID文档内容
1雷军买了四斤小米
2雷军发布了小米手机

目标文档进行分词(目的:方便建立倒排索引和查找):

  • 文档1[雷军买了四斤小米 ]: 雷军/买/四斤/小米/四斤小米
  • 文档2[雷军发布了小米手机]:雷军/发布/小米/小米手机

停止词:了,的,吗,a,the,一般我们在分词的时候可以不考虑

关键字(具有唯一性)文档ID
雷军文档1,文档2
文档1
四斤文档1
小米文档1,文档2
四斤小米文档1
发布文档2
小米手机文档2

模拟一次查找的过程:
用户输入:小米 -> 倒排索引中查找 -> 提取出文档ID(1,2) -> 根据正排索引 -> 找到文档的内容 ->
title+conent(desc)+url 文档结果进行摘要->构建响应结果

五、编写数据去标签与数据清洗的模块 Parser

目前只需要boost_1_79_0/doc/html目录下的html文件,用它来进行建立索引

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "Util.hpp"

const std::string src_path = "data/input";
const std::string output = "data/raw_html/raw.txt";

typedef struct DocInfo
{
public:
    std::string title;   // 文档标题
    std::string content; // 文档内容
    std::string url;     // 网址
} DocInfo_t;

// const & 输入
// * 输出
// & 输入输出
bool EnumFile(const std::string &src_path, std::vector<std::string> *file_list);

bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results);

bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output);

int main()
{
    std::vector<std::string> files_list;
    // 第一步,递归式将每个HTML文件名带路径,保存在files_list当中;方便后期一个一个读取
    if (!EnumFile(src_path, &files_list))
    {
        std::cerr << "EnumFile error" << std::endl;
        return 1;
    }
    // 第二步,按照file_list读取每一个文件中的内容,并进行解析
    std::vector<DocInfo_t> results;
    if (!ParseHtml(files_list, &results))
    {
        std::cerr << "ParseHtml error" << std::endl;
        return 2;
    }
    // 第三步,把解析完成的各个文件内容,写入到output里面,按照\n作为每个文档的分隔符 \3作为分割doc里面的各个数据
    if (!SaveHtml(results, output))
    {
        std::cerr << "SaveHtml error" << std::endl;
        return 3;
    }
    return 0;
}

bool EnumFile(const std::string &src_path, std::vector<std::string> *files_list)
{
    namespace fs = boost::filesystem;
    fs::path root_path(src_path);
    // 判断当前路径是否存在
    if (!fs::exists(root_path))
    {
        std::cerr << src_path << " not exists" << std::endl;
        return false;
    }
    // 定义一个迭代器,来判断递归结束
    fs::recursive_directory_iterator end;
    for (fs::recursive_directory_iterator iter(root_path); iter != end; ++iter)
    {
        // 判断是否是普通文件,HTML是普通文件
        if (!fs::is_regular_file(*iter))
        {
            continue;
        }
        // 判断后缀是否是html
        if (iter->path().extension() != ".html")
        {
            continue;
        }
        // std::cout << "debug:" << iter->path().string() << std::endl;
        //  当前路径一定是合法的,一html为后缀的普通文件
        //将当前路径后缀为HTML的文件名保存在files_list,方便进行文本分析
        files_list->push_back(std::move(iter->path().string())); // move 减少拷贝
    }
    return true;
}

static bool ParseTitle(const std::string &file, std::string *title)
{
    size_t begin = file.find("<title>");
    if (begin == std::string::npos)
    {
        return false;
    }
    size_t end = file.find("</title>");
    if (end == std::string::npos)
    {
        return false;
    }

    begin += std::string("<title>").size(); // begin指向正文
    *title = file.substr(begin, end - begin);
    return true;
}

static bool ParseContent(const std::string &file, std::string *content)
{
    // 去标签,编写一个简单的状态机
    enum status
    {
        LABLE,
        CONTENT
    };

    enum status s = LABLE;
    for (char ch : file)
    {
        switch (s)
        {
        case LABLE:
            if (ch == '>')
            {
                s = CONTENT;
            }
            break;
        case CONTENT:
            if (ch == '<')
            {
                s = LABLE;
            }
            else
            {
                if (ch == '\n')
                {
                    ch = ' ';
                }
                *content += ch;
            }
            break;
        default:
            break;
        }
    }
    return true;
}

static bool ParseUrl(const std::string &file_path, std::string *url)
{
    std::string url_head = "https://www.boost.org/doc/libs/1_79_0/doc/html";
    std::string url_tail = file_path.substr(src_path.size());
    *url = url_head + url_tail;
    return true;
}

// for debug
static void ShowDoc(DocInfo_t &doc)
{
    std::cout << doc.title << std::endl;
    std::cout << doc.content << std::endl;
    std::cout << doc.url << std::endl;
}

bool ParseHtml(const std::vector<std::string> &files_list, std::vector<DocInfo_t> *results)
{
    for (const std::string &file : files_list)
    {
        // 1,读取文件内容
        std::string result; // 文件内容
        if (!ns_util::FileUtil::ReadFile(file, &result))
        {
            continue;
        }
        DocInfo_t doc;
        // 2.解析指定文件的title
        if (!ParseTitle(result, &doc.title))
        {
            continue;
        }
        // 解析指定文件的content
        if (!ParseContent(result, &doc.content))
        {
            continue;
        }
        // 解析指定文件的url
        if (!ParseUrl(file, &doc.url))
        {
            continue;
        }
        // debug doc
        // ShowDoc(doc);
        // break;
        // 提取完毕,当前文件的相关结果都保存在了doc里面
        results->push_back(doc); // 细节,会发生拷贝
    }
    return true;
}

bool SaveHtml(const std::vector<DocInfo_t> &results, const std::string &output)
{
#define SEP '\3'
    std::ofstream out(output, std::ios::out | std::ios::binary);
    if (!out.is_open())
    {
        std::cerr << "open " << output << " failed" << std::endl;
        return false;
    }
    // 把解析完成的各个文件内容,写入到output里面,按照\n作为每个文档的分隔符 \3作为分割doc里面的各个数据
    for (auto &it : results)
    {
        std::string out_string;
        out_string += it.title;
        out_string += SEP;
        out_string += it.content;
        out_string += SEP;
        out_string += it.url;
        out_string += '\n';
        out.write(out_string.c_str(), out_string.size());
    }
    out.close();
    return true;
}

六、编写建立索引的模块 Index

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include "Util.hpp"
#include <mutex>
#include "Log.hpp"

namespace ns_index
{
    //
    struct DocInfo
    {
        std::string _title;   // 文档标题
        std::string _content; // 文档内容
        std::string _url;     // 文档url
        uint64_t _doc_id;     // 文档id,方便构建倒排拉链
    };

    //倒排拉链
    struct InvertedElem
    {
        uint64_t _doc_id;
        std::string _word;
        int _weight;
    };
    typedef std::vector<InvertedElem> InvertedList;

    class Index
    {
    private:
        Index()
        {
        }
        Index(const Index &) = delete;
        Index &operator=(const Index &) = delete;

    public:
        static Index *GetInstance()
        {
            if (_instance == nullptr)
            {
                std::unique_lock<std::mutex> ulck(_mtx);
                if (_instance == nullptr)
                {
                    _instance = new Index;
                }
            }
            return _instance;
        }

        ~Index()
        {
        }

    public:
        DocInfo *GetForwardIndex(uint64_t doc_id)
        {
            if (doc_id >= _forward_index.size())
            {
                std::cerr << "doc_id out of range" << std::endl;
                return nullptr;
            }
            return &_forward_index[doc_id];
        }

        InvertedList *GetInvertedIndex(const std::string &word)
        {
            auto iter = _inverted_index.find(word);
            if (iter == _inverted_index.end())
            {
                std::cerr << word << "have no Inverted!" << std::endl;
                return nullptr;
            }
            return &(iter->second);
        }

        // parse.cc处理完的数据给我
        // /home/ts/procedure_life/program/boost_sercher/data/raw_html
        bool BuildIndex(const std::string &input)
        {
            std::ifstream in(input, std::ios::in | std::ios::binary);
            std::cout << "file name: " << input << std::endl;
            if (!in.is_open())
            {
                std::cerr << "open " << input << " failed!" << std::endl;
                return false;
            }
            std::string line;
            int count = 0;
            while (std::getline(in, line))
            {
                ++count;
                DocInfo *doc = BuildForwardIndex(line);
                // 构建正排
                if (nullptr == doc)
                {
                    std::cerr << "bulid " << line << " error" << std::endl;
                    continue;
                }
                // 构建倒排
                BuildInvertedIndex(*doc);
                if (count % 50 == 0)
                    // std::cout << "当前正在建立文档:" << count << std::endl;
                    LOG(NORMAL, "构建正排和倒排索引:" + std::to_string(count));
            }

            in.close();
            return true;
        }

    private:
        DocInfo *BuildForwardIndex(const std::string &line)
        {
            // 1.解析line,字符串切分
            std::vector<std::string> results;
            const std::string sep = "\3"; // 行内分隔符
            ns_util::StringUtil::Split(line, &results, sep);
            if (results.size() != 3)
            {
                return nullptr;
            }
            // 2.将字符串填充到DocInfo
            DocInfo doc;
            doc._title = results[0];
            doc._content = results[1];
            doc._url = results[2];
            doc._doc_id = _forward_index.size();
            // 3.插入到正排索引_forward_index中
            _forward_index.push_back(std::move(doc));
            return &_forward_index.back();
        }

        bool BuildInvertedIndex(const DocInfo &doc)
        {
            // title conten url id
            // word -> 倒排拉链

            // 1.对title和content进行jieba分词
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::CutString(doc._title, &title_words);
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CutString(doc._content, &content_words);

            // 2.统计词频
            struct word_cnt
            {
                int title_cnt;
                int content_cnt;

                word_cnt() : title_cnt(0), content_cnt(0) {}
            };

            std::unordered_map<std::string, word_cnt> word_map;
            // title Hello
            for (auto iter : title_words)
            {
                boost::to_lower(iter);
                word_map[iter].title_cnt++;
            }

            for (auto iter : content_words)
            {
                boost::to_lower(iter);
                word_map[iter].content_cnt++;
            }

            // 3.自定义相关性
#define X 10
#define Y 1
            for (auto &iter : word_map)
            {
                InvertedElem tmp;
                tmp._doc_id = doc._doc_id;
                tmp._word = iter.first;
                tmp._weight = iter.second.title_cnt * X + iter.second.content_cnt * Y;
                InvertedList &inver_list = _inverted_index[iter.first];
                inver_list.push_back(tmp);
            }
            return true;
        }

    private:
        // 正排索引用数组就可以
        std::vector<DocInfo> _forward_index;
        // 倒排索引是关键字和一组InverteLIst的对应  [关键字和倒排拉链的映射]
        std::unordered_map<std::string, InvertedList> _inverted_index;
        static Index *_instance;
        static std::mutex _mtx;
    };
    Index *Index::_instance = nullptr;
    std::mutex Index::_mtx;
}

七、编写搜索引擎模块 Searcher

#pragma once
#include "Index.hpp"
#include "Util.hpp"
#include <algorithm>
#include <jsoncpp/json/json.h>
#include <iterator>
#include "Log.hpp"

namespace ns_sercher
{
    struct InvertedElemPrint
    {
        uint64_t _doc_id = 0;
        int _weight = 0;
        std::vector<std::string> _words;
    };

    class Searcher
    {
    public:
        Searcher() {}
        ~Searcher() {}

    public:
        void InitSearcher(const std::string &input)
        {
            // 1. 获取或则创建index对象
            _index = ns_index::Index::GetInstance();
            // std::cout << "获取单例成功" << std::endl;
            LOG(NORMAL, "获取单例成功...");
            // 2. 根据index对象建立索引
            _index->BuildIndex(input);
            // std::cout << "建立正排和倒排成功" << std::endl;
            LOG(NORMAL, "建立正排和倒排索引成功...");

        }

        void Search(const std::string &query, std::string *json_string)
        {
            // 1.分词,对我们的query进行按照searcher的要求
            std::vector<std::string> words;
            ns_util::JiebaUtil::CutString(query, &words);
            // 2.触发,根据分词的各个词进行index查找
            //ns_index::InvertedList inverted_list_all;
            std::vector<InvertedElemPrint> inverted_list_all;
            std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;

            for (std::string word : words)
            {
                boost::to_lower(word);
                ns_index::InvertedList *inverted_List = _index->GetInvertedIndex(word);
                if (nullptr == inverted_List)
                {
                    continue;
                }
                //inverted_list_all.insert(inverted_list_all.begin(), (*inverted_List).begin(), (*inverted_List).end());
                for(const auto& elem : *inverted_List)
                {
                    InvertedElemPrint& item = tokens_map[elem._doc_id];
                    item._weight += elem._weight;
                    item._doc_id = elem._doc_id;
                    item._words.push_back(elem._word);
                }
            }
            
            for(const auto& item :  tokens_map)
            {
                inverted_list_all.push_back(std::move(item.second));
            }

            // 3.合并排序,汇总查询结果,按照相关性(weight)降序排列
            // std::sort(inverted_list_all.begin(), inverted_list_all.end(), 
            // [](const ns_index::InvertedElem& e1, const ns_index::InvertedElem& e2) {
            //     return e1._weight > e2._weight;
            // });
            std::sort(inverted_list_all.begin(), inverted_list_all.end(), 
            [](const InvertedElemPrint& e1, const InvertedElemPrint& e2){
                return e1._weight > e2._weight;
            });
            // 4.构建,根据查找结果,构建json串,
            Json::Value root;
            for(auto& iter : inverted_list_all)
            {
                ns_index::DocInfo* pdoc = _index->GetForwardIndex(iter._doc_id);
                if(nullptr == pdoc)
                {
                    continue;
                }
                Json::Value elem;
                elem["title"] = pdoc->_title;
                elem["content"] = GetDes(pdoc->_content, iter._words[0]); // 文档是去掉标签后的结果,但是不是我们想要的结果,我们想要的是一部分
                // elem["content"] = pdoc->_content; // 文档是去掉标签后的结果,但是不是我们想要的结果,我们想要的是一部分
                elem["url"] = pdoc->_url;
                //elem["id"] = (int)iter._doc_id;
                //elem["weight"] = iter._weight;
                root.append(elem); 
            }
            Json::FastWriter writer;
            *json_string = writer.write(root);
        }

        std::string GetDes(const std::string& html_content, const std::string& word)
        {
            // 从第一次出现word的位置开始向前找50个字节,向后找100个字节
            const size_t prev_step = 50;
            const size_t next_step = 100;
            // 找到在content中第一次出现word的位置
            auto iter = std::search(html_content.begin(),html_content.end(), word.begin(),word.end(), 
            [](int x, int y){ return std::tolower(x) == std::tolower(y);});
            if(iter == html_content.end())
            {
                return "None1";
            }
            size_t pos = std::distance(html_content.begin(), iter);
            //错误查找
            // size_t pos = html_content.find(word);
            // if(pos == std::string::npos)
            // {
            //     return "None word";
            // }

            size_t start = 0;
            size_t end = html_content.size() - 1;
            if(start + prev_step < pos)
            {
                start = pos - prev_step;
            }
            if(pos + next_step < end)
            {
                end = pos + next_step;
            }
            if(start > end)
            {
                return "None2";
            }
            // 获取start-end之间的字符串
            return html_content.substr(start, end - start);
        }

    private:
        ns_index::Index* _index; // 提供查找的索引
    };
}

八、编写http_server 模块

#include "./cpp-httplib/httplib.h"
#include "Sercher.hpp"
#include "Log.hpp"

const std::string root_path = "./wwwroot";
const std::string input = "data/raw_html/raw.txt";

int main()
{
    httplib::Server svr;
    ns_sercher::Searcher searcher;
    searcher.InitSearcher(input);
    svr.set_base_dir(root_path.c_str());
    svr.Get("/s", [&searcher](const httplib::Request& req, httplib::Response& res){
        if(!req.has_param("word"))
        {
            res.set_content("必须要有搜索关键系!", "text/plain; charset=utf-8");
            return;
        }
        LOG(NORMAL, "搜索关键词成功...");
        std::string word = req.get_param_value("word");
        std::string json_string;
        searcher.Search(word, &json_string);
        res.set_content(json_string, "application/json; charset=utf-8");
        //res.set_content("Hello World!", "text/plain; charset=utf-8");
    });
    LOG(NORMAL, "服务器启动成功...");
    svr.listen("0.0.0.0", 8080);
    return 0;
}

九、编写前端模块

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <title>boost 搜索引擎</title>
    <style>
        /* 去掉网页中的所有的默认内外边距,html的盒子模型 */
        * {
            /* 设置外边距 */
            margin: 0;
            /* 设置内边距 */
            padding: 0;
        }
        /* 将我们的body内的内容100%和html的呈现吻合 */
        html,
        body {
            height: 100%;
        }
        /* 类选择器.container */
        .container {
            /* 设置div的宽度 */
            width: 800px;
            /* 通过设置外边距达到居中对齐的目的 */
            margin: 0px auto;
            /* 设置外边距的上边距,保持元素和网页的上部距离 */
            margin-top: 15px;
        }
        /* 复合选择器,选中container 下的 search */
        .container .search {
            /* 宽度与父标签保持一致 */
            width: 100%;
            /* 高度设置为52px */
            height: 52px;
        }
        /* 先选中input标签, 直接设置标签的属性,先要选中, input:标签选择器*/
        /* input在进行高度设置的时候,没有考虑边框的问题 */
        .container .search input {
            /* 设置left浮动 */
            float: left;
            width: 600px;
            height: 50px;
            /* 设置边框属性:边框的宽度,样式,颜色 */
            border: 1px solid black;
            /* 去掉input输入框的有边框 */
            border-right: none;
            /* 设置内边距,默认文字不要和左侧边框紧挨着 */
            padding-left: 10px;
            /* 设置input内部的字体的颜色和样式 */
            color: #CCC;
            font-size: 14px;
        }
        /* 先选中button标签, 直接设置标签的属性,先要选中, button:标签选择器*/
        .container .search button {
            /* 设置left浮动 */
            float: left;
            width: 150px;
            height: 52px;
            /* 设置button的背景颜色,#4e6ef2 */
            background-color: #4e6ef2;
            /* 设置button中的字体颜色 */
            color: #FFF;
            /* 设置字体的大小 */
            font-size: 19px;
            font-family:Georgia, 'Times New Roman', Times, serif;
        }
        .container .result {
            width: 100%;
        }
        .container .result .item {
            margin-top: 15px;
        }

        .container .result .item a {
            /* 设置为块级元素,单独站一行 */
            display: block;
            /* a标签的下划线去掉 */
            text-decoration: none;
            /* 设置a标签中的文字的字体大小 */
            font-size: 20px;
            /* 设置字体的颜色 */
            color: #4e6ef2;
        }
        .container .result .item a:hover {
            text-decoration: underline;
        }
        .container .result .item p {
            margin-top: 5px;
            font-size: 16px;
            font-family:'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
        }

        .container .result .item i{
            /* 设置为块级元素,单独站一行 */
            display: block;
            /* 取消斜体风格 */
            font-style: normal;
            color: green;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="search">
            <input type="text" value="请输入搜索关键字">
            <button onclick="Search()">搜索一下</button>
        </div>
        <div class="result">
            <!-- 动态生成网页内容 -->
            <!-- <div class="item">
                <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item">
                <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item">
                <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item">
                <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div>
            <div class="item">
                <a href="#">这是标题</a>
                <p>这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要这是摘要</p>
                <i>https://search.gitee.com/?skin=rec&type=repository&q=cpp-httplib</i>
            </div> -->
        </div>
    </div>
    <script>
        function Search(){
            // 是浏览器的一个弹出框
            // alert("hello js!");
            // 1. 提取数据, $可以理解成就是JQuery的别称
            let query = $(".container .search input").val();
            console.log("query = " + query); //console是浏览器的对话框,可以用来进行查看js数据

            //2. 发起http请求,ajax: 属于一个和后端进行数据交互的函数,JQuery中的
            $.ajax({
                type: "GET",
                url: "/s?word=" + query,
                success: function(data){
                    console.log(data);
                    BuildHtml(data);
                }
            });
        }

        function BuildHtml(data){
            // 获取html中的result标签
            let result_lable = $(".container .result");
            // 清空历史搜索结果
            result_lable.empty();

            for( let elem of data){
                // console.log(elem.title);
                // console.log(elem.url);
                let a_lable = $("<a>", {
                    text: elem.title,
                    href: elem.url,
                    // 跳转到新的页面
                    target: "_blank"
                });
                let p_lable = $("<p>", {
                    text: elem.desc
                });
                let i_lable = $("<i>", {
                    text: elem.url
                });
                let div_lable = $("<div>", {
                    class: "item"
                });
                a_lable.appendTo(div_lable);
                p_lable.appendTo(div_lable);
                i_lable.appendTo(div_lable);
                div_lable.appendTo(result_lable);
            }
        }
    </script>
</body>
</html>

十、添加日志

#pragma once

#include <iostream>
#include <string>
#include <ctime>

#define NORMAL 1
#define WARNING 2
#define DEBUG 3
#define FATAL 4

#define LOG(LEVEL, MESSAGE) Log(#LEVEL, MESSAGE, __FILE__, __LINE__)

void Log(const std::string& level, const std::string& message, const std::string& file, int line)
{
    std::cout << "[level:" << level << "]" << "[time:" << time(nullptr) << "]" << "[message:" << message << "]"
    << "[file:" << file << "]" << "[line:" << line << "]" << std::endl;
}

10.1 部署服务到 linux 上

nohup ./http_server > log/log.txt 2>&1 &

十一、结项总结

项目扩展方向

  1. 建立整站搜索
  2. 设计一个在线更新的方案,信号,爬虫,完成整个服务器的设计
  3. 不使用组件,而是自己设计一下对应的各种方案(有时间,有精力)
  4. 在我们的搜索引擎中,添加竞价排名(强烈推荐)
  5. 热次统计,智能显示搜索关键词(字典树,优先级队列)(比较推荐)
  6. 设置登陆注册,引入对mysql的使用(比较推荐的)
  • 12
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小唐学渣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值