URL with HashIDs in Rails App

https://viblo.asia/tienthanht/posts/PwRGgmNqkEd

Câu view tí nào =)) Đùa đấy, các bạn có nhìn thấy đoạn link trên của viblo sử dụng 1 đoạn mã loằng ngoằng thay cho ID như bình thường không ạ? Ví dụ bài post của mình là bản ghi thứ 20000 trong DB thì đường URL nó sẽ trông như thế này

https://viblo.asia/tienthanht/posts/20000

rõ ràng việc thay URL bằng 1 đoạn hashids như trên trông sẽ chuyên nghiệp và có vẻ bảo mật hơn (không để lộ ID trong cơ sở dữ liệu của bạn) rất nhiều so với để ID theo thứ tự 1, 2, 3 ...

Hôm nay mình sẽ giới thiệu 1 chút về hashids và implement vào ứng dụng Rails

Hashid là gì?

Hashids là một thư viện mã nguồn mở nhỏ, id ngắn độc đáo, không tuần tự từ số. Nó chuyển đổi các con số như 347 thành chuỗi như "yr8", hoặc mảng các số như [27, 986] vào "3kTMd". Bạn cũng có thể giải mã những id lại. Nó được thiết kế cho các trang web để sử dụng trong URL shortening, các công cụ theo dõi hoặc làm cho các trang tin

Các tạo ra chuỗi Hashid như thế nào?

Hashids được tạo ra tương tự như cách chuyển đổi số nguyên sáng dạng hex, nhưng cũng có 1 vài trường hợp ngoại lệ:

  1. Bảng chữ cái dùng base62 là mặc định, không phải là base16
  2. Bảng chữ được xáo trộn (shuffle) dựa trên salt

*Note: chú thích 1 chút Salt là chuỗi random được thêm vào kèm theo 1 thuật toán hash nào đó khi mã hóa, điều này đảm bảo output sau đó là không trùng nhau.

Dưới đây là hàm JavaScript để conver từ số nguyên sang kiểu hash. Có thể coi là 1 phần của Hashids

function toHex(input) {

  var hash = "",
    alphabet = "0123456789abcdef",
    alphabetLength = alphabet.length;

  do {
    hash = alphabet[input % alphabetLength] + hash;
    input = parseInt(input / alphabetLength, 10);
  } while (input);

  return hash;

}

Nếu chúng ta conver số 1234 sang kiểu hex toHex(1234), output sẽ là "4d2" Nếu chúng ta thay đổi biến alphabet ở trên thành alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", khi đó output sẽ là "t5" Tóm lại khi thay đổi biến alphabet (bằng cách xáo trộn thứ tự chúng lên) sẽ có 1 output mới sinh ra khi mã hóa. Lại có câu hỏi ở đây, là làm thế nào để chúng ta có thể tạo ra 1 bảng chữ cái shuffle ngẫu nhiên trong mỗi lần gọi hàm hash ở trên, ở trong HashIDs người ta định nghĩa alphabet shuffle bằng thuật toán Fisher-Yates algorithm

function consistentShuffle(alphabet, salt) {

  var integer, j, temp, i, v, p;

  for (i = alphabet.length - 1, v = 0, p = 0; i > 0; i--, v++) {

    v %= salt.length;
    p += integer = salt[v].charCodeAt(0);
    j = (integer + v + p) % i;

    temp = alphabet[j];
    alphabet = alphabet.substr(0, j) + alphabet[i] + alphabet.substr(j + 1);
    alphabet = alphabet.substr(0, i) + temp + alphabet.substr(i + 1);

  }

  return alphabet;

}

Đoạn code trên nhìn qua trông khá phức tạp, nhưng thực ra nó khá đơn giản, chỉ là xáo trộn alphabet dự trên salt của user Ví dụ 1 người dùng sử salt là "abc1", alphabet output cUpI6isqCa0brWZnJA8wNTzDHEtLXOYgh5fQm2uRj4deM91oB7FkSGKxvyVP3l 1 người dùng khác với salt là "abc2" thì alphabet output tRvkhHx0ZefcF46YuaAqGLDKgM1W5Vp2T8n9s7BSoCjiQOdrEbJmUINywzXP3l Có thể thấy shuffle output sinh ra là khá ổn dù cho giá trị salt không khác nhau quá nhiều. Tạo cơ sở tốt cho Hashids làm việc.

Tóm lại, đây là 1 cái nhìn tổng quát về cách Hashids được cấu trúc. Quá trình giải mã được thực hiện theo cách tương tự nhưng ngược lại, về cơ bản Hashids cần các giá trị salt để giải mã

Implement to Rails App

Có rất nhiều cách để Implement Hashid vào ứng dụng của mình, bạn có thể viêt hàm tự encode ID sang dạng hash, nhưng ở đây mình sẽ suggest các bạn dùng gem cho easy :v

acts_as_hashids

Như bình thường chúng ta cần add vào Gemfile và bundle install chúng

gem "acts_as_hashids"

Sau khi Implement xong thì việc sử dụng Hash id thay cho ID thông thường trở nên dễ dàng hơn bao giờ hết Để active Hashids chúng ta chỉ cần thêm vào model tương ứng như sau

class Foo < ActiveRecord::Base
     acts_as_hashids
end

OK, rồi giờ chúng ta mở console lên test thử nhé (rails c)

foo = Foo.create
# => #<Foo:0x007feb5978a7c0 id: 3>

foo.to_param
# => "ePQgabdg"

Foo.find(3)
# => #<Foo:0x007feb5978a7c0 id: 3>

Foo.find("ePQgabdg")
# => #<Foo:0x007feb5978a7c0 id: 3>

Foo.with_hashids("ePQgabdg").first
# => #<Foo:0x007feb5978a7c0 id: 3>

Mặc định chiều dài hash ids là 8 ký tự, chúng ta có thể settings số kí tự, ví dụ

class Foo < ActiveRecord::Base
  acts_as_hashids length: 2
end

Foo.create.to_param
# => "Rx"

Ok, như vậy việc implement hashid vào rails app đã xong, rất đơn giản phải không các bạn 😄

Tài liệu tham khảo

http://hashids.org/ https://github.com/dtaniwaki/acts_as_hashids https://github.com/ivanakimov/hashids.js


All Rights Reserved