BaseConverter.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <?php
  2. namespace PhpLife\Frame\Library;
  3. /**
  4. * BASE62 解析类,专门针对长10进制与62进制互转
  5. */
  6. class BaseConverter
  7. {
  8. private static $string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  9. private static $encodeBlockSize = 7;
  10. private static $decodeBlockSize = 4;
  11. /**
  12. * 批量从10进制转换到62进制
  13. *
  14. * @param array $mids
  15. * @return array 以10进制mid为key的62进制mid数组
  16. */
  17. public static function multi_from10to62(array $mids)
  18. {
  19. $ret = array();
  20. foreach ($mids as $mid) {
  21. $ret[$mid] = self::from10to62($mid);
  22. }
  23. return $ret;
  24. }
  25. /**
  26. * 批量从62进制转换到10进制
  27. *
  28. * @param array $mids
  29. * @param bool $compat
  30. * @param bool $for_mid
  31. * @return array 以62进制mid为key的10进制mid数组
  32. */
  33. public static function multi_from62to10(array $mids, $compat = false, $for_mid = true)
  34. {
  35. $ret = array();
  36. foreach ($mids as $mid) {
  37. $ret[$mid] = self::from62to10($mid, $compat, $for_mid);
  38. }
  39. return $ret;
  40. }
  41. /**
  42. * 将mid从10进制转换成62进制字符串
  43. *
  44. * @param string $mid
  45. * @return string
  46. */
  47. public static function from10to62($mid)
  48. {
  49. $str = "";
  50. $midlen = strlen($mid);
  51. $segments = ceil($midlen / self::$encodeBlockSize);
  52. $start = $midlen;
  53. for ($i = 1; $i < $segments; $i += 1) {
  54. $start -= self::$encodeBlockSize;
  55. $seg = substr($mid, $start, self::$encodeBlockSize);
  56. $seg = self::encodeSegment($seg);
  57. $str = str_pad($seg, self::$decodeBlockSize, '0', STR_PAD_LEFT) . $str;
  58. }
  59. $str = self::encodeSegment(substr($mid, 0, $start)) . $str;
  60. return $str;
  61. }
  62. /**
  63. * 将62进制字符串转成10进制mid
  64. *
  65. * $compat $for_mid 参数意图不确定,直接从原作者处考过来。使用时直接使用默认参数即可。
  66. *
  67. * @param string $str
  68. * @param bool $compat
  69. * @param bool $for_mid
  70. * @return string
  71. */
  72. public static function from62to10($str, $compat = false, $for_mid = true)
  73. {
  74. $mid = "";
  75. $strlen = strlen($str);
  76. $segments = ceil($strlen / self::$decodeBlockSize);
  77. $start = $strlen;
  78. for ($i = 1; $i < $segments; $i += 1) {
  79. $start -= self::$decodeBlockSize;
  80. $seg = substr($str, $start, self::$decodeBlockSize);
  81. $seg = self::decodeSegment($seg);
  82. /* Note by Rodin:
  83. * 由于 decodeBlockSize设置为4,而 encodeBlockSize 设置为 7
  84. * 而 base 62里4位最大的数为 ZZZZ,对应到10进制为 14776336,比encodeBlockSize
  85. * 多1位……所以,将10进制转成62进制且在不足位时左补0,不会有任何问题。反之,
  86. * 在62转成10进制的时候就可能会出现溢出,这是个潜在的bug,不清楚为什么目前没有
  87. * bug爆出且当初采用了这种不标准不严谨的62进制转换法……
  88. * 目前常用的mid里存在类似: l4ETJ4DfL (9位),在每segment大约出现大于 GZZZ 的串以后
  89. * 总位数就会不符合目前的预期……
  90. * 例如:
  91. * ZZZZZZZZZ转换成10进制会成为 611477633614776336,总位数已经达到了20位,而常见的7位
  92. * mid的62进制数字通常只有16位
  93. */
  94. $mid = str_pad($seg, self::$encodeBlockSize, '0', STR_PAD_LEFT) . $mid;
  95. }
  96. $mid = self::decodeSegment(substr($str, 0, $start)) . $mid;
  97. //判断v3、v4版本mid
  98. if ($for_mid) {
  99. $midlen = strlen($mid);
  100. $first = substr($mid, 0, 1);
  101. if ($midlen == 16 && ($first == '3' || $first == '4')) {
  102. return $mid;
  103. }
  104. if ($midlen == 19 && $first == '5') {
  105. return $mid;
  106. }
  107. }
  108. //end
  109. if ($compat && !in_array(substr($mid, 0, 3), array('109', '110', '201', '211', '221', '231', '241'))) {
  110. $mid = self::decodeSegment(substr($str, 0, 4)) . self::decodeSegment(substr($str, 4));
  111. }
  112. if ($for_mid) {
  113. if (substr($mid, 0, 1) == '1' && substr($mid, 7, 1) == '0') {
  114. $mid = substr($mid, 0, 7) . substr($mid, 8);
  115. }
  116. }
  117. return $mid;
  118. }
  119. /**
  120. * 将10进制转换成62进制
  121. *
  122. * @param string $str 10进制字符串
  123. * @return string
  124. */
  125. private static function encodeSegment($str)
  126. {
  127. $out = '';
  128. while ($str > 0) {
  129. $idx = $str % 62;
  130. $out = substr(self::$string, $idx, 1) . $out;
  131. $str = floor($str / 62);
  132. }
  133. return $out;
  134. }
  135. /**
  136. * 将62进制转换成10进制
  137. *
  138. * @param string $str 62进制字符串
  139. * @return string
  140. */
  141. private static function decodeSegment($str)
  142. {
  143. $out = 0;
  144. $base = 1;
  145. for ($t = strlen($str) - 1; $t >= 0; $t -= 1) {
  146. $out = $out + $base * strpos(self::$string, substr($str, $t, 1));
  147. $base *= 62;
  148. }
  149. return strval($out);
  150. }
  151. }