ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue美食推荐商城实战:从数据库到推荐算法全解析

SpringBoot+Vue美食推荐商城实战:从数据库到推荐算法全解析 这个项目我太熟了。SpringBoot Vue 前后端分离、Java MySQL MyBatis这套组合在近几年的毕业设计和课程设计里出现频率极高“美食推荐商城”这个名字则把业务场景落到了具体的餐饮食域。先说结论这类项目不是单纯写个CRUD那么简单核心价值在“推荐”两个字——用户进首页该看到哪些美食、按什么规则排、后台怎么维护菜品和标签这些才是设计的真正难点也是面试和评委最常追问的地方。如果你正在做毕业设计、准备Java软件项目或者单纯想拿一个完整的前后端分离项目来练手这篇内容应该能帮你少走不少弯路。我会按一个完整项目的推进顺序从技术选型、数据库设计、后端实现、前端搭建到问题排查把每个环节的关键决策和不方便写进文档的坑都捋一遍。不同学校、不同导师对系统的要求会有差异但底层思路是通用的把“美食”换成“图书”“二手闲置”或者别的品类架构基本不需要大改。1. 项目整体设计与技术选型拆解1.1 为什么是SpringBoot、Vue、MyBatis这套组合先聊选型因为很多同学在开题时就被问住了。SpringBoot的核心价值是“少配置、快启动”内嵌Tomcat打包成可执行jar后丢到服务器上一条命令就能跑不需要额外装容器这对课程设计和中小型项目来说是巨大的便利。早期用SSHSpringStrutsHibernate或者SSMSpringSpringMVCMyBatis的年代光维护一堆XML配置就能让人崩溃SpringBoot把绝大多数配置变成了约定大于规则项目里只留一个application.yml就够了。Vue为什么流行它的响应式机制让页面状态和DOM自动同步组件化开发让页面可以拆成卡片、轮播、列表一个个独立模块复用。对一个商城类的系统来说首页、列表页、详情页、购物车、后台管理天然适合组件化拆分。Vue的学习曲线比React平缓中文文档和社区资料极其丰富遇到问题基本都能搜到答案所以学校项目选它非常稳。MyBatis是这套组合里最有争议也最值得聊的一环。它属于半自动ORMSQL由开发者自己写不像JPA那样自动生成SQL。推荐类项目恰恰需要大量自定义SQL——按销量排序、按评分排序、按标签交集筛选这些用JPA的自动查询会写得很别扭而在MyBatis的XML里就是几条SELECT的事。另外国内不少公司的数据访问层就是MyBatis或MyBatis-Plus面试聊这个项目时对方不会陌生交流成本低。MySQL则纯粹是图省心和生态成熟免费、轻量、资料多一个中型商城的表量和并发量完全扛得住。版本上给个实际操作建议如果环境还是JDK8老老实实用SpringBoot 2.7.xSpringBoot 3.x要求JDK17起步很多实训环境的JDK版本并不支持一启动就是UnsupportedClassVersionError这种环境问题排查起来特别浪费时间。数据库连接池用SpringBoot默认的HikariCP就够了性能好、零配置没必要再引Druid除非导师明确要求。1.2 业务模块划分与前后端分离的架构分层这个项目从功能上通常拆成前台商城和后台管理两大块。前台面向普通用户首页推荐位、美食分类、美食列表与详情、标签筛选、购物车、下单结算、订单中心、登录注册、我的评价。后台面向运营人员菜品管理增删改查和图片上传、分类管理、标签管理、订单管理发货/完成、用户管理、简单的数据统计看板。前后端分离的架构下前端是一个独立的Vue工程后端是一个SpringBoot工程两边通过RESTful API通信。后端项目的分包要清晰我习惯按controller、service、mapper、entity、vo、dto、config、common、utils来组织。很多新手喜欢把entity传到前端直接当返回结果这个习惯要改entity对应数据库表结构不该暴露给前端接口返回用VO比如食物实体里没有分类名称和标签列表但详情页需要这时候就要建一个FoodDetailVO把categoryName和tags组合进去。前后端分离还有一个绕不开的话题——跨域。本地开发时前端跑在5173端口Vite默认或8080端口Vue CLI后端跑在8080端口两边端口不同就存在跨域。解决方式有两个前端配proxy代理或者后端写CORS配置。实战中两者经常同时配置后面联调章节我会把配置细节完整写出来。2. 美食推荐商城的数据库设计2.1 核心表拆分用户、商品、订单、评价、标签数据库是这种管理系统项目的灵魂表设计得好不好直接决定后端代码是写得顺手还是要天天写兼容逻辑。以美食推荐商城为例我建议至少设计下面九张核心表t_user用户表字段包括id、username、password、nickname、avatar、phone、status、create_time。密码字段建议存加密后的密文不要明文课程设计虽然不用上生产但这个安全意识可以从现在就建立。t_category美食分类表字段包括id、name、icon、sort、status。分类和食物是一对多关系。t_food美食商品表字段包括id、category_id、name、description、cover_img、price、stock、rating、sales、status、create_time。字段设计上注意价格用DECIMAL(10,2)评分用DECIMAL(2,1)库存用INT销量用INT。t_tag标签表字段包括id、name。美食标签可以是“微辣”“清淡”“川菜”“甜品”“适合拍照”这类贴近业务的描述标签是推荐算法的核心素材。t_food_tag食物与标签关联表字段包括id、food_id、tag_id。多对多关系必须用关联表不要在设计食物表时加一个tags字段用逗号拼接后面推荐查询会非常痛苦。t_cart购物车表字段包括id、user_id、food_id、quantity、checked、create_time。checked字段用于记录用户是否勾选了该商品参与结算。t_order订单表字段包括id、order_no、user_id、total_amount、status、receiver_name、receiver_phone、address、remark、create_time。订单号要单独生成不能用数据库自增ID当订单号自增ID容易被遍历且不专业。t_order_item订单明细表字段包括id、order_id、food_id、food_name、food_img、price、quantity。注意这里冗余了food_name和food_img这是故意的——下单后菜品可能改名或删图但订单历史必须能还原当时的商品信息。t_comment评价表字段包括id、user_id、food_id、order_id、rating、content、create_time。评分数据会成为推荐算法的重要输入。这些表之间用逻辑关联就够不要建物理外键。外键约束写入数据库后删除操作会被各种FOREIGN KEY限制卡住而实际开发中这些约束靠应用层代码保障就足够了。物理外键在分库分表或测试数据清理时还会变成麻烦。2.2 推荐数据落库评分、销量与标签怎么设计美食推荐系统的数据基础其实就三个维度评分、销量、标签。评分和销量可以直接冗余在t_food表里下单成功后执行UPDATE t_food SET sales sales 1 WHERE id ?用户提交评价后重新计算该美食的平均分并写回rating字段。很多人会问为什么不用实时统计答案很简单对于课程设计和中小型项目每次打开首页都去order_item和comment表实时SUM和AVG数据量小的时候没问题但没必要冗余字段用一次事务更新就能保证一致性查询还快。标签推荐的数据来源是用户行为。推荐系统要回答“这个用户可能喜欢什么”的问题基础的思路是先找到用户最近购买或评价过的美食从这些美食的标签中统计出高频标签再去找拥有这些标签的其他美食。实现上和真实的协同过滤还有很大差距但作为课程设计这个逻辑既能让评委听懂又能体现“个性化推荐”的设计思想。为了支撑这个逻辑建议在t_comment表里已经能查到用户和食物的关系如果还想追踪用户的浏览行为可以加一张t_footprint浏览记录表字段包括id、user_id、food_id、create_time。有了这张表即使未购买的用户也能被推荐。推荐核心SQL的思路大概是这样的——查用户浏览过的高频标签SELECT tag_id, COUNT(*) AS cnt FROM t_food_tag WHERE food_id IN ( SELECT food_id FROM t_footprint WHERE user_id #{userId} ) GROUP BY tag_id ORDER BY cnt DESC LIMIT 3;再根据这些标签召回候选美食排除用户已经买过或看过的SELECT DISTINCT f.id, f.name, f.cover_img, f.rating, f.sales FROM t_food f INNER JOIN t_food_tag ft ON f.id ft.food_id WHERE ft.tag_id IN (...) AND f.status 1 AND f.id NOT IN (...) ORDER BY f.rating DESC, f.sales DESC LIMIT 8;这个逻辑讲清楚了推荐模块的架构就有了。2.3 建表细节与索引设计的实战建议建表时有一些细节代码写多了自然能体会到。第一字符集务必选utf8mb4排序规则用utf8mb4_general_ci或者utf8mb4_0900_ai_ci。美食名称、昵称、评论内容里经常出现中文和生僻字老旧的utf8字符集存emoji会报错连带整个插入事务失败。第二金额永远用DECIMAL(10,2)不要用FLOAT或DOUBLE浮点数在订单金额计算上会有精度问题两个小数看似差别不大累计起来账对不上非常麻烦。第三所有表加上create_time、update_time两个通用字段后台管理列表按时间倒序排序时就会觉得特别方便。软删除是另一个值得说的习惯。业务表上最好加一个status或delete_flag字段正常状态为1删除状态为0。用户删除一条美食记录时做的其实是UPDATE t_food SET status 0 WHERE id ?而不是DELETE。这样数据不会真正消失订单历史、评价关联依然能查到同时也符合电商系统的真实做法。注意所有查询SQL里都要带status 1条件漏掉一个都会导致已下架的美食出现在前端页面。索引设计上不要盲目加索引但下面这几个场景一定要加t_food表的category_id和status组合索引因为分类页的典型查询是WHERE category_id ? AND status 1t_food表的sales和rating字段各建一个普通索引因为首页推荐和热门排行都用它们做排序t_order表的user_id字段建索引因为个人订单中心查的是WHERE user_id ? ORDER BY create_time DESC。索引建好后把热门SQL拿到EXPLAIN里扫一眼能看到key列命中索引就说明没浪费。3. 后端SpringBootMyBatis的实现要点3.1 后端骨架统一返回、全局异常、分层分包后端代码规范要从第一层开始建立。我强烈建议项目里先写一个ResultT统一返回类结构就三个字段code、msg、data。成功时返回Result.ok(object)失败时返回Result.error(业务提示)。这么做的最大好处是前端axios拦截器可以统一处理返回格式不用每个接口单独判断成功还是失败。定义统一返回类Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }全局异常处理用RestControllerAdvice加ExceptionHandler组合将业务异常统一拦截。比如下订单时库存不足、购物车为空这些是业务异常可以直接抛一个自定义的BusinessException由全局处理器兜底转成Result.error返回前端。这样Controller层方法不需要每次写try-catch代码干净非常多。分层上再强调一次Controller只做参数接收Service写核心业务Mapper只负责SQL。不要把Controller写得又臭又长。推荐逻辑放在ForegroundService里后台管理的增删改查放在AdminService里用户的登录注册单独拆一个AuthService模块清晰评分答辩也方便讲。3.2 登录鉴权与接口访问控制这类管理系统一定需要登录鉴权。实现方案上Session和JWT是两大方向。虽然Session简单但前后端分离项目里我更推荐用JWT——它天然适合无状态API前端把token存在localStorage请求时塞进Authorization头后端用拦截器统一校验链路简单。JWT生成用io.jsonwebtoken:jjwt库版本注意选0.9.1这是兼容JDK8的常用版本。生成token的核心代码不复杂把用户id和用户名作为负载封装进去设置过期时间一般24小时用SignatureAlgorithm.HS256签名密钥写死在配置文件中就够个人项目用了。拦截器需要处理两个关键细节。第一个是放行白名单登录接口、注册接口、首页推荐列表、美食详情这些不需要登录就能访问的路径必须放行否则用户没登录就访问首页直接被拦了前端全黑屏。第二个是注意放行OPTIONS请求浏览器的跨域预检请求如果被拦截器拦截返回401前端一样会报跨域错误排查时非常容易忽略。跨域配置建议在后端单独建一个CorsConfig类实现WebMvcConfigurer接口的同时配置拦截器注册和跨域映射代码大概是这样Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/api/auth/login, /api/auth/register, /api/food/**, /api/category/**); } Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意这里配置了addPathPatterns(/**)拦截器拿到token后调用JwtUtil.parseToken()解析失败就抛401。3.3 推荐逻辑怎么实现热销推荐、评分推荐、标签推荐推荐的商业逻辑是整个项目的亮点也是最容易在答辩时被追问的部分。我把推荐拆成三个维度来落地复杂度递增但都有明确的实现路径。热销推荐最简单也最实用。首页顶部“今日热门”的定位就是给大多数用户看的对所有人展示同一批菜品。SQL就是SELECT * FROM t_food WHERE status 1 ORDER BY sales DESC, rating DESC LIMIT 8。这里把sales放在rating前面是因为销量代表大众接受度对一个没有登录的用户来说“大家都买过的”比“专家评分高的”更有说服力。评分推荐用于“高分必吃”模块SQL是ORDER BY rating DESC, sales DESC LIMIT 8。注意排序字段不是单一评分评分相同的时候销量高的排在前面避免多道菜并列时排序不稳定页面上来回刷新顺序会跳。标签推荐这个是真正的个性化部分。我上面已经给了核心SQL这里把完整流程理一遍。第一步从浏览记录表查出用户最近浏览过的一批美食id第二步拿这批美食id去t_food_tag关联表里统计高频标签取前三第三步用这些标签召回其它美食排除用户已经浏览或买过的第四步对被召回的候选集按评分和销量再次排序取前8条。整个过程可以用一个Service方法串起来前端调用一个/api/recommend/{userId}接口就能拿到数据。这套推荐逻辑本质上是基于内容的推荐虽然和工业界的协同过滤差别很大但在课程设计口径下完全够用而且每一步SQL都能在答辩现场讲清楚原理比单纯做增删改查有说服力得多。3.4 MyBatis高频点缓存、TypeHandler、配置解析MyBatis是这套项目里面试官最爱追问的部分下面这几个高频问题在项目里都有对应的实践场景。缓存问题永远是第一个。MyBatis的一级缓存是SqlSession级别的默认开启同一个SqlSession内执行两次相同的查询第二次不会查数据库。但Spring整合MyBatis后每次请求都会重新创建SqlSession所以一级缓存基本只在事务方法内生效比如一个Transactional方法里连续两次查询同一个商品第二次会走缓存。二级缓存是Mapper级别的跨SqlSession共享但这个缓存默认是不建议开的。原因很简单二级缓存存在脏数据风险商品的库存、销量这些字段更新极其频繁如果开了二级缓存一次UPDATE后旧数据还留在缓存里用户看到的还是修改前的价格和库存这个坑我在实际项目里踩过排查了整整一下午。如果你想让首页的热门推荐列表加速更靠谱的方案是把热点数据放到Redis里或者用Spring Cache的Cacheable注解设置过期时间而不是直接开MyBatis的二级缓存。TypeHandler是个容易被忽视但面试常考的点。它的作用是在JDBC类型和Java类型之间做转换。实际项目里有一个很好用的场景美食图片的URL在数据库里存的是相对路径/images/xxx.jpg但前端需要展示完整地址http://ip:8080/images/xxx.jpg这个拼接工作可以在TypeHandler里完成。自定义TypeHandler需要继承BaseTypeHandlerString实现setNonNullParameter和getNullableResult四个方法然后在Mapper XML的resultMap里用typeHandlercom.xxx.handler.ImageTypeHandler指定。如果你不想这么麻烦也可以建一个VO层统一拼接但能讲出TypeHandler的原理面试官会高看你一眼。MyBatis的启动流程和配置解析也是高频题。SqlSessionFactoryBuilder读取mybatis-config.xml或Spring Boot配置后会调用XMLConfigBuilder进行配置解析最终生成Configuration对象。Configuration里维护了MappedStatement每条SQL的封装、MapperRegistryMapper接口的注册中心、Environment数据源和事务工厂等核心内容。理解了这个流程就能明白为什么Mapper接口和XML的namespace必须一致也就能定位“Invalid bound statement”这类最常见报错。4. 前端Vue的搭建与前后端联调4.1 初始化项目与依赖安装前端部分先从建项目开始。Vue 3项目我建议用Vite作为构建工具启动速度快热更新几乎是秒级开发体验比Webpack好太多。命令很简单npm create vitelatest food-mall-front -- --template vue cd food-mall-front npm install npm run dev如果环境里Node版本比较老或者是导师要求用Vue 2 ElementUI的组合那就用Vue CLI那套老方案。这里有个经验Vite 4以上版本通常要求Node 14.18或16如果你的电脑装的是老版本Node项目初始化时会直接报错提示升级遇到这种情况不要硬刚按提示升级Node或者换Vue CLI方式创建都能解决。必装的依赖就四个vue-router路由、axiosHTTP请求、element-plusUI组件库、pinia状态管理。个人项目里购物车数量、用户token这类状态用Pinia管理比到处localStorage取值舒服很多。装完依赖后建议把项目目录结构先搭出来src/router、src/api、src/views、src/components、src/store每个模块放什么一目了然。前端页面的组织逻辑是登录注册页、首页、美食列表页、美食详情页、购物车页、订单确认页、订单中心、后台管理用一套独立布局。后台管理部分可以单独弄一个/admin路由组用子路由管理菜品列表、分类管理、订单列表等页面和前台完全分离。4.2 axios封装与路由守卫axios封装是前端工程质量的分水岭。不要在每一个页面里直接import axios然后发请求一定封装一个统一的请求模块。核心配置是三段baseURL、请求拦截器、响应拦截器。baseURL可以设置为/api开发时配合Vite代理转发到后端地址。请求拦截器里从localStorage或Pinia取出token加到headers.Authorization上。响应拦截器统一处理返回结果service.interceptors.response.use( (response) { const res response.data; if (res.code 200) { return res.data; } if (res.code 401) { router.push(/login); return Promise.reject(new Error(res.msg)); } ElMessage.error(res.msg || 系统繁忙); return Promise.reject(new Error(res.msg)); }, (error) { ElMessage.error(error.message || 网络异常); return Promise.reject(error); } );这段拦截器代码能帮你省掉90%的重复错误处理逻辑后端返回非200状态码前端自动弹提示后端返回401前端自动跳登录页。这是很多人容易忽略的细节——如果不做统一拦截每个页面都要自己判断res.code维护成本翻倍。路由守卫的目的是防止未登录用户访问需要登录的页面比如购物车、个人订单中心、后台管理。在router.beforeEach里判断目标路由的meta.requiresAuth字段有登录状态就放行没有就重定向到登录页并带上redirect参数方便登录后跳回原页面。后台管理还需要判断用户角色是否管理员如果不是就拦截并提示无权限。4.3 跨域代理与联调细节前后端联调跨域是出现频率最高的问题。Vite本地开发时只需要在vite.config.js里配置代理server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api/food/list时Vite开发服务器会把请求转发到http://localhost:8080/api/food/list浏览器层面没有跨域自然就不需要后端配置复杂的CORS了。注意changeOrigin必须设为true否则后端的反向代理钩子可能识别不了真实来源。联调时经常遇到下面几个状况。接口404先检查是前端路径和后端路径不匹配还是代理规则没生效。路径不匹配常见于前端路径写/food/list而后端接口是/api/food/list看Network面板的请求URL一目了然。接口返回401那就不是跨域问题而是token没传或者过期了检查请求头里Authorization对不对。ERR_CONNECTION_REFUSED后端服务压根没启动先回去java -jar或者启动IDE里的Application类。生产环境部署就完全不同了前端构建后产物是dist目录下的静态文件可以扔到Nginx里做静态资源服务再配置API反向代理到后端地址也可以把dist目录直接放到SpringBoot的resources/static里打成一个包访问入口变成同一个端口彻底避开跨域问题。课程设计用第二种方案更省事答辩演示时只需要一个java进程两个请求入口。5. 常见问题排查与避坑实录5.1 MySQL安装与连接踩坑MySQL相关的问题占了环境报错的大头。安装阶段最常见的坑是忘记初始化数据和设置字符集Windows环境下用免安装版zip解压后一定要执行mysqld --initialize-insecure否则后续服务根本启动不起来。连接阶段的高频报错有三个。第一个是Communications link failure或者SSL connection errorMySQL 8默认开了SSL而JDBC连接串没配SSL相关参数会出现连接时断时续或者直接拒绝。解决方案是给连接串加useSSLfalseallowPublicKeyRetrievaltrue其中allowPublicKeyRetrieval在MySQL 8里很关键很多教程里都不提不加就会报Public Key Retrieval is not allowed。第二个是The server time zone value ... is unrecognizedMySQL 8的时区设置需要明确指定连接串上加上serverTimezoneAsia/Shanghai就能解决。顺手把JDBC驱动类名再确认一遍MySQL 8对应com.mysql.cj.jdbc.DriverMySQL 5对应com.mysql.jdbc.Driver用错版本会直接ClassNotFound。第三个是3306端口被占用。Windows下用netstat -ano | findstr 3306查端口占用找到进程号后在任务管理器里结束或者把MySQL的端口改成3307。这种问题几乎每个环境都会遇到一次记住排查命令比瞎重启有用得多。5.2 SpringBoot与MyBatis运行期报错后端运行期最经典的报错是Invalid bound statement (not found)看到这个报错你的第一反应是去检查Mapper接口和XML的对应关系Mapper接口的包路径、接口名、方法名和XML里namespace、id是否全对得上。第二反应是检查target目录SpringBoot打包时经常因为resources过滤配置不对导致mapper/*.xml没有编译到target/classes下。有个简单的解决办法是在pom.xml的build节点下显式声明把src/main/resources下的XML文件包含进打包列表或者直接把XML放到底层resources/mapper目录里。Consider defining a bean of type xxxMapper这个报错多半是忘了加MapperScan。如果Mapper接口分散在多个包下逐个加Mapper注解容易漏我建议直接在启动类上加MapperScan(com.xxx.mapper)一劳永逸。第三类经典问题是端口冲突Port 8080 was already in use改application.yml里的server.port换一个端口或者找到占用进程杀掉。还有一个隐蔽但高频的问题application.yml配置文件里数据库连接的driver-class-name、url、username、password四个配置项缺一不可而且url里如果前面漏了jdbc:mysql://HikariCP启动时会报一堆看不懂的SQLException很多人会在这一步浪费大量时间。看一眼配置文件的英文标点是否有中文逗号也是不可忽视的细节。5.3 拿到jar包后怎么快速还原项目结构网上下了个完整源码或者拿到一个可运行的jar包怎么快速看懂项目结构很多新手拿到jar包习惯直接双击解压结果看到一堆class文件傻眼了。正确做法是先看META-INF/MANIFEST.MF里的Start-Class字段它指向SpringBoot启动类。然后关注三个位置BOOT-INF/classes下是项目的class文件和resourcesBOOT-INF/lib下是全部依赖jar包application.yml就躺在classes目录里。想要还原出可阅读的Java源码可以用IDEA自带的Fernflower反编译器或者独立的JD-GUI、Luyten工具把BOOT-INF/classes整体拖进去class文件会被还原成近似源码的Java代码。之后按照Controller包找接口入口、Service包找业务逻辑、Mapper XML找SQL这个顺序去读整条链路就能理清楚。这里多提醒一句反编译得到的代码仅供学习和理解业务不能直接拿去商业使用或提交成自己的毕业设计该自己写的代码还是要自己写。5.4 面试官对项目的高频追问与准备如果这个项目是你面试时拿来聊的项目有些问题必须先想好答案。MyBatis和JPA怎么选这个问题的关键是说清场景MyBatis适合自定义SQL多、查询复杂的系统推荐排行和标签检索这类SQL用MyBatis写起来直观JPA在简单CRUD上效率高但遇到复杂查询还是得退回到SQL或者JPQL学习成本也不低。第二问通常是一级缓存和二级缓存的区别按我上面说的从SqlSession级别和Mapper级别两个角度回答顺便提一句二级缓存脏数据风险就能把这个问题答扎实。下一问大概率是事务相关。下单接口必须加Transactional原理是保证扣库存、创建订单、计算订单明细这几个操作同时成功或同时失败。老练的面试官还会追问事务为什么会失效列举三种方法里自己catch了异常导致事务感知不到、同类内部方法自调用导致代理失效、方法不是public。提前把这些坑说出来面试官会认为你真的写过项目而不是背了理论。还有一个追问点是你如何防止超卖。顺序执行下单时如果扣库存直接UPDATE t_food SET stock stock - 1 WHERE id ?并发场景下就会有超卖。正确写法是UPDATE t_food SET stock stock - 1 WHERE id ? AND stock 0通过受影响行数判断是否成功没成功就抛“库存不足”。另外还可以提一下乐观锁在食物表加version字段更新时校验版本号。这两个方案讲清楚就比大多数只会说“用synchronized”的候选人有说服力。对于推荐系统本身还要准备好“推荐算法还能怎么优化”这个问题。可以回答引入基于用户行为的协同过滤计算用户间相似度或者把结果缓存到Redis减少数据库压力。不一定真的做出来但思路要清晰这和上面实现的基于标签的内容推荐可以形成递进关系。把下面这张速查表存一下基本上能覆盖这个项目的日常运维和答辩问题问题类型常见报错核心解决手段MySQL连接SSL error / Public Key RetrievalURL加useSSLfalseallowPublicKeyRetrievaltrueMySQL连接serverTimezone不识别URL加serverTimezoneAsia/Shanghai后端启动Port 8080 was already in use换端口或杀进程MyBatisInvalid bound statement检查namespace/id、XML是否打进targetMyBatisbean xxxMapper not found启动类加MapperScan前端启动Node版本过低升级Node或改用Vue CLI联调接口404检查代理是否命中、路径是否匹配联调接口401检查token是否传对、是否过期最后分享一个我自己的习惯。每次拿到这类SpringBootVue的项目我第一件事不是一头扎进代码而是先跑一遍启动脚本、看一眼数据库ER图和README把这个链路的入口和出口摸清楚。读别人的代码或者自己重新梳理项目时从数据库表关系入手往往比从Controller入手快得多。这个习惯帮我避免了很多“代码看了三天一启动全是环境报错”的无效劳动。今天这篇也算是把这套流程完整走了一遍希望你在自己的项目上少踩几个坑。
返回列表