Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
API Token Service
|
||||
"""
|
||||
import hashlib
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
from sqlalchemy import select, func, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.api_token.model import ApiToken
|
||||
|
||||
TOKEN_PREFIX = "zqpat_"
|
||||
|
||||
|
||||
def generate_token() -> str:
|
||||
"""生成随机API Token"""
|
||||
return TOKEN_PREFIX + secrets.token_hex(32)
|
||||
|
||||
|
||||
def hash_token(token: str) -> str:
|
||||
"""对token进行SHA-256哈希"""
|
||||
return hashlib.sha256(token.encode()).hexdigest()
|
||||
|
||||
|
||||
def get_token_display_prefix(token: str) -> str:
|
||||
"""获取token的展示前缀(前12位 + ...)"""
|
||||
return token[:12] + "..."
|
||||
|
||||
|
||||
class ApiTokenService:
|
||||
"""API Token 服务层"""
|
||||
|
||||
@classmethod
|
||||
async def create_token(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
user_id: str,
|
||||
name: str,
|
||||
expires_at: Optional[datetime] = None,
|
||||
description: Optional[str] = None,
|
||||
) -> Tuple[ApiToken, str]:
|
||||
"""
|
||||
创建API Token
|
||||
|
||||
:return: (数据库记录, 明文token) - 明文token仅返回一次
|
||||
"""
|
||||
raw_token = generate_token()
|
||||
|
||||
db_obj = ApiToken(
|
||||
name=name,
|
||||
token_hash=hash_token(raw_token),
|
||||
token_prefix=get_token_display_prefix(raw_token),
|
||||
user_id=user_id,
|
||||
expires_at=expires_at,
|
||||
description=description,
|
||||
is_active=True,
|
||||
sys_creator_id=user_id,
|
||||
)
|
||||
|
||||
db.add(db_obj)
|
||||
await db.commit()
|
||||
await db.refresh(db_obj)
|
||||
return db_obj, raw_token
|
||||
|
||||
@classmethod
|
||||
async def get_user_tokens(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
user_id: str,
|
||||
) -> List[ApiToken]:
|
||||
"""获取用户的所有API Token"""
|
||||
result = await db.execute(
|
||||
select(ApiToken)
|
||||
.where(
|
||||
ApiToken.user_id == user_id,
|
||||
ApiToken.is_deleted == False, # noqa: E712
|
||||
)
|
||||
.order_by(desc(ApiToken.sys_create_datetime))
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def get_token_by_id(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
token_id: str,
|
||||
user_id: str,
|
||||
) -> Optional[ApiToken]:
|
||||
"""根据ID获取Token(限定用户)"""
|
||||
result = await db.execute(
|
||||
select(ApiToken).where(
|
||||
ApiToken.id == token_id,
|
||||
ApiToken.user_id == user_id,
|
||||
ApiToken.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@classmethod
|
||||
async def revoke_token(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
token_id: str,
|
||||
user_id: str,
|
||||
) -> bool:
|
||||
"""撤销(软删除)Token"""
|
||||
token = await cls.get_token_by_id(db, token_id, user_id)
|
||||
if not token:
|
||||
return False
|
||||
token.is_deleted = True
|
||||
token.is_active = False
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
async def verify_token(
|
||||
cls,
|
||||
db: AsyncSession,
|
||||
raw_token: str,
|
||||
) -> Optional[ApiToken]:
|
||||
"""
|
||||
验证API Token
|
||||
|
||||
:return: 有效则返回Token记录,否则返回None
|
||||
"""
|
||||
token_hash_value = hash_token(raw_token)
|
||||
result = await db.execute(
|
||||
select(ApiToken).where(
|
||||
ApiToken.token_hash == token_hash_value,
|
||||
ApiToken.is_deleted == False, # noqa: E712
|
||||
ApiToken.is_active == True, # noqa: E712
|
||||
)
|
||||
)
|
||||
token = result.scalar_one_or_none()
|
||||
if not token:
|
||||
return None
|
||||
|
||||
now_utc = datetime.now(timezone.utc)
|
||||
if token.expires_at:
|
||||
expires = token.expires_at if token.expires_at.tzinfo else token.expires_at.replace(tzinfo=timezone.utc)
|
||||
if expires < now_utc:
|
||||
return None
|
||||
|
||||
token.last_used_at = now_utc.replace(tzinfo=None)
|
||||
await db.commit()
|
||||
return token
|
||||
Reference in New Issue
Block a user