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 = '版权所有://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://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版本的就花费了两周时间了,不过这个大体结构成型,后边如果想做应该会简单些。这次更加深刻了解了面向对象,上学没学好,毕业了都得补回来