chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>app-platform-memory</artifactId>
<groupId>com.xspaceagi</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-memory-infra</artifactId>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-memory-domain</artifactId>
</dependency>
<dependency>
<groupId>com.xspaceagi</groupId>
<artifactId>system-sdk</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,84 @@
package com.xspaceagi.memory.infra.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 记忆单元实体
* @TableName memory_unit
*/
@TableName(value = "memory_unit")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemoryUnit {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 租户ID
*/
@TableField(value = "_tenant_id")
private Long tenantId;
/**
* 用户ID
*/
private Long userId;
/**
* 代理ID
*/
private Long agentId;
/**
* 一级分类
*/
private String category;
/**
* 二级分类
*/
private String subCategory;
/**
* 内容JSON
*/
private String contentJson;
/**
* 是否敏感信息(0:否 1:是)
*/
private Boolean isSensitive;
/**
* 状态
*/
private String status;
/**
* 创建时间
*/
private Date created;
/**
* 修改时间
*/
private Date modified;
}

View File

@@ -0,0 +1,64 @@
package com.xspaceagi.memory.infra.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 记忆单元标签实体
* @TableName memory_unit_tag
*/
@TableName(value = "memory_unit_tag")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemoryUnitTag {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 租户ID
*/
@TableField(value = "_tenant_id")
private Long tenantId;
/**
* 用户ID
*/
private Long userId;
/**
* 记忆ID
*/
private Long memoryId;
/**
* 标签名称
*/
private String tagName;
/**
* 创建时间
*/
private Date created;
/**
* 修改时间
*/
private Date modified;
}

View File

@@ -0,0 +1,137 @@
package com.xspaceagi.memory.infra.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.xspaceagi.memory.infra.dao.entity.MemoryUnit;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 记忆单元Mapper
*
* @description 针对表【memory_unit(记忆单元表)】的数据库操作Mapper
* @createDate 2026-03-06
* @Entity com.xspaceagi.memory.infra.dao.entity.MemoryUnit
*/
public interface MemoryUnitMapper extends BaseMapper<MemoryUnit> {
/**
* 总条数查询
*
* @param queryMap 筛选条件
* @return 总条数
*/
Long queryTotal(@Param("queryMap") Map<String, Object> queryMap);
/**
* 列表查询
*
* @param queryMap 筛选条件
* @param orderColumns 排序
* @param startIndex 索引开始位置
* @param pageSize 业务大小
* @return 列表
*/
List<MemoryUnit> queryList(@Param("queryMap") Map<String, Object> queryMap,
@Param("orderColumns") List<OrderItem> orderColumns,
@Param("startIndex") Long startIndex, @Param("pageSize") Long pageSize);
/**
* 根据用户ID和分类查询记忆单元
*
* @param userId 用户ID
* @param category 一级分类
* @return 记忆单元列表
*/
List<MemoryUnit> findByUserIdAndCategory(@Param("userId") Long userId, @Param("agentId") Long agentId, @Param("category") String category, @Param("subCategory") String subCategory);
/**
* 根据代理ID查询记忆单元
*
* @param agentId 代理ID
* @return 记忆单元列表
*/
List<MemoryUnit> findByAgentId(@Param("agentId") Long agentId);
/**
* 根据分类和JSON字段值查询记忆单元精确匹配
*
* @param tenantId 租户ID
* @param userId 用户ID
* @param category 一级分类
* @param subCategory 二级分类
* @param jsonKey JSON中的键
* @param jsonValue JSON中的值
* @return 记忆单元列表
*/
List<MemoryUnit> findByCategoryAndJsonKeyValue(
@Param("tenantId") Long tenantId,
@Param("userId") Long userId,
@Param("category") String category,
@Param("subCategory") String subCategory,
@Param("jsonKey") String jsonKey,
@Param("jsonValue") String jsonValue
);
/**
* 根据分类和JSON字段值查询记忆单元模糊匹配
*
* @param tenantId 租户ID
* @param userId 用户ID
* @param category 一级分类
* @param subCategory 二级分类
* @param jsonKey JSON中的键
* @param jsonValue JSON中的值
* @return 记忆单元列表
*/
List<MemoryUnit> findByCategoryAndJsonKeyValueLike(
@Param("tenantId") Long tenantId,
@Param("userId") Long userId,
@Param("category") String category,
@Param("subCategory") String subCategory,
@Param("jsonKey") String jsonKey,
@Param("jsonValue") String jsonValue
);
/**
* 根据分类和JSON多个字段值查询记忆单元
*
* @param tenantId 租户ID
* @param userId 用户ID
* @param category 一级分类
* @param subCategory 二级分类
* @param jsonKeyValues JSON键值对 Map<key, value>
* @return 记忆单元列表
*/
List<MemoryUnit> findByCategoryAndJsonKeyValues(
@Param("tenantId") Long tenantId,
@Param("userId") Long userId,
@Param("category") String category,
@Param("subCategory") String subCategory,
@Param("jsonKeyValues") Map<String, String> jsonKeyValues
);
/**
* 根据用户ID、代理ID和标签查询记忆单元
*
* @param userId 用户ID
* @param agentId 代理ID
* @param tags 标签列表
* @param startTime 开始时间
* @param endTime 结束时间
* @param startIndex 索引开始位置
* @param pageSize 业务大小
* @return 记忆单元列表
*/
List<MemoryUnit> selectWithJoinTags(@Param("userId") Long userId,
@Param("agentId") Long agentId,
@Param("tags") List<String> tags,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("startIndex") Long startIndex,
@Param("pageSize") Long pageSize);
}

View File

@@ -0,0 +1,58 @@
package com.xspaceagi.memory.infra.dao.mapper;
import com.xspaceagi.memory.infra.dao.entity.MemoryUnitTag;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 记忆单元标签Mapper
* @description 针对表【memory_unit_tag(记忆单元标签表)】的数据库操作Mapper
* @createDate 2026-03-06
* @Entity com.xspaceagi.memory.infra.dao.entity.MemoryUnitTag
*/
public interface MemoryUnitTagMapper extends BaseMapper<MemoryUnitTag> {
/**
* 根据记忆ID查询标签列表
*
* @param memoryId 记忆ID
* @return 标签列表
*/
List<MemoryUnitTag> findByMemoryId(@Param("memoryId") Long memoryId);
/**
* 根据用户ID查询标签列表
*
* @param userId 用户ID
* @return 标签列表
*/
List<MemoryUnitTag> findByUserId(@Param("userId") Long userId);
/**
* 根据标签名称查询标签列表
*
* @param tagName 标签名称
* @return 标签列表
*/
List<MemoryUnitTag> findByTagName(@Param("tagName") String tagName);
/**
* 根据记忆ID删除标签
*
* @param memoryId 记忆ID
* @return 删除数量
*/
int deleteByMemoryId(@Param("memoryId") Long memoryId);
/**
* 批量插入标签
*
* @param tags 标签列表
* @return 插入数量
*/
int batchInsert(@Param("tags") List<MemoryUnitTag> tags);
}

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xspaceagi.memory.infra.dao.mapper.MemoryUnitMapper">
<resultMap id="BaseResultMap" type="com.xspaceagi.memory.infra.dao.entity.MemoryUnit">
<id property="id" column="id"/>
<result property="tenantId" column="_tenant_id"/>
<result property="userId" column="user_id"/>
<result property="agentId" column="agent_id"/>
<result property="category" column="category"/>
<result property="subCategory" column="sub_category"/>
<result property="contentJson" column="content_json"/>
<result property="isSensitive" column="is_sensitive"/>
<result property="status" column="status"/>
<result property="created" column="created"/>
<result property="modified" column="modified"/>
</resultMap>
<!-- 查询条件 -->
<sql id="base_where">
<if test="queryMap.tenantId != null">
and _tenant_id = #{queryMap.tenantId}
</if>
<if test="queryMap.userId != null">
and user_id = #{queryMap.userId}
</if>
<if test="queryMap.agentId != null">
and agent_id = #{queryMap.agentId}
</if>
<if test="queryMap.category != null and queryMap.category != ''">
and category = #{queryMap.category}
</if>
<if test="queryMap.subCategory != null and queryMap.subCategory != ''">
and sub_category = #{queryMap.subCategory}
</if>
<if test="queryMap.isSensitive != null">
and is_sensitive = #{queryMap.isSensitive}
</if>
<if test="queryMap.status != null and queryMap.status != ''">
and status = #{queryMap.status}
</if>
<if test="queryMap.keyword != null and queryMap.keyword != ''">
and (category like concat('%',#{queryMap.keyword},'%')
or sub_category like concat('%',#{queryMap.keyword},'%')
or content_json like concat('%',#{queryMap.keyword},'%'))
</if>
</sql>
<!-- 总条数查询 -->
<select id="queryTotal" resultType="java.lang.Long">
select count(*)
from memory_unit
<where>
<include refid="base_where"></include>
</where>
</select>
<!-- 列表查询 -->
<select id="queryList" resultMap="BaseResultMap">
select *
from memory_unit
<where>
<include refid="base_where"></include>
</where>
<choose>
<when test="orderColumns != null and orderColumns.size > 0">
<foreach collection="orderColumns" item="entity" index="index" separator="," open="order by " close="">
<if test="entity != null">
${entity.column}
<if test="!entity.asc">
desc
</if>
</if>
</foreach>
</when>
<otherwise>
order by created desc
</otherwise>
</choose>
limit #{startIndex}, #{pageSize}
</select>
<!-- 根据用户ID和分类查询 -->
<select id="findByUserIdAndCategory" resultMap="BaseResultMap">
select *
from memory_unit
where user_id = #{userId}
<if test="agentId != null">
and agent_id = #{agentId}
</if>
and category = #{category}
and sub_category = #{subCategory}
and status = 'active'
order by created desc limit 50
</select>
<!-- 根据代理ID查询 -->
<select id="findByAgentId" resultMap="BaseResultMap">
select *
from memory_unit
where agent_id = #{agentId}
and status = 'active'
order by created desc
</select>
<!-- 根据分类和JSON字段值查询精确匹配 -->
<select id="findByCategoryAndJsonKeyValue" resultMap="BaseResultMap">
select *
from memory_unit
where _tenant_id = #{tenantId}
and user_id = #{userId}
and category = #{category}
<if test="subCategory != null and subCategory != ''">
and sub_category = #{subCategory}
</if>
and JSON_CONTAINS(content_json, JSON_QUOTE(#{jsonValue}), '$.${jsonKey}')
and status = 'active'
order by created desc
</select>
<!-- 根据分类和JSON字段值查询模糊匹配 -->
<select id="findByCategoryAndJsonKeyValueLike" resultMap="BaseResultMap">
select *
from memory_unit
where _tenant_id = #{tenantId}
and user_id = #{userId}
and category = #{category}
<if test="subCategory != null and subCategory != ''">
and sub_category = #{subCategory}
</if>
and JSON_EXTRACT(content_json, '$.${jsonKey}') LIKE CONCAT('%', #{jsonValue}, '%')
and status = 'active'
order by created desc
</select>
<!-- 根据分类和JSON多个字段值查询 -->
<select id="findByCategoryAndJsonKeyValues" resultMap="BaseResultMap">
select *
from memory_unit
where _tenant_id = #{tenantId}
and user_id = #{userId}
and category = #{category}
<if test="subCategory != null and subCategory != ''">
and sub_category = #{subCategory}
</if>
<if test="jsonKeyValues != null and jsonKeyValues.size > 0">
and
<trim prefix="" prefixOverrides="and">
<foreach collection="jsonKeyValues" item="value" index="key" separator=" and ">
JSON_CONTAINS(content_json, JSON_QUOTE(#{value}), '$.keyValues.${key}')
</foreach>
</trim>
</if>
and status = 'active'
order by created desc
</select>
<!-- 根据用户ID、代理ID和标签查询记忆单元 -->
<select id="selectWithJoinTags" resultMap="BaseResultMap">
select distinct mu.*
from memory_unit mu
inner join memory_unit_tag mut on mu.id = mut.memory_id
<where>
<if test="userId != null">
and mu.user_id = #{userId}
</if>
<if test="agentId != null">
and mu.agent_id = #{agentId}
</if>
<if test="tags != null and tags.size > 0">
and mut.tag_name in
<foreach collection="tags" item="tag" open="(" separator="," close=")">
#{tag}
</foreach>
</if>
<if test="startTime != null">
and mu.created &gt;= #{startTime}
</if>
<if test="endTime != null">
and mu.created &lt;= #{endTime}
</if>
and mu.status = 'active'
</where>
order by mu.created desc
<if test="startIndex != null and pageSize != null">
limit #{startIndex}, #{pageSize}
</if>
</select>
</mapper>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xspaceagi.memory.infra.dao.mapper.MemoryUnitTagMapper">
<resultMap id="BaseResultMap" type="com.xspaceagi.memory.infra.dao.entity.MemoryUnitTag">
<id property="id" column="id"/>
<result property="tenantId" column="_tenant_id"/>
<result property="userId" column="user_id"/>
<result property="memoryId" column="memory_id"/>
<result property="tagName" column="tag_name"/>
<result property="created" column="created"/>
<result property="modified" column="modified"/>
</resultMap>
<!-- 根据记忆ID查询标签列表 -->
<select id="findByMemoryId" resultMap="BaseResultMap">
select *
from memory_unit_tag
where memory_id = #{memoryId}
order by created desc
</select>
<!-- 根据用户ID查询标签列表 -->
<select id="findByUserId" resultMap="BaseResultMap">
select *
from memory_unit_tag
where user_id = #{userId}
order by created desc
</select>
<!-- 根据标签名称查询标签列表 -->
<select id="findByTagName" resultMap="BaseResultMap">
select *
from memory_unit_tag
where tag_name = #{tagName}
order by created desc
</select>
<!-- 根据记忆ID删除标签 -->
<delete id="deleteByMemoryId">
delete from memory_unit_tag
where memory_id = #{memoryId}
</delete>
<!-- 批量插入标签 -->
<insert id="batchInsert">
insert into memory_unit_tag (_tenant_id, user_id, memory_id, tag_name, created)
values
<foreach collection="tags" item="tag" separator=",">
(#{tag.tenantId}, #{tag.userId}, #{tag.memoryId}, #{tag.tagName}, #{tag.created})
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,153 @@
# 记忆管理模块 - 数据字典
## 表1: memory_unit (记忆单元表)
### 表说明
用于存储AI Agent的记忆单元数据支持多种类型的记忆分类和管理。
### 字段说明
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT(20) | 是 | AUTO_INCREMENT | 主键ID自增 |
| _tenant_id | BIGINT(20) | 是 | - | 租户ID多租户隔离标识 |
| user_id | BIGINT(20) | 是 | - | 用户ID记忆所属用户 |
| agent_id | BIGINT(20) | 否 | NULL | 代理ID关联的AI代理 |
| category | VARCHAR(50) | 是 | - | 一级分类,如:工作、学习、生活等 |
| sub_category | VARCHAR(100) | 否 | NULL | 二级分类,更细粒度的分类 |
| content_json | JSON | 否 | NULL | 记忆内容JSON格式存储结构化数据 |
| is_sensitive | TINYINT(1) | 是 | 0 | 是否敏感信息0-否1-是 |
| status | ENUM | 是 | 'active' | 状态active-活跃archived-已归档deleted-已删除 |
| created | DATETIME | 是 | CURRENT_TIMESTAMP | 创建时间 |
| modified | DATETIME | 是 | CURRENT_TIMESTAMP ON UPDATE | 修改时间 |
### 索引说明
| 索引名 | 字段 | 类型 | 说明 |
|--------|------|------|------|
| PRIMARY | id | PRIMARY | 主键索引 |
| idx_tenant_user | _tenant_id, user_id | INDEX | 租户用户联合索引,支持多租户查询 |
| idx_agent | agent_id | INDEX | 代理ID索引支持按代理查询 |
| idx_category | category, sub_category | INDEX | 分类联合索引,支持分类查询 |
| idx_status | status | INDEX | 状态索引,支持状态筛选 |
| idx_created | created | INDEX | 创建时间索引,支持时间范围查询 |
### 字段约束
- `status` 字段只能为:'active', 'archived', 'deleted'
- `is_sensitive` 只能为 0 或 1
- `_tenant_id``user_id` 必须有值,用于数据隔离
### 使用场景
1. **短期记忆**: 临时存储对话上下文
2. **长期记忆**: 持久化存储重要信息
3. **工作记忆**: 当前任务相关的活跃记忆
4. **敏感信息**: 标记需要特殊保护的数据
---
## 表2: memory_unit_tag (记忆单元标签表)
### 表说明
用于为记忆单元添加标签,支持灵活的分类和检索。
### 字段说明
| 字段名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | BIGINT(20) | 是 | AUTO_INCREMENT | 主键ID自增 |
| _tenant_id | BIGINT(20) | 是 | - | 租户ID多租户隔离标识 |
| user_id | BIGINT(20) | 是 | - | 用户ID标签创建者 |
| memory_id | BIGINT(20) | 是 | - | 记忆ID关联到memory_unit表 |
| tag_name | VARCHAR(100) | 是 | - | 标签名称 |
| created | DATETIME | 是 | CURRENT_TIMESTAMP | 创建时间 |
| modified | DATETIME | 是 | CURRENT_TIMESTAMP ON UPDATE | 修改时间 |
### 索引说明
| 索引名 | 字段 | 类型 | 说明 |
|--------|------|------|------|
| PRIMARY | id | PRIMARY | 主键索引 |
| idx_tenant_user | _tenant_id, user_id | INDEX | 租户用户联合索引 |
| idx_memory | memory_id | INDEX | 记忆ID索引支持按记忆查询标签 |
| idx_tag | tag_name | INDEX | 标签名称索引,支持按标签查询 |
### 外键约束
| 约束名 | 字段 | 关联表 | 关联字段 | 级联操作 |
|--------|------|--------|----------|----------|
| fk_memory_tag | memory_id | memory_unit | id | ON DELETE CASCADE |
### 级联删除说明
当删除 memory_unit 表中的记录时,会自动删除 memory_unit_tag 表中对应的标签记录。
---
## 表关系
```
memory_unit (1) ──────< (N) memory_unit_tag
一对多关系
一个记忆可以有多个标签
```
---
## 业务规则
### 1. 数据隔离
- 所有数据按 `_tenant_id``user_id` 进行隔离
- 查询时必须包含租户和用户条件
### 2. 状态管理
- 新创建的记忆默认状态为 'active'
- 删除操作采用软删除,将状态设置为 'deleted'
- 过期记忆应归档为 'archived'
### 3. 标签规则
- 一个记忆可以有多个标签
- 标签名称不区分大小写(建议在应用层处理)
- 重复标签由应用层控制
### 4. 敏感信息处理
- 标记为敏感的信息需要特殊处理
- 敏感信息不应在日志中显示完整内容
---
## 性能优化建议
### 1. 分区策略
对于大数据量场景,建议按时间或租户进行分区:
```sql
-- 按创建时间分区(可选)
ALTER TABLE memory_unit PARTITION BY RANGE (TO_DAYS(created)) (
PARTITION p_old VALUES LESS THAN (TO_DAYS('2026-01-01')),
PARTITION p_current VALUES LESS THAN (MAXVALUE)
);
```
### 2. 查询优化
- 使用覆盖索引减少回表
- 避免SELECT *,只查询需要的字段
- 合理使用LIMIT分页
### 3. 数据清理
- 定期清理已删除超过30天的数据
- 清理孤立的标签数据
- 定期执行ANALYZE TABLE和OPTIMIZE TABLE
---
## 数据迁移注意事项
1. **字符集**: 使用 utf8mb4 以支持emoji等特殊字符
2. **时区**: 确保应用服务器和数据库时区一致
3. **JSON字段**: MySQL 5.7+ 支持JSON类型低版本需要使用TEXT
4. **外键**: 如需禁用外键检查(批量导入时):
```sql
SET FOREIGN_KEY_CHECKS=0;
-- 执行导入操作
SET FOREIGN_KEY_CHECKS=1;
```

View File

@@ -0,0 +1,243 @@
# 记忆管理模块 - MySQL表创建脚本使用说明
## 📁 文件说明
### 1. `memory_schema.sql` - 完整版脚本
**包含内容:**
- 完整的表创建语句
- 示例数据插入
- 常用查询示例
- 数据清理和维护脚本
- 性能优化建议
**适用场景:** 开发环境搭建、功能测试、学习参考
### 2. `memory_tables_simple.sql` - 简化版脚本
**包含内容:**
- 纯净的表创建语句
- 必要的索引和约束
**适用场景:** 生产环境部署、快速部署
### 3. `verify_tables.sql` - 验证脚本
**包含内容:**
- 表结构检查
- 索引验证
- 外键约束检查
- 数据完整性检查
**适用场景:** 部署后验证、故障排查
### 4. `DATA_DICTIONARY.md` - 数据字典
**包含内容:**
- 详细的字段说明
- 索引说明
- 业务规则
- 性能优化建议
**适用场景:** 开发参考、文档维护
---
## 🚀 使用步骤
### 步骤1: 选择合适的脚本
```bash
# 开发环境(推荐)
mysql -u root -p < memory_schema.sql
# 生产环境
mysql -u root -p < memory_tables_simple.sql
```
### 步骤2: 验证表创建
```bash
mysql -u root -p your_database < verify_tables.sql
```
### 步骤3: 检查结果
登录MySQL查看
```sql
USE your_database;
SHOW TABLES LIKE 'memory_unit%';
DESCRIBE memory_unit;
DESCRIBE memory_unit_tag;
```
---
## 📋 表结构概览
### memory_unit (记忆单元表)
存储AI Agent的各种记忆数据支持分类、标签、状态管理。
**核心字段:**
- `id`: 主键
- `_tenant_id`, `user_id`: 多租户隔离
- `category`, `sub_category`: 分类体系
- `content_json`: JSON格式存储记忆内容
- `status`: 状态管理
### memory_unit_tag (记忆标签表)
为记忆单元添加灵活的标签支持。
**核心字段:**
- `id`: 主键
- `memory_id`: 关联记忆单元(外键)
- `tag_name`: 标签名称
**关系:** 一个记忆可以有多个标签(一对多)
---
## 🔧 高级配置
### 字符集设置
```sql
-- 确保使用 utf8mb4 支持emoji
ALTER DATABASE your_database CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
```
### 性能优化
```sql
-- 分析表优化查询
ANALYZE TABLE memory_unit;
ANALYZE TABLE memory_unit_tag;
-- 优化表空间
OPTIMIZE TABLE memory_unit;
OPTIMIZE TABLE memory_unit_tag;
```
### 备份恢复
```bash
# 备份
mysqldump -u root -p your_database memory_unit memory_unit_tag > backup.sql
# 恢复
mysql -u root -p your_database < backup.sql
```
---
## 📊 数据示例
### 记忆单元示例
```json
{
"title": "AI项目开发",
"description": "负责AI代理平台的开发工作",
"priority": "高",
"tags": ["重要", "正在进行"],
"metadata": {
"start_date": "2026-01-01",
"end_date": "2026-06-30"
}
}
```
### 标签示例
```
重要、正在进行、技术、学习、运动、日常
```
---
## ⚠️ 注意事项
### 1. 外键约束
- `memory_unit_tag.memory_id` 有外键约束指向 `memory_unit.id`
- 删除记忆会自动删除相关标签CASCADE
### 2. 级联删除
如需临时禁用外键检查(批量操作时):
```sql
SET FOREIGN_KEY_CHECKS=0;
-- 执行批量操作
SET FOREIGN_KEY_CHECKS=1;
```
### 3. JSON字段
- MySQL 5.7+ 原生支持JSON类型
- 提供JSON函数JSON_EXTRACT、JSON_SEARCH等
- 自动验证JSON格式
### 4. 状态管理
- 新建记录默认状态为 'active'
- 删除采用软删除设置status为'deleted'
- 定期清理归档数据
---
## 🔍 常用查询
### 查询用户的活跃记忆
```sql
SELECT * FROM memory_unit
WHERE user_id = 1001
AND status = 'active'
ORDER BY created DESC;
```
### 查询记忆及其标签
```sql
SELECT m.*, t.tag_name
FROM memory_unit m
LEFT JOIN memory_unit_tag t ON m.id = t.memory_id
WHERE m.user_id = 1001
AND m.status = 'active';
```
### 按标签查询记忆
```sql
SELECT DISTINCT m.*
FROM memory_unit m
INNER JOIN memory_unit_tag t ON m.id = t.memory_id
WHERE t.tag_name = '重要'
AND m.status = 'active';
```
### 统计用户记忆数量(按分类)
```sql
SELECT category, COUNT(*) as count
FROM memory_unit
WHERE user_id = 1001
AND status = 'active'
GROUP BY category;
```
---
## 🛠️ 故障排查
### 问题1: 外键约束错误
```sql
-- 检查外键状态
SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND TABLE_NAME = 'memory_unit_tag';
```
### 问题2: 字符编码问题
```sql
-- 检查字符集设置
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation%';
```
### 问题3: JSON格式错误
```sql
-- 验证JSON数据
SELECT id, JSON_VALID(content_json) as is_valid
FROM memory_unit
WHERE content_json IS NOT NULL;
```
---
## 📞 技术支持
如有问题,请参考:
1. `DATA_DICTIONARY.md` - 详细的数据字典
2. `verify_tables.sql` - 表结构验证脚本
3. 项目文档:`README.md`