通常我們遞增的id作為請求資源的標識,但如果站點直接使用遞增的id字段作為業務屬性使用,那么對站點會造成如下影響(不限於如下列出的):
-
對於用戶資源,站點很容易被窺測出總注冊用戶量、時間段內注冊用戶量等。
A站
和B站
的個人主頁地址采用的就是遞增的id,e.g.: 第一位用戶:~/1
;第二位用戶:~/2
,以此類推。 -
對於視頻資源,很容通過爬蟲得到站點所有視頻資源。
B站
已經從原來的"av" + 遞增id
改為"BV" + base58(av號)
,但是本質只是對數據庫表av號進行了base58操作,在視頻頁,通過在瀏覽器F12 Consoles中輸入aid即可獲得BV號所對應的av號,並且視頻頁的HTML.meta標簽中依然存在av號地址。
當某些資源對外提供時,我們希望能夠有使用一種編碼算法能夠將具有遞增性的標識碼進行編碼為無遞增性的標識。
Hashids
Hashids是一款非常小巧跨語言的開源庫,可以將數字
或者16進制字符串
轉為短且唯一不連續的字符串,Hashids
是雙向編碼(支持encode
和decode
),比如,它可以將347
之類的數字轉換為yr8
之類的字符串,也可以將yr8
之類的字符串重新解碼為347
之類的數字。
youtobe
的視頻地址鏈接中?v=
參數后使用的就是Hashids。騰訊視頻
和愛奇藝
使用的應該是類似這種的Hash算法。
與youtobe不同,TikTok網頁版的短視頻編碼是基於發號器的唯一id。
Java版實現的Hashids
Hashids有着不同語言的實現,可根據不同語言去官方參考相應的工具庫使用,我們挑選Java版本的org.hashids >> hashids >> 1.0.3
實現庫進行說明。
Hashids java版本的原理實現可參照源代碼和借鑒這篇文章。
pom.xml
依賴
<dependency>
<groupId>org.hashids</groupId>
<artifactId>hashids</artifactId>
<version>1.0.3</version>
</dependency>
測試用例
Hashids可以自定義salt,但必須保證編碼和解碼過程中使用同一套salt,務必妥善保管salt。測試用例中使用的salt全部為this is my salt
。
需要注意的點:
-
在生產環境中設置的salt要保持足夠的復雜度,並且要妥善保存好salt,泄漏或者丟失salt要做的彌補工作會非常麻煩。
-
Hashids可以自定義編碼后結果的最小長度,測試用例中統一使用
11
位最小編碼結果長度。11位最小編碼長度,意味着不同的輸入得到的結果長度是可能超過11位的。 -
org.hashids >> hashids >> 1.0.3
在編碼和解碼過程中嚴格區別大小寫。
編碼long類型
的可變參數為字符串
@Test
void test_hashids_encode_method() {
final String SALT = "this is my salt";
final int MIN_HASH_LENGTH = 11;
Hashids hashids = new Hashids(SALT, MIN_HASH_LENGTH);
String encryptString = hashids.encode(347L);
System.out.println(encryptString); // Y5bAyr8dLO4
}
encode(long...numbers)
中的參數是long類型
的可變參數,可以將多個long類型
的參數編碼到一個字符串中,將可變參數編碼為一個字符串的場景可自行思考靈活設計。
解碼字符串為Long類型數組
@Test
void test_hashids_decode_method() {
final String SALT = "this is my salt";
final int MIN_HASH_LENGTH = 11;
Hashids hashids = new Hashids(SALT, MIN_HASH_LENGTH);
long[] decrypedNumbers = hashids.decode("Y5bAyr8dLO4");
Arrays.stream(decrypedNumbers).forEach(item -> System.out.println(item)); // 347
}
如果編碼和解碼過程中使用的salt不一致,則long[]
為空數組。
編碼16進制字符串為Hashids字符串
Hashids支持對16進制字符串進行編碼,所以如果使用mongodb
數據庫時,那么就可以將系統自動生成的_id
字符串使用Hashids進行編碼。
/**
* {@link <a href="https://stackoverflow.com/a/27137224">node.js - get hash from strings, like hashids - Stack Overflow</a>}
* <p>
* {@link <a href="https://hashids.org/java/">hashids</a>}
*/
@Test
void test_hashids_encodeHex_method() {
final String SALT = "this is my salt";
final int MIN_HASH_LENGTH = 11;
Hashids hashids = new Hashids(SALT, MIN_HASH_LENGTH);
String encryptString = hashids.encodeHex(cn.hutool.core.util.HexUtil.encodeHexStr("this is a string"));
System.out.println(encryptString); // 1prnZLrKPlS5EEe61reMCNzkJXP
}
解碼Hashids字符串為16進制字符串
與編碼16進制字符串相應的,可以根據字符串解碼得到16進制字符串。
/**
* {@link <a href="https://stackoverflow.com/a/27137224">node.js - get hash from strings, like hashids - Stack Overflow</a>}
* <p>
* {@link <a href="https://hashids.org/java/">hashids</a>}
*/
@Test
void test_hashids_decodeHex_method() {
final String SALT = "this is my salt";
final int MIN_HASH_LENGTH = 11;
Hashids hashids = new Hashids(SALT, MIN_HASH_LENGTH);
String decrypedNumbers = hashids.decodeHex("1prnZLrKPlS5EEe61reMCNzkJXP");
System.out.println(cn.hutool.core.util.HexUtil.decodeHexStr(decrypedNumbers)); // this is a string
}
如果編碼和解碼過程中使用的salt不一致,則long[]
為空數組。
自定義字母表映射集
默認映射字母表為:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
,可以根據其構造函數自定義映射字母表,測試用例中我們使用和youtobe
相似的映射字母表。
自定義的字母表中的字符至少應含有16個字符。
@Test
void test_hashids_costom_alphabet_by_Constructor() {
final String SALT = "this is my salt";
final int MIN_HASH_LENGTH = 11;
final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-";
Hashids hashids = new Hashids(SALT, MIN_HASH_LENGTH, ALPHABET);
String encryptString = hashids.encode(347L);
System.out.println(encryptString); // kqBg-Kpg7_J
}
文章同步發布在各大主流知識共享平台,所以設github
為統一的反饋區
疑問、討論、問題反饋:https://github.com/weixsun/discussion
關注微信公眾號 obmq 及時了解最新動態