1. 项目概述与背景
校园二手交易一直是个高频但低效的场景。记得我刚上大学时,想买本二手教材得满校园跑公告栏,而毕业时成堆的专业书又不知如何处理。这种供需不匹配的现象几乎存在于每所高校。传统线下交易方式存在三个痛点:信息不对称导致交易效率低下、缺乏安全保障机制、价格形成过程不透明。
针对这些问题,我设计开发了一套校园在线拍卖系统。与普通二手交易平台不同,我们引入了英式拍卖机制(价格由低到高竞价),配合实时通信技术还原线下拍卖会的紧张氛围。系统采用前后端分离架构,后端使用SpringBoot+MyBatis实现业务逻辑,前端基于Vue3+Element Plus构建交互界面,通过JWT保障接口安全,Redis缓存热点数据,WebSocket实现实时竞价通知。
技术选型思考:选择SpringBoot而非传统SSM框架,主要考虑其自动配置特性可快速集成Redis、WebSocket等组件;Vue3的Composition API相比Options API更利于复杂拍卖页面的状态管理。
2. 核心架构设计
2.1 系统分层架构
系统采用经典的四层架构设计:
code复制[用户层]
浏览器/移动端 → Nginx负载均衡 → CDN静态资源
[应用层]
Vue3前端应用集群 ← WebSocket → SpringBoot应用集群
↑HTTPS↓
[服务层]
Redis缓存集群 ←→ MySQL主从集群
↘ 对象存储OSS ↗
2.2 技术栈深度解析
2.2.1 认证与授权方案
采用JWT+RBAC组合方案:
- 登录成功生成包含用户ID、角色的JWT令牌
- 前端存储于localStorage并附加到请求Header
- 后端通过过滤器验证签名和有效期
- 注解式权限控制(如@PreAuthorize("hasRole('ADMIN')"))
java复制// JWT生成核心代码示例
public String generateToken(Long userId, Integer role) {
return Jwts.builder()
.setSubject(userId.toString())
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
2.2.2 实时竞价实现
竞价流程的技术实现要点:
- 前端建立WebSocket连接时携带JWT
- 服务端维护ConcurrentHashMap保存session信息
- 出价时后端先完成数据库事务
- 通过WebSocket广播价格更新事件
javascript复制// 前端竞价处理逻辑
const handleBid = () => {
if (bidAmount <= currentPrice.value) {
ElMessage.error('出价必须高于当前价格');
return;
}
socket.send(JSON.stringify({
type: 'bid',
auctionId: route.params.id,
amount: bidAmount
}));
// 乐观更新UI
currentPrice.value = bidAmount;
lastBidder.value = userStore.userInfo.nickname;
};
3. 关键业务实现
3.1 拍卖引擎设计
3.1.1 状态机模型
定义拍卖的四种状态:
code复制待开始 → 进行中 → (流拍/成交) → 已结束
使用状态模式封装状态转换逻辑:
java复制public interface AuctionState {
void handleStart(AuctionContext context);
void handleBid(AuctionContext context, BidRequest request);
void handleEnd(AuctionContext context);
}
// 具体状态实现示例:进行中状态
public class OngoingState implements AuctionState {
@Override
public void handleBid(AuctionContext context, BidRequest request) {
// 验证出价有效性
if (request.getAmount() <= context.getCurrentPrice()) {
throw new IllegalStateException("无效出价");
}
// 更新状态
context.setCurrentPrice(request.getAmount());
context.setLeadingBidder(request.getUserId());
// 持久化+通知
context.save();
context.notifyObservers();
}
}
3.1.2 自动出价算法
实现类似eBay的代理竞价机制:
- 用户设置最高出价上限
- 系统自动以最小加价幅度跟进
- 当被超越时立即响应直至达到上限
sql复制-- 竞价记录表设计
CREATE TABLE `bid` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`auction_id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
`is_auto` TINYINT(1) DEFAULT 0,
`create_time` DATETIME NOT NULL,
INDEX `idx_auction` (`auction_id`),
INDEX `idx_user` (`user_id`)
) ENGINE=InnoDB;
3.2 高并发优化策略
3.2.1 缓存设计
采用多级缓存方案:
- 本地缓存(Caffeine):存储商品基础信息
- Redis缓存:
- 使用Hash存储拍卖实时数据
- ZSet维护竞价排行榜
- 分布式锁控制并发出价
java复制// 出价服务加锁示例
public BidResult placeBid(Long auctionId, Long userId, BigDecimal amount) {
String lockKey = "auction:" + auctionId + ":lock";
try {
// 获取分布式锁(Redisson实现)
RLock lock = redissonClient.getLock(lockKey);
if (lock.tryLock(1, 5, TimeUnit.SECONDS)) {
// 业务处理
return doPlaceBid(auctionId, userId, amount);
}
throw new RuntimeException("系统繁忙,请稍后重试");
} finally {
lock.unlock();
}
}
3.2.2 数据库优化
- 读写分离:查询走从库
- 分库分表:按学期拆分历史数据
- 索引优化:
- 联合索引(auction_id, create_time)加速竞价查询
- 覆盖索引避免回表
4. 典型问题解决方案
4.1 竞价时序问题
现象:网络延迟导致出价顺序错乱
解决方案:
- 后端维护单调递增的版本号
- 客户端携带最后已知版本号
- 服务端拒绝旧版本请求
javascript复制// 前端处理竞态条件
let lastVersion = 0;
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.version <= lastVersion) return;
lastVersion = data.version;
updateUI(data);
};
4.2 支付超时处理
场景:中标后24小时未支付
处理流程:
- 创建延迟队列消息(RabbitMQ TTL+死信队列)
- 到期检查支付状态
- 未支付则:
- 订单状态变更为"超时关闭"
- 商品重新上架
- 通知第二高出价者
java复制// 订单状态检查任务
@Scheduled(fixedRate = 60000)
public void checkPaymentTimeout() {
List<Order> unpaidOrders = orderMapper.selectTimeoutOrders();
unpaidOrders.forEach(order -> {
order.setStatus(OrderStatus.TIMEOUT_CLOSED);
orderMapper.updateById(order);
// 触发重新上架逻辑
auctionService.relistAuction(order.getAuctionId());
});
}
5. 部署与监控
5.1 容器化部署方案
使用Docker Compose编排服务:
yaml复制version: '3'
services:
frontend:
image: nginx:alpine
ports: ["80:80"]
volumes:
- ./dist:/usr/share/nginx/html
depends_on: [backend]
backend:
image: openjdk:11-jre
ports: ["8080:8080"]
environment:
- SPRING_PROFILES_ACTIVE=prod
volumes:
- ./application-prod.yml:/config/application.yml
redis:
image: redis:6
ports: ["6379:6379"]
volumes:
- redis_data:/data
mysql:
image: mysql:8
ports: ["3306:3306"]
environment:
- MYSQL_ROOT_PASSWORD=secret
volumes:
- mysql_data:/var/lib/mysql
volumes:
redis_data:
mysql_data:
5.2 监控指标配置
Prometheus监控关键指标:
- 接口响应时间(Histogram)
- WebSocket连接数(Gauge)
- 竞价并发量(Counter)
- Redis内存使用率(Gauge)
yaml复制# SpringBoot Actuator配置示例
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: campus-auction
6. 项目演进方向
- 信用体系构建:引入校园卡认证+交易评分机制
- 智能推荐:基于用户专业和交易历史的商品推荐
- 拍卖形式扩展:支持荷兰式拍卖(降价拍卖)
- 移动端优化:PWA实现离线访问能力
经过三个月的生产运行,系统日均完成交易120+笔,平均竞价次数8.7次/件,最热门的编程教材经过43轮竞价最终以原价65%成交。这个项目让我深刻体会到,好的技术方案必须建立在对业务场景的深度理解之上。