网站收藏功能数据库设计的核心,是把“谁收藏了哪个网站、网站有哪些信息、收藏属于哪个分类、是否添加标签以及何时操作”保存清楚。一个实用的收藏网站不应只建立一张用户表和一张网址表,而应区分用户、收藏记录、分类目录、标签和操作状态。这样既能支持基础收藏,也便于后续扩展搜索、排序、取消收藏、公开分享等功能。
先明确收藏功能中的数据关系
在业务上,用户与网站通常是多对多关系:一个用户可以收藏多个网站,一个网站也可能被多个用户收藏。因此,不能简单地在网站表中增加一个 user_id,也不宜直接把多个用户编号保存到一个字段里。
更合理的做法是将“网站信息”和“用户收藏行为”拆开。网站信息用于保存标题、网址、描述等相对稳定的内容;收藏记录用于表示某个用户收藏了该网站,并保存收藏时间、备注、分类和状态。
- 用户表:保存用户账号、身份和基础状态。
- 网站表:保存网站名称、标准网址、描述等目标信息。
- 收藏表:连接用户与网站,是收藏功能的核心业务表。
- 分类表:保存“开发工具”“设计资源”“学习资料”等目录。
- 标签表:用于更灵活地描述收藏内容,例如 PHP、前端、文档。
核心表如何设计
如果系统只服务于单个用户,可以将收藏记录直接作为主要业务表;如果存在注册、登录和多用户收藏,则建议采用用户表、网站表和收藏表三层结构。下面的字段是常见设计,实际项目可以根据业务范围取舍。
| 数据表 | 关键字段 | 主要作用 |
|---|---|---|
| users | id、username、status、created_at | 保存用户及账号状态 |
| websites | id、title、url、normalized_url、description | 保存网站的基础信息 |
| user_bookmarks | id、user_id、website_id、note、is_starred、created_at | 记录用户是否收藏及收藏属性 |
| bookmark_folders | id、user_id、name、parent_id、sort_order | 保存用户的分类目录 |
| tags | id、user_id、name | 保存用户可重复使用的标签 |
| bookmark_tags | bookmark_id、tag_id | 建立收藏与标签的多对多关系 |
用户表与网站表
用户表的主键一般使用自增整数或具有唯一性的字符串标识。用户名、邮箱等登录字段需要设置唯一约束,账号状态可以使用正常、禁用等业务值。
网站表中的 url 保存原始地址,normalized_url 保存经过统一处理后的地址。两者同时保留有助于展示用户输入的原始网址,也便于去重。标准化时可以统一协议、域名大小写和末尾斜杠规则,但是否去除查询参数需要结合业务判断,因为部分查询参数可能代表不同页面或推广来源。
网站标题可以由用户填写,也可以在提交后由系统尝试获取;在无法获取页面信息时,标题仍应允许为空或使用用户自定义内容。图标、封面、页面摘要属于可选信息,不宜作为收藏记录能否创建的必要条件。
收藏表是功能的中心
user_bookmarks 表应至少包含 user_id、website_id 和 created_at。用户点击“收藏”时创建一条记录,点击“取消收藏”时可以物理删除,也可以使用 deleted_at 进行软删除。选择软删除时,重复收藏前要明确处理规则:是恢复原记录,还是创建新的收藏记录,不能让唯一约束与业务逻辑相互冲突。
常用的扩展字段包括 note、is_starred、is_archived、last_viewed_at 和 updated_at。note 用于保存用户对网站的个人备注;is_starred 可以支持“特别关注”或“置顶”状态;last_viewed_at 可用于“最近访问”排序。若系统只需要普通收藏,不要为尚未使用的状态设计过多字段。
在同一用户不能重复收藏同一网站的前提下,可对 user_id 与 website_id 建立联合唯一约束。若业务允许同一个网址出现在多个目录中,不要在收藏表中直接只保存一个 folder_id,而应增加 bookmark_folder_relations 关系表。这样一条收藏可以同时放入多个分类,分类移动和批量整理也更灵活。
分类和标签应该如何区分
分类通常表示稳定的层级目录,例如“工作资料”下面有“后端开发”和“数据库”;标签则表示横向特征,例如一个网站同时具有“免费”“API”“教程”等属性。两者承担的作用不同,不建议用一张表混合实现。
bookmark_folders 可以通过 parent_id 实现树状目录。顶级分类的 parent_id 为空,子分类保存父分类编号。创建、修改分类时,应检查分类是否属于当前用户,并避免把分类移动到自身或自己的子分类下。sort_order 可用于保存用户自定义的目录顺序。
标签通常是用户级数据,因此 tags 表中可以设置 user_id 与 name 的联合唯一约束,避免同一用户重复创建名称相同的标签。bookmark_tags 使用两个外键组成联合主键或联合唯一索引,防止同一个标签被重复关联到同一条收藏。
网址去重与唯一约束不能忽略
收藏网站时,用户可能分别输入带有末尾斜杠、不同协议或不同大小写的地址。如果直接对原始 url 判断重复,容易出现同一网站多条收藏记录。数据库设计应先在应用层完成网址格式校验和标准化,再将结果写入 normalized_url。
去重规则要由产品需求决定。若系统只关心域名,可以按域名去重;若系统保存的是具体网页,则应按完整规范化 URL 去重。对于同一域名下的不同路径、不同锚点和不同查询参数,也不能在没有业务依据时强行合并。
推荐在收藏表上建立 user_id、created_at 的组合索引,以支持按用户查询和按收藏时间排序;在 user_id、website_id 上建立唯一约束,以支持快速判断是否已经收藏。若需要按标题或备注搜索,还可以根据数据库能力建立全文索引,而不是只依赖模糊查询。
收藏、取消收藏和查询流程
用户执行收藏操作时,系统应先验证网址格式,再处理网站信息,最后写入用户收藏关系。网站已经存在时可以复用原有网站记录;网站不存在时再创建新记录。整个过程最好放在事务中,避免网站创建成功但收藏关系写入失败。
- 确认当前用户具有正常的收藏权限。
- 校验 URL,并生成 normalized_url。
- 查找或创建网站基础信息。
- 检查当前用户是否已有有效收藏记录。
- 创建收藏记录,并按需要关联分类和标签。
- 提交事务后返回收藏状态。
取消收藏时,应按当前用户和收藏编号进行操作,不能只根据网站编号删除,否则可能误删其他用户的收藏。查询收藏列表时,同样必须带上 user_id 条件;后台管理或公开分享场景则需要另行设计权限规则。
哪些字段适合放在收藏表
判断字段归属时,可以看它描述的是“网站本身”还是“用户与网站的关系”。网站标题、网站地址和公共描述通常放在 websites 表中;个人备注、星标状态、所属分类、收藏时间和最近查看时间应放在 user_bookmarks 或关联表中。
例如,同一个网站被两个用户收藏时,两个用户可能设置不同的备注、标签和分类,因此这些内容不能放在网站表中。若网站标题允许每个用户自定义,则应额外设置 bookmark_title,覆盖网站的公共标题,而不是直接修改 websites.title。
面向扩展功能的设计建议
如果后续计划增加公开收藏夹、团队收藏或收藏分享,可以在收藏关系上增加 visibility、owner_type 等权限字段,也可以单独建立分享表,保存分享令牌、有效期限和访问范围。权限判断应围绕收藏的所有者执行,不能仅凭前端传来的收藏编号放行。
如果需要记录网站失效、页面抓取时间或标题更新状态,可以增加检查状态、checked_at 和 error_message 等字段。自动检查网址时应设置频率限制和超时处理,不要让页面访问失败直接删除用户收藏。对用户来说,失效网址仍可能是有价值的历史资料。
对于小型收藏网站,users、websites、user_bookmarks、folders 和 tags 已经能够支撑大多数基础功能;当出现多分类、团队协作、分享权限或操作审计需求时,再拆分关系表和权限表。好的网站收藏功能数据库设计并不是表越多越好,而是先保证收藏关系清晰、数据不重复、权限可判断,并为明确的扩展需求预留合理结构。





