基于Node.js的医药搜索平台网站设计与实现

LeftEar

发布日期: 2018-10-03 22:09:09 浏览量: 333
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

摘 要

随着科技的快速发展, 越来越多的医药公司积攒了大量的医药文档。这些文档资源如何高效、快速的被管理员管理,以及被用户检索,如何统一的实现资源管理与资源开放,成为了当下医药公司待解决的问题。

普通的、零散的、单一的文档管理方式已经不能满足企业的要求,企业需要的是综合、协同、集成化的文档搜索解决方案。构建基于Node.js的医药搜索平台,实现对文档的添加、删除、编辑等常见的操作,以及对大规模数据的快速检索匹配,用户对文档的查看权限等功能。为了解决如上问题,需要使用文档管理系统来对文档进行管理,如果文档是面向用户的还需要添加用户管理系统,权限系统等相应的辅助系统。

建立文档管理系统的目的就是要实现对文档的集中存储和管理,从而可以很好地保证文档的存储安全,提高文档的安全访问级别,很好地实现文档的分发、查询、共享,提高企业的文档管理和使用效率。在Internet环境下,我们设计的新型文档管理系统的体系结构采用B/S结构。本地用户可以通过企业内部网络直接进入文档管理系统,当然也可以进入企业的其他的业务系统。移动办公用户以及企业的客户可以通过门户站点访问到本系统,分支机构以及企业的合作伙伴可以通过Web服务方式建立与系统的连接。

本文最终实现一个基于Node.js的医药文档搜索平台,其中包括用户管理模块、文档管理模块、系统设置模块等多个子模块。并利用Sphinx和MYSQL实现了具有分词功能的多语言文档搜索引擎。前端利用Angular实现了Single Page Application。本系统现在已经在阿里云服务器上部署应用,并上线。

关键词:Node.js,Sphinx,Angular.js,ORM

ABSTRACT

With the rapid development of technology, more and more pharmaceutical companies accumulate a large number of medical documents. These documents how resource efficient, rapid management by an administrator, and retrieved by the user, how to achieve a unified resource management and resource opened as the current problems to be solved pharmaceutical companies.

Ordinary,fragmented, single document management methods can not meet the requirements of enterprises, enterprises need a comprehensive, coordinated, integrated document search solutions. Construction Node.js based medical search platform, to add to the document, delete, edit and other common operations, and rapid retrieval of large-scale data matching the user permission to view the document on Zen and other functions. In order to solve the above problem, we need to use document management system to manage the document, if the document is a user-oriented management systems also need to add a user, system privileges and other appropriate assistance systems.

The purpose of establishing the document management system is to achieve the centralized document storage and management, which can ensure the safe storage of documents, improve security access level of the document, to achieve a good distribution of the document, query, share, and improving document management and efficiency. In the Internet environment, the architecture we designed a new document management system using B / S structure. Local users can go directly through the corporate intranet document management system, of course, can also enter other business systems business. Mobile office users and corporate customers can access the system through the portal, branch offices and business partner scan establish a connection with the system through a Web service.

In this paper, the ultimate realization of a medical document search based Node.js platform,including user management module, document management module, system settings module multiple sub-modules. And using Sphinx and have realized the word MYSQL function multi-language document search engine. Angular use front-end to achieve a Single Page Application. The system is now deployed on the application server Ali cloud and on-line.

Key words: Node.js, Sphinx, Angular.js, ORM

1 绪论

1.1 课题来源及研究背景

本课题源于实际生产,目的是为西安泰科迈医药科技开发医药文档管理系统来优化现有的文档管理系统。

原有的文档管理方式为手工编辑Excel,利用目录来对文档进行分类和查询。当需要修改一个文档的时候,需根据目录结构进行检索,逐目录的查找文档,然后编辑后保存。

但这样的查询有一定的局限性,如只能根据文件的名称进行字典排序,或者根据文档的关键词来进行分类。

无论哪种的方法都不适合大量的文档管理,如当文档的名称被修改后,需要根据名称进行重新的排序,或者一个文档的关键词被修改后,需要根据新的关键词来重新分类,或者当一个文档有多个关键词的时候,分类方式变的极其复杂。

基于上述的缺点和不足,我们将实现一个医药文档搜索和管理平台。利用程序来实现常见的文档操作,比如文档的上传、下载、修改、搜索、查看等,因为文档是面向用户的,用户分为游客、普通用户、管理员,每个用户对文档都有不同的操作权限,所以我们还需要实现角色权限系统。

1.2 技术栈的选择

系统需要分别实现前端和后端。

在后端框架上,我们有很多选择,比如PHP下的Yii、ThinkPhP等框架都很流行,Java下也有 Spring、Play 等框架,还有近两年比较火的Node.js。在本系统中我选择了使用Node.js来搭建后端系统,具体有如下原因。

  • 由于常见的操作,均为I/O 型操作,所以使用异步非阻塞I/O模型和事件编程可以提高系统的吞吐量。相比于PHP或者Java,Node.js天生就支持异步非阻塞I/O模型和事件编程。所以这里Node.js 更加合适。
  • 本系统将运行在单核服务器上,考虑到系统的资源有限,由于Node.js为单线程的,所以很适合在单核CUP上跑,相比于PHP或者Java, Node.js由于不需要频繁的线程切换,加上内置的Event Loop机制[1],所以Node.js在此环境下更加快。
  • 考虑到是一个人开发前后端,使用JS全栈开发效率更高。

前端的开发上,我选择使用Angular.js 来进行SAP开发,由于我们开发的是医药文档搜素和管理平台,并不需要支持搜素引擎友好,并且前端逻辑页面逻辑比较复杂,所以选择Angular.js来进行单页面Webapp 开发。

总上所述我最终选择了使用Mean栈来进行开发,Mean栈包括MySQL、Express、Angular.js 、Node.js。

1.3 论文主要工作内容

为了实现本系统,我们首先的从实现数据层开始,提供良好的数据层接口,可以保证我们后期实现Model层时更加的快速和稳定。

接下来我们要基于数据层实现用户管理模块和角色权限模块的接口,因为网站是面向用户的,所以基本上所有功能都依赖于用户。将用户模块和角色权限模块放在第二步实现,会保证我们后期的其他模块实现起来更加方便。

然后我们就可以建立文档模块,文档模块主要有文档的上传,修改,删除,搜索等功能。在第四章中,我们会详细介绍到如何将原始数据Excel解析并存储到MySQL中,以及如何从MySQL中将数据还原到Excel中。还有文档项权限的设置等技术。

文档的搜索,我是基于Sphinx进行开发的,所以在第五章我会详细的讲解如何配置和使用Sphinx来提供多语言全文搜索功能。

当我们将后端服务器全部构建完毕后,我们便可以编写前端代码。前端我是基于Angular.js 构建的SPA 应用。我会在第6章介绍利用Angular实现本系统前端界面的核心代码。

2 系统数据层的设计与实现

2.1 基于ORM实现的ActiveRecord

在MVC的开发模式中,我们通常需要一个model层用于对数据的抽象。

ActiveRecord是一种领域模型模式,特定是一个模型对应关系型数据中的一个表,而模型类的一个实例对应表中的一个记录。在数据库中,不同的表之间往往通过外键来关联。ActiveRecord中通过对象的关联和聚集来实现这种关系映射。

这样做的好处是我们可以将数据抽象为对象。从而更加直观和方便的进行数据操作,也方便后期的维护。

在node的中Express框架中并没有实现数据层,所以我么需要借助其它工具来实现本系统中的ActiveRecord。

2.2 用Bookshelf.js来构建数据层基类

Bookshelf.js[4]是Node.js中的一个ORM框架。建立在 KnexSql生成器上。同时实现了Promise以及传统的Callback调用方式。并支持transaction、一对一关系、一对多关系、多对多等数据映射关系。它可以很好的与PostgreSQL、MySQL、以及SQLite工作。

Bookshelf.js基于knex.js开发,所以关于数据库的链接需要使用Knex来进行操作。

Knex 数据库的链接代码如下:

  1. var knex = require(‘knex’)({
  2. client: mysql’, // 数据库类型
  3. connection: config.mysqldb // mysqldb中包含数据库的地址,帐号和密码等信息
  4. });

接着在Bookshelf的初始化中加入之前创建的Knex对象便可完成基类ActiveRecord的创建, 实例代码如下:

  1. varmarkBookshelf = require(‘bookshelf’)(kenx);
  2. markBookshelf.Model= markBookshelf.Model.extend({
  3. // 在此添加私有方法
  4. }, {
  5. // 在此添加静态方法
  6. });

由代码我们可以看出,我们将之前创建好的knex对象交由bookshelf工厂函数,生产一个markBookshelf基类,之后我们可以通过使用markBookshelf.Model的extend方法扩展Model。extend方法接受两个参数,第一个参数对象中的所有方法和值会变为Model的私有方法,第二个参数中的所有对象和值会变成Model中的静态方法。之后我们在建立更多得model的时候只需要继承markBookShelf基类便可以。

2.3 示例:构建用户 Model

目前系统数据层的models如图2-1所示:

有了上面的markBookshelf基类后,我们便很容易生成一个数据模型,我们这里将建立一个user model,代码如下所示:

  1. var User = markBookshelf.Model.extend({
  2. table: User’, // 和数据库中的user表名相对应
  3. initialize: function() { /* 实例初始化的时候会调用 */ },
  4. toJson: function() { /* 序列化对象 */ }
  5. // 我们可以根据需求天假更多的私有方法或静态方法
  6. });
  7. var user = new User({id: 1});
  8. user.set({username: markstock’});
  9. user.save();

我们可以很简单的就创建一个user model,后续对user表的操作不必在写SQL语句进行操作,只需要实例化一个User对象便可完成所需的操作。

3 角色权限系统与用户管理模块的实现

3.1 基于角色的访问控制

基于角色的访问控制[6] (RBAC, Role-Based Access Control) 有效的克服了传统访问控制技术中的不足之处,是当今广泛流行的访问控制技术模型之一。

在角色访问控制中引入了角色这一个概念[7]。它的基本思想是将访问的权限分给不同的角色,在将角色划分给不同的用户。用户的每个操作的权限检查,其实是通过用户所拥有的角色是否拥有该权限来确定的。角色的不同,访问权限也不同。这样整个访问控制工作分为两个部分,即访问权限与角色的关联,角色与用户的关联。从而实现了用户与访问权限的逻辑分离。

在角色访问控制系统中,每个用户的权限都不可自主定义,权限仅受限于用户所拥有的角色,一个用户只能同一时间拥有一个角色。拥有同一角色的用户拥有同样的权限。正因为角色访问控制系统有这样的限制,所以它不适合用来设计复杂的权限系统。

3.2 权限控制

权限控制可以简单的描述为WWH操作[8],即“Who 对 What 进行了How的操作”的逻辑表达式是否为真的检验。它的基本任务是防止一个合法用户对系统资源的非法访问和使用。它可以约束一个用户在系统中可进行的操作。在本系统中,我们将实现一个canI的函数来对WWH进行检验。如果用户没有权限便返回422提示用户没有权限进行当前的操作。

3.3 用户动态权限数据表的设计

如图3-1所示,要构建角色权限访问控制,我们至少需要5个表。

Table permission 我们用来定义所有的权限,它包括权限的类型和具体的权限。部分数据如下表所示:

如其中的第一行所示,user为what,browse为how,即验证用户是否有权对user进行browse操作。

Table roles 用来定义我们的角色, 本系统目前有三个角色。

因为permissions和roles 为n对n的关系,roles和users为n对1的关系,所有我们需要permissions_roles 和roles_users两个中间表来纪录关系。

3.4 用户权限动态分配功能实现

因为我们是使用ActiveRecored来做数据库的表和对象的映射,所以我们可以将角色权限系统中的表根据我们在第一章创建的ActiveRecord基类来创建模型类。我们可以使用如下代码创建Permission model。

  1. var Permission = markBookshelf.Model.extend({
  2. tableName: permission’,
  3. roles: function roles() {
  4. return this.belongsToMany(‘Role’);
  5. }
  6. });

如上述代码所示,我们创建了一个Permission类,在bookshelf.js中,当有Many to Many 或者 One to Many的表间关系时[10],我们并不需要生成中间表的类,比如roles_users表和permissions_roles表,我们只需在Permission中指出需要关联的表,比如上述代码有一个roles方法,其中调用了belongsToMany(‘Role’)说明Permission表和Roles表是多对多的关系。

根据如上我们可以创建Roles类。因为roles表和permission表为多对多的关系,roles表和users表为多对一的关系。所以我们需要在Role的模型中加入users和permissions方法。

  1. var Role = markBookshelf.Model.extend({
  2. tableName: roles’,
  3. users: function users() {
  4. return this.belongsToMany(‘User’);
  5. },
  6. permissions: function permissions() {
  7. return this.belongsToMany(‘Permission’)
  8. }
  9. });

在系统启动的时候我们需要将所有的permission从数据库查出,并加载进内存,这样每次查询权限的时候我们便不需要查询数据库,只需要从内存中读出permission信息便可以,实现代码如下。

  1. var actionMap = {};
  2. var init = function() {
  3. return models.Permission.findAll().then(function(perms) {
  4. _.each(perms.models, function(perms) {
  5. var actionType = perm.get(‘action_type’),
  6. objectType = perm.get(‘object_type’);
  7. actionMap[actionType] = actionsMap[actionType] || [];
  8. });
  9. });
  10. };

models.Permission.findAll为查找出所有permission表中的数据,之后的then为promise风格的异步调用,当findAll成功调用后,调用then中的回调函数。

每次需要检查用户是否有相应的操作权限时,我们需要根据用户的user_id来查找其的role然后根据role来判断其是否有相应的操作权限,实现的伪代码如下所示。

  1. canI(obj_type, action_type, user_id) {
  2. var user = Get user from User Class with user_id;
  3. var role = user.getRole();
  4. return check if user with role can do this action_type;
  5. }

3.5 用户管理模块的实现

有了前面的数据模型和方法我们可以很方便的创建出用户的增删改除等功能。这里以实现后台管理员查看用户列表为例。管理员可以在后台管理页面中查看所有的用户列表,并以分页的形式展现结果。

如下函数将会根据提供的搜素请求获取用户的分页数据信息。

  1. function doQuery(options) { models.User.findPage(options); }

options中包含了当前页page,以及每页显示的数量num。查找的原理是基于如下的SQL语句(伪代码)的来实现的:

  1. SELECT * FROM user limit page * (num - 1), num;

用户管理界面如图3-4和图3-5所示。


如图为后台管理页面中的用户管理页面,管理员可以根据用户的邮箱、昵称、或者角色查询用户,管理员可以修改其他用户的信息,或者删除其他非管理人员。

对于前台页面在实现了用户模块后,我们可以实现登陆,注册,用户账号设置等功能。

用户登陆窗口 用户注册窗口

4 文档的存储与文档项权限的设计

4.1 文档管理功能描述

原有的文档是通过Excel进行存储,通过目录来进行分类。因为要建立搜索功能,我们取消分类的功能,用户可以通过关键词来搜索到相关的文档。

管理员可以通过后台来编辑文档、查看文档、搜索文档、以及重新上传文档。

对于文档的搜索功能,我们需要实现可以进行中文搜索,英文搜索,以及中英文分词搜索的功能,而且我们还需要可以查出所有文档的接口来供管理员使用。对于搜索的结果我们要做分页处理,用户可以选择每页显示20项或者40项结果,默认以20条方式呈现。

每个文档的每一项我们称为一个文档项。文档项有多种存储类型,具体如下所示:

Image

此类型用来标记当前项目用来存储图片,每个文档都可以存储多张药品图片,来供展示。管理员需要可以上传和删除图片。

Text

纯文本类型,如药品的介绍、规格、生成常见等都是纯文本存储。

Text + En

对于药品的通用名、商品名均有中英文两种表示方式,所以此项用来分别存储中文和英文信息。

Download

有些项目可以用来存储其它文档资料,比如PDF,或者word等格式的文件来供用户下载,所以此项用来存储其它文件。

Download + Text

和Download项类似,都可以用来存储其它文件,但是此项还可以加上下载说明等额外的信息。

每个文档项都有一定的查看权限,只有满足权限的用户才可以产看或下载,所以我们必须要存储每个文档项的权限,并且管理员可以更改。

目前文档项的权限分为如下三类:

  • 游客可以查看或下载
  • 通用户可以查看或下载
  • VIP用户可以查看或下载

4.2 面向文档动态权限的数据格式

数据均需要从用户上传的Excel文档进行解析,所以为了解析方便,我们对原始文档的格式进行了定义,数据格式如图4-1所示。

对于Text 和 Text + En的类型其值直接填写进中文值和英文值中即可。

对于Image 和 Download类型只填写名称就好,后续的文件通过后台管理平台在进行添加。

Download + Text中的Text部分填写进中文值就好。

因为每个文档项目均有查看和下载权限,目前有四种用户类型Guest、User、

VIP、Administrator。所以我们赋予每中用户一定的权限值,如下所示:

  1. Guest = 1; User = 10; VIP = 20; Administrator = 40;

每个角色只能查看或下载小于其值的文档项,比如当前用户为VIP, 则它可以查看权限值等于1、10、20的文档项。

对于数据库中的存储格式,我们选择利用JSON字符串进行存储,针对每种不同的文档项,都有不同的JSON数据格式。

Image格式

我们将图片的具体存储地址,存储在image数组中,每个图片用逗号分隔。当我们需要添加新的图片的时候,只要将图片上传到服务器上,然后将地址插入进image数组中便可。

  1. {
  2. name”: “产品图片”,
  3. image”: [],
  4. value”: “”
  5. }

Text

对于Text类型的数据,我们只需要将值存进value中即可。

  1. {
  2. name : “化学名”,
  3. role: 1”,
  4. value”: 26-二甲基-4-( 2-硝基苯基)-14-二氢-35-吡啶二甲酸二甲酯”
  5. }

}

Text+ En

对于 Text + En类型的值,我们分别存储为zhValue 和 enValue。

  1. {
  2. role”: 1”,
  3. name”: “商品名(中英文)”,
  4. zhValue”: “肠虫清”,
  5. enValue”: Eskazole
  6. }

Download

  1. {
  2. file”: {
  3. path”: path/to/the/file/on/server”,
  4. filename”: file name”,
  5. fid”: the identify of the file
  6. },
  7. role”: 1
  8. name”: “质量标准JP
  9. }

对于文件的数据格式,我们将文件的存储路径存储在file.path中,fid(唯一文件标识符)用来做下载验证,避免用户非法批量下载。

4.3 文档动态权限数据表的设计

为了实现文档项可扩展的文档管理系统,我们需要两张表,一个是文档项表 Attributes , 一个是Documents表。

因为每个文档的文档表项都大致相同,所以文档项表用来纪录所有类型的文档项。文档项表的用途,只在解析原始文档和生成新文档的时候用来保证信息的一致性。

文档项表的设计如图4-2所示:

attr_name 为文档项的名称,每个原始文档的每一项的名称都得与attr_name相对应,才可以被识别,否则会被忽略。Alias与documents表中得文档项别名一一对应,type为当前文档项得类型。

部分文档项表数据如图4-3所示:

documents表比较简单,只包括id和各个文档项得值,部分column如图4-4所示:

4.4 功能设计及分析

4.4.1 元文档的上传与解析

搭建文档管理系统的第一步就是上传文档,解析原始excel数据,存储进数据库。为了更加方便的解析excel文档,我们使用node-excel插件,它会将excel解析为JSON对象。

  1. var filepath = req.files.file.path;
  2. try {
  3. obj = nodexlsx.parse(filepath);
  4. } catch {
  5. console.error(e);
  6. }

我们只需将上传文件的路径地址传送给nodexlsx的parse方法,便可获取到json对象,如果失败则上传的文件无法被识别,可能不是excel文件。

解析出原始数据,我们便可以去格式化数据,然后存储进数据库了。

  1. _.each(xlsxData, function(rowData) {
  2. if (rowData[0] === attr.get(‘attr_name’)) {
  3. if (attr.get(‘type’) === text || attr.get(‘type’) === download+text’) {
  4. obj.value = rowData[1];
  5. } else if (attr.get(‘type’) === text+en’) {
  6. obj.zhValue = rowData[1];
  7. obj.enValue = rowData[2]
  8. }
  9. }
  10. insertData[attr.get(‘alias’)] = JSON.stringify(obj);
  11. });

如代码所示,其中的xlsxData及为解析出的原始excel数据,attr为一个文档项,我们一次遍历所有的原始数据去匹配文档项,然后格式化数据。

当所有的文档项都处理完后,我们既得到格式化后的数据,然后只要调用Document类的add方法就可以存储进数据库了。

  1. return models.IDocument.add(insertData, options);

当文件的大小超过了5Mb的时候就会提升异常文件,当文件的格式不正确,无法正常解析的时候就会提示错误的格式。

4.4.2 元文档的编辑

当我们需要编辑文档的时候,我们需要先将文档项中的JSON字符串解析为对象,然后修改对象中的值,在将对象stringify化存储起来。其核心代码如下:

  1. attrs = attrs.models;
  2. _.each(attrs, function(attr) {
  3. if (options.data.idocuments[0][attr.get(‘alias’)])
  4. options.data.idocuments[0][attr.get(‘alias’)] = JSON.stringify(options.data.idocuments[0][attr.get(‘alias’)]);
  5. });

我们将修改后的对象重新stringify化后便可存储。

4.4.3 Excel格式元文档的下载

文档的下载可以看成为上传的逆过程,上传是从excel将数据解析到数据库中,下载则是将数据库中的数据解析为excel文档。因为我们在将excel中的数据解析到数据库中的时候,只解析了类型为Text 和 Text + En类型的值,但是在将数据库中的数据解析到Excel中时,我们必须要考虑Download 和 Image以及Download + Text类型的值。

因为Excel中不能存储文件,以及node-excel不能将图片添加进excel文件中,我们将文件和图片的地址添加进Excel中。

首先我们从数据库中获取所有的数据,并解析为数组,代码如下:

  1. _.each(iattributes, function(iattribute) {
  2. try {
  3. // 尝试去解析 json string
  4. idocument.set(iattribute.get(‘alias’), JSON.parse(idocument.get(iattribute.get(‘alias’))));
  5. if (iattribute.get(‘type’) === text’) {
  6. // 填充text类型的数据
  7. } else if (iattribute.get(‘type’) === text+en’) {
  8. // 填充text + en类型的数据
  9. } else if (iattribute.get(‘type’) === download’) {
  10. // 填充download类型的数据
  11. } else if (iattribute.get(‘type’) === download+text’) {
  12. // 填充download+text类型的数据
  13. } else if (iattribute.get(‘type’) === image’) {
  14. // 填充image类型的数据
  15. }
  16. } catch (e) {
  17. xlsxData.push([iattribute.get(‘attr_name’), ‘’]);
  18. }
  19. });

当我们解析出数据xlsxData时,我们便可以生成excel文件,但是在发送给浏览器的时候,我们必须要设置response header, 以及将生成的excel转变为字节流传送给浏览器。具体代码如下所示:

  1. var wb = new Workbook(),
  2. ws = sheet_from_array_of_array(xlsxData),
  3. ws_name = SheetJS’;
  4. wb.SheetName.push(ws_name);
  5. wb.Sheets[ws_name] = ws;
  6. // 生成excel
  7. var wbout = nodeXlsx.write(wb, {
  8. bookType: xlsx’,
  9. bookSST: true,
  10. type: binary
  11. });
  12. // 设置 excel文件 response header
  13. res.setHeader(‘Content-disposition’, attachment; filename=’ + filename);
  14. res.setHeader(‘Content-type’,‘application/vnd.openxmlformats-officedocument.spredsheetml.sheet’);
  15. // 将文档数据转换为字节流传给浏览器
  16. res.send(new Buffer(s2ab(wbout)));

5 基于Sphinx的检索子系统

5.1 使用Sphinx和MySQL实现多国语言全文搜索

如过只是依赖MySQL的like语句来实现全文搜索,那么局限性将很大,首先不能实现分词功能,比如我们以阿莫西林为关键词来进行搜索,如果使用SQL语句我们可能就要写成like ‘%阿莫西林%’ 这样。但这样我们只能查到是否有阿莫西林整个词出现在内容中,而不能匹配到“阿”,“阿莫”,“阿莫西”等词的匹配情况。

可能“阿”,“阿莫”,“阿莫西”这样的词并不会匹配到任何结果,看不出分词的作用性。但如果用户想根据两个关键词来搜索文档,那么分词的作用性就会体现出来。

分词系统首先会对输入进行分词,然后根据每个分词在文档中的匹配情况,计算匹配权值,结果会根据权值的大小排序进行返回。

Sphinx是一个基于SQL的全文搜索引擎。它会从MySQL数据库或者PostgreSQL中读取数据,然后建立索引。

首先我们要配置数据源具体代码如下:

  1. source medical {
  2. type = mysql
  3. sql_host = localhost
  4. sql_user = mysql-user
  5. sql_pass = mysql-user-password
  6. sql_db = med_dev
  7. sql_port = 3306
  8. sql_query = SELECT id, kxm, spm, type, cpsy, jx, gg, sccj ,yyycg FROM documents
  9. sql_query_pre = SET NAMES utf8
  10. }

我们在配置信息中配置了MySQL的链接信息,以及获取数据的SQL语句。在启动Sphinx后,sphinx会自动链接MySQL获取数据,然后建立索引。

建立索引的代码如下,我们设置了最小分词长度为1,然后支持中文分词。

  1. Index medical {
  2. source = medical
  3. path = /var/lib/sphinxsearch/data/medical
  4. ngram_len = 1
  5. ngram_chars = U+4E00...U+9FBB, U+3400..U+4D85 …….
  6. }

要运行Sphinx需要两步,第一步是生成索引,第二部是启动sphinx服务器。

  • 建立索引:/usr/local/sphinx/bin/indexer–config /usr/local/sphinx/etc/sphinx.conf
  • 启动服务:/usr/local/sphinx/bin/sphinx start

5.2 文档搜索接口的实现

Sphinx实现了node API 插件sphinxapi16],所以我们可以很轻松的使用node.js访问sphinx服务器。

要连接Sphinx服务器我们首先要创建Sphinx客户端实例,具体代码如下:

  1. var SphinxClient = require(‘sphinxapi’),
  2. config = require(‘../config’),
  3. cl;
  4. cl = new ShpinxClient();
  5. cl.setServer(config.sphinx.host, config.sphinx.port);
  6. cl.setMatchMode(SphinxClient.SPH_MATCH_EXTENED2);

我们使用SphinxClient创建了一个客户端实例,然后设置了sphinx服务器的地址和端口。有了创建好的实例,我们便可以通过指定的索引来进行搜索了。

  1. Sphinx.QueryAsync(keyword, medical’).then(function(ret) { });

QueryAsync接收两参数,第一个为要搜索的关键词,第二个为要查找的索引,如果查找成功会调用then中的回调函数。ret中会有两个主要值,一个是ret.total_found 为查找到的总结果数,一个是ret.matches 为匹配到的结果集。

但我们从sphinx中查找出的只是匹配的id集合,我们还需要通过id集合来从MySQL中获取真正的数据,其核心代码如下。

  1. _.forEach(ret.matches, function(match) {
  2. ids.unshift(‘id + match.id);
  3. });
  4. if (ids.length) {
  5. options.filter = ids.join();
  6. return models.IDocument.findPage(options);
  7. } else {
  8. return Promise.resolve({ documents: [] });
  9. }

首先我们从sphinx中查处的结果中获取所有的id,然后将id拼接为字符串如“id1id2 id3 …”, 如果ret_matches为空则直接返回空数组。

5.3 前台搜索结果分页显示

有了前面设计的接口我们便可以实现搜索RESTFUL API,如图5-1所示

如图5-1所示我们通过浏览器发送GET请求,RequestUrl为/api/documents。为了可以获取搜索结果,我们需要添加几个请求参数。

如图5-2所示,其中keyword为我们要搜索的关键词,因为关键词可能包含非法字符,所有在我们发送请求前,我们应该利用encodeUrlComponent函数对参数进行解析,limit为每页显示的结果数,系统默认为20个,可选数量为40个。page为当前的页数。

访问如上的API我们便可以获取搜索结果,服务端会返回如下结果。

其中documents为所搜索到的文档结果,meta为搜索结果附加数据,其中包括当前关键词可搜索到的结果的总数total,以及当前的页数,以及每页显示结果数。有了这些数据我们便可以编写前台分页视图。

在前台页面中,我们只需在输入框中输入关键词,并选择当前页数便可以显示所有搜索结果,效果图如图5-4和图5-5所示。


6 基于Angular.js实现前台SPA

6.1 文档检索平台的 UX 与 SPA

所谓单页面应用,指的是在一个页面上集成多种功能,甚至整个系统就只有一个页面,所有的业务功能都是它的子模块,通过特定的方式挂接到主界面上。它是AJAX技术的进一步升华,把AJAX的无刷新机制发挥到极致,因此能造就与桌面程序媲美的流畅用户体验[16]。
但单页面应用也有缺点,那就是搜索引擎不友好性。搜索引擎利用爬虫来爬取页面,但是并不能解析页面中的JS代码。单页面中的页面渲染,数据的获取,以及路由全交由前端JS代码来执行,所以搜索引擎并不能收藏到网页的所有页面。

但本系统是面向内部人士进行开放,所以并不需要支持搜索引擎友好性,所以才用SPA可以有效提高UX。

6.2 初始化 Angular 项目

Angular 有着诸多特性,最为核心的是:MVVM、模块化、自动化双向数据绑定、语义化标签、依赖注入等等。

要使用Angular 我们必须先初始化项目,网站的前端分为两个部分一个是front端,供普通用户使用;一个是admin端,供管理员使用;

因为Angular.js为模块化开发,所以我们设定根模块为“Medical”,front端和admin端分别继承根模块进行开发,根据路由的地址不同,加载不同的JS文件,从而有效的减少了资源的传输。路由代码如下所示:

  1. // 加载Admin页面
  2. app.route(‘/medi_admin_panel/*’).get(core.rednerAadminIndex);
  3. // 加载Front页面
  4. app.route(‘/*’).get(core.renderIndex);

对于子模块的划分,如图6-1所示:

接下来我们主要以front端为主来讲解如何使用Angular.js。Angular.js一大特点就是依赖注入,依赖注入就是一个模块要使用另外一个模块功能的时候,只要在注册的时候,将另一个模块注入进来即可。

Medical模块为我们的根模块,它主要是用来加载一个Angular.js的核心模块,这样其自模块便可以直接使用。

Medical的配置代码如下所示:

  1. var applicationModuleName = 'Medical';
  2. var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ngMessages', 'ngSanitize', 'ui.router', 'ui.utils', 'ui.bootstrap', 'ngFileUpload', 'ngImgCrop', 'ngStorage'];
  3. // Add a new vertical module
  4. var registerModule = function(moduleName, dependencies) {
  5. // Create angular module
  6. angular.module(moduleName, dependencies || []);
  7. // Add the module to the AngularJS configuration file
  8. angular.module(applicationModuleName).requires.push(moduleName);
  9. };

我们在applicationModuleVenderDependencis中声明了要依赖的模块。然后利用registerModule方法注册根模块即可。

6.3 SPA路由前置

因为我们搭建的是SPA应用,所以我们的路由器要定义在前端,使用angular可以很方便定义路由,比如我们需要定义documents的相关路由,我们创建一个documents.routes模块即可,用documents.routes来管理与documents相关的路由。

在front端,我们需要定义三个关于documents的路由。

  • 路由一:/documents/search?*keyword
  • 路由二:/documents/view/:document-Id
  • 路由三:/documents/not-found

路由一为当我们搜索一个文档时,我们要提供一个关键词,当有结果返回时,我们就用文档列表展示结果,例如我们搜索阿莫西林就可以访问路由http://drugago.com/documents/search/阿莫西林。当我们需要查看编号为1的文档的具体内容时,我们便可以访问http://drugago.com/documents/view/1。如果一个文档没有找到时,就会跳转到/documents/not-found。

定于document路由的部分代码如下(我们只定义来搜索的路由,其他路由省略)。

  1. Angular.module(‘documents.routes’).config([‘$stateProvider’, function($stateProvider) {
  2. $stateProvider.state(‘documents-search’, {
  3. url: ‘/documents/search?*keyword’,
  4. templateUrl: modules/documents/client/views/document-search.client.view.html’,
  5. controller: SearchDocumentsController
  6. });
  7. // 添加更多的路由
  8. });

6.4 Service的实现与数据预加载

Angular.js有一个factory函数,我们可以用它来包装resource服务来生成据源Service,当我们需要利用Ajax动态获取数据的时候,我们可以直接调用resource 的中相应的http方法。

常见的Http方法有Get、Post、Put、Delete等。

定义资源Service的核心代码如下,这里我们只定义了document的Service。

  1. angular.module('core').factory('iDocuments', ['$resource',
  2. function($resource) {
  3. var url = '/api/documents/:id';
  4. return (function() {
  5. var defaults = {
  6. update: {method: 'PUT'},
  7. create: {method: 'POST'},
  8. query: {method: 'GET', isArray: false},
  9. destory: {method: 'DELETE'},
  10. get: {method:'GET'}
  11. };
  12. return $resource(url, {id: '@id'}, defaults);
  13. }());
  14. }
  15. ]);

单页面应用有一个常见的缺点就是当页面加载的时候,是先加载JS文件和模版,然后在去加载数据,但是当数据还没有返回的时候,浏览器就已经改变了路由,此时会显示空白页面,当Ajax数据返回后,内容突然一下全显示出来,这在前端叫闪屏。解决闪屏的方法就是数据预加载,当我们在改变路由之前,先去加载数据,当数据返回后,浏览器才改变路由。从而解决了闪屏的问题,提高了用户友好性。

比如我们在查看一个文档时候,我们先去预加载文档的内容,然后在做页面跳转。要实现数据预加载我们需要在定义路由的时候添加resolve方法。

  1. .state('admin.documents-view', {
  2. url:'/view/:documentId',
  3. templateUrl: 'modules/documents/client/views/admin/documents-view.client.view.html',
  4. controller: 'ViewDocumentsController',
  5. resolve: {
  6. // 预先加载数据
  7. idocumentResolve: ['$stateParams', 'iDocuments', '$q', function($stateParams, iDocuments, $q) {
  8. var defer = $q.defer();
  9. iDocuments.get({ id: $stateParams.documentId}, function(data) {
  10. defer.resolve(data);
  11. },function(err) {
  12. defer.reject(err);
  13. });
  14. return defer.promise;
  15. }]
  16. },
  17. })

在resolve方法中,我们调用了Document Service的get方法获取文档数据,然后直接返回promise对象。

7 结 论

Javascript是一个事件驱动语言,Node利用了这个优点,编写出可扩展性高的服务器。Node采用了一个称为“事件循环(event loop)”的架构,使得编写可扩展性高的服务器变得既容易又安全。提高服务器性能的技巧有多种多样。Node选择了一种既能提高性能,又能减低开发复杂度的架构。这是一个非常重要的特性。并发编程通常很复杂且布满地雷。Node绕过了这些,但仍提供很好的性能。

基于Node.js的医药搜索平台设计与实现采用MEAN栈开发,使用了很多Node.js下的库,比如使用Gulp基于流自动化构建工具,使用Express框架构建web服务端,使用Bookshelf实现数据层ActiveRecord,使用Bluebird.js 优化异步流等。

本系统大致可以划分为如下几个模块,角色权限模块、邮件模块,系统设置模块,用户管理模块,文档管理模块,搜索模块。其中角色权限模块、邮件模块、用户管理模块和系统设置模块都是一般系统中常见的模块,为了实现各个模块以及使各个模块可以很好的协同工作,在代码编写的过程中参考了很多资料,以及学习了很多编程技巧,比如更加的熟练函数式编程,以及更加的熟练Promise异步编程。同时使自己更加的了解Node.js以及web开发。

在整个毕业设计的过程中,从最初的系统模型设计,代码实现,前台页面的设计均有一人完成。整个过程中总结了一大堆理论并转化为自己的知识,不断的学习使自己不断的接近的自己的目标--成为一名全栈工程师。虽然在系统实现的过程中,遇到很多的困难,比如底层数据层的实现,前台页面的设计,角色权限系统的设计和代码的实现,excel的解析和生成,sphinx的安装和配置,如何优化系统性能等。虽然遇到很多问题,但都自己通过查资料一个个解决。通过一个多月的不断学习和编码,最终完成了本系统的开发,并上线使用,可以访问http://www.drugago.com查看。

通过这次毕业设计,我深深的体会到了一个完整的系统从前到后搭建起来是有多么的不易,在解决的一个又一个问题之后,对自己的技术也更加的自信,使自己有能力面对毕业后的工作。

大学,将要在毕业设计中结束,心中多的是无尽的怀念和十分的舍不得。

参考文献

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop . Concurrency modeland Event Loop

[2] http://meanjs.org/ . Open-SourceFull-Stack Solution For MEAN Applications

[3]Marshall K, Pytel C, Yurek J. Introducing active record[J]. Pro Active Record:Databases with Ruby and Rails, 2007: 1-24.

[4] http://bookshelfjs.org/ . bookshelf.js

[5] http://knexjs.org/#Builder .A SQL Query Builder for Javascript

[6] Ferraiolo D, Cugini J,Kuhn D R. Role-based access control (RBAC): Features andmotivations[C]//Proceedings of 11th annual computer security applicationconference. 1995: 241-48.

[7] Halpin T. Object-role modeling (ORM/NIAM)[M]//Handbook onarchitectures of information systems. Springer Berlin Heidelberg, 1998: 81-103.

[8]Halpin T. Object rolemodeling: An overview[J]. white paper,(online at www. orm. net).gadamowmebulia, 2001, 20: 2007.

[9] Keet C M. Part-wholerelations in object-role models[C]//On the Move to Meaningful Internet Systems2006: OTM 2006 Workshops. Springer Berlin Heidelberg, 2006: 1118-1127.

[10] http://bookshelfjs.org/#associations .booshelf associations

[11] Cantelon M, Harter M,Holowaychuk T J, et al. Node. js in Action[M]. Manning, 2014.

[12] Tilkov S, Vinoski S.Node. js: Using javascript to build high-performance network programs[J]. IEEEInternet Computing, 2010, 14(6): 80.

[13] https://www.npmjs.com/package/node-excel . Simple data set export to Excel xlsx file

[14] Suchal J, Návrat P.Full text search engine as scalable k-nearest neighbor recommendationsystem[M]//Artificial Intelligence in Theory and Practice III. Springer BerlinHeidelberg, 2010: 165-173.

[15] Aksyonoff A.Introduction to Search with Sphinx: From installation to relevance tuning[M].” O’Reilly Media, Inc.”, 2011.

[16] https://www.npmjs.com/package/sphinxapi.SphinxSearch Client for NodeJS

[17] Mikowski M S, Powell J C.Single Page Web Applications[J]. B and W, 2013.

[18] http://www.apjs.net/ .angular.js 中文网

[19] 许会元,何利力. NodeJS的异步非阻塞NodeJS研究[J]. 工业控制计算机,2015,03:127-129.

[20] 杨伟超,刘阳,李淑霞. 基于搜索引擎的一站式检索平台设计与实现[J].计算机与现代化,2012,11:220-222.

[21] 王金龙,宋斌,丁锐. Node.js:一种新的Web应用构建技术[J].现代电子技术,2015,06:70-73.

[22] Pasquali S. Mastering Node. js[M]. Packt Publishing Ltd, 2013.

[23] Tilkov S,Vinoski S. Node. js: Using javascript to build high-performance network programs[J]. IEEE Internet Computing, 2010, 14(6): 80.

[24] Ihrig C J.Pro Node. js for developers[M]. Apress, 2013.

[25] Mardan A.Publishing Node. js Modules and Contributing to Open Source[M]//Practical Node.js. Apress, 2014: 261-267.

[26] Yu-yang L I U Q P, Zi-cheng P. The Design and Implementation of BuildingWebsite Internal Search Engine Based on Sphinx [J][J]. MicrocomputerInformation, 2010, 15: 050.

[27] 姚立.IBM云计算平台下NodeJS应用支持环境的设计与实现[D].哈尔滨工业大学,2013.

[28] 袁婷.RESTful Web服务的形式化建模与分析[D].华东师范大学,2015.

[29] 王金龙,宋斌,and 丁锐.”Node. js: 一种新的 Web 应用构建技术.” 现代电子技术 38.6 (2015): 70-73.

[30] 高飞,何利力,and 高金标.”基于Node. JS 内存缓存的 Web 服务性能研究.”工业控制计算机 11 (2015): 047.

上传的附件 cloud_download 毕业设计-程序和文档.7z ( 7.44mb, 3次下载 )
error_outline 下载需要12点积分

keyboard_arrow_left上一篇 : 基于Python与Node.js实现的医疗图像库在线存储与检索平台网站 基于Node.js中间层的微信图书借阅平台网站的设计与实现 : 下一篇keyboard_arrow_right



LeftEar
2018-10-03 22:10:46
基于Node.js的实现的医药文档搜索平台,其中包括用户管理模块、文档管理模块、系统设置模块等多个子模块。并利用Sphinx和MYSQL实现了具有分词功能的多语言文档搜索引擎。前端利用Angular实现了Single Page Application

发送私信

借着别人的话,说着自己的心里话

4
文章数
5
评论数
eject