BỘ GIÁO DỤC VÀ ĐÀO TẠO

VIỆN HÀN LÂM KHOA HỌC VÀ CÔNG NGHỆ VN

HỌC VIỆN KHOA HỌC VÀ CÔNG NGHỆ

N G U Y Ễ N V Ă N T H

Ì N

Nguyễn Văn Thìn

ỨNG DỤNG MÃ NGUỒN MỞ ELASTICSEARCH VÀO HỆ THỐNG TÌM KIẾM DANH BẠ Y TẾ HIỆU QUẢ

H Ệ T H Ố N G T H Ô N G T I N

LUẬN VĂN THẠC SĨ NGÀNH MÁY TÍNH

2 0 2 1

Thành phố Hồ Chí Minh - 2021

BỘ GIÁO DỤC VIỆN HÀN LÂM

VÀ ĐÀO TẠO KHOA HỌC VÀ CÔNG NGHỆ VN

HỌC VIỆN KHOA HỌC VÀ CÔNG NGHỆ

Nguyễn Văn Thìn

ỨNG DỤNG MÃ NGUỒN MỞ ELASTICSEARCH VÀO HỆ

THỐNG TÌM KIẾM DANH BẠ Y TẾ HIỆU QUẢ

Chuyên ngành : Hệ Thống Thông Tin.

Mã số : 8480104

LUẬN VĂN THẠC SĨ NGÀNH MÁY TÍNH

NGƯỜI HƯỚNG DẪN KHOA HỌC: TS TRẦN TRỌNG TOÀN

Thành phố Hồ Chí Minh – 2021

i

LỜI CAM ĐOAN

Tôi cam đoan luận văn “Ứng dụng mã nguồn mở ElasticSearch vào hệ thống

tìm kiếm danh bạ y tế hiệu quả” là công trình nghiên cứu của riêng tôi dưới sự hướng dẫn của Thầy TS Trần Trọng Toàn. Sự gần gũi và nhiệt tình hướng dẫn của thầy là

nguồn động lực rất lớn đối với tôi trong suốt thời gian thực hiện.

Các số liệu, kết quả nêu trong luận văn là trung thực và chưa từng được ai

công bố trong bất kỳ công trình nào khác.

Thành phố Hồ Chí Minh, ngày tháng năm 2021

Học viên thực hiện

Nguyễn Văn Thìn

ii

LỜI CẢM ƠN

Tôi xin gửi lời cảm ơn sâu sắc đến thầy TS. Trần Trọng Toàn đã tận tình hướng dẫn

và giúp đỡ tôi trong suốt quá trình thực hiện luận văn.

Cảm ơn quý thầy cô Khoa Công Nghệ Thông Tin và Viễn Thông, cũng như các Thầy Cô của Học Viện Khoa học và Công Nghệ, quý thầy cô tham gia giảng dạy và truyền

đạt kiến thức cho bản thân tôi trong suốt khóa học 2018 – 2020.

Cho phép tôi gửi lời cảm ơn tới các bạn, đồng nghiệp đã thường xuyên quan tâm,

giúp đỡ, chia sẽ kinh nghiệm trong suốt thời gian học tập, nghiên cứu tại Học viện cũng như trong suốt quá trình thực hiện luận văn.

Tôi xin bày tỏ sự biết ơn sâu sắc đến cha, mẹ, vợ, những người thân trong gia đình đã

luôn ở bên tôi, động viên, dành cho tôi những gì tốt đẹp nhất trong suốt quá trình thực

hiện luận văn này.

Cuối cùng tôi xin chân thành cảm ơn Công ty Cổ phần MediHub đã tạo điều kiện

giúp tôi thực hiện tốt luận văn.

Trân trọng cảm ơn!

iii

MỤC LỤC

LỜI CAM ĐOAN ........................................................................................................ i

LỜI CẢM ƠN ............................................................................................................. ii

MỤC LỤC ................................................................................................................. iii

DANH MỤC VIẾT TẮT ........................................................................................... iv

DANH MỤC CÁC BẢNG ......................................................................................... vi

DANH MỤC CÁC HÌNH MINH HỌA ................................................................... vii

MỞ ĐẦU ..................................................................................................................... 1

CHƯƠNG 1 - TỔNG QUAN VỀ HỆ THỐNG TÌM KIẾM THÔNG TIN ............... 4

1.1 Khái niệm về tìm kiếm thông tin ....................................................................... 4

1.2 Khái niệm về hệ thống tìm kiếm thông tin ........................................................ 6

1.2.1 Khái niệm về hệ thống tìm kiếm thông tin .................................................. 6

1.2.2 Các bộ phận cấu thành hệ thống tìm kiếm thông tin ................................... 6

CHƯƠNG 2 - GIỚI THIỆU BÀI TOÁN VÀ LỰA CHỌN CÔNG NGHỆ .............. 9

2.1 Giới thiệu bài toán ............................................................................................. 9

2.2 Phương pháp giải quyết ..................................................................................... 9

2.3 Tổng quan ElasticSearch ................................................................................. 10

2.3.1 Khái niệm về ElasticSearch ....................................................................... 10

2.3.2 Các khái niệm cần biết trong ElasticSearch .............................................. 13

2.3.3. Analyzers và mô hình truy hồi thông tin của ElasticSearch .................... 21

2.3.4. Query DSL (domain- Specific Language) trong ElasticSearch .............. 29

2.3.5 Mô hình truy hồi thông tin của ElasticSearch ........................................... 36

CHƯƠNG 3. THỰC NGHIỆM XÂY DỰNG WEBSITE TÌM KIẾM DANH BẠ Y TẾ .............................................................................................................................. 45

3.1 Phân tích .......................................................................................................... 45

3.2 Thiết kế ............................................................................................................ 46

3.3 Cài đặt .............................................................................................................. 55

3.4 Giao diện .......................................................................................................... 55

iv

3.4.1. Giao diện cho người sử dụng ................................................................... 55

3.4.2. Giao diện cho người quản trị .................................................................... 64

3.5 Đánh giá và thử nghiệm ................................................................................... 66

3.5.1. Mô hình kiến trúc ứng dụng thử nghiệm .................................................. 66

3.5.2. Kịch bản và kết quả .................................................................................. 67

3.5.3 Đánh giá kết quả nghiên cứu ..................................................................... 70

CHƯƠNG 4. KẾT LUẬN ......................................................................................... 72

DANH MỤC TÀI LIỆU THAM KHẢO .................................................................. 73

v

DANH MỤC CÁC THUẬT NGỮ, CHỮ VIẾT TẮT

Công nghệ thông tin CNTT

Cơ sở dữ liệu CSDL

Máy chủ Server

Tài liệu Document

Chỉ mục Index

ElasticSearch ES

Hồ sơ sức khỏe điện tử EHR

Information Retrieval IR

Phân đoạn Shard

Truy vấn Query

domain- Specific Language DSL

Địa chỉ website URL

Từ khóa tìm kiếm Term

Tăng cường Boost

Application Programming Interface API

RESTful API Một tiêu chuẩn trong việc thiết kế API

Dòng row

bảng table

là 1 thuật ngữ được sử dụng trong mạng máy tính

để mô tả cách thức truyền tin được gửi từ 1 điểm

đến 1 điểm khác Unicast

Liên kết link

vi

DANH MỤC CÁC BẢNG

Bảng 1: Số liệu mapping các từ khóa. ...................................................................... 53

Bảng 2: Kịch bản tìm kiếm. ....................................................................................... 68

vii

DANH MỤC CÁC HÌNH MINH HỌA

Hình 1: Lịch sử hình thành công ty ElasticSearch ................................................... 10

Hình 2: Các tập đoàn sử dụng ElasticSearch ........................................................... 11 Hình 3: Các đối thủ của ElasticSearch ..................................................................... 11

Hình 4: Bảng so sánh các dịch vụ ............................................................................. 12

Hình 5: Hệ thống phân tán của ElasticSearch ......................................................... 13

Hình 6: Các khái niệm cần biết trong ElasticSearch................................................ 13

Hình 7: Index trong ElasticSearch ............................................................................ 14 Hình 8: Sharding trong Index ................................................................................... 15

Hình 9: Primary Shard và Replica Shard ................................................................. 15

Hình 10: quá trình chuyển dữ liệu ............................................................................ 17

Hình 11: Cluster trong ElasticSearch ....................................................................... 18

Hình 12: Ví dụ về sơ đồ cơ sở dữ liệu của Mapping ................................................ 19

Hình 13: Analyzer trong ElasticSearch .................................................................... 21

Hình 14: Kết quả tìm kiếm Match all query .............................................................. 30

Hình 15: Kết quả Match query.................................................................................. 30

Hình 16: Kết quả Match query thêm and .................................................................. 31

Hình 17: Kết quả Match phrase query ...................................................................... 31 Hình 18: Kết quả Match Phrase Prefix Query ......................................................... 32

Hình 19: Kết quả Multi Match Query ....................................................................... 32

Hình 20: Query có các parameters ........................................................................... 33

Hình 21: Query với format ngày ............................................................................... 33

Hình 22: Wildcard Query .......................................................................................... 34

Hình 23: Bool Query ................................................................................................. 34

Hình 24: Fuzzy Query ............................................................................................... 36 Hình 25: B25M .......................................................................................................... 38 Hình 26: BM25 tiệm cận ........................................................................................... 40 Hình 27: BM25 với độ dài trung bình ....................................................................... 41 Hình 28: Mô hình tìm kiếm văn bản tiếng Việt ......................................................... 46 Hình 29: lược đồ về Analyzer ................................................................................... 47

Hình 30: lược đồ cơ sở dữ liệu Danh bạ y tế ............................................................ 53 Hình 31: Mô hình cho người sử dụng ....................................................................... 54

Hình 32: giao diện gợi ý khi nhập từ khóa ............................................................... 56

Hình 33: kết quả tìm kiếm có dấu ............................................................................. 56

Hình 34: kết quả tìm kiếm tiếng Việt không dấu ....................................................... 57

viii

Hình 35: Kết quả tìm kiếm ........................................................................................ 58 Hình 36: Kết quả theo định vị ................................................................................... 59

Hình 37: Tìm kiếm theo chuyên khoa ........................................................................ 61

Hình 38 thông tin chi tiết .......................................................................................... 61

Hình 39: thông tin bản đồ theo địa chỉ của Profile .................................................. 62 Hình 40: nút lưu profile ............................................................................................ 62

Hình 41: hỏi đáp. ...................................................................................................... 63

Hình 42: Thông tin đã lưu, thích ............................................................................... 63

Hình 43: Trang quản trị Admin ................................................................................ 64

Hình 44: Cập nhật dữ liệu mới ................................................................................. 65 Hình 45: Quản trị tài khoản ...................................................................................... 65

Hình 46 Kiến trúc Mô hình thử nghiệm .................................................................... 67

1

MỞ ĐẦU

Hiện nay sức khỏe đang là mối quan tâm hàng đầu của nhiều người. Ngoài

mong muốn được tiếp cận các thông tin sức khỏe chính thống - hữu ích để chăm sóc

bản thân và gia đình tốt hơn, nhu cầu tìm kiếm nơi khám chữa bệnh uy tín, bác sĩ giỏi chuyên môn của người dân cũng rất cao. Để đáp ứng nhu cầu này thì cần có các công

cụ tìm kiếm nhanh, ngoài hỗ trợ người dân tiếp cận các thông tin cần biết, còn giúp

chọn lựa được nơi chăm sóc sức khỏe, bác sĩ tốt nhất thông qua những tính năng

tương tác trên các website trực tuyến.

Song song đó, cùng với sự phát triển của Internet và điện thoại thông minh cho phép người dùng có thể kết nối mạng ở bất cứ đâu, truy cập hồ sơ bác sĩ/cơ sở khám

chữa bệnh một cách dễ dàng để đánh giá, bình luận… điều này giúp tăng độ tin cậy

và chính xác của các thông tin trên. Càng ngày, với sự “góp sức” của nhiều người,

những thông tin ngày càng được hoàn thiện và thực sự hữu ích cho người dùng sau.

Tuy nhiên, trong lĩnh vực y tế, việc phân chia các cơ sở khám chữa bệnh, hồ

sơ bác sĩ về chuyên khoa; dịch vụ khám chữa bệnh, địa chỉ, số điện thoại, thời gian

làm việc… sao cho người dùng có thể tìm kiếm một cách dễ dàng – nhanh chóng –

chính xác nhất thực sự là vấn đề nan giải. Các website về lĩnh vực y tế trong nước

như edoctor.io, bacsi247.net … đã có hỗ trợ thông tin tìm kiếm bác sĩ, phòng khám,

bệnh viện… tuy nhiên việc tìm kiếm tiếng Việt vẫn là vấn đề nan giải và các website này vẫn chưa đáp ứng được.

Gần đây, nhiều thư viện nguồn mở hỗ trợ mạnh việc tìm kiếm thông tin nhanh

như Elastic Search, Solr … Điều đặc biệt hơn cả là việc xuất hiện nhiều mã nguồn

mở xử lý ngôn ngữ tiếng Việt do các kỹ sư CNTT hay các nhà khoa học tại Việt Nam

phát triển như underthesea, vn_tokenizer… đã làm cho việc tìm kiếm dữ liệu tiếng

Việt ngày càng chính xác hơn.

Ngoài ra các Website lớn trên thế giới như eBay [1], Vimeo [2] đã sử dụng ElasticSearch vào việc phát triển hệ thống tìm kiếm của mình, và về lĩnh vực y tế - sức khỏe UCLA Health [3] đã sử dụng ElasticSearch vào hệ thống Hồ sơ sức khỏe điện tử (EHR) để phục vụ cho các Bác sĩ lâm sàng và các nhà nghiên cứu trong việc

tìm kiếm bệnh lý, bao gồm ghi chú lâm sàng, kết quả phòng thí nghiệm, văn bản, lịch sử khám chữa bệnh của bệnh nhân để giúp chẩn đoán bệnh.

Trong nước, cũng có khá nhiều tác giả quan tâm nghiên cứu về lĩnh vực tìm kiếm thông tin như “Hỗ trợ Tìm kiếm Thông tin, thuộc lãnh vực CNTT trên Internet qua

2

từ khóa bằng Tiếng Việt” [4], “Phát Triển hệ truy hồi thông tin tiếng Việt dựa trên mã nguồn mở” [5]. Tuy nhiên chúng ta vẫn chưa thấy một hệ thống tìm kiếm nào về

danh bạ y tế đáp ứng được nhu cầu của người dân hiện nay, và chúng ta vẫn còn phụ

thuộc vào công cụ tìm kiếm Google nên đôi khi phải mất nhiều thời gian mới tìm thấy

được những thông tin mà mình cần.

Tính cấp thiết của đề tài:

Từ những khó khăn như phân tích ở trên, yêu cầu cấp thiết là xây dựng một hệ

thống tìm kiếm Tiếng Việt về danh bạ y tế để giúp người dùng dễ dàng tìm kiếm các

thông tin như: hồ sơ bác sĩ, phòng khám, bệnh viện…đồng thời có thể tiếp cận được

các thông tin hữu ích về sức khỏe chính thống từ các bác sĩ, chuyên gia y tế, và cũng chính họ là người sẽ tương tác với các tính năng sẵn có của hệ thống như bình luận,

đánh giá,… làm tăng độ tin cậy của thông tin cho người dùng sau.

Đối tượng và phạm vi nghiên cứu:

Để xây dựng được hệ thống tìm kiếm Tiếng Việt mang tính cấp thiết như đã đề

cập ở trên, đối tượng nghiên cứu được chọn là:

Nghiên cứu dữ liệu và thông tin y tế với nguồn dữ liệu từ hệ thống tìm kiếm cơ

sở y tế của công ty MediHub.

Nghiên cứu về tìm kiếm thông tin, hệ thống tìm kiếm thông tin và các thành phần

cấu tạo.

Nghiên cứu mã nguồn mở ElasticSearch để ứng dụng vào việc đánh chỉ mục cho

hệ thống tìm kiếm thông tin.

Nghiên cứu Asp.Net Core, C# để xây dựng một hệ thống tìm kiếm thông tin hoàn

chỉnh và các tính năng tương tác trên danh bạ y tế.

Mục tiêu nghiên cứu:

Để thực hiện thành công, luận văn cần nghiên cứu các nội dung sau:

Nghiên cứu, phân tích, thiết kế và triển khai hệ thống tìm kiếm thông tin về danh

bạ y tế.

Nghiên cứu các kỹ thuật về truy hồi thông tin và các công nghệ, nền tảng nổi bậc

về các Search Engine, trong đó trọng tâm nghiên cứu ElasticSearch để xây dựng một ứng dụng tìm kiếm danh bạ y tế.

3

Sản phẩm sẽ giúp người dùng dễ dàng tìm kiếm các thông tin, danh bạ y tế mà mình cần và tạo các tính năng tương tính tác trên hệ thống để tăng độ tin cậy cho

người dùng sau.

Phương pháp nghiên cứu:

Để đạt được mục tiêu đặt ra, luận văn sử dụng các phương pháp sau:

Thu thập, phân tích và chuẩn hóa dữ liệu danh bạ y tế đã có.

Đánh giá và chọn lọc các thuật toán, tính năng trong ElasticSearch để làm cho

phần tìm kiếm dữ liệu.

Phân tích và thiết kế hệ thống Tìm kiếm thông tin, tích hợp ElasticSearch để trả

kết quả tìm kiếm Tiếng Việt chính xác hơn, đồng thời cũng xây dựng các tính năng tiện ích giúp người dùng dễ dàng tìm kiếm và tương tác trên Website.

Để thực hiện mục tiêu đã đề ra chúng tôi bố cục của luận văn như sau:

Chương 1: Nghiên cứu tổng quan về hệ thống tìm kiếm thông tin, các thành

phần và nguyên lý hoạt động của hệ thống tìm kiếm thông tin.

Chương 2: Giới thiệu bài toán và lựa chọn công nghệ.

Chương 3: Trên cơ sở nghiên cứu về Hệ thống tìm kiếm thông tin và mã nguồn

mở ElasticSearch, tôi đề xuất xây dựng thử nghiệm hệ thống tìm kiếm Danh bạ y tế

với hai thành phần chính là: Tạo chỉ mục và Tìm kiếm.

4

CHƯƠNG 1 - TỔNG QUAN VỀ HỆ THỐNG TÌM KIẾM THÔNG TIN

1.1 Khái niệm về tìm kiếm thông tin

Ngày nay sự phát triển mạnh mẽ và phổ biến của công nghệ thông tin, dữ liệu,

văn bản có đến hàng tỉ trang website, song song đó, nhu cầu khai thác thông tin này

để phục vụ công việc là nhu cầu cần thiết và cấp bách. Bất cứ hệ thống nào sau khi

xây dựng đều đòi hỏi có hỗ trợ chức năng tìm kiếm, tuy nhiên đối với việc tìm kiếm

nội dung trong văn bản lại là vấn đề lớn. Có những công cụ hỗ trợ tìm kiếm thông tin và hoạt động hiệu quả như Google, Bing, Yahoo, Baidu, Yandex, DuckDuckGo…

tuy nhiên, vì đây là những sản phẩm đã được thương mại hóa như bài báo khoa học

[6] cũng đã đề cập, nên chúng ta không thể biết được các kỹ thuật triển khai bên dưới

cũng như công nghệ ứng dụng của chúng.

Sau đây sẽ là định nghĩa về tìm kiếm thông tin của một số tác giả [6]

Khái niệm [6]: Tìm kiếm thông tin (Information Retrieval – IR) là tìm kiếm tài

nguyên (thường là các tài liệu - documents) trên một tập lớn các dữ liệu phi cấu trúc (thường là văn bản – text) được lưu trữ trên các máy tính nhằm thỏa mãn nhu cầu về

thông tin [6].

Mục đích cuối cùng trong việc tìm kiếm là đưa ra thông tin sao cho đúng với

nhu cầu tìm kiếm của người dùng, do đó cần phải có cách lưu trữ thông tin và tổ chức

lại dữ liệu sao cho dễ dàng tìm kiếm và truy xuất nhanh và hiệu quả nhất. Trong việc

tìm kiếm có 2 phần chính:

• Các kỹ thuật để biễu diễn thông tin: bao gồm cách biểu diễn thông tin nào cần thiết cho việc truy vấn (query) từ nhu cầu người dùng, và các

thông tin nào được chọn (văn bản, tài liệu).

• Các phương pháp so sánh khi biễu diễn thông tin, nhằm mục đích là để kiểm tra so sánh tính toán dữ liệu, sao cho cuối cùng kết quả tính toán trả về phải giống với kết quả được mong đợi khi người dùng thực hiện

câu truy vấn.

Việc đánh giá mức độ xử lý khi trả về kết quả trong việc tìm kiếm thông tin

trong một tập tài liệu và câu truy vấn cho tài liệu đó dựa vào các cách sau:

5

• Độ chính xác (Precision): được đo bởi tỉ lệ của tài liệu trả về chính xác trên tổng tài liệu nhận được [6].

Độ chính xác = (1) {tài liệu liên quan}  {tài liệu nhận được} {tài liệu nhận được}

• Độ bao phủ (Recall): tỉ lệ tài liệu trả về chính xác trên tổng tài liệu có liên quan [6].

Độ bao phủ = (2) {tài liệu liên quan}  {tài liệu nhận được} {tài liệu liên quan}

• Kết quả sai (fall - out): tỉ lệ tài liệu không có liên quan trả về trên tổng tài liệu không liên quan [6].

Kết quả sai = (3) {tài liệu không liên quan}  {tài liệu nhận được} {tài liệu không liên quan}

Ví dụ [6]: trong tập 1000 tài liệu được sử dụng cho tìm kiếm với 200 tài liệu

liên quan đến thông tin “tin học”, một hệ thống tìm kiếm thông tin “tin học” trả về

{200} ∩ {150}

được 150 tài liệu, trong đó có 130 tài liệu chính xác. Khi đó:

{150}

{200} ∩ {150}

= ≈ 87% Độ chính xác = {130} {150}

{200}

{800} ∩ {150}

Độ bao phủ = = ≈ 65% {130} {200}

{800}

Kết quả sai = = ≈ 2.5% {20} {800}

6

1.2 Khái niệm về hệ thống tìm kiếm thông tin

1.2.1 Khái niệm về hệ thống tìm kiếm thông tin [7]

Theo Kowalski [8] đã định nghĩa về hệ thống tìm kiếm thông tin như sau:

“Hệ thống truy tìm thông tin là một hệ thống có khả năng lưu trữ, truy tìm và

duy trì thông tin. Thông tin trong các trường hợp này có thể bao gồm văn bản (bao gồm cả số liệu ngày tháng), hình ảnh, âm thanh, video và những đối tượng đa phương

tiện khác.”

Gerard Salton [9, 10]: “Hệ thống tìm kiếm thông tin là một hệ thống thông tin

được sử dụng để lưu trữ các mục thông tin cần được xử lý, tìm kiếm, truy xuất và trả về cho người dùng với các yêu cầu khác nhau. Việc truy tìm những thông tin phụ

thuộc vào tổ chức thông tin được lưu trữ và các phương pháp tìm kiếm nhanh chóng

từ các yêu cầu, được đánh giá bằng cách so sánh các giá trị của các thuộc tính đối với

thông tin được lưu trữ và các yêu cầu về thông tin.”

Do đó ta có thể tóm lại đơn giản hơn: hệ thống tìm kiếm thông tin là một hệ

thống thông tin dùng để lưu trữ, xử lý, tìm kiếm và đưa ra các thông tin cho người sử

dụng. Hệ thống tìm kiếm thông tin thường thao tác các dữ liệu dạng văn bản và không

có giới hạn về nội dung và thông tin trong văn bản.

1.2.2 Các bộ phận cấu thành hệ thống tìm kiếm thông tin [7]

1.2.2.1 Bộ phận thu thập thông tin - Robot

Bộ phận thu thập thông tin [11] là một chương trình chạy tự động dùng duyệt qua các cấu trúc siêu liên kết (hyperlink) để đi thu thập tài liệu, và một cách đệ quy

nó sẽ nhận về tài liệu có liên kết với tài liệu này, nó sẽ quét để trích xuất toàn bộ

thông tin của website đó từ tiêu đề, hình ảnh đến từ khóa, các liên kết (link) đến trang khác ... Dữ liệu sẽ được quét theo thứ tự từ trên xuống dưới từ trái qua phải. Thực tế, bộ phận thu thập dữ liệu sẽ có những con Robot thu thập dữ liệu, được gọi là spider, những spider này sẽ truy cập từng trang web, thu thập dữ liệu trên trang đó một cách âm thầm và nhanh chóng. Sau đó nó lấy dữ liệu và lưu trữ các nội dung từ các trang

web trên Internet.

Bộ phận này có các thành phần chính: một thành phần để theo dõi và phát hiện các URL mới, hoặc các URL đã thay đổi. Một thành phần dùng để đọc nội dung tài

liệu của tất cả các trang web một cách đệ quy từ một tập các URL đã có, sau đó nó sẽ

7

phân tích tài liệu, trích xuất nội dung tài liệu dưới các định dạng như html, pdf, excel… và lưu trữ về cơ sở dữ liệu thu thập.

1.2.2.2 Bộ phận lập chỉ mục - Index

Hệ thống lập chỉ mục hay còn gọi là hệ thống phân tích và xử lý dữ liệu, thực

hiện việc phân tích và tối ưu hóa tốc độ và hiệu suất trong việc tìm kiếm các tài liệu

có liên quan cho một truy vấn tìm kiếm [11]. Với các từ khoá nhập vào của người

dùng nó có thể chỉ rõ các từ khoá nào xuất hiện ở trang nào, địa chỉ nào. Nếu không

có chỉ mục, công cụ tìm kiếm sẽ quét tất cả các tài liệu trong cơ sở dữ liệu lưu trữ,

đòi hỏi thời gian và tài nguyên tính toán đáng kể. Ví dụ [12] như Google là máy tìm

kiếm phổ biến nhất hiện nay, được đồng sáng chế bởi Lary Page và Sergey Brin năm

1997, đi vào hoạt động từ năm 1998. Google hoạt động dựa vào lập trình hệ thống PageRank (bằng sáng chế năm 1998) và là Search Engine hiện đại nhất ngày nay.

Trung bình, hệ thống PageRank xử lý hơn 3 tỷ truy vấn mỗi ngày, và hàng tỷ thông

tin được xử lý, cập nhật vào hệ thống cơ sở dữ liệu của Google. Với tốc độ xử lý ưu

việt, và luôn phát triển, đổi mới với những thuật toán chống spam, thao túng kết quả

tìm kiếm. Google luôn mong muốn mang đến những thông tin hữu ích và trải nghiệm

tốt nhất cho người dùng trên toàn thế giới.

1.2.2.3 Bộ phận tìm kiếm thông tin và Search Engine

Bộ phận này chịu trách nhiệm tìm kiếm các tài liệu từ yêu cầu của người sử

dụng, sau đó trả về danh sách các tài liệu chính xác với yêu cầu nhất, do số lượng các

trang web rất lớn và thông thường người dùng chỉ đưa đưa vào một vài từ khóa trong

câu truy vấn nên tập kết quả thường rất lớn. Tiền xử lý khoá tìm kiếm, thực hiện phân

tích từ khoá tìm kiếm, xử lý các toán tử tìm kiếm cơ bản (AND, OR, NOT,...), xử lý

tìm kiếm chính xác và xây dựng câu truy vấn dữ liệu. Vì vậy bộ xếp hạng (Ranking)

có nhiệm vụ sắp xếp các tài liệu này theo mức độ hợp lệ với yêu cầu tìm kiếm và hiển thị kết quả cho người sử dụng [5].

Search Engine là cụm từ dùng chỉ toàn bộ hệ thống bao gồm bộ thu thập thông tin, bộ lập chỉ mục và bộ tìm kiếm thông tin [13]. Các bộ phận này hoạt động liên tục từ lúc khởi động hệ thống, chúng phụ thuộc lẫn nhau về mặt dữ liệu nhưng độc lập với nhau về mặt hoạt động.

Nguyên lý hoạt động của Search Engine:

Search Engine điều khiển các robot đi thu thập thông tin trên mạng thông qua các siêu liên kết (hyperlink). Khi các robot phát hiện ra một website mới, nó gởi tài liệu (nội dung trong web page) về cho máy chủ (Server) chính để tạo cơ sở dữ liệu

8

chỉ mục phục vụ cho nhu cầu tìm kiếm thông tin [12, 13]. Bởi vì thông tin trên mạng luôn thay đổi nên các robot phải liên tục cập nhật các website cũ. Mật độ cập nhật

phụ thuộc vào từng hệ thống Search Engine về cách cấu hình thời gian cập nhật. Khi

Search Engine nhận câu truy vấn từ người dùng, nó sẽ tiến hành phân tích, tìm trong

cơ sở dữ liệu chỉ mục và trả về những tài liệu thoả yêu cầu.

9

CHƯƠNG 2 - GIỚI THIỆU BÀI TOÁN VÀ LỰA CHỌN CÔNG NGHỆ

2.1 Giới thiệu bài toán

Từ những khó khăn trong việc tìm kiếm tiếng Việt đã được nêu ở phần mở

đầu, cũng như việc tạo ra một hệ thống tìm kiếm thông tin về lĩnh vực y tế để đáp

ứng các nhu cầu tìm kiếm của người dùng. Bài toán: “Ứng dụng mã nguồn mở

ElasticSearch vào hệ thống tìm kiếm danh bạ y tế hiệu quả” sẽ giải quyết các vấn

đề sau:

- Xây dựng một hệ thống tìm kiếm Tiếng Việt về danh bạ y tế để giúp người dùng dễ dàng tìm kiếm các thông tin như: hồ sơ bác sĩ, phòng khám, bệnh

viện…

- Đồng thời người dùng có thể tiếp cận được các thông tin hữu ích về sức khỏe chính thống từ các bác sĩ, chuyên gia y tế, và cũng chính họ là người

sẽ tương tác với các tính năng sẵn có của hệ thống như bình luận, đánh

giá,…

- Tăng độ tin cậy của thông tin cho người dùng sau.

Luận văn sử dụng thư viện mã nguồn mở ElasticSearch cho phần lập chỉ mục

và tìm kiếm, kết hợp với ngôn ngữ lập trình Web như Asp.Net Core để xây dựng một

hệ thống tìm kiếm thông tin.

2.2 Phương pháp giải quyết

Để giải quyết được bài toán đã nêu bên trên, chúng tôi đã lựa chọn các phương

pháp sau:

- Sử dụng mã nguồn mở ElasticSearch cho việc đánh lập chỉ mục. - Nghiên cứu tổng quan về ElasticSearch và kiến trúc của nó để cài đặt và

cấu hình hệ thống phù hợp.

- Nghiên cứu Plugin “Vietnamese Analysis Plugin for Elasticsearch” của tác giả Duy Đỗ [14] để phân tích và tìm kiếm với dữ liệu text là tiếng Việt.

Trong plugin tác giả cũng đã kế thừa và sử dụng lại bộ công cụ tách từ tiếng Việt “Lê Hồng Phương”, công cụ này sử dụng từ điển và ngram, trong đó mô hình ngram được huấn luyện để sử dụng treebank tiếng Việt với độ chính xác 97% [15]

10

- Nghiên cứu về Analyzer và mô hình truy hồi thông tin của ElasticSearch, sử dụng các Analyzer có sẵn của ElasticSearch hoặc tạo ra các analyzer

mới để tách từ và cấu trúc lại dữ liệu phục vụ cho việc tìm kiếm nhanh và

hiệu quả hơn. Ngoài ra tài liệu [16] cũng đã đề cập sử dụng Analyzer của

ElasticSearch cho việc truy vấn mở rộng (Query Expansion) cũng như tìm kiếm theo ngữ nghĩa (semantic search).

- Nghiên cứu về các loại truy vấn trong ElasticSearch để lựa chọn các loại

truy vấn phù hợp cho việc tìm kiếm dữ liệu.

- Nghiên cứu về truy vấn Geo_Point và cách tổ chức dữ liệu để phục vụ cho việc tìm kiếm dữ liệu theo tọa độ và đưa ra các kết quả tìm kiếm gần với vị trí người dùng.

2.3 Tổng quan ElasticSearch

Trong phần này sẽ trình bày tổng quan về mã nguồn mở ElasticSeach và các tính

năng của ElasticSeach dựa trên các tài liệu [17, 18, 19] .

2.3.1 Khái niệm về ElasticSearch

ElasticSearch là một công cụ tìm kiếm (Search Engine) dựa trên nền

tảng Apache Lucene. Nó cung cấp một bộ máy tìm kiếm dạng phân tán, có đầy đủ

công cụ với một giao diện web HTTP có hỗ trợ dữ liệu JSON. ElasticSearch được

phát triển bằng Java và được phát hành dạng nguồn mở theo giấy phép Apache.

Tiền thân của ElasticSearch là Compas được Shay Banon tạo ra vào 2004 và

đến 2/2010 Shay Banon cho ra phiên bản đầu tiên. Công ty Elasticsearch được thành

Hình 1: Lịch sử hình thành công ty ElasticSearch

lập vào năm 2012 và vào tháng 5 năm 2015 thì đổi tên thành công ty Elastic.

11

Hình 2: Các tập đoàn sử dụng ElasticSearch

Các tập đoàn đang dùng ElasticSearch:

Hình 3: Các đối thủ của ElasticSearch

Các đối thủ hiện nay:

12

Hình 4: Bảng so sánh các dịch vụ

Bảng so sánh các dịch vụ:

Đặc điểm chính của ElasticSearch:

• Dữ liệu tìm kiếm và chỉ mục

• Hoạt động như 1 web server, có khả năng tìm kiếm và đưa ra kết quả

nhanh chóng (near realtime) thông qua cơ chế RESTful API

• Có khả năng phân tích và thống kê dữ liệu thông qua việc sử dụng công

cụ Kibana.

• Chạy trên máy chủ (server) riêng và đồng thời giao tiếp thông qua RESTful, do đó ElasticSearch không phụ thuộc vào máy trạm (client)

phát triển bằng bất kỳ ngôn ngữ nào. Nên ta rất dễ dàng tích hợp

ElasticSearch vào hệ thống của mình. Chúng ta chỉ cần gửi yêu cầu qua giao thức http là ElasticSearch sẽ trả về kết quả theo yêu cầu.

• ElasticSearch là một hệ thống phân tán và có khả năng mở rộng theo chiều ngang (Horizontal scalability) rất tốt. Nếu ta chỉ cần tăng thêm số lượng nút (node) cho nó là nó tự động mở rộng cho hệ thống của mình.

Hình 5: Hệ thống phân tán của ElasticSearch

13

• Dễ dàng khởi động và chạy

2.3.2 Các khái niệm cần biết trong ElasticSearch

Hình 6: Các khái niệm cần biết trong ElasticSearch

2.3.2.1. Document (tài liệu)

Document (tài liệu) là một đối tượng dạng JSON với một số dữ liệu. Đây là đơn vị nhỏ nhất để lưu trữ dữ liệu trong ElasticSearch. Document giống như row của table trong cơ sở dữ liệu quan hệ.

14

Hình 7: Index trong ElasticSearch [19]

2.3.2.2. Index (Chỉ mục)

Index ở đây không phải là chỉ số mà là một tập hợp các document, nó tương

đương với khái niệm một cơ sở dữ liệu. Trong ElasticSearch, sử dụng một cấu trúc

được gọi là inverted index. Nó được thiết kế để cho phép tìm kiếm full-text search.

2.3.2.3. Shard - Phân đoạn

(phân đoạn) là đối tượng của Lucene, là tập con

• Shard

các document của 1 Index. Một Index có thể được chia thành nhiều Shard.

• Mỗi Node bao gồm nhiều Shard. Chính vì thế Shard mà là đối tượng nhỏ nhất, hoạt động ở mức thấp nhất và đóng vai trò trong việc lưu trữ

dữ liệu.

Hình 8: Sharding trong Index [19]

15

• ElasticSearch đã hỗ trợ toàn bộ việc giao tiếp cũng như tự động thay đổi các Shard khi cần thiết, do đo chúng ta gần như không bao giờ làm

việc trực tiếp với các Shard.

Hình 9: Primary Shard và Replica Shard

• Có 2 loại Shard là: Primary Shard và Replica Shard.

16

Primary Shard:

• Primary Shard là sẽ lưu trữ dữ liệu và đánh index. Sau khi đánh xong

dữ liệu sẽ được vận chuyển tới các Replica Shard.

• Mặc định của ElasticSearch là mỗi index sẽ có 5 Primary shard và với

mỗi Primary shard thì sẽ đi kèm với 1 Replica Shard.

Replica Shard:

• Replica Shard đúng như cái tên của nó, nó là nơi lưu trữ dữ liệu nhân

bản của Primary Shard.

• Replica Shard có vai trò đảm bảo tính toàn vẹn của dữ liệu khi Primary

Shard xãy ra vấn đề.

• Ngoài ra Replica Shard còn có thể giúp tăng cường tốc độ tìm kiếm vì chúng ta có thể cấu hình số lượng Replica Shard nhiều hơn mặc định

của ElasticSearch.

• ElasticSearch sử dụng công thức sau để chọn shard cho việc lưu trữ.

shard = hash(routing) % number_of_primary_shards

Công thức tính Shard:

Hash: là một hàm tính toán cố định của ElasticSearch, routing là 1 đoạn text

duy nhất theo document, nó thường là _id của document đó.

Number_of_primary_shard: là số lượng của primary shard trong cluster.

Giá trị Shard sẽ đi kèm với document, nó dùng để xác định Shard nào sẽ lưu trong document nào và đồng thời dùng để cho routing, vì hệ thống sẽ tìm document

theo id của nó. Và đây cũng là lý do chúng ta không nên thay đổi số lượng Primary Shard, nếu không thì công thức trên sẽ không cho kết quả như ban đầu.

Quá trình lưu dữ liệu:

Hình 10: quá trình chuyển dữ liệu [17]

17

2.3.2.4. Node (Nút)

Là một server duy nhất, là một phần của cluster, là trung tâm hoạt động của

ElasticSearch, là nơi lưu trữ dữ liệu, tham gia vào chức năng lập chỉ mục và thực hiện các thao tác tìm kiếm.

Tên của Node rất quan trọng trong việc xác định nút này thuộc cụm nào trong

hệ thống ElasticSearch. Mỗi node được định danh bằng 1 tên duy nhất (unique name).

Việc đặt tên được tiến hành khi thiết lập, có thể tự định danh cho Node của server

mình. Tất cả các Node đều biết tất cả các Node khác của Cluster và có thể chuyển

tiếp yêu cầu từ client đến Node thích hợp. Ngoài ra, mỗi Node phục vụ một hoặc

nhiều mục đích:

• Master-eligible node: một Node có node.master (nút chính) được đặt giá trị bằng True làm mặc định, điều đó làm cho nó đủ điều kiện để được chọn làm Node chính, và điều khiển Cluster.

• Data node: một Node có node.data được đặt giá trị bằng True làm mặc định. Các Node dữ liệu có chức năng chứa dữ liệu và thực hiện các hoạt động liên quan đến dữ liệu như thêm, xóa, sửa, tìm kiếm và tổng hợp.

• Node Ingest: được sử dụng khi ta cần chạy lại (pre-process) các Document trước khi chúng được index. Ingest node sẽ can thiệp vào giữa quá trình xử lý cho số lượng lớn dữ liệu (bulk) và index, thực hiện các phép biến đổi, sau đó truyền kết quả trở lại index/bulk api. Một Node có node.ingest được đặt giá trị bằng True làm mặc định. Các Node

18

Ingest có thể áp dụng một đường dẫn nhập vào tài liệu để chuyển đổi và làm phong phú tài liệu trước khi lập chỉ mục.

2.3.2.5. Cluster (Cụm)

Là tập hợp các Node hoạt động cùng với nhau, cùng nắm giữ toàn bộ dữ liệu,

cung cấp khả năng lập chỉ mục và tìm kiếm liên kết giữa các Node với thông qua tên

của Cluster (cluster name). Chính vì thế Cluster sẽ được xác định bằng 1 tên duy nhất

Hình 11: Cluster trong ElasticSearch

(unique name). Việc đặt tên cho các Cluster trùng tên sẽ gây ra lỗi cho các Node vì vậy khi cài đặt chúng ta cần lưu ý về vấn đề này.

Mỗi Cluster có một Node chính (master), được lựa chọn một cách tự động và

có thể thay thế nếu sự cố xãy ra. Một Cluster có thể gồm 1 hoặc nhiều Node. Các Node có thể hoạt động trên cùng 1 Server. Tuy nhiên trong thực tế, một Cluster sẽ gồm nhiều Node hoạt động trên các Server khác nhau để đảm bảo nếu 1 Server gặp sự cố thì Server khác (Node khác) vẫn có thể hoạt động bình thường và đảm bảo sự ổn định cho hệ thống tìm kiếm. Các Node có thể tìm thấy nhau để hoạt động trên

cùng 1 Cluster thông qua giao thức Unicast.

Chức năng chính của Cluster đó chính là quyết định xem Shard nào được phân

bổ cho Node nào và khi nào thì di chuyển các Cluster để tạo cân bằng lại Cluster.

19

2.3.2.6. Mapping

Mapping là quá trình định nghĩa làm thế nào một Document và các trường dữ liệu của nó được lưu trữ và đánh Index. Ví dụ ta sử dụng Mapping trong việc để định nghĩa như sau:

• String fields nào được sử dụng như là full-text fields

• Fields nào chứa numbers, dates hay geolocations

• Liệu giá trị của tất cả các trường dữ liệu trong document được đánh

index catch-all

• Format của các date field

• Các custom rules để điều khiển mapping

• Mapping Type: mỗi một index có một hoặc nhiều mapping type, chúng được sử dụng để chia các document trong một index thành các nhóm

logic (logical groups). Ví dụ có index tên là my_index dùng để lưu trữ

cả thông tin user và blogpost. User có thể được lưu trong user type và blog posts trong một blogpost type

Ví dụ về một mapping trong ElasticSearch như sau:

Hình 12: Ví dụ về sơ đồ cơ sở dữ liệu của Mapping

Ta có sơ đồ quan hệ giữa 2 table user và blogpost

Ta thực hiện trên ElasticSearch như sau, thông qua cơ chế RESTfull:

PUT my_index

{

20

"mappings": {

"user": {

"_all": { "enabled": false },

"properties": {

"title": { "type": "text" },

"name": { "type": "text" },

"age": { "type": "integer" }

}

},

"blogpost": {

"_all": { "enabled": false },

"properties": {

"title": { "type": "text" },

"body": { "type": "text" },

"user_id": {

"type": "keyword"

},

"created": {

"type": "date",

"format": "strict_date_optional_time||epoch_millis"

}

}

}

21

}

}

2.3.3. Analyzers và mô hình truy hồi thông tin của ElasticSearch

2.3.3.1. Analyzer

Analyzer là một công cụ của ElasticSearch trong việc tách từ và cấu trúc dữ

liệu giúp cho việc tìm kiếm văn bản cho các ngôn ngữ khác nhau. Trong ElasticSearch

đã có hỗ trợ sẵn khá nhiều analyzer cho các ngôn ngữ khác nhau, tuy nhiên với ngôn ngữ Tiếng Việt thì chúng ta cần phải cài thêm plugin mới sử dụng được (vi_analyzer

của tác giả Duy Đỗ [14], hiện tại có phiên bản 7.3.1).

ElasticSearch cung cấp cách để tùy chỉnh cách mọi thứ được lập chỉ mục với

các trình phân tích của index analysis module. Analyzers là quá trình phân tích và lập

chỉ mục dữ liệu. Mỗi analyzer bao gồm:

• 0 or more CharFilters

• 1 Tokenizer

• 0 or more TokenFilters Tokenizers được sử dụng để tách một chuỗi

Hình 13: Analyzer trong ElasticSearch

thành các dòng mã riêng (stream of tokens).

22

"Our goal at Tryolabs is to help Startups"

-> Tokenizer ->

["Our", "goal", "at", Tryolabs", "is", "to", "help", "Startups"]

Ví dụ: Một Tokenizer cơ bản sẽ thực hiện các thao tác sau:

Tokenizers gồm tokenizer cơ bản và có thể sửa đổi, xóa chúng hoặc

thêm những tokenizer mới. Ví dụ để đặt tên sao cho có nhiều ý nghĩa nhất có thể, một

TokenFilter có thể áp dụng stemming (chuyển đổi các token về từ gốc theo ngữ pháp), loại bỏ các từ không có nghĩa (stop_words), thêm từ đồng nghĩa (Synonyms).

CharFilters được sử dụng để xử lý trước các ký tự trước khi được gửi

tới Tokenizers.

ElasticSearch cung cấp rất nhiều Tokenizers, TokenFilters và chúng ta có thể

tạo các tùy chỉnh và cài đặt chúng dưới dạng plugin.

• Standard Analyzer: chia văn bản thành các thuật ngữ về ranh giới từ,

Các loại cấu hình Analyzer trong ElasticSearch:

như được xác định bởi thuật toán phân đoạn văn bản Unicode. Nó loại

bỏ hầu hết các dấu câu, chuyển tất cả các ký tự thành chữ thường

(lowercases) của các từ khóa và cũng hỗ trợ loại bỏ stop_words (các từ

không liên quan đến nội dung và ý nghĩa của văn bản: như a, an, many,

the….).

• Simple Analyzer: chia văn bản thành các thuật ngữ bất cứ khi nào nó gặp một ký tự không phải là một chữ cái. Nó sẽ chuyển các ký tự thành chữ thường tất cả từ khóa.

• Whitespace Analyzer: chia văn bản thành các thuật ngữ bất cứ khi nào nó gặp bất kỳ ký tự khoảng trắng nào. Nó sẽ không chuyển các ký tự thành chữ thường như Simple Analyzer.

• Stop Analyzer: giống như Simple Analyzer nhưng có thêm tính năng

loại bỏ stop_words

• Keyword Analyzer: chấp nhận bất kỳ văn bản nào và đưa ra chính xác

văn bản như 1 thuật ngữ.

• Pattern Analyzer: thường dùng trong việc kiểm tra số điện thoại hoặc

Email, domain... Hỗ trợ cả lower-casing and stop words.

23

• Language Analyzers: cung cấp nhiều phân tích ngôn ngữ cụ thể như

tiếng Anh, tiếng Pháp…

• Fingerprint Analyzer: là sự phân tích chuyên dụng tạo ra dấu vân tay

có thể được sử dụng để phát hiện trùng lặp.

Chúng ta có thể điều chỉnh Analyzers (custom Analyzers): nếu không tìm thấy

sự phân tích nào phù hợp bên trên thì chúng ta cũng có thể tạo ra một bộ phân tích tùy chỉnh kết hợp character filters, tokenizer, và token filters.

2.3.3.2. Cách sử dụng và Kiểm tra Analyzer

Để sử dụng các kết hợp khác nhau của Tokenizers và TokenFilters, chúng ta

cần tạo một Analyzer trong index settings và sau đó sử dụng nó trong mapping.

Ví dụ, giả sử chúng ta muốn một Analyzer để tokenizer dưới dạng tiêu chuẩn,

và áp dụng bộ lọc lowercase filter và stemming như sau:

• Tạo Index:

PUT /news

{

"settings" : {

"index" : {

"number_of_shards" : 3,

"number_of_replicas" : 2

}

}

}

Cách sử dụng Kibana:

24

PUT my_index

{

"settings": {

"analysis": {

"analyzer": {

"std_folded": {

"type": "custom",

"tokenizer": "standard",

"filter": [

"lowercase",

"asciifolding"

]

}

}

}

},

"mappings": {

"my_type": {

"properties": {

"my_text": {

"type": "text",

"analyzer": "std_folded"

• Sau đó cài đặt:

25

}

}

}

}

}

Ở trên, chúng ta có hai có 2 bước cài đặt và mapping. Tiếp theo ta tạo ra

{

"custom_english_stemmer": {

"type": "stemmer",

"name": "english"

}

}

{

"analyzer": {

"custom_lowercase_stemmed": {

"tokenizer": "standard",

"filter": [

"lowercase",

"custom_english_stemmer"

]

}

}

custom Analyzer như sau:

26

}

Ở đây chúng ta đặt tên cho analyzer là custom_lowercase_stemmed nhưng chúng ta có thể đặt bất kỳ tên nào. Trong ví dụ này, chúng ta đang sử dụng tokenizer

“standard” và lowercase là bộ lọc được cung cấp bởi ElasticSearch nên không cần

cấu hình thêm nữa và custom_english_stemmer là cái mà chúng ta đã chỉnh sửa.

Thứ tự của danh sách là quan trọng vì nó sẽ là thứ tự các tokens được xử lý

trong index.

Cuối cùng, chúng ta có thể sử dụng analyzer mới được tạo ra này

{

"mappings": {

"test": {

"properties": {

"text": {

"type": "string",

"analyzer": "custom_lowercase_stemmed"

}

}

}

}

}

trong mappings.

Kiểm tra Analyzer, trong phần này sau khi đã tạo ra vi_analyzer dựa trên

GET danhba_yte/_analyze

Plugin của tác giả Duy Đỗ, ta kiểm tra kết quả của nó như sau:

27

"analyzer": "vi_analyzer",

"text": "Bác sĩ Phương chuyên khoa tai mũi họng"

}

{

{

"tokens" : [

{

"token" : "bác sĩ",

"start_offset" : 0,

"end_offset" : 6,

"type" : "",

"position" : 0

},

{

"token" : "phương",

"start_offset" : 7,

"end_offset" : 13,

"type" : "",

"position" : 1

},

{

"token" : "chuyên khoa",

Kết quả truy vấn:

28

"end_offset" : 25,

"type" : "",

"position" : 2

},

{

"token" : "tai",

"start_offset" : 26,

"end_offset" : 29,

"type" : "",

"position" : 3

},

{

"token" : "mũi",

"start_offset" : 30,

"end_offset" : 33,

"type" : "",

"position" : 4

},

{

"token" : "họng",

"start_offset" : 34,

"end_offset" : 38,

"type" : "",

"start_offset" : 14,

29

}

]

}

"position" : 5

2.3.4. Query DSL (domain- Specific Language) trong ElasticSearch

Elasticsearch cung cấp đầy đủ Query DSL dựa trên JSON để định nghĩa truy

vấn. Có 2 loại:

Câu truy vấn đơn: tìm kiếm một giá trị cụ thể trong một trường (field) cụ

thể. Gồm các câu: match, term, range.

Câu truy vấn kép: bao gồm nhiều câu truy vấn đơn cùng các truy vấn kép,

được sử dụng kết hợp theo một logic hợp lý. Ví dụ: bool, dis_max.

2.3.4.1 Match all query

GET news/_search

{

"query": {

"match_all": {}

}

}

Câu truy vấn đơn giản nhất, phù hợp với tất cả các tài liệu.

30

Hình 14: Kết quả tìm kiếm Match all query

Kết quả

2.3.4.2 Full text queries

Loại truy vấn full text cấp cao này thường được sử dụng cho các trường full

text như nội dung email. Các trường này thường được phân tích từ trước, và có các

loại phân tích (analyzer) cho mỗi loại field.

• Match query: truy vấn chuẩn để thực hiện full text query. Bao gồm truy vấn kết hợp và truy vấn cụm từ hoặc gần đúng. Match query chấp

Hình 15: Kết quả Match query

nhận văn bản, số, ngày tháng.

31

Kết quả trả về là tất cả các record mà trong tên có ”phẫu” hoặc ”thuật”.

Ta cũng có thể thêm điều kiện là “and” đối với query (mặc định là or) thì

ta có kết quả khác:

Hình 16: Kết quả Match query thêm and

• Match Phrase Query: Truy vấn match_phrase phân tích văn bản và

Hình 17: Kết quả Match phrase query

• Match Phrase Prefix Query: Cũng giống match_phrase, nhưng thêm

tạo 1 truy vấn cụm từ.

điều kiện khớp với tiền tố của từ trong văn bản.

Hình 18: Kết quả Match Phrase Prefix Query

32

Với truy vấn này thì "phẫu thuật tim" cũng match mà "phẫu thuật thành" cũng

đúng.

• Multi Match Query: Sử dụng từ truy vấn match và cho phép tìm kiếm

Hình 19: Kết quả Multi Match Query

nhiều trường:

2.3.4.3 Term level queries

Truy vấn này thường được sử dụng cho các dữ liệu kiểu số, ngày tháng, enum.

Ngoài ra cho phép bạn tạo truy vấn cấp thấp, bỏ qua bước phân tích.

• Term Query: Truy vấn term tìm những bản ghi (record) có chứa cụm

từ chính xác trong query

• Range Query: Trả về các record với trường khớp với phạm vi nhất

định.

33

Truy vấn range cho phép truyền vào các tham số (params):

- gte: Lớn hơn hoặc bằng - gt: Lớn hơn - -

Hình 20: Query có các parameters

lte: Nhỏ hơn hoặc bằng lt: Nhỏ hơn

Hình 21: Query với format ngày

• Date format in range queries: truy vấn theo định dạng ngày tháng

• Wildcard Query: Trả về các record khớp với các ký tự đại diện được

đưa ra. Kiểu này giống like ‘%query%’ trong truy vấn sql

Hình 22: Wildcard Query

34

2.3.4.4 Bool Query

Cho phép kết hợp các câu truy vấn khác để tạo ra một logic hợp lý. Có các

loại:

• must: phải phù hợp với tất cả các điều kiện và đóng góp vào điểm số.

• filter: giống với must nhưng bỏ qua điểm số.

• should: chỉ cần phù hợp với một trong các điều kiện.

• must_not: ngược lại với must, phải không phù hợp với tất cả các điều

Hình 23: Bool Query

kiện.

35

2.3.4.5 Fuzzy Query

The fuzzy query sử dụng dựa trên khoảng cách Levenshtein (khoảng cách này có thể đọc thêm tại đây [20]), đo lường số lần chỉnh sửa ký tự đơn cần thiết để chuyển từ này thành từ kia. Và cho phép ta cấu hình tham số fuzziness để cho kết quả phù hợp nhất với nhu cầu của mình:

• Fuzziness: Khoảng cách chỉnh sửa tối đa. Mặc định là TỰ ĐỘNG.

Khi truy vấn các trường văn bản hoặc từ khóa, độ mờ được hiểu là khoảng cách chỉnh

sửa Levenshtein - số lượng một ký tự thay đổi cần được thực hiện thành một chuỗi

để làm cho nó giống với một chuỗi khác.

Tham số độ mờ có thể được chỉ định là: 0, 1, 2

Khoảng cách chỉnh sửa tối đa được phép của Levenshtein (hoặc số lần chỉnh

sửa) TỰ ĐỘNG: tạo khoảng cách chỉnh sửa dựa trên độ dài của thuật ngữ. Đối số

khoảng cách thấp và cao có thể được cung cấp tùy chọn AUTO: [low], [high]. Nếu

không được chỉ định, các giá trị mặc định là 3 và 6, tương đương với AUTO: 3, 6 tạo

ra độ dài:

- 0..2: phải khớp chính xác - 3., 5: một chỉnh sửa được cho phép - > 5: cho phép hai chỉnh sửa AUTO thường là giá trị ưa thích cho độ mờ.

• prefix_length: số lượng ký tự ban đầu sẽ không được làm mờ. Điều này giúp giảm số lượng các điều khoản phải được kiểm tra. Mặc định

là 0.

• max_expansions: số lượng thuật ngữ tối đa mà truy vấn mờ sẽ mở rộng

thành. Mặc định là 50.

• Transpositions: liệu chuyển vị mờ (ab → ba) có được hỗ trợ hay

không. Mặc định là đúng.

Ví dụ truy vấn từ “tết1” nhưng hệ thống vẫn tìm được dữ liệu tết mặc dù dữ

liệu đầu vào bị dư số 1

Hình 24: Fuzzy Query

36

2.3.4.5 Geo queries

ElaticSearch hỗ trợ hai loại dữ liệu Geo: trường geo_point hỗ trợ các cặp lat /

lon và trường Geo_shape, hỗ trợ các điểm, đường, vòng tròn, đa giác, đa giác, ...

• Truy vấn geo_shape: tìm tài liệu có hình dạng địa lý giao nhau, được

chứa bởi hoặc không giao nhau với hình dạng địa lý đã chỉ định.

• Truy vấn geo_bounding_box: tìm tài liệu có điểm địa lý rơi vào hình

chữ nhật đã chỉ định.

• Truy vấn geo_distance: tìm tài liệu có điểm địa lý trong khoảng cách

chỉ định của điểm trung tâm.

• Truy vấn geo_polygon: tìm tài liệu với các điểm địa lý trong đa giác

được chỉ định.

2.3.5 Mô hình truy hồi thông tin của ElasticSearch

2.3.5.1 Term frequency/inverse document frequency:

Thuật toán đánh giá được sử dụng rất phổ biến trong Elasticsearch có tên gọi là term frequency/inverse document frequency, gọi tắt là TF/IDF, bao gồm các yếu tố đánh giá: Term frequency, Inverse document frequency, Field-length norm.

Term frequency: [21] yếu tố này đánh giá tần suất xuất hiện của term trong

1 field. Càng xuất hiện nhiều, relevance (sự liên quan) càng cao, cho ta thấy, một field mà từ khoá xuất hiện 5 lần sẽ cho relevance cao hơn là field mà từ khoá chỉ xuất

hiện 1 lần.

37

Ví dụ [22]: nếu bạn tìm kiếm với từ khoá "quick", thì rõ ràng field bên trên sẽ

{ "title": "The quick brown fox jumps over the quick dog" }

{ "title": "The quick brown fox" }

cho TF cao hơn (xuất hiện 2 lần) filed bên dưới (xuất hiện 1 lần):

(4)

TF được tính theo công thức sau [21]:

Giải thích: term frequency (tf) của t trong document d được tính bằng căn bậc

hai của số lần t xuất hiện trong d.

Inverse document frequency (idf) [21]: yếu tố này đánh giá tần suất xuất

hiện của term trên toàn bộ index. Điều đáng chú ý ở đây là càng xuất hiện nhiều,

(5)

càng ít thích hợp.

Giải thích: inverse document frequency (idf) của t là logarit cơ số e (logarit tự

nhiên) của thương giữa tổng số tài liệu trong index và số tài liệu xuất hiện và được cộng thêm 1 (để tránh xảy ra lỗi Division by zero).

Field-length norm: yếu tố này đánh giá độ dài của trường (field). Field càng ngắn, thì term sẽ có giá trị càng cao và ngược lại. Điều này hoàn toàn dễ hiểu, chúng ta có thể thấy một từ xuất hiện trong tiêu đề sẽ có giá trị hơn rất nhiều cũng từ đó nhưng xuất hiện trong nội dung văn bản.

(6)

Công thức [21]:

Giải thích: field-length norm (norm) là nghịch đảo của căn bậc hai số lượng

term trong field. (có thể hiểu là số lượng chữ của field đó)

38

IDF score * TF score * fieldNorms

(7)

Putting it together: _score cuối cùng sẽ là tích của 3 giá trị trên:

numDocs: chính là maxDocs, đôi khi bao gồm cả những document đã bị xóa.

2.3.5.2 Thuật toán BM25:

Từ phiên bản >=5.0 Elastic Search sử dụng thuật toán BM25 làm thuật toán đánh giá chính. BM25 [23] thuộc mô hình xác suất trong khi TF/IDF là mô hình

Hình 25: B25M

không gian vectơ nhưng nó vẫn dựa trên nền tảng của TF/IDF [24] và cải tiến dựa trên lý thuyết của probabilitistic information retrieval.

Trên biểu đồ cho thấy IDF trong BM25 khá giống IDF trong TF/IDF. Tuy nhiên BM25 đã chỉnh sửa công thức tính lại để thêm khả năng đưa ra score âm khi

tần suất xuất hiện của term trên toàn bộ index rất cao [23].

39

Công thức [23]:

(8)

Trong đó:

• b là hằng số và có giá trị là 0.75

• qi : term thứ i

Ví dụ [23] ta muốn tìm kiếm “shane connelly” trong tiếng Anh , thì từ shane

là q0 và connelly là q1

• IDF(qi): là inverse document frequency của term thứ i và có công thức

là :

(9)

Ví dụ ta có 4 tài liệu chứa từ “shane” thì IDF(shane):

Và từ “connelly” chỉ xuất hiện ở 2 tài liệu thì có IDF(connelly):

Chúng ta thấy mặc dù từ “shane” xuất hiện ở 4 tài liệu nhưng score vẫn thấp

hơn từ “connelly”. Điều này có nghĩa trực quan hơn một từ xuất hiện ở nhiều tài liệu thì sẽ ít quan trọng hơn từ xuất hiện ở ít tài liệu hơn và sẽ đóng góp score thấp hơn.

• f(qi,D): frequency của term trong document

• k1: là hằng số và thường có giá trị là 1.2

40

((k1 + 1) * tf) / (k1 + tf) (10)

Với công thức [23]:

Hình 26: BM25 tiệm cận

Ta có biểu đồ:

Chúng ta thấy, đường cong này tiệm cận (k1 + 1) tiệm cận (ở đây là k1 = 1.2).

Nhiều tf hơn luôn có nghĩa là có nhiều sự liên quan hơn. Tuy nhiên nó cũng nhanh

chóng bão hòa và không có giá trị tăng nữa. Mặt khác, tf trong Lucene liên tục tăng và không bao giờ đạt đến điểm bão hòa.

Đối với BM25, k1 thường được đặt thành 1.2. Thay đổi k1 có thể là một cách tiếp cận điều chỉnh hữu ích để sửa đổi tác động của TF. Sửa đổi k rõ ràng làm cho đường tiệm cận di chuyển. Tuy nhiên, điều quan trọng hơn là k cao hơn khiến TF mất

nhiều thời gian hơn để đạt được độ bão hòa. Bằng cách kéo dài điểm bão hòa, cho thấy được sự khác biệt về mức độ liên quan giữa tài liệu tần suất các từ cao hơn và thấp hơn.

41

• L = fieldLength / avgFieldLength: là tỉ lệ giữa độ dài của tài liệu so

với độ dài trung bình của tất cả các tài liệu.

((k1 + 1) * tf) / (k1 * (1.0 - b + b * L) + tf) (11)

Với công thức [23]:

Hình 27: BM25 với độ dài trung bình

Và biểu đồ thể hiện giá trị của TF đối với các độ dài khác nhau của document:

DELETE /bm25_index

PUT /bm25_index

{ "settings": { "number_of_shards": 1 }}

POST /bm25_index/my_type/_bulk

{ "index": { "_id": 1 }}

Dưới đây là các bước chuẩn bị dữ liệu và kiểm tra lại công thức của BM25:

42

{ "index": { "_id": 2 }}

{ "title": "The quick brown fox jumps over the lazy dog" }

{ "index": { "_id": 3 }}

{ "title": "The quick brown fox jumps over the quick dog" }

{ "index": { "_id": 4 }}

{ "title": "Brown fox brown dog" }

GET /bm25_index/my_type/_search

{

"query": {

"term": {

"title": "jumps"

}

}

}

{ "title": "The quick brown fox" }

Kết quả: sẽ cho _id = 3 và _id = 4.

log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))

= log(1 + (4-2 + 0.5) / (2+0.5))

= 0.6931471805599453

IDF = docCount = 4, docFreq = 2

Với k1=1.2, b=0.75 ta có:

(freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))

= (1 * (1.2 + 1) / (1 + 1.2 * (1 - 0.75 + 0.75 * (9/6.5))))

= 0.864048338

43

Vậy _score = 0.6931471805599453*0.864048338 = 0.5989126693522066

Thực tế: truy vấn ở ElasticSearch cho ta kết quả cũng tổng 2 tài liệu được tìm

thấy và max_core là: 0.59891266 và cũng cho kết quả gần giống với kết quả đã được

{

"took" : 0,

"timed_out" : false,

"_shards" : {

"total" : 1,

"successful" : 1,

"skipped" : 0,

"failed" : 0

},

"hits" : {

"total" : {

"value" : 2,

"relation" : "eq"

},

"max_score" : 0.59891266,

"hits" : [

tính toán bên trên.

44

"_index" : "bm25_index",

"_type" : "my_type",

"_id" : "2",

"_score" : 0.59891266,

"_source" : {

"title" : "The quick brown fox jumps over the lazy dog"

}

},

{

"_index" : "bm25_index",

"_type" : "my_type",

"_id" : "3",

"_score" : 0.59891266,

"_source" : {

"title" : "The quick brown fox jumps over the quick dog"

}

}

]

}

}

{

45

CHƯƠNG 3. THỰC NGHIỆM XÂY DỰNG WEBSITE TÌM KIẾM DANH BẠ Y TẾ

Phần này sẽ trình bày rõ cách thức xây dựng Website tìm kiếm danh bạ y tế

dựa trên nội dung như: phân tích, thiết kế, cài đặt, thử nghiệm, đánh giá hệ thống. Quá trình xây dựng website tìm kiếm danh bạ y tế được thực hiện dựa trên Asp.net

MVC Core Framework, ngôn ngữ phát triển C#, cơ sở dữ liệu MSSQL Express, mã

nguồn mở ElasticSearch.

3.1 Phân tích

Để chuẩn bị cho việc xây dựng một hệ thống tìm kiếm danh bạ y tế hiệu quả

thì cần phải có các bước sau:

• Phân tích và xử lý dữ liệu danh bạ y tế hiện có như bác sĩ, phòng khám,

bệnh viện, bài viết liên quan.

• Sau khi cài đặt Vietnamese Analysis Plugin [14] của tác giả Duy Đỗ ta dùng plugin này cho quá trình tách từ tiếng Việt và cấu trúc dữ liệu giúp

cho việc tìm kiếm tiếng Việt chính xác hơn.

• Xây dựng tính năng đọc dữ liệu danh bạ y tế từ MSSQL và đưa dữ liệu

này lên ElasticSearch.

Để xây dựng công cụ tìm kiếm hiệu quả dựa trên mã nguồn mở ElasticSearch

cần phải làm hai giai đoạn chính là: chuẩn hóa dữ liệu đầu vào và áp dụng các thuật

toán hiện có của ElasticSearch để áp dụng cho tập dữ liệu danh bạ y tế. Chẳng hạn

như việc xử lý các cụm từ viết tắt như BS, bac si, PK, phong kham, BV, benh vien…, và các từ chuyên khoa như tai mũi họng hoặc TMH… vì vậy để xây dựng được chúng

ta cần:

• Tìm hiểu các cụm từ viết tắt trong danh bạ y tế để tạo các CharFilters

trong ElasticSearch

• Chọn lọc các dữ liệu nào cho việc tạo Index và tìm kiếm, để đưa lên ElasticSearch, trong phần này có tìm hiểu các thông tin cần tìm kiếm

của người dùng và tham khảo tài liệu [25], sau đó tôi chọn các dữ liệu: tên phòng khám, bác sĩ, bệnh viện, các chuyên khoa, dịch vụ khám chữa bệnh vì phù hợp với nhu cầu tìm kiếm của người dùng.

46

3.2 Thiết kế

Hệ thống tìm kiếm danh bạ y tế xây dựng các phần sau:

• Từ dữ liệu thô Danh bạ y tế phải tiền xử lý để lọc bỏ dữ liệu thừa và

phân loại bác sĩ, phòng khám, bệnh viện, bài viết…

• Thiết kế cơ sở dữ liệu để lưu trữ thông tin dữ liệu đã xử lý

• Xây dựng tính năng đọc dữ liệu từ Cơ sở dữ liệu (Database), sau đó xử lý tách từ Tiếng Việt, đánh Index và đưa dữ liệu tìm kiếm lên

ElasticSearch.

• Tạo bộ vn_analyzer, vi_tokenizer, mapping dữ liệu với các cụm từ viết tắt, hoặc gần nghĩa trong y tế để ElasticSearch dễ dàng tìm kiếm với

các từ đó.

Ta có mô hình tìm kiếm văn bản Tiếng Việt trong lĩnh vực danh bạ y tế như

Hình 28: Mô hình tìm kiếm văn bản tiếng Việt

sau:

• Dữ liệu danh bạ y tế sẽ được xử lý trước khi đưa vào lưu trữ cơ sở dữ liệu, như phân loại dữ liệu bác sĩ, phòng khám, bệnh viện, bài viết…

• Phát triển hệ thống có tự động đọc CSDL và xử lý tiếng Việt, sau đó đưa dữ liệu vào ElasticSearch. Trong phần này chúng ta phải tạo ra CharFilters để mapping các dữ liệu lại để dễ dàng tìm kiếm từ những từ khóa viết tắt của người dùng, cũng như mapping các chuyên khoa

phù hợp với nhu cầu tìm kiếm của người dùng.

47

Hình 29: lược đồ về Analyzer

Cách thực hiện như sau:

Từ lược đồ trên ta có thể thực hiện như sau:

var createIndexResponse = client.Indices.Create(indexName, c => c

.Settings(s => s

.Analysis(a => a

.CharFilters(cf => cf

.Mapping("healhcareES_custom", mcf => mcf

.Mappings(

"Bs=> Bác sĩ",

"bs=> Bác sĩ",

"BS => Bác sĩ",

• Cách 1: trên ngôn ngữ C#:

48

"bac si=>Bác sĩ",

"PK=>Phòng khám",

"pk=>Phòng khám",

"phong kham=>Phòng khám",

"BV=> Bệnh viện",

"bv=> Bệnh viện",

"TMH=>Tai mũi họng",

"tmh=>Tai mũi họng",

"RHM=> Răng hàm mặt",

"rhm=> Răng hàm mặt",

"X-ray=> X quang",

"x-quang=> X quang",

"Sản phụ khoa=> khám thai"

)))

.Analyzers(an => an

.Custom("vn_analyzer", ca => ca

.CharFilters("html_strip", "healhcareES_custom")

.Tokenizer("vi_tokenizer")

.Filters("lowercase", "stop", "icu_folding"))

)))

"Bac si=>Bác sĩ",

49

PUT /healthcare

{

"settings": {

"analysis": {

"analyzer": {

"my_analyzer": {

"tokenizer": "vi_tokenizer",

"char_filter": [ "html_strip" ,"healhcareES_custom"],

"filter": [

"icu_folding"

]

}

},

"char_filter": {

"my_mappings_char_filter": {

"type": "mapping",

"mappings": [

"Bs=> Bác sĩ",

"bs=> Bác sĩ",

"BS => Bác sĩ",

"Bac si=>Bác sĩ",

"bac si=>Bác sĩ",

• Cách 2: ta có thể dùng Kibana và sử dụng phương thức PUT

50

"pk=>Phòng khám",

"phong kham=>Phòng khám",

"BV=> Bệnh viện",

"bv=> Bệnh viện",

"TMH=>Tai mũi họng",

"tmh=>Tai mũi họng",

"RHM=> Răng hàm mặt",

"rhm=> Răng hàm mặt",

"X-ray=> X quang",

"x-quang=> X quang",

"Sản phụ khoa=> khám thai"

]

}

}

}

}

}

"PK=>Phòng khám",

GET healthcare/_analyze

{

"analyzer": "vn_analyzer",

"text": "bs"

Sau khi tạo xong mapping, chúng ta có thể kiểm tra lại kết quả:

51

Kết quả:

{

"tokens" : [

{

"token" : "bac si",

"start_offset" : 0,

"end_offset" : 2,

"type" : "",

"position" : 0

}

]

}

GET healthcare/_analyze

{

"analyzer": "vn_analyzer",

"text": "tmh"

}

Kết quả:

{

"tokens" : [

{

"token" : "tai",

"start_offset" : 0,

}

52

"type" : "",

"position" : 0

},

{

"token" : "mui",

"start_offset" : 2,

"end_offset" : 2,

"type" : "",

"position" : 1

},

{

"token" : "hong",

"start_offset" : 2,

"end_offset" : 3,

"type" : "",

"position" : 2

}

]

}

"end_offset" : 2,

Rõ ràng kết quả trên cũng cho ta thấy với cách cài đặt mapping như trên dễ dàng tìm được các cụm từ viết tắt của người dùng, giúp ta cho được kết quả mong muốn.

Tương tự ta có bảng kết quả với các từ khóa đã mapping:

Bảng 1: Số liệu mapping các từ khóa.

53

Từ khóa Kết quả

BS bac si

PK phong kham

rhm rang ham và từ mat

X-ray x quang

khám thai san phu và từ khoa

thai sản san phu và từ khoa

Hình 30: lược đồ cơ sở dữ liệu Danh bạ y tế

Thông tin thiết kế lược đồ cơ sở dữ liệu lưu trữ như sau:

Trong đó các bảng sau: AspNetUser, Doctors, Hospitals, UserLikeDoctors,

UserSaveData, UserLikeHospital, QnAToDoctor:

• AspNetUser: lưu trữ thông tin tài khoản người dùng

• Doctors: lưu trữ thông tin bác sĩ

• Hospital: lưu trữ thông tin bệnh viện, phòng khám, hai tập dữ liệu này

phân biệt bởi colunm Type.

• UserLikeDoctors: lưu trữ thông tin người dùng đã thích các bác sĩ

54

• UserLikeHospital: lưu trữ thông tin người dùng đã thích các phòng

khám, bệnh viện…

• UserSaveData: lưu trữ thông tin người dùng muốn lưu dữ liệu bác sĩ, phòng khám, bệnh viện trong hồ sơ của mình, phục vụ cho việc sử dụng lại và không cần tra cứu lại.

• QnAToDoctor: lưu trữ thông tin câu hỏi của người dùng dành cho bác

sĩ.

Mô hình cho người sử dụng (bao gồm người quản lý và người sử dụng gọi tắt

Hình 31: Mô hình cho người sử dụng

là người dùng):

• Luồng thứ nhất: người dùng vào Website tìm kiếm thông tin mình cần,

lúc này hệ thống sẽ truy vấn trên dữ liệu của ElasticSearch

• Luồng thứ hai: sau khi người dùng đã tìm thấy thông tin mình cần thì xem hồ sơ thông tin Danh bạ y tế của bác sĩ, phòng khám, bệnh viện thì

hệ thống sẽ lấy dữ liệu từ MSSQL Server rồi trả kết quả cho người dùng

trên Website.

55

3.3 Cài đặt

Hệ thống được xây dựng trên AspNet MVC Core 3.1 và cài đặt trên môi trường Win 10. Hệ thống gồm hai phần giao diện chính (tất cả giao diện được thiết kế để có

thể tương thích hầu hết các thiết bị):

• Admin: dành cho admin quản lý và thêm dữ liệu mới, chỉnh sửa dữ liệu và xóa dữ liệu. Khi admin có thay đổi thông tin bác sĩ, phòng khám,

bệnh viện thì hệ thống cập nhật lại trong cơ sở dữ liệu và đưa lên

ElasticSearch

• Frontend: dành cho người dùng tìm kiếm thông tin danh bạ y tế, lưu

thông tin, và tương tác với dữ liệu đã tìm kiếm

• Cơ sở dữ liệu MSSQL Express 2017;

• AspNet Core >=3.1;

• IIS >=7.5

• Cài đặt ElasticSearch và Kibana (công cụ dành cho việc quản lý ElasticSearch và thao tác, truy vấn dữ liệu trên ElasticSearch) được

download tại link: https://www.elastic.co/downloads

• Cài đặt pluginVietnamese Analysis Plugin for Elasticsearch của tác giả

Duy Đỗ theo link

3.4 Giao diện

3.4.1. Giao diện cho người sử dụng

3.4.1.1 Giao diện tìm kiếm gợi ý từ khóa:

Hình 32: giao diện gợi ý khi nhập từ khóa

56

Dựa vào các từ khóa nhập vào từ người dùng, hệ thống sẽ gợi ý với 20 kết quả

gần nhất giúp gợi nhớ cho người dùng các thông tin hữu ít nếu cần.

Trong phần này ta xây dựng hệ thống tìm kiếm phù hợp với hai hình thức như

sau:

• Tạo tìm kiếm với kết quả khi người dùng nhập vào có dấu tiếng Việt:

Hình 33: kết quả tìm kiếm có dấu

Với từ khóa nhập vào: “bệnh vien tai mũi họng” kết quả như sau:

• Tạo tìm kiếm với kết quả khi người dùng nhập vào tiếng việt không dấu

Với từ khóa nhập vào: “benh vien tai mui hong” kết quả như sau:

Hình 34: kết quả tìm kiếm tiếng Việt không dấu

57

Ta thấy mặt dù hai tập kết quả khác nhau nhưng trong mỗi tập dữ liệu trả về

đều chứa “Bệnh viện Tai Mũi Họng” kết quả này cũng là kết quả mà người dùng

mong muốn.

var response = await _elasticClient.SearchAsync(s => s

.Query(q => q

.Match(m => m

.Field(f => f.KeyWords)

.Query(term).Analyzer("my_analyzer").Fuzziness(Fuzziness.Auto)

)

).From((page - 1) * pageSize).Size(pageSize).Sort(s =>

s.Descending(SortSpecialField.Score)).MinScore(0.4)

).ConfigureAwait(false);

Sau đây là code cấu hình và chọn “Analyzer” trong ngôn ngữ C# như sau:

• term là từ khóa người dùng nhập vào

58

• KeyWords là field ta chọn để tìm kiếm và dữ liệu này đã được đánh

index

• Analyzer("my_analyzer"): tên analyzer mà ta đã tạo ở bước trên

Fuzziness: sử dụng cho việc người dùng nhập sai từ nhưng vẫn gợi ý được từ gần đúng với term nhập vào.

3.4.1.2 Giao diện kết quả tìm kiếm:

Tính năng này sau khi tìm kiếm với từ khóa đã nhập. Thông tin hồ sơ bác sĩ,

phòng khám, bệnh viện gồm: tên, địa chỉ, số lượt thích, số lượt người dùng đã xem... đồng thời cũng có nút “xem thông tin” để thông tin chi tiết hồ sơ nếu người dùng tìm

Hình 35: Kết quả tìm kiếm

thấy kết quả cần tìm.

3.4.1.3 Giao diện tìm kiếm theo địa chỉ gần bạn:

Tính năng này sẽ tìm kiếm theo vị trí (location) của người sử dụng. Nếu người dùng sử dụng thiết bị máy tính thì hệ thống dựa vào mạng (network) internet để định vị tọa độ, nếu người dụng sử dụng thiết bị điện thoại thông minh hệ thống sẽ lấy định vị theo GPS. Với việc sử dụng máy tính thì việc sai sót định vị thường xuyên xãy ra

vì nó phụ thuộc vào đơn vị cung cấp dịch vụ internet và tốc độ đường truyền.

Hình 36: Kết quả theo định vị

59

Sau khi có dữ liệu gợi ý định vị, người dụng có thể nhập thêm thông tin tên bác

sĩ, hoặc bệnh viện, chuyên khoa để tìm thêm thông tin mình cần với các đơn vị gần mình

và hệ thống sẽ tìm kiếm theo các từ khóa nhập vào với vị trí gần với vị trí hiện tại theo

thiết bị đã định vị.

var geo = await _elasticClient.SearchAsync(s =>

s.From(0).Size(pageSize)

.Query(q => q

.Match(m => m

.Field(f => f.KeyWords)

.Query(keyword)

)

)

.PostFilter(q => q

.GeoDistance(g => g

.Boost(1.1)

Sau đây là code trong ngôn ngữ C# như sau:

60

.Field(p => p.GeoLocation)

.DistanceType(type)

.Location(lat, ln)

.Distance(distance)

.ValidationMethod(GeoValidationMethod.IgnoreMalformed)

)

)

);

.Name("named_query")

• lat và ln là tọa độ x, y của vị trí hiện tại

• Keyword là từ khóa người dùng nhập vào: ví dụ “tai mũi họng” hệ thống sẽ tìm danh bạ y tế có chuyên khoa tai mũi họng tưng ứng với vị trí hiện

tại với khoảng cách (distance) đã được cấu hình sẵn

• Distance: là cấu hình khoảng cách của địa chỉ cần tìm so với vị trí hiện

tại. Ví dụ 5 km, 3km, 1km…

3.4.1.3 Giao diện tìm kiếm theo chuyên khoa:

Hệ thống sẽ gợi ý các chuyên khoa có sẵn, dữ liệu này đã được chọn lọc theo các

chuyên khoa, giúp người dùng dễ dàng tìm kiếm các thông theo chuyên khoa mình cần:

Hình 37: Tìm kiếm theo chuyên khoa

61

Việc gợi ý tìm kiếm theo chuyên khoa sẽ giúp ích cho người dùng tìm kiếm

nhanh cơ sở khám chữa bệnh với chuyên khoa mình cần, từ đó người dùng dễ dàng

tìm thấy các thông tin mình cần hơn.

3.4.1.4 Giao diện chi tiết Hồ sơ - Profile:

Sau khi tìm kiếm được kết quả người dùng có thể bấm vào nút xem thông tin để

Hình 38 thông tin chi tiết

xem thêm thông tin hồ sơ đã tìm thấy:

Ngoài các thông tin chung giống với thông tin tìm kiếm bên ngoài, trang

profile còn có thêm các thông tin sau:

• Thông tin giới thiệu về profile và các dịch vụ khám chữa bệnh

• Thông tin bản đồ vị trí theo địa chỉ của hồ sơ

Hình 39: thông tin bản đồ theo địa chỉ của Profile

62

• Số điện thoại (nếu có)

• Bảng giá dịch vụ (nếu có)

• Người dùng có thể bấm vào “Chỉ đường” để được hướng dẫn chỉ đường đến phòng khám, bệnh viện nơi mình cần đến, hệ thống tự động trỏ link

qua google map để giúp người dùng coi trên bản đồ.

• Các tính năng tương tác với thông tin Profile như:

Hình 40: nút lưu profile

- Lưu lại profile để sau này cần:

Người dùng bấm vào dụng lại mà không cần phải tìm để lưu lại thông tin để sau này có thể sử kiếm lại.

63

- Tính năng “Thích” để tăng độ tin cậy cho Profile, càng nhiều lượt thích thì người dùng sau sẽ thấy Profile này đáng tin cậy và

họ có thể chọn tìm đến thông tin này.

Người dùng bấm vào để thích, nó còn giúp người dùng xem lại

thông tin trong danh sách Profile mình đã thích.

- Tính năng hỏi đáp: với những bác sĩ, bệnh viện hay phòng khám mà hệ thống có liên kết hợp tác thì có thể tạo thêm tính năng hỏi

đáp giúp người dùng dễ dàng đặt câu hỏi và tương tác thông qua

Hình 41: hỏi đáp.

tính năng này.

Người dùng dễ dàng xem lại các danh sách danh bạ y tế đã lưu, hoặc đã thích,

Hình 42: Thông tin đã lưu, thích

vì vậy dễ dàng sử dùng lại thông tin này khi cần:

64

- Tính năng đặt lịch hẹn khám bệnh, với những cơ sở có liên kết khám chữa bệnh thì hệ thống bật tính năng đặt lịch hẹn khám

chữa bệnh giúp người dùng dễ dàng đặt lịch với cơ sở khám chữa

bệnh mình cần với thời gian đã chọn

3.4.2. Giao diện cho người quản trị

Chức năng cho người quản trị bao gồm các chức năng nhập liệu dữ liệu về

thông tin bác sĩ, phòng khám, bệnh viện, ngoài ra còn có các chức năng quản trị tài

Hình 43: Trang quản trị Admin

khoản, danh mục, xem danh sách câu hỏi, lịch hẹn khám chữa bệnh…

3.4.2.1 Giao diện nhập liệu bác sĩ, phòng khám, bệnh viện:

Ngoài việc có tập dữ liệu ban đầu, thì hệ thống còn có tính năng cập nhật bổ

sung dữ liệu mới cũng như cập nhật dữ liệu cũ lại và đồng bộ tập dữ liệu đã cập nhật

này lên hệ thống ElasticSearch:

Hình 44: Cập nhật dữ liệu mới

65

3.4.2.2 Giao diện quản trị tài khoản:

Tính năng này chủ yếu cho người quản trị thống kê số lượng tài khoản đã đăng

ký và sử dụng để đo lường sự hiệu quả của hệ thống khi triển khai, từ đó có thể phát

Hình 45: Quản trị tài khoản

triển thêm tính năng tạo sự thu hút cho người dùng ngày càng nhiều hơn.

66

3.5 Đánh giá và thử nghiệm

3.5.1. Mô hình kiến trúc ứng dụng thử nghiệm

Hệ thống thử nghiệm gồm có các thành phần chính như sau:

• Dữ liệu thô thử nghiệm danh bạ y tế: Dữ liệu này sẽ được đưa vào CSLD MSSQL Server Express 2017, sau đó được cấu trúc lại và phân

loại theo bác sĩ, phòng khám, bệnh viện. Tập dữ liệu này có khoảng

8973 bác sĩ, 2941 phòng khám và 69 bệnh viện tại TP. Hồ Chí Minh.

• Tạo index và đưa vào hệ thống ElasticSearch: từ dữ liệu đã cấu trúc và phân loại, chúng ta tạo ra ứng dụng đọc dữ liệu này từ CSLD SQL

Server và thực hiện các bước tạo CharFilters, vn_analyzer, vi_tokenizer

và đưa tập dữ liệu lên ElasticSearch để đánh index, phục vụ cho việc tìm kiếm tiếng Việt có dấu và không dấu.

• Tìm kiếm danh bạ y tế: Người dùng truy cập vào đường dẫn phần mềm thử nghiệm và gõ từ khóa tìm kiếm thông tin về danh bạ y tế mà

mình cần. Hệ thống sẽ thực hiện tìm kiếm ở trong cơ sở dữ liệu trên

ElasticSearch và trả về kết quả cho người dùng. Từ danh sách kết quả

tìm kiếm trả ra theo _score giảm dần, với ngưỡng thấp nhất (min) cấu

hình ban đầu, với mục đích là trả ra kết quả có độ chính xác gần nhất

với từ khóa người dùng nhập vào.

Hình 46 Kiến trúc Mô hình thử nghiệm

67

3.5.2. Kịch bản và kết quả

Với dữ liệu Danh bạ y tế đã được đánh chỉ mục trên ElasticSearch, tôi thử

nghiệm sử dụng tính năng tìm kiếm với kịch bản như sau:

• Tìm kiếm theo từ khóa bất kỳ với tiếng Việt có dấu

• Tìm kiếm từ khóa bất kỳ với tiếng Việt không dấu

• Tìm kiếm bao gồm tiếng Việt có dấu và không dấu

• Tìm kiếm chính xác từ khóa

68

• Tìm kiếm các từ khóa có từ viết tắt như “bs” hoặc “bv” hoặc những từ

khóa chuyên khoa đã cấu hình sẵn như “tmh”…

• Tìm kiếm với gợi ý (Autocomplete) có sử dụng fuzzy trong

ElasticSearch

• Tìm kiếm theo GEO có cấu hình giới hạn về khoảng cách, phạm vi cần

tìm

• Tìm kiếm theo GEO và có từ khóa kèm theo

Bảng 2: Kịch bản tìm kiếm.

• Tìm kiếm theo chuyên khoa

Từ khóa Số lượng Thời Nhận xét Kịch bản kết quả gian tìm

kiếm (giây)

tai mũi họng 583 0.03 Hệ thống tìm thấy Tìm kiếm theo từ toàn bộ danh sách khóa bất kỳ với Bác sĩ, phòng tiếng Việt có dấu khám, Bệnh viện có

chuyên khoa tai mũi

họng

tai mui hong 583 0.02 Hệ thống tìm thấy Tìm kiếm từ khóa toàn bộ danh sách bất kỳ với tiếng Bác sĩ, phòng Việt không dấu khám, Bệnh viện có

chuyên khoa tai mũi

họng

511 0.02

bác sĩ Phương tai mui hong

Tìm kiếm bao gồm tiếng Việt có dấu và không dấu

Hệ thống đã tìm thấy danh sách bác sĩ tên Phương hoặc tên Phượng có chuyên khoa tai mũi

họng và đưa ra _score cao nhất

bs Phương tmh 511 0.01 Hệ thống đã tìm Tìm kiếm các từ thấy danh sách bác khóa có từ viết tắt

69

như “bs” hoặc “bv” hoặc những sĩ tên Phương hoặc tên Phượng có

từ khóa chuyên chuyên khoa tai mũi

khoa đã cấu hình họng và đưa ra

sẵn như “tmh”… _score cao nhất

Bệnh viện Tai 509 0.01 Bệnh viện Tai mũi Tìm kiếm chính mũi họng họng TPHCM được xác từ khóa đưa ra _score cao

nhất và danh sách Bs, Pk có khám

chữa bệnh tai mũi

họng cũng được tìm

thấy

bv măt1 Chỉ lấy top 0.01 Hệ thống vẫn tìm Tìm kiếm với gợi thấy được kết quả 20 kết quả ý (Autocomplete) bệnh viện Mắt mặc có sử dụng fuzzy dù người dùng đã trong nhập sai chữ “măt1” ElasticSearch

Chỉ lấy top 0.01 Hệ thống sẽ tìm và Tìm kiếm theo gợi ý tất cả bác sĩ, 50 kết quả GEO có cấu hình phòng khám, bệnh khoảng cách 3km viện với tất cả

chuyên khoa dịch vụ

trong phạm vi 3km

Khám thai 195 0.01 Hệ thống tìm và đưa Tìm kiếm theo

từ GEO và có khóa kèm theo và hình cấu có khoảng cách 3km ra danh sách Bác sĩ, phòng khám, bệnh viện có chuyên khoa Phụ sản hoặc Sản phụ khoa

Nha khoa 1828 0.01 theo thống đưa ra Hệ danh sách bác sĩ, Tìm kiếm chuyên khoa phòng khám, bệnh

viện răng hàm mặt

70

3.5.3 Đánh giá kết quả nghiên cứu

3.5.3.1 Kết quả đạt được

Dựa vào bảng kết quả cho ta thấy rằng việc ứng dụng ElasticSearch cho kết quả

với các tài liệu liên quan phù hợp với nhu cầu tìm kiếm của người dùng rất hiệu quả và tốc độ nhanh hơn so với kiểu truy vấn truyền thống “like” của ngôn ngữ SQL thông

thường. Vì kiểu thông thường của các trang web hiện nay phải kết hợp nhiều trường

thông tin lại và tìm kiếm dựa vào truy vấn “like” của SQL nên sẽ chậm về hệ thống,

cũng như không đáp ứng hết nhu cầu tìm kiếm của người dùng, do đó sẽ trả kết quả không như mong đợi. Ngoài ra Luận văn cũng hỗ trợ người dùng cũng có thể tìm

kiếm các từ khóa gần gũi với mình như cụm từ viết tắt, hoặc các từ hay dùng hằng

ngày như “khám thai” nhưng vẫn trả được kết quả tương ứng như sản phụ khoa hay sản nhi, việc này thì kiểu tìm kiếm truyền thống chưa làm được.

Luận văn cũng đã từng bước xây dựng hoàn chỉnh một hệ thống tìm thông tin

về danh bạ y tế với tập dữ liệu bác sĩ, phòng khám, bệnh viện tại TP. Hồ Chí Minh.

Các tính năng tìm kiếm trong hệ thống cũng giúp người dùng dễ dàng tìm kiếm các

thông tin mà mình cần. Luận văn cũng đã nghiên cứu hành vi, nhu cầu tìm kiếm của

người dùng từ đó đưa ra những dữ liệu phù hợp với nhu cầu hơn. Bên cạnh đó cũng

đã xây dựng các tính năng tương tác như “thích”, “lưu”, “bình luận” để tăng độ tin

cậy của dữ liệu cho người dùng sau.

Nhìn chung hệ thống chạy với độ chính xác trên 97% [15] điều này là chấp

nhận được bởi vì hệ thống dựa vào vnTokenizer có trong plugin elasticsearch-

analysis-vietnamese (với độ chính xác 97%) để tách từ trong tiếng Việt thành những

từ có nghĩa phục vụ cho việc đánh index và tìm kiếm.

3.5.3.2 Hạn chế

Bên cạnh những kết quả đạt được thì luận văn còn có những mặt hạn chế như: luận văn dựa trên plugin Tiếng Việt của mã nguồn mở ElasticSearch để tìm kiếm văn bản tiếng Việt đôi khi độ chính xác vẫn còn thiếu sót nên cần cập nhật dữ liệu từ điển về lĩnh vực y tế để cho việc tìm kiếm ngày càng chính xác hơn. Trên máy tính không có cảm biến tọa độ địa lý (GPS sensor) nên chức năng định vị trong “tìm kiếm cơ sở

y tế gần tôi” sẽ không chính xác bằng các điện thoại thông minh có phần cứng GPS và phải phụ thuộc vào nhà cung cấp dịch vụ Internet (Internet service provider).

3.5.3.3 Hướng phát triển

Luận văn từng bước xây dựng, bổ sung từ điển tiếng Việt cho lĩnh vực y tế.

71

Cần khảo sát thêm nhu cầu tìm kiếm của người dùng để xây dựng các bộ từ

khóa tìm kiếm sao cho phù hợp với nhu cầu thực tế hơn.

Liên kết và hợp tác với bác sĩ, phòng khám, bệnh viện để xây dựng tính năng

đặt lịch hẹn khám chữa bệnh để sau khi tìm kiếm xong thì người dùng có thể đặt lịch

hẹn với cơ sở mình cần đến, tạo sự gắn kết giữa họ và tăng tính hữu ích của hệ thống.

Hệ thống cần thu thập thêm dữ liệu danh bạ y tế cả nước để việc tìm kiếm ngày

càng đa dạng hơn, đồng thời cũng phát triển thêm trên Mobile App để tận dụng GPS

của thiết bị di động làm cho việc tìm kiếm GEO hiệu quả hơn, giúp người dùng dễ

dàng xác định vị trí của mình và tìm kiếm bác sĩ, phòng khám, bệnh viện gần mình

hơn.

72

CHƯƠNG 4. KẾT LUẬN

Luận văn nghiên cứu, phân tích, thiết kế và xây dựng một hệ thống tìm kiếm

thông tin dựa trên mã nguồn mở ElasticSearch kết hợp với ngôn ngữ lập trình ASP.NET CORE và plugin “elasticsearch-analysis-vietnames” giúp giải quyết vấn

đề tìm kiếm Tiếng Việt với độ chính xác 97% [15].

Luận văn cũng đã cung cấp cách thức truy xuất cũng như tìm kiếm văn bản

một cách nhanh chóng. Người dùng có thể tra cứu danh bạ y tế mình cần một cách

hiệu quả, phù hợp với nội dung tìm kiếm mà mình cần. Người dùng có thể tra cứu được các từ viết tắt như “tmh” thay cho từ khóa “tai mũi họng”, hoặc “khám thai”

thay cho từ khóa “thai nhi hoặc sản nhi” hay từ khóa “răng hàm mặt” thay thế cho

“nha khoa” hoặc các từ Tiếng Việt không dấu, dựa vào cách thức cấu hình và tạo

mapping, tokenizer, analyzer, để hỗ trợ tốt hơn cho việc tìm kiếm, đa dạng nội dung

các từ khóa phù hợp với nhu cầu tìm kiếm của người dùng.

Luận văn cũng đã xây dựng tính năng “tìm kiếm cơ sở y tế gần tôi” dựa trên

việc định vị tọa độ địa lý giúp người dùng có thể lựa chọn, tiếp cận các cơ sở khám

chữa bệnh gần mình, tiết kiệm được thời gian hơn. Bên cạnh đó luận văn còn cung

cấp các tính năng tiện ích như “Thích”, “Lưu” để giúp người dùng dễ dàng lưu trữ và

sử dụng lại dữ liệu cho những lần kế tiếp.

Từ các kết quả đạt được như trên, thì các vấn đề đặt ra trong luận văn đã được

giải quyết như: xây dựng một hệ thống tìm kiếm thông tin Tiếng Việt về danh bạ y tế

hoàn chỉnh, giúp người dùng dễ dàng tìm kiếm và tiếp cận các thông tin y tế, hồ sơ

bác sĩ, phòng khám, bệnh viện…Thông qua các tính năng “thích”, “hỏi đáp”, “lưu”

người dùng dễ dàng sử dụng lại dữ liệu của mình khi cần thiết. Điều đó sẽ giúp ích

cho việc giảm được thời gian tìm kiếm và đồng thời làm tăng độ tin cậy cho những tập dữ liệu đó.

73

DANH MỤC TÀI LIỆU THAM KHẢO

eBay

https://elasticsearch.cn/uploads/slides/20191210/2d32de50d21190aac153993a8fb74 0b4.pdf, access date: Sep 9, 2020

[1] Donggeng Yu (2019),Pronto Elasticsearch Extension Practice in eBay, Pronto,

[2] https://www.elastic.co/elasticon/tour/2017/new-york/elastic-vimeo-

elasticsearch-for-search, access date: Sep 9, 2020

[3] https://www.elastic.co/elasticon/conf/2017/sf/streaming-healthcare-and- research-story-of-elasticsearch-at-ucla-health, access date: Sep 9, 2020

[4] Đỗ Phúc, Đỗ Hoàng Cường, Nguyễn Tri Tuấn, Huỳnh Thụy Bảo Trân, Nguyễn Văn Khiết, Nguyễn Việt Hoàng, Nguyễn Việt Thành, Phạm Phú Hội, Dương

Ngọc Long Nam, Nguyễn Phước Thanh Hải, “Phát triển một Hệ thống S.E” Hỗ

trợ Tìm kiếm Thông tin, thuộc lãnh vực CNTT trên Internet qua từ khóa bằng

tiếng Việt, Đại học Khoa Học Tự Nhiên, TP.HCM, 2004.

[5] Tuan Anh Tran, Dao Thi Thanh Loan “Phát Triển hệ truy hồi thông tin tiếng Việt dựa trên mã nguồn mở (Vietnamese language information retrieval using

open source)” JOURNAL OF SCIENCE OF HNUE Interdisciplinary Science,

2013, Vol. 58, No. 1, pp. 37-45.

[7] Nguyễn Thị Loan (2017), Nghiên cứu công nghệ tìm kiếm (Mã nguồn mở)

[6] Huỳnh Đức Việt, Võ Duy Thanh, Võ Trung Hùng (2010), “Nghiên cứu ứng dụng mã nguồn mỡ Lucene để xây dựng phần mềm tìm kiếm thông tin trên văn bản”, Tạp chí khoa học và công nghệ, số 4, trang 308 – 315.

Lucene áp dụng giải quyết bài toán tìm kiếm trong hệ thống Văn bản, Luận văn

Salton, Donna Harman

(January 2003), “Information retrieval”,

Thạc sĩ, Trường Đại học Công nghệ - Đại học Quốc Gia Hà Nội, Hà Nội. [8] Gerald J. Kowalski, Mark T. Maybury (2002), INFORMATION STORAGE AND RETRIEVAL SYSTEMS Theory and Implementation Second Edition, Kluwer Academic Publishers New York, Boston, Dordrecht, London, Moscow

Encyclopedia of Computer Science, Pages 858–863

[10] Gerard Salton, Michael J. McGill (1983), Introduction to Modern Information Retrieval, McGraw-Hill Book Company, Printed in the United States of America.

[9] Gerard

74

[11] Christopher D.Manning Prabhakar Raghavan Hinrich Schütze (2009), Introduction to Infomation Retrieval, Cambridgec University Press Cambridge,

England.

[12] https://seongon.com/seo/kien-thuc-seo/seach-engine.html, access date: Oct

10, 2020

[13] http://www.searchenginehistory.com/, access date: Dec 10, 2020 [14] Duy Đỗ “Vietnamese Analysis Plugin for Elasticsearch”,

https://github.com/duydo/elasticsearch-analysis-vietnamese, access date: Dec

27, 2020

[15] https://github.com/phuonglh/vn.vitk, access date: Dec 20, 2020 [16] Brian D. Sloan (2017), ES-ESA: An Information Retrieval Prototype Using

[17] Clinton Gormley & Zachary Tong (2015), Elasticsearch: The Denitive Guide, Printed in the United States of America, Published by O’Reilly Media, Inc. , 1005 Gravenstein Highway North, Sebastopol, CA 95472.

Explicit Semantic Analysis and Elasticsearch, The Graduate Center, City University of New York.

[18] Elastic Search,

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index.html

[19] https://pluralsight.com/courses/elasticsearch-indexing-data [20] https://en.wikipedia.org/wiki/Levenshtein_distance, access date: Dec 20,

2020

[21] https://www.compose.com/articles/how-scoring-works-in-elasticsearch/,

access date: Dec 30, 2020 [22] Vô danh (2020), Thuật toán đánh giá _score trong ElasticSearch,

https://viblo.asia/p/thuat-toan-danh-gia-score-trong-elasticsearch-

OeVKBY7y5kW , truy cập ngày: 08/10/2020

[23] BM25 The Next Generation (2015),

of Lucene Relevance https://opensourceconnections.com/blog/2015/10/16/bm25-the-next- generation-of-lucene-relevation, access date: Oct 10, 2020

[24] S. E. Robertson and H. Zaragoza. 2009. “The Probabilistic Relevance Framework: BM25 and Beyond”. Foundations and Trends in Information Retrieval 3(4): 333-389. [25] William R. Hersh, Chapter 14: Information Retrieval for Healthcare, Department of Medical Informatics & Clinical Epidemiology (DMICE) Oregon Health & Science University Portland.

PHỤ LỤC

// Tạo interface cho các method public interface IHealthcareElastichSearchService

{

Task> GetDataSearch(string keyword, int pageSize,

int page, int score);

Task> GetDataSearchGeo(double lat, double ln,

GeoDistanceType type, string distance, int pageSize, int page, string keyword);

Task> GetDataSearchGeo(double lat, double ln,

1 2 3 4 5 6 7 8 9 10 11 12 13 14

GeoDistanceType type, string distance, int pageSize, int page);

15

Task> GetDataSuggestion(string keyword, int page, int pageSize);

Task> GetDataSuggestionWithIcu(string term, int page, int pageSize);

Task InsertDataHealthcare(HealthCareModel model);

void UpdateDataHealthcare(HealthCareModel model);

void DeleteDataHealthcare(HealthCareModel model);

HealthCareModel GetDataHealthcarebyId(string id);

Task UpdateHealthcare(string id);

Task> GetDataSearchWithSpecialist(string

specialist, int pageSize, int page, float score);

}

// Implement Code kế thừa từ interface public class HealthcareElastichSearchService : IHealthcareElastichSearchService

{

private readonly IElasticClient _elasticClient; private readonly string _index; public HealthcareElastichSearchService(IElasticClient elasticClient,

IConfiguration configuration)

{

_elasticClient = elasticClient; _index = configuration["elasticsearch:index"];

}

// Lấy dữ liệu từ document theo Id public HealthCareModel GetDataHealthcarebyId(string id) {

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

var response = _elasticClient.Get(id, u =>

u.Index(_index)).Source;

return response;

}

// Tìm kiếm dữ liệu theo từ khóa và phân trang, dùng stopwatch để kiểm tra thời gian lấy dữ li

ệu

public async Task> GetDataSearch(string keyword,

int pageSize, int page, int score)

{

Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); var response = await _elasticClient.SearchAsync(s => s

.Query(q => q

.Match(m => m

.Field(f => f.KeyWords) .Query(keyword)

)

).From((page - 1) * pageSize).Size(pageSize) .Sort(s =>

s.Descending(SortSpecialField.Score)).MinScore(score)

).ConfigureAwait(false);

long total = response.HitsMetadata.Total.Value; var rs = new PaginatedList(response.Documents.ToList(), total,

page, pageSize);

return rs;

}

// Tìm kiếm theo chuyên khoa public async Task>

GetDataSearchWithSpecialist(string specialist, int pageSize, int page, float score)

{

var response = await _elasticClient.SearchAsync(

s => s.Query(q => q.QueryString(d => d.Query(specialist).Fields(f =>

f.Fields(f => f.KeyWords)))).Sort(s => s.Descending(SortSpecialField.Score))

.From((page - 1) * pageSize).MinScore(score) .Size(pageSize)).ConfigureAwait(false);

var rs = new PaginatedList(response.Documents.ToList(),

response.Documents.Count, page, pageSize);

return rs;

}

// Tìm kiếm theo chuyên khoa và từ khóa public async Task> GetDataSpecialistSearch(string

specialist, string keyword, int pageSize, int page, int score)

{

var response = await _elasticClient.SearchAsync(

s => s.Query(q => q.QueryString(d => d.Query(keyword))).Sort(s =>

s.Descending(SortSpecialField.Score))

.From((page - 1) * pageSize) .Size(pageSize)).ConfigureAwait(false);

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

var rs = new PaginatedList(response.Documents.ToList(),

response.Documents.Count, page, pageSize);

return rs;

}

// Tìm kiếm theo tọa độ public async Task> GetDataSearchGeo(double lat,

double ln, GeoDistanceType type, string distance, int pageSize, int page)

{

var geo = await _elasticClient.SearchAsync(s => s.From(0).Size(pageSize)//.ScriptFields(sf => sf.ScriptField("distance", d => d.Source("if(doc['geo'].size(){doc['geo'].arcDistance(" + lat + "," + ln + ")}")))

.Query(q => q

.GeoDistance(g => g .Boost(1.1) .Name("named_query") .Field(p => p.GeoLocation)

.DistanceType(type) .Location(lat, ln) .Distance(distance) .ValidationMethod(GeoValidationMethod.IgnoreMalformed)

))

);

return new PaginatedList(geo.Documents.ToList(),

geo.Documents.Count, page, pageSize);

}

// Tìm kiếm theo tọa độ và từ khóa public async Task> GetDataSearchGeo(double lat,

double ln, GeoDistanceType type, string distance, int pageSize, int page, string keyword)

{

var geo = await _elasticClient.SearchAsync(s =>

s.From(0).Size(pageSize)

.Query(q => q

.Match(m => m

.Field(f => f.KeyWords) .Query(keyword)

)

).MinScore(5)

.PostFilter(q => q

.GeoDistance(g => g .Boost(1.1) .Name("named_query") .Field(p => p.GeoLocation)

.DistanceType(type) .Location(lat, ln)

109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

.Distance(distance) .ValidationMethod(GeoValidationMethod.IgnoreMalformed)

) )

);

return new PaginatedList(geo.Documents.ToList(),

geo.Documents.Count, page, pageSize);

}

// lấy dữ liệu gợi ý cho từ khóa public async Task> GetDataSuggestion(string term, int page, int

pageSize) {

var response = await _elasticClient.SearchAsync(s => s

.Query(q => q

.Match(m => m

.Field(f => f.KeyWords) .Query(term).Fuzziness(Fuzziness.Auto)

)

).From((page - 1) * pageSize).Size(pageSize).Sort(s =>

s.Descending(SortSpecialField.Score))

).ConfigureAwait(false);

var rs = response.Documents.Select(x => $" {x.Name} -{x.Specialist}");

return rs.ToList();

}

// Lấy dữ liệu gợi ý với từ khóa không dấu public async Task> GetDataSuggestionWithIcu(string term, int page, int

pageSize) {

var response = await _elasticClient.SearchAsync(s => s

.Query(q => q

.Match(m => m

.Field(f => f.KeyWords) .Query(term).Analyzer("my_analyzer").Fuzziness(F

uzziness.Auto)

)

).From((page - 1) * pageSize).Size(pageSize).Sort(s =>

s.Descending(SortSpecialField.Score)).MinScore(0.4) ).ConfigureAwait(false);

var rs = response.Documents.Select(x => $" {x.Name} -{x.Specialist}");

return rs.ToList();

}

// Đẩy dữ liệu lên ElasticSearch public async Task InsertDataHealthcare(HealthCareModel model) {

_ = await _elasticClient.IndexDocumentAsync(model);

}

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218

// Cập nhật lại dữ liệu trên ElasticSearch

public void UpdateDataHealthcare(HealthCareModel model) {

_elasticClient.Delete(model.Id, u => u.Index(_index));

_elasticClient.IndexDocument(model);

}

// Cập nhật lại dữ liệu trên ElasticSearch với cơ chế acsync public async Task UpdateHealthcare(HealthCareModel model) {

var response = await _elasticClient.UpdateAsync(model.Id, u

=> u.Index(_index).Doc(model));

}

// Cập nhật lại dữ liệu View từ người dùng public async Task UpdateHealthcare(string id) {

var update = GetDataHealthcarebyId(id); if (update != null) {

update.Viewed += 1; await _elasticClient.UpdateAsync(update.Id, u =>

u.Index(_index).Doc(update));

}

}

}

// Controller và xử lý route url public class HealthcareController : Controller

{

private readonly IHealthcareElastichSearchService

_healthcareElastichSearchService;

private readonly IDoctorRepository _doctorRepository; private readonly IHospitalRepository _hospitalRepository; private readonly ILogger _logger; public HealthcareController(

IHealthcareElastichSearchService healthcareElastichSearchService, IDoctorRepository doctorRepository, IHospitalRepository hospitalRepository, ILogger logger)

{

_healthcareElastichSearchService = healthcareElastichSearchService; _doctorRepository = doctorRepository; _hospitalRepository = hospitalRepository; _logger = logger;

}

// Trang chủ public IActionResult Index() {

return View();

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

}

// Trang tìm kiếm [HttpGet("healthcare/Search")] public async Task Search(int? page) {

string term = HttpContext.Request.Query["term"].ToString(); ; page = page ?? 1;

var rs = await _healthcareElastichSearchService.GetDataSearch(term, 100,

page.Value, 10);

ViewBag.term = term; return View(rs);

}

// Trang gợi ý dữ liệu với vị trí gần tôi [HttpGet("healthcare/FindNearby")] public IActionResult FindNearby(double lat, double ln) {

return View();

}

// Trang tìm kiếm từ khóa theo độ tọa [HttpGet("healthcare/FindNearme/{lat}/{ln}")] public async Task FindNearme(double lat, double ln) {

var keyword = HttpContext.Request.Query["keyword"];

if (lat != 0 && ln != 0) {

PaginatedList rs = null; if (string.IsNullOrEmpty(keyword)) {

rs = await _healthcareElastichSearchService.GetDataSearchGeo(lat, ln,

Nest.GeoDistanceType.Arc, "3000m", 50, 1);

} else {

rs = await _healthcareElastichSearchService.GetDataSearchGeo(lat, ln,

Nest.GeoDistanceType.Arc, "3000m", 100, 1, keyword);

} ViewBag.Lat = lat; ViewBag.Ln = ln; return View(rs);

} return View();

}

// Tìm kiếm theo chuyên khoa [HttpGet("healthcare/FindbySpecialist")]

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

public async Task FindbySpecialist(string specialist) {

ViewBag.Selected = specialist; var rs = await

_healthcareElastichSearchService.GetDataSearchWithSpecialist(specialist, 30, 1, 5);

return View(rs);

}

// Get địa chỉ [HttpGet("healthcare/GetAddress")] public JsonResult GetAddress(double lat, double ln) {

var rs = CommonUtils.GetAddress(lat, ln); var displayname = rs.display_name.Replace(", Long Điền, 711001, Việt Nam", "");

return Json(new {

success = true, message = "Thành công", data = $"{displayname}"

});

}

// Gợi ý từ khóa [HttpGet("healthcare/SearchSuggest")] public async Task SearchSuggest() {

string term = HttpContext.Request.Query["term"].ToString();

var rs = await _healthcareElastichSearchService.GetDataSuggestion(term, 1,

20);

return Ok(rs);

}

// Trang profile Bác sĩ [HttpGet("healthcare/doctorprofile/{slug}")] public async Task DoctorProfile(string slug) {

var doctor = _doctorRepository.GetAllData().Where(x => x.Slug.Equals(slug))

.Include(i => i.Questions).FirstOrDefault();

await _healthcareElastichSearchService.UpdateHealthcare(doctor.DocId); doctor.Viewed += 1; _doctorRepository.Update(doctor); return View(doctor);

}

// Trrang profile phòng khám và bệnh viện [HttpGet("healthcare/pospitalorclinic/{slug}")] public async Task HospitalOrClinicProfile(string slug)

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383

{

var data = _hospitalRepository.GetAllData().Where(x =>

x.Slug.Equals(slug)).FirstOrDefault();

await _healthcareElastichSearchService.UpdateHealthcare(data.DocId); data.Viewed += 1; _hospitalRepository.Update(data); return View(data);

}

// Load dữ liệu thô từ MSSQL lên ElasticSearch [Authorize] [HttpGet("healthcare/InputData")] public async Task RunDataIndex() {

// Doctor var rs = _doctorRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >

0).ToList().Select(x => new HealthCareModel

{

Id = x.DocId, Name = x.Name ?? "", Address = x.OfficeAddress, KeyWords = $"{x.Name} , {x.Specialist}, {x.Services}", Specialist = x.Specialist ?? "", TypeHealthCare = HealthCareEnum.Doctor.GetHashCode(), PhoneNumber = x.PhoneNumber ?? "", Viewed = x.Viewed, WorkPlace = x.OfficeAddress, GeoLocation = new GeoLocation(x.Lat, x.Lon), Slug = x.Slug,

// Description = x.Services ?? "",

}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList(); foreach (var doc in rs) {

try {

await _healthcareElastichSearchService.InsertDataHealthcare(doc);

} catch (Exception ex) {

var x = ex.ToString(); _logger.LogError(ex, ex.Message);

}

} var host = _hospitalRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >

0).ToList().Select(x =>

new HealthCareModel {

Id = x.DocId, Name = x.FullName ?? "", Address = x.OfficeAddress, KeyWords = $"{x.FullName} , {x.Specialist},

{x.Services}",

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438

Specialist = x.Specialist ?? "", TypeHealthCare = x.Type == "benh-vien" ?

HealthCareEnum.Hospital.GetHashCode() : HealthCareEnum.Clinic.GetHashCode(),

PhoneNumber = x.PhoneNumber ?? "", Viewed = x.Viewed, WorkPlace = x.OfficeAddress, GeoLocation = new GeoLocation(x.Lat, x.Lon), Slug = x.Slug, // Description = x.Services ?? "",

}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList(); foreach (var r in host) {

try {

await _healthcareElastichSearchService.InsertDataHealthcare(r);

} catch (Exception ex) {

var x = ex.ToString(); _logger.LogError(ex, ex.Message);

}

}

return View();

}

// Xử lý update dữ liệu [HttpGet("healthcare/UpdateData")] public async Task UpdateData() {

var host = _hospitalRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >

0).ToList().Select(x =>

new HealthCareModel {

{x.Services}",

Id = x.DocId, Name = x.FullName ?? "", Address = x.OfficeAddress, KeyWords = $"{x.FullName} , {x.Specialist}, Specialist = x.Specialist ?? "", TypeHealthCare = x.Type == "benh-vien" ?

HealthCareEnum.Hospital.GetHashCode() : HealthCareEnum.Clinic.GetHashCode(),

PhoneNumber = x.PhoneNumber ?? "",

WorkPlace = x.OfficeAddress, GeoLocation = new GeoLocation(x.Lat, x.Lon), Slug = x.Slug, // Description = x.Services ?? "",

}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList(); foreach (var r in host) {

try

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493

{

_healthcareElastichSearchService.UpdateDataHealthcare(r);

} catch (Exception ex) {

var x = ex.ToString(); _logger.LogError(ex, ex.Message);

}

}

return View();

}

// Hàm xử lý tính năng Like Bác sĩ [HttpGet("healthcare/likedoctor/{slug}")] public IActionResult LikeDoctor(string slug) {

if (!string.IsNullOrEmpty(slug)) {

var doctor = _doctorRepository.GetByFiler(f => f.Slug ==

slug).FirstOrDefault();

var userId = string.Empty; bool isCookie = false; if (User.Identity.IsAuthenticated) {

userId = User.FindFirst(ClaimTypes.Name)?.Value;

} else {

//var user = User.Identity.Name; var cookie = HttpContext.Request.Cookies["x-header-userdata"];

if (string.IsNullOrEmpty(cookie)) {

cookie = Guid.NewGuid().ToString(); Response.Cookies.Append("x-header-userdata",

cookie, new CookieOptions() {

Path = "/"

}

); userId = cookie;

} isCookie = true;

}

var check = _doctorRepository.GetUserLikeDoctor(userId, doctor.Id); if (check == null) {

doctor.Liked = doctor.Liked ?? 0 + 1; _doctorRepository.InsertLike(userId, doctor.Id, isCookie); _doctorRepository.SaveChanges();

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548

return Json(new { Status = true, Message = $"Cảm ơn bạn đã đánh giá

hài lòng bác sĩ {doctor.Name}" }); }

return Json(new { Status = false, Message = $"Bạn đã đánh giá hài lòng bác

sĩ {doctor.Name} rồi." }); } return Json(new { Status = false, Message = "Thông tin không hợp lệ" });

}

// Hàm xử ký tính năng lưu Bác sĩ

[HttpGet("healthcare/savedoctor/{slug}")] public IActionResult SaveDoctor(string slug) {

if (!string.IsNullOrEmpty(slug)) {

slug = slug.Replace("{", "").Replace("}", ""); var doctor = _doctorRepository.GetByFiler(f => f.Slug ==

slug).FirstOrDefault();

var userId = string.Empty; if (User.Identity.IsAuthenticated) {

userId = User.FindFirst(ClaimTypes.Name)?.Value;

} else {

return RedirectToAction("login", "Accounts", new { returnUrl =

$"/healthcare/savedoctor/{slug}" });

}

var check = _doctorRepository.GetUserSaveDoctor(userId, doctor.Id); if (check == null) {

_doctorRepository.InsertSave(userId, doctor.Id); _doctorRepository.SaveChanges();

}

return RedirectToAction("MyBookmark", "Accounts");

} return Json(new { Status = false, Message = "Thông tin không hợp lệ" });

}

// Hàm xử lý tính năng like phòng khám bệnh viện [HttpGet("healthcare/likehospital/{slug}")] public IActionResult LikeHospital(string slug) {

if (!string.IsNullOrEmpty(slug)) {

var hospital = _hospitalRepository.GetByFiler(f => f.Slug ==

slug).FirstOrDefault();

var userId = string.Empty; bool isCookie = false;

549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603

if (User.Identity.IsAuthenticated) {

userId = User.FindFirst(ClaimTypes.Name)?.Value;

} else {

//var user = User.Identity.Name; var cookie = HttpContext.Request.Cookies["x-header-userdata"];

if (string.IsNullOrEmpty(cookie)) {

cookie = Guid.NewGuid().ToString(); Response.Cookies.Append("x-header-userdata",

cookie, new CookieOptions() {

Path = "/"

}

); userId = cookie;

} isCookie = true;

}

var check = _hospitalRepository.GetUserLikeHospital(userId, hospital.Id); if (check == null) {

hospital.Liked = hospital.Liked ?? 0 + 1; _hospitalRepository.InsertLike(userId, hospital.Id, isCookie); _hospitalRepository.SaveChanges(); return Json(new { Status = true, Message = $"Cảm ơn bạn đã đánh giá

hài lòng {hospital.FullName}" }); }

return Json(new { Status = false, Message = $"Bạn đã đánh giá hài lòng

{hospital.FullName} rồi." });

} return Json(new { Status = false, Message = "Thông tin không hợp lệ" });

}

// Hàm xử lý tính năng lưu phòng khám bệnh viện [HttpGet("healthcare/savehospital/{slug}")] public IActionResult SaveHospital(string slug) {

if (!string.IsNullOrEmpty(slug)) {

slug = slug.Replace("{", "").Replace("}", ""); var hospital = _hospitalRepository.GetByFiler(f => f.Slug ==

slug).FirstOrDefault();

var userId = string.Empty; if (User.Identity.IsAuthenticated) {

userId = User.FindFirst(ClaimTypes.Name)?.Value;

604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658

} else {

return RedirectToAction("login", "Accounts", new { returnUrl =

$"/healthcare/savehospital/{slug}" });

}

var check = _hospitalRepository.GetUserSaveHospital(userId, hospital.Id); if (check == null) {

_hospitalRepository.InsertSave(userId, hospital.Id); _hospitalRepository.SaveChanges();

}

return RedirectToAction("MyBookmark", "Accounts");

} return Json(new { Status = false, Message = "Thông tin không hợp lệ" });

}

}

// Controller cho Admin quản lý thêm Bác sĩ public class DoctorsController : Controller

{

private readonly IDoctorRepository _doctorRepository; private readonly ILogger _logger; private readonly IMapper _mapper; private readonly int _pageSize = 10; public DoctorsController(IDoctorRepository doctorRepository, IMapper mapper,

ILogger logger)

{

_doctorRepository = doctorRepository; _logger = logger; _mapper = mapper;

}

[HttpGet("doctors/getall")] public async Task Index(int? page) {

var filter = HttpContext.Request.Query["search"]; if (page == null) {

page = 1;

} var rs = _doctorRepository.GetAllData(); if (!string.IsNullOrEmpty(filter.ToString())) {

ViewData["CurrentFilter"] = filter.ToString(); rs = rs.Where(x =>x.Name.Contains(filter.ToString())).AsNoTracking();

}

return View(await PaginatedList.CreateAsync(rs.OrderByDescending(x =>

x.CreatedDate), page ?? 1, _pageSize));

659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713

}

[HttpGet("doctors/AddDoctor")] public IActionResult AddDoctor() {

return View();

}

[HttpPost("doctors/AddDoctor")] public IActionResult AddDoctor(DoctorModel model) {

if (ModelState.IsValid) {

var doctor = _mapper.Map(model); doctor.CreatedDate = DateTime.Now; doctor.UpdatedDate = DateTime.Now; doctor.Slug =

StringUtil.RemoveSign4VietnameseString(model.Name).Replace(" ", "_");

if (model.FileImage != null) {

string folder = $"UploadFiles/Images/Doctor/{DateTime.Now:yyyyMMdd}/";

doctor.ImageLink = ImageUtils.UploadFileImage(model.FileImage,

folder);

} _doctorRepository.Add(doctor); return RedirectToAction("Index");

} return View();

}

[HttpGet("doctors/EditDoctor/{id}")] public IActionResult EditDoctor(int id) {

var doctor = _doctorRepository.GetById(id); if(doctor != null) {

return View(_mapper.Map(doctor));

} return RedirectToAction("Error404", "Home");

}

}

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759