当前位置: 首页 > news >正文

【项目】Boost 搜索引擎

文章目录

    • 1.背景
    • 2.宏观原理
    • 3.相关技术与开发环境
    • 4. 实现原理
      • 1.下载
      • 2.加载与解析文件
        • 2.1获取指定目录下的所有网页文件
        • 2.2. 获取网页文件中的关键信息
        • 2.3. 对读取文件进行保存
      • 3.索引
        • 3.1正排与倒排
        • 3.2获取正排和倒排索引
        • 3.3建立索引
          • 3.3.1正排索引
          • 3.3.2倒排索引
      • 4.搜索
        • 4.1 初始化
        • 4.2 搜索功能
      • 5. http_server
        • 5.1 升级gcc
        • 5.2 安装cpp-httplib
        • 5.3编写http_server.cc
      • 6. 编写前端模块
        • 6.1 HTML
        • 6.2 CSS
        • 6.4 JavaScript
  • 总结 与 拓展
  • 尾序

  • 效果图:
    在这里插入图片描述
  • 项目源码:链接

1.背景

  • 百度,360,谷歌等搜索引擎的实现门槛过高,几乎不可能由个人进行实现。
  • 站内搜索,其中的资源相对比较垂直,适合个人进行实现,并借此达到管中窥豹的效果。
  • Boost库是没有站内搜索的,实现更有意义。

搜索相关内容:

  • 共性:都含有标题,摘要,网站的网址。

说明:有些网站还有图片,广告等信息,但由于我们做的是Boost库的搜索,这些信息知道即可。

2.宏观原理

在这里插入图片描述

3.相关技术与开发环境

  • 技术栈:C/C++,C++11,STL,JsonCpp,Boost,Cpp-Httplib,Jquery,正排与倒排索引。
  • 开发环境: Centos,云服务器,vim/g++/gcc/Makefile,VsDode/VS2019。

4. 实现原理

  • 说明: 为了方便理解代码,博主将项目的目录进行贴出,因为下面我们include包含使用的库,使用的是绝对路径。
    在这里插入图片描述

  • 项目的路径为:/home/shun_hua/practical-projects/Boost_Search

  • 原因:相对目录是基于进程的工作目录,进程的工作目录在项目的路径下,而我们写的代码是在项目的子目录的路径下,因此要么把所有的文件都放在项目的路径下,要么就用绝对路径,虽然长,但是可以把文件整理分类,看着比较简洁。

1.下载

  • 进入boost官网:点击进入
  • 第一步:

在这里插入图片描述

  • 第二步:

在这里插入图片描述

  • 第三步:下载完成之后,在对应的Linux操作系统系统上,输入上传文件,进行上传。
[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ Boost_Search]$rz -E 
[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ Boost_Search]$ ls
boost_1_84_0.tar.gz
[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ Boost_Search]$ tar xzf boost* 
[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ Boost_Search]$ ls
boost_1_84_0  boost_1_84_0.tar.gz

说明:

  • rz - E是上传较大文件时进行使用。
  • tar -xzf 是对文件进行解压缩。
  • boost_1_84_0是解压缩之后的文件。
  • 安装rz : sudo yum install -y rz
  • 第四步:找到解压缩文件中的html提取出来用于作为搜索引擎的数据。
  • 路径:/boost_1_84_0/doc/html
[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ Boost_Search]$ ls
boost_1_84_0
[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ Boost_Search]$ cp -r ./boost_1_84_0/doc/html ./input 
[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ Boost_Search]$ ls
boost_1_84_0  input

说明:cp -r [指定路径的目录] [目标路径 + 重命名]

[shun_hua@iZ2zebfc5jur5cm0zu2n3gZ input]$ ls -R | grep -E ".html" | wc -l
8586

说明:

  • ls -R 显示所有文件,目录递归显示所有文件。
  • grep -E [字符串] [文件], 显示出带有指定字符的信息。
  • wc -l [文件], 显示出文件的行数。

2.加载与解析文件

基本框架:

在这里插入图片描述

2.1获取指定目录下的所有网页文件
  • 引入文件库:Boost文件库,具体使用里面的filesystem里面的接口。

  • 接口:文档

命名空间:boost::filesystem

类:class path

string string(const codecvt_type& cvt=codecvt()) const; ————将path对象转换为string类。
path  extension() const; ——文件的后缀。

迭代器:class recursive_directory_iterator

recursive_directory_iterator() noexcept;//默认构造,其实执向的是end
explicit recursive_directory_iterator(const path& p, directory_options opts\= directory_options::none);//用根目录初始化,即整个多叉树根。

接口:

bool is_regular_file(const path& p); //判断是否是普通的文件,目录不是普通文件。
bool exists(const path& p); //判断是否文件的目录是否存在。
  • 实现代码:
bool GetPathFiles(const string& path,vector<string>* files)
{//首先将path转化为boost库的path便于处理//防止命名污染的情况namespace fs = boost::filesystem;fs::path root_path(path);if(!exists(root_path)){lg(CRIT,"path is not exist!");return false;}fs::recursive_directory_iterator end;for(fs::recursive_directory_iterator cur(root_path); cur != end; cur++){//如果不是普通文件,例如目录。if(!is_regular_file(cur->path())){continue;}string suffix = cur->path().extension().string();if(suffix != ".html"){continue;}string path = cur->path().string();// cout << path << endl;files->push_back(cur->path().string());}return true;
}
2.2. 获取网页文件中的关键信息

核心:

  • 标题——title
  • 内容——content
  • 网址——url

网页的大致内容:

在这里插入图片描述

说明:

  • …. 之间的为标题,即网页窗口显示的内容。
  • 除去<…> 之间的内容其余的都为内容。
  • 网址,根据网页的基本内容结合本地的相对目录,获取到具体boost库的网址。

解析文件的基本流程:

  1. 读取网页文件的内容
//使用命名空间,避免命名污染
namespace util
{namespace filesystem{bool ReadFiles(const std::string& file_path,std::string *text){std::ifstream fin(file_path);if(!fin.is_open()){lg(WARNNING,"open file fail!");return false;}string line;while(getline(fin,line)){*text += line;                }return true;}}
}
  1. 获取标题
bool PraseTile(const string& text,string *title)
{string prefix = "<title>";string suffix = "</title>";auto begin = text.find(prefix);auto end = text.find(suffix);if(begin == string::npos || end == string::npos) return false;begin += prefix.size();*title = text.substr(begin,end - begin);return true;
}
  1. 获取内容
    • 此处采用的是状态机的实现方式,即除了<….> 都是内容。
bool PraseContent(const string& text,string *content)
{State s = LABLE;for(char ch : text){switch (s){case LABLE:if(ch == '>')s = CONTENT;break;case CONTENT:       if(ch == '<'){s = LABLE;break;}//把换行符去掉。*content += ch == '\n' ? ' ' : ch;}}return true;
}
  1. 解析网站的url

    实现原理:

    • 我们用的是/doc/html下的所有*.html文件。
    • 因此网站的前缀为:https://www.boost.org/doc/libs/1_84_0/doc/html/
    • 根据对应的*.html文件,再去掉本地目录的前缀:
      /home/shun_hua/practical-projects/Boost_Search,得到资源的后缀。
    • 前缀与后缀拼接出来的结果,即为搜索网站的Url。

    实现代码:

   bool ParseUrl(const string& path,string* url){string url_head = "https://www.boost.org/doc/libs/1_84_0/doc/html";// /doc/html文件中存放的是帮助文档的html,用于搜索引擎的查找。// 其它目录下的html先暂时不做考虑。auto pos = path.find(src_path);if(pos == string::npos) return false;pos += src_path.size();string url_tail = path.substr(pos);*url = url_head + url_tail;return true;}

因此,我们的解析网页的实现代码为:

bool PraseHtmls(const vector<string>& files,vector<HtmlInfor>* contents)
{//先读取文件的内容int cnt = 5;for(auto file_path : files){//1.打开文件读取对应的内容。string text;namespace u_fs = util::filesystem;if(!u_fs::ReadFiles(file_path,&text)){lg(WARNNING,"read files content fail!");continue;}//2.解析网站的标题HtmlInfor htm;if(!PraseTile(text,&htm.title)){lg(WARNNING,"Prase html title content fail!");continue;}//3.解析网站的内容if(!PraseContent(text,&htm.content)){lg(WARNNING,"parse content fail!");continue;}if(!ParseUrl(file_path,&htm.url)){lg(WARNNING,"parse url fail!");continue;}//for debug:// cout << htm.title << endl;// cout << htm.url << endl;// cout << htm.content << endl;// break;contents->push_back(htm);}return true;
}
2.3. 对读取文件进行保存
  • 保存信息的结构体为:
struct HtmlInfor
{string title;string content;string url;
};
  • 保存方式:

    • 在进行读取时,我们希望一次能读取一个文件的内容。因此文件与文件之间用 ‘\n’ 进行划分。
    • 在分析一个文件的内容时,我们需要获取到标题,内容,url信息,因此这之间需要用一个控制字符 ‘\3’划分即可。
  • 实现代码:

bool SaveHtmls(const vector<HtmlInfor>& contents,const string& path)
{std::fstream out(path,ios_base::binary | ios_base::out);if(!out.is_open()){lg(ERRO,"open file %s fail!",path.c_str());return false;}int rate;int cur = 0;for(const HtmlInfor& infor : contents){rate = 100 * (++cur) / contents.size();processbar(rate);string mes = infor.title + "\3" + infor.url + \"\3" + infor.content + '\n';out.write(mes.c_str(),mes.size());        }return true;
}

此处用之前学到的进度条程序,显示保存文件的进度,实现可视化。

进度条代码:

#pragma once
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define MAX 102
#define STYLE  '#'
#define RIGHT  '>'
typedef void (*call_back)(int);
void processbar(int rate);
void init();
char buf[MAX];
char signal[5] = {'-','\\','|','/','\0'};
void init()
{memset(buf,'\0',sizeof(buf));
}
void processbar(int rate)
{if(rate > 100 || rate < 0){return;}int len  = strlen(signal);if(rate == 100){printf("[%-100s][%-3d%%] sourse load done!\r\n",buf,rate);usleep(1000);buf[rate++] = STYLE;return;}printf("[%-100s][%-3d%%][%c]\r",buf,rate,signal[rate%len]);usleep(10);//刷新缓冲区fflush(stdout);//更新存储的进度条buf[rate++] = STYLE;if(rate < 100){buf[rate] = RIGHT;}
}

3.索引

3.1正排与倒排

采用技术:正排索引与倒排索引。

举例:

  • 假设针对如下三个标题建立正排和倒排索引。

    • 乔布斯买苹果手机。

    • 乔布斯吃苹果。

    • 乔布斯看手机。

  1. 建立正排索引的文档
文档ID文档内容
1乔布斯买苹果手机。
2乔布斯吃苹果。
3乔布斯看手机。
  1. 提取关键字
  • 乔布斯 苹果手机 苹果 手机 买 吃 看
  1. 根据关键字建立倒排索引
关键字文档ID
乔布斯1,2,3
苹果手机1
苹果2
手机1,3
2
3
1

总结一下:

  • 正排索引即对读取的文件内容进行编号。
  • 倒排索引是针对关键字找到文件的编号,从而找到文件的内容。

基本框架:

typedef std::vector<int> Interved_List;
//正排索引
struct DocInfor
{DocInfor(){}DocInfor(int Idx,string Title,string Url,string Content):idx(Idx),title(Title),url(Url),content(Content){};int idx;//文档IDstd::string title;std::string url; std::string content;
};
//索引类,实现正排和倒排索引
class Index
{//Document 
public:Index(){}~Index(){}DocInfor* GetDocInfor(int doc_idx);Interved_List* GetInterList(const string& keyword);//从文件中读取内容,建立正排和倒排索引bool BuildForwardIndex(const string& path);
private://这里的vector的数组的下标天然就可以当做DocInfor的idxstd::vector<DocInfor> forward_index;//正排索引std::unordered_map<std::string,Interved_List> interved_index;//倒排索引
};
3.2获取正排和倒排索引
  • 正排索引根据文档ID进行获取
  • 倒排索引根据关键词,获取对应的倒排拉链。

实现代码:

DocInfor* GetDocInfor(int doc_idx)
{if(doc_idx > forward_index.size()){lg(ERRO,"doc_idx:%d,out of range.",doc_idx);return nullptr;}return &forward_index[doc_idx];
}
Interved_List* GetInterList(const string& str)
{auto it = interved_index.find(str);if(it == interved_index.end()){lg(ERRO,"keyword is not exist.");return nullptr;}return &it->second;
}
3.3建立索引
3.3.1正排索引
  • 说明: 我们可以采用string 容器的 find 与 substr接口实现文档内容的截取,但是基于学习Boost文件库的目的,这里直接使用现成的截取文档的接口。

接口:

// In header: <boost/algorithm/string/string.hpp>//函数声明
template<typename SequenceSequenceT, typename RangeT, typename PredicateT> SequenceSequenceT & split(SequenceSequenceT & Result, RangeT & Input, PredicateT Pred, token_compress_mode_type eCompress = token_compress_off);/*
参数1:vector<type>类型的,用于存放切割后的内容。2:切割的内容。3:分割符。4:切割的模式,一般设置为token_compress_on,意为将连续的分割符看成一个。
*///例:
#include<iostream>
#include<boost/algorithm/string.hpp>
#include<string>
#include<vector>
int main()
{std::vector<std::string> res;std::string text = "aaaaaaaa\3\3bbbbbbbbbbb\3cccccccccc";std::string split_str = "\3";boost::split(res,text,boost::is_any_of(split_str),boost::token_compress_on);for(auto &str : res){std::cout << str << std::endl;}return 0;
}
/*
output:aaaaaaaa bbbbbbbbbbbcccccccccc
*/

接口:

static DocInfor* GetForwardIndex(const string& split,const string &line,\vector<DocInfor>* forward_index)
{vector<string> tmp;boost::split(tmp,line,boost::is_any_of(split),boost::token_compress_on);if(tmp.size() != 3){lg(ERRO,"split fail:GetForwardIndex");return nullptr;}forward_index->push_back(DocInfor(forward_index-\>size(),move(tmp[0]),move(tmp[1]),move(tmp[2])));return &forward_index->back();
}//说明:此函数封装在命名空间util的struct String内
3.3.2倒排索引
  1. 建立倒排的索引的结构体对象。如:文档ID,关键词,相关系数。
struct InterElem
{//默认构造InterElem(){}//写了构造,编译器就不会自动生成默认构造函数。InterElem(int id,std::string key,int rate):idx(id),word(key),weight(rate){}int idx;//文档idstd::string word;//关键词int weight;//权重
};
  1. 对文档内容和标题进行分词。
  • 说明:倒排需要对内容进行分词,而分词的工作有现成的库,因此我们采用jieba库分词即可。
  • 安装jieba工具:

    • 网址链接:jieba分词

    • 使用git clone https://gitcode.com/yanyiwu/cppjieba.git克隆到本地。

    • 将库进行调整:使用cp命令将cppjieba/deps/limonp 拷贝到 cppjieba/include/jieba 目录下。

    • cppjieba/test/demo.cpp,拷贝到与cppjieba同级目录下进行测试。

    • 在与cppjieba同级目录下建立软连接:ln -s cppjieba/include/jieba jiebaln -s cppjieba/dict dict

测试代码:

#include "jieba/Jieba.hpp"
using namespace std;
std::string prefix = "/home/shun_hua/practical-projects/Boost_Search/Modules/Utils/";
std::string DICT_PATH = prefix + "dict/jieba.dict.utf8";
std::string HMM_PATH = prefix + "dict/hmm_model.utf8";
std::string USER_DICT_PATH = prefix + "dict/user.dict.utf8";
std::string IDF_PATH = prefix + "dict/idf.utf8";
std::string STOP_WORD_PATH = prefix + "dict/stop_words.utf8";
int main() 
{cppjieba::Jieba jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH);vector<string> words;vector<cppjieba::Word> jiebawords;string s;string result;s = "小明硕士毕业于中国科学院计算所,后在日本京都大学深造";cout << s << endl;jieba.CutForSearch(s, words);cout << "[demo] CutForSearch" << endl;cout << limonp::Join(words.begin(), words.end(), "/") << endl;
}

说明:我们的目的是针对关键词进行搜索,因此使用CutForSearch接口即可。

我们用类进行封装:

#include "jieba/Jieba.hpp"
std::string prefix = "/home/shun_hua/practical-projects/Boost_Search/Modules/Utils/";
std::string DICT_PATH = prefix + "dict/jieba.dict.utf8";
std::string HMM_PATH = prefix + "dict/hmm_model.utf8";
std::string USER_DICT_PATH = prefix + "dict/user.dict.utf8";
std::string IDF_PATH = prefix + "dict/idf.utf8";
std::string STOP_WORD_PATH = prefix + "dict/stop_words.utf8";
//这里的JieBa类是用于封装的,而cppjieba::Jieba是一个类型,请注意进行区分。
struct JieBa
{private:static cppjieba::Jieba jieba;public:static void CutString(const std::string& content,std::vector<std::string>& words){jieba.CutForSearch(content,words);}
};
cppjieba::Jieba JieBa::jieba(DICT_PATH,HMM_PATH,USER_DICT_PATH,IDF_PATH,STOP_WORD_PATH);
  • 补充:中文和英文的分词jieba都支持,博主已经测试过。
  1. 对关键词次数进行分析。对标题和内容都要进行分析。
struct Word_Cnt
{int title_cnt = 0;//标题中关键词的出现次数。int content_cnt = 0;//内容中关键词的出现次数。
};
  1. 计算相关系数,将获取到完整的InterElem元素,打散到倒排索引中。

说明:实际相关系数要考虑到多个维度,且要基于数据进行分析,这里我们不做那么复杂,使用标题与内容中关键词的出现次数进行分析即可。

  • 系数公式:N*word_cnt.title_cnt + M * word.content_cnt, 这里的N, M姑且就分别设置为10 与 1,即标题的相关性占比较大。
  • 细节:
  1. 这里我们在对网页去标签时,内容中也含有标签,因此标签中的关键字在内容中被重复计算了一次。

  2. jieba分词可能没有将我们所搜索的关键词在内容中分出来,因此可能会跟实际有一点点偏差,比实际的小一点。

实现代码:

void GenerateIntervedIndex(const DocInfor &doc)
{// 首先文档id是已经有的。// 剩余需要的是:// 1.分析出关键词。std::vector<std::string> title_words;std::vector<std::string> content_words;// 进行jieba分词.....util::JieBa::CutString(doc.title, title_words);util::JieBa::CutString(doc.content, content_words);std::unordered_map<std::string, Word_Cnt> kv;// 2.记录关键词的出现次数。// 注意:在实际搜索的过程中是忽略大小写的,因此的对标题和内容都忽略大小写。for (auto word : title_words){boost::to_lower(word);kv[word].title_cnt++;}for (auto word : content_words){boost::to_lower(word);kv[word].content_cnt++;}// 3.计算相关系数,打散到interverd_index中。for (auto &it : kv){auto &key_word = it.first;auto &cnt = it.second;interved_index[key_word].push_back({doc.idx, move(key_word),\cnt.content_cnt + 10 * cnt.title_cnt});}
}

说明:

  • 在实际的搜索过程中是忽略大小写的,因此这里我们采用了boost库中的to_lower,统一转换为小写。
  • 由于to_lower会对传进去的内容本身进行修改,因此使用范围for时,应使用拷贝,不可使用引用获取。
  • 我们并不需要对文档内容进行大小写转换,只需要将索引的关键词和搜索的关键词进行大小写转化,即可完成忽略大小写。

索引构建代码:

bool BuildIndex(const string &path)
{std::ifstream in(path, std::ios_base::in | std::ios_base::binary);if (!in.is_open()){lg(ERRO, "open file fail,path is %s.", path);return false;}string line;const int sum = 8586;int cnt = 0;// getline的默认分割符为'\n'string split_str = "\3";while (getline(in, line)){cnt++;processbar(cnt * 100 / sum );// 获取正排索引DocInfor *doc = GetForwardIndex(split_str,line);if (doc == nullptr){lg(WARNNING, "doc is not exist.");continue;}// 获取倒排索引GenerateIntervedIndex(*doc);}return true;
}

说明:

  1. 文档在建立时可能会比较慢,因此写了一个进度条,实现进度可视化。

  2. sum为读取文件的数目,如果处理文件的方式不变的话,一般sum的值是不变的。

4.搜索

基本框架:

#pragma once
#include "../Index/index.hpp"
#include <jsoncpp/json/json.h>
namespace bs_search
{const string data_path = "/home/shun_hua/practical-\projects/Boost_Search/DataSource/output/data_processed.txt";class Searcher{private:bs_index::Index *index;public:Searcher(){}~Searcher(){}void InitSeacher(){//1.对单例index进行获取。//2.对index进行构建。}//对查询进行搜索void Search(const string &query,string* json_str){//1.分词,即对query进行分词,便于查询。//2.根据关键词在倒排索引中进行查找。//3.根据相关系数,即weight对查找的内容进行降序排序。//4.根据排序之后的结果,构建对应的字符串。}};
}

说明:

  • 因为索引的构建的文件过大,如果反复进行过程较慢,因此我们这里的使用指针的形式,并且将Index类设为单例(懒汉)。
  • 我们输入的查询语句也需要进行分词,下面博主贴一个例子进行举例。且分词之后的结果需要进行大小写转换与倒排索引对应。
  • 我们查询之后的结果使用现成的json串,内容基本情况的获取,并且由于由于内容过大,返回有关键词的摘要即可。

补充:Index类

  1. 单例模式
static Index *instance()
{if (index_ptr == nullptr){mtx.lock();if (index_ptr == nullptr){index_ptr = new Index();}mtx.unlock();}return index_ptr;
}

说明:

  1. index_ptr 与 mtx的类型分别为Index * 与 mutex——C++的锁,头文件为mutex。
  2. 都需要设置为静态变量,便于进行外部通过类域进行获取。
  3. 这里外面的第一层的if是为了提高并发度, 因为多线程访问大多数情况是index不为空的情况。
  1. 获取关键描述

实现代码:

std::string GetDesc(const std::string& content,\
const std:: string& word)
{   auto pos = content.find(word);if(pos == std::string::npos){return "word is not in content";}//返回里面含有关键词的描述。int presize = 50,sufsize = 50;//获取pos前50个字节和后50个字节当做内容的描述int pre = pos - presize,suf = pos + sufsize;int begin = pre > 0 ? pre : 0;int end  = suf < content.size() ? suf : content.size();//返回摘要即可。return content.substr(begin,end - begin + 1);
}
  • 说明: 这个函数我设置的很简单,只需要对内容进行搜索关键词的位置,返回附近的内容即可,如果没有我们设置一个默认值进行返回即可。
4.1 初始化
  • Index类提供了 instance 和 BuildIndex进行初始化,因此直接调用接口即可。

实现代码:

void InitSeacher()
{//1.对单例index进行获取。index = bs_index::Index::instance();//2.对index进行构建。index->BuildIndex(data_path);
}
4.2 搜索功能
  1. 对查询进行分词。
    在这里插入图片描述

说明:

  • 索引的句子也进行了分词,使用关键词查找,并呈现对应的内容。
  • 在之前我们在 util::Jieba 即命名空间的对应类域中使用了jieba库的封装的接口CutString
  1. 获取倒排索引。
    在这里插入图片描述
  • 在Index我们内含成员存有正排和倒排索引,并实现了对应的接口——GetInterList,直接用即可。
  • 注意:对分词的结果也要忽略大小写,即为了与上面的索引模块对应,统一转换为小写即可。
  • 说明: 转换为小写,使用boost库中的to_lower接口即可。
  1. 降序排序
  • 对获取的倒排索引进行降序排序,使用algorithm 库里的sort,使用lambda表达式自定义对应的排序规则即可。
  • 说明:自定义对象为我们Index中实现的struct InterElem
struct InterElem
{// 默认构造InterElem(){}// 写了构造,编译器就不会自动生成默认构造函数。InterElem(int id, std::string key, int rate): idx(id), word(key), weight(rate){}int idx;          // 文档idstd::string word; // 关键词int weight;       // 权重
};
  1. 获取文档内容,封装为Json串
  • demo:
#include<iostream>    
#include<jsoncpp/json/json.h>    
#include<string>    
int main()    
{    int age = 18;    std::string name = "Shun_Hua";    int id = 12314213;    Json::Value root;    Json::StyledWriter wri;    root["age"] = age;    root["name"] = name;    root["id"] = id;    std::string json_str = wri.write(root);std::cout << json_str << std::endl;return 0;    
}

说明:

  • g++ 编译时,需要用 -l 选项包含对应的库名。
  • 编译指令:g++ json_demo.cc -std=c++11 -ljsoncpp
  • CenOs按照json库的指令:sudo yum install -y jsoncpp-devel

执行结果:

在这里插入图片描述


搜索功能实现代码:

void Search(const string &query,string* json_str)
{//1.分词,即对query进行分词,便于查询。std::vector<string> words;util::JieBa::CutString(query,words);//2.根据关键词在倒排索引中进行查找。//获取与关键词相关的所有倒排拉链bs_index::Interved_List lists;for(auto& word : words){//在此之前,我们需要对关键词进行大小写转换boost::to_lower(word);bs_index::Interved_List* list = index->GetInterList(word);if(nullptr == list){continue;}//说明:这里的内容可能会有大量的重复,最后可以保留较大的权值的文档ID,进行排序。//template <class InputIterator>//void insert (iterator position, InputIterator first, InputIterator last);//在指定的迭代器位置插入对应的容器的迭代器区间。lists.insert(lists.end(),list->begin(),list->end());}//将list的文档ID可能会有大量的重复,我们采用unordered_map进行去重,用文档ID作为索引值,//保留权值较高的元素或者将权值进行累加即可。std::unordered_map<int,bs_index::InterElem> kv;for(auto& elem : lists){int id = elem.idx;if(kv.count(id)){//保留文档权值较高的即可。//kv[id] = elem.weight > kv[id].weight ? elem : kv[id];//对权值累加。kv[id] += elem.weight;}else{kv[id] = elem;}}bs_index::Interved_List Deduplication;for(auto &pair : kv){Deduplication.push_back(std::move(pair.second));}//去重之后的结果,进行赋值。lists = move(Deduplication);//3.根据相关系数,即weight对查找的内容进行降序排序。sort(lists.begin(),lists.end(),[&](const bs_index::InterElem& x,\const bs_index::InterElem& y){return x.weight > y.weight;});//4.根据排序之后的结果,构建对应的字符串。//是根据对应的索引内容进行构建的。Json::Value root;Json::StyledWriter write;//这里我们还可以用:Json::FastWriter writer; 没有上面那一种美观。for(auto& interved : lists){bs_index::DocInfor* doc = index->GetDocInfor(interved.idx);Json::Value val;val["title"] = doc->title;val["url"] = doc->url;val["content"] = index->GetDesc(doc->content,interved.word);//追加在root后面。root.append(val);}*json_str = write.write(root);
}
  • 说明:去重时,我们采用unordered_map<int,InterElem>,相同文档ID时,保留权值较大的文档即可,便于后面的排序,另外这里我们再赋值时,使用move减少拷贝的次数。

测试:

  • 编写索引与搜索模块完毕,我们创建一个search.cc文件进行测试。
#include<iostream>                                             
#include"search.hpp"    
int main()    
{    //获取搜索服务的对象。    bs_search::Searcher sear;    sear.InitSeacher();    std::string query;    while(true)    {    cout << "Please Enter query@";    std::getline(std::cin,query);    string json_str;    sear.Search(query,&json_str);    cout << json_str << endl;    }    return 0;    
}

效果:

在这里插入图片描述

说明:由于查询的关键词反馈的内容可能过多,此处就不再显示了。

5. http_server

5.1 升级gcc
  • 原因:CentOs 7 的默认版本较老,使用http_server的库会编译出错,因此需要对gcc/g++进行升级。

  • 查看gcc/g++ 版本:gcc -v
    在这里插入图片描述

  • 说明:升级到7以上的版本即可。

  1. 安装扩展源:scl
sudo yum install -y http://mirror.centos.org/centos/7/extras/x86_64/Packages/centos-release-scl-rh-2-3.el7.centos.noarch.rpm

说明:

  1. 可用su命令输入root密码,切换至root进行安装。

  2. 普通用户添加至信任白名单后, 输入用户密码进行安装。具体操作链接:详见文章开头

  1. 安装devtoolset
sudo yum install devtoolset-9-gcc-c++

说明: 这里的 -9 意为按照 g++/gcc 9的版本,这里我们使用7以上的即可。

  1. 激活devtooset
scl enable devtoolset-9 bash

说明:

  1. 这里的-9与第二步的意思相同,你上一步输入了几,这里就还输入几。
  2. 在每次启动会话时,版本就回退到原先的,所以我们还需要输入,为了避免重复输入,我们可以放在用户对应的配置文件中。

具体步骤:

  1. 命令行输入:vim ~/.bash_profile。
  2. 将激活代码贴到最后一行,标好注释即可。
5.2 安装cpp-httplib
  1. 安装稳定版本的cpp-httplib, 这里推荐 v0.7.15 版本的。

在命令行输入如下命令进行安装。

git clone https://gitee.com/linzhipong/cpp-httplib.git
  1. 安装网站对应的样例进行测试。
  • 测试代码:
#include"./cpp-httplib-v0.7.15/httplib.h" 
int main()
{ httplib::Server svr;svr.Get("/hi", [](const httplib::Request & req, httplib::Response &res){    res.set_content("Hello World!", "text/plain charset=utf-8");//说明:charset=utf-8是支持编码的格式,即避免中文在网页中显示乱码。});    svr.listen("0.0.0.0", 8080);    return 0;    
}    

在网站上输入:http:// ip地址:8080/hi 会显示如下的效果:

在这里插入图片描述

5.3编写http_server.cc
  • 基本思路:

在此之前,我们还需对cpp-httplib的接口有一定的了解。

  1. 我们需要创建一个http类型的server对象,用于接收http请求。假设对象名为svr
  2. 对象的Get方法,参数需要构建即获取url的路径,接收和处理http请求。
  • 请求对象设为 req, 其中有has_param方法,判断url中的是否有搜索的word。
  • 其中的get_param_value(”word“), 可解析出word的内容。
  • 比如url为/s?word=XXX, has_param(“word”)即判断其中是否有word;
  • get_param_value(“word”)可将XXX提取出来。
  1. 提取出请求之后,我们可以用之前写的search对象,提供对应的搜索服务。

    • 使用响应对象,设为其名称为rep,其中的set_content方法,可帮助我们返回搜索内容,上有详细例子可辅助进行理解。
  2. 最后别忘了设置服务器的状态为监听状态哦!不然服务器可起不来。

实现代码:

#include"/home/shun_hua/practical-projects/Boost_Search/Modules\
/Server/cpp-httplib/httplib.h"#include"/home/shun_hua/practical-projects/Boost_Search/Modules\
/Search/search.hpp"
#include<string>
const std::string root_path = "/home/shun_hua/practical-projects/\
Boost_Search/Modules/Server/wwwroot";int main()
{bs_search::Searcher search;search.InitSeacher();httplib::Server ser;ser.set_base_dir(root_path.c_str());ser.Get("/s", [&](const httplib::Request& req, httplib::Response& res) {//  res.set_content("Hello world!","text/plain");if(!req.has_param("word")){res.set_content("必须要有搜索内容","text/plain; charset=utf-8");return;}std::string word = req.get_param_value("word");cout << "搜索内容为:" << word << endl;std::string json_str;search.Search(word,&json_str);res.set_content(json_str,"application/json,charset=utf-8");});ser.listen("0.0.0.0",8080);return 0;
}

6. 编写前端模块

说明:

  • HTML : 确定网页的骨骼。
  • CSS: 决定网页的皮肉,即是否好看。
  • JavaScirpt: 决定网页的灵魂,即动态效果,此项目指的是实现前后端交互。

开发环境:vscode,编写网页较为轻松,且可连云服务器,比较轻量化。

6.1 HTML

基本知识:

<meta charset="UTF-8">
<!-- 字符编码形式为UTF-8的形式-->
<html lang="en">
<!-- 设置网站站点为英文 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 设置网页的显示形式,确保能够正确的显示。-->
<title>XXXX</title> 
<!-- 设置标题为XXXX-->
<html>....</html><!-- 中间用来放置 网页文档的内容-->
<body>....</body>
<!--中间放置网页的可见内容-->
<div class="XXXX"></div> 
<!--设置一个类名为XXX的div元素,方便进行选择和设置格式。-->
<h1 align="center">XXXX</h1>
<!--设置一个一级标题XXXX,并且居中显示。-->
<input type="text" value="XXXXX" >
<!--设置一个输入框,内容为XXXX-->
<button onclick = "XXXX">XXXXX</button>
<!--设置一个button按钮,点击执行XXXX的javascript的函数动作-->

实现代码:

<!-- 在vscode下编写,只需要按下! 与 Tab键,就可生成网页的基本骨架--><!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Boost 搜索引擎</title>
</head>
<body><div class="container"> <!-- 居中显示--><h1 align="center">Boost</h1><div class="search"><input type="text" value="请输入关键词" ><button onclick="Search()">搜索一下</button></div><div class = "search_res"><!-- 注释部分主要用于CS网页进行测试--><!--下面是显示对应的搜索结果,对应搜索结果的对应的具体格式。--><!-- <div class="elem"><h3><a href="">标题:XXXXXX</a></h3><p>这是摘要XXXXXXXXXXXXXXXXXXXXXXXXXX</p><i>https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin</i></div><div class="elem"><h3><a href="">标题:XXXXXX</a></h3><td align="left"><p>XXXXXXXXXXXXXXXX</p><i>https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin</i></div><div class="elem"><h3><a href="">标题:XXXXXX</a></h3><td align="left"><p>XXXXXXXXXXXXXXXX</p><i>https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin</i></div><div class="elem"><h3><a href="">标题:XXXXXX</a></h3><td align="left"><p>XXXXXXXXXXXXXXXX</p><i>https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin</i></div><div class="elem"><h3><a href="">标题:XXXXXX</a></h3><td align="left"><p>XXXXXXXXXXXXXXXX</p><i>https://developer.mozilla.org/zh-CN/docs/Web/CSS/margin</i></div> --></div></div>
</body>
</html>
6.2 CSS

基础知识:

/*拓展:盒子模型,流动模型。*//*选择网页的所有内容*/
*{}
/*选择指定的一些标签*/
[标签],[标签]{}
/*选择类名,和标签对指的的内容框架进行页表修改。*/
.[类名] [标签]{}/*外边距,单位em,即相对于父元素的外边距的大小。*/
margin: xxem;
/*内边距,同外边距。*/
padding: xx em;/*设置高度为 父元素的比例,即100%继承父元素*/
height: xxx %;
/*设置高度为 xx 像素点,即绝对长度。px是最常用的长度单位。*/
height: xx px;
/*宽度同理*/
width: xxx %;
width: xxx px;/*设置居中对齐*/
margin:0px auto;/*设置盒子与顶部的距离*/
margin-top: xx px;
/*设置盒子与底部的距离*/
margin-bottom: xx px/* 设置边框的颜色和样式,参数为2个像素点,实体灰边框*/
border: 2px solid grey;
/*设置右边框的样式:这里的无。*/
border-right: none;/*设置字体的大小*/
font-size: xx px;
/*设置为字体的样式*/
font-family:xxx; 
/*将字体的风格设置为普通字样*/
font-style: normal;
/*设置字体距离内边框的左边距*/
padding-left: xxpx;/*设置字体的颜色, 参数为可选的或者颜色对应的参数。*/
color: xxx;
/*设置背景颜色*/
background-color: xxx;/*设置元素为块级元素,独占一行,便于调参和修改*/
display: block;
/*将下划线设置为无*/
text-decoration: none;

实现代码:

<!-- 网页的CSS部分-->
<style>/*对所有内容去重内外边距 */*{margin:0em; /*外边距*/padding:0; /*内边距*/}/*网页body 和 html的内容完全吻合*/html,body{height: 100%;}/*这是类选择器,即选择class container*/.container{/*设置盒子的宽度*/width: 700px;/*通过设置内外边距来达到居中对齐的效果*/margin:0px auto;/*设置盒子与顶部的距离,达到美化的效果*/margin-top: 20px;}.container h1{/*设置盒子与顶部的距离,达到美化的效果*/width: 600px;margin-bottom: 20px;}/* 复合选择器,先选择第一个类,再选择第二个类,中间用空格隔开。 */.container .search{/*宽度与父标签保持一致*/width: 100%;/*高度设置*/height: 50px;}/*选择form表单中的input标签设置搜索框, 直接选中设置标签的属性*/.container .search input{/*设置left左浮动*/float: left;width: 480px;height: 50px;/* 设置边框的颜色和宽度*/border: 2px solid grey;/* 将右边框设为无*/border-right: none;/* 设置字体的大小*/font-size: 15px;/* 设置字体距离内边框的左边距 */padding-left: 10px;color: #ccc;}.container .search button{/*设置left左浮动*/float: left;width: 120px;height: 54px;border: 2px solid #4e6ef2;border-left: none;/* background-color: blue;*//* 设置按钮颜色 */background-color: #4e6ef2;/* 设置字体颜色 */color: white;/* 设置字体大小 */font-size: 20px;/* font-family:'Times New Roman', Times, serif; *//* font-family:Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; */font-family:cursive;/* font-family:monospace; *//* font-family:serif; */}.container .search_res{margin-top: 30px;}.container .search_res .elem{margin-top: 20px;}.container .search_res .elem a{/*设置为块级元素,只占一行*/display: block;/* 将标题的下划线去掉*/text-decoration: none;/* 设置标题的大小 */font-size: 20px;/* 设置标题的颜色 */color: #4e6ef2;}/*设置光标的动作,即选中链接显示下划线。*/.container .search_res .elem a:hover{text-decoration: underline;}.container .search_res .elem i{/*设置为块级元素,只占一行*/display: block;margin-top: 8px;font-style: normal;font-size: 15px;color: green;}.container .search_res .elem p{margin-top: 8px;font-family: 'Times New Roman', Times, serif;}</style>
6.4 JavaScript

说明:由于我们不是前端,所以为了降低开发难度,所以要引入Jquery;

<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

实现思路:

  • 提取button中的内容。
  • 将内容转换为查询,发送给后端。
  • 后端返回对应搜索的json串。
  • 前端根据返回的json串构建网页内容。

基础知识:

/*提取指定标签的内容*/
let query = $("xxxxxxx").val()
/*在网页按下F12,显示网页的前端界面,点击上面的console运行网页时,可看到对应的内容,便于进行测试*/
console.log(query); $.ajax({type: "Get",url: "" /*请求网页内容的资源,对应后端的服务于Get请求的界面*/success: function(data){//请求成功时对应的数据//对数据进行处理。}error: function(data){//请求失败时对应的内容。}
});
//选择指定的类里面存放搜索的内容
let res = $("xxxxx");
//循环遍历data中的元素,实际在提取时,这里的emem为json串,可供参数的选择。for(let elem of data)
{/*这里我们设置一个a标签,并用元素填充其内容。*/let a_lable = $("<a>",text:elem.title,href:elem.url,target: "_blank");//其余的标签内容,放在实现中.let div_lable = $("<div>", class: "elem");div_lable.appendTo(a_lable)res.appendTo(div_lable)
}

实现代码:

function Search()
{// 获取.container .search input中的输入的关键字。let query = $(".container .search input").val();$.ajax({type: "Get",url: "/s?word=" + query,success:function(data){//构建对应的网页BulidHtml(data);}})
}
function BulidHtml(data)
{let result = $(".container .search_res"); result.empty();for(let elem of data){let a_lable = $("<a>",{text: elem.title,href: elem.url,//点击链接会跳转到新的网页。target: "_blank"});let p_lable = $("<p>",{text:elem.content});let i_lable = $("<i>",{text:elem.url});let div_lable= $("<div>",{class:"elem"});a_lable.appendTo(div_lable);p_lable.appendTo(div_lable);i_lable.appendTo(div_lable);div_lable.appendTo(result);}
}

总结 与 拓展

  1. 总结

    • 此项目我们只是实现了一个基础的版本,难度并不是很大,并且我们使用了相应的库来降低开发难度。
  2. 拓展

    • 暂停词,比如is,a,an诸如此类,可以进行过滤,以便于提高搜索的效率。
    • URL,我们可以读取所有的URL,这里我们只读取到了/doc/html目录下的html文件。
    • 爬虫程序,我们这里是通过官方下载,也可以通过爬虫程序获取到 html文件。
    • 普通的网页,可以看到广告的网页,我们也可以通过设置系数,来完成竞价功能。
    • 在搜索界面,当我们输入一部分词时,会显示相应的可能满足我们搜索要求的,即智能显示。
    • 设置登录注册,引入MySQL。
  • 对于扩展内容,博主在这里挖个坑,后面有时间和精力会进行补充的。

尾序

  • 我是舜华,期待与你的下次相遇!

相关文章:

【项目】Boost 搜索引擎

文章目录 1.背景2.宏观原理3.相关技术与开发环境4. 实现原理1.下载2.加载与解析文件2.1获取指定目录下的所有网页文件2.2. 获取网页文件中的关键信息2.3. 对读取文件进行保存 3.索引3.1正排与倒排3.2获取正排和倒排索引3.3建立索引3.3.1正排索引3.3.2倒排索引 4.搜索4.1 初始化…...

vue3 (六)自定义指令

1.定义自定义指令&#xff1a; app.directive(pos,{mounted(el,bunding){el.style[bunding.arg] bunding.value px;}, updated(el,bunding){el.style[bunding.arg] bunding.value px;} }) app.directive(指令名,{ mounted(el,bunding){}, updated(el,bunding){} }) 如果只…...

vite、mode如果为production打包后 .env.production 中 VITE_API_DOMAIN变量作为API地址吗

Vite 是一个现代化的前端构建工具&#xff0c;它使用 .env 文件来管理不同环境下的环境变量。通过为不同的环境&#xff08;如开发环境、生产环境等&#xff09;设置不同的 .env 文件&#xff0c;你可以控制这些环境中的变量&#xff0c;这些变量在构建时会被注入到项目中 当你…...

静态时序分析:SDC约束命令set_fasle_path详解

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 目录 指定建立/保持时间检查 指定上升/下降沿 指定时序路径起点 删除虚假路径 添加注释 简单使用 写在最后 在之前的文章中&#xff0c;我们讨论了如何使…...

浅谈马尔科夫链蒙特卡罗方法(MCMC)算法的理解

1.解决的问题 计算机怎么在任意给定的概率分布P上采样&#xff1f;首先可以想到把它拆成两步&#xff1a; &#xff08;1&#xff09;首先等概率的从采样区间里取一个待定样本x&#xff0c;并得到它的概率为p(x) &#xff08;2&#xff09;然后在均匀分布U[0,1]上取一个值&a…...

2403C++,C++20协程库

原文 基于C20协程的http库--cinatra cinatra是基于C20无栈协程实现的跨平台,仅头,高性能,易用的http/https库(http1.1),包括httpserver和httpclient,功能完备,不仅支持最普通的getpost等请求,还支持restfulapi,websocket,chunked,ranges,multipart,静态文件服务和反向代理等功…...

mybatis动态加载mapper.xml

mybatis动态加载mapper.xml mybatis动态加载mapper.xml、springboot mybatis动态加载mapper.xml 教程连接&#xff1a;https://blog.csdn.net/weixin_44480167/article/details/136356398...

安卓类加载机制

目录 一、ClassLoader介绍二、双亲委托机制三、类的加载过程 一、ClassLoader介绍 任何一个 Java 程序都是由一个或多个 class 文件组成&#xff0c;在程序运行时&#xff0c;需要将 class 文件加载到 JVM 中才可以使用&#xff0c;负责加载这些 class 文件的就是 Java 的类加…...

FPGA高端项目:FPGA基于GS2971的SDI视频接收+HLS图像缩放+多路视频拼接,提供4套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收转HDMI输出应用本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS动态字符叠加输出应用本方案的SDI接收HLS多路视频融合叠加应用本方案…...

[计算机网络]--五种IO模型和select

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、五种IO…...

【力扣经典面试题】14. 最长公共前缀

目录 一、题目描述 二、解题思路 三、解题步骤 四、代码实现&#xff08;C版详细注释&#xff09; 五、总结 欢迎点赞关注哦&#xff01;创作不易&#xff0c;你的支持是我的不竭动力&#xff0c;更多精彩等你哦。 一、题目描述 编写一个函数来查找字符串数组中的最长公共前缀。…...

Linux操作系统的vim常用命令和vim 键盘图

在vi编辑器的命令模式下&#xff0c;命令的组成格式是&#xff1a;nnc。其中&#xff0c;字符c是命令&#xff0c;nn是整数值&#xff0c;它表示该命令将重复执行nn次&#xff0c;如果不给出重复次数的nn值&#xff0c;则命令将只执行一次。例如&#xff0c;在命令模式下按j键表…...

SpringCloudGateway工作原理与链路图

SpringCloudGateway基本介绍 Spring Cloud Gateway 构建于Spring Boot 2.x、 Spring WebFlux和Project Reactor之上。因此,在使用 Spring Cloud Gateway 时,您可能不会应用许多熟悉的同步库(例如 Spring Data 和 Spring Security)和模式。 Spring Cloud Gateway 需要 Sprin…...

VUE2与VUE3之间的主要区别

当谈到 Vue.js 的版本时&#xff0c;Vue 2 和 Vue 3 是最常被提及的两个版本。下面是 Vue 2 和 Vue 3 之间的一些主要区别&#xff1a; 1. 性能提升&#xff1a; Vue 3 在底层核心重写了响应式系统&#xff0c;采用了 Proxy 对象&#xff0c;大幅提高了性能。Vue 3 还引入了静…...

CSS浮动实战,经典好文

零基础学web前端开发要怎么去学? 首先要学习的就是基础知识&#xff1a;html、css和JavaScript。HTML是内容&#xff0c;CSS是表现&#xff0c;JavaScript是行为。前端开发的门槛其实非常低&#xff0c;与服务器端语言先慢后快的学习曲线相比&#xff0c;前端开发的学习曲线是…...

如何搭建Nacos集群

1.搭建Nacos集群 众所周知&#xff0c;在实际的工作中&#xff0c;Nacos的生成环境下一定要部署为集群状态 其中包含3个nacos节点&#xff0c;然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。 我们计划的集群结构&#xff1a; 我就直接在本机上开三个Nacos来搭…...

未来已来!AI大模型引领科技革命

未来已来&#xff01;AI大模型正以惊人的速度引领着科技革命。随着科技的发展&#xff0c;人工智能在各个领域展现出了非凡的能力和潜力&#xff0c;大模型更是成为了科技领域的明星。从自然语言处理到图像识别&#xff0c;从智能推荐到语音识别&#xff0c;大模型的应用正在改…...

VBA如何记录单元格中字符内容和格式

实例需求&#xff1a;Excel单元格中的字符可以设置不同的字体格式&#xff08;大小、颜色等&#xff09;&#xff0c;有时需要将这些信息保存起来&#xff0c;以便于后续代码的处理&#xff08;例如替换后恢复原字体颜色&#xff0c;或者统计某种指定格式字符个数等等&#xff…...

逻辑漏洞(pikachu)

#水平&#xff0c;垂直越权&#xff0c;未授权访问 通过个更换某个id之类的身份标识&#xff0c;从而使A账号获取&#xff08;修改、删除&#xff09;B账号数据 使用低权限身份的账号&#xff0c;发送高权限账号才能有的请求&#xff0c;获得其高权限操作 通过删除请求中的认…...

阿里云服务器2核4G多少钱?支持多少在线?并发数性能测试

阿里云2核4G服务器多少钱一年&#xff1f;2核4G配置1个月多少钱&#xff1f;2核4G服务器30元3个月、轻量应用服务器2核4G4M带宽165元一年、企业用户2核4G5M带宽199元一年。可以在阿里云CLUB中心查看 aliyun.club 当前最新2核4G服务器精准报价、优惠券和活动信息。 阿里云官方2…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...