详细介绍基于MySQL的搜索引擎MySQLFullltext

本文涵盖了一个简单的C实现的搜索引擎的搭建始末。 我通常使用SQL Server和C #,但我教C/C++的朋友要远离微软。在过去,MySQL不是我想要的数据库,因为标准安装版不支持事务,但它变得

 本文涵盖了一个简单的C实现的搜索引擎的搭建始末。

我通常使用SQL Server和C #,但我教C/C++的朋友要远离微软。在过去,MySQL不是我想要的数据库,因为标准安装版不支持事务,但它变得越来越成熟。我使用64位InnoDB引擎的MySQL 5.6,使用Unicode(utf8)编码,这是我新数据库的默认设置。

Freetext是InnoDB的新特征,它在MySQL5.6版中被首次推出。

与C相比我通常更喜欢C++,即使在小项目中:不用知道所有的函数名,而且有一些内置的常用操作和漂亮的IntelliSense支持。在C++中,还有有STL及集合和字符串助手。

C++的Mysql接口比较弱,而C的接口很成熟,所以我决定使用C接口。

C的dll文件是和WCF一起发布的,以便完成AJAX请求,在Visual Studio Ultimate 2012中我使用C#的"WCF Service Application"模板,我搜索了使用C++搭建WebService的方法,但只找到一些使用C++处理WebServices调用的例子。

用户界面是一个使用Jquery和Jquery-UI自动提示的HTML界面,页面被增加到"WCF服务应用",项目被命名为VisionWeb

网页看起来是这样的: 

201542294730966.png (360×303)

 我在.NET框架4.0,64位系统上配置这个项目,如果你使用32位的Mysql服务器,你必需随之做些更改。记得设置UNICODE选项为默认值。

配置MySQL

你有可能会从VisionSmall中打开这个VisionDAL项目, 假定你必须修改连接MySQL的C程序接口. 在这儿,我介绍了如何在新项目中安装MySQL接口: 检查那些设置是否符合你的要求,尤其是mysql.lib文件和VisionDAL.dll的路径.

在Visual Studio中,添加一个VisionDAL工程, 通过这个流程"Other Languages/Visual C++/Empty Project". 在这之中, 你只需要改变"应用类型" 为DLL. 把VisionDAL.cpp改名为VisionDAL.c, 这就清楚的告诉Visual Studio把编译器从C++改为C. 给这个工程添加一个头文件命名为VisionDAL.h.

在窗口中, 右击VisionDAL工程并选择属性. 然后在"配置属性"/Linker/Input, 选择 "Additional Dependencies" 并且添加libmysql.lib 到这个路径, 不要忘记了分隔符 ";".

在 "配置属性"/Linker/General这个菜单下, 选择"添加库目录" ,对我来说就是添加 C:\Program Files\MySQL\MySQL Server 5.6\lib>这个目录. 现在我们已经连接到C接口, 但是在libmysql.lib中调用执行的DLL必须是系统的可执行路径: 从控制面板, 选择系统, 点击 "高级系统设置", "点出环境变量" 在 "系统变量"下面,选择路径, 并添加这个 libmysql.lib 的路径 (DLL和这个lib文件在相同的文件夹里): C:\Program Files\MySQL\MySQL Server 5.6\lib.

我们也需要把这个VisionDal.dll放到我们的path路径里, IIS 并不能从这个网站的bin目录中取到DLL文件. 添加 <项目路径>/x64/debug 到路径变量path里. 重启后生效. 当网站得到一个request请求时将会加载VisionDAL.dll; 如果你现在重建项目, 你会得到一个VisionDAL.dll的写入错误: 为了解决它, 重启该网站或是用unlocker之类的解锁.

如果需要指定VisonDAL的包含属性. 在 "配置属性"/"C/C++" 菜单下添加MYSQL的头文件路径, 例如像这样: C:\Program Files\MySQL\MySQL Server 5.6\include.

下面我们在“C/C++”/"预编译头"菜单栏中,从“预编译头”切换到“不使用预编译头”,设置Preproccessor定义防止使用strcpy和fopen时产生的错误消息:在"C/C++"/预编译器/"预编译器定义 "中设定SE_STANDARD_FILE_FUNCTIONS和_CRT_SECURE_NO_WARNINGS。

当你现在连接,mysqllib引用的问题并没有解决,因为它们是64位处理器。通过在VisionDal中打开工程属性,选择“配置管理”,然后设置为x64平台。

现在我们来创建名为 Vision 的样本数据库

打开SQL Development 中的 MySql 工作台,打开你的实例。将会出现一个新窗口 "SQL File 1" 。 双击VisionDAL项目中的 Sql.txt 文件。复制所有内容到剪贴板,粘贴到工作台中的"SQL File 1"窗口。 点击螺栓图标(左边第三个图标),创建样本数据库。 接下来我们需要用来数据库登录的通用信息。

我们有一个关于此的配置文件: VisionSmall\x64\Debug\VisionConfiguration.txt, 看起来像这样:  复制代码 代码如下: Host: localhost User: root Password: frob4frob Database: vision Port: 3306

修改这些数值以匹配你的SQL-Configuration。

Vision 数据库

数据库中只有一张表  

CREATE TABLE 'document' ( 'DocumentID' int(11) NOT NULL AUTO_INCREMENT, 'Title' varchar(255) DEFAULT NULL, 'Text' text, PRIMARY KEY ('DocumentID'), FULLTEXT KEY 'ft' ('Title','Text'), FULLTEXT KEY 'ftTitle' ('Title') ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

搜索的时候我们使用名为'ft'的全文索引,查找自动完成单词的时候我们使用名为'ftTitle'的全文索引。

如果你拥有一个很多字段的全文索引,你可以在Microsoft SQL Server中选择,查询的时候,哪个字段被包含进搜索。在MySQL中,通常全文索引的所有字段都被搜索,所以我们必须指定额外的全文索引'ftTitle'。

通过C接口进行MySQL查询 首先呢,为了执行查询我们需要连接到数据库并取得一个MYSQL的指针:  

MYSQL Connect(){ MYSQL conn; // Connection

// 连接到MySQL conn = mysql_init(NULL); if(mysql_real_connect( conn, Configuration.Host, Configuration.User, Configuration.Password, Configuration.Database, Configuration.Port, NULL, 0) == NULL) { fprintf(stderr, "sorry, no database connection ...\n"); return NULL; } return conn; }

在启动的时候我们把VisionConfiguration.txt文件里的变量赋值到全局变量, 这个文件应该和我们的程序在同一目录. 这是一个例行操作.获取当前运行程序目录是通过Win32 API的GetModuleFileName函数,如下:

 

TCHAR GetExecutablePath(){ TCHAR pBuf = (TCHAR *)malloc(512); int bytes = GetModuleFileName(NULL, pBuf, 255); if(bytes == 0) return NULL; else return pBuf; }

这里只有一个程序我们想要说明:GetDocuments. 在头文件定义:  

#define FORMAT_TEXT 0 #define FORMAT_JSON 1

__declspec(dllexport) TCHAR*   __cdecl GetDocuments(TCHAR *search, int format, int forAutocomplete); 在资源文件中定义:   __declspec(dllexport) TCHAR* GetDocuments(TCHAR *search, int format, int forAutocomplete)

__declspec(dllexport)的声明和定义实现是通过添加到VisionDAL.lib文件并通过VisionDAL.dll文件输出.__cdecl定义如何调用这个过程, 这里我们使用C风格的调用约定.当UNICODE定义被设置时,TCHAR和WCHAR是一样的,否则TCHAR就是一个简单的char, 假定我们这里的UNICODE 已经设置好了.

    注意这里有一些不同的 Unicode格式:     在C语言里我们使用两个字节来表示一个char值     在MYSQL和.NET 框架的UTF-8格式, 它意味着一个字节对应一个字符并且仅在超过一个字节被使用时     在终端程序中通过用一个字符对应一个字符并且当值大于127时使用Codepage 850.

参数格式是 FORMAT_TEXT 和 FORMAT_JSON,来保证输出在text和 JSON之间.

如果forAutocomplete是true,那么只有标题被搜索并返回.

VisionDALClientConsole

VisionDALClientConsole是一个很小的Windows Console应用程序。测试我们的GetDocuments程序将会涉及到VisionDAL工程集合,它将文件从VisionDAL输出到 VisionSmall\x64\Debug 。

VisionDALClientConsole 发出搜索字串请求,包括通配符“*”,它将会搜索title列和text列,并通过调用GetDocuments将字符输出。

一个简单的例子: 

201542295001192.png (677×343)

 main 入口:  

int _tmain(int argc,TCHAR argv[]) { char c; TCHAR result; TCHAR search = (TCHAR )malloc(10002); char searchA = (char *)malloc(1000); int retval = 1; char buffer[32000];

buffer[0]=0; printf("Search for: "); / wscanf doesn't get umlauts / if(scanf("%[^\n]", searchA) <= 0){ printf("Could not read input - retrieving all Documents \n"); *search=0; }else{ MultiByteToWideChar(850,0,searchA, -1,search, 999); } result=GetDocuments(search, FORMAT_TEXT, 0); if(result == NULL){ retval = 0;
}else{ WideCharToMultiByte(850,0,result, -1,buffer, 32000,NULL,NULL); printf("%s", buffer); } fflush(stdin); printf("Press RETURN Key to Exit\n"); getchar(); return retval; }

在Microsoft C V.12中按照惯例可以处理Unicode-16字串。在函数字串开始加上w或是用wcs替换str,如:wscanf,wprintf以及wcslen替换的是strlen。用wscanf不能正确的处理宽窄字符转化。我用MultiByteToWideChar,codepage用850来转化宽字符和用WideCharToMultiByte转化为一般字符。

查询MySQL数据库

上面我演示了如何连接数据库以及获得一个叫做conn的连接点。

接下来我们建立SQL查询:  

mysql_query(conn, "SET NAMES 'utf8'"); if(forAutocomplete){ if(search == NULL || wcslen(search) ==0){ WideCharToMultiByte(CP_UTF8,0, L"SELECT Title from Document LIMIT 20",-1,sql,1000,NULL,NULL); }else{ wsprintf(lbuffer, L"SELECT Title, match(Title) against('%ls' IN BOOLEAN MODE) as Score from Document where match(Title) against('%ls' IN BOOLEAN MODE) > 0.001 order by Score Desc LIMIT 20", search, search); WideCharToMultiByte(CP_UTF8,0,lbuffer,-1,sql,1000,NULL,NULL); } }else if(search == NULL || wcslen(search) ==0){ WideCharToMultiByte(CP_UTF8,0,L"SELECT DocumentID, Title, Text from Document",-1,sql,1000,NULL,NULL); }else{
wsprintf(lbuffer, L"SELECT DocumentID, Title, Text, match(Title, Text) against('%ls' IN BOOLEAN MODE) as Score from Document where match(Title, Text) against('%ls' IN BOOLEAN MODE) > 0.001 order by Score Desc", search, search); WideCharToMultiByte(CP_UTF8,0,lbuffer,-1,sql,1000,NULL,NULL); }

查询match(Title, Text) against('%ls' IN BOOLEAN MODE)在列Title和Text中查询要搜索的字符串,并返回一个反馈查询匹配情况的值。只有分数大于0.001的文档将显示,输出结果按评分排序。

IN BOOLEAN MODE时多个单词的搜索分别进行。

在搜索字符串中,你可以使用“*”作为通配符,它匹配0到n个字符。例如“as*”会匹配ASP。搜索不区分大小写。在SQL server中有些例外,“as**”不匹配任何内容,“*SP”也不匹配,你可以在字符串的开头匹配通配符。

获得数据

 

if(mysql_query(conn, sql)) { fprintf(stderr, "%s\n", mysql_error(conn)); fprintf(stderr, "%s\n", sql); return NULL; } // Process results result = mysql_store_result(conn); ... while((row = mysql_fetch_row(result)) != NULL) { if(format == FORMAT_TEXT){ MultiByteToWideChar(CP_UTF8,0,row[0], -1,buffer, 255); wsprintf(resultBufferp,L"%s\t", buffer); resultBufferp+=wcslen(buffer)+1; MultiByteToWideChar(CP_UTF8,0,row[1], -1,buffer, 255); wsprintf(resultBufferp,L"%s\t", buffer); resultBufferp+=wcslen(buffer)+1; MultiByteToWideChar(CP_UTF8,0,row[2], -1,buffer, 32000); wsprintf(resultBufferp,L"%s\n", buffer); resultBufferp+=wcslen(buffer)+1; }else if(format == FORMAT_JSON){ if(!forAutocomplete){ MultiByteToWideChar(CP_UTF8,0,row[0], -1,buffer, 255); wsprintf(resultBufferp,L"{\"DocumentID\": %s, ", buffer); resultBufferp+=wcslen(buffer)+wcslen(L"{\"DocumentID\": , "); MultiByteToWideChar(CP_UTF8,0,row[1], -1,buffer, 255); wsprintf(resultBufferp,L"\"Title\": \"%s\", ", buffer); resultBufferp+=wcslen(buffer)+wcslen(L"\"Title\": \"\", "); MultiByteToWideChar(CP_UTF8,0,row[2], -1,buffer, 32000); wsprintf(resultBufferp,L"\"Text\": \"%s\"},", buffer); resultBufferp+=wcslen(buffer)+wcslen(L"\"Text\": \"\"},"); }else{ MultiByteToWideChar(CP_UTF8,0,row[0], -1,buffer, 255); wsprintf(resultBufferp,L"\"%s\",", buffer); resultBufferp+=wcslen(buffer)+wcslen(L"\"\","); } } }

mysql_query 将查询发送到服务器。mysql_store_result将结果准备为一个集合,你可用mysql_fetch_row(result)进行迭代。无论列具有什么数据类型,每行都是一个字符串数组。我更喜欢ADO.NET中的具有类型的列。在.NET中,我们可能使用StringBuilder来聚集结果字符串,这里我们通过malloc和增长resultBufferp指针来定位char[]。我们使用MultiByteToWideChar来转换到WCHAR。   JSON 格式

我决定不采用XML格式,而使用轻量级的 JSON-格式,以此来从Web页面通过AJAX与Webservice通讯。

JSON-输出看起来像这样  

[{"DocumentID": 1, "Title": "ASP MVC 4", "Text": "Was für Profis"},{"DocumentID": 2, "Title": "JQuery", "Text": "Hat Ajax Support"},{"DocumentID": 3, "Title": " WebServices", "Text": "Visual C++ kanns nicht"},{"DocumentID": 4, "Title": "Boost", "Text": "Muss Extra installiert werden"}] 在参数自动完成为真的时候,JSON-看起来像这样: ? 1

["ASP MVC 4","JQuery","WebServices","Boost"]

  "[]" 符号表明了一个数组的开始与结束, "{}" 标明了一个对象的开始与结束。在一个对象中,":"前面的部分是属性名称,在它后面的部分是属性值。与之类似的,在你用JavaScript编码的时候也差不多一样。通过JavaScript-命令JSON.parse,你得到一个完整的对象,这个对象的属性可以通过通常的"." 符号访问。

为 GetDocuments 方法搭建 Webservice

我使用"Visual C#/WCF/WCF Service Application"模板创建了 VisionWeb项目,需要添加必要的System.ServiceModel引用。

下一步我们使用 NuGet 来添加必要的 JavaScript 库。选择 "Tools/Library Packet Manager/Package Manager Console" 并执行如下命令:  

Install-Package jQuery Install-Package jQuery.UI.Combined

下一步我们在 “  App-Code/IVisionService.cs” 文件中定义 service contract  :  

namespace VisionServices { [ServiceContract(SessionMode = SessionMode.Allowed)] public interface IVisionService { [OperationContract] [WebInvoke( Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] string GetDocuments(string search, int format, int forautocomplete);
} }

WebInvoke 属性是保证 service 能够被Ajax调用。我选择POST作为在HTTP请求中传递参数的方式。这个可选择的 GET 方式, 会加密并且暴露在URL中的参数。 我们指定以JSON格式发送请求和响应。当传递一个或多个参数时必须使用

BodyStyle = WebMessageBodyStyle.WrappedRequest。 你可以使用 WebMessageBodyStyle.Bareif ,这样你会得到零或者一个参数。

Webservice的实现

我们将实现定义在 "App-Code/IVisionService.cs"中:  

namespace VisionServices { public class PInvoke { [DllImport("VisionDAL.dll", CharSet = CharSet.Unicode)] public static extern string GetDocuments(string search, int format, int forAutocomplete); } public class VisionService : IVisionService { public string GetDocuments(string search, int format, int forautocomplete) { string result = PInvoke.GetDocuments(search, format, forautocomplete).ToString(); return result; } } }

VisionService.svc的实现

 

<%@ ServiceHost Language="C#" Debug="true" Service="VisionServices.VisionService" CodeBehind="App_Code\VisionService.cs" %>

这里定义了调用"http://:VisionService.svc"时的服务端点 ,调用GetDocuments函数的URL地址是 "http://:VisionService.svc/GetDocuments"。

Web.config 文件  

这是允许Ajax请求的配置。 你可以使用很多选项来配置WCF。你可以到Safari上查看更多类似于[2]的文档。

配置了一个提供元数据交换的端点,通过元数据你可以自动生成代码来获得WebService代理,比如使用svcutil。选择"Programs/Microsoft Visual Studio 2012/Visual Studio Tools/Developer Command Prompt for VS2012". 输入svcutil http://localhost:8001/VisionService.svc.一个名为VisionService.cs 的文件就生成了, 在其他情况下也会生成一个包含了Webservice配置信息的文件。

托管网站

启动“设置/控制面板/管理工具/ Internet信息服务(IIS)管理器”。当没有安装iis的时候,导航到“应用程序池”,找到正在运行.Net Framework 4.0版本应用程序池的名称,或者添加一个新的应用程序池。导航到“网站”节点,右击它,然后选择“添加网站...”,使用Vision作为网站的名称,为这个应用选择一个正在运行的 .Net Framework 4.0版本应用池。使用 /VisionWeb作为物理路径,设置端口为8001.选择属性上VisionWeb项目,选择“网络”,选中“使用自定义的Web服务器”,输入服务器URL http://localhost:8001。你可以使用其他的选项来托管网站,例如在IIS Express中,但是如果你不想改变Default.html文件,你必须将端口设置为8001。

HTML/JQuery 页面

在VisionWeb中有个名为Default.html的单一HTML页面,它包含了HTML与JavaScript的内容,它被标为起始页。

同样这里是这个页面的样子:

201542295039151.png (360×303)

Search

站点声明:本站部分内容转载自网络,作品版权归原作者及来源网站所有,任何内容转载、商业用途等均须联系原作者并注明来源。

相关侵权、举报、投诉及建议等,请发邮件至E-mail:service@mryunwei.com

回到顶部