2018-11-28

Case study: Tối ưu hoá hình ảnh cho Ehkoo

Case study: Tối ưu hoá hình ảnh cho Ehkoo

Công khai PR bài gốc ở Ê-ku có sửa sang chút đỉnh :bow: https://ehkoo.com/bai-viet/ehkoo-what-is-webp-image-optimization-cloudinary


Hình chụp bởi Alora Griffiths. Nguồn: Unsplash

Chuyện như chưa bắt đầu

Nếu bạn chưa biết thì Cloudinary là một dịch vụ lưu trữ, quản lý và phân phối media gần như miễn phí. Bạn có thể upload hình ảnh, video lên Cloudinary rồi phân phối chúng qua hệ thống CDN. Cloudinary dựa vào CDN của những ông lớn như Akamai, Fastly, và CloudFront, tự động chọn lấy nhà phân phối đang có hiệu suất tốt nhất để đảm bảo tài liệu được chuyển đến người sử dụng chỉ trong vòng vài nốt nhạc.
Ngoài chuyện upload, bạn còn có thể trực tiếp chỉnh sửa tài liệu (gọi là "transformation") chỉ bằng việc thay đổi tham số trên URL của tài liệu, chẳng hạn như chỉnh kích thước ảnh, đổi định dạng, crop hình, vân vân và mây mây. Cloudinary còn có những tính năng nâng cao khác mà bạn có thể tìm hiểu thêm ở đây.
Mình sử dụng Cloudinary cho Ehkoo từ những ngày đầu tiên. Cloudinary đặc biệt hào phóng khi cung cấp gói Free cho phép chứa 10GB media, 20GB bandwidth, 300k tập tin và 20k thao tác chuyển đổi hàng tháng. Theo ước lượng ban đầu của một trang không ai thèm vào thì gói này là đủ rồi, cho đến một ngày...
Khoảng 3 tuần trước, mình nhận được một email từ Cloudinary nhắc nhở rằng tài khoản đã sử dụng gần hết bandwidth trong tháng. Bình thường thì website sử dụng khoảng 80-90% bandwidth cho phép, nên chuyện này cũng không có gì lạ lắm. Nhưng vì đây là lần đầu tiên nhận được email cảnh báo nên cũng đáng để xem xét thử.
Thống kê cho thấy ngày hôm đó có 8.64k requests, ngốn 1.06GB bandwidth.
Suy nghĩ đầu tiên: Hmm, anh đẹp trai nào hotlink mấy tấm hình của iem rồi.
Nhưng "Thật bất ngờ - Trúc Nhân", thống kê khác cho thấy phần lớn requests lại đến từ chính ehkoo.com. Có nghĩa là website "vô tình" có thêm traffic, đẩy bandwidth hình ảnh lên cao.

Tuy nhiên cũng không quên chỉ mặt điểm danh anh Tét Văn Thót Chấm Vê Nờ đã tích cực lấy bài đồng thời tích cực hotlink nha.
Suy nghĩ tiếp theo: Có khi nào Cloudinary giả vờ tính sai để ép mình mua gói trả phí không?
Trong khoảng thời gian từ 1/10 đến 31/10/2018 thì Ehkoo có 12,637 lượt truy cập. Tính trung bình mỗi lần load trang khoảng 10 hình (ngoài trang chủ thì nhiều hình hơn), mỗi hình tầm 200KB, vị chi là khoảng 25,274,000KB ~ 25.274GB :scream:. Vậy là Cloudinary có lý khi gửi email cảnh báo.
Thành thật xin lỗi anh Cloudinary vì em đã nghi ngờ anh :bow:.
Thiệt may vì ngoài chuyện "la làng", Cloudinary còn tốt bụng gợi ý thêm vài chiêu để giảm bandwidth xuống.
25% of your bandwidth is being used to deliver PNG images. You can save considerable bandwidth by delivering non-transparent PNGs as JPGs. If you aren’t already doing so, consider using the ‘lossy’ flag to do this conversion automatically.
Consider using automatic WebP, JPEG-XR and JPEG image format selection for each different browser using 'f_auto' in delivery URLs.
Accessing original images without any transformation is responsible for 89% of your bandwidth usage. If possible, specify image dimensions to match the actual view size.
Ồ kê, đã có hướng dẫn, bắt đầu làm thôi.

Bắt đầu tối ưu hoá

Chuyển PNG thành JPG

Bước đầu tiên quá rõ ràng rồi, chuyển tất cả hình PNG thành JPG để giảm dung lượng file xuống. Thao tác này khá đơn giản vì Cloudinary cho phép bạn đổi định dạng hình ảnh chỉ bằng cách đổi đuôi file trên URL là xong.
-https://res.cloudinary.com/ehkoo/image/upload/v1541164603/imqusj.png
+https://res.cloudinary.com/ehkoo/image/upload/v1541164603/imqusj.jpg
Có một điểm lưu ý là tập tin JPG không hỗ trợ nền trong suốt (transparent background), nhưng vì web của Ehkoo nền trắng nên cũng không thành vấn đề. Bài học ở đây là nên thiết kế web nền trắng nhé.

Tự động chuyển qua WebP nếu trình duyệt hỗ trợ

WebP là một định dạng ảnh mới được phát triển bởi Google từ 2010. So với JPG thì WebP thường có dung lượng nhỏ hơn khoảng 30%, nhưng vẫn đảm bảo chất lượng tương đương. WebP hỗ trợ nền trong suốt như với PNG nhưng có thể nhẹ hơn đến 25%. WebP cũng hỗ trợ ảnh động như GIF nhưng có vẻ vẫn chưa phổ biến lắm.
Hiện tại các trình duyệt có thể đọc hiểu WebP bao gồm Chrome (duh), Opera và Edge. Firefox sẽ hỗ trợ WebP từ phiên bản 65, riêng Safari thì vẫn chưa thấy nói gì. :point_right: Xem hỗ trợ WebP ở CanIUse.
Bằng cách thêm vào tham số f_auto, Cloudinary sẽ tự động trả về file hình ở định dạng WebP nếu trình duyệt của người dùng hỗ trợ. Trong trường hợp còn lại, hình JPG sẽ được trả về.
-https://res.cloudinary.com/ehkoo/image/upload/v1541164603/imqusj.jpg
+https://res.cloudinary.com/ehkoo/image/upload/f_auto/v1541164603/imqusj.jpg
Thêm lý do nữa, là 63% lượng khách truy cập Ehkoo sử dụng Chrome cùng với 11% sử dụng Chrome Mobile, nên chỉ cần hiển thị WebP cho 3/4 tổng lượng truy cập cũng đã là một cải tiến lớn rồi.
Ngoài lề, liệu WebP có thống trị định dạng hình ảnh sau này?
Hên xui. Một trong những những hạn chế của WebP là không hỗ trợ tải ảnh theo kiểu tăng tiến (progressively). Nghĩa là trình duyệt vẫn phải download hết file ảnh trước khi hiển thị. Ở mức độ nào đó thì hạn chế này có thể ảnh hưởng đến trải nghiệm người dùng. MozJPEG, một định dạng ảnh được phát triển bởi Mozilla, có thể giải quyết hạn chế này, đồng thời vẫn đảm bảo những tính năng hay ho của WebP.
Nếu bạn quan tâm hơn về chủ đề tối ưu hoá hình ảnh, đừng bỏ qua bài thuyết trình dưới đây của Kornel Lesiński tại hội nghị performance.now() vừa diễn ra hồi đầu tháng 11. Kornel giải thích cách tập tin JPG được mã hoá, trình bày một kỹ thuật sử dụng HTTP2 để tiến hành tải ảnh một cách tăng tiến (ở cả server của bạn lẫn CDN), và một mẹo "kỳ cục" để sử dụng hình được nén bằng AV1 ngay hôm nay :point_down:
Mách nhỏ
Bạn nên nghía qua https://squoosh.app/, công cụ chuyển đổi ảnh trực tuyến vừa được Google giới thiệu tại hội nghị Chrome Dev Summit 2018 vừa qua. Ứng dụng này cho phép bạn chuyển đổi qua lại giữa các định dạng với nhau, hỗ trợ cả WebP và MozJPEG.
Nếu không sử dụng Cloudinary để tự động truyền tải WebP, bạn có thể dùng thẻ PICTURE để thực hiện fallback.
<picture>
  <source srcset="img.webp" type="image/webp" />
  <img src="img.jpg" alt="My image" />
</picture>
Trình duyệt sẽ tự động chọn lấy nguồn ảnh phù hợp, và trong trường hợp xấu nhất, sử dụng hình của thẻ IMG. Thẻ SOURCE còn hỗ trợ thuộc tính media, cho phép bạn quy định media queries khi hình ảnh này được hiển thị.
<picture>
  <source srcset="/media/examples/surfer-240-200.jpg" media="(min-width: 800px)" />
  <img src="/media/examples/painted-hand-298-332.jpg" />
</picture>
Hiện tại thì thẻ PICTURE đã có thể dùng được trên tất cả trình duyệt thường xuân (evergreen) trừ IE ra nhé :p

Thu nhỏ hình ảnh

Cái này thì nhờ vào tình hình thực tế của Ehkoo thôi. Website hiện tại đang có giao diện giống như bên dưới.

Vì container không bao giờ vượt quá 1280px và nội dung chính không vượt quá 960px, Ehkoo chỉ cần để chiều rộng của hình trong khoảng 1000px là tương đối hiệu quả rồi. Để tự động điều chỉnh kích thước ảnh, bạn thêm vào tham số c_scale,w_XXX với XXX là chiều rộng mong muốn. Cloudinary sẽ giữ nguyên tỉ lệ ảnh sau khi chỉnh sửa.
-https://res.cloudinary.com/ehkoo/image/upload/v1541164603/imqusj.jpg
+https://res.cloudinary.com/ehkoo/image/upload/f_auto,c_scale,w_1000/v1541164603/imqusj.jpg

Kết quả


Ngày 1/11 là khi bắt đầu sử dụng WebP đó nha
Đây là tình hình của Ehkoo trong 30 ngày qua. Sau khi áp dụng tất cả chiêu trò ở trên thì mọi thứ có vẻ khá khả quan. Lấy con số ra hù nhau một chút:
NgàyRequests (1000)Bandwidth (GB)
29/108.641.06
06/1122.2 (257%)1.25 (118%)
12/1125.7 (297%)1.71 (161%)
Số % là so sánh với ngày 29/10 - trước khi áp dụng tối ưu hóa
Trong những ngày đỉnh điểm, lượng request tăng lên 2.5 - 3 lần, trong khi bandwidth tiêu thụ chỉ tăng khoảng 1.2 - 1.6 lần. Hiện tại, website chỉ đang sử dụng khoảng 74% tổng lượng bandwidth cho phép.
Trong khi đó, số pageviews và sessions vẫn tiếp tục tăng.

Tạm kết

Tình hình đã tạm ổn nhưng vẫn còn nhiều chỗ cần cải tiến thêm. Chẳng hạn như hình ảnh ngoài trang chủ vẫn đang sử dụng hình với kích thước gốc, hay hình thu nhỏ ở phần Bài viết liên quan cũng vậy. Dù sao thì nếu website/ứng dụng của bạn cần chứa hình ảnh, hãy cân nhắc chuyển qua WebP và sử dụng Cloudinary nhé.

Binary Index Tree trong Cơ sở dữ liệu

Binary Index Tree trong Cơ sở dữ liệu

Một trong những điểm thú vị nhất khi phát triển các hệ thống Business là lập báo cáo doanh thu.
Mình đã từng maintain hệ thống cảnh báo sớm của Cục quản lý cạnh tranh, hệ thống do đối tác nào đó viết không phải mình. Trong bài viết này mình sẽ lấy hệ thống trên làm ví dụ và cách xử lý của mình để đảm bảo tốc độ truy xuất báo cáo.

Demonstration

Dữ liệu gồm mấy trăm triệu dòng có kiểu định dạng như sau: quốc gia, loại mặt hàng, loại nhóm hàng, tổng nhập khẩu, tổng xuất khẩu, thời gian (định dạng yyyy-mm)
Để đánh giá thị trường nhập khẩu chẳng hạn thì cần so sánh tổng nhập khẩu của quốc gia này với quốc gia kia trong cùng quý, cùng tháng. Thậm chí còn cần gom lại theo cả mặt hàng và nhóm hàng nữa.
Các yêu cầu khác thì như sau:
  • Người dùng được phép tùy chọn gom lại theo cái gì bằng thao tác trên Website của hệ thống.
  • Dữ liệu của những năm/tháng cũ có thể được cập nhật lại vào bất kì thời điểm nào. Ví dụ dữ liệu năm 2012 có thể có thay đổi về mặt doanh thu.
Các bạn có thể thấy với chỉ 5 6 trường dữ liệu, chúng ta có rất nhiều chiều để tính tổng và so sánh.

Câu chuyện cũ

Giải pháp ban đầu tiên của đối tác kia là sử dụng nó nhiều pivot table (bạn hình dung nó là một temporary table), thực hiện full table scan cả trăm triệu dòng để lấy ra các thông tin aggregation xác định.
Ví dụ: Table X thì gom theo quý 1 - 2015 và theo quốc gia. Table Y thì gom theo quý 1 2015 là theo nhóm hàng, theo quốc gia.
Chưa nói tới số lượng table phải quản lý là khá nhiều, bản thân cách lưu trữ này đòi hỏi phải tính lại nếu có có sự thay đổi về dữ liệu. Mỗi lần tính toán hoặc cập nhật như vậy hệ thống chạy gần 3 ngày mới xong (hệ thống DB gồm 3 máy chủ oracle database, master-slave).

Ý tưởng xử lý ban đầu

Thường thì chúng ta nghĩ ngay tới là sử dụng các hệ thống BigData. Tuy nhiên:
  • Bạn cần deploy thêm máy chủ và thay đổi toàn bộ cấu trúc dữ liệu.
  • Vẫn phải lưu các pivot data vì query data là động từ phía người dùng. Không thể mỗi lần người dùng query lại đi chạy Map Reduce để tính toán được :D
  • Vẫn không giải quyết được triệt để độ linh động về thời gian. Chẳng han không phải tính theo quý mà chỉ từ tháng 12 năm trước tới tháng 1 năm nay mà thôi.
  • Khi cập nhật dữ liệu, vẫn cần tính toán lại rất rất nhiều. Thông thường một lần cập nhật là cập nhật luôn cả mấy chục triệu dòng. Cập nhật tháng 10 năm 2014 thì đồng nghĩa với dữ liệu tổng từ năm 2012 đến bây giờ cũng phải cập nhật theo.

TL;DR

Thời điểm đó mình quyết định phải xử lý bài toán này bằng algorithm. Và một thuật toán kinh điển rất phù hợp cho bài toán này là binary index tree. Tuy nhiên việc đưa nó cơ sở dữ liệu lại không dễ dàng, mặc dù hết sức thú vị.
Đầu tiên bạn có thể tìm hiểu về thuật toán trong link này: TopCoder - Binary Index Tree và GeeksforGeeks - Binary Index Tree
Mình sẽ nói ngắn gọn ở đây
  • Giả sử mình có một mảng gồm N số nguyên
  • Mình có 2 yêu cầu là: cập nhật bất kì số nguyên nào của mảng và tính tổng các số trong một khoảng bất kì nào đó (từ phần tử mảng ở vị trí A tới vị trí B chẳng hạn)
  • Binary index tree hoạt động như sau: một node có index là K1 sẽ quản lý tổng (hoặc chỉ số nào đó khác tùy bài toán của bạn) của các phần tử từ K1 tới K2 = K1 - lowbit(K1) = K1 - (K1 & -K1).
  • Tổng các phần tử từ 1 đến K1 sẽ được tính bằng S(K1) = F[K1] + F[K2 = K1 - (K1 & -K1)] + F[K3 = K2 - (K2 & -K2)] + ... + F[1]. Tổng số bước nhảy của phép tính trên không quá là log(N)
  • Để tính tổng các phần tử từ vị trí A tới B thì: tính tổng từ 1 tới B và từ 1 tới A - 1 rồi trừ cho nhau là xong: S[A,B] = S(B) - S(A-1)
  • Thao tác thay đổi giá trị tại bất kì thời điểm nào cũng diễn ra trong logN

Whew!

Quay lại bài toán ban đầu, điểm đáng lưu ý nhất của chúng ta là trường thời gian. Nó biến thiên, đóng vai trò là xương sống của các phép tính aggregation.
Nếu bạn hình dung thời gian đóng vai trò index giống mấy cái K1 hoặc K2 ở trên; còn giá trị cần tính là tổng nhập khẩu hoặc tổng xuất khẩu; chiều cần tính là quốc gia, mặt hàng, loại mặt hàng!! Tưởng tượng chút nhỉ.

Thiết kế và cập nhật dữ liệu

Mình sẽ thiết kế một table như sau:
CREATE TABLE pivot_nhap_khau (
    Index bigint, # index ở đây chính là chiều thời gian, chính là k1 k2 ... của chúng ta
    Nation int,
    ProductType int,
    ProductID bigint,
    Value float,
    Primary Key (Index, Nation, ProductType, ProductID)
)
Với mỗi dữ liệu được cập nhật hoặc thêm mới từ cái table cỡ bự phía trên, ta cũng đồng thời cập nhật vào table này, tác động tới một loạt các dòng dữ liệu, tuy nhiên không quá log(N) trong đó N chính là tổng số tháng.
Ví dụ thời điểm bắt đầu dữ liệu là tháng 1 năm 2012, và chúng ta muốn hệ thống này hoạt động 100 năm, tức là chúng ta có 12 * 100 = 1200 tháng. Vậy một thao tác cập nhật sẽ tác động không quá log(1200), cỡ 12 dòng gì đó.
Điểm thú vị lớn nhất là chiều dữ liệu. Ví dụ:
  • Để gom theo ProductType và ProductID mà không quan tâm tới quốc gia, ta ghi nhận thêm một dòng dữ liệu đặc biệt nữa là (index, 0, ProductType, ProductID) trong đó 0 chính là đại diện cho việc gom của chúng ta.
  • Ta sẽ cập nhật cho các điểm: (index1, 0, 34, 12) rồi (index2, 0, 34, 12) and so on. Trong đó index2 = index1 + (index1 & -index1), cứ như thế cho tới khi cái index của chúng ta vượt quá 1200 (lưu ý ở đây: giá trị 34 là id của loại hàng thủy hải sản, còn 12 là id của mặt hàng cá ngừ)
Tương tự, nếu muốn gom theo nation mà không quan tâm tới product thì ta cập nhật theo (index, Nation, 0, 0)

Select dữ liệu

Để tính tổng giá trị nhập khẩu của các ngừ trên toàn thế giới từ tháng 1 2012 tới tháng 7 năm 2015 thì ta tính như sau:
  • Tổng cá ngừ toàn thế giới từ điểm start point tới tháng 1 - 2012: index1 = tháng 1 2012, tính sum của các row sau: (index1, 0, 34, 12) + (index2, 0, 34, 12) + ... trong đó index2 = index1 - (index1 & -index1) (khác với ở trên nhé, ở trên là update nên ta cộng với lowbit, ở đây là trừ)
  • Tương tự với tháng 7 2015
  • Trừ hai kết quả cho nhau là xong
  • Tổng số row cần lấy ra không quá 2 * log(N), cỡ 24 row gì đó.

Kết quả thu được

  • Cập nhật lại dữ liệu của một tháng diễn ra trong vài tiếng thay vì vài ngày như trước.
  • Select sum cực nhanh, đáp ứng yêu cầu nhiều lượt truy cập cùng lúc của người dùng.
  • Có thể tính toán tại bất kì range nào thay vì chỉ theo quý. Ở ví dụ trên ta tính toán dữ liệu của nhiều năm liền, nhiều tháng liền nhau.

Bonus point

Điểm lý thú khác chính là khả năng sharding của dữ liệu thời gian, bài toán trên mình giới hạn 100 năm, muốn thêm 100 năm nữa thì mình sẽ thêm một table như vậy, lưu điểm mốc từ năm thứ 100 tới năm thứ 200. And so on.
PS: mình đã và đang sharding cho dự án hiện tại đang làm. Advantage là khả năng scale và tăng tốc độ xử lý cả update lẫn select.

Bài toán phân quyền vấn đề muôn thuở và rất khó hiểu.

Bài toán phân quyền vấn đề muôn thuở và rất khó hiểu.

(* Trong bài viết này mình chỉ nêu quan điểm và những thứ mình đã từng làm với việc phân quyền trên thực tế, chỉ mang tính tham khảo cho mọi người. Nếu có bất kỳ ý kiến góp ý nào hãy bình luận ở dưới để chúng ta cùng thảo luận thêm.)
Phân quyền công việc mang đầy tính năng nề đối với dev nhưng lại đầy sáng tạo cho người dùng. Như bạn biết khi phần mềm được dựng lên là có vô vàn rắc rối xoay quanh quyền hạn người sử dụng, Vì thế lập trình viên đã tạo ra một thứ mang tên phân quyền để tránh tình trạng mọi người đều là vua trong chương trình đó đơn giản với việc "Bạn được quyền làm việc đó hay không?".

1. Phân quyền là gì?

Tưởng tượng đơn giản như này nhé.
  • Bạn là sếp bạn có toàn quyền với nhân viên của mình
  • Bạn là trưởng phòng bạn chỉ có quyền với nhân viên trong phòng của mình
  • Bạn là trưởng nhóm dự án bạn chỉ có quyền với các thành viên trong nhóm
  • Và Nhóm < Phòng < công ty.
  • Như vậy bạn có thể thấy trong công ty này có 3 loại quyền hạn và chúng ta cần phân quyền theo nó

2. Bạn thường dùng loại phân quyền như thế nào?

  • Câu hỏi này mình đặt ra ban đầu là "Có các loại phân quyền nào?" nhưng thật sự việc phân quyền này rất ít được public do nó mang tính chất riêng tư của những dự án. Nên nếu bạn search thì chủ yếu sẽ chỉ tìm được cách tạo tài khoản sử dụng cho user trong các HQTCSDL.
  • Ở bài viết này mình sẽ nói về việc phân quyền bằng nhóm quyền (Group) Và đưa ra 1 số cách dựng CSDL (db).

3. Phân quyền theo group là gì và làm như thế nào ?

  • Phân quyền theo group là cách gọi chung của mình cho việc bạn nhóm nhiều thành viên trong 1 tổ chức có cùng một quyền hạn thực thi công việc. Lúc đó ta có thể nhóm họ vào 1 group để dễ dàng trao quyền hạn.
  • Như vậy từ cách phân quyền theo group này ta có thể sử dụng với "3 nấc" khác nhau.
Ví dụ đơn giản về 3 cách này nhé :D

1. Phân quyền theo cấp bậc

  • Loại hình này chúng ta thường thiết kế db đơn giản như sau
  • Khi đó dữ liệu bạn dùng sẽ có dạng như thế này.
  • role ở đây là 1, 2, 3 tức là có 3 mức quyền hạn và lớn nhất hay bé nhất còn tùy thuộc vào quy định của mỗi công ty. Ví dụ

- Ưu điểm
Việc sử dụng kiểu phân quyền này dễ dàng cho những người mới bắt đầu. Những nhóm quyền được lập lên nhanh chóng có thể sử dụng luôn, Và việc phải check cũng tương đối là đơn giản, bạn chỉ cần 1 cần
select count(*) from tbl_... where id = ? and role = ?
hoặc
select role from tbl_... where id = ?
Rất dễ dàng để sử dụng đúng không. :D
- Nhược điểm
Bạn biết đó việc sử dụng dữ liệu như này tồn tại 1 số nhược điểm rất lớn sau
  • Rất khó có thể mở rộng dự án
  • Trong thực tế không phải lúc nào cũng có 3 role. Nó có thể phát sinh nhiều role kì dị. Ví dụ: Thư ký giám đốc ngoài quyền đuổi việc ra còn lại nó sẽ có quyền của giám đốc vậy trường hợp này thuộc role 1 hay 2 ?.
  • Rất khó để phân quyền chi tiết.

2. Phân quyền theo chức năng


Loại phân quyền này được sử dụng rất nhiều trong thực tế. Nó rất hiệu quả và dễ thao tác đối với người cấp quyền.
Ta thiết kế db đơn giản trong ví dụ này như sau:

Nhưng để Demo mình sẽ tóm gọn 2 bảng tbl_action và tbl_per_action thành bảng tbl_per_detail để dễ thao tác. Và ta có một Database như sau.
alt text

Chi tiết của việc thiết kế DB như sau:
  • tbl_user: bảng lưu người dùng bao gồm các thuộc tính như ID, Name,.... Bảng không có khóa ngoại.
  • tbl_permision: bảng chứa nhóm quyền hạn. bao gồm các thuộc tính, ID nhóm quyền hạn, tên nhóm quyền hạn.
  • tbl_permision_detail: là bảng sẽ chứa những quyền hạn cụ thể dành cho nhóm quyền hạn. Trường action_name không cần thiết bạn có thể bỏ. Trường action_code là để khi lập trình mình định nghĩa một thao tác nhất định trong bằng code này ví dụ quyền sửa thì code nó là EDIT chẳng hạn.
  • tbl_per_relationship: là bảng lưu mối liên hệ giữa người dùng và nhóm quyền hạn. Mục đích của bảng này không phải là để một người dùng có nhiều nhóm quyền mà để không phải truy vấn lại bảng user chứa thông tin nhạy cảm như username và password. Bạn cũng có thể bỏ qua bảng này và liên hệ trực tiếp giữa bảng user và permision luôn, nhưng mình khuyên bạn nên sử dụng thêm bảng này vì có nhiều trường hợp user có nhiều quyền hạn.
1. Kiểm tra dữ liệu trong các bảng
  • tbl_user

  • tbl_permision

  • tbl_per_detail

  • tbl_user_per

2. Làm một số ví dụ
  • Kiểm tra quyền của người dùng ví dụ: Hãy kiểm tra quyền của user có id là 1:
DECLARE @result NVARCHAR(1000)
SET @result = N'Những quyền hiện tại của user ('

select @result = @result + name_user + ') là: ' from tbl_user where id_user = 1
select @result = @result + action_name + ', ' from tbl_user as u
    join tbl_user_per as up on u.id_user = up.id_user
    join tbl_permision as p on up.id_per = p.id_per
    join tbl_per_detail as pd on p.id_per = pd.id_per
    where u.id_user = 1 and up.licensed = 1 and pd.check_action = 1
select @result = substring(@result, 0, len(@result))

print @result
  • kết quả

  • Kiểm tra xem user 2 có quyền xóa bài viết không ?
DECLARE @result bit
select @result = check_action from tbl_user as u
    join tbl_user_per as up on u.id_user = up.id_user
    join tbl_permision as p on up.id_per = p.id_per
    join tbl_per_detail as pd on p.id_per = pd.id_per
    where u.id_user = 2 and up.licensed = 1 and action_code = 'DELETE'

begin
    if @result = 1
        print N'Bạn CÓ quyền xóa post'
    else
        print N'Bạn KHÔNG có quyền xóa post'
end
  • kết quả

  • Đó là một số ví dụ đơn giản tron tình huống này khi sử dụng phân quyền theo nhóm (group)
3. Kết luận
- Ưu điểm
  • Việc phân quyền này như mình có nêu ngay từ ban đầu rất dễ thao tác đối với những admin khi họ muốn chuyển nhóm quyền hoặc thỏa sức sáng tạo trong việc tạo ra những quyền mới từ những quyền ban đầu. Ví dụ như người dùng vừa có thể EDIT và DELETE,....
  • Ngoài ra việc thực hiện những câu querry cũng rất dễ dàng cho những lập trình viên.
- Nhược điểm
  • Vấn đề sử dụng quyền hành động rất dễ khi chúng ta làm việc trên 1 group, nhưng nếu trong chương trình của bạn có nhiều group và phân cấp nhiều tầng thì nó lại là một vấn đề nan giải khác, khi bạn không chỉ phải check quyền hành động mà bạn còn phải check xem quyền hành động này của người dùng có thể áp dùng được trong group khác hay không?.

3. Phân quyền theo Hành động của các nhóm Group theo những cấp bậc khác nhau

  • Đây là loại phân quyền lằng ngằng nhất nhưng lại là quan trọng nhất, bởi các lý do sau đây:
  • Các tổ chức sử dụng phần mềm để thực hiện thao tác của họ đều có phân cấp rõ ràng
  • Trong những tổ chức có những người nắm full quyền của nhiều nhóm
  • Có những thành viên thuộc nhiều nhóm
  • Có những thành viên tuy chỉ là nhân viên nhưng lại có quyền của các sếp (thư ký)
  • Chính vì có nhiều trường hợp như vậy những lập trình viên sinh ra được rất nhiều case trong code.
Giải quyết vấn đề này bạn có thể tìm hiểu cách thự hiện phân quyền trong odoo.
  • Phân quyền theo model: Có nghĩ là người dùng được thao tác thực hiện với những bảng dữ liệu nào
    Ví dụ: Admin có thể thực hiện với thao tác với bảng user của họ
  • phân quyền theo raw: Người dùng được thực hiện việc thao tác với các raw được chỉ định
    Ví dụ: Leader A chỉ có thể thực hiện thao tác với những thành viên của mình trong bảng user
    
  • phân quyền theo column: Người dùng sẽ được quyền thao tac với nhưng column đó
    Ví dụ: Chỉ giám đốc mới có thể đuổi việc nhân viên, ở đây ta sẽ có 1 column tên là is_working để biết việc nhân viên đó còn đi làm hay không :D
    

4. Kết luận

  • Việc bạn sử dụng cách nào hay thứ tự ra sao tưởng rằng không quan trọng nhưng nếu nhìn xa hơn trong việc mở rộng cũng như update phần mềm thì đó có thể là cực hình cho người sau.
  • Nếu bạn được quyền tham gia vào thiết kế database (thường là review và góp ý) thì hãy xem xét đến các vấn đề về việc những trường hợp phát sinh đề phòng những người tạo ra DB thiếu sót.
Loại phân quyền thứ 3 có rất nhiều kiểu biến hóa trong từng phần mềm của từng công ty, Và mình sẽ cố gắng tìm hiểu thêm để viết một bài nói về kiểu này ngoài ra mình cũng sẽ cố gắng viết 1 bài nói về kiểu phân quyền của odoo. Thực chất nó cũng gần giống kiểu phân quyền trong group thôi :D.
Cảm ơn các bạn đã đọc bài viết của mình. Văn phong hơi lủng củng nên mong các bạn thông cảm!.