lty/qy_lty/aiapp/models.py
pmc 30c7caff41 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 模型层
2026-05-07 17:34:38 +08:00

93 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.db import models
from userapp.models import ParadiseUser
class Bot(models.Model):
name = models.CharField(max_length=255, verbose_name='机器人姓名')
description = models.TextField(blank=False, null=False, verbose_name='机器人提示词')
class Meta:
verbose_name = '机器人'
verbose_name_plural = '机器人'
def __str__(self):
return self.name
class ChatMessage(models.Model):
SENDER_USER = 'user'
SENDER_BOT = 'assistant'
SENDER_SYSTEM = 'system'
SENDER_CHOICES = [
(SENDER_USER, 'user'),
(SENDER_BOT, 'assistant'),
(SENDER_SYSTEM, 'system'),
]
MESSAGE_TYPE_TEXT = 'text'
MESSAGE_TYPE_AUDIO = 'audio'
MESSAGE_TYPE_VIDEO = 'video'
MESSAGE_TYPE_CHOICES = [
(MESSAGE_TYPE_TEXT, 'text'),
(MESSAGE_TYPE_AUDIO, 'audio'),
(MESSAGE_TYPE_VIDEO, 'video'),
]
user = models.ForeignKey(ParadiseUser, on_delete=models.CASCADE, verbose_name='用户')
bot = models.ForeignKey(Bot, on_delete=models.CASCADE, verbose_name='机器人')
message = models.TextField(max_length=2048, null=False, blank=False, verbose_name='消息内容')
message_audio_url = models.TextField(max_length=2048, blank=True, verbose_name='消息内容语音链接')
message_video_url = models.TextField(max_length=2048, blank=True, verbose_name='消息内容视频链接')
timestamp = models.DateTimeField(auto_now_add=True, verbose_name='时间戳')
sender = models.CharField(max_length=10, choices=SENDER_CHOICES, verbose_name='发送者')
message_type = models.CharField(max_length=10, choices=MESSAGE_TYPE_CHOICES, default=MESSAGE_TYPE_TEXT, verbose_name='消息类型')
class Meta:
ordering = ['timestamp'] # 按时间顺序排序
verbose_name = '聊天消息'
verbose_name_plural = '聊天消息'
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