书签管理系统 - 数据库重构完整计划
一、重构背景
1.1 现有问题
原系统采用简化的数据表设计(4 张表:users, devices, bookmarks, refresh_tokens),存在以下问题:
| 问题 |
现状 |
影响 |
| 标签存储 |
书签表中的 Tags 字段使用 JSON 数组存储 |
无法高效查询、无法统计标签使用量、无法为标签设置颜色/图标 |
| 设备权限 |
书签表中的 AllowedDevices 字段使用 JSON 数组存储 |
无法建立外键约束、删除设备后残留无效数据 |
| 组织结构 |
书签只能通过标签分类,无层级结构 |
不支持文件夹嵌套、无法按目录组织书签 |
| 功能缺失 |
无收藏集合、分享、访问历史功能 |
用户体验受限 |
1.2 重构目标
将现有的 4 张表扩展为 12 张表,实现:
- 标签管理:独立 Tag 表 + 关联表,支持标签元数据(颜色、图标、排序)
- 层级结构:新增文件夹表,支持多级嵌套
- 设备权限:改用关联表,支持外键约束和级联删除
- 新增功能:收藏集合、分享链接、访问历史记录
1.3 表结构变化概览
| 类别 |
改造前 |
改造后 |
| 核心表 |
users, devices, refresh_tokens |
保持不变 |
| 书签表 |
bookmarks (含 JSON 字段) |
bookmarks (精简) |
| 标签 |
- |
tags, bookmark_tags |
| 文件夹 |
- |
folders |
| 设备权限 |
- |
bookmark_device_permissions |
| 收藏集合 |
- |
collections, collection_bookmarks |
| 分享功能 |
- |
bookmark_shares |
| 访问历史 |
- |
bookmark_visits |
二、后端重构(已完成)
2.1 实体类变更
修改的实体
| 文件 |
变更内容 |
Bookmark.cs |
移除 Tags(string[])、AllowedDevices(Guid[]);新增 FolderId;Order 从 long 改为 int;LastVisitTime 改名为 LastVisitAt;新增导航属性 |
新增的实体
| 文件 |
说明 |
Folder.cs |
文件夹实体,支持 ParentId 自引用实现多级嵌套 |
Tag.cs |
标签实体,包含 Color、Icon、Order 字段 |
BookmarkTag.cs |
书签-标签多对多关联表 |
BookmarkDevicePermission.cs |
书签-设备权限关联表 |
Collection.cs |
收藏集合实体 |
CollectionBookmark.cs |
集合-书签多对多关联表 |
BookmarkShare.cs |
分享链接实体,支持密码保护、过期时间、查看次数限制 |
BookmarkVisit.cs |
访问历史实体 |
新增的枚举
| 文件 |
说明 |
ShareType.cs |
分享类型:Bookmark(1)/Folder(2)/Collection(3) |
2.2 DTO 变更
| DTO |
变更内容 |
BookmarkDto |
新增 FolderId;Tags 从 string[] 改为 List<TagDto>;LastVisitTime 改名为 LastVisitAt;Order 从 long 改为 int |
TagDto |
新增 Id、Color、Icon、Order 字段 |
CreateBookmarkRequest |
新增 FolderId 字段 |
UpdateBookmarkRequest |
新增 FolderId、UpdateFolder 字段 |
2.3 服务层重构
| 服务 |
变更内容 |
BookmarkService |
完全重写,使用 IncludeMany 加载 BookmarkTags 和 DevicePermissions;新增 SyncBookmarkTagsAsync、SyncDevicePermissionsAsync 私有方法 |
TagService |
完全重写,使用独立 Tag 表;新增 CreateTagAsync、UpdateTagAsync、GetTagAsync 方法 |
2.4 接口变更
| 接口 |
变更内容 |
ITagService |
新增 CreateTagAsync、UpdateTagAsync、GetTagAsync 方法签名 |
IBookmarkService |
GetUserBookmarksAsync 新增 folderId 参数 |
2.5 控制器变更
| 控制器 |
变更内容 |
TagsController |
新增完整的 CRUD 操作(创建、更新、删除标签) |
BookmarksController |
GetBookmarks 新增 folderId 查询参数 |
AdminController |
修复 GetUserBookmarks 方法,使用新的实体结构 |
2.6 数据库初始化
FreeSqlSetup.cs 已更新:
- 注册所有 12 个实体类型
- 启动时自动同步表结构
- 提供
SyncAllTables() 方法手动同步
- 提供
CheckDatabaseStatusAsync() 方法检查数据库状态
三、前端重构计划(已完成)
3.1 需要修改的文件清单
3.1.1 类型定义 (src/frontend/src/types/index.ts)
| 修改项 |
当前 |
修改后 |
Bookmark.tags |
string[] |
Tag[] |
Bookmark.lastVisitTime |
lastVisitTime |
lastVisitAt |
新增 Bookmark.folderId |
- |
folderId?: string |
Tag 接口 |
{ name: string; count: number } |
{ id: string; name: string; color?: string; icon?: string; order: number; count: number } |
| 新增 |
- |
Folder 接口 |
| 新增 |
- |
Collection 接口 |
// 修改后的 Tag 接口
interface Tag {
id: string;
name: string;
color?: string;
icon?: string;
order: number;
count: number;
}
// 修改后的 Bookmark 接口
interface Bookmark {
id: string;
folderId?: string; // 新增
title: string;
url: string;
description?: string;
icon?: string;
tags: Tag[]; // 从 string[] 改为 Tag[]
visitCount: number;
lastVisitAt?: string; // 重命名
order: number;
visibility: VisibilityType;
allowedDevices?: string[];
createdAt: string;
updatedAt: string;
}
// 新增 Folder 接口
interface Folder {
id: string;
parentId?: string;
name: string;
icon?: string;
order: number;
children?: Folder[];
bookmarks?: Bookmark[];
}
3.1.2 API 层修改
src/frontend/src/api/bookmark.ts
| 方法 |
修改内容 |
getBookmarks() |
新增 folderId 参数:getBookmarks(tag?: string, folderId?: string) |
| 新增 |
getFolders() - 获取文件夹列表 |
| 新增 |
createFolder() - 创建文件夹 |
| 新增 |
updateFolder() - 更新文件夹 |
| 新增 |
deleteFolder() - 删除文件夹 |
| 新增 |
moveBookmark() - 移动书签到文件夹 |
src/frontend/src/api/tag.ts
| 方法 |
修改内容 |
| 新增 |
createTag(name, color?, icon?) - 创建标签 |
| 新增 |
updateTag(id, name?, color?, icon?) - 更新标签 |
| 修改 |
deleteTag(id) - 参数从标签名改为标签ID |
| 修改 |
mergeTags(sourceTagIds, targetTagId) - 参数从标签名改为标签ID |
3.1.3 Store 层修改
src/frontend/src/stores/bookmark.ts
| 修改项 |
说明 |
| 新增状态 |
currentFolderId: string | null - 当前文件夹 |
| 新增状态 |
folders: Folder[] - 文件夹列表 |
修改 fetchBookmarks |
支持 folderId 参数 |
| 新增方法 |
fetchFolders() - 获取文件夹列表 |
| 新增方法 |
createFolder() - 创建文件夹 |
| 新增方法 |
deleteFolder() - 删除文件夹 |
| 新增方法 |
setCurrentFolder() - 切换文件夹 |
| 修改计算属性 |
适配新的 Tag 对象结构 |
src/frontend/src/stores/tag.ts
| 修改项 |
说明 |
| 新增方法 |
createTag(name, color?, icon?) |
| 新增方法 |
updateTag(id, name?, color?, icon?) |
| 修改方法 |
deleteTag 和 mergeTags 使用 ID 而非名称 |
3.1.4 组件修改
src/frontend/src/components/bookmark/BookmarkList.vue
| 修改项 |
说明 |
| 标签显示 |
从显示字符串改为显示带颜色的标签组件 |
| 新增 |
面包屑导航(显示当前文件夹路径) |
src/frontend/src/components/bookmark/BookmarkEditor.vue
| 修改项 |
说明 |
| 标签选择 |
从字符串输入改为标签选择器(支持颜色预览) |
| 新增 |
文件夹选择器(树形结构) |
| 标签管理 |
支持创建带颜色的新标签 |
新增组件
| 组件 |
说明 |
FolderTree.vue |
文件夹树形组件,支持展开/折叠、拖拽移动 |
TagBadge.vue |
带颜色的标签徽章组件 |
TagManager.vue |
标签管理弹窗(编辑颜色、图标、排序) |
3.1.5 视图页面修改
src/frontend/src/views/HomeView.vue
| 修改项 |
说明 |
| 左侧边栏 |
新增文件夹树导航 |
| 标签列表 |
显示标签颜色 |
| 面包屑 |
显示当前文件夹路径 |
3.2 前端修改优先级
| 优先级 |
任务 |
影响范围 |
| P0 |
修改类型定义 (types/index.ts) |
全局 |
| P0 |
修改 API 层适配新接口 |
全局 |
| P0 |
修改 Store 层适配新数据结构 |
全局 |
| P1 |
修改 BookmarkList 组件显示标签 |
书签列表 |
| P1 |
修改 BookmarkEditor 组件标签选择 |
书签编辑 |
| P2 |
新增文件夹相关组件和功能 |
新功能 |
| P2 |
新增标签管理功能 |
新功能 |
四、浏览器插件重构计划
4.1 需要修改的文件清单
4.1.1 API 层 (src/extension/shared/api.js)
| 方法 |
修改内容 |
getBookmarks(tag) |
新增 folderId 参数:getBookmarks(tag, folderId) |
createBookmark(data) |
请求体新增 folderId 字段 |
| 新增 |
getFolders() - 获取文件夹列表 |
| 新增 |
createTag(name, color) - 创建标签(可选) |
// 修改后的 getBookmarks
async getBookmarks(tag = null, folderId = null) {
const params = new URLSearchParams();
if (tag) params.append('tag', tag);
if (folderId) params.append('folderId', folderId);
return this.request(`/bookmarks?${params.toString()}`);
}
// 修改后的 createBookmark
async createBookmark(data) {
return this.request('/bookmarks', {
method: 'POST',
body: JSON.stringify({
title: data.title,
url: data.url,
description: data.description,
tags: data.tags, // 标签名数组
folderId: data.folderId, // 新增
visibility: data.visibility || 0
})
});
}
4.1.2 后台脚本 (src/extension/background/background.js)
| 修改项 |
说明 |
onBookmarkCreated |
收集标签时考虑新的标签对象结构 |
| 新增 |
支持将浏览器书签文件夹映射到系统文件夹(可选) |
popup.js 修改
| 修改项 |
说明 |
| 标签输入 |
保持逗号分隔输入方式(后端会自动创建标签) |
| 新增 |
文件夹选择下拉框(可选功能) |
popup.html 修改
| 修改项 |
说明 |
| 新增 |
文件夹选择器 UI(可选功能) |
4.1.4 内容脚本 (src/extension/content/content.js)
| 修改项 |
说明 |
| 搜索结果渲染 |
适配新的 Tag 对象结构,显示标签颜色 |
// 修改后的搜索结果渲染
function renderSearchResult(bookmark) {
const tagsHtml = bookmark.tags.map(tag => {
const style = tag.color ? `background-color: ${tag.color}` : '';
return `<span class="tag" style="${style}">${tag.name}</span>`;
}).join('');
return `
<div class="search-result-item">
<img src="${bookmark.icon || 'default-icon.png'}" alt="">
<div class="info">
<div class="title">${bookmark.title}</div>
<div class="url">${bookmark.url}</div>
<div class="tags">${tagsHtml}</div>
</div>
</div>
`;
}
4.1.5 新标签页 (src/extension/newtab/index.html)
| 修改项 |
说明 |
| 书签卡片 |
适配新的 Tag 对象结构 |
| 标签显示 |
显示标签颜色 |
| 新增 |
文件夹导航(可选功能) |
4.2 插件修改优先级
| 优先级 |
任务 |
影响范围 |
| P0 |
修改 api.js 适配新接口 |
全局 |
| P0 |
修改 content.js 搜索结果渲染 |
全局搜索 |
| P0 |
修改 newtab 书签显示 |
新标签页 |
| P1 |
修改 popup 保存功能 |
快速保存 |
| P2 |
新增文件夹选择功能 |
新功能 |
五、API 接口变更说明
5.1 书签相关接口
| 接口 |
方法 |
变更 |
/api/bookmarks |
GET |
新增 folderId 查询参数 |
/api/bookmarks |
POST |
请求体新增 folderId 字段 |
/api/bookmarks/{id} |
PUT |
请求体新增 folderId、updateFolder 字段 |
5.2 标签相关接口
| 接口 |
方法 |
变更 |
/api/tags |
GET |
返回完整的 TagDto 对象(含 id, color, icon, order) |
/api/tags |
POST |
新增 - 创建标签 |
/api/tags/{id} |
GET |
新增 - 获取标签详情 |
/api/tags/{id} |
PUT |
新增 - 更新标签 |
/api/tags/{id} |
DELETE |
参数从标签名改为标签 ID |
/api/tags/merge |
POST |
请求体使用标签 ID 数组 |
5.3 响应数据结构变更
BookmarkDto 响应示例
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"folderId": "660e8400-e29b-41d4-a716-446655440001",
"title": "Example Site",
"url": "https://example.com",
"description": "An example website",
"icon": "data:image/png;base64,...",
"tags": [
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "工作",
"color": "#FF5733",
"icon": "briefcase",
"order": 1
},
{
"id": "880e8400-e29b-41d4-a716-446655440003",
"name": "技术",
"color": "#33FF57",
"icon": "code",
"order": 2
}
],
"visitCount": 42,
"lastVisitAt": "2024-12-25T10:30:00Z",
"order": 1,
"visibility": 0,
"allowedDevices": null,
"createdAt": "2024-12-01T00:00:00Z",
"updatedAt": "2024-12-25T10:30:00Z"
}
六、数据表详细设计
6.1 users 表(用户)- 保持不变
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
用户ID |
| Email |
string(200) |
Unique, Not Null |
邮箱 |
| UserName |
string(100) |
Not Null |
用户名 |
| PasswordHash |
string(500) |
Not Null |
密码哈希 |
| Avatar |
string(500) |
Nullable |
头像URL |
| Role |
int |
Default 0 |
角色 |
| Status |
int |
Default 0 |
状态 |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
| LastLoginAt |
DateTime |
Nullable |
最后登录 |
6.2 devices 表(设备)- 保持不变
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
设备ID |
| UserId |
Guid |
FK |
所属用户 |
| DeviceName |
string(200) |
Not Null |
设备名称 |
| DeviceType |
string(200) |
Not Null |
设备类型 |
| DeviceFingerprint |
string(500) |
Nullable |
设备指纹 |
| IsAdmin |
bool |
Default false |
管理员设备 |
| Status |
int |
Default 0 |
状态 |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
| LastActiveAt |
DateTime |
Not Null |
最后活跃 |
6.3 folders 表(文件夹)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
文件夹ID |
| UserId |
Guid |
FK |
所属用户 |
| ParentId |
Guid |
FK, Nullable |
父文件夹 |
| Name |
string(200) |
Not Null |
名称 |
| Icon |
string(100) |
Nullable |
图标 |
| Order |
int |
Default 0 |
排序 |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
| UpdatedAt |
DateTime |
Not Null |
更新时间 |
索引: idx_folder_user_parent (UserId, ParentId)
6.4 bookmarks 表(书签)- 重构
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
书签ID |
| UserId |
Guid |
FK |
所属用户 |
| FolderId |
Guid |
FK, Nullable |
所属文件夹 |
| Title |
string(500) |
Not Null |
标题 |
| Url |
string(2000) |
Not Null |
URL |
| Description |
string(2000) |
Nullable |
描述 |
| Icon |
string(MAX) |
Nullable |
图标 |
| Order |
int |
Default 0 |
排序 |
| Visibility |
int |
Default 0 |
可见性 |
| VisitCount |
int |
Default 0 |
访问次数 |
| LastVisitAt |
DateTime |
Nullable |
最后访问 |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
| UpdatedAt |
DateTime |
Not Null |
更新时间 |
变更: 移除 Tags、AllowedDevices;新增 FolderId
6.5 tags 表(标签)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
标签ID |
| UserId |
Guid |
FK |
所属用户 |
| Name |
string(100) |
Not Null |
名称 |
| Color |
string(20) |
Nullable |
颜色 |
| Icon |
string(100) |
Nullable |
图标 |
| Order |
int |
Default 0 |
排序 |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
索引: idx_tag_user_name (UserId, Name) - Unique
6.6 bookmark_tags 表(书签-标签关联)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
关联ID |
| BookmarkId |
Guid |
FK |
书签ID |
| TagId |
Guid |
FK |
标签ID |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
索引: idx_bookmark_tag_unique (BookmarkId, TagId) - Unique
6.7 bookmark_device_permissions 表(设备权限)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
权限ID |
| BookmarkId |
Guid |
FK |
书签ID |
| DeviceId |
Guid |
FK |
设备ID |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
索引: idx_permission_unique (BookmarkId, DeviceId) - Unique
6.8 collections 表(收藏集合)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
集合ID |
| UserId |
Guid |
FK |
所属用户 |
| Name |
string(200) |
Not Null |
名称 |
| Description |
string(500) |
Nullable |
描述 |
| Icon |
string(100) |
Nullable |
图标 |
| Color |
string(20) |
Nullable |
主题色 |
| IsPublic |
bool |
Default false |
是否公开 |
| Order |
int |
Default 0 |
排序 |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
| UpdatedAt |
DateTime |
Not Null |
更新时间 |
6.9 collection_bookmarks 表(集合-书签关联)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
关联ID |
| CollectionId |
Guid |
FK |
集合ID |
| BookmarkId |
Guid |
FK |
书签ID |
| Order |
int |
Default 0 |
排序 |
| CreatedAt |
DateTime |
Not Null |
添加时间 |
6.10 bookmark_shares 表(分享链接)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
分享ID |
| UserId |
Guid |
FK |
创建者 |
| ShareCode |
string(50) |
Unique |
分享码 |
| ShareType |
int |
Not Null |
类型 |
| TargetId |
Guid |
Not Null |
目标ID |
| Title |
string(200) |
Nullable |
标题 |
| Password |
string(100) |
Nullable |
密码 |
| ViewCount |
int |
Default 0 |
查看次数 |
| MaxViews |
int |
Nullable |
最大查看 |
| ExpiresAt |
DateTime |
Nullable |
过期时间 |
| IsActive |
bool |
Default true |
是否启用 |
| CreatedAt |
DateTime |
Not Null |
创建时间 |
6.11 bookmark_visits 表(访问历史)- 新增
| 字段 |
类型 |
约束 |
说明 |
| Id |
Guid |
PK |
记录ID |
| UserId |
Guid |
FK |
用户ID |
| BookmarkId |
Guid |
FK |
书签ID |
| DeviceId |
Guid |
FK, Nullable |
设备ID |
| VisitedAt |
DateTime |
Not Null |
访问时间 |
七、实体关系图
┌─────────────┐
│ users │
└──────┬──────┘
│
│ 1:N
├────────────────┬────────────────┬────────────────┬────────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ devices │ │ folders │ │ tags │ │ collections │ │ shares │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │ 1:N │ │
│ │ (self-ref) │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │ bookmarks │◄────────┘ │
│ └──────┬──────┘ │
│ │ │
│ │ N:M │ N:M
│ ├─────────────────────────────────┤
│ │ │
│ ▼ ▼
│ ┌─────────────┐ ┌─────────────┐
│ │bookmark_tags│ │ collection_ │
│ └─────────────┘ │ bookmarks │
│
├─────────────────────────────────────────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────┐
│ bookmark_device_ │ │ bookmark_ │
│ permissions │ │ visits │
└─────────────────────┘ └─────────────┘
八、重构进度跟踪
8.1 后端重构(已完成 ✅)
8.2 前端重构(已完成 ✅)
8.3 浏览器插件重构(已完成 ✅)
九、注意事项
- 数据迁移:本次重构不考虑旧数据迁移,直接使用新表结构
- 向后兼容:API 响应格式已变更,前端和插件必须同步更新
- 标签创建:创建书签时传入的标签名会自动创建不存在的标签
- 级联删除:删除用户时需级联删除所有关联数据
- 性能考虑:bookmark_visits 表数据量可能很大,考虑定期清理
十、版本信息
- 文档版本:v2.2
- 创建日期:2024-12-25
- 最后更新:2025-12-25
- 作者:Claude Code
- 更新说明:完成浏览器插件重构