25 数据库设计与表结构规划:5 张表的设计思路

前言

在这里插入图片描述

图:25 数据库设计与表结构规划:5 张表的设计思路 运行效果截图(HarmonyOS NEXT)

数据库表结构的设计直接关系到应用的数据组织方式、查询效率和扩展性。一个良好的表结构设计应该满足当前需求、预留扩展空间、并保持查询效率

本文以"鹿鹿·笔迹心理分析"项目中的 5 张核心表为例,详细讲解鸿蒙 RDB 数据库的表结构设计思路、字段类型选择、约束策略和索引优化方案。

鸿蒙官方·RDB 建表语法:developer.huawei.com
[Models.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/Models.ets) — 实体类型定义

数据库 5 张表关系图

图:鹿鹿项目 5 张表的 ER 关系——user、archive、handwriting、report、relation

1:N

1:N

1:1

N:M

partner_archive_id

user
用户表

archive
档案表

handwriting
手写记录表

report
报告表

relation
关系表

一、数据模型概览

1.1 5 张表的关系

user ──1:N──→ archive ──1:N──→ handwriting ──1:1──→ report
  │              │
  │              └── relation(partner_archive_id)
  └──────── relation(user_id)
表名 中文名 核心作用 数据量级 外键关联
user 用户 本地用户信息 ~1 条 关联 archive.user_id
archive 档案 按人分组管理 5-20 条 关联 user.id
handwriting 笔迹 每次采集的记录 数百条 关联 archive.id
report 报告 AI 分析结果 同 handwriting 关联 handwriting.id
relation 关系 双人合盘绑定 ~1 条 关联 user.id

1.2 DDL 语句

// DatabaseService.ets — upgrade 方法中的建表 SQL
const ddls: string[] = [
  `CREATE TABLE IF NOT EXISTS user (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    open_id TEXT UNIQUE,
    name TEXT NOT NULL,
    avatar TEXT,
    created_at INTEGER NOT NULL
  )`,

  `CREATE TABLE IF NOT EXISTS archive (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    name TEXT NOT NULL,
    emoji TEXT,
    relation TEXT,
    color TEXT,
    last_record_at INTEGER,
    record_count INTEGER NOT NULL DEFAULT 0,
    created_at INTEGER NOT NULL
  )`,

  `CREATE TABLE IF NOT EXISTS handwriting (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    archive_id INTEGER,
    source TEXT NOT NULL,
    image_path TEXT,
    ocr_text TEXT,
    feature_json TEXT,
    energy_score INTEGER,
    device_type TEXT DEFAULT '',
    word_count INTEGER DEFAULT 0,
    write_duration INTEGER DEFAULT 0,
    created_at INTEGER NOT NULL
  )`,

  `CREATE TABLE IF NOT EXISTS report (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    handwriting_id INTEGER NOT NULL,
    archive_id INTEGER,
    personality_type TEXT,
    summary TEXT,
    radar_json TEXT,
    tags_json TEXT,
    created_at INTEGER NOT NULL
  )`,

  `CREATE TABLE IF NOT EXISTS relation (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    partner_archive_id INTEGER NOT NULL,
    bound_method TEXT,
    match_score INTEGER,
    created_at INTEGER NOT NULL
  )`
]

二、表结构详解

2.1 user(用户表)

CREATE TABLE IF NOT EXISTS user (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,   -- 主键,自增
  open_id     TEXT UNIQUE,                          -- 用户标识(HMS/本地)
  name        TEXT NOT NULL,                        -- 用户名
  avatar      TEXT,                                 -- 头像路径
  created_at  INTEGER NOT NULL                      -- 创建时间戳
)

设计要点:

字段 类型选择 设计理由
id INTEGER PRIMARY KEY AUTOINCREMENT 自增主键,保证唯一性和插入效率
open_id TEXT UNIQUE 未来对接 HMS 账号时使用,local 表示本地用户
name TEXT NOT NULL 用户昵称,NOT NULL 确保始终有显示名
avatar TEXT(NULL 允许) 头像路径,暂无头像时可为空
created_at INTEGER NOT NULL 时间戳以毫秒为单位(13 位),not null

2.2 archive(档案表)

CREATE TABLE IF NOT EXISTS archive (
  id              INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id         INTEGER NOT NULL,                 -- 关联 user.id
  name            TEXT NOT NULL,                    -- 档案名称
  emoji           TEXT,                             -- 档案图标(emoji 字符串)
  relation        TEXT,                             -- 关系标签("伴侣"/"未设置")
  color           TEXT,                             -- 主题色(hex 字符串)
  last_record_at  INTEGER,                          -- 最近记录时间
  record_count    INTEGER NOT NULL DEFAULT 0,       -- 记录总数
  created_at      INTEGER NOT NULL
)

设计要点:

字段 作用 业务映射
user_id 归属用户 一个用户可有多个档案
relation 关系分类 '伴侣' 的档案收入"合盘"模块
emoji + color 视觉标识 档案卡片上的图标和背景色
last_record_at 排序依据 首页按最近记录时间排序
record_count 展示计数 冗余字段,避免每次都 COUNT 查询

2.3 handwriting(笔迹表)

CREATE TABLE IF NOT EXISTS handwriting (
  id              INTEGER PRIMARY KEY AUTOINCREMENT,
  archive_id      INTEGER,                          -- 档案(NULL=未归类)
  source          TEXT NOT NULL,                     -- 来源:camera/gallery/handwrite/demo
  image_path      TEXT,                             -- 图片路径
  ocr_text        TEXT,                             -- OCR 识别文本
  feature_json    TEXT,                             -- JSON:6 维特征向量
  energy_score    INTEGER,                          -- 能量值(0-100)
  device_type     TEXT DEFAULT '',                   -- 设备类型(v2 新增)
  word_count      INTEGER DEFAULT 0,                -- 字数统计(v2 新增)
  write_duration  INTEGER DEFAULT 0,                -- 书写时长 ms(v2 新增)
  created_at      INTEGER NOT NULL
)

设计要点:

字段 说明 业务价值
source TEXT NOT NULL 区分数据分析来源(拍照/相册/手写板)
feature_json TEXT(JSON) 存储 6 维特征向量,避免建立 6 个独立字段
archive_id 允许 NULL archive_id=0 表示"未归类"
created_at INTEGER 毫秒时间戳,支持时间的精确排序
v2 新增字段 含默认值 向前兼容的字段扩展示例

2.4 report(报告表)

CREATE TABLE IF NOT EXISTS report (
  id              INTEGER PRIMARY KEY AUTOINCREMENT,
  handwriting_id  INTEGER NOT NULL,                 -- 关联 handwriting.id
  archive_id      INTEGER,                          -- 冗余,便于按档案查询
  personality_type TEXT,                            -- 人格类型(如"温和理性型")
  summary         TEXT,                             -- 一句话总结
  radar_json      TEXT,                             -- JSON: 6 维雷达分数
  tags_json       TEXT,                             -- JSON: 标签数组
  created_at      INTEGER NOT NULL
)

JSON 字段的设计理由:

// 如果不用 JSON 字段,雷达图需要 6 列
// ❌ 方案 A:展开为 6 个独立字段
energy REAL, thinking REAL, action REAL,
emotion REAL, social REAL, detail REAL

// ✅ 方案 B:JSON 字符串(本项目采用)
radar_json TEXT  -- '{"energy":62,"thinking":68,"action":55,"emotion":71,"social":60,"detail":75}'

JSON 字段的优势:

对比项 6 个独立字段 JSON 字段
列数 +6 列 +1 列
扩展性 新增维度需 ALTER TABLE 直接修改 JSON 结构
代码量 6 个 getter/setter 序列化/反序列化一次
查询 可 WHERE 过滤某个维度 无法直接过滤
适用场景 需要按维度筛选 仅展示,不需要筛选

2.5 relation(关系表)

CREATE TABLE IF NOT EXISTS relation (
  id                INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id           INTEGER NOT NULL,              -- 用户
  partner_archive_id INTEGER NOT NULL,              -- 伴侣的档案 ID
  bound_method      TEXT,                          -- 绑定方式:tap/qrcode/phone
  match_score       INTEGER,                       -- 匹配度 0-100
  created_at        INTEGER NOT NULL
)

设计要点:

  • 通过 partner_archive_id 关联实际伴侣的档案数据
  • bound_method 记录绑定方式(NFC 碰一碰/二维码/手机号)
  • match_score 缓存匹配度计算结果

提示:relation 表设计的核心思路是"用一个外键引用 partner 的档案,而非引用 partner 的用户表"。这样可以直接复用 archive 中的档案数据。

三、字段类型对照

3.1 鸿蒙 RDB 支持的类型

SQLite 类型 鸿蒙映射 使用场景
INTEGER number(getLong) 主键、时间戳、数值
REAL number(getDouble) 小数(本项目未使用)
TEXT string(getString) 文字、JSON、路径
BLOB ArrayBuffer(getBlob) 二进制(本项目未使用)

3.2 项目中实际使用的类型

项目类型 SQL 类型 Getter Setter 使用字段
主键 ID INTEGER getLong 自增 所有表的 id
文本 TEXT getString string name、summary、OCR
时间戳 INTEGER getLong Date.now() created_at、last_record_at
计数 INTEGER getLong number record_count、word_count
JSON TEXT getString JSON.stringify() feature_json、radar_json
枚举 TEXT getString string literal source、bound_method

四、索引设计

4.1 项目中的索引

CREATE INDEX IF NOT EXISTS idx_handwriting_archive
  ON handwriting(archive_id, created_at DESC)    -- 组合索引,支持按档案+时间查询

CREATE INDEX IF NOT EXISTS idx_report_handwriting
  ON report(handwriting_id)                       -- 单列索引,支持笔迹→报告查询

4.2 索引覆盖的查询场景

索引名称 加速的查询 涉及方法
idx_handwriting_archive WHERE archive_id = ? ORDER BY created_at DESC HandwritingDao.listByArchive
idx_report_handwriting WHERE handwriting_id = ? ReportDao.findByHandwriting

4.3 索引设计原则

  • 为高频 WHERE 字段建索引:archive_id、handwriting_id 频繁出现在查询条件中
  • 为 ORDER BY 字段建索引:created_at DESC 是最常用的排序方向
  • 避免过量索引:5 张表只建 2 个索引,因为大部分查询在小数据量(<500 条)下走全表扫描也很快
  • 组合索引的列顺序:将区分度高的列放在前面(archive_id 比 created_at 更数据集中)

五、实体类型定义

5.1 接口定义

// Models.ets
export interface HandwritingEntity {
  id?: number
  archive_id?: number
  source: string              // 'camera' | 'gallery' | 'handwrite'
  image_path?: string
  ocr_text?: string
  feature_json?: string       // 6 维特征向量
  energy_score?: number
  device_type?: string
  word_count?: number
  write_duration?: number
  created_at: number
}

5.2 可选字段的语义

写法 说明 对应 SQL
id?: number 主键用于读取时赋值 PRIMARY KEY AUTOINCREMENT
archive_id?: number 可为 NULL,未归类 archive_id INTEGER
source: string NOT NULL,必传字段 source TEXT NOT NULL
energy_score?: number NULL 允许 energy_score INTEGER

提示:Entity 接口中的可选字段 ? 对应 SQL 中的 NULL 允许字段。NOT NULL 的字段在接口中写为 field: type

六、表结构设计总结

6.1 设计检查清单

检查项 项目中示例 是否满足
每张表都有主键 ✅ 所有表有 id INTEGER PRIMARY KEY AUTOINCREMENT
外键关系明确 ✅ user→archive→handwriting→report
冗余字段合理 ✅ archive.record_count(避免 COUNT 查询)
时间戳统一 ✅ 所有 created_at 为 INTEGER(毫秒)
JSON 字段适度 ✅ 仅在 feature_json、radar_json、tags_json 使用
索引覆盖高频查询 ✅ 2 个索引覆盖所有关联查询
预留扩展 ✅ 字段有默认值,v2 可新增列

6.2 不采用的方案

方案 原因 替代选择
使用自增 UUID 做主键 INTEGER 更高效、更易读 INTEGER PRIMARY KEY
雷达成 6 维独立列 扩展性差 JSON 列
storage 存储附件图片 图片通过文件系统管理 数据库中只存 image_path
使用外键约束 鸿蒙 RDB 不强制外键 应用层保证一致性

八、注意事项与常见问题

8.1 开发注意事项

在实际开发过程中,需特别注意以下几点:

  • API 兼容性:部分接口仅在特定 HarmonyOS NEXT 版本中可用,需做版本条件判断
  • 权限模型:采用静态声明(module.json5)+ 动态申请(requestPermissionsFromUser)的两阶段授权
  • 生命周期:合理使用 aboutToAppear()aboutToDisappear() 管理资源初始化与释放
  • 状态同步:跨页面数据通过 AppStorage 共享,组件内状态使用 @State / @Prop / @Link 装饰器

8.2 常见错误与解决方案

常见问题快速排查表:

问题类型 排查方向 参考方法
应用崩溃 查看 hilog 错误日志 hilog.error(TAG, "...", e.message)
状态丢失 检查 AppStorage 键名拼写 统一使用常量管理键名
动画不流畅 避免在 animateTo 回调中执行 I/O 动画与数据操作分离

总结

本文详细解析了"鹿鹿"项目的 5 张数据库表设计:

  1. user:用户基础信息,open_id 预留 HMS 账号对接
  2. archive:档案分组管理,emoji+color 视觉标识
  3. handwriting:笔迹采集记录,feature_json 存 6 维特征
  4. report:AI 分析报告,radar_json 存雷达分数
  5. relation:双人合盘绑定,通过 partner_archive_id 引用档案

设计原则:规范化为主,适度冗余为辅;明确外键关系,但不由数据库强制;JSON 字段存储结构化但不需要独立查询的复合数据。

下一篇文章将深入 数据库版本升级策略——从 v1 到 v2 的增量迁移实践。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


参考资源:


思考题: 在你的鸿蒙项目中,是如何处理复杂关联关系(如多对多)的?

  • A. 使用中间关联表(如本项目 relation 表)
  • B. 在字段中存储 JSON 数组(如 partner_ids: string
  • C. 使用多个独立查询在 TypeScript 层面做联接
  • D. 直接使用原始 SQL JOIN 语句

欢迎在评论区分享你的数据建模经验!

七、设计原则总结

在本项目 5 张表的设计中,遵循了以下核心原则:

原则 说明 本项目体现
第三范式(3NF) 消除传递依赖 report 独立于 handwriting
最小化 NULL 关键字段设 NOT NULL id、user_id 等主键字段
时间戳标准化 统一使用 ISO 8601 created_at TEXT 存储字符串
JSON 序列化 数组/对象存为 TEXT keywordsradar_scores 字段
索引策略 只为高频查询列建索引 user_idcreated_at 建索引
外键约束 逻辑外键(无 FK 约束) 由应用层保证引用完整性

鸿蒙 RDB(SQLite)默认不启用 FOREIGN KEY 约束,需要显式执行 PRAGMA foreign_keys = ON; 才能生效。在本项目中,通过 DAO 层的业务逻辑保证数据的引用完整性。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐