mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/ - 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层 - 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构 - 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations) - 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image - 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
420
_deprecated_py_src/services/management_token/service.py
Normal file
420
_deprecated_py_src/services/management_token/service.py
Normal file
@@ -0,0 +1,420 @@
|
||||
"""Management Token 服务"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.config.settings import config
|
||||
from src.core.logger import logger
|
||||
from src.models.database import ManagementToken
|
||||
|
||||
|
||||
def validate_ip_list(ips: list[str] | None) -> list[str] | None:
|
||||
"""验证 IP 白名单格式
|
||||
|
||||
- None: 不限制 IP
|
||||
- 非空列表: 只允许列表中的 IP
|
||||
- 空列表: 不允许(会抛出错误)
|
||||
"""
|
||||
if ips is None:
|
||||
return None
|
||||
if len(ips) == 0:
|
||||
raise ValueError("IP 白名单不能为空列表,如需取消限制请不提供此字段")
|
||||
|
||||
validated = []
|
||||
for i, ip_str in enumerate(ips):
|
||||
original = ip_str
|
||||
ip_str = ip_str.strip()
|
||||
if not ip_str:
|
||||
raise ValueError(f"IP 白名单第 {i + 1} 项为空")
|
||||
try:
|
||||
if "/" in ip_str:
|
||||
ipaddress.ip_network(ip_str, strict=False)
|
||||
else:
|
||||
ipaddress.ip_address(ip_str)
|
||||
validated.append(ip_str)
|
||||
except ValueError:
|
||||
raise ValueError(f"无效的 IP 地址或 CIDR: {original}")
|
||||
|
||||
if not validated:
|
||||
raise ValueError("IP 白名单不能为空,如需取消限制请不提供此字段")
|
||||
|
||||
return validated
|
||||
|
||||
|
||||
def parse_expires_at(v: Any, allow_past: bool = False) -> datetime | None:
|
||||
"""解析过期时间,确保时区安全
|
||||
|
||||
前端 datetime-local 输入返回本地时间字符串(无时区信息)。
|
||||
后端要求:
|
||||
- 如果是字符串且不含时区,视为 UTC
|
||||
- 如果是 datetime 且无时区,视为 UTC
|
||||
- 带时区的输入直接使用
|
||||
- 默认要求过期时间必须在未来(allow_past=False)
|
||||
|
||||
Args:
|
||||
v: 时间值(字符串或 datetime)
|
||||
allow_past: 是否允许过去的时间(用于清除过期时间等场景)
|
||||
|
||||
Returns:
|
||||
解析后的 datetime 或 None
|
||||
"""
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, str):
|
||||
if not v.strip():
|
||||
return None
|
||||
try:
|
||||
dt = datetime.fromisoformat(v.replace("Z", "+00:00"))
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无效的时间格式: {v}") from e
|
||||
elif isinstance(v, datetime):
|
||||
dt = v
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
else:
|
||||
raise ValueError(f"不支持的时间类型: {type(v)}")
|
||||
|
||||
if not allow_past and dt <= datetime.now(timezone.utc):
|
||||
raise ValueError("过期时间必须在未来")
|
||||
|
||||
return dt
|
||||
|
||||
|
||||
def token_to_dict(
|
||||
token: ManagementToken,
|
||||
raw_token: str | None = None,
|
||||
include_user: bool = False,
|
||||
) -> dict:
|
||||
"""将 ManagementToken 转换为字典
|
||||
|
||||
Args:
|
||||
token: ManagementToken 实例
|
||||
raw_token: 明文 Token(仅在创建/重新生成时提供)
|
||||
include_user: 是否包含用户信息(管理员视图使用)
|
||||
|
||||
Returns:
|
||||
Token 字典表示
|
||||
"""
|
||||
result = {
|
||||
"id": token.id,
|
||||
"user_id": token.user_id,
|
||||
"name": token.name,
|
||||
"description": token.description,
|
||||
"token_display": token.get_display_token(),
|
||||
"allowed_ips": token.allowed_ips,
|
||||
"expires_at": token.expires_at.isoformat() if token.expires_at else None,
|
||||
"last_used_at": token.last_used_at.isoformat() if token.last_used_at else None,
|
||||
"last_used_ip": token.last_used_ip,
|
||||
"usage_count": token.usage_count,
|
||||
"is_active": token.is_active,
|
||||
"created_at": token.created_at.isoformat() if token.created_at else None,
|
||||
"updated_at": token.updated_at.isoformat() if token.updated_at else None,
|
||||
}
|
||||
if raw_token:
|
||||
result["token"] = raw_token
|
||||
if include_user and token.user:
|
||||
result["user"] = {
|
||||
"id": token.user.id,
|
||||
"email": token.user.email,
|
||||
"username": token.user.username,
|
||||
"role": token.user.role.value if token.user.role else None,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
class ManagementTokenService:
|
||||
"""Management Token 服务类"""
|
||||
|
||||
@staticmethod
|
||||
def create_token(
|
||||
db: Session,
|
||||
user_id: str,
|
||||
name: str,
|
||||
description: str | None = None,
|
||||
allowed_ips: list[str] | None = None,
|
||||
expires_at: datetime | None = None,
|
||||
) -> tuple[ManagementToken, str]:
|
||||
"""创建 Management Token
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user_id: 用户 ID
|
||||
name: Token 名称
|
||||
description: 描述
|
||||
allowed_ips: IP 白名单
|
||||
expires_at: 过期时间
|
||||
|
||||
Returns:
|
||||
(ManagementToken, 明文 Token) 元组
|
||||
|
||||
Raises:
|
||||
ValueError: 如果名称已存在或超过数量限制
|
||||
"""
|
||||
# 检查用户 Token 数量限制
|
||||
token_count = int(
|
||||
db.query(func.count(ManagementToken.id))
|
||||
.filter(ManagementToken.user_id == user_id)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
max_tokens = config.management_token_max_per_user
|
||||
if token_count >= max_tokens:
|
||||
raise ValueError(f"已达到 Token 数量上限({max_tokens})")
|
||||
|
||||
# 检查名称是否已存在
|
||||
existing = (
|
||||
db.query(ManagementToken)
|
||||
.filter(ManagementToken.user_id == user_id, ManagementToken.name == name)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
raise ValueError(f"已存在名为 '{name}' 的 Token")
|
||||
|
||||
# 生成 Token
|
||||
raw_token = ManagementToken.generate_token()
|
||||
|
||||
# 创建记录
|
||||
token = ManagementToken(
|
||||
user_id=user_id,
|
||||
name=name,
|
||||
description=description,
|
||||
allowed_ips=allowed_ips,
|
||||
expires_at=expires_at,
|
||||
)
|
||||
token.set_token(raw_token)
|
||||
|
||||
db.add(token)
|
||||
try:
|
||||
db.commit()
|
||||
except IntegrityError:
|
||||
db.rollback()
|
||||
# 并发创建导致唯一约束冲突
|
||||
raise ValueError(f"已存在名为 '{name}' 的 Token")
|
||||
db.refresh(token)
|
||||
|
||||
logger.info(f"创建 Management Token: {token.id} for user {user_id}")
|
||||
|
||||
return token, raw_token
|
||||
|
||||
@staticmethod
|
||||
def get_token_by_id(
|
||||
db: Session, token_id: str, user_id: str | None = None
|
||||
) -> ManagementToken | None:
|
||||
"""根据 ID 获取 Token
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
token_id: Token ID
|
||||
user_id: 用户 ID(如果提供,则只查询该用户的 Token)
|
||||
|
||||
Returns:
|
||||
ManagementToken 或 None
|
||||
"""
|
||||
query = db.query(ManagementToken).filter(ManagementToken.id == token_id)
|
||||
if user_id:
|
||||
query = query.filter(ManagementToken.user_id == user_id)
|
||||
return query.first()
|
||||
|
||||
@staticmethod
|
||||
def list_tokens(
|
||||
db: Session,
|
||||
user_id: str | None = None,
|
||||
is_active: bool | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> tuple[list[ManagementToken], int]:
|
||||
"""列出 Tokens
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user_id: 用户 ID(如果提供,则只查询该用户的 Token)
|
||||
is_active: 筛选激活状态
|
||||
skip: 跳过记录数
|
||||
limit: 返回记录数
|
||||
|
||||
Returns:
|
||||
(Token 列表, 总数) 元组
|
||||
"""
|
||||
query = db.query(ManagementToken)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(ManagementToken.user_id == user_id)
|
||||
if is_active is not None:
|
||||
query = query.filter(ManagementToken.is_active == is_active)
|
||||
|
||||
total = int(query.with_entities(func.count(ManagementToken.id)).scalar() or 0)
|
||||
tokens = query.order_by(ManagementToken.created_at.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
return tokens, total
|
||||
|
||||
@staticmethod
|
||||
def update_token(
|
||||
db: Session,
|
||||
token_id: str,
|
||||
user_id: str | None = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
allowed_ips: list[str] | None = None,
|
||||
expires_at: datetime | None = None,
|
||||
is_active: bool | None = None,
|
||||
clear_description: bool = False,
|
||||
clear_allowed_ips: bool = False,
|
||||
clear_expires_at: bool = False,
|
||||
) -> ManagementToken | None:
|
||||
"""更新 Token
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
token_id: Token ID
|
||||
user_id: 用户 ID(如果提供,则只更新该用户的 Token)
|
||||
name: 新名称
|
||||
description: 新描述
|
||||
allowed_ips: 新 IP 白名单
|
||||
expires_at: 新过期时间
|
||||
is_active: 新激活状态
|
||||
clear_description: 是否清空描述(True 时 description 被忽略)
|
||||
clear_allowed_ips: 是否清空 IP 白名单(True 时 allowed_ips 被忽略)
|
||||
clear_expires_at: 是否清空过期时间(True 时 expires_at 被忽略)
|
||||
|
||||
Returns:
|
||||
更新后的 ManagementToken 或 None
|
||||
|
||||
Raises:
|
||||
ValueError: 如果新名称已被其他 Token 使用
|
||||
"""
|
||||
token = ManagementTokenService.get_token_by_id(db, token_id, user_id)
|
||||
if not token:
|
||||
return None
|
||||
|
||||
# 如果更新名称,检查是否与其他 Token 冲突
|
||||
if name is not None and name != token.name:
|
||||
existing = (
|
||||
db.query(ManagementToken)
|
||||
.filter(
|
||||
ManagementToken.user_id == token.user_id,
|
||||
ManagementToken.name == name,
|
||||
ManagementToken.id != token_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
raise ValueError(f"已存在名为 '{name}' 的 Token")
|
||||
token.name = name
|
||||
|
||||
# 处理 description:支持清空
|
||||
if clear_description:
|
||||
token.description = None
|
||||
elif description is not None:
|
||||
token.description = description
|
||||
|
||||
# 处理 allowed_ips:支持清空
|
||||
if clear_allowed_ips:
|
||||
token.allowed_ips = None
|
||||
elif allowed_ips is not None:
|
||||
token.allowed_ips = allowed_ips if allowed_ips else None
|
||||
|
||||
# 处理 expires_at:支持清空
|
||||
if clear_expires_at:
|
||||
token.expires_at = None
|
||||
elif expires_at is not None:
|
||||
token.expires_at = expires_at
|
||||
|
||||
if is_active is not None:
|
||||
token.is_active = is_active
|
||||
|
||||
db.commit()
|
||||
db.refresh(token)
|
||||
|
||||
logger.info(f"更新 Management Token: {token.id}")
|
||||
|
||||
return token
|
||||
|
||||
@staticmethod
|
||||
def delete_token(db: Session, token_id: str, user_id: str | None = None) -> bool:
|
||||
"""删除 Token
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
token_id: Token ID
|
||||
user_id: 用户 ID(如果提供,则只删除该用户的 Token)
|
||||
|
||||
Returns:
|
||||
是否删除成功
|
||||
"""
|
||||
token = ManagementTokenService.get_token_by_id(db, token_id, user_id)
|
||||
if not token:
|
||||
return False
|
||||
|
||||
db.delete(token)
|
||||
db.commit()
|
||||
|
||||
logger.info(f"删除 Management Token: {token_id}")
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def toggle_status(
|
||||
db: Session, token_id: str, user_id: str | None = None
|
||||
) -> ManagementToken | None:
|
||||
"""切换 Token 状态
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
token_id: Token ID
|
||||
user_id: 用户 ID(如果提供,则只操作该用户的 Token)
|
||||
|
||||
Returns:
|
||||
更新后的 ManagementToken 或 None
|
||||
"""
|
||||
token = ManagementTokenService.get_token_by_id(db, token_id, user_id)
|
||||
if not token:
|
||||
return None
|
||||
|
||||
token.is_active = not token.is_active
|
||||
db.commit()
|
||||
db.refresh(token)
|
||||
|
||||
logger.info(f"切换 Management Token 状态: {token.id} -> {token.is_active}")
|
||||
|
||||
return token
|
||||
|
||||
@staticmethod
|
||||
def regenerate_token(
|
||||
db: Session, token_id: str, user_id: str | None = None
|
||||
) -> tuple[ManagementToken | None, str | None, str | None]:
|
||||
"""重新生成 Token
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
token_id: Token ID
|
||||
user_id: 用户 ID(如果提供,则只操作该用户的 Token)
|
||||
|
||||
Returns:
|
||||
(ManagementToken, 新的明文 Token, 旧的 token_hash) 元组,失败返回 (None, None, None)
|
||||
"""
|
||||
token = ManagementTokenService.get_token_by_id(db, token_id, user_id)
|
||||
if not token:
|
||||
return None, None, None
|
||||
|
||||
# 保存旧的 token_hash 用于审计
|
||||
old_token_hash = token.token_hash
|
||||
|
||||
# 生成新 Token
|
||||
raw_token = ManagementToken.generate_token()
|
||||
token.set_token(raw_token)
|
||||
|
||||
db.commit()
|
||||
db.refresh(token)
|
||||
|
||||
logger.info(f"重新生成 Management Token: {token.id}")
|
||||
|
||||
return token, raw_token, old_token_hash
|
||||
Reference in New Issue
Block a user