@ -0,0 +1 @@ | |||||
#!/usr/bin/env python3 |
@ -0,0 +1,4 @@ | |||||
from be import serve | |||||
if __name__ == "__main__": | |||||
serve.be_run() |
@ -0,0 +1 @@ | |||||
#!/usr/bin/env python3 |
@ -0,0 +1,189 @@ | |||||
import sqlite3 as sqlite | |||||
import uuid | |||||
import json | |||||
import logging | |||||
from be.model import db_conn | |||||
from be.model import error | |||||
class Buyer(db_conn.DBConn): | |||||
def __init__(self): | |||||
db_conn.DBConn.__init__(self) | |||||
def new_order( | |||||
self, user_id: str, store_id: str, id_and_count: [(str, int)] | |||||
) -> (int, str, str): | |||||
order_id = "" | |||||
try: | |||||
if not self.user_id_exist(user_id): | |||||
return error.error_non_exist_user_id(user_id) + (order_id,) | |||||
if not self.store_id_exist(store_id): | |||||
return error.error_non_exist_store_id(store_id) + (order_id,) | |||||
uid = "{}_{}_{}".format(user_id, store_id, str(uuid.uuid1())) | |||||
for book_id, count in id_and_count: | |||||
cursor = self.conn.execute( | |||||
"SELECT book_id, stock_level, book_info FROM store " | |||||
"WHERE store_id = ? AND book_id = ?;", | |||||
(store_id, book_id), | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return error.error_non_exist_book_id(book_id) + (order_id,) | |||||
stock_level = row[1] | |||||
book_info = row[2] | |||||
book_info_json = json.loads(book_info) | |||||
price = book_info_json.get("price") | |||||
if stock_level < count: | |||||
return error.error_stock_level_low(book_id) + (order_id,) | |||||
cursor = self.conn.execute( | |||||
"UPDATE store set stock_level = stock_level - ? " | |||||
"WHERE store_id = ? and book_id = ? and stock_level >= ?; ", | |||||
(count, store_id, book_id, count), | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_stock_level_low(book_id) + (order_id,) | |||||
self.conn.execute( | |||||
"INSERT INTO new_order_detail(order_id, book_id, count, price) " | |||||
"VALUES(?, ?, ?, ?);", | |||||
(uid, book_id, count, price), | |||||
) | |||||
self.conn.execute( | |||||
"INSERT INTO new_order(order_id, store_id, user_id) " | |||||
"VALUES(?, ?, ?);", | |||||
(uid, store_id, user_id), | |||||
) | |||||
self.conn.commit() | |||||
order_id = uid | |||||
except sqlite.Error as e: | |||||
logging.info("528, {}".format(str(e))) | |||||
return 528, "{}".format(str(e)), "" | |||||
except BaseException as e: | |||||
logging.info("530, {}".format(str(e))) | |||||
return 530, "{}".format(str(e)), "" | |||||
return 200, "ok", order_id | |||||
def payment(self, user_id: str, password: str, order_id: str) -> (int, str): | |||||
conn = self.conn | |||||
try: | |||||
cursor = conn.execute( | |||||
"SELECT order_id, user_id, store_id FROM new_order WHERE order_id = ?", | |||||
(order_id,), | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return error.error_invalid_order_id(order_id) | |||||
order_id = row[0] | |||||
buyer_id = row[1] | |||||
store_id = row[2] | |||||
if buyer_id != user_id: | |||||
return error.error_authorization_fail() | |||||
cursor = conn.execute( | |||||
"SELECT balance, password FROM user WHERE user_id = ?;", (buyer_id,) | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return error.error_non_exist_user_id(buyer_id) | |||||
balance = row[0] | |||||
if password != row[1]: | |||||
return error.error_authorization_fail() | |||||
cursor = conn.execute( | |||||
"SELECT store_id, user_id FROM user_store WHERE store_id = ?;", | |||||
(store_id,), | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return error.error_non_exist_store_id(store_id) | |||||
seller_id = row[1] | |||||
if not self.user_id_exist(seller_id): | |||||
return error.error_non_exist_user_id(seller_id) | |||||
cursor = conn.execute( | |||||
"SELECT book_id, count, price FROM new_order_detail WHERE order_id = ?;", | |||||
(order_id,), | |||||
) | |||||
total_price = 0 | |||||
for row in cursor: | |||||
count = row[1] | |||||
price = row[2] | |||||
total_price = total_price + price * count | |||||
if balance < total_price: | |||||
return error.error_not_sufficient_funds(order_id) | |||||
cursor = conn.execute( | |||||
"UPDATE user set balance = balance - ?" | |||||
"WHERE user_id = ? AND balance >= ?", | |||||
(total_price, buyer_id, total_price), | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_not_sufficient_funds(order_id) | |||||
cursor = conn.execute( | |||||
"UPDATE user set balance = balance + ?" "WHERE user_id = ?", | |||||
(total_price, seller_id), | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_non_exist_user_id(seller_id) | |||||
cursor = conn.execute( | |||||
"DELETE FROM new_order WHERE order_id = ?", (order_id,) | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_invalid_order_id(order_id) | |||||
cursor = conn.execute( | |||||
"DELETE FROM new_order_detail where order_id = ?", (order_id,) | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_invalid_order_id(order_id) | |||||
conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" | |||||
def add_funds(self, user_id, password, add_value) -> (int, str): | |||||
try: | |||||
cursor = self.conn.execute( | |||||
"SELECT password from user where user_id=?", (user_id,) | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return error.error_authorization_fail() | |||||
if row[0] != password: | |||||
return error.error_authorization_fail() | |||||
cursor = self.conn.execute( | |||||
"UPDATE user SET balance = balance + ? WHERE user_id = ?", | |||||
(add_value, user_id), | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_non_exist_user_id(user_id) | |||||
self.conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" |
@ -0,0 +1,37 @@ | |||||
from be.model import store | |||||
class DBConn: | |||||
def __init__(self): | |||||
self.conn = store.get_db_conn() | |||||
def user_id_exist(self, user_id): | |||||
cursor = self.conn.execute( | |||||
"SELECT user_id FROM user WHERE user_id = ?;", (user_id,) | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return False | |||||
else: | |||||
return True | |||||
def book_id_exist(self, store_id, book_id): | |||||
cursor = self.conn.execute( | |||||
"SELECT book_id FROM store WHERE store_id = ? AND book_id = ?;", | |||||
(store_id, book_id), | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return False | |||||
else: | |||||
return True | |||||
def store_id_exist(self, store_id): | |||||
cursor = self.conn.execute( | |||||
"SELECT store_id FROM user_store WHERE store_id = ?;", (store_id,) | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return False | |||||
else: | |||||
return True |
@ -0,0 +1,65 @@ | |||||
error_code = { | |||||
401: "authorization fail.", | |||||
511: "non exist user id {}", | |||||
512: "exist user id {}", | |||||
513: "non exist store id {}", | |||||
514: "exist store id {}", | |||||
515: "non exist book id {}", | |||||
516: "exist book id {}", | |||||
517: "stock level low, book id {}", | |||||
518: "invalid order id {}", | |||||
519: "not sufficient funds, order id {}", | |||||
520: "", | |||||
521: "", | |||||
522: "", | |||||
523: "", | |||||
524: "", | |||||
525: "", | |||||
526: "", | |||||
527: "", | |||||
528: "", | |||||
} | |||||
def error_non_exist_user_id(user_id): | |||||
return 511, error_code[511].format(user_id) | |||||
def error_exist_user_id(user_id): | |||||
return 512, error_code[512].format(user_id) | |||||
def error_non_exist_store_id(store_id): | |||||
return 513, error_code[513].format(store_id) | |||||
def error_exist_store_id(store_id): | |||||
return 514, error_code[514].format(store_id) | |||||
def error_non_exist_book_id(book_id): | |||||
return 515, error_code[515].format(book_id) | |||||
def error_exist_book_id(book_id): | |||||
return 516, error_code[516].format(book_id) | |||||
def error_stock_level_low(book_id): | |||||
return 517, error_code[517].format(book_id) | |||||
def error_invalid_order_id(order_id): | |||||
return 518, error_code[518].format(order_id) | |||||
def error_not_sufficient_funds(order_id): | |||||
return 519, error_code[518].format(order_id) | |||||
def error_authorization_fail(): | |||||
return 401, error_code[401] | |||||
def error_and_message(code, message): | |||||
return code, message |
@ -0,0 +1,76 @@ | |||||
import sqlite3 as sqlite | |||||
from be.model import error | |||||
from be.model import db_conn | |||||
class Seller(db_conn.DBConn): | |||||
def __init__(self): | |||||
db_conn.DBConn.__init__(self) | |||||
def add_book( | |||||
self, | |||||
user_id: str, | |||||
store_id: str, | |||||
book_id: str, | |||||
book_json_str: str, | |||||
stock_level: int, | |||||
): | |||||
try: | |||||
if not self.user_id_exist(user_id): | |||||
return error.error_non_exist_user_id(user_id) | |||||
if not self.store_id_exist(store_id): | |||||
return error.error_non_exist_store_id(store_id) | |||||
if self.book_id_exist(store_id, book_id): | |||||
return error.error_exist_book_id(book_id) | |||||
self.conn.execute( | |||||
"INSERT into store(store_id, book_id, book_info, stock_level)" | |||||
"VALUES (?, ?, ?, ?)", | |||||
(store_id, book_id, book_json_str, stock_level), | |||||
) | |||||
self.conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" | |||||
def add_stock_level( | |||||
self, user_id: str, store_id: str, book_id: str, add_stock_level: int | |||||
): | |||||
try: | |||||
if not self.user_id_exist(user_id): | |||||
return error.error_non_exist_user_id(user_id) | |||||
if not self.store_id_exist(store_id): | |||||
return error.error_non_exist_store_id(store_id) | |||||
if not self.book_id_exist(store_id, book_id): | |||||
return error.error_non_exist_book_id(book_id) | |||||
self.conn.execute( | |||||
"UPDATE store SET stock_level = stock_level + ? " | |||||
"WHERE store_id = ? AND book_id = ?", | |||||
(add_stock_level, store_id, book_id), | |||||
) | |||||
self.conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" | |||||
def create_store(self, user_id: str, store_id: str) -> (int, str): | |||||
try: | |||||
if not self.user_id_exist(user_id): | |||||
return error.error_non_exist_user_id(user_id) | |||||
if self.store_id_exist(store_id): | |||||
return error.error_exist_store_id(store_id) | |||||
self.conn.execute( | |||||
"INSERT into user_store(store_id, user_id)" "VALUES (?, ?)", | |||||
(store_id, user_id), | |||||
) | |||||
self.conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" |
@ -0,0 +1,66 @@ | |||||
import logging | |||||
import os | |||||
import sqlite3 as sqlite | |||||
import threading | |||||
class Store: | |||||
database: str | |||||
def __init__(self, db_path): | |||||
self.database = os.path.join(db_path, "be.db") | |||||
self.init_tables() | |||||
def init_tables(self): | |||||
try: | |||||
conn = self.get_db_conn() | |||||
conn.execute( | |||||
"CREATE TABLE IF NOT EXISTS user (" | |||||
"user_id TEXT PRIMARY KEY, password TEXT NOT NULL, " | |||||
"balance INTEGER NOT NULL, token TEXT, terminal TEXT);" | |||||
) | |||||
conn.execute( | |||||
"CREATE TABLE IF NOT EXISTS user_store(" | |||||
"user_id TEXT, store_id, PRIMARY KEY(user_id, store_id));" | |||||
) | |||||
conn.execute( | |||||
"CREATE TABLE IF NOT EXISTS store( " | |||||
"store_id TEXT, book_id TEXT, book_info TEXT, stock_level INTEGER," | |||||
" PRIMARY KEY(store_id, book_id))" | |||||
) | |||||
conn.execute( | |||||
"CREATE TABLE IF NOT EXISTS new_order( " | |||||
"order_id TEXT PRIMARY KEY, user_id TEXT, store_id TEXT)" | |||||
) | |||||
conn.execute( | |||||
"CREATE TABLE IF NOT EXISTS new_order_detail( " | |||||
"order_id TEXT, book_id TEXT, count INTEGER, price INTEGER, " | |||||
"PRIMARY KEY(order_id, book_id))" | |||||
) | |||||
conn.commit() | |||||
except sqlite.Error as e: | |||||
logging.error(e) | |||||
conn.rollback() | |||||
def get_db_conn(self) -> sqlite.Connection: | |||||
return sqlite.connect(self.database) | |||||
database_instance: Store = None | |||||
# global variable for database sync | |||||
init_completed_event = threading.Event() | |||||
def init_database(db_path): | |||||
global database_instance | |||||
database_instance = Store(db_path) | |||||
def get_db_conn(): | |||||
global database_instance | |||||
return database_instance.get_db_conn() |
@ -0,0 +1,176 @@ | |||||
import jwt | |||||
import time | |||||
import logging | |||||
import sqlite3 as sqlite | |||||
from be.model import error | |||||
from be.model import db_conn | |||||
# encode a json string like: | |||||
# { | |||||
# "user_id": [user name], | |||||
# "terminal": [terminal code], | |||||
# "timestamp": [ts]} to a JWT | |||||
# } | |||||
def jwt_encode(user_id: str, terminal: str) -> str: | |||||
encoded = jwt.encode( | |||||
{"user_id": user_id, "terminal": terminal, "timestamp": time.time()}, | |||||
key=user_id, | |||||
algorithm="HS256", | |||||
) | |||||
return encoded.decode("utf-8") | |||||
# decode a JWT to a json string like: | |||||
# { | |||||
# "user_id": [user name], | |||||
# "terminal": [terminal code], | |||||
# "timestamp": [ts]} to a JWT | |||||
# } | |||||
def jwt_decode(encoded_token, user_id: str) -> str: | |||||
decoded = jwt.decode(encoded_token, key=user_id, algorithms="HS256") | |||||
return decoded | |||||
class User(db_conn.DBConn): | |||||
token_lifetime: int = 3600 # 3600 second | |||||
def __init__(self): | |||||
db_conn.DBConn.__init__(self) | |||||
def __check_token(self, user_id, db_token, token) -> bool: | |||||
try: | |||||
if db_token != token: | |||||
return False | |||||
jwt_text = jwt_decode(encoded_token=token, user_id=user_id) | |||||
ts = jwt_text["timestamp"] | |||||
if ts is not None: | |||||
now = time.time() | |||||
if self.token_lifetime > now - ts >= 0: | |||||
return True | |||||
except jwt.exceptions.InvalidSignatureError as e: | |||||
logging.error(str(e)) | |||||
return False | |||||
def register(self, user_id: str, password: str): | |||||
try: | |||||
terminal = "terminal_{}".format(str(time.time())) | |||||
token = jwt_encode(user_id, terminal) | |||||
self.conn.execute( | |||||
"INSERT into user(user_id, password, balance, token, terminal) " | |||||
"VALUES (?, ?, ?, ?, ?);", | |||||
(user_id, password, 0, token, terminal), | |||||
) | |||||
self.conn.commit() | |||||
except sqlite.Error: | |||||
return error.error_exist_user_id(user_id) | |||||
return 200, "ok" | |||||
def check_token(self, user_id: str, token: str) -> (int, str): | |||||
cursor = self.conn.execute("SELECT token from user where user_id=?", (user_id,)) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return error.error_authorization_fail() | |||||
db_token = row[0] | |||||
if not self.__check_token(user_id, db_token, token): | |||||
return error.error_authorization_fail() | |||||
return 200, "ok" | |||||
def check_password(self, user_id: str, password: str) -> (int, str): | |||||
cursor = self.conn.execute( | |||||
"SELECT password from user where user_id=?", (user_id,) | |||||
) | |||||
row = cursor.fetchone() | |||||
if row is None: | |||||
return error.error_authorization_fail() | |||||
if password != row[0]: | |||||
return error.error_authorization_fail() | |||||
return 200, "ok" | |||||
def login(self, user_id: str, password: str, terminal: str) -> (int, str, str): | |||||
token = "" | |||||
try: | |||||
code, message = self.check_password(user_id, password) | |||||
if code != 200: | |||||
return code, message, "" | |||||
token = jwt_encode(user_id, terminal) | |||||
cursor = self.conn.execute( | |||||
"UPDATE user set token= ? , terminal = ? where user_id = ?", | |||||
(token, terminal, user_id), | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_authorization_fail() + ("",) | |||||
self.conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)), "" | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)), "" | |||||
return 200, "ok", token | |||||
def logout(self, user_id: str, token: str) -> bool: | |||||
try: | |||||
code, message = self.check_token(user_id, token) | |||||
if code != 200: | |||||
return code, message | |||||
terminal = "terminal_{}".format(str(time.time())) | |||||
dummy_token = jwt_encode(user_id, terminal) | |||||
cursor = self.conn.execute( | |||||
"UPDATE user SET token = ?, terminal = ? WHERE user_id=?", | |||||
(dummy_token, terminal, user_id), | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_authorization_fail() | |||||
self.conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" | |||||
def unregister(self, user_id: str, password: str) -> (int, str): | |||||
try: | |||||
code, message = self.check_password(user_id, password) | |||||
if code != 200: | |||||
return code, message | |||||
cursor = self.conn.execute("DELETE from user where user_id=?", (user_id,)) | |||||
if cursor.rowcount == 1: | |||||
self.conn.commit() | |||||
else: | |||||
return error.error_authorization_fail() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" | |||||
def change_password( | |||||
self, user_id: str, old_password: str, new_password: str | |||||
) -> bool: | |||||
try: | |||||
code, message = self.check_password(user_id, old_password) | |||||
if code != 200: | |||||
return code, message | |||||
terminal = "terminal_{}".format(str(time.time())) | |||||
token = jwt_encode(user_id, terminal) | |||||
cursor = self.conn.execute( | |||||
"UPDATE user set password = ?, token= ? , terminal = ? where user_id = ?", | |||||
(new_password, token, terminal, user_id), | |||||
) | |||||
if cursor.rowcount == 0: | |||||
return error.error_authorization_fail() | |||||
self.conn.commit() | |||||
except sqlite.Error as e: | |||||
return 528, "{}".format(str(e)) | |||||
except BaseException as e: | |||||
return 530, "{}".format(str(e)) | |||||
return 200, "ok" |
@ -0,0 +1,47 @@ | |||||
import logging | |||||
import os | |||||
from flask import Flask | |||||
from flask import Blueprint | |||||
from flask import request | |||||
from be.view import auth | |||||
from be.view import seller | |||||
from be.view import buyer | |||||
from be.model.store import init_database, init_completed_event | |||||
bp_shutdown = Blueprint("shutdown", __name__) | |||||
def shutdown_server(): | |||||
func = request.environ.get("werkzeug.server.shutdown") | |||||
if func is None: | |||||
raise RuntimeError("Not running with the Werkzeug Server") | |||||
func() | |||||
@bp_shutdown.route("/shutdown") | |||||
def be_shutdown(): | |||||
shutdown_server() | |||||
return "Server shutting down..." | |||||
def be_run(): | |||||
this_path = os.path.dirname(__file__) | |||||
parent_path = os.path.dirname(this_path) | |||||
log_file = os.path.join(parent_path, "app.log") | |||||
init_database(parent_path) | |||||
logging.basicConfig(filename=log_file, level=logging.ERROR) | |||||
handler = logging.StreamHandler() | |||||
formatter = logging.Formatter( | |||||
"%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s" | |||||
) | |||||
handler.setFormatter(formatter) | |||||
logging.getLogger().addHandler(handler) | |||||
app = Flask(__name__) | |||||
app.register_blueprint(bp_shutdown) | |||||
app.register_blueprint(auth.bp_auth) | |||||
app.register_blueprint(seller.bp_seller) | |||||
app.register_blueprint(buyer.bp_buyer) | |||||
init_completed_event.set() | |||||
app.run() |
@ -0,0 +1 @@ | |||||
#!/usr/bin/env python3 |
@ -0,0 +1,57 @@ | |||||
from flask import Blueprint | |||||
from flask import request | |||||
from flask import jsonify | |||||
from be.model import user | |||||
bp_auth = Blueprint("auth", __name__, url_prefix="/auth") | |||||
@bp_auth.route("/login", methods=["POST"]) | |||||
def login(): | |||||
user_id = request.json.get("user_id", "") | |||||
password = request.json.get("password", "") | |||||
terminal = request.json.get("terminal", "") | |||||
u = user.User() | |||||
code, message, token = u.login( | |||||
user_id=user_id, password=password, terminal=terminal | |||||
) | |||||
return jsonify({"message": message, "token": token}), code | |||||
@bp_auth.route("/logout", methods=["POST"]) | |||||
def logout(): | |||||
user_id: str = request.json.get("user_id") | |||||
token: str = request.headers.get("token") | |||||
u = user.User() | |||||
code, message = u.logout(user_id=user_id, token=token) | |||||
return jsonify({"message": message}), code | |||||
@bp_auth.route("/register", methods=["POST"]) | |||||
def register(): | |||||
user_id = request.json.get("user_id", "") | |||||
password = request.json.get("password", "") | |||||
u = user.User() | |||||
code, message = u.register(user_id=user_id, password=password) | |||||
return jsonify({"message": message}), code | |||||
@bp_auth.route("/unregister", methods=["POST"]) | |||||
def unregister(): | |||||
user_id = request.json.get("user_id", "") | |||||
password = request.json.get("password", "") | |||||
u = user.User() | |||||
code, message = u.unregister(user_id=user_id, password=password) | |||||
return jsonify({"message": message}), code | |||||
@bp_auth.route("/password", methods=["POST"]) | |||||
def change_password(): | |||||
user_id = request.json.get("user_id", "") | |||||
old_password = request.json.get("oldPassword", "") | |||||
new_password = request.json.get("newPassword", "") | |||||
u = user.User() | |||||
code, message = u.change_password( | |||||
user_id=user_id, old_password=old_password, new_password=new_password | |||||
) | |||||
return jsonify({"message": message}), code |
@ -0,0 +1,42 @@ | |||||
from flask import Blueprint | |||||
from flask import request | |||||
from flask import jsonify | |||||
from be.model.buyer import Buyer | |||||
bp_buyer = Blueprint("buyer", __name__, url_prefix="/buyer") | |||||
@bp_buyer.route("/new_order", methods=["POST"]) | |||||
def new_order(): | |||||
user_id: str = request.json.get("user_id") | |||||
store_id: str = request.json.get("store_id") | |||||
books: [] = request.json.get("books") | |||||
id_and_count = [] | |||||
for book in books: | |||||
book_id = book.get("id") | |||||
count = book.get("count") | |||||
id_and_count.append((book_id, count)) | |||||
b = Buyer() | |||||
code, message, order_id = b.new_order(user_id, store_id, id_and_count) | |||||
return jsonify({"message": message, "order_id": order_id}), code | |||||
@bp_buyer.route("/payment", methods=["POST"]) | |||||
def payment(): | |||||
user_id: str = request.json.get("user_id") | |||||
order_id: str = request.json.get("order_id") | |||||
password: str = request.json.get("password") | |||||
b = Buyer() | |||||
code, message = b.payment(user_id, password, order_id) | |||||
return jsonify({"message": message}), code | |||||
@bp_buyer.route("/add_funds", methods=["POST"]) | |||||
def add_funds(): | |||||
user_id = request.json.get("user_id") | |||||
password = request.json.get("password") | |||||
add_value = request.json.get("add_value") | |||||
b = Buyer() | |||||
code, message = b.add_funds(user_id, password, add_value) | |||||
return jsonify({"message": message}), code |
@ -0,0 +1,44 @@ | |||||
from flask import Blueprint | |||||
from flask import request | |||||
from flask import jsonify | |||||
from be.model import seller | |||||
import json | |||||
bp_seller = Blueprint("seller", __name__, url_prefix="/seller") | |||||
@bp_seller.route("/create_store", methods=["POST"]) | |||||
def seller_create_store(): | |||||
user_id: str = request.json.get("user_id") | |||||
store_id: str = request.json.get("store_id") | |||||
s = seller.Seller() | |||||
code, message = s.create_store(user_id, store_id) | |||||
return jsonify({"message": message}), code | |||||
@bp_seller.route("/add_book", methods=["POST"]) | |||||
def seller_add_book(): | |||||
user_id: str = request.json.get("user_id") | |||||
store_id: str = request.json.get("store_id") | |||||
book_info: str = request.json.get("book_info") | |||||
stock_level: str = request.json.get("stock_level", 0) | |||||
s = seller.Seller() | |||||
code, message = s.add_book( | |||||
user_id, store_id, book_info.get("id"), json.dumps(book_info), stock_level | |||||
) | |||||
return jsonify({"message": message}), code | |||||
@bp_seller.route("/add_stock_level", methods=["POST"]) | |||||
def add_stock_level(): | |||||
user_id: str = request.json.get("user_id") | |||||
store_id: str = request.json.get("store_id") | |||||
book_id: str = request.json.get("book_id") | |||||
add_num: str = request.json.get("add_stock_level", 0) | |||||
s = seller.Seller() | |||||
code, message = s.add_stock_level(user_id, store_id, book_id, add_num) | |||||
return jsonify({"message": message}), code |
@ -0,0 +1,213 @@ | |||||
## 注册用户 | |||||
#### URL: | |||||
POST http://$address$/auth/register | |||||
#### Request | |||||
Body: | |||||
``` | |||||
{ | |||||
"user_id":"$user name$", | |||||
"password":"$user password$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 用户名 | N | |||||
password | string | 登陆密码 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 注册成功 | |||||
5XX | 注册失败,用户名重复 | |||||
Body: | |||||
``` | |||||
{ | |||||
"message":"$error message$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
message | string | 返回错误消息,成功时为"ok" | N | |||||
## 注销用户 | |||||
#### URL: | |||||
POST http://$address$/auth/unregister | |||||
#### Request | |||||
Body: | |||||
``` | |||||
{ | |||||
"user_id":"$user name$", | |||||
"password":"$user password$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 用户名 | N | |||||
password | string | 登陆密码 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 注销成功 | |||||
401 | 注销失败,用户名不存在或密码不正确 | |||||
Body: | |||||
``` | |||||
{ | |||||
"message":"$error message$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
message | string | 返回错误消息,成功时为"ok" | N | |||||
## 用户登录 | |||||
#### URL: | |||||
POST http://$address$/auth/login | |||||
#### Request | |||||
Body: | |||||
``` | |||||
{ | |||||
"user_id":"$user name$", | |||||
"password":"$user password$", | |||||
"terminal":"$terminal code$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 用户名 | N | |||||
password | string | 登陆密码 | N | |||||
terminal | string | 终端代码 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 登录成功 | |||||
401 | 登录失败,用户名或密码错误 | |||||
Body: | |||||
``` | |||||
{ | |||||
"message":"$error message$", | |||||
"token":"$access token$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
message | string | 返回错误消息,成功时为"ok" | N | |||||
token | string | 访问token,用户登录后每个需要授权的请求应在headers中传入这个token | 成功时不为空 | |||||
#### 说明 | |||||
1.terminal标识是哪个设备登录的,不同的设备拥有不同的ID,测试时可以随机生成。 | |||||
2.token是登录后,在客户端中缓存的令牌,在用户登录时由服务端生成,用户在接下来的访问请求时不需要密码。token会定期地失效,对于不同的设备,token是不同的。token只对特定的时期特定的设备是有效的。 | |||||
## 用户更改密码 | |||||
#### URL: | |||||
POST http://$address$/auth/password | |||||
#### Request | |||||
Body: | |||||
``` | |||||
{ | |||||
"user_id":"$user name$", | |||||
"oldPassword":"$old password$", | |||||
"newPassword":"$new password$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 用户名 | N | |||||
oldPassword | string | 旧的登陆密码 | N | |||||
newPassword | string | 新的登陆密码 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 更改密码成功 | |||||
401 | 更改密码失败 | |||||
Body: | |||||
``` | |||||
{ | |||||
"message":"$error message$", | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
message | string | 返回错误消息,成功时为"ok" | N | |||||
## 用户登出 | |||||
#### URL: | |||||
POST http://$address$/auth/logout | |||||
#### Request | |||||
Headers: | |||||
key | 类型 | 描述 | |||||
---|---|--- | |||||
token | string | 访问token | |||||
Body: | |||||
``` | |||||
{ | |||||
"user_id":"$user name$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 用户名 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 登出成功 | |||||
401 | 登出失败,用户名或token错误 | |||||
Body: | |||||
``` | |||||
{ | |||||
"message":"$error message$" | |||||
} | |||||
``` | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
message | string | 返回错误消息,成功时为"ok" | N |
@ -0,0 +1,144 @@ | |||||
## 买家下单 | |||||
#### URL: | |||||
POST http://[address]/buyer/new_order | |||||
#### Request | |||||
##### Header: | |||||
key | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
token | string | 登录产生的会话标识 | N | |||||
##### Body: | |||||
```json | |||||
{ | |||||
"user_id": "buyer_id", | |||||
"store_id": "store_id", | |||||
"books": [ | |||||
{ | |||||
"id": "1000067", | |||||
"count": 1 | |||||
}, | |||||
{ | |||||
"id": "1000134", | |||||
"count": 4 | |||||
} | |||||
] | |||||
} | |||||
``` | |||||
##### 属性说明: | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 买家用户ID | N | |||||
store_id | string | 商铺ID | N | |||||
books | class | 书籍购买列表 | N | |||||
books数组: | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
id | string | 书籍的ID | N | |||||
count | string | 购买数量 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 下单成功 | |||||
5XX | 买家用户ID不存在 | |||||
5XX | 商铺ID不存在 | |||||
5XX | 购买的图书不存在 | |||||
5XX | 商品库存不足 | |||||
##### Body: | |||||
```json | |||||
{ | |||||
"order_id": "uuid" | |||||
} | |||||
``` | |||||
##### 属性说明: | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
order_id | string | 订单号,只有返回200时才有效 | N | |||||
## 买家付款 | |||||
#### URL: | |||||
POST http://[address]/buyer/payment | |||||
#### Request | |||||
##### Body: | |||||
```json | |||||
{ | |||||
"user_id": "buyer_id", | |||||
"order_id": "order_id", | |||||
"password": "password" | |||||
} | |||||
``` | |||||
##### 属性说明: | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 买家用户ID | N | |||||
order_id | string | 订单ID | N | |||||
password | string | 买家用户密码 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 付款成功 | |||||
5XX | 账户余额不足 | |||||
5XX | 无效参数 | |||||
401 | 授权失败 | |||||
## 买家充值 | |||||
#### URL: | |||||
POST http://[address]/buyer/add_funds | |||||
#### Request | |||||
##### Body: | |||||
```json | |||||
{ | |||||
"user_id": "user_id", | |||||
"password": "password", | |||||
"add_value": 10 | |||||
} | |||||
``` | |||||
##### 属性说明: | |||||
key | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 买家用户ID | N | |||||
password | string | 用户密码 | N | |||||
add_value | int | 充值金额,以分为单位 | N | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 充值成功 | |||||
401 | 授权失败 | |||||
5XX | 无效参数 |
@ -0,0 +1,178 @@ | |||||
## 创建商铺 | |||||
#### URL | |||||
POST http://[address]/seller/create_store | |||||
#### Request | |||||
Headers: | |||||
key | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
token | string | 登录产生的会话标识 | N | |||||
Body: | |||||
```json | |||||
{ | |||||
"user_id": "$seller id$", | |||||
"store_id": "$store id$" | |||||
} | |||||
``` | |||||
key | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 卖家用户ID | N | |||||
store_id | string | 商铺ID | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 创建商铺成功 | |||||
5XX | 商铺ID已存在 | |||||
## 商家添加书籍信息 | |||||
#### URL: | |||||
POST http://[address]/seller/add_book | |||||
#### Request | |||||
Headers: | |||||
key | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
token | string | 登录产生的会话标识 | N | |||||
Body: | |||||
```json | |||||
{ | |||||
"user_id": "$seller user id$", | |||||
"store_id": "$store id$", | |||||
"book_info": { | |||||
"tags": [ | |||||
"tags1", | |||||
"tags2", | |||||
"tags3", | |||||
"..." | |||||
], | |||||
"pictures": [ | |||||
"$Base 64 encoded bytes array1$", | |||||
"$Base 64 encoded bytes array2$", | |||||
"$Base 64 encoded bytes array3$", | |||||
"..." | |||||
], | |||||
"id": "$book id$", | |||||
"title": "$book title$", | |||||
"author": "$book author$", | |||||
"publisher": "$book publisher$", | |||||
"original_title": "$original title$", | |||||
"translator": "translater", | |||||
"pub_year": "$pub year$", | |||||
"pages": 10, | |||||
"price": 10, | |||||
"binding": "平装", | |||||
"isbn": "$isbn$", | |||||
"author_intro": "$author introduction$", | |||||
"book_intro": "$book introduction$", | |||||
"content": "$chapter1 ...$" | |||||
}, | |||||
"stock_level": 0 | |||||
} | |||||
``` | |||||
属性说明: | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 卖家用户ID | N | |||||
store_id | string | 商铺ID | N | |||||
book_info | class | 书籍信息 | N | |||||
stock_level | int | 初始库存,大于等于0 | N | |||||
book_info类: | |||||
变量名 | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
id | string | 书籍ID | N | |||||
title | string | 书籍题目 | N | |||||
author | string | 作者 | Y | |||||
publisher | string | 出版社 | Y | |||||
original_title | string | 原书题目 | Y | |||||
translator | string | 译者 | Y | |||||
pub_year | string | 出版年月 | Y | |||||
pages | int | 页数 | Y | |||||
price | int | 价格(以分为单位) | N | |||||
binding | string | 装帧,精状/平装 | Y | |||||
isbn | string | ISBN号 | Y | |||||
author_intro | string | 作者简介 | Y | |||||
book_intro | string | 书籍简介 | Y | |||||
content | string | 样章试读 | Y | |||||
tags | array | 标签 | Y | |||||
pictures | array | 照片 | Y | |||||
tags和pictures: | |||||
tags 中每个数组元素都是string类型 | |||||
picture 中每个数组元素都是string(base64表示的bytes array)类型 | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | --- | |||||
200 | 添加图书信息成功 | |||||
5XX | 卖家用户ID不存在 | |||||
5XX | 商铺ID不存在 | |||||
5XX | 图书ID已存在 | |||||
## 商家添加书籍库存 | |||||
#### URL | |||||
POST http://[address]/seller/add_stock_level | |||||
#### Request | |||||
Headers: | |||||
key | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
token | string | 登录产生的会话标识 | N | |||||
Body: | |||||
```json | |||||
{ | |||||
"user_id": "$seller id$", | |||||
"store_id": "$store id$", | |||||
"book_id": "$book id$", | |||||
"add_stock_level": 10 | |||||
} | |||||
``` | |||||
key | 类型 | 描述 | 是否可为空 | |||||
---|---|---|--- | |||||
user_id | string | 卖家用户ID | N | |||||
store_id | string | 商铺ID | N | |||||
book_id | string | 书籍ID | N | |||||
add_stock_level | int | 增加的库存量 | N | |||||
#### Response | |||||
Status Code: | |||||
码 | 描述 | |||||
--- | :-- | |||||
200 | 创建商铺成功 | |||||
5XX | 商铺ID不存在 | |||||
5XX | 图书ID不存在 |
@ -0,0 +1 @@ | |||||
#!/usr/bin/env python3 |
@ -0,0 +1 @@ | |||||
#!/usr/bin/env python3 |
@ -0,0 +1,42 @@ | |||||
import requests | |||||
from urllib.parse import urljoin | |||||
class Auth: | |||||
def __init__(self, url_prefix): | |||||
self.url_prefix = urljoin(url_prefix, "auth/") | |||||
def login(self, user_id: str, password: str, terminal: str) -> (int, str): | |||||
json = {"user_id": user_id, "password": password, "terminal": terminal} | |||||
url = urljoin(self.url_prefix, "login") | |||||
r = requests.post(url, json=json) | |||||
return r.status_code, r.json().get("token") | |||||
def register(self, user_id: str, password: str) -> int: | |||||
json = {"user_id": user_id, "password": password} | |||||
url = urljoin(self.url_prefix, "register") | |||||
r = requests.post(url, json=json) | |||||
return r.status_code | |||||
def password(self, user_id: str, old_password: str, new_password: str) -> int: | |||||
json = { | |||||
"user_id": user_id, | |||||
"oldPassword": old_password, | |||||
"newPassword": new_password, | |||||
} | |||||
url = urljoin(self.url_prefix, "password") | |||||
r = requests.post(url, json=json) | |||||
return r.status_code | |||||
def logout(self, user_id: str, token: str) -> int: | |||||
json = {"user_id": user_id} | |||||
headers = {"token": token} | |||||
url = urljoin(self.url_prefix, "logout") | |||||
r = requests.post(url, headers=headers, json=json) | |||||
return r.status_code | |||||
def unregister(self, user_id: str, password: str) -> int: | |||||
json = {"user_id": user_id, "password": password} | |||||
url = urljoin(self.url_prefix, "unregister") | |||||
r = requests.post(url, json=json) | |||||
return r.status_code |
@ -0,0 +1,97 @@ | |||||
import os | |||||
import sqlite3 as sqlite | |||||
import random | |||||
import base64 | |||||
import simplejson as json | |||||
class Book: | |||||
id: str | |||||
title: str | |||||
author: str | |||||
publisher: str | |||||
original_title: str | |||||
translator: str | |||||
pub_year: str | |||||
pages: int | |||||
price: int | |||||
currency_unit: str | |||||
binding: str | |||||
isbn: str | |||||
author_intro: str | |||||
book_intro: str | |||||
content: str | |||||
tags: [str] | |||||
pictures: [bytes] | |||||
def __init__(self): | |||||
self.tags = [] | |||||
self.pictures = [] | |||||
class BookDB: | |||||
def __init__(self, large: bool = False): | |||||
parent_path = os.path.dirname(os.path.dirname(__file__)) | |||||
self.db_s = os.path.join(parent_path, "data/book.db") | |||||
self.db_l = os.path.join(parent_path, "data/book_lx.db") | |||||
if large: | |||||
self.book_db = self.db_l | |||||
else: | |||||
self.book_db = self.db_s | |||||
def get_book_count(self): | |||||
conn = sqlite.connect(self.book_db) | |||||
cursor = conn.execute("SELECT count(id) FROM book") | |||||
row = cursor.fetchone() | |||||
return row[0] | |||||
def get_book_info(self, start, size) -> [Book]: | |||||
books = [] | |||||
conn = sqlite.connect(self.book_db) | |||||
cursor = conn.execute( | |||||
"SELECT id, title, author, " | |||||
"publisher, original_title, " | |||||
"translator, pub_year, pages, " | |||||
"price, currency_unit, binding, " | |||||
"isbn, author_intro, book_intro, " | |||||
"content, tags, picture FROM book ORDER BY id " | |||||
"LIMIT ? OFFSET ?", | |||||
(size, start), | |||||
) | |||||
for row in cursor: | |||||
book = Book() | |||||
book.id = row[0] | |||||
book.title = row[1] | |||||
book.author = row[2] | |||||
book.publisher = row[3] | |||||
book.original_title = row[4] | |||||
book.translator = row[5] | |||||
book.pub_year = row[6] | |||||
book.pages = row[7] | |||||
book.price = row[8] | |||||
book.currency_unit = row[9] | |||||
book.binding = row[10] | |||||
book.isbn = row[11] | |||||
book.author_intro = row[12] | |||||
book.book_intro = row[13] | |||||
book.content = row[14] | |||||
tags = row[15] | |||||
picture = row[16] | |||||
for tag in tags.split("\n"): | |||||
if tag.strip() != "": | |||||
book.tags.append(tag) | |||||
for i in range(0, random.randint(0, 9)): | |||||
if picture is not None: | |||||
encode_str = base64.b64encode(picture).decode("utf-8") | |||||
book.pictures.append(encode_str) | |||||
books.append(book) | |||||
# print(tags.decode('utf-8')) | |||||
# print(book.tags, len(book.picture)) | |||||
# print(book) | |||||
# print(tags) | |||||
return books |
@ -0,0 +1,50 @@ | |||||
import requests | |||||
import simplejson | |||||
from urllib.parse import urljoin | |||||
from fe.access.auth import Auth | |||||
class Buyer: | |||||
def __init__(self, url_prefix, user_id, password): | |||||
self.url_prefix = urljoin(url_prefix, "buyer/") | |||||
self.user_id = user_id | |||||
self.password = password | |||||
self.token = "" | |||||
self.terminal = "my terminal" | |||||
self.auth = Auth(url_prefix) | |||||
code, self.token = self.auth.login(self.user_id, self.password, self.terminal) | |||||
assert code == 200 | |||||
def new_order(self, store_id: str, book_id_and_count: [(str, int)]) -> (int, str): | |||||
books = [] | |||||
for id_count_pair in book_id_and_count: | |||||
books.append({"id": id_count_pair[0], "count": id_count_pair[1]}) | |||||
json = {"user_id": self.user_id, "store_id": store_id, "books": books} | |||||
# print(simplejson.dumps(json)) | |||||
url = urljoin(self.url_prefix, "new_order") | |||||
headers = {"token": self.token} | |||||
r = requests.post(url, headers=headers, json=json) | |||||
response_json = r.json() | |||||
return r.status_code, response_json.get("order_id") | |||||
def payment(self, order_id: str): | |||||
json = { | |||||
"user_id": self.user_id, | |||||
"password": self.password, | |||||
"order_id": order_id, | |||||
} | |||||
url = urljoin(self.url_prefix, "payment") | |||||
headers = {"token": self.token} | |||||
r = requests.post(url, headers=headers, json=json) | |||||
return r.status_code | |||||
def add_funds(self, add_value: str) -> int: | |||||
json = { | |||||
"user_id": self.user_id, | |||||
"password": self.password, | |||||
"add_value": add_value, | |||||
} | |||||
url = urljoin(self.url_prefix, "add_funds") | |||||
headers = {"token": self.token} | |||||
r = requests.post(url, headers=headers, json=json) | |||||
return r.status_code |
@ -0,0 +1,10 @@ | |||||
from fe import conf | |||||
from fe.access import buyer, auth | |||||
def register_new_buyer(user_id, password) -> buyer.Buyer: | |||||
a = auth.Auth(conf.URL) | |||||
code = a.register(user_id, password) | |||||
assert code == 200 | |||||
s = buyer.Buyer(conf.URL, user_id, password) | |||||
return s |
@ -0,0 +1,10 @@ | |||||
from fe import conf | |||||
from fe.access import seller, auth | |||||
def register_new_seller(user_id, password) -> seller.Seller: | |||||
a = auth.Auth(conf.URL) | |||||
code = a.register(user_id, password) | |||||
assert code == 200 | |||||
s = seller.Seller(conf.URL, user_id, password) | |||||
return s |
@ -0,0 +1,54 @@ | |||||
import requests | |||||
from urllib.parse import urljoin | |||||
from fe.access import book | |||||
from fe.access.auth import Auth | |||||
class Seller: | |||||
def __init__(self, url_prefix, seller_id: str, password: str): | |||||
self.url_prefix = urljoin(url_prefix, "seller/") | |||||
self.seller_id = seller_id | |||||
self.password = password | |||||
self.terminal = "my terminal" | |||||
self.auth = Auth(url_prefix) | |||||
code, self.token = self.auth.login(self.seller_id, self.password, self.terminal) | |||||
assert code == 200 | |||||
def create_store(self, store_id): | |||||
json = { | |||||
"user_id": self.seller_id, | |||||
"store_id": store_id, | |||||
} | |||||
# print(simplejson.dumps(json)) | |||||
url = urljoin(self.url_prefix, "create_store") | |||||
headers = {"token": self.token} | |||||
r = requests.post(url, headers=headers, json=json) | |||||
return r.status_code | |||||
def add_book(self, store_id: str, stock_level: int, book_info: book.Book) -> int: | |||||
json = { | |||||
"user_id": self.seller_id, | |||||
"store_id": store_id, | |||||
"book_info": book_info.__dict__, | |||||
"stock_level": stock_level, | |||||
} | |||||
# print(simplejson.dumps(json)) | |||||
url = urljoin(self.url_prefix, "add_book") | |||||
headers = {"token": self.token} | |||||
r = requests.post(url, headers=headers, json=json) | |||||
return r.status_code | |||||
def add_stock_level( | |||||
self, seller_id: str, store_id: str, book_id: str, add_stock_num: int | |||||
) -> int: | |||||
json = { | |||||
"user_id": seller_id, | |||||
"store_id": store_id, | |||||
"book_id": book_id, | |||||
"add_stock_level": add_stock_num, | |||||
} | |||||
# print(simplejson.dumps(json)) | |||||
url = urljoin(self.url_prefix, "add_stock_level") | |||||
headers = {"token": self.token} | |||||
r = requests.post(url, headers=headers, json=json) | |||||
return r.status_code |
@ -0,0 +1 @@ | |||||
#!/usr/bin/env python3 |
@ -0,0 +1 @@ | |||||
add performance test here |
@ -0,0 +1,22 @@ | |||||
from fe.bench.workload import Workload | |||||
from fe.bench.session import Session | |||||
def run_bench(): | |||||
wl = Workload() | |||||
wl.gen_database() | |||||
sessions = [] | |||||
for i in range(0, wl.session): | |||||
ss = Session(wl) | |||||
sessions.append(ss) | |||||
for ss in sessions: | |||||
ss.start() | |||||
for ss in sessions: | |||||
ss.join() | |||||
# if __name__ == "__main__": | |||||
# run_bench() |
@ -0,0 +1,61 @@ | |||||
from fe.bench.workload import Workload | |||||
from fe.bench.workload import NewOrder | |||||
from fe.bench.workload import Payment | |||||
import time | |||||
import threading | |||||
class Session(threading.Thread): | |||||
def __init__(self, wl: Workload): | |||||
threading.Thread.__init__(self) | |||||
self.workload = wl | |||||
self.new_order_request = [] | |||||
self.payment_request = [] | |||||
self.payment_i = 0 | |||||
self.new_order_i = 0 | |||||
self.payment_ok = 0 | |||||
self.new_order_ok = 0 | |||||
self.time_new_order = 0 | |||||
self.time_payment = 0 | |||||
self.thread = None | |||||
self.gen_procedure() | |||||
def gen_procedure(self): | |||||
for i in range(0, self.workload.procedure_per_session): | |||||
new_order = self.workload.get_new_order() | |||||
self.new_order_request.append(new_order) | |||||
def run(self): | |||||
self.run_gut() | |||||
def run_gut(self): | |||||
for new_order in self.new_order_request: | |||||
before = time.time() | |||||
ok, order_id = new_order.run() | |||||
after = time.time() | |||||
self.time_new_order = self.time_new_order + after - before | |||||
self.new_order_i = self.new_order_i + 1 | |||||
if ok: | |||||
self.new_order_ok = self.new_order_ok + 1 | |||||
payment = Payment(new_order.buyer, order_id) | |||||
self.payment_request.append(payment) | |||||
if self.new_order_i % 100 ==0 or self.new_order_i == len( | |||||
self.new_order_request | |||||
): | |||||
self.workload.update_stat( | |||||
self.new_order_i, | |||||
self.payment_i, | |||||
self.new_order_ok, | |||||
self.payment_ok, | |||||
self.time_new_order, | |||||
self.time_payment, | |||||
) | |||||
for payment in self.payment_request: | |||||
before = time.time() | |||||
ok = payment.run() | |||||
after = time.time() | |||||
self.time_payment = self.time_payment + after - before | |||||
self.payment_i = self.payment_i + 1 | |||||
if ok: | |||||
self.payment_ok = self.payment_ok + 1 | |||||
self.payment_request = [] |
@ -0,0 +1,194 @@ | |||||
import logging | |||||
import uuid | |||||
import random | |||||
import threading | |||||
from fe.access import book | |||||
from fe.access.new_seller import register_new_seller | |||||
from fe.access.new_buyer import register_new_buyer | |||||
from fe.access.buyer import Buyer | |||||
from fe import conf | |||||
class NewOrder: | |||||
def __init__(self, buyer: Buyer, store_id, book_id_and_count): | |||||
self.buyer = buyer | |||||
self.store_id = store_id | |||||
self.book_id_and_count = book_id_and_count | |||||
def run(self) -> (bool, str): | |||||
code, order_id = self.buyer.new_order(self.store_id, self.book_id_and_count) | |||||
return code == 200, order_id | |||||
class Payment: | |||||
def __init__(self, buyer: Buyer, order_id): | |||||
self.buyer = buyer | |||||
self.order_id = order_id | |||||
def run(self) -> bool: | |||||
code = self.buyer.payment(self.order_id) | |||||
return code == 200 | |||||
class Workload: | |||||
def __init__(self): | |||||
self.uuid = str(uuid.uuid1()) | |||||
self.book_ids = {} | |||||
self.buyer_ids = [] | |||||
self.store_ids = [] | |||||
self.book_db = book.BookDB(conf.Use_Large_DB) | |||||
self.row_count = self.book_db.get_book_count() | |||||
self.book_num_per_store = conf.Book_Num_Per_Store | |||||
if self.row_count < self.book_num_per_store: | |||||
self.book_num_per_store = self.row_count | |||||
self.store_num_per_user = conf.Store_Num_Per_User | |||||
self.seller_num = conf.Seller_Num | |||||
self.buyer_num = conf.Buyer_Num | |||||
self.session = conf.Session | |||||
self.stock_level = conf.Default_Stock_Level | |||||
self.user_funds = conf.Default_User_Funds | |||||
self.batch_size = conf.Data_Batch_Size | |||||
self.procedure_per_session = conf.Request_Per_Session | |||||
self.n_new_order = 0 | |||||
self.n_payment = 0 | |||||
self.n_new_order_ok = 0 | |||||
self.n_payment_ok = 0 | |||||
self.time_new_order = 0 | |||||
self.time_payment = 0 | |||||
self.lock = threading.Lock() | |||||
# 存储上一次的值,用于两次做差 | |||||
self.n_new_order_past = 0 | |||||
self.n_payment_past = 0 | |||||
self.n_new_order_ok_past = 0 | |||||
self.n_payment_ok_past = 0 | |||||
def to_seller_id_and_password(self, no: int) -> (str, str): | |||||
return "seller_{}_{}".format(no, self.uuid), "password_seller_{}_{}".format( | |||||
no, self.uuid | |||||
) | |||||
def to_buyer_id_and_password(self, no: int) -> (str, str): | |||||
return "buyer_{}_{}".format(no, self.uuid), "buyer_seller_{}_{}".format( | |||||
no, self.uuid | |||||
) | |||||
def to_store_id(self, seller_no: int, i): | |||||
return "store_s_{}_{}_{}".format(seller_no, i, self.uuid) | |||||
def gen_database(self): | |||||
logging.info("load data") | |||||
for i in range(1, self.seller_num + 1): | |||||
user_id, password = self.to_seller_id_and_password(i) | |||||
seller = register_new_seller(user_id, password) | |||||
for j in range(1, self.store_num_per_user + 1): | |||||
store_id = self.to_store_id(i, j) | |||||
code = seller.create_store(store_id) | |||||
assert code == 200 | |||||
self.store_ids.append(store_id) | |||||
self.book_ids[store_id] = [] | |||||
row_no = 0 | |||||
while row_no < self.book_num_per_store: | |||||
books = self.book_db.get_book_info(row_no, self.batch_size) | |||||
if len(books) == 0: | |||||
break | |||||
for bk in books: | |||||
code = seller.add_book(store_id, self.stock_level, bk) | |||||
assert code == 200 | |||||
self.book_ids[store_id].append(bk.id) | |||||
row_no = row_no + len(books) | |||||
logging.info("seller data loaded.") | |||||
for k in range(1, self.buyer_num + 1): | |||||
user_id, password = self.to_buyer_id_and_password(k) | |||||
buyer = register_new_buyer(user_id, password) | |||||
buyer.add_funds(self.user_funds) | |||||
self.buyer_ids.append(user_id) | |||||
logging.info("buyer data loaded.") | |||||
def get_new_order(self) -> NewOrder: | |||||
n = random.randint(1, self.buyer_num) | |||||
buyer_id, buyer_password = self.to_buyer_id_and_password(n) | |||||
store_no = int(random.uniform(0, len(self.store_ids) - 1)) | |||||
store_id = self.store_ids[store_no] | |||||
books = random.randint(1, 10) | |||||
book_id_and_count = [] | |||||
book_temp = [] | |||||
for i in range(0, books): | |||||
book_no = int(random.uniform(0, len(self.book_ids[store_id]) - 1)) | |||||
book_id = self.book_ids[store_id][book_no] | |||||
if book_id in book_temp: | |||||
continue | |||||
else: | |||||
book_temp.append(book_id) | |||||
count = random.randint(1, 10) | |||||
book_id_and_count.append((book_id, count)) | |||||
b = Buyer(url_prefix=conf.URL, user_id=buyer_id, password=buyer_password) | |||||
new_ord = NewOrder(b, store_id, book_id_and_count) | |||||
return new_ord | |||||
def update_stat( | |||||
self, | |||||
n_new_order, | |||||
n_payment, | |||||
n_new_order_ok, | |||||
n_payment_ok, | |||||
time_new_order, | |||||
time_payment, | |||||
): | |||||
# 获取当前并发数 | |||||
thread_num = len(threading.enumerate()) | |||||
# 加索 | |||||
self.lock.acquire() | |||||
self.n_new_order = self.n_new_order + n_new_order | |||||
self.n_payment = self.n_payment + n_payment | |||||
self.n_new_order_ok = self.n_new_order_ok + n_new_order_ok | |||||
self.n_payment_ok = self.n_payment_ok + n_payment_ok | |||||
self.time_new_order = self.time_new_order + time_new_order | |||||
self.time_payment = self.time_payment + time_payment | |||||
# 计算这段时间内新创建订单的总数目 | |||||
n_new_order_diff = self.n_new_order - self.n_new_order_past | |||||
# 计算这段时间内新付款订单的总数目 | |||||
n_payment_diff = self.n_payment - self.n_payment_past | |||||
if ( | |||||
self.n_payment != 0 | |||||
and self.n_new_order != 0 | |||||
and (self.time_payment + self.time_new_order) | |||||
): | |||||
# TPS_C(吞吐量):成功创建订单数量/(提交订单时间/提交订单并发数 + 提交付款订单时间/提交付款订单并发数) | |||||
# NO=OK:新创建订单数量 | |||||
# Thread_num:以新提交订单的数量作为并发数(这一次的TOTAL-上一次的TOTAL) | |||||
# TOTAL:总提交订单数量 | |||||
# LATENCY:提交订单时间/处理订单笔数(只考虑该线程延迟,未考虑并发) | |||||
# P=OK:新创建付款订单数量 | |||||
# Thread_num:以新提交付款订单的数量作为并发数(这一次的TOTAL-上一次的TOTAL) | |||||
# TOTAL:总付款提交订单数量 | |||||
# LATENCY:提交付款订单时间/处理付款订单笔数(只考虑该线程延迟,未考虑并发) | |||||
logging.info( | |||||
"TPS_C={}, NO=OK:{} Thread_num:{} TOTAL:{} LATENCY:{} , P=OK:{} Thread_num:{} TOTAL:{} LATENCY:{}".format( | |||||
int( | |||||
self.n_new_order_ok | |||||
/ ( | |||||
self.time_payment / n_payment_diff | |||||
+ self.time_new_order / n_new_order_diff | |||||
) | |||||
), # 吞吐量:完成订单数/((付款所用时间+订单所用时间)/并发数) | |||||
self.n_new_order_ok, | |||||
n_new_order_diff, | |||||
self.n_new_order, | |||||
self.time_new_order | |||||
/ self.n_new_order, # 订单延迟:(创建订单所用时间/并发数)/新创建订单数 | |||||
self.n_payment_ok, | |||||
n_payment_diff, | |||||
self.n_payment, | |||||
self.time_payment / self.n_payment, # 付款延迟:(付款所用时间/并发数)/付款订单数 | |||||
) | |||||
) | |||||
self.lock.release() | |||||
# 旧值更新为新值,便于下一轮计算 | |||||
self.n_new_order_past = self.n_new_order | |||||
self.n_payment_past = self.n_payment | |||||
self.n_new_order_ok_past = self.n_new_order_ok | |||||
self.n_payment_ok_past = self.n_payment_ok |
@ -0,0 +1,11 @@ | |||||
URL = "http://127.0.0.1:5000/" | |||||
Book_Num_Per_Store = 2000 | |||||
Store_Num_Per_User = 2 | |||||
Seller_Num = 2 | |||||
Buyer_Num = 10 | |||||
Session = 1 | |||||
Request_Per_Session = 1000 | |||||
Default_Stock_Level = 1000000 | |||||
Default_User_Funds = 10000000 | |||||
Data_Batch_Size = 100 | |||||
Use_Large_DB = True |
@ -0,0 +1,29 @@ | |||||
import requests | |||||
import threading | |||||
from urllib.parse import urljoin | |||||
from be import serve | |||||
from be.model.store import init_completed_event | |||||
from fe import conf | |||||
thread: threading.Thread = None | |||||
# 修改这里启动后端程序,如果不需要可删除这行代码 | |||||
def run_backend(): | |||||
# rewrite this if rewrite backend | |||||
serve.be_run() | |||||
def pytest_configure(config): | |||||
global thread | |||||
print("frontend begin test") | |||||
thread = threading.Thread(target=run_backend) | |||||
thread.start() | |||||
init_completed_event.wait() | |||||
def pytest_unconfigure(config): | |||||
url = urljoin(conf.URL, "shutdown") | |||||
requests.get(url) | |||||
thread.join() | |||||
print("frontend end test") |
@ -0,0 +1,425 @@ | |||||
# coding=utf-8 | |||||
from lxml import etree | |||||
import sqlite3 | |||||
import re | |||||
import requests | |||||
import random | |||||
import time | |||||
import logging | |||||
user_agent = [ | |||||
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 " | |||||
"Safari/534.50", | |||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 " | |||||
"Safari/534.50", | |||||
"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0", | |||||
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR " | |||||
"3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) like Gecko", | |||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", | |||||
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)", | |||||
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)", | |||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", | |||||
"Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", | |||||
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11", | |||||
"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", | |||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 " | |||||
"Safari/535.11", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET " | |||||
"CLR 2.0.50727; SE 2.X MetaSr 1.0)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)", | |||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)", | |||||
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) " | |||||
"Version/5.0.2 Mobile/8J2 Safari/6533.18.5", | |||||
"Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) " | |||||
"Version/5.0.2 Mobile/8J2 Safari/6533.18.5", | |||||
"Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) " | |||||
"Version/5.0.2 Mobile/8J2 Safari/6533.18.5", | |||||
"Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) " | |||||
"Version/4.0 Mobile Safari/533.1", | |||||
"MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) " | |||||
"AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", | |||||
"Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10", | |||||
"Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) " | |||||
"Version/4.0 Safari/534.13", | |||||
"Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 " | |||||
"Mobile Safari/534.1+", | |||||
"Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) " | |||||
"wOSBrowser/233.70 Safari/534.6 TouchPad/1.0", | |||||
"Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) " | |||||
"AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124", | |||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)", | |||||
"UCWEB7.0.2.37/28/999", | |||||
"NOKIA5700/ UCWEB7.0.2.37/28/999", | |||||
"Openwave/ UCWEB7.0.2.37/28/999", | |||||
"Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999", | |||||
# iPhone 6: | |||||
"Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 " | |||||
"Mobile/10A5376e Safari/8536.25", | |||||
] | |||||
def get_user_agent(): | |||||
headers = {"User-Agent": random.choice(user_agent)} | |||||
return headers | |||||
class Scraper: | |||||
database: str | |||||
tag: str | |||||
page: int | |||||
def __init__(self): | |||||
self.database = "book.db" | |||||
self.tag = "" | |||||
self.page = 0 | |||||
self.pattern_number = re.compile(r"\d+\.?\d*") | |||||
logging.basicConfig(filename="scraper.log", level=logging.ERROR) | |||||
def get_current_progress(self) -> (): | |||||
conn = sqlite3.connect(self.database) | |||||
results = conn.execute("SELECT tag, page from progress where id = '0'") | |||||
for row in results: | |||||
return row[0], row[1] | |||||
return "", 0 | |||||
def save_current_progress(self, current_tag, current_page): | |||||
conn = sqlite3.connect(self.database) | |||||
conn.execute( | |||||
"UPDATE progress set tag = '{}', page = {} where id = '0'".format( | |||||
current_tag, current_page | |||||
) | |||||
) | |||||
conn.commit() | |||||
conn.close() | |||||
def start_grab(self) -> bool: | |||||
self.create_tables() | |||||
scraper.grab_tag() | |||||
current_tag, current_page = self.get_current_progress() | |||||
tags = self.get_tag_list() | |||||
for i in range(0, len(tags)): | |||||
no = 0 | |||||
if i == 0 and current_tag == tags[i]: | |||||
no = current_page | |||||
while self.grab_book_list(tags[i], no): | |||||
no = no + 20 | |||||
return True | |||||
def create_tables(self): | |||||
conn = sqlite3.connect(self.database) | |||||
try: | |||||
conn.execute("CREATE TABLE tags (tag TEXT PRIMARY KEY)") | |||||
conn.commit() | |||||
except sqlite3.Error as e: | |||||
logging.error(str(e)) | |||||
conn.rollback() | |||||
try: | |||||
conn.execute( | |||||
"CREATE TABLE book (" | |||||
"id TEXT PRIMARY KEY, title TEXT, author TEXT, " | |||||
"publisher TEXT, original_title TEXT, " | |||||
"translator TEXT, pub_year TEXT, pages INTEGER, " | |||||
"price INTEGER, currency_unit TEXT, binding TEXT, " | |||||
"isbn TEXT, author_intro TEXT, book_intro text, " | |||||
"content TEXT, tags TEXT, picture BLOB)" | |||||
) | |||||
conn.commit() | |||||
except sqlite3.Error as e: | |||||
logging.error(str(e)) | |||||
conn.rollback() | |||||
try: | |||||
conn.execute( | |||||
"CREATE TABLE progress (id TEXT PRIMARY KEY, tag TEXT, page integer )" | |||||
) | |||||
conn.execute("INSERT INTO progress values('0', '', 0)") | |||||
conn.commit() | |||||
except sqlite3.Error as e: | |||||
logging.error(str(e)) | |||||
conn.rollback() | |||||
def grab_tag(self): | |||||
url = "https://book.douban.com/tag/?view=cloud" | |||||
r = requests.get(url, headers=get_user_agent()) | |||||
r.encoding = "utf-8" | |||||
h: etree.ElementBase = etree.HTML(r.text) | |||||
tags: [] = h.xpath( | |||||
'/html/body/div[@id="wrapper"]/div[@id="content"]' | |||||
'/div[@class="grid-16-8 clearfix"]/div[@class="article"]' | |||||
'/div[@class=""]/div[@class="indent tag_cloud"]' | |||||
"/table/tbody/tr/td/a/@href" | |||||
) | |||||
conn = sqlite3.connect(self.database) | |||||
c = conn.cursor() | |||||
try: | |||||
for tag in tags: | |||||
t: str = tag.strip("/tag") | |||||
c.execute("INSERT INTO tags VALUES ('{}')".format(t)) | |||||
c.close() | |||||
conn.commit() | |||||
conn.close() | |||||
except sqlite3.Error as e: | |||||
logging.error(str(e)) | |||||
conn.rollback() | |||||
return False | |||||
return True | |||||
def grab_book_list(self, tag="小说", pageno=1) -> bool: | |||||
logging.info("start to grab tag {} page {}...".format(tag, pageno)) | |||||
self.save_current_progress(tag, pageno) | |||||
url = "https://book.douban.com/tag/{}?start={}&type=T".format(tag, pageno) | |||||
r = requests.get(url, headers=get_user_agent()) | |||||
r.encoding = "utf-8" | |||||
h: etree.Element = etree.HTML(r.text) | |||||
li_list: [] = h.xpath( | |||||
'/html/body/div[@id="wrapper"]/div[@id="content"]' | |||||
'/div[@class="grid-16-8 clearfix"]' | |||||
'/div[@class="article"]/div[@id="subject_list"]' | |||||
'/ul/li/div[@class="info"]/h2/a/@href' | |||||
) | |||||
next_page = h.xpath( | |||||
'/html/body/div[@id="wrapper"]/div[@id="content"]' | |||||
'/div[@class="grid-16-8 clearfix"]' | |||||
'/div[@class="article"]/div[@id="subject_list"]' | |||||
'/div[@class="paginator"]/span[@class="next"]/a[@href]' | |||||
) | |||||
has_next = True | |||||
if len(next_page) == 0: | |||||
has_next = False | |||||
if len(li_list) == 0: | |||||
return False | |||||
for li in li_list: | |||||
li.strip("") | |||||
book_id = li.strip("/").split("/")[-1] | |||||
try: | |||||
delay = float(random.randint(0, 200)) / 100.0 | |||||
time.sleep(delay) | |||||
self.crow_book_info(book_id) | |||||
except BaseException as e: | |||||
logging.error( | |||||
logging.error("error when scrape {}, {}".format(book_id, str(e))) | |||||
) | |||||
return has_next | |||||
def get_tag_list(self) -> [str]: | |||||
ret = [] | |||||
conn = sqlite3.connect(self.database) | |||||
results = conn.execute( | |||||
"SELECT tags.tag from tags join progress where tags.tag >= progress.tag" | |||||
) | |||||
for row in results: | |||||
ret.append(row[0]) | |||||
return ret | |||||
def crow_book_info(self, book_id) -> bool: | |||||
conn = sqlite3.connect(self.database) | |||||
for _ in conn.execute("SELECT id from book where id = ('{}')".format(book_id)): | |||||
return | |||||
url = "https://book.douban.com/subject/{}/".format(book_id) | |||||
r = requests.get(url, headers=get_user_agent()) | |||||
r.encoding = "utf-8" | |||||
h: etree.Element = etree.HTML(r.text) | |||||
e_text = h.xpath('/html/body/div[@id="wrapper"]/h1/span/text()') | |||||
if len(e_text) == 0: | |||||
return False | |||||
title = e_text[0] | |||||
elements = h.xpath( | |||||
'/html/body/div[@id="wrapper"]' | |||||
'/div[@id="content"]/div[@class="grid-16-8 clearfix"]' | |||||
'/div[@class="article"]' | |||||
) | |||||
if len(elements) == 0: | |||||
return False | |||||
e_article = elements[0] | |||||
book_intro = "" | |||||
author_intro = "" | |||||
content = "" | |||||
tags = "" | |||||
e_book_intro = e_article.xpath( | |||||
'div[@class="related_info"]' | |||||
'/div[@class="indent"][@id="link-report"]/*' | |||||
'/div[@class="intro"]/*/text()' | |||||
) | |||||
for line in e_book_intro: | |||||
line = line.strip() | |||||
if line != "": | |||||
book_intro = book_intro + line + "\n" | |||||
e_author_intro = e_article.xpath( | |||||
'div[@class="related_info"]' | |||||
'/div[@class="indent "]/*' | |||||
'/div[@class="intro"]/*/text()' | |||||
) | |||||
for line in e_author_intro: | |||||
line = line.strip() | |||||
if line != "": | |||||
author_intro = author_intro + line + "\n" | |||||
e_content = e_article.xpath( | |||||
'div[@class="related_info"]' | |||||
'/div[@class="indent"][@id="dir_' + book_id + '_full"]/text()' | |||||
) | |||||
for line in e_content: | |||||
line = line.strip() | |||||
if line != "": | |||||
content = content + line + "\n" | |||||
e_tags = e_article.xpath( | |||||
'div[@class="related_info"]/' | |||||
'div[@id="db-tags-section"]/' | |||||
'div[@class="indent"]/span/a/text()' | |||||
) | |||||
for line in e_tags: | |||||
line = line.strip() | |||||
if line != "": | |||||
tags = tags + line + "\n" | |||||
e_subject = e_article.xpath( | |||||
'div[@class="indent"]' | |||||
'/div[@class="subjectwrap clearfix"]' | |||||
'/div[@class="subject clearfix"]' | |||||
) | |||||
pic_href = e_subject[0].xpath('div[@id="mainpic"]/a/@href') | |||||
picture = None | |||||
if len(pic_href) > 0: | |||||
res = requests.get(pic_href[0], headers=get_user_agent()) | |||||
picture = res.content | |||||
info_children = e_subject[0].xpath('div[@id="info"]/child::node()') | |||||
e_array = [] | |||||
e_dict = dict() | |||||
for e in info_children: | |||||
if isinstance(e, etree._ElementUnicodeResult): | |||||
e_dict["text"] = e | |||||
elif isinstance(e, etree._Element): | |||||
if e.tag == "br": | |||||
e_array.append(e_dict) | |||||
e_dict = dict() | |||||
else: | |||||
e_dict[e.tag] = e | |||||
book_info = dict() | |||||
for d in e_array: | |||||
label = "" | |||||
span = d.get("span") | |||||
a_label = span.xpath("span/text()") | |||||
if len(a_label) > 0 and label == "": | |||||
label = a_label[0].strip() | |||||
a_label = span.xpath("text()") | |||||
if len(a_label) > 0 and label == "": | |||||
label = a_label[0].strip() | |||||
label = label.strip(":") | |||||
text = d.get("text").strip() | |||||
e_a = d.get("a") | |||||
text.strip() | |||||
text.strip(":") | |||||
if label == "作者" or label == "译者": | |||||
a = span.xpath("a/text()") | |||||
if text == "" and len(a) == 1: | |||||
text = a[0].strip() | |||||
if text == "" and e_a is not None: | |||||
text_a = e_a.xpath("text()") | |||||
if len(text_a) > 0: | |||||
text = text_a[0].strip() | |||||
text = re.sub(r"\s+", " ", text) | |||||
if text != "": | |||||
book_info[label] = text | |||||
sql = ( | |||||
"INSERT INTO book(" | |||||
"id, title, author, " | |||||
"publisher, original_title, translator, " | |||||
"pub_year, pages, price, " | |||||
"currency_unit, binding, isbn, " | |||||
"author_intro, book_intro, content, " | |||||
"tags, picture)" | |||||
"VALUES(" | |||||
"?, ?, ?, " | |||||
"?, ?, ?, " | |||||
"?, ?, ?, " | |||||
"?, ?, ?, " | |||||
"?, ?, ?, " | |||||
"?, ?)" | |||||
) | |||||
unit = None | |||||
price = None | |||||
pages = None | |||||
conn = sqlite3.connect(self.database) | |||||
try: | |||||
s_price = book_info.get("定价") | |||||
if s_price is None: | |||||
# price cannot be NULL | |||||
logging.error( | |||||
"error when scrape book_id {}, cannot retrieve price...", book_id | |||||
) | |||||
return None | |||||
else: | |||||
e = re.findall(self.pattern_number, s_price) | |||||
if len(e) != 0: | |||||
number = e[0] | |||||
unit = s_price.replace(number, "").strip() | |||||
price = int(float(number) * 100) | |||||
s_pages = book_info.get("页数") | |||||
if s_pages is not None: | |||||
# pages can be NULL | |||||
e = re.findall(self.pattern_number, s_pages) | |||||
if len(e) != 0: | |||||
pages = int(e[0]) | |||||
conn.execute( | |||||
sql, | |||||
( | |||||
book_id, | |||||
title, | |||||
book_info.get("作者"), | |||||
book_info.get("出版社"), | |||||
book_info.get("原作名"), | |||||
book_info.get("译者"), | |||||
book_info.get("出版年"), | |||||
pages, | |||||
price, | |||||
unit, | |||||
book_info.get("装帧"), | |||||
book_info.get("ISBN"), | |||||
author_intro, | |||||
book_intro, | |||||
content, | |||||
tags, | |||||
picture, | |||||
), | |||||
) | |||||
conn.commit() | |||||
except sqlite3.Error as e: | |||||
logging(str(e)) | |||||
conn.rollback() | |||||
except TypeError as e: | |||||
logging.error("error when scrape {}, {}".format(book_id, str(e))) | |||||
conn.rollback() | |||||
return False | |||||
conn.close() | |||||
return True | |||||
if __name__ == "__main__": | |||||
scraper = Scraper() | |||||
scraper.start_grab() |
@ -0,0 +1,61 @@ | |||||
import random | |||||
from fe import conf | |||||
from fe.access import book | |||||
from fe.access.new_seller import register_new_seller | |||||
class GenBook: | |||||
def __init__(self, user_id, store_id): | |||||
self.user_id = user_id | |||||
self.store_id = store_id | |||||
self.password = self.user_id | |||||
self.seller = register_new_seller(self.user_id, self.password) | |||||
code = self.seller.create_store(store_id) | |||||
assert code == 200 | |||||
self.__init_book_list__() | |||||
def __init_book_list__(self): | |||||
self.buy_book_info_list = [] | |||||
self.buy_book_id_list = [] | |||||
def gen( | |||||
self, non_exist_book_id: bool, low_stock_level, max_book_count: int = 100 | |||||
) -> (bool, []): | |||||
self.__init_book_list__() | |||||
ok = True | |||||
book_db = book.BookDB(conf.Use_Large_DB) | |||||
rows = book_db.get_book_count() | |||||
start = 0 | |||||
if rows > max_book_count: | |||||
start = random.randint(0, rows - max_book_count) | |||||
size = random.randint(1, max_book_count) | |||||
books = book_db.get_book_info(start, size) | |||||
book_id_exist = [] | |||||
book_id_stock_level = {} | |||||
for bk in books: | |||||
if low_stock_level: | |||||
stock_level = random.randint(0, 100) | |||||
else: | |||||
stock_level = random.randint(2, 100) | |||||
code = self.seller.add_book(self.store_id, stock_level, bk) | |||||
assert code == 200 | |||||
book_id_stock_level[bk.id] = stock_level | |||||
book_id_exist.append(bk) | |||||
for bk in book_id_exist: | |||||
stock_level = book_id_stock_level[bk.id] | |||||
if stock_level > 1: | |||||
buy_num = random.randint(1, stock_level) | |||||
else: | |||||
buy_num = 0 | |||||
# add a new pair | |||||
if non_exist_book_id: | |||||
bk.id = bk.id + "_x" | |||||
if low_stock_level: | |||||
buy_num = stock_level + 1 | |||||
self.buy_book_info_list.append((bk, buy_num)) | |||||
for item in self.buy_book_info_list: | |||||
self.buy_book_id_list.append((item[0].id, item[1])) | |||||
return ok, self.buy_book_id_list |
@ -0,0 +1 @@ | |||||
add functionality test here |
@ -0,0 +1,51 @@ | |||||
import pytest | |||||
from fe import conf | |||||
from fe.access.new_seller import register_new_seller | |||||
from fe.access import book | |||||
import uuid | |||||
class TestAddBook: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
# do before test | |||||
self.seller_id = "test_add_books_seller_id_{}".format(str(uuid.uuid1())) | |||||
self.store_id = "test_add_books_store_id_{}".format(str(uuid.uuid1())) | |||||
self.password = self.seller_id | |||||
self.seller = register_new_seller(self.seller_id, self.password) | |||||
code = self.seller.create_store(self.store_id) | |||||
assert code == 200 | |||||
book_db = book.BookDB(conf.Use_Large_DB) | |||||
self.books = book_db.get_book_info(0, 2) | |||||
yield | |||||
# do after test | |||||
def test_ok(self): | |||||
for b in self.books: | |||||
code = self.seller.add_book(self.store_id, 0, b) | |||||
assert code == 200 | |||||
def test_error_non_exist_store_id(self): | |||||
for b in self.books: | |||||
# non exist store id | |||||
code = self.seller.add_book(self.store_id + "x", 0, b) | |||||
assert code != 200 | |||||
def test_error_exist_book_id(self): | |||||
for b in self.books: | |||||
code = self.seller.add_book(self.store_id, 0, b) | |||||
assert code == 200 | |||||
for b in self.books: | |||||
# exist book id | |||||
code = self.seller.add_book(self.store_id, 0, b) | |||||
assert code != 200 | |||||
def test_error_non_exist_user_id(self): | |||||
for b in self.books: | |||||
# non exist user id | |||||
self.seller.seller_id = self.seller.seller_id + "_x" | |||||
code = self.seller.add_book(self.store_id, 0, b) | |||||
assert code != 200 |
@ -0,0 +1,29 @@ | |||||
import pytest | |||||
import uuid | |||||
from fe.access.new_buyer import register_new_buyer | |||||
class TestAddFunds: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.user_id = "test_add_funds_{}".format(str(uuid.uuid1())) | |||||
self.password = self.user_id | |||||
self.buyer = register_new_buyer(self.user_id, self.password) | |||||
yield | |||||
def test_ok(self): | |||||
code = self.buyer.add_funds(1000) | |||||
assert code == 200 | |||||
code = self.buyer.add_funds(-1000) | |||||
assert code == 200 | |||||
def test_error_user_id(self): | |||||
self.buyer.user_id = self.buyer.user_id + "_x" | |||||
code = self.buyer.add_funds(10) | |||||
assert code != 200 | |||||
def test_error_password(self): | |||||
self.buyer.password = self.buyer.password + "_x" | |||||
code = self.buyer.add_funds(10) | |||||
assert code != 200 |
@ -0,0 +1,54 @@ | |||||
import pytest | |||||
from fe import conf | |||||
from fe.access.new_seller import register_new_seller | |||||
from fe.access import book | |||||
import uuid | |||||
class TestAddStockLevel: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.user_id = "test_add_book_stock_level1_user_{}".format(str(uuid.uuid1())) | |||||
self.store_id = "test_add_book_stock_level1_store_{}".format(str(uuid.uuid1())) | |||||
self.password = self.user_id | |||||
self.seller = register_new_seller(self.user_id, self.password) | |||||
code = self.seller.create_store(self.store_id) | |||||
assert code == 200 | |||||
book_db = book.BookDB(conf.Use_Large_DB) | |||||
self.books = book_db.get_book_info(0, 5) | |||||
for bk in self.books: | |||||
code = self.seller.add_book(self.store_id, 0, bk) | |||||
assert code == 200 | |||||
yield | |||||
def test_error_user_id(self): | |||||
for b in self.books: | |||||
book_id = b.id | |||||
code = self.seller.add_stock_level( | |||||
self.user_id + "_x", self.store_id, book_id, 10 | |||||
) | |||||
assert code != 200 | |||||
def test_error_store_id(self): | |||||
for b in self.books: | |||||
book_id = b.id | |||||
code = self.seller.add_stock_level( | |||||
self.user_id, self.store_id + "_x", book_id, 10 | |||||
) | |||||
assert code != 200 | |||||
def test_error_book_id(self): | |||||
for b in self.books: | |||||
book_id = b.id | |||||
code = self.seller.add_stock_level( | |||||
self.user_id, self.store_id, book_id + "_x", 10 | |||||
) | |||||
assert code != 200 | |||||
def test_ok(self): | |||||
for b in self.books: | |||||
book_id = b.id | |||||
code = self.seller.add_stock_level(self.user_id, self.store_id, book_id, 10) | |||||
assert code == 200 |
@ -0,0 +1,8 @@ | |||||
from fe.bench.run import run_bench | |||||
def test_bench(): | |||||
try: | |||||
run_bench() | |||||
except Exception as e: | |||||
assert 200 == 100, "test_bench过程出现异常" |
@ -0,0 +1,26 @@ | |||||
import pytest | |||||
from fe.access.new_seller import register_new_seller | |||||
import uuid | |||||
class TestCreateStore: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.user_id = "test_create_store_user_{}".format(str(uuid.uuid1())) | |||||
self.store_id = "test_create_store_store_{}".format(str(uuid.uuid1())) | |||||
self.password = self.user_id | |||||
yield | |||||
def test_ok(self): | |||||
self.seller = register_new_seller(self.user_id, self.password) | |||||
code = self.seller.create_store(self.store_id) | |||||
assert code == 200 | |||||
def test_error_exist_store_id(self): | |||||
self.seller = register_new_seller(self.user_id, self.password) | |||||
code = self.seller.create_store(self.store_id) | |||||
assert code == 200 | |||||
code = self.seller.create_store(self.store_id) | |||||
assert code != 200 |
@ -0,0 +1,39 @@ | |||||
import time | |||||
import pytest | |||||
from fe.access import auth | |||||
from fe import conf | |||||
class TestLogin: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.auth = auth.Auth(conf.URL) | |||||
# register a user | |||||
self.user_id = "test_login_{}".format(time.time()) | |||||
self.password = "password_" + self.user_id | |||||
self.terminal = "terminal_" + self.user_id | |||||
assert self.auth.register(self.user_id, self.password) == 200 | |||||
yield | |||||
def test_ok(self): | |||||
code, token = self.auth.login(self.user_id, self.password, self.terminal) | |||||
assert code == 200 | |||||
code = self.auth.logout(self.user_id + "_x", token) | |||||
assert code == 401 | |||||
code = self.auth.logout(self.user_id, token + "_x") | |||||
assert code == 401 | |||||
code = self.auth.logout(self.user_id, token) | |||||
assert code == 200 | |||||
def test_error_user_id(self): | |||||
code, token = self.auth.login(self.user_id + "_x", self.password, self.terminal) | |||||
assert code == 401 | |||||
def test_error_password(self): | |||||
code, token = self.auth.login(self.user_id, self.password + "_x", self.terminal) | |||||
assert code == 401 |
@ -0,0 +1,58 @@ | |||||
import pytest | |||||
from fe.test.gen_book_data import GenBook | |||||
from fe.access.new_buyer import register_new_buyer | |||||
import uuid | |||||
class TestNewOrder: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.seller_id = "test_new_order_seller_id_{}".format(str(uuid.uuid1())) | |||||
self.store_id = "test_new_order_store_id_{}".format(str(uuid.uuid1())) | |||||
self.buyer_id = "test_new_order_buyer_id_{}".format(str(uuid.uuid1())) | |||||
self.password = self.seller_id | |||||
self.buyer = register_new_buyer(self.buyer_id, self.password) | |||||
self.gen_book = GenBook(self.seller_id, self.store_id) | |||||
yield | |||||
def test_non_exist_book_id(self): | |||||
ok, buy_book_id_list = self.gen_book.gen( | |||||
non_exist_book_id=True, low_stock_level=False | |||||
) | |||||
assert ok | |||||
code, _ = self.buyer.new_order(self.store_id, buy_book_id_list) | |||||
assert code != 200 | |||||
def test_low_stock_level(self): | |||||
ok, buy_book_id_list = self.gen_book.gen( | |||||
non_exist_book_id=False, low_stock_level=True | |||||
) | |||||
assert ok | |||||
code, _ = self.buyer.new_order(self.store_id, buy_book_id_list) | |||||
assert code != 200 | |||||
def test_ok(self): | |||||
ok, buy_book_id_list = self.gen_book.gen( | |||||
non_exist_book_id=False, low_stock_level=False | |||||
) | |||||
assert ok | |||||
code, _ = self.buyer.new_order(self.store_id, buy_book_id_list) | |||||
assert code == 200 | |||||
def test_non_exist_user_id(self): | |||||
ok, buy_book_id_list = self.gen_book.gen( | |||||
non_exist_book_id=False, low_stock_level=False | |||||
) | |||||
assert ok | |||||
self.buyer.user_id = self.buyer.user_id + "_x" | |||||
code, _ = self.buyer.new_order(self.store_id, buy_book_id_list) | |||||
assert code != 200 | |||||
def test_non_exist_store_id(self): | |||||
ok, buy_book_id_list = self.gen_book.gen( | |||||
non_exist_book_id=False, low_stock_level=False | |||||
) | |||||
assert ok | |||||
code, _ = self.buyer.new_order(self.store_id + "_x", buy_book_id_list) | |||||
assert code != 200 |
@ -0,0 +1,59 @@ | |||||
import uuid | |||||
import pytest | |||||
from fe.access import auth | |||||
from fe import conf | |||||
class TestPassword: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.auth = auth.Auth(conf.URL) | |||||
# register a user | |||||
self.user_id = "test_password_{}".format(str(uuid.uuid1())) | |||||
self.old_password = "old_password_" + self.user_id | |||||
self.new_password = "new_password_" + self.user_id | |||||
self.terminal = "terminal_" + self.user_id | |||||
assert self.auth.register(self.user_id, self.old_password) == 200 | |||||
yield | |||||
def test_ok(self): | |||||
code = self.auth.password(self.user_id, self.old_password, self.new_password) | |||||
assert code == 200 | |||||
code, new_token = self.auth.login( | |||||
self.user_id, self.old_password, self.terminal | |||||
) | |||||
assert code != 200 | |||||
code, new_token = self.auth.login( | |||||
self.user_id, self.new_password, self.terminal | |||||
) | |||||
assert code == 200 | |||||
code = self.auth.logout(self.user_id, new_token) | |||||
assert code == 200 | |||||
def test_error_password(self): | |||||
code = self.auth.password( | |||||
self.user_id, self.old_password + "_x", self.new_password | |||||
) | |||||
assert code != 200 | |||||
code, new_token = self.auth.login( | |||||
self.user_id, self.new_password, self.terminal | |||||
) | |||||
assert code != 200 | |||||
def test_error_user_id(self): | |||||
code = self.auth.password( | |||||
self.user_id + "_x", self.old_password, self.new_password | |||||
) | |||||
assert code != 200 | |||||
code, new_token = self.auth.login( | |||||
self.user_id, self.new_password, self.terminal | |||||
) | |||||
assert code != 200 |
@ -0,0 +1,72 @@ | |||||
import pytest | |||||
from fe.access.buyer import Buyer | |||||
from fe.test.gen_book_data import GenBook | |||||
from fe.access.new_buyer import register_new_buyer | |||||
from fe.access.book import Book | |||||
import uuid | |||||
class TestPayment: | |||||
seller_id: str | |||||
store_id: str | |||||
buyer_id: str | |||||
password: str | |||||
buy_book_info_list: [Book] | |||||
total_price: int | |||||
order_id: str | |||||
buyer: Buyer | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.seller_id = "test_payment_seller_id_{}".format(str(uuid.uuid1())) | |||||
self.store_id = "test_payment_store_id_{}".format(str(uuid.uuid1())) | |||||
self.buyer_id = "test_payment_buyer_id_{}".format(str(uuid.uuid1())) | |||||
self.password = self.seller_id | |||||
gen_book = GenBook(self.seller_id, self.store_id) | |||||
ok, buy_book_id_list = gen_book.gen( | |||||
non_exist_book_id=False, low_stock_level=False, max_book_count=5 | |||||
) | |||||
self.buy_book_info_list = gen_book.buy_book_info_list | |||||
assert ok | |||||
b = register_new_buyer(self.buyer_id, self.password) | |||||
self.buyer = b | |||||
code, self.order_id = b.new_order(self.store_id, buy_book_id_list) | |||||
assert code == 200 | |||||
self.total_price = 0 | |||||
for item in self.buy_book_info_list: | |||||
book: Book = item[0] | |||||
num = item[1] | |||||
if book.price is None: | |||||
continue | |||||
else: | |||||
self.total_price = self.total_price + book.price * num | |||||
yield | |||||
def test_ok(self): | |||||
code = self.buyer.add_funds(self.total_price) | |||||
assert code == 200 | |||||
code = self.buyer.payment(self.order_id) | |||||
assert code == 200 | |||||
def test_authorization_error(self): | |||||
code = self.buyer.add_funds(self.total_price) | |||||
assert code == 200 | |||||
self.buyer.password = self.buyer.password + "_x" | |||||
code = self.buyer.payment(self.order_id) | |||||
assert code != 200 | |||||
def test_not_suff_funds(self): | |||||
code = self.buyer.add_funds(self.total_price - 1) | |||||
assert code == 200 | |||||
code = self.buyer.payment(self.order_id) | |||||
assert code != 200 | |||||
def test_repeat_pay(self): | |||||
code = self.buyer.add_funds(self.total_price) | |||||
assert code == 200 | |||||
code = self.buyer.payment(self.order_id) | |||||
assert code == 200 | |||||
code = self.buyer.payment(self.order_id) | |||||
assert code != 200 |
@ -0,0 +1,43 @@ | |||||
import time | |||||
import pytest | |||||
from fe.access import auth | |||||
from fe import conf | |||||
class TestRegister: | |||||
@pytest.fixture(autouse=True) | |||||
def pre_run_initialization(self): | |||||
self.user_id = "test_register_user_{}".format(time.time()) | |||||
self.password = "test_register_password_{}".format(time.time()) | |||||
self.auth = auth.Auth(conf.URL) | |||||
yield | |||||
def test_register_ok(self): | |||||
code = self.auth.register(self.user_id, self.password) | |||||
assert code == 200 | |||||
def test_unregister_ok(self): | |||||
code = self.auth.register(self.user_id, self.password) | |||||
assert code == 200 | |||||
code = self.auth.unregister(self.user_id, self.password) | |||||
assert code == 200 | |||||
def test_unregister_error_authorization(self): | |||||
code = self.auth.register(self.user_id, self.password) | |||||
assert code == 200 | |||||
code = self.auth.unregister(self.user_id + "_x", self.password) | |||||
assert code != 200 | |||||
code = self.auth.unregister(self.user_id, self.password + "_x") | |||||
assert code != 200 | |||||
def test_register_error_exist_user_id(self): | |||||
code = self.auth.register(self.user_id, self.password) | |||||
assert code == 200 | |||||
code = self.auth.register(self.user_id, self.password) | |||||
assert code != 200 |
@ -0,0 +1,9 @@ | |||||
simplejson | |||||
lxml | |||||
codecov | |||||
coverage | |||||
flask | |||||
pre - commit | |||||
pytest | |||||
PyJWT | |||||
requests |
@ -0,0 +1,6 @@ | |||||
#!/bin/sh | |||||
export PATHONPATH=`pwd` | |||||
coverage run --timid --branch --source fe,be --concurrency=thread -m pytest -v --ignore=fe/data | |||||
coverage combine | |||||
coverage report | |||||
coverage html |
@ -0,0 +1,22 @@ | |||||
import setuptools | |||||
with open("README.md", "r") as fh: | |||||
long_description = fh.read() | |||||
setuptools.setup( | |||||
name="bookstore", | |||||
version="0.0.1", | |||||
author="DaSE-DBMS", | |||||
author_email="DaSE-DBMS@DaSE-DBMS.com", | |||||
description="Buy Books Online", | |||||
long_description=long_description, | |||||
long_description_content_type="text/markdown", | |||||
url="https://github.com/DaSE-DBMS/bookstore.git", | |||||
packages=setuptools.find_packages(), | |||||
classifiers=[ | |||||
"Programming Language :: Python :: 3", | |||||
"License :: OSI Approved :: MIT License", | |||||
"Operating System :: OS Independent", | |||||
], | |||||
python_requires=">=3.6", | |||||
) |