Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 0 additions & 50 deletions python_package/_321CQU/service.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
from enum import Enum

from ._mock import MockApnStub
from micro_services_protobuf.notification_center import service_pb2_grpc as notification_grpc
from micro_services_protobuf.mycqu_service import mycqu_service_pb2_grpc as mycqu_grpc
from micro_services_protobuf.edu_admin_center import eac_service_pb2_grpc as eac_grpc
from micro_services_protobuf.course_score_query import service_pb2_grpc as csq_grpc
from micro_services_protobuf.control_center import control_center_service_pb2_grpc as cc_grpc


class ServiceEnum(str, Enum):
Expand All @@ -24,58 +19,13 @@ class ServiceEnum(str, Enum):

CourseScoreQuery = 'course_score_query'

@property
def service_name(self) -> str:
if self == ServiceEnum.WechatManager:
return 'wechat_manager'
elif self == ServiceEnum.ApnsService:
return 'notification_center'
elif self == ServiceEnum.WechatService:
return 'notification_center'
elif self == ServiceEnum.NotificationService:
return 'notification_center'
elif self == ServiceEnum.ImportantInfoService:
return 'important_info'
elif self == ServiceEnum.MycquService:
return 'mycqu'
elif self == ServiceEnum.CardService:
return 'mycqu'
elif self == ServiceEnum.LibraryService:
return 'mycqu'
elif self == ServiceEnum.EduAdminCenter:
return 'edu_admin_center'
elif self == ServiceEnum.CourseScoreQuery:
return 'course_score_query'

Comment on lines -27 to -49
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

service_name是公开的属性,在未对所有可能使用此方法的代码进行检查前最好不要直接删除该属性(保证API稳定性),而是考虑保留该属性的方案

@property
def is_http_service(self):
if self == ServiceEnum.WechatManager:
return True

return False

def _get_stub_class(self):
if self == ServiceEnum.ApnsService:
return notification_grpc.ApnsStub
elif self == ServiceEnum.WechatService:
return notification_grpc.WechatStub
elif self == ServiceEnum.NotificationService:
return notification_grpc.NotificationStub
elif self == ServiceEnum.ImportantInfoService:
return cc_grpc.ImportantInfoServiceStub
elif self == ServiceEnum.MycquService:
return mycqu_grpc.MycquFetcherStub
elif self == ServiceEnum.CardService:
return mycqu_grpc.CardFetcherStub
elif self == ServiceEnum.LibraryService:
return mycqu_grpc.LibraryFetcherStub
elif self == ServiceEnum.EduAdminCenter:
return eac_grpc.EduAdminCenterStub
elif self == ServiceEnum.CourseScoreQuery:
return csq_grpc.CourseScoreQueryStub
else:
raise RuntimeError("未提供对应服务Stub")

def _get_mock_stub_class(self):
if self == ServiceEnum.ApnsService:
return MockApnStub
Expand Down
186 changes: 186 additions & 0 deletions python_package/_321CQU/service_handler.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用Handler解决了长串if else的问题,但是没有解决if else 可能存在遗漏的问题。例如,增加了一个新枚举但是忘记创建Handler的情况仍然存在。由于被分散到不同的文件,遗漏的可能性甚至增加了

Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
from python_package._321CQU.service import ServiceEnum
from abc import ABC, abstractmethod
from micro_services_protobuf.notification_center import service_pb2_grpc as notification_grpc
from micro_services_protobuf.mycqu_service import mycqu_service_pb2_grpc as mycqu_grpc
from micro_services_protobuf.edu_admin_center import eac_service_pb2_grpc as eac_grpc
from micro_services_protobuf.course_score_query import service_pb2_grpc as csq_grpc
from micro_services_protobuf.control_center import control_center_service_pb2_grpc as cc_grpc


class ServiceHandler(ABC):
"""
服务处理器抽象类
"""

@abstractmethod
def support(self, name: ServiceEnum) -> bool:
"""
判断是否支持该服务
@param name: 服务名枚举
"""
pass

@abstractmethod
@property
def get_service_name(self) -> str:
"""
获取服务名
"""
pass

@abstractmethod
def get_stub_class(self):
"""
获取stub类
"""
pass


class WechatManagerHandler(ServiceHandler):
"""
微信管理器服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.WechatManager

def get_service_name(self) -> str:
return 'wechat_manager'

def get_stub_class(self):
pass


class ApnsServiceHandler(ServiceHandler):
"""
Apns服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.ApnsService

def get_service_name(self) -> str:
return 'notification_center'

def get_stub_class(self):
return notification_grpc.ApnsStub


class WechatServiceHandler(ServiceHandler):
"""
微信服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.WechatService

def get_service_name(self) -> str:
return 'notification_center'

def get_stub_class(self):
return notification_grpc.WechatStub


class NotificationServiceHandler(ServiceHandler):
"""
通知服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.NotificationService

def get_service_name(self) -> str:
return 'notification_center'

def get_stub_class(self):
return notification_grpc.NotificationStub


class ImportantInfoServiceHandler(ServiceHandler):
"""
重要信息服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.ImportantInfoService

def get_service_name(self) -> str:
return 'important_info'

def get_stub_class(self):
return cc_grpc.ImportantInfoServiceStub


class MycquServiceHandler(ServiceHandler):
"""
Mycqu服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.MycquService

def get_service_name(self) -> str:
return 'mycqu'

def get_stub_class(self):
return mycqu_grpc.MycquFetcherStub


class CardServiceHandler(ServiceHandler):
"""
一卡通服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.CardService

def get_service_name(self) -> str:
return 'mycqu'

def get_stub_class(self):
return mycqu_grpc.CardFetcherStub


class LibraryServiceHandler(ServiceHandler):
"""
图书馆服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.LibraryService

def get_service_name(self) -> str:
return 'mycqu'

def get_stub_class(self):
return mycqu_grpc.LibraryFetcherStub


class EduAdminCenterHandler(ServiceHandler):
"""
教务中心服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.EduAdminCenter

def get_service_name(self) -> str:
return 'edu_admin_center'

def get_stub_class(self):
return eac_grpc.EduAdminCenterStub


class CourseScoreQueryHandler(ServiceHandler):
"""
课程成绩查询服务处理器
"""

def support(self, name: ServiceEnum) -> bool:
return name == ServiceEnum.CourseScoreQuery

def get_service_name(self) -> str:
return 'course_score_query'

def get_stub_class(self):
return csq_grpc.CourseScoreQueryStub
29 changes: 18 additions & 11 deletions python_package/_321CQU/tools/gRPCManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
__all__ = ['gRPCManager', 'MockGRPCManager']

from ..service import ServiceEnum
from ..service_handler import ServiceHandler


class gRPCManager(metaclass=Singleton):
def __init__(self, handler: ConfigHandler = _CONFIG_HANDLER):
# 初始化时创建每个ServiceHandler子类的实例
self.service_instances = [cls() for cls in ServiceHandler.__subclasses__()]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不是所有服务都是使用gRPC进行编写的,考虑在这里通过is_http_service过滤不必要的实例;其次,反复调用子类构造方法会造成不必要的变量创建,ServiceEnum实际上的作用是将可用服务(如名称、所属配置文件)转化为枚举便于穷举和避免编写错误,起到了“配置”的作用,gRPC则通过“配置”创建服务的stub。既然是配置,那应该是唯一的,类似于单例,创建多个Handler实例指向唯一的“配置”似乎较为冗余。一种可行的方案是,为ServiceHandler创建一个私有类属性和一个静态方法,该静态方法接受一个filter(当前可以是简单的bool值,区分gRPC和http服务),在类属性为None时先获取所有实例并赋值给类属性,类属性有值时使用filter过滤不必要的实例回传以替代该方法,参考如下:

class ServiceHandler(ABC):
    _subclass_instances = None
    
    @staticmethod
    def get_instances(is_grpc_service: bool) -> [ServiceHandler]
        if ServiceHandler._subclass_instances is None:
            ServiceHandler._subclass_instances = [cls() for cls in ServiceHandler.__subclasses__()]
        return filter(lambda x: not is_http_service(x), ServiceHandler._subclass_instances)

注意,上述代码中的is_http_service方法由于当前代码使用Handler类而非ServiceEnum,需要考虑其他实现

all_options = handler.get_options('ServiceSetting')

service_hosts = list(filter(lambda x: x.endswith('_service_host'), all_options))
Expand All @@ -31,22 +34,26 @@ def __init__(self, handler: ConfigHandler = _CONFIG_HANDLER):
})

def get_service_config(self, service: ServiceEnum) -> Tuple[str, str]:
return (self._service_host[service.service_name + "_service_host"],
self._service_ports[service.service_name + "_service_port"])
for instance in self.service_instances:
if instance.support(name=service):
return (self._service_host[instance.get_service_name + "_service_host"],
self._service_ports[instance.get_service_name + "_service_port"])
Comment on lines +37 to +40
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

未过滤Handler实例会在此处引入异常,可能会试图为http服务获取gRPC调用的配置(当然,之前的实现也存在这个问题)


@asynccontextmanager
async def get_stub(self, service: ServiceEnum):
if service.is_http_service:
raise RuntimeError("该服务不是gRPC服务")

target = service._get_stub_class()

if target is not None:
host = self._service_host[service.service_name + "_service_host"]
port = self._service_ports[service.service_name + "_service_port"]
target_url = host + ":" + port
async with insecure_channel(target_url) as channel:
yield target(channel)
for instance in self.service_instances:
if instance.support(name=service):
Comment on lines +46 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同前,可能会为http服务创建gRPC的stub

target = instance.get_stub_class()
if target is not None:
host = self._service_host[instance.get_service_name + "_service_host"]
port = self._service_ports[instance.get_service_name + "_service_port"]
target_url = host + ":" + str(port)
async with insecure_channel(target_url) as channel:
yield target(channel)
else:
raise RuntimeError("未提供对应服务Stub")


class MockGRPCManager(gRPCManager):
Expand Down
13 changes: 8 additions & 5 deletions python_package/_321CQU/tools/httpServiceManager.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

参照gRPCManager.py中的建议修改

Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
from .Singleton import Singleton

from ..service import ServiceEnum
from ..service_handler import ServiceHandler


class HttpServiceManager(metaclass=Singleton):
def __init__(self, handler: ConfigHandler = _CONFIG_HANDLER):
# 初始化时创建每个ServiceHandler子类的实例
self.service_instances = [cls() for cls in ServiceHandler.__subclasses__()]
all_options = handler.get_options('ServiceSetting')

service_hosts = list(filter(lambda x: x.endswith('_http_host'), all_options))
Expand All @@ -26,8 +29,8 @@ def __init__(self, handler: ConfigHandler = _CONFIG_HANDLER):
def host(self, service: ServiceEnum) -> str:
if not service.is_http_service:
raise RuntimeError("该服务不是http服务")

host = self._service_host[service.service_name + "_http_host"]
port = self._service_ports[service.service_name + "_http_port"]

return host + ":" + port
for instance in self.service_instances:
if instance.support(name=service):
host = self._service_host[instance.get_service_name + "_http_host"]
port = self._service_ports[instance.get_service_name + "_http_port"]
return host + ":" + port