前言
前面的兩篇文章中,我們已經學習了通過Flask開發GET和POST請求接口,但一直沒有實現操作數據庫,那么我們今天的目的,就是學習如何將MySQL數據庫運用到當前的接口項目中。
本人環境:Python 3.7.0 、MySQL 5.7
Flask操作MySQL的2種方式
一般情況,Flask操作MySQL比較常見的方式有2種:SQLAlchemy操作
和 SQL操作
。通過SQLAlchemy操作時,因為大多是通過數據庫對象來操作,所以不需要寫多少SQL語句,但為了順便鞏固一下SQL知識,在本文章中,我們將采用SQL操作來進行學習。
目前網上的相關文章中,大多數介紹的都是SQLAlchemy操作,SQLAlchemy操作相對也更簡單好用一些,因為SQLAlchemy為Python與SQL之間建立了映射,如果使用它可以極大減少SQL語句的編寫。
在使用SQL操作方式時,我們需要知道如何利用Python操作MySQL數據庫,如果不太清楚的話,可以參考我之前的一篇文章:利用Python操作MySQL數據庫
數據庫設計
我們創建一個數據庫,命名為 flask_demo
,然后新建一個數據表 user
,建表語句如下:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(255) NOT NULL,
`role` tinyint(1) NOT NULL,
`sex` tinyint(1) DEFAULT NULL,
`telephone` varchar(255) NOT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `telephone` (`telephone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
user表中各字段對應含義如下:
id:用戶id號,自增長
username:用戶名
password:密碼
role:用戶角色,0表示管理員用戶,1表示普通用戶
sex:性別,0表示男性,1表示女性,允許為空
telephone:手機號
address:聯系地址,允許為空
user表創建完成后,通過 DESC user
來查看下表結構。
mysql> DESC user;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(20) | NO | | NULL | |
| password | varchar(255) | NO | | NULL | |
| role | tinyint(1) | NO | | NULL | |
| sex | tinyint(1) | YES | | NULL | |
| telephone | varchar(255) | NO | UNI | NULL | |
| address | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
新增配置文件
我們在項目根路徑下新建一個包 config
,在該包下存放配置文件 setting.py
,該文件用於配置 MySQL 的服務器地址、端口、用戶名及密碼、數據庫名等參數。
# 服務端口配置
SERVER_PORT = 9999
# MySQL配置
MYSQL_HOST = "192.168.89.128"
MYSQL_PORT = 3306
MYSQL_USER = "root"
MYSQL_PASSWD = "123456"
MYSQL_DB = "flask_demo"
Python操作MySQL
我們在項目根路徑下新建一個包 common
,在該包下新建文件 mysql_operate.py
,該文件下封裝了Python操作MySQL的代碼,后續將通過調用該文件的 db
對象及方法來操作數據庫。
import pymysql
from config.setting import MYSQL_HOST,MYSQL_PORT,MYSQL_USER,MYSQL_PASSWD,MYSQL_DB
class MysqlDb():
def __init__(self, host, port, user, passwd, db):
# 建立數據庫連接
self.conn = pymysql.connect(
host=host,
port=port,
user=user,
passwd=passwd,
db=db
)
# 通過 cursor() 創建游標對象,並讓查詢結果以字典格式輸出
self.cur = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
def __del__(self): # 對象資源被釋放時觸發,在對象即將被刪除時的最后操作
# 關閉游標
self.cur.close()
# 關閉數據庫連接
self.conn.close()
def select_db(self, sql):
"""查詢"""
# 檢查連接是否斷開,如果斷開就進行重連
self.conn.ping(reconnect=True)
# 使用 execute() 執行sql
self.cur.execute(sql)
# 使用 fetchall() 獲取查詢結果
data = self.cur.fetchall()
return data
def execute_db(self, sql):
"""更新/新增/刪除"""
try:
# 檢查連接是否斷開,如果斷開就進行重連
self.conn.ping(reconnect=True)
# 使用 execute() 執行sql
self.cur.execute(sql)
# 提交事務
self.conn.commit()
except Exception as e:
print("操作出現錯誤:{}".format(e))
# 回滾所有更改
self.conn.rollback()
db = MysqlDb(MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWD, MYSQL_DB)
完善GET和POST請求接口
- 獲取所有用戶信息(GET接口)
@app.route("/users", methods=["GET"])
def get_all_users():
"""獲取所有用戶信息"""
sql = "SELECT * FROM user"
data = db.select_db(sql)
print("獲取所有用戶信息 == >> {}".format(data))
return jsonify({"code": "0", "data": data, "msg": "查詢成功"})
- 獲取某個用戶信息(GET接口)
@app.route("/users/<string:username>", methods=["GET"])
def get_user(username):
"""獲取某個用戶信息"""
sql = "SELECT * FROM user WHERE username = '{}'".format(username)
data = db.select_db(sql)
print("獲取 {} 用戶信息 == >> {}".format(username, data))
if data:
return jsonify({"code": "0", "data": data, "msg": "查詢成功"})
return jsonify({"code": "1004", "msg": "查不到相關用戶的信息"})
- 用戶注冊接口(POST接口)
@app.route("/register", methods=['POST'])
def user_register():
"""用戶注冊"""
username = request.json.get("username").strip() # 用戶名
password = request.json.get("password").strip() # 密碼
sex = request.json.get("sex", "0").strip() # 性別,默認為0(男性)
telephone = request.json.get("telephone", "").strip() # 手機號,默認為空串
address = request.json.get("address", "").strip() # 地址,默認為空串
if username and password and telephone:
sql1 = "SELECT username FROM user WHERE username = '{}'".format(username)
res1 = db.select_db(sql1)
print("查詢到用戶名 ==>> {}".format(res1))
sql2 = "SELECT telephone FROM user WHERE telephone = '{}'".format(telephone)
res2 = db.select_db(sql2)
print("查詢到手機號 ==>> {}".format(res2))
if res1:
return jsonify({"code": 2002, "msg": "用戶名已存在,注冊失敗!!!"})
elif not (sex == "0" or sex == "1"):
return jsonify({"code": 2003, "msg": "輸入的性別只能是 0(男) 或 1(女)!!!"})
elif not (len(telephone) == 11 and re.match("^1[3,5,7,8]\d{9}$", telephone)):
return jsonify({"code": 2004, "msg": "手機號格式不正確!!!"})
elif res2:
return jsonify({"code": 2005, "msg": "手機號已被注冊!!!"})
else:
sql3 = "INSERT INTO user(username, password, role, sex, telephone, address) " \
"VALUES('{}', '{}', '1', '{}', '{}', '{}')".format(username, password, sex, telephone, address)
db.execute_db(sql3)
print("新增用戶信息 ==>> {}".format(sql3))
return jsonify({"code": 0, "msg": "恭喜,注冊成功!"})
else:
return jsonify({"code": 2001, "msg": "用戶名/密碼/手機號不能為空,請檢查!!!"})
在這里,我們實現用戶注冊的時候,設置只能注冊 role
字段值為 1
的普通用戶,不允許直接注冊管理員用戶。相關的接口返回碼和請求場景如下:
接口返回碼 | 請求場景 |
---|---|
0 | 請求參數正確,注冊成功 |
2001 | 請求參數中,用戶名/密碼/手機號,任一參數為空 |
2002 | 請求參數中,用戶名已被其他人注冊使用 |
2003 | 請求參數中, sex 性別字段值不是 0 或 1 |
2004 | 請求參數中,手機號格式不正確 |
2005 | 請求參數中,手機號已被其他人注冊使用 |
可參考如下進行用戶注冊接口請求:
請求方式:POST
請求地址:http://127.0.0.1:5000/register
請求頭:
Content-Type: application/json
Body:{"username": "wintest5", "password": "123456", "sex": "1", "telephone":"13500010005", "address": "上海市黃浦區"}
- 用戶登錄接口(POST接口)
@app.route("/login", methods=['POST'])
def user_login():
"""用戶登錄"""
username = request.values.get("username").strip()
password = request.values.get("password").strip()
if username and password:
sql1 = "SELECT username FROM user WHERE username = '{}'".format(username)
res1 = db.select_db(sql1)
print("查詢到用戶名 ==>> {}".format(res1))
if not res1:
return jsonify({"code": 1003, "msg": "用戶名不存在!!!"})
sql2 = "SELECT * FROM user WHERE username = '{}' and password = '{}'".format(username, password)
res2 = db.select_db(sql2)
print("獲取 {} 用戶信息 == >> {}".format(username, res2))
if res2:
return jsonify({"code": 0, "msg": "恭喜,登錄成功!"})
return jsonify({"code": 1002, "msg": "用戶名或密碼錯誤!!!"})
else:
return jsonify({"code": 1001, "msg": "用戶名或密碼不能為空!!!"})
相關的接口返回碼和請求場景如下:
接口返回碼 | 請求場景 |
---|---|
0 | 用戶名和密碼正確,登錄成功 |
1001 | 請求參數中,用戶名或密碼為空 |
1002 | 請求參數中,用戶名正確,密碼錯誤 |
1003 | 請求參數中,使用了未注冊的用戶名 |
可參考如下進行用戶登錄接口請求:
請求方式:POST
請求地址:http://127.0.0.1:5000/login
請求頭:
Content-Type: application/x-www-form-urlencoded
Body:username=wintest&password=123456
將項目在服務器部署
- app.run() 啟動應用時配置參數
在Flask中,我們通過 app.run()
啟動應用時,可以配置一些參數,比如可以配置如下:
from config.setting import SERVER_PORT
if __name__ == '__main__':
# host為主機ip地址,port指定訪問端口號,debug=True設置調試模式打開
app.run(host="0.0.0.0", port=SERVER_PORT, debug=True)
上面代碼中,host需設置為 0.0.0.0
,這樣才能在外網進行訪問,比如我們在Linux下部署項目,如果想在Windows中進行請求訪問,那么就需要設置為 0.0.0.0
。
- 將項目根路徑加入環境變量
我們把項目根路徑下的 app.py
當做該項目的應用啟動入口文件,那么就需要將項目的根路徑臨時加入到環境變量中,否則啟動應用時可能會提示找不到相關模塊,具體配置如下:
import os, sys
from config.setting import SERVER_PORT
from api.user import app
# 項目根路徑
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, BASE_PATH) # 將項目根路徑臨時加入環境變量,程序退出后失效
if __name__ == '__main__':
# host為主機ip地址,port指定訪問端口號,debug=True設置調試模式打開
app.run(host="0.0.0.0", port=SERVER_PORT, debug=True)
- 命令行啟動應用
我們這個項目的啟動,只需要一行命令就可以搞定,以Linux下后台運行方式啟動為例,命令如下:
# /root/flaskDemo/app.py表示項目根路徑下的app.py啟動入口文件路徑
# /root/flaskDemo/flaskDemo.log表示輸出的日志文件路徑
nohup python3 /root/flaskDemo/app.py >/root/flaskDemo/flaskDemo.log 2>&1 &
OK,通過以上內容,我們已經成功將MySQL引入到我們的接口項目中,並可以成功進行接口請求,相關代碼已上傳到GitHub,大家有興趣的可以基於此進行簡單部署及開展接口測試。
GitHub源碼地址:https://github.com/wintests/flaskDemo