论如何设计博客分类标签系统

    xiaoxiao2025-12-12  22

    本文描述了这个博客背后归档系统的设计与实现思路。

    逻辑模型设计

    通常博客所用的归档方式有三种:

    按发布时间归档分类体系标签体系 这三种归档体系是可以同时存在的,而且这样的设计也非常常见。 其中按时间归档实在是Trivial,而且归档逻辑性也相对较弱,故在此略去不提。

    通常分类与标签体系最大的区别在于,文章和分类是多对一的关系,而标签和文章是多对多的关系。 实际上在使用中,我发现分类确实不如标签好用。 在使用OneNote整理笔记的时候,经常出现一类很让人头疼的Delimma。比如,用Python做的可视化演示,究竟是应当放入Python分类中呢?还是放在可视化分类中呢? 避免这个问题,需要分类设计做到完全正交,然而真正能做到完全正交的分类体系又往往不甚实用……。更重要的一点是,每当我检索笔记的时候,往往是通过类似标签形式的关键词搜索去定位文章的……分类体系更是显得累赘了……

    不过完全不要分类体系好不好?当然也不好,当然,多级分类应该拍扁成一级分类,这样分类就可以做成一个字段放入文章表中了。

    所以在我看来,博客的归档系统,应当以标签系统为主,分类系统为辅。 分类系统必须是扁平的,不可有过多的类目,且尽量做到正交。 我的博客目前拟采用以下分类

    INSERT INTO categories (cate_name)VALUES ('未分类'), ('随笔文章'), ('生活记录'), ('工作记录'), ('文档剪藏'), ('读书笔记'), ('前端戏法'), ('后端杂技'), ('算法心得'), ('数据库'), ('部署运维'),('知识积累'), ('游戏杂谈');

    而标签就会显得比较随意了。

    数据模型设计

    传统方法

    传统上,按照数据库设计理论Blabla,应当这样设计:

    ------------- 基础表定义 --------------- 登录信息表CREATE TABLE IF NOT EXISTS login ( user_id SERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE, hashed_password TEXT NOT NULL);-- 用户信息表CREATE TABLE IF NOT EXISTS users ( user_id INTEGER PRIMARY KEY REFERENCES login (user_id), email TEXT NOT NULL UNIQUE, name TEXT NOT NULL, avatar TEXT);-- 博文分类表CREATE TABLE IF NOT EXISTS categories ( cate_id SERIAL PRIMARY KEY, cate_name TEXT NOT NULL UNIQUE);-- 博客文章表CREATE TABLE IF NOT EXISTS articles ( article_id SERIAL PRIMARY KEY, user_id INT NOT NULL REFERENCES users (user_id), title TEXT NOT NULL, content TEXT NOT NULL, description TEXT, thumb TEXT, cate_id INTEGER NOT NULL REFERENCES categories (cate_id) DEFAULT 1, private BOOLEAN DEFAULT FALSE, ctime TIMESTAMP NOT NULL DEFAULT current_timestamp, mtime TIMESTAMP NOT NULL DEFAULT current_timestamp, read_cnt INT DEFAULT 0, upvote_cnt INT DEFAULT 0);-- 标签表CREATE TABLE IF NOT EXISTS tags ( tag_id SERIAL PRIMARY KEY, tag_name TEXT NOT NULL UNIQUE);-- 标签映射表CREATE TABLE IF NOT EXISTS tag_mapping ( article_id INTEGER REFERENCES articles (article_id) ON DELETE CASCADE, tag_id INTEGER REFERENCES tags (tag_id), PRIMARY KEY (article_id, tag_id));---------------- 复合类型定义 ------------------ 文章类型,包括文章的详细信息CREATE TYPE article AS ( user_id INTEGER, cate_id INTEGER, article_id INTEGER, title TEXT, content TEXT, description TEXT, thumb TEXT, private BOOLEAN, ctime TIMESTAMP, mtime TIMESTAMP, read_cnt INTEGER, upvote_cnt INTEGER, cate_name TEXT, email TEXT, name TEXT, avatar TEXT, tags JSONB);-- 文章摘要,除了content字段外的所有信息CREATE TYPE article_entry AS ( user_id INTEGER, cate_id INTEGER, article_id INTEGER, title TEXT, description TEXT, thumb TEXT, private BOOLEAN, ctime TIMESTAMP, mtime TIMESTAMP, read_cnt INTEGER, upvote_cnt INTEGER, cate_name TEXT, email TEXT, name TEXT, avatar TEXT, tags JSONB);-- 标签信息CREATE TYPE tag AS ( tag_id INTEGER, tag_name TEXT, article_cnt INTEGER, articles INTEGER []);-- 类目信息CREATE TYPE category AS ( cate_id INTEGER, cate_name TEXT, article_cnt INTEGER, articles INTEGER []);-- 用户信息CREATE TYPE user_entry AS ( user_id INTEGER, email TEXT, hashed_password TEXT, nickname TEXT, avatar TEXT);

    这样设计的好处,当时只要对映射表轻松地聚合,就可以实现查询文章所有的标签和查询标签对应的所有文章,正查倒查效率都很高。当然坏处就是特么的查个标签最后我要Join三张表……

    JSON方法

    事实上,如果把标签作为一个Json字段放在文章表里,就可以省却很多这类破事了。 尤其是当PostgreSQL支持JSON索引的时候,直接通过where子句筛选标签字段,同样可以实现传统方法的功能,而且不需要额外的Join了。 不过后来,我发现JSON的设计还是不给力,因为如果我想统计每个标签下面究竟挂着多少文章,JSON的设计就蛋疼了。仔细想想,关系型数据库的崛起确实是因为它的确具有优越性。当然,最后定义了视图里面,还是采用了一些JSON字段的。

    相关资源:python入门教程(PDF版)
    最新回复(0)