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版本的就花費了兩周時間了,不過這個大體結構成型,後邊如果想做應該會簡單些。這次更加深刻了解了面向對象,上學沒學好,畢業了都得補回來