service.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import hashlib
  2. import hmac
  3. import secrets
  4. import time
  5. from datetime import datetime, timedelta
  6. from typing import Optional
  7. from app.api.v1.module_system.auth.schema import AuthSchema
  8. from app.plugin.module_payment.apikey.crud import TenantApiKeyCRUD, TenantApiLogCRUD
  9. from app.plugin.module_payment.apikey.model import TenantApiKeyModel
  10. from app.plugin.module_payment.apikey.schema import TenantApiKeyListResponse, ApiKeyQueryParam
  11. class TenantApiKeyService:
  12. """租户API Key服务"""
  13. @staticmethod
  14. def generate_api_key(tenant_id: int) -> tuple[str, str]:
  15. random_part = secrets.token_hex(32)
  16. timestamp = str(int(time.time()))
  17. secure_number = secrets.randbelow(9000) + 1000
  18. api_key = f"{secure_number}{tenant_id}{timestamp}{random_part[:16]}"
  19. api_secret = secrets.token_hex(64)
  20. return api_key, api_secret
  21. @staticmethod
  22. def generate_signature(api_secret: str, request_data: dict) -> str:
  23. sorted_data = sorted(request_data.items(), key=lambda x: x[0])
  24. sign_str = "&".join([f"{k}={v}" for k, v in sorted_data])
  25. signature = hmac.new(
  26. api_secret.encode('utf-8'),
  27. sign_str.encode('utf-8'),
  28. hashlib.sha256
  29. ).hexdigest()
  30. return signature
  31. @staticmethod
  32. def verify_signature(api_secret: str, request_data: dict, signature: str) -> bool:
  33. expected_signature = TenantApiKeyService.generate_signature(api_secret, request_data)
  34. return expected_signature == signature
  35. @staticmethod
  36. async def create_api_key(
  37. auth: AuthSchema,
  38. tenant_id: int,
  39. expired_days: Optional[int] = 365,
  40. description: Optional[str] = None,
  41. ) -> TenantApiKeyModel:
  42. api_key, api_secret = TenantApiKeyService.generate_api_key(tenant_id)
  43. return await TenantApiKeyCRUD(auth).create_crud(
  44. api_key=api_key,
  45. api_secret=api_secret,
  46. tenant_id=tenant_id,
  47. expired_at=datetime.now() + timedelta(days=expired_days or 365),
  48. description=description,
  49. )
  50. @staticmethod
  51. async def validate_api_key(auth: AuthSchema, api_key: str) -> Optional[TenantApiKeyModel]:
  52. return await TenantApiKeyCRUD(auth).get_by_api_key(api_key)
  53. @staticmethod
  54. async def get_api_key_page_service(
  55. auth: AuthSchema,
  56. page_no: int,
  57. page_size: int,
  58. search: Optional[ApiKeyQueryParam] = None,
  59. order_by: Optional[list[dict[str, str]]] = None,
  60. ) -> dict:
  61. offset = (page_no - 1) * page_size
  62. return await TenantApiKeyCRUD(auth).page(
  63. offset=offset,
  64. limit=page_size,
  65. order_by=order_by or [{"created_time": "desc"}],
  66. search=search.__dict__ if search else {},
  67. out_schema=TenantApiKeyListResponse,
  68. )
  69. @staticmethod
  70. async def update_api_key_status(
  71. auth: AuthSchema,
  72. api_key_id: int,
  73. status: str,
  74. ) -> Optional[TenantApiKeyModel]:
  75. return await TenantApiKeyCRUD(auth).update_status_crud(api_key_id=api_key_id, status=status)
  76. @staticmethod
  77. async def delete_api_key(auth: AuthSchema, api_key_id: int) -> None:
  78. await TenantApiKeyCRUD(auth).delete_crud(api_key_id=api_key_id)
  79. @staticmethod
  80. async def log_api_call(
  81. auth: AuthSchema,
  82. api_key_id: Optional[int],
  83. tenant_id: int,
  84. endpoint: str,
  85. method: str,
  86. request_ip: str,
  87. request_data: Optional[dict],
  88. response_code: int,
  89. start_time: float,
  90. ) -> None:
  91. response_time = (time.time() - start_time) * 1000
  92. await TenantApiLogCRUD(auth).create_crud(
  93. api_key_id=api_key_id,
  94. tenant_id=tenant_id,
  95. endpoint=endpoint,
  96. method=method,
  97. request_ip=request_ip,
  98. request_data=str(request_data) if request_data else None,
  99. response_code=response_code,
  100. response_time=response_time,
  101. )
  102. @staticmethod
  103. async def update_last_used(auth: AuthSchema, api_key_id: int) -> None:
  104. await TenantApiKeyCRUD(auth).update_last_used_crud(api_key_id)