├── .gitignore ├── config └── config.php ├── src ├── Facades │ └── InviteCode.php ├── InviteCodeProvider.php ├── InviteCode.php └── Libs │ ├── HashGenerator.php │ └── Hashids.php ├── README.md ├── composer.json └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | 6, //邀请码长度 4 | 'char' => '',//生成邀请码的字符,默认a-zA-Z0-9 5 | 'salt' => '',//加密盐,默认使用APP_KEY 6 | ]; 7 | -------------------------------------------------------------------------------- /src/Facades/InviteCode.php: -------------------------------------------------------------------------------- 1 | app->singleton('InviteCode', function ($app) { 17 | return new InviteCode($app['config']); 18 | }); 19 | } 20 | 21 | /** 22 | * Bootstrap services. 23 | * 24 | * @return void 25 | */ 26 | public function boot() 27 | { 28 | $this->publishes([ 29 | __DIR__.'/../config/config.php' => config_path('invitecode.php'), 30 | ],'invitecode'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/InviteCode.php: -------------------------------------------------------------------------------- 1 | config = $config->get('invitecode'); 16 | $salt = $this->config['salt']; 17 | if(empty($salt)){ 18 | $salt = env('APP_KEY'); 19 | } 20 | $this->hashIds = new Hashids($salt,$this->config['length'],$this->config['char']); 21 | } 22 | /** 23 | * 生成邀请码 24 | */ 25 | public function enCode($id){ 26 | return $this->hashIds->encode($id); 27 | } 28 | /** 29 | * 根据邀请码获取用户ID 30 | */ 31 | public function deCode($code){ 32 | $code = $this->hashIds->decode($code); 33 | if(is_array($code)){ 34 | return current($code); 35 | }else{ 36 | return $code; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-invitecode 2 | Invit code extend package for laravel, generates invite code using user ID and decodes user ID according to the invite code. It supports custom defined invite code with fixed length 3 | 基于Laravel的邀请码扩展包,使用用户ID生成邀请码,并可根据邀请码解码出用户ID,支持自定义邀请码固定长度。 4 | 5 | ## 安装 6 | 7 | ``` 8 | composer require hinet/laravel-invitecode 9 | ``` 10 | 11 | ## 配置 12 | 13 | ``` 14 | php artisan vendor:publish --tag=invitecode 15 | ``` 16 | 17 | 修改config/invitecode.php自定义你的配置 18 | ```php 19 | 20 | return [ 21 | 'length' => 6, //邀请码长度 22 | 'char' => '',//生成邀请码的字符,默认a-zA-Z0-9 23 | 'salt' => '',//加密盐,默认使用APP_KEY 24 | ]; 25 | ``` 26 | 27 | ## 使用示例 28 | 29 | ```php 30 | namespace App\Http\Controllers; 31 | use App\Http\Controllers\Controller; 32 | use InviteCode; 33 | class DemoController extends Controller{ 34 | public function index(){ 35 | echo InviteCode::enCode(123); 36 | //print NDZ0kA 37 | //echo echo InviteCode::deCode('NDZ0kA'); 38 | //print 123 39 | } 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hinet/laravel-invitecode", 3 | "description": "invite code for laravel", 4 | "keywords": ["invite code", "laravel"], 5 | "require": { 6 | "php": "^7.3|^8.0", 7 | "illuminate/support": "^5.5|^6.0|^7.0|^8.0|^9.0|^10.0" 8 | }, 9 | "license": "MIT", 10 | "autoload": { 11 | "classmap": [ 12 | "src/Libs/HashGenerator.php", 13 | "src/Libs/Hashids.php" 14 | ], 15 | "psr-4": { 16 | "Hinet\\InviteCode\\": "src/" 17 | } 18 | }, 19 | "extra": { 20 | "laravel": { 21 | "providers": [ 22 | "Hinet\\InviteCode\\InviteCodeProvider" 23 | ], 24 | "aliases": { 25 | "InviteCode": "Hinet\\InviteCode\\Facades\\InviteCode" 26 | } 27 | } 28 | }, 29 | "authors": [ 30 | { 31 | "name": "hinet", 32 | "email": "63603636@qq.com" 33 | } 34 | ], 35 | "minimum-stability": "dev" 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 hinet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Libs/HashGenerator.php: -------------------------------------------------------------------------------- 1 | _max_int_value */ 28 | 29 | if (function_exists('gmp_add')) { 30 | $this->_math_functions['add'] = 'gmp_add'; 31 | $this->_math_functions['div'] = 'gmp_div'; 32 | $this->_math_functions['str'] = 'gmp_strval'; 33 | } else if (function_exists('bcadd')) { 34 | $this->_math_functions['add'] = 'bcadd'; 35 | $this->_math_functions['div'] = 'bcdiv'; 36 | $this->_math_functions['str'] = 'strval'; 37 | } 38 | 39 | $this->_lower_max_int_value = $this->_max_int_value; 40 | if ($this->_math_functions) { 41 | $this->_max_int_value = PHP_INT_MAX; 42 | } 43 | 44 | /* handle parameters */ 45 | 46 | $this->_salt = $salt; 47 | 48 | if ((int)$min_hash_length > 0) { 49 | $this->_min_hash_length = (int)$min_hash_length; 50 | } 51 | 52 | if ($alphabet) { 53 | $this->_alphabet = implode('', array_unique(str_split($alphabet))); 54 | } 55 | 56 | if (strlen($this->_alphabet) < self::MIN_ALPHABET_LENGTH) { 57 | throw new \Exception(sprintf(self::E_ALPHABET_LENGTH, self::MIN_ALPHABET_LENGTH)); 58 | } 59 | 60 | if (is_int(strpos($this->_alphabet, ' '))) { 61 | throw new \Exception(self::E_ALPHABET_SPACE); 62 | } 63 | 64 | $alphabet_array = str_split($this->_alphabet); 65 | $seps_array = str_split($this->_seps); 66 | 67 | $this->_seps = implode('', array_intersect($alphabet_array, $seps_array)); 68 | $this->_alphabet = implode('', array_diff($alphabet_array, $seps_array)); 69 | $this->_seps = $this->_consistent_shuffle($this->_seps, $this->_salt); 70 | 71 | if (!$this->_seps || (strlen($this->_alphabet) / strlen($this->_seps)) > self::SEP_DIV) { 72 | 73 | $seps_length = (int)ceil(strlen($this->_alphabet) / self::SEP_DIV); 74 | 75 | if ($seps_length == 1) { 76 | $seps_length++; 77 | } 78 | 79 | if ($seps_length > strlen($this->_seps)) { 80 | 81 | $diff = $seps_length - strlen($this->_seps); 82 | $this->_seps .= substr($this->_alphabet, 0, $diff); 83 | $this->_alphabet = substr($this->_alphabet, $diff); 84 | 85 | } else { 86 | $this->_seps = substr($this->_seps, 0, $seps_length); 87 | } 88 | 89 | } 90 | 91 | $this->_alphabet = $this->_consistent_shuffle($this->_alphabet, $this->_salt); 92 | $guard_count = (int)ceil(strlen($this->_alphabet) / self::GUARD_DIV); 93 | 94 | if (strlen($this->_alphabet) < 3) { 95 | $this->_guards = substr($this->_seps, 0, $guard_count); 96 | $this->_seps = substr($this->_seps, $guard_count); 97 | } else { 98 | $this->_guards = substr($this->_alphabet, 0, $guard_count); 99 | $this->_alphabet = substr($this->_alphabet, $guard_count); 100 | } 101 | 102 | } 103 | 104 | public function encode() { 105 | 106 | $ret = ''; 107 | $numbers = func_get_args(); 108 | 109 | if (func_num_args() == 1 && is_array(func_get_arg(0))) { 110 | $numbers = $numbers[0]; 111 | } 112 | 113 | if (!$numbers) { 114 | return $ret; 115 | } 116 | 117 | foreach ($numbers as $number) { 118 | 119 | $is_number = ctype_digit((string)$number); 120 | 121 | if (!$is_number || $number < 0 || $number > $this->_max_int_value) { 122 | return $ret; 123 | } 124 | 125 | } 126 | 127 | return $this->_encode($numbers); 128 | 129 | } 130 | 131 | public function decode($hash) { 132 | 133 | $ret = array(); 134 | 135 | if (!$hash || !is_string($hash) || !trim($hash)) { 136 | return $ret; 137 | } 138 | 139 | return $this->_decode(trim($hash), $this->_alphabet); 140 | 141 | } 142 | 143 | public function encode_hex($str) { 144 | 145 | if (!ctype_xdigit((string)$str)) { 146 | return ''; 147 | } 148 | 149 | $numbers = trim(chunk_split($str, 12, ' ')); 150 | $numbers = explode(' ', $numbers); 151 | 152 | foreach ($numbers as $i => $number) { 153 | $numbers[$i] = hexdec('1' . $number); 154 | } 155 | 156 | return call_user_func_array(array($this, 'encode'), $numbers); 157 | 158 | } 159 | 160 | public function decode_hex($hash) { 161 | 162 | $ret = ""; 163 | $numbers = $this->decode($hash); 164 | 165 | foreach ($numbers as $i => $number) { 166 | $ret .= substr(dechex($number), 1); 167 | } 168 | 169 | return $ret; 170 | 171 | } 172 | 173 | public function get_max_int_value() { 174 | return $this->_max_int_value; 175 | } 176 | 177 | private function _encode(array $numbers) { 178 | 179 | $alphabet = $this->_alphabet; 180 | $numbers_size = sizeof($numbers); 181 | $numbers_hash_int = 0; 182 | 183 | foreach ($numbers as $i => $number) { 184 | $numbers_hash_int += ($number % ($i + 100)); 185 | } 186 | 187 | $lottery = $ret = $alphabet[$numbers_hash_int % strlen($alphabet)]; 188 | foreach ($numbers as $i => $number) { 189 | 190 | $alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet))); 191 | $ret .= $last = $this->_hash($number, $alphabet); 192 | 193 | if ($i + 1 < $numbers_size) { 194 | $number %= (ord($last) + $i); 195 | $seps_index = $number % strlen($this->_seps); 196 | $ret .= $this->_seps[$seps_index]; 197 | } 198 | 199 | } 200 | 201 | if (strlen($ret) < $this->_min_hash_length) { 202 | 203 | $guard_index = ($numbers_hash_int + ord($ret[0])) % strlen($this->_guards); 204 | 205 | $guard = $this->_guards[$guard_index]; 206 | $ret = $guard . $ret; 207 | 208 | if (strlen($ret) < $this->_min_hash_length) { 209 | 210 | $guard_index = ($numbers_hash_int + ord($ret[2])) % strlen($this->_guards); 211 | $guard = $this->_guards[$guard_index]; 212 | 213 | $ret .= $guard; 214 | 215 | } 216 | 217 | } 218 | 219 | $half_length = (int)(strlen($alphabet) / 2); 220 | while (strlen($ret) < $this->_min_hash_length) { 221 | 222 | $alphabet = $this->_consistent_shuffle($alphabet, $alphabet); 223 | $ret = substr($alphabet, $half_length) . $ret . substr($alphabet, 0, $half_length); 224 | 225 | $excess = strlen($ret) - $this->_min_hash_length; 226 | if ($excess > 0) { 227 | $ret = substr($ret, $excess / 2, $this->_min_hash_length); 228 | } 229 | 230 | } 231 | 232 | return $ret; 233 | 234 | } 235 | 236 | private function _decode($hash, $alphabet) { 237 | 238 | $ret = array(); 239 | 240 | $hash_breakdown = str_replace(str_split($this->_guards), ' ', $hash); 241 | $hash_array = explode(' ', $hash_breakdown); 242 | 243 | $i = 0; 244 | if (sizeof($hash_array) == 3 || sizeof($hash_array) == 2) { 245 | $i = 1; 246 | } 247 | 248 | $hash_breakdown = $hash_array[$i]; 249 | if (isset($hash_breakdown[0])) { 250 | 251 | $lottery = $hash_breakdown[0]; 252 | $hash_breakdown = substr($hash_breakdown, 1); 253 | 254 | $hash_breakdown = str_replace(str_split($this->_seps), ' ', $hash_breakdown); 255 | $hash_array = explode(' ', $hash_breakdown); 256 | 257 | foreach ($hash_array as $sub_hash) { 258 | $alphabet = $this->_consistent_shuffle($alphabet, substr($lottery . $this->_salt . $alphabet, 0, strlen($alphabet))); 259 | $ret[] = (int)$this->_unhash($sub_hash, $alphabet); 260 | } 261 | 262 | if ($this->_encode($ret) != $hash) { 263 | $ret = array(); 264 | } 265 | 266 | } 267 | 268 | return $ret; 269 | 270 | } 271 | 272 | private function _consistent_shuffle($alphabet, $salt) { 273 | 274 | if (!strlen($salt)) { 275 | return $alphabet; 276 | } 277 | 278 | for ($i = strlen($alphabet) - 1, $v = 0, $p = 0; $i > 0; $i--, $v++) { 279 | 280 | $v %= strlen($salt); 281 | $p += $int = ord($salt[$v]); 282 | $j = ($int + $v + $p) % $i; 283 | 284 | $temp = $alphabet[$j]; 285 | $alphabet[$j] = $alphabet[$i]; 286 | $alphabet[$i] = $temp; 287 | 288 | } 289 | 290 | return $alphabet; 291 | 292 | } 293 | 294 | private function _hash($input, $alphabet) { 295 | 296 | $hash = ''; 297 | $alphabet_length = strlen($alphabet); 298 | 299 | do { 300 | 301 | $hash = $alphabet[$input % $alphabet_length] . $hash; 302 | if ($input > $this->_lower_max_int_value && $this->_math_functions) { 303 | $input = $this->_math_functions['str']($this->_math_functions['div']($input, $alphabet_length)); 304 | } else { 305 | $input = (int)($input / $alphabet_length); 306 | } 307 | 308 | } while ($input); 309 | 310 | return $hash; 311 | 312 | } 313 | 314 | private function _unhash($input, $alphabet) { 315 | 316 | $number = 0; 317 | if (strlen($input) && $alphabet) { 318 | 319 | $alphabet_length = strlen($alphabet); 320 | $input_chars = str_split($input); 321 | 322 | foreach ($input_chars as $i => $char) { 323 | 324 | $pos = strpos($alphabet, $char); 325 | if ($this->_math_functions) { 326 | $number = $this->_math_functions['str']($this->_math_functions['add']($number, $pos * pow($alphabet_length, (strlen($input) - $i - 1)))); 327 | } else { 328 | $number += $pos * pow($alphabet_length, (strlen($input) - $i - 1)); 329 | } 330 | 331 | } 332 | 333 | } 334 | 335 | return $number; 336 | 337 | } 338 | 339 | } --------------------------------------------------------------------------------