板凳-------Mysql cookbook学习 (十一--------12)

发布于:2025-07-18 ⋅ 阅读:(15) ⋅ 点赞:(0)

第16章:使用存储例程、触发器和事件
16.0 引言

mysql> -- 首先设置分隔符(避免分号被解释为语句结束)
mysql> DELIMITER //
mysql>
mysql> -- 创建第一个存储过程
mysql> CREATE PROCEDURE get_time()
    -> BEGIN
    ->     SET @current_time = CURTIME();
    -> END//
ERROR 1304 (42000): PROCEDURE get_time already exists
mysql>
mysql> -- 创建第二个存储过程(调用第一个)
mysql> CREATE PROCEDURE part_of_day()
    -> BEGIN
    ->     CALL get_time();
    ->
    ->     -- 这里可以添加更多逻辑,例如:
    ->     IF HOUR(@current_time) < 12 THEN
    ->         SELECT 'Morning' AS part_of_day;
    ->     ELSEIF HOUR(@current_time) < 18 THEN
    ->         SELECT 'Afternoon' AS part_of_day;
    ->     ELSE
    ->         SELECT 'Evening' AS part_of_day;
    ->     END IF;
    -> END//
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> -- 恢复默认分隔符
mysql> DELIMITER ;
mysql>
mysql> -- 测试调用
mysql> CALL part_of_day();
+-------------+
| part_of_day |
+-------------+
| Evening     |
+-------------+
1 row in set (0.01 sec)

Query OK, 0 rows affected (0.02 sec)
关键点说明:
1.	DELIMITER 命令:临时将语句分隔符从 ; 改为 //,这样存储过程中的分号不会提前终止创建过程
2.	BEGIN/END 块:每个存储过程体必须用 BEGINEND 包围
3.	调用顺序:确保被调用的存储过程(get_time())已经存在
4.	完整语法:存储过程可以包含复杂的逻辑(如我的示例中添加了时间段判断)

16.1 创建复合语句对象

mysql> -- 首先设置分隔符
mysql> DELIMITER //
mysql>
mysql> -- 创建第一个无参数函数(已成功)
mysql> CREATE FUNCTION avg_mail_size()
    -> RETURNS FLOAT READS SQL DATA
    -> RETURN (SELECT AVG(size) FROM mail)//
ERROR 1304 (42000): FUNCTION avg_mail_size already exists
mysql>
mysql> -- 创建带参数的函数
mysql> CREATE FUNCTION avg_mail_size(user VARCHAR(8))
    -> RETURNS FLOAT READS SQL DATA
    -> BEGIN
    ->     IF user IS NULL THEN
    ->         RETURN (SELECT AVG(size) FROM mail);
    ->     ELSE
    ->         RETURN (SELECT AVG(size) FROM mail WHERE srcuser = user);
    ->     END IF;
    -> END//
ERROR 1304 (42000): FUNCTION avg_mail_size already exists
mysql>
mysql> -- 恢复默认分隔符
mysql> DELIMITER ;
mysql>

mysql> DROP FUNCTION IF EXISTS avg_mail_size;
Query OK, 0 rows affected (0.02 sec)

mysql> DELIMITER $$
mysql> CREATE FUNCTION avg_mail_size(user VARCHAR(8))
    -> RETURNS FLOAT READS SQL DATA
    -> BEGIN
    ->     IF user IS NULL THEN
    ->         RETURN (SELECT AVG(size) FROM mail);
    ->     ELSE
    ->         RETURN (SELECT AVG(size) FROM mail WHERE srcuser = user);
    ->     END IF;
    -> END$$
Query OK, 0 rows affected (0.02 sec)

mysql> DELIMITER ;
mysql> select avg_mail_size(null), avg_mail_size('barb');
+---------------------+-----------------------+
| avg_mail_size(null) | avg_mail_size('barb') |
+---------------------+-----------------------+
|              237387 |                 52232 |
+---------------------+-----------------------+
1 row in set (0.01 sec)

16.2 使用存储函数封装计算

mysql> DELIMITER $$
mysql>
mysql> CREATE FUNCTION sales_tax(state_param CHAR(2), amount_param DECIMAL(10, 2))
    -> RETURNS DECIMAL(10, 2) READS SQL DATA
    -> BEGIN
    ->     DECLARE rate_var DECIMAL(3, 2);  -- 修正声明语法
    ->     DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET rate_var = 0;
    ->
    ->     SELECT tax_rate INTO rate_var
    ->     FROM sales_tax_rate
    ->     WHERE state = state_param;
    ->
    ->     RETURN amount_param * rate_var;
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;

mysql> select sales_tax('ZZ', 100.00);
+-------------------------+
| sales_tax('ZZ', 100.00) |
+-------------------------+
|                    0.00 |
+-------------------------+
1 row in set (0.01 sec)

完整测试流程示例

mysql> -- 1. 确保表结构正确
mysql> CREATE TABLE IF NOT EXISTS sales_tax_rate (
    ->     state CHAR(2) PRIMARY KEY,
    ->     tax_rate DECIMAL(3,2)
    -> );
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql>
mysql> -- 2. 清空并重置测试数据
mysql> TRUNCATE TABLE sales_tax_rate;
Query OK, 0 rows affected (0.07 sec)

mysql> INSERT INTO sales_tax_rate VALUES
    ->     ('CA', 0.08),
    ->     ('NY', 0.09),
    ->     ('TX', 0.0625);
Query OK, 3 rows affected, 1 warning (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 1

mysql>
mysql> -- 3. 重新创建函数(确保使用最新版本)
mysql> DELIMITER $$
mysql> DROP FUNCTION IF EXISTS sales_tax$$
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE FUNCTION sales_tax(state_param CHAR(2), amount_param DECIMAL(10,2))
    -> RETURNS DECIMAL(10,2) READS SQL DATA
    -> BEGIN
    ->     DECLARE rate_var DECIMAL(3,2) DEFAULT 0;
    ->     DECLARE CONTINUE HANDLER FOR NOT FOUND SET rate_var = 0;
    ->
    ->     SELECT tax_rate INTO rate_var
    ->     FROM sales_tax_rate
    ->     WHERE state = state_param;
    ->
    ->     RETURN amount_param * rate_var;
    -> END$$
Query OK, 0 rows affected (0.02 sec)

mysql> DELIMITER ;
mysql>
mysql> -- 4. 全面测试
mysql> SELECT
    ->     sales_tax('CA', 100) AS California,
    ->     sales_tax('NY', 100) AS NewYork,
    ->     sales_tax('TX', 100) AS Texas,
    ->     sales_tax('XX', 100) AS UnknownState;
+------------+---------+-------+--------------+
| California | NewYork | Texas | UnknownState |
+------------+---------+-------+--------------+
|       8.00 |    9.00 |  6.00 |         0.00 |
+------------+---------+-------+--------------+
1 row in set (0.00 sec)

16.3 使用存储过程来“返回”多个值

mysql> DELIMITER $$
mysql>
mysql> CREATE PROCEDURE mail_sends_stats(
    ->     IN user VARCHAR(8),
    ->     OUT messages INT,
    ->     OUT total_size FLOAT,
    ->     OUT avg_size FLOAT
    -> )
    -> BEGIN
    ->     SELECT COUNT(*), IFNULL(SUM(size), 0), IFNULL(AVG(size), 0)
    ->     INTO messages, total_size, avg_size
    ->     FROM mail WHERE srcuser = user;
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> DELIMITER ;

mysql> call mail_sends_stats('barb', @messages, @total_size, @avg_size);
Query OK, 1 row affected (0.00 sec)

mysql> select @messages, @total_size, @avg_size;
+-----------+-------------+-----------+
| @messages | @total_size | @avg_size |
+-----------+-------------+-----------+
|         3 |      156696 |     52232 |
+-----------+-------------+-----------+
1 row in set (0.00 sec)
16.4 用触发器来定义动态的默认列值
mysql> create table doc_table(
    -> author varchar(100) not null,
    -> title  varchar(100) not null,
    -> documer mediumblob not null,
    -> doc_hash char(32) not null,
    -> primary key (doc_hash)
    -> );
Query OK, 0 rows affected (0.05 sec)


mysql> create trigger bi_doc_table before insert on doc_table
    -> for each row set new.doc_hash = MD5(new.documer);
Query OK, 0 rows affected (0.01 sec)

mysql> insert into doc_table (author, title, documer)
    -> values('Mr. Famous Writer', 'My life as a writer', 'This is the document');
Query OK, 1 row affected (0.01 sec)


mysql> SELECT * FROM doc_table\G
*************************** 1. row ***************************
  author: Mr. Famous Writer
   title: My life as a writer
 documer: 0x546869732069732074686520646F63756D656E74
doc_hash: 5282317909724f9f1e65318be129539c
1 row in set (0.00 sec)

mysql> CREATE TRIGGER bu_doc_table
    -> BEFORE UPDATE ON doc_table
1.	    -> FOR EACH ROW  #确保 FOR EACH ROW 作为完整的语法单元
    -> SET NEW.doc_hash = MD5(NEW.documer);
Query OK, 0 rows affected (0.02 sec)

mysql> -- 删除已存在的同名触发器(如果存在)
mysql> DROP TRIGGER IF EXISTS bu_doc_table;
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> -- 创建更新触发器
mysql> CREATE TRIGGER bu_doc_table
    -> BEFORE UPDATE ON doc_table
    -> FOR EACH ROW
    -> SET NEW.doc_hash = MD5(NEW.documer);
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> -- 测试触发器
mysql> UPDATE doc_table
    -> SET documer = 'Updated document content'
    -> WHERE author = 'Mr. Famous Writer';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select MD5('a new document');
+----------------------------------+
| MD5('a new document')            |
+----------------------------------+
| a5136f98d2313cc850527957b4293f60 |
+----------------------------------+
1 row in set (0.00 sec)

16.5 为其他日期和时间类型模拟timestamp属性

mysql> create table ts_emulate(
    -> data char(10),
    -> d    date,
    -> t    time,
    -> dt    datetime
    -> );
Query OK, 0 rows affected (0.04 sec)

mysql> create trigger bi_ts_emulate before insert on ts_emulate
    -> for each row set new.d = curdate(), new.t = curtime(), new.dt = now();
Query OK, 0 rows affected (0.01 sec)

mysql> insert into ts_emulate (data) values ('cat');
Query OK, 1 row affected (0.01 sec)

mysql> insert into ts_emulate (data) values ('dog');
Query OK, 1 row affected (0.01 sec)

mysql> select * from ts_emulate;
+------+------------+----------+---------------------+
| data | d          | t        | dt                  |
+------+------------+----------+---------------------+
| cat  | 2025-07-15 | 21:40:48 | 2025-07-15 21:40:48 |
| dog  | 2025-07-15 | 21:40:59 | 2025-07-15 21:40:59 |
+------+------------+----------+---------------------+
2 rows in set (0.00 sec)

mysql> update ts_emulate set data = 'axolot1' where data = 'cat';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from ts_emulate;
+---------+------------+----------+---------------------+
| data    | d          | t        | dt                  |
+---------+------------+----------+---------------------+
| axolot1 | 2025-07-15 | 21:40:48 | 2025-07-15 21:40:48 |
| dog     | 2025-07-15 | 21:40:59 | 2025-07-15 21:40:59 |
+---------+------------+----------+---------------------+
2 rows in set (0.00 sec)

mysql> update ts_emulate set data = data;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 2  Changed: 0  Warnings: 0

mysql> select * from ts_emulate;
+---------+------------+----------+---------------------+
| data    | d          | t        | dt                  |
+---------+------------+----------+---------------------+
| axolot1 | 2025-07-15 | 21:40:48 | 2025-07-15 21:40:48 |
| dog     | 2025-07-15 | 21:40:59 | 2025-07-15 21:40:59 |
+---------+------------+----------+---------------------+
2 rows in set (0.00 sec)

16.6 使用触发器记录表的变化

拍卖流程图 java 拍卖系统开发_mob6454cc780924的技术博客_51CTO博客

drop database if exists auction;

create database auction;

use auction;

#用户表
create table auction_user(
  user_id int(11) auto_increment,
  username varchar(50) not null,
  userpass varchar(50) not null,
  email varchar(100) not null,
  primary key(user_id),
  unique(username)
);

INSERT INTO auction_user (username,userpass,email) VALUES ('tomcat','tomcat','spring_test@');
INSERT INTO auction_user (username,userpass,email) VALUES ('mysql','mysql','spring_test@');

#物品种类表
create table kind(
  kind_id int(11) auto_increment,
  kind_name varchar(50) not null, 
  kind_desc varchar(255) not null,
  primary key(kind_id)
);

INSERT INTO kind (kind_name,kind_desc) VALUES ('电脑硬件','这里并不是很主流的产品,但价格绝对令你心动');
INSERT INTO kind (kind_name,kind_desc) VALUES ('房产','提供非常稀缺的房源');

#物品状态表
create table state(
  state_id int(11) auto_increment,
  state_name varchar(10),
  primary key(state_id)
);

INSERT INTO state (state_name) VALUES ('拍卖中');
INSERT INTO state (state_name) VALUES ('拍卖成功');
INSERT INTO state (state_name) VALUES ('流拍');

#物品表
create table item(
  item_id int(11) auto_increment,
  item_name varchar(255) not null,
  item_remark varchar(255),
  item_desc varchar(255),
  kind_id int(11) not null, 
  addtime date not null,
  endtime date not null,
  init_price double not null, 
  max_price double not null, 
  owner_id int(11) not null, 
  winer_id int(11), 
  state_id int(11) not null, 
  primary key(item_id),
  FOREIGN KEY(kind_id) REFERENCES kind(kind_id), 
  FOREIGN KEY(owner_id) REFERENCES auction_user(user_id),
  FOREIGN KEY(winer_id) REFERENCES auction_user(user_id),
  FOREIGN KEY(state_id) REFERENCES state(state_id)
); 

# 拍卖中的物品
INSERT INTO item ( item_name , item_remark , item_desc, kind_id, addtime , endtime, init_price,  max_price,  owner_id,  winer_id,  state_id)
	VALUES ( '主板', '老式主板', '老主板,还可以用', 1, ADDDATE(CURDATE(), -5), ADDDATE(CURDATE(), 30) , 230, 250, 1,  null,  1);
# 流派的物品
INSERT INTO item ( item_name , item_remark , item_desc, kind_id, addtime , endtime, init_price,  max_price,  owner_id,  winer_id,  state_id)
	VALUES ( '显卡', '老式显卡', '老显卡,还可以用', 1, ADDDATE(CURDATE(), -9), ADDDATE(CURDATE(), -2), 210, 210, 2,  null,  3);
# 被竞得的物品
INSERT INTO item ( item_name , item_remark , item_desc, kind_id, addtime , endtime, init_price,  max_price,  owner_id,  winer_id,  state_id)
	VALUES ( '老房子', '老式房子', '40年的老房子', 2, ADDDATE(CURDATE(), -9), ADDDATE(CURDATE(), -5), 21000, 25000, 2,  1,  2);

#竞标历史表
create table bid(
  bid_id int(11) auto_increment,
  user_id int(11) not null,
  item_id int(11) not null,
  bid_price double not null,
  bid_date date not null, 
  primary key(bid_id),
  unique(item_id , bid_price),
  FOREIGN KEY(user_id) REFERENCES auction_user(user_id), 
  FOREIGN KEY(item_id) REFERENCES item(item_id)   
);

INSERT INTO bid ( user_id , item_id , bid_price, bid_date)
	VALUES ( 2, 1, 250, ADDDATE(CURDATE(), -2));
INSERT INTO bid ( user_id , item_id , bid_price, bid_date)
	VALUES ( 1, 3, 25000, ADDDATE(CURDATE(), -6));

Python Web服务器搭建 - 拍卖系统
下面是将Java PO/VO对象和MySQL数据库转换为Python的实现:

1. 数据库模型 (PO对象)
python
from datetime import datetime
from typing import List, Set, Optional
from sqlalchemy import Column, Integer, String, Float, Date, ForeignKey, Table
from sqlalchemy.orm import relationship, declarative_base

Base = declarative_base()

# 关联表
bid_item_user = Table('bid', Base.metadata,
    Column('bid_id', Integer, primary_key=True, autoincrement=True),
    Column('user_id', Integer, ForeignKey('auction_user.user_id'), nullable=False),
    Column('item_id', Integer, ForeignKey('item.item_id'), nullable=False),
    Column('bid_price', Float, nullable=False),
    Column('bid_date', Date, nullable=False)
)

class User(Base):
    __tablename__ = 'auction_user'
    
    user_id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), unique=True, nullable=False)
    userpass = Column(String(50), nullable=False)
    email = Column(String(100), nullable=False)
    
    # 关系
    owned_items = relationship("Item", back_populates="owner", foreign_keys="Item.owner_id")
    won_items = relationship("Item", back_populates="winer", foreign_keys="Item.winer_id")
    bids = relationship("Bid", back_populates="user")

class Kind(Base):
    __tablename__ = 'kind'
    
    kind_id = Column(Integer, primary_key=True, autoincrement=True)
    kind_name = Column(String(50), nullable=False)
    kind_desc = Column(String(255), nullable=False)
    
    items = relationship("Item", back_populates="kind")

class State(Base):
    __tablename__ = 'state'
    
    state_id = Column(Integer, primary_key=True, autoincrement=True)
    state_name = Column(String(10))
    
    items = relationship("Item", back_populates="item_state")

class Item(Base):
    __tablename__ = 'item'
    
    item_id = Column(Integer, primary_key=True, autoincrement=True)
    item_name = Column(String(255), nullable=False)
    item_remark = Column(String(255))
    item_desc = Column(String(255))
    
    # 外键
    kind_id = Column(Integer, ForeignKey('kind.kind_id'), nullable=False)
    owner_id = Column(Integer, ForeignKey('auction_user.user_id'), nullable=False)
    winer_id = Column(Integer, ForeignKey('auction_user.user_id'))
    state_id = Column(Integer, ForeignKey('state.state_id'), nullable=False)
    
    addtime = Column(Date, nullable=False)
    endtime = Column(Date, nullable=False)
    init_price = Column(Float, nullable=False)
    max_price = Column(Float, nullable=False)
    
    # 关系
    kind = relationship("Kind", back_populates="items")
    owner = relationship("User", back_populates="owned_items", foreign_keys=[owner_id])
    winer = relationship("User", back_populates="won_items", foreign_keys=[winer_id])
    item_state = relationship("State", back_populates="items")
    bids = relationship("Bid", back_populates="item")

class Bid(Base):
    __tablename__ = 'bid'
    
    bid_id = Column(Integer, primary_key=True, autoincrement=True)
    bid_price = Column(Float, nullable=False)
    bid_date = Column(Date, nullable=False)
    
    # 外键
    user_id = Column(Integer, ForeignKey('auction_user.user_id'), nullable=False)
    item_id = Column(Integer, ForeignKey('item.item_id'), nullable=False)
    
    # 关系
    user = relationship("User", back_populates="bids")
    item = relationship("Item", back_populates="bids")
2. 业务对象 (VO对象)
python
from dataclasses import dataclass
from datetime import datetime

@dataclass
class ItemBean:
    id: int
    name: str
    desc: str
    remark: str
    kind: str
    owner: str
    winer: str
    state: str
    init_price: float
    max_price: float
    add_time: datetime
    end_time: datetime

@dataclass
class BidBean:
    id: int
    user: str
    item: str
    price: float
    bid_date: datetime

@dataclass
class KindBean:
    id: int
    kind_name: str
    kind_desc: str
3. 数据库初始化
python
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 创建数据库连接
DATABASE_URL = "mysql+pymysql://username:password@localhost/auction"
engine = create_engine(DATABASE_URL)

# 创建表
Base.metadata.create_all(engine)

# 创建会话工厂
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
4. FastAPI 路由示例
python
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from datetime import datetime, timedelta

app = FastAPI()

# 获取所有拍卖物品
@app.get("/items/", response_model=List[ItemBean])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    db_items = db.query(Item).offset(skip).limit(limit).all()
    items = []
    for item in db_items:
        items.append(ItemBean(
            id=item.item_id,
            name=item.item_name,
            desc=item.item_desc,
            remark=item.item_remark,
            kind=item.kind.kind_name,
            owner=item.owner.username,
            winer=item.winer.username if item.winer else None,
            state=item.item_state.state_name,
            init_price=item.init_price,
            max_price=item.max_price,
            add_time=item.addtime,
            end_time=item.endtime
        ))
    return items

# 创建新物品
@app.post("/items/", response_model=ItemBean)
def create_item(item: ItemBean, db: Session = Depends(get_db)):
    db_item = Item(
        item_name=item.name,
        item_desc=item.desc,
        item_remark=item.remark,
        kind_id=item.kind_id,
        addtime=datetime.now(),
        endtime=datetime.now() + timedelta(days=30),
        init_price=item.init_price,
        max_price=item.init_price,
        owner_id=item.owner_id,
        state_id=1  # 默认拍卖中
    )
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return item

# 获取单个物品详情
@app.get("/items/{item_id}", response_model=ItemBean)
def read_item(item_id: int, db: Session = Depends(get_db)):
    db_item = db.query(Item).filter(Item.item_id == item_id).first()
    if db_item is None:
        raise HTTPException(status_code=404, detail="Item not found")
    return ItemBean(
        id=db_item.item_id,
        name=db_item.item_name,
        desc=db_item.item_desc,
        remark=db_item.item_remark,
        kind=db_item.kind.kind_name,
        owner=db_item.owner.username,
        winer=db_item.winer.username if db_item.winer else None,
        state=db_item.item_state.state_name,
        init_price=db_item.init_price,
        max_price=db_item.max_price,
        add_time=db_item.addtime,
        end_time=db_item.endtime
    )
5. 数据库初始化数据
python
def init_db():
    db = SessionLocal()
    
    try:
        # 创建测试用户
        user1 = User(username="tomcat", userpass="tomcat", email="spring_test@")
        user2 = User(username="mysql", userpass="mysql", email="spring_test@")
        db.add_all([user1, user2])
        db.commit()
        
        # 创建物品种类
        kind1 = Kind(kind_name="电脑硬件", kind_desc="这里并不是很主流的产品,但价格绝对令你心动")
        kind2 = Kind(kind_name="房产", kind_desc="提供非常稀缺的房源")
        db.add_all([kind1, kind2])
        db.commit()
        
        # 创建物品状态
        state1 = State(state_name="拍卖中")
        state2 = State(state_name="拍卖成功")
        state3 = State(state_name="流拍")
        db.add_all([state1, state2, state3])
        db.commit()
        
        # 创建物品
        item1 = Item(
            item_name="主板",
            item_remark="老式主板",
            item_desc="老主板,还可以用",
            kind_id=kind1.kind_id,
            addtime=datetime.now() - timedelta(days=5),
            endtime=datetime.now() + timedelta(days=30),
            init_price=230,
            max_price=250,
            owner_id=user1.user_id,
            state_id=state1.state_id
        )
        
        item2 = Item(
            item_name="显卡",
            item_remark="老式显卡",
            item_desc="老显卡,还可以用",
            kind_id=kind1.kind_id,
            addtime=datetime.now() - timedelta(days=9),
            endtime=datetime.now() - timedelta(days=2),
            init_price=210,
            max_price=210,
            owner_id=user2.user_id,
            state_id=state3.state_id
        )
        
        item3 = Item(
            item_name="老房子",
            item_remark="老式房子",
            item_desc="40年的老房子",
            kind_id=kind2.kind_id,
            addtime=datetime.now() - timedelta(days=9),
            endtime=datetime.now() - timedelta(days=5),
            init_price=21000,
            max_price=25000,
            owner_id=user2.user_id,
            winer_id=user1.user_id,
            state_id=state2.state_id
        )
        
        db.add_all([item1, item2, item3])
        db.commit()
        
        # 创建竞标记录
        bid1 = Bid(
            user_id=user2.user_id,
            item_id=item1.item_id,
            bid_price=250,
            bid_date=datetime.now() - timedelta(days=2)
        )
        
        bid2 = Bid(
            user_id=user1.user_id,
            item_id=item3.item_id,
            bid_price=25000,
            bid_date=datetime.now() - timedelta(days=6)
        )
        
        db.add_all([bid1, bid2])
        db.commit()
        
    except Exception as e:
        db.rollback()
        raise e
    finally:
        db.close()

# 调用初始化函数
init_db()
6. 运行FastAPI应用
创建一个main.py文件:

python
from fastapi import FastAPI
from models import Base
from database import engine, init_db
import uvicorn

# 创建表
Base.metadata.create_all(bind=engine)

# 初始化数据
init_db()

app = FastAPI()

# 导入路由
from routes import items

app.include_router(items.router)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
7. 依赖安装
创建一个requirements.txt文件:

text
fastapi
uvicorn
sqlalchemy
pymysql
python-dotenv
python-dateutil
然后运行:

bash
pip install -r requirements.txt
8. 运行应用
bash
python main.py
现在你的拍卖系统Web服务器应该运行在 http://localhost:8000 上,你可以访问 /items/ 端点来获取所有拍卖物品的列表。

这个实现使用了:

SQLAlchemy 作为ORM

FastAPI 作为Web框架

Pydantic (通过FastAPI的response_model) 进行数据验证和序列化

MySQL 作为数据库

你可以根据需要进一步扩展功能,比如添加用户认证、竞价功能、物品搜索等。

===================================================
mysql> create table auction(
    -> id int unsigned not null auto_increment,
    -> ts  timestamp,
    -> item varchar(30) not null,
    -> bid decimal(10, 2) not null,
    -> primary key(id)
    -> );
Query OK, 0 rows affected (0.04 sec)

mysql> create table auction_log(
    -> action enum('create', 'update','delete'),
    -> id int unsigned not null,
    -> ts timestamp,
    -> item varchar(30) not null,
    -> bid decimal(10, 2) not null,
    -> index  (id)
    -> );
Query OK, 0 rows affected (0.09 sec)

mysql>  DELIMITER ;
mysql> DELIMITER $$
mysql>
mysql> CREATE TRIGGER ai_auction AFTER INSERT ON auction
    -> FOR EACH ROW
    -> BEGIN
    ->    INSERT INTO auction_log (action, id, ts, item, bid)
    ->    VALUES('create', NEW.id, NOW(), NEW.item, NEW.bid);
    -> END$$
Query OK, 0 rows affected (0.02 sec)

mysql>
mysql> CREATE TRIGGER au_auction AFTER UPDATE ON auction
    -> FOR EACH ROW
    -> BEGIN
    ->    INSERT INTO auction_log (action, id, ts, item, bid)
    ->    VALUES('update', NEW.id, NOW(), NEW.item, NEW.bid);
    -> END$$
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> CREATE TRIGGER ad_auction AFTER DELETE ON auction
    -> FOR EACH ROW
    -> BEGIN
    ->    INSERT INTO auction_log (action, id, ts, item, bid)
    ->    VALUES('delete', OLD.id, OLD.ts, OLD.item, OLD.bid);
    -> END$$
Query OK, 0 rows affected (0.02 sec)

mysql>
mysql> DELIMITER ;
mysql> insert into auction (item, bid) values('chintz pillows', 5.00);
Query OK, 1 row affected (0.01 sec)

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                1 |
+------------------+
1 row in set (0.00 sec)

mysql> -- 1. 首先插入一条记录
mysql> INSERT INTO auction (item, bid) VALUES('chintz pillows', 5.00);
Query OK, 1 row affected (0.01 sec)

mysql>
mysql> -- 2. 获取刚插入的ID(假设返回的是1)
mysql> SELECT last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)

mysql>
mysql> -- 3. 对这条记录进行更新(使用正确的ID)
mysql> UPDATE auction SET bid = 7.50 WHERE id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE auction SET bid = 9.00 WHERE id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> UPDATE auction SET bid = 10.00 WHERE id = 1;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql>
mysql> -- 4. 查询日志(使用正确的ID)
mysql> SELECT * FROM auction_log WHERE id = 1 ORDER BY ts;
+--------+----+---------------------+----------------+-------+
| action | id | ts                  | item           | bid   |
+--------+----+---------------------+----------------+-------+
| create |  1 | 2025-07-16 20:33:13 | chintz pillows |  5.00 |
| update |  1 | 2025-07-16 20:37:51 | chintz pillows |  7.50 |
| update |  1 | 2025-07-16 20:37:51 | chintz pillows |  9.00 |
| update |  1 | 2025-07-16 20:37:51 | chintz pillows | 10.00 |
+--------+----+---------------------+----------------+-------+
4 rows in set (0.00 sec)

如果您想完全重现书上的示例(ID=792),可以这样做:
-- 1. 首先手动插入ID=792的记录(需要暂时关闭自增)
SET FOREIGN_KEY_CHECKS=0;
INSERT INTO auction (id, item, bid) VALUES(792, 'chintz pillows', 5.00);
SET FOREIGN_KEY_CHECKS=1;

-- 2. 然后执行更新操作
UPDATE auction SET bid = 7.50 WHERE id = 792;
UPDATE auction SET bid = 9.00 WHERE id = 792;
UPDATE auction SET bid = 10.00 WHERE id = 792;

-- 3. 查询日志
SELECT * FROM auction_log WHERE id = 792 ORDER BY ts;


16.7 使用事件调度数据库动作
mysql> describe mark_log ;
+---------+--------------+------+-----+-------------------+-----------------------------------------------+
| Field   | Type         | Null | Key | Default           | Extra                                         |
+---------+--------------+------+-----+-------------------+-----------------------------------------------+
| id      | int          | NO   | PRI | NULL              | auto_increment                                |
| ts      | timestamp    | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
| message | varchar(100) | YES  |     | NULL              |                                               |
+---------+--------------+------+-----+-------------------+-----------------------------------------------+
3 rows in set (0.07 sec)

mysql> select * from mark_log;
Empty set (0.01 sec)

mysql> show variables like 'event_scheduler';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| event_scheduler | ON    |
+-----------------+-------+
1 row in set, 1 warning (0.01 sec)

mysql> set global event_scheduler = 1;
Query OK, 0 rows affected (0.00 sec)

网站公告

今日签到

点亮在社区的每一天
去签到