from django.db import models class VolcAccount(models.Model): """火山引擎主账号配置(加密存储)""" name = models.CharField('账号名称', max_length=100, default='默认主账号') access_key_enc = models.TextField('AccessKey(加密)', blank=True) secret_key_enc = models.TextField('SecretKey(加密)', blank=True) access_key_hint = models.CharField('AK 提示(前4后4)', max_length=20, blank=True) is_active = models.BooleanField('启用', default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = '火山主账号' verbose_name_plural = '火山主账号' db_table = 'airgate_volc_account' def __str__(self): return f"{self.name} ({self.access_key_hint})" class IAMUser(models.Model): """受管理的 IAM 子账号""" class Status(models.TextChoices): ACTIVE = 'active', '正常' DISABLED = 'disabled', '已停用' UNKNOWN = 'unknown', '未知' volc_account = models.ForeignKey(VolcAccount, on_delete=models.CASCADE, related_name='iam_users') username = models.CharField('IAM 用户名', max_length=200, db_index=True) display_name = models.CharField('显示名', max_length=200, blank=True) user_id = models.CharField('火山 UserID', max_length=100, blank=True) email = models.EmailField('邮箱', blank=True) phone = models.CharField('手机号', max_length=20, blank=True) project_name = models.CharField('关联项目名', max_length=200, blank=True, help_text='用于按项目维度追踪消费') status = models.CharField('状态', max_length=20, choices=Status.choices, default=Status.UNKNOWN) # Access keys (stored as JSON list of AK IDs, not secrets) access_key_ids = models.JSONField('AccessKey ID 列表', default=list, blank=True) # Monitoring config monitor_enabled = models.BooleanField('启用消费监控', default=True) auto_disable_enabled = models.BooleanField('启用自动停用', default=True) alert_threshold = models.DecimalField('告警阈值(元)', max_digits=12, decimal_places=2, null=True, blank=True, help_text='为空则使用全局默认值') disable_threshold = models.DecimalField('停用阈值(元)', max_digits=12, decimal_places=2, null=True, blank=True, help_text='为空则使用全局默认值') # Spending cache current_month_spending = models.DecimalField('本月消费(元)', max_digits=12, decimal_places=2, default=0) spending_updated_at = models.DateTimeField('消费更新时间', null=True, blank=True) remark = models.TextField('备注', blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = 'IAM 子账号' verbose_name_plural = 'IAM 子账号' db_table = 'airgate_iam_user' unique_together = [('volc_account', 'username')] def __str__(self): return f"{self.display_name or self.username} ({self.status})" def get_alert_threshold(self): if self.alert_threshold is not None: return self.alert_threshold config = GlobalConfig.get_solo() return config.default_alert_threshold def get_disable_threshold(self): if self.disable_threshold is not None: return self.disable_threshold config = GlobalConfig.get_solo() return config.default_disable_threshold class GlobalConfig(models.Model): """全局配置(单例)""" default_alert_threshold = models.DecimalField('默认告警阈值(元)', max_digits=12, decimal_places=2, default=1000) default_disable_threshold = models.DecimalField('默认停用阈值(元)', max_digits=12, decimal_places=2, default=5000) monitor_interval_seconds = models.IntegerField('监控间隔(秒)', default=3600) feishu_webhook_url = models.URLField('飞书 Webhook URL', max_length=500, blank=True) feishu_alert_mobiles = models.CharField('飞书通知手机号(逗号分隔)', max_length=500, blank=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = '全局配置' verbose_name_plural = '全局配置' db_table = 'airgate_global_config' @classmethod def get_solo(cls): obj, _ = cls.objects.get_or_create(pk=1) return obj def __str__(self): return '全局配置' class AlertRecord(models.Model): """告警记录""" class AlertType(models.TextChoices): WARNING = 'warning', '告警' DISABLE = 'disable', '自动停用' ERROR = 'error', '错误' MANUAL = 'manual', '手动操作' iam_user = models.ForeignKey(IAMUser, on_delete=models.CASCADE, related_name='alerts', null=True, blank=True) alert_type = models.CharField('告警类型', max_length=20, choices=AlertType.choices) title = models.CharField('标题', max_length=200) content = models.TextField('详情') spending_amount = models.DecimalField('触发时消费金额', max_digits=12, decimal_places=2, null=True, blank=True) threshold_amount = models.DecimalField('触发阈值', max_digits=12, decimal_places=2, null=True, blank=True) notified = models.BooleanField('已通知', default=False) created_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name = '告警记录' verbose_name_plural = '告警记录' db_table = 'airgate_alert_record' ordering = ['-created_at'] def __str__(self): return f"[{self.alert_type}] {self.title}" class SpendingRecord(models.Model): """月度消费快照""" iam_user = models.ForeignKey(IAMUser, on_delete=models.CASCADE, related_name='spending_records') bill_period = models.CharField('账期 (YYYY-MM)', max_length=7, db_index=True) amount = models.DecimalField('消费金额(元)', max_digits=12, decimal_places=2, default=0) detail = models.JSONField('消费明细', default=dict, blank=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = '消费记录' verbose_name_plural = '消费记录' db_table = 'airgate_spending_record' unique_together = [('iam_user', 'bill_period')] def __str__(self): return f"{self.iam_user.username} {self.bill_period}: ¥{self.amount}"