
最近几年计算机毕业设计里“外卖点餐系统”几乎是长盛不衰的选题。但如果你还停留在用户管理、菜品管理、订单CRUD这一套很容易被评委判定为“业务堆砌没有技术难点”。同样是外卖系统加上AI推荐之后技术含量和答辩说服力完全不一样。这篇文章想聊清楚一件事一个基于SpringBoot 微信小程序 AI的智能外卖点餐推荐系统到底该怎么从零搭建AI推荐在这里是噱头还是真能落地如果你正在选毕业设计题目或者准备复现一个可演示、可答辩、可写进简历的项目这篇文章会给你一条清晰的路线。先给结论这个项目的核心难点不在SpringBoot也不在微信小程序而在“推荐算法如何和小程序业务闭环”。也就是说用户在小程序里看了什么、点了什么、给谁点过赞这些行为数据要通过后端收集起来落到数据库里再通过一个推荐算法算出来“这个用户可能喜欢什么菜品”最后在小程序首页展示出来。链路完整才有答辩素材。1. 这个选题真正值得做的原因很多同学选外卖系统是因为它业务直观数据库表设计容易前端页面也容易找参考。但正因为太常见如果只是“用户—商品—订单”三件套没有任何亮点在毕业设计答辩里会显得很单薄。加上AI推荐之后情况就不一样了。这个选题变成了一个“有业务、有算法、有工程”的综合项目。从技术角度看它至少覆盖了这几层能力SpringBoot后端RESTful接口设计、MyBatis/MyBatis-Plus操作数据库、拦截器或Spring Security做登录鉴权。微信小程序前端WXML页面布局、微信登录流程、请求封装、本地缓存。AI推荐模块用户行为数据整理、基于协同过滤或基于内容的推荐策略、冷启动处理。工程实践数据库设计、接口联调、部署上线、日志排查。这个组合对毕业设计来说非常合适。它既有传统Web项目的工程性又有一个“智能”的着力点。答辩时评委问得最多的两个问题一个是“你的智能体现在哪里”一个是“推荐算法是怎么实现的”这个项目都能给出明确回答。另外从就业角度讲现在Java后端岗位面试高频词汇里SpringBoot是基础而推荐系统、用户行为分析、小程序开发都是加分项。做完这个项目你写在简历上的时候是可以理直气壮写“独立完成基于用户行为分析的推荐系统设计”的。当然这个项目也有不适合的人群。如果你的时间只剩两周而且Java基础还停留在“能看懂但写不出来”的程度建议先做传统外卖系统保底。AI推荐是加分项不是救命符。2. 系统架构SpringBoot、微信小程序、AI推荐如何分工先看整体架构。一个典型的智能外卖点餐推荐系统从前到后分成三层展示层、业务层、数据层。展示层就是微信小程序。用户在手机微信里搜索并打开小程序看到首页推荐菜品、点开菜品详情、加入购物车、提交订单、查看个人中心。小程序不直接访问数据库所有数据都通过后端接口获取。业务层是SpringBoot后端。它负责处理小程序的请求包括微信登录接口、菜品列表接口、订单接口、推荐接口。最重要的一个职责就是把用户的每一次点击行为记录到数据库里形成行为日志供推荐模块使用。数据层是MySQL数据库。核心表有用户表、菜品表、订单表、用户行为表、推荐结果表。其中用户行为表是整个推荐系统的基础没有它AI推荐就是空中楼阁。AI推荐模块在后端中独立出来它可以是一个Service类的实现也可以单独做成一个推荐模块包。基本流程是定时任务或实时请求触发推荐计算读取用户的浏览记录、收藏记录、订单记录按照推荐算法计算候选菜品集合再过滤掉已下架或用户不吃的菜品最终返回给小程序展示。这里有一个关键设计推荐模块不能直接返回“排序后的菜品列表”就完事还必须考虑可解释性。小程序端最好能看到“根据你的口味推荐”这样的提示甚至展示推荐理由比如“因为你喜欢辣味川菜所以推荐水煮牛肉”。这在答辩时非常加分。如果用一张图来理解大概是这样小程序端 → 收集用户行为 → 调用后端接口后端 → 解析请求 → 写入/读取数据库 → 调用推荐服务 → 返回结果推荐服务 → 读取行为数据 → 算法计算 → 生成推荐列表这套架构的优点是模块边界清楚。即使推荐算法换一种实现后端接口和小程序端都不用改动只需要替换推荐Service内部逻辑。这也是工程化思维的一个体现。3. 推荐系统核心原理不是拍脑袋而是可解释的策略组合推荐系统在互联网产品里已经非常成熟常见的有三种策略基于协同过滤、基于内容、基于热度。协同过滤的核心思想是“物以类聚人以群分”。进一步细分又分成基于用户的协同过滤和基于物品的协同过滤。基于用户的协同过滤是找到和你兴趣相似的其他用户把那些用户喜欢的但你还没看过的东西推荐给你。比如用户A和用户B都买过黄焖鸡和麻辣香锅说明两人口味相近用户A还买过奶茶那就可以把奶茶推荐给用户B。基于物品的协同过滤则是计算物品之间的相似度。买过“辣子鸡丁”的人大概率也会喜欢“水煮肉片”这两种菜品在行为数据上经常同时出现说明它们相似于是系统会把水煮肉片推荐给正在看辣子鸡丁的用户。基于内容的推荐是根据菜品本身的属性来推荐。菜品有分类、口味标签辣、不辣、微辣、食材标签鸡、鱼、牛肉。如果用户最近点过很多辣味鸡肉菜品系统就把同样属于“辣鸡肉”的菜品拿出来推荐。基于热度推荐就是搞一个“大家都在点”排行榜。新品上线或者新用户第一次进入首页时没有历史行为数据这时候直接用销量排行、好评排行做兜底。在我们这个毕业设计项目里推荐策略绝对不能只用一种。因为系统刚上线时所有用户都是冷启动状态如果只用协同过滤推荐结果大概率是空的。更稳妥的做法是分层次处理第一层用户没有任何行为时推荐综合热度Top N。第二层用户有一定点击/收藏行为时基于内容的推荐 热门兜底。第三层用户已经产生多笔订单时优先基于协同过滤参考相似用户的口味。这个策略组合技术含量适中但逻辑非常完整答辩时可以说是“一个可落地的混合推荐方案”。4. 环境准备与前置条件在开始敲代码之前先把环境准备好。这个项目涉及到的工具不少提前整理一份清单会省掉很多临时找环境的麻烦。操作系统建议Windows 10/11或macOS内存至少8GB。如果电脑配置太低后面要同时启动MySQL、SpringBoot后端和微信开发者工具会比较吃力。后端环境JDK 1.8即可不建议一上来就用JDK 17或21。因为部分教学资料和依赖版本可能会不一致。Maven 3.6用来管理依赖。IDEA或用Eclipse个人更推荐IDEA激活社区版也能完成这个项目。MySQL 5.7或8.0本项目用MySQL 8.0问题不大但连接驱动的配置要注意版本匹配。小程序端微信开发者工具目前稳定版本即可。需要一个小程序AppID。如果你没有注册小程序账号可以使用测试号但测试号无法体验完整的微信登录流程这一点要提前知道。其他工具Navicat或DBeaver用于可视化查看数据库。Postman或Apifox用于测试后端接口是否通。这里特别提醒一个热门坑SpringBoot版本太高导致的兼容性问题。很多同学从网上下载项目一运行就报错去看依赖发现spring-boot-starter-parent版本是3.x而课程里用的是2.x。SpringBoot 3.x要求JDK 17以上如果本机还是JDK 1.8直接编译失败。建议本项目使用SpringBoot 2.7.x版本搭配JDK 1.8或JDK 8兼容性最稳定。如果你后面想升级到SpringBoot 3.x需要同时把JDK升级到17并检查MyBatis、Dubbo等第三方依赖是否有支持Jakarta命名空间的版本。5. 项目搭建与核心代码实现这一部分是整个项目的核心我会按照“后端主服务—微信登录—推荐模块—小程序交互”这条链路来拆解每一步给出关键代码和文件位置代码以最小可用为原则。5.1 创建SpringBoot项目并配置基础依赖在IDEA中新建Spring Initializr项目Group填com.exampleArtifact填smart-order。核心依赖包括spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok。pom.xml的关键依赖如下保留核心部分parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent properties java.version1.8/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意mybatis-plus-boot-starter 3.5.x系列对SpringBoot 2.x支持很好不建议使用太新版本。MySQL驱动版本和数据库版本要保持一致8.0.33对应MySQL 8.x。在application.yml中配置数据源和MyBatis-Plusserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/smart_order?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true启动类直接使用SpringBoot默认生成的Application类即可package com.example.smartorder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class SmartOrderApplication { public static void main(String[] args) { SpringApplication.run(SmartOrderApplication.class, args); } }5.2 数据库表设计数据库是这个项目的地基。推荐系统的效果好不好很大程度取决于有没有把用户行为数据存下来。在建表时至少要包含以下六张核心表。用户表CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信openid唯一标识, nickname VARCHAR(64) DEFAULT NULL COMMENT 昵称, avatar_url VARCHAR(255) DEFAULT NULL COMMENT 头像地址, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;菜品表CREATE TABLE dish ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 菜品名称, category_id BIGINT DEFAULT NULL COMMENT 分类ID, price DECIMAL(10,2) DEFAULT NULL, image VARCHAR(255) DEFAULT NULL COMMENT 图片地址, tags VARCHAR(128) DEFAULT NULL COMMENT 标签如辣、鸡肉、下饭, sales INT DEFAULT 0 COMMENT 销量, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;菜品分类表、订单表这里不展开重点看用户行为表和推荐结果表。用户行为表是AI推荐的“原材料”CREATE TABLE user_behavior ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 用户ID, dish_id BIGINT NOT NULL COMMENT 菜品ID, behavior_type VARCHAR(16) NOT NULL COMMENT click/favorite/order, score TINYINT DEFAULT 1 COMMENT 行为权重, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_dish (user_id, dish_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的行为权重值得解释一下。点击行为权重设为1收藏设为3下单设为5。这样在推荐算法计算用户偏好时不是简单统计次数而是加权评分结果更符合真实口味。5.3 微信登录流程与后端实现微信小程序登录是很多同学第一次接触时容易卡住的地方。整个流程并不复杂但每个环节都有容易踩的坑。小程序端调用wx.login获取一个临时code。这个code只能使用一次有效期五分钟。然后小程序把code发送到自己的后端接口。后端拿着code加上小程序的appid和secret去请求微信的接口jscode2session换来openid。这个openid就是用户在微信生态里的唯一标识后端应该拿它作为用户表的主键关联。后端代码如下package com.example.smartorder.controller; import com.alibaba.fastjson.JSONObject; import com.example.smartorder.common.Result; import com.example.smartorder.entity.User; import com.example.smartorder.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; RestController RequestMapping(/api/auth) public class AuthController { Autowired private UserService userService; Autowired private RestTemplate restTemplate; Value(${wechat.appid}) private String appid; Value(${wechat.secret}) private String secret; PostMapping(/login) public Result login(RequestBody MapString, String params) { String code params.get(code); String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code code grant_typeauthorization_code; String response restTemplate.getForObject(url, String.class); JSONObject json JSONObject.parseObject(response); String openid json.getString(openid); if (openid null) { return Result.error(微信登录失败 response); } User user userService.findByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); userService.save(user); } // 实际项目中会生成自定义token返回给小程序 return Result.success(user.getId()); } }注意RestTemplate需要先声明为Bean否则无法注入。configuration里简单声明package com.example.smartorder.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; Configuration public class RestTemplateConfig { Bean public RestTemplate restTemplate() { return new RestTemplate(); } }小程序端登录代码如下// utils/auth.js function wxLogin() { return new Promise((resolve, reject) { wx.login({ success: (res) { if (res.code) { wx.request({ url: http://localhost:8080/api/auth/login, method: POST, data: { code: res.code }, success: (resp) { if (resp.data.code 200) { wx.setStorageSync(userId, resp.data.data); resolve(resp.data.data); } else { reject(resp.data.msg); } }, fail: (err) reject(err) }); } else { reject(wx.login获取code失败); } }, fail: (err) reject(err) }); }); } module.exports { wxLogin };这里建议使用自定义登录态即后端登录成功后返回一个自定义token而不是直接用openid。原因有两个一是openid属于敏感信息不能暴露到前端二是小程序后续请求需要带上token方便拦截器做鉴权。5.4 AI推荐模块核心实现推荐模块是整个项目的技术亮点。我用一个可运行且代码量适中的例子来解释基于用户行为加权的协同过滤推荐。算法核心思路分成三步第一步读取所有用户的行为数据转换成“用户-菜品-评分”的Rating对象列表。 第二步计算目标用户与其他用户之间的相似度这里用余弦相似度或皮尔逊相关系数都可以毕业设计用余弦相似度更容易讲清楚。 第三步选取相似度最高的Top N个用户那些用户喜欢但目标用户没看过的菜品按综合评分排序输出。推荐Service代码示例package com.example.smartorder.service.impl; import com.example.smartorder.entity.Dish; import com.example.smartorder.entity.UserBehavior; import com.example.smartorder.service.DishService; import com.example.smartorder.service.RecommendService; import com.example.smartorder.service.UserBehaviorService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; Service public class RecommendServiceImpl implements RecommendService { Autowired private UserBehaviorService behaviorService; Autowired private DishService dishService; Override public ListDish recommendForUser(Long userId, int topN) { // 1. 获取所有行为数据 ListUserBehavior allBehaviors behaviorService.list(); // 2. 构建 用户 - (菜品 - 得分) 的映射 MapLong, MapLong, Double userDishScoreMap buildUserDishScoreMap(allBehaviors); // 3. 如果用户没有行为数据走热度推荐 if (!userDishScoreMap.containsKey(userId) || userDishScoreMap.get(userId).isEmpty()) { return dishService.listHotDishes(topN); } // 4. 计算当前用户与其他用户的余弦相似度 MapLong, Double userSimilarityMap new HashMap(); MapLong, Double currentUserDishes userDishScoreMap.get(userId); for (Map.EntryLong, MapLong, Double entry : userDishScoreMap.entrySet()) { Long otherUserId entry.getKey(); if (otherUserId.equals(userId)) { continue; } double similarity cosineSimilarity(currentUserDishes, entry.getValue()); userSimilarityMap.put(otherUserId, similarity); } // 5. 取相似度最高的前N个用户 ListMap.EntryLong, Double topSimilarUsers userSimilarityMap.entrySet().stream() .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) .limit(5) .collect(Collectors.toList()); // 6. 推荐候选集合并计算推荐分数 MapLong, Double recommendScoreMap new HashMap(); for (Map.EntryLong, Double similarUser : topSimilarUsers) { Long similarUserId similarUser.getKey(); double simScore similarUser.getValue(); MapLong, Double similarUserDishes userDishScoreMap.get(similarUserId); for (Map.EntryLong, Double dishEntry : similarUserDishes.entrySet()) { Long dishId dishEntry.getKey(); if (currentUserDishes.containsKey(dishId)) { continue; } recommendScoreMap.merge(dishId, simScore * dishEntry.getValue(), Double::sum); } } // 7. 按推荐分排序 ListLong recommendDishIds recommendScoreMap.entrySet().stream() .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) .limit(topN) .map(Map.Entry::getKey) .collect(Collectors.toList()); return dishService.listByIds(recommendDishIds); } private MapLong, MapLong, Double buildUserDishScoreMap(ListUserBehavior behaviors) { MapLong, MapLong, Double userDishScoreMap new HashMap(); for (UserBehavior behavior : behaviors) { userDishScoreMap .computeIfAbsent(behavior.getUserId(), k - new HashMap()) .merge(behavior.getDishId(), behavior.getScore().doubleValue(), Double::sum); } return userDishScoreMap; } private double cosineSimilarity(MapLong, Double user1, MapLong, Double user2) { SetLong commonDishes new HashSet(user1.keySet()); commonDishes.retainAll(user2.keySet()); if (commonDishes.isEmpty()) { return 0.0; } double dotProduct 0.0; for (Long dishId : commonDishes) { dotProduct user1.get(dishId) * user2.get(dishId); } double norm1 Math.sqrt(user1.values().stream().mapToDouble(v - v * v).sum()); double norm2 Math.sqrt(user2.values().stream().mapToDouble(v - v * v).sum()); if (norm1 0.0 || norm2 0.0) { return 0.0; } return dotProduct / (norm1 * norm2); } }这个实现虽然是简化版但已经跑通了“加载行为—计算相似度—生成推荐”的完整推荐链路。它的优点在于没有引入额外的计算框架直接用JDK的集合操作完成评委问起来你能清晰解释每一步。如果想在论文里写得更高级可以替换为Spark MLlib或Python的协同过滤接口但那是加分项不是必需品。5.5 小程序端首页推荐展示小程序首页建议使用竖向排列的推荐卡片顶部展示“智能推荐”四个字下面接菜品列表。每个菜品卡片包含图片、名称、价格、标签和推荐理由。页面结构比较简单核心点在于请求推荐接口后要处理加载态和空态// pages/index/index.js Page({ data: { dishList: [], loading: true }, onLoad() { const app getApp(); const userId wx.getStorageSync(userId); if (!userId) { // 未登录先走登录流程完成后重新请求 app.login().then(() this.loadRecommendDishes()); } else { this.loadRecommendDishes(); } }, loadRecommendDishes() { const userId wx.getStorageSync(userId); wx.request({ url: http://localhost:8080/api/recommend/dishes, data: { userId: userId, topN: 6 }, success: (res) { this.setData({ dishList: res.data.data, loading: false }); }, fail: () { this.setData({ loading: false }); wx.showToast({ title: 推荐加载失败, icon: none }); } }); } });推荐接口的Controller如下package com.example.smartorder.controller; import com.example.smartorder.common.Result; import com.example.smartorder.entity.Dish; import com.example.smartorder.service.RecommendService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; RestController RequestMapping(/api/recommend) public class RecommendController { Autowired private RecommendService recommendService; GetMapping(/dishes) public Result recommendDishes(RequestParam Long userId, RequestParam(defaultValue 6) int topN) { ListDish dishes recommendService.recommendForUser(userId, topN); return Result.success(dishes); } }6. 运行结果与效果验证项目写完不是终点能跑起来并给评委演示才是目标。下面按步骤说明如何验证整个系统。第一步启动数据库导入表结构和测试数据。在Navicat中执行建表SQL然后插入20道以上的菜品数据模拟真实的分类和标签。注意图片字段如果本地没有图片可以先放一个占位地址。第二步启动SpringBoot后端。在IDEA中运行SmartOrderApplication看控制台输出出现“Started SmartOrderApplication”表示启动成功。然后用Apifox测试接口POST /api/auth/login传一个假的code会返回微信请求失败但能证明接口通GET /api/recommend/dishes?userId1topN6如果数据库里有行为数据会返回菜品列表。第三步用微信开发者工具打开小程序项目。在详情里修改AppID为你的小程序ID在右上角上传域名校验。本地开发时需要在“本地设置”中勾选“不校验合法域名”否则请求http://localhost:8080会被拦截。第四步验证推荐效果。首次登录新用户首页显示热门菜品用同一个账号点击浏览几个川菜菜品后再次进入推荐页可以看到系统推荐了相似口味的其他菜品并且可能带出“购买了XXX的用户也购买了XXX”的提示。这是推荐系统生效的最好证明。如果推荐结果没有变化优先检查用户行为表里是否有新记录。最容易出问题的是小程序端点击菜品后没有调用后端记录行为接口。7. 常见问题与排查思路项目中后期最耗时的一定是排错。我把这个项目里高频出现的问题整理成一个清单遇到问题可以对照排查。问题现象可能原因排查方式解决方案后端启动失败SpringBoot 3.x搭配JDK 8查看启动日志中Java版本报错改用SpringBoot 2.7.x或升级JDK 17小程序请求后端404前端请求地址写错或后端未启动用Postman直接请求接口确认后端端口和请求路径wx.login获取登录后的用户失败AppID和secret不匹配或使用了测试号查看后端响应中openid是否为空在微信公众平台重新获取AppID或改用真实小程序账号数据库中文乱码连接URL缺少characterEncoding查看表和连接串编码统一使用utf8mb4URL加characterEncodingutf8推荐结果为空用户行为表没有数据查询user_behavior表先手动插入几条行为记录调通后再联调前端小程序端无法显示图片图片路径是本地地址查看网络面板请求状态上传图片到对象存储或使用临时图片地址跨域问题前后端分离导致CORS拦截浏览器控制台CORS报错后端添加CORS配置类其中跨域问题需要单列一类解决。SpringBoot后端加一个配置类即可package com.example.smartorder.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }8. 最佳实践与工程建议项目到了能运行的程度建议再花一点时间打磨工程细节。这些改动不复杂但对项目的质量观感影响很大。8.1 接口返回统一结构不要一个接口返回Map一个接口返回JSONObject。建议定义一个Result类统一code、msg、data三个字段。前端解析时也方便不用每个接口单独写一套解析逻辑。8.2 登录鉴权建议使用拦截器小程序端除了登录接口和菜品列表接口其他接口都应该校验token。SpringBoot里实现一个HandlerInterceptor在preHandle方法里检查请求头中的Authorization字段。这个方法不难但能体现你对Web安全的意识。8.3 用户行为记录要异步化如果每点一次菜品后端都同步写一次user_behavior表在高并发下会有性能问题。毕业设计虽然不用应对高并发但可以在代码里用Async注解把行为入库操作异步化面试时这是个很好的延伸讨论点。8.4 日志要打关键链路在登录接口、推荐接口、订单接口中至少打一行info日志记录请求参数和响应状态。演示的时候如果出现bug看日志定位问题比瞎猜快得多。8.5 数据库要有测试数据答辩演示时不能只演示空数据库状态。至少要准备两个微信号的模拟数据、30道菜品、多个用户行为记录。这样演示推荐效果时才有说服力。可以在项目里放一个data.sql初始化脚本方便评委拿到项目后一键导入。8.6 答辩准备思路这个项目的答辩问题基本集中在三个方向为什么选SpringBoot做后端因为它生态成熟、配置简化、和微信小程序配合方便。推荐算法的原理是什么把余弦相似度公式讲清楚再结合代码解释三步流程。冷启动怎么解决用热度推荐兜底。项目哪里可以改进比如引入实时推荐、缓存、算法效果评估。回答这些问题时不需要说得特别深但一定要逻辑自洽能撑起“理解和掌握了”的印象。9. 总结与后续学习方向这个项目的价值在于它不是把SpringBoot、微信小程序、AI推荐三样东西简单拼在一起而是通过“用户行为—推荐计算—前端展示”这条业务线把三者串成了一个完整闭环。做完这个项目你既掌握了一个可演示的毕业设计也理解了推荐系统的基本实现思路。后续如果你想继续深入有三个方向值得研究。第一个方向是推荐效果评估。目前推荐完就结束了没有验证推荐得好不好。可以引入准确率、召回率、F1值来评估对比不同算法在同一份数据上的表现。答辩时能拿出评估数据项目的技术深度会明显提升。第二个方向是把推荐服务独立出去。真正的互联网项目里推荐服务通常是一个独立服务通过RPC或HTTP接口对外提供能力不会直接写在业务系统里。你可以把推荐模块改造成一个独立的SpringBoot服务通过Feign调用这个改动对理解微服务有帮助。第三个方向是安全加固和权限控制。目前系统对权限的处理比较基础可以引入Spring Security或Sa-Token实现细粒度的角色权限控制比如管理员可以管理菜品和订单普通用户只能点餐。这会成为一个新的答辩亮点。建议先把这个项目完整跑通然后挑一个延伸方向做优化。你现在不缺资料缺的是一台能跑代码的电脑和两三个晚上连续调试的时间。每周推进一个模块毕业设计一点都不难。