王聪聪 2 years ago
parent
commit
2a1a33ddf0
49 changed files with 2745 additions and 0 deletions
  1. +1
    -0
      be/__init__.py
  2. +4
    -0
      be/app.py
  3. +1
    -0
      be/model/__init__.py
  4. +162
    -0
      be/model/buyer.py
  5. +30
    -0
      be/model/db_conn.py
  6. +66
    -0
      be/model/error.py
  7. +60
    -0
      be/model/seller.py
  8. +63
    -0
      be/model/store.py
  9. +169
    -0
      be/model/user.py
  10. +46
    -0
      be/serve.py
  11. +1
    -0
      be/view/__init__.py
  12. +53
    -0
      be/view/auth.py
  13. +42
    -0
      be/view/buyer.py
  14. +42
    -0
      be/view/seller.py
  15. +213
    -0
      doc/auth.md
  16. +144
    -0
      doc/buyer.md
  17. +178
    -0
      doc/seller.md
  18. +1
    -0
      fe/__init__.py
  19. +1
    -0
      fe/access/__init__.py
  20. +49
    -0
      fe/access/auth.py
  21. +97
    -0
      fe/access/book.py
  22. +42
    -0
      fe/access/buyer.py
  23. +10
    -0
      fe/access/new_buyer.py
  24. +10
    -0
      fe/access/new_seller.py
  25. +52
    -0
      fe/access/seller.py
  26. +1
    -0
      fe/bench/__init__.py
  27. +1
    -0
      fe/bench/bench.md
  28. +22
    -0
      fe/bench/run.py
  29. +53
    -0
      fe/bench/session.py
  30. +166
    -0
      fe/bench/workload.py
  31. +11
    -0
      fe/conf.py
  32. +27
    -0
      fe/conftest.py
  33. BIN
      fe/data/book.db
  34. +425
    -0
      fe/data/scraper.py
  35. +57
    -0
      fe/test/gen_book_data.py
  36. +1
    -0
      fe/test/test.md
  37. +51
    -0
      fe/test/test_add_book.py
  38. +29
    -0
      fe/test/test_add_funds.py
  39. +46
    -0
      fe/test/test_add_stock_level.py
  40. +8
    -0
      fe/test/test_bench.py
  41. +26
    -0
      fe/test/test_create_store.py
  42. +39
    -0
      fe/test/test_login.py
  43. +48
    -0
      fe/test/test_new_order.py
  44. +47
    -0
      fe/test/test_password.py
  45. +70
    -0
      fe/test/test_payment.py
  46. +43
    -0
      fe/test/test_register.py
  47. +9
    -0
      requirements.txt
  48. +6
    -0
      script/test.sh
  49. +22
    -0
      setup.py

+ 1
- 0
be/__init__.py View File

@ -0,0 +1 @@
#!/usr/bin/env python3

+ 4
- 0
be/app.py View File

@ -0,0 +1,4 @@
from be import serve
if __name__ == "__main__":
serve.be_run()

+ 1
- 0
be/model/__init__.py View File

@ -0,0 +1 @@
#!/usr/bin/env python3

+ 162
- 0
be/model/buyer.py View File

@ -0,0 +1,162 @@
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, buyer_id))
if cursor.rowcount == 0:
return error.error_non_exist_user_id(buyer_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"

+ 30
- 0
be/model/db_conn.py View File

@ -0,0 +1,30 @@
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

+ 66
- 0
be/model/error.py View File

@ -0,0 +1,66 @@
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

+ 60
- 0
be/model/seller.py View File

@ -0,0 +1,60 @@
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"

+ 63
- 0
be/model/store.py View File

@ -0,0 +1,63 @@
import logging
import os
import sqlite3 as sqlite
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
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()

+ 169
- 0
be/model/user.py View File

@ -0,0 +1,169 @@
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"

+ 46
- 0
be/serve.py View File

@ -0,0 +1,46 @@
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
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)
app.run()

+ 1
- 0
be/view/__init__.py View File

@ -0,0 +1 @@
#!/usr/bin/env python3

+ 53
- 0
be/view/auth.py View File

@ -0,0 +1,53 @@
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

+ 42
- 0
be/view/buyer.py View File

@ -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

+ 42
- 0
be/view/seller.py View File

@ -0,0 +1,42 @@
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

+ 213
- 0
doc/auth.md View File

@ -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

+ 144
- 0
doc/buyer.md View File

@ -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 | 无效参数

+ 178
- 0
doc/seller.md View File

@ -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不存在

+ 1
- 0
fe/__init__.py View File

@ -0,0 +1 @@
#!/usr/bin/env python3

+ 1
- 0
fe/access/__init__.py View File

@ -0,0 +1 @@
#!/usr/bin/env python3

+ 49
- 0
fe/access/auth.py View File

@ -0,0 +1,49 @@
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

+ 97
- 0
fe/access/book.py View File

@ -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
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

+ 42
- 0
fe/access/buyer.py View File

@ -0,0 +1,42 @@
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

+ 10
- 0
fe/access/new_buyer.py View File

@ -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

+ 10
- 0
fe/access/new_seller.py View File

@ -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

+ 52
- 0
fe/access/seller.py View File

@ -0,0 +1,52 @@
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

+ 1
- 0
fe/bench/__init__.py View File

@ -0,0 +1 @@
#!/usr/bin/env python3

+ 1
- 0
fe/bench/bench.md View File

@ -0,0 +1 @@
add performance test here

+ 22
- 0
fe/bench/run.py View File

@ -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()

+ 53
- 0
fe/bench/session.py View File

@ -0,0 +1,53 @@
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 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 = []

+ 166
- 0
fe/bench/workload.py View File

@ -0,0 +1,166 @@
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)
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
if i == 1 and j == 1:
self.book_ids.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) - 1))
book_id = self.book_ids[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

+ 11
- 0
fe/conf.py View File

@ -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 = False

+ 27
- 0
fe/conftest.py View File

@ -0,0 +1,27 @@
import requests
import threading
from urllib.parse import urljoin
from be import serve
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()
def pytest_unconfigure(config):
url = urljoin(conf.URL, "shutdown")
requests.get(url)
thread.join()
print("frontend end test")

BIN
fe/data/book.db View File


+ 425
- 0
fe/data/scraper.py View File

@ -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()

+ 57
- 0
fe/test/gen_book_data.py View File

@ -0,0 +1,57 @@
import random
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()
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

+ 1
- 0
fe/test/test.md View File

@ -0,0 +1 @@
add functionality test here

+ 51
- 0
fe/test/test_add_book.py View File

@ -0,0 +1,51 @@
import pytest
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()
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

+ 29
- 0
fe/test/test_add_funds.py View File

@ -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

+ 46
- 0
fe/test/test_add_stock_level.py View File

@ -0,0 +1,46 @@
import pytest
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()
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

+ 8
- 0
fe/test/test_bench.py View File

@ -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过程出现异常"

+ 26
- 0
fe/test/test_create_store.py View File

@ -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

+ 39
- 0
fe/test/test_login.py View File

@ -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

+ 48
- 0
fe/test/test_new_order.py View File

@ -0,0 +1,48 @@
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

+ 47
- 0
fe/test/test_password.py View File

@ -0,0 +1,47 @@
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

+ 70
- 0
fe/test/test_payment.py View File

@ -0,0 +1,70 @@
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

+ 43
- 0
fe/test/test_register.py View File

@ -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

+ 9
- 0
requirements.txt View File

@ -0,0 +1,9 @@
simplejson
lxml
codecov
coverage
flask
pre-commit
pytest
PyJWT
requests

+ 6
- 0
script/test.sh View File

@ -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

+ 22
- 0
setup.py View File

@ -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",
)

Loading…
Cancel
Save