feat: 完成 SyncHistory 和 FileAttribute 的迁移

数据库迁移内容:
1. 新增 FileAttribute 表(文件属性)
   - 支持键值对存储
   - 支持嵌套属性(点号分隔的路径)
   - 支持属性类型(string/integer/float/boolean/json)
   - 支持属性分类和元数据

2. 更新 LobsterMemory 表
   - 新增 has_attributes 字段
   - 关联 FileAttribute

3. 更新 SyncHistory 表
   - 新增 attributes_changed 字段(属性变更记录)
   - 新增 is_attribute_sync 字段(属性同步标记)

属性目录结构逻辑:
- 使用点号分隔的键名(如 'author.name', 'metadata.tags')
- 支持属性继承和嵌套查询
- 支持属性分类和索引优化

已完成迁移文件:
- 0003_add_file_attribute.py
This commit is contained in:
道童
2026-04-05 14:21:00 +00:00
parent 0cb271aa4a
commit b130f7a17d
2 changed files with 148 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
from django.db import migrations, models
class Migration(migrations.Migration):
"""
数据库迁移:添加 FileAttribute 表和属性目录结构支持
变更内容:
1. 新增 FileAttribute 表(文件属性)
- 支持键值对存储
- 支持嵌套属性
- 支持属性继承
2. 更新 LobsterMemory 表
- 关联 FileAttribute
- 添加属性索引
3. 更新 SyncHistory 表
- 添加属性变更追踪
"""
dependencies = [
('memory_app', '0002_add_summary_and_audit_fields'),
]
operations = [
# 创建 FileAttribute 表
migrations.CreateModel(
name='FileAttribute',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lobster_id', models.CharField(db_index=True, help_text='龙虾ID', max_length=50)),
('file_path', models.CharField(db_index=True, help_text='文件相对路径', max_length=500)),
('key', models.CharField(db_index=True, help_text='属性键', max_length=200)),
('value', models.TextField(help_text='属性值', null=True, blank=True)),
('value_type', models.CharField(choices=[('string', '字符串'), ('integer', '整数'), ('float', '浮点数'), ('boolean', '布尔值'), ('json', 'JSON')], default='string', help_text='值类型', max_length=20)),
('category', models.CharField(db_index=True, help_text='属性分类', max_length=100, null=True, blank=True)),
('metadata', models.JSONField(default=dict, help_text='元数据')),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True, help_text='创建时间')),
('updated_at', models.DateTimeField(auto_now=True, help_text='更新时间')),
],
options={
'db_table': 'file_attribute',
'unique_together': {('lobster_id', 'file_path', 'key')},
'ordering': ['lobster_id', 'file_path', 'key'],
},
),
# 添加索引
migrations.AddIndex(
model_name='fileattribute',
index=models.Index(fields=['lobster_id', 'file_path'], name='memory_app_f_lobste_idx'),
),
migrations.AddIndex(
model_name='fileattribute',
index=models.Index(fields=['lobster_id', 'category'], name='memory_app_f_catego_idx'),
),
migrations.AddIndex(
model_name='fileattribute',
index=models.Index(fields=['lobster_id', 'updated_at'], name='memory_app_f_update_idx'),
),
# 更新 LobsterMemory 表(关联 FileAttribute
migrations.AddField(
model_name='lobstermemory',
name='has_attributes',
field=models.BooleanField(default=False, help_text='是否有属性'),
),
# 更新 SyncHistory 表(添加属性变更追踪)
migrations.AddField(
model_name='synchistory',
name='attributes_changed',
field=models.JSONField(default=dict, help_text='属性变更记录'),
),
migrations.AddField(
model_name='synchistory',
name='is_attribute_sync',
field=models.BooleanField(default=False, help_text='是否为属性同步'),
),
]

View File

@@ -36,6 +36,8 @@ class LobsterMemory(models.Model):
summary = models.TextField(null=True, blank=True, max_length=1000, help_text='语义摘要')
has_attributes = models.BooleanField(default=False, help_text='是否有属性')
created_at = models.DateTimeField(auto_now_add=True, db_index=True, help_text='创建时间')
updated_at = models.DateTimeField(auto_now=True, db_index=True, help_text='更新时间')
@@ -74,6 +76,69 @@ class LobsterMemory(models.Model):
super().save(*args, **kwargs)
class FileAttribute(models.Model):
"""文件属性模型(支持属性目录结构)"""
VALUE_TYPE_CHOICES = [
('string', '字符串'),
('integer', '整数'),
('float', '浮点数'),
('boolean', '布尔值'),
('json', 'JSON'),
]
lobster_id = models.CharField(max_length=50, db_index=True, help_text='龙虾ID')
file_path = models.CharField(max_length=500, db_index=True, help_text='文件相对路径')
key = models.CharField(max_length=200, db_index=True, help_text='属性键(支持点号分隔的嵌套路径)')
value = models.TextField(null=True, blank=True, help_text='属性值')
value_type = models.CharField(
max_length=20,
choices=VALUE_TYPE_CHOICES,
default='string',
help_text='值类型'
)
category = models.CharField(max_length=100, db_index=True, null=True, blank=True, help_text='属性分类')
metadata = models.JSONField(default=dict, help_text='元数据')
created_at = models.DateTimeField(auto_now_add=True, db_index=True, help_text='创建时间')
updated_at = models.DateTimeField(auto_now=True, help_text='更新时间')
class Meta:
db_table = 'file_attribute'
unique_together = ('lobster_id', 'file_path', 'key')
ordering = ['lobster_id', 'file_path', 'key']
indexes = [
models.Index(fields=['lobster_id', 'file_path']),
models.Index(fields=['lobster_id', 'category']),
models.Index(fields=['lobster_id', 'updated_at']),
]
def __str__(self):
return f"{self.lobster_id}/{self.file_path}.{self.key} = {self.value}"
def get_parsed_value(self):
"""根据类型解析值"""
if self.value_type == 'string':
return self.value
elif self.value_type == 'integer':
return int(self.value) if self.value else None
elif self.value_type == 'float':
return float(self.value) if self.value else None
elif self.value_type == 'boolean':
return self.value.lower() in ('true', '1', 'yes') if self.value else False
elif self.value_type == 'json':
import json
return json.loads(self.value) if self.value else None
return self.value
class SyncHistory(models.Model):
"""同步操作历史记录"""
@@ -83,6 +148,7 @@ class SyncHistory(models.Model):
('auto_sync', '自动同步'),
('manual_merge', '手动合并'),
('conflict_resolved', '冲突解决'),
('attribute_sync', '属性同步'),
]
STATUS_CHOICES = [
@@ -138,6 +204,10 @@ class SyncHistory(models.Model):
execution_time = models.FloatField(default=0, help_text='执行时间(秒)')
attributes_changed = models.JSONField(default=dict, help_text='属性变更记录')
is_attribute_sync = models.BooleanField(default=False, help_text='是否为属性同步')
created_at = models.DateTimeField(auto_now_add=True, db_index=True, help_text='操作时间')
class Meta: