Backend (Django 4.2 + DRF): - Admin auth with SimpleJWT - Volcengine API client with HMAC-SHA256 signing - IAM user management (create/sync/import/disable/enable) - Billing query with pagination - Feishu webhook notifications (async) - APScheduler for periodic spending checks - AES-256 encrypted credential storage - API key auth for external system integration Frontend (Vue 3 + Element Plus): - Login page - Dashboard with stats overview - IAM user list with per-user threshold config - Billing view with spending progress bars - Alert history with type filtering - Settings page for global config and Volcengine account management Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
149 lines
6.5 KiB
Python
149 lines
6.5 KiB
Python
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}"
|