feat: associate generation calls with devices
This commit is contained in:
@@ -67,8 +67,10 @@ from apps.portal.models import DownloadRelease
|
||||
from apps.licensing.authentication import DeviceSessionAuthentication
|
||||
from apps.licensing.services import (
|
||||
DeviceRegistrationError,
|
||||
DeviceSessionValidationError,
|
||||
record_device_heartbeat,
|
||||
register_device,
|
||||
resolve_optional_device_session,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -84,13 +86,55 @@ class ExternalApiView(APIView):
|
||||
raise AuthenticationFailed(api_error("unauthorized", "缺失或无效 API Key"))
|
||||
super().permission_denied(request, message=message, code=code)
|
||||
|
||||
def optional_client_device(self, request):
|
||||
raw_token = request.headers.get("X-Device-Session", "")
|
||||
request.device_session_present = bool(raw_token.strip())
|
||||
try:
|
||||
session = resolve_optional_device_session(
|
||||
user=request.user,
|
||||
raw_token=raw_token,
|
||||
)
|
||||
except DeviceSessionValidationError as exc:
|
||||
http_status = (
|
||||
status.HTTP_401_UNAUTHORIZED
|
||||
if exc.code == "device_session_invalid"
|
||||
else status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
raise ApiRequestError(exc.code, exc.message, http_status) from exc
|
||||
request.client_device = session.device if session is not None else None
|
||||
return request.client_device
|
||||
|
||||
|
||||
class GenerateTitleView(ExternalApiView):
|
||||
throttle_classes = (GenerateRateThrottle,)
|
||||
|
||||
def post(self, request):
|
||||
started = telemetry_start_time()
|
||||
alias = request_alias(request.data)
|
||||
try:
|
||||
client_device = self.optional_client_device(request)
|
||||
except ApiRequestError as exc:
|
||||
log_generation_route_usage(
|
||||
route_type="title",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code=exc.code,
|
||||
http_status=exc.http_status,
|
||||
)
|
||||
return Response(exc.as_response_data(), status=exc.http_status)
|
||||
serializer = GenerateTitleRequestSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
log_generation_route_usage(
|
||||
route_type="title",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code="bad_request",
|
||||
http_status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response(
|
||||
api_error("bad_request", "参数错误"),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -99,10 +143,28 @@ class GenerateTitleView(ExternalApiView):
|
||||
data = generate_title_response(
|
||||
user=request.user,
|
||||
api_key=request.auth,
|
||||
client_device=client_device,
|
||||
request_data=serializer.validated_data,
|
||||
)
|
||||
except ApiRequestError as exc:
|
||||
log_generation_route_usage(
|
||||
route_type="title",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code=exc.code,
|
||||
http_status=exc.http_status,
|
||||
)
|
||||
return Response(exc.as_response_data(), status=exc.http_status)
|
||||
log_generation_route_usage(
|
||||
route_type="title",
|
||||
request=request,
|
||||
alias=data.get("alias") or alias,
|
||||
status="success",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
http_status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -110,8 +172,32 @@ class AnalyzeImagesView(ExternalApiView):
|
||||
throttle_classes = (GenerateRateThrottle,)
|
||||
|
||||
def post(self, request):
|
||||
started = telemetry_start_time()
|
||||
alias = request_alias(request.data)
|
||||
try:
|
||||
client_device = self.optional_client_device(request)
|
||||
except ApiRequestError as exc:
|
||||
log_generation_route_usage(
|
||||
route_type="vision",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code=exc.code,
|
||||
http_status=exc.http_status,
|
||||
)
|
||||
return Response(exc.as_response_data(), status=exc.http_status)
|
||||
serializer = AnalyzeImagesRequestSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
log_generation_route_usage(
|
||||
route_type="vision",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code="bad_request",
|
||||
http_status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response(
|
||||
api_error("bad_request", "参数错误"),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -120,10 +206,28 @@ class AnalyzeImagesView(ExternalApiView):
|
||||
data = analyze_images_response(
|
||||
user=request.user,
|
||||
api_key=request.auth,
|
||||
client_device=client_device,
|
||||
request_data=serializer.validated_data,
|
||||
)
|
||||
except ApiRequestError as exc:
|
||||
log_generation_route_usage(
|
||||
route_type="vision",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code=exc.code,
|
||||
http_status=exc.http_status,
|
||||
)
|
||||
return Response(exc.as_response_data(), status=exc.http_status)
|
||||
log_generation_route_usage(
|
||||
route_type="vision",
|
||||
request=request,
|
||||
alias=data.get("alias") or alias,
|
||||
status="success",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
http_status=status.HTTP_200_OK,
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -133,6 +237,19 @@ class GenerateImageView(ExternalApiView):
|
||||
def post(self, request):
|
||||
started = telemetry_start_time()
|
||||
alias = request_alias(request.data)
|
||||
try:
|
||||
client_device = self.optional_client_device(request)
|
||||
except ApiRequestError as exc:
|
||||
log_generation_route_usage(
|
||||
route_type="sync",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code=exc.code,
|
||||
http_status=exc.http_status,
|
||||
)
|
||||
return Response(exc.as_response_data(), status=exc.http_status)
|
||||
serializer = GenerateImageRequestSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
log_generation_route_usage(
|
||||
@@ -153,6 +270,7 @@ class GenerateImageView(ExternalApiView):
|
||||
data = generate_image_response(
|
||||
user=request.user,
|
||||
api_key=request.auth,
|
||||
client_device=client_device,
|
||||
request_data=serializer.validated_data,
|
||||
image_url_builder=request.build_absolute_uri,
|
||||
)
|
||||
@@ -184,6 +302,19 @@ class GenerateImageTaskSubmitView(ExternalApiView):
|
||||
def post(self, request):
|
||||
started = telemetry_start_time()
|
||||
alias = request_alias(request.data)
|
||||
try:
|
||||
client_device = self.optional_client_device(request)
|
||||
except ApiRequestError as exc:
|
||||
log_generation_route_usage(
|
||||
route_type="async",
|
||||
request=request,
|
||||
alias=alias,
|
||||
status="error",
|
||||
latency_ms=telemetry_elapsed_ms(started),
|
||||
error_code=exc.code,
|
||||
http_status=exc.http_status,
|
||||
)
|
||||
return Response(exc.as_response_data(), status=exc.http_status)
|
||||
serializer = GenerateImageRequestSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
log_generation_route_usage(
|
||||
@@ -204,6 +335,7 @@ class GenerateImageTaskSubmitView(ExternalApiView):
|
||||
task, _created = create_image_generation_task(
|
||||
user=request.user,
|
||||
api_key=request.auth,
|
||||
client_device=client_device,
|
||||
request_data=serializer.validated_data,
|
||||
idempotency_key=request.headers.get("Idempotency-Key", ""),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user