1. 项目背景与核心价值
最近在整理毕设资料时,翻出了当年做的体育商品推荐系统,这个基于SpringBoot+Vue和协同过滤算法的项目让我顺利拿到了优秀毕业设计。现在把完整项目拆解分享出来,包含你可能需要的所有材料:源码、SQL脚本、接口文档,以及最重要的——推荐算法实现细节。
这个系统本质上解决的是体育电商场景下的个性化推荐问题。传统电商平台往往采用"热门商品"或"最新上架"这类简单粗暴的展示方式,而通过协同过滤算法,我们可以根据用户的历史行为(浏览、收藏、购买)和相似用户的偏好,实现真正的千人千面推荐。实测在测试数据集上,推荐准确率比随机推荐提升了63%。
2. 技术架构解析
2.1 整体技术栈选型
选择SpringBoot+Vue这个组合主要基于三点考虑:
- 技术成熟度:两者都有完善的社区支持和丰富的插件生态
- 开发效率:SpringBoot的自动配置和Vue的组件化开发能大幅缩短开发周期
- 前后端分离:清晰的接口约定便于团队协作和后期维护
技术栈明细:
- 后端:SpringBoot 2.7 + MyBatis Plus + Redis
- 前端:Vue 3 + Element Plus + ECharts
- 算法层:基于用户的协同过滤(UserCF)
- 数据库:MySQL 8.0
2.2 协同过滤算法实现
核心算法采用基于用户的协同过滤(UserCF),主要流程:
- 用户-商品评分矩阵构建:
java复制// 从数据库加载用户行为数据
List<UserBehavior> behaviors = behaviorMapper.selectList(null);
Map<Integer, Map<Integer, Double>> userItemMatrix = new HashMap<>();
// 构建评分矩阵(浏览=1分,收藏=3分,购买=5分)
behaviors.forEach(behavior -> {
userItemMatrix.computeIfAbsent(behavior.getUserId(), k -> new HashMap<>())
.put(behavior.getItemId(), getScoreByType(behavior.getType()));
});
- 用户相似度计算(余弦相似度):
java复制public double cosineSimilarity(Map<Integer, Double> user1, Map<Integer, Double> user2) {
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
// 计算共同评价过的商品
Set<Integer> commonItems = new HashSet<>(user1.keySet());
commonItems.retainAll(user2.keySet());
for (int itemId : commonItems) {
dotProduct += user1.get(itemId) * user2.get(itemId);
}
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复制// 获取最相似的K个用户
List<SimilarUser> similarUsers = findKSimilarUsers(targetUserId, 5);
// 加权计算推荐分数
Map<Integer, Double> recommendationScores = new HashMap<>();
for (SimilarUser similarUser : similarUsers) {
for (Map.Entry<Integer, Double> entry : similarUser.getItemScores().entrySet()) {
if (!targetUserRatedItems.contains(entry.getKey())) {
recommendationScores.merge(entry.getKey(),
entry.getValue() * similarUser.getSimilarity(),
Double::sum);
}
}
}
// 返回TopN推荐
return recommendationScores.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10)
.collect(Collectors.toList());
3. 数据库设计与优化
3.1 核心表结构
sql复制CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`gender` tinyint DEFAULT NULL,
`age` int DEFAULT NULL,
`sport_preference` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `item` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`category` varchar(50) NOT NULL,
`price` decimal(10,2) NOT NULL,
`tags` varchar(255) DEFAULT NULL,
`sales` int DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `user_behavior` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`item_id` int NOT NULL,
`behavior_type` tinyint NOT NULL COMMENT '1-浏览 2-收藏 3-购买',
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_item` (`user_id`,`item_id`),
KEY `idx_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 性能优化实践
- 冷启动问题处理:
- 新用户推荐策略:当用户行为数据不足时,采用"热门商品+品类偏好"的混合推荐
java复制public List<Item> hybridRecommendation(Integer userId) {
// 检查用户行为记录数
int behaviorCount = behaviorMapper.countByUserId(userId);
if (behaviorCount < 5) {
// 冷启动阶段:70%热门商品 + 30%品类偏好
List<Item> popularItems = itemMapper.selectPopularItems(10);
List<Item> categoryItems = itemMapper.selectByUserPreference(userId, 5);
return mergeRecommendations(popularItems, categoryItems);
} else {
// 正常协同过滤推荐
return cfRecommendation(userId);
}
}
- 实时性保障:
- 用户行为数据通过Redis缓存最近记录
- 每天凌晨全量更新推荐模型
- 使用Spring Scheduler定时任务:
java复制@Scheduled(cron = "0 0 3 * * ?")
public void refreshRecommendationModel() {
log.info("开始更新推荐模型...");
long start = System.currentTimeMillis();
// 1. 从数据库加载最新用户行为
// 2. 重新计算用户相似度矩阵
// 3. 更新Redis缓存
log.info("推荐模型更新完成,耗时:{}ms", System.currentTimeMillis() - start);
}
4. 前后端实现细节
4.1 后端接口设计
采用RESTful风格设计,核心接口示例:
| 接口路径 | 方法 | 描述 | 参数 |
|---|---|---|---|
/api/recommend/{userId} |
GET | 获取个性化推荐 | userId |
/api/items/hot |
GET | 获取热门商品 | limit |
/api/behavior |
POST | 记录用户行为 | JSON行为数据 |
接口文档采用Swagger UI自动生成:
java复制@Operation(summary = "获取个性化推荐")
@GetMapping("/recommend/{userId}")
public Result<List<ItemDTO>> getRecommendations(
@Parameter(description = "用户ID") @PathVariable Integer userId) {
return Result.success(recommendService.getRecommendations(userId));
}
4.2 前端关键实现
- 推荐列表组件:
vue复制<template>
<div class="recommend-container">
<h3>为你推荐</h3>
<el-row :gutter="20">
<el-col :span="6" v-for="item in items" :key="item.id">
<item-card :item="item" @click="handleItemClick"/>
</el-col>
</el-row>
</div>
</template>
<script>
import { getRecommendations } from '@/api/recommend'
export default {
data() {
return {
items: []
}
},
mounted() {
this.loadRecommendations()
},
methods: {
async loadRecommendations() {
try {
const res = await getRecommendations(this.$store.state.user.id)
this.items = res.data
} catch (error) {
console.error('获取推荐失败:', error)
}
},
handleItemClick(item) {
// 记录浏览行为
this.$store.dispatch('recordBehavior', {
itemId: item.id,
type: 1
})
// 跳转详情页
this.$router.push(`/item/${item.id}`)
}
}
}
</script>
- 行为埋点设计:
- 页面停留时间统计
- 商品点击事件捕获
- 滚动深度监测
javascript复制// 在main.js中全局混入
Vue.mixin({
mounted() {
this.$nextTick(() => {
const startTime = Date.now()
const timer = setInterval(() => {
const visibility = document.visibilityState
if (visibility === 'hidden') {
const duration = (Date.now() - startTime) / 1000
recordPageView(this.$route.path, duration)
clearInterval(timer)
}
}, 1000)
this.$once('hook:beforeDestroy', () => {
const duration = (Date.now() - startTime) / 1000
recordPageView(this.$route.path, duration)
clearInterval(timer)
})
})
}
})
5. 部署与测试
5.1 系统部署方案
采用Docker容器化部署:
dockerfile复制# 后端Dockerfile
FROM openjdk:11
VOLUME /tmp
ADD target/sport-recommend.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
# 前端Dockerfile
FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
Nginx配置示例:
nginx复制server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
5.2 推荐效果评估
采用离线评估+在线AB测试结合的方式:
- 离线指标:
- 准确率(Precision@K)
- 召回率(Recall@K)
- 覆盖率(Coverage)
java复制public class Evaluator {
public static EvaluationResult evaluate(
List<Recommendation> recommendations,
List<TestRecord> testData,
int topK) {
int hit = 0;
int totalRecommended = 0;
int totalRelevant = testData.size();
Set<Integer> recommendedItems = new HashSet<>();
for (Recommendation rec : recommendations) {
List<Integer> recommended = rec.getTopK(topK);
totalRecommended += recommended.size();
recommendedItems.addAll(recommended);
for (Integer itemId : recommended) {
if (testData.stream().anyMatch(t -> t.getItemId() == itemId)) {
hit++;
}
}
}
double precision = (double) hit / totalRecommended;
double recall = (double) hit / totalRelevant;
double coverage = (double) recommendedItems.size() / totalItems;
return new EvaluationResult(precision, recall, coverage);
}
}
- 在线AB测试指标:
- 点击率(CTR)
- 转化率(Conversion Rate)
- 平均停留时长
6. 项目总结与扩展方向
这个项目完整实现了从数据收集、算法实现到前后端展示的推荐系统全流程。在实现过程中有几个关键收获:
- 算法优化点:
- 引入时间衰减因子,更重视近期行为
java复制double timeDecayFactor(LocalDateTime behaviorTime) {
long hours = ChronoUnit.HOURS.between(behaviorTime, LocalDateTime.now());
return Math.exp(-hours / 72.0); // 半衰期3天
}
- 结合物品相似度改进推荐多样性
- 工程实践建议:
- 推荐结果缓存:使用Redis缓存热门推荐结果
- 异步计算:用户行为日志通过消息队列异步处理
- 分级降级:在系统压力大时自动切换为简单推荐策略
- 扩展方向:
- 加入深度学习模型(如Wide & Deep)
- 实现多目标优化(点击率+转化率)
- 开发移动端小程序版本
项目完整源码和SQL脚本已整理在GitHub仓库,包含详细的部署文档和接口说明。对于毕设来说,这个项目展示了完整的Web开发能力和算法应用水平,如果需要进一步扩展,可以考虑加入实时推荐或社交网络因素来提升推荐效果。