参考资料:
https://www.cnblogs.com/ibrahim/p/REST-API.html
https://blog.csdn.net/weixin_43649997/article/details/107005060
REST风格的架构为轻量级面向服务的SOA架构
设计原则
- 通过URI来表示一种资源
- 统一接口,不同HTTP方法对应着一种对资源的操作。需要创建资源,使用POST方法进行请求;检索查找某些资源,使用GET方法;修改资源,使用PUT方法;删除某个资源,使用DELETE方法
- 资源多重表述
- 无状态
典型应用表
例子:
我有一个书籍管理系统,我们需要定位每一本书籍,对书籍进行增删查改。
书用ISBN号进行识别。
查询ISBN号为123456的一本书
[GET] /books/123456
删除ISBN号为445690的一本书
[DELETE] /books/445690
新增ISBN号为111111的一本书
[POST] /books/111111
改动ISBN号为987654的书的信息
[PUT] /books/987654
项目设计
db
db目录下存放MysqlDB.py,主要是封装了pymysql的相关语句操作。
MysqlDB.py文件内容
# -*- coding: utf-8 -*-
# Time : 2022/6/16 14:18
# Author : Horace
import pymysql
from logs.config import *
class MysqlDB(object):
"""
mysql数据类,用于进行各种数据操作:增删查改
"""
def __init__(self, host="127.0.0.1", port=3306, user="root",
password="htht0928", database="doubanbook"):
# connect to the database
self.host = host
self.port = port
self.user = user
self.password = password
self.database = database
self.conn = pymysql.connect(
host=host,
user=user,
port=port,
password=password,
database=database
)
self.cur = self.conn.cursor()
def sql_find(self, sql, data=None):
"""
封装sql的select(查)操作,返回查找结果
:param sql: sql语句
:param data: 要查询的据
:return: 查找结果results,没有查找到就返回None
"""
if data is None:
try:
self.conn.ping(reconnect=True) # 时间长了可能会断开数据库的连接,需要重连
self.cur.execute(sql)
results = self.cur.fetchall() # 找到的元组
appLogger.info("Success!")
return results
except Exception as e:
appLogger.debug(e)
return None
else:
try:
self.conn.ping(reconnect=True) # 时间长了可能会断开数据库的连接,需要重连
self.cur.execute(sql, data)
results = self.cur.fetchone() # 找到的元组
appLogger.info("select Success!")
return results
except Exception as e:
appLogger.debug(e)
return None
def sql_modify(self, sql, data):
"""
封装sql的insert(增),delete(删),update(改)操作,提交结果
:param cur: 光标对象
:param sql: sql语句
:param data: 需要操作的数据
:return: 操作是否成功,'success'或'fault'
"""
try:
self.conn.ping(reconnect=True)
self.cur.execute(sql, data)
self.conn.commit()
appLogger.info("modify Success!")
return 'success'
except Exception as e:
appLogger.debug(e)
self.conn.rollback()
return 'fault'
def close(self):
self.conn.close()
logs
config.py文件主要记录日志的配置,app.log存放接口的日志
# -*- coding: utf-8 -*-
# Time : 2022/6/16 14:20
# Author : Horace
import logging
import os
log_file = r'F:\Codefield\CODE_py\BookApi\logs\app.log' # 此处填日志文件路径
if not os.path.exists(log_file):
f = open(log_file, 'a')
f.write("")
f.close()
else:
pass
# 日志文件配置
appLogger = logging.getLogger("AppLog") # 创建一个名为AppLog的logger
appLogger.setLevel(logging.DEBUG) # 将级别设计为debug
fh = logging.FileHandler(filename=log_file, mode="a") # 日志文件处理器
fmt = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)-9s - %(filename)-8s : %(lineno)s line - %(message)s"
, datefmt="%Y/%m/%d %H:%M:%S")
# 给处理器分配格式
fh.setFormatter(fmt)
# 记录器设置处理器
appLogger.addHandler(fh)
这里为相关日志的记录,日志会方便开发时修复bug(惨痛的教训)
templates
主要存放相关前端页面文件,此处略
app.py
app.py,此处编写flask接口
from flask import Flask, jsonify, abort, request
from db.MysqlDB import *
from logs.config import *
from flask_cors import *
app = Flask(__name__)
book_db = MysqlDB() # 自定义数据库对象
CORS(app, supports_credentials=True) # 避免跨域造成的无法识别HTTP方法问题
index2book = {1: 'book_name', 2: 'author', 3: 'press', 4: 'publishing_year', 5: 'score', 6: 'rating_num',
7: 'page_num', 8: 'price', 9: 'ISBN'}
@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'
@app.route('/books', methods=['GET'])
def get_10_books():
new = []
try:
sql_f = "SELECT * FROM douban_book_release"
res = book_db.sql_find(sql_f)
for one in res:
mid = one[:-3]
data = {'book_name': mid[0], 'author': mid[1], 'press': mid[2], 'publishing_year': mid[3],
'score': mid[4], 'rating_num': mid[5], 'page_num': mid[6], 'price': mid[7], 'ISBN': mid[-1]}
new.append(data)
print(new)
return jsonify(new[:10])
except Exception as e:
appLogger.debug(e)
abort(500)
@app.route('/books/<int:ISBN>', methods=['GET'])
def get_book(ISBN):
"""查询书籍信息"""
res = None
try:
sql_f = "SELECT * FROM douban_book_release WHERE ISBN = %s"
res = book_db.sql_find(sql_f, ISBN)[:-3]
if res is not None:
data = {'book_name': res[0], 'author': res[1], 'press': res[2], 'publishing_year': res[3],
'score': res[4], 'rating_num': res[5], 'page_num': res[6], 'price': res[7], 'ISBN': res[-1]}
return jsonify(data)
else:
abort(404) # 找不到此书
except Exception as e:
appLogger.debug('{}, GET book ISBN={} makes error.'.format(e, ISBN))
abort(500)
@app.route('/books/<int:ISBN>', methods=['DELETE'])
def delete_book(ISBN):
"""删除书籍信息"""
try:
sql_d = "DELETE FROM douban_book_release WHERE ISBN = %s"
res = book_db.sql_modify(sql_d, ISBN)
return 'delete ' + res
except Exception as e:
appLogger.debug('{}, GET book_id={} makes error.'.format(e, ISBN))
abort(500)
@app.route('/books/<int:ISBN>', methods=['PUT'])
def update_book(ISBN):
"""修改书籍信息"""
lines = request.form.get('lines')
# 从request.form中获取相应列的数据,构造sql语句所需的修改数据
column_names = [index2book[int(line)] for line in lines]
data = [request.form.get(name) for name in column_names]
data.append(ISBN)
data = tuple(data)
# 负责构造相应的sql语句
sep = ', '
new = []
for line in lines:
new.append(index2book[int(line)] + '=%s')
mid_sql = sep.join(new)
sql_update = "UPDATE douban_book_release SET " + mid_sql + ' WHERE ISBN=%s'
# 执行sql语句
res = book_db.sql_modify(sql_update, data)
return 'put ' + res
@app.route('/books', methods=['POST'])
def create_book():
"""增加书籍"""
book_name = request.form.get('book_name')
author = request.form.get('author')
press = request.form.get('press')
publishing_year = request.form.get('publishing_year')
score = request.form.get('score')
rating_num = request.form.get('rating_num')
page_num = request.form.get('page_num')
price = request.form.get('price')
ISBN = request.form.get('ISBN')
print(ISBN)
sql_i = """
INSERT INTO douban_book_release(book_name, author, press, publishing_year, score, rating_num, page_num, price, ISBN)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
data = (book_name, author, press, publishing_year, score, rating_num, page_num, price, ISBN)
res = book_db.sql_modify(sql_i, data)
return 'post ' + res
# api.add_resource(Book, '/books')
if __name__ == '__main__':
app.run(debug=True)
运行效果展示
初始界面为登录界面,登录界面有用户名输入栏和密码输入栏,按下登录按钮后,根据输入的信息,进入到不同的页面中。
若为管理员账号,会提示登陆成功,点击确认后页面跳转到管理员页面,在该页面中有四个按钮,“查询”,“添加”,“修改”,“删除”。
在搜索栏中填入查询书籍的ISBN号,点击查询后,可跳转到书籍信息页面,具体显示书籍的各项信息,如书名,作者,出版社,出版日期等信息。
点击添加按钮,会跳转到添加图书信息界面,此页面中有多项图书信息需要填入,若不填全信息,则会提示添加书籍失败。填全信息后方可成功添加书籍。添加页面内有“返回”按钮,点击则会返回管理员页面。
点击“修改”按钮,跳转到修改图书信息界面,此页面中必须填入要修改书籍的ISBN号,其他字段按需进行填入以修改,只会更改填入字段的信息。若未填入ISBN号,则会提示修改失败。修改页面内有“返回”按钮,点击则会返回管理员页面。
点击“删除”按钮,会跳出提示栏,在输入栏中填入要删除的书的ISBN号,按下确定,即可删除。输入正确的ISBN号则可删除,错误的ISBN号则会报错。
若为普通用户,则其只有查询功能,同管理员查询功能。
登录
填入管理员账号和密码,点击登陆后,提示进入管理员系统。
填入用户账户密码,进入用户系统。
查询
在搜索栏输入书籍ISBN号,点击查询即可查阅相关书籍信息。
若无此书籍,则不显示信息。
添加
点击“添加”按钮即可进入添加页面。
要求添加数据时需填写全部字段。若没有填写完整,则会提示。
填齐信息后按下按钮,提示书籍添加成功。
可在数据库中查询到此条数据。
修改
管理员页面点击“修改”按钮即可进入修改页面。
此处修改我们刚刚新增书籍的信息,修改书名为“我是修改后的书!”,作者修改为“黄海涛”。其他字段不进行修改。
从下方数据库中查找到此本书,确实只更改了书名和作者,其余字段无更改。
由于修改功能根据ISBN定位书籍资源,因此不填入ISBN号会“修改失败”。
删除
管理员界面点击“删除”按钮,跳出输入栏,提示填入要删除书的ISBN号。
此处输入刚刚新增书籍的ISBN号,点击确认,提示“删除成功”。
从上图可知,数据库中已无此条数据,删除成功。