"""根路由。 所有 api/v1/* 端点都允许匿名访问(ping、JWT 登录),业务 API 在 ViewSet 层用 permission 拦截。 """ from django.contrib import admin from django.urls import path, include from rest_framework_simplejwt.views import ( TokenObtainPairView, TokenRefreshView, ) from rest_framework.permissions import AllowAny from rest_framework.decorators import ( authentication_classes, permission_classes, api_view, ) from rest_framework.response import Response from datetime import datetime, timezone import django import granian import sys from apps.core.urls import core_urlpatterns from apps.finance.views import public_statement from apps.core.demo import enter_demo @api_view(["GET"]) @authentication_classes([]) @permission_classes([AllowAny]) def ping(request): return Response({ "ok": True, "service": "dealerhub", "ts": datetime.now(timezone.utc).isoformat(), "versions": { "python": sys.version.split()[0], "django": django.get_version(), "granian": getattr(granian, "__version__", "unknown"), }, }) def spa_index(request): """Vue3 SPA 入口(hash 路由,仅需托管根路径)。""" from django.conf import settings from django.http import FileResponse, JsonResponse index_path = settings.FRONTEND_DIST / "index.html" if index_path.exists(): return FileResponse(index_path.open("rb"), content_type="text/html") return JsonResponse( {"detail": "前端资源未构建:请先执行 npm run build 并重新打包镜像"}, status=404, ) urlpatterns = [ path("admin/", admin.site.urls), path("api/v1/ping/", ping, name="ping"), path("api/v1/auth/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("api/v1/auth/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path("api/v1/open/statements//", public_statement, name="public-statement"), path("api/v1/demo/enter/", enter_demo, name="demo-enter"), path("api/v1/", include(core_urlpatterns)), # Vue3 SPA(静态资源走 /static/ 由 whitenoise 托管) # 营销官网(批次 C3):/site/* 静态页,与 SPA 分离便于 SEO path("site/", include("apps.website.urls")), path("", spa_index, name="spa-index"), ]