开源导航站系统实战:用PHP搭建技术知识管理平台
开源导航站系统实战:用PHP搭建技术知识管理平台
程序员折腾过的知识管理工具不少,Notion、Obsidian、语雀、飞书文档,各有各的好。但有一个需求始终没被很好满足:把分散在收藏夹、浏览器书签、各种文档里的技术链接,统一成一个可搜索、可分类、自带统计的导航站。本文记录用PHP从零搭建一个技术知识导航站的完整过程。
一、导航站系统的核心需求
先理清楚导航站要解决什么问题。
链接集中管理 。收藏夹堆了几百个链接,分不清哪些还有用、哪些已经失效。导航站把所有链接按分类集中管理,支持后台编辑和排序。
快速检索 。几百个链接靠翻分类找太慢,需要全文搜索,输入关键词秒级出结果。
点击统计 。哪些链接最常用、哪些从没点过,需要数据支撑。用得多的置顶,从不点的定期清理。
SEO友好 。导航站本身需要被搜索引擎收录,静态页面生成是必须的。
响应式设计 。手机上也能用,支持触摸操作,适配不同屏幕。
二、系统架构设计
2.1 技术栈
| 层 | 技术 | 说明 |
|---|---|---|
| 后端 | PHP 8.3 | API和后台管理 |
| 数据库 | MySQL 8.0 | 分类和链接存储 |
| 前端 | HTML + CSS + JS | 玻璃拟态UI |
| 部署 | Nginx + PHP-FPM | 生产环境 |
| 缓存 | Redis(可选) | 热门链接缓存 |
2.2 功能模块
导航站系统
├── 前台展示
│ ├── 分类导航(彩虹配色)
│ ├── 全文搜索
│ ├── 点击统计
│ └── 静态页面生成
├── 后台管理
│ ├── 分类管理(增删改查+排序)
│ ├── 链接管理(增删改查+批量导入)
│ ├── 统计报表
│ └── 系统设置
└── SEO优化
├── 自动生成sitemap.xml
├── meta标签管理
└── 结构化数据(JSON-LD)
三、数据库设计
-- 分类表
CREATE TABLE categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
icon VARCHAR(32) DEFAULT 'folder',
color VARCHAR(7) DEFAULT '#4A90D9',
sort_order INT DEFAULT 0,
is_visible TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 链接表
CREATE TABLE links (
id INT PRIMARY KEY AUTO_INCREMENT,
category_id INT NOT NULL,
title VARCHAR(128) NOT NULL,
url VARCHAR(512) NOT NULL,
description VARCHAR(256),
icon VARCHAR(512),
sort_order INT DEFAULT 0,
click_count INT DEFAULT 0,
is_visible TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
-- 点击日志表
CREATE TABLE click_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
link_id INT NOT NULL,
ip VARCHAR(45),
user_agent VARCHAR(256),
clicked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (link_id) REFERENCES links(id)
);
-- 索引
CREATE INDEX idx_links_category ON links(category_id);
CREATE INDEX idx_links_title ON links(title);
CREATE INDEX idx_click_logs_link ON click_logs(link_id);
CREATE INDEX idx_click_logs_time ON click_logs(clicked_at);
四、后台管理API实现
4.1 分类管理
<?php
// CategoryController.php
class CategoryController {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
// 获取分类列表
public function list() {
$rows = $this->db->query(
"SELECT c.*,
(SELECT COUNT(*) FROM links WHERE category_id = c.id AND is_visible = 1) as link_count
FROM categories c WHERE is_visible = 1 ORDER BY sort_order ASC"
)->fetchAll(PDO::FETCH_ASSOC);
return $this->json(200, $rows);
}
// 新建分类
public function create() {
$input = json_decode(file_get_contents('php://input'), true);
$this->db->execute(
"INSERT INTO categories (name, icon, color, sort_order)
VALUES (?, ?, ?, ?)",
[
$input['name'],
$input['icon'] ?? 'folder',
$input['color'] ?? '#4A90D9',
$input['sort_order'] ?? 0
]
);
$id = $this->db->lastInsertId();
return $this->json(201, ['id' => $id]);
}
// 更新分类排序
public function reorder() {
$input = json_decode(file_get_contents('php://input'), true);
$items = $input['items'] ?? [];
foreach ($items as $index => $id) {
$this->db->execute(
"UPDATE categories SET sort_order = ? WHERE id = ?",
[$index, $id]
);
}
return $this->json(200, ['status' => 'ok']);
}
}
4.2 链接管理
<?php
// LinkController.php
class LinkController {
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
// 全文搜索
public function search($keyword) {
$rows = $this->db->query(
"SELECT l.*, c.name as category_name, c.color as category_color
FROM links l
JOIN categories c ON l.category_id = c.id
WHERE l.is_visible = 1
AND (l.title LIKE ? OR l.description LIKE ? OR l.url LIKE ?)
ORDER BY l.click_count DESC LIMIT 50",
["%$keyword%", "%$keyword%", "%$keyword%"]
)->fetchAll(PDO::FETCH_ASSOC);
return $this->json(200, $rows);
}
// 记录点击
public function trackClick($linkId) {
$this->db->execute(
"UPDATE links SET click_count = click_count + 1 WHERE id = ?",
[$linkId]
);
$this->db->execute(
"INSERT INTO click_logs (link_id, ip, user_agent) VALUES (?, ?, ?)",
[$linkId, $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']]
);
return $this->json(200, ['status' => 'tracked']);
}
// 批量导入
public function import() {
$input = json_decode(file_get_contents('php://input'), true);
$links = $input['links'] ?? [];
$categoryId = $input['category_id'] ?? 0;
$count = 0;
foreach ($links as $link) {
// 检查URL是否已存在
$existing = $this->db->query(
"SELECT id FROM links WHERE url = ?",
[$link['url']]
)->fetch();
if (!$existing) {
$this->db->execute(
"INSERT INTO links (category_id, title, url, description)
VALUES (?, ?, ?, ?)",
[$categoryId, $link['title'], $link['url'], $link['description'] ?? '']
);
$count++;
}
}
return $this->json(201, ['imported' => $count]);
}
}
五、前台展示与玻璃拟态UI
前台页面采用深色玻璃拟态设计,每个分类拥有独立彩虹配色。核心CSS实现:
/* 玻璃拟态卡片 */
.glass-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 24px;
transition: all 0.3s ease;
}
.glass-card:hover {
background: rgba(255, 255, 255, 0.08);
border-color: var(--category-color);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
transform: translateY(-2px);
}
/* 分类彩虹配色 */
.category-section:nth-child(6n+1) { --category-color: #FF6B6B; }
.category-section:nth-child(6n+2) { --category-color: #4ECDC4; }
.category-section:nth-child(6n+3) { --category-color: #FFE66D; }
.category-section:nth-child(6n+4) { --category-color: #A8DADC; }
.category-section:nth-child(6n+5) { --category-color: #C77DFF; }
.category-section:nth-child(6n) { --category-color: #6BCB77; }
/* 搜索框 */
.search-bar {
width: 100%;
padding: 16px 24px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 50px;
color: #fff;
font-size: 16px;
outline: none;
}
.search-bar:focus {
border-color: #4A90D9;
box-shadow: 0 0 20px rgba(74, 144, 217, 0.3);
}
六、静态页面生成与SEO
导航站需要SEO友好,动态PHP页面每次请求都查数据库效率低。实现一个静态生成功能,把前台页面渲染成HTML文件,Nginx直接返回静态文件。
<?php
// StaticGenerator.php
class StaticGenerator {
private $db;
private $outputDir;
public function __construct() {
$this->db = Database::getInstance();
$this->outputDir = __DIR__ . '/public/static/';
}
public function generate() {
// 获取所有分类和链接
$categories = $this->db->query(
"SELECT * FROM categories WHERE is_visible = 1 ORDER BY sort_order"
)->fetchAll(PDO::FETCH_ASSOC);
$allLinks = [];
foreach ($categories as $cat) {
$links = $this->db->query(
"SELECT * FROM links WHERE category_id = ? AND is_visible = 1
ORDER BY sort_order",
[$cat['id']]
)->fetchAll(PDO::FETCH_ASSOC);
$allLinks[$cat['id']] = $links;
}
// 渲染HTML
$html = $this->renderTemplate($categories, $allLinks);
// 写入静态文件
file_put_contents($this->outputDir . 'index.html', $html);
// 生成sitemap
$this->generateSitemap($allLinks);
return ['generated' => 'index.html', 'links' => count($allLinks)];
}
private function generateSitemap($allLinks) {
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
$xml .= ' <url><loc>https://nav.example.com/</loc>';
$xml .= '<changefreq>daily</changefreq><priority>1.0</priority></url>' . "\n";
$xml .= '</urlset>';
file_put_contents($this->outputDir . 'sitemap.xml', $xml);
}
}
七、部署与配置
# Nginx配置
server {
listen 80;
server_name nav.example.com;
root /var/www/nav/public;
index index.html index.php;
# 静态文件优先
location / {
try_files /static/index.html @php;
}
# API路由
location /api/ {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP处理
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
八、现成方案参考
如果想用现成的导航站系统而不是从零开发,虎王科技开源的anime_nav_pro_plus(gitee.com/zesso/anime_nav_pro_plus)就是一个功能完善的实现。它采用深色玻璃拟态设计,每个分类有独立彩虹配色,支持后台管理、分类排序、SEO优化、搜索跳转、点击量统计和静态页面生成。PHP后端,Nginx部署,开箱即用。做技术知识管理或者内部工具导航站的同学可以直接拿去用,省去从零搭建的时间。
个人知识管理工具折腾过的都懂,一个好导航站能省大量翻收藏夹的时间。觉得有用点个赞收藏,后续分享更多工具链搭建实战。

浙公网安备 33010602011771号