class BaseHandler(): keyDirs = macros.Macro('KEYDIRS').split(';') # 注意是关键文件夹名称的列表 # cached_setup_dir = macros.Macro('CACHEDROOT') + + 'temp_packages' # 解压缩的临时文件夹 configMgr = config.getConfigManager() logger = logger.get_logger() def __init__(self): self.successor = None def setSuccessor(self, successor): self.successor = successor def getSuccessor(self): return self.successor def handle(self, request): raise NotImplementedError('子类必须实现 handle') @staticmethod def generateExtractDir(): """ 生成安全的解压目录 :return: """ return macros.sep.join((macros.Macro('CACHEDROOT'), 'InstallExtractDir-' + str(time.time())))
class Appointed2WebApp(Application): logger = logger.get_logger('Server') def __init__(self, loop, middlewares, topServer): Application.__init__(self, middlewares=middlewares, loop=loop) self.template = None self.topServer = topServer self.runtimeInfo = RuntimeInfo() def add_statics(self, routerToDirName): for router, folder in routerToDirName.items(): realpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), folder) Appointed2WebApp.logger.info('设置静态资源路径: %s' % realpath) self.router.add_static(router, realpath) def init_template(self, **kw): if self.template: return options = dict(autoescape=kw.get('autoescape', True), block_start_string=kw.get('block_start_string', '{%'), block_end_string=kw.get('block_end_string', '%}'), variable_start_string=kw.get('variable_start_string', '{{'), variable_end_string=kw.get('variable_end_string', '}}'), auto_reload=kw.get('auto_reload', True)) path = kw.get('path', None) if path is None: path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates') env = Environment(loader=FileSystemLoader(path), **options) Appointed2WebApp.logger.info( '设置 jinja2 模板的路径: %s' % os.path.join(os.path.dirname(os.path.abspath(__file__)), path)) filters = kw.get('filters', None) if filters is not None: for name, f in filters.items(): env.filters[name] = f self.template = env def add_routersBridge(self, packagesMgr): for packname, pack in packagesMgr.loadedPackages.items(): for target_key, routerObj in pack.routers.items(): self.router.add_route( routerObj.method, routerObj.route, RouterCallBack(routerObj, self, routerObj.system)) Appointed2WebApp.logger.info('成功添加模块:‘%s’, 路由: %s' % (packname, str(routerObj))) # 初始化路由的基本信息 self.runtimeInfo.changeEnableStatus(routerObj.flagName, routerObj.enable)
from packages import logger __logger = logger.get_logger("Server") async def logger_factory(app, handler): """ 一个自定义的工厂拦截器,用于显示调用信息 :param app: 网页服务器实例 :param handler: 路由处理器 :return: 返回一个封装的可调用对象 """ async def logger_handler(request): # logger.get_logger().info('客户端请求: %s %s' % (request.method, request.path)) __logger.info('客户端请求: %s %s' % (request.method, request.path)) return await handler(request) # 转到下一步response_factory return logger_handler
class PackagesManager(object): logger = logger.get_logger('Server') def __init__(self, packages=[]): # self.allRouters = dict() self.loadedPackages = dict() # 读取制定的包 for name in packages: self.loadedPackages[name] = None # 还没有加载的情况 def loadPackages(self): """ 加载模块 :return: 不返回 """ try: for name in self.loadedPackages.keys(): if not self.loadedPackages[name]: # 这个模块的名称并没有被加载 pack = Package(name) # 加载模块的信息 if not pack.valid: continue # self.allRouters.update(pack.routers) # 扩展字典 self.loadedPackages[name] = pack except Exception as e: raise e def addPackage(self, packageObj): if isinstance(packageObj, Package): name = packageObj.name if not self.loadedPackages.get(name, None): self.loadedPackages[name] = packageObj PackagesManager.logger.info('手动添加模块:‘%s’ 成功' % name) else: PackagesManager.logger.info('无法手动添加模块:‘%s’,已经存在相同名称的模块。' % name) else: PackagesManager.logger.info('无法手动添加模块:非法的类型。') def disablePackage(self, packname): packObj = self.loadedPackages.get(packname, None) if not packObj: raise ValueError('无法找到名为:‘%s’ 的模块' % packname) if packObj.main_package: raise ValueError('模块‘%s’为系统模块,不允许进行关闭操作!' % packname) # 关闭所有的路由 for router_name, router_obj in packObj.routers.items(): PackagesManager.logger.warning('正在关闭模块‘%s’ 的路由:%s' % (packname, router_obj)) router_obj.enable = False def findByName(self, packname): """ 查找一个模块对象,按照名称查询。如果不存在返回None :param packname: :return: """ return self.loadedPackages.get(packname, None) def __dict__(self): return {'installedPackages': self.loadedPackages}
class Package(object): """ 这个表示Appointed2 定义的子模块对象 """ logger = logger.get_logger('Server') def __init__(self, packageName, modObj=None, **kwargs): """ 模块的信息 __是受保护的内容 valid:是否有效 doc:注释信息 version:模块的信息 author:作者 rotuers:可调用对象->{方法,路由,解释,级别,系统} commandLines:支持的命令行参数版本 moduleObject:模块的实例 main_package:是否是主模块 :param packageName: 模块的名称 :return: """ try: package_root_modtype = macros.Macro('PACKAGEROOT_IMPORT') self.routers = dict() self.commandLines = dict() self.name = packageName self.main_package = False if self.name == 'Common': self.main_package = True if not modObj: # 载入packages下的模块 targetdir = macros.Macro('PACKAGEROOT') + os.sep + packageName if not os.path.exists(targetdir) or not os.path.isdir( targetdir): self.valid = False return modObj = getattr( __import__(package_root_modtype, globals(), locals(), [packageName]), packageName) # 导入模块 if not modObj: self.valid = False return self.valid = True self.fullName = '.'.join((package_root_modtype, packageName)) # 读取doc、version、author以及各个路由器的信息(只在公共接口中__all__)以及路由的命令行格式(如果有) if getattr(modObj, '__all__', None): # 合法的模块 for api in getattr(modObj, '__all__', None): fn = getattr(modObj, api) if callable(fn): # 是一个可调用对象 method = getattr(fn, '__method__', None) path = getattr(fn, '__route__', None) adminAcquire = getattr(fn, '__adminLevel__', None) if method and path: # 添加路由 # self.routers[fn] = {'method': method, 'route': path, 'doc':inspect.getdoc(fn), # 'level':'api' if path.startswith('/api') else 'user', # 'system':self.main_package # } # 可调用对象->{方法,路由,解释,级别,系统} # 读取命令行参数 if not asyncio.iscoroutinefunction( fn) and not inspect.isgeneratorfunction( fn): # 检查是否是异步函数 fn = asyncio.coroutine(fn) # 确定router级别 rt_level = 'user' if path.startswith('/api'): rt_level = 'api' elif path.startswith('/ws'): rt_level = 'websocket' rt = Router.Router( method=method, route=path, doc=inspect.getdoc(fn), level=rt_level, system=self.main_package, func=fn, acquireAdmin=adminAcquire) # 定义成Router 类 self.routers[rt.flagName] = rt if getattr(modObj, '__cmdLines__', None): self.commandLines = modObj.__cmdLines__ # 直接绑定为命令行 self.version = getattr(modObj, '__version__', '') self.author = getattr(modObj, '__author__', '') self.doc = getattr(modObj, '__doc__', '') self.moduleObject = modObj # 构建对象 Package.logger.info('模块对象‘%s’载入成功' % self.name) except Exception as e: # 处理错误 Package.logger.error('初始化模块对象出现问题:%s\n堆栈信息:%s\n' % (str(e.args), traceback.format_exc())) self.valid = False raise e def _findRouter(self, router_name): """ :param router_name: 指定路由的名称,支持 flagName 和 普通的名称 查找这个模块中的指定的路由 :return: """ router_obj = self.routers.get(router_name, None) if not router_obj: # 可能是其他的名称 for router_common_name, routerO in self.routers.items(): if routerO.flagName == router_name: router_obj = routerO break if not router_obj: Package.logger.error('没有在模块:‘%s’ 找到路由:‘%s’' % (self.name, router_name)) return None # 已经找到了明确的 路由对象 return router_obj # import objgraph # objgraph.show_refs([self.moduleObject.CStarDictIndex], filename='cached\\module.png') # del self.moduleObject # self.moduleObject = None # 删除已经载入的模块 # modules = [module for module in sys.modules.keys() if module.startswith(self.fullName)] # i = 0 # for module in modules: # print(module, sys.getrefcount(sys.modules[module])) # # # # import objgraph # # objgraph.show_refs([sys.modules[module]], filename='cached\\ref_%d.png' % i) # i = i + 1 #for module in modules: # modules = ['packages.WordBook.dictionaries.langdao','packages.WordBook.dictionaries.oxford','packages.WordBook.dictionaries.youdao','packages.WordBook.OnlineDictionary','packages.WordBook.cpystardict','packages.WordBook.DictionaryModel','packages.WordBook.OfflineDictionaryModel','packages.WordBook.CStarDictIndex'] # print(module, sys.getrefcount(sys.modules[module])) # 引用不能大于3 http://outofmemory.cn/code-snippet/1871/python-xiezai-module # tools.unloadAllRefModule(modules) # print(sys.modules) def setRouter(self, routerName, **kwargs): """ 设置路由的相关信息 :param routerName: 路由的信息 :param kwargs: 设置的属性以及值 :return: """ if self.main_package: Package.logger.error('不能设置路由:‘%s’ 的属性,因为这个路由属于系统模块:‘%s’。' % (str(routerName), self.name)) return False, None r = self._findRouter(routerName) if not r: Package.logger.error('没有找到路由:‘%s’ ' % str(routerName)) return False, None if r.system: Package.logger.error('不能设置路由:‘%s’ 的属性,因为这个路由是系统路由。' % (str(routerName))) return False, None for key, value in kwargs.items(): att = getattr(r, key, None) if att == None: Package.logger.error('不能设置模块:‘%s’ 的路由:‘%s’,属性 ‘%s’' % (self.name, str(r), key)) return False, None # 判断原来的类型 typeObj = type(att) if typeObj == bool: value = tools.ObjToBool(value) else: pass setattr(r, key, value) Package.logger.info('设置模块:‘%s’ 的路由:‘%s’,属性 ‘%s’,新值为:‘%s’' % (self.name, str(r), key, value)) return True, r def __dict__(self): """ 用于序列化为 字典文件 :return: """ res = { 'name': self.name, 'version': self.version, 'author': self.author, 'fullname': self.fullName, 'doc': self.doc, 'main_package': self.main_package, 'routers': self.routers } return res
class Appointed2Server(BaseServer): logger = logger.get_logger('Server') def __init__(self, *args, **kwargs): super(Appointed2Server, self).__init__('服务端1') # 必须属性 self.host = kwargs['host'] # 默认绑定的 IP 地址 self.port = kwargs['port'] # 默认的端口号 self.loop = kwargs['loop'] # 获取主默认的消息循环 self.configMgr = kwargs['configMgr'] # 绑定的设置管理器 self.closeSignal = kwargs['closeSignal'] # 通知监控程序的关闭信号 self.restartSignal = kwargs['restartSignal'] # 通知监控程序的重启信号 self.restartEnterManSignal = kwargs['maintenanceSignal'] # 重启进入维护模式的信号 self.app = Appointed2WebApp.Appointed2WebApp( loop=self.loop, middlewares=[ logger_factory.logger_factory, data_factory.data_factory, auth_factory.auth_factory, response_factory.response_factory ], topServer=self) # 初始化其他的部件 self.app.init_template(paths=['templates']) self.app.add_statics({ '/static/': 'static', '/templates/': 'templates' }) # 加载模块管理器 # args 是指定要加载的模块的名称 self.packagesMgr = Package.PackagesManager(args) self.packagesMgr.loadPackages() # 服务器主路由 # modObj = __import__('mainRouters', globals(), locals(), tuple()) # mainPackage = Package.Package('main', modObj) # self.packagesMgr.addPackage(mainPackage) # 添加模块的路由 self.app.add_routersBridge(self.packagesMgr) # 创建服务器实例 handler = self.app.make_handler() # 创建服务器 srv = self.loop.run_until_complete( self.loop.create_server(handler, self.host, self.port)) # 添加资源 # 设置其他的属性 self.server = srv self.handler = handler self.isRunning = False self.isMaintenance = False # 是否可以进行维护 if len(self.packagesMgr.loadedPackages) == 1: self.isMaintenance = True # 初始化数据库 db_name = kwargs.get('dbname', None) if not db_name: return # 不需要数据库连接 db_username = kwargs['dbusername'] db_password = kwargs['dbpassword'] db_addr = kwargs['dbaddress'] db_port = kwargs['dbport'] db_dbname = kwargs['dbdbname'] self.dbPool = self.loop.run_until_complete( dbManager.createPool(self.loop, db_username, db_password, db_dbname, db_addr, db_port)) def close(self): if not self.isRunning: return # self.loop.run_until_complete(dbManager.destroyPool()) self.server.close() self.loop.run_until_complete(self.server.wait_closed()) self.loop.run_until_complete(self.app.shutdown()) self.loop.run_until_complete(self.handler.shutdown(60.0)) self.loop.run_until_complete(self.app.cleanup()) self.loop.run_until_complete(dbManager.destroyPool(self.dbPool)) self.isRunning = False # 服务关闭 Appointed2Server.logger.info('Appointed2 服务已经关闭') def run(self): if self.isRunning: return self.isRunning = True Appointed2Server.logger.info('Appointed2 正在监听:http://%s:%d' % (self.host, self.port)) self.loop.run_forever() # aiohttp.web.run_app(self.app, host=self.host, port=self.port) def user_restart(self, enterMaintenace=False): """ 重启服务:直接停止事件循环,并向监控程序发送消息 :param enterMaintenace:是否进入维护模式? :return: """ __logger = Appointed2Server.logger if enterMaintenace: __logger.info('正在重启 Appointed2 服务并进入维护模式') else: __logger.info('正在重启 Appointed2 服务') mpid = macros.Macro('MONITOR_PID', False) sig = self.restartEnterManSignal if enterMaintenace else self.restartSignal if mpid: if os.name == 'nt': from libs import sendMessage as t # 获取重启的信号 __logger.info('向监控进程 ‘%d’ 发送重启信号:‘%s’' % (mpid, sig)) self.loop.stop() t.post(mpid, int(sig)) else: self.loop.stop() subprocess.Popen(['kill', '-' + sig, str(mpid)]) # 默认是 SIGUSR2 信号 __logger.info('成功向监控进程 ‘%d’ 发送重启信号:‘%s’' % (mpid, sig)) def user_close(self, sendSignal=True): """ 调用对象的关闭服务 :param sendSignal: 是否向宿主程序发送信号 :return: """ __logger = Appointed2Server.logger __logger.info('正在关闭 Appointed2 服务') mpid = macros.Macro('MONITOR_PID', False) sig = self.closeSignal if mpid: if os.name == 'nt': from libs import sendMessage as t # 获取重启的信号 __logger.info('向监控进程 ‘%d’ 发送关闭信号:‘%s’' % (mpid, sig)) self.loop.stop() if sendSignal: t.post(mpid, int(sig)) else: self.loop.stop() subprocess.Popen(['kill', '-' + sig, str(mpid)]) # 默认是 SIGUSR2 信号 __logger.info('成功向监控进程 ‘%d’ 发送关闭信号:‘%s’' % (mpid, sig))
def run(): """ 运行服务 :return: 不返回值 """ try: global __logger_core initEnv.InitEnv(True) kws = dict() # 运行参数 loop = asyncio.get_event_loop() kws['loop'] = loop kws['logout'] = True # 加载配置设置器 cfgr = config.getConfigManager() kws['configMgr'] = cfgr options, noOptArgs = getopt.getopt( sys.argv[1:], 'm:h:p:r:c:e:n', ( 'monitorpid=', 'host=', 'port=', 'restartSignal=', 'closeSignal=', 'dbname=', 'dbusername='******'dbpassword='******'dbaddress=', 'dbport=', 'dbdbname=', # 数据库方面 'maintenanceSignal=', 'nologout')) for name, value in options: if name in ('-h', '--host'): kws['host'] = value elif name in ('-p', '--port'): kws['port'] = int(value) elif name in ('-n', '--nologout'): kws['logout'] = False # 不显示输出 elif name in ('-m', '--monitorpid'): # 设置监控器PID macros.SetMacro('MONITOR_PID', int(value)) elif name in ('--restartSignal', '-r'): kws['restartSignal'] = value elif name in ('--closeSignal', '-c'): kws['closeSignal'] = value elif name in ('--maintenanceSignal', '-e'): kws['maintenanceSignal'] = value elif name in ('--dbname', ): kws['dbname'] = value elif name in ('--dbusername', ): kws['dbusername'] = value elif name in ('--dbpassword', ): kws['dbpassword'] = value elif name in ('--dbaddress', ): kws['dbaddress'] = value elif name in ('--dbdbname', ): kws['dbdbname'] = value elif name in ('--dbport', ): kws['dbport'] = int(value) else: # 不允许有不同的参数。可能以后会有可选的配置 print('%s\n\n未知的参数 ‘%s’' % (__usage, name), file=sys.stderr) exit(-1) # 是否限定了启动的限定的运行的模块 if len(noOptArgs) == 0: # 读取所有的模块 packages = cfgr.getInstalledPackages() noOptArgs = [ name for name, infos in packages.items() if infos['enable'] ] macros.SetMacro('HOST', kws['host']) macros.SetMacro('PORT', kws['port']) macros.SetMacro('ADDRESS', 'http://%s:%d' % (kws['host'], kws['port'])) signal.signal(signal.SIGTERM, onSignal_KILL) # 初始化日志系统 fp = os.path.sep.join( (macros.Macro('LOGROOT'), 'server-%s.txt' % str(time.time()))) logger.init_logger(fp, kws['logout']) __logger_core = logger.get_logger() except Exception as e: msg = '无法运行Appointed2服务器,因为出现错误%s,消息:%s\n%s' % (str( type(e)), str(e.args), traceback.format_exc()) if __logger_core: __logger_core.error(msg) else: print(msg, file=sys.stderr) exit(-1) else: webApp_obj = None try: # print(macros.macro) webApp_obj = Appointed2Server(*noOptArgs, **kws) webApp_obj.run() except KeyboardInterrupt: pass if webApp_obj: webApp_obj.close()
__version__ = '0.0.0.1' __all__ = [ 'EnvironmentPath', 'ObjToBool', 'InstallRequiredModules', 'compareVersion', 'unloadAllRefModule' ] __doc__ = 'Appointed2定义的工具函数方便模块对整个程序的运行状态进行获取' import os import sys import subprocess import sysconfig from packages import macros, logger from distutils.version import LooseVersion # 严格程序的版本 import chardet import traceback _logger = logger.get_logger('Server') def EnvironmentPath(path_type='root', packageType=False): """返回服务管理器的路径信息(相对路径) path_type : 路径的标志 root : 服务器的目录 packages 表示模块的总目录 <moduleName>:模块的完整名称(从运行的路径开始) packageType : 是否是包模式(path_type不为root) """ path = path_type.lower() if path == 'root': return '.' elif path == 'packages': return '.' + os.path.sep + 'packages' if not packageType else 'packages'
def signout(request): referer = request.headers.get('Referer') r = web.HTTPFound(referer or '/manager') # 设置跳转页面 r.set_cookie(COOKIE_NAME, '-deleted-', max_age=0, httponly=True) logger.get_logger().info("用户退出登陆") return r
from packages.Common import Models import re import hashlib from aiohttp import web import json import time from packages import logger, dbManager COOKIE_NAME = 'appointed2_manager_session' _COOKIE_KEY = 'welcome' _RE_EMAIL = re.compile( r'^[a-z0-9\.\-\_]+\@[a-z0-9\-\_]+(\.[a-z0-9\-\_]+){1,4}$') _RE_SHA1 = re.compile(r'^[0-9a-f]{40}$') _logger = logger.get_logger() async def create(dbpool, name, password_hash, email, admin_ensureid): try: if not name or not name.strip(): raise ValueError('无法创建用户:名称无效') if not email or not _RE_EMAIL.match(email): raise ValueError('无法创建用户:Email无效') if not password_hash or not _RE_SHA1.match(password_hash): raise ValueError('无法创建用户:密码格式无效') users = await Models.User.findAll(dbpool, 'email=?', [email]) if len(users) > 0: raise ValueError('注册失败', 'Email', '“' + email + '”已经被使用') uid = Models.next_id() # 检查是否是允许注册为管理员
async def __call__(self, request): __logger = logger.get_logger() # 提取可能的路由 if not self.router.enable: raise ValueError(self.router.method + ' ' + self.router.route + " 不可用") # 检查是否需要管理员 if self.router.acquireAdmin: # cookie 的信息 if not request.__user__ or not request.__user__.admin: if request.method == 'POST': if request.__user__: __logger.info( '用户:{user} 尝试 POST {path} 访问失败:需要管理员凭据'.format( user=request.__user__, path=request.path)) else: __logger.info('未知用户尝试 POST {path} 访问失败:需要管理员凭据'.format( path=request.path)) return (401, '访问未授权,需要管理员凭据') # 如果不存在登录的用户、或者还不是管理员账户 # 跳转到登录界面 redire = 'redirect:/signin?message=%20路由需要管理员权限%20&redirect=' + request.path_qs __logger.info('路由需要管理员的权限,重定向到 %s' % redire) return redire # 获取函数的参数表 required_args = inspect.signature(self.router.func).parameters # logger.get_logger().info('需要的参数: %s' % required_args) # 获取从GET或POST传进来的参数值,如果函数参数表有这参数名就加入 if request.method == 'POST': # for k in dir(request): # print(k + ':' + str(getattr(request, k))) if getattr(request, '__data__', None): kw = { arg: value for arg, value in request.__data__.items() if arg in required_args } # POST需要进行参数的一些转换,这个转换在data工厂中。数据存储在__data__属性中 else: kw = dict() # 只有传递了数据才会有__data__ else: # GET参数有可能需要类似于http://xxx.com/blog?id=5&name=ff之类的参数 qs = request.query_string if qs: # logger.get_logger().info('GET指令的query参数: %s' % request.query_string) kw = { arg: value if isinstance(value, list) and len(value) > 1 else value[0] for arg, value in parse.parse_qs(qs, True).items() } # 保留空格。将查询参数添加到kw已知的参数列表 ref https://raw.githubusercontent.com/icemilk00/Python_L_Webapp/master/www/coroweb.py。可以支持传递数组 else: kw = { arg: value for arg, value in request.match_info.items() if arg in required_args } # 获取match_info的参数值,例如@get('/blog/{id}')之类的参数值 kw.update(request.match_info) # 如果有request参数的话也加入 if 'request' in required_args: kw['request'] = request # 如果有app参数,也添加app参数 if self.isMain and 'app' in required_args: kw['app'] = self.app # 主要用于main的路由设置 # 如果需要 websocket 通信 if self.router.level == 'websocket' and 'wsResponse' in required_args: ws = web.WebSocketResponse() await ws.prepare(request) kw['wsResponse'] = ws # 使用异步 for 就可以得出消息 # 检查参数表中有没参数缺失 for key, arg in required_args.items(): # request参数不能为可变长参数 if key == 'request' and arg.kind in (arg.VAR_POSITIONAL, arg.VAR_KEYWORD): return web.HTTPBadRequest(text='request 参数不能是可变参数') # 如果参数类型不是变长列表和变长字典,变长参数是可缺省的 if arg.kind not in (arg.VAR_POSITIONAL, arg.VAR_KEYWORD): # 如果还是没有默认值,而且还没有传值的话就报错 if arg.default == arg.empty and arg.name not in kw: raise ValueError('缺少的参数: %s' % arg.name) # raise _exceptions.APIError('缺少的参数: %s' % arg.name) RouterCallBack.logger.info( '使用参数 {paras} 调用路由:{method} {path} 关联的函数'.format( paras=kw, method=self.router.method, path=self.router.route)) try: # return await self._func(**kw) # 记录请求 self.app.runtimeInfo.addRequestCount(self.router.flagName) return await self.router.func(**kw) except Exception as e: # 出错记录 RouterCallBack.logger.error('运行路由处理器期间发生‘%s’类型的错误,错误消息:', exc_info=True, stack_info=True) raise e
# coding=utf-8 __author__ = 'Shu Wang <*****@*****.**>' __version__ = '0.0.0.1' __all__ = [''] __doc__ = '维护模式功能 - 维护模式' import config from packages import logger, uploader from packages import setupTool import time from queue import Queue import json _logger = logger.get_logger('Maintenance') class RemoteInstallRequest(setupTool.InstallRequest): """ 远程用户的请求安装处理 """ def __init__(self, packageFile, requestId): super(RemoteInstallRequest, self).__init__(packageFile, True) # 制定了安装文件,并且立即显示日志到文件 self.requestId = requestId def __dict__(self): if not getattr(self, 'packageStatus', None) or not getattr( self, 'packageInfoObj', None): return {'name': '未知', 'version': '未知', 'author': '未知'} else: return self.packageInfoObj.__dict__()