返回介绍

16.5 系统功能模块设计

发布于 2024-01-29 22:54:22 字数 18268 浏览 0 评论 0 收藏 0

16.5.1 用户登录模块

OManager平台的登录采用了双重安全校验机制:一种为传统的用户名与密码匹配,另一种为密钥文件校验方式,实现的原理是在密钥文件中输入任意随机字符串,通过平台自带的md5sum.exe工具计算出该文件的md5,将生成的md5字符串更新到users(用户表)管理员账号对应的Privatekey字段,以root用户的密钥numbers/root.pem为例,使用方法见图16-6和图16-7。

图16-6 查看密钥文件md5

图16-7 数据库存储的密钥文件md5数据

管理员登录时首先获得选择密钥文件的md5,再与数据库中的Privatekey字段进行匹配,建议由超级管理员提前开设好所有用户的账号信息,包括用户名、密码及密钥。再统一将密钥文件以人为单位进行发放。验证的实现方法源码如下:

def Check(self,name, password,Privatekey):
     import md5
     m = md5.new(password)   #使用md5模块计算密码的md5串
     md5pass=m.hexdigest
     myrow=DBclass       #创建数据库连接对象(自定义类)
     sql = "select admin,privileges from users where admin='%s' and
passwd='%s' \
     and Privatekey='%s'"% (name, md5pass,Privatekey)   #参照MySQL中的用户名、密码、密钥进行校验
     result = myrow.fetchallq(sql)
     return result   #返回结果集

下面是计算密钥文件md5的实现方法,主要用到了hashlib模块:

#计算文件md5值,参数fileName为实体文件路径;参数excludeLine为排除的文本行;
#参数includeLine为额外包含的行
def md5(fileName, excludeLine="", includeLine=""):
  m = hashlib.md5   #使用hashlib模块生成一个md5 hash对象
  try:
     fd = open(fileName,"rb")   #打开密钥文件
  except IOError:
     print "Unable to open the file in readmode:", filename
     return
  eachLine = fd.readline
  while eachLine:   #遍历密钥文件
     if excludeLine and eachLine.startswith(excludeLine):   #排除指定的行
        continue
     m.update(eachLine)   #用update方法对行字符串进行md5加密且不断做更新处理
     eachLine = fd.readline
  m.update(includeLine)   #对额外包含的行做更新和加密处理
  fd.close
  return m.hexdigest   #返回十六进制结果

调用计算文件密钥md5方法:

md5(self.Privatekey.GetValue)   #self.Privatekey.GetValue为用户选择的密钥文件路径

16.5.2 系统配置功能

OManager平台将常用的参数配置化,包括连接数据库、主控端、传输密钥等信息,当外部环境发生变化时无须做代码变更,简单更新配置即可,提高了平台的易用性,降低使用门槛,具体是通过ConfigParser模块操作ini文件实现,效果见图16-8。

图16-8 系统配置功能

手工修改ini文件与界面操作达到的效果是一样的,平台ini文件格式如下:

【data/config.ini】

[system]
height = 765
width = 1024
version = v2014
upversion = 10026
ip = 192.168.1.20
port = 11511
timeout = 10
max_servers = 10
secret_key = ctmj#&;8hrgow_^sj$ejt@9fzsmh_o)-=(byt5jmg=e3#foya6u
upgrade_url = http://update.domain.com/upgrade
[db]
db_ip = 192.168.1.10
db_user = servmanageruser
db_pass = 123456#abc
db_name = OManager

使用ConfigParser模块操作ini配置文件非常方便,通过get、set方法来读取与更新配置文件。读配置源码如下:

self.cf = ConfigParser.ConfigParser   #创建ConfigParser对象
self.cf.read(sys.path[0]+'/data/config.ini',encoding='utf8')    #读取配置文件
#读取“system”节中所有键值到指定的变量
self.cf = ConfigParser.ConfigParser
self.cf.read(sys.path[0]+'/data/config.ini')
self._syswidth= self.cf.get("system","Width")
self._sysheight= self.cf.get("system","Height")
self._timeout=self.cf.get("system","Timeout")
self._ip=self.cf.get("system","IP")
self._port=self.cf.get("system","Port")
self._max_servers=self.cf.get("system","max_servers")
self._secret_key=self.cf.get("system","secret_key")
self._sysversion= self.cf.get("system","Version")
self._sysUpversion= self.cf.get("system","Upversion")
self._upgrade_url= self.cf.get("system","upgrade_url")
#读取“db”节中所有键值到指定的变量
self._db_ip= self.cf.get("db","db_ip")
self._db_user= self.cf.get("db","db_user")
self._db_pass= self.cf.get("db","db_pass")
self._db_name= self.cf.get("db","db_name")

更新配置也非常简单,将get方法更换成set,再指定ini的节、键、值三个元素即可。下面是更新数据库IP参数的代码,其中self.DB_ip.GetValue为输入框的内容。

self.cf.set("db", "db_ip", self.DB_ip.GetValue)

16.5.3 服务器分类模块

为了让OManager更具通用性,平台的服务器信息依赖企业现有资产库数据,通过平台规范好的格式生成XML文件,结合Tree与ListBox控件实现功能分类与服务器联动,效果见图16-9。

图16-9 服务器分类选择

服务器分类的组织形式与OMserver保持一致,即功能分类→业务分类→服务器。图16-10为服务器类别的XML数据文件,用来描述服务器类别信息。

【data/ServerOptioninfo.xml】

图16-10 服务器分类的XML文件

其中,“<AppClass id="1">”标签id属性值为功能分类ID号,“<appname>应用服务器</appname>”使用<appname>子元素描述功能分类名称。服务器信息的XML数据文件用来描述服务器的详细属性,详细内容见图16-11,属性与子元素说明见表16-2。

【data/Serverinfo.xml】(部分内容)

图16-11 服务器信息的XML文件

表16-2 属性与子元素说明

每个管理员所负责的服务器资源通常都不一样,一般以服务器功能分类的维度划分。OManager可为这种权限要求提供支持,实现的思路是在users表的privileges(权限角色)字段定义服务器分类ID,其中“root”为特殊权限,代表超级管理员,所有服务器资源都可见。账号“demo”的权限配置,以及在平台中展示的效果见图16-12。

图16-12 用户权限配置及展示效果

当窗体(wx.Frame)初始化时,“服务器类别”控件会自动加载数据,实现的方法是通过遍历以上提到的两个XML数据,将当前账号的权限ID列表与服务器类别ID进行关联,获取所具备的权限,即拥有的服务器类别ID。“应用名称”则通过服务器信息的“<option>”元素与服务器类别ID进行匹配,实现源码如下:

import xml.etree.ElementTree as ET
import os
import sys
root_tree = ET.parse(sys.path[0]+'/data/ServerOptioninfo.xml') #打开服务器类别XML文档
class_tree = ET.parse(sys.path[0]+'/data/Serverinfo.xml')    #打开服务器信息XML文档
root_doc = root_tree.getroot   #获得服务器类别XML文档root节点
class_doc = class_tree.getroot  #获得服务器信息XML文档root节点
class ServerClassList:
  def Resurn_list(self,UserPrivileges):   #返回服务器类别、应用方法
     ServerList_KEY=[]   #定义返回的服务器类别、应用信息列表对象
     serverclass=[]     #定义服务器类别列表对象
     serverapp=[]      #定义业务应用列表对象
     for root_child in root_doc:   #遍历服务器类别节点
        if not root_child.get('id') in UserPrivileges and not UserPrivileges[0]=="root":
          continue   #没有权限的服务器类别将被忽略
        serverclass.append(root_child[0].text.encode('gbk')) # 追加服务器类别名称 <appname>
        serverapp=[]
        for class_child in class_doc:   #遍历服务器信息节点
          #如与功能分类ID相匹配,则追加 <app> 到 serverapp
          #通过index方法产生的异常判断当前 <app> 是否已经存在于 serverapp 中
          if class_child[6].text==root_child.get('id'):
             try:
                serverapp.index(class_child[4].text.encode('gbk'))
             except:
                serverapp.append(class_child[4].text.encode('gbk'))
        serverclass.append(serverapp)
        ServerList_KEY.append(serverclass)
        serverclass=[]
#返回结果串格式:[['应用服务器',['www.a.com','www.b.com']],['数据库服务器',['www.c.com']]...]
     return ServerList_KEY

16.5.4 系统升级功能

相比B/S结构程序,C/S结构的另一缺点是不方便升级,部分软件甚至要求重新安装、重启计算机等操作。为了解决此问题,OManager在系统升级方面结合了B/S的模式,将升级包放在远端,由管理员触发升级操作,同时不影响当前的其他操作,重启OManager程序后即可完成升级。OManager系统升级流程图见图16-13。

图16-13 系统升级流程图

OManager系统升级的原理:首先将升级描述文件(updateMS.xml)、升级包上传至版本服务器,由管理员触发升级操作,再通过urllib模块实现HTTP方式下载updateMS.xml文件并进行分析,获取所有需要升级的程序包,包括远程URL及下载本地存储地址,最后遍历下载所有升级包到指定的位置,完成整个升级过程。下面是升级描述文件updateMS.xml的示例:

【tmp/updateMS.xml】

<?xml version="1.0" encoding="UTF-8"?>
<wml>
<AppClass id="1">
  <localsrc>data/Serverinfo.xml</localsrc>
  <remotesrc>/data/Serverinfo.xml</remotesrc>
</AppClass>
<AppClass id="2">
  <localsrc>data/ServerOptioninfo.xml</localsrc>
  <remotesrc>/data/ServerOptioninfo.xml</remotesrc>
</AppClass>
<AppClass id="3">
  <localsrc>OManager.10026.exe</localsrc>
  <remotesrc>/OManager.exe</remotesrc>
</AppClass>
</wml>

在此XML文件中,localsrc与remotesrc分别表示本地存储地址及远程URL路径,远程URL文件与本地路径建议保持一致,可以提高系统的可维护性,如本地的“data/Serverinfo.xml”与远程的“/data/Serverinfo.xml”,远程升级包目录结构见图16-14。

图16-14 远程升级包存储路径

在此配置中,需要升级的程序包为OManager.exe、Serverinfo.xml、ServerOptioninfo.xml三个,变更项包括了添加主机信息、主程序优化等,下面介绍升级步骤。

1)上传升级相关文件到版本服务器指定位置,具体见图16-14。

2)更新数据库中平台最新版本号,即更新upgrade表的version字段,如更新版本号为“10026”;用户会根据data/config.ini中的upversion键值与最新版本号进行匹配,当小于最新版本号时将触发升级。

3)点击“系统升级”工具栏图标进行升级,升级成功后如图16-15所示。

升级结束后,平台根目录下多了一个“OManager.10026.exe”最新版本的程序包,见图16-16。

运行“OManager.10026.exe”,在主程序左侧的服务器列表框中多了一台“218.31.20.11”主机,见图16-17。再查看data/config.ini中的upversion键值,已经改为“10026”,说明系统已成功升级。

图16-15 系统升级成功

图16-16 升级后的文件列表

图16-17 更新后的主机列表

介绍完系统升级的操作过程,下面介绍OManager实现升级功能的源码分析,使用模块urllib.urlopen(url).read方法实现HTTP协议文件下载,使用xml.etree.ElementTree模块实现XML文件的分析。

def load_data(self,event):   # 系统升级方法
try:
  if self.button.GetLabel==u"关闭":
    self.Destroy
  url=self.updateURL+"/updateMS.xml"   # 指定升级描述文件远程及本地路径
  localfile=sys.path[0]+'/tmp/updateMS.xml'
  if not self.download(url,localfile):   #下载升级描述文件
    return
except Exception,e:
  wx.MessageBox(u"更新描述文件下载失败"+str(e),u"OManager:",style=wx.OK|wx.ICON_ERROR)
  self.Destroy
  return
try:   #打开升级描述文件,为下面的分析做好准备
  import xml.etree.ElementTree as ET
  update_tree = ET.parse(sys.path[0]+'/tmp/updateMS.xml')
  up_doc = update_tree.getroot
except Exception,e:
  wx.MessageBox(u"导入更新包出错",u"OManager:",style=wx.OK|wx.ICON_ERROR)
  self.Destroy
  return
try:    #遍历描述文件,获取升级描述文件中所有程序包的远程及本地路径,调用
     #download方法实现下载
  upgrade_count=0
  for cur_child in up_doc:
    upgrade_count+=1
    url=self.updateURL+cur_child[1].text
    localfile=sys.path[0]+'/'+cur_child[0].text
    if self.download(url,localfile)==False:
       break
  self.cf.set("system", "Upversion", self.lastversion) # 更新config.ini #最新版本号
  self.cf.write(open(sys.path[0]+'/data/config.ini', "w"))
  self.ConnStaticText.SetLabel(u"成功更新"+str(upgrade_count)+"个数据包...")
  self.button.SetLabel(u"关闭")
except Exception,e:
  wx.MessageBox(u"系统文件下载失败","OManager",style=wx.OK|wx.ICON_ERROR)
  self.Destroy
  return
finally:
  pass
event.Skip

16.5.5 客户端模块编写

OManager提供客户端模块开发支持,与OMserver的实现思想一样,区别是OMserver基于HTML表单来定义,而OManager基于XRC。XRC(XML Resource)的设计来源于wxWidgets,原理是将界面设计的工作从程序中独立出来,类似于Django开发框架中模板系统的角色,目的是将业务逻辑与界面进行分离,好处是代码的结构会更加清晰,可读性也会大大提高。具体做法是通过XML格式定义系统界面,当程序运行时再载入。XRC的使用手册见http://wiki.wxwidgets.org/Using_XML_Resources_with_XRC。OManager平台将功能模块采用XRC设计,在主程序中按功能分类导入,效果见图16-18和图16-19。

图16-18 功能模块菜单

OManager平台提供了最多2个控件参数的定义,控件类别支持wxSpinCtrl(微调控制器)、wxListBox(列表控件)、wxTextCtrl(文本输入控件)等,当然,扩展更多的控件类型也非常简单,前提是需要了解各控件的属性及方法,其中控件值会被当成模块参数通过rpyc传输到服务器端。下面为“bas_1001_系统日志.xrc”功能模块的设计,包括一个容量控件wxPanel对象,wxPanel对象包含了两个对象,一个文字标签控件wxStaticText对象,通过<label>元素定义该模块的功能文字说明;另一个对象为微调控制器wxSpinCtrl,通过<value>元素定义默认值,<min>与<max>定义控件的最小值及最大值。更多的控件介绍请参考:http://wiki.wxwidgets.org/Using_XML_Resources_with_XRC。该功能模块的XRC定义内容如下:

图16-19 功能模块窗口

【Module/bas_1001_系统日志.xrc】

<?xml version="1.0" encoding="utf-8"?>
<resource>
     <object class="wxPanel" name="panel">
        <size>200,100</size>
          <object class="wxStaticText" name="label1">
             <label>功能描述:显示服务器Message最新选择条数的记录。</label>
             <pos>30,20</pos>
           </object>
     <object class="wxSpinCtrl" name="Parameter1_object_id">
      <style>wxSP_ARROW_KEYS</style>
         <value>30</value>
         <min>1</min>
         <max>100</max>
         <pos>30,50</pos>
      </object>
    </object>
</resource>

在主程序中,通过xrc.XmlResource方法加载XRC模块文件,使用xrc.XRCCTRL方法获取控件对象,使用对象的GetValue或GetStringSelection方法得到控件输入值,其中wxSpinCtrl、wxTextCtrl控件使用GetValue方法,wxListBox控件使用GetStringSelection方法。在主程序中调用XRC的方法,源码(部分)如下:

from wx import xrc
self.res = xrc.XmlResource(sys.path[0]+'/Module/bas_1001_系统日志.xrc')
#加载模块资源文件
panel = self.res.LoadPanel(self, "panel")   #加载panel面板控件
try:
  self.Parameter1 = xrc.XRCCTRL(panel, 'Parameter1_object_id')  #加载控件1对象名
except Exception,e:
  pass
#获取不同控件的返回值,GetClassName方法返回控件类别名,用于定位不同控件获取value的方法
try:
  if self.Parameter1.GetClassName=="wxSpinCtrl":
    self.Parameter1_value=self.Parameter1.GetValue
  elif self.Parameter1.GetClassName=="wxListBox":
    self.Parameter1_value=self.Parameter1.GetStringSelection
except Exception,e:
pass
…

平台功能模块XRC文件命名遵循一定的标准规范,即“模块功能类别_模块ID_功能中文名称.xrc”,文件名将会以“_”作为分隔符,拆分的数据将应用到系统功能中,比如文件名前缀“模块功能类别”会根据不同类别代号加载到不同功能菜单,实现源码(部分)如下:

bashmenu = wx.Menu     #定义"基本功能"二级菜单
appmenu = wx.Menu      #定义"应用功能"二级菜单
dbmenu = wx.Menu      #定义"数据库功能"二级菜单
servicemenu = wx.Menu   #定义"后台服务功能"二级菜单
middlemenu = wx.Menu    #定义"中间件功能"二级菜单
#根据不同XRC文件前缀,将三级菜单追加到对应的二级菜单中
for file_info in self.Moduledetail:
  file_array=string.split(file_info,'_')
  if file_info[0:3]=="bas":
     bashmenu.Append(int(file_array[1]),file_array[2],file_array[2])
  elif file_info[0:3]=="app":
     appmenu.Append(int(file_array[1]),file_array[2],file_array[2])
  elif file_info[0:3]=="dba":
     dbmenu.Append(int(file_array[1]),file_array[2],file_array[2])
  elif file_info[0:3]=="ser":
     servicemenu.Append(int(file_array[1]),file_array[2],file_array[2])
  elif file_info[0:3]=="mid":
     middlemenu.Append(int(file_array[1]),file_array[2],file_array[2])

文件“模块ID”段将作为该模块的唯一标识,与服务器端模块进行匹配。另外,要求模块XRC文件必须存放于平台Module目录。以下为客户端的所有模块清单,其中ID为“100*”的模块,服务器端已完成对接,其他部分读者可以根据自身的需求自行开发或扩展,平台功能模块XRC文件列表见图16-20。

图16-20 功能模块XRC文件列表

16.5.6 执行功能模块

由于OManager只有两层结构,与服务器端的通信就是一个交互过程,由客户端发起任务请求,服务器执行任务并返回操作结果,操作步骤见图16-21。

图16-21 功能模块执行步骤

为提高平台的通用性及兼容度,OManager的数据封装、传输、加密方式及服务器端与OMserver一致,即传输采用了rpyc框架、RC4加密算法、服务器端同一监听服务。服务器端的实现本节不再做介绍,具体可参考13.5.3节。下面介绍基于wxPython实现的客户端提交任务的几个方法。

try:
  conn=rpyc.connect(self._ip,int(self._port))   #连接rpyc服务器
  #调用login方法实现通信账号、密码校验
  conn.root.login('OMuser','KJS23o4ij09gHF734iuhsdfhkGYSihoiwhj38u4h')
except Exception,e:
  message=u"系统提示:连接远程服务器超时。"+str(e)
  wx.MessageBox(message,u"OManager服务器管理平台:",style=wx.OK|wx.ICON_ERROR)
  return
#调用OnGetSelectServerinfo方法获取计算机名、字符串、服务器数量
_server_list=self.OnGetSelectServerinfo('serverserial_ip',1,int(self._max_servers))
#判断用户是否选择了至少一台服务器,不选择则直接返回
if not _server_list:
  return
#操作记录调用了Addsyslogs方法写入user_logs表,用于操作记录追溯
Intologs.Addsyslogs(self.CurrentAdmin,u"操作对象:"+\
self.OnGetSelectServerinfo('lip',1,20)+u"-操作MID:"+GetModelestrrow[0])
#合并提交串,格式:“模块ID@@主机IP*主机名,N@@参数1@@参数2@@”
例如:“1001@@192.168.1.21*SN2013-08-021@@30@@”
put_string+=str(GetModelestrrow[0])+"@@"+_server_list+"@@"+Parameter_string
#调用tencode方法对提交串进行加密
put_string=FunApp.tencode(put_string,self._secret_key)
# #调用rpyc的Runcommands方法执行任务,返回的结果通过tdecode方法解密OPresult= FunApp.tdecode(conn.root.Runcommands(put_string),self._secret_key).decode('utf8')
#在“输出消息”框输出返回结果
self.OnWriteMessageBox(FunApp.format_str(OPresult))
conn.close

下面为“输出消息”框输出消息方法,使用SetInsertionPoint(0)获取消息插入点,通过WriteText方法写入消息,代码如下:

def OnWriteMessageBox(self,message):
  t = time.localtime(time.time)
  st = time.strftime("%Y-%m-%d %H:%M:%S", t)   #获取当前系统时间
  self.SysMessaegText.SetInsertionPoint(0)    #设置消息框插入点,参数0为开始位置
  #将方法参数message(消息内容)写入消息框
  self.SysMessaegText.WriteText("++++++++++++"+str(st)+"++++++++++++++++\n"+message+"\n")
  self.SysMessaegText.SetInsertionPoint(0)

执行任务返回的结果见图16-22。另外OManager的窗体元素支持任意角度的组合、分离、拖动等,管理员可以根据不同喜好进行调整。

16.5.7 平台程序发布

为了让平台在没有Python以及第三方模块包的环境中正常运行,对源程序进行打包发布是项目最后一个环节,对此pyinstaller(http://www.pyinstaller.org)提供了很好的解决方案,其支持Linux与Windows平台可执行程序的制作,简单易用。Pyinstaller 2.0无须安装,解压即可使用,下面为平台打包的bat批处理脚本。

图16-22 功能模块执行结果

【install.bat】

cd D:\python\OManager\OManager
d:
rd /S /Q dist
rd /S /Q build
del logdict2.7.3.final.0-1.log
python d:/soft/pyinstaller-2.0/pyinstaller.py --onedir -w --icon=img/imac.ico OManager.py
copy MD5sum.exe dist\OManager
xcopy /s data dist\OManager\data\
xcopy /s img dist\OManager\img\
xcopy /s Module dist\OManager\Module\
xcopy /s numbers dist\OManager\numbers\
xcopy /s tmp dist\OManager\tmp\
rd /S /Q build
rd /S /Q build
del logdict2.7.3.final.0-1.log

假设项目目录为“D:\python\OManager\OManager”,参数“--onedir”为创建的一个目录,包含exe文件以及相关依赖类包;“-w”表示制作视窗界面,无控制台(命令行);“--icon”指定执行程序图标;“OManager.py”为平台入口源程序。通过xcopy复制平台相关目录到打包路径(如dist\OManager)。打包后的目录结构见图16-23。

图16-23 打包后生成的文件列表

最后一步就是制作安装包,我们可以简单对目录制作压缩包发布,也可以使用更加专业的安装包制作工具,如Advanced Installer、Inno Setup、Smart Install Maker等,最终将生成一个安装包文件“Setup.exe”,单击安装后的效果见图16-24。

图16-24 系统安装界面

参考提示  RC4加密算法参考文章 http://www.snip2code.com/Snippet/27937/Blockout-encryption-decryption-methods-p 。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文