From b130f7a17d981df1b063de77eb733859e670dbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=81=93=E7=AB=A5?= Date: Sun, 5 Apr 2026 14:21:00 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=20SyncHistory=20?= =?UTF-8?q?=E5=92=8C=20FileAttribute=20=E7=9A=84=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 数据库迁移内容: 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 --- .../migrations/0003_add_file_attribute.py | 78 +++++++++++++++++++ backend/memory_app/models.py | 70 +++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 backend/memory_app/migrations/0003_add_file_attribute.py diff --git a/backend/memory_app/migrations/0003_add_file_attribute.py b/backend/memory_app/migrations/0003_add_file_attribute.py new file mode 100644 index 0000000..ae48a6a --- /dev/null +++ b/backend/memory_app/migrations/0003_add_file_attribute.py @@ -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='是否为属性同步'), + ), + ] \ No newline at end of file diff --git a/backend/memory_app/models.py b/backend/memory_app/models.py index b20b88a..375dcf6 100644 --- a/backend/memory_app/models.py +++ b/backend/memory_app/models.py @@ -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: