首页 / 资讯中心 / 文章详情

SpringBoot2+Vue3+MyBatis-Plus美食推荐系统开发实战

SpringBoot2+Vue3+MyBatis-Plus美食推荐系统开发实战 ★ FEATURED ARTICLE
最近在整理一套 Java Web 美食信息推荐系统的完整源码技术栈是 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0配套开发文档和数据库脚本都齐全。这套项目很适合作为毕业设计也能直接改造成面试演示项目。我把它从环境搭建、数据库初始化到前后端联调完整跑了一遍中间确实遇到了一些坑今天把整个实现思路和操作细节整理出来希望能帮你节省大量调试时间。这个系统解决的核心问题并不复杂用户打开网站后不再需要一页页翻美食列表而是系统根据用户的浏览记录、收藏行为、食材偏好、评分数据等自动推送最可能感兴趣的美食信息。用户端有推荐首页、美食详情、收藏夹、我的评价管理端有菜品管理、菜品类别管理、用户管理、推荐规则配置等。整体功能覆盖了完整的业务闭环不是那种只有增删改查的“空壳项目”。我先把这套项目最值得关注的技术点放在前面SpringBoot2提供稳定的后端服务能力Vue3 Element Plus构建交互层MyBatis-Plus极大减少SQL样板代码MySQL8.0的窗口函数和JSON能力为推荐算法扩展留了空间。这套组合在2025年的中小型Web项目中依然非常能打下面我会把每一层的实现细节都拆开讲。1. 项目整体设计与技术选型思路1.1 这个美食推荐系统到底做了什么先明确项目边界避免一上来就陷入复杂推荐算法。它并不是做基于深度学习的AI推荐而是采用“用户行为 内容标签”的混合推荐策略。系统内部维护了两类数据一类是美食本身的画像数据包括菜名、分类、食材清单、口味标签、热量、价格、图片、描述另一类是用户行为数据包括浏览记录、收藏、点赞、评分、评论。推荐引擎每天定时或者用户点击刷新时通过计算用户对各类标签的偏好权重召回TopN条美食再结合热度因子排序最终推送给用户。整个系统模块划分非常清晰用户模块注册、登录、个人信息维护、密码加密美食内容模块菜品分类维护、菜品信息管理、图片上传、上下架推荐模块基于标签权重的召回、热门榜、新品榜、个性化推荐列表交互模块收藏、评分、评论、浏览记录后台管理模块用户管理、菜品审核、推荐参数配置、数据统计概览为什么要把推荐模块独立出来因为如果推荐逻辑和业务代码耦合在一起后期想换算法、调整权重都要动核心业务代码。放在独立Service层前端只需要调用一个接口后端无论怎么改返回的数据结构保持不变。1.2 为什么选择 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0很多人纠结版本选择我的观点是在毕业设计和中小型项目中稳定优先于新潮。SpringBoot2.x经过多年生产验证生态文档最多遇到问题随便搜都有解决方案SpringBoot3虽然已经普及但如果你用的公共组件版本不兼容排查成本会陡增。Vue3同样如此组合式API写起来比Vue2清爽得多配合Vite构建速度极快而且Element Plus组件库已经很成熟不需要自己造轮子。MyBatis-Plus是我个人非常推荐的数据访问层框架。它本质上是在MyBatis之上做增强核心价值是单表CRUD直接继承BaseMapper完成不用写XML分页插件内置逻辑删除、自动填充、乐观锁全都是注解搞定。对于这种以业务逻辑为核心的系统MyBatis-Plus可以把数据访问代码量压缩至少三分之二。但要注意复杂多表查询依然需要手写SQLMyBatis-Plus的Wrapper并不适合所有场景所以我在推荐模块的统计查询中仍然保留了自定义XML。MySQL8.0相比5.7有几个关键升级默认字符集utf8mb4支持窗口函数支持公用表表达式CTEJSON操作更高效。这些特性在做推荐结果排序、用户行为序列分析时非常好用。比如计算“用户最近30天浏览的所有菜品类别分布”用窗口函数一条SQL就能搞定。选择这套组合还有一个现实原因招聘市场上Java后端岗位的技术要求长期集中在SpringBoot、MyBatis、MySQL、Vue这些技能上。用它做项目简历上的关键词密度高面试官也不陌生沟通成本低。1.3 工程结构怎么拆分分包规则是什么后端我用标准的Maven多模块思想但考虑到项目规模没有拆成多模块而是单模块按包分层com.food.recommend ├── common // 通用返回结果、异常处理、常量、工具类 ├── config // 全局配置跨域、MyBatis-Plus、拦截器 ├── controller // 接口层 ├── service // 业务逻辑层含推荐算法 ├── mapper // MyBatis-Plus接口和自定义XML ├── entity // 数据库实体类 ├── dto // 请求和响应对象 └── job // 定时任务热门榜刷新、标签权重归一化前端Vue3部分则按页面维度拆viewscomponents里放公共组件api目录单独管理所有请求接口。这样前后端都能做到“改动局部不影响全局”。我建议你在拿到源码后先看文件结构不要直接跑起来心里有数比快速启动重要得多。2. 数据库设计与后端核心实现2.1 表结构设计与关系梳理数据库命名我统一使用小写加下划线user、food、food_category、user_favorite、user_rating、food_comment、user_browse_history、recommend_config。具体字段设计上有几个点特别值得注意food表核心字段CREATE TABLE food ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL, food_name VARCHAR(100) NOT NULL, description TEXT, main_ingredients VARCHAR(255), taste_tags VARCHAR(255), -- 例如 辣,咸,鲜 cooking_method VARCHAR(50), -- 炒/蒸/煮/炸 calories INT, price DECIMAL(8,2), image_url VARCHAR(500), status TINYINT DEFAULT 1, -- 1上架 0下架 view_count INT DEFAULT 0, favorite_count INT DEFAULT 0, rating_score DECIMAL(3,2) DEFAULT 0, create_time DATETIME, update_time DATETIME );taste_tags字段我用逗号分隔存标签ID虽然不符合严格第三范式但在国内主流业务开发中很常见。因为美食标签是一次性写入、频繁整体读取的数据拆成关联表每次推荐要多次join反而拖慢性能。这里属于典型“用空间换时间”的取舍。用户行为表采用“一张大表行为类型字段”还是拆表我选择拆成三张独立表user_favorite、user_rating、user_browse_history。原因很简单三张表的读写频率差异大浏览记录增长最快需要定期清理收藏和评分数百条以内查询要求快。合在一起虽然表少但后期统计SQL复杂度高索引设计也不好做。特别注意所有外键关联我都没有在数据库层面建立只在实体映射和SQL查询中使用逻辑关联。理由是MySQL在高并发写入场景下物理外键会造成锁竞争而且删除菜品时需要先手动处理关联表用逻辑外键更好控制。recommend_config表用来存推荐算法权重参数例如标签权重衰减系数、浏览量热度因子、收藏权重、评分权重。这个表设计的出发点是推荐策略不能写死在代码里运营人员调整参数后系统不需要发版就能生效。实测下来这对项目答辩很有帮助你可以现场改一个权重数值前端推荐结果立刻变化这比口述算法理论要有说服力得多。2.2 MyBatis-Plus 通用CRUD与业务封装实战MyBatis-Plus最核心的是继承BaseMapperT这是所有基础能力的入口。我在FoodMapper中的代码public interface FoodMapper extends BaseMapperFood { // 自定义多表关联查询写XML ListFoodRecommendDTO selectFoodsWithCategory(Param(offset) int offset, Param(limit) int limit); }Service层封装时不要直接透传Mapper接口给Controller而是定义业务方法。比如分页查询菜品用LambdaQueryWrapperpublic PageResultFoodVO pageFoods(int page, int size, String keyword, Long categoryId) { PageFood pageInfo new Page(page, size); LambdaQueryWrapperFood wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(keyword), Food::getFoodName, keyword) .eq(categoryId ! null, Food::getCategoryId, categoryId) .eq(Food::getStatus, 1) .orderByDesc(Food::getCreateTime); IPageFood result foodMapper.selectPage(pageInfo, wrapper); // 转VO返回避免直接暴露数据库字段 }注意like和eq的第一个参数是布尔条件MyBatis-Plus会在条件为false时跳过该条件这是防止SQL注入和空条件拼接最优雅的写法。我见过太多人用字符串拼接SQL那是绝对不可取的。分页插件配置必须在MyBatis-Plus配置类中显式加入Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }不加这个插件selectPage实际执行的是全表查询然后内存分页数据量到几十万必崩。这个坑我已经见过不少人踩过。逻辑删除也是MyBatis-Plus的亮点。在实体字段上加上TableLogic注解TableLogic private Integer deleted;之后所有deleteById操作都自动变成UPDATE ... SET deleted 1 WHERE id ?查询自动追加deleted 0条件。这样无论菜品还是评论都能保留历史数据万一误删还能恢复。2.3 推荐算法的落地实现推荐模块是本系统的灵魂我用的是“标签偏好加权 发布时间/热度平滑”的召回排序策略完整步骤如下第一步计算用户标签偏好矩阵从user_browse_history联合food表统计每个用户最近30天浏览过的食物的标签频次。举个例子用户浏览了20道菜其中10道是“辣”。那么“辣”的初始权重就是10。收藏行为加3倍权重评分行为加5倍权重。这样用户的行为梯度就能拉开。第二步标签权重归一化与时间衰减用户的口味会随时间变化不能把三个月前的浏览和今天的浏览同等看待。我在SQL中直接用DATEDIFF(NOW(), create_time)计算天数差权重乘以exp(-0.05 * 天数差)。7天前浏览的权重衰减到0.730天前衰减到0.22这个衰减系数我放在推荐配置表中方便调整。第三步召回候选集遍历用户偏好标签权重最高的前5个标签找到包含这些标签且用户没看过的菜品按菜品综合得分排序。综合得分计算公式score 0.6 * 标签匹配得分 0.2 * 热度得分 0.2 * 新鲜度得分其中标签匹配得分是当前用户对该菜品所有标签权重之和热度得分是浏览量、收藏数、评分数归一化后的加权和新鲜度得分按发布时间线性递减。第四步冷启动策略新用户没有任何行为数据系统直接推荐“热门榜”和“新品榜”。热门榜不是简单的浏览量排序而是SELECT food_id, LOG(view_count 1) * 0.5 LOG(favorite_count 1) * 0.3 LOG(rating_count 1) * 0.2 AS heat_score FROM food WHERE status 1 ORDER BY heat_score DESC LIMIT 20;用LOG是为了压缩头部流量悬殊带来的差距避免爆款永远霸占榜单。这个细节可以让推荐列表更合理写进文档里也是加分项。第五步定时任务刷新推荐缓存个性化推荐不能每次请求都现算否则并发一高数据库就扛不住。我在FoodRecommendJob中每30分钟全量刷新一次TopN结果到Redis。如果项目环境没装Redis也可以缓存到内存Map或recommend_result表。我建议直接存库因为毕业设计环境中部署Redis多一个服务容易出问题存一张表结构简单、排查直观。事实证明这个选择在答辩演示时非常稳即使Redis没启动也不影响系统运行。3. 前端Vue3工程实现与前后端联调3.1 Vue3工程搭建与核心配置前端我直接用Vite创建的Vue3项目原因很简单Vite启动速度快热更新秒级响应而且对Vue3的Composition API支持天然友好。创建命令npm create vitelatest food-recommend-front -- --template vue然后安装核心依赖npm install vue-router4 pinia axios element-plus路由我用createWebHistory模式但要注意部署到Nginx时如果直接访问子路由会出现404需要配置try_files重写到index.html。如果是开发模式Vite会自己处理不需要担心。跨域配置放在Vite的vite.config.js中server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }为什么用proxy而不是直接发起跨域请求因为浏览器的CORS限制如果前端直连后端每次请求都要处理预检和跨域响应头Session/Cookie传递也会反复出问题。Vite代理的方案从浏览器视角看所有请求都是同源的省去90%的跨域烦恼。后端同时开一层CORS配置作为兜底双保险。3.2 页面拆解从推荐首页到评价闭环推荐首页是核心页面布局上拆成四个区块顶部搜索栏、左侧菜品分类树、中间推荐信息流、右侧用户行为面板。中间推荐信息流使用不定高卡片列表图片懒加载下面展示菜品标签和评分点击卡片跳转详情。详情页是最容易出亮点的地方我建议增强三个功能相关菜品推荐、用户评分交互、评论时间线。相关菜品推荐在详情页底部展示逻辑是“和当前菜品拥有至少一个共同标签的其他菜品”接口由后端返回。评分交互用Vue3的reactive状态管理实时分数点击星星后调接口提交成功后更新平均分。用户收藏夹和浏览历史页面都是列表页使用ElTable或ElCard重点是展示删除、清空操作。评价页需要弹窗表单字段包括评分、口味标签多选、文字评论。提交后不能只刷新当前页还要通过Pinia共享状态更新全局评论数。3.3 登录鉴权与前后端会话保持技巧登录模块我使用的是传统Session Cookie方案而不是JWT。很多人一提到前后端分离就默认用JWT但在这个项目里Session更合适管理端需要对用户做封禁操作Session可以随时在服务端销毁JWT一旦签发无法主动失效。Session模式中前端通过axios的withCredentials: true携带Cookie后端SpringBoot配置Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.setAllowCredentials(true); config.addAllowedHeader(*); config.addAllowedMethod(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这里有几个关键细节。allowedOriginPattern(*)和setAllowCredentials(true)必须同时出现否则前端会报“请求资源不包含Access-Control-Allow-Credentials”错误。另外后端接口的Session生成依赖于Cookie中的JSESSIONID如果前端代理路径和登录接口路径不一致可能导致每次请求都生成新的Session。我在实际项目中就把所有接口统一放在/api前缀下登录接口是/api/user/login这样代理规则一致Session才能始终保持。Session失效的默认时间是30分钟管理系统通常要更长。我在application.yml中设置了server: servlet: session: timeout: 120m3.4 Vue3状态管理与接口封装接口封装我把每个模块的API单独建文件比如api/food.jsimport request from /utils/request export function getRecommendList(data) { return request({ url: /food/recommend, method: post, data }) }request模块里做了统一拦截请求前在header中带上Token如果用的Session则可以忽略响应后统一解包后端返回的{code, message, data}结构如果code不是200直接弹错误提示。Pinia存储用户信息和主题配置。每个页面组件中拿用户状态时用storeToRefs保证响应式不被解构丢失。这里一定要小心很多人习惯直接const { userInfo } store这样会丢失响应性页面数据变不了。我实际开发中使用了storeToRefs(useUserStore)才不会出现登录后首页还显示未登录的诡异问题。4. MySQL8.0安装部署与容器化实践4.1 本机安装MySQL8.0的完整流程与踩坑记录无论你自己开发还是部署项目MySQL8.0都是第一道门槛。Windows安装时最常遇到的是“安装后服务无法启动”九成是Data目录初始化失败。我建议不要用安装包向导而是用命令行方式安装可控性最高。简单说下步骤从MySQL官网下载8.0的ZIP压缩包解压到指定目录在根目录新建my.ini配置文件[mysqld] basedirD:/mysql-8.0.36-winx64 datadirD:/mysql-8.0.36-winx64/data port3306 character-set-serverutf8mb4 default-storage-engineINNODB然后用管理员权限执行mysqld --initialize-insecure mysqld --install net start mysql这里必须用--initialize-insecure它会生成一个root用户且密码为空然后用ALTER USER修改密码。如果直接用--initialize生成的随机密码在data目录的err文件里新手经常找不到。连接工具方面我推荐先用命令行测试mysql -u root -p如果报错Access denied for user rootlocalhost多半是密码策略或host限制。MySQL8.0默认密码策略是caching_sha2_password老版本客户端连接会报Authentication plugin caching_sha2_password cannot be loaded。这个问题在SpringBoot连接时尤其常见解决方法是创建用户时指定CREATE USER food% IDENTIFIED WITH mysql_native_password BY yourpassword; GRANT ALL PRIVILEGES ON fooddb.* TO food%; FLUSH PRIVILEGES;如果你不想改驱动也可以在application.yml中使用mysql-connector-j8.x驱动它本身支持caching_sha2_password但需要配置时区参数。4.2 使用Docker快速启动MySQL8.0如果本机环境比较乱或者想在答辩前快速部署一套干净环境Docker是更好的选择。拉取官方镜像并启动docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEfooddb \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0启动后需要等待几秒初始化查看日志确认docker logs -f mysql8出现ready for connections就说明成功了。用Docker跑数据库最大的好处是环境隔离不会污染宿主机删除只需要docker rm -f mysql8。但坏处是容器默认时区是UTC连接SpringBoot如果不设置serverTimezoneAsia/Shanghai时间字段会和本地相差8小时。解决办法有两种容器启动时加环境变量TZAsia/Shanghai或者JDBC URL中直接指定时区。我建议两种都加避免任何时间错乱。Docker方式导入初始化SQL也很简单docker exec -i mysql8 mysql -uroot -p123456 fooddb fooddb.sql4.3 SpringBoot连接MySQL8.0的JDBC配置详解这是整个项目最容易报错的地方。SpringBoot2.6版本依赖的mysql-connector-java是5.x连接MySQL8.0时经常炸。我的完整配置如下spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/fooddb?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456四个参数缺一不可useSSLfalse本地开发不需要SSL否则会告警且可能握手失败serverTimezoneAsia/Shanghai不设置默认是UTC日期全乱allowPublicKeyRetrievaltrue解决Public Key Retrieval is not allowed错误这是MySQL8.0的caching_sha2_password认证机制引起的characterEncodingutf8与后端加useUnicodetrue配合防止中文乱码另外driver-class-name一定是com.mysql.cj.jdbc.Driver不是com.mysql.jdbc.Driver。前者是新驱动后者已经废弃很多老博客还在写旧的复制了必报ClassNotFound。5. 常见问题与排查技巧实录5.1 编译启动阶段高频错误后端启动失败的第一大原因就是端口被占用。SpringBoot默认使用8080端口如果你之前跑过其他服务要提前查netstat -ano | findstr 8080然后去任务管理器根据PID结束进程。不知道你有没有遇到过APPLICATION FAILED TO START提示Web server failed to start. Port 8080 was already in use这种情况下改application.yml的端口也可以但注意前端代理也要同步改。前端依赖安装慢是国内网络环境的老问题。把npm镜像切换成国内源可以在项目根目录新建.npmrc文件registryhttps://registry.npmmirror.com再用npm install速度提升肉眼可见。如果某个包安装还失败就清缓存npm cache clean --force再试。5.2 MyBatis-Plus使用中的经典坑分页不生效是一个高频问题。现象是返回的total为0或者数据不是按页返回。原因基本就是没配置PaginationInnerInterceptor。我有一次在另一个项目里发现问题出在MyBatis-Plus版本与SpringBoot2的兼容性上后来统一使用mybatis-plus-boot-starter3.5.x版本兼容性最好。另一个坑是字段自动填充不生效。我设计菜品表时希望create_time和update_time自动填充在实体类中加了TableField(fill FieldFill.INSERT)但忘了实现MetaObjectHandler接口Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }如果没有这个类所有时间字段都是空。实际上每条SQL都要手动设置时间非常痛苦。5.3 前后端联调时的经典翻车现场跨域配置完成后最常见的是“会话丢失”。用户登录成功后前端跳转页面再请求用户信息发现未登录。排查方法是在浏览器开发者工具中查看每个请求的Cookie登录请求返回了Set-Cookie但后续请求没有携带JSESSIONID。出现这种情况检查axios实例是否设置了withCredentials: trueconst request axios.create({ baseURL: /api, timeout: 10000, withCredentials: true })前端设置了这个后端CorsConfig又开了allowCredentials(true)双管齐下才能保证Cookie正常。还有一点Cookie的属性中如果SameSite没有设置现代浏览器默认会拦截跨域Cookie传递。我通过在启动类添加一个CookieSerializerBean解决Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer new DefaultCookieSerializer(); serializer.setSameSite(Lax); serializer.setCookieName(JSESSIONID); return serializer; }5.4 Vue3渲染问题与Element Plus样式异常Vue3的数据响应式问题集中在reactive和ref的选择上。我建议所有类型为对象或数组的用reactive基础类型用ref。但在数组操作上Vue3不能直接用索引赋值比如this.list[0] newItem不会触发更新必须用splice或Object.assign。我之前因为原生属性直接更新DOM不刷新排查了大半天最后才发现是直接写food.name xxx把响应式数据当作普通对象操作了。Element Plus组件库偶尔样式失效多半是引入方式不对。推荐全量引入在main.js中import ElementPlus from element-plus import element-plus/dist/index.css app.use(ElementPlus)按需引入虽然能减小打包体积但毕业设计项目没必要增加复杂度。5.5 数据库层面的常见问题速查表我把自己在使用中遇到的MySQL问题整理成了一张速查表现象原因解决方案连接失败Access denied用户没创建或密码错误CREATE USER并用mysql_native_password指定认证插件Public Key Retrieval is not allowedJDBC连接MySQL8.0时未加allowPublicKeyRetrievalURL中添加allowPublicKeyRetrievaltrue中文乱码数据库默认字符集不是utf8mb4建库时指定DEFAULT CHARACTER SET utf8mb4时间差8小时容器或JDBC未设置时区容器设置TZAsia/Shanghai连接URL加serverTimezone[ERR] 2003 - Cant connect to MySQL server端口不通或服务未启动检查netstat端口监听Linux防火墙开放3306这套速查表我写进了项目文档中答辩时被问到数据库细节直接能背出来。6. 项目文档如何组织与二次开发方向6.1 需求分析文档与API文档的编排拿到源码后不要急着看代码先看配套文档。项目的需求说明书建议包含系统背景、角色分析普通用户、管理员、功能用例图、核心业务流程图、数据库ER图。 API文档我使用的是Swagger自动生成SpringBoot依赖中加springfox-boot-starter即可启动后访问/swagger-ui/index.html每个接口的请求参数和响应结构一目了然。答辩时把Swagger页面投到屏幕上比干讲代码有说服力得多。需要注意的是Swagger3和SpringBoot2.6有路径匹配策略冲突需要设置spring: mvc: pathmatch: matching-strategy: ant_path_matcher否则启动报IllegalArgumentException: No match found。6.2 从毕业设计到上线部署的扩展思路这套系统目前是单体架构如果想要扩展有两条路径值得尝试。第一条是推荐算法升级。现在的标签加权模型是离线批处理式的只能做到30分钟更新一次。后续可以引入Redis实时记录用户点击事件流配合用户画像组件做到分钟级甚至实时推荐。实现方式不难SpringBoot把点击事件写入Redis的SortedSetscore是当前时间戳再写一个定时任务把这部分数据做增量合并到用户行为表。第二条是前后端分离的部署拆分。前端打包后放到Nginx后端打成jar包独立运行数据库用云数据库或Docker。部署脚本我建议写成Docker Composeversion: 3 services: mysql8: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD123456 - MYSQL_DATABASEfooddb volumes: - ./mysql-data:/var/lib/mysql - ./sql:/docker-entrypoint-initdb.d ports: - 3306:3306 backend: build: ./backend depends_on: - mysql8 ports: - 8080:8080 frontend: build: ./frontend depends_on: - backend ports: - 80:80这样一条docker-compose up -d就能把三个服务全部拉起迁移环境时非常省心。我在本地实测过从零开始到三个容器全部正常运行大约需要10分钟前提是镜像下载顺利。一些真实的项目体会这套系统我从拿到源码到完全跑通前后总共花了两个下午。第一下午做环境和数据库导入第二下午联调前端页面和推荐接口。中间最让我花时间的地方有两个一是MySQL8.0的连接参数二是前端Session跨域保持。这两个问题单独报错信息都很模糊网上答案各说各话最终是两种方案组合在一起才解决。所以我在文档里特意加了一个“快速排错清单”把这两个坑的完整排查路径写清楚后来再跑别的环境照着操作基本一遍过。另外一个小建议如果你准备把这份源码作为毕业设计不要只停留在“能跑”。把推荐配置表中的权重参数改一改观察推荐结果前后的变化然后把这一过程写进论文里这比贴大段代码有价值得多。毕竟推荐系统的核心是策略迭代而不是堆技术名词。最后再分享一个我在项目里顺手做的小功能管理员在后台可以手动干预推荐位把某些新店菜品置顶并设置置顶过期时间。这个功能技术上很简单就是一张recommend_manual表业务上却很出彩因为它体现实操思维。答辩老师问“推荐系统如何保证商业公平性”时我就拿这个功能举例效果远比背算法公式好。你后续扩展这个项目时也可以从这个方向多想一想。
阅读完成 · 觉得有帮助?
咨询建站