1. 项目背景与核心价值
社区生鲜电商平台是近年来随着消费升级和冷链物流发展而兴起的新型零售模式。传统菜市场存在营业时间固定、商品信息不透明、购物体验差等问题,而大型电商平台又难以满足消费者对生鲜产品即时性的需求。这个毕业设计项目正是瞄准了这一市场空白,通过Java技术栈构建了一个融合智能推荐功能的社区化生鲜交易平台。
我在实际开发中发现,这类平台有三个关键痛点需要解决:一是如何实现生鲜商品的精准推荐,避免用户在海量商品中盲目选择;二是如何保证订单处理的实时性,特别是对于需要即时配送的生鲜商品;三是如何设计合理的库存预警机制,防止生鲜商品因过期造成损耗。本系统通过协同过滤算法、SpringBoot的异步处理机制以及Redis缓存等技术方案,较好地解决了这些问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体技术选型
系统采用经典的三层架构设计:
- 前端:Vue.js + ElementUI(考虑到毕业设计的时间成本,也可以选择Thymeleaf模板引擎)
- 后端:SpringBoot 2.7 + MyBatis-Plus
- 数据库:MySQL 8.0
- 缓存:Redis 6.x
- 消息队列:RabbitMQ(用于异步处理订单和库存变更)
提示:在实际部署时,建议将图片等静态资源存储在OSS对象存储服务中,而不是直接放在服务器本地,这能显著提高页面加载速度。
2.2 协同过滤算法实现
协同过滤是本项目的核心技术亮点,我们采用基于用户的协同过滤(UserCF)算法来实现商品推荐。核心实现步骤如下:
- 构建用户-商品评分矩阵:
java复制// 从数据库获取用户行为数据
List<UserBehavior> behaviors = userBehaviorMapper.selectList(
new QueryWrapper<UserBehavior>()
.select("user_id", "product_id", "behavior_type", "create_time")
);
// 构建评分矩阵 Map<userId, Map<productId, score>>
Map<Long, Map<Long, Double>> userProductMatrix = new HashMap<>();
behaviors.forEach(behavior -> {
double score = calculateScore(behavior.getBehaviorType());
userProductMatrix.computeIfAbsent(behavior.getUserId(), k -> new HashMap<>())
.put(behavior.getProductId(), score);
});
- 计算用户相似度(余弦相似度):
java复制public double cosineSimilarity(Map<Long, Double> user1, Map<Long, Double> user2) {
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
// 计算两个用户共同评价过的商品
Set<Long> commonProducts = new HashSet<>(user1.keySet());
commonProducts.retainAll(user2.keySet());
for (Long productId : commonProducts) {
dotProduct += user1.get(productId) * user2.get(productId);
}
for (double score : user1.values()) {
norm1 += Math.pow(score, 2);
}
for (double score : user2.values()) {
norm2 += Math.pow(score, 2);
}
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
- 生成推荐结果:
java复制public List<Long> recommendProducts(Long userId, int topN) {
// 1. 找到最相似的K个用户
Map<Long, Double> similarityMap = new HashMap<>();
Map<Long, Double> currentUser = userProductMatrix.get(userId);
userProductMatrix.forEach((otherUserId, otherUserVector) -> {
if (!otherUserId.equals(userId)) {
double similarity = cosineSimilarity(currentUser, otherUserVector);
similarityMap.put(otherUserId, similarity);
}
});
// 2. 按相似度排序取前K个用户
List<Long> similarUsers = similarityMap.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(TOP_K)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// 3. 聚合这些用户喜欢的商品(排除当前用户已购买)
Map<Long, Double> recommendProducts = new HashMap<>();
similarUsers.forEach(similarUser -> {
userProductMatrix.get(similarUser).forEach((productId, score) -> {
if (!currentUser.containsKey(productId)) {
recommendProducts.merge(productId, score, Double::sum);
}
});
});
// 4. 返回推荐得分最高的topN个商品
return recommendProducts.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
2.3 数据库设计要点
生鲜电商平台的数据库设计有几个特殊考虑:
- 商品表需要记录保质期和库存预警阈值:
sql复制CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '商品名称',
`category_id` bigint NOT NULL COMMENT '分类ID',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`origin_price` decimal(10,2) DEFAULT NULL COMMENT '原价',
`stock` int NOT NULL DEFAULT '0' COMMENT '库存',
`alert_stock` int DEFAULT '10' COMMENT '库存预警值',
`shelf_life` int DEFAULT NULL COMMENT '保质期(小时)',
`produce_date` datetime DEFAULT NULL COMMENT '生产日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 订单表需要特别关注状态流转:
sql复制CREATE TABLE `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '订单编号',
`user_id` bigint NOT NULL,
`total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
`pay_amount` decimal(10,2) NOT NULL COMMENT '实付金额',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0未支付 1已支付 2已发货 3已完成 4已取消',
`payment_time` datetime DEFAULT NULL COMMENT '支付时间',
`delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
`complete_time` datetime DEFAULT NULL COMMENT '完成时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现细节
3.1 生鲜商品智能推荐
推荐功能的完整调用链路如下:
- 前端请求推荐接口:
javascript复制// 在首页组件加载时调用推荐接口
created() {
this.$axios.get('/recommend/products', {
params: {
topN: 6
}
}).then(response => {
this.recommendProducts = response.data
})
}
- 后端Controller处理:
java复制@GetMapping("/recommend/products")
public Result<List<ProductVO>> recommendProducts(
@RequestParam(defaultValue = "6") Integer topN,
HttpServletRequest request) {
Long userId = JwtUtil.getUserId(request);
List<Long> productIds = recommendService.recommendProducts(userId, topN);
if (productIds.isEmpty()) {
// 冷启动问题处理:当用户行为数据不足时,返回热销商品
productIds = productService.getHotProducts(topN);
}
List<ProductVO> products = productService.listByIds(productIds)
.stream()
.map(product -> convertToVO(product))
.collect(Collectors.toList());
return Result.success(products);
}
3.2 订单超时自动取消
生鲜商品的时效性要求订单必须在规定时间内支付,我们通过RabbitMQ的延迟队列实现:
- 订单创建时发送延迟消息:
java复制public void createOrder(OrderCreateDTO dto) {
// 1. 创建订单
Order order = new Order();
// ...设置订单属性
orderMapper.insert(order);
// 2. 发送延迟消息(30分钟未支付自动取消)
rabbitTemplate.convertAndSend(
"order.delay.exchange",
"order.delay.routingKey",
order.getId(),
message -> {
message.getMessageProperties().setDelay(30 * 60 * 1000); // 30分钟
return message;
}
);
}
- 消费端处理:
java复制@RabbitListener(queues = "order.cancel.queue")
public void handleOrderCancel(Long orderId) {
Order order = orderMapper.selectById(orderId);
if (order != null && order.getStatus() == 0) { // 还是未支付状态
order.setStatus(4); // 已取消
orderMapper.updateById(order);
// 恢复库存
List<OrderItem> items = orderItemMapper.selectList(
new QueryWrapper<OrderItem>().eq("order_id", orderId)
);
items.forEach(item -> {
productMapper.updateStock(item.getProductId(), item.getQuantity());
});
}
}
3.3 库存实时更新与预警
生鲜商品对库存准确性要求极高,我们采用Redis+Lua脚本保证原子性:
java复制public boolean reduceStock(Long productId, Integer quantity) {
String script =
"local stock = tonumber(redis.call('get', KEYS[1])) " +
"if stock >= tonumber(ARGV[1]) then " +
" redis.call('decrby', KEYS[1], ARGV[1]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList("product:stock:" + productId),
quantity.toString()
);
if (result == 1) {
// 异步更新数据库
asyncTask.updateStockInDB(productId, quantity);
return true;
}
return false;
}
库存预警通过定时任务检查:
java复制@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
public void checkStockAlert() {
List<Product> products = productMapper.selectList(
new QueryWrapper<Product>()
.select("id", "name", "stock", "alert_stock")
.lt("stock", "alert_stock")
);
if (!products.isEmpty()) {
String message = products.stream()
.map(p -> p.getName() + "库存仅剩" + p.getStock() + "件")
.collect(Collectors.joining("\n"));
// 发送预警通知(邮件/短信)
notificationService.sendAlertToAdmin("库存预警", message);
}
}
4. 部署与性能优化
4.1 系统部署方案
推荐使用Docker Compose进行服务编排,docker-compose.yml示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: fresh
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- ./redis/data:/data
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: admin
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
- rabbitmq
environment:
SPRING_PROFILES_ACTIVE: prod
4.2 性能优化实践
- 接口响应优化:
- 启用SpringBoot的Gzip压缩:
properties复制server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
server.compression.min-response-size=1024
- 添加缓存注解:
java复制@Cacheable(value = "products", key = "#productId")
public Product getProductById(Long productId) {
return productMapper.selectById(productId);
}
@CacheEvict(value = "products", key = "#productId")
public void updateProduct(Product product) {
productMapper.updateById(product);
}
- 数据库优化:
- 为常用查询字段添加索引:
sql复制ALTER TABLE `order_item` ADD INDEX `idx_product_id` (`product_id`);
ALTER TABLE `user_behavior` ADD INDEX `idx_user_product` (`user_id`, `product_id`);
- 配置连接池参数:
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.connection-timeout=2000
5. 常见问题与解决方案
5.1 冷启动问题
问题描述:新用户或新商品缺乏足够的行为数据,导致推荐效果差。
解决方案:
- 混合推荐策略:当用户行为数据不足时,采用基于热销商品、新品上架等规则的推荐
java复制public List<Long> getHybridRecommendations(Long userId, int topN) {
// 尝试协同过滤推荐
List<Long> cfRecommendations = recommendProducts(userId, topN);
if (cfRecommendations.size() >= topN) {
return cfRecommendations;
}
// 不足部分用热销商品补足
int remaining = topN - cfRecommendations.size();
List<Long> hotProducts = productService.getHotProducts(remaining);
// 合并结果(去重)
Set<Long> result = new LinkedHashSet<>(cfRecommendations);
result.addAll(hotProducts);
return new ArrayList<>(result).subList(0, topN);
}
- 收集显式反馈:在新用户注册时,通过问卷调查了解其饮食偏好
5.2 生鲜商品保质期管理
问题描述:生鲜商品易变质,需要特殊处理临近过期的商品。
解决方案:
- 定时扫描即将过期商品:
java复制@Scheduled(cron = "0 0 6,18 * * ?") // 每天6点和18点执行
public void handleExpiringProducts() {
LocalDateTime now = LocalDateTime.now();
List<Product> expiringProducts = productMapper.selectList(
new QueryWrapper<Product>()
.apply("produce_date + INTERVAL shelf_life HOUR <= {0}", now.plusHours(12))
.eq("status", 1) // 上架状态
);
expiringProducts.forEach(product -> {
// 1. 自动打折
BigDecimal discountPrice = product.getPrice().multiply(new BigDecimal("0.7"));
product.setPrice(discountPrice);
productMapper.updateById(product);
// 2. 加入促销专区
promotionService.addToPromotion(product.getId(), "临期特惠");
});
}
- 过期自动下架:
java复制@Scheduled(cron = "0 0 * * * ?") // 每小时执行一次
public void autoOffExpiredProducts() {
productMapper.update(
new Product().setStatus(0), // 下架
new UpdateWrapper<Product>()
.apply("produce_date + INTERVAL shelf_life HOUR <= NOW()")
.eq("status", 1)
);
}
5.3 高并发下单问题
问题描述:秒杀活动时可能出现超卖。
解决方案:
- Redis分布式锁:
java复制public boolean tryLock(String key, long expireTime) {
String value = UUID.randomUUID().toString();
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
return Boolean.TRUE.equals(result);
}
public void unlock(String key) {
redisTemplate.delete(key);
}
- 乐观锁更新库存:
java复制public boolean reduceStockWithOptimisticLock(Long productId, Integer quantity) {
Product product = productMapper.selectById(productId);
if (product.getStock() < quantity) {
return false;
}
int rows = productMapper.update(
null,
new UpdateWrapper<Product>()
.setSql("stock = stock - " + quantity)
.eq("id", productId)
.ge("stock", quantity)
.eq("version", product.getVersion())
);
return rows > 0;
}
6. 项目扩展方向
在实际开发中,这个基础平台还可以进一步扩展:
- 配送路线优化:集成地图API,根据实时订单分布计算最优配送路径
java复制public List<DeliveryRoute> calculateRoutes(List<Order> orders) {
// 使用Dijkstra算法或调用第三方地图API
// ...
}
- 智能补货预测:基于历史销售数据和天气等因素预测未来需求
python复制# 可以用Python实现后通过gRPC调用
def predict_demand(sales_history, weather_data):
# 使用时间序列模型(如ARIMA或LSTM)
# ...
-
区块链溯源:将生鲜商品的生产、运输信息上链,增强可信度
-
社交化功能:增加用户间的菜谱分享、拼团购物等社交电商功能
这个项目完整实现了社区生鲜电商的核心业务流程,特别是在个性化推荐和库存管理方面有深入实践。我在开发过程中最大的体会是:生鲜电商系统不仅要考虑常规的电商功能,更要特别关注商品的时效性和库存准确性,这需要合理的技术方案和严谨的业务流程设计来保证。
