All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m22s
密码管理:用户自助修改密码(个人中心弹窗)、管理员重置用户密码(审计日志记录) 错误提示:补全火山 ARK 错误码映射(+7 个)、修复创建失败时前端不显示真实错误、 轮询失败走 ERROR_MESSAGES 映射、前端 catch 统一取后端 message Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
4.7 KiB
Python
115 lines
4.7 KiB
Python
from django.contrib.auth.models import AbstractUser
|
|
from django.db import models
|
|
|
|
|
|
class Team(models.Model):
|
|
"""团队模型 — 额度管理的核心单位。"""
|
|
name = models.CharField(max_length=100, unique=True, verbose_name='团队名称')
|
|
total_seconds_pool = models.BigIntegerField(default=0, verbose_name='总额度池(秒)')
|
|
total_seconds_used = models.FloatField(default=0, verbose_name='已消耗总秒数')
|
|
monthly_seconds_limit = models.IntegerField(default=6000, verbose_name='每月消费上限(秒)')
|
|
daily_member_limit_default = models.IntegerField(default=600, verbose_name='新成员默认每日限额(秒)')
|
|
is_active = models.BooleanField(default=True, verbose_name='启用状态')
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
|
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
|
|
|
class Meta:
|
|
verbose_name = '团队'
|
|
verbose_name_plural = '团队'
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@property
|
|
def remaining_seconds(self):
|
|
return self.total_seconds_pool - self.total_seconds_used
|
|
|
|
|
|
class User(AbstractUser):
|
|
"""Extended user model — Phase 5: team-based quota."""
|
|
email = models.EmailField(unique=True, verbose_name='邮箱')
|
|
team = models.ForeignKey(
|
|
Team, on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='members',
|
|
verbose_name='所属团队',
|
|
)
|
|
is_team_admin = models.BooleanField(default=False, verbose_name='团队管理员')
|
|
daily_seconds_limit = models.IntegerField(default=600, verbose_name='每日秒数上限')
|
|
monthly_seconds_limit = models.IntegerField(default=6000, verbose_name='每月秒数上限')
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
|
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
|
|
|
class Meta:
|
|
verbose_name = '用户'
|
|
verbose_name_plural = '用户'
|
|
|
|
def __str__(self):
|
|
return self.username
|
|
|
|
@property
|
|
def role(self):
|
|
if self.is_staff and self.team is None:
|
|
return 'super_admin'
|
|
if self.is_team_admin and self.team is not None:
|
|
return 'team_admin'
|
|
return 'member'
|
|
|
|
|
|
class AdminAuditLog(models.Model):
|
|
"""管理员操作审计日志"""
|
|
ACTION_CHOICES = [
|
|
('team_create', '创建团队'),
|
|
('team_update', '更新团队'),
|
|
('team_topup', '团队充值'),
|
|
('team_set_pool', '设置团队额度池'),
|
|
('team_create_admin', '创建团队管理员'),
|
|
('user_create', '创建用户'),
|
|
('user_quota_update', '更新用户额度'),
|
|
('user_status_toggle', '切换用户状态'),
|
|
('settings_update', '更新系统设置'),
|
|
('member_create', '创建团队成员'),
|
|
('member_quota_update', '更新成员额度'),
|
|
('member_status_toggle', '切换成员状态'),
|
|
('user_password_reset', '重置用户密码'),
|
|
]
|
|
|
|
operator = models.ForeignKey(
|
|
User, on_delete=models.SET_NULL,
|
|
null=True, related_name='audit_logs',
|
|
verbose_name='操作人',
|
|
)
|
|
operator_name = models.CharField(max_length=150, verbose_name='操作人用户名')
|
|
action = models.CharField(max_length=30, choices=ACTION_CHOICES, verbose_name='操作类型')
|
|
target_type = models.CharField(max_length=20, verbose_name='目标类型')
|
|
target_id = models.IntegerField(null=True, blank=True, verbose_name='目标ID')
|
|
target_name = models.CharField(max_length=200, blank=True, default='', verbose_name='目标名称')
|
|
before = models.JSONField(null=True, blank=True, verbose_name='变更前')
|
|
after = models.JSONField(null=True, blank=True, verbose_name='变更后')
|
|
ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP地址')
|
|
created_at = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='操作时间')
|
|
|
|
class Meta:
|
|
verbose_name = '审计日志'
|
|
verbose_name_plural = '审计日志'
|
|
ordering = ['-created_at']
|
|
|
|
def __str__(self):
|
|
return f'{self.operator_name} - {self.get_action_display()} - {self.target_name}'
|
|
|
|
|
|
def log_admin_action(request, action, target_type, target_id=None, target_name='', before=None, after=None):
|
|
"""记录管理员操作日志"""
|
|
ip = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')[0].strip() or request.META.get('REMOTE_ADDR')
|
|
AdminAuditLog.objects.create(
|
|
operator=request.user,
|
|
operator_name=request.user.username,
|
|
action=action,
|
|
target_type=target_type,
|
|
target_id=target_id,
|
|
target_name=target_name,
|
|
before=before,
|
|
after=after,
|
|
ip_address=ip,
|
|
)
|