当代数据库管理系统课程实验二
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

20 KiB

华东师范大学数据科学与工程学院实验报告

课程名称:当代数据库管理系统 年级 :2020级 上机实践成绩
指导教师 :高明 姓名 :杨舜、姚嘉和 学号 :10205501415、10205501436
上机实践名称 :BookStore 上机实践日期:2022.11.28 —— 2022.12.10
上机实践编号 组号 :21 上机实践时间:2022.11.28 —— 2022.12.10

$\color{red}{报告你就写一下下面我标红字的那些地方,顺便注意下排版之类的,补充实现思路的那个就参考后面我单独发的那个readme里面的实现思路,就简单看着代码说说在那些表上查什么就行}$

实验过程

一. 分析原有的数据库结构

分析demo中/be/model/store.py中创建数据库表的sql语句可知原有数据库的结构的ER图大致如下 avatar

有上述ER图可以得到原有数据库表如下

user表:

user_id password balance token terminal
主键为user_id

store表:

store_id stock_level
主键为store_id

store_book表:

store_id book_id book_info stock_level
主键为联合主键(store_id,book_id)

user_store表:

user_id store_id
外键为(user_id,store_id)

new_order表:

order_id user_id store_id
主键为(order_id)

new_order_detail表:

oeder_id book_id count price
主键为联合主键(order_id,book_id)

 

二. 依据上述分析构建数据库table(前60%)

  1. 利用sqlalchemy连接远程的aliyun数据库并创建上述table postgreSQLORM.py
class con:
 def connect():
     '''Returns a connection and a metadata object'''
     # We connect with the help of the PostgreSQL URL
     
     url = 'postgresql://stu10205501415:Stu10205501415@dase-cdms-2022-pub.pg.rds.aliyuncs.com:5432/stu10205501415'

     # The return value of create_engine() is our connection object
     con = create_engine(url, client_encoding='utf8')

     # We then bind the connection to MetaData()
     meta = MetaData(bind=con)

     return con, meta

class User(Base):
  __tablename__ = 'user'
 user_id = Column(TEXT, primary_key=True, comment="主键")
 password = Column(TEXT, nullable=False, comment="密码")
 balance = Column(Integer, nullable=False, comment="")
 token = Column(TEXT, comment="缓存的令牌")
 terminal = Column(TEXT, comment="终端代码")

class Store(Base):
 __tablename__ = 'store'
 store_id = Column(TEXT, primary_key=True,comment="主键")
 stock_level = Column(Integer, comment = "货存")

class Store_Book(Base):
 __tablename__ = 'store_book'
 store_id = Column(TEXT, comment="主键")
 book_id = Column(TEXT, comment="主键")
 book_info = Column(TEXT, comment="书籍信息")
 stock_level = Column(Integer, comment = "货存")

 __table_args__ = (
     PrimaryKeyConstraint('store_id', 'book_id'),
 )

class User_Store(Base):
 __tablename__ = 'user_store'

 id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")

 fk_user_id = Column(
     TEXT,
     ForeignKey(
         "user.user_id",
         ondelete="CASCADE",
         onupdate="CASCADE",
     ),
     nullable=False,
     comment="user外键"
 )
 fk_store_id = Column(
     TEXT,
     ForeignKey(
         "store.store_id",
         ondelete="CASCADE",
         onupdate="CASCADE",
     ),
     nullable=False,
     comment="store外键"
 )
 # 多对多关系的中间表必须使用联合唯一约束,防止出现重复数据
 __table_args__ = (
     UniqueConstraint("fk_user_id", "fk_store_id"),
 )

class New_Order(Base):
 __tablename__ = 'new_order'
 order_id = Column(TEXT, primary_key = True, comment = '订单id')
 
 fk_user_id = Column(
     TEXT,
     ForeignKey(
         "user.user_id",
         ondelete="CASCADE",
         onupdate="CASCADE",
     ),
     nullable=False,
     comment="user外键"
 )
 fk_store_id = Column(
     TEXT,
     ForeignKey(
         "store.store_id",
         ondelete="CASCADE",
         onupdate="CASCADE",
     ),
     nullable=False,
     comment="store外键"
 )

class New_Order_Detail(Base):
 __tablename__ = 'new_order_detail'
 order_id = Column(TEXT, comment='订单id')
 book_id = Column(TEXT, comment='订单书籍')
 count = Column(Integer, comment='购买书籍数')
 price = Column(Integer, comment='单价')

 __table_args__ = (
     PrimaryKeyConstraint('order_id','book_id'),
 )
 
engine, meta = con.connect()
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()
  1. 在上述创建的table中添加初始数据并利用该数据测试后端服务器与数据库的连接(2022.11.29 15:10 杨舜) avatar avatar

  2. 类比原有demo分别为不同的路由绑定不同的蓝图

 app.register_blueprint(auth.bp_auth)
 app.register_blueprint(seller.bp_seller)
 app.register_blueprint(buyer.bp_buyer)
  1. 在/be/model目录下创建User类用书实现User对于数据库的一些交互功能
  2. 修改model/db_conn.py中查询的操作为orm操作,其中修改DBConn中的连接conn为sqlachlemy中的session,将会话作为连接
class DBConn:
 def __init__(self):
     self.session = postgreSQLORM.session
     return
     # self.conn = store.get_db_conn()

 def user_id_exist(self, user_id):
     row = self.session.query(User).filter(User.user_id==user_id).first()
     # 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):
     row = self.session.query(Store_Book).filter(Store_Book.book_id==book_id and Store_Book.store_id==store_id).first()
     # 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):
     row = self.session.query(User_Store).filter(User_Store.fk_store_id==store_id).first()
     # 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


  1. 修改/be/model/user.py中User类以及其中函数的定义使其满足题目要求的ORM模型,同时仅对注册功能进行测试(2022.11.30 15:40 杨舜)
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):
     ## 判断用户是否注册过了
     if self.user_id_exist(user_id=user_id):
         return error.error_exist_user_id(user_id)
     else:
     # try:
         terminal = "terminal_{}".format(str(time.time()))
         token = jwt_encode(user_id, terminal)
         ## 为新注册的用户创建对象
         new_user = postgreSQLORM.User(user_id=user_id,password=password,balance=0,token=token,terminal=terminal)
         self.session.add(new_user)
         self.session.commit()
         
         # 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"

avatar

  1. 另外对于auth路由中的其他功能接口(注销、登录、登出、更改密码)进行类似上述注册接口的修改,此处不在单独贴出代码,只是给出postman的测试截图,至此auth中的路由全部实现(2022.11.30 17:50 杨舜)

$\color{red}{补充各个接口的实现思路}$

avatar avatar avatar avatar

  1. 利用上述类似的实现auth路由接口的方式完成seller路由接口,并利用postman测试实现(2022.12.01 19:20 杨舜) avatar avatar avatar

  2. 利用上述类似的实现auth路由接口的方式完成buyer路由接口,并利用postman测试实现(2022.12.02 12:10 杨舜) avatar avatar avatar

三、根据要求实现后续的40%的功能并为其编写测试接口

  1. 对数据库结构进行改造(添加table的列)

为了实现发货,收获,订单状态的查询,可以在new_order的订单的table中添加status,并利用不同的状态码来表示当前次订单的状态

status code status
-1 取消
0 初始值(未付款)
1 已付款
2 已发货
3 已收货

因此修改postgreSQLORM.py文件中New_Order的类

  1. 修改对应payment接口中删除订单的操作未修改订单状态未1
row = session.query(New_Order).filter(New_Order.order_id==order_id).update({'status':1})
  1. 为seller路由新增发货(修改订单状态为2)接口、buyer路由新增收货(修改订单状态为3)接口、buyer路由新增取消订单(修改订单状态为-1)接口
   ## /view/seller.py
   @bp_seller.routr("/send_out",methods=["POST"])
   def send_out():
       order_id: str = request.json.get("order_id")
       user_id: str = request.json.get("user_id")
       s = seller.Seller()
       code, message = s.send_out(order_id)

       return jsonify({"message": message}), code
   ## /model/seller.py
   def send_out(self, order_id:str):
        session = self.session
        try:
            if not self.user_id_exist(user_id):
                return error.error_non_exist_user_id(user_id)
                
            row = session.query(New_Order).filter(New_Order.order_id==order_id).first()
            if row is None:
                return error.error_invalid_order_id(order_id)
            if row.status != 1:
                return error.error_invalid_order_id(order_id)
            
            row = session.query(New_Order).filter(New_Order.order_id==order_id).update({'status':2})

            if row == 0:
                return error.error_invalid_order_id(order_id)

            session.commit()

        except SQLAlchemyError as e:
            return 528, "{}".format(str(e))
        except BaseException as e:
            # print('touch3')
            return 530, "{}".format(str(e))
        return 200, "ok"

$\color{red}{补充实现思路}$ $\color{red}{顺便将这部分的下面这种接口说明移动到doc目录下的文件夹里面}$

商家发货

URL

POST http://[address]/seller/send_out

Request Headers:

key 类型 描述 是否可为空
token string 登录产生的会话标识 N

Body:

{
  "user_id": "$seller id$",
  "order_id": "$store id$",
}
key 类型 描述 是否可为空
user_id String 卖家用户ID N
order_id String 订单号 N

Response

Status Code:

描述
200 发货成功
5XX 买家用户ID不存在
5XX 无效参数
## /view/buyer.py
@bp_buyer.route("/take_over", methods=["POST"])
def take_over():
    user_id = request.json.get("user_id")
    order_id = request.json.get("order_id")
    b = Buyer()
    code, message = b.take_over(user_id, order_id)
    return jsonify({"message": message}), code

## /model/buyer.py
def take_over(self, user_id, order_id):
    session = self.session
    try:
        if not self.user_id_exist(user_id):
            return error.error_non_exist_user_id(user_id)
            
        row = session.query(New_Order).filter(and_(New_Order.order_id==order_id,New_Order.fk_user_id==user_id)).first()
        if row is None:
            return error.error_invalid_order_id(order_id)
        if row.status != 2:
            return error.error_invalid_order_id(order_id)
        
        row = session.query(New_Order).filter(and_(New_Order.order_id==order_id,New_Order.fk_user_id==user_id)).update({'status':3})

        if row == 0:
            return error.error_invalid_order_id(order_id)

        session.commit()

    except SQLAlchemyError as e:
        return 528, "{}".format(str(e))
    except BaseException as e:
        # print('touch3')
        return 530, "{}".format(str(e))
    return 200, "ok"

卖家收货

URL

POST http://[address]/buyer/take_over

Request Headers:

key 类型 描述 是否可为空
token string 登录产生的会话标识 N

Body:

{
  "user_id": "$seller id$",
  "order_id": "$store id$",
}
key 类型 描述 是否可为空
user_id String 买家用户ID N
order_id String 订单号 N

Response

Status Code:

描述
200 收货成功
5XX 买家用户ID不存在
5XX 无效参数
## /view/buyer.py
@bp_buyer.route("/order_cancel", methods=["POST"])
def take_over():
    user_id = request.json.get("user_id")
    order_id = request.json.get("order_id")
    b = Buyer()
    code, message = b.order_cancel(user_id, order_id)
    return jsonify({"message": message}), code
## /model/buyer.py
def order_cancel(self, user_id, order_id):
    session = self.session
    try:
        if not self.user_id_exist(user_id):
            return error.error_non_exist_user_id(user_id)
                
        row = session.query(New_Order).filter(and_(New_Order.order_id==order_id,New_Order.fk_user_id==user_id)).first()
        if row is None:
            return error.error_invalid_order_id(order_id)
        if row.status != 0:
            return error.error_invalid_order_id(order_id)
            
        row = session.query(New_Order).filter(and_(New_Order.order_id==order_id,New_Order.fk_user_id==user_id)).update({'status':-1})

        if row == 0:
            return error.error_invalid_order_id(order_id)

        session.commit()

    except SQLAlchemyError as e:
        return 528, "{}".format(str(e))
    except BaseException as e:
        # print('touch3')
        return 530, "{}".format(str(e))
    return 200, "ok"

买家取消订单

URL

POST http://[address]/buyer/order_cancel

Request Headers:

key 类型 描述 是否可为空
token string 登录产生的会话标识 N

Body:

{
  "user_id": "$seller id$",
  "order_id": "$store id$",
}
key 类型 描述 是否可为空
user_id String 买家用户ID N
order_id String 订单号 N

Response

Status Code:

描述
200 收货成功
5XX 买家用户ID不存在
5XX 无效参数
  1. 像buyer路由中添加一个查询历史订单的接口,为此首先需要在原来的New_Order表中添加creat_time列,然后为buyer路由添加history_order接口
## /view/buyer.py
@bp_buyer.route("/history_order", methods=["POST"])
def take_over():
    user_id = request.json.get("user_id")
    b = Buyer()
    code, message = b.history_order(user_id)
    return jsonify({"message": message}), code
## /model/buyer.py

历史订单查询

URL

POST http://[address]/buyer/history_order

Request Headers:

key 类型 描述 是否可为空
token string 登录产生的会话标识 N

Body:

{
  "user_id": "$buyer id$"
}
key 类型 描述 是否可为空
user_id String 买家用户ID N

Response

Status Code:

描述
200 查询成功
5XX 买家用户ID不存在
5XX 无效参数

Body:

{
  "order_id": ["uuid"]
}
变量名 类型 描述 是否可为空
order_id string 订单号,只有返回200时才有效 N
  1. 为上面添加的路由编写测试接口并进行测试 avatar

  2. 为实现书店的搜索图书的功能,稍微修改数据库的结构,为书籍添加数据表,搜索标题表,搜索标签表,搜索作者表,搜索书本内容表

$$\color{red}{看着postgreSQLORM里面Class Book以及后面的几个class写一下几个table的表头,参照我写的一个}$$

Search_title

search_id title book_id
联合主键(search_id,book_id)
外键(book_id)
  1. 修改seller中的add_book的路由

$\color{red}{补充实现思路}$

  1. 在auth中添加搜索的路由(只包含全局搜索,没有店铺内搜索)
@bp_auth.route("/search_author", methods=["POST"])
def search_author():
    author = request.json.get("author", "")
    page = request.json.get("page", "")
    u = user.User()
    code, message = u.search_author(author=author, page=page)
    return jsonify({"message": message}), code

@bp_auth.route("/search_book_intro", methods=["POST"])
def search_book_intro():
    book_intro = request.json.get("book_intro", "")
    page = request.json.get("page", "")
    u = user.User()
    code, message = u.search_book_intro(book_intro=book_intro, page=page)
    return jsonify({"message": message}), code

@bp_auth.route("/search_tags", methods=["POST"])
def search_tags():
    tags = request.json.get("tags", "")
    page = request.json.get("page", "")
    u = user.User()
    code, message = u.search_tags(tags=tags, page=page)
    return jsonify({"message": message}), code

@bp_auth.route("/search_title", methods=["POST"])
def search_title():
    title = request.json.get("title", "")
    page = request.json.get("page", "")
    u = user.User()
    code, message = u.search_title(title=title, page=page)
    return jsonify({"message": message}), code
  1. 为搜索编写测试接口

$$ \color{red}{测试截图,需要终端里面的那些截图和导出的html文件(htmlcov目录中index.html)截图}$$

测试结果

$\color{red}{交给你了( ᗜ ‸ ᗜ )}$

总结

$\color{red}{交给你了( ᗜ ‸ ᗜ )}$

分工

$\color{red}{交给你了( ᗜ ‸ ᗜ )}$