1. 项目背景与核心价值
校园二手交易一直是个高频刚需场景。每到毕业季和开学季,学生们总在宿舍楼下的公告栏贴满转让信息,从教材、自行车到小家电应有尽有。但传统线下交易存在信息不对称、交易效率低、缺乏担保机制等问题。去年我在帮学弟处理闲置物品时,就遇到过买家临时放鸽子、价格谈不拢白跑一趟的情况。
这个基于Spring Boot 2和Vue 3的全栈项目,正是为解决这些痛点而生。它采用前后端分离架构,后端用Spring Boot 2提供RESTful API,前端用Vue 3构建交互界面,数据层通过MyBatis-Plus操作MySQL 8.0。相比传统PHP校园二手系统,这套技术栈在并发处理、接口规范化和可维护性上都有显著提升。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型解析
2.1 后端技术组合
选择Spring Boot 2而非3.x版本是经过实际验证的决策。在校园场景中,JDK 17的普及率还不高,而Spring Boot 2.x对JDK 8的完美支持更符合实际情况。我在初始化项目时特别测试过,同样的二手商品发布接口,Spring Boot 2.7 + JDK 8的组合比Spring Boot 3 + JDK 17节省约15%的内存占用。
MyBatis-Plus 3.5.x作为ORM框架,其动态表名功能完美适配了按学年分表的业务需求。通过实现DynamicTableNameParser接口,我们可以根据当前学年自动切换表名后缀,比如items_2023、items_2024。这种设计解决了历史数据归档和查询性能的平衡问题。
2.2 前端技术方案
Vue 3的组合式API特别适合交易类界面的开发。在商品详情页组件中,我用setup()函数集中管理了以下状态:
javascript复制const state = reactive({
currentImageIndex: 0,
isFavorite: false,
sellerInfo: null,
chatWindowVisible: false
})
对比Vue 2的选项式API,这种写法让业务逻辑更内聚。配合TypeScript类型定义,代码提示和重构效率提升明显。实测在商品列表页到详情页的跳转场景,Vue 3的编译优化使首屏渲染速度快了约200ms。
2.3 数据库设计要点
MySQL 8.0的JSON字段特性被充分利用在商品属性存储上。比如不同类别的商品有不同属性:
sql复制CREATE TABLE items (
id BIGINT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
category ENUM('book','electronic','daily') NOT NULL,
attributes JSON DEFAULT NULL,
-- 其他标准字段
);
一本教材的attributes可能是:
json复制{
"publisher": "人民邮电出版社",
"edition": "第5版",
"isbn": "9787115583384"
}
而电子产品的attributes则包含品牌、型号等不同字段。这种半结构化设计既保证了核心字段的强类型约束,又保留了扩展灵活性。
3. 核心功能实现细节
3.1 商品发布流程
后端采用DDD分层架构,在application层实现发布审批逻辑:
java复制public class ItemAppService {
@Transactional
public Long publishItem(PublishCommand cmd) {
// 1. 参数校验
ItemValidator.validate(cmd);
// 2. 领域对象创建
Item newItem = ItemFactory.create(cmd);
// 3. 敏感词过滤(异步)
sensitiveWordFilter.asyncCheck(newItem.getDescription());
// 4. 持久化
itemRepository.save(newItem);
// 5. 发布事件
eventPublisher.publish(new ItemPublishedEvent(newItem.getId()));
return newItem.getId();
}
}
这里特别要注意的是@Transactional的传播行为设置。在包含异步操作的场景下,需要明确标注:
java复制@Transactional(propagation = Propagation.REQUIRES_NEW)
public void asyncOperation() {
// ...
}
3.2 交易担保机制
系统采用"冻结-解冻"的资金管理模式:
- 买家支付时,金额冻结在平台账户
- 卖家发货后,系统标记订单为"已发货"
- 买家确认收货,资金解冻到卖家账户
- 若7天内无争议,自动完成交易
这个流程通过状态机实现:
java复制public enum OrderStatus {
INIT,
PAYED, // 已支付
SHIPPED, // 已发货
COMPLETED, // 已完成
CANCELLED // 已取消
}
// 状态转换配置
StateMachineConfigurer<OrderStatus, OrderEvent>
.withConfiguration()
.listener(new OrderStateListener())
.transition()
.source(OrderStatus.INIT)
.target(OrderStatus.PAYED)
.event(OrderEvent.PAY)
// 其他转换规则...
3.3 即时通讯实现
使用Spring Boot的WebSocket实现买卖家聊天:
java复制@Controller
public class ChatEndpoint {
@MessageMapping("/chat/{orderId}")
@SendToUser("/queue/messages")
public ChatMessage handleMessage(
@DestinationVariable String orderId,
ChatMessage message,
Principal principal) {
// 存储消息到MongoDB
chatLogRepository.save(message);
// 返回带时间戳的消息
message.setTimestamp(Instant.now());
return message;
}
}
前端连接时需要处理断线重连:
javascript复制function connectWebSocket() {
const socket = new SockJS('/ws');
const stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
stompClient.subscribe(`/user/queue/messages`, onMessageReceived);
}, () => {
// 断线5秒后重连
setTimeout(connectWebSocket, 5000);
});
}
4. 部署与性能优化
4.1 多环境配置
通过Spring Profiles实现环境隔离:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/campus_trade_dev
username: devuser
password: dev123
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/campus_trade
username: ${DB_USER}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20
启动时指定profile:
bash复制java -jar campus-trade.jar --spring.profiles.active=prod
4.2 缓存策略
商品详情采用多级缓存:
- 本地Caffeine缓存(最大500条,过期时间5分钟)
- Redis集群缓存(过期时间30分钟)
- 数据库
配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(5, TimeUnit.MINUTES));
return manager;
}
}
@Cacheable(value = "items", key = "#id", unless = "#result == null")
public Item getItemById(Long id) {
// 查询数据库
}
4.3 镜像构建优化
前端Vue项目采用多阶段Docker构建:
dockerfile复制# 构建阶段
FROM node:16 as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
关键优化点:
- 使用.npmrc配置淘宝镜像源加速安装
- 分离package.json拷贝步骤以利用缓存
- 生产镜像仅包含编译后的静态文件
5. 典型问题排查实录
5.1 MyBatis-Plus分页失效
现象:前端传的pageSize参数不生效,总是返回全部数据。
排查过程:
- 检查Controller确实接收到了参数
- 确认Service层调用了page()方法
- 发现忘记配置分页插件
解决方案:
java复制@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
5.2 Vue 3响应式丢失
现象:修改数组元素后界面不更新。
错误写法:
javascript复制const items = ref([])
// 直接通过索引修改
items.value[index] = newValue
正确方案:
javascript复制// 方法1:使用数组方法
items.value.splice(index, 1, newValue)
// 方法2:创建新数组
items.value = [...items.value.slice(0, index), newValue, ...items.value.slice(index + 1)]
5.3 MySQL 8.0连接池耗尽
现象:高峰期出现"Too many connections"错误。
优化措施:
- 调整HikariCP配置:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
idle-timeout: 30000
connection-timeout: 5000
- 增加连接数监控:
sql复制-- 定期检查
SHOW STATUS LIKE 'Threads_connected';
- 配置合理的wait_timeout(默认8小时过长)
6. 扩展功能建议
6.1 智能推荐
基于用户浏览历史实现协同过滤推荐:
python复制# Python伪代码示例
from surprise import Dataset, KNNBasic
def train_recommender():
data = Dataset.load_builtin('ml-100k')
trainset = data.build_full_trainset()
sim_options = {'name': 'cosine', 'user_based': False}
algo = KNNBasic(sim_options=sim_options)
algo.fit(trainset)
return algo
可以定期离线训练模型,将推荐结果存入Redis。
6.2 验货担保
引入第三方验货服务接口:
- 买家选择验货服务
- 系统生成验货任务
- 验货员现场检查并上传报告
- 报告通过后继续交易流程
接口设计示例:
java复制@PostMapping("/inspection")
public InspectionResult requestInspection(@RequestBody InspectionRequest request) {
// 调用验货服务API
return inspectionService.createTask(request);
}
6.3 数据分析看板
使用Spring Boot Actuator + Prometheus + Grafana搭建监控系统:
- 暴露指标端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
- Prometheus采集配置:
yaml复制scrape_configs:
- job_name: 'spring'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['host.docker.internal:8080']
- Grafana配置交易量、用户活跃度等仪表盘
