feat: associate generation calls with devices

This commit is contained in:
QiuSW
2026-07-21 09:40:40 +08:00
parent 69f4517958
commit 11c0d63ac8
16 changed files with 398 additions and 19 deletions
+132
View File
@@ -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", ""),
)