"""模块:子服务器 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, UserTokenAuthentication 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 UserMeView(APIView): """当前用户上下文——员工 iframe 访问(用户令牌透传验证) Authorization: Bearer 返回主服务器透传的用户/角色/公司/租户上下文。 """ authentication_classes = [UserTokenAuthentication] permission_classes = [IsAuthenticated] def get(self, request): user = request.user return Response(_ok({ 'UserUUID': getattr(user, 'UserUUID', None), 'RoleUUID': getattr(user, 'RoleUUID', None), 'CompanyUUID': getattr(user, 'CompanyUUID', None), 'TenantUUID': getattr(user, 'TenantUUID', None), })) 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}))