在线拍卖系统低层设计(LLD)实战:基于 awesome-low-level-design 的 C++ 实现全解析
在线拍卖系统低层设计(LLD)实战:基于 awesome-low-level-design 的 C++ 实现全解析
本篇技术指南以 awesome-low-level-design 仓库中 solutions/cpp/onlineauctionsystem/README.md 的设计文档为核心骨架,结合仓库内完整的 C++ 源码实现,深入讲解在线拍卖系统的对象建模、状态机设计、出价校验与交易流程。读完本文,你将掌握如何用面向对象的方式设计用户注册、商品上架、拍卖生命周期管理、实时最高价维护与成交结算等核心能力,并理解一份可编译运行的 LLD 参考实现应当如何组织。
需求分析:在线拍卖系统要解决什么问题
原文档明确了该系统的 8 条核心需求,它们是后续所有类设计的出发点:
- 用户能够注册并登录自己的账户;
- 用户能够创建新的拍卖条目,包含商品名称、描述、起拍价与拍卖时长;
- 用户能够基于多种条件(商品名、品类、价格区间等)浏览与搜索拍卖条目;
- 用户能够在进行中的拍卖条目上出价;
- 系统自动更新当前最高出价并通知相关竞拍者;
- 拍卖在指定时长到达后结束,最高出价者被宣布为获胜者;
- 系统需要处理对拍卖条目的并发访问并保证数据一致性;
- 系统应具备可扩展性,以容纳未来的增强与新功能。
在仓库的 C++ 实现中,这些需求被映射为四大领域类(User、Item、Auction、Bid)加一个核心门面服务类(AuctionSystem),并通过枚举驱动拍卖状态流转。值得注意的是:README 中描述的 Singleton 与 ConcurrentHashMap/CopyOnWriteArrayList 并发结构属于 Java 版设计意图;当前目录下的 C++ 实现是单线程教学演示版本,存储容器为 std::vector,这部分差异会在下文逐一说明。
领域模型设计:四个核心类与三个枚举
User:用户主体
User.hpp 中定义的 User 类承载账户基础信息与资金、交易记录:
class User {
private:
std::string userId;
std::string username;
std::string email;
double balance;
std::vector<Item*> listedItems;
std::vector<Item*> purchasedItems;
bool active;
};
除了文档提到的 id/username/email 属性,C++ 实现还扩展了三个关键能力:
balance余额:支持addBalance(double amount)充值,以及deductBalance(double amount)扣款(余额不足时返回false,见 User.cpp);listedItems/purchasedItems:分别记录该用户发布的商品与最终拍得的商品;active状态位:setActive(bool)用于禁用账户,禁止非活跃用户参与交易。
User 的析构函数特意只清空指针列表而 delete 对象——所有权由 AuctionSystem 统一管理(见下文),避免双重释放。
Item:被拍卖的商品
Item.hpp 定义了两个业务枚举与商品类本身:
enum class ItemCategory { ELECTRONICS, FASHION, HOME, SPORTS, BOOKS, COLLECTIBLES, OTHER };
enum class ItemStatus { AVAILABLE, IN_AUCTION, SOLD, WITHDRAWN };
Item 持有 itemId、name、description、category、startingPrice、status、sellerId。其核心价值在于 ItemStatus 状态位与拍卖生命周期的联动:商品刚创建时为 AVAILABLE,进入拍卖后变为 IN_AUCTION,成交后变为 SOLD,取消拍卖则恢复 AVAILABLE。这一设计让商品状态与拍卖状态解耦、可独立查询。
Bid 与 Auction:出价记录与拍卖聚合
Auction.hpp 同时定义了 Bid 结构体与 Auction 类:
struct Bid {
std::string bidderId;
double amount;
std::time_t timestamp;
Bid(std::string bidderId, double amount)
: bidderId(bidderId), amount(amount), timestamp(std::time(nullptr)) {}
};
enum class AuctionStatus { PENDING, ACTIVE, ENDED, CANCELLED };
class Auction {
private:
std::string auctionId;
Item* item;
std::time_t startTime, endTime;
double currentPrice;
std::vector<Bid> bids;
AuctionStatus status;
std::string winnerId;
};
Bid 在构造时自动记录 std::time(nullptr) 时间戳,出价历史以 std::vector<Bid> 顺序保存。Auction 是典型的"聚合根":它持有 Item* 引用、维护 currentPrice 最高价、winnerId 当前领先者,并对外暴露 placeBid/start/end/cancel/isActive 四个状态操作方法。
核心服务:AuctionSystem 的职责与实现
AuctionSystem.hpp 中的 AuctionSystem 是系统的门面(Facade),对外提供注册、建品、开拍、出价、结算、搜索等全部业务入口,内部维护三个容器与两个自增计数器:
std::vector<User*> users;
std::vector<Item*> items;
std::vector<Auction*> auctions;
int itemIdCounter; // 从 1 开始,生成 "I1"、"I2"...
int auctionIdCounter; // 从 1 开始,生成 "A1"、"A2"...
各业务方法的实现要点(均位于 AuctionSystem.cpp):
registerUser:ID 自动按users.size() + 1生成U1/U2...;createItem:先校验卖家存在且isActive(),商品 ID 由generateItemId()生成,同时把商品挂到卖家的listedItems;createAuction:先校验商品存在且状态为AVAILABLE(一件商品不能同时参与两场拍卖);placeBid:执行完整的出价校验链(详见下一节);endAuction:拍卖结束后,若存在获胜者,将其物品加入获胜者的purchasedItems;searchItems:按query对name与description做std::string::find子串匹配,且仅返回AVAILABLE状态商品;getActiveAuctions:线性过滤出所有isActive()的拍卖。
从源码结构看,C++ 版本的查找(findUser/findItem/findAuction)均采用 std::find_if 线性扫描,这是教学实现为了可读性对 README 所述并发哈希结构的简化替代。
拍卖生命周期:状态机与状态联动
AuctionStatus 定义了四个状态,Auction::start/end/cancel 构成状态流转的核心逻辑(见 Auction.cpp):
void Auction::start() {
if (status == AuctionStatus::PENDING) {
status = AuctionStatus::ACTIVE;
item->setStatus(ItemStatus::IN_AUCTION);
}
}
void Auction::end() {
if (status == AuctionStatus::ACTIVE) {
status = AuctionStatus::ENDED;
item->setStatus(ItemStatus::SOLD);
}
}
void Auction::cancel() {
status = AuctionStatus::CANCELLED;
item->setStatus(ItemStatus::AVAILABLE);
winnerId.clear();
}
整个生命周期可用如下状态流转概括:
PENDING --start()--> ACTIVE --end()--> ENDED(物品 SOLD)
|
+--cancel()--> CANCELLED(物品恢复 AVAILABLE)
每一次状态变更都同步改写关联 Item 的状态位,保证"拍卖状态—商品状态"双视图一致。displayInfo() 则把拍卖 ID、商品详情、起止时间、当前价、出价历史与获胜者统一打印,形成完整的可读快照。
出价流程:校验链与资金处理
placeBid 是整个系统业务复杂度最高的方法,其校验链值得逐条拆解(AuctionSystem.cpp):
bool AuctionSystem::placeBid(std::string auctionId, std::string bidderId, double amount) {
Auction* auction = findAuction(auctionId);
User* bidder = findUser(bidderId);
if (!auction || !bidder || !bidder->isActive() || !auction->isActive() ||
bidder->getBalance() < amount) {
return false;
}
if (auction->placeBid(bidderId, amount)) {
bidder->deductBalance(amount);
return true;
}
return false;
}
完整校验分两层:系统层先检查拍卖/竞拍者存在、竞拍者账户活跃、竞拍者余额 ≥ 出价额;领域层(Auction::placeBid)再检查拍卖处于 ACTIVE 且 amount > currentPrice(严格大于,防止"平拍")。通过后:
- 追加一条
Bid记录(自动打时间戳); currentPrice更新为新出价;winnerId更新为当前出价者;- 竞拍者余额立即扣减出价额。
需要如实指出的简化点:该演示实现不退还被超越者的已扣金额(每次成功出价都扣款,没有"退回上一最高出价"逻辑),且扣款发生在 bidder 而非获胜者结算时,属于教学简化。生产系统通常需要冻结(hold)+ 结算退还的完整资金流,这是阅读该实现时需要注意的边界。
演示程序:从注册到成交的完整闭环
AuctionSystemDemo.cpp 是 main 入口,完整演示了"注册 → 充值 → 建品 → 开拍 → 出价 → 结束 → 展示结果"的端到端流程:
- 注册卖家
john_seller与竞拍者alice_bidder、bob_bidder; - 分别为两位竞拍者充值
1000.0与1500.0; - 卖家创建
Smartphone(起拍价 500,ELECTRONICS)与Luxury Watch(起拍价 1000,FASHION); - 为两件商品创建
now到now + 3600(1 小时)的拍卖并startAuction; - 手机拍卖依次收到 600 与 700 的出价,手表拍卖收到 1200 的出价;
endAuction结束后打印最终结果与双方余额。
关键时序:now + 3600 演示了"拍卖时长"如何用 std::time_t 表达;出价 600→700 的次序演示了 currentPrice 的滚动覆盖;结束后的余额打印可以直观看到每次出价的扣款效果。
编译与运行
该目录未附带 CMake/构建脚本,最简方式是用 g++ 将 5 个源文件一并编译(需支持 C++11 及以上):
g++ -std=c++11 AuctionSystemDemo.cpp Auction.cpp AuctionSystem.cpp Item.cpp User.cpp -o online_auction_demo
./online_auction_demo
运行后控制台会依次输出初始余额、商品信息、活跃拍卖、出价结果、当前拍卖状态、最终成交结果与最终余额,可用于逐行对照本文的状态机与校验逻辑。
设计模式与可扩展性
- 门面模式:
AuctionSystem封装底层容器与领域对象,调用方(Demo)只面向系统级 API,符合 README 第 8 条"可扩展性"目标; - 状态模式思想:
AuctionStatus/ItemStatus枚举 + 条件守卫是状态模式的轻量变体,后续可平滑升级为独立状态类; - ID 生成策略:
U/I/A前缀 + 自增计数器的组合,便于按类型区分实体; - 未来增强点(从代码结构推断):搜索目前是线性子串匹配,可替换为倒排索引;出价通知目前未实现,可引入观察者模式(仓库 design-patterns 目录中有各语言 Observer 模式参考);资金结算可参考 digitalwalletservice 的冻结/退款思路;并发场景可参考 README 描述的 Java 版
ConcurrentHashMap方案演进。
本文对应的类图位于 class-diagrams/onlineauctionsystem-class-diagram.png,可对照查看四类一结构的完整关联关系。
总体而言,这份 C++ 实现在 8 个源文件中完整覆盖了 README 列出的全部需求点,是学习"需求→建模→状态机→业务流程"LLD 链路的高质量参考样例;阅读时可同时对照仓库内 Java 版解决方案 与 问题描述,对比不同语言下同一设计的表达差异。
