1. 项目概述
二手交易平台是近年来快速发展的互联网应用类型,它解决了个人闲置物品流通的痛点需求。基于SpringBoot框架开发的二手交易系统,能够充分利用Java生态的成熟技术栈,快速构建高可用的在线交易平台。
我去年参与开发了一个校园二手交易系统,从技术选型到上线运维全程跟进。这个项目让我深刻体会到SpringBoot在快速开发中的优势,也积累了不少实战经验。本文将分享从系统设计到具体实现的完整方案,包含那些教科书上不会写的"踩坑"记录。
2. 系统架构设计
2.1 整体技术栈选型
核心采用SpringBoot 2.7.x + MyBatis-Plus组合,这是经过多个项目验证的黄金搭配。SpringBoot的自动配置特性让项目初始化变得极其简单,而MyBatis-Plus的ActiveRecord模式大幅简化了数据库操作。
前端选用Vue 3 + Element Plus,通过RESTful API与后端交互。这里有个经验之谈:前后端分离时,Swagger文档一定要同步开发,我们项目初期没重视这个,导致前后端联调时浪费了大量时间。
数据库使用MySQL 8.0,考虑到二手商品的非结构化数据特点,同时引入了MongoDB存储商品详情中的富文本和图片信息。这种混合存储方案在实践中表现很稳定。
2.2 微服务化考量
虽然SpringCloud可以实现完善的微服务架构,但对于中小型二手平台,我的建议是:
- 初期采用单体架构,使用package分层
- 将支付、消息等明确独立的功能模块拆分为子模块
- 预留FeignClient接口,为后期服务拆分做准备
我们项目就吃过过度设计的亏,一开始就上SpringCloud,结果服务器成本增加了40%,而实际并发量前半年都没超过1000QPS。
2.3 安全设计要点
二手平台要特别注意的安全环节:
- 用户密码必须BCrypt加密
- 商品发布需内容审核(我们接入了阿里云内容安全API)
- 支付环节要双重验证
- 敏感操作需要日志审计
3. 核心功能实现
3.1 商品模块设计
商品表的核心字段设计:
public class Item { private Long id; private String title; // 商品标题 private Integer categoryId; // 分类ID private BigDecimal price; // 价格 private Integer status; // 状态:1-在售 2-已售 3-下架 private Long sellerId; // 卖家ID private LocalDateTime createTime; private LocalDateTime updateTime; // 省略getter/setter }特别注意:价格字段一定要用BigDecimal,我们最早用Double类型,结果出现了0.1+0.2=0.30000000000000004的经典精度问题。
3.2 搜索功能实现
采用Elasticsearch构建搜索服务,关键点:
- 建立商品索引时,title字段要用ik_smart分词器
- 价格区间过滤要用range查询
- 搜索结果需要高亮显示
一个性能优化技巧:对热门分类的商品索引单独建立分片,比如"手机"类商品的查询频率是"家具"类的20倍,我们就给手机类分配了更多的资源。
3.3 交易流程实现
交易状态机设计:
待支付 -> 已支付 -> 待发货 -> 已发货 -> 已完成 -> 已取消关键代码示例:
@Transactional public boolean createOrder(OrderDTO dto) { // 1. 校验商品状态 Item item = itemService.getById(dto.getItemId()); if(item.getStatus() != 1) { throw new BusinessException("商品已下架"); } // 2. 扣减库存(乐观锁) boolean success = itemService.update() .setSql("stock = stock - 1") .eq("id", item.getId()) .gt("stock", 0) .update(); if(!success) { throw new BusinessException("库存不足"); } // 3. 创建订单 Order order = new Order(); // 省略属性设置 return orderService.save(order); }4. 性能优化实践
4.1 缓存策略
采用多级缓存方案:
- 本地缓存(Caffeine):缓存用户基础信息,TTL 5分钟
- Redis缓存:
- 商品详情:TTL 1小时
- 分类列表:永不过期
- 热门商品:ZSET结构,按点击量排序
重要经验:缓存雪崩防护一定要做,我们在一次大促时所有商品缓存同时失效,导致数据库瞬间被打满。后来改为基础TTL+随机偏移量的方式解决。
4.2 数据库优化
- 索引优化:为查询条件建立组合索引,比如(status, category_id)
- 读写分离:使用Sharding-JDBC实现
- 大表分片:用户交易记录按月分表
一个真实案例:商品表最初没有对status字段建索引,当平台有10万商品时,查询"在售商品"需要全表扫描,响应时间超过2秒。加上索引后降到50ms以内。
5. 部署与监控
5.1 容器化部署
使用Docker Compose编排服务:
version: '3' services: app: image: openjdk:11-jre ports: - "8080:8080" volumes: - ./app.jar:/app.jar command: java -jar /app.jar depends_on: - redis - mysql redis: image: redis:6 ports: - "6379:6379" mysql: image: mysql:8 environment: MYSQL_ROOT_PASSWORD: 123456 ports: - "3306:3306"5.2 监控方案
- SpringBoot Actuator暴露健康检查
- Prometheus收集指标
- Grafana展示监控数据
- ELK收集日志
特别提醒:一定要监控慢SQL,我们通过Arthas发现一个N+1查询问题,某个页面竟然产生了200+条SQL,优化后降到3条。
6. 典型问题排查
6.1 事务失效场景
我们遇到过的事务坑:
- 同类方法内调用:A方法调用同类B方法,B方法上的@Transactional失效
- 解决:将B方法移到另一个Service
- 异常被捕获:try-catch吞掉了异常
- 解决:catch中throw new RuntimeException(e)
6.2 循环依赖问题
当UserService依赖OrderService,同时OrderService又依赖UserService时,Spring启动会报错。我们的解决方案:
- 使用@Lazy延迟加载
- 重构代码,提取公共逻辑到第三个Service
6.3 高并发场景下的超卖问题
采用Redis分布式锁+数据库乐观锁双重保障:
public boolean purchase(Long itemId) { String lockKey = "item:" + itemId; // 获取分布式锁 boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); if(!locked) { throw new BusinessException("操作太频繁"); } try { // 乐观锁更新 int updated = itemMapper.updateStock(itemId); return updated > 0; } finally { // 释放锁 redisTemplate.delete(lockKey); } }7. 扩展功能建议
7.1 推荐系统
基于用户行为实现简单的推荐:
- 协同过滤:购买过A商品的用户也买了B
- 内容相似:同分类下的相似商品
- 实时热门:最近1小时销量Top
7.2 消息推送
集成WebSocket实现:
- 订单状态变更通知
- 买家留言提醒
- 系统公告推送
实现要点:需要处理连接保活和断线重连,我们用的是Spring的STOMP协议支持。
7.3 物流跟踪
对接第三方物流API的注意事项:
- 异步查询结果
- 合理设置查询间隔(快递公司一般限制频率)
- 缓存查询结果
8. 项目演进思考
从单体到微服务的演进路线建议:
- 先按功能拆分模块
- 将支付、搜索等独立功能抽离为服务
- 引入API网关
- 逐步迁移其他模块
技术债务管理经验:我们专门建立了技术债务看板,将已知问题按优先级排序,每个迭代固定分配20%资源来处理。