"""开放平台模型: APIKey 外部系统访问令牌(哈希安全存储、Scope 细粒度权限控制、调用频率与最后使用审计) """ import hashlib import secrets from django.db import models from apps.core.base_models import TenantScopedModel class APIKey(TenantScopedModel): """外部第三方对接 API Key。""" name = models.CharField(max_length=128, help_text="接入方系统名称,如'下游分销A系统'") prefix = models.CharField(max_length=16, db_index=True, help_text="Key前缀用于检索") hashed_key = models.CharField(max_length=128, help_text="SHA256 哈希值") # 权限范围:["products:read", "stocks:read", "orders:write"] scopes = models.JSONField(default=list, blank=True, help_text="授权的作用域列表") is_active = models.BooleanField(default=True) rate_limit = models.IntegerField(default=120, help_text="每分钟最大请求次数") expires_at = models.DateTimeField(null=True, blank=True, help_text="过期时间") last_used_at = models.DateTimeField(null=True, blank=True, help_text="最后一次调用时间") class Meta: db_table = "openapi_api_key" verbose_name_plural = "开放平台APIKey" ordering = ["-created_at"] def __str__(self): return f"{self.name} ({self.prefix}****)" @classmethod def generate(cls, tenant, name: str, scopes: list = None, expires_at=None, created_by=None): """生成并安全存储一个新的 API Key,返回 (instance, raw_key_string)。 注意:raw_key_string 仅在创建时返回一次! """ raw_secret = secrets.token_urlsafe(32) prefix = f"dh_{secrets.token_hex(4)}" raw_key = f"{prefix}.{raw_secret}" hashed_key = hashlib.sha256(raw_key.encode("utf-8")).hexdigest() instance = cls.objects.create( tenant=tenant, name=name, prefix=prefix, hashed_key=hashed_key, scopes=scopes or ["products:read", "stocks:read", "orders:write"], expires_at=expires_at, created_by=created_by, ) return instance, raw_key def verify_key(self, raw_key: str) -> bool: """校验原始 Key 与哈希是否匹配。""" computed = hashlib.sha256(raw_key.encode("utf-8")).hexdigest() return secrets.compare_digest(self.hashed_key, computed)