├── .gitignore ├── screenshot.png ├── sqlite.db.exp ├── mysql.db.sql ├── License ├── README.md └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | data.db 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isecret/short/HEAD/screenshot.png -------------------------------------------------------------------------------- /sqlite.db.exp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isecret/short/HEAD/sqlite.db.exp -------------------------------------------------------------------------------- /mysql.db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `url` ( 2 | `id` int(11) NOT NULL AUTO_INCREMENT, 3 | `code` varchar(32) NOT NULL, 4 | `hash` varchar(32) NOT NULL, 5 | `url` text NOT NULL, 6 | `ip` varchar(64) NOT NULL, 7 | `user_agent` text, 8 | `visit_count` int(11) NOT NULL DEFAULT 0, 9 | `create_date` datetime DEFAULT CURRENT_TIMESTAMP, 10 | PRIMARY KEY (`id`), 11 | KEY `idx_code` (`code`), 12 | KEY `idx_hash` (`hash`) 13 | ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; 14 | 15 | CREATE TABLE `visit_history` ( 16 | `id` int(11) NOT NULL AUTO_INCREMENT, 17 | `url_id` int(11) NOT NULL, 18 | `ip` varchar(64) NOT NULL, 19 | `user_agent` text, 20 | `create_date` datetime DEFAULT CURRENT_TIMESTAMP, 21 | PRIMARY KEY (`id`), 22 | KEY `idx_url_id` (`url_id`) 23 | ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; 24 | 25 | CREATE TABLE `blacklist` ( 26 | `id` int(11) NOT NULL AUTO_INCREMENT, 27 | `domain` varchar(255) NOT NULL, 28 | `create_date` datetime DEFAULT CURRENT_TIMESTAMP, 29 | PRIMARY KEY (`id`) 30 | ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4; -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2017 iissnan 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 短链接生成 2 | 3 | 在线演示: [0x64.cn](https://0x64.cn) 4 | 5 | 又一个基于 PHP 简单实现的短链接在线生成工具,简单配置,快速搭建。 6 | 7 |  8 | 9 | ## 配置 10 | 11 | ### 安装 12 | #### 1. 下载源码,部署至服务器,环境 `PHP >= 5.6`,需安装 `PDO` 扩展。 13 | #### 2. 配置 Nginx,参考如下: 14 | ```conf 15 | server { 16 | listen 80; 17 | server_name 0x64.cn; 18 | root /www/0x64.cn; 19 | index index.php index.html index.htm; 20 | 21 | access_log /dev/null; 22 | error_log /var/log/nginx/nginx.0x64.error.log warn; 23 | 24 | # 伪静态 必须 25 | location / { 26 | try_files $uri $uri/ /index.php?$query_string; 27 | } 28 | 29 | # sqlite 数据库文件禁止访问 必须 30 | location ~ /(data\.db) { 31 | deny all; 32 | } 33 | 34 | location ~ \.php$ { 35 | fastcgi_pass unix:/dev/shm/php-cgi.sock; 36 | include fastcgi-php.conf; 37 | include fastcgi_params; 38 | } 39 | } 40 | ``` 41 | #### 3. 配置数据库,支持 MySQL 和 SQLite。 42 | 43 | ##### 3.1 MySQL 配置 44 | 45 | ###### 3.1.1 编辑 index.php 46 | 47 | ``` 48 | prepare("select * from url where hash = ?"); 40 | $stmt->execute([$hash]); 41 | $data = $stmt->fetchAll(); 42 | } else { 43 | $code = $_POST['code']; 44 | $stmt = $db->prepare("select * from url where code = ?"); 45 | $stmt->execute([$code]); 46 | $codeExist = $stmt->fetchAll(); 47 | if ($codeExist) { 48 | if (current($codeExist)['hash'] == $hash) { 49 | $data[] = ['code' => current($codeExist)['code']]; 50 | } else { 51 | json(-7, '该 Code 已占用,更换一个呗!'); 52 | } 53 | } 54 | } 55 | 56 | if ($data) { 57 | $code = current($data)['code']; 58 | } else { 59 | if (empty($code)) { 60 | $code = generate_code(); 61 | } 62 | $ip = get_client_ip(); 63 | $user_agent = $_SERVER['HTTP_USER_AGENT']; 64 | 65 | $stmt = $db->prepare("select * from url where code = ?"); 66 | $stmt->execute([$code]); 67 | $exist = $stmt->fetchColumn(); 68 | if ($exist) { 69 | json(-4, '天选之子,再来一次!'); 70 | } 71 | 72 | $stmt = $db->prepare("insert into url(code, hash, url, ip, user_agent) values (?, ?, ?, ?, ?)"); 73 | $result = $stmt->execute([$code, $hash, $url, $ip, $user_agent]); 74 | if (!$result) { 75 | json(-5, '系统繁忙,请稍后再试!'); 76 | } 77 | } 78 | 79 | $base_url = $_SERVER['HTTP_HOST'] .'/'. $code; 80 | json(0, 'OK',[ 81 | 'short' => $base_url, 82 | 'generic' => 'http://' . $base_url, 83 | 'long' => 'https://' . $base_url, 84 | ]); 85 | } else if ($_SERVER['REQUEST_METHOD'] == 'GET') { 86 | header("Content-Type: text/html"); 87 | if ($_SERVER['REQUEST_URI'] != '/') { 88 | $code = substr($_SERVER['REQUEST_URI'], 1); 89 | $db = db(); 90 | $stmt = $db->prepare("select * from url where code = ?"); 91 | $stmt->execute([$code]); 92 | $data = $stmt->fetchAll(); 93 | if ($data) { 94 | $url = current($data)['url']; 95 | $domain = parse_url($url)['host']; 96 | $url_id = current($data)['id']; 97 | $ip = get_client_ip(); 98 | $user_agent = $_SERVER['HTTP_USER_AGENT']; 99 | $visit_count = current($data)['visit_count'] + 1; 100 | 101 | $stmt = $db->prepare("update url set visit_count=? where id = ?"); 102 | $stmt->execute([$visit_count, $url_id]); 103 | 104 | $stmt = $db->prepare("insert into visit_history(url_id, ip, user_agent) values (?, ?, ?)"); 105 | $stmt->execute([$url_id, $ip, $user_agent]); 106 | 107 | if (is_domain_blocked($domain)) { 108 | http_response_code(403); 109 | } else { 110 | header("Location: {$url}"); 111 | } 112 | } else { 113 | header("Location: /"); 114 | } 115 | exit(0); 116 | } 117 | } 118 | 119 | function json($code, $msg, $data=[]) { 120 | header("Content-Type: application/json; charset=utf-8"); 121 | echo json_encode(['code' => $code, 'msg' => $msg, 'data' => $data]); 122 | exit(0); 123 | } 124 | 125 | function db() { 126 | try { 127 | $pdo = new PDO(DB_DSN, DB_USER, DB_PASSWD); 128 | } catch (PDOException $e) { 129 | json(-1, '数据库连接失败!'.$e->getMessage()); 130 | } 131 | 132 | return $pdo; 133 | } 134 | 135 | function generate_code() { 136 | $seeds = array_merge(range('a', 'z'), range('A', 'Z'), range(0, 9)); 137 | $depository = []; 138 | for ($i = 0; $i < CODE_LENGTH-1; $i++) { 139 | $depository = array_merge($depository, $seeds); 140 | } 141 | shuffle($depository); 142 | return join('', array_slice($depository, 0, CODE_LENGTH)); 143 | } 144 | 145 | function get_client_ip() { 146 | if (!empty($_SERVER['HTTP_CLIENT_IP'])) { 147 | return $_SERVER['HTTP_CLIENT_IP']; 148 | } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { 149 | return $_SERVER['HTTP_X_FORWARDED_FOR']; 150 | } else { 151 | return $_SERVER['REMOTE_ADDR']; 152 | } 153 | } 154 | 155 | function is_domain_blocked($domain) { 156 | $db = db(); 157 | $stmt = $db->prepare("select * from blacklist"); 158 | $stmt->execute(); 159 | $blacklist = $stmt->fetchAll(); 160 | 161 | foreach ($blacklist as $blocked_domain) { 162 | if (fnmatch($blocked_domain['domain'], $domain, FNM_CASEFOLD | FNM_NOESCAPE)) { 163 | return true; 164 | } 165 | } 166 | return false; 167 | } 168 | 169 | header("Content-Type: text/html"); 170 | 171 | ?> 172 | 173 | 174 | 175 |
176 | 177 | 178 | 179 |