python:GUI图形化数据库巡检工具
阅读原文时间:2023年07月09日阅读:5

问题描述:时间过得真快,一眨眼又一个月过去,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")

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()

显示关于部分类

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,这条命令是去查询数据库自身版本得到,任何用户都可以执行

2.在屏幕上输出巡检的结果,本来取得数据库返回值然后给屏幕上就可以了,但是我的views.py调用class MySQL类失败,所以最后直接把查询结果保存在了文件中,前台输出的时候打开文件,然后调整文件格式就输出了,败笔啊这一步

3.将mysql连接返回的报错做成异常处理即可,只不过当时做的时候逻辑有一点问题一直没显示成功

结论收获:

本来是想做一个全套的数据库巡检GUI系统,桌面版本的已经做好了,但是没有到光做一个GUI+mysql版本的就花费了两周时间了,不过这个大体结构成型,后边如果想做应该会简单些。这次更加深刻了解了面向对象,上学没学好,毕业了都得补回来