# 《WaLiOffice - AI Agent 智能办公平台》第2-3节:数据库设计与会话持久化

作者:小傅哥
博客:https://bugstack.cn (opens new window)

沉淀、分享、成长,让自己和他人都能有所收获!😄

大家好,我是技术UP主小傅哥。

上一节我们把 LLM 客户端封装好了——能调 API、能流式输出。但有一个问题要:数据不持久化。现在的状态是:

  • 用户发一条消息 → AI 回复 → 会话结束就什么都没了
  • 下次再发消息 → AI 不知道你们之前聊过什么
  • 生成的 PPT 文件 → 服务器重启就没了

这显然不行。一个真正的 AI Agent 系统,会话持久化是基础设施——没有它,AI 就是"金鱼记忆",每次对话都是从零开始。

所以这一节,我们来设计 WaLiOffice 的数据库层。

但你想过没有:为什么 WaLiOffice 既支持 SQLite 又支持 MySQL?一个数据库不够用吗?——还真不够。SQLite 适合本地开发和个人用户(零配置、零运维),MySQL 适合多用户团队和云端部署(并发好、运维成熟)。两者各有场景,所以 WaLiOffice 两个都要,通过 sqlxAnyPool 统一接口。

# 一、本章诉求

  1. 理解 WaLiOffice 的数据库表设计(users / sessions / messages / session_artifacts)
  2. 掌握 AnyPool 双数据库连接池初始化(SQLite + MySQL)
  3. 学会 session_repo 会话仓储的所有操作(CRUD + 历史消息加载 + 产物管理)
  4. 理解 UPSERT 模式(ON CONFLICT / ON DUPLICATE KEY UPDATE)
  5. 掌握 sqlx 异步查询的正确写法(避免列索引硬编码陷阱)

# 二、流程设计

# 2.1 数据库表结构

-- 用户表
CREATE TABLE users (
    id TEXT PRIMARY KEY,
    username TEXT UNIQUE NOT NULL,
    email TEXT UNIQUE,
    password_hash TEXT NOT NULL,
    role TEXT NOT NULL DEFAULT 'user',
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

-- 项目表(会话分组)
CREATE TABLE projects (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    tool_kind TEXT NOT NULL DEFAULT 'general',
    owner_id TEXT NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

-- 会话表(核心)
CREATE TABLE sessions (
    id TEXT PRIMARY KEY,
    owner_id TEXT NOT NULL,
    project_id TEXT,
    tool_kind TEXT,
    title TEXT NOT NULL,           -- 会话标题(取用户第一条消息前30字)
    summary TEXT,                  -- AI 总结(用于侧边栏展示)
    message_count INTEGER NOT NULL DEFAULT 0,
    order_col INTEGER NOT NULL DEFAULT 0,  -- 排序字段
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

-- 会话消息表
CREATE TABLE messages (
    id TEXT PRIMARY KEY,
    session_id TEXT NOT NULL,
    role TEXT NOT NULL,           -- 'user' | 'assistant'
    content TEXT NOT NULL,
    tool_name TEXT,               -- 工具名(记录 AI 调用了什么)
    tool_input TEXT,              -- 工具输入 JSON
    tool_output TEXT,             -- 工具输出(作为 tool_role 消息的 content)
    created_at TEXT NOT NULL
);

-- 会话产物表(JSON 列存储 AI 生成的文件)
CREATE TABLE session_artifacts (
    id TEXT PRIMARY KEY,
    session_id TEXT NOT NULL UNIQUE,  -- 每个会话只有一条产物记录
    payload TEXT NOT NULL,            -- JSON: Vec<Artifact>
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

👩🏻‍🏫敲黑板:有几个设计细节值得注意:

  1. order_col 字段:不是用 updated_at 排序,而是用一个可自定义的整数排序。这样用户可以拖拽调整会话顺序,而且排序不需要改数据库结构。
  2. session_artifacts 表每会话只有一条记录:产物以 JSON 数组形式存在一个字段里(payload TEXT),而不是拆成多行。这样查询快、写入简单(一次 UPSERT),缺点是产物多了 JSON 会很大——但实际使用中每个会话的产物不会太多,完全够用。
  3. TEXT 类型存储时间created_atupdated_at 存的是 RFC3339 字符串(如 2024-01-01T12:00:00Z),而不是数据库原生的 DATETIME。这样跨数据库(SQLite/MySQL)迁移时不需要类型转换。

# 2.2 连接池初始化流程