Files
SMANAGE/ATestCServer/apps/sub_server/views.py
T

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}))