1. 项目背景与核心价值
二手交易平台在数字经济时代已经成为资源循环利用的重要载体。基于SpringBoot的二手交易系统设计,本质上是通过技术手段解决传统二手交易中信息不对称、交易风险高、流程繁琐等痛点。我去年参与过一个校园二手书交易平台的重构项目,发现SpringBoot的快速开发特性与二手交易场景的需求高度契合。
这种系统通常需要处理几个核心问题:商品信息的标准化展示、买卖双方的信用体系构建、交易流程的安全保障以及个性化推荐机制。SpringBoot的自动配置和起步依赖特性,让我们能够快速集成这些功能模块所需的组件,比如Spring Security用于认证授权、Elasticsearch实现商品搜索、Redis处理高并发访问。
2. 系统架构设计
2.1 技术栈选型
后端采用SpringBoot 2.7 + MyBatis-Plus的组合,这个选择基于三个实际考量:
- MyBatis-Plus的代码生成器可以快速产出商品、订单等核心模块的CRUD代码
- 其内置的分页插件完美适配二手商品列表的瀑布流展示需求
- Lambda表达式链式调用让多条件商品查询的代码可读性大幅提升
前端采用Vue3 + Element Plus,这个组合在管理后台开发中特别高效。我们在商品审核模块就利用到了ElTable的多级表头功能,可以同时展示商品基础信息、卖家资质和平台审核状态。
2.2 微服务拆分策略
虽然单体架构也能满足基本需求,但我们还是按功能域做了服务拆分:
- 商品服务:处理商品CRUD、分类管理、搜索推荐
- 交易服务:负责订单创建、支付回调、物流对接
- 用户服务:管理注册登录、信用评级、消息通知
- 风控服务:实现敏感词过滤、交易异常检测
这种拆分在后期进行校园专区功能扩展时显现出优势,可以单独对商品服务进行定制化开发而不影响其他模块。
3. 核心功能实现细节
3.1 商品信息的多维度展示
商品表设计采用了纵表+横表结合的模式:
sql复制-- 商品基础表(横表)
CREATE TABLE item (
id BIGINT PRIMARY KEY,
title VARCHAR(100),
category_id INT,
price DECIMAL(10,2),
...
);
-- 商品属性表(纵表)
CREATE TABLE item_attribute (
item_id BIGINT,
attr_key VARCHAR(50),
attr_value TEXT,
PRIMARY KEY(item_id, attr_key)
);
这种设计让手机、图书等不同类目商品可以灵活扩展属性。在实现商品详情页时,我们通过MyBatis的@MapKey注解将纵表数据转为Java Map,前端直接渲染成属性表格。
3.2 信用评价体系实现
评价模块采用了"交易快照+增量更新"的设计:
java复制// 评价实体
public class Rating {
private Long orderId; // 关联订单
private Integer score; // 1-5星
private String content;
private JSONObject itemSnapshot; // 交易时的商品快照
}
// 用户信用分计算(定时任务)
@Scheduled(cron = "0 0 3 * * ?")
public void calculateUserCredit() {
// 采用加权算法:近期评价权重更高
String sql = "UPDATE user SET credit_score = (" +
"SELECT SUM(score * EXP(-0.1*(DATEDIFF(NOW(),create_time)/7))) " +
"FROM rating WHERE seller_id = user.id)";
jdbcTemplate.update(sql);
}
4. 高并发场景优化
4.1 商品详情页缓存策略
采用多级缓存方案:
- 热点商品使用Redis缓存完整HTML片段
- 普通商品缓存VO对象
- 使用@Cacheable注解实现方法级缓存
特别要注意缓存击穿问题,我们的解决方案:
java复制public ItemDetailVO getItemDetail(Long itemId) {
String cacheKey = "item:" + itemId;
// 1. 先查本地缓存
ItemDetailVO vo = localCache.get(cacheKey);
if (vo != null) return vo;
// 2. 查Redis时使用分布式锁
RLock lock = redissonClient.getLock("lock:" + cacheKey);
try {
lock.lock(3, TimeUnit.SECONDS);
// 双重检查
vo = redisTemplate.opsForValue().get(cacheKey);
if (vo != null) return vo;
// 3. 查数据库
vo = itemMapper.selectDetail(itemId);
if (vo != null) {
redisTemplate.opsForValue().set(cacheKey, vo, 30, TimeUnit.MINUTES);
}
return vo;
} finally {
lock.unlock();
}
}
4.2 订单创建流程设计
采用TCC模式解决分布式事务问题:
- Try阶段:预扣库存、预冻结余额
- Confirm阶段:实际扣减、生成订单
- Cancel阶段:释放预占资源
核心代码结构:
java复制@Transactional
public boolean createOrder(OrderDTO dto) {
// 1. Try阶段
if (!stockService.prepareReduce(dto.getItemId(), dto.getQuantity())) {
throw new BusinessException("库存不足");
}
if (!accountService.prepareDeduct(dto.getUserId(), dto.getTotalAmount())) {
stockService.cancelReduce(dto.getItemId(), dto.getQuantity());
throw new BusinessException("余额不足");
}
// 2. Confirm阶段
try {
Order order = buildOrder(dto);
orderMapper.insert(order);
stockService.commitReduce(dto.getItemId(), dto.getQuantity());
accountService.commitDeduct(dto.getUserId(), dto.getTotalAmount());
return true;
} catch (Exception e) {
// 3. Cancel阶段
stockService.cancelReduce(dto.getItemId(), dto.getQuantity());
accountService.cancelDeduct(dto.getUserId(), dto.getTotalAmount());
throw e;
}
}
5. 安全防护方案
5.1 敏感内容过滤
采用DFA算法实现高效敏感词检测:
java复制public class SensitiveFilter {
private static class TrieNode {
Map<Character, TrieNode> children = new HashMap<>();
boolean isEnd;
}
private final TrieNode root = new TrieNode();
public void addWord(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
node = node.children.computeIfAbsent(c, k -> new TrieNode());
}
node.isEnd = true;
}
public String filter(String text) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < text.length(); ) {
int matchLength = check(text, i);
if (matchLength > 0) {
result.append("***");
i += matchLength;
} else {
result.append(text.charAt(i++));
}
}
return result.toString();
}
private int check(String text, int start) {
TrieNode node = root;
int end = start;
for (int i = start; i < text.length(); i++) {
node = node.children.get(text.charAt(i));
if (node == null) break;
if (node.isEnd) end = i + 1;
}
return end - start;
}
}
5.2 交易风控规则
基于规则引擎实现的多维度风控检查:
- 设备指纹检测:同一设备频繁操作触发验证码
- 行为模式分析:异常浏览路径触发人工审核
- 交易金额监控:大额交易强制延时到账
- 关联账号识别:同一IP多个账号自动标记
6. 运维监控体系
6.1 健康检查端点配置
SpringBoot Actuator的定制化配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
probes:
enabled: true
prometheus:
enabled: true
6.2 日志收集方案
采用ELK栈实现集中式日志管理:
- 使用LogstashLogbackEncoder直接输出JSON格式日志
- 在logback-spring.xml中配置异步Appender
- 通过Kibana创建交易异常监控看板
关键配置示例:
xml复制<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5044</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app":"second-hand","env":"${spring.profiles.active}"}</customFields>
</encoder>
</appender>
7. 踩坑经验分享
-
商品搜索的精度问题:初期直接使用LIKE查询导致性能极差,后来改用Elasticsearch的拼音+同义词插件,搜索体验提升明显。建议在项目初期就引入ES,不要等性能出现问题再加。
-
图片存储的教训:最早用本地存储,扩容时出现各种路径问题。最终改用MinIO对象存储,配合CDN加速,成本只增加了20%但稳定性提升显著。
-
支付对接的注意点:微信支付接口需要配置白名单,测试环境要用ngrok做内网穿透。建议在开发文档中特别标注这些非技术但关键的配置项。
-
缓存一致性的痛:商品修改后出现过缓存未更新的情况。最终解决方案是使用Redis的Pub/Sub机制,任何数据变更都发送通知消息。
-
短信服务的防刷:没有做限制时被恶意调用导致高额费用。后来增加滑动窗口限流(1分钟1条),并在注册环节添加图形验证码。