目錄

Flask 中使用 MySQL

前言

我一開始學 Flask 時是看 Flask 的官方教學學的,不得不說 Flask 官方文檔寫得很好。因為 Flask 官方教學是使用 Sqlite 做為數據庫,但是我想要使用 MySQL,在自己網上搜索,加上自己的鑽研手做,這裡紀錄我摸索的結果。因為我偏好使用原生 SQL 語法,所以下面的數據庫操作都是原生語句,例子也是我之前做的某個項目裡面拿出來的。

安裝

1
pip install mysql-connector-python

引入、連接數據庫

因為是 MySQL,所以自己要在自己的電腦本地數據庫寫好要用的數據庫。下面的連接參數要自己改。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import mysql.connector
from mysql.connector import Error

# 連接 MySQL 資料庫
connection = mysql.connector.connect(
    host='localhost',          # 主機名稱
    database='myTelegram',  # 資料庫名稱
    user='Rem',        # 帳號
    password='')  # 密碼

cursor = connection.cursor(buffered=True)
cursor.execute("SELECT DATABASE();")
record = cursor.fetchone()

CRUD

CRUD 就是 Create(新增)、Read(讀取)、Update(更新)、Delete(刪除),數據庫基本操作。

Create 新增

以註冊帳戶並驗證完 Email Captcha 後,將帳戶資料增入 Database 為例子。這裡的設計概念是註冊後不會將帳戶資料寫入數據庫,註冊完會將暫時的帳戶資料存到數據庫,註冊的下一步是驗證 Email,驗證完發送到 Email 的 Captcha 就可以存入數據庫。關於發送 Email,我會另外寫一篇文章。

註:因為 Flask 可以寫 Restful,所以會出現判斷請求方法是不是 POST 或是 GET,增加複用性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from flask import Flask, jsonify, request
from flask_cors import CORS
import mysql.connector
from mysql.connector import Error
# 引入 string
import string

app = Flask(__name__)
# enable CORS
CORS(app)

# 註冊 email 驗證後的緩存
registerTempData = {
    "id": "",
    "username": "",
    "email": "",
    "phone": "",
    "password": "",
}

# 全局驗證碼
captcha = 0

# 驗證 註冊的 Email
@app.route("/user/validateEmail", methods=['GET', 'POST'])
def user_register_email_validate():
    if request.method == 'POST':
        response_object = {'status': 'success'}
        response_object['code'] = 200
        requestCaptcha = request.form.get('captcha')
        print("requestCaptcha: " + str(requestCaptcha))
        print("captcha: " + str(captcha))
        # 如果驗證碼相同
        if str(requestCaptcha) == str(captcha):
            global registerTempData
            # 新增資料
            sql = "INSERT INTO user (id, username, email, phone, password) VALUES (%s, %s, %s, %s, %s);"
            # 將緩存的註冊資料加入
            new_data = (registerTempData["id"], registerTempData["username"],
                        registerTempData["email"], registerTempData["phone"], registerTempData["password"])
            print(new_data)
            cursor = connection.cursor()
            cursor.execute(sql, new_data)

            # 確認資料有存入資料庫
            connection.commit()
            response_object['message'] = 'Your account is activated!'
            response_object['data'] = True

            # 初始化
            registerTempData = {
                "id": "",
                "username": "",
                "email": "",
                "phone": "",
                "password": "",
            }
        else:
            response_object['message'] = 'The captcha is not true!'
            response_object['data'] = False

        return jsonify(response_object)

上面的 Code 需要注意的地方是全局變量如果要在局部函數中使用,就需要使用 global 聲明全局變量,像上面例子的 global registerTempData

Read 讀取

普通查詢

就是 “",這裡以返回某帳戶資訊為例:

下面的 @jwt_optional 是關於 JWT 的使用,因為不在這文章的討論範疇,所以不會細講。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 返回用戶資訊
@app.route("/user/getDetail", methods=['GET', 'POST'])
@jwt_optional
def user_getDetail():
    if request.method == 'POST':
        response_object = {'status': 'success'}
        response_object['code'] = 200
        requestId = request.form.get('id')
        sqlSearchResult = []

        # 驗證 jwt_token
        current_user = get_jwt_identity()

        # 驗證失敗 回傳失敗
        if not current_user:
            response_object['message'] = 'anonymous user'
            response_object['data'] = False

            return jsonify(response_object)

        # 用 id 查詢用戶資訊
        cursor.execute(
            "SELECT id, username, email, phone, password FROM user WHERE id = %s;", (requestId,))
        for (id, username, email, phone, password) in cursor:
            sqlSearchResult.append({
                "id": id,
                "username": username,
                "email": email,
                "phone": phone,
                "password": password
            })

        print(sqlSearchResult)

        # 如果有資料
        if cursor.rowcount > 0:
            response_object['message'] = 'Searching is Success. Have Data'
            response_object['data'] = True
            # 將搜到的數據添入 response_object
            response_object.update({
                'userData': {
                    'id': sqlSearchResult[0]['id'],
                    'username': sqlSearchResult[0]['username'],
                    'email': sqlSearchResult[0]['email'],
                    'phone': sqlSearchResult[0]['phone'],
                    'password': sqlSearchResult[0]['password']
                }
            })

        # 如果沒有資料
        else:
            response_object['message'] = 'There is not this account in the database.'
            response_object['data'] = False

        return jsonify(response_object)

模糊搜索

糊糊搜索最關鍵的地方就是如何將 Keyword 加入 SQL 語句,下面是個實際例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 模糊搜索 所有 channelType 是 group 的群 
@app.route("/user/channels/searchChannels", methods=['GET', 'POST'])
@jwt_optional
def user_channels_search_channels():
    if request.method == 'POST':
        response_object = {'status': 'success'}
        response_object['code'] = 200
        requestKeyword = request.form.get('keyword')

        sqlSearchResult = []

        # 驗證 jwt_token
        current_user = get_jwt_identity()

        # 驗證失敗 回傳失敗
        if not current_user:
            response_object['message'] = 'anonymous user'
            response_object['data'] = False

            return jsonify(response_object)

        # 用 keyword 模糊查詢 所有 channel (channelType = group)
        cursor.execute(
            "SELECT id, channelType, adminId, channelName FROM channel WHERE channelType = 'group' \
             AND (id LIKE CONCAT('%', %s, '%') OR adminId LIKE CONCAT('%', %s, '%') OR \
              channelName LIKE CONCAT('%', %s, '%')) GROUP BY id;",
               (requestKeyword, requestKeyword, requestKeyword))
        for (id, channelType, adminId, channelName) in cursor:
            sqlSearchResult.append({
                "id": id,
                "channelType": channelType,
                "adminId": adminId,
                "channelName": channelName,
            })
        # 如果有 資料
        if cursor.rowcount > 0:
            response_object['message'] = 'Get the channel List success.'
            response_object['data'] = True
            # 將資料添加到 response_object
            response_object.update({
                'searchResult': sqlSearchResult
            })
        # 沒有 message
        else:
            response_object['message'] = 'There is no data.'
            response_object['data'] = False

        return jsonify(response_object)

Update 更新

以些改某個帳戶資料為例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 修改更新用戶 phone 資料
# 這裡前端要先判斷輸入的新 Phone 是否跟原本的一樣,如果相同就不准請求
@app.route("/user/update/phone", methods=['GET', 'POST'])
@jwt_optional
def user_update_phone():
    if request.method == 'POST':
        response_object = {'status': 'success'}
        response_object['code'] = 200
        # 請求資料
        requestId = request.form.get('id')
        requestPhone = request.form.get('phone')

        # 驗證 jwt_token
        current_user = get_jwt_identity()

        # 驗證失敗 回傳失敗
        if not current_user:
            response_object['message'] = 'anonymous user'
            response_object['data'] = False

            return jsonify(response_object)

        # 用 id 查詢用戶的 phone
        cursor.execute(
            "SELECT id, phone FROM user WHERE id = %s;", (requestId,))

        # 有找到資料
        if cursor.rowcount > 0:
            # 更新資料
            sql = "UPDATE user SET phone = %s WHERE id = %s;"
            cursor.execute(sql, (requestPhone, requestId))

            # 確認資料有存入資料庫
            connection.commit()
            response_object['message'] = 'Update phone success.'
            response_object['data'] = True
        # 沒有找到資料
        else:
            response_object['message'] = 'Update phone fail.'
            response_object['data'] = False

        return jsonify(response_object)

Delete 刪除

以刪除某帳戶為例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 刪除帳戶
@app.route("/user/delete", methods=['GET', 'POST'])
@jwt_optional
def user_delete_account():
    if request.method == 'POST':
        response_object = {'status': 'success'}
        response_object['code'] = 200
        # 請求資料
        requestId = request.form.get('id')
        requestPassword = request.form.get('password')

        sqlSearchResult = []

        # 驗證 jwt_token
        current_user = get_jwt_identity()

        # 驗證失敗 回傳失敗
        if not current_user:
            response_object['message'] = 'anonymous user'
            response_object['data'] = False

            return jsonify(response_object)

        # 用請求 id 查詢該 id 是否存在
        cursor.execute(
            "SELECT id, username FROM user WHERE id = %s;", (requestId,))

        # 如果沒找到該 id
        if cursor.rowcount < 1:
            response_object['message'] = 'This account is not exist.'
            response_object['data'] = False

            return jsonify(response_object)

        # 先判斷 requestPassword 是否跟原來的密碼相符
        cursor.execute(
            "SELECT id, password FROM user WHERE id = %s;", (requestId,))
        for (id, password) in cursor:
            sqlSearchResult.append({
                "id": id,
                "password": password
            })

        # md5 加密轉換
        transferPassword = hashlib.md5(requestPassword.encode())

        # 如果密碼相等
        if transferPassword.hexdigest() == sqlSearchResult[0]['password']:
            # 更新資料
            # 刪除用戶的基本資料
            sql = "DELETE FROM user WHERE id = %s;"
            cursor.execute(sql, (requestId,))

            # 確認資料有存入資料庫
            connection.commit()

            # 刪除用戶的好友資料
            # 用 id 查詢用戶的朋友 id 統計好友數
            cursor.execute(
                "SELECT id, friendId FROM friend WHERE id = %s;", (requestId,))
            # 有好友
            if cursor.rowcount > 0:
                # 循環刪除
                for i in range(0, cursor.rowcount * 2):
                    sql = "DELETE FROM friend WHERE id = %s or friendId = %s;"
                    cursor.execute(sql, (requestId, requestId))

                    # 確認資料有存入資料庫
                    connection.commit()

            # token 加入黑名單
            jti = get_raw_jwt()['jti']
            blacklist.add(jti)
            response_object['message'] = 'Delete the account success.'
            response_object['data'] = True
        else:
            response_object['message'] = 'Delete the account fail. The password is not true.'
            response_object['data'] = False

        return jsonify(response_object)

Reference