Files
TermiNexus f812413de5 feat(GVSDServiceServer): 新增 Venom 一键重建+重新声明脚本
- rebuild-venom.ps1:npm run build 后自动调 /api/sub/declare-page/,主服务器登记入口同时清空该产品资源缓存,重建产物立即生效(无需等 24h)

- 声明端点支持请求体传 ApiKey/Secret(缺省回落环境变量),便于脚本按次传凭据
2026-10-07 17:48:14 +08:00

136 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:GVSDServiceServer 视图
健康检查 / MSYNC 状态 / Venom 定制页远端组件 / 声明定制页(API Key + 密钥)
"""
import logging
from pathlib import Path
from django.conf import settings
from django.http import HttpResponse
from rest_framework.response import Response
from rest_framework.views import APIView
from .remote_pages import VENOM_REMOTE_ENTRY_JS
logger = logging.getLogger(__name__)
def _ok(data=None, msg='success'):
return {'code': 200, 'msg': msg, 'data': data}
def _err(msg, code=400):
return {'code': code, 'msg': msg, 'data': None}
class HomeView(APIView):
def get(self, request):
return Response(_ok({
'Server': 'GVSDServiceServer',
'ServiceName': settings.MSYNC_SERVICE_NAME,
'RemoteEntry': settings.VENOM_REMOTE_ENTRY,
}))
class HealthView(APIView):
def get(self, request):
msync = getattr(settings, 'MSYNC_CLIENT', None)
return Response(_ok({
'Server': 'GVSDServiceServer',
'Status': 'running',
'MSync': {
'Connected': bool(msync and msync.IsConnected),
'Authenticated': bool(msync and msync.IsAuthenticated),
'Registered': bool(msync and msync.IsRegistered),
'ServiceName': msync.ServiceName if msync else settings.MSYNC_SERVICE_NAME,
'ServiceDomain': msync.ServiceDomain if msync else settings.MSYNC_SERVICE_DOMAIN,
'MasterURL': msync.MasterURL if msync else settings.MSYNC_MASTER_URL,
},
'Subscription': {
'HasApiKey': bool(settings.SUBSCRIPTION_API_KEY),
'HasSecret': bool(settings.SUBSCRIPTION_SECRET),
'MasterURL': settings.GVSDSDK_MSYNC_MASTER_URL,
},
}))
class MSyncStatusView(APIView):
def get(self, request):
msync = getattr(settings, 'MSYNC_CLIENT', None)
if not msync:
return Response(_err('MSYNC 未初始化(未配置 MSYNC_SERVICE_SECRET)', 500), status=500)
try:
peers = msync.DiscoverAll()
except Exception as e:
peers = {'Error': str(e)}
return Response(_ok({
'ServiceName': msync.ServiceName,
'ServiceDomain': msync.ServiceDomain,
'MasterURL': msync.MasterURL,
'Authenticated': msync.IsAuthenticated,
'Registered': msync.IsRegistered,
'Connected': msync.IsConnected,
'PeerServices': peers,
}))
class VenomRemoteEntryView(APIView):
"""Venom 定制页的远端入口(ES module,由主服务代理缓存后交给 GERP 宿主渲染)
内容来自 venom_ui 的 Vite 构建产物(venom_ui/dist/venom-entry.js);
未构建时回退到占位提示模块,避免 404/500。
浏览器不再直连本服务:主服务按产品的 RemoteEntry 回源到本端点并缓存
(见 gvsds 订阅应用 `GET /API/Subscription/RemoteAsset/<GroupUUID>/<Path>`),
因此这里无需再放行 CORS。
"""
def get(self, request):
dist_file = Path(settings.BASE_DIR) / 'venom_ui' / 'dist' / 'venom-entry.js'
if dist_file.is_file():
content = dist_file.read_text(encoding='utf-8')
else:
content = VENOM_REMOTE_ENTRY_JS
response = HttpResponse(content, content_type='text/javascript; charset=utf-8')
response['Cache-Control'] = 'no-store'
return response
class DeclarePageView(APIView):
"""用「产品 API Key + 密钥」调用主服务器,声明本产品定制页的远端入口
POST /api/sub/declare-page/ {ApiKey?, Secret?, RemoteEntry?, RemoteModule?}
—— 先 Ping 验证鉴权,再 DeclareCustomPage 登记远端入口。
凭据优先取请求体(便于 rebuild-venom.ps1 一键重建+声明时按次传入),
缺省回落到本服务环境变量(SUBSCRIPTION_API_KEY / SUBSCRIPTION_SECRET)。
主服务器登记入口的同时会清空该产品的资源缓存,故重新声明 = 重建产物立即生效。
"""
def post(self, request):
from gvsdsdk.subscription import SubscriptionClient, SubscriptionClientError
api_key = (request.data.get('ApiKey') or settings.SUBSCRIPTION_API_KEY or '').strip()
secret = (request.data.get('Secret') or settings.SUBSCRIPTION_SECRET or '').strip()
if not api_key or not secret:
return Response(
_err('缺少订阅凭据:请在请求体传 ApiKey/Secret,或注入环境变量 '
'SUBSCRIPTION_API_KEY / SUBSCRIPTION_SECRET', 500),
status=500,
)
entry = (request.data.get('RemoteEntry') or settings.VENOM_REMOTE_ENTRY or '').strip()
module = (request.data.get('RemoteModule') or 'default').strip() or 'default'
client = SubscriptionClient(api_key, secret, MasterURL=settings.GVSDSDK_MSYNC_MASTER_URL)
try:
ping = client.Ping()
page = client.DeclareCustomPage(entry, module)
except SubscriptionClientError as e:
logger.error('声明定制页失败: %s', e)
return Response(_err(str(e), 502), status=502)
logger.info('定制页入口已登记: %s', entry)
return Response(_ok({
'Ping': ping, 'Page': page, 'RemoteEntry': entry, 'RemoteModule': module,
}))