feat(01-01): aiapp 新增 CredentialSlot 单例模型

- 在 aiapp/models.py 末尾追加 CredentialSlot(不动 Bot / ChatMessage)
- 字段:app_id CharField(128) / access_token CharField(512) / updated_at auto_now
- 单例三件套:pk=1 + save() 钩子重定向 + get_solo() 类方法(1:1 复刻 AffinitySetting)
- 不引入 gettext_lazy / created_at,沿用仓库中文 verbose_name 实操约定
- 覆盖需求 CRED-01 模型层
This commit is contained in:
pmc 2026-05-07 17:34:38 +08:00
parent a9c25eb2ac
commit 30c7caff41

View File

@ -50,3 +50,44 @@ class ChatMessage(models.Model):
def __str__(self):
return f"{self.sender}: {self.message[:50]} ({self.timestamp})"
class CredentialSlot(models.Model):
"""通用凭据槽位(单例)— Milestone v1.0 / Phase 1
全局唯一一条记录存第三方服务Kimi / 阿里云 / 火山等 APP ID + Access Token
通过 pk=1 + get_or_create + save() 钩子三件套保证单例
- 任何 .save() 在已有记录时把新对象 pk 改成现有那条
- get_solo() 是单一访问入口Phase 2/3 视图统一调用
- DB 层无额外约束绕过 ORM bulk_create / 原始 SQL 不在保护范围
"""
app_id = models.CharField(
'APP ID', max_length=128, blank=True, default='',
help_text='第三方服务商分配的 APP ID运营在 Admin 录入'
)
access_token = models.CharField(
'Access Token', max_length=512, blank=True, default='',
help_text='第三方服务商访问令牌DB 明文存储Admin 列表/查看态末 4 位脱敏'
)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '凭据槽位'
verbose_name_plural = '凭据槽位'
def __str__(self):
return f"凭据槽位 (updated {self.updated_at:%Y-%m-%d %H:%M})"
def save(self, *args, **kwargs):
# 强制单例:新增时如果已有记录则覆盖到现有 pk
if not self.pk and CredentialSlot.objects.exists():
existing = CredentialSlot.objects.first()
self.pk = existing.pk
super().save(*args, **kwargs)
@classmethod
def get_solo(cls):
"""获取单例实例不存在则用默认值创建pk=1"""
instance, _ = cls.objects.get_or_create(pk=1)
return instance