为什么我们需要 MySQL 数据库?

但首先,让我们讨论一下我们“为什么”需要 MySQL 数据库。当我们需要将数据永久存储在某个地方时,就需要数据库。较大的应用程序(如 Reporting Software、Graphing 应用程序等)需要一些位置来存储相关数据,以便以后可以从中检索数据。

一种常见的替代方法是使用 “text files” 来存储数据。尽管文本文件方法更简单(在短期内),但数据库有几个优点。

  1. 可扩展性
  2. 性能
  3. 安全
  4. 备份

先决条件

我们假设您已经有一个有效的 MySQL 安装设置。请务必记住在安装过程中使用的用户名和密码。别忘了。

安装 MySQL 后,我们需要做的下一件事是在 Python 中安装“mysql-connector-python”库。

pip show mysql-connector-python

此库将允许我们从 Python 代码连接 MySQL 数据库并执行 SQL 查询。

初始化 Tkinter 项目的 MySQL 连接

由于这是一个大型应用程序,我将采取一种比平时更合适的方法。我们将创建两个文件,一个名为database.py,一个称为UI.py。首先,在继续处理UI.py文件之前,我们将在database.py文件中创建和定义一些基本函数。

这是第一个函数,用于初始化与MySQL数据库的连接。

def initialize_connection():
    conn = mysql.connector.connect(
        host = "localhost",
        user = "root",
        password = "db1963"
    )
 
    cursor = conn.cursor()
    return conn, cursor

在这里,我们用三个参数调用“connect”方法——主机、用户和密码。

“host”参数指定MySQL数据库服务器的位置。在这段代码中,位置设置为“localhost”,这意味着MySQL服务器运行在执行Python代码的同一台机器上。

“user”参数指定用于登录MySQL数据库的用户名。“password”参数用于指定与用户帐户关联的密码。在此处输入您用于设置MySQL数据库的用户名和密码。

创建连接对象后,下一行代码将创建一个游标对象。游标对象用于执行SQL查询并与MySQL数据库交互。然后,我们从该函数返回游标对象和连接对象。

我们稍后将从UI.py文件调用此函数。

创建 MySQL 数据库

虽然我们已经初始化了MySQL连接,但我们仍然需要为我们的项目创建一个数据库。已经创建了一个默认数据库,名为“sys”,但我们将创建一个名为“tutorial”的新数据库。对于这个应用程序。

下面的代码以安全的方式创建了一个新的数据库。它首先检查以确保没有名为“tutorial”的数据库。如果它不存在,它将执行“CREATE DATABASE tutorial”命令来创建它。

def create_database(cursor):
    cursor.execute("SHOW DATABASES")
    temp = cursor.fetchall()
    databases = [item[0] for item in temp]
     
    if "tutorial" not in databases:
        cursor.execute("CREATE DATABASE tutorial")
     
    cursor.execute("USE tutorial")

最后,它调用“USE-tutorial”命令,从默认数据库切换到新数据库。无论我们的数据库是否已经存在,都必须调用此行。

我们还没有完成我们的设置。

接下来,我们必须创建一个“表”。表基本上是我们为特定实体/目的存储数据的地方。在较大的应用程序中,数据库由多个表组成。例如,在游戏中,可能有一张“玩家”的桌子,一张“NPC”的桌子、一张“敌人”的桌子和一张“任务”的桌子等等。

在我们的应用程序中,我们将为用户存储记录。因此,我们将只有一个“用户”表。下面的代码以与创建数据库相同的方式创建了此表。

def create_table(cursor):  
    cursor.execute("SHOW TABLES")
    temp = cursor.fetchall()
    tables = [item[0] for item in temp]
     
    if "users" not in tables:
        cursor.execute("""CREATE TABLE users(
            id INT AUTO_INCREMENT PRIMARY KEY,
            firstName VARCHAR(100),
            lastName VARCHAR(100),
            password VARCHAR(30),
            email VARCHAR(100) UNIQUE,
            gender VARCHAR(1),
            age INT,
            address VARCHAR(200)
         )""")

由于表很大,有几个字段,上面的代码有点SQL繁重。每个字段都有一个名称、一个数据类型和一些可选约束。我们在这里使用了三个这样的约束。首先,我们有“PRIMARY KEY”,它将“id”字段指定为唯一标识符,我们可以使用它来查询特定用户。

同样,我们也将“电子邮件”声明为唯一列。这些约束将自动防止输入重复项。我们还有用于“id”的AUTO_INCREMENT,这意味着我不会指定id,而是会自动生成数字序列。

我们还可以为每个基于文本的字段定义长度。密码限制为30,姓名和电子邮件限制为100,性别限制为1个字符。

最后,在所有这些之后,我们需要在某个地方调用这两个方法。

我们将把它们都放入我们之前创建的initialize_connection()函数中。

def initialize_connection():
    conn = mysql.connector.connect(
        host = "localhost",
        user = "root",
        password = "db1963"
    )
 
    cursor = conn.cursor()
    create_database(cursor)
    create_table(cursor)
 
    return conn, cursor

我们的设置现已完成!

创建 Tkinter 项目

下面的代码展示了我们组合在一起的一个小型Tkinter项目,我们将MySQL数据库代码集成到其中。应用程序中没有额外的东西,只有一个注册和登录窗口。主窗口是空白的,因为它与本教程没有任何关系。我们唯一关心的是将用户注册到我们的数据库中,然后用他们的凭据稍后登录。

其中大部分只是标准的tkinter代码,所以慢慢来。现在唯一值得注意的是登录和注册窗口,以及我们从database.py文件(第二行)中导入的内容。我们调用了initialize_connection()方法,该方法返回了连接和游标对象。稍后调用登录、注册和函数时,我们将需要这些。

(以下代码后显示的应用程序截图)

import tkinter as tk
from database import *
 
conn, cursor = initialize_connection()
 
def center_window(width, height):
    x = (root.winfo_screenwidth() // 2) - (width // 2)
    y = (root.winfo_screenheight() // 2) - (height // 2)
    root.geometry(f'{width}x{height}+{x}+{y}')
 
 
class WelcomeWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        self.master.title("Welcome")
        center_window(240, 120)
         
        login_button = tk.Button(self, text="Login", width=10,
                          command=self.open_login_window)
        login_button.pack(padx=20, pady=(20, 10))
         
        register_button = tk.Button(self, text="Register", width=10, 
                          command=self.open_register_window)
        register_button.pack(pady=10)
        self.pack()
         
    def open_login_window(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        LoginWindow(self.master)
         
    def open_register_window(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        RegisterWindow(self.master)
 
 
class LoginWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        self.master.title("Login")
        self.master.resizable(False, False)
        center_window(240, 150)
         
        tk.Label(self, text="Username:").grid(row=0, column=0)
        self.username_entry = tk.Entry(self)
        self.username_entry.grid(row=0, column=1, padx=10, pady=10)
         
        tk.Label(self, text="Password:").grid(row=1, column=0)
        self.password_entry = tk.Entry(self, show="*")
        self.password_entry.grid(row=1, column=1, padx=10, pady=10)
         
        submit_button = tk.Button(self, text="Submit", width=8, command=self.submit)
        submit_button.grid(row=2, column=1, sticky="e", padx=10, pady=(10, 0))
 
        submit_button = tk.Button(self, text="Back", width=8, command=self.back)
        submit_button.grid(row=2, column=0, sticky="w", padx=10, pady=(10, 0))
        self.pack()
             
    def submit(self):
        pass
 
    def back(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        WelcomeWindow(self.master)
 
 
class RegisterWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        self.master.title("Register")
        self.master.resizable(False, False)
        center_window(320, 350)
         
        tk.Label(self, text="First Name:").grid(row=0, column=0, sticky="w")
        self.first_name_entry = tk.Entry(self, width=26)
        self.first_name_entry.grid(row=0, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Last Name:").grid(row=1, column=0, sticky="w")
        self.last_name_entry = tk.Entry(self, width=26)
        self.last_name_entry.grid(row=1, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Password:").grid(row=2, column=0, sticky="w")
        self.password_entry = tk.Entry(self, show="*", width=26)
        self.password_entry.grid(row=2, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Email:").grid(row=3, column=0, sticky="w")
        self.email_entry = tk.Entry(self, width=26)
        self.email_entry.grid(row=3, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Gender:").grid(row=4, column=0, sticky="w")
        self.gender_entry = tk.Entry(self, width=10)
        self.gender_entry.grid(row=4, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Age:").grid(row=5, column=0, sticky="w")
        self.age_entry = tk.Entry(self, width=10)
        self.age_entry.grid(row=5, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Address:").grid(row=6, column=0, sticky="w")
        self.address_entry = tk.Text(self, width=20, height=3)
        self.address_entry.grid(row=6, column=1, padx=10, pady=10, sticky="e")
         
        submit_button = tk.Button(self, text="Submit", width=8, command=self.submit)
        submit_button.grid(row=7, column=1, padx=10, pady=10, sticky="e")
 
        submit_button = tk.Button(self, text="Back", width=8, command=self.back)
        submit_button.grid(row=7, column=0, sticky="w", padx=10, pady=(10, 10))
        self.pack()
         
    def submit(self):
        pass
 
    def back(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        WelcomeWindow(self.master)
 
 
class MainWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        center_window(600, 400)
        self.pack()
 
 
root = tk.Tk()
root.eval('tk::PlaceWindow . center')
WelcomeWindow(root)
root.mainloop()

欢迎页面:

创建基于Mysql的Tkinter项目:用户注册和登陆_Tkinter 项目

登录页面:

创建基于Mysql的Tkinter项目:用户注册和登陆_数据库_02

注册页面:

创建基于Mysql的Tkinter项目:用户注册和登陆_数据库_03

需要注意的是,在上面的代码中,Login 和 Register 窗口的 “submit” 函数都是空的。我们将在接下来的两节中开发它们。

注册用户

让我们在代码中实现注册功能。我们需要做的第一件事是从tkinter小部件(在注册表窗口中)中提取所需的数据,然后将这些数据传递给我们即将创建的database.py文件中的“register”函数。

这是我们在注册窗口中填写的提交函数。我们只需将所有数据提取到字典中,然后将其与之前使用initialize_connection()函数创建的连接和游标一起传递给寄存器函数。

def submit(self):
    data = {}
    data["firstName"]= self.first_name_entry.get()
    data["lastName"]= self.last_name_entry.get()
    data["password"]= self.password_entry.get()
    data["email"]= self.email_entry.get()
    data["gender"]= self.gender_entry.get()
    data["age"]= self.age_entry.get()
    data["address"]= self.address_entry.get(1.0, tk.END)
 
    register(cursor, conn, data)

接下来,我们将在database.py文件中实现此注册函数。

我们在这里使用了字符串格式,将字典中的所有值插入到SQL insert into命令中,该命令将值插入到表中。这些值必须按照我们定义表的顺序排列。(第一个值是NULL,因为这是id,它将被自动分配)

def register(cursor, conn, data):
 
    cursor.execute(f"""INSERT INTO users values( 
        NULL,
        '{data["firstName"]}', 
        '{data["lastName"]}', 
        '{data["password"]}', 
        '{data["email"]}', 
        '{data["gender"]}', 
        '{data["age"]}', 
        '{data["address"]}'
    )""")
 
    conn.commit()

最后,我们需要调用“commit”方法,它基本上实现了我们所做的更改。把它想象成“保存”对数据库的更改。当涉及到事务控制和数据库中的恢复时,提交的概念很重要,这是一个完全独立的主题。

我们的注册功能现已完成!我们可以开始注册用户,但让我们等到实现了登录功能。

用户登录

接下来,我们将研究登录功能的两个函数,登录窗口中的submit()函数和数据库.py文件中的login()函数。

这是submit()函数。我们还没有定义login()函数,但我们已经事先决定,根据登录是否成功,它将返回True或False。

def submit(self):
    data = {}
    data["email"] = self.username_entry.get()
    data["password"] = self.password_entry.get()
     
    if login(cursor, data) == True:
        print("successful login")
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        MainWindow(self.master)
    else:
        print("unsuccessful login")

这是login()函数,我们在其中执行“SELECT”命令,通过使用“where”子句根据给定的“电子邮件”和“密码”过滤用户。如果登录成功,我们将销毁当前窗口,并打开主窗口。

def login(cursor, data):
    cursor.execute(f"""SELECT * FROM users WHERE email = '{data["email"]}' 
                       AND password = '{data["password"]}' """)
     
    if cursor.fetchone() != None:
        return True
    return False

然后,根据记录是否成功返回,我们返回True或False值。“无”结果表示未找到记录。

我们决定使用“email”作为“用户名”,因为这是一个独特的字段。期望用户使用数字作为登录名并不是一个好主意,而且没有其他字段是唯一可用作用户名的。如果需要,您可能希望在注册过程中为“用户名”创建一个单独的字段。

测试项目

现在让我们启动应用程序并试用一下。首先要做的是在 register 窗口中注册一个新用户。

创建基于Mysql的Tkinter项目:用户注册和登陆_Tkinter 项目_04

接下来,我将点击提交以注册用户,然后导航到登录页面以尝试登录。如果登录成功,我们应该被重定向到主窗口。

创建基于Mysql的Tkinter项目:用户注册和登陆_数据库_05

完整的代码

UI.py 文件

import tkinter as tk
from database import *
 
conn, cursor = initialize_connection()
 
def center_window(width, height):
    x = (root.winfo_screenwidth() // 2) - (width // 2)
    y = (root.winfo_screenheight() // 2) - (height // 2)
    root.geometry(f'{width}x{height}+{x}+{y}')
 
 
class WelcomeWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        self.master.title("Welcome")
        center_window(240, 120)
         
        login_button = tk.Button(self, text="Login", width=10, command=self.open_login_window)
        login_button.pack(padx=20, pady=(20, 10))
         
        register_button = tk.Button(self, text="Register", width=10, command=self.open_register_window)
        register_button.pack(pady=10)
        self.pack()
         
    def open_login_window(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        LoginWindow(self.master)
         
    def open_register_window(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        RegisterWindow(self.master)
 
 
 
class LoginWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        self.master.title("Login")
        self.master.resizable(False, False)
        center_window(240, 150)
         
        tk.Label(self, text="Username:").grid(row=0, column=0)
        self.username_entry = tk.Entry(self)
        self.username_entry.grid(row=0, column=1, padx=10, pady=10)
         
        tk.Label(self, text="Password:").grid(row=1, column=0)
        self.password_entry = tk.Entry(self, show="*")
        self.password_entry.grid(row=1, column=1, padx=10, pady=10)
         
        submit_button = tk.Button(self, text="Submit", width=8, command=self.submit)
        submit_button.grid(row=2, column=1, sticky="e", padx=10, pady=(10, 0))
 
        submit_button = tk.Button(self, text="Back", width=8, command=self.back)
        submit_button.grid(row=2, column=0, sticky="w", padx=10, pady=(10, 0))
        self.pack()
             
    def submit(self):
        data = {}
        data["email"] = self.username_entry.get()
        data["password"] = self.password_entry.get()
         
        if login(cursor, data) == True:
            print("successful login")
            for widget in self.winfo_children(): 
                widget.destroy()
            self.destroy()
            MainWindow(self.master)
        else:
            print("unsuccessful login")
 
    def back(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        WelcomeWindow(self.master)
 
 
class RegisterWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        self.master.title("Register")
        self.master.resizable(False, False)
        center_window(320, 350)
         
        tk.Label(self, text="First Name:").grid(row=0, column=0, sticky="w")
        self.first_name_entry = tk.Entry(self, width=26)
        self.first_name_entry.grid(row=0, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Last Name:").grid(row=1, column=0, sticky="w")
        self.last_name_entry = tk.Entry(self, width=26)
        self.last_name_entry.grid(row=1, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Password:").grid(row=2, column=0, sticky="w")
        self.password_entry = tk.Entry(self, show="*", width=26)
        self.password_entry.grid(row=2, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Email:").grid(row=3, column=0, sticky="w")
        self.email_entry = tk.Entry(self, width=26)
        self.email_entry.grid(row=3, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Gender:").grid(row=4, column=0, sticky="w")
        self.gender_entry = tk.Entry(self, width=10)
        self.gender_entry.grid(row=4, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Age:").grid(row=5, column=0, sticky="w")
        self.age_entry = tk.Entry(self, width=10)
        self.age_entry.grid(row=5, column=1, padx=10, pady=10, sticky="e")
         
        tk.Label(self, text="Address:").grid(row=6, column=0, sticky="w")
        self.address_entry = tk.Text(self, width=20, height=3)
        self.address_entry.grid(row=6, column=1, padx=10, pady=10, sticky="e")
         
        submit_button = tk.Button(self, text="Submit", width=8, command=self.submit)
        submit_button.grid(row=7, column=1, padx=10, pady=10, sticky="e")
 
        submit_button = tk.Button(self, text="Back", width=8, command=self.back)
        submit_button.grid(row=7, column=0, sticky="w", padx=10, pady=(10, 10))
        self.pack()
         
    def submit(self):
        data = {}
        data["firstName"] = self.first_name_entry.get()
        data["lastName"] = self.last_name_entry.get()
        data["password"] = self.password_entry.get()
        data["email"] = self.email_entry.get()
        data["gender"] = self.gender_entry.get()
        data["age"] = self.age_entry.get()
        data["address"] = self.address_entry.get(1.0, tk.END)
 
        register(cursor, conn, data)
 
    def back(self):
        for widget in self.winfo_children(): 
            widget.destroy()
        self.destroy()
        WelcomeWindow(self.master)
 
 
class MainWindow(tk.Frame):
    def __init__(self, master):
        super().__init__()
        self.master = master
        center_window(600, 400)
        self.pack()
 
 
root = tk.Tk()
root.eval('tk::PlaceWindow . center')
WelcomeWindow(root)
root.mainloop()

database.py 文件。

mport mysql.connector 
 
def initialize_connection():
    conn = mysql.connector.connect(
        host = "localhost",
        user = "root",
        password = "db1963"
    )
 
    cursor = conn.cursor()
    create_database(cursor)
    create_table(cursor)
 
    return conn, cursor
 
def create_database(cursor):
    cursor.execute("SHOW DATABASES")
    temp = cursor.fetchall()
    databases = [item[0] for item in temp]
     
    if "tutorial" not in databases:
        cursor.execute("CREATE DATABASE tutorial")
     
    cursor.execute("USE tutorial")
 
def create_table(cursor):  
    cursor.execute("SHOW TABLES")
    temp = cursor.fetchall()
    tables = [item[0] for item in temp]
     
    if "users" not in tables:
        cursor.execute("""CREATE TABLE users(
            id INT AUTO_INCREMENT PRIMARY KEY,
            firstName VARCHAR(100),
            lastName VARCHAR(100),
            password VARCHAR(30),
            email VARCHAR(100) UNIQUE,
            gender VARCHAR(1),
            age INT,
            address VARCHAR(200)
         )""")
 
def login(cursor, data):
    cursor.execute(f"""SELECT * FROM users WHERE email = '{data["email"]}' 
                       AND password = '{data["password"]}' """)
     
    if cursor.fetchone() != None:
        return True
    return False
 
def register(cursor, conn, data):
    print(data)
 
    cursor.execute(f"""INSERT INTO users values( 
        NULL,
        '{data["firstName"]}', 
        '{data["lastName"]}', 
        '{data["password"]}', 
        '{data["email"]}', 
        '{data["gender"]}', 
        '{data["age"]}', 
        '{data["address"]}'
    )""")
 
    conn.commit()