python:GUI图形化数据库巡检工具
问题描述:时间过得真快,一眨眼又一个月过去,2022又过去大半,7月的尾巴,终于稍微做出来点 东西,本人也不是开发,也是在不断学习的一枚小白。这次使用tkinter制作了一个mysql的巡检工具,使用图形化操作,边学边操作,一路踩坑,写的不好,但是能交出来一个东西,学习的过程中加深了对class的理解,学习了tkinter布局,如何连接数据库等等。
python:Python 3.8.1
数据库对象:MySQL
一、实现效果
二、问题解决
过程中遇到的问题
1.因为做的是数据库巡检系统,所以登录的账号往往是权限较大的比如root,然后如何去校验root账号,跟正常的管理系统不一样,不是创建好用户和密码表,然后去比对。root是存放在数据库系统中的,没办法去校验root的用户和密码。这里的root是作为参数传进来的,所以如何去比对权限较大的用户呢?
2.显示页面如何去加载数据库中的查询结果呢?按理说应该跟简单,取得数据库查询的返回值,然后加载在页面中。但最后这个显示结果还不是很理想
3.数据库登录的时候如何返回mysql连接给的报错值,如果只设置判断是否登录成功失败很简单,但是在返回mysql报错值的时候如何返回给前台呢
三、代码部分
LoginMain.py
登录类
class Application(Frame): username = '' password = '' ip = '' port = '' def __init__(self,master=None): super().__init__(master) self.master = master self.pack() self.createWidget() def createWidget(self): #创建组件容器 page = Frame(self) page.pack() self.usernameGet = StringVar() self.usernameGet.set('root') self.passwordGet = StringVar() self.passwordGet.set('zabbix.9.31') self.ipGet = StringVar() self.ipGet.set('192.168.163.21') self.portGet = StringVar() #这里如果设置IntVar,GUI上会显示0,整数可以为0,不能是空 self.portGet.set('33306') Label(page).grid(row=0,column=0) Label(page,text='账户:').grid(row=1,column=1) Entry(page,textvariable=self.usernameGet).grid(row=1,column=2) Label(page,text='密码:').grid(row=2,column=1) Entry(page,textvariable=self.passwordGet,show='*').grid(row=2,column=2) Label(page,text='IP地址:').grid(row=3,column=1) Entry(page,textvariable=self.ipGet).grid(row=3,column=2) Label(page,text='端口:').grid(row=4,column=1) Entry(page,textvariable=self.portGet).grid(row=4,column=2) Button(page,text='登录',command=self.login_check).grid(row=5,column=1,pady=10) Button(page,text='退出',command=page.quit).grid(row=5,column=2) def login_check(self): #设置登录退出按钮 i = self.ipGet.get() p = int(self.portGet.get()) u = self.usernameGet.get() pa = self.passwordGet.get() db_name = 'mysql' #实例化Mysql类 mysqlLogin = Mysql(i,p,u,pa,db_name) #这里不能用mysqllogin对象做布尔值判断,只要输入正确,他的布尔值一直是true results = mysqlLogin.select('select @@version') print(bool(mysqlLogin));print(bool(results)) if results: messagebox.showinfo('提示', '连接成功,正在为您生成巡检报告') # print(type(i),type(p),type(u),type(pa)) self.destroy() MainPage(root) mysqlLogin.show()
数据库连接类
class Mysql(object): # mysql 端口号,注意:必须是int类型 def __init__(self, host, port, user, passwd, db_name): self.host = host self.user = user self.passwd = passwd self.port = port self.db_name = db_name def select(self, sql): """ 执行sql命令 :param sql: sql语句 :return: 元祖 """ try: conn = pymysql.connect( host=self.host, user=self.user, passwd=self.passwd, port=self.port, database=self.db_name, charset='utf8', #cursorclass=pymysql.cursors.DictCursor ) cur = conn.cursor() # 创建游标 # conn.cursor() cur.execute(sql) # 执行sql命令 #print(type(cur.execute(sql))) res = cur.fetchall() # 获取执行的返回结果 #print(type(res)) cur.close() conn.close() # print(res) return res except Exception as e: messagebox.showerror('提示',e) return False def show(self): sql1 = "show global variables" # sql2 = "show master status;" # sql3 = "SELECT table_schema,SUM((AVG_ROW_LENGTH*TABLE_ROWS+INDEX_LENGTH))/1024 AS total_KB FROM information_schema.TABLES GROUP BY table_schema ORDER BY total_KB DESC ;" res1 = dict(self.select(sql1)) # res2 = self.select(sql2) # res3 = self.select(sql3) filename = r"D:\{}".format('mysql_check.txt') with open(filename, mode='w', encoding='utf-8') as f: # 检查MySQL版本 #print("\033[1;32m当前数据库的版本是:\033[0m" + res1['version']) f.write("当前数据库的版本是:" + res1['version'] + "\n") f.write("当前数据库的version_comment是:" + res1['version_comment'] + "\n") f.write("当前数据库的version_compile_machine是:" + res1['version_compile_machine'] + "\n") f.write("当前数据库的version_compile_os是:" + res1['version_compile_os'] + "\n") f.write("当前数据库的version_compile_zlib是:" + res1['version_compile_zlib'] + "\n") f.write("当前数据库的sql_mode是:" + res1['sql_mode'] + "\n") # 检查MySQL端口 #print("\033[1;35m当前数据库的端口是:\033[0m" + res1['port']) f.write("当前数据库的端口是:" + res1['port'] + "\n") # 检查server_id #print("\033[1;32m当前数据库的server_id是:\033[0m" + res1['server_id']) f.write("当前数据库的server_id是:" + res1['server_id'] + "\n") # 检查basedir目录 #print("\033[1;32m当前数据库的basedir在:\033[0m" + res1['basedir']) f.write("当前数据库的basedir在:" + res1['basedir'] + "\n") # 检查datadir目录 #print("\033[1;35m当前数据库的datadir在:\033[0m" + res1['datadir']) f.write("当前数据库的datadir在:" + res1['datadir'] + "\n") # 检查tmpdir目录 #print("\033[1;32m当前数据库的tmpdir在:\033[0m" + res1['tmpdir']) f.write("当前数据库的tmpdir在:" + res1['tmpdir'] + "\n") #pid_file f.write("当前数据库的pid_file在:" + res1['pid_file'] + "\n") #optimizer_switch f.write("当前数据库的optimizer_switch:" + res1['optimizer_switch'] + "\n") #mysqlx_socket f.write("mysqlx_socket:" + res1['mysqlx_socket'] + "\n") #log_bin_basename f.write("当前数据库的log_bin_basename在:" + res1['log_bin_basename'] + "\n") #log_error f.write("当前数据库的log_error在:" + res1['log_error'] + "\n") #slow_query_log_file f.write("当前数据库的slow_query_log_file在:" + res1['slow_query_log_file'] + "\n")
MainPage.py
class MainPage: def __init__(self,master: tk.Tk): self.root = master self.root.title('数据库巡检系统') self.root.geometry('600x400') self.create_page() def create_page(self): self.about_frame = AboutFrame(self.root) #调用views中的aboutframe类,显示关于的信息 # tk.Label(self.about_frame,text = '关于作品:数据库巡检系统').pack() # tk.Label(self.about_frame,text = '关于作者:我爱睡莲').pack() # tk.Label(self.about_frame,text = '版权所有:https://www.cnblogs.com/houzhiheng/').pack() self.check_frame = CheckFrame(self.root) menubar = tk.Menu(self.root) menubar.add_command(label='巡检结果',command=self.show_check) menubar.add_command(label='关于',command=self.show_about) self.root['menu'] = menubar def show_check(self): self.check_frame.pack() self.about_frame.pack_forget() #选择性遗忘其他加载过的页面,要不然都会加载在页面当中 def show_about(self): self.about_frame.pack() self.check_frame.pack_forget()
views.py
显示关于部分类
class AboutFrame(tk.Frame): def __init__(self,root): super().__init__(root) tk.Label(self, text='Production:数据库巡检系统').pack() tk.Label(self, text='Author:我爱睡莲').pack() tk.Label(self, text='Version:1.0').pack() tk.Label(self, text='@Copyright:https://www.cnblogs.com/houzhiheng/').pack()
显示数据库巡检结果类
class CheckFrame(tk.Frame): def __init__(self,root): super().__init__(root) # tk.Label(self, text='巡检结果').pack() self.table_view = tk.Frame() self.table_view.pack() self.create_page() tk.Button(self,text='保存文件',command=self.save_data_frame).pack(anchor=tk.E,pady=5) def create_page(self): # self.tree_view = ttk.Treeview(self,show='headings') # columns = ("check_results") # columns_values = ("数据库巡检报告") # top = Tk() # 设置窗口 # sb = Scrollbar(top) # 设置窗口滚动条 # sb.pack(side=RIGHT, fill=) # 设置窗口滚动条位置 # self.sb = Scrollbar() # self.sb.pack(side=RIGHT,fill= Y) # self.tree_view = ttk.Treeview(self,show='headings',columns=columns) # self.tree_view.column('check_results',width=500,anchor='center') # self.tree_view.heading('check_results',text=columns_values) # self.tree_view.pack(fill=tk.BOTH,expand=True) # self.show_data_frame() with open(r'D:\mysql_check.txt', 'r', encoding='utf-8') as f: lines2 = [l.split() for l in f.readlines() if l.strip()] # 滚动条初始化(scrollBar为垂直滚动条,scrollBarx为水平滚动条) scrollBar = Scrollbar(self) scrollBarx = Scrollbar(self, orient=HORIZONTAL) # 靠右,充满Y轴 scrollBar.pack(side=RIGHT, fill=Y) # 靠下,充满X轴 scrollBarx.pack(side=BOTTOM, fill=X) lb = Text(self, width=100, height=25,) lb.pack() # db = Mysql('192.168.163.21', 33306, 'root', 'zabbix.9.31', 'mysql') # res = db.show() textvar = "1:{} \n2:{}\n3:{}\n4:{}\n5:{}\n6:{}\n7:{}\n8:{}\n9:{}\n10:{}\n11:{}\n12:{}\n13:{}\n14:{}\n15:{}"\ .format(lines2[0],lines2[1],lines2[2],lines2[3],lines2[4],lines2[5],lines2[6],lines2[7],lines2[8],lines2[9],lines2[10],lines2[11],lines2[12],lines2[13],lines2[14],lines2[15],lines2[16]) lb.insert('insert', textvar + '\n') lb.update() # 而当用户操纵滚动条的时候,自动调用 Treeview 组件的 yview()与xview() 方法 # 即滚动条与页面内容的位置同步 scrollBar.config(command=lb.yview) scrollBarx.config(command=lb.xview) def show_data_frame(self): #把查询的内容输出到屏幕上来 pass def save_data_frame(self): messagebox.showinfo('提示','您的文件已保存在D:\mysql_check.txt中!')
四、结论与收获
问题处理:
1.如何去对比root登录是否成功呢,从上面代码可以看到,我没法去直接登录数据库去校验用户名和密码,但是我可以让用户名登录成功去执行命令来判断是否登录成功,如果root登陆并且成功执行命令,我就返回一个结果为TRUE,如果执行失败,就返回一个结果为FALSE,这条命令是去查询数据库自身版本得到,任何用户都可以执行
2022/9/16更,也可以使用mysqladmin ping的命令来判断数据库运行的状态
2.在屏幕上输出巡检的结果,本来取得数据库返回值然后给屏幕上就可以了,但是我的views.py调用class MySQL类失败,所以最后直接把查询结果保存在了文件中,前台输出的时候打开文件,然后调整文件格式就输出了,败笔啊这一步
3.将mysql连接返回的报错做成异常处理即可,只不过当时做的时候逻辑有一点问题一直没显示成功
结论收获:
本来是想做一个全套的数据库巡检GUI系统,桌面版本的已经做好了,但是没想到单做一个GUI+mysql版本的就花费了两周时间了,不过这个大体结构成型,后边如果想做应该会简单些。这次更加深刻了解了面向对象,上学没学好,毕业了都得补回来