news 2026/9/26 3:45:02

PHP网址导航系统源码拆解与部署指南:从数据表到二次开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP网址导航系统源码拆解与部署指南:从数据表到二次开发

简介:一套基于PHP8.1与MySQL5.7开发的2025网址推广导航系统源码,采用ThinkPHP8.0与Funadmin后台框架,适合有PHP基础的个人站长、中小企业与开发者快速搭建、二次开发或学习整合技术。系统内置网站设置、分类管理、友情链接、广告管理、主题切换等模块,后台一键统计访问人数,便于掌握站点运营情况;同时预置多套热门模板,可灵活适配不同推广场景。资源包为zip格式,约43MB,共2000个文件,其中包含454个PHP核心逻辑、697个JS脚本以及CSS、HTML、图片等静态资源,目录划分清晰,便于按功能部署和修改;PHP与JS文件分别承担后端业务逻辑与前端交互,结构完整利于二次开发。已有312人学习,源码附带的说明与环境配置示例可降低上手门槛。对想低成本搭建网址导航与聚合推广入口的读者来说,这套源码可直接部署使用,也可作为理解ThinkPHP8与Funadmin整合开发的实战参考。

1. 这套PHP网址导航系统到底是什么:一个自带后台的推广落地页工具

做网址推广的人应该都有同感:手里一堆链接,要么扔在浏览器收藏夹里自己看,要么做成静态HTML发给客户,改一次要动一次源码,数据还统计不了。这套2025版PHP全新UI网址推广导航系统,解决的正是这个问题——它是一个自带后台管理的网址导航站,管理员可以在线增删改分类和链接,访客进来看到的是一个干净、带搜索、按分类展示的导航页面。适合三类人:接网站定制的外包开发者(拿源码改改就能交付)、做推广落地页的运营人员(需要快速搭一个能展示大量链接的页面)、以及想学习PHP后台管理系统的初学者(完整的前后端代码,带数据库,能顺着流程捋清一个增删改查项目是怎么组织起来的)。

2. 拆开源码看结构:数据表、路由与导航展示的核心链路

拿到源码包,先别急着上传服务器。我习惯先把目录结构和数据库设计过一遍,知道每个文件在干什么,后面改起来才不慌。

2.1 目录结构与入口文件

解压之后,典型的PHP项目布局是这样的:

/wwwroot ├── index.php // 前台入口,负责加载路由和渲染模板 ├── admin.php // 后台入口,独立的管理端入口 ├── config │ └── config.php // 数据库连接、站点配置 ├── includes │ ├── db.php // PDO数据库连接封装 │ ├── functions.php // 公共函数:过滤、分页、格式化 │ └── auth.php // 后台登录验证 ├── templates │ ├── default │ │ ├── index.html // 前台首页模板 │ │ └── css/ js/ // 静态资源 ├── admin │ ├── category.php // 分类管理 │ ├── link.php // 链接管理 │ └── setting.php // 站点设置 └── data └── install.sql // 数据库初始化脚本

入口文件拆成前台和后台两个,是这类系统的常见做法。前台index.php只做一件事:接收参数,调用数据查询函数,把结果丢给模板渲染。后台admin.php则需要先走auth.php的登录校验,没登录的直接跳转到登录页。这个分离设计的好处是,前台即使被攻击也不会暴露后台路径,后台逻辑和前台展示完全隔离。

2.2 数据表设计与URL入库流程

打开install.sql,核心就两张表:分类表和链接表。链接表是主力,字段设计直接决定功能上限。

CREATE TABLE `nav_link` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL DEFAULT 0, `title` varchar(200) NOT NULL DEFAULT '', `url` varchar(500) NOT NULL DEFAULT '', `description` varchar(500) NOT NULL DEFAULT '', `sort_order` int(11) NOT NULL DEFAULT 0, `status` tinyint(1) NOT NULL DEFAULT 1, `create_time` int(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意几个关键点:url字段给了 500 长度,因为推广链接经常带很长的一串追踪参数,255 不够用;sort_order是排序权重,数字越大越靠前;status是上下架开关,值为 0 时前台不展示,这个字段在做临时推广活动时非常实用,不用删数据,直接下架就行。索引建在category_id和status上,因为前台首页的典型查询是WHERE category_id = ? AND status = 1。

PHP端的入库逻辑走预处理,防止SQL注入,这段代码在admin/link.php里:

<?php // 新增或编辑链接的入库逻辑 $title = trim($_POST['title'] ?? ''); $url = trim($_POST['url'] ?? ''); $category_id = intval($_POST['category_id'] ?? 0); $sort_order = intval($_POST['sort_order'] ?? 0); if ($title === '' || $url === '') { die('标题和URL不能为空'); } // 简单校验URL格式,防止写入非法地址 if (!preg_match('#^https?://#i', $url)) { die('URL必须以 http:// 或 https:// 开头'); } $pdo = get_db_connection(); // 来自 includes/db.php $stmt = $pdo->prepare( 'INSERT INTO nav_link (category_id, title, url, description, sort_order, status, create_time) VALUES (?, ?, ?, ?, ?, 1, ?)' ); $stmt->execute([ $category_id, $title, $url, $_POST['description'] ?? '', $sort_order, time() ]); header('Location: link.php?msg=added');

这段代码的核心是preg_match对URL的前缀校验。很多导航系统翻车就翻在URL格式过滤太松——用户往后台填了个javascript:alert(1),前端点击链接直接执行脚本,这就是存储型XSS。加了#^https?://#i之后,只有http和https开头的字符串能入库,把一大半安全问题挡在门外。intval()对category_id和sort_order做了强转,就算传了字符串也不会进库。

2.3 前台UI渲染与搜索实现

前台首页的渲染逻辑在index.php,它干的事可以概括为:取全部分类,再取每个分类下的前 N 条启用链接,拼成树形结构丢给模板。

<?php $pdo = get_db_connection(); // 取全部分类,按排序字段排列 $categories = $pdo->query( 'SELECT id, name FROM nav_category ORDER BY sort_order ASC, id ASC' )->fetchAll(PDO::FETCH_ASSOC); // 逐个分类查链接,限制取前50条 foreach ($categories as &$cat) { $stmt = $pdo->prepare( 'SELECT id, title, url, description FROM nav_link WHERE category_id = ? AND status = 1 ORDER BY sort_order DESC, id DESC LIMIT 50' ); $stmt->execute([$cat['id']]); $cat['links'] = $stmt->fetchAll(PDO::FETCH_ASSOC); } unset($cat); // 把数据交给模板 $template = __DIR__ . '/templates/default/index.html'; $content = file_get_contents($template); foreach ($categories as $cat) { // 用 str_replace 做最轻量的模板变量替换 } echo $content;

这段代码有两个值得注意的地方。第一个是foreach ($categories as &$cat)用了引用传值,这样才能把查到的链接塞回分类数组里,循环结束后必须unset($cat)断开引用,否则后续对$categories的操作会意外改到最后一个元素。第二个是模板渲染用了最原始的file_get_contents加str_replace,没有引入模板引擎。好处是零依赖,丢到任何PHP 7.4+ 环境都能跑;代价是模板里不能写复杂的循环逻辑,渲染嵌套列表时需要在PHP端先拼好HTML字符串。

搜索功能是这套UI的亮点之一,前台顶部放一个搜索框,提交后走search.php:

<?php $keyword = trim($_GET['kw'] ?? ''); if ($keyword === '') { header('Location: index.php'); exit; } $pdo = get_db_connection(); $stmt = $pdo->prepare( 'SELECT id, title, url, description FROM nav_link WHERE status = 1 AND (title LIKE ? OR description LIKE ?) ORDER BY id DESC LIMIT 100' ); $like = '%' . $keyword . '%'; $stmt->execute([$like, $like]); $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

LIKE '%keyword%'的写法在数据量大时全表扫描会变慢,但导航站通常只有几百到几千条数据,这个量级完全没压力。这里有个小细节:LIMIT 100是必须的,不然有人拿搜索接口刷全站数据,把数据库拖垮。如果你要接搜索建议或热词统计,这个文件就是改造的起点。

3. 从下载到上线:环境检测、伪静态配置与后台初始化

源码到手后的第一件事不是改代码,而是把环境跑通。PHP项目大部分部署问题出在版本不兼容、伪静态没开、扩展缺失这三件事上。

3.1 运行环境要求与检测脚本

这套系统要求 PHP 7.4 以上、MySQL 5.7 以上,需要开启的扩展是pdo_mysql和mbstring。PHP 7.4 是个分界点,低于这个版本很多语法跑不起来,比如??空合并运算符是 PHP 7.0 引入的,str_contains这类函数要 PHP 8.0 才有,源码里杂糅了新旧写法,所以别拿 PHP 5.x 试。

上传到服务器后,我先放一个探针文件check.php,确认环境再正式安装:

<?php // 环境检测:版本、扩展、关键函数 echo 'PHP版本: ', PHP_VERSION, PHP_EOL; $required_extensions = ['pdo_mysql', 'mbstring']; foreach ($required_extensions as $ext) { echo $ext, ': ', extension_loaded($ext) ? 'OK' : 'MISSING', PHP_EOL; } // 检查关键函数是否被禁用 $disabled = explode(',', ini_get('disable_functions')); foreach (['file_get_contents', 'json_encode'] as $fn) { echo $fn, ': ', in_array($fn, $disabled) ? 'DISABLED' : 'OK', PHP_EOL; }

跑完这个探针,一眼就能看出环境能不能装。pdo_mysql缺失是最常见的,换PHP版本或装扩展包就好;disable_functions里有file_get_contents会导致前台首页白屏,因为模板渲染靠的就是它。检查完记得删掉check.php,部署环境里留探针文件等于给攻击者递地图。

3.2 Nginx与Apache伪静态配置

这套系统默认带URL重写,把index.php?action=category&id=1之类的地址伪装成/category/1.html这样的静态形式。原因很实际:一是URL更短,推广时好看;二是方便SEO,搜索引擎对静态化地址的收录评分更好。

Nginx 配置写在站点配置文件的server块里:

location / { index index.php; try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }

Apache 走.htaccess:

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [QSA,L]

区别在QSA参数——Apache 模式下,重写时保留原有查询字符串是必须的,比如/category/1.html?page=2里的page=2不能被吃掉。Nginx 的try_files天然保留了查询串,不需要额外处理。配置完记得重启服务,Apache 还要确认AllowOverride All是开的,否则.htaccess直接不生效。

3.3 后台初始化与第一轮配置

导入数据库、改完config/config.php里的数据库账号密码后,访问/admin.php,默认账号密码通常在README或install.sql的注释里,形如admin / admin123。

登录进去后,我建议按这个顺序完成初始化,不要跳步:

步骤操作位置具体设置备注
1站点设置站点标题、关键词、描述写具体词,别留默认值
2分类管理建 5~8 个分类覆盖主流访问场景
3链接管理逐个录入,填标题、URL、描述描述控制在30字内
4模板设置选择默认模板有多个模板时才需要切

默认账号admin / admin123是最大的安全隐患。登录进去第一件事就是改密码。生产环境里我习惯顺便把admin.php重命名成一个不常见的文件名,比如manage-8f3a.php,再从后台设置里同步更新入口路径,等于给后台加了一道非标准路径的隐性门槛。这不算安全上的银弹,但能挡掉大部分扫描器乱撞。

4. 改UI与加模块:模板分层、路由注册与统计埋点

这套系统能拿来直接用的前提是UI风格符合需求。如果客户要求换个风格,或者加一个“热门推荐”模块,就需要动模板和路由了。

4.1 模板结构与UI替换

模板目录在templates/default/,里面是纯HTML加CSS,PHP变量以{xxx}的形式占位。换UI的核心思路是:保留占位符结构,替换CSS和HTML骨架。比如现在前端UI框架流行用 Element UI 这类组件库的视觉语言,你可以把模板里的CSS文件整体替换成自己的,占位符不动就成。

<!-- templates/default/index.html 中的分类区块 --> <div class="nav-grid"> {CATEGORY_LIST} </div>

{CATEGORY_LIST}这个占位符在index.php里被替换成一段拼好的HTML字符串。你要改布局,比如从两列改成三列,只需要去index.php找到对应拼串处,把<div class="col-2">改成<div class="col-3">,模板这边不用动。

这种“PHP拼字符串、模板放占位符”的做法,优点是简单粗暴,改哪里一目了然,对不熟悉模板引擎的新手非常友好;缺点是逻辑和HTML混在一起,数据量一大时index.php会变得臃肿。改UI的时候,PHP端拼接的HTML控制着整体结构,模板里的静态部分控制着样式,两边配合着改,一次改完刷新看效果。

4.2 新增“热门推荐”模块

客户常提的需求是首页顶部加一个独立的“热门推荐”区域,展示指定链接,和分类列表分开。实现路径是:数据库加字段标记热门 → 查询时加条件 → 前端拼HTML。

<?php // 热门推荐查询,加在 index.php 的顶部区域 $stmt = $pdo->prepare( 'SELECT id, title, url, description FROM nav_link WHERE status = 1 AND is_hot = 1 ORDER BY sort_order DESC LIMIT 10' ); $stmt->execute(); $hot_links = $stmt->fetchAll(PDO::FETCH_ASSOC); // 拼HTML,输出到模板顶部占位符 {HOT_LINKS} $hot_html = ''; foreach ($hot_links as $link) { $hot_html .= '<a class="hot-item" href="' . htmlspecialchars($link['url'], ENT_QUOTES) . '" target="_blank">' . htmlspecialchars($link['title'], ENT_QUOTES) . '</a>'; }

这里需要先执行一条 ALTER 语句,给nav_link表加一个is_hot tinyint(1) DEFAULT 0字段,后台管理界面加一个“设为热门”的复选框,这一步操作在admin/link.php的表单里补一个 input 即可。注意拼接HTML时用htmlspecialchars($url, ENT_QUOTES)做转义——URL里可能带&参数,不转义的话HTML结构会被破坏,链接点开就404了。

4.3 数据统计埋点与跨域JSONP

推广场景一定会问:这些链接每天有多少点击?从哪个渠道来的?标准做法是给每个外链加一个跳转统计接口,先记日志再重定向。新增redirect.php:

<?php $id = intval($_GET['id'] ?? 0); // 记录点击日志:IP、来源、时间 $log_stmt = $pdo->prepare( 'INSERT INTO nav_click_log (link_id, ip, referer, create_time) VALUES (?, ?, ?, ?)' ); $log_stmt->execute([ $id, $_SERVER['REMOTE_ADDR'] ?? '', $_SERVER['HTTP_REFERER'] ?? '', time() ]); // 查原地址并跳转 $link_stmt = $pdo->prepare('SELECT url FROM nav_link WHERE id = ? AND status = 1'); $link_stmt->execute([$id]); $link = $link_stmt->fetch(); if ($link) { header('Location: ' . $link['url'], true, 302); exit; } header('Location: index.php');

这样链接库里存的地址就不是目标站点了,而是/redirect.php?id=xxx。点击量全在nav_click_log表里,后台加个报表页就能看排行。

这套PHP系统做跨域数据接口时,最常见的坑是前后端分离场景下的跨域问题。比如你的运营大屏放在另一个域名,需要读取点击数据。后台加一个JSONP接口最省事:

<?php // api/stats.php 输出跨域可读的统计数据 $callback = $_GET['callback'] ?? ''; $stats = $pdo->query( 'SELECT COUNT(*) AS total, SUM(1) AS today FROM nav_click_log' )->fetch(); header('Content-Type: application/javascript; charset=utf-8'); echo $callback . '(' . json_encode($stats, JSON_UNESCAPED_UNICODE) . ')';

JSONP的原理是通过<script>标签加载外部脚本,绕开浏览器的同源策略。前端拿到的是一段形如callback({...})的JavaScript代码,直接执行回调函数。但要注意,$callback必须做白名单校验,否则反射型XSS直接打穿——只允许字母数字和下划线:if (!preg_match('/^[a-zA-Z0-9_]+$/', $callback)) { die('invalid callback'); }。除了JSONP,现代项目更推荐用CORS头,在接口里加Access-Control-Allow-Origin就能放开跨域限制,比JSONP干净。

5. 部署与二次开发避坑记录:五个常见问题的现象、原因与解决

这套系统我前后部署过不同版本,也在本地复现过经典故障。下面五条是踩过最多遍的坑,每一条都按“现象→原因→解决”给你写清楚。

5.1 后台登录后跳转回登录页

现象:输入正确的账号密码,点击登录,页面一闪又回到登录页,没有任何报错。

原因:九成是session目录不可写。auth.php登录成功后写入session,如果session.save_path指定的目录没有写权限,session写入失败,$_SESSION['admin']永远是空,登录态存不住。另外还有$_SESSION['admin'] = true;这类赋值语句如果出现在session_start之前,也会导致类似问题。

解决:在config/config.php里显式指定session目录,或者给PHP的session目录加写权限。

<?php // 在入口文件顶部设置session路径,确保可写 session_save_path('/tmp/php_session'); ini_set('session.use_only_cookies', 1); session_start();

session_save_path指向一个真实存在的目录,并确保PHP进程(www-data)对该目录有写权限。用/tmp/php_session再手动mkdir -m 777一次,是性价比最高的处理方式。

5.2 fastadmin后台admin模块提示“请先登录”但明明已经登录

现象:登录成功后在后台列表页能正常看到数据,但切到某个子模块就提示“请先登录”,跳回登录页。

原因:这类系统里,后台每个子页面的权限判断走auth.php里的同一个session变量。常见情况是后台入口文件和子模块文件所在目录不同,PHP session的cookie路径没配对,导致子页面读不到session。另一种更隐蔽的情况是站点启用了HTTPS,而后台地址还通过HTTP访问,session cookie被标记为secure,HTTP请求带不上。

解决:统一后台访问协议。在配置里强制所有后台请求走HTTPS,配置Nginx跳转:

server { listen 80; server_name nav.example.com; return 301 https://$host$request_uri; }

同时检查admin.php里有没有写session_set_cookie_params(0, '/'),确保cookie路径是全局/,不能只覆盖admin目录。

5.3 分类名和链接标题出现乱码

现象:后台录入中文标题,前台显示一堆æ ¾ˆ之类的乱码。

原因:三个环节任一环节字符集不一致都会出问题——数据库表不是utf8mb4、PHP连接未设置utf8mb4、HTML页面meta声明不是utf-8。常见的是install.sql导入时用了默认的latin1,或者PHP的PDO连接串没加charset=utf8mb4。

解决:先改数据库表和字段的字符集,再改PDO连接串。

mysql -u root -p -e "ALTER DATABASE nav_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

PHP连接串统一加上charset=utf8mb4:

<?php $dsn = 'mysql:host=localhost;dbname=nav_db;charset=utf8mb4'; $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);

注意utf8mb4和utf8的区别:utf8在MySQL里最多存3字节,像Emoji和某些生僻字直接报错,所以统一用utf8mb4。改完字符集再重录数据,别指望转码能救回已经乱的数据。

5.4 模板改了没反应,前台还是老样子

现象:改了templates/default/index.html,刷新页面,前台UI没有任何变化。

原因:可能你改错了文件,注意看index.php里写的是templates/default/index.html还是templates/default/下的另一个文件;也可能是缓存。这套系统本身没有模板缓存,但很多PHP环境开启了OPcache,opcache.revalidate_freq默认可能设为60秒甚至更大,改完文件要等一阵子才生效。

解决:开发环境把OPcache临时关掉,改完文件立刻生效。

opcache.enable=0

opcache.revalidate_freq=2也可以,意思是每2秒检查一次文件修改时间,兼顾性能和开发体验。如果是宝塔这类面板环境,面板自带的PHP缓存配置里直接切换就行。

5.5 链接入库后URL被截断或点击404

现象:后台录入的推广链接带一长串utm_source=...&utm_campaign=...,保存后链接后半截没了,或者点开报404。

原因:常见的三连坑——数据库字段长度不够(表单输入框没限制但数据库url只有255,超出的部分被MySQL自动截断);$_POST['url']里含特殊字符如&,在拼接SQL时被转义或过滤掉一部分;伪静态规则把带参数的URL当作路由处理,直接404。

解决:第一步加长字段(改为VARCHAR(1000)),第二步确保入库用htmlspecialchars配合stripslashes(如果开了magic_quotes需要stripslashes)。点404要看伪静态规则是否把?后面的参数弄丢了,用上面给的Nginxtry_files就不会丢。这条属于典型的“三个小问题叠加成大故障”,排查时按库、按代码、按Web服务器三个层面依次排除。

6. 上线前的四项收尾检查:安全加固、SEO、性能与数据验证

系统部署完、模块改完了,别急着交付。我每次收尾都强制走一遍下面的检查清单,这是从那台“上线三天被注入”的服务器上学乖的。

第一项:安全收尾。删掉上传的探针文件,确认config.php是644权限,data/install.sql移到站点目录外,检查PHP代码里的$_GET、$_POST是否都有过滤。重点搜索代码里有没有include $_GET['file']这类变量包含,这种写法等于把源码直接交给攻击者,配合php://filter伪协议就能读出任意文件内容,属于高危漏洞,必须全部改成白名单路由。

第二项:SEO基础。导航站的SEO关键是每个分类页和详情页要有独立的标题和描述。如果你忘了给分类页做title区分,搜索引擎会把所有页面都当成首页缓存,权重全废。检查分类页的title是否拼入了分类名,形如“分类名 - 站点名”:

<?php $page_title = $category_name . ' - ' . $site_name;

同时确认页面里有meta name="keywords"和meta name="description",描述从分类描述字段里取,没有则取前60个字符截断。

第三项:性能压测。用Apache自带的ab工具跑一轮,看首页响应时间:

ab -n 1000 -c 50 http://your-domain.com/

重点看Failed requests是不是0、Requests per second在多少。如果吞吐低于 50 req/s,优先检查数据库有没有走索引,nav_link表的category_id和status联合查询场景多,确认索引存在。再不够就上OPcache,PHP这类项目性能大头在重复解析文件,开了OPcache能提不少。

第四项:数据验证。拿一个真实链接走全流程:后台录入 → 前台展示 → 点击跳转 → 统计日志落库 → 后台报表展示。六步全绿才算是验收通过。

从那台被SQL注入的服务器之后,我每次项目交付前都会强制走一遍这四项检查,特别是安全那一项,省得半夜接到客户电话说网站被人挂了木马。做技术的,最值钱的就是把坑提前踩掉。希望这份拆解能帮你少走几步弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 3:44:22

当 AI 开始“摆烂”:GitHub 7.5k 星项目用职场 PUA“鞭策”AI 编程 Agent,TaoToken 统一 Key 接入 Claude Code 与 Cursor 的配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 3:44:16

二叉树遍历还原详解:后序+中序推先序,先序+中序推后序

先根遍历、中根遍历、后根遍历&#xff0c;这三个词凑在一起&#xff0c;任何一个学过数据结构的人都眼熟。可真正让大批人卡住的&#xff0c;不是三种遍历本身怎么写&#xff0c;而是它的逆过程&#xff1a;已知一棵二叉树的后根遍历序列和中根遍历序列&#xff0c;怎么推出先…

作者头像 李华