# Home4j 数据库设计文档 > **版本**:v1.0 > **更新日期**:2026年2月3日 > **数据库**:H2(默认)/ MySQL(可选) --- ## 一、设计原则 1. **简洁优先**:MVP 阶段保持表结构简单,避免过度设计 2. **扩展预留**:关键字段预留扩展空间(如 JSON 类型的 extra 字段) 3. **软删除**:重要数据支持软删除,便于恢复 4. **审计字段**:所有表包含创建时间、更新时间 --- ## 二、实体关系图(ER Diagram) ``` ┌─────────────────┐ ┌─────────────────┐ │ t_user │ │ t_config │ ├─────────────────┤ ├─────────────────┤ │ id (PK) │ │ id (PK) │ │ username │ │ config_key │ │ password_hash │ │ config_value │ │ role │ │ config_type │ │ ... │ │ ... │ └─────────────────┘ └─────────────────┘ ┌─────────────────┐ ┌─────────────────┐ │ t_group │ │ t_bookmark │ ├─────────────────┤ ├─────────────────┤ │ id (PK) │◄──────│ id (PK) │ │ name │ 1:N │ group_id (FK) │ │ icon │ │ name │ │ sort_order │ │ url │ │ ... │ │ icon │ └─────────────────┘ │ sort_order │ │ ... │ └─────────────────┘ ``` --- ## 三、数据表详细设计 ### 3.1 用户表(t_user) > 存储管理员账号信息,MVP 阶段仅支持单管理员 | 字段名 | 类型 | 约束 | 默认值 | 说明 | |-------|------|------|--------|------| | `id` | BIGINT | PK, AUTO_INCREMENT | - | 主键 | | `username` | VARCHAR(50) | NOT NULL, UNIQUE | - | 用户名 | | `password_hash` | VARCHAR(255) | NOT NULL | - | 密码哈希(BCrypt) | | `nickname` | VARCHAR(100) | - | NULL | 显示昵称 | | `email` | VARCHAR(100) | - | NULL | 邮箱(预留) | | `role` | VARCHAR(20) | NOT NULL | 'ADMIN' | 角色:ADMIN / USER | | `status` | TINYINT | NOT NULL | 1 | 状态:1-正常 0-禁用 | | `last_login_time` | TIMESTAMP | - | NULL | 最后登录时间 | | `last_login_ip` | VARCHAR(50) | - | NULL | 最后登录IP | | `created_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 创建时间 | | `updated_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 更新时间 | **索引**: - `uk_username` UNIQUE (`username`) **SQL**: ```sql CREATE TABLE t_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL, nickname VARCHAR(100), email VARCHAR(100), role VARCHAR(20) NOT NULL DEFAULT 'ADMIN', status TINYINT NOT NULL DEFAULT 1, last_login_time TIMESTAMP, last_login_ip VARCHAR(50), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT uk_username UNIQUE (username) ); ``` --- ### 3.2 分组表(t_group) > 书签的分类容器,支持排序和折叠 | 字段名 | 类型 | 约束 | 默认值 | 说明 | |-------|------|------|--------|------| | `id` | BIGINT | PK, AUTO_INCREMENT | - | 主键 | | `name` | VARCHAR(100) | NOT NULL | - | 分组名称 | | `icon` | VARCHAR(255) | - | NULL | 图标(图标名/URL/base64) | | `description` | VARCHAR(500) | - | NULL | 分组描述 | | `sort_order` | INT | NOT NULL | 0 | 排序序号(升序) | | `collapsed` | BOOLEAN | NOT NULL | FALSE | 是否默认折叠 | | `visible` | BOOLEAN | NOT NULL | TRUE | 是否可见 | | `color` | VARCHAR(20) | - | NULL | 主题色(预留 v1.0) | | `created_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 创建时间 | | `updated_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 更新时间 | **索引**: - `idx_sort_order` (`sort_order`) **SQL**: ```sql CREATE TABLE t_group ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, icon VARCHAR(255), description VARCHAR(500), sort_order INT NOT NULL DEFAULT 0, collapsed BOOLEAN NOT NULL DEFAULT FALSE, visible BOOLEAN NOT NULL DEFAULT TRUE, color VARCHAR(20), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE INDEX idx_group_sort_order ON t_group (sort_order); ``` --- ### 3.3 书签表(t_bookmark) > 核心实体,存储书签链接信息 | 字段名 | 类型 | 约束 | 默认值 | 说明 | |-------|------|------|--------|------| | `id` | BIGINT | PK, AUTO_INCREMENT | - | 主键 | | `group_id` | BIGINT | FK, NOT NULL | - | 所属分组ID | | `name` | VARCHAR(100) | NOT NULL | - | 书签名称 | | `url` | VARCHAR(2000) | NOT NULL | - | 链接地址 | | `icon` | VARCHAR(500) | - | NULL | 图标(图标名/URL/base64) | | `icon_type` | VARCHAR(20) | NOT NULL | 'TEXT' | 图标类型:BUILTIN/URL/UPLOAD/TEXT/EMOJI | | `description` | VARCHAR(500) | - | NULL | 书签描述 | | `sort_order` | INT | NOT NULL | 0 | 排序序号(升序) | | `open_in_new_tab` | BOOLEAN | NOT NULL | TRUE | 是否新标签页打开 | | `pinned` | BOOLEAN | NOT NULL | FALSE | 是否置顶 | | `visible` | BOOLEAN | NOT NULL | TRUE | 是否可见 | | `click_count` | INT | NOT NULL | 0 | 点击次数(预留 v1.0) | | `last_click_time` | TIMESTAMP | - | NULL | 最后点击时间(预留 v1.0) | | `created_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 创建时间 | | `updated_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 更新时间 | **索引**: - `idx_group_id` (`group_id`) - `idx_group_sort` (`group_id`, `sort_order`) **外键**: - `fk_bookmark_group` FOREIGN KEY (`group_id`) REFERENCES `t_group`(`id`) **SQL**: ```sql CREATE TABLE t_bookmark ( id BIGINT AUTO_INCREMENT PRIMARY KEY, group_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, url VARCHAR(2000) NOT NULL, icon VARCHAR(500), icon_type VARCHAR(20) NOT NULL DEFAULT 'TEXT', description VARCHAR(500), sort_order INT NOT NULL DEFAULT 0, open_in_new_tab BOOLEAN NOT NULL DEFAULT TRUE, pinned BOOLEAN NOT NULL DEFAULT FALSE, visible BOOLEAN NOT NULL DEFAULT TRUE, click_count INT NOT NULL DEFAULT 0, last_click_time TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_bookmark_group FOREIGN KEY (group_id) REFERENCES t_group(id) ON DELETE CASCADE ); CREATE INDEX idx_bookmark_group_id ON t_bookmark (group_id); CREATE INDEX idx_bookmark_group_sort ON t_bookmark (group_id, sort_order); ``` --- ### 3.4 配置表(t_config) > 存储应用级配置,Key-Value 结构 | 字段名 | 类型 | 约束 | 默认值 | 说明 | |-------|------|------|--------|------| | `id` | BIGINT | PK, AUTO_INCREMENT | - | 主键 | | `config_key` | VARCHAR(100) | NOT NULL, UNIQUE | - | 配置键 | | `config_value` | TEXT | - | NULL | 配置值 | | `config_type` | VARCHAR(20) | NOT NULL | 'STRING' | 值类型:STRING/NUMBER/BOOLEAN/JSON | | `category` | VARCHAR(50) | NOT NULL | 'GENERAL' | 分类:GENERAL/APPEARANCE/LAYOUT | | `description` | VARCHAR(255) | - | NULL | 配置说明 | | `created_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 创建时间 | | `updated_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 更新时间 | **索引**: - `uk_config_key` UNIQUE (`config_key`) - `idx_category` (`category`) **SQL**: ```sql CREATE TABLE t_config ( id BIGINT AUTO_INCREMENT PRIMARY KEY, config_key VARCHAR(100) NOT NULL, config_value TEXT, config_type VARCHAR(20) NOT NULL DEFAULT 'STRING', category VARCHAR(50) NOT NULL DEFAULT 'GENERAL', description VARCHAR(255), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT uk_config_key UNIQUE (config_key) ); CREATE INDEX idx_config_category ON t_config (category); ``` **预置配置项**: | config_key | config_value | config_type | category | 说明 | |-----------|--------------|-------------|----------|------| | `app.title` | Home4j | STRING | GENERAL | 网站标题 | | `app.subtitle` | Welcome Home | STRING | GENERAL | 副标题 | | `app.logo` | NULL | STRING | GENERAL | Logo URL | | `app.favicon` | NULL | STRING | GENERAL | Favicon URL | | `app.theme` | light | STRING | APPEARANCE | 当前主题 | | `app.language` | zh-CN | STRING | GENERAL | 界面语言 | | `layout.columns` | 4 | NUMBER | LAYOUT | 每行列数 | | `layout.card_size` | normal | STRING | LAYOUT | 卡片尺寸 | | `layout.density` | normal | STRING | LAYOUT | 显示密度 | | `search.enabled` | true | BOOLEAN | GENERAL | 启用搜索 | | `search.placeholder` | 搜索书签... | STRING | GENERAL | 搜索框占位符 | | `init.completed` | false | BOOLEAN | GENERAL | 初始化是否完成 | --- ### 3.5 书签标签表(t_bookmark_tag)- 预留 v0.5 > 书签的标签,支持多对多关系(预留) | 字段名 | 类型 | 约束 | 默认值 | 说明 | |-------|------|------|--------|------| | `id` | BIGINT | PK, AUTO_INCREMENT | - | 主键 | | `name` | VARCHAR(50) | NOT NULL, UNIQUE | - | 标签名称 | | `color` | VARCHAR(20) | - | NULL | 标签颜色 | | `created_at` | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 创建时间 | **SQL**(预留): ```sql CREATE TABLE t_bookmark_tag ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, color VARCHAR(20), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uk_tag_name UNIQUE (name) ); -- 书签-标签关联表 CREATE TABLE t_bookmark_tag_rel ( bookmark_id BIGINT NOT NULL, tag_id BIGINT NOT NULL, PRIMARY KEY (bookmark_id, tag_id), CONSTRAINT fk_rel_bookmark FOREIGN KEY (bookmark_id) REFERENCES t_bookmark(id) ON DELETE CASCADE, CONSTRAINT fk_rel_tag FOREIGN KEY (tag_id) REFERENCES t_bookmark_tag(id) ON DELETE CASCADE ); ``` --- ## 四、枚举值定义 ### 4.1 用户角色(UserRole) ```java public enum UserRole { ADMIN, // 管理员,可编辑 USER // 普通用户(预留 v1.0) } ``` ### 4.2 图标类型(IconType) ```java public enum IconType { BUILTIN, // 内置图标库 URL, // 外部 URL UPLOAD, // 用户上传 TEXT, // 文字首字母 EMOJI // Emoji 表情 } ``` ### 4.3 配置类型(ConfigType) ```java public enum ConfigType { STRING, // 字符串 NUMBER, // 数字 BOOLEAN, // 布尔 JSON // JSON 对象 } ``` ### 4.4 配置分类(ConfigCategory) ```java public enum ConfigCategory { GENERAL, // 常规设置 APPEARANCE, // 外观设置 LAYOUT // 布局设置 } ``` --- ## 五、Quarkus Panache 实体示例 ### 5.1 Bookmark 实体 ```java package com.home4j.entity; import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import java.time.LocalDateTime; @Entity @Table(name = "t_bookmark") public class Bookmark extends PanacheEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "group_id", nullable = false) public BookmarkGroup group; @Column(nullable = false, length = 100) public String name; @Column(nullable = false, length = 2000) public String url; @Column(length = 500) public String icon; @Enumerated(EnumType.STRING) @Column(name = "icon_type", nullable = false, length = 20) public IconType iconType = IconType.TEXT; @Column(length = 500) public String description; @Column(name = "sort_order", nullable = false) public Integer sortOrder = 0; @Column(name = "open_in_new_tab", nullable = false) public Boolean openInNewTab = true; @Column(nullable = false) public Boolean pinned = false; @Column(nullable = false) public Boolean visible = true; @Column(name = "click_count", nullable = false) public Integer clickCount = 0; @Column(name = "last_click_time") public LocalDateTime lastClickTime; @Column(name = "created_at", nullable = false, updatable = false) public LocalDateTime createdAt; @Column(name = "updated_at", nullable = false) public LocalDateTime updatedAt; @PrePersist public void prePersist() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate public void preUpdate() { updatedAt = LocalDateTime.now(); } // 常用查询方法 public static List findByGroupId(Long groupId) { return find("group.id = ?1 ORDER BY pinned DESC, sortOrder ASC", groupId).list(); } public static List findVisible() { return find("visible = true ORDER BY pinned DESC, sortOrder ASC").list(); } } ``` ### 5.2 BookmarkGroup 实体 ```java package com.home4j.entity; import io.quarkus.hibernate.orm.panache.PanacheEntity; import jakarta.persistence.*; import java.time.LocalDateTime; import java.util.List; @Entity @Table(name = "t_group") public class BookmarkGroup extends PanacheEntity { @Column(nullable = false, length = 100) public String name; @Column(length = 255) public String icon; @Column(length = 500) public String description; @Column(name = "sort_order", nullable = false) public Integer sortOrder = 0; @Column(nullable = false) public Boolean collapsed = false; @Column(nullable = false) public Boolean visible = true; @Column(length = 20) public String color; @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, orphanRemoval = true) public List bookmarks; @Column(name = "created_at", nullable = false, updatable = false) public LocalDateTime createdAt; @Column(name = "updated_at", nullable = false) public LocalDateTime updatedAt; @PrePersist public void prePersist() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate public void preUpdate() { updatedAt = LocalDateTime.now(); } // 常用查询方法 public static List findAllOrdered() { return find("ORDER BY sortOrder ASC").list(); } public static List findVisible() { return find("visible = true ORDER BY sortOrder ASC").list(); } } ``` --- ## 六、数据初始化 ### 6.1 首次启动初始化 ```sql -- 插入默认分组 INSERT INTO t_group (name, icon, sort_order, collapsed, visible) VALUES ('默认分组', 'folder', 0, FALSE, TRUE); -- 插入默认配置 INSERT INTO t_config (config_key, config_value, config_type, category, description) VALUES ('app.title', 'Home4j', 'STRING', 'GENERAL', '网站标题'), ('app.subtitle', 'Welcome Home', 'STRING', 'GENERAL', '副标题'), ('app.theme', 'light', 'STRING', 'APPEARANCE', '当前主题'), ('app.language', 'zh-CN', 'STRING', 'GENERAL', '界面语言'), ('layout.columns', '4', 'NUMBER', 'LAYOUT', '每行列数'), ('layout.card_size', 'normal', 'STRING', 'LAYOUT', '卡片尺寸'), ('search.enabled', 'true', 'BOOLEAN', 'GENERAL', '启用搜索'), ('init.completed', 'false', 'BOOLEAN', 'GENERAL', '初始化是否完成'); ``` --- ## 七、数据库配置 ### 7.1 H2 配置(开发/默认) ```properties # application.properties quarkus.datasource.db-kind=h2 quarkus.datasource.jdbc.url=jdbc:h2:file:./data/home4j;AUTO_SERVER=TRUE quarkus.datasource.username=sa quarkus.datasource.password= quarkus.hibernate-orm.database.generation=update ``` ### 7.2 MySQL 配置(生产可选) ```properties # application-mysql.properties quarkus.datasource.db-kind=mysql quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3306/home4j?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai quarkus.datasource.username=home4j quarkus.datasource.password=your_password quarkus.hibernate-orm.database.generation=update ``` --- ## 八、表结构总览 | 表名 | 说明 | MVP | v0.5 | v1.0 | |-----|------|-----|------|------| | `t_user` | 用户表 | ✅ | ✅ | ✅ | | `t_group` | 分组表 | ✅ | ✅ | ✅ | | `t_bookmark` | 书签表 | ✅ | ✅ | ✅ | | `t_config` | 配置表 | ✅ | ✅ | ✅ | | `t_bookmark_tag` | 标签表 | - | ✅ | ✅ | | `t_bookmark_tag_rel` | 书签标签关联 | - | ✅ | ✅ | --- > **文档结束** > 本设计将随项目进展持续更新