第16章:使用存储例程、触发器和事件
16.0 引言
sql
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 块:每个存储过程体必须用 BEGIN 和 END 包围
3. 调用顺序:确保被调用的存储过程(get_time())已经存在
4. 完整语法:存储过程可以包含复杂的逻辑(如我的示例中添加了时间段判断)
16.1 创建复合语句对象
sql
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 使用存储函数封装计算
sql
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 使用存储过程来"返回"多个值
sql
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属性
sql
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 使用触发器记录表的变化
sql
拍卖流程图 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)