hashids.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <?php
  2. /*
  3. Hashids
  4. http://hashids.org/php
  5. (c) 2013 Ivan Akimov
  6. https://github.com/ivanakimov/hashids.php
  7. hashids may be freely distributed under the MIT license.
  8. */
  9. /**
  10. * HashGenerator is a contract for generating hashes
  11. */
  12. interface HashGenerator {
  13. /**
  14. * Encodes a variable number of parameters to generate a hash
  15. *
  16. * @param mixed ...
  17. *
  18. * @return string the generated hash
  19. */
  20. public function encode();
  21. /**
  22. * Decodes a hash to the original parameter values
  23. *
  24. * @param string $hash the hash to decode
  25. *
  26. * @return array
  27. */
  28. public function decode($hash);
  29. /**
  30. * Encodes hexadecimal values to generate a hash
  31. *
  32. * @param string $str hexadecimal string
  33. *
  34. * @return string the generated hash
  35. */
  36. public function encode_hex($str);
  37. /**
  38. * Decodes hexadecimal hash
  39. *
  40. * @param string $hash
  41. *
  42. * @return string hexadecimal string
  43. */
  44. public function decode_hex($hash);
  45. }
  46. class Hashids implements HashGenerator {
  47. const VERSION = '1.0.5';
  48. /* internal settings */
  49. const MIN_ALPHABET_LENGTH = 16;
  50. const SEP_DIV = 3.5;
  51. const GUARD_DIV = 12;
  52. /* error messages */
  53. const E_ALPHABET_LENGTH = 'alphabet must contain at least %d unique characters';
  54. const E_ALPHABET_SPACE = 'alphabet cannot contain spaces';
  55. /* set at constructor */
  56. private $_alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
  57. private $_seps = 'cfhistuCFHISTU';
  58. private $_min_hash_length = 0;
  59. private $_math_functions = array();
  60. private $_max_int_value = 1000000000;
  61. const hashids_length = 6; // 加密字符串长度
  62. const hashids_salt = 'test'; // 加密钥匙
  63. const hashids_alphabet = ''; // 字符仓库,不填写默认为扩展里的字符仓库
  64. private static $instance; //单例
  65. /**
  66. * 初始化
  67. * @param array $options
  68. * @return static
  69. */
  70. public static function instance($length = null, $salt = null, $alphabet = null)
  71. {
  72. if (is_null(self::$instance)) {
  73. if ($length === null) $length = self::hashids_length;
  74. if ($salt === null) $salt = self::hashids_salt;
  75. if ($alphabet === null) self::hashids_alphabet;
  76. self::$instance = new static($salt, $length, $alphabet);
  77. }
  78. return self::$instance;
  79. }
  80. public function __construct($salt = '', $min_hash_length = 8, $alphabet = '') {
  81. /* if either math precision library is present, raise $this->_max_int_value */
  82. if (function_exists('gmp_add')) {
  83. $this->_math_functions['add'] = 'gmp_add';
  84. $this->_math_functions['div'] = 'gmp_div';
  85. $this->_math_functions['str'] = 'gmp_strval';
  86. } else if (function_exists('bcadd')) {
  87. $this->_math_functions['add'] = 'bcadd';
  88. $this->_math_functions['div'] = 'bcdiv';
  89. $this->_math_functions['str'] = 'strval';
  90. }
  91. $this->_lower_max_int_value = $this->_max_int_value;
  92. if ($this->_math_functions) {
  93. $this->_max_int_value = PHP_INT_MAX;
  94. }
  95. /* handle parameters */
  96. $this->_salt = $salt;
  97. if ((int)$min_hash_length > 0) {
  98. $this->_min_hash_length = (int)$min_hash_length;
  99. }
  100. if ($alphabet) {
  101. $this->_alphabet = implode('', array_unique(str_split($alphabet)));
  102. }
  103. if (strlen($this->_alphabet) < self::MIN_ALPHABET_LENGTH) {
  104. throw new \Exception(sprintf(self::E_ALPHABET_LENGTH, self::MIN_ALPHABET_LENGTH));
  105. }
  106. if (is_int(strpos($this->_alphabet, ' '))) {
  107. throw new \Exception(self::E_ALPHABET_SPACE);
  108. }
  109. $alphabet_array = str_split($this->_alphabet);
  110. $seps_array = str_split($this->_seps);
  111. $this->_seps = implode('', array_intersect($alphabet_array, $seps_array));
  112. $this->_alphabet = implode('', array_diff($alphabet_array, $seps_array));
  113. $this->_seps = $this->_consistent_shuffle($this->_seps, $this->_salt);
  114. if (!$this->_seps || (strlen($this->_alphabet) / strlen($this->_seps)) > self::SEP_DIV) {
  115. $seps_length = (int)ceil(strlen($this->_alphabet) / self::SEP_DIV);
  116. if ($seps_length == 1) {
  117. $seps_length++;
  118. }
  119. if ($seps_length > strlen($this->_seps)) {
  120. $diff = $seps_length - strlen($this->_seps);
  121. $this->_seps .= substr($this->_alphabet, 0, $diff);
  122. $this->_alphabet = substr($this->_alphabet, $diff);
  123. } else {
  124. $this->_seps = substr($this->_seps, 0, $seps_length);
  125. }
  126. }
  127. $this->_alphabet = $this->_consistent_shuffle($this->_alphabet, $this->_salt);
  128. $guard_count = (int)ceil(strlen($this->_alphabet) / self::GUARD_DIV);
  129. if (strlen($this->_alphabet) < 3) {
  130. $this->_guards = substr($this->_seps, 0, $guard_count);
  131. $this->_seps = substr($this->_seps, $guard_count);
  132. } else {
  133. $this->_guards = substr($this->_alphabet, 0, $guard_count);
  134. $this->_alphabet = substr($this->_alphabet, $guard_count);
  135. }
  136. }
  137. public function encode() {
  138. $ret = '';
  139. $numbers = func_get_args();
  140. if (func_num_args() == 1 && is_array(func_get_arg(0))) {
  141. $numbers = $numbers[0];
  142. }
  143. if (!$numbers) {
  144. return $ret;
  145. }
  146. foreach ($numbers as $number) {
  147. $is_number = ctype_digit((string)$number);
  148. if (!$is_number || $number < 0 || $number > $this->_max_int_value) {
  149. return $ret;
  150. }
  151. }
  152. return $this->_encode($numbers);
  153. }
  154. public function decode($hash) {
  155. $ret = array();
  156. if (!$hash || !is_string($hash) || !trim($hash)) {
  157. return $ret;
  158. }
  159. return $this->_decode(trim($hash), $this->_alphabet);
  160. }
  161. public function encode_hex($str) {
  162. if (!ctype_xdigit((string)$str)) {
  163. return '';
  164. }
  165. $numbers = trim(chunk_split($str, 12, ' '));
  166. $numbers = explode(' ', $numbers);
  167. foreach ($numbers as $i => $number) {
  168. $numbers[$i] = hexdec('1' . $number);
  169. }
  170. return call_user_func_array(array($this, 'encode'), $numbers);
  171. }
  172. public function decode_hex($hash) {
  173. $ret = "";
  174. $numbers = $this->decode($hash);
  175. foreach ($numbers as $i => $number) {
  176. $ret .= substr(dechex($number), 1);
  177. }
  178. return $ret;
  179. }
  180. public function get_max_int_value() {
  181. return $this->_max_int_value;
  182. }
  183. private function _encode(array $numbers) {
  184. $alphabet = $this->_alphabet;
  185. $numbers_size = sizeof($numbers);
  186. $numbers_hash_int = 0;
  187. foreach ($numbers as $i => $number) {
  188. $numbers_hash_int += ($number % ($i + 100));
  189. }
  190. $lottery = $ret = $alphabet[$numbers_hash_int % strlen($alphabet)];
  191. foreach ($numbers as $i => $number) {
  192. $alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet)));
  193. $ret .= $last = $this->_hash($number, $alphabet);
  194. if ($i + 1 < $numbers_size) {
  195. $number %= (ord($last) + $i);
  196. $seps_index = $number % strlen($this->_seps);
  197. $ret .= $this->_seps[$seps_index];
  198. }
  199. }
  200. if (strlen($ret) < $this->_min_hash_length) {
  201. $guard_index = ($numbers_hash_int + ord($ret[0])) % strlen($this->_guards);
  202. $guard = $this->_guards[$guard_index];
  203. $ret = $guard . $ret;
  204. if (strlen($ret) < $this->_min_hash_length) {
  205. $guard_index = ($numbers_hash_int + ord($ret[2])) % strlen($this->_guards);
  206. $guard = $this->_guards[$guard_index];
  207. $ret .= $guard;
  208. }
  209. }
  210. $half_length = (int)(strlen($alphabet) / 2);
  211. while (strlen($ret) < $this->_min_hash_length) {
  212. $alphabet = $this->_consistent_shuffle($alphabet, $alphabet);
  213. $ret = substr($alphabet, $half_length) . $ret . substr($alphabet, 0, $half_length);
  214. $excess = strlen($ret) - $this->_min_hash_length;
  215. if ($excess > 0) {
  216. $ret = substr($ret, $excess / 2, $this->_min_hash_length);
  217. }
  218. }
  219. return $ret;
  220. }
  221. private function _decode($hash, $alphabet) {
  222. $ret = array();
  223. $hash_breakdown = str_replace(str_split($this->_guards), ' ', $hash);
  224. $hash_array = explode(' ', $hash_breakdown);
  225. $i = 0;
  226. if (sizeof($hash_array) == 3 || sizeof($hash_array) == 2) {
  227. $i = 1;
  228. }
  229. $hash_breakdown = $hash_array[$i];
  230. if (isset($hash_breakdown[0])) {
  231. $lottery = $hash_breakdown[0];
  232. $hash_breakdown = substr($hash_breakdown, 1);
  233. $hash_breakdown = str_replace(str_split($this->_seps), ' ', $hash_breakdown);
  234. $hash_array = explode(' ', $hash_breakdown);
  235. foreach ($hash_array as $sub_hash) {
  236. $alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet)));
  237. $ret[] = (int)$this->_unhash($sub_hash, $alphabet);
  238. }
  239. if ($this->_encode($ret) != $hash) {
  240. $ret = array();
  241. }
  242. }
  243. //修改为直接返回字符串
  244. if(isset($ret[0])){
  245. return $ret[0];
  246. }else{
  247. return false;
  248. }
  249. }
  250. private function _consistent_shuffle($alphabet, $salt) {
  251. if (!strlen($salt)) {
  252. return $alphabet;
  253. }
  254. for ($i = strlen($alphabet) - 1, $v = 0, $p = 0; $i > 0; $i--, $v++) {
  255. $v %= strlen($salt);
  256. $p += $int = ord($salt[$v]);
  257. $j = ($int + $v + $p) % $i;
  258. $temp = $alphabet[$j];
  259. $alphabet[$j] = $alphabet[$i];
  260. $alphabet[$i] = $temp;
  261. }
  262. return $alphabet;
  263. }
  264. private function _hash($input, $alphabet) {
  265. $hash = '';
  266. $alphabet_length = strlen($alphabet);
  267. do {
  268. $hash = $alphabet[$input % $alphabet_length] . $hash;
  269. if ($input > $this->_lower_max_int_value && $this->_math_functions) {
  270. $input = $this->_math_functions['str']($this->_math_functions['div']($input, $alphabet_length));
  271. } else {
  272. $input = (int)($input / $alphabet_length);
  273. }
  274. } while ($input);
  275. return $hash;
  276. }
  277. private function _unhash($input, $alphabet) {
  278. $number = 0;
  279. if (strlen($input) && $alphabet) {
  280. $alphabet_length = strlen($alphabet);
  281. $input_chars = str_split($input);
  282. foreach ($input_chars as $i => $char) {
  283. $pos = strpos($alphabet, $char);
  284. if ($this->_math_functions) {
  285. $number = $this->_math_functions['str']($this->_math_functions['add']($number, $pos * pow($alphabet_length, (strlen($input) - $i - 1))));
  286. } else {
  287. $number += $pos * pow($alphabet_length, (strlen($input) - $i - 1));
  288. }
  289. }
  290. }
  291. return $number;
  292. }
  293. }