
1. 项目概述解决吃什么的终极难题每次站在食堂窗口前纠结今天吃什么的时候我都想有个能帮我做决定的工具。这个基于Java和微信小程序的智能美食推荐系统就是为解决这个日常痛点而生的。它不仅能随机推荐菜品还能根据用户的历史选择、口味偏好进行个性化推荐相当于一个装在手机里的美食顾问。系统采用前后端分离架构微信小程序负责用户交互界面Java后端处理业务逻辑和数据处理。这种组合既利用了微信小程序的便捷性无需安装、即用即走又发挥了Java在企业级应用开发中的稳定性优势。我在实际开发中发现这种架构特别适合校园点餐、外卖推荐等场景日均能处理上千次推荐请求而不卡顿。提示选择Java作为后端语言时建议使用Spring Boot框架。它内置Tomcat服务器能快速构建RESTful API接口与微信小程序对接时异常顺畅。2. 核心功能设计解析2.1 随机推荐引擎的实现系统的核心是一个加权随机算法。基础版本很简单——将所有菜品ID存入数组用Java的Random类生成随机索引。但实际使用中发现纯随机会导致某些菜品反复出现而冷门菜永远不被推荐。优化后的算法包含三个关键参数基础权重管理员可为不同菜品设置初始权重比如招牌菜权重更高衰减因子每次被推荐后权重暂时降低避免重复推荐用户偏好系数根据用户历史点击调整权重// 加权随机算法示例代码 public Dish weightedRandomSelect(ListDish dishes, String userId) { double totalWeight 0; MapString, Double weightMap new HashMap(); // 计算每个菜品的综合权重 for (Dish dish : dishes) { double weight dish.getBaseWeight() * Math.pow(0.9, dish.getRecentShownCount()) * getUserPreferenceFactor(userId, dish.getId()); weightMap.put(dish.getId(), weight); totalWeight weight; } // 执行加权随机选择 double random Math.random() * totalWeight; double cumulativeWeight 0; for (Dish dish : dishes) { cumulativeWeight weightMap.get(dish.getId()); if (random cumulativeWeight) { dish.incrementShownCount(); return dish; } } return dishes.get(0); // 默认返回第一个 }2.2 用户偏好建模系统通过三种维度收集用户偏好数据显式反馈用户对推荐菜品的评分1-5星隐式反馈用户查看菜品详情的时长、最终是否下单社交反馈好友推荐的菜品自动获得权重加成这些数据通过微信小程序的用户行为追踪接口收集后端使用Redis暂存最终批量写入MySQL。实际运行中这种混合采集方式比单纯依赖评分更准确——很多用户懒得评分但浏览行为不会说谎。2.3 微信小程序端的交互设计小程序界面遵循三步完成推荐的原则首页大按钮直接点击获取随机推荐二级筛选可设置忌口、菜系、价格区间历史记录查看过往推荐并重新选择特别要注意的是微信小程序的授权处理流程。首次使用时需要获取用户微信昵称和头像但不能强制索要手机号会违反微信平台规则。我的经验是先用wx.login获取openid建立账户等用户主动下单时再引导补充联系方式。3. 技术实现关键点3.1 后端技术栈选型Spring Boot 2.7简化配置内嵌TomcatMySQL 8.0存储菜品数据和用户信息Redis 6缓存热门推荐结果和用户会话MyBatis-Plus简化数据库操作SwaggerAPI文档自动生成部署时遇到的一个坑微信小程序要求HTTPS连接但校内测试环境通常只有HTTP。解决方案是用nginx反向代理配置SSL证书。阿里云有免费的DV证书可用申请后配置到nginx即可server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://localhost:8080; } }3.2 数据库设计要点核心表结构设计经历三次迭代后定型dish表菜品数据CREATE TABLE dish ( id varchar(32) PRIMARY KEY, name varchar(50) NOT NULL, price decimal(10,2) DEFAULT 0, category enum(川菜,粤菜,湘菜,西餐) NOT NULL, spicy_level tinyint DEFAULT 0, base_weight float DEFAULT 1.0, image_url varchar(255), description text, create_time datetime DEFAULT CURRENT_TIMESTAMP );user_preference表用户偏好CREATE TABLE user_preference ( id int AUTO_INCREMENT PRIMARY KEY, user_id varchar(32) NOT NULL, dish_id varchar(32) NOT NULL, explicit_score tinyint DEFAULT NULL, implicit_score float DEFAULT 0, last_click_time datetime, UNIQUE KEY idx_user_dish (user_id,dish_id) );注意user_id对应微信的openid不要存储unionid除非需要跨公众号/小程序关联3.3 性能优化实践当用户量突破5000时发现三个性能瓶颈推荐算法执行时间从50ms飙升到300ms微信登录接口频繁超时热门时段数据库连接池耗尽对应的解决方案算法优化预计算菜品权重每小时批量更新一次到Redis缓存策略使用两级缓存 - Redis缓存热门推荐本地缓存用户最近5次选择连接池配置调整HikariCP参数增加最大连接数到50设置合理的空闲超时// Spring Boot数据源配置示例 spring.datasource.hikari.maximum-pool-size50 spring.datasource.hikari.idle-timeout30000 spring.datasource.hikari.connection-timeout20004. 开发踩坑实录4.1 微信小程序审核问题第一次提交审核被拒原因有三含有测试字样的菜品需改为正式数据未处理用户拒绝授权的情况需添加友好提示页面加载时有明显白屏需添加骨架屏解决方案准备两套数据开发用测试数据上线前替换为真实菜品完善授权流程的异常处理使用小程序提供的骨架屏组件// 小程序授权处理示例 wx.getSetting({ success(res) { if (!res.authSetting[scope.userInfo]) { wx.showModal({ title: 提示, content: 需要获取您的昵称和头像提供个性化服务, success(res) { if (res.confirm) { wx.openSetting() } } }) } } })4.2 并发推荐冲突当多个用户同时获取推荐时出现菜品权重更新冲突。例如用户A读取菜品X权重为1.0用户B也读取菜品X权重为1.0两者都计算新权重为0.91.0*0.9最终权重被错误地设为0.9而非预期的0.81采用Redis的WATCH/MULTI命令实现乐观锁// 使用Redis事务保证权重更新原子性 public boolean updateDishWeight(String dishId) { try (Jedis jedis jedisPool.getResource()) { jedis.watch(dishId); double currentWeight Double.parseDouble(jedis.hget(dish_weights, dishId)); Transaction tx jedis.multi(); tx.hset(dish_weights, dishId, String.valueOf(currentWeight * 0.9)); ListObject results tx.exec(); return results ! null !results.isEmpty(); } }4.3 跨域会话保持微信小程序前端域名与后端API域名不同导致登录状态无法保持每次请求都需要重新认证部分手机网络环境复杂如校园网加剧问题最终方案使用微信小程序自带的storage存储session_key后端生成token时同时返回过期时间前端在token过期前静默续期// Token生成与验证工具类 public class JwtUtil { private static final String SECRET your_secret_key; private static final long EXPIRATION 7200; // 2小时 public static String generateToken(String openid) { return Jwts.builder() .setSubject(openid) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION * 1000)) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }5. 项目扩展方向在实际使用中同学们提出了几个有价值的改进建议5.1 社交化功能增强好友口味对比可视化显示你与好友的口味匹配度组团点餐多人同时获取推荐取交集生成共同适合的菜品菜品漂流瓶匿名分享今日餐品并附上点评5.2 智能推荐升级时段感知早餐推荐粥类夜宵推荐小吃天气适配雨天推荐暖汤炎热天推荐凉菜健康管理对接微信运动数据高热量日推荐清淡饮食5.3 运营功能补充商家入口允许餐厅管理员上传新菜品限时优惠特定菜品在用餐高峰前提高推荐权重菜品故事为每个菜品添加来历典故等文化元素实现这些扩展时建议采用微服务架构拆分系统。例如将推荐算法独立为单独服务方便后期引入机器学习模型。Spring Cloud Alibaba套件能很好地支持这种演进特别是它的Nacos服务发现和Sentinel流量控制功能。