拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Java微信小程序点餐系统:个性化推荐与高并发实战指南

Java微信小程序点餐系统:个性化推荐与高并发实战指南

这个题目放在计算机毕业设计里,乍一看确实有点唬人——又是java、又是web、又是微信小程序,还加了个“个性化推荐”。但把三个小标题拼在一起,说白了就一件事:做一个能在手机上点餐、带智能推荐、同时配一个后台管理系统的完整餐饮订餐平台。前两年我帮人做过类似的项目,自己也踩了不少坑,这篇就把从选题拆解到推荐算法落地、再到底层并发和移动端优化的完整思路写出来。不管你是正在选毕设题目的学生,还是想接手一个点餐类小程序练手的开发者,这篇文章应该能帮你少走很多弯路。

1. 项目拆解与整体设计思路

1.1 三句话拆穿这个“豪华”题目

这个题目表面上有三个项目标题,本质上是同一个系统的三种描述方式。第一个强调“java基于web点餐小程序”,第二个强调“微信小程序智能餐饮推荐与点餐平台”,第三个强调“面向移动端的个性化美食推送与订餐系统”。合并之后,系统的功能边界就很清晰了:用户通过微信小程序浏览菜品、获取个性化推荐、下单点餐;商家或管理员通过Web管理端维护菜品、处理订单、查看统计数据;后端用Java技术栈提供接口,并且在整个系统里塞进去一个“个性化推荐”模块作为亮点。

为什么要强调“个性化推荐”?因为纯点的CRUD点餐系统在毕业设计里太常见了,答辩老师大概率不会给高分。而推荐算法模块既可以展示你的编程能力,又可以展示你对业务场景的理解,性价比非常高。但这里有个忠告:不要一上来就上深度学习、复杂模型。餐饮系统的实际场景是菜品数量有限、用户行为数据稀疏,用基于物品的协同过滤或者简单的标签推荐就足够撑起整个项目,而且代码可控,答辩时也能讲得清楚。

核心用户流程设计成四步:用户打开小程序登录,看到个性化推荐菜品列表,把感兴趣的菜加入购物车,提交订单支付,商家后台收到新订单后开始制作。注意,MVP版本一定要砍掉实时配送、第三方支付对接、会员营销这些重度功能,支付可以做成模拟支付,订单状态可以由后台手动流转。这样做的好处是,三四周就能把核心链路跑通,剩下时间集中精力打磨推荐算法和异常处理。

1.2 技术选型与架构分层

技术栈的选型要兼顾开发效率和答辩体验,我建议这么搭:

后端用Spring Boot 2.7 + MyBatis Plus + MySQL + Redis。Spring Boot生态成熟,MyBatis Plus能把单表CRUD的代码量压到很低,Redis主要用来缓存推荐结果和做库存扣减的原子操作。小程序端用原生微信小程序,不要为了省事用uniapp,因为原生小程序出问题时更容易定位,而且毕业设计演示时直接跑微信开发者工具最稳妥。Web管理端用Vue3 + Element Plus,前后端分离的架构在答辩时比Thymeleaf模板引擎更有说服力。

整个系统按经典三层结构拆分:Controller层负责接收请求参数和返回结果,Service层承载业务逻辑和推荐算法,Mapper层操作数据库。此外我建议把推荐模块单独抽成一个recommend包,不要和订单、菜品这些业务代码混在一起,后续维护和答辩讲模块划分时会轻松很多。

数据库设计是本体的基础,核心表大致如下:

表名说明关键字段
user用户表id, openid, nickname, avatar, preference_tags
category菜品分类表id, name, sort
dish菜品表id, name, category_id, price, image, stock, tags, sales, status
orders订单表id, user_id, total_amount, status, create_time
order_item订单明细表id, order_id, dish_id, dish_name, quantity, price
user_behavior用户行为日志表id, user_id, dish_id, behavior_type, score, create_time
recommend_cache推荐缓存表(可选)user_id, dish_ids, expire_time

user_behavior这张表是整个推荐系统的数据地基,推荐算法能不能跑起来全靠它。所有用户的浏览、收藏、加购、下单行为都要往这张表里写,而且越早开始设计越好,不然后面补埋点非常痛苦。

1.3 MVP功能边界:先做减法再做加法

很多同学做毕业设计容易犯一个毛病:项目还没开工,脑子里已经装了一堆功能——优惠券、秒杀、配送员定位、在线客服。最后结果往往是核心功能做得粗糙,边缘功能也没做完。我建议按照MVP思路,把功能分成必须做、可做可不做、坚决不做三档。

必须做的功能只有这几个:微信登录、菜品分类浏览、个性化推荐、菜品详情、购物车、提交订单、模拟支付、后台菜品管理、后台订单管理、用户行为埋点。可做可不做的包括:评价系统、优惠券、数据统计图表、WebSocket实时订单通知。坚决不做的包括:真实微信支付(需要企业资质和复杂的回调处理)、地图配送、多商家入驻、供应链管理。

这个边界不是拍脑袋定的,而是结合点餐业务的真实链路。用户打开小程序的第一诉求是“看到我今天想吃的菜”,第二诉求是“快速下单”,推荐和点餐必须无缝衔接。如果一开始就把推荐页面做得很复杂,或者把支付流程卡住,用户根本走不通完整链路,项目演示就会出现断点。

2. 个性化推荐模块:从算法到落地

2.1 基于物品协同过滤的简化实现

推荐算法选择上,我强烈建议用基于物品的协同过滤(ItemCF),不要碰基于用户的协同过滤。原因很简单:餐饮场景下菜品数量远小于用户数量,物品相似度矩阵的计算和存储压力小,而且“和你喜欢相似菜品的人也喜欢这个菜”这个逻辑在点餐场景里比“找相似用户”更容易解释。

先定义行为打分规则:浏览菜品记1分,收藏记3分,加购物车记4分,下单支付记5分。这个评分体系不需要那么精确,但要在代码里统一管理。用户每产生一次行为,就往user_behavior表里插入一条带分数的记录。

物品相似度计算采用余弦相似度。先构建一个“用户-物品”评分矩阵,然后对每一对菜品i和菜品j,用它们在共同评分用户上的向量计算余弦值。用代码实现时不需要真的构建二维矩阵,可以用Map嵌套结构省内存。

下面是核心算法的Java实现思路,我用简化代码展示:

public class ItemCFFactory { // 用户行为Map: key为userId, value为Map<dishId, score> private Map<Long, Map<Long, Double>> userItemScores; // 为每个用户构建评分向量后,计算物品相似度 public Map<Long, Map<Long, Double>> buildItemSimilarity() { Map<Long, Map<Long, Double>> simMatrix = new HashMap<>(); List<Long> dishIds = getAllDishIds(); for (Long i : dishIds) { for (Long j : dishIds) { if (i.equals(j)) continue; double dotProduct = 0.0; double normI = 0.0; double normJ = 0.0; for (Map.Entry<Long, Map<Long, Double>> entry : userItemScores.entrySet()) { Map<Long, Double> itemScores = entry.getValue(); double sI = itemScores.getOrDefault(i, 0.0); double sJ = itemScores.getOrDefault(j, 0.0); dotProduct += sI * sJ; normI += sI * sI; normJ += sJ * sJ; } if (normI == 0.0 || normJ == 0.0) continue; double sim = dotProduct / (Math.sqrt(normI) * Math.sqrt(normJ)); simMatrix.computeIfAbsent(i, k -> new HashMap<>()).put(j, sim); } } return simMatrix; } }

实际工程中,这个双重循环在小数据量(比如100道菜)下完全够用,但如果菜品上千,建议只在“被同一用户消费过的物品对”上面做计算,而不是全量两两遍历。相似度矩阵算完之后,可以用一个定时任务每天或者每小时更新一次,存到Redis里面。

给用户做推荐时,根据用户近期产生过行为的菜品列表,逐个去相似度矩阵里取TopN相似菜品,再按“相似度×用户对目标菜品的兴趣分数”加权聚合,过滤掉用户已经下单或明确不喜欢的菜品,最后返回得分最高的前10个。

2.2 冷启动与多路召回策略

推荐系统最头疼的问题就是冷启动。新用户没有任何行为数据,新菜品也没有被用户评分过,如果这时候硬跑协同过滤,结果一定是空列表。我的做法是设计一套“多路召回+加权融合”的策略,不把所有宝押在协同过滤一条路上。

第一路是热门推荐。统计最近一周销量最高、评分最好的菜品,直接作为推荐池。这里要注意用时间衰减防止“老热门永远是热门”,可以按类似Hacker News的评分公式,让新品也有机会往上浮。第二路是根据用户登录时的性别、口味偏好标签做粗筛,比如用户选了“辣”,就把辣味菜品权重提高。第三路是关联推荐,也就是用户正在浏览某个菜品时,推荐和它同分类、同口味标签的菜品,这个逻辑用数据库查询就能实现,不需要复杂模型。

多路召回的结果最后汇总成一个候选池,每路的结果乘以不同的权重取总分。权重可以放到后台“推荐配置”页面,让运营或者使用者能调整。答辩时你如果能解释“我用了多路召回解决冷启动,用协同过滤解决个性化”,老师通常会认可。

下面是一个推荐接口的简单示例:

@Service public class RecommendService { @Autowired private RedisTemplate redisTemplate; @Autowired private DishMapper dishMapper; public List<DishVO> recommend(Long userId, int size) { // 1. 先读缓存 String cacheKey = "recommend:user:" + userId; List<DishVO> cacheList = redisTemplate.opsForList().range(cacheKey, 0, size - 1); if (cacheList != null && cacheList.size() > 0) { return cacheList; } // 2. 缓存未命中则走计算 List<DishVO> result = compute(userId, size); // 3. 回写缓存,设置过期时间1小时 redisTemplate.opsForList().rightPushAll(cacheKey, result); redisTemplate.expire(cacheKey, 1, TimeUnit.HOURS); return result; } }

缓存和计算一定要分开。不然每次用户刷新页面都全量计算相似度矩阵,数据库和CPU都扛不住。实际项目里,我测过Redis缓存方案,接口响应从1秒多降到50毫秒,体验提升非常明显。

2.3 行为埋点与推荐效果验证

个性化推荐能不能work,前提是行为数据要真实、连续、可回溯。我在小程序端和前端Web端都埋了统一的上报逻辑:页面onShow时上报浏览,点击菜品时上报点击,加入购物车时上报加购,下单支付后上报支付。后端提供一个统一的POST /api/behavior接口,接收userId、dishId、behaviorType,根据类型映射成对应分数写入user_behavior表。

埋点上报有一个非常容易漏掉的细节:用户在小程序里切换到后台时,onHide会触发,如果这时候上报了浏览,很可能会误报。我后来统一改成在上报前先检查当前页面记录,加上时间戳去重。连续5秒内的重复浏览行为只算一次。

效果怎么验证?不需要搞复杂的A/B测试,最简单的方法是在后台做一个“推荐命中率”的统计:用户在推荐列表点击的菜品数除以推荐列表曝光数。如果这个数字低于5%,说明推荐结果不符合用户口味,需要调权重或者检查行为日志是否采集正常。我手上的测试账号在连续浏览十几次菜品后,推荐列表已经能明显营造出“越看越准”的效果。

3. 小程序端和Web管理端的实现细节

3.1 小程序端:请求封装、列表加载更多、导航栏适配

微信小程序端的开发,有四个高频问题我觉得值得单独讲一下。

第一个是请求封装。原生wx.request是一个回调函数,如果不做Promise封装,业务代码很容易出现一层套一层的回调地狱。我在utils/request.js里做了统一封装:携带token、统一处理HTTP状态码、业务错误码、全局loading和错误提示。每次请求只需传入url和data,不用重复写那些样板代码。核心思路是:

function request({ url, method = 'GET', data = {}, showLoading = false }) { return new Promise((resolve, reject) => { if (showLoading) { wx.showLoading({ title: '加载中', mask: true }); } wx.request({ url: BASE_URL + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (wx.getStorageSync('token') || '') }, success(res) { // 业务成功判断 if (res.data.code === 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message, icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); }, complete() { if (showLoading) { wx.hideLoading(); } } }); }); }

注意开发环境下,微信开发者工具经常遇到“域名不合法”的报错,解决办法是在工具栏勾选“不校验合法域名、web-view、TLS版本”,但上线前必须把合法域名配到微信公众平台。

第二个是列表加载更多。点餐小程序的菜单列表、推荐列表通常都要分页。我用onReachBottom触发下一页加载,page/pageSize参数由后端定义。这里最怕的是用户在加载过程中疯狂上滑,重复触发请求。我在业务层加了locked标记,请求发出时置为true,返回后再置为false,只有false状态才允许发起新请求。

第三个是顶部导航栏高度适配。这个坑是实打实踩过的:不同机型状态栏高度不一样,尤其全面屏手机,如果用系统默认导航栏,自定义样式就容易顶到状态栏。推荐的做法是用wx.getWindowInfo和wx.getMenuButtonBoundingClientRect拿到胶囊按钮的位置,据此计算导航栏高度和左右边距:

const info = wx.getWindowInfo(); const rect = wx.getMenuButtonBoundingClientRect(); const navBarHeight = (rect.top - info.statusBarHeight) * 2 + rect.height; const statusBarHeight = info.statusBarHeight;

把封装好的值传给自定义导航栏组件,就能在iPhone和安卓机上保持一致。

第四个是监听用户离开小程序。为了给推荐系统积累行为数据,我需要在用户进入后台或离开页面时记录停留时长。理论上有App.onHide和Page.onHide两个维度。App.onHide能监听小程序整体进入后台,Page.onHide能监听页面切换。我的方案是页面onShow时记录startTime,onHide时计算时长并上报,同时App.onHide时再做一次全局兜底,防止用户直接切后台导致部分页面onHide未触发。

3.2 Web管理端:菜品管理与订单处理

Web管理端我用Vue3 + Element Plus做了完整的管理界面,包括登录页、菜品列表、菜品编辑弹窗、订单管理、用户列表、推荐配置。其中菜品管理里的图片上传是一个容易踩坑的点。前端通过Element Plus的Upload组件把图片文件提交到后端,后端用MultipartFile接收,然后保存到服务器本地,并把访问路径存到数据库。

这里有一个开发环境需要注意的事:图片保存路径不能写成绝对路径,否则部署到别的机器就找不到图了。我统一在application.yml里配置upload.path和访问映射,然后通过WebMvcConfigurer注册一个资源映射到/upload/**,这样图片就能通过相对URL访问。

订单管理页面除了展示订单列表,我还加了一个“查看详情”的抽屉,里面列出订单明细和状态。有学弟问我是不是要接一个在线打印小票的功能,我的建议是MVP阶段不要做浏览器直接调打印机,太容易出兼容性问题。可以先用window.print()打印订单详情页,很多管理系统都是这么干的。如果想让生成的文档更整齐,可以用html2canvas把订单详情区域转成图片再打印,这个方案在Web端很稳定。

订单状态流转建议这样设计:待支付→已支付→制作中→已完成。后台可以手动点击按钮切换状态,同时通过WebSocket通知小程序端刷新订单状态,这样用户就能实时看到“商家已接单”的反馈。状态变更的接口务必做幂等校验,不能出现重复提交导致状态乱跳的问题。

3.3 移动端性能优化实战

移动端性能优化是答辩老师经常追问的点,也是实际体验的决定因素。我实践的优化手段按优先级排序大概是这几个。

第一,小程序分包加载。个人主体小程序主包限制是2M,单包不能超过2M,但可以通过分包把菜品菜单、订单页面拆到独立分包里。首页和公共组件放主包,分包只在用户点击时加载。实测分包后首屏加载时间减少三分之一。第二,图片压缩和懒加载。菜品图如果不能控制上传者,就在后端压缩成webp格式,前端用image标签的lazy-load属性,滚动到视口附近才加载。第三,减少setData体积。一些同学喜欢在onLoad时一次性把整个菜品列表setData进去,结果就是在低端手机上卡顿。正确做法是分页请求,一次只setData新增的一页数据,并且把多张图片的url数组按需填充。

第四,骨架屏。推荐页在等待接口返回的间隙,如果是一片白屏,用户很可能直接退出。我给推荐页写了一个简单的骨架屏,用灰色块模拟菜品卡片布局,数据返回后切换成真实内容。这个对观感提升很大,写起来也不复杂。

还有一个容易被忽略的点:小程序请求合并。推荐页需要同时请求推荐菜品的详细信息、用户的口味标签、分类列表,如果每个都单独发请求,网络开销会非常大。我的做法是将这些接口合并成一个 GET /api/home/data 的聚合接口,一次请求返回所有首页数据。接口合并不单是为了性能,也能减少用户流量消耗。

4. 后端高难度细节:WebSocket、并发与数据一致性

4.1 Spring Boot集成WebSocket:配置与避坑

订单状态实时推送是我觉得整个后端最有“技术含量”的部分。用户下单后,商家后台要立刻收到通知,小程序端也应该在订单状态变化后及时显示最新状态。用前端轮询最简单,但不够优雅,而且对服务器压力大。因此我决定集成WebSocket。

先在pom.xml引入spring-boot-starter-websocket依赖。然后写一个配置类注册WebSocket的处理器和拦截器。这里很多新手会搞混:application.yml里不需要特别配置“端点”,但可以配置端口和上下文路径。WebSocket端点地址是在配置类里定义的:

@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(orderWebSocketHandler(), "/ws/order") .setAllowedOrigins("*"); } @Bean public WebSocketHandler orderWebSocketHandler() { return new OrderWebSocketHandler(); } }

我最开始就是用默认的嵌入式Tomcat,连接没问题,但如果项目里用了Undertow,WebSocket就起不来,因为Undertow对标准WebSocket支持方式不一样。建议直接用默认Tomcat起步。

前端小程序里使用WebSocket时,开发环境地址是ws://localhost:8080/ws/order,上线必须是wss://域名/ws/order,并且要在小程序后台配置socket合法域名。另外要注意,如果使用了Nginx反向代理,必须显式设置Upgrade和Connection请求头,否则WebSocket握手会失败。

4.2 JWT登录与拦截器实现

因为小程序端不能用传统的Cookie-Session机制,我采用JWT(JSON Web Token)做用户认证。用户通过微信登录后,后端调用微信的code2Session接口拿到openid,然后生成JWT返回给前端。后续所有需要登录的接口都在请求头加上Authorization: Bearer token。

拦截器核心逻辑很简单:

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录和健康检查接口 String uri = request.getRequestURI(); if (uri.contains("/login") || uri.contains("/health")) { return true; } // OPTIONS请求直接放行 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); // 解析token逻辑 Claims claims = JwtUtil.parseToken(token); request.setAttribute("userId", claims.get("userId")); return true; } response.setStatus(401); return false; } }

这里有两个容易踩的坑。第一个是OPTIONS预检请求,跨域场景下前置请求不带Authorization头,如果不放行,前端请求会直接失败。第二个是token过期,我建议在拦截器里区分“token无效”和“token过期”,过期时返回特定code,让前端自动跳转登录页重新获取token。

4.3 防止超卖与重复下单

点餐系统里最核心的并发问题有两个:菜品库存超卖和用户重复下单。比如一道菜只剩最后一份,两个用户同时提交,数据库如果不做控制,就可能卖出去两份。

解决库存超卖最简单的方案是数据库乐观锁。在dish表加一个version字段,每次扣库存时执行:

UPDATE dish SET stock = stock - 1, version = version + 1 WHERE id = #{id} AND stock > 0 AND version = #{version};

如果更新影响行数为0,说明库存已经不足或者版本冲突,直接提示用户“手慢了”。对于高频秒杀场景还可以用Redis的DECR命令做预扣,但毕设场景用乐观锁已经完全够用。

重复下单问题则是在创建订单时加分布式锁。我用Redis的SETNX实现一个简单的锁,以userId为key设置超时时间,只有拿到锁的请求才能创建订单。另外建议在数据库订单表上增加一个user_id和dish_id的联合唯一索引(同一用户同一菜品在短时间只能创建一个订单)作为最后一道保证。数据一致性的兜底方案就是数据库约束,这一点一定不能省。

还有一个容易被忽略的点:事务问题。订单创建涉及orders、order_item、库存扣减三步操作,需要在一个事务里完成。但Redis扣减是外部操作,不能纳入数据库事务。正确做法是先用Redis做库存预扣,成功后再开启数据库事务,事务失败时把Redis里的库存还回去。整个过程用编程式事务手动控制提交和回滚,比单纯在Service方法上加@Transactional更稳。

5. 常见Bug与排查技巧实录

5.1 小程序端常见问题速查表

我在开发过程中把遇到的高频问题都记下来了,整理成一张表,以后可以直接对照排查。

问题原因解决方案
wx.request请求失败开发工具未勾选“不校验合法域名”工具栏勾选对应选项;上线前配置合法域名
列表加载更多重复触发onReachBottom频繁触发加locked标记,请求未完成时禁止新请求
自定义导航栏顶到状态栏未适配不同机型用getMenuButtonBoundingClientRect计算高度
小程序分享卡片白屏分享路径未配置在onShareAppMessage里配置path参数
图片显示404后端未配置资源映射添加web静态资源映射到上传目录
微信开发者工具无法发给别人试用未上传体验版微信公众平台上传代码,添加体验成员
h5唤起小程序链接无法访问业务域名未配置公众号和后台都配置业务域名
小程序年审问题企业主体等审核执行提前在后台确认年审时间,个人主体没有年审但类目受限
单选框样式丑原生radio不可控自定义单选组件或用CSS覆盖样式

其中“h5唤起小程序”这个问题我在后期加了一个“从H5页面打开小程序”的功能,结果踩了半天坑才发现是因为业务域名没有配置。微信规则是:H5页面必须绑定在公众号的JS接口安全域名下,然后通过wx-open-launch-weapp开放标签才能拉起小程序,不是随便一个网页就能跳的。如果你要做这个功能,记得把H5的域名同时配置到微信公众平台的“网页授权域名”和“业务域名”里,少一个都不行。

5.2 后端与推荐模块排查实录

后端的问题比较集中,我分享三个印象最深的。

第一个是Spring Boot集成WebSocket后连接不上。我一开始以为配置写错了,反复检查配置类,最后发现是项目里依赖了Undertow导致的,换成Tomcat后立即恢复。如果你也遇到类似问题,先看一眼自己的内置服务器。

第二个是推荐结果永远为空。排查代码逻辑没毛病,最后发现是user_behavior表一条数据都没有,因为小程序端的行为上报接口被登录拦截器拦截了,埋点请求没带token,全部返回401。这个问题的教训是:埋点接口必须放行登录拦截,或者在上报逻辑里加上“未登录不丢弃数据,先存本地延迟上报”的处理。

第三个是库存超卖。本地测试时很难复现并发问题,直到我用JMeter模拟100个并发请求同时下单,才发现数据库锁边界没控制好。修复方式就是前面说的乐观锁+分布式锁双重方案,改完后压测通过。

5.3 答辩时的高频提问准备

最后说说答辩准备。这个项目最容易被问的地方就是“个性化推荐是怎么实现的”。我建议你在答辩前把推荐逻辑用一条线讲清楚:行为采集→评分映射→物品相似度矩阵→多路召回加权融合→缓存结果。如果能当场画一下相似度计算的公式,效果会更好。

还有被高频问到的是“怎么保证数据一致性”和“移动端性能优化做了哪些”。这两个问题直接引用第3章和第4章的内容就行。最忌讳的是对系统里不存在的功能说谎,比如老师问你接没接微信支付,你如果没做,就坦诚说“MVP阶段采用了模拟支付,接真实支付需要企业资质”,然后把模拟支付的流程讲清楚,并表示后续可以平滑替换,这样反而会加分。

结尾:一些个人体会

做完这个项目,我最大的感受是毕业设计其实更像一次“项目管理”训练。技术上Spring Boot、小程序、Vue这些框架都有大量资料,真正难的是把推荐算法、实时推送、并发控制这些散点组合成一个能自洽的完整系统。如果你时间紧,建议先跑通核心链路,再做推荐模块,最后补性能和异常处理。

最后再分享一个小技巧:在答辩演示之前,一定要准备一个“带真实行为数据”的测试账号,不要用空数据库演示推荐。我一同学毕设答辩时,现场随机点开推荐页,因为用户没有历史行为,推荐列表被后端兜底成了“热门推荐”,老师一句“这跟个性化有什么关系”直接问住了。如果你提前用一个日常使用过的账号登录,推荐效果会漂亮很多,也能把这个项目的核心竞争力真正展示出来。

返回列表