420 lines
14 KiB
Python
420 lines
14 KiB
Python
"""模块:子服务器 REST API 视图
|
|
提供业务 CRUD 端点 + MSYNC 状态查询 + 服务间调用演示 + FormService ORM 管理。"""
|
|
|
|
import logging
|
|
from django.conf import settings
|
|
from django.shortcuts import render
|
|
from rest_framework.views import APIView
|
|
from rest_framework.response import Response
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
from .service_auth import SubServerServiceAuthentication
|
|
from .models import BusinessUser, BusinessOrder, SyncLog
|
|
from .serializers import (
|
|
BusinessUserSerializer,
|
|
BusinessOrderSerializer,
|
|
OrderCreateSerializer,
|
|
OGMRegisterSerializer,
|
|
SyncLogSerializer,
|
|
)
|
|
|
|
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}
|
|
|
|
|
|
def _get_msync():
|
|
"""获取 MSYNC 客户端实例"""
|
|
return getattr(settings, 'MSYNC_CLIENT', None)
|
|
|
|
|
|
def _log_sync(event_type, detail, status=1):
|
|
"""记录同步日志"""
|
|
try:
|
|
SyncLog.objects.create(
|
|
EventType=event_type,
|
|
EventDetail=detail,
|
|
EventStatus=status,
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
class HealthView(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
|
|
def get(self, request):
|
|
msync = _get_msync()
|
|
token = msync.AccessToken if msync else None
|
|
msync_status = {
|
|
'Connected': msync.IsConnected if msync else False,
|
|
'Authenticated': msync.IsAuthenticated if msync else False,
|
|
'Registered': msync.IsRegistered if msync else False,
|
|
'ServiceName': msync.ServiceName if msync else None,
|
|
'ServiceDomain': msync.ServiceDomain if msync else None,
|
|
'MasterURL': msync.MasterURL if msync else None,
|
|
'AccessToken': 'exists' if token else 'none',
|
|
}
|
|
|
|
# SDK 版本信息
|
|
sdk_info = {'Name': 'gvsdsdk'}
|
|
try:
|
|
import gvsdsdk
|
|
sdk_info.update(gvsdsdk.get_version_info())
|
|
except ImportError as e:
|
|
sdk_info['Error'] = str(e)
|
|
|
|
# SAAS 数据库连接状态
|
|
saas_db_status = {'Configured': 'saas' in settings.DATABASES}
|
|
try:
|
|
from django.db import connections
|
|
conn = connections['saas']
|
|
conn.ensure_connection()
|
|
saas_db_status['Connected'] = True
|
|
# 尝试统计用户数验证连接可用
|
|
from gvsdsdk.models import User
|
|
saas_db_status['UserCount'] = User.objects.using('saas').count()
|
|
except Exception as e:
|
|
saas_db_status['Connected'] = False
|
|
saas_db_status['Error'] = str(e)
|
|
|
|
return Response(_ok({
|
|
'Server': 'ATestCServer',
|
|
'Status': 'running',
|
|
'MSync': msync_status,
|
|
'SDK': sdk_info,
|
|
'SAASDB': saas_db_status,
|
|
}))
|
|
|
|
|
|
class MSyncStatusView(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
|
|
def get(self, request):
|
|
msync = _get_msync()
|
|
if not msync:
|
|
return Response(_err('MSYNC 未初始化', 500), status=500)
|
|
|
|
services = msync.DiscoverAll()
|
|
token = msync.AccessToken
|
|
return Response(_ok({
|
|
'ServiceName': msync.ServiceName,
|
|
'ServiceDomain': msync.ServiceDomain,
|
|
'MasterURL': msync.MasterURL,
|
|
'Authenticated': msync.IsAuthenticated,
|
|
'Registered': msync.IsRegistered,
|
|
'Connected': msync.IsConnected,
|
|
'AccessToken': 'exists' if token else 'none',
|
|
'PeerServices': services,
|
|
}))
|
|
|
|
|
|
class UserListView(APIView):
|
|
|
|
def get(self, request):
|
|
users = BusinessUser.objects.filter(UserStatus=1).order_by('-JoinTime')
|
|
return Response(_ok(BusinessUserSerializer(users, many=True).data))
|
|
|
|
def post(self, request):
|
|
username = request.data.get('username', '').strip()
|
|
nickname = request.data.get('nickname', '')
|
|
|
|
if not username:
|
|
return Response(_err('用户名不能为空', 400), status=400)
|
|
|
|
user, created = BusinessUser.objects.get_or_create(
|
|
Username=username,
|
|
defaults={'Nickname': nickname or username},
|
|
)
|
|
|
|
if created:
|
|
msync = _get_msync()
|
|
if msync:
|
|
try:
|
|
ogm_result = msync.OGMRegister(
|
|
object_type='user',
|
|
local_id=str(user.UserID),
|
|
service_name=msync.ServiceName,
|
|
metadata={'username': username, 'nickname': nickname or username},
|
|
)
|
|
if ogm_result:
|
|
user.OGMMappingID = ogm_result.get('ObjectGlobalID', '')
|
|
user.save(update_fields=['OGMMappingID'])
|
|
_log_sync('ogm_register', {
|
|
'object_type': 'user',
|
|
'local_id': str(user.UserID),
|
|
'global_id': user.OGMMappingID,
|
|
})
|
|
except Exception as e:
|
|
logger.warning(f'OGM注册失败: {e}')
|
|
|
|
status_code = 201 if created else 200
|
|
return Response(_ok(BusinessUserSerializer(user).data), status=status_code)
|
|
|
|
|
|
class UserDetailView(APIView):
|
|
|
|
def get(self, request, user_id):
|
|
try:
|
|
user = BusinessUser.objects.get(UserID=user_id)
|
|
except BusinessUser.DoesNotExist:
|
|
return Response(_err('用户不存在', 404), status=404)
|
|
return Response(_ok(BusinessUserSerializer(user).data))
|
|
|
|
|
|
class OrderListView(APIView):
|
|
|
|
def get(self, request):
|
|
orders = BusinessOrder.objects.all().order_by('-CreateTime')[:50]
|
|
return Response(_ok(BusinessOrderSerializer(orders, many=True).data))
|
|
|
|
def post(self, request):
|
|
ser = OrderCreateSerializer(data=request.data)
|
|
if not ser.is_valid():
|
|
return Response(_err(ser.errors, 400), status=400)
|
|
|
|
username = ser.validated_data['username']
|
|
amount = ser.validated_data['amount']
|
|
|
|
try:
|
|
user = BusinessUser.objects.get(Username=username)
|
|
except BusinessUser.DoesNotExist:
|
|
return Response(_err('用户不存在', 404), status=404)
|
|
|
|
import uuid
|
|
order = BusinessOrder.objects.create(
|
|
OrderCode=f'SUB-{uuid.uuid4().hex[:12].upper()}',
|
|
User=user,
|
|
Amount=amount,
|
|
OrderStatus=0,
|
|
)
|
|
|
|
msync = _get_msync()
|
|
if msync:
|
|
try:
|
|
ogm_result = msync.OGMRegister(
|
|
object_type='order',
|
|
local_id=str(order.OrderID),
|
|
service_name=msync.ServiceName,
|
|
metadata={
|
|
'order_code': order.OrderCode,
|
|
'amount': str(amount),
|
|
'user_id': str(user.UserID),
|
|
},
|
|
)
|
|
if ogm_result:
|
|
order.OGMMappingID = ogm_result.get('ObjectGlobalID', '')
|
|
order.save(update_fields=['OGMMappingID'])
|
|
_log_sync('ogm_register', {
|
|
'object_type': 'order',
|
|
'local_id': str(order.OrderID),
|
|
'global_id': order.OGMMappingID,
|
|
})
|
|
except Exception as e:
|
|
logger.warning(f'订单OGM注册失败: {e}')
|
|
|
|
return Response(_ok(BusinessOrderSerializer(order).data), status=201)
|
|
|
|
|
|
class OrderDetailView(APIView):
|
|
|
|
def get(self, request, order_id):
|
|
try:
|
|
order = BusinessOrder.objects.get(OrderID=order_id)
|
|
except BusinessOrder.DoesNotExist:
|
|
return Response(_err('订单不存在', 404), status=404)
|
|
return Response(_ok(BusinessOrderSerializer(order).data))
|
|
|
|
|
|
class OGMRegisterView(APIView):
|
|
|
|
def post(self, request):
|
|
ser = OGMRegisterSerializer(data=request.data)
|
|
if not ser.is_valid():
|
|
return Response(_err(ser.errors, 400), status=400)
|
|
|
|
msync = _get_msync()
|
|
if not msync:
|
|
return Response(_err('MSYNC 未初始化', 500), status=500)
|
|
|
|
result = msync.OGMRegister(
|
|
object_type=ser.validated_data['object_type'],
|
|
local_id=ser.validated_data['local_id'],
|
|
service_name=msync.ServiceName,
|
|
metadata=ser.validated_data.get('metadata', {}),
|
|
)
|
|
|
|
if result:
|
|
_log_sync('ogm_register_manual', {
|
|
'object_type': ser.validated_data['object_type'],
|
|
'local_id': ser.validated_data['local_id'],
|
|
'global_id': result.get('ObjectGlobalID', ''),
|
|
})
|
|
return Response(_ok(result), status=201)
|
|
|
|
return Response(_err('OGM注册失败', 500), status=500)
|
|
|
|
|
|
class OGMResolveView(APIView):
|
|
|
|
def get(self, request, global_id):
|
|
msync = _get_msync()
|
|
if not msync:
|
|
return Response(_err('MSYNC 未初始化', 500), status=500)
|
|
|
|
result = msync.OGMResolve(global_id)
|
|
if result:
|
|
return Response(_ok(result))
|
|
return Response(_err('映射不存在', 404), status=404)
|
|
|
|
|
|
class RemoteCallDemoView(APIView):
|
|
|
|
def get(self, request):
|
|
msync = _get_msync()
|
|
if not msync:
|
|
return Response(_err('MSYNC 未初始化', 500), status=500)
|
|
|
|
services = msync.DiscoverAll()
|
|
results = []
|
|
|
|
for svc in services:
|
|
svc_name = svc.get('ServiceName', '')
|
|
if svc_name == msync.ServiceName:
|
|
continue
|
|
|
|
target_domain = svc.get('ServiceDomain', '')
|
|
resp = msync.CallRemote(
|
|
service_name=svc_name,
|
|
method='GET',
|
|
path='/health/',
|
|
)
|
|
|
|
if resp is None:
|
|
results.append({
|
|
'service': svc_name,
|
|
'domain': target_domain,
|
|
'error': '远端调用失败',
|
|
})
|
|
else:
|
|
# CallRemote 返回 requests.Response(或 dict 时兼容)
|
|
if isinstance(resp, dict):
|
|
body = resp
|
|
else:
|
|
try:
|
|
body = resp.json()
|
|
except ValueError:
|
|
body = {'code': resp.status_code, 'msg': resp.text[:200], 'data': None}
|
|
results.append({
|
|
'service': svc_name,
|
|
'domain': target_domain,
|
|
'code': body.get('code'),
|
|
'msg': body.get('msg'),
|
|
'data': body.get('data'),
|
|
})
|
|
|
|
return Response(_ok(results))
|
|
|
|
|
|
class SyncLogView(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
|
|
def get(self, request):
|
|
logs = SyncLog.objects.all()[:50]
|
|
return Response(_ok(SyncLogSerializer(logs, many=True).data))
|
|
|
|
|
|
def StatusPageView(request):
|
|
"""连接状态仪表盘页面"""
|
|
return render(request, 'status.html', {
|
|
'service_name': settings.MSYNC_SERVICE_NAME,
|
|
'service_domain': settings.MSYNC_SERVICE_DOMAIN,
|
|
'master_url': settings.MSYNC_MASTER_URL,
|
|
})
|
|
|
|
|
|
class LocalExecuteView(APIView):
|
|
"""本地事务执行端点——由主服务器 Saga 编排引擎调用(需主服务器 Service 令牌)"""
|
|
|
|
authentication_classes = [SubServerServiceAuthentication]
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
def post(self, request):
|
|
from gvsdsdk.msync.local_tx import local_tx
|
|
|
|
step_name = request.data.get('StepName', '')
|
|
payload = request.data.get('payload') or {}
|
|
idempotency_key = request.headers.get('Idempotency-Key')
|
|
|
|
if not step_name:
|
|
return Response(_err('StepName 不能为空', 400), status=400)
|
|
|
|
result = local_tx.ExecuteStep(
|
|
name=step_name,
|
|
payload=payload,
|
|
idempotency_key=idempotency_key,
|
|
)
|
|
|
|
if result['success']:
|
|
return Response(_ok(result['result']))
|
|
return Response(_err(result['error'], 500), status=500)
|
|
|
|
|
|
class LocalCompensateView(APIView):
|
|
"""本地事务补偿端点——由主服务器 Saga 编排引擎调用(需主服务器 Service 令牌)"""
|
|
|
|
authentication_classes = [SubServerServiceAuthentication]
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
def post(self, request):
|
|
from gvsdsdk.msync.local_tx import local_tx
|
|
|
|
step_name = request.data.get('StepName', '')
|
|
payload = request.data.get('payload') or {}
|
|
action_result = request.data.get('ActionResult') or {}
|
|
idempotency_key = request.headers.get('Idempotency-Key')
|
|
|
|
if not step_name:
|
|
return Response(_err('StepName 不能为空', 400), status=400)
|
|
|
|
result = local_tx.CompensateStep(
|
|
name=step_name,
|
|
payload=payload,
|
|
action_result=action_result,
|
|
idempotency_key=idempotency_key,
|
|
)
|
|
|
|
if result['success']:
|
|
return Response(_ok(result['result']))
|
|
return Response(_err(result['error'], 500), status=500)
|
|
|
|
|
|
class LocalHandlersView(APIView):
|
|
"""列出已注册的本地事务步骤处理器"""
|
|
|
|
authentication_classes = [SubServerServiceAuthentication]
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
def get(self, request):
|
|
from gvsdsdk.msync.local_tx import local_tx
|
|
return Response(_ok(local_tx.ListHandlers()))
|
|
|
|
|
|
class LocalResetView(APIView):
|
|
"""重置实验处理器状态(demo_flaky 重试计数清零),仅限本地回环使用"""
|
|
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
|
|
def post(self, request):
|
|
from .local_handlers import ResetExperimentState
|
|
ResetExperimentState()
|
|
return Response(_ok({'reset': True})) |