第三章:系统架构总览

好,咱们进入正题。这一章我打算把整个NFT拍卖系统的骨架给你拆开看看。说白了,就是回答三个问题:系统长什么样?模块怎么切?技术选型怎么定?

我记得刚入行那会儿,最怕听人说「架构设计」。总觉得那是大佬们的事。后来自己带项目了才发现,架构其实就是「分而治之」——把大问题切成小块,再给每块找个合适的工具。

整体架构设计:三层一链

我习惯把系统分成三层,再加一条链。你想想看,NFT拍卖的核心是什么?是「链上确权」+「链下体验」。所以架构必须兼顾这两头。

核心架构分层:

  • 展示层(前端):用户看到的界面,包括拍卖大厅、出价面板、钱包连接
  • 业务层(后端):处理拍卖逻辑、订单管理、用户系统
  • 合约层(智能合约):链上的拍卖合约、NFT合约、结算合约
  • 数据层(链+库):链上事件索引 + 关系型数据库

这里有个坑,我踩过。千万别把业务逻辑全塞进合约里。Gas费贵不说,调试起来能让你怀疑人生。我建议:合约只做核心资产操作,业务逻辑放后端

模块划分:七个核心模块

模块怎么切?我一般按「职责单一」来分。每个模块只干一件事,干好。

模块名称 职责 关键组件
用户模块 注册、登录、钱包绑定 JWT认证、MetaMask集成
NFT管理模块 铸造、元数据存储、转移 IPFS、ERC-721合约
拍卖引擎 创建拍卖、出价、成交 拍卖合约、时间锁
订单模块 出价记录、成交记录 MySQL + Redis缓存
通知模块 出价提醒、成交通知 WebSocket、邮件服务
索引模块 监听链上事件,同步到数据库 ethers.js、消息队列
管理后台 平台配置、风控、数据统计 React Admin、图表库

嗯,这里要注意。拍卖引擎是核心中的核心。我见过不少项目,拍卖逻辑写在后端,结果前端和合约对不上时间戳,导致用户出价失败。我的做法是:拍卖时间以链上区块时间为准,后端只做展示

技术栈选型:我的推荐清单

选技术栈这事儿,没有银弹。但我可以给你一套经过验证的组合。这套组合我在三个项目里用过,没出过大问题。

前端技术栈:

  • React + TypeScript(生态成熟,组件库多)
  • wagmi + RainbowKit(钱包连接,比web3-react好用)
  • Tailwind CSS(快速出界面,适合迭代)

后端技术栈:

  • Node.js + Express(轻量,适合API服务)
  • PostgreSQL(关系型数据,支持JSON字段)
  • Redis(缓存出价状态,防重复提交)
  • RabbitMQ(处理链上事件,异步解耦)

合约技术栈:

  • Solidity + Hardhat(开发调试方便)
  • OpenZeppelin(安全合约库,别自己造轮子)
  • Ethers.js(链上交互,比web3.js轻)

为什么选PostgreSQL?我刚开始用MongoDB,后来发现NFT的元数据、订单记录经常需要关联查询。关系型数据库在这方面天然优势。你想想看,查「某个用户所有竞拍过的NFT」,SQL一句搞定,MongoDB得写聚合管道。

系统交互流程:一次完整的拍卖

光说理论没意思。咱们走一遍流程,看看一次拍卖从创建到成交,系统内部发生了什么。

// 伪代码:拍卖创建流程
1. 用户A在前端点击「创建拍卖」
2. 前端调用合约:createAuction(nftAddress, tokenId, startPrice, duration)
3. 合约校验:A是否拥有该NFT?是否已授权?
4. 合约锁定NFT到拍卖合约地址
5. 合约触发事件:AuctionCreated(auctionId, nft, startPrice, endTime)
6. 索引模块监听到事件,写入数据库
7. 后端更新Redis缓存:该NFT状态改为「拍卖中」
8. 前端通过WebSocket收到更新,展示拍卖信息

出价流程更刺激。我遇到过一个问题:用户出价时,前端显示的价格和链上实际价格差了0.1个ETH。为什么?因为前端用了缓存数据,没实时查链。

避坑指南:

我曾经在一个项目里,出价逻辑只依赖后端数据库。结果用户A出价后,用户B看到的还是旧价格,直接出价失败。后来我改了:每次出价前,前端先调合约查当前最高价,再让用户确认。虽然多了一步交互,但安全。

// 伪代码:出价流程(安全版)
1. 用户B点击出价,输入金额
2. 前端调合约:getCurrentHighestPrice(auctionId)
3. 前端比较:用户输入 > 当前最高价?
4. 如果是,调合约:bid(auctionId, { value: userInput })
5. 合约校验:出价时间是否在拍卖期内?
6. 合约校验:出价金额是否高于当前最高价 + 最小加价幅度?
7. 合约退回前一个最高出价者的ETH
8. 合约更新最高出价者
9. 触发事件:BidPlaced(auctionId, bidder, amount)
10. 索引模块更新数据库,通知模块推送消息

成交环节相对简单。拍卖结束后,最高出价者调用合约的settleAuction方法,合约自动把NFT转给出价者,把ETH转给卖家。这里有个细节:我建议设置一个「宽限期」。比如拍卖结束后24小时内,出价者可以claim NFT。超过时间,卖家可以取消拍卖。防止有人出价后不付钱。

数据流:链上链下怎么同步

这是架构里最容易被忽视的部分。链上数据是「最终真相」,但查询慢、成本高。链下数据快,但可能过时。怎么平衡?

我的方案是:链上事件驱动 + 链下缓存

  • 所有核心操作(创建、出价、成交)都走合约,触发事件
  • 索引模块监听事件,写入PostgreSQL
  • 前端优先读数据库,关键操作(如出价前)再查链
  • Redis存热点数据:当前最高价、拍卖倒计时

举个例子。用户打开拍卖大厅,看到100个拍卖。如果每个都去查链,得调100次合约,慢得要命。我的做法是:数据库里存一份「拍卖快照」,每5秒刷新一次。用户看到的是缓存数据,但出价时强制查链。这样体验和安全性都兼顾了。

嗯,这一章内容不少。你消化一下。下一章咱们深入拍卖合约的细节,包括时间锁、加价机制、防机器人策略。那些才是真正考验架构能力的地方。