SpringBoot+Vue美食分享平台实战:集成协同过滤推荐算法
在开发一个具备个性化推荐功能的美食分享平台时很多同学会面临前后端分离架构整合、推荐算法落地以及项目工程化部署的难题。网上资料往往只讲理论或只给片段代码难以形成完整的、可运行的项目闭环。本文将手把手带你从零构建一个基于 SpringBoot Vue 的美食分享平台并集成协同过滤推荐算法。文章不仅提供前后端全栈代码更会详细解释每一步的设计思路和配置原理确保你不仅能跑通项目更能理解其背后的技术选型与实现逻辑。无论是用于个人技术提升、课程设计还是毕业设计这都将是一个功能完整、页面美观、可直接复用的优质项目。1. 项目背景与核心价值1.1 什么是美食分享平台美食分享平台是一个典型的 Web 应用它允许用户注册登录发布自己制作或品尝过的美食图文浏览其他用户的分享并对喜欢的内容进行点赞、收藏和评论。其核心价值在于构建一个以“食”会友的社区通过内容聚合与用户互动形成高质量的美食内容沉淀。1.2 为什么需要个性化推荐在一个内容社区中随着用户和内容的增长信息过载问题会日益凸显。如果所有用户看到的内容都一样新用户可能找不到兴趣点老用户也会逐渐失去浏览动力。协同过滤算法正是解决这一问题的经典方案。它通过分析用户的历史行为数据如点赞、收藏发现用户之间的相似性或物品美食分享之间的相似性从而向目标用户推荐其可能感兴趣但尚未接触过的内容。集成该算法能使平台从“人找内容”升级为“内容找人”极大提升用户粘性和活跃度。1.3 技术选型SpringBoot Vue后端 (SpringBoot)作为当前 Java 领域最主流的微服务框架SpringBoot 提供了极简的配置和快速开发能力。它能轻松整合 MyBatis-Plus数据访问、Spring Security安全控制、Redis缓存与Session管理等组件为后端 API 开发提供稳定、高效的基础。前端 (Vue 3 Element Plus)Vue 是一套渐进式 JavaScript 框架以其轻量、易学和强大的响应式系统著称。Vue 3 的组合式 API 让代码组织更灵活。配合 Element Plus 组件库可以快速搭建出美观、交互一致的管理后台和用户界面。前后端分离架构该架构下前端与后端通过 RESTful API 进行通信职责清晰。前端专注于页面渲染和用户交互后端专注于业务逻辑和数据处理。这种模式有利于团队并行开发、独立部署和后期维护。2. 环境准备与项目结构2.1 开发环境清单在开始编码前请确保你的开发环境已就绪。以下版本为本文示例环境你可以根据实际情况调整。工具/环境推荐版本说明操作系统Windows 10/11, macOS, Linux均可本文以 Windows 为例JDK1.8 或 11SpringBoot 2.x 兼容性较好Node.js16.x 或 18.xVue 开发运行环境Maven3.6Java 项目构建与依赖管理IDEIntelliJ IDEA后端开发社区版即可IDEVS Code前端开发数据库MySQL 5.7 / 8.0主数据存储缓存Redis 6.x存储会话、缓存热门数据版本控制Git代码管理2.2 项目整体结构项目采用标准的前后端分离目录结构清晰明了。food-sharing-platform/ ├── food-server/ # SpringBoot 后端项目 │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/food/platform/ │ │ │ │ ├── config/ # 配置类安全、Redis、跨域等 │ │ │ │ ├── controller/ # 控制器接收请求 │ │ │ │ ├── entity/ # 实体类对应数据库表 │ │ │ │ ├── mapper/ # MyBatis-Plus Mapper 接口 │ │ │ │ ├── service/ # 业务逻辑层接口及实现 │ │ │ │ │ └── impl/ │ │ │ │ ├── dto/ # 数据传输对象 │ │ │ │ └── FoodSharingPlatformApplication.java # 启动类 │ │ │ └── resources/ │ │ │ ├── application.yml # 主配置文件 │ │ │ └── mapper/ # MyBatis XML 文件如需 │ │ └── test/ │ └── pom.xml # Maven 依赖管理文件 └── food-web/ # Vue 3 前端项目 ├── public/ ├── src/ │ ├── api/ # 封装所有后端 API 请求 │ ├── assets/ # 静态资源 │ ├── components/ # 可复用组件 │ ├── router/ # Vue Router 路由配置 │ ├── store/ # Pinia/Vuex 状态管理 │ ├── utils/ # 工具函数 │ ├── views/ # 页面组件 │ ├── App.vue │ └── main.js ├── package.json └── vite.config.js # 构建配置如使用 Vite3. 后端核心实现SpringBoot 搭建3.1 初始化项目与依赖配置使用 Spring Initializr或 IDEA 内置功能创建项目选择以下依赖Spring Web,Lombok,MyBatis Framework,MySQL Driver,Redis。创建后手动在pom.xml中添加 MyBatis-Plus 和 Hutool 工具包依赖。!-- pom.xml 关键依赖补充 -- dependencies !-- ... 其他 Spring Initializr 生成的依赖 ... -- !-- MyBatis-Plus 增强 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency !-- Hutool 工具类 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.20/version /dependency !-- JWT 用于 Token 认证 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency /dependencies3.2 数据库设计与实体类创建根据业务需求设计核心表。这里以用户表、美食分享表、点赞记录表为例。-- 用户表 (user) CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码加密后, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(500) DEFAULT NULL COMMENT 头像URL, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 美食分享表 (food_post) CREATE TABLE food_post ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, user_id bigint NOT NULL COMMENT 发布者ID, title varchar(100) NOT NULL COMMENT 标题, content text COMMENT 详细内容, images json DEFAULT NULL COMMENT 图片URL数组, likes int DEFAULT 0 COMMENT 点赞数, collects int DEFAULT 0 COMMENT 收藏数, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT美食分享表; -- 用户行为表 (user_action) - 用于协同过滤 CREATE TABLE user_action ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 用户ID, post_id bigint NOT NULL COMMENT 分享ID, action_type tinyint NOT NULL COMMENT 行为类型1点赞 2收藏, action_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 行为时间, PRIMARY KEY (id), UNIQUE KEY uk_user_post_action (user_id,post_id,action_type), KEY idx_post_id (post_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户行为记录表;在 Java 中创建对应的实体类使用 Lombok 简化代码。// 文件路径food-server/src/main/java/com/food/platform/entity/User.java package com.food.platform.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String username; private String password; private String nickname; private String avatar; private LocalDateTime createTime; }3.3 核心业务逻辑用户认证与内容管理实现用户注册登录JWT Token、美食分享的增删改查、点赞收藏等基础功能。这里展示用户登录和发布美食的核心代码。1. 用户登录 Service 实现// 文件路径food-server/src/main/java/com/food/platform/service/impl/UserServiceImpl.java package com.food.platform.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.food.platform.entity.User; import com.food.platform.mapper.UserMapper; import com.food.platform.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.food.platform.utils.JwtUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; Slf4j Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { Autowired private JwtUtil jwtUtil; Override public String login(String username, String password) { // 1. 查询用户 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, username); User user this.getOne(wrapper); if (user null) { throw new RuntimeException(用户名或密码错误); } // 2. 验证密码 (示例使用MD5生产环境请用BCrypt) String encryptedPwd DigestUtils.md5DigestAsHex(password.getBytes()); if (!encryptedPwd.equals(user.getPassword())) { throw new RuntimeException(用户名或密码错误); } // 3. 生成JWT Token String token jwtUtil.generateToken(user.getId(), username); log.info(用户 {} 登录成功Token已生成, username); return token; } }2. 发布美食分享 Controller// 文件路径food-server/src/main/java/com/food/platform/controller/FoodPostController.java package com.food.platform.controller; import com.food.platform.common.Result; import com.food.platform.entity.FoodPost; import com.food.platform.service.FoodPostService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; RestController RequestMapping(/api/post) public class FoodPostController { Autowired private FoodPostService foodPostService; PostMapping(/create) public ResultFoodPost createPost(RequestBody FoodPost post, HttpServletRequest request) { // 从请求属性中获取当前登录用户ID由JWT拦截器设置 Long userId (Long) request.getAttribute(userId); post.setUserId(userId); boolean saved foodPostService.save(post); if (saved) { return Result.success(post); } else { return Result.error(发布失败); } } GetMapping(/list) public Result? listPosts(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { // 分页查询美食分享列表可按时间、热度排序 return Result.success(foodPostService.getPostPage(pageNum, pageSize)); } }4. 算法核心协同过滤推荐实现4.1 协同过滤算法原理简述协同过滤主要分为两类基于用户的协同过滤UserCF找到与目标用户兴趣相似的其他用户将这些用户喜欢而目标用户未看过的物品推荐给他。核心是计算用户相似度。基于物品的协同过滤ItemCF找到与目标用户历史喜欢物品相似的其他物品将这些物品推荐给用户。核心是计算物品相似度。本项目采用基于物品的协同过滤ItemCF因为它更稳定物品相似度变化不频繁可离线计算适合美食分享这种物品帖子数量相对稳定的场景。计算步骤构建用户-物品行为矩阵矩阵的行代表用户列代表物品美食分享值代表用户对物品的行为权重如点赞1收藏2。计算物品相似度通常使用余弦相似度或改进的余弦相似度计算每两个物品之间的相似度。生成推荐列表对于目标用户找出他有过正反馈的物品集合然后找出与这些物品最相似的 Top-N 个物品剔除用户已接触过的加权排序后生成推荐列表。4.2 算法 Java 实现示例我们实现一个简单的离线 ItemCF 推荐服务。首先我们需要从user_action表中拉取数据。// 文件路径food-server/src/main/java/com/food/platform/service/impl/RecommendServiceImpl.java package com.food.platform.service.impl; import com.food.platform.entity.UserAction; import com.food.platform.mapper.UserActionMapper; import com.food.platform.service.RecommendService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.*; import java.util.stream.Collectors; Slf4j Service public class RecommendServiceImpl implements RecommendService { Autowired private UserActionMapper userActionMapper; Autowired private RedisTemplateString, Object redisTemplate; // 物品相似度矩阵缓存键 private static final String ITEM_SIM_KEY recommend:item_sim; /** * 项目启动后计算一次并定时更新例如每天凌晨 */ PostConstruct Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public void calculateItemSimilarity() { log.info(开始计算物品相似度...); // 1. 获取所有用户行为数据 ListUserAction allActions userActionMapper.selectList(null); // 2. 构建用户-物品倒排表: MapuserId, ListitemId MapLong, ListLong userItemMap allActions.stream() .collect(Collectors.groupingBy(UserAction::getUserId, Collectors.mapping(UserAction::getPostId, Collectors.toList()))); // 3. 构建物品-用户倒排表: MapitemId, SetuserId MapLong, SetLong itemUserMap new HashMap(); for (Map.EntryLong, ListLong entry : userItemMap.entrySet()) { Long userId entry.getKey(); for (Long itemId : entry.getValue()) { itemUserMap.computeIfAbsent(itemId, k - new HashSet()).add(userId); } } // 4. 计算物品相似度 (余弦相似度简化版) MapString, Double itemSimMap new HashMap(); // 用 itemA:itemB 作为key ListLong itemIds new ArrayList(itemUserMap.keySet()); for (int i 0; i itemIds.size(); i) { Long itemA itemIds.get(i); SetLong usersA itemUserMap.get(itemA); if (usersA null || usersA.isEmpty()) continue; for (int j i 1; j itemIds.size(); j) { Long itemB itemIds.get(j); SetLong usersB itemUserMap.get(itemB); if (usersB null || usersB.isEmpty()) continue; // 求交集 SetLong intersection new HashSet(usersA); intersection.retainAll(usersB); if (intersection.isEmpty()) continue; // 计算余弦相似度 double sim intersection.size() / Math.sqrt(usersA.size() * usersB.size()); // 只保留相似度较高的 if (sim 0.1) { itemSimMap.put(itemA : itemB, sim); itemSimMap.put(itemB : itemA, sim); // 对称 } } } // 5. 将相似度矩阵存入Redis供推荐时快速读取 redisTemplate.opsForHash().putAll(ITEM_SIM_KEY, itemSimMap); log.info(物品相似度计算完成共 {} 对关系存入Redis, itemSimMap.size()); } /** * 为指定用户生成推荐 * param userId 用户ID * param topN 推荐数量 * return 推荐的物品ID列表 */ Override public ListLong recommendForUser(Long userId, int topN) { // 1. 获取用户历史喜欢的物品 ListLong userLikedItems userActionMapper.selectLikedPostIdsByUser(userId); // 2. 初始化物品得分Map MapLong, Double itemScoreMap new HashMap(); // 3. 遍历用户喜欢的每个物品找到相似物品并累加得分 for (Long likedItem : userLikedItems) { // 从Redis获取与likedItem相似的所有物品 MapObject, Object simEntries redisTemplate.opsForHash().entries(ITEM_SIM_KEY); for (Map.EntryObject, Object entry : simEntries.entrySet()) { String key (String) entry.getKey(); String[] items key.split(:); if (items.length ! 2) continue; Long itemA Long.parseLong(items[0]); Long itemB Long.parseLong(items[1]); Double sim (Double) entry.getValue(); // 如果 likedItem 是 itemA则 itemB 是相似物品 if (likedItem.equals(itemA) !userLikedItems.contains(itemB)) { itemScoreMap.put(itemB, itemScoreMap.getOrDefault(itemB, 0.0) sim); } } } // 4. 按得分排序取TopN return itemScoreMap.entrySet().stream() .sorted((e1, e2) - e2.getValue().compareTo(e1.getValue())) .limit(topN) .map(Map.Entry::getKey) .collect(Collectors.toList()); } }对应的 Mapper 接口// 文件路径food-server/src/main/java/com/food/platform/mapper/UserActionMapper.java package com.food.platform.mapper; import com.food.platform.entity.UserAction; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserActionMapper extends BaseMapperUserAction { Select(SELECT DISTINCT post_id FROM user_action WHERE user_id #{userId} AND action_type IN (1,2)) ListLong selectLikedPostIdsByUser(Long userId); }4.3 集成推荐接口在 Controller 中暴露一个推荐接口供前端调用。// 文件路径food-server/src/main/java/com/food/platform/controller/RecommendController.java package com.food.platform.controller; import com.food.platform.common.Result; import com.food.platform.service.RecommendService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.List; RestController RequestMapping(/api/recommend) public class RecommendController { Autowired private RecommendService recommendService; GetMapping(/forMe) public ResultListLong getRecommendations(HttpServletRequest request, RequestParam(defaultValue 10) Integer topN) { Long userId (Long) request.getAttribute(userId); if (userId null) { // 用户未登录可以返回热门推荐或空列表 return Result.success(Collections.emptyList()); } ListLong recommendedPostIds recommendService.recommendForUser(userId, topN); return Result.success(recommendedPostIds); } }5. 前端核心实现Vue 3 Element Plus5.1 项目初始化与路由配置使用 Vite 创建 Vue 3 项目并安装必要依赖。# 在 food-web 目录下执行 npm create vuelatest . -- --typescript --router --pinia # 按提示选择然后安装 Element Plus 和 Axios npm install element-plus element-plus/icons-vue axios npm install --save-dev sass配置路由 (router/index.ts)定义主要页面。// 文件路径food-web/src/router/index.ts import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import LoginView from ../views/LoginView.vue import RegisterView from ../views/RegisterView.vue import PostDetailView from ../views/PostDetailView.vue import UserCenterView from ../views/UserCenterView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: HomeView, meta: { requiresAuth: true } // 需要登录 }, { path: /login, name: login, component: LoginView }, { path: /register, name: register, component: RegisterView }, { path: /post/:id, name: postDetail, component: PostDetailView, meta: { requiresAuth: true } }, { path: /user, name: userCenter, component: UserCenterView, meta: { requiresAuth: true } } ] }) // 路由守卫检查登录状态 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } }) export default router5.2 首页与推荐模块实现首页 (HomeView.vue) 需要展示推荐流和全部流。我们使用 Element Plus 的布局和卡片组件。!-- 文件路径food-web/src/views/HomeView.vue -- template div classhome-container el-container !-- 侧边栏导航 -- el-aside width200px !-- 用户信息、导航菜单 -- /el-aside el-container el-header el-menu modehorizontal :default-activeactiveTab selecthandleTabChange el-menu-item indexrecommend为你推荐/el-menu-item el-menu-item indexlatest最新动态/el-menu-item el-menu-item indexhot热门榜单/el-menu-item /el-menu /el-header el-main !-- 发布框 -- div classpost-creator el-input v-modelpostContent placeholder分享你的美食心得... typetextarea :rows3/ div classaction-bar el-upload action/api/upload list-typepicture-card :on-successhandleUploadSuccess el-iconPlus //el-icon /el-upload el-button typeprimary clicksubmitPost发布/el-button /div /div !-- 帖子列表 -- div classpost-list el-card v-forpost in postList :keypost.id classpost-card template #header div classpost-header el-avatar :srcpost.userAvatar / span classusername{{ post.userNickname }}/span span classpost-time{{ formatTime(post.createTime) }}/span /div /template h3{{ post.title }}/h3 p{{ post.content }}/p div v-ifpost.images post.images.length classpost-images el-image v-for(img, idx) in post.images :keyidx :srcimg fitcover / /div template #footer div classpost-footer el-button :iconStar text{{ post.likes }} 点赞/el-button el-button :iconChatDotRound text clickshowComment(post.id)评论/el-button el-button :iconCollection text{{ post.collects }} 收藏/el-button /div /template /el-card el-empty v-ifpostList.length 0 description暂无内容 / /div /el-main /el-container /el-container /div /template script setup langts import { ref, onMounted } from vue import { Star, ChatDotRound, Collection, Plus } from element-plus/icons-vue import { ElMessage } from element-plus import { getRecommendPosts, getLatestPosts, createPost } from /api/post import type { Post } from /types/post const activeTab ref(recommend) const postContent ref() const postList refPost[]([]) // 加载推荐帖子 const loadRecommendPosts async () { try { const res await getRecommendPosts() postList.value res.data } catch (error) { ElMessage.error(加载推荐失败) } } // 加载最新帖子 const loadLatestPosts async () { try { const res await getLatestPosts() postList.value res.data } catch (error) { ElMessage.error(加载最新动态失败) } } const handleTabChange (key: string) { activeTab.value key if (key recommend) { loadRecommendPosts() } else if (key latest) { loadLatestPosts() } } const submitPost async () { if (!postContent.value.trim()) { ElMessage.warning(请输入内容) return } try { await createPost({ title: 美食分享, content: postContent.value }) ElMessage.success(发布成功) postContent.value loadLatestPosts() // 刷新列表 } catch (error) { ElMessage.error(发布失败) } } onMounted(() { loadRecommendPosts() }) /script style scoped langscss .home-container { height: 100vh; } .post-creator { margin-bottom: 20px; padding: 15px; background: #fff; border-radius: 8px; } .action-bar { margin-top: 10px; display: flex; justify-content: space-between; align-items: center; } .post-card { margin-bottom: 15px; } .post-header { display: flex; align-items: center; .username { margin-left: 10px; font-weight: bold; } .post-time { margin-left: auto; font-size: 0.8em; color: #999; } } .post-images { margin-top: 10px; .el-image { width: 150px; height: 150px; margin-right: 10px; border-radius: 4px; } } /style5.3 API 请求封装使用 Axios 封装统一的请求拦截器自动携带 Token 并处理响应。// 文件路径food-web/src/utils/request.ts import axios from axios import { ElMessage } from element-plus import router from /router const service axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, // 从 .env 文件读取 timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( (config) { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer ${token} } return config }, (error) { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( (response) { const res response.data // 假设后端统一返回格式为 { code: number, data: any, message: string } if (res.code 200) { return res } else { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message || Error)) } }, (error) { if (error.response?.status 401) { ElMessage.error(登录已过期请重新登录) localStorage.removeItem(token) router.push(/login) } else { ElMessage.error(error.message || 网络错误) } return Promise.reject(error) } ) export default service// 文件路径food-web/src/api/post.ts import request from /utils/request import type { Post, CreatePostParams } from /types/post // 获取推荐帖子 export function getRecommendPosts() { return request.getPost[](/api/recommend/forMe) } // 获取最新帖子 export function getLatestPosts(pageNum 1, pageSize 10) { return request.get(/api/post/list, { params: { pageNum, pageSize } }) } // 创建帖子 export function createPost(data: CreatePostParams) { return request.post(/api/post/create, data) }6. 项目部署与联调6.1 后端打包与运行配置application.yml设置数据库、Redis连接信息。# food-server/src/main/resources/application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/food_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password redis: host: localhost port: 6379 database: 0 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发时开启SQL日志打包在food-server目录下执行mvn clean package生成target/food-server-0.0.1-SNAPSHOT.jar。运行使用命令java -jar food-server-0.0.1-SNAPSHOT.jar启动后端服务。6.2 前端打包与 Nginx 配置配置环境变量在food-web根目录创建.env.production文件设置后端 API 地址。VITE_APP_BASE_APIhttp://your-server-ip:8080打包执行npm run build生成dist目录。Nginx 配置将dist目录内容放到 Nginx 的 html 目录下并配置代理解决跨域问题。# nginx.conf 部分配置 server { listen 80; server_name localhost; location / { root /path/to/food-web/dist; index index.html index.htm; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } # 代理后端API请求 location /api/ { proxy_pass http://localhost:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }启动 Nginx。6.3 前后端联调常见问题问题现象可能原因解决思路前端访问后端 API 4041. Nginx 代理配置路径错误。2. 后端服务未启动或端口不对。3. 前端请求的 baseURL 配置错误。1. 检查 Nginxproxy_pass地址和路径。2. 确认后端服务在8080端口运行 (netstat -ano | findstr :8080)。3. 检查前端.env文件中的VITE_APP_BASE_API。跨域错误 (CORS)后端未配置跨域且前端直接访问后端IP:Port。方案一推荐使用 Nginx 代理同源访问。方案二在后端WebMvcConfig中配置CorsFilter。页面空白控制台报 JS/CSS 404前端资源路径错误。检查vite.config.ts中的base配置Nginx 中root指向是否正确。推荐接口返回空列表1. 用户无行为数据。2. Redis 相似度矩阵未生成。3. 算法逻辑问题。1. 模拟用户点赞、收藏行为。2. 检查calculateItemSimilarity方法是否执行Redis 中是否有recommend:item_sim键。3. 调试算法打印中间数据。7. 项目优化与扩展方向7.1 性能与稳定性优化推荐算法优化增量更新当前是全量计算相似度可改为基于用户新行为进行增量更新。引入时间衰减用户近期行为权重应高于早期行为。使用更高效的相似度计算库如 Apache Mahout 或 Spark MLlib处理大规模数据。数据库优化为user_action表的user_id,post_id,action_type建立联合索引加速查询。对food_post表的create_time建立索引优化按时间排序查询。缓存策略热门帖子列表、用户基本信息可缓存到 Redis设置合理过期时间。用户个性化推荐结果可缓存避免每次请求都实时计算。接口防刷对点赞、发布等写操作接口使用 Redis 记录用户操作频率进行限流。7.2 功能扩展建议社交功能增加关注/粉丝系统实现基于社交关系的推荐。内容搜索集成 Elasticsearch实现美食标题、内容的全文检索。标签系统为美食分享打上标签如#川菜、#烘焙实现基于标签的分类和推荐。消息通知当帖子被点赞、评论或关注的人发布新内容时通过 WebSocket 或消息队列发送实时通知。管理后台使用 Vue 3 Element Plus 搭建独立的管理后台实现对用户、内容、举报的审核与管理。部署升级使用 Docker 容器化部署配合 Jenkins 或 GitLab CI/CD 实现自动化构建与发布。7.3 安全注意事项密码存储切勿明文存储密码。示例中的 MD5 仅为演示生产环境必须使用 BCryptPasswordEncoder 等强哈希算法加盐存储。SQL 注入使用 MyBatis-Plus 等框架的预编译功能避免手动拼接 SQL。XSS 防护用户输入的富文本内容如帖子内容在前端展示时要进行转义或使用安全的渲染方式。文件上传限制上传文件的类型、大小并对文件进行重命名避免直接使用用户上传的文件名。API 权限控制使用 Spring Security 细化接口访问权限确保用户只能操作自己的数据。通过以上步骤你已经完成了一个具备基础功能和个性化推荐的美食分享平台的全栈开发。这个项目涵盖了从需求分析、技术选型、数据库设计、后端业务逻辑与算法实现、前端页面交互到最终部署上线的完整流程。在理解并实践本项目的基础上你可以根据上述优化和扩展建议继续深入将其打造成一个更加健壮、功能丰富的作品这无疑会成为你技术履历中一个亮眼的实战项目。

相关新闻

STM32智能农业大棚

STM32智能农业大棚

STM32智能农业大棚(蓝牙版) 土壤温湿度采集 光照强度采集 风扇控制 水泵控制 加热器控制 照明灯控制 蜂鸣器报警 蓝牙APP 提供后期指导!!!需要资料可留下邮箱,关注点赞+收藏哦!【实物资料】 一、功能介绍&…

2026/8/11 12:17:34 阅读更多 →
游戏开发实战:图结构与回溯算法在A*寻路与迷宫生成中的应用

游戏开发实战:图结构与回溯算法在A*寻路与迷宫生成中的应用

你是不是也遇到过这样的场景:在开发一款游戏时,想实现一个复杂的迷宫寻路,或者设计一个NPC的智能行为,比如让它能探索地图、收集物品、躲避敌人,最后还能找到回家的路。你试过用简单的 if-else 逻辑堆砌,…

2026/8/11 12:17:34 阅读更多 →
构建高质量AI应用:从提示工程到RAG与质量评估的工程实践

构建高质量AI应用:从提示工程到RAG与质量评估的工程实践

最近在技术社区看到不少关于“AI大垃圾时代”的讨论,观点认为从2026年起,AI生成内容的泛滥将导致信息质量急剧下降。作为一名长期关注技术落地的开发者,我认为与其陷入对未来的担忧,不如深入探讨其背后的技术根源,并思…

2026/8/11 12:16:34 阅读更多 →

最新新闻

中文CLIP模型:5分钟快速上手的跨模态AI实战指南

中文CLIP模型:5分钟快速上手的跨模态AI实战指南

中文CLIP模型:5分钟快速上手的跨模态AI实战指南 【免费下载链接】Chinese-CLIP Chinese version of CLIP which achieves Chinese cross-modal retrieval and representation generation. 项目地址: https://gitcode.com/GitHub_Trending/ch/Chinese-CLIP Ch…

2026/8/11 13:09:55 阅读更多 →
FF14钓鱼计时器终极指南:5分钟掌握渔人直感完整使用技巧

FF14钓鱼计时器终极指南:5分钟掌握渔人直感完整使用技巧

FF14钓鱼计时器终极指南:5分钟掌握渔人直感完整使用技巧 【免费下载链接】Fishers-Intuition 渔人的直感,最终幻想14钓鱼计时器 项目地址: https://gitcode.com/gh_mirrors/fi/Fishers-Intuition 在《最终幻想14》的广阔世界中,钓鱼不…

2026/8/11 13:09:55 阅读更多 →
Java开发者必知的Git实战技巧与生存指南

Java开发者必知的Git实战技巧与生存指南

1. 项目概述:从Git惨案看Java开发者必备技能树 那天中午收到学弟的微信消息时,我正在调试Spring Boot的AOP切面。手机震动显示:"哥,我试用期被辞退了...主管说连Git基本操作都不会的Java开发没有培养价值"。作为经历过三…

2026/8/11 13:09:55 阅读更多 →
如何用键盘完全控制鼠标:Mouseable 键盘鼠标终极指南

如何用键盘完全控制鼠标:Mouseable 键盘鼠标终极指南

如何用键盘完全控制鼠标:Mouseable 键盘鼠标终极指南 【免费下载链接】mouseable Mouseable is intended to replace a mouse or trackpad. 项目地址: https://gitcode.com/gh_mirrors/mo/mouseable 你是否厌倦了频繁在键盘和鼠标之间切换?是否希…

2026/8/11 13:09:55 阅读更多 →
Visual Studio安装无响应?系统化排查与修复指南

Visual Studio安装无响应?系统化排查与修复指南

1. 问题概述:当安装程序按下“开始”按钮后,一切戛然而止 如果你正在尝试安装 Visual Studio 2019 或 2022,却卡在了安装程序启动的初始阶段,点击“继续”或“安装”按钮后,安装程序界面直接消失,或者进度…

2026/8/11 13:09:55 阅读更多 →
AI小生意实战指南:从需求筛选到年入百万美金的技术与商业路径

AI小生意实战指南:从需求筛选到年入百万美金的技术与商业路径

1. 从“AI小生意”到年入500万美金,核心路径是什么? 看到“一人靠AI小生意年入500万美金”这种标题,第一反应往往是怀疑。这背后不是一夜暴富的神话,而是一个高度聚焦、将AI能力产品化并找到精准付费场景的典型案例。它解决的核心…

2026/8/11 13:08:54 阅读更多 →

日新闻

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/v…

2026/8/11 0:00:02 阅读更多 →
前后端分离项目中控制台与接口工具数据差异排查指南

前后端分离项目中控制台与接口工具数据差异排查指南

1. 问题现象解析:控制台与Apifox的数据差异 最近在调试一个前后端分离项目时,遇到了一个典型问题:后端服务在本地开发环境控制台能正常输出查询数据,但通过Apifox测试时却返回空结果。这种"控制台有数据,接口工具…

2026/8/11 0:00:03 阅读更多 →
AI编程实战:从Claude Code踩坑到游戏开发入门

AI编程实战:从Claude Code踩坑到游戏开发入门

1. 从“AI能帮我做游戏”到“AI让我重新学编程”最近身边不少朋友,尤其是一些非技术背景、但对游戏开发有浓厚兴趣的朋友,都在问我同一个问题:“听说现在用Claude Code这种AI编程工具,小白也能做游戏了,是真的吗&#…

2026/8/11 0:00:03 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/11 1:08:05 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/11 1:08:05 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/11 1:08:05 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/10 17:07:33 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/11 1:08:06 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/10 17:07:33 阅读更多 →