ARTICLE DETAIL

资讯详情

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

微信小程序+Java后端:个性化推荐点餐平台设计与实现全解析

微信小程序+Java后端:个性化推荐点餐平台设计与实现全解析 这套题目我一看就很有共鸣——每年毕业设计季总有大量同学在“微信小程序 Java后端”这个组合上反复纠结题目看着热闹落地时却处处是坑。这个标题把三个关键词串起来了个性化推荐、点餐平台、微信小程序背后本质是一个“移动端美食内容分发 交易闭环”的系统工程。本文我不打算给你堆概念而是以实际做过这类项目的角度把技术选型、推荐算法落地、前后端联调、冷启动处理这些最容易卡住人的环节完整拆给你看。我们一步一步说清楚。1. 整体设计与技术选型思路1.1 为什么是“微信小程序 Java Web”而不是纯App或H5先聊一个最实际的问题这套系统为什么要把前端放在微信小程序里而不是做一个独立的Android/iOS App或者干脆用H5网页。从毕业设计和中小型商业项目两个角度看微信小程序几乎是当前最优解。第一微信自带流量分发和登录体系用户不需要额外注册账号wx.login()拿到code后端调微信接口换openid一套身份认证就完成了省掉了短信验证码、邮箱绑定的全套流程。第二小程序天然适配移动端场景点餐这种“打开即用、用完即走”的需求和微信“去中心化”的使用习惯完全吻合。第三从开发成本看小程序原生开发或者用uni-app做跨端学习曲线比原生App平缓得多前后端分离的调试方式也和Web开发高度一致。后端选Java WebSpring Boot的理由同样直接稳定、生态成熟、岗位需求大。Spring Boot简化了配置内置Tomcat用一套注解就能把Controller、Service、Mapper串起来。我在实际开发中用Spring Boot 2.7.x MyBatis-Plus MySQL 8.0这套组合从零搭建一个带用户体系、菜品管理、订单流程、评价系统的最小后端两天就能跑通主链路。如果你对Spring的IOC、AOP还有印象做数据权限、日志记录、事务管理都会顺手很多。这套方案解决的痛点也很明确移动端用户能快速浏览推荐菜品一键下单商家能通过后台管理菜品和订单推荐系统能根据用户历史行为动态调整推送内容。适合三类人参考准备做毕业设计的学生、想快速搭建餐饮小程序MVP的创业团队、以及刚接触全栈项目想找完整案例的初级开发者。1.2 个性化推荐算法选型的现实考量题目里“个性化推荐”这四个字是很多人最头疼的部分。先泼一盆冷水不要一上来就上深度学习模型。在毕业设计和中小型项目中你没有海量行为数据也没有GPU训练环境真实的用户可能就几千个这时候用复杂的算法只会给自己挖坑而且论文答辩时也讲不清楚。更务实的路线是“协同过滤 基于内容的混合推荐”。协同过滤分两种基于用户的User-based CF和基于物品的Item-based CF。用户协同过滤的核心逻辑是“和你口味相似的人喜欢什么就推荐给你什么”物品协同过滤则是“你喜欢的菜品和它相似的菜品也推给你”。在实际的餐饮点餐场景里我更推荐以基于物品的协同过滤为主辅以基于用户的协同过滤做兜底。为什么餐饮场景有一个特点用户口味会漂移今天想吃辣明天可能想吃清淡的而且用户历史行为通常稀疏——大多数人一周只点几次餐。基于物品的协同过滤可以通过菜品本身的口味标签、类别、价格区间做相似度计算即使新用户只有一两次点击记录也能快速给出合理推荐。后面我会给你一套具体的打分公式和代码实现照着改就能跑通。1.3 项目目录结构与核心模块划分用我习惯的工程结构给大家一个直观参考smart-canteen ├── backend │ ├── src/main/java/com/example/canteen │ │ ├── controller // 接口层登录、菜品、订单、推荐 │ │ ├── service // 业务层订单流程、推荐逻辑 │ │ ├── mapper // 数据访问层MyBatis-Plus接口 │ │ ├── entity // 实体类User、Dish、Order等 │ │ ├── common // 统一返回结果、异常处理、工具类 │ │ └── config // 跨域、拦截器、微信配置 │ └── src/main/resources │ ├── application.yml │ └── mapper/*.xml ├── miniapp // 微信小程序前端 │ ├── pages │ │ ├── index // 首页推荐流 │ │ ├── category // 分类点餐 │ │ ├── cart // 购物车 │ │ ├── order // 订单列表与详情 │ │ ├── profile // 个人中心 │ └── utils // request封装、工具函数 └── sql └── canteen.sql // 建表脚本模块划分的原则一句话前端按页面拆后端按业务域拆。推荐模块单独拆一个recommend包出来不要和订单业务耦合在一起否则后面你想调算法参数的时候会痛不欲生。2. 核心细节解析与实操要点2.1 用户登录与身份识别的完整链路微信小程序的登录流程很多同学第一次接触会懵我画了一个最简链路小程序端调用wx.login()拿到临时code。小程序把code发送到后端/api/auth/login。后端拿着code请求微信接口https://api.weixin.qq.com/sns/jscode2session带上小程序的appid和secret换回openid和session_key。后端用openid查数据库如果不存在就自动注册一个新用户然后生成一个自定义token我习惯用UUID也可以引入JWT返回给小程序。小程序把token存到wx.setStorageSync之后的每次请求都在 header 里带上Authorization: token。这里有几个容易踩的坑我逐个说清楚。第一个坑code只能使用一次。code有效期只有五分钟而且用完就作废。如果你在调试时发现第二次请求就报invalid code多半是前端重复调用了wx.login()。第二个坑session_key不要下发到前端。这是微信官方安全要求session_key是用于解密手机号、敏感信息的密钥只能保存在后端。很多教程图省事把session_key直接返回这样一旦被截获用户数据就有泄露风险。第三个坑接口域名必须是HTTPS且在小程序后台配置。在开发工具里可以勾选“不校验合法域名”跳过但真机预览必须在小程序管理后台把后端域名加入 request 合法域名列表。我用阿里云的一台轻量服务器部署后端用Certbot申请了免费SSL证书前后折腾了大半天才搞定域名校验。做毕设的同学如果没条件买域名可以用内网穿透工具临时解决但正式提交答辩前建议还是部署到云服务器上。登录这一步的直接产出是一个user表我给你一个参考DDLCREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid, nickname varchar(50) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, gender tinyint(1) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;openid一定要加唯一索引因为它是用户在微信生态里的“身份证”一个用户一个openid不可重复。2.2 小程序端请求封装与状态管理小程序原生开发没有 axios但我们可以封装一个类似 axios 的 request 工具把 baseURL、token注入、错误处理、加载状态统一管起来。这是我的utils/request.js精简版const BASE_URL https://your-domain.com/api; function request(path, method GET, data {}) { return new Promise((resolve, reject) { const token wx.getStorageSync(token); wx.request({ url: ${BASE_URL}${path}, method, data, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : , }, success(res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { // token过期重新登录 wx.removeStorageSync(token); login().then(() resolve(request(path, method, data))); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); }, }); }); } module.exports { request, login };注意这个401自动重试的写法实际体验差别很大。微信小程序的token不像Web端天然有session机制token过期后用户懵懵懂懂继续点如果不做静默重登录用户只会看到一堆报错弹窗然后给差评。状态管理方面小程序原生没有Vuex/Pinia我一般用两种方式全局数据量小的直接用getApp().globalData需要跨页同步的比如购物车徽标数量配合wx.setStorageSync做持久化。也可以用官方提供的mobx-miniprogram插件但对于点餐这种体量GlobalData Storage已经够了别过度设计。2.3 数据库设计从菜品到订单再到行为埋点数据库设计好坏直接决定推荐算法能不能落地。除了上面说的user表核心表我给你们一一列出来。菜品表dishCREATE TABLE dish ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, category_id bigint(20) NOT NULL COMMENT 分类id, price decimal(10,2) NOT NULL, image varchar(255) DEFAULT NULL, description varchar(500) DEFAULT NULL, tags varchar(200) DEFAULT NULL COMMENT 口味标签英文逗号分隔, sales_count int(11) DEFAULT 0 COMMENT 销量, rating decimal(3,2) DEFAULT 5.00 COMMENT 平均评分, status tinyint(3) DEFAULT 1 COMMENT 1上架 0下架, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;tags字段是给推荐算法用的关键原料比如“微辣、川菜、荤菜、下饭”方便后面做基于内容的相似度计算。实际项目里可以把标签拆成dish_tag关联表但对毕设或小项目来说一个文本字段加逗号分隔完全够用。订单相关表order_info订单主表和order_item订单明细表。订单主表记录下单用户、总金额、状态待支付/已支付/已完成/已取消明细表记录每道菜的数量和价格相当于下单那一刻的“快照”。为什么要快照因为菜品价格可能调整如果只存菜品ID后续改价会让历史订单金额对不上。评价表evaluation记录用户对菜品的评分1-5分和评价内容。这张表是协同过滤的评分数据来源所以一定包含三个关键外键user_id、dish_id、score。行为表user_behavior记录用户浏览、点击、收藏、加入购物车等行为。推荐系统做召回时单一评分数据往往稀疏而浏览/点击行为产生得更频繁用它做降级策略再合适不过。字段可以设计成CREATE TABLE user_behavior ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, dish_id bigint(20) NOT NULL, behavior_type tinyint(3) NOT NULL COMMENT 1浏览 2收藏 3加购 4下单, weight int(11) DEFAULT 1 COMMENT 行为权重, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_dish (user_id, dish_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;行为权重在推荐打分时会用到比如下单行为的权重是浏览的5倍。这个在原理上很好理解下单说明用户是真的喜欢浏览可能只是随便看看。3. 个性化推荐模块的设计与实现3.1 基于标签的菜品相似度计算前面我提过这个项目我会以基于物品的协同过滤为主。但纯基于物品的协同过滤依赖“用户-物品”评分矩阵的稀疏计算在数据量不足的毕设场景里可以直接用标签相似度做一个快速版本。具体做法给每道菜打标签比如“辣”、“清淡”、“川菜”、“粤菜”、“素食”、“硬菜”等然后为每道菜生成一个标签向量。两个菜品的相似度用余弦相似度公式计算cos(θ) (A · B) / (|A| × |B|)这里我用一个简单的例子说明。假设菜品A的标签向量是{辣:1, 川菜:1, 荤菜:1}菜品B的标签向量是{辣:1, 川菜:1, 素菜:1}菜品C是{清淡:1, 粤菜:1, 素菜:1}。A和B有两个共同标签A和C没有共同标签所以A和B的相似度明显更高。在Java里这个计算用HashMap就能实现不需要引入任何机器学习库。实际项目中我给每道菜的tags字段存的是逗号分隔的字符串算法启动时一次性加载到内存里构建一个MapLong, MapString, Integer的菜品-标签向量然后两两计算相似度。菜品量级在几百到几千时这个全量计算耗时不到一秒完全够用。如果菜品上万可以用倒排索引只算有共同标签的菜品对性能会好很多。3.2 基于用户历史行为的个性化打分有了相似菜品关系之后推荐列表怎么来我的做法是分三步召回、过滤、排序。召回阶段根据用户最近30天的行为找出用户“感兴趣”的菜品集合。怎么判定感兴趣直接用行为类型加权打分浏览1分收藏2分加购3分下单5分。累计得分超过阈值比如3分的菜品作为用户偏好种子。然后从每个偏好种子菜品出发找到相似度最高的Top-N菜品相似度阈值我一般取0.4以上合并成一个候选池。过滤阶段把用户已经买过、已经明确不喜欢的菜品评分低于2分过滤掉同时过滤掉下架商品。排序阶段候选池里的菜品按照一个综合得分排序这个得分我这样定义score 0.4 × 种子相似度 0.3 × 菜品综合评分 0.2 × 销量热度 0.1 × 位置距离衰减每一项都归一化到0-1区间。实际跑下来这个公式比单纯按相似度排序效果明显更好因为相似度只衡量“像不像”而综合评分和销量能反映“好不好”。用户最终要的是“像我喜欢吃的而且好吃”的菜不是单纯“长得像”的菜。3.3 冷启动问题的三个兜底方案任何推荐系统都绕不开冷启动餐饮小程序尤其严重——新用户进来一个行为数据都没有新菜品上架也没有评分和销量。我踩过几次坑之后总结出三个兜底策略方案一热度推荐兜底。新用户没有任何行为时直接推销量最高、评分最好的Top-N。SQL一句话搞定SELECT * FROM dish WHERE status 1 ORDER BY sales_count DESC, rating DESC LIMIT #{limit};这个策略简单可靠能保证新用户进来至少看到的是“大家都在吃”的东西不至于面对空页面。方案二时间衰减的新品加权。新上架的菜品没有销量、没有评分不代表没法推。给新品一个时间衰减加权上架前7天在排序公式的销量项里加一个虚拟销量权重随时间衰减让新品有机会曝光避免推荐结果全是老品。方案三分类冷启动。用户第一次进小程序还没点菜时可以让他先选“口味偏好”或“菜品分类”——喜欢川菜还是粤菜、吃辣还是不吃辣。这个信息存到user_preference表里推荐时直接按偏好标签做基于内容的推荐。这一步很笨但非常有效而且交互上也让用户觉得系统很智能。我当时实际项目里三个方案是叠加使用的新用户第1次推荐用方案三如果选了偏好或方案一没选偏好之后每产生一次行为就切换到个性化推荐。这里要特别注意用户第一次下单之前不要过度依赖协同过滤因为没有真实反馈数据协同过滤推出来的东西往往很随机。3.4 核心推荐代码示例我这里给一个基于用户行为加权的推荐服务核心代码语言是Java用Spring Boot的Service实现Service public class RecommendService { Resource private DishMapper dishMapper; Resource private UserBehaviorMapper behaviorMapper; Resource private EvaluationMapper evaluationMapper; /** * 获取推荐菜品列表 */ public ListDishVO recommendForUser(Long userId, int limit) { // 1. 获取用户行为种子菜品及权重 MapLong, Integer seedMap getUserSeedDishes(userId); // 2. 如果种子为空走冷启动推荐 if (seedMap.isEmpty()) { return coldStartRecommend(limit); } // 3. 构建候选池并打分 MapDish, Double candidateScoreMap new HashMap(); for (Map.EntryLong, Integer seed : seedMap.entrySet()) { ListSimilarDish similarList getSimilarDishes(seed.getKey(), 10); for (SimilarDish s : similarList) { if (s.getScore() 0.4) continue; // 相似度阈值过滤 Dish dish dishMapper.selectById(s.getDishId()); if (dish null || dish.getStatus() ! 1) continue; if (seedMap.containsKey(dish.getId())) continue; // 过滤已“种子”菜品 double finalScore s.getScore() * seed.getValue() 0.3 * (dish.getRating() / 5.0) 0.2 * Math.min(dish.getSalesCount() / 1000.0, 1.0); candidateScoreMap.merge(dish, finalScore, Double::sum); } } // 4. 按分数排序取TopN return candidateScoreMap.entrySet().stream() .sorted(Map.Entry.Dish, DoublecomparingByValue().reversed()) .limit(limit) .map(e - toVO(e.getKey())) .collect(Collectors.toList()); } /** * 从行为表获取用户偏好种子 */ private MapLong, Integer getUserSeedDishes(Long userId) { ListUserBehavior behaviors behaviorMapper.selectList( new LambdaQueryWrapperUserBehavior() .eq(UserBehavior::getUserId, userId) .gt(UserBehavior::getCreatedAt, LocalDate.now().minusDays(30))); MapLong, Integer seedMap new HashMap(); for (UserBehavior b : behaviors) { int weight switch (b.getBehaviorType()) { case 1 - 1; // 浏览 case 2 - 2; // 收藏 case 3 - 3; // 加购 case 4 - 5; // 下单 default - 0; }; seedMap.merge(b.getDishId(), weight, Integer::sum); } // 只保留分数 3 的种子 return seedMap.entrySet().stream() .filter(e - e.getValue() 3) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } }代码里几个细节我解释一下。seedMap存的是“用户对每个候选种子的兴趣权重”相似度乘以这个权重意思是“越像用户感兴趣的菜权重越高”。候选池里每个菜品可能和多个种子相似分数用Double::sum累加这样一道菜如果和用户感兴趣的多个菜都相似得分就会更高更可能被推荐出来。相似度计算我这里省略了getSimilarDishes的具体实现因为实践中我一般启动时用PostConstruct把菜品标签相似矩阵加载到内存Map里查询直接走内存。你们做的时候也建议这么干别每次都查库实时计算那个SQL写起来复杂性能还差。4. 实操过程与核心环节实现4.1 一键点餐从首页推荐到下单支付的完整流程推荐系统只是前端页面的一个数据来源用户最终要的是“快速吃到饭”。我把完整流程串一遍你们对照自己的模块检查有没有漏东西。用户打开小程序进入首页调/api/recommend/daily?userIdxxx拿到10-20道推荐菜。每道菜卡片展示图片、名称、价格、月销量、评分。用户点了某道菜进去看详情页这时候前端调/api/dish/{id}获取详情同时埋点一个“浏览”行为调/api/behavior提交behaviorType1。用户觉得不错点“加入购物车”前端把{dishId, quantity}发给后端/api/cart/add后端校验菜品状态和库存成功后返回购物车最新数量。购物车页面调/api/cart/list展示已选菜品用户点“去结算”进入确认订单页选择就餐方式堂食/自取和备注提交/api/order/create。后端createOrder是一个典型事务方法校验库存 → 计算总金额 → 生成订单主表记录 → 批量插入订单明细 → 扣减库存 → 清空购物车对应项。任何一个环节失败都得回滚所以这个方法必须加Transactional(rollbackFor Exception.class)。我见过很多同学少了事务注解结果出现“订单创建了但库存没扣”的灵异事件。下单成功后前端跳转支付页。真正对接微信支付需要商户号毕设项目99%没有所以一般用模拟支付前端弹出底部弹窗显示待支付金额点“确认支付”后调/api/order/pay后端把订单状态改成“已支付”。这里其实埋了一个设计决策把“支付”和“下单”拆成两个接口而不是一个接口一把梭。这样以后如果真接微信支付只需要替换pay接口内部逻辑不影响下单主流程。支付完成后用户可以到“我的订单”查看订单列表订单完成后可以评价打分文字。评价提交后写入evaluation表这一条评分数据会实时进入推荐算法的输入里影响下一次推荐。4.2 推荐接口的数据组装与返回格式推荐接口的返回格式我用一个统一的RepVOT包裹{code, message, data}。data里是一个化妆品列表每项包含{ dishId: 12, name: 水煮鱼, price: 68.00, image: https://..., rating: 4.8, salesCount: 1024, tags: [麻辣, 川菜, 荤菜], recommendReason: 因为你喜欢水煮肉片 }注意recommendReason这个字段很多同学忽略它但我强烈建议加上。这是个性化推荐系统“被感知”的关键用户看到“因为你喜欢水煮肉片”这句话会觉得这个推荐是真懂他的而不是随机推的。实现也很简单排序时谁贡献的相似度最高就用谁的种子菜品名生成推荐理由。4.3 关键参数选择与计算过程推荐链路里有几个关键参数我直接给出一套实测可用的初始值你们可以根据自己数据微调相似度阈值0.4。阈值设太高候选池空空如也设太低推荐结果全是“八竿子打不着”的菜。我跑下来0.4是一个均衡点菜品标签维度在5-10个左右时效果最稳。行为时间窗口30天。餐饮行为时效性很强三个月前的点餐记录对当前口味参考价值很低30天是个比较合理的折中值。种子行为累加分阈值3分。意味着用户至少下单一次5分或收藏加购组合235分才会被当作有效偏好种子。这样能过滤掉“随手浏览”产生的噪声。推荐列表长度首页10道菜详情页“相似推荐”5道菜。太短显得贫瘠太长用户刷不到底反而会疲劳。真实计算演示假设用户A最近行为是浏览了宫保鸡丁1次、加购了鱼香肉丝1次那么种子分是宫保鸡丁:1、鱼香肉丝:3。系统在候选池里发现麻婆豆腐和鱼香肉丝相似度0.72、和宫保鸡丁相似度0.65那么麻婆豆腐的得分就是0.72×3 0.65×1 2.81。与此同时辣子鸡和宫保鸡丁相似度0.85、和鱼香肉丝相似度0.30得分是0.85×1 0.30×3 1.75。排序后麻婆豆腐排到辣子鸡前面推荐理由显示“因为你喜欢鱼香肉丝”。整个过程完全可解释答辩时老师问起来也答得清楚。4.4 后台管理与菜品维护虽然题目重点在“点餐推荐”但一个完整的平台必须有管理端。我建议用Spring Boot的RestController做一个简单的管理接口集合页面可以直接用VueElement-UI也可以做成分离的Web管理端甚至偷懒一点直接用Swagger文档管理菜品。但不管用什么做前端管理端至少要覆盖这些功能菜品上下架PUT /api/admin/dish/{id}/status菜品信息维护POST /api/admin/dish订单状态管理GET /api/admin/order?status待支付基础数据统计今日订单数、今日销售额、热门菜品Top10热门菜品Top10这个统计接口可以直接给推荐系统做热度兜底数据源。两者共用一个查询逻辑代码复用一举两得。5. 常见问题与排查技巧实录5.1 典型问题速查表我把做这个项目时遇到最多的问题整理成一张表每一个都是实打实踩过坑的问题现象根因分析解决方案小程序请求后端报 404前端baseURL和后端context-path不一致检查server.servlet.context-path是否配置统一为/api前缀真机预览无法请求接口域名未配置或证书无效在小程序后台添加request合法域名保证HTTPS证书完整登录后token一直失效后端校验逻辑和前端header字段不一致统一约定header名为Authorizationtoken拼接方式必须一致推荐列表为空种子菜品过滤太严格或相似度阈值过高调低阈值到0.3行为时间窗口从30天改到60天购物车库存显示不准并发扣减库存出现超卖下单接口加Transactional库存扣减使用乐观锁或UPDATE ... WHERE stock quantity图片加载缓慢图片体积大且无压缩上传时压缩到200KB以内推荐用对象存储CDN加速重复提交订单用户多次点击“提交订单”按钮前端加防重复提交状态后端加订单号唯一校验或Redis分布式锁菜品标签相似度计算慢全量两两计算无优化菜品量大时用倒排索引只计算共享标签的菜品对5.2 推荐系统调试的独家心得推荐系统调试和普通CRUD调试不太一样它没有“对错”之分只有“好坏”之别。我调试时最常用的方法是在接口返回里加debug字段把推荐理由和相似度得分带出来。比如返回{ dishId: 12, recommendReason: 因为你喜欢鱼香肉丝, debugScore: 2.81, debugSeed: 鱼香肉丝:3, 宫保鸡丁:1 }这样做有一个显而易见的好处测试人员或者你自己在页面上看到推荐结果能立刻知道这条推荐是怎么算出来的。如果某条推荐明显不合理直接看debug字段就知道是哪个种子带偏了不用去翻日志猜。上线前记得把debug字段去掉避免暴露算法逻辑。5.3 性能优化与代码健壮性移动端性能优化是热搜词里反复出现的点餐小程序也一样适用。初次加载推荐页时图片懒加载必须做推荐接口最好加上Redis缓存缓存时间设为5分钟——用户口味不会5分钟内剧变但缓存能挡住大量并发请求打到数据库。下单接口要做幂等处理前端统一生成一个clientOrderNo后端用唯一索引兜底防止重复下单。数据库层面的优化也不能省。user_behavior表增长很快一定要定期清理90天前的历史数据或者迁移到历史表。推荐算法读取行为数据时SQL尽量只查最近30天的记录别一次性把全表捞出来。代码健壮性方面我强烈建议全局异常处理器加上。用RestControllerAdvice捕获业务异常、参数异常、兜底异常统一返回{code, message}格式。否则前端wx.request遇到后端500空指针只会收到一个看不懂的 HTML 页面排查问题效率极低。5.4 扩展思路怎么把项目做得更有亮点如果你的项目要拿高分或者你想把代码写得更像工业级系统可以在下面几个方向上做扩展智能推荐理由模板化。把“因为你喜欢XX”换成更丰富的文案比如“最近很多人点鱼香肉丝时也会选它”让推荐理由不再是模板字符串而是结合实时热门数据生成的话术。基于LBS的附近店铺推荐。餐饮和位置强相关引入腾讯位置服务按距离衰减调整推荐排序实现“附近的人都在吃什么”。库存联动和菜品高峰期预测。根据历史订单数据预测未来两小时的菜品需求量辅助商家备货。这属于一个超出毕设范围的进阶玩法但如果你有余力这绝对是一个让人眼前一亮的加分项。接入WebSocket实现订单状态实时推送。小程序点餐后用户希望第一时间知道“商家已接单”“正在制作”。后端用WebSocket推送订单状态变化前端wx.connectSocket建立长连接体验会提升一大截。我实际开发时把WebSocket做成了一个小模块订单状态变更时通过WebSocketSession推送给对应用户效果很直观。毕业设计和简历上写这个比写一屏CRUD接口有说服力得多。结尾一点个人经验分享最后说几句掏心窝的话。这个项目我从零搭到完整跑通前后大约用了一个月业余时间。最耗时间的不是推荐算法反而是那些看起来“很简单”的环节——微信小程序的登录联调、域名HTTPS配置、购物车的并发一致性。所以如果你们做的时候发现卡在了某个“简单”环节别怀疑自己能力这是所有做小程序项目的人都会经历的阶段。关于推荐系统我的建议是先把“基于标签相似度行为加权”这套简单方案彻底跑通再考虑要不要换成更复杂的协同过滤。原因很简单推荐算法的价值不在算法本身而在你能不能把用户行为数据完整、准确地采集下来。没有一个干净的数据管道再高级的算法都是空中楼阁。把埋点、存储、召回、排序、兜底这一条链路走通你的系统就已经超越大多数同类毕设了。最后再分享一个小经验做推荐系统一定要养成“可解释”的习惯。每次推荐结果出来问自己一句“为什么推这个”。答不出来说明你的推荐逻辑还不够好。答得出来写到论文里、讲到面试中都是实打实的亮点。希望这篇文章能帮你把项目稳稳落地微信小程序端、Java后端、个性化推荐这条技术线走通之后你会发现它不仅是一个毕业设计更是一套能够真正支撑业务的产品原型。
返回列表