# 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<Bookmark> findByGroupId(Long groupId) {
        return find("group.id = ?1 ORDER BY pinned DESC, sortOrder ASC", groupId).list();
    }
    
    public static List<Bookmark> 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<Bookmark> 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<BookmarkGroup> findAllOrdered() {
        return find("ORDER BY sortOrder ASC").list();
    }
    
    public static List<BookmarkGroup> 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` | 书签标签关联 | - | ✅ | ✅ |

---

> **文档结束**  
> 本设计将随项目进展持续更新
