BỘ GIÁO DỤC VÀ ĐÀO TẠO
VIỆN HÀN LÂM KHOA HỌC VÀ CÔNG NGHỆ VN
HỌC VIỆN KHOA HỌC VÀ CÔNG NGHỆ
N G U Y Ễ N V Ă N T H
Ì N
Nguyễn Văn Thìn
ỨNG DỤNG MÃ NGUỒN MỞ ELASTICSEARCH VÀO HỆ THỐNG TÌM KIẾM DANH BẠ Y TẾ HIỆU QUẢ
H Ệ T H Ố N G T H Ô N G T I N
LUẬN VĂN THẠC SĨ NGÀNH MÁY TÍNH
2 0 2 1
Thành phố Hồ Chí Minh - 2021
BỘ GIÁO DỤC VIỆN HÀN LÂM
VÀ ĐÀO TẠO KHOA HỌC VÀ CÔNG NGHỆ VN
HỌC VIỆN KHOA HỌC VÀ CÔNG NGHỆ
Nguyễn Văn Thìn
ỨNG DỤNG MÃ NGUỒN MỞ ELASTICSEARCH VÀO HỆ
THỐNG TÌM KIẾM DANH BẠ Y TẾ HIỆU QUẢ
Chuyên ngành : Hệ Thống Thông Tin.
Mã số : 8480104
LUẬN VĂN THẠC SĨ NGÀNH MÁY TÍNH
NGƯỜI HƯỚNG DẪN KHOA HỌC: TS TRẦN TRỌNG TOÀN
Thành phố Hồ Chí Minh – 2021
i
LỜI CAM ĐOAN
Tôi cam đoan luận văn “Ứng dụng mã nguồn mở ElasticSearch vào hệ thống
tìm kiếm danh bạ y tế hiệu quả” là công trình nghiên cứu của riêng tôi dưới sự hướng dẫn của Thầy TS Trần Trọng Toàn. Sự gần gũi và nhiệt tình hướng dẫn của thầy là
nguồn động lực rất lớn đối với tôi trong suốt thời gian thực hiện.
Các số liệu, kết quả nêu trong luận văn là trung thực và chưa từng được ai
công bố trong bất kỳ công trình nào khác.
Thành phố Hồ Chí Minh, ngày tháng năm 2021
Học viên thực hiện
Nguyễn Văn Thìn
ii
LỜI CẢM ƠN
Tôi xin gửi lời cảm ơn sâu sắc đến thầy TS. Trần Trọng Toàn đã tận tình hướng dẫn
và giúp đỡ tôi trong suốt quá trình thực hiện luận văn.
Cảm ơn quý thầy cô Khoa Công Nghệ Thông Tin và Viễn Thông, cũng như các Thầy Cô của Học Viện Khoa học và Công Nghệ, quý thầy cô tham gia giảng dạy và truyền
đạt kiến thức cho bản thân tôi trong suốt khóa học 2018 – 2020.
Cho phép tôi gửi lời cảm ơn tới các bạn, đồng nghiệp đã thường xuyên quan tâm,
giúp đỡ, chia sẽ kinh nghiệm trong suốt thời gian học tập, nghiên cứu tại Học viện cũng như trong suốt quá trình thực hiện luận văn.
Tôi xin bày tỏ sự biết ơn sâu sắc đến cha, mẹ, vợ, những người thân trong gia đình đã
luôn ở bên tôi, động viên, dành cho tôi những gì tốt đẹp nhất trong suốt quá trình thực
hiện luận văn này.
Cuối cùng tôi xin chân thành cảm ơn Công ty Cổ phần MediHub đã tạo điều kiện
giúp tôi thực hiện tốt luận văn.
Trân trọng cảm ơn!
iii
MỤC LỤC
LỜI CAM ĐOAN ........................................................................................................ i
LỜI CẢM ƠN ............................................................................................................. ii
MỤC LỤC ................................................................................................................. iii
DANH MỤC VIẾT TẮT ........................................................................................... iv
DANH MỤC CÁC BẢNG ......................................................................................... vi
DANH MỤC CÁC HÌNH MINH HỌA ................................................................... vii
MỞ ĐẦU ..................................................................................................................... 1
CHƯƠNG 1 - TỔNG QUAN VỀ HỆ THỐNG TÌM KIẾM THÔNG TIN ............... 4
1.1 Khái niệm về tìm kiếm thông tin ....................................................................... 4
1.2 Khái niệm về hệ thống tìm kiếm thông tin ........................................................ 6
1.2.1 Khái niệm về hệ thống tìm kiếm thông tin .................................................. 6
1.2.2 Các bộ phận cấu thành hệ thống tìm kiếm thông tin ................................... 6
CHƯƠNG 2 - GIỚI THIỆU BÀI TOÁN VÀ LỰA CHỌN CÔNG NGHỆ .............. 9
2.1 Giới thiệu bài toán ............................................................................................. 9
2.2 Phương pháp giải quyết ..................................................................................... 9
2.3 Tổng quan ElasticSearch ................................................................................. 10
2.3.1 Khái niệm về ElasticSearch ....................................................................... 10
2.3.2 Các khái niệm cần biết trong ElasticSearch .............................................. 13
2.3.3. Analyzers và mô hình truy hồi thông tin của ElasticSearch .................... 21
2.3.4. Query DSL (domain- Specific Language) trong ElasticSearch .............. 29
2.3.5 Mô hình truy hồi thông tin của ElasticSearch ........................................... 36
CHƯƠNG 3. THỰC NGHIỆM XÂY DỰNG WEBSITE TÌM KIẾM DANH BẠ Y TẾ .............................................................................................................................. 45
3.1 Phân tích .......................................................................................................... 45
3.2 Thiết kế ............................................................................................................ 46
3.3 Cài đặt .............................................................................................................. 55
3.4 Giao diện .......................................................................................................... 55
iv
3.4.1. Giao diện cho người sử dụng ................................................................... 55
3.4.2. Giao diện cho người quản trị .................................................................... 64
3.5 Đánh giá và thử nghiệm ................................................................................... 66
3.5.1. Mô hình kiến trúc ứng dụng thử nghiệm .................................................. 66
3.5.2. Kịch bản và kết quả .................................................................................. 67
3.5.3 Đánh giá kết quả nghiên cứu ..................................................................... 70
CHƯƠNG 4. KẾT LUẬN ......................................................................................... 72
DANH MỤC TÀI LIỆU THAM KHẢO .................................................................. 73
v
DANH MỤC CÁC THUẬT NGỮ, CHỮ VIẾT TẮT
Công nghệ thông tin CNTT
Cơ sở dữ liệu CSDL
Máy chủ Server
Tài liệu Document
Chỉ mục Index
ElasticSearch ES
Hồ sơ sức khỏe điện tử EHR
Information Retrieval IR
Phân đoạn Shard
Truy vấn Query
domain- Specific Language DSL
Địa chỉ website URL
Từ khóa tìm kiếm Term
Tăng cường Boost
Application Programming Interface API
RESTful API Một tiêu chuẩn trong việc thiết kế API
Dòng row
bảng table
là 1 thuật ngữ được sử dụng trong mạng máy tính
để mô tả cách thức truyền tin được gửi từ 1 điểm
đến 1 điểm khác Unicast
Liên kết link
vi
DANH MỤC CÁC BẢNG
Bảng 1: Số liệu mapping các từ khóa. ...................................................................... 53
Bảng 2: Kịch bản tìm kiếm. ....................................................................................... 68
vii
DANH MỤC CÁC HÌNH MINH HỌA
Hình 1: Lịch sử hình thành công ty ElasticSearch ................................................... 10
Hình 2: Các tập đoàn sử dụng ElasticSearch ........................................................... 11 Hình 3: Các đối thủ của ElasticSearch ..................................................................... 11
Hình 4: Bảng so sánh các dịch vụ ............................................................................. 12
Hình 5: Hệ thống phân tán của ElasticSearch ......................................................... 13
Hình 6: Các khái niệm cần biết trong ElasticSearch................................................ 13
Hình 7: Index trong ElasticSearch ............................................................................ 14 Hình 8: Sharding trong Index ................................................................................... 15
Hình 9: Primary Shard và Replica Shard ................................................................. 15
Hình 10: quá trình chuyển dữ liệu ............................................................................ 17
Hình 11: Cluster trong ElasticSearch ....................................................................... 18
Hình 12: Ví dụ về sơ đồ cơ sở dữ liệu của Mapping ................................................ 19
Hình 13: Analyzer trong ElasticSearch .................................................................... 21
Hình 14: Kết quả tìm kiếm Match all query .............................................................. 30
Hình 15: Kết quả Match query.................................................................................. 30
Hình 16: Kết quả Match query thêm and .................................................................. 31
Hình 17: Kết quả Match phrase query ...................................................................... 31 Hình 18: Kết quả Match Phrase Prefix Query ......................................................... 32
Hình 19: Kết quả Multi Match Query ....................................................................... 32
Hình 20: Query có các parameters ........................................................................... 33
Hình 21: Query với format ngày ............................................................................... 33
Hình 22: Wildcard Query .......................................................................................... 34
Hình 23: Bool Query ................................................................................................. 34
Hình 24: Fuzzy Query ............................................................................................... 36 Hình 25: B25M .......................................................................................................... 38 Hình 26: BM25 tiệm cận ........................................................................................... 40 Hình 27: BM25 với độ dài trung bình ....................................................................... 41 Hình 28: Mô hình tìm kiếm văn bản tiếng Việt ......................................................... 46 Hình 29: lược đồ về Analyzer ................................................................................... 47
Hình 30: lược đồ cơ sở dữ liệu Danh bạ y tế ............................................................ 53 Hình 31: Mô hình cho người sử dụng ....................................................................... 54
Hình 32: giao diện gợi ý khi nhập từ khóa ............................................................... 56
Hình 33: kết quả tìm kiếm có dấu ............................................................................. 56
Hình 34: kết quả tìm kiếm tiếng Việt không dấu ....................................................... 57
viii
Hình 35: Kết quả tìm kiếm ........................................................................................ 58 Hình 36: Kết quả theo định vị ................................................................................... 59
Hình 37: Tìm kiếm theo chuyên khoa ........................................................................ 61
Hình 38 thông tin chi tiết .......................................................................................... 61
Hình 39: thông tin bản đồ theo địa chỉ của Profile .................................................. 62 Hình 40: nút lưu profile ............................................................................................ 62
Hình 41: hỏi đáp. ...................................................................................................... 63
Hình 42: Thông tin đã lưu, thích ............................................................................... 63
Hình 43: Trang quản trị Admin ................................................................................ 64
Hình 44: Cập nhật dữ liệu mới ................................................................................. 65 Hình 45: Quản trị tài khoản ...................................................................................... 65
Hình 46 Kiến trúc Mô hình thử nghiệm .................................................................... 67
1
MỞ ĐẦU
Hiện nay sức khỏe đang là mối quan tâm hàng đầu của nhiều người. Ngoài
mong muốn được tiếp cận các thông tin sức khỏe chính thống - hữu ích để chăm sóc
bản thân và gia đình tốt hơn, nhu cầu tìm kiếm nơi khám chữa bệnh uy tín, bác sĩ giỏi chuyên môn của người dân cũng rất cao. Để đáp ứng nhu cầu này thì cần có các công
cụ tìm kiếm nhanh, ngoài hỗ trợ người dân tiếp cận các thông tin cần biết, còn giúp
chọn lựa được nơi chăm sóc sức khỏe, bác sĩ tốt nhất thông qua những tính năng
tương tác trên các website trực tuyến.
Song song đó, cùng với sự phát triển của Internet và điện thoại thông minh cho phép người dùng có thể kết nối mạng ở bất cứ đâu, truy cập hồ sơ bác sĩ/cơ sở khám
chữa bệnh một cách dễ dàng để đánh giá, bình luận… điều này giúp tăng độ tin cậy
và chính xác của các thông tin trên. Càng ngày, với sự “góp sức” của nhiều người,
những thông tin ngày càng được hoàn thiện và thực sự hữu ích cho người dùng sau.
Tuy nhiên, trong lĩnh vực y tế, việc phân chia các cơ sở khám chữa bệnh, hồ
sơ bác sĩ về chuyên khoa; dịch vụ khám chữa bệnh, địa chỉ, số điện thoại, thời gian
làm việc… sao cho người dùng có thể tìm kiếm một cách dễ dàng – nhanh chóng –
chính xác nhất thực sự là vấn đề nan giải. Các website về lĩnh vực y tế trong nước
như edoctor.io, bacsi247.net … đã có hỗ trợ thông tin tìm kiếm bác sĩ, phòng khám,
bệnh viện… tuy nhiên việc tìm kiếm tiếng Việt vẫn là vấn đề nan giải và các website này vẫn chưa đáp ứng được.
Gần đây, nhiều thư viện nguồn mở hỗ trợ mạnh việc tìm kiếm thông tin nhanh
như Elastic Search, Solr … Điều đặc biệt hơn cả là việc xuất hiện nhiều mã nguồn
mở xử lý ngôn ngữ tiếng Việt do các kỹ sư CNTT hay các nhà khoa học tại Việt Nam
phát triển như underthesea, vn_tokenizer… đã làm cho việc tìm kiếm dữ liệu tiếng
Việt ngày càng chính xác hơn.
Ngoài ra các Website lớn trên thế giới như eBay [1], Vimeo [2] đã sử dụng ElasticSearch vào việc phát triển hệ thống tìm kiếm của mình, và về lĩnh vực y tế - sức khỏe UCLA Health [3] đã sử dụng ElasticSearch vào hệ thống Hồ sơ sức khỏe điện tử (EHR) để phục vụ cho các Bác sĩ lâm sàng và các nhà nghiên cứu trong việc
tìm kiếm bệnh lý, bao gồm ghi chú lâm sàng, kết quả phòng thí nghiệm, văn bản, lịch sử khám chữa bệnh của bệnh nhân để giúp chẩn đoán bệnh.
Trong nước, cũng có khá nhiều tác giả quan tâm nghiên cứu về lĩnh vực tìm kiếm thông tin như “Hỗ trợ Tìm kiếm Thông tin, thuộc lãnh vực CNTT trên Internet qua
2
từ khóa bằng Tiếng Việt” [4], “Phát Triển hệ truy hồi thông tin tiếng Việt dựa trên mã nguồn mở” [5]. Tuy nhiên chúng ta vẫn chưa thấy một hệ thống tìm kiếm nào về
danh bạ y tế đáp ứng được nhu cầu của người dân hiện nay, và chúng ta vẫn còn phụ
thuộc vào công cụ tìm kiếm Google nên đôi khi phải mất nhiều thời gian mới tìm thấy
được những thông tin mà mình cần.
Tính cấp thiết của đề tài:
Từ những khó khăn như phân tích ở trên, yêu cầu cấp thiết là xây dựng một hệ
thống tìm kiếm Tiếng Việt về danh bạ y tế để giúp người dùng dễ dàng tìm kiếm các
thông tin như: hồ sơ bác sĩ, phòng khám, bệnh viện…đồng thời có thể tiếp cận được
các thông tin hữu ích về sức khỏe chính thống từ các bác sĩ, chuyên gia y tế, và cũng chính họ là người sẽ tương tác với các tính năng sẵn có của hệ thống như bình luận,
đánh giá,… làm tăng độ tin cậy của thông tin cho người dùng sau.
Đối tượng và phạm vi nghiên cứu:
Để xây dựng được hệ thống tìm kiếm Tiếng Việt mang tính cấp thiết như đã đề
cập ở trên, đối tượng nghiên cứu được chọn là:
Nghiên cứu dữ liệu và thông tin y tế với nguồn dữ liệu từ hệ thống tìm kiếm cơ
sở y tế của công ty MediHub.
Nghiên cứu về tìm kiếm thông tin, hệ thống tìm kiếm thông tin và các thành phần
cấu tạo.
Nghiên cứu mã nguồn mở ElasticSearch để ứng dụng vào việc đánh chỉ mục cho
hệ thống tìm kiếm thông tin.
Nghiên cứu Asp.Net Core, C# để xây dựng một hệ thống tìm kiếm thông tin hoàn
chỉnh và các tính năng tương tác trên danh bạ y tế.
Mục tiêu nghiên cứu:
Để thực hiện thành công, luận văn cần nghiên cứu các nội dung sau:
Nghiên cứu, phân tích, thiết kế và triển khai hệ thống tìm kiếm thông tin về danh
bạ y tế.
Nghiên cứu các kỹ thuật về truy hồi thông tin và các công nghệ, nền tảng nổi bậc
về các Search Engine, trong đó trọng tâm nghiên cứu ElasticSearch để xây dựng một ứng dụng tìm kiếm danh bạ y tế.
3
Sản phẩm sẽ giúp người dùng dễ dàng tìm kiếm các thông tin, danh bạ y tế mà mình cần và tạo các tính năng tương tính tác trên hệ thống để tăng độ tin cậy cho
người dùng sau.
Phương pháp nghiên cứu:
Để đạt được mục tiêu đặt ra, luận văn sử dụng các phương pháp sau:
Thu thập, phân tích và chuẩn hóa dữ liệu danh bạ y tế đã có.
Đánh giá và chọn lọc các thuật toán, tính năng trong ElasticSearch để làm cho
phần tìm kiếm dữ liệu.
Phân tích và thiết kế hệ thống Tìm kiếm thông tin, tích hợp ElasticSearch để trả
kết quả tìm kiếm Tiếng Việt chính xác hơn, đồng thời cũng xây dựng các tính năng tiện ích giúp người dùng dễ dàng tìm kiếm và tương tác trên Website.
Để thực hiện mục tiêu đã đề ra chúng tôi bố cục của luận văn như sau:
Chương 1: Nghiên cứu tổng quan về hệ thống tìm kiếm thông tin, các thành
phần và nguyên lý hoạt động của hệ thống tìm kiếm thông tin.
Chương 2: Giới thiệu bài toán và lựa chọn công nghệ.
Chương 3: Trên cơ sở nghiên cứu về Hệ thống tìm kiếm thông tin và mã nguồn
mở ElasticSearch, tôi đề xuất xây dựng thử nghiệm hệ thống tìm kiếm Danh bạ y tế
với hai thành phần chính là: Tạo chỉ mục và Tìm kiếm.
4
CHƯƠNG 1 - TỔNG QUAN VỀ HỆ THỐNG TÌM KIẾM THÔNG TIN
1.1 Khái niệm về tìm kiếm thông tin
Ngày nay sự phát triển mạnh mẽ và phổ biến của công nghệ thông tin, dữ liệu,
văn bản có đến hàng tỉ trang website, song song đó, nhu cầu khai thác thông tin này
để phục vụ công việc là nhu cầu cần thiết và cấp bách. Bất cứ hệ thống nào sau khi
xây dựng đều đòi hỏi có hỗ trợ chức năng tìm kiếm, tuy nhiên đối với việc tìm kiếm
nội dung trong văn bản lại là vấn đề lớn. Có những công cụ hỗ trợ tìm kiếm thông tin và hoạt động hiệu quả như Google, Bing, Yahoo, Baidu, Yandex, DuckDuckGo…
tuy nhiên, vì đây là những sản phẩm đã được thương mại hóa như bài báo khoa học
[6] cũng đã đề cập, nên chúng ta không thể biết được các kỹ thuật triển khai bên dưới
cũng như công nghệ ứng dụng của chúng.
Sau đây sẽ là định nghĩa về tìm kiếm thông tin của một số tác giả [6]
Khái niệm [6]: Tìm kiếm thông tin (Information Retrieval – IR) là tìm kiếm tài
nguyên (thường là các tài liệu - documents) trên một tập lớn các dữ liệu phi cấu trúc (thường là văn bản – text) được lưu trữ trên các máy tính nhằm thỏa mãn nhu cầu về
thông tin [6].
Mục đích cuối cùng trong việc tìm kiếm là đưa ra thông tin sao cho đúng với
nhu cầu tìm kiếm của người dùng, do đó cần phải có cách lưu trữ thông tin và tổ chức
lại dữ liệu sao cho dễ dàng tìm kiếm và truy xuất nhanh và hiệu quả nhất. Trong việc
tìm kiếm có 2 phần chính:
• Các kỹ thuật để biễu diễn thông tin: bao gồm cách biểu diễn thông tin nào cần thiết cho việc truy vấn (query) từ nhu cầu người dùng, và các
thông tin nào được chọn (văn bản, tài liệu).
• Các phương pháp so sánh khi biễu diễn thông tin, nhằm mục đích là để kiểm tra so sánh tính toán dữ liệu, sao cho cuối cùng kết quả tính toán trả về phải giống với kết quả được mong đợi khi người dùng thực hiện
câu truy vấn.
Việc đánh giá mức độ xử lý khi trả về kết quả trong việc tìm kiếm thông tin
trong một tập tài liệu và câu truy vấn cho tài liệu đó dựa vào các cách sau:
5
• Độ chính xác (Precision): được đo bởi tỉ lệ của tài liệu trả về chính xác trên tổng tài liệu nhận được [6].
Độ chính xác = (1) {tài liệu liên quan} {tài liệu nhận được} {tài liệu nhận được}
• Độ bao phủ (Recall): tỉ lệ tài liệu trả về chính xác trên tổng tài liệu có liên quan [6].
Độ bao phủ = (2) {tài liệu liên quan} {tài liệu nhận được} {tài liệu liên quan}
• Kết quả sai (fall - out): tỉ lệ tài liệu không có liên quan trả về trên tổng tài liệu không liên quan [6].
Kết quả sai = (3) {tài liệu không liên quan} {tài liệu nhận được} {tài liệu không liên quan}
Ví dụ [6]: trong tập 1000 tài liệu được sử dụng cho tìm kiếm với 200 tài liệu
liên quan đến thông tin “tin học”, một hệ thống tìm kiếm thông tin “tin học” trả về
{200} ∩ {150}
được 150 tài liệu, trong đó có 130 tài liệu chính xác. Khi đó:
{150}
{200} ∩ {150}
= ≈ 87% Độ chính xác = {130} {150}
{200}
{800} ∩ {150}
Độ bao phủ = = ≈ 65% {130} {200}
{800}
Kết quả sai = = ≈ 2.5% {20} {800}
6
1.2 Khái niệm về hệ thống tìm kiếm thông tin
1.2.1 Khái niệm về hệ thống tìm kiếm thông tin [7]
Theo Kowalski [8] đã định nghĩa về hệ thống tìm kiếm thông tin như sau:
“Hệ thống truy tìm thông tin là một hệ thống có khả năng lưu trữ, truy tìm và
duy trì thông tin. Thông tin trong các trường hợp này có thể bao gồm văn bản (bao gồm cả số liệu ngày tháng), hình ảnh, âm thanh, video và những đối tượng đa phương
tiện khác.”
Gerard Salton [9, 10]: “Hệ thống tìm kiếm thông tin là một hệ thống thông tin
được sử dụng để lưu trữ các mục thông tin cần được xử lý, tìm kiếm, truy xuất và trả về cho người dùng với các yêu cầu khác nhau. Việc truy tìm những thông tin phụ
thuộc vào tổ chức thông tin được lưu trữ và các phương pháp tìm kiếm nhanh chóng
từ các yêu cầu, được đánh giá bằng cách so sánh các giá trị của các thuộc tính đối với
thông tin được lưu trữ và các yêu cầu về thông tin.”
Do đó ta có thể tóm lại đơn giản hơn: hệ thống tìm kiếm thông tin là một hệ
thống thông tin dùng để lưu trữ, xử lý, tìm kiếm và đưa ra các thông tin cho người sử
dụng. Hệ thống tìm kiếm thông tin thường thao tác các dữ liệu dạng văn bản và không
có giới hạn về nội dung và thông tin trong văn bản.
1.2.2 Các bộ phận cấu thành hệ thống tìm kiếm thông tin [7]
1.2.2.1 Bộ phận thu thập thông tin - Robot
Bộ phận thu thập thông tin [11] là một chương trình chạy tự động dùng duyệt qua các cấu trúc siêu liên kết (hyperlink) để đi thu thập tài liệu, và một cách đệ quy
nó sẽ nhận về tài liệu có liên kết với tài liệu này, nó sẽ quét để trích xuất toàn bộ
thông tin của website đó từ tiêu đề, hình ảnh đến từ khóa, các liên kết (link) đến trang khác ... Dữ liệu sẽ được quét theo thứ tự từ trên xuống dưới từ trái qua phải. Thực tế, bộ phận thu thập dữ liệu sẽ có những con Robot thu thập dữ liệu, được gọi là spider, những spider này sẽ truy cập từng trang web, thu thập dữ liệu trên trang đó một cách âm thầm và nhanh chóng. Sau đó nó lấy dữ liệu và lưu trữ các nội dung từ các trang
web trên Internet.
Bộ phận này có các thành phần chính: một thành phần để theo dõi và phát hiện các URL mới, hoặc các URL đã thay đổi. Một thành phần dùng để đọc nội dung tài
liệu của tất cả các trang web một cách đệ quy từ một tập các URL đã có, sau đó nó sẽ
7
phân tích tài liệu, trích xuất nội dung tài liệu dưới các định dạng như html, pdf, excel… và lưu trữ về cơ sở dữ liệu thu thập.
1.2.2.2 Bộ phận lập chỉ mục - Index
Hệ thống lập chỉ mục hay còn gọi là hệ thống phân tích và xử lý dữ liệu, thực
hiện việc phân tích và tối ưu hóa tốc độ và hiệu suất trong việc tìm kiếm các tài liệu
có liên quan cho một truy vấn tìm kiếm [11]. Với các từ khoá nhập vào của người
dùng nó có thể chỉ rõ các từ khoá nào xuất hiện ở trang nào, địa chỉ nào. Nếu không
có chỉ mục, công cụ tìm kiếm sẽ quét tất cả các tài liệu trong cơ sở dữ liệu lưu trữ,
đòi hỏi thời gian và tài nguyên tính toán đáng kể. Ví dụ [12] như Google là máy tìm
kiếm phổ biến nhất hiện nay, được đồng sáng chế bởi Lary Page và Sergey Brin năm
1997, đi vào hoạt động từ năm 1998. Google hoạt động dựa vào lập trình hệ thống PageRank (bằng sáng chế năm 1998) và là Search Engine hiện đại nhất ngày nay.
Trung bình, hệ thống PageRank xử lý hơn 3 tỷ truy vấn mỗi ngày, và hàng tỷ thông
tin được xử lý, cập nhật vào hệ thống cơ sở dữ liệu của Google. Với tốc độ xử lý ưu
việt, và luôn phát triển, đổi mới với những thuật toán chống spam, thao túng kết quả
tìm kiếm. Google luôn mong muốn mang đến những thông tin hữu ích và trải nghiệm
tốt nhất cho người dùng trên toàn thế giới.
1.2.2.3 Bộ phận tìm kiếm thông tin và Search Engine
Bộ phận này chịu trách nhiệm tìm kiếm các tài liệu từ yêu cầu của người sử
dụng, sau đó trả về danh sách các tài liệu chính xác với yêu cầu nhất, do số lượng các
trang web rất lớn và thông thường người dùng chỉ đưa đưa vào một vài từ khóa trong
câu truy vấn nên tập kết quả thường rất lớn. Tiền xử lý khoá tìm kiếm, thực hiện phân
tích từ khoá tìm kiếm, xử lý các toán tử tìm kiếm cơ bản (AND, OR, NOT,...), xử lý
tìm kiếm chính xác và xây dựng câu truy vấn dữ liệu. Vì vậy bộ xếp hạng (Ranking)
có nhiệm vụ sắp xếp các tài liệu này theo mức độ hợp lệ với yêu cầu tìm kiếm và hiển thị kết quả cho người sử dụng [5].
Search Engine là cụm từ dùng chỉ toàn bộ hệ thống bao gồm bộ thu thập thông tin, bộ lập chỉ mục và bộ tìm kiếm thông tin [13]. Các bộ phận này hoạt động liên tục từ lúc khởi động hệ thống, chúng phụ thuộc lẫn nhau về mặt dữ liệu nhưng độc lập với nhau về mặt hoạt động.
Nguyên lý hoạt động của Search Engine:
Search Engine điều khiển các robot đi thu thập thông tin trên mạng thông qua các siêu liên kết (hyperlink). Khi các robot phát hiện ra một website mới, nó gởi tài liệu (nội dung trong web page) về cho máy chủ (Server) chính để tạo cơ sở dữ liệu
8
chỉ mục phục vụ cho nhu cầu tìm kiếm thông tin [12, 13]. Bởi vì thông tin trên mạng luôn thay đổi nên các robot phải liên tục cập nhật các website cũ. Mật độ cập nhật
phụ thuộc vào từng hệ thống Search Engine về cách cấu hình thời gian cập nhật. Khi
Search Engine nhận câu truy vấn từ người dùng, nó sẽ tiến hành phân tích, tìm trong
cơ sở dữ liệu chỉ mục và trả về những tài liệu thoả yêu cầu.
9
CHƯƠNG 2 - GIỚI THIỆU BÀI TOÁN VÀ LỰA CHỌN CÔNG NGHỆ
2.1 Giới thiệu bài toán
Từ những khó khăn trong việc tìm kiếm tiếng Việt đã được nêu ở phần mở
đầu, cũng như việc tạo ra một hệ thống tìm kiếm thông tin về lĩnh vực y tế để đáp
ứng các nhu cầu tìm kiếm của người dùng. Bài toán: “Ứng dụng mã nguồn mở
ElasticSearch vào hệ thống tìm kiếm danh bạ y tế hiệu quả” sẽ giải quyết các vấn
đề sau:
- Xây dựng một hệ thống tìm kiếm Tiếng Việt về danh bạ y tế để giúp người dùng dễ dàng tìm kiếm các thông tin như: hồ sơ bác sĩ, phòng khám, bệnh
viện…
- Đồng thời người dùng có thể tiếp cận được các thông tin hữu ích về sức khỏe chính thống từ các bác sĩ, chuyên gia y tế, và cũng chính họ là người
sẽ tương tác với các tính năng sẵn có của hệ thống như bình luận, đánh
giá,…
- Tăng độ tin cậy của thông tin cho người dùng sau.
Luận văn sử dụng thư viện mã nguồn mở ElasticSearch cho phần lập chỉ mục
và tìm kiếm, kết hợp với ngôn ngữ lập trình Web như Asp.Net Core để xây dựng một
hệ thống tìm kiếm thông tin.
2.2 Phương pháp giải quyết
Để giải quyết được bài toán đã nêu bên trên, chúng tôi đã lựa chọn các phương
pháp sau:
- Sử dụng mã nguồn mở ElasticSearch cho việc đánh lập chỉ mục. - Nghiên cứu tổng quan về ElasticSearch và kiến trúc của nó để cài đặt và
cấu hình hệ thống phù hợp.
- Nghiên cứu Plugin “Vietnamese Analysis Plugin for Elasticsearch” của tác giả Duy Đỗ [14] để phân tích và tìm kiếm với dữ liệu text là tiếng Việt.
Trong plugin tác giả cũng đã kế thừa và sử dụng lại bộ công cụ tách từ tiếng Việt “Lê Hồng Phương”, công cụ này sử dụng từ điển và ngram, trong đó mô hình ngram được huấn luyện để sử dụng treebank tiếng Việt với độ chính xác 97% [15]
10
- Nghiên cứu về Analyzer và mô hình truy hồi thông tin của ElasticSearch, sử dụng các Analyzer có sẵn của ElasticSearch hoặc tạo ra các analyzer
mới để tách từ và cấu trúc lại dữ liệu phục vụ cho việc tìm kiếm nhanh và
hiệu quả hơn. Ngoài ra tài liệu [16] cũng đã đề cập sử dụng Analyzer của
ElasticSearch cho việc truy vấn mở rộng (Query Expansion) cũng như tìm kiếm theo ngữ nghĩa (semantic search).
- Nghiên cứu về các loại truy vấn trong ElasticSearch để lựa chọn các loại
truy vấn phù hợp cho việc tìm kiếm dữ liệu.
- Nghiên cứu về truy vấn Geo_Point và cách tổ chức dữ liệu để phục vụ cho việc tìm kiếm dữ liệu theo tọa độ và đưa ra các kết quả tìm kiếm gần với vị trí người dùng.
2.3 Tổng quan ElasticSearch
Trong phần này sẽ trình bày tổng quan về mã nguồn mở ElasticSeach và các tính
năng của ElasticSeach dựa trên các tài liệu [17, 18, 19] .
2.3.1 Khái niệm về ElasticSearch
ElasticSearch là một công cụ tìm kiếm (Search Engine) dựa trên nền
tảng Apache Lucene. Nó cung cấp một bộ máy tìm kiếm dạng phân tán, có đầy đủ
công cụ với một giao diện web HTTP có hỗ trợ dữ liệu JSON. ElasticSearch được
phát triển bằng Java và được phát hành dạng nguồn mở theo giấy phép Apache.
Tiền thân của ElasticSearch là Compas được Shay Banon tạo ra vào 2004 và
đến 2/2010 Shay Banon cho ra phiên bản đầu tiên. Công ty Elasticsearch được thành
Hình 1: Lịch sử hình thành công ty ElasticSearch
lập vào năm 2012 và vào tháng 5 năm 2015 thì đổi tên thành công ty Elastic.
11
Hình 2: Các tập đoàn sử dụng ElasticSearch
Các tập đoàn đang dùng ElasticSearch:
Hình 3: Các đối thủ của ElasticSearch
Các đối thủ hiện nay:
12
Hình 4: Bảng so sánh các dịch vụ
Bảng so sánh các dịch vụ:
Đặc điểm chính của ElasticSearch:
• Dữ liệu tìm kiếm và chỉ mục
• Hoạt động như 1 web server, có khả năng tìm kiếm và đưa ra kết quả
nhanh chóng (near realtime) thông qua cơ chế RESTful API
• Có khả năng phân tích và thống kê dữ liệu thông qua việc sử dụng công
cụ Kibana.
• Chạy trên máy chủ (server) riêng và đồng thời giao tiếp thông qua RESTful, do đó ElasticSearch không phụ thuộc vào máy trạm (client)
phát triển bằng bất kỳ ngôn ngữ nào. Nên ta rất dễ dàng tích hợp
ElasticSearch vào hệ thống của mình. Chúng ta chỉ cần gửi yêu cầu qua giao thức http là ElasticSearch sẽ trả về kết quả theo yêu cầu.
• ElasticSearch là một hệ thống phân tán và có khả năng mở rộng theo chiều ngang (Horizontal scalability) rất tốt. Nếu ta chỉ cần tăng thêm số lượng nút (node) cho nó là nó tự động mở rộng cho hệ thống của mình.
Hình 5: Hệ thống phân tán của ElasticSearch
13
• Dễ dàng khởi động và chạy
2.3.2 Các khái niệm cần biết trong ElasticSearch
Hình 6: Các khái niệm cần biết trong ElasticSearch
2.3.2.1. Document (tài liệu)
Document (tài liệu) là một đối tượng dạng JSON với một số dữ liệu. Đây là đơn vị nhỏ nhất để lưu trữ dữ liệu trong ElasticSearch. Document giống như row của table trong cơ sở dữ liệu quan hệ.
14
Hình 7: Index trong ElasticSearch [19]
2.3.2.2. Index (Chỉ mục)
Index ở đây không phải là chỉ số mà là một tập hợp các document, nó tương
đương với khái niệm một cơ sở dữ liệu. Trong ElasticSearch, sử dụng một cấu trúc
được gọi là inverted index. Nó được thiết kế để cho phép tìm kiếm full-text search.
2.3.2.3. Shard - Phân đoạn
(phân đoạn) là đối tượng của Lucene, là tập con
• Shard
các document của 1 Index. Một Index có thể được chia thành nhiều Shard.
• Mỗi Node bao gồm nhiều Shard. Chính vì thế Shard mà là đối tượng nhỏ nhất, hoạt động ở mức thấp nhất và đóng vai trò trong việc lưu trữ
dữ liệu.
Hình 8: Sharding trong Index [19]
15
• ElasticSearch đã hỗ trợ toàn bộ việc giao tiếp cũng như tự động thay đổi các Shard khi cần thiết, do đo chúng ta gần như không bao giờ làm
việc trực tiếp với các Shard.
Hình 9: Primary Shard và Replica Shard
• Có 2 loại Shard là: Primary Shard và Replica Shard.
16
Primary Shard:
• Primary Shard là sẽ lưu trữ dữ liệu và đánh index. Sau khi đánh xong
dữ liệu sẽ được vận chuyển tới các Replica Shard.
• Mặc định của ElasticSearch là mỗi index sẽ có 5 Primary shard và với
mỗi Primary shard thì sẽ đi kèm với 1 Replica Shard.
Replica Shard:
• Replica Shard đúng như cái tên của nó, nó là nơi lưu trữ dữ liệu nhân
bản của Primary Shard.
• Replica Shard có vai trò đảm bảo tính toàn vẹn của dữ liệu khi Primary
Shard xãy ra vấn đề.
• Ngoài ra Replica Shard còn có thể giúp tăng cường tốc độ tìm kiếm vì chúng ta có thể cấu hình số lượng Replica Shard nhiều hơn mặc định
của ElasticSearch.
• ElasticSearch sử dụng công thức sau để chọn shard cho việc lưu trữ.
shard = hash(routing) % number_of_primary_shards
Công thức tính Shard:
Hash: là một hàm tính toán cố định của ElasticSearch, routing là 1 đoạn text
duy nhất theo document, nó thường là _id của document đó.
Number_of_primary_shard: là số lượng của primary shard trong cluster.
Giá trị Shard sẽ đi kèm với document, nó dùng để xác định Shard nào sẽ lưu trong document nào và đồng thời dùng để cho routing, vì hệ thống sẽ tìm document
theo id của nó. Và đây cũng là lý do chúng ta không nên thay đổi số lượng Primary Shard, nếu không thì công thức trên sẽ không cho kết quả như ban đầu.
Quá trình lưu dữ liệu:
Hình 10: quá trình chuyển dữ liệu [17]
17
2.3.2.4. Node (Nút)
Là một server duy nhất, là một phần của cluster, là trung tâm hoạt động của
ElasticSearch, là nơi lưu trữ dữ liệu, tham gia vào chức năng lập chỉ mục và thực hiện các thao tác tìm kiếm.
Tên của Node rất quan trọng trong việc xác định nút này thuộc cụm nào trong
hệ thống ElasticSearch. Mỗi node được định danh bằng 1 tên duy nhất (unique name).
Việc đặt tên được tiến hành khi thiết lập, có thể tự định danh cho Node của server
mình. Tất cả các Node đều biết tất cả các Node khác của Cluster và có thể chuyển
tiếp yêu cầu từ client đến Node thích hợp. Ngoài ra, mỗi Node phục vụ một hoặc
nhiều mục đích:
• Master-eligible node: một Node có node.master (nút chính) được đặt giá trị bằng True làm mặc định, điều đó làm cho nó đủ điều kiện để được chọn làm Node chính, và điều khiển Cluster.
• Data node: một Node có node.data được đặt giá trị bằng True làm mặc định. Các Node dữ liệu có chức năng chứa dữ liệu và thực hiện các hoạt động liên quan đến dữ liệu như thêm, xóa, sửa, tìm kiếm và tổng hợp.
• Node Ingest: được sử dụng khi ta cần chạy lại (pre-process) các Document trước khi chúng được index. Ingest node sẽ can thiệp vào giữa quá trình xử lý cho số lượng lớn dữ liệu (bulk) và index, thực hiện các phép biến đổi, sau đó truyền kết quả trở lại index/bulk api. Một Node có node.ingest được đặt giá trị bằng True làm mặc định. Các Node
18
Ingest có thể áp dụng một đường dẫn nhập vào tài liệu để chuyển đổi và làm phong phú tài liệu trước khi lập chỉ mục.
2.3.2.5. Cluster (Cụm)
Là tập hợp các Node hoạt động cùng với nhau, cùng nắm giữ toàn bộ dữ liệu,
cung cấp khả năng lập chỉ mục và tìm kiếm liên kết giữa các Node với thông qua tên
của Cluster (cluster name). Chính vì thế Cluster sẽ được xác định bằng 1 tên duy nhất
Hình 11: Cluster trong ElasticSearch
(unique name). Việc đặt tên cho các Cluster trùng tên sẽ gây ra lỗi cho các Node vì vậy khi cài đặt chúng ta cần lưu ý về vấn đề này.
Mỗi Cluster có một Node chính (master), được lựa chọn một cách tự động và
có thể thay thế nếu sự cố xãy ra. Một Cluster có thể gồm 1 hoặc nhiều Node. Các Node có thể hoạt động trên cùng 1 Server. Tuy nhiên trong thực tế, một Cluster sẽ gồm nhiều Node hoạt động trên các Server khác nhau để đảm bảo nếu 1 Server gặp sự cố thì Server khác (Node khác) vẫn có thể hoạt động bình thường và đảm bảo sự ổn định cho hệ thống tìm kiếm. Các Node có thể tìm thấy nhau để hoạt động trên
cùng 1 Cluster thông qua giao thức Unicast.
Chức năng chính của Cluster đó chính là quyết định xem Shard nào được phân
bổ cho Node nào và khi nào thì di chuyển các Cluster để tạo cân bằng lại Cluster.
19
2.3.2.6. Mapping
Mapping là quá trình định nghĩa làm thế nào một Document và các trường dữ liệu của nó được lưu trữ và đánh Index. Ví dụ ta sử dụng Mapping trong việc để định nghĩa như sau:
• String fields nào được sử dụng như là full-text fields
• Fields nào chứa numbers, dates hay geolocations
• Liệu giá trị của tất cả các trường dữ liệu trong document được đánh
index catch-all
• Format của các date field
• Các custom rules để điều khiển mapping
• Mapping Type: mỗi một index có một hoặc nhiều mapping type, chúng được sử dụng để chia các document trong một index thành các nhóm
logic (logical groups). Ví dụ có index tên là my_index dùng để lưu trữ
cả thông tin user và blogpost. User có thể được lưu trong user type và blog posts trong một blogpost type
Ví dụ về một mapping trong ElasticSearch như sau:
Hình 12: Ví dụ về sơ đồ cơ sở dữ liệu của Mapping
Ta có sơ đồ quan hệ giữa 2 table user và blogpost
Ta thực hiện trên ElasticSearch như sau, thông qua cơ chế RESTfull:
PUT my_index
{
20
"mappings": {
"user": {
"_all": { "enabled": false },
"properties": {
"title": { "type": "text" },
"name": { "type": "text" },
"age": { "type": "integer" }
}
},
"blogpost": {
"_all": { "enabled": false },
"properties": {
"title": { "type": "text" },
"body": { "type": "text" },
"user_id": {
"type": "keyword"
},
"created": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
}
21
}
}
2.3.3. Analyzers và mô hình truy hồi thông tin của ElasticSearch
2.3.3.1. Analyzer
Analyzer là một công cụ của ElasticSearch trong việc tách từ và cấu trúc dữ
liệu giúp cho việc tìm kiếm văn bản cho các ngôn ngữ khác nhau. Trong ElasticSearch
đã có hỗ trợ sẵn khá nhiều analyzer cho các ngôn ngữ khác nhau, tuy nhiên với ngôn ngữ Tiếng Việt thì chúng ta cần phải cài thêm plugin mới sử dụng được (vi_analyzer
của tác giả Duy Đỗ [14], hiện tại có phiên bản 7.3.1).
ElasticSearch cung cấp cách để tùy chỉnh cách mọi thứ được lập chỉ mục với
các trình phân tích của index analysis module. Analyzers là quá trình phân tích và lập
chỉ mục dữ liệu. Mỗi analyzer bao gồm:
• 0 or more CharFilters
• 1 Tokenizer
• 0 or more TokenFilters Tokenizers được sử dụng để tách một chuỗi
Hình 13: Analyzer trong ElasticSearch
thành các dòng mã riêng (stream of tokens).
22
"Our goal at Tryolabs is to help Startups"
-> Tokenizer ->
["Our", "goal", "at", Tryolabs", "is", "to", "help", "Startups"]
Ví dụ: Một Tokenizer cơ bản sẽ thực hiện các thao tác sau:
Tokenizers gồm tokenizer cơ bản và có thể sửa đổi, xóa chúng hoặc
thêm những tokenizer mới. Ví dụ để đặt tên sao cho có nhiều ý nghĩa nhất có thể, một
TokenFilter có thể áp dụng stemming (chuyển đổi các token về từ gốc theo ngữ pháp), loại bỏ các từ không có nghĩa (stop_words), thêm từ đồng nghĩa (Synonyms).
CharFilters được sử dụng để xử lý trước các ký tự trước khi được gửi
tới Tokenizers.
ElasticSearch cung cấp rất nhiều Tokenizers, TokenFilters và chúng ta có thể
tạo các tùy chỉnh và cài đặt chúng dưới dạng plugin.
• Standard Analyzer: chia văn bản thành các thuật ngữ về ranh giới từ,
Các loại cấu hình Analyzer trong ElasticSearch:
như được xác định bởi thuật toán phân đoạn văn bản Unicode. Nó loại
bỏ hầu hết các dấu câu, chuyển tất cả các ký tự thành chữ thường
(lowercases) của các từ khóa và cũng hỗ trợ loại bỏ stop_words (các từ
không liên quan đến nội dung và ý nghĩa của văn bản: như a, an, many,
the….).
• Simple Analyzer: chia văn bản thành các thuật ngữ bất cứ khi nào nó gặp một ký tự không phải là một chữ cái. Nó sẽ chuyển các ký tự thành chữ thường tất cả từ khóa.
• Whitespace Analyzer: chia văn bản thành các thuật ngữ bất cứ khi nào nó gặp bất kỳ ký tự khoảng trắng nào. Nó sẽ không chuyển các ký tự thành chữ thường như Simple Analyzer.
• Stop Analyzer: giống như Simple Analyzer nhưng có thêm tính năng
loại bỏ stop_words
• Keyword Analyzer: chấp nhận bất kỳ văn bản nào và đưa ra chính xác
văn bản như 1 thuật ngữ.
• Pattern Analyzer: thường dùng trong việc kiểm tra số điện thoại hoặc
Email, domain... Hỗ trợ cả lower-casing and stop words.
23
• Language Analyzers: cung cấp nhiều phân tích ngôn ngữ cụ thể như
tiếng Anh, tiếng Pháp…
• Fingerprint Analyzer: là sự phân tích chuyên dụng tạo ra dấu vân tay
có thể được sử dụng để phát hiện trùng lặp.
Chúng ta có thể điều chỉnh Analyzers (custom Analyzers): nếu không tìm thấy
sự phân tích nào phù hợp bên trên thì chúng ta cũng có thể tạo ra một bộ phân tích tùy chỉnh kết hợp character filters, tokenizer, và token filters.
2.3.3.2. Cách sử dụng và Kiểm tra Analyzer
Để sử dụng các kết hợp khác nhau của Tokenizers và TokenFilters, chúng ta
cần tạo một Analyzer trong index settings và sau đó sử dụng nó trong mapping.
Ví dụ, giả sử chúng ta muốn một Analyzer để tokenizer dưới dạng tiêu chuẩn,
và áp dụng bộ lọc lowercase filter và stemming như sau:
• Tạo Index:
PUT /news
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}
}
Cách sử dụng Kibana:
24
PUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"std_folded": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"asciifolding"
]
}
}
}
},
"mappings": {
"my_type": {
"properties": {
"my_text": {
"type": "text",
"analyzer": "std_folded"
• Sau đó cài đặt:
25
}
}
}
}
}
Ở trên, chúng ta có hai có 2 bước cài đặt và mapping. Tiếp theo ta tạo ra
{
"custom_english_stemmer": {
"type": "stemmer",
"name": "english"
}
}
{
"analyzer": {
"custom_lowercase_stemmed": {
"tokenizer": "standard",
"filter": [
"lowercase",
"custom_english_stemmer"
]
}
}
custom Analyzer như sau:
26
}
Ở đây chúng ta đặt tên cho analyzer là custom_lowercase_stemmed nhưng chúng ta có thể đặt bất kỳ tên nào. Trong ví dụ này, chúng ta đang sử dụng tokenizer
“standard” và lowercase là bộ lọc được cung cấp bởi ElasticSearch nên không cần
cấu hình thêm nữa và custom_english_stemmer là cái mà chúng ta đã chỉnh sửa.
Thứ tự của danh sách là quan trọng vì nó sẽ là thứ tự các tokens được xử lý
trong index.
Cuối cùng, chúng ta có thể sử dụng analyzer mới được tạo ra này
{
"mappings": {
"test": {
"properties": {
"text": {
"type": "string",
"analyzer": "custom_lowercase_stemmed"
}
}
}
}
}
trong mappings.
Kiểm tra Analyzer, trong phần này sau khi đã tạo ra vi_analyzer dựa trên
GET danhba_yte/_analyze
Plugin của tác giả Duy Đỗ, ta kiểm tra kết quả của nó như sau:
27
"analyzer": "vi_analyzer",
"text": "Bác sĩ Phương chuyên khoa tai mũi họng"
}
{
{
"tokens" : [
{
"token" : "bác sĩ",
"start_offset" : 0,
"end_offset" : 6,
"type" : "
"position" : 0
},
{
"token" : "phương",
"start_offset" : 7,
"end_offset" : 13,
"type" : "
"position" : 1
},
{
"token" : "chuyên khoa",
Kết quả truy vấn:
28
"end_offset" : 25,
"type" : "
"position" : 2
},
{
"token" : "tai",
"start_offset" : 26,
"end_offset" : 29,
"type" : "
"position" : 3
},
{
"token" : "mũi",
"start_offset" : 30,
"end_offset" : 33,
"type" : "
"position" : 4
},
{
"token" : "họng",
"start_offset" : 34,
"end_offset" : 38,
"type" : "
"start_offset" : 14,
29
}
]
}
"position" : 5
2.3.4. Query DSL (domain- Specific Language) trong ElasticSearch
Elasticsearch cung cấp đầy đủ Query DSL dựa trên JSON để định nghĩa truy
vấn. Có 2 loại:
Câu truy vấn đơn: tìm kiếm một giá trị cụ thể trong một trường (field) cụ
thể. Gồm các câu: match, term, range.
Câu truy vấn kép: bao gồm nhiều câu truy vấn đơn cùng các truy vấn kép,
được sử dụng kết hợp theo một logic hợp lý. Ví dụ: bool, dis_max.
2.3.4.1 Match all query
GET news/_search
{
"query": {
"match_all": {}
}
}
Câu truy vấn đơn giản nhất, phù hợp với tất cả các tài liệu.
30
Hình 14: Kết quả tìm kiếm Match all query
Kết quả
2.3.4.2 Full text queries
Loại truy vấn full text cấp cao này thường được sử dụng cho các trường full
text như nội dung email. Các trường này thường được phân tích từ trước, và có các
loại phân tích (analyzer) cho mỗi loại field.
• Match query: truy vấn chuẩn để thực hiện full text query. Bao gồm truy vấn kết hợp và truy vấn cụm từ hoặc gần đúng. Match query chấp
Hình 15: Kết quả Match query
nhận văn bản, số, ngày tháng.
31
Kết quả trả về là tất cả các record mà trong tên có ”phẫu” hoặc ”thuật”.
Ta cũng có thể thêm điều kiện là “and” đối với query (mặc định là or) thì
ta có kết quả khác:
Hình 16: Kết quả Match query thêm and
• Match Phrase Query: Truy vấn match_phrase phân tích văn bản và
Hình 17: Kết quả Match phrase query
• Match Phrase Prefix Query: Cũng giống match_phrase, nhưng thêm
tạo 1 truy vấn cụm từ.
điều kiện khớp với tiền tố của từ trong văn bản.
Hình 18: Kết quả Match Phrase Prefix Query
32
Với truy vấn này thì "phẫu thuật tim" cũng match mà "phẫu thuật thành" cũng
đúng.
• Multi Match Query: Sử dụng từ truy vấn match và cho phép tìm kiếm
Hình 19: Kết quả Multi Match Query
nhiều trường:
2.3.4.3 Term level queries
Truy vấn này thường được sử dụng cho các dữ liệu kiểu số, ngày tháng, enum.
Ngoài ra cho phép bạn tạo truy vấn cấp thấp, bỏ qua bước phân tích.
• Term Query: Truy vấn term tìm những bản ghi (record) có chứa cụm
từ chính xác trong query
• Range Query: Trả về các record với trường khớp với phạm vi nhất
định.
33
Truy vấn range cho phép truyền vào các tham số (params):
- gte: Lớn hơn hoặc bằng - gt: Lớn hơn - -
Hình 20: Query có các parameters
lte: Nhỏ hơn hoặc bằng lt: Nhỏ hơn
Hình 21: Query với format ngày
• Date format in range queries: truy vấn theo định dạng ngày tháng
• Wildcard Query: Trả về các record khớp với các ký tự đại diện được
đưa ra. Kiểu này giống like ‘%query%’ trong truy vấn sql
Hình 22: Wildcard Query
34
2.3.4.4 Bool Query
Cho phép kết hợp các câu truy vấn khác để tạo ra một logic hợp lý. Có các
loại:
• must: phải phù hợp với tất cả các điều kiện và đóng góp vào điểm số.
• filter: giống với must nhưng bỏ qua điểm số.
• should: chỉ cần phù hợp với một trong các điều kiện.
• must_not: ngược lại với must, phải không phù hợp với tất cả các điều
Hình 23: Bool Query
kiện.
35
2.3.4.5 Fuzzy Query
The fuzzy query sử dụng dựa trên khoảng cách Levenshtein (khoảng cách này có thể đọc thêm tại đây [20]), đo lường số lần chỉnh sửa ký tự đơn cần thiết để chuyển từ này thành từ kia. Và cho phép ta cấu hình tham số fuzziness để cho kết quả phù hợp nhất với nhu cầu của mình:
• Fuzziness: Khoảng cách chỉnh sửa tối đa. Mặc định là TỰ ĐỘNG.
Khi truy vấn các trường văn bản hoặc từ khóa, độ mờ được hiểu là khoảng cách chỉnh
sửa Levenshtein - số lượng một ký tự thay đổi cần được thực hiện thành một chuỗi
để làm cho nó giống với một chuỗi khác.
Tham số độ mờ có thể được chỉ định là: 0, 1, 2
Khoảng cách chỉnh sửa tối đa được phép của Levenshtein (hoặc số lần chỉnh
sửa) TỰ ĐỘNG: tạo khoảng cách chỉnh sửa dựa trên độ dài của thuật ngữ. Đối số
khoảng cách thấp và cao có thể được cung cấp tùy chọn AUTO: [low], [high]. Nếu
không được chỉ định, các giá trị mặc định là 3 và 6, tương đương với AUTO: 3, 6 tạo
ra độ dài:
- 0..2: phải khớp chính xác - 3., 5: một chỉnh sửa được cho phép - > 5: cho phép hai chỉnh sửa AUTO thường là giá trị ưa thích cho độ mờ.
• prefix_length: số lượng ký tự ban đầu sẽ không được làm mờ. Điều này giúp giảm số lượng các điều khoản phải được kiểm tra. Mặc định
là 0.
• max_expansions: số lượng thuật ngữ tối đa mà truy vấn mờ sẽ mở rộng
thành. Mặc định là 50.
• Transpositions: liệu chuyển vị mờ (ab → ba) có được hỗ trợ hay
không. Mặc định là đúng.
Ví dụ truy vấn từ “tết1” nhưng hệ thống vẫn tìm được dữ liệu tết mặc dù dữ
liệu đầu vào bị dư số 1
Hình 24: Fuzzy Query
36
2.3.4.5 Geo queries
ElaticSearch hỗ trợ hai loại dữ liệu Geo: trường geo_point hỗ trợ các cặp lat /
lon và trường Geo_shape, hỗ trợ các điểm, đường, vòng tròn, đa giác, đa giác, ...
• Truy vấn geo_shape: tìm tài liệu có hình dạng địa lý giao nhau, được
chứa bởi hoặc không giao nhau với hình dạng địa lý đã chỉ định.
• Truy vấn geo_bounding_box: tìm tài liệu có điểm địa lý rơi vào hình
chữ nhật đã chỉ định.
• Truy vấn geo_distance: tìm tài liệu có điểm địa lý trong khoảng cách
chỉ định của điểm trung tâm.
• Truy vấn geo_polygon: tìm tài liệu với các điểm địa lý trong đa giác
được chỉ định.
2.3.5 Mô hình truy hồi thông tin của ElasticSearch
2.3.5.1 Term frequency/inverse document frequency:
Thuật toán đánh giá được sử dụng rất phổ biến trong Elasticsearch có tên gọi là term frequency/inverse document frequency, gọi tắt là TF/IDF, bao gồm các yếu tố đánh giá: Term frequency, Inverse document frequency, Field-length norm.
Term frequency: [21] yếu tố này đánh giá tần suất xuất hiện của term trong
1 field. Càng xuất hiện nhiều, relevance (sự liên quan) càng cao, cho ta thấy, một field mà từ khoá xuất hiện 5 lần sẽ cho relevance cao hơn là field mà từ khoá chỉ xuất
hiện 1 lần.
37
Ví dụ [22]: nếu bạn tìm kiếm với từ khoá "quick", thì rõ ràng field bên trên sẽ
{ "title": "The quick brown fox jumps over the quick dog" }
{ "title": "The quick brown fox" }
cho TF cao hơn (xuất hiện 2 lần) filed bên dưới (xuất hiện 1 lần):
(4)
TF được tính theo công thức sau [21]:
Giải thích: term frequency (tf) của t trong document d được tính bằng căn bậc
hai của số lần t xuất hiện trong d.
Inverse document frequency (idf) [21]: yếu tố này đánh giá tần suất xuất
hiện của term trên toàn bộ index. Điều đáng chú ý ở đây là càng xuất hiện nhiều,
(5)
càng ít thích hợp.
Giải thích: inverse document frequency (idf) của t là logarit cơ số e (logarit tự
nhiên) của thương giữa tổng số tài liệu trong index và số tài liệu xuất hiện và được cộng thêm 1 (để tránh xảy ra lỗi Division by zero).
Field-length norm: yếu tố này đánh giá độ dài của trường (field). Field càng ngắn, thì term sẽ có giá trị càng cao và ngược lại. Điều này hoàn toàn dễ hiểu, chúng ta có thể thấy một từ xuất hiện trong tiêu đề sẽ có giá trị hơn rất nhiều cũng từ đó nhưng xuất hiện trong nội dung văn bản.
(6)
Công thức [21]:
Giải thích: field-length norm (norm) là nghịch đảo của căn bậc hai số lượng
term trong field. (có thể hiểu là số lượng chữ của field đó)
38
IDF score * TF score * fieldNorms
(7)
Putting it together: _score cuối cùng sẽ là tích của 3 giá trị trên:
numDocs: chính là maxDocs, đôi khi bao gồm cả những document đã bị xóa.
2.3.5.2 Thuật toán BM25:
Từ phiên bản >=5.0 Elastic Search sử dụng thuật toán BM25 làm thuật toán đánh giá chính. BM25 [23] thuộc mô hình xác suất trong khi TF/IDF là mô hình
Hình 25: B25M
không gian vectơ nhưng nó vẫn dựa trên nền tảng của TF/IDF [24] và cải tiến dựa trên lý thuyết của probabilitistic information retrieval.
Trên biểu đồ cho thấy IDF trong BM25 khá giống IDF trong TF/IDF. Tuy nhiên BM25 đã chỉnh sửa công thức tính lại để thêm khả năng đưa ra score âm khi
tần suất xuất hiện của term trên toàn bộ index rất cao [23].
39
Công thức [23]:
(8)
Trong đó:
• b là hằng số và có giá trị là 0.75
• qi : term thứ i
Ví dụ [23] ta muốn tìm kiếm “shane connelly” trong tiếng Anh , thì từ shane
là q0 và connelly là q1
• IDF(qi): là inverse document frequency của term thứ i và có công thức
là :
(9)
Ví dụ ta có 4 tài liệu chứa từ “shane” thì IDF(shane):
Và từ “connelly” chỉ xuất hiện ở 2 tài liệu thì có IDF(connelly):
Chúng ta thấy mặc dù từ “shane” xuất hiện ở 4 tài liệu nhưng score vẫn thấp
hơn từ “connelly”. Điều này có nghĩa trực quan hơn một từ xuất hiện ở nhiều tài liệu thì sẽ ít quan trọng hơn từ xuất hiện ở ít tài liệu hơn và sẽ đóng góp score thấp hơn.
• f(qi,D): frequency của term trong document
• k1: là hằng số và thường có giá trị là 1.2
40
((k1 + 1) * tf) / (k1 + tf) (10)
Với công thức [23]:
Hình 26: BM25 tiệm cận
Ta có biểu đồ:
Chúng ta thấy, đường cong này tiệm cận (k1 + 1) tiệm cận (ở đây là k1 = 1.2).
Nhiều tf hơn luôn có nghĩa là có nhiều sự liên quan hơn. Tuy nhiên nó cũng nhanh
chóng bão hòa và không có giá trị tăng nữa. Mặt khác, tf trong Lucene liên tục tăng và không bao giờ đạt đến điểm bão hòa.
Đối với BM25, k1 thường được đặt thành 1.2. Thay đổi k1 có thể là một cách tiếp cận điều chỉnh hữu ích để sửa đổi tác động của TF. Sửa đổi k rõ ràng làm cho đường tiệm cận di chuyển. Tuy nhiên, điều quan trọng hơn là k cao hơn khiến TF mất
nhiều thời gian hơn để đạt được độ bão hòa. Bằng cách kéo dài điểm bão hòa, cho thấy được sự khác biệt về mức độ liên quan giữa tài liệu tần suất các từ cao hơn và thấp hơn.
41
• L = fieldLength / avgFieldLength: là tỉ lệ giữa độ dài của tài liệu so
với độ dài trung bình của tất cả các tài liệu.
((k1 + 1) * tf) / (k1 * (1.0 - b + b * L) + tf) (11)
Với công thức [23]:
Hình 27: BM25 với độ dài trung bình
Và biểu đồ thể hiện giá trị của TF đối với các độ dài khác nhau của document:
DELETE /bm25_index
PUT /bm25_index
{ "settings": { "number_of_shards": 1 }}
POST /bm25_index/my_type/_bulk
{ "index": { "_id": 1 }}
Dưới đây là các bước chuẩn bị dữ liệu và kiểm tra lại công thức của BM25:
42
{ "index": { "_id": 2 }}
{ "title": "The quick brown fox jumps over the lazy dog" }
{ "index": { "_id": 3 }}
{ "title": "The quick brown fox jumps over the quick dog" }
{ "index": { "_id": 4 }}
{ "title": "Brown fox brown dog" }
GET /bm25_index/my_type/_search
{
"query": {
"term": {
"title": "jumps"
}
}
}
{ "title": "The quick brown fox" }
Kết quả: sẽ cho _id = 3 và _id = 4.
log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))
= log(1 + (4-2 + 0.5) / (2+0.5))
= 0.6931471805599453
IDF = docCount = 4, docFreq = 2
Với k1=1.2, b=0.75 ta có:
(freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))
= (1 * (1.2 + 1) / (1 + 1.2 * (1 - 0.75 + 0.75 * (9/6.5))))
= 0.864048338
43
Vậy _score = 0.6931471805599453*0.864048338 = 0.5989126693522066
Thực tế: truy vấn ở ElasticSearch cho ta kết quả cũng tổng 2 tài liệu được tìm
thấy và max_core là: 0.59891266 và cũng cho kết quả gần giống với kết quả đã được
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.59891266,
"hits" : [
tính toán bên trên.
44
"_index" : "bm25_index",
"_type" : "my_type",
"_id" : "2",
"_score" : 0.59891266,
"_source" : {
"title" : "The quick brown fox jumps over the lazy dog"
}
},
{
"_index" : "bm25_index",
"_type" : "my_type",
"_id" : "3",
"_score" : 0.59891266,
"_source" : {
"title" : "The quick brown fox jumps over the quick dog"
}
}
]
}
}
{
45
CHƯƠNG 3. THỰC NGHIỆM XÂY DỰNG WEBSITE TÌM KIẾM DANH BẠ Y TẾ
Phần này sẽ trình bày rõ cách thức xây dựng Website tìm kiếm danh bạ y tế
dựa trên nội dung như: phân tích, thiết kế, cài đặt, thử nghiệm, đánh giá hệ thống. Quá trình xây dựng website tìm kiếm danh bạ y tế được thực hiện dựa trên Asp.net
MVC Core Framework, ngôn ngữ phát triển C#, cơ sở dữ liệu MSSQL Express, mã
nguồn mở ElasticSearch.
3.1 Phân tích
Để chuẩn bị cho việc xây dựng một hệ thống tìm kiếm danh bạ y tế hiệu quả
thì cần phải có các bước sau:
• Phân tích và xử lý dữ liệu danh bạ y tế hiện có như bác sĩ, phòng khám,
bệnh viện, bài viết liên quan.
• Sau khi cài đặt Vietnamese Analysis Plugin [14] của tác giả Duy Đỗ ta dùng plugin này cho quá trình tách từ tiếng Việt và cấu trúc dữ liệu giúp
cho việc tìm kiếm tiếng Việt chính xác hơn.
• Xây dựng tính năng đọc dữ liệu danh bạ y tế từ MSSQL và đưa dữ liệu
này lên ElasticSearch.
Để xây dựng công cụ tìm kiếm hiệu quả dựa trên mã nguồn mở ElasticSearch
cần phải làm hai giai đoạn chính là: chuẩn hóa dữ liệu đầu vào và áp dụng các thuật
toán hiện có của ElasticSearch để áp dụng cho tập dữ liệu danh bạ y tế. Chẳng hạn
như việc xử lý các cụm từ viết tắt như BS, bac si, PK, phong kham, BV, benh vien…, và các từ chuyên khoa như tai mũi họng hoặc TMH… vì vậy để xây dựng được chúng
ta cần:
• Tìm hiểu các cụm từ viết tắt trong danh bạ y tế để tạo các CharFilters
trong ElasticSearch
• Chọn lọc các dữ liệu nào cho việc tạo Index và tìm kiếm, để đưa lên ElasticSearch, trong phần này có tìm hiểu các thông tin cần tìm kiếm
của người dùng và tham khảo tài liệu [25], sau đó tôi chọn các dữ liệu: tên phòng khám, bác sĩ, bệnh viện, các chuyên khoa, dịch vụ khám chữa bệnh vì phù hợp với nhu cầu tìm kiếm của người dùng.
46
3.2 Thiết kế
Hệ thống tìm kiếm danh bạ y tế xây dựng các phần sau:
• Từ dữ liệu thô Danh bạ y tế phải tiền xử lý để lọc bỏ dữ liệu thừa và
phân loại bác sĩ, phòng khám, bệnh viện, bài viết…
• Thiết kế cơ sở dữ liệu để lưu trữ thông tin dữ liệu đã xử lý
• Xây dựng tính năng đọc dữ liệu từ Cơ sở dữ liệu (Database), sau đó xử lý tách từ Tiếng Việt, đánh Index và đưa dữ liệu tìm kiếm lên
ElasticSearch.
• Tạo bộ vn_analyzer, vi_tokenizer, mapping dữ liệu với các cụm từ viết tắt, hoặc gần nghĩa trong y tế để ElasticSearch dễ dàng tìm kiếm với
các từ đó.
Ta có mô hình tìm kiếm văn bản Tiếng Việt trong lĩnh vực danh bạ y tế như
Hình 28: Mô hình tìm kiếm văn bản tiếng Việt
sau:
• Dữ liệu danh bạ y tế sẽ được xử lý trước khi đưa vào lưu trữ cơ sở dữ liệu, như phân loại dữ liệu bác sĩ, phòng khám, bệnh viện, bài viết…
• Phát triển hệ thống có tự động đọc CSDL và xử lý tiếng Việt, sau đó đưa dữ liệu vào ElasticSearch. Trong phần này chúng ta phải tạo ra CharFilters để mapping các dữ liệu lại để dễ dàng tìm kiếm từ những từ khóa viết tắt của người dùng, cũng như mapping các chuyên khoa
phù hợp với nhu cầu tìm kiếm của người dùng.
47
Hình 29: lược đồ về Analyzer
Cách thực hiện như sau:
Từ lược đồ trên ta có thể thực hiện như sau:
var createIndexResponse = client.Indices.Create(indexName, c => c
.Settings(s => s
.Analysis(a => a
.CharFilters(cf => cf
.Mapping("healhcareES_custom", mcf => mcf
.Mappings(
"Bs=> Bác sĩ",
"bs=> Bác sĩ",
"BS => Bác sĩ",
• Cách 1: trên ngôn ngữ C#:
48
"bac si=>Bác sĩ",
"PK=>Phòng khám",
"pk=>Phòng khám",
"phong kham=>Phòng khám",
"BV=> Bệnh viện",
"bv=> Bệnh viện",
"TMH=>Tai mũi họng",
"tmh=>Tai mũi họng",
"RHM=> Răng hàm mặt",
"rhm=> Răng hàm mặt",
"X-ray=> X quang",
"x-quang=> X quang",
"Sản phụ khoa=> khám thai"
)))
.Analyzers(an => an
.Custom("vn_analyzer", ca => ca
.CharFilters("html_strip", "healhcareES_custom")
.Tokenizer("vi_tokenizer")
.Filters("lowercase", "stop", "icu_folding"))
)))
"Bac si=>Bác sĩ",
49
PUT /healthcare
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "vi_tokenizer",
"char_filter": [ "html_strip" ,"healhcareES_custom"],
"filter": [
"icu_folding"
]
}
},
"char_filter": {
"my_mappings_char_filter": {
"type": "mapping",
"mappings": [
"Bs=> Bác sĩ",
"bs=> Bác sĩ",
"BS => Bác sĩ",
"Bac si=>Bác sĩ",
"bac si=>Bác sĩ",
• Cách 2: ta có thể dùng Kibana và sử dụng phương thức PUT
50
"pk=>Phòng khám",
"phong kham=>Phòng khám",
"BV=> Bệnh viện",
"bv=> Bệnh viện",
"TMH=>Tai mũi họng",
"tmh=>Tai mũi họng",
"RHM=> Răng hàm mặt",
"rhm=> Răng hàm mặt",
"X-ray=> X quang",
"x-quang=> X quang",
"Sản phụ khoa=> khám thai"
]
}
}
}
}
}
"PK=>Phòng khám",
GET healthcare/_analyze
{
"analyzer": "vn_analyzer",
"text": "bs"
Sau khi tạo xong mapping, chúng ta có thể kiểm tra lại kết quả:
51
Kết quả:
{
"tokens" : [
{
"token" : "bac si",
"start_offset" : 0,
"end_offset" : 2,
"type" : "
"position" : 0
}
]
}
GET healthcare/_analyze
{
"analyzer": "vn_analyzer",
"text": "tmh"
}
Kết quả:
{
"tokens" : [
{
"token" : "tai",
"start_offset" : 0,
}
52
"type" : "
"position" : 0
},
{
"token" : "mui",
"start_offset" : 2,
"end_offset" : 2,
"type" : "
"position" : 1
},
{
"token" : "hong",
"start_offset" : 2,
"end_offset" : 3,
"type" : "
"position" : 2
}
]
}
"end_offset" : 2,
Rõ ràng kết quả trên cũng cho ta thấy với cách cài đặt mapping như trên dễ dàng tìm được các cụm từ viết tắt của người dùng, giúp ta cho được kết quả mong muốn.
Tương tự ta có bảng kết quả với các từ khóa đã mapping:
Bảng 1: Số liệu mapping các từ khóa.
53
Từ khóa Kết quả
BS bac si
PK phong kham
rhm rang ham và từ mat
X-ray x quang
khám thai san phu và từ khoa
thai sản san phu và từ khoa
Hình 30: lược đồ cơ sở dữ liệu Danh bạ y tế
Thông tin thiết kế lược đồ cơ sở dữ liệu lưu trữ như sau:
Trong đó các bảng sau: AspNetUser, Doctors, Hospitals, UserLikeDoctors,
UserSaveData, UserLikeHospital, QnAToDoctor:
• AspNetUser: lưu trữ thông tin tài khoản người dùng
• Doctors: lưu trữ thông tin bác sĩ
• Hospital: lưu trữ thông tin bệnh viện, phòng khám, hai tập dữ liệu này
phân biệt bởi colunm Type.
• UserLikeDoctors: lưu trữ thông tin người dùng đã thích các bác sĩ
54
• UserLikeHospital: lưu trữ thông tin người dùng đã thích các phòng
khám, bệnh viện…
• UserSaveData: lưu trữ thông tin người dùng muốn lưu dữ liệu bác sĩ, phòng khám, bệnh viện trong hồ sơ của mình, phục vụ cho việc sử dụng lại và không cần tra cứu lại.
• QnAToDoctor: lưu trữ thông tin câu hỏi của người dùng dành cho bác
sĩ.
Mô hình cho người sử dụng (bao gồm người quản lý và người sử dụng gọi tắt
Hình 31: Mô hình cho người sử dụng
là người dùng):
• Luồng thứ nhất: người dùng vào Website tìm kiếm thông tin mình cần,
lúc này hệ thống sẽ truy vấn trên dữ liệu của ElasticSearch
• Luồng thứ hai: sau khi người dùng đã tìm thấy thông tin mình cần thì xem hồ sơ thông tin Danh bạ y tế của bác sĩ, phòng khám, bệnh viện thì
hệ thống sẽ lấy dữ liệu từ MSSQL Server rồi trả kết quả cho người dùng
trên Website.
55
3.3 Cài đặt
Hệ thống được xây dựng trên AspNet MVC Core 3.1 và cài đặt trên môi trường Win 10. Hệ thống gồm hai phần giao diện chính (tất cả giao diện được thiết kế để có
thể tương thích hầu hết các thiết bị):
• Admin: dành cho admin quản lý và thêm dữ liệu mới, chỉnh sửa dữ liệu và xóa dữ liệu. Khi admin có thay đổi thông tin bác sĩ, phòng khám,
bệnh viện thì hệ thống cập nhật lại trong cơ sở dữ liệu và đưa lên
ElasticSearch
• Frontend: dành cho người dùng tìm kiếm thông tin danh bạ y tế, lưu
thông tin, và tương tác với dữ liệu đã tìm kiếm
• Cơ sở dữ liệu MSSQL Express 2017;
• AspNet Core >=3.1;
• IIS >=7.5
• Cài đặt ElasticSearch và Kibana (công cụ dành cho việc quản lý ElasticSearch và thao tác, truy vấn dữ liệu trên ElasticSearch) được
download tại link: https://www.elastic.co/downloads
• Cài đặt pluginVietnamese Analysis Plugin for Elasticsearch của tác giả
Duy Đỗ theo link
3.4 Giao diện
3.4.1. Giao diện cho người sử dụng
3.4.1.1 Giao diện tìm kiếm gợi ý từ khóa:
Hình 32: giao diện gợi ý khi nhập từ khóa
56
Dựa vào các từ khóa nhập vào từ người dùng, hệ thống sẽ gợi ý với 20 kết quả
gần nhất giúp gợi nhớ cho người dùng các thông tin hữu ít nếu cần.
Trong phần này ta xây dựng hệ thống tìm kiếm phù hợp với hai hình thức như
sau:
• Tạo tìm kiếm với kết quả khi người dùng nhập vào có dấu tiếng Việt:
Hình 33: kết quả tìm kiếm có dấu
Với từ khóa nhập vào: “bệnh vien tai mũi họng” kết quả như sau:
• Tạo tìm kiếm với kết quả khi người dùng nhập vào tiếng việt không dấu
Với từ khóa nhập vào: “benh vien tai mui hong” kết quả như sau:
Hình 34: kết quả tìm kiếm tiếng Việt không dấu
57
Ta thấy mặt dù hai tập kết quả khác nhau nhưng trong mỗi tập dữ liệu trả về
đều chứa “Bệnh viện Tai Mũi Họng” kết quả này cũng là kết quả mà người dùng
mong muốn.
var response = await _elasticClient.SearchAsync
.Query(q => q
.Match(m => m
.Field(f => f.KeyWords)
.Query(term).Analyzer("my_analyzer").Fuzziness(Fuzziness.Auto)
)
).From((page - 1) * pageSize).Size(pageSize).Sort(s =>
s.Descending(SortSpecialField.Score)).MinScore(0.4)
).ConfigureAwait(false);
Sau đây là code cấu hình và chọn “Analyzer” trong ngôn ngữ C# như sau:
• term là từ khóa người dùng nhập vào
58
• KeyWords là field ta chọn để tìm kiếm và dữ liệu này đã được đánh
index
• Analyzer("my_analyzer"): tên analyzer mà ta đã tạo ở bước trên
Fuzziness: sử dụng cho việc người dùng nhập sai từ nhưng vẫn gợi ý được từ gần đúng với term nhập vào.
•
3.4.1.2 Giao diện kết quả tìm kiếm:
Tính năng này sau khi tìm kiếm với từ khóa đã nhập. Thông tin hồ sơ bác sĩ,
phòng khám, bệnh viện gồm: tên, địa chỉ, số lượt thích, số lượt người dùng đã xem... đồng thời cũng có nút “xem thông tin” để thông tin chi tiết hồ sơ nếu người dùng tìm
Hình 35: Kết quả tìm kiếm
thấy kết quả cần tìm.
3.4.1.3 Giao diện tìm kiếm theo địa chỉ gần bạn:
Tính năng này sẽ tìm kiếm theo vị trí (location) của người sử dụng. Nếu người dùng sử dụng thiết bị máy tính thì hệ thống dựa vào mạng (network) internet để định vị tọa độ, nếu người dụng sử dụng thiết bị điện thoại thông minh hệ thống sẽ lấy định vị theo GPS. Với việc sử dụng máy tính thì việc sai sót định vị thường xuyên xãy ra
vì nó phụ thuộc vào đơn vị cung cấp dịch vụ internet và tốc độ đường truyền.
Hình 36: Kết quả theo định vị
59
Sau khi có dữ liệu gợi ý định vị, người dụng có thể nhập thêm thông tin tên bác
sĩ, hoặc bệnh viện, chuyên khoa để tìm thêm thông tin mình cần với các đơn vị gần mình
và hệ thống sẽ tìm kiếm theo các từ khóa nhập vào với vị trí gần với vị trí hiện tại theo
thiết bị đã định vị.
var geo = await _elasticClient.SearchAsync
s.From(0).Size(pageSize)
.Query(q => q
.Match(m => m
.Field(f => f.KeyWords)
.Query(keyword)
)
)
.PostFilter(q => q
.GeoDistance(g => g
.Boost(1.1)
Sau đây là code trong ngôn ngữ C# như sau:
60
.Field(p => p.GeoLocation)
.DistanceType(type)
.Location(lat, ln)
.Distance(distance)
.ValidationMethod(GeoValidationMethod.IgnoreMalformed)
)
)
);
.Name("named_query")
• lat và ln là tọa độ x, y của vị trí hiện tại
• Keyword là từ khóa người dùng nhập vào: ví dụ “tai mũi họng” hệ thống sẽ tìm danh bạ y tế có chuyên khoa tai mũi họng tưng ứng với vị trí hiện
tại với khoảng cách (distance) đã được cấu hình sẵn
• Distance: là cấu hình khoảng cách của địa chỉ cần tìm so với vị trí hiện
tại. Ví dụ 5 km, 3km, 1km…
3.4.1.3 Giao diện tìm kiếm theo chuyên khoa:
Hệ thống sẽ gợi ý các chuyên khoa có sẵn, dữ liệu này đã được chọn lọc theo các
chuyên khoa, giúp người dùng dễ dàng tìm kiếm các thông theo chuyên khoa mình cần:
Hình 37: Tìm kiếm theo chuyên khoa
61
Việc gợi ý tìm kiếm theo chuyên khoa sẽ giúp ích cho người dùng tìm kiếm
nhanh cơ sở khám chữa bệnh với chuyên khoa mình cần, từ đó người dùng dễ dàng
tìm thấy các thông tin mình cần hơn.
3.4.1.4 Giao diện chi tiết Hồ sơ - Profile:
Sau khi tìm kiếm được kết quả người dùng có thể bấm vào nút xem thông tin để
Hình 38 thông tin chi tiết
xem thêm thông tin hồ sơ đã tìm thấy:
Ngoài các thông tin chung giống với thông tin tìm kiếm bên ngoài, trang
profile còn có thêm các thông tin sau:
• Thông tin giới thiệu về profile và các dịch vụ khám chữa bệnh
• Thông tin bản đồ vị trí theo địa chỉ của hồ sơ
Hình 39: thông tin bản đồ theo địa chỉ của Profile
62
• Số điện thoại (nếu có)
• Bảng giá dịch vụ (nếu có)
• Người dùng có thể bấm vào “Chỉ đường” để được hướng dẫn chỉ đường đến phòng khám, bệnh viện nơi mình cần đến, hệ thống tự động trỏ link
qua google map để giúp người dùng coi trên bản đồ.
• Các tính năng tương tác với thông tin Profile như:
Hình 40: nút lưu profile
- Lưu lại profile để sau này cần:
Người dùng bấm vào dụng lại mà không cần phải tìm để lưu lại thông tin để sau này có thể sử kiếm lại.
63
- Tính năng “Thích” để tăng độ tin cậy cho Profile, càng nhiều lượt thích thì người dùng sau sẽ thấy Profile này đáng tin cậy và
họ có thể chọn tìm đến thông tin này.
Người dùng bấm vào để thích, nó còn giúp người dùng xem lại
thông tin trong danh sách Profile mình đã thích.
- Tính năng hỏi đáp: với những bác sĩ, bệnh viện hay phòng khám mà hệ thống có liên kết hợp tác thì có thể tạo thêm tính năng hỏi
đáp giúp người dùng dễ dàng đặt câu hỏi và tương tác thông qua
Hình 41: hỏi đáp.
tính năng này.
Người dùng dễ dàng xem lại các danh sách danh bạ y tế đã lưu, hoặc đã thích,
Hình 42: Thông tin đã lưu, thích
vì vậy dễ dàng sử dùng lại thông tin này khi cần:
64
- Tính năng đặt lịch hẹn khám bệnh, với những cơ sở có liên kết khám chữa bệnh thì hệ thống bật tính năng đặt lịch hẹn khám
chữa bệnh giúp người dùng dễ dàng đặt lịch với cơ sở khám chữa
bệnh mình cần với thời gian đã chọn
3.4.2. Giao diện cho người quản trị
Chức năng cho người quản trị bao gồm các chức năng nhập liệu dữ liệu về
thông tin bác sĩ, phòng khám, bệnh viện, ngoài ra còn có các chức năng quản trị tài
Hình 43: Trang quản trị Admin
khoản, danh mục, xem danh sách câu hỏi, lịch hẹn khám chữa bệnh…
3.4.2.1 Giao diện nhập liệu bác sĩ, phòng khám, bệnh viện:
Ngoài việc có tập dữ liệu ban đầu, thì hệ thống còn có tính năng cập nhật bổ
sung dữ liệu mới cũng như cập nhật dữ liệu cũ lại và đồng bộ tập dữ liệu đã cập nhật
này lên hệ thống ElasticSearch:
Hình 44: Cập nhật dữ liệu mới
65
3.4.2.2 Giao diện quản trị tài khoản:
Tính năng này chủ yếu cho người quản trị thống kê số lượng tài khoản đã đăng
ký và sử dụng để đo lường sự hiệu quả của hệ thống khi triển khai, từ đó có thể phát
Hình 45: Quản trị tài khoản
triển thêm tính năng tạo sự thu hút cho người dùng ngày càng nhiều hơn.
66
3.5 Đánh giá và thử nghiệm
3.5.1. Mô hình kiến trúc ứng dụng thử nghiệm
Hệ thống thử nghiệm gồm có các thành phần chính như sau:
• Dữ liệu thô thử nghiệm danh bạ y tế: Dữ liệu này sẽ được đưa vào CSLD MSSQL Server Express 2017, sau đó được cấu trúc lại và phân
loại theo bác sĩ, phòng khám, bệnh viện. Tập dữ liệu này có khoảng
8973 bác sĩ, 2941 phòng khám và 69 bệnh viện tại TP. Hồ Chí Minh.
• Tạo index và đưa vào hệ thống ElasticSearch: từ dữ liệu đã cấu trúc và phân loại, chúng ta tạo ra ứng dụng đọc dữ liệu này từ CSLD SQL
Server và thực hiện các bước tạo CharFilters, vn_analyzer, vi_tokenizer
và đưa tập dữ liệu lên ElasticSearch để đánh index, phục vụ cho việc tìm kiếm tiếng Việt có dấu và không dấu.
• Tìm kiếm danh bạ y tế: Người dùng truy cập vào đường dẫn phần mềm thử nghiệm và gõ từ khóa tìm kiếm thông tin về danh bạ y tế mà
mình cần. Hệ thống sẽ thực hiện tìm kiếm ở trong cơ sở dữ liệu trên
ElasticSearch và trả về kết quả cho người dùng. Từ danh sách kết quả
tìm kiếm trả ra theo _score giảm dần, với ngưỡng thấp nhất (min) cấu
hình ban đầu, với mục đích là trả ra kết quả có độ chính xác gần nhất
với từ khóa người dùng nhập vào.
Hình 46 Kiến trúc Mô hình thử nghiệm
67
3.5.2. Kịch bản và kết quả
Với dữ liệu Danh bạ y tế đã được đánh chỉ mục trên ElasticSearch, tôi thử
nghiệm sử dụng tính năng tìm kiếm với kịch bản như sau:
• Tìm kiếm theo từ khóa bất kỳ với tiếng Việt có dấu
• Tìm kiếm từ khóa bất kỳ với tiếng Việt không dấu
• Tìm kiếm bao gồm tiếng Việt có dấu và không dấu
• Tìm kiếm chính xác từ khóa
68
• Tìm kiếm các từ khóa có từ viết tắt như “bs” hoặc “bv” hoặc những từ
khóa chuyên khoa đã cấu hình sẵn như “tmh”…
• Tìm kiếm với gợi ý (Autocomplete) có sử dụng fuzzy trong
ElasticSearch
• Tìm kiếm theo GEO có cấu hình giới hạn về khoảng cách, phạm vi cần
tìm
• Tìm kiếm theo GEO và có từ khóa kèm theo
Bảng 2: Kịch bản tìm kiếm.
• Tìm kiếm theo chuyên khoa
Từ khóa Số lượng Thời Nhận xét Kịch bản kết quả gian tìm
kiếm (giây)
tai mũi họng 583 0.03 Hệ thống tìm thấy Tìm kiếm theo từ toàn bộ danh sách khóa bất kỳ với Bác sĩ, phòng tiếng Việt có dấu khám, Bệnh viện có
chuyên khoa tai mũi
họng
tai mui hong 583 0.02 Hệ thống tìm thấy Tìm kiếm từ khóa toàn bộ danh sách bất kỳ với tiếng Bác sĩ, phòng Việt không dấu khám, Bệnh viện có
chuyên khoa tai mũi
họng
511 0.02
bác sĩ Phương tai mui hong
Tìm kiếm bao gồm tiếng Việt có dấu và không dấu
Hệ thống đã tìm thấy danh sách bác sĩ tên Phương hoặc tên Phượng có chuyên khoa tai mũi
họng và đưa ra _score cao nhất
bs Phương tmh 511 0.01 Hệ thống đã tìm Tìm kiếm các từ thấy danh sách bác khóa có từ viết tắt
69
như “bs” hoặc “bv” hoặc những sĩ tên Phương hoặc tên Phượng có
từ khóa chuyên chuyên khoa tai mũi
khoa đã cấu hình họng và đưa ra
sẵn như “tmh”… _score cao nhất
Bệnh viện Tai 509 0.01 Bệnh viện Tai mũi Tìm kiếm chính mũi họng họng TPHCM được xác từ khóa đưa ra _score cao
nhất và danh sách Bs, Pk có khám
chữa bệnh tai mũi
họng cũng được tìm
thấy
bv măt1 Chỉ lấy top 0.01 Hệ thống vẫn tìm Tìm kiếm với gợi thấy được kết quả 20 kết quả ý (Autocomplete) bệnh viện Mắt mặc có sử dụng fuzzy dù người dùng đã trong nhập sai chữ “măt1” ElasticSearch
Chỉ lấy top 0.01 Hệ thống sẽ tìm và Tìm kiếm theo gợi ý tất cả bác sĩ, 50 kết quả GEO có cấu hình phòng khám, bệnh khoảng cách 3km viện với tất cả
chuyên khoa dịch vụ
trong phạm vi 3km
Khám thai 195 0.01 Hệ thống tìm và đưa Tìm kiếm theo
từ GEO và có khóa kèm theo và hình cấu có khoảng cách 3km ra danh sách Bác sĩ, phòng khám, bệnh viện có chuyên khoa Phụ sản hoặc Sản phụ khoa
Nha khoa 1828 0.01 theo thống đưa ra Hệ danh sách bác sĩ, Tìm kiếm chuyên khoa phòng khám, bệnh
viện răng hàm mặt
70
3.5.3 Đánh giá kết quả nghiên cứu
3.5.3.1 Kết quả đạt được
Dựa vào bảng kết quả cho ta thấy rằng việc ứng dụng ElasticSearch cho kết quả
với các tài liệu liên quan phù hợp với nhu cầu tìm kiếm của người dùng rất hiệu quả và tốc độ nhanh hơn so với kiểu truy vấn truyền thống “like” của ngôn ngữ SQL thông
thường. Vì kiểu thông thường của các trang web hiện nay phải kết hợp nhiều trường
thông tin lại và tìm kiếm dựa vào truy vấn “like” của SQL nên sẽ chậm về hệ thống,
cũng như không đáp ứng hết nhu cầu tìm kiếm của người dùng, do đó sẽ trả kết quả không như mong đợi. Ngoài ra Luận văn cũng hỗ trợ người dùng cũng có thể tìm
kiếm các từ khóa gần gũi với mình như cụm từ viết tắt, hoặc các từ hay dùng hằng
ngày như “khám thai” nhưng vẫn trả được kết quả tương ứng như sản phụ khoa hay sản nhi, việc này thì kiểu tìm kiếm truyền thống chưa làm được.
Luận văn cũng đã từng bước xây dựng hoàn chỉnh một hệ thống tìm thông tin
về danh bạ y tế với tập dữ liệu bác sĩ, phòng khám, bệnh viện tại TP. Hồ Chí Minh.
Các tính năng tìm kiếm trong hệ thống cũng giúp người dùng dễ dàng tìm kiếm các
thông tin mà mình cần. Luận văn cũng đã nghiên cứu hành vi, nhu cầu tìm kiếm của
người dùng từ đó đưa ra những dữ liệu phù hợp với nhu cầu hơn. Bên cạnh đó cũng
đã xây dựng các tính năng tương tác như “thích”, “lưu”, “bình luận” để tăng độ tin
cậy của dữ liệu cho người dùng sau.
Nhìn chung hệ thống chạy với độ chính xác trên 97% [15] điều này là chấp
nhận được bởi vì hệ thống dựa vào vnTokenizer có trong plugin elasticsearch-
analysis-vietnamese (với độ chính xác 97%) để tách từ trong tiếng Việt thành những
từ có nghĩa phục vụ cho việc đánh index và tìm kiếm.
3.5.3.2 Hạn chế
Bên cạnh những kết quả đạt được thì luận văn còn có những mặt hạn chế như: luận văn dựa trên plugin Tiếng Việt của mã nguồn mở ElasticSearch để tìm kiếm văn bản tiếng Việt đôi khi độ chính xác vẫn còn thiếu sót nên cần cập nhật dữ liệu từ điển về lĩnh vực y tế để cho việc tìm kiếm ngày càng chính xác hơn. Trên máy tính không có cảm biến tọa độ địa lý (GPS sensor) nên chức năng định vị trong “tìm kiếm cơ sở
y tế gần tôi” sẽ không chính xác bằng các điện thoại thông minh có phần cứng GPS và phải phụ thuộc vào nhà cung cấp dịch vụ Internet (Internet service provider).
3.5.3.3 Hướng phát triển
Luận văn từng bước xây dựng, bổ sung từ điển tiếng Việt cho lĩnh vực y tế.
71
Cần khảo sát thêm nhu cầu tìm kiếm của người dùng để xây dựng các bộ từ
khóa tìm kiếm sao cho phù hợp với nhu cầu thực tế hơn.
Liên kết và hợp tác với bác sĩ, phòng khám, bệnh viện để xây dựng tính năng
đặt lịch hẹn khám chữa bệnh để sau khi tìm kiếm xong thì người dùng có thể đặt lịch
hẹn với cơ sở mình cần đến, tạo sự gắn kết giữa họ và tăng tính hữu ích của hệ thống.
Hệ thống cần thu thập thêm dữ liệu danh bạ y tế cả nước để việc tìm kiếm ngày
càng đa dạng hơn, đồng thời cũng phát triển thêm trên Mobile App để tận dụng GPS
của thiết bị di động làm cho việc tìm kiếm GEO hiệu quả hơn, giúp người dùng dễ
dàng xác định vị trí của mình và tìm kiếm bác sĩ, phòng khám, bệnh viện gần mình
hơn.
72
CHƯƠNG 4. KẾT LUẬN
Luận văn nghiên cứu, phân tích, thiết kế và xây dựng một hệ thống tìm kiếm
thông tin dựa trên mã nguồn mở ElasticSearch kết hợp với ngôn ngữ lập trình ASP.NET CORE và plugin “elasticsearch-analysis-vietnames” giúp giải quyết vấn
đề tìm kiếm Tiếng Việt với độ chính xác 97% [15].
Luận văn cũng đã cung cấp cách thức truy xuất cũng như tìm kiếm văn bản
một cách nhanh chóng. Người dùng có thể tra cứu danh bạ y tế mình cần một cách
hiệu quả, phù hợp với nội dung tìm kiếm mà mình cần. Người dùng có thể tra cứu được các từ viết tắt như “tmh” thay cho từ khóa “tai mũi họng”, hoặc “khám thai”
thay cho từ khóa “thai nhi hoặc sản nhi” hay từ khóa “răng hàm mặt” thay thế cho
“nha khoa” hoặc các từ Tiếng Việt không dấu, dựa vào cách thức cấu hình và tạo
mapping, tokenizer, analyzer, để hỗ trợ tốt hơn cho việc tìm kiếm, đa dạng nội dung
các từ khóa phù hợp với nhu cầu tìm kiếm của người dùng.
Luận văn cũng đã xây dựng tính năng “tìm kiếm cơ sở y tế gần tôi” dựa trên
việc định vị tọa độ địa lý giúp người dùng có thể lựa chọn, tiếp cận các cơ sở khám
chữa bệnh gần mình, tiết kiệm được thời gian hơn. Bên cạnh đó luận văn còn cung
cấp các tính năng tiện ích như “Thích”, “Lưu” để giúp người dùng dễ dàng lưu trữ và
sử dụng lại dữ liệu cho những lần kế tiếp.
Từ các kết quả đạt được như trên, thì các vấn đề đặt ra trong luận văn đã được
giải quyết như: xây dựng một hệ thống tìm kiếm thông tin Tiếng Việt về danh bạ y tế
hoàn chỉnh, giúp người dùng dễ dàng tìm kiếm và tiếp cận các thông tin y tế, hồ sơ
bác sĩ, phòng khám, bệnh viện…Thông qua các tính năng “thích”, “hỏi đáp”, “lưu”
người dùng dễ dàng sử dụng lại dữ liệu của mình khi cần thiết. Điều đó sẽ giúp ích
cho việc giảm được thời gian tìm kiếm và đồng thời làm tăng độ tin cậy cho những tập dữ liệu đó.
73
DANH MỤC TÀI LIỆU THAM KHẢO
eBay
https://elasticsearch.cn/uploads/slides/20191210/2d32de50d21190aac153993a8fb74 0b4.pdf, access date: Sep 9, 2020
[1] Donggeng Yu (2019),Pronto Elasticsearch Extension Practice in eBay, Pronto,
[2] https://www.elastic.co/elasticon/tour/2017/new-york/elastic-vimeo-
elasticsearch-for-search, access date: Sep 9, 2020
[3] https://www.elastic.co/elasticon/conf/2017/sf/streaming-healthcare-and- research-story-of-elasticsearch-at-ucla-health, access date: Sep 9, 2020
[4] Đỗ Phúc, Đỗ Hoàng Cường, Nguyễn Tri Tuấn, Huỳnh Thụy Bảo Trân, Nguyễn Văn Khiết, Nguyễn Việt Hoàng, Nguyễn Việt Thành, Phạm Phú Hội, Dương
Ngọc Long Nam, Nguyễn Phước Thanh Hải, “Phát triển một Hệ thống S.E” Hỗ
trợ Tìm kiếm Thông tin, thuộc lãnh vực CNTT trên Internet qua từ khóa bằng
tiếng Việt, Đại học Khoa Học Tự Nhiên, TP.HCM, 2004.
[5] Tuan Anh Tran, Dao Thi Thanh Loan “Phát Triển hệ truy hồi thông tin tiếng Việt dựa trên mã nguồn mở (Vietnamese language information retrieval using
open source)” JOURNAL OF SCIENCE OF HNUE Interdisciplinary Science,
2013, Vol. 58, No. 1, pp. 37-45.
[7] Nguyễn Thị Loan (2017), Nghiên cứu công nghệ tìm kiếm (Mã nguồn mở)
[6] Huỳnh Đức Việt, Võ Duy Thanh, Võ Trung Hùng (2010), “Nghiên cứu ứng dụng mã nguồn mỡ Lucene để xây dựng phần mềm tìm kiếm thông tin trên văn bản”, Tạp chí khoa học và công nghệ, số 4, trang 308 – 315.
Lucene áp dụng giải quyết bài toán tìm kiếm trong hệ thống Văn bản, Luận văn
Salton, Donna Harman
(January 2003), “Information retrieval”,
Thạc sĩ, Trường Đại học Công nghệ - Đại học Quốc Gia Hà Nội, Hà Nội. [8] Gerald J. Kowalski, Mark T. Maybury (2002), INFORMATION STORAGE AND RETRIEVAL SYSTEMS Theory and Implementation Second Edition, Kluwer Academic Publishers New York, Boston, Dordrecht, London, Moscow
Encyclopedia of Computer Science, Pages 858–863
[10] Gerard Salton, Michael J. McGill (1983), Introduction to Modern Information Retrieval, McGraw-Hill Book Company, Printed in the United States of America.
[9] Gerard
74
[11] Christopher D.Manning Prabhakar Raghavan Hinrich Schütze (2009), Introduction to Infomation Retrieval, Cambridgec University Press Cambridge,
England.
[12] https://seongon.com/seo/kien-thuc-seo/seach-engine.html, access date: Oct
10, 2020
[13] http://www.searchenginehistory.com/, access date: Dec 10, 2020 [14] Duy Đỗ “Vietnamese Analysis Plugin for Elasticsearch”,
https://github.com/duydo/elasticsearch-analysis-vietnamese, access date: Dec
27, 2020
[15] https://github.com/phuonglh/vn.vitk, access date: Dec 20, 2020 [16] Brian D. Sloan (2017), ES-ESA: An Information Retrieval Prototype Using
[17] Clinton Gormley & Zachary Tong (2015), Elasticsearch: The Denitive Guide, Printed in the United States of America, Published by O’Reilly Media, Inc. , 1005 Gravenstein Highway North, Sebastopol, CA 95472.
Explicit Semantic Analysis and Elasticsearch, The Graduate Center, City University of New York.
[18] Elastic Search,
https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index.html
[19] https://pluralsight.com/courses/elasticsearch-indexing-data [20] https://en.wikipedia.org/wiki/Levenshtein_distance, access date: Dec 20,
2020
[21] https://www.compose.com/articles/how-scoring-works-in-elasticsearch/,
access date: Dec 30, 2020 [22] Vô danh (2020), Thuật toán đánh giá _score trong ElasticSearch,
https://viblo.asia/p/thuat-toan-danh-gia-score-trong-elasticsearch-
OeVKBY7y5kW , truy cập ngày: 08/10/2020
[23] BM25 The Next Generation (2015),
of Lucene Relevance https://opensourceconnections.com/blog/2015/10/16/bm25-the-next- generation-of-lucene-relevation, access date: Oct 10, 2020
[24] S. E. Robertson and H. Zaragoza. 2009. “The Probabilistic Relevance Framework: BM25 and Beyond”. Foundations and Trends in Information Retrieval 3(4): 333-389. [25] William R. Hersh, Chapter 14: Information Retrieval for Healthcare, Department of Medical Informatics & Clinical Epidemiology (DMICE) Oregon Health & Science University Portland.
PHỤ LỤC
// Tạo interface cho các method public interface IHealthcareElastichSearchService
{
Task
int page, int score);
Task
GeoDistanceType type, string distance, int pageSize, int page, string keyword);
Task
1 2 3 4 5 6 7 8 9 10 11 12 13 14
GeoDistanceType type, string distance, int pageSize, int page);
15
Task> GetDataSuggestion(string keyword, int page, int pageSize);
Task> GetDataSuggestionWithIcu(string term, int page, int pageSize);
Task InsertDataHealthcare(HealthCareModel model);
void UpdateDataHealthcare(HealthCareModel model);
void DeleteDataHealthcare(HealthCareModel model);
HealthCareModel GetDataHealthcarebyId(string id);
Task UpdateHealthcare(string id);
Task
specialist, int pageSize, int page, float score);
}
// Implement Code kế thừa từ interface public class HealthcareElastichSearchService : IHealthcareElastichSearchService
{
private readonly IElasticClient _elasticClient; private readonly string _index; public HealthcareElastichSearchService(IElasticClient elasticClient,
IConfiguration configuration)
{
_elasticClient = elasticClient; _index = configuration["elasticsearch:index"];
}
// Lấy dữ liệu từ document theo Id public HealthCareModel GetDataHealthcarebyId(string id) {
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
var response = _elasticClient.Get
u.Index(_index)).Source;
return response;
}
// Tìm kiếm dữ liệu theo từ khóa và phân trang, dùng stopwatch để kiểm tra thời gian lấy dữ li
ệu
public async Task
int pageSize, int page, int score)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var response = await _elasticClient.SearchAsync
.Query(q => q
.Match(m => m
.Field(f => f.KeyWords) .Query(keyword)
)
).From((page - 1) * pageSize).Size(pageSize) .Sort(s =>
s.Descending(SortSpecialField.Score)).MinScore(score)
).ConfigureAwait(false);
long total = response.HitsMetadata.Total.Value;
var rs = new PaginatedList
page, pageSize);
return rs;
}
// Tìm kiếm theo chuyên khoa
public async Task
GetDataSearchWithSpecialist(string specialist, int pageSize, int page, float score)
{
var response = await _elasticClient.SearchAsync
s => s.Query(q => q.QueryString(d => d.Query(specialist).Fields(f =>
f.Fields(f => f.KeyWords)))).Sort(s => s.Descending(SortSpecialField.Score))
.From((page - 1) * pageSize).MinScore(score) .Size(pageSize)).ConfigureAwait(false);
var rs = new PaginatedList
response.Documents.Count, page, pageSize);
return rs;
}
// Tìm kiếm theo chuyên khoa và từ khóa
public async Task
specialist, string keyword, int pageSize, int page, int score)
{
var response = await _elasticClient.SearchAsync
s => s.Query(q => q.QueryString(d => d.Query(keyword))).Sort(s =>
s.Descending(SortSpecialField.Score))
.From((page - 1) * pageSize) .Size(pageSize)).ConfigureAwait(false);
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
var rs = new PaginatedList
response.Documents.Count, page, pageSize);
return rs;
}
// Tìm kiếm theo tọa độ
public async Task
double ln, GeoDistanceType type, string distance, int pageSize, int page)
{
var geo = await _elasticClient.SearchAsync
.Query(q => q
.GeoDistance(g => g .Boost(1.1) .Name("named_query") .Field(p => p.GeoLocation)
.DistanceType(type) .Location(lat, ln) .Distance(distance) .ValidationMethod(GeoValidationMethod.IgnoreMalformed)
))
);
return new PaginatedList
geo.Documents.Count, page, pageSize);
}
// Tìm kiếm theo tọa độ và từ khóa
public async Task
double ln, GeoDistanceType type, string distance, int pageSize, int page, string keyword)
{
var geo = await _elasticClient.SearchAsync
s.From(0).Size(pageSize)
.Query(q => q
.Match(m => m
.Field(f => f.KeyWords) .Query(keyword)
)
).MinScore(5)
.PostFilter(q => q
.GeoDistance(g => g .Boost(1.1) .Name("named_query") .Field(p => p.GeoLocation)
.DistanceType(type) .Location(lat, ln)
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
.Distance(distance) .ValidationMethod(GeoValidationMethod.IgnoreMalformed)
) )
);
return new PaginatedList
geo.Documents.Count, page, pageSize);
}
// lấy dữ liệu gợi ý cho từ khóa
public async Task> GetDataSuggestion(string term, int page, int
pageSize) {
var response = await _elasticClient.SearchAsync
.Query(q => q
.Match(m => m
.Field(f => f.KeyWords) .Query(term).Fuzziness(Fuzziness.Auto)
)
).From((page - 1) * pageSize).Size(pageSize).Sort(s =>
s.Descending(SortSpecialField.Score))
).ConfigureAwait(false);
var rs = response.Documents.Select(x => $" {x.Name} -{x.Specialist}");
return rs.ToList();
}
// Lấy dữ liệu gợi ý với từ khóa không dấu
public async Task> GetDataSuggestionWithIcu(string term, int page, int
pageSize) {
var response = await _elasticClient.SearchAsync
.Query(q => q
.Match(m => m
.Field(f => f.KeyWords) .Query(term).Analyzer("my_analyzer").Fuzziness(F
uzziness.Auto)
)
).From((page - 1) * pageSize).Size(pageSize).Sort(s =>
s.Descending(SortSpecialField.Score)).MinScore(0.4) ).ConfigureAwait(false);
var rs = response.Documents.Select(x => $" {x.Name} -{x.Specialist}");
return rs.ToList();
}
// Đẩy dữ liệu lên ElasticSearch public async Task InsertDataHealthcare(HealthCareModel model) {
_ = await _elasticClient.IndexDocumentAsync(model);
}
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
// Cập nhật lại dữ liệu trên ElasticSearch
public void UpdateDataHealthcare(HealthCareModel model) {
_elasticClient.Delete
_elasticClient.IndexDocument(model);
}
// Cập nhật lại dữ liệu trên ElasticSearch với cơ chế acsync public async Task UpdateHealthcare(HealthCareModel model) {
var response = await _elasticClient.UpdateAsync
=> u.Index(_index).Doc(model));
}
// Cập nhật lại dữ liệu View từ người dùng public async Task UpdateHealthcare(string id) {
var update = GetDataHealthcarebyId(id); if (update != null) {
update.Viewed += 1;
await _elasticClient.UpdateAsync
u.Index(_index).Doc(update));
}
}
}
// Controller và xử lý route url public class HealthcareController : Controller
{
private readonly IHealthcareElastichSearchService
_healthcareElastichSearchService;
private readonly IDoctorRepository _doctorRepository;
private readonly IHospitalRepository _hospitalRepository;
private readonly ILogger
IHealthcareElastichSearchService healthcareElastichSearchService,
IDoctorRepository doctorRepository,
IHospitalRepository hospitalRepository,
ILogger
{
_healthcareElastichSearchService = healthcareElastichSearchService; _doctorRepository = doctorRepository; _hospitalRepository = hospitalRepository; _logger = logger;
}
// Trang chủ public IActionResult Index() {
return View();
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
}
// Trang tìm kiếm
[HttpGet("healthcare/Search")]
public async Task
string term = HttpContext.Request.Query["term"].ToString(); ; page = page ?? 1;
var rs = await _healthcareElastichSearchService.GetDataSearch(term, 100,
page.Value, 10);
ViewBag.term = term; return View(rs);
}
// Trang gợi ý dữ liệu với vị trí gần tôi [HttpGet("healthcare/FindNearby")] public IActionResult FindNearby(double lat, double ln) {
return View();
}
// Trang tìm kiếm từ khóa theo độ tọa
[HttpGet("healthcare/FindNearme/{lat}/{ln}")]
public async Task
var keyword = HttpContext.Request.Query["keyword"];
if (lat != 0 && ln != 0) {
PaginatedList
rs = await _healthcareElastichSearchService.GetDataSearchGeo(lat, ln,
Nest.GeoDistanceType.Arc, "3000m", 50, 1);
} else {
rs = await _healthcareElastichSearchService.GetDataSearchGeo(lat, ln,
Nest.GeoDistanceType.Arc, "3000m", 100, 1, keyword);
} ViewBag.Lat = lat; ViewBag.Ln = ln; return View(rs);
} return View();
}
// Tìm kiếm theo chuyên khoa [HttpGet("healthcare/FindbySpecialist")]
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
public async Task
ViewBag.Selected = specialist; var rs = await
_healthcareElastichSearchService.GetDataSearchWithSpecialist(specialist, 30, 1, 5);
return View(rs);
}
// Get địa chỉ [HttpGet("healthcare/GetAddress")] public JsonResult GetAddress(double lat, double ln) {
var rs = CommonUtils.GetAddress(lat, ln); var displayname = rs.display_name.Replace(", Long Điền, 711001, Việt Nam", "");
return Json(new {
success = true, message = "Thành công", data = $"{displayname}"
});
}
// Gợi ý từ khóa
[HttpGet("healthcare/SearchSuggest")]
public async Task
string term = HttpContext.Request.Query["term"].ToString();
var rs = await _healthcareElastichSearchService.GetDataSuggestion(term, 1,
20);
return Ok(rs);
}
// Trang profile Bác sĩ
[HttpGet("healthcare/doctorprofile/{slug}")]
public async Task
var doctor = _doctorRepository.GetAllData().Where(x => x.Slug.Equals(slug))
.Include(i => i.Questions).FirstOrDefault();
await _healthcareElastichSearchService.UpdateHealthcare(doctor.DocId); doctor.Viewed += 1; _doctorRepository.Update(doctor); return View(doctor);
}
// Trrang profile phòng khám và bệnh viện
[HttpGet("healthcare/pospitalorclinic/{slug}")]
public async Task
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
{
var data = _hospitalRepository.GetAllData().Where(x =>
x.Slug.Equals(slug)).FirstOrDefault();
await _healthcareElastichSearchService.UpdateHealthcare(data.DocId); data.Viewed += 1; _hospitalRepository.Update(data); return View(data);
}
// Load dữ liệu thô từ MSSQL lên ElasticSearch
[Authorize]
[HttpGet("healthcare/InputData")]
public async Task
// Doctor var rs = _doctorRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >
0).ToList().Select(x => new HealthCareModel
{
Id = x.DocId, Name = x.Name ?? "", Address = x.OfficeAddress, KeyWords = $"{x.Name} , {x.Specialist}, {x.Services}", Specialist = x.Specialist ?? "", TypeHealthCare = HealthCareEnum.Doctor.GetHashCode(), PhoneNumber = x.PhoneNumber ?? "", Viewed = x.Viewed, WorkPlace = x.OfficeAddress, GeoLocation = new GeoLocation(x.Lat, x.Lon), Slug = x.Slug,
// Description = x.Services ?? "",
}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList(); foreach (var doc in rs) {
try {
await _healthcareElastichSearchService.InsertDataHealthcare(doc);
} catch (Exception ex) {
var x = ex.ToString(); _logger.LogError(ex, ex.Message);
}
} var host = _hospitalRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >
0).ToList().Select(x =>
new HealthCareModel {
Id = x.DocId, Name = x.FullName ?? "", Address = x.OfficeAddress, KeyWords = $"{x.FullName} , {x.Specialist},
{x.Services}",
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
Specialist = x.Specialist ?? "", TypeHealthCare = x.Type == "benh-vien" ?
HealthCareEnum.Hospital.GetHashCode() : HealthCareEnum.Clinic.GetHashCode(),
PhoneNumber = x.PhoneNumber ?? "", Viewed = x.Viewed, WorkPlace = x.OfficeAddress, GeoLocation = new GeoLocation(x.Lat, x.Lon), Slug = x.Slug, // Description = x.Services ?? "",
}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList(); foreach (var r in host) {
try {
await _healthcareElastichSearchService.InsertDataHealthcare(r);
} catch (Exception ex) {
var x = ex.ToString(); _logger.LogError(ex, ex.Message);
}
}
return View();
}
// Xử lý update dữ liệu
[HttpGet("healthcare/UpdateData")]
public async Task
var host = _hospitalRepository.GetAll().Where(x => x.Lon > 0 && x.Lat >
0).ToList().Select(x =>
new HealthCareModel {
{x.Services}",
Id = x.DocId, Name = x.FullName ?? "", Address = x.OfficeAddress, KeyWords = $"{x.FullName} , {x.Specialist}, Specialist = x.Specialist ?? "", TypeHealthCare = x.Type == "benh-vien" ?
HealthCareEnum.Hospital.GetHashCode() : HealthCareEnum.Clinic.GetHashCode(),
PhoneNumber = x.PhoneNumber ?? "",
WorkPlace = x.OfficeAddress, GeoLocation = new GeoLocation(x.Lat, x.Lon), Slug = x.Slug, // Description = x.Services ?? "",
}).Where(x => !string.IsNullOrEmpty(x.Address)).ToList(); foreach (var r in host) {
try
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
{
_healthcareElastichSearchService.UpdateDataHealthcare(r);
} catch (Exception ex) {
var x = ex.ToString(); _logger.LogError(ex, ex.Message);
}
}
return View();
}
// Hàm xử lý tính năng Like Bác sĩ [HttpGet("healthcare/likedoctor/{slug}")] public IActionResult LikeDoctor(string slug) {
if (!string.IsNullOrEmpty(slug)) {
var doctor = _doctorRepository.GetByFiler(f => f.Slug ==
slug).FirstOrDefault();
var userId = string.Empty; bool isCookie = false; if (User.Identity.IsAuthenticated) {
userId = User.FindFirst(ClaimTypes.Name)?.Value;
} else {
//var user = User.Identity.Name; var cookie = HttpContext.Request.Cookies["x-header-userdata"];
if (string.IsNullOrEmpty(cookie)) {
cookie = Guid.NewGuid().ToString(); Response.Cookies.Append("x-header-userdata",
cookie, new CookieOptions() {
Path = "/"
}
); userId = cookie;
} isCookie = true;
}
var check = _doctorRepository.GetUserLikeDoctor(userId, doctor.Id); if (check == null) {
doctor.Liked = doctor.Liked ?? 0 + 1; _doctorRepository.InsertLike(userId, doctor.Id, isCookie); _doctorRepository.SaveChanges();
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
return Json(new { Status = true, Message = $"Cảm ơn bạn đã đánh giá
hài lòng bác sĩ {doctor.Name}" }); }
return Json(new { Status = false, Message = $"Bạn đã đánh giá hài lòng bác
sĩ {doctor.Name} rồi." }); } return Json(new { Status = false, Message = "Thông tin không hợp lệ" });
}
// Hàm xử ký tính năng lưu Bác sĩ
[HttpGet("healthcare/savedoctor/{slug}")] public IActionResult SaveDoctor(string slug) {
if (!string.IsNullOrEmpty(slug)) {
slug = slug.Replace("{", "").Replace("}", ""); var doctor = _doctorRepository.GetByFiler(f => f.Slug ==
slug).FirstOrDefault();
var userId = string.Empty; if (User.Identity.IsAuthenticated) {
userId = User.FindFirst(ClaimTypes.Name)?.Value;
} else {
return RedirectToAction("login", "Accounts", new { returnUrl =
$"/healthcare/savedoctor/{slug}" });
}
var check = _doctorRepository.GetUserSaveDoctor(userId, doctor.Id); if (check == null) {
_doctorRepository.InsertSave(userId, doctor.Id); _doctorRepository.SaveChanges();
}
return RedirectToAction("MyBookmark", "Accounts");
} return Json(new { Status = false, Message = "Thông tin không hợp lệ" });
}
// Hàm xử lý tính năng like phòng khám bệnh viện [HttpGet("healthcare/likehospital/{slug}")] public IActionResult LikeHospital(string slug) {
if (!string.IsNullOrEmpty(slug)) {
var hospital = _hospitalRepository.GetByFiler(f => f.Slug ==
slug).FirstOrDefault();
var userId = string.Empty; bool isCookie = false;
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
if (User.Identity.IsAuthenticated) {
userId = User.FindFirst(ClaimTypes.Name)?.Value;
} else {
//var user = User.Identity.Name; var cookie = HttpContext.Request.Cookies["x-header-userdata"];
if (string.IsNullOrEmpty(cookie)) {
cookie = Guid.NewGuid().ToString(); Response.Cookies.Append("x-header-userdata",
cookie, new CookieOptions() {
Path = "/"
}
); userId = cookie;
} isCookie = true;
}
var check = _hospitalRepository.GetUserLikeHospital(userId, hospital.Id); if (check == null) {
hospital.Liked = hospital.Liked ?? 0 + 1; _hospitalRepository.InsertLike(userId, hospital.Id, isCookie); _hospitalRepository.SaveChanges(); return Json(new { Status = true, Message = $"Cảm ơn bạn đã đánh giá
hài lòng {hospital.FullName}" }); }
return Json(new { Status = false, Message = $"Bạn đã đánh giá hài lòng
{hospital.FullName} rồi." });
} return Json(new { Status = false, Message = "Thông tin không hợp lệ" });
}
// Hàm xử lý tính năng lưu phòng khám bệnh viện [HttpGet("healthcare/savehospital/{slug}")] public IActionResult SaveHospital(string slug) {
if (!string.IsNullOrEmpty(slug)) {
slug = slug.Replace("{", "").Replace("}", ""); var hospital = _hospitalRepository.GetByFiler(f => f.Slug ==
slug).FirstOrDefault();
var userId = string.Empty; if (User.Identity.IsAuthenticated) {
userId = User.FindFirst(ClaimTypes.Name)?.Value;
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
} else {
return RedirectToAction("login", "Accounts", new { returnUrl =
$"/healthcare/savehospital/{slug}" });
}
var check = _hospitalRepository.GetUserSaveHospital(userId, hospital.Id); if (check == null) {
_hospitalRepository.InsertSave(userId, hospital.Id); _hospitalRepository.SaveChanges();
}
return RedirectToAction("MyBookmark", "Accounts");
} return Json(new { Status = false, Message = "Thông tin không hợp lệ" });
}
}
// Controller cho Admin quản lý thêm Bác sĩ public class DoctorsController : Controller
{
private readonly IDoctorRepository _doctorRepository;
private readonly ILogger
ILogger
{
_doctorRepository = doctorRepository; _logger = logger; _mapper = mapper;
}
[HttpGet("doctors/getall")]
public async Task
var filter = HttpContext.Request.Query["search"]; if (page == null) {
page = 1;
} var rs = _doctorRepository.GetAllData(); if (!string.IsNullOrEmpty(filter.ToString())) {
ViewData["CurrentFilter"] = filter.ToString(); rs = rs.Where(x =>x.Name.Contains(filter.ToString())).AsNoTracking();
}
return View(await PaginatedList
x.CreatedDate), page ?? 1, _pageSize));
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
}
[HttpGet("doctors/AddDoctor")] public IActionResult AddDoctor() {
return View();
}
[HttpPost("doctors/AddDoctor")] public IActionResult AddDoctor(DoctorModel model) {
if (ModelState.IsValid) {
var doctor = _mapper.Map
StringUtil.RemoveSign4VietnameseString(model.Name).Replace(" ", "_");
if (model.FileImage != null) {
string folder = $"UploadFiles/Images/Doctor/{DateTime.Now:yyyyMMdd}/";
doctor.ImageLink = ImageUtils.UploadFileImage(model.FileImage,
folder);
} _doctorRepository.Add(doctor); return RedirectToAction("Index");
} return View();
}
[HttpGet("doctors/EditDoctor/{id}")] public IActionResult EditDoctor(int id) {
var doctor = _doctorRepository.GetById(id); if(doctor != null) {
return View(_mapper.Map
} return RedirectToAction("Error404", "Home");
}
}
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759