ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MyBatis+MySQL企业级购物推荐系统实战拆解

SpringBoot+Vue+MyBatis+MySQL企业级购物推荐系统实战拆解 搞企业级项目这么多年我一直觉得SpringBoot Vue MyBatis MySQL这套组合是Java后端最务实的落地配方。前端Vue负责交互体验后端SpringBoot承接业务逻辑MyBatis管持久层MySQL存数据四个环节各司其职中间没有任何过度封装的黑盒子。今天要拆的这个“企业级购物推荐网站管理系统”就是把这套技术栈完整串起来的一个典型案例——它不只是一个能增删改查的CRUD demo还带了推荐引擎、用户行为采集、后台管理和订单闭环真正把“电商网站”从页面到数据库完整落地了。这个项目源码适合谁三类人一是正在做毕业设计或课程项目的学生需要一套结构清晰、能讲明白原理的完整系统二是想从“会写接口”进阶到“能扛项目”的Java全栈开发者这套代码里的模块划分、接口设计、异常处理值得细看三是中小型团队想快速搭建电商业务原型直接在这个骨架上去扩展营销、库存、支付比从零开始省太多事。我自己在帮团队搭内部商城系统时验证过这套架构的实际承载能力——支撑日活几万的业务没有问题关键在于你愿不愿意把细节抠到位。下面我会从架构选择、数据库设计、推荐逻辑、前后端联调到避坑实录把整个项目从头到尾拆一遍。1. 项目整体架构与选型思路1.1 核心需求拆解购物推荐网站到底在做什么先别急着看代码拿到一个项目先问自己业务方要的到底是一个“能买东西的网站”还是一个“能把东西卖出去的网站”这两个诉求差很远。标题里写了“企业级”“购物”“推荐”本质上包含了三条业务主线第一商品浏览与检索。用户进网站要能看分类、搜商品、看详情、查库存这里的核心不是“能查出数据”而是“海量商品下查询不能慢”。所以数据库层面要有合理的索引查询层要做缓存接口层要做分页。第二交易闭环。购物车、下单、订单状态流转这是电商系统的地基。没有这块前面商品展示得再漂亮也只是个商品画册。订单状态怎么设计、库存怎么扣减、支付回调怎么处理这些都是企业级系统里藏得很深的坑。第三推荐系统。这是“推荐”二字的核心也是最容易做砸的地方。真正可落地的方式不是上一套TensorFlow而是把用户点击、加购、购买行为记录下来基于标签和协同过滤做商品召回再排序输出。这套项目里的推荐模块走的就是这个务实的路子后面第三章我会详细拆。除了这三条主线后台管理也是企业级系统的必备项——商品上架下架、订单处理、用户管理、推荐规则配置。我见过太多项目把精力全砸在前台页面后台靠手工改数据库这在真实业务里完全跑不通。1.2 技术组合为什么是SpringBoot Vue MyBatis MySQL先聊技术选型这里面的每项选择背后都是有理由的不是“别人用我也用”。SpringBoot解决的是后端开发的效率问题。它把Spring那套繁琐的XML配置干掉内置Tomcat一个main方法就能启动整个服务。更重要的是它的生态——Spring Security做认证授权、Spring Data Redis做缓存、Spring Cloud做微服务以后项目要长胖每块肌肉都有现成的锻炼方案。购物网站这种业务核心是想清楚业务逻辑而不是浪费时间在配置Bean上SpringBoot就是让你把精力花在刀刃上。Vue在前后端分离架构里胜在“渐进式”三个字。项目初期可以用来做简单的页面渲染项目复杂后可以引入Vuex做状态管理、Vue Router做路由控制还可以配合Element UI这类组件库快速搭后台管理界面。相比React的学习曲线Vue对大多数后端出身、要兼前端技术的开发者更友好模板语法和指令系统基本看一遍就能上手。MyBatis在持久层的位置可以说是一个“半自动的灵活派”。它能让你手写SQL来精细控制每一条查询也能用动态SQL拼接解决复杂的搜索条件。和Hibernate这种全自动ORM比MyBatis在复杂查询和性能调优上有明显优势——电商系统里经常要搞多表联查、报表统计全自动ORM在关联查询上让你想死的心都有MyBatis直接写SQL效率最高也最可控。这套项目没用MyBatis-Plus我觉得是刻意为之因为Plus虽然写CRUD快但封装太多后很多人连SQL都不写了这对理解底层没好处。MySQL选择它是从成本、稳定性和生态三个维度考虑的。企业级项目对数据一致性要求高MySQL的InnoDB引擎支持事务和行级锁能保证订单和库存操作不出一致性问题。而且现在MySQL 8.0在窗口函数、CTE、JSON处理上的能力已经很强做推荐的数据统计、行为分析完全够用。团队里随便找人就能维护省下的是隐性人力成本。这套组合还有一个隐形的优势招聘市场上这套栈的人才储备最充足出了问题Stack Overflow上一搜一大把解决方案。技术选型不单是技术问题更是团队效率问题。2. 数据库设计与MyBatis核心机制2.1 核心表结构规划从商品到推荐日志的完整链路数据库设计是整个项目里最不能偷懒的部分因为后面所有代码都是在给表结构打工。我见过太多项目前期表设计随意后期需求一变更代码改得跟地震现场一样。这套项目的表设计我认为是合格的核心表可以分成四组用户与权限域user用户表字段含手机号、密码、昵称、头像、状态、role角色表、user_role用户角色关联表。为什么不直接在用户表里加一个role字段因为企业级系统的权限往往是多角色模式——一个用户可能既是普通会员又是运营人员拆成关联表才符合“用户-角色-权限”的RBAC模型后面要扩展权限点也方便。商品与分类域category分类表支持父子级用parent_id自关联、product商品表字段包含标题、副标题、主图、详情、价格、库存、销量、product_skuSKU表承载规格组合比如颜色、尺码。注意商品表和SKU表是分开的这个很重要——一个商品有多个规格每个规格价格、库存都不同如果不拆表库存管理、订单明细都会乱套。交易域cart购物车表、orders订单主表记录订单号、总金额、状态、下单时间、order_item订单明细表记录每件商品的快照信息。订单表记录商品快照是很多新手容易忽略的——用户下单后商品改了价格和名称历史订单必须还是当时的快照这是财务合规的要求做电商一定不要省这张表。行为与推荐域user_behavior用户行为日志表字段含用户ID、商品ID、行为类型、时间戳、recommendation_log推荐日志表记录给用户推荐了什么商品、用户有没有点。这两张表是这个项目区别于普通电商系统的点睛之笔。没有行为数据推荐系统就是个空壳没有推荐日志你永远无法评估推荐效果是好是坏。表与表之间的主外键关系也要理清楚。我的建议是数据库层面外键约束可以不用加太多但应用层的逻辑外键必须严格维护——大厂生产环境普遍禁用数据库物理外键因为高频写入时会带来锁竞争和性能瓶颈但表关联字段比如product_id一定不能省索引一定要建。2.2 手写SQL的掌控力MyBatis映射与动态SQL表结构定了接下来就是持久层。MyBatis的核心理念是“SQL和Java代码分离”将SQL写在Mapper.xml文件里避免了在Java代码里拼字符串那种灾难般的可读性。这个项目里我建议重点看三块内容结果映射。数据库字段是create_timeJava属性是createTime这种下划线与驼峰的映射MyBatis提供了一个开关mapUnderscoreToCamelCase建议在配置文件里打开。但复杂的多表查询就需要手写resultMap了。比如商品详情页需要把商品信息、分类名称、SKU列表一次性查出来用resultMap做嵌套映射比在Java里for循环拼装高效得多也更能体现你对数据访问层的控制力。动态SQL是MyBatis的灵魂。电商系统的商品搜索条件是极其灵活的——可能按分类查可能按价格区间查可能按关键词模糊查还可能是多个条件组合。如果每个条件组合都写一条SQL那代码量会膨胀到无法维护。MyBatis的where标签配合if标签可以完美解决这个问题。select idsearchProducts resultTypecom.example.entity.Product SELECT * FROM product where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR subtitle LIKE CONCAT(%, #{keyword}, %)) /if if testminPrice ! null AND price #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if /where ORDER BY choose when testsort price_ascprice ASC/when when testsort price_descprice DESC/when otherwisecreate_time DESC/otherwise /choose /select这个小片段基本覆盖了商品列表页的所有需求。where标签会自动去掉第一个条件前面的AND不用操心SQL语法拼接的边界问题。注意排序字段这里用了choose做白名单映射千万不要直接接收前端传的排序字段去拼接SQL这是一个极其危险的SQL注入入口。TypeHandler的自定义是MyBatis里比较进阶的用法这套项目里有使用场景。比如user_behavior表里的behavior_type字段在Java里希望用枚举数据库里用VARCHAR默认的TypeHandler只能帮你处理基础类型这时实现一个自定义的EnumTypeHandler就能让代码更优雅。又比如商品详情页大段的富文本用JSON类型存储MyBatis查出来是String你也可以写一个JsonTypeHandler自动序列化/反序列化。说白了TypeHandler就是“数据库字段类型”与“Java属性类型”之间的翻译官需要什么格式就自己定义什么翻译规则。再说说MyBatis的缓存这里值得踩一次坑。一级缓存是SqlSession级别的同一个会话里查询相同SQL直接走缓存看起来很美但实际上如果你在一个长事务里先查后改再查拿到的可能是脏数据。所以一级缓存默认开着就好别去动它二级缓存跨SqlSession的建议直接关闭因为企业级多节点部署时每个节点的本地缓存无法同步商品价格这种敏感数据如果缓存不同步用户看到的价格混乱就是事故。这个项目里我测试过把二级缓存打开后出现商品信息更新不及时的问题关掉后一切正常——缓存不是越多越好是越“可控”越好。3. 推荐模块的落地实现不玩花哨只讲效果3.1 两步走推荐策略标签匹配召回 行为相似度排序推荐系统是这个项目的核心亮点也是我当初拿到源码后最先研究的部分。这套项目的推荐逻辑没有用复杂的深度学习模型而是走了“规则召回 协同过滤排序”的经典组合路线对于大部分业务场景已经足够。第一步基于内容的标签召回。product表里我给每个商品保留了一个tags字段存逗号分隔的标签比如“休闲、透气、夏季”。用户注册时系统让用户选择兴趣标签或者根据他历史上点击过的商品去提取高频标签。然后推荐查询的逻辑就清晰了找出与用户兴趣标签匹配度最高的前50个商品作为候选集。SELECT product_id, SUM(CASE WHEN tag IN (#{tagList}) THEN 1 ELSE 0 END) AS match_score FROM product_tag WHERE tag IN (#{tagList}) GROUP BY product_id ORDER BY match_score DESC LIMIT 50为什么先做召回而非直接排序因为全量商品可能有几万条在几万里精确算推荐顺序性能扛不住、逻辑也复杂。先粗筛出50个候选再用下面的逻辑精细排序这是工业界推荐的经典“漏斗”思路。第二步基于用户行为的协同过滤排序。协同过滤的核心思想不复杂面前有几十个候选商品怎么排看“和你行为最相似的其他用户”对它们的评价。比如用户A和你一样都浏览过、收藏过某个跑步机那A下单过的另一款瑜伽垫推荐给你的优先级就比排在列表末端的商品高。实现上我建议项目里保存一张“用户相似度矩阵”的缓存表定时计算而不是每次请求实时算。计算相似度最简单的公式是余弦相似度相似度(A, B) A和B共同交互过的商品数 / (A交互过的商品总数 * B交互过的商品总数 的开方)逻辑上简单直观但效果确实稳定。协同过滤刚上线时容易踩的坑是数据稀疏——新用户都没什么行为记录相似度算出来全是0推荐结果变成空的或随机的。所以一定要配合兜底策略当候选集不足或相似度为0时直接走“热门商品榜”按销量、评分综合排序顶上。这就叫“冷启动解决方案”——对新用户让热门商品先服务他等行为积累起来了再让个性化推荐逐渐接管。3.2 用户行为埋点推荐效果的评估前提推荐模块能不能产生价值前提是能不能采集到真实的用户行为数据。很多项目把推荐做成一次性静态列表那就完全失去了“个性化”的意义。这个项目里有个值得借鉴的设计前后端协议里约定了一个“曝光-点击-加购-下单”四层行为上报模型。前端在商品卡片进入用户视口时上报一条impression事件带上推荐位ID和商品ID用户点击时上报click事件加购上报cart事件下单支付成功上报order事件。后端的/api/behavior/report接口收到这些事件后统一写入user_behavior表。有了这套埋点数据你能做的事情就太多了不仅可以实时调整推荐权重还能在后台看到每个推荐位的“曝光-点击转化率”“点击-下单转化率”。我经常用两个指标来量化推荐效果点击率(CTR)是否高于非推荐的常规位以及推荐订单占比是否逐步提升。如果投了一周推荐发现这两个指标纹丝不动那说明推荐逻辑或者埋点数据一定有问题需要回头排查。这里有个项目组经常手忙脚乱的问题行为日志表的数据量会增长得极快。一台测试服务器一天可能产生几十万条行为记录如果不处理就是性能灾难。务实的做法是分表存储——按月份做分区表定期把三个月前的数据归档到历史库。这篇项目里MySQL的PARTITION BY RANGE做了按月分区查询时按时间条件自动走对应分区数据量大的时候也不至于把单表拖崩。4. SpringBoot Vue前后端打通的关键配置4.1 后端三板斧跨域、鉴权、统一返回体前后端分离架构最难的不是各自开发而是“打通”。这套项目里后端SpringBoot对前端Vue的支持我认为是教科书级别的重点看三块配置。跨域问题CORS。开发环境下Vue跑在localhost:8080后端API跑在localhost:8081不同端口就是跨域浏览器默认会拦截。解决方案不是在前端配代理就万事大吉生产环境部署到Nginx后一样会踩跨域坑而是在后端做全局CORS配置。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(ConfigurerRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOrigins(*)和allowCredentials(true)不能同时使用这是浏览器安全策略的限制allowedOriginPatterns可以绕开这个限制兼容带Cookie的请求。无状态JWT鉴权。企业级系统不可能每个请求都查一遍Redis里的登录态那样性能太拉胯。方案是JWT——用户登录成功后后端发放一个带签名和过期时间的Token前端把它存在localStorage里之后每个请求在Header里带上Authorization: Bearer token后端用一个拦截器解析Token并校验签名就能确认用户身份。这个项目的拦截器逻辑值得细看核心代码不复杂public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { Claims claims JwtUtil.parseToken(token.replace(Bearer , )); // 将用户ID放入请求上下文 request.setAttribute(userId, claims.get(userId)); return true; } response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } }在SpringBoot里通过InterceptorRegistry注册同时用excludePathPatterns放行登录、注册、商品列表、商品详情等不需要鉴权的接口。注意放行清单要维护好漏掉一个公开接口就会导致前端疯狂报401。统一返回体。这个项目后端不是把数据裸返回而是包了一层ResultT结构code状态码、message提示信息、data业务数据。这是一个很小的设计但能省掉大量前后端联调的撕扯。前端拿到响应后先判断code 200再进行业务处理其他情况统一弹错误提示。如果没有这层包装后端随便抛一个异常前端解析到的就是你不想让用户看到的堆栈信息。4.2 前端工程化路由守卫、Axios封装与权限控制Vue这端的核心不是页面写得多漂亮而是工程化做得到不到位。这套项目里Vue部分有几个值得学习的地方。Axios实例的统一封装。很多新手在每个组件里单独import axios调接口结果要做全局错误处理时发现无从下手。正确的做法是创建一个request.js封装一个带请求拦截器、响应拦截器的Axios实例import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求前自动携带Token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应统一处理 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) if (res.code 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(new Error(res.message)) } return res.data }, error { Message.error(error.message) return Promise.reject(error) } ) export default service注意拦截器里401的处理Token失效时自动清除本地登录态跳转到登录页。这是企业级系统的标准行为。Vue Router动态路由。这个项目的权限控制做到了路由层面——管理员和普通用户登录后看到的菜单、可访问的页面不一样。实现方案是登录后根据用户角色从后端动态获取可访问路由表用router.addRoutes()动态注册。比如管理员可以看到“商品管理”“订单管理”“用户管理”普通用户只能看“我的订单”“商品浏览”。这种“角色-权限-路由”三层联动的设计是这个项目在“企业级”三个字上立得住的理由。路由懒加载与打包优化。重点聊一下m3u8和视频播放这些不在标题里的场景——这套系统的商品详情页把主要精力花在性能和体验上组件层面全部用() import(/views/xxx.vue)形式实现路由懒加载一个页面一个独立JS块用户打开首页时不会加载后面的5个页面代码首屏加载速度能差出好几倍。我在本地测试过不做懒加载时首屏白屏时间在3秒以上做了懒加载加CDN缓存能压到1秒以内。前端性能优化懒加载是性价比最高的一步。5. 从0到1完整搭建实操记录5.1 环境准备与项目初始化拿到源码后第一件事不是双击运行而是把环境整理干净。我平时搭建类似项目的标准环境组合如下组件版本要求备注JDK1.8建议8u191以上SpringBoot 2.x对JDK8支持最好Maven3.6镜像建议配阿里云不然依赖下载等到怀疑人生MySQL5.7 / 8.08.0注意认证插件坑参考第六章Node.js1416/18都行Vue CLI 4要求14以上IDEIDEA / VS Code后端推荐IDEA前端VS Code足够初始化项目的步骤可以走一遍# 1. 创建数据库并导入初始化SQL mysql -u root -p sql/init.sql # 2. 后端项目启动项目根目录 mvn clean install -DskipTests mvn spring-boot:run # 3. 前端项目依赖安装与启动 cd frontend npm install npm run serve这里特别强调npm install这一步的坑。Vue项目如果package.json里的依赖版本和Node版本不匹配会报各种gyp ERR或Module not found错误。实测下来最稳的做法是先删掉node_modules目录和package-lock.json然后重新安装。如果你在npm install时碰到Python not found之类的报错多半是node-sass这个老古董在编译解决办法是把依赖从node-sass换成sassdart-sassAPI基本兼容但构建速度快很多。5.2 核心配置与踩坑点后端启动前重点检查application.yml里的三个配置spring: datasource: url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: trueserverTimezoneAsia/Shanghai必须加不然MySQL 8.0下JDBC驱动会报时区错误。useSSLfalse必须加不然本地开发环境没有SSL证书会报SSL连接错误。driver-class-name在MySQL 8.0下必须是com.mysql.cj.jdbc.Driver注意是cj和5.7的com.mysql.jdbc.Driver不同。MyBatis的配置里有个细节mapper-locations指的是存放XML文件的路径type-aliases-package定义了实体类的包名。这两个配置错了启动时十有八九会报Invalid bound statement (not found)意思是Mapper接口找到了但XML里的SQL没绑定上。排查思路很简单检查接口名和XML的namespace是否一致检查方法名和XML里的id是否一致检查mapper-locations路径是否覆盖到了XML文件。5.3 前后端联调与打包部署本地开发时前后端联调后端接口地址如何让前端访问这个项目的做法是前端根目录创建.env.development文件专门存开发环境变量。VUE_APP_BASE_API http://localhost:8081/api然后在vue.config.js里配置devServer代理把/api开头的请求都转发到后端服务这样前端代码里写的请求路径就是干净的/api/product/list不需要写死某个IP地址。顺带解决跨域问题。module.exports { devServer: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } }生产环境的部署我推荐两种方案。方案一是前端打包后用Nginx托管静态文件后端SpringBoot独立跑在服务器Nginx配置反向代理把/api转发到后端端口方案二是把前端打包后的dist目录复制到SpringBoot的resources/static下打成单个jar包运行。方案二适合小项目和个人服务器省一台机器方案一适合企业级多应用场景前后端可以独立扩缩容。# 前端打包产物 npm run build # 产物在 frontend/dist 目录 # jar包构建 mvn clean package -DskipTests # 产物在 target/mall-server.jar # 用Nginx托管前端 反向代理后端 server { listen 80; server_name your-domain.com; # 前端静态资源 root /opt/mall/dist; index index.html; # 刷新页面时避免404 location / { try_files $uri $uri/ /index.html; } # API反向代理到后端 location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有个前端路由的大坑必须讲Vue Router在history模式下直接访问/product/123的URLNginx会去找这个路径下的物理文件找不到就404。解决方案就是上面配置里的try_files $uri $uri/ /index.html——把请求全部回退到index.html让前端路由接管。这个坑我在第一次部署Vue项目时踩过排查了几个小时才意识到是Nginx配置问题。6. 常见问题与排查技巧实录6.1 高频报错我把现场还原了一遍项目跑不起来90%的问题集中在几个固定位置下面是我把高频问题整理成的速查表。报错信息可能原因解决方案Access denied for user rootlocalhost数据库用户名或密码错误检查application.yml确认账号密码与MySQL实际一致Communications link failureMySQL服务没启动或端口被占用service mysql start检查3306端口Unknown database mall初始化SQL没执行先CREATE DATABASE mall DEFAULT CHARSET utf8mb4再导入SQLInvalid bound statementMapper接口与XML映射失效检查namespace、方法id、mapper-locations路径Table xxx doesnt exist表名大小写问题Linux区分大小写表名统一小写查询语句保持一致npm ERR! code ELIFECYCLE前端依赖版本冲突删除node_modules和package-lock.json重新安装TypeError: Cannot read property xxx of undefined后端返回的数据结构不对用Postman先调接口确认返回Result结构里data字段CORS policy: No Access-Control-Allow-Origin跨域配置未生效检查后端CorsConfig是否注册或Nginx是否加了跨域头特别提醒一个问题MySQL 8.0的认证插件。如果你用MySQL 8.0创建用户默认认证方式是caching_sha2_password而老版本JDBC驱动不支持这种插件会报Public Key Retrieval is not allowed。两种解决办法一是JDBC连接URL加allowPublicKeyRetrievaltrue二是创建用户时指定IDENTIFIED WITH mysql_native_password BY password。推荐第二种一次配好后面所有工具连接都不会遇到麻烦。6.2 性能优化与项目心得这套项目如果要做性能压测首当其冲的瓶颈大概率在数据库。user_behavior表写入太频繁、商品搜索的模糊匹配太慢都会让接口响应时间直线上升。我的优化思路是按优先级排索引优先。凡是出现在WHERE、ORDER BY、GROUP BY、JOIN ON后面的字段优先检查有没有索引。商品表的category_id、price行为表的user_id、product_id、create_time这些字段建好索引查询性能立竿见影。注意不要所有字段都加索引写多读少的表索引多了反而降低插入效率。缓存兜底。商品分类、热门商品列表这类高频读但低频改的数据缓存到Redis里设置5-10分钟的过期时间能挡住80%的重复查询压力。接口层加缓存要小心数据一致性更新商品后要主动删缓存而不是等它过期避免用户看到旧价格。分页防深翻。MySQL深分页比如跳过10万条取第100001条慢得离谱因为要先把前10万条全扫描一遍。解决思路是“标签记录法”——记住上一页最后一条的ID或时间戳下一页直接WHERE id #{lastId} ORDER BY id DESC LIMIT 20这种方式性能是稳定的不会随着翻页变深而退化。这个项目的商品列表翻页如果数据量大建议改成这种模式。最后聊一点我做这类项目的心得。这个购物推荐系统从技术难度上说不是那种需要顶尖算法能力的项目它考验的是“把一件事完整地想清楚”的能力数据怎么流转用户怎么通过一次点击最终形成一笔订单推荐怎么真正产生价值而不是做摆设。我带着这套源码去给团队做内部分享时最有价值的讨论点往往不是某一行代码怎么写而是“如果并发翻十倍哪里会先崩”“如果商品数量翻百倍推荐策略要不要换”。把这些想明白项目才会从“能跑”变成“能抗住”这也是“企业级”三个字的分量所在。如果你准备拿这个项目去面试我建议把推荐模块的冷启动策略、MyBatis动态SQL的拼接原理、JWT的Token刷新方案都吃透。面试官随便挑一个问你都能讲出设计取舍和踩坑经历这比背十篇理论八股都有说服力。项目不是写完就完了把它讲清楚才是它真正开始为你创造价值的时候。
返回列表