1. 项目概述
校园二手交易平台是每个大学生都需要的实用工具。作为一名在校园里摸爬滚打多年的老学长,我深知同学们经常面临这样的困扰:毕业时带不走的专业书籍、只用过几次的健身器材、换新后闲置的电子产品...这些物品扔了可惜,留着又占地方。传统的QQ群、微信群交易方式信息杂乱,缺乏保障,而闲鱼等平台又存在校外人员混杂的问题。
这个基于Spring Boot和Vue的校园闲置物品交易系统,正是为解决这些痛点而生。它采用前后端分离架构,后端使用Spring Boot框架,前端基于Vue.js开发,数据库选用MySQL,是一套专为高校环境设计的轻量级解决方案。
提示:系统已集成支付宝沙箱支付功能,虽然不能真实收款,但完整模拟了线上交易全流程,非常适合学习电商系统开发原理。
2. 技术架构解析
2.1 后端技术栈
Spring Boot 2.7.x作为后端框架,这是我经过多个项目验证的稳定选择。它内置Tomcat服务器,通过简单的starter依赖就能快速搭建RESTful API服务。特别配置了以下核心依赖:
xml复制<dependencies>
<!-- Web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 安全认证 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- 缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
JWT(JSON Web Token)实现认证是经过多方对比后的选择。相比传统的Session认证,JWT更适合前后端分离架构,它的无状态特性让系统更容易扩展。我特别设计了包含用户基础信息和权限的claims结构:
java复制public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("userId", user.getUserId())
.claim("role", user.getRole())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
2.2 前端技术选型
Vue 3 + Element Plus的组合让前端开发效率大幅提升。通过Vue CLI创建的工程结构清晰,配合Vue Router和Vuex,实现了良好的状态管理。特别优化了以下几个点:
- 基于axios的请求拦截器自动添加JWT Token
- 路由守卫实现权限控制
- 商品图片上传采用压缩+CDN方案
- 使用WebSocket实现实时消息通知
javascript复制// axios拦截器示例
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => {
return Promise.reject(error);
});
2.3 数据库设计
MySQL 8.0作为关系型数据库,InnoDB引擎支持事务处理。除了项目描述中的三张核心表外,系统还包含以下辅助表:
- 收藏表(favorites):记录用户收藏商品关系
- 消息表(messages):存储系统通知和私信
- 评价表(reviews):买卖双方互评记录
- 分类表(categories):商品分类信息
特别设计了以下索引提升查询效率:
sql复制-- 商品表的多列索引
CREATE INDEX idx_item_search ON items(title, category, price);
-- 用户表的唯一索引
CREATE UNIQUE INDEX idx_user_email ON users(email);
CREATE UNIQUE INDEX idx_user_phone ON users(phone);
3. 核心功能实现
3.1 用户认证模块
采用JWT+Redis的方案实现安全的认证体系。登录流程如下:
- 用户提交用户名密码
- 后端验证通过后生成JWT
- 将JWT和用户信息存入Redis(设置过期时间)
- 返回JWT给前端存储
- 后续请求通过拦截器验证JWT有效性
重要:Redis中存储的Token设置了双过期时间 - JWT本身的过期时间和Redis的过期时间(比JWT稍长),这样可以实现Token的主动失效。
密码存储采用BCrypt加密,这是目前最推荐的方式:
java复制public class PasswordUtil {
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
public static String encode(String rawPassword) {
return encoder.encode(rawPassword);
}
public static boolean matches(String rawPassword, String encodedPassword) {
return encoder.matches(rawPassword, encodedPassword);
}
}
3.2 商品管理模块
商品发布流程包含几个关键技术点:
- 图片处理:前端使用compressor.js压缩图片,后端使用阿里云OSS存储
- 富文本处理:商品描述使用Quill编辑器,后端做XSS过滤
- 敏感词过滤:接入第三方API实时检测商品标题和描述
商品搜索功能基于Elasticsearch实现,支持:
- 关键字匹配
- 分类筛选
- 价格区间
- 距离排序(需用户授权地理位置)
java复制// 商品搜索服务示例
public Page<Item> searchItems(String keyword, Long categoryId,
BigDecimal minPrice, BigDecimal maxPrice,
Pageable pageable) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
if (StringUtils.hasText(keyword)) {
boolQuery.must(QueryBuilders.multiMatchQuery(keyword, "title", "description"));
}
if (categoryId != null) {
boolQuery.filter(QueryBuilders.termQuery("categoryId", categoryId));
}
if (minPrice != null || maxPrice != null) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
if (minPrice != null) rangeQuery.gte(minPrice);
if (maxPrice != null) rangeQuery.lte(maxPrice);
boolQuery.filter(rangeQuery);
}
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withPageable(pageable)
.build();
return elasticsearchOperations.search(searchQuery, Item.class);
}
3.3 交易流程设计
完整的交易状态机包含以下环节:
- 买家发起购买请求
- 卖家确认订单
- 买家付款(支付宝沙箱)
- 卖家发货(填写物流信息)
- 买家确认收货
- 双方互评
每个状态变更都会触发相应的事件通知:
java复制// 交易状态变更事件处理
@TransactionalEventListener
public void handleTransactionEvent(TransactionEvent event) {
Transaction transaction = event.getTransaction();
User user = transaction.getBuyer();
switch (event.getType()) {
case CREATED:
messageService.sendNotification(user, "订单创建成功");
break;
case PAID:
messageService.sendNotification(user, "支付成功");
break;
case SHIPPED:
messageService.sendNotification(user, "卖家已发货");
break;
case COMPLETED:
messageService.sendNotification(user, "交易完成");
break;
}
}
4. 性能优化实践
4.1 缓存策略
采用多级缓存架构提升系统响应速度:
- 本地缓存:使用Caffeine缓存热点数据(如商品分类)
- 分布式缓存:Redis缓存用户会话和商品详情
- HTTP缓存:通过ETag和Last-Modified头减少重复传输
缓存更新策略特别重要,我采用了以下方案:
- 商品基础信息:缓存30分钟 + 被动失效
- 商品库存:不缓存,实时查询
- 商品详情:编辑时主动清除
java复制@Cacheable(value = "items", key = "#itemId")
public Item getItemById(Long itemId) {
return itemRepository.findById(itemId).orElseThrow();
}
@CacheEvict(value = "items", key = "#itemId")
public void updateItem(Long itemId, ItemUpdateDTO dto) {
// 更新逻辑
}
4.2 数据库优化
针对校园场景的读多写少特点,做了以下优化:
- 主从复制:读写分离
- 慢查询分析:使用pt-query-digest工具
- 连接池调优:HikariCP配置
yaml复制# application.yml配置
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
4.3 前端性能提升
- 代码分割:按路由懒加载组件
- 图片懒加载:Intersection Observer API
- 预加载关键资源:使用
- 服务端渲染(SSR):对SEO要求高的页面
javascript复制// 路由懒加载示例
const routes = [
{
path: '/item/:id',
component: () => import('./views/ItemDetail.vue')
}
]
5. 安全防护措施
5.1 常见攻击防护
-
XSS防护:
- 前端:DOMPurify净化HTML
- 后端:Jackson转义JSON输出
-
CSRF防护:
- 同源策略
- 敏感操作二次验证
-
SQL注入:
- 使用JPA/Hibernate等ORM框架
- 必须用原生SQL时使用参数化查询
java复制// 不安全的写法
@Query("SELECT i FROM Item i WHERE i.title LIKE '%:keyword%'")
List<Item> searchUnsafe(@Param("keyword") String keyword);
// 安全的写法
@Query("SELECT i FROM Item i WHERE i.title LIKE %:keyword%")
List<Item> searchSafe(@Param("keyword") String keyword);
5.2 数据安全
- 敏感数据加密:手机号、邮箱等字段加密存储
- 日志脱敏:避免记录敏感信息
- 数据库备份:每日全备+binlog增量
java复制// 数据脱敏示例
public String maskPhone(String phone) {
if (phone == null || phone.length() < 7) return phone;
return phone.substring(0, 3) + "****" + phone.substring(7);
}
5.3 权限控制
采用RBAC模型设计权限系统:
- 角色:游客、普通用户、管理员、超级管理员
- 权限细粒度到API级别
- 前端菜单动态生成
java复制@PreAuthorize("hasRole('USER') && #userId == authentication.principal.id")
public User getUserProfile(Long userId) {
return userRepository.findById(userId).orElseThrow();
}
6. 部署与运维
6.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: campus_trade
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- redis_data:/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
6.2 CI/CD流程
GitHub Actions自动化部署:
yaml复制name: Backend CI/CD
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Docker Build
run: docker build -t campus-trade-backend .
- name: Deploy to Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker-compose pull backend
docker-compose up -d backend
6.3 监控方案
- Spring Boot Actuator:健康检查
- Prometheus + Grafana:指标监控
- ELK:日志收集分析
yaml复制# Actuator配置
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
7. 常见问题与解决方案
7.1 开发环境问题
问题1:MySQL连接失败
- 检查MySQL服务是否启动
- 确认application.yml中的配置正确
- 测试网络连通性:telnet 127.0.0.1 3306
问题2:前端跨域错误
- 确认后端CORS配置
- 开发环境可配置proxyTable
- 生产环境应通过Nginx反向代理
java复制// CORS配置示例
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
7.2 生产环境问题
问题1:Redis连接超时
- 检查Redis内存使用情况
- 调整连接池配置
- 考虑集群方案
问题2:文件上传失败
- 检查存储空间权限
- 确认前端FormData格式正确
- 限制文件大小(Spring Boot默认1MB)
yaml复制# 文件上传配置
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
7.3 业务逻辑问题
问题1:商品状态不一致
- 引入分布式锁(Redisson)
- 关键操作添加事务注解
- 重要状态变更记录日志
java复制@Transactional
public void purchaseItem(Long itemId, Long userId) {
Item item = itemRepository.findById(itemId)
.orElseThrow(() -> new BusinessException("商品不存在"));
if (item.getStatus() != ItemStatus.AVAILABLE) {
throw new BusinessException("商品不可用");
}
item.setStatus(ItemStatus.SOLD);
itemRepository.save(item);
Transaction transaction = new Transaction();
transaction.setItemId(itemId);
transaction.setBuyerId(userId);
transaction.setSellerId(item.getUserId());
transactionRepository.save(transaction);
}
问题2:支付回调处理
- 验证签名防止伪造请求
- 处理幂等性问题
- 记录完整回调日志
java复制@PostMapping("/pay/callback")
public String handlePayCallback(@RequestBody String body,
HttpServletRequest request) {
// 验证签名
if (!alipayService.verifyCallback(request)) {
throw new BusinessException("非法回调");
}
// 解析回调参数
PayCallbackDTO callback = parseCallback(body);
// 根据业务ID查询订单
Transaction transaction = transactionRepository
.findByOrderId(callback.getOutTradeNo())
.orElseThrow(() -> new BusinessException("订单不存在"));
// 检查订单状态
if (transaction.getStatus() != TransactionStatus.WAITING_PAYMENT) {
return "success"; // 幂等处理
}
// 更新订单状态
transaction.setStatus(TransactionStatus.PAID);
transactionRepository.save(transaction);
// 发送通知
notificationService.sendPaymentSuccess(transaction.getBuyerId());
return "success";
}
8. 项目扩展方向
8.1 功能扩展建议
- 即时通讯:集成WebSocket实现买卖家实时沟通
- 推荐系统:基于用户行为推荐相关商品
- 信用体系:建立用户信用评分机制
- 物流跟踪:对接快递100 API
- 数据分析:商品销售趋势分析
8.2 技术深化方向
- 微服务改造:按功能拆分为用户服务、商品服务、交易服务等
- 分布式事务:引入Seata处理跨服务事务
- 全链路追踪:集成SkyWalking
- 压力测试:使用JMeter模拟高并发场景
- 自动化测试:基于Testcontainers的集成测试
8.3 项目演进思考
在实际部署运营后,有几个关键点需要持续关注:
- 用户增长策略:如何通过校园推广获取首批用户
- 交易安全保障:建立纠纷处理机制
- 系统扩展性:应对毕业季等高峰期的流量冲击
- 商业模式探索:在不影响用户体验的前提下实现可持续运营
这个项目从技术实现到业务运营都有很大的探索空间,后续我计划引入更多真实用户反馈来持续优化系统。对于校园开发者来说,它不仅是一个学习项目,更可以成为创业实践的起点。