简介一份基于PHP开发的黑色简洁短网址生成系统源码面向需要自建短链接服务的个人站长、开发学习者。前端提供了URL创建、自定义别名、密码保护、链接统计、暗色主题、小书签以及复制与共享链接等功能交互简洁后端支持网址删除、网站设置、广告位添加与编辑、访问分析及自定义CSS方便运营维护。压缩包共103个文件约681KB主体为34个PHP文件并包含SCSS、LESS、CSS样式文件JavaScript脚本以及字体、图片、SQL数据库文件和导入说明等整体结构清晰适合本地部署和二次开发。目前已有212人学习下载。借助这套源码读者能快速搭建一套功能完整的短链平台也可以深入理解短链接生成、参数校验、前端界面定制与广告管理模块的实现路径同时可作为PHP实战练手的良好范例。1. 黑色简洁的PHP短网址系统先搞清它能做什么短网址这种需求听着不起眼实际做起来全是细节。你给运营发一条带 UTM 参数的长链接被聊天工具折叠、被短信平台截断或者打印在物料上根本没法手输这时候一个能用的短链接系统比什么都实在。这份黑色主题的 PHP 短网址源码核心做三件事把长链接转成短码、提供跳转访问、带一个能添加和删除链接的后台。所谓“黑色简洁”是指它的管理界面和控制页用了深色主题视觉上干净适合直接挂到自己的站内工具下而不是那种花里胡哨的建站模板。适合谁用自己有小网站、想给推广链接做短链或者纯粹想研究“短码生成 重定向 伪静态”这套 PHP 实现的开发者。它不是那种 SaaS 平台没有用户体系、没有分布式发号器它就是一个单机可跑的完整闭环。你把它放到任意一台有 PHP MySQL 的机器上配好伪静态就能得到一套自己掌控的短链服务。接下来我从架构、核心代码到部署和改造一层层拆开讲。读过之后你不用看网上那些付费课程自己也能把一个短网址服务从零跑通。2. 架构与技术选型单入口、伪静态与手写核心的取舍这类源码包解压后常见的项目结构高度一致因为它解决的问题本身很固定。绝大多数短网址系统的骨架逃不出这几块入口文件负责路由和跳转后台文件负责管理配置文件和公共函数单独抽出来样式资源放在独立目录里。下面的目录结构是这类源码最常见的组织方式也是我拆解这份源码时的分析基准。/ ├── index.php # 入口文件接收短码请求并完成跳转 ├── admin.php # 后台管理登录、添加、删除、查看链接列表 ├── config.php # 数据库连接与站点基础配置 ├── includes/ │ ├── db.php # PDO 数据库封装 │ ├── shortener.php # 短码生成与查询核心 │ └── auth.php # 后台登录校验 ├── static/ │ ├── css/ # 黑色主题样式 │ └── js/ # 列表展示与交互脚本 └── .htaccess # Apache 伪静态规则2.1 目录结构与请求流程这个结构里最值得关注的不是文件数量而是职责边界。入口文件 index.php 承担了两个角色一个是路由负责从 URL 里解析短码另一个是控制器负责把短码换成目标链接并执行跳转。很多新手会在这一层踩坑把查询逻辑直接塞进入口里导致后期想加统计、加拦截时无从下手。这里的 includes/shortener.php 把查询封装成函数入口只做调用就是一个合格的分层习惯。请求流程用大白话讲是这样的用户访问https://yourdomain.com/abc123Web 服务器先通过伪静态规则把请求交给 index.phpPHP 拿到$_GET[code]或从REQUEST_URI截取到abc123然后查数据库拿到对应长链接后发送Location头完成 302 跳转。如果短码不存在则返回一个自定义的 404 页面或引导页。整体架构属于典型的“一个入口 一张表”模型。它没有用 MVC 框架因为短网址系统本身没有复杂业务引入框架反而会把部署门槛抬高。你要在只有 1G 内存的 VPS 上跑它PHP-FPM 加 Nginx 就够了连缓存都不用开。数据库只维护一张映射表这就是它能保持轻量的根本原因。2.2 数据库表设计URL 映射表与参数说明短网址系统的核心表设计并不复杂但字段选择上有个容易忽略的点短码列必须加唯一索引。否则并发请求下两个相同的短码可能同时被插入成功。把表和字段拆开看CREATE TABLE short_urls ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, short_code CHAR(8) NOT NULL COMMENT 短码唯一, long_url VARCHAR(2048) NOT NULL COMMENT 原始长链接, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, hits INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 点击次数, status TINYINT(1) NOT NULL DEFAULT 1 COMMENT 1正常 0停用, PRIMARY KEY (id), UNIQUE KEY uk_short_code (short_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;short_code用 CHAR(8) 是因为短码长度固定CHAR 比 VARCHAR 在等值查询时性能略好这个量级虽然感知不到差异但属于合理的建模习惯。long_url用 VARCHAR(2048) 覆盖绝大多数合法链接如果遇到更长地址可以在应用层做截断或拒绝。hits字段用于点击统计后面二次开发可以直接用。status字段不参与跳转逻辑但它方便你停用某个链接而不必删除记录。索引方面除了主键uk_short_code唯一索引是这张表的灵魂。短码是整个系统的门牌号跳转查询走的就是这个索引。这里给一句忠告不要在long_url上建索引没有任何短网址系统会按长链接反查短码建了只会浪费写性能。2.3 为什么不用框架这个体量下从零手写的理由看到这你可能会问用 Laravel 或 ThinkPHP 不是更省事吗这个问题的答案取决于你对“省事”的定义。框架解决的是路由分发、ORM、中间件等通用问题但短网址系统真正的工作是短码生成、碰撞重试、伪静态配置和 302/301 策略这些框架一个都不直接提供。你用框架能省下的无非是一个 Request 对象却要付出部署 Composer 依赖、配置框架路由的成本。更现实的问题是运行环境。这类源码的典型使用场景是一台普通的虚拟主机或低配 VPS上面可能只有一个 PHP 环境没有 SSH 权限也不一定能跑 Composer。手写核心让整个项目只依赖 PDO 扩展和 PHP 内置函数任何支持 PHP 5.6 以上的环境都能跑这才是这类源码存在的意义。我一般会跟朋友说如果你想学框架去拿框架练手如果你就想快速落地一个短链服务不用怀疑手写就是最优解。3. 核心实现拆解短码生成、跳转链路与后台管理有了整体结构现在进入到整个源码里最值得读的部分。短网址系统的技术含量其实集中在三块短码怎么生成才不碰撞、跳转时该用 301 还是 302、后台的登录态怎么维护。这三块代码写清楚了这个项目你就吃透了八成。3.1 短码生成算法从随机字符到碰撞重试短码生成有很多路数自增 ID 转 base62、随机字符、哈希截取。这份源码用随机字符法这也是最直观、最容易解释的方案。核心代码大致长这样function generateShortCode(int $length 6): string { $alphabet abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789; $maxIndex strlen($alphabet) - 1; $code ; for ($i 0; $i $length; $i) { $code . $alphabet[random_int(0, $maxIndex)]; } return $code; }这里用了random_int()而不是rand()或mt_rand()。random_int()基于操作系统的随机源生成避免可预测性。虽然短码不存在反查风险但使用安全的随机函数是一个不需要付出额外成本的好习惯。字符集刻意排除了容易混淆的字符比如 0/O、1/l这一点在短信和纸质传播场景里很实用。短码生成后必须做碰撞检查这是网上很多半成品源码最容易忽略的地方。如果只生成不检查一旦两个链接生成同一个短码后写入的会把先写入的覆盖掉问题出现时你根本不知道哪条链接会被跳转到错误的目标。function createShortUrl(PDO $pdo, string $longUrl): string { do { $shortCode generateShortCode(6); $stmt $pdo-prepare( INSERT INTO short_urls (short_code, long_url) VALUES (:code, :url) ); $stmt-execute([:code $shortCode, :url $longUrl]); } catch (PDOException $e) { // 捕获唯一索引冲突重新生成 if ($e-getCode() 23000) { continue; } throw $e; } return $shortCode; }这段代码有个精妙的地方它没有“先 SELECT 再 INSERT”而是直接 INSERT靠唯一索引兜底。如果短码撞了数据库抛一个唯一约束冲突捕获后重新生成。在并发量不大的场景下这种“乐观插入”比“先查再插”更高效还避免了并发下的竞态条件。你要改成“先生成再查重再插入”也可以但遇到两个请求同时查到一个可用短码时依然可能埋雷。直接靠数据库约束兜底是最省心的做法。3.2 跳转处理器一次请求从进入到返回的完整链路跳转逻辑是短网址系统最重要的路径每一行代码都值得细看。入口文件 router 的逻辑可以简化成下面这段// index.php require_once __DIR__ . /includes/db.php; require_once __DIR__ . /includes/shortener.php; $requestUri $_SERVER[REQUEST_URI] ?? /; $shortCode trim(parse_url($requestUri, PHP_URL_PATH), /); if ($shortCode || $shortCode index.php) { // 展示默认引导页或跳转到管理后台 header(Location: admin.php); exit; } // 验证短码格式避免将 JS 或 CSS 路径当短码查库 if (!preg_match(/^[a-zA-Z0-9]{4,10}$/, $shortCode)) { http_response_code(404); exit; }这一步的preg_match很容易被忽略但特别关键。如果不校验格式任何不存在的路径都会触发一次数据库查询等于给攻击者留了一个无成本的探针。加了格式校验后非法路径在数据库查询之前就被挡掉了。拿到短码之后查库逻辑写在shortener.php里function findLongUrl(PDO $pdo, string $shortCode): ?string { $stmt $pdo-prepare( SELECT long_url, status FROM short_urls WHERE short_code :code LIMIT 1 ); $stmt-execute([:code $shortCode]); $row $stmt-fetch(PDO::FETCH_ASSOC); if (!$row || (int)$row[status] 0) { return null; } return $row[long_url]; }拿到长链接后的跳转动作有个核心决策点用 301 还是 302。$longUrl findLongUrl($pdo, $shortCode); if ($longUrl null) { http_response_code(404); exit(链接不存在或已被停用); } // 统计点击数记录在业务日志或数据库 $pdo-prepare(UPDATE short_urls SET hits hits 1 WHERE short_code :code) -execute([:code $shortCode]); header(Location: . $longUrl, true, 302); exit;这段代码必须解释一下。302表示“临时重定向”每次访问都会重新请求短链接服务器再跳转过去。如果改成301浏览器会缓存跳转结果后续访问直接跳转到长链接不再请求短网址服务器。对短网址服务而言几乎总是应该用 302因为你需要知道这次点击发生的时间、来源、次数301 会让这些数据失真甚至导致你没法在后台停用链接因为客户端根本不回来问你了。这条经验对后续做统计改造至关重要。3.3 增删改查后台管理模块的权限思路后台管理页通常只提供一个简单的会话密码校验。这类源码不搞复杂的用户表常见做法是配置一个管理员密码登录后把标记写进$_SESSION。session_start(); $adminPassword your-secret-password; // 建议改成强密码 if ($_SERVER[REQUEST_METHOD] POST isset($_POST[password])) { if (hash_equals($adminPassword, $_POST[password])) { $_SESSION[admin_logged_in] true; } else { $error 密码错误; } } if (empty($_SESSION[admin_logged_in])) { // 输出登录表单 exit; }hash_equals()是 PHP 提供的字符串比较函数它能避免时序攻击。普通的或比较密码时比较耗时和字符串前缀匹配程度有关攻击者可以据此逐步猜解密码hash_equals()固定了比较耗时堵住了这个旁路。密码本身直接写在配置里虽然不够优雅但对这种单管理员场景来说性价比最高。你可以改成读环境变量或独立配置文件但不建议为了这事引入一个完整的 RBAC 权限体系那是过度设计。后台的链接管理列表本质上就是一次分页查询加几个表单操作。添加链接复用前面讲的createShortUrl()删除就是按短码DELETE停用则是把status置 0。整个后台的精髓在于——每一次操作都只针对一个资源不做批量操作逻辑简单意味着出错的概率低。4. 常见问题与避坑记录部署报错、伪静态翻车与数据丢失排查我在把这类短网址系统部署到不同环境时踩过不少坑。这里挑几条最有代表性的按“现象 → 原因 → 解决”的方式写下来你在实战中如果遇到类似问题可以直接对症下药。4.1 环境检查PHP 版本与扩展缺失的判断第一步永远是确认环境而不是急着丢代码上去。短码跳转的核心逻辑只依赖 PDO 扩展后台登录要确认session可用。把源码放到服务器之后先别急着访问首页先执行一个探针脚本php -m | grep -E pdo|session如果输出里没有pdo_mysql和session说明扩展没装。PHP 7 以上的环境还需要确认PDO::FETCH_ASSOC可用这个一般没问题但要重点检查的是 PHP 版本是否过旧。有些老虚拟主机还停在 PHP 5.3random_int()是 PHP 7.0 才引入的函数如果代码里用了且环境版本过低整个页面会直接白屏。处理办法是降级为mt_rand()或者在函数里做一层兼容判断。function secureRandomInt(int $min, int $max): int { if (function_exists(random_int)) { return random_int($min, $max); } return mt_rand($min, $max); }这段兼容代码建议直接内置到项目里很多生产环境管理员没有升级 PHP 的权限这层保护能让源码在 5.6 到 8.2 之间通吃。部署完成后用php -l index.php逐个检查语法能提前暴露因为 PHP 版本差异导致的语法不兼容问题。4.2 伪静态配置Nginx 与 Apache 的两种写法伪静态是短网址系统绕不过的一关。访问/abc123时Web 服务器得知道把请求转给 index.php这一步配置错了出现的典型现象就是访问短链返回 404。Apache 环境相对简单根目录放一份.htaccess就能工作RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^([a-zA-Z0-9])/?$ index.php?code$1 [L,QSA]这组规则的含义是如果请求的不是真实文件也不是真实目录就把纯字母数字路径捕获为短码参数传给 index.php。注意RewriteCond的两行不能省否则静态资源路径也会被错误路由。Nginx 环境没有.htaccess的概念要在 server 块里写location / { try_files $uri $uri/ /index.php?code$uri; }try_files的逻辑是先尝试找真实文件找不到就交给 index.php。它和 Apache 规则的效果等价但要注意$uri里会带前导斜杠PHP 端要用ltrim($uri, /)处理一下否则查库时短码会多一个斜杠导致永远匹配不到记录。这个小坑非常隐蔽是 Nginx 环境部署时最高频的翻车点。4.3 避坑记录三条部署与运维的真实教训先说跳转成功但浏览器地址栏变成乱码的问题。现象是你点击短链接后跳到目标站时 URL 参数中的中文内容变成了一堆%开头的字符或者更严重的直接跳转失败。原因是创建链接时没有对长链接做 URL 编码处理或者说数据库字段字符集不匹配。解决办法是在入库前统一执行filter_var($url, FILTER_SANITIZE_URL)并规范字符集为 UTF-8同时在跳转时用htmlspecialchars($longUrl, ENT_QUOTES, UTF-8)输出。长链接里有中文参数很常见这一条几乎是必踩的。再说后台添加链接后前台访问显示 404 的问题。我遇到过两次一次是伪静态规则写到了子目录但访问用了根域名另一次是实际代码里短码从$_GET[code]读取但 Nginx 的 try_files 没有把参数传过去。排查路径很直接先打印$_SERVER[REQUEST_URI]看 PHP 到底收到了什么再去比对伪静态规则。绝大多数 404 都是这一步能定位的别急着怀疑代码。最后一条是最高发的坑改完代码后页面白屏连错误信息都不显示。原因通常是 PHP 配置把display_errors关掉了而项目里又没有写错误日志。建议在项目入口最顶部加上一套开发环境专用的错误显示配置if (defined(DEV_MODE) DEV_MODE) { ini_set(display_errors, 1); error_reporting(E_ALL); }这样开发时能看到具体报错行生产环境把DEV_MODE置为 false 就不会暴露信息。这三种问题在短网址系统的部署过程中出现频率最高遇到时不用慌按现象找原因基本都能在五分钟内定位。最后再提醒一句和代码无关但经常翻车的点短网址的短码路由会和站内已有路径冲突。如果你的站点已经有一个admin.php或api.php文件伪静态规则又允许纯字母路径进入路由那么访问/admin时可能会被错误解析成短码。解决办法是在路由解析前先判断路径是否为已存在的真实文件或者把保留字列表明确排除掉。代码实现很简单但事前不做这个设计上线后就只能靠改规则救火颇为被动。5. 二次开发思路给短网址系统扩展统计、自定义码与 API到这里短网址系统已经能跑起来了。接下来谈改造。多数人拿到源码不会满足于“能用”而是希望它有自定义短码、点击统计或者能被其他系统调用。这三个改造方向覆盖了大部分真实需求每个都能独立落地。5.1 改造一支持自定义短码默认生成的随机短码不具备语义化比如https://yourdomain.com/x7k2p9用户看一眼根本不知道是什么。如果改成https://yourdomain.com/promo-521在推广物料上就能直接传递信息。做法是在添加表单里加一个“自定义短码”输入框提交时按规则校验$customCode trim($_POST[custom_code] ?? ); if ($customCode ! ) { if (!preg_match(/^[a-zA-Z0-9_-]{3,20}$/, $customCode)) { $error 短码仅支持字母、数字、下划线和连字符长度 3-20 位; } else { // 直接用自定义短码插入唯一索引兜底冲突 createShortUrl($pdo, $longUrl, $customCode); } }这里把createShortUrl的签名加一个可选参数传入则用它不传则随机生成。唯一索引会拦截重复短码你只需要捕获异常并提示用户换一个就行。这个改造的代码量很小但能解决运营场景里非常实际的问题。5.2 改造二点击量统计持久化前面的跳转代码已经把hits字段自增了但这只是总数统计。如果你想看每天的趋势需要在改造时加一张日志表CREATE TABLE short_url_click_logs ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, short_code VARCHAR(20) NOT NULL, ip VARCHAR(45) DEFAULT NULL, user_agent VARCHAR(255) DEFAULT NULL, referer VARCHAR(500) DEFAULT NULL, clicked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_code_time (short_code, clicked_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;跳转处理器里在执行header(Location: ...)之前插入这条记录。注意这里不需要删除旧记录短网址服务的日志天然就是只增长的。后台列表页可以加一个数字展示今日点击和总点击。这个改造能让你的短链系统具备基本的运营分析能力比单纯一个hits数字有意义得多。5.3 改造三对外提供 API 接口最后一个高频需求是让其他系统通过接口创建短链。新建一个api.php接收 JSON 请求即可$input json_decode(file_get_contents(php://input), true); if (empty($input[url]) || !filter_var($input[url], FILTER_VALIDATE_URL)) { http_response_code(422); echo json_encode([error url 参数缺失或格式不正确]); exit; } $shortCode createShortUrl($pdo, $input[url]); header(Content-Type: application/json); echo json_encode([ short_url https://yourdomain.com/ . $shortCode, code $shortCode, ]);这个接口写好后运维系统、客服工具都能通过一行curl调用来生成短链。注意返回的 JSON 里不要拼接完整域名而是返回相对路径让调用方自己拼这样换域名时不用改接口逻辑。我一直保持一个习惯每次改完跳转逻辑都先去浏览器开发者工具里确认响应头。是 302 还是 301Location头带没带对一眼就能看出来。确认逻辑没有问题后再把接口文档补上。二次开发并不可怕可怕的是改完不敢验证。希望帮到你。本文还有配套的精品资源点击获取