BỘ GIÁO DỤC VIỆN HÀN LÂM Y O KHOA HỌC VÀ CÔNG NGHỆ VN HỌC …
Transcript of BỘ GIÁO DỤC VIỆN HÀN LÂM Y O KHOA HỌC VÀ CÔNG NGHỆ VN HỌC …
NG
UY
ỄN
VĂ
N T
HÌN
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Ệ
HỆ
TH
ỐN
G T
HÔ
NG
TIN
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Ả
LUẬN VĂN THẠC SĨ NGÀNH MÁY TÍNH
2021
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
CNTT Công nghệ thông tin
CSDL Cơ sở dữ liệu
Server Máy chủ
Document Tài liệu
Index Chỉ mục
ES ElasticSearch
EHR Hồ sơ sức khỏe điện tử
IR Information Retrieval
Shard Phân đoạn
Query Truy vấn
DSL domain- Specific Language
URL Địa chỉ website
Term Từ khóa tìm kiếm
Boost Tăng cường
API Application Programming Interface
RESTful API Một tiêu chuẩn trong việc thiết kế API
row Dòng
table bảng
Unicast
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
link Liên kết
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 = {tài liệu liên quan} {tài liệu nhận được}
{tài liệu nhận được} (1)
• Độ 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ủ = {tài liệu liên quan} {tài liệu nhận được}
{tài liệu liên quan} (2)
• 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 = {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} (3)
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ề
được 150 tài liệu, trong đó có 130 tài liệu chính xác. Khi đó:
Độ chính xác = {200} ∩ {150}
{150} =
{130}
{150} ≈ 87%
Độ bao phủ = {200} ∩ {150}
{200} =
{130}
{200} ≈ 65%
Kết quả sai = {800} ∩ {150}
{800} =
{20}
{800} ≈ 2.5%
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
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.
Hình 1: Lịch sử hình thành công ty ElasticSearch
11
Các tập đoàn đang dùng ElasticSearch:
Hình 2: Các tập đoàn sử dụng ElasticSearch
Các đối thủ hiện nay:
Hình 3: Các đối thủ của ElasticSearch
12
Bảng so sánh các dịch vụ:
Hình 4: 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.
13
Hình 5: Hệ thống phân tán của ElasticSearch
• 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
2.3.2.2. Index (Chỉ mục)
Hình 7: Index trong ElasticSearch [19]
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
• Shard (phân đoạn) là đối tượng của Lucene, là tập con
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.
15
Hình 8: Sharding trong Index [19]
• 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.
• Có 2 loại Shard là: Primary Shard và Replica Shard.
Hình 9: 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ữ.
Công thức tính Shard:
shard = hash(routing) % number_of_primary_shards
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:
17
Hình 10: quá trình chuyển dữ liệu [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
(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.
Hình 11: Cluster trong ElasticSearch
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:
Ta có sơ đồ quan hệ giữa 2 table user và blogpost
Hình 12: Ví dụ về sơ đồ cơ sở dữ liệu của Mapping
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
thành các dòng mã riêng (stream of tokens).
Hình 13: Analyzer trong ElasticSearch
22
Ví dụ: Một Tokenizer cơ bản sẽ thực hiện các thao tác sau:
"Our goal at Tryolabs is to help Startups"
-> Tokenizer ->
["Our", "goal", "at", Tryolabs", "is", "to", "help", "Startups"]
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.
Các loại cấu hình Analyzer trong ElasticSearch:
• Standard Analyzer: chia văn bản thành các thuật ngữ về ranh giới từ,
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:
Cách sử dụng Kibana:
• Tạo Index:
PUT /news
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}
}
24
• Sau đó cài đặt:
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"
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 Analyzer như sau:
{
"custom_english_stemmer": {
"type": "stemmer",
"name": "english"
}
}
{
"analyzer": {
"custom_lowercase_stemmed": {
"tokenizer": "standard",
"filter": [
"lowercase",
"custom_english_stemmer"
]
}
}
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
trong mappings.
{
"mappings": {
"test": {
"properties": {
"text": {
"type": "string",
"analyzer": "custom_lowercase_stemmed"
}
}
}
}
}
Kiểm tra Analyzer, trong phần này sau khi đã tạo ra vi_analyzer dựa trên
Plugin của tác giả Duy Đỗ, ta kiểm tra kết quả của nó như sau:
GET danhba_yte/_analyze
27
{
"analyzer": "vi_analyzer",
"text": "Bác sĩ Phương chuyên khoa tai mũi họng"
}
Kết quả truy vấn:
{
"tokens" : [
{
"token" : "bác sĩ",
"start_offset" : 0,
"end_offset" : 6,
"type" : "<PHRASE>",
"position" : 0
},
{
"token" : "phương",
"start_offset" : 7,
"end_offset" : 13,
"type" : "<PHRASE>",
"position" : 1
},
{
"token" : "chuyên khoa",
28
"start_offset" : 14,
"end_offset" : 25,
"type" : "<PHRASE>",
"position" : 2
},
{
"token" : "tai",
"start_offset" : 26,
"end_offset" : 29,
"type" : "<PHRASE>",
"position" : 3
},
{
"token" : "mũi",
"start_offset" : 30,
"end_offset" : 33,
"type" : "<PHRASE>",
"position" : 4
},
{
"token" : "họng",
"start_offset" : 34,
"end_offset" : 38,
"type" : "<PHRASE>",
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
Câu truy vấn đơn giản nhất, phù hợp với tất cả các tài liệu.
GET news/_search
{
"query": {
"match_all": {}
}
}
30
Kết quả
Hình 14: Kết quả tìm kiếm Match all query
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
nhận văn bản, số, ngày tháng.
Hình 15: Kết quả Match query
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à
tạo 1 truy vấn cụm từ.
Hình 17: Kết quả Match phrase query
• Match Phrase Prefix Query: Cũng giống match_phrase, nhưng thêm
điều kiện khớp với tiền tố của từ trong văn bản.
32
Hình 18: Kết quả Match Phrase Prefix Query
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
nhiều trường:
Hình 19: Kết quả Multi Match Query
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
- lte: Nhỏ hơn hoặc bằng
- lt: Nhỏ hơn
Hình 20: Query có các parameters
• Date format in range queries: truy vấn theo định dạng ngày tháng
Hình 21: Query với format ngày
• 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
34
Hình 22: Wildcard Query
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
kiện.
Hình 23: Bool Query
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
36
Hình 24: Fuzzy Query
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ẽ
cho TF cao hơn (xuất hiện 2 lần) filed bên dưới (xuất hiện 1 lần):
{ "title": "The quick brown fox jumps over the quick dog" }
{ "title": "The quick brown fox" }
TF được tính theo công thức sau [21]:
frequency = d) tf(t, (4)
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,
càng ít thích hợp.
1)) + (docFreq / (numDocsln + 1 = idf(t) (5)
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.
Công thức [21]:
numTerms / 1 = norm(d) (6)
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
Putting it together: _score cuối cùng sẽ là tích của 3 giá trị trên:
IDF score * TF score * fieldNorms
)/1(**))1/(ln( numTermsfrequencydocFrednumDocs += (7)
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
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.
Hình 25: B25M
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]:
+−+
+n
ii
ii
navgFieldLe
fieldLenbbkDqf
kDqfqIDF
)1(*1),(
)11(*),()(
(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à :
)5.0)(
5.0)((1ln(
+
+−+
i
i
qf
qfdocCount
(9)
Ví dụ ta có 4 tài liệu chứa từ “shane” thì IDF(shane):
578261053605156.0)5.4
5.01ln()
5.04
5.0441ln( =+=
+
+−+
Và từ “connelly” chỉ xuất hiện ở 2 tài liệu thì có IDF(connelly):
599456931471805.0)5.2
5.21ln()
5.02
)5.024(1ln( =+=
+
+−+
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
Với công thức [23]:
((k1 + 1) * tf) / (k1 + tf) (10)
Ta có biểu đồ:
Hình 26: BM25 tiệm cận
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.
Với công thức [23]:
((k1 + 1) * tf) / (k1 * (1.0 - b + b * L) + tf) (11)
Và biểu đồ thể hiện giá trị của TF đối với các độ dài khác nhau của document:
Hình 27: BM25 với độ dài trung bình
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:
DELETE /bm25_index
PUT /bm25_index
{ "settings": { "number_of_shards": 1 }}
POST /bm25_index/my_type/_bulk
{ "index": { "_id": 1 }}
42
{ "title": "The quick brown fox" }
{ "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"
}
}
}
Kết quả: sẽ cho _id = 3 và _id = 4.
IDF = docCount = 4, docFreq = 2
log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))
= log(1 + (4-2 + 0.5) / (2+0.5))
= 0.6931471805599453
Với k1=1.2, b=0.75 ta có:
43
(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
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
tính toán bên trên.
{
"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" : [
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ư
sau:
Hình 28: Mô hình tìm kiếm văn bản tiếng Việt
• 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
Cách thực hiện như sau:
Hình 29: lược đồ về Analyzer
Từ lược đồ trên ta có thể thực hiện như sau:
• Cách 1: trên ngôn ngữ C#:
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ĩ",
48
"Bac si=>Bác sĩ",
"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"))
)))
49
• Cách 2: ta có thể dùng Kibana và sử dụng phương thức PUT
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ĩ",
50
"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"
]
}
}
}
}
}
Sau khi tạo xong mapping, chúng ta có thể kiểm tra lại kết quả:
GET healthcare/_analyze
{
"analyzer": "vn_analyzer",
"text": "bs"
51
}
Kết quả:
{
"tokens" : [
{
"token" : "bac si",
"start_offset" : 0,
"end_offset" : 2,
"type" : "<PHRASE>",
"position" : 0
}
]
}
GET healthcare/_analyze
{
"analyzer": "vn_analyzer",
"text": "tmh"
}
Kết quả:
{
"tokens" : [
{
"token" : "tai",
"start_offset" : 0,
52
"end_offset" : 2,
"type" : "<PHRASE>",
"position" : 0
},
{
"token" : "mui",
"start_offset" : 2,
"end_offset" : 2,
"type" : "<PHRASE>",
"position" : 1
},
{
"token" : "hong",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<PHRASE>",
"position" : 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:
53
Bảng 1: Số liệu mapping các từ khóa.
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
Thông tin thiết kế lược đồ cơ sở dữ liệu lưu trữ như sau:
Hình 30: lược đồ cơ sở dữ liệu Danh bạ y tế
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
là người dùng):
Hình 31: Mô hình cho người sử 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:
56
Hình 32: giao diện gợi ý khi nhập từ khóa
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:
Với từ khóa nhập vào: “bệnh vien tai mũi họng” kết quả như sau:
Hình 33: kết quả tìm kiếm có dấu
• 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:
57
Hình 34: kết quả tìm kiếm tiếng Việt không dấu
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.
Sau đây là code cấu hình và chọn “Analyzer” trong ngôn ngữ C# như sau:
var response = await _elasticClient.SearchAsync<HealthCareModel>(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);
• 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
thấy kết quả cần tìm.
Hình 35: Kết quả tìm kiế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.
59
Hình 36: Kết quả theo định vị
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ị.
Sau đây là code trong ngôn ngữ C# như sau:
var geo = await _elasticClient.SearchAsync<HealthCareModel>(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)
60
.Name("named_query")
.Field(p => p.GeoLocation)
.DistanceType(type)
.Location(lat, ln)
.Distance(distance)
.ValidationMethod(GeoValidationMethod.IgnoreMalformed)
)
)
);
• 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:
61
Hình 37: Tìm kiếm theo chuyên khoa
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 để
xem thêm thông tin hồ sơ đã tìm thấy:
Hình 38 thông tin chi tiết
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ơ
62
Hình 39: thông tin bản đồ theo địa chỉ của Profile
• 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ư:
- Lưu lại profile để sau này cần:
Hình 40: nút lưu profile
Người dùng bấm vào để lưu lại thông tin để sau này có thể sử
dụng lại mà không cần phải tìm 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
tính năng này.
Hình 41: hỏi đáp.
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,
vì vậy dễ dàng sử dùng lại thông tin này khi cần:
Hình 42: Thông tin đã lưu, thích
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
khoản, danh mục, xem danh sách câu hỏi, lịch hẹn khám chữa bệnh…
Hình 43: Trang quản trị Admin
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:
65
Hình 44: Cập nhật dữ liệu mới
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
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.
Hình 45: Quản trị tài khoả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.
67
Hình 46 Kiến trúc Mô hình thử nghiệm
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
• Tìm kiếm theo chuyên khoa
Bảng 2: Kịch bản tìm kiếm.
Kịch bản Từ khóa Số lượng
kết quả
Thời
gian tìm
kiếm
(giây)
Nhận xét
Tìm kiếm theo từ
khóa bất kỳ với
tiếng Việt có dấu
tai mũi họng 583 0.03 Hệ thống tìm thấy
toàn bộ danh sách
Bác sĩ, phòng
khám, Bệnh viện có
chuyên khoa tai mũi
họng
Tìm kiếm từ khóa
bất kỳ với tiếng
Việt không dấu
tai mui hong 583 0.02 Hệ thống tìm thấy
toàn bộ danh sách
Bác sĩ, phòng
khám, Bệnh viện có
chuyên khoa tai mũi
họng
Tìm kiếm bao
gồm tiếng Việt có
dấu và không dấu
bác sĩ Phương
tai mui hong
511 0.02 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
Tìm kiếm các từ
khóa có từ viết tắt
bs Phương tmh 511 0.01 Hệ thống đã tìm
thấy danh sách bác
69
như “bs” hoặc
“bv” hoặc những
từ khóa chuyên
khoa đã cấu hình
sẵn như “tmh”…
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
Tìm kiếm chính
xác từ khóa
Bệnh viện Tai
mũi họng
509 0.01 Bệnh viện Tai mũi
họng TPHCM được
đư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
Tìm kiếm với gợi
ý (Autocomplete)
có sử dụng fuzzy
trong
ElasticSearch
bv măt1 Chỉ lấy top
20 kết quả
0.01 Hệ thống vẫn tìm
thấy được kết quả
bệnh viện Mắt mặc
dù người dùng đã
nhập sai chữ “măt1”
Tìm kiếm theo
GEO có cấu hình
khoảng cách 3km
Chỉ lấy top
50 kết quả
0.01 Hệ thống sẽ tìm và
gợi ý tất cả bác sĩ,
phòng khám, bệnh
viện với tất cả
chuyên khoa dịch vụ
trong phạm vi 3km
Tìm kiếm theo
GEO và có từ
khóa kèm theo và
có cấu hình
khoảng cách 3km
Khám thai 195 0.01 Hệ thống tìm và đưa
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
Tìm kiếm theo
chuyên khoa
Nha khoa 1828 0.01 Hệ thống đưa ra
danh sách bác sĩ,
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
[1] Donggeng Yu (2019),Pronto Elasticsearch Extension Practice in eBay, Pronto,
eBay
https://elasticsearch.cn/uploads/slides/20191210/2d32de50d21190aac153993a8fb74
0b4.pdf, access date: Sep 9, 2020
[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.
[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.
[7] Nguyễn Thị Loan (2017), Nghiên cứu công nghệ tìm kiếm (Mã nguồn mở)
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
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
[9] Gerard Salton, Donna Harman (January 2003), “Information retrieval”,
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.
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
Explicit Semantic Analysis and Elasticsearch, The Graduate Center, City
University of New York.
[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.
[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 of Lucene Relevance (2015),
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ỤC1234
// Tạo interface cho các method5public interface IHealthcareElastichSearchService6
{7Task<PaginatedList<HealthCareModel>> GetDataSearch(string keyword, int pageSize,8
int page, int score);910
Task<PaginatedList<HealthCareModel>> GetDataSearchGeo(double lat, double ln,11GeoDistanceType type, string distance, int pageSize, int page, string keyword);12
13Task<PaginatedList<HealthCareModel>> GetDataSearchGeo(double lat, double ln,14
GeoDistanceType type, string distance, int pageSize, int page);1516
Task<List<string>> GetDataSuggestion(string keyword, int page, int pageSize);1718
Task<List<string>> GetDataSuggestionWithIcu(string term, int page, int pageSize);1920
Task InsertDataHealthcare(HealthCareModel model);2122
void UpdateDataHealthcare(HealthCareModel model);2324
void DeleteDataHealthcare(HealthCareModel model);2526
HealthCareModel GetDataHealthcarebyId(string id);2728
Task UpdateHealthcare(string id);2930
Task<PaginatedList<HealthCareModel>> GetDataSearchWithSpecialist(string31specialist, int pageSize, int page, float score);32
33}34
353637
// Implement Code kế thừa từ interface38public class HealthcareElastichSearchService : IHealthcareElastichSearchService39
{40private readonly IElasticClient _elasticClient;41private readonly string _index;42public HealthcareElastichSearchService(IElasticClient elasticClient,43
IConfiguration configuration)44{45
_elasticClient = elasticClient;46_index = configuration["elasticsearch:index"];47
}48495051
// Lấy dữ liệu từ document theo Id52public HealthCareModel GetDataHealthcarebyId(string id)53{54
var response = _elasticClient.Get<HealthCareModel>(id, u =>55u.Index(_index)).Source;56
return response;57}58
59// 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ữ li60
ệu61public async Task<PaginatedList<HealthCareModel>> GetDataSearch(string keyword,62
int pageSize, int page, int score)63{64
Stopwatch stopWatch = new Stopwatch();65stopWatch.Start();66var response = await _elasticClient.SearchAsync<HealthCareModel>(s => s67
.Query(q => q68.Match(m => m69
.Field(f => f.KeyWords)70
.Query(keyword)71)72
).From((page - 1) * pageSize).Size(pageSize)73.Sort(s =>74
s.Descending(SortSpecialField.Score)).MinScore(score)75).ConfigureAwait(false);76
77long total = response.HitsMetadata.Total.Value;78var rs = new PaginatedList<HealthCareModel>(response.Documents.ToList(), total,79
page, pageSize);80return rs;81
}828384
// Tìm kiếm theo chuyên khoa85public async Task<PaginatedList<HealthCareModel>>86
GetDataSearchWithSpecialist(string specialist, int pageSize, int page, float score)87{88
var response = await _elasticClient.SearchAsync<HealthCareModel>(89s => s.Query(q => q.QueryString(d => d.Query(specialist).Fields(f =>90
f.Fields(f => f.KeyWords)))).Sort(s => s.Descending(SortSpecialField.Score))91.From((page - 1) * pageSize).MinScore(score)92.Size(pageSize)).ConfigureAwait(false);93
var rs = new PaginatedList<HealthCareModel>(response.Documents.ToList(),94response.Documents.Count, page, pageSize);95
return rs;96}97
9899
// Tìm kiếm theo chuyên khoa và từ khóa100public async Task<PaginatedList<HealthCareModel>> GetDataSpecialistSearch(string101
specialist, string keyword, int pageSize, int page, int score)102{103
var response = await _elasticClient.SearchAsync<HealthCareModel>(104s => s.Query(q => q.QueryString(d => d.Query(keyword))).Sort(s =>105
s.Descending(SortSpecialField.Score))106.From((page - 1) * pageSize)107.Size(pageSize)).ConfigureAwait(false);108
var rs = new PaginatedList<HealthCareModel>(response.Documents.ToList(),109response.Documents.Count, page, pageSize);110
return rs;111}112
113// Tìm kiếm theo tọa độ114public async Task<PaginatedList<HealthCareModel>> GetDataSearchGeo(double lat,115
double ln, GeoDistanceType type, string distance, int pageSize, int page)116{117
118var geo = await _elasticClient.SearchAsync<HealthCareModel>(s =>119
s.From(0).Size(pageSize)//.ScriptFields(sf => sf.ScriptField("distance", d =>120d.Source("if(doc['geo'].size(){doc['geo'].arcDistance(" + lat + "," + ln + ")}")))121
122.Query(q => q123
124.GeoDistance(g => g125
.Boost(1.1)126
.Name("named_query")127
.Field(p => p.GeoLocation)128129
.DistanceType(type)130
.Location(lat, ln)131
.Distance(distance)132
.ValidationMethod(GeoValidationMethod.IgnoreMalformed)133))134
);135136
return new PaginatedList<HealthCareModel>(geo.Documents.ToList(),137geo.Documents.Count, page, pageSize);138
}139140141
// Tìm kiếm theo tọa độ và từ khóa142public async Task<PaginatedList<HealthCareModel>> GetDataSearchGeo(double lat,143
double ln, GeoDistanceType type, string distance, int pageSize, int page, string keyword)144{145
146var geo = await _elasticClient.SearchAsync<HealthCareModel>(s =>147
s.From(0).Size(pageSize)148.Query(q => q149
.Match(m => m150.Field(f => f.KeyWords)151.Query(keyword)152
)153).MinScore(5)154
.PostFilter(q => q155156
.GeoDistance(g => g157.Boost(1.1)158.Name("named_query")159.Field(p => p.GeoLocation)160
161.DistanceType(type)162.Location(lat, ln)163
.Distance(distance)164
.ValidationMethod(GeoValidationMethod.IgnoreMalformed)165)166)167
);168169
return new PaginatedList<HealthCareModel>(geo.Documents.ToList(),170geo.Documents.Count, page, pageSize);171
}172173
// lấy dữ liệu gợi ý cho từ khóa174public async Task<List<string>> GetDataSuggestion(string term, int page, int175
pageSize)176{177
var response = await _elasticClient.SearchAsync<HealthCareModel>(s => s178.Query(q => q179
.Match(m => m180.Field(f => f.KeyWords)181.Query(term).Fuzziness(Fuzziness.Auto)182
)183).From((page - 1) * pageSize).Size(pageSize).Sort(s =>184
s.Descending(SortSpecialField.Score))185).ConfigureAwait(false);186
var rs = response.Documents.Select(x => $" {x.Name} -{x.Specialist}");187188
return rs.ToList();189}190
191// Lấy dữ liệu gợi ý với từ khóa không dấu192public async Task<List<string>> GetDataSuggestionWithIcu(string term, int page, int193
pageSize)194{195
var response = await _elasticClient.SearchAsync<HealthCareModel>(s => s196.Query(q => q197
.Match(m => m198.Field(f => f.KeyWords)199.Query(term).Analyzer("my_analyzer").Fuzziness(F200
uzziness.Auto)201)202
).From((page - 1) * pageSize).Size(pageSize).Sort(s =>203s.Descending(SortSpecialField.Score)).MinScore(0.4)204
).ConfigureAwait(false);205var rs = response.Documents.Select(x => $" {x.Name} -{x.Specialist}");206
207return rs.ToList();208
}209210211
// Đẩy dữ liệu lên ElasticSearch212public async Task InsertDataHealthcare(HealthCareModel model)213{214
_ = await _elasticClient.IndexDocumentAsync(model);215}216
217// Cập nhật lại dữ liệu trên ElasticSearch218
public void UpdateDataHealthcare(HealthCareModel model)219{220
_elasticClient.Delete<dynamic>(model.Id, u => u.Index(_index));221222
_elasticClient.IndexDocument(model);223}224
225// Cập nhật lại dữ liệu trên ElasticSearch với cơ chế acsync226public async Task UpdateHealthcare(HealthCareModel model)227{228
var response = await _elasticClient.UpdateAsync<HealthCareModel>(model.Id, u229=> u.Index(_index).Doc(model));230
231}232
233// Cập nhật lại dữ liệu View từ người dùng234public async Task UpdateHealthcare(string id)235{236
var update = GetDataHealthcarebyId(id);237if (update != null)238{239
update.Viewed += 1;240await _elasticClient.UpdateAsync<HealthCareModel>(update.Id, u =>241
u.Index(_index).Doc(update));242}243
244}245
}246247
// Controller và xử lý route url248public class HealthcareController : Controller249
{250private readonly IHealthcareElastichSearchService251
_healthcareElastichSearchService;252private readonly IDoctorRepository _doctorRepository;253private readonly IHospitalRepository _hospitalRepository;254private readonly ILogger<HealthcareController> _logger;255public HealthcareController(256
IHealthcareElastichSearchService healthcareElastichSearchService,257IDoctorRepository doctorRepository,258IHospitalRepository hospitalRepository,259ILogger<HealthcareController> logger)260
{261_healthcareElastichSearchService = healthcareElastichSearchService;262_doctorRepository = doctorRepository;263_hospitalRepository = hospitalRepository;264_logger = logger;265
}266267268
// Trang chủ269public IActionResult Index()270{271
272return View();273
}274275
// Trang tìm kiếm276[HttpGet("healthcare/Search")]277public async Task<IActionResult> Search(int? page)278{279
string term = HttpContext.Request.Query["term"].ToString();280;281page = page ?? 1;282
283var rs = await _healthcareElastichSearchService.GetDataSearch(term, 100,284
page.Value, 10);285ViewBag.term = term;286return View(rs);287
}288289290
// Trang gợi ý dữ liệu với vị trí gần tôi291[HttpGet("healthcare/FindNearby")]292public IActionResult FindNearby(double lat, double ln)293{294
295return View();296
}297298
// Trang tìm kiếm từ khóa theo độ tọa299[HttpGet("healthcare/FindNearme/{lat}/{ln}")]300public async Task<IActionResult> FindNearme(double lat, double ln)301{302
303var keyword = HttpContext.Request.Query["keyword"];304
305if (lat != 0 && ln != 0)306{307
308PaginatedList<HealthCareModel> rs = null;309if (string.IsNullOrEmpty(keyword))310{311
rs = await _healthcareElastichSearchService.GetDataSearchGeo(lat, ln,312Nest.GeoDistanceType.Arc, "3000m", 50, 1);313
}314else315{316
rs = await _healthcareElastichSearchService.GetDataSearchGeo(lat, ln,317Nest.GeoDistanceType.Arc, "3000m", 100, 1, keyword);318
}319ViewBag.Lat = lat;320ViewBag.Ln = ln;321return View(rs);322
}323return View();324
}325326
// Tìm kiếm theo chuyên khoa327[HttpGet("healthcare/FindbySpecialist")]328
public async Task<IActionResult> FindbySpecialist(string specialist)329{330
331ViewBag.Selected = specialist;332var rs = await333
_healthcareElastichSearchService.GetDataSearchWithSpecialist(specialist, 30, 1, 5);334return View(rs);335
}336337
// Get địa chỉ338[HttpGet("healthcare/GetAddress")]339public JsonResult GetAddress(double lat, double ln)340{341
342var rs = CommonUtils.GetAddress(lat, ln);343var displayname = rs.display_name.Replace(", Long Điền, 711001, Việt Nam", "");344
345return Json(new346{347
success = true,348message = "Thành công",349data = $"{displayname}"350
});351}352
353// Gợi ý từ khóa354[HttpGet("healthcare/SearchSuggest")]355public async Task<IActionResult> SearchSuggest()356{357
358string term = HttpContext.Request.Query["term"].ToString();359
360var rs = await _healthcareElastichSearchService.GetDataSuggestion(term, 1,361
20);362return Ok(rs);363
364}365
366// Trang profile Bác sĩ367[HttpGet("healthcare/doctorprofile/{slug}")]368public async Task<IActionResult> DoctorProfile(string slug)369{370
var doctor = _doctorRepository.GetAllData().Where(x => x.Slug.Equals(slug))371.Include(i => i.Questions).FirstOrDefault();372
373await _healthcareElastichSearchService.UpdateHealthcare(doctor.DocId);374doctor.Viewed += 1;375_doctorRepository.Update(doctor);376return View(doctor);377
}378379380
// Trrang profile phòng khám và bệnh viện381[HttpGet("healthcare/pospitalorclinic/{slug}")]382public async Task<IActionResult> HospitalOrClinicProfile(string slug)383
{384var data = _hospitalRepository.GetAllData().Where(x =>385
x.Slug.Equals(slug)).FirstOrDefault();386await _healthcareElastichSearchService.UpdateHealthcare(data.DocId);387data.Viewed += 1;388_hospitalRepository.Update(data);389return View(data);390
}391392393
// Load dữ liệu thô từ MSSQL lên ElasticSearch394[Authorize]395[HttpGet("healthcare/InputData")]396public async Task<IActionResult> RunDataIndex()397{398
// Doctor399var rs = _doctorRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >400
0).ToList().Select(x => new HealthCareModel401{402
Id = x.DocId,403Name = x.Name ?? "",404Address = x.OfficeAddress,405KeyWords = $"{x.Name} , {x.Specialist}, {x.Services}",406Specialist = x.Specialist ?? "",407TypeHealthCare = HealthCareEnum.Doctor.GetHashCode(),408PhoneNumber = x.PhoneNumber ?? "",409Viewed = x.Viewed,410WorkPlace = x.OfficeAddress,411GeoLocation = new GeoLocation(x.Lat, x.Lon),412Slug = x.Slug,413
414// Description = x.Services ?? "",415
416}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList();417foreach (var doc in rs)418{419
try420{421
await _healthcareElastichSearchService.InsertDataHealthcare(doc);422}423catch (Exception ex)424{425
var x = ex.ToString();426_logger.LogError(ex, ex.Message);427
}428429
}430var host = _hospitalRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >431
0).ToList().Select(x =>432new HealthCareModel433{434
Id = x.DocId,435Name = x.FullName ?? "",436Address = x.OfficeAddress,437KeyWords = $"{x.FullName} , {x.Specialist}, {x.Services}",438
Specialist = x.Specialist ?? "",439TypeHealthCare = x.Type == "benh-vien" ?440
HealthCareEnum.Hospital.GetHashCode() : HealthCareEnum.Clinic.GetHashCode(),441PhoneNumber = x.PhoneNumber ?? "",442Viewed = x.Viewed,443WorkPlace = x.OfficeAddress,444GeoLocation = new GeoLocation(x.Lat, x.Lon),445Slug = x.Slug,446// Description = x.Services ?? "",447
448}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList();449foreach (var r in host)450{451
try452{453
await _healthcareElastichSearchService.InsertDataHealthcare(r);454}455catch (Exception ex)456{457
var x = ex.ToString();458_logger.LogError(ex, ex.Message);459
}460461
}462463
return View();464}465
466// Xử lý update dữ liệu467[HttpGet("healthcare/UpdateData")]468public async Task<IActionResult> UpdateData()469{470
471var host = _hospitalRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >472
0).ToList().Select(x =>473new HealthCareModel474{475
Id = x.DocId,476Name = x.FullName ?? "",477Address = x.OfficeAddress,478KeyWords = $"{x.FullName} , {x.Specialist}, {x.Services}",479Specialist = x.Specialist ?? "",480TypeHealthCare = x.Type == "benh-vien" ?481
HealthCareEnum.Hospital.GetHashCode() : HealthCareEnum.Clinic.GetHashCode(),482PhoneNumber = x.PhoneNumber ?? "",483
484WorkPlace = x.OfficeAddress,485GeoLocation = new GeoLocation(x.Lat, x.Lon),486Slug = x.Slug,487// Description = x.Services ?? "",488
489}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList();490foreach (var r in host)491{492
try493
{494_healthcareElastichSearchService.UpdateDataHealthcare(r);495
}496catch (Exception ex)497{498
var x = ex.ToString();499_logger.LogError(ex, ex.Message);500
}501502
}503504
return View();505}506
507// Hàm xử lý tính năng Like Bác sĩ508[HttpGet("healthcare/likedoctor/{slug}")]509public IActionResult LikeDoctor(string slug)510{511
if (!string.IsNullOrEmpty(slug))512{513
var doctor = _doctorRepository.GetByFiler(f => f.Slug ==514slug).FirstOrDefault();515
var userId = string.Empty;516bool isCookie = false;517if (User.Identity.IsAuthenticated)518{519
userId = User.FindFirst(ClaimTypes.Name)?.Value;520}521else522{523
//var user = User.Identity.Name;524var cookie = HttpContext.Request.Cookies["x-header-userdata"];525
526if (string.IsNullOrEmpty(cookie))527{528
cookie = Guid.NewGuid().ToString();529Response.Cookies.Append("x-header-userdata",530
cookie,531new CookieOptions()532{533
Path = "/"534}535
);536userId = cookie;537
538}539isCookie = true;540
}541542
var check = _doctorRepository.GetUserLikeDoctor(userId, doctor.Id);543if (check == null)544{545
doctor.Liked = doctor.Liked ?? 0 + 1;546_doctorRepository.InsertLike(userId, doctor.Id, isCookie);547_doctorRepository.SaveChanges();548
return Json(new { Status = true, Message = $"Cảm ơn bạn đã đánh giá549hài lòng bác sĩ {doctor.Name}" });550
}551552
return Json(new { Status = false, Message = $"Bạn đã đánh giá hài lòng bác553sĩ {doctor.Name} rồi." });554
}555return Json(new { Status = false, Message = "Thông tin không hợp lệ" });556
557}558
559// Hàm xử ký tính năng lưu Bác sĩ560
[HttpGet("healthcare/savedoctor/{slug}")]561public IActionResult SaveDoctor(string slug)562{563
if (!string.IsNullOrEmpty(slug))564{565
slug = slug.Replace("{", "").Replace("}", "");566var doctor = _doctorRepository.GetByFiler(f => f.Slug ==567
slug).FirstOrDefault();568var userId = string.Empty;569if (User.Identity.IsAuthenticated)570{571
userId = User.FindFirst(ClaimTypes.Name)?.Value;572}573else574{575
return RedirectToAction("login", "Accounts", new { returnUrl =576$"/healthcare/savedoctor/{slug}" });577
}578579
var check = _doctorRepository.GetUserSaveDoctor(userId, doctor.Id);580if (check == null)581{582
_doctorRepository.InsertSave(userId, doctor.Id);583_doctorRepository.SaveChanges();584
585}586
587return RedirectToAction("MyBookmark", "Accounts");588
}589return Json(new { Status = false, Message = "Thông tin không hợp lệ" });590
591}592
593// Hàm xử lý tính năng like phòng khám bệnh viện594[HttpGet("healthcare/likehospital/{slug}")]595public IActionResult LikeHospital(string slug)596{597
if (!string.IsNullOrEmpty(slug))598{599
var hospital = _hospitalRepository.GetByFiler(f => f.Slug ==600slug).FirstOrDefault();601
var userId = string.Empty;602bool isCookie = false;603
if (User.Identity.IsAuthenticated)604{605
userId = User.FindFirst(ClaimTypes.Name)?.Value;606}607else608{609
//var user = User.Identity.Name;610var cookie = HttpContext.Request.Cookies["x-header-userdata"];611
612if (string.IsNullOrEmpty(cookie))613{614
cookie = Guid.NewGuid().ToString();615Response.Cookies.Append("x-header-userdata",616
cookie,617new CookieOptions()618{619
Path = "/"620}621
);622userId = cookie;623
624}625isCookie = true;626
}627628
var check = _hospitalRepository.GetUserLikeHospital(userId, hospital.Id);629if (check == null)630{631
hospital.Liked = hospital.Liked ?? 0 + 1;632_hospitalRepository.InsertLike(userId, hospital.Id, isCookie);633_hospitalRepository.SaveChanges();634return Json(new { Status = true, Message = $"Cảm ơn bạn đã đánh giá635
hài lòng {hospital.FullName}" });636}637
638return Json(new { Status = false, Message = $"Bạn đã đánh giá hài lòng639
{hospital.FullName} rồi." });640}641return Json(new { Status = false, Message = "Thông tin không hợp lệ" });642
643}644
645// Hàm xử lý tính năng lưu phòng khám bệnh viện646[HttpGet("healthcare/savehospital/{slug}")]647public IActionResult SaveHospital(string slug)648{649
if (!string.IsNullOrEmpty(slug))650{651
slug = slug.Replace("{", "").Replace("}", "");652var hospital = _hospitalRepository.GetByFiler(f => f.Slug ==653
slug).FirstOrDefault();654var userId = string.Empty;655if (User.Identity.IsAuthenticated)656{657
userId = User.FindFirst(ClaimTypes.Name)?.Value;658
}659else660{661
return RedirectToAction("login", "Accounts", new { returnUrl =662$"/healthcare/savehospital/{slug}" });663
}664665
var check = _hospitalRepository.GetUserSaveHospital(userId, hospital.Id);666if (check == null)667{668
_hospitalRepository.InsertSave(userId, hospital.Id);669_hospitalRepository.SaveChanges();670
671}672
673return RedirectToAction("MyBookmark", "Accounts");674
}675return Json(new { Status = false, Message = "Thông tin không hợp lệ" });676
677}678
679}680
681// Controller cho Admin quản lý thêm Bác sĩ682public class DoctorsController : Controller683
{684private readonly IDoctorRepository _doctorRepository;685private readonly ILogger<DoctorsController> _logger;686private readonly IMapper _mapper;687private readonly int _pageSize = 10;688public DoctorsController(IDoctorRepository doctorRepository, IMapper mapper,689
ILogger<DoctorsController> logger)690{691
_doctorRepository = doctorRepository;692_logger = logger;693_mapper = mapper;694
}695696
[HttpGet("doctors/getall")]697public async Task<IActionResult> Index(int? page)698{699
var filter = HttpContext.Request.Query["search"];700if (page == null)701{702
page = 1;703}704var rs = _doctorRepository.GetAllData();705if (!string.IsNullOrEmpty(filter.ToString()))706{707
ViewData["CurrentFilter"] = filter.ToString();708rs = rs.Where(x =>x.Name.Contains(filter.ToString())).AsNoTracking();709
}710711
return View(await PaginatedList<Doctor>.CreateAsync(rs.OrderByDescending(x =>712x.CreatedDate), page ?? 1, _pageSize));713
}714715
[HttpGet("doctors/AddDoctor")]716public IActionResult AddDoctor()717{718
return View();719}720
721[HttpPost("doctors/AddDoctor")]722public IActionResult AddDoctor(DoctorModel model)723{724
if (ModelState.IsValid)725{726
var doctor = _mapper.Map<Doctor>(model);727doctor.CreatedDate = DateTime.Now;728doctor.UpdatedDate = DateTime.Now;729doctor.Slug =730
StringUtil.RemoveSign4VietnameseString(model.Name).Replace(" ", "_");731732
if (model.FileImage != null)733{734
string folder =735$"UploadFiles/Images/Doctor/{DateTime.Now:yyyyMMdd}/";736
doctor.ImageLink = ImageUtils.UploadFileImage(model.FileImage,737folder);738
739}740_doctorRepository.Add(doctor);741return RedirectToAction("Index");742
743}744return View();745
}746747
[HttpGet("doctors/EditDoctor/{id}")]748public IActionResult EditDoctor(int id)749{750
751var doctor = _doctorRepository.GetById(id);752if(doctor != null)753{754
return View(_mapper.Map<DoctorModel>(doctor));755}756return RedirectToAction("Error404", "Home");757
}758}759