开源导航站系统实战:用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部署,开箱即用。做技术知识管理或者内部工具导航站的同学可以直接拿去用,省去从零搭建的时间。

个人知识管理工具折腾过的都懂,一个好导航站能省大量翻收藏夹的时间。觉得有用点个赞收藏,后续分享更多工具链搭建实战。

posted @ 2026-09-25 15:59  虎王科技  阅读(3)  评论(0)    收藏  举报