ARTICLE DETAIL

资讯详情

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

Java毕设租房系统:机器人问答与智能推荐的核心实现

Java毕设租房系统:机器人问答与智能推荐的核心实现 简介这套Java毕业设计源码实现了一个基于机器人问答的智能房源推荐租房系统面向毕业设计学生与Java全栈开发者。项目基于Spring Boot构建后端整合RESTful API、MyBatis/Hibernate持久层并引入自然语言处理与机器学习技术让用户通过聊天机器人提出需求系统即可生成相似房源、热门房源等多维度推荐结果。资源共2913个文件压缩包27.27MB内部Java源码、XML配置、Vue前端、CSV训练数据、Scala推荐脚本、JAR依赖一应俱全目录结构按数据采集、模型训练、接口服务、前端展示划分便于按模块研读。目前已有232人学习适合作为毕业设计参考或全栈实践案例。通过该项目可深入理解Word2Vec房源特征化、协同过滤推荐、MongoDB存储、JWT安全校验、Rasa式问答交互等具体实现并掌握从需求分析到部署测试的完整开发流程。丰富的图片与日志文件还能辅助研究调试思路对想要构建智能推荐系统的开发者很有帮助。1. 大学毕设做租房系统为什么“机器人问答智能推荐”比普通 CRUD 更吃香临近毕业手头的 Java 毕设选题十个里有八个是“某某管理系统”你把增删改查做得再端正答辩老师最多给个“工作量不足”的评语。而“基于机器人问答的智能房源推荐的租房系统”这个标题一眼就能看出它有三个记忆点租房业务真实、机器人问答有交互感、智能推荐带算法。哪怕它是从网盘里下载的既有项目源码只要你能在原项目基础上把这两块逻辑讲透、改出自已的版本它就是一份能写进简历、能应对“java面试题”里常见项目深挖的合格作品。我在帮几个学弟学妹改这类毕设时发现大多数人卡在同一个地方不知道“机器人问答”到底要做到什么程度“智能推荐”到底用哪套算法才不至于像个黑匣子。其实这套系统落地起来并不神秘前端给用户一个对话入口后端把用户的话拆成意图和关键词再跟房源数据匹配最后把结果按推荐打分排序返回。本文就按这个路径把技术选型、数据表设计、Java 实现、避坑点和答辩验证方法一次拆透让新手的每一步都能照着写让熟手能直接拿去改参数。2. 拆解这个毕设四层结构、三张表和两条核心逻辑链2.1 技术选型Spring Boot MyBatis Vue稳到能顺利演示整个项目的骨架最常见的组合是 Spring Boot 2.x 做后端、MyBatis 做数据库映射、MySQL 8.0 存业务数据前端用 Vue 2 或 3配合 Element UI 搭后台管理页另找一张页面做租房浏览和问答对话窗口。选这套不是因为它多惊艳而是因为市面上能找到的参考代码最多出了问题百度也最好搜。单元测试用 JUnit 5构建工具选 Maven因为大部分网上下载的源码包都是 Maven 结构用 IDE 打开直接等依赖下完就能起动。后端包结构我建议按 com.rental 分模块controller放 REST 接口/api/chat处理问答/api/recommend处理推荐service放问答匹配、推荐评分、用户偏好采集mapper放 MyBatis 接口配合application.yml里的数据库连接配置entity放房源、用户、问答记录、收藏记录等实体类utils放分词工具、相似度计算、权重配置读取工具。这样的分层能保证后面的代码不是一堆方法堆在 Controller 里。做毕业设计时面试官或答辩老师几乎一定会问“如果问答模块并发高了怎么办”你可以回答“把分词和匹配逻辑放到 service 层controller 只转参数后面加 Redis 缓存命中结果加线程池隔离”。哪怕你只是口头说出来印象分也会不一样。2.2 业务模型房源、用户、问答意图、推荐分值怎么互相引用任何租房系统核心都绕不开三张主表house、user、intent。我见过不少自学的项目把房源信息直接写在 JSON 里答辩几轮追问就露馅。正确做法是把房源拆成结构化字段至少包括house_id、title、area所在区域/商圈、layout户型比如“两室一厅”存成字符串、rent_price月租金Decimal、area_size面积Int)、orientation朝向、subway_distance到最近地铁站步行分钟数、tags如“整租”“近地铁”“朝南”、status0下架1上架、create_time。用户表是推荐系统的数据来源字段要有user_id、username、phone、budget_min、budget_max、preferred_area意向商圈多个用逗号分隔、preferred_layout、create_time。很多项目把“用户偏好”做成用户自己填表其实不如把收藏和行为当隐式信号来得真实后面第四章我会专门讲评分逻辑。第三张“隐含的表”是问答意图表你可以叫faq_pattern它存的是用户问句模板和对应的回复动作。比如用户问“有没有朝阳两居室”这个模板会把意图分类到search_house并抽取约束“朝向东/南、户型两居”。每个意图对应一个“处理策略”要么返回一段说明文字要么触发一次房源查询 SQL。用一句话记住这几张表的关系用户通过问答对话说出需求问答模块把需求变成结构化查询条件推荐模块在查询基础上打分排序最终把结果送去前端。2.3 机器人问答不是聊天是“意图识别 实体抽取 查询”刚接触这类系统的人最容易被“机器人”三个字带偏以为得接大模型、用深度学习甚至租服务器跑模型。作为毕业设计完全没必要。实际常用的套路是“词典分词 规则匹配”也就是先把用户输入的中文句子按词切开然后把这些词和事先维护好的词典、正则模板比对判断用户究竟是想“找房”“问租金”还是“约看房”。这个方案在限定域里效果不差而且代码量少面试时你能讲清楚“为什么不用 BERT”反而是一个加分项。比如用户问“帮我搜一下浦东两室一厅、预算五千以内”。分析下来意图是“搜索房源”实体有“浦东、两室一厅、五千以内”把这些实体组装成 SQL 条件就完成了问答到查询的闭环。真正的难点在于中文没有空格词与词之间的边界要靠分词工具。常见做法是引入 HanLP 或者 Ansj 的 jar 包自己再维护一份租房词典比如“两室一厅”“整租”“近地铁”必须作为一个词切出来。2.4 智能推荐不是玄学是“权重打分 排序”推荐模块容易陷入两种极端一种是把最近浏览的房源直接返回来这顶多叫“浏览历史”不叫推荐另一种是强行写一个协同过滤结果用户-行为矩阵稀疏得一塌糊涂算出来的相似度全是 0。对租房项目来说最务实的是多因子评分模型把和用户决策最相关的几个维度——预算差、面积、区域、户型、朝向、地铁距离、行为热度——分别计算出匹配分再乘上权重求和。这个模型既能让答辩老师听懂也能在有限的毕设数据里稳定产出结果。推荐结果最后要走一个“解释”步骤告诉用户“因为您偏好浦东且预算 5000这套房朝南且月租 4800所以推荐度最高”。这件事听起来像附加功能实际上是答辩时最能体现工程思维的亮点最后一章我会给一个完整的实现思路。3. 把机器人问答做进租房系统从 intent 表到问答接口3.1 设计 FAQ 语料库和意图词典先建一张faq_pattern表。我一般在 MySQL 里这样建CREATE TABLE faq_pattern ( id int NOT NULL AUTO_INCREMENT, intent varchar(32) NOT NULL COMMENT 意图名如 search_house / ask_price / ask_layout, pattern varchar(255) NOT NULL COMMENT 匹配模板如 想找{area}的{layout}, keywords varchar(255) DEFAULT NULL COMMENT 逗号分隔的触发关键词如 找,搜,看, action_type varchar(16) NOT NULL COMMENT reply 或 query, reply_text varchar(500) DEFAULT NULL COMMENT action_typereply 时的静态回复, query_template varchar(500) DEFAULT NULL COMMENT action_typequery 时的 SQL 模板或查询类型, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么建这张表而不是硬写在代码里因为项目源码交付后你自己或后来接手的人要改问答规则不需要重新编译 Java直接改数据库即可。而且答辩时你可以说“这种设计把知识库和业务逻辑解耦了运营人员可维护”这就是工程化的加分项。常用模板数据可以准备这么几条intentpattern / keywordsaction_typesearch_house推荐、找、搜、看房queryask_price多少钱、租金、价格queryask_layout几室、户型、几居queryask_schedule约看、预约、看房时间replybye再见、拜拜reply3.2 中文分词与关键词匹配的 Java 实现我常用的分词方案是 HanLP因为它对自定义词典支持比较好。在pom.xml里引入依赖dependency groupIdcom.hankcs/groupId artifactIdhanlp/artifactId versionportable-1.8.4/version /dependency然后把自己的租房词典放进resources下的rental.txt格式是一行一个词两室一厅 近地铁 整租 朝南 浦东 预算在 Java 里用CustomDictionary加载词典后写一个拆“关键词”的小工具// 从用户输入中抽取用于查询的关键词后续做意图识别与条件拼装 public class KeywordMatcher { /** * 对一句话分词并过滤掉停用词。 * 停用词列表太短会导致“的”“了”参与匹配建议至少准备 30 个。 */ public static ListString segment(String input) { // 先用自定义词典切分再过滤长度小于2的无意义字 ListString words HanLP.segment(input) .stream() .map(term - term.word) .filter(word - word.length() 1) .collect(Collectors.toList()); // 把“两室一厅”这类自定义词合并防止被切散 ListString merged new ArrayList(); for (String word : words) { // 这里可按业务扩展把“两”“室”“一厅”拼成“两室一厅” merged.add(word); } return merged; } /** * 判断一句话是否包含某个意图的触发关键词。 * 命中次数越多越认为是核心意图。 */ public static MatchResult matchIntent(String input, ListFaqPattern patterns) { ListString words segment(input); String bestIntent unknown; int bestScore 0; for (FaqPattern pattern : patterns) { String[] keywords pattern.getKeywords().split(,); int score 0; for (String kw : keywords) { if (input.contains(kw) || words.contains(kw)) { score; } } if (score bestScore) { bestScore score; bestIntent pattern.getIntent(); } } return new MatchResult(bestIntent, bestScore, words); } }这段逻辑看着朴素但它已经把选择题想清楚了用关键词命中次数来判断意图而不是第一个匹配到就去执行。很多初版代码写的就是if (input.contains(找)) { ... } return;用户问“我再找找别的”也会被打回搜索意图所以记住“比较后再返回”这条规则。3.3 问答接口接收提问、解析意图、回填参数、查库返回接口设计中我一般让前端传给后端的参数只有userId和message。后端做四件事分词、意图识别、从原文抽条件、执行业务查询。// Controller 层只做参数接收和异常包装不写业务逻辑 RestController RequestMapping(/api/chat) public class ChatController { private final ChatService chatService; public ChatController(ChatService chatService) { this.chatService chatService; } /** * 用户在前端输入一段话返回问答结果或推荐房源列表。 * 比如 “帮我找浦东两室一厅预算5000” 进入 search_house 意图。 */ PostMapping(/send) public Result sendMessage(RequestBody ChatRequest request) { // request.userId 用于记录用户的行为日志给推荐模块留数据 return chatService.handleMessage(request.getUserId(), request.getMessage()); } }问题在于“抽实体”这一段。我的做法是先写好一个HouseQueryCondition对象字段包括 area、layout、maxPrice、minPrice、orientation然后写一个规则抽取类// 把用户问句中的价格/户型/区域/朝向抽出来拼成查询条件 public class ConditionExtractor { public static HouseQueryCondition extract(ListString words, String rawInput) { HouseQueryCondition condition new HouseQueryCondition(); // 1. 区域在词典里维护了一个 areaList顺序优先的长词优先 for (String area : Dict.areaList) { if (rawInput.contains(area)) { condition.setArea(area); break; } } // 2. 户型优先匹配 “X室X厅”用正则把数字从文字里抠出来 Pattern layoutPattern Pattern.compile(([一二两三室])室([一二两厅])?厅?); Matcher matcher layoutPattern.matcher(rawInput); if (matcher.find()) { condition.setLayout(matcher.group(0)); } // 3. 预算出现“预算”“万”“以内”时把数字串拿出来 Pattern pricePattern Pattern.compile(预算?([0-9一二三五六七八九十])); Matcher pMatcher pricePattern.matcher(rawInput); if (pMatcher.find() rawInput.contains(以内)) { condition.setMaxPrice(Integer.parseInt(pMatcher.group(1))); } log.info(抽取结果: {}, condition); return condition; } }这里最值得注意的坑是“数字单位”。用户说“五千”和“5k”和“5000”程序要统一处理成整型元。建议在抽出数字后立刻归一化不要等到拼 SQL 再处理否则查询日志里全是“预算五千以内”拼不上的记录。3.4 参数怎么调阈值、同义词、兜底话术整个问答模块能调的就三个地方关键词触发阈值、同义词扩展、兜底话术。我提供一组实测后比较稳的初始值意图匹配分数最低为 1 才触发如果有多个意图同分优先顺序是search_houseask_priceask_layout。兜底话术至少要写三种不同文案随机返回避免用户觉得系统死板。比如“我还不太明白您可以试试说‘推荐浦东两室一厅’”。同义词表建议放 Redis 或一份 properties 里不要写死在 Java 中。因为你要在答辩现场演示“我加一个叫‘咨询’的同义词不用重启项目就能生效”这就是运行时配置的价值。下面是一个问答 service 的整合流程不复杂但能说明整个模块是怎么串起来的// 问答主流程识别意图 → 抽参数 → 查询或回复 → 记录日志 public Result handleMessage(Long userId, String message) { // 1. 从数据库或缓存中加载意图模板 ListFaqPattern patterns faqPatternMapper.findAll(); // 2. 识别意图 MatchResult match KeywordMatcher.matchIntent(message, patterns); // 3. 抽参数并查询房源 if (search_house.equals(match.getIntent())) { HouseQueryCondition condition ConditionExtractor.extract(match.getWords(), message); ListHouse houses houseMapper.queryByCondition(condition); // 4. 如果命中房源太少就放宽价格条件再查一次 if (houses.size() 3) { condition.setMaxPrice(condition.getMaxPrice() null ? 6000 : condition.getMaxPrice() 500); houses houseMapper.queryByCondition(condition); } return Result.success(houses); } // 4. 其他意图按模板返回静态话术 FaqPattern pattern matchPattern(match.getIntent(), patterns); return Result.success(pattern.getReplyText()); }如果你认真看这段代码会发现它故意做了一个“放宽价格”的动作。这是很多问答机器人翻车的地方用户问“预算 4000 以内”数据库里没有系统直接回复“没有找到”用户马上失去信任。实际方案应该做两段式召回先严格查数据量少于 3 条就放宽价格限制或去掉一个条件再查返回时还要加一句“已经帮您放宽了价格范围”。4. 智能房源推荐用多因子打分给用户排一个“最合适”的列表4.1 推荐算法选型为什么毕设不做协同过滤谈到智能推荐稍微关注过算法的人第一反应是“基于用户的协同过滤”和“基于物品的协同过滤”。但租房场景有个现实问题普通毕设数据库中用户数也就几十到几百每个用户收藏或浏览的房源数量也少得可怜user-item 矩阵极度稀疏。你算出来的相似用户可能只有一条共同记录推荐结果基本是随机答辩时自己都没底。而且协同过滤需要一个“获取用户隐式反馈”的循环毕业设计没那么多时间攒数据。因此我建议用“多因子加权评分”也叫基于知识或规则的推荐。原理是拆出影响租房决策的特征把每个特征和用户偏好的匹配程度换算成分值再按权重加权求和。这种方案的优势有三个一是可解释性好你能明确说出为什么推荐第 3 套房二是代码实现用纯 Java 就能跑不依赖机器学习库三是冷启动问题容易解决——新用户没行为数据就把权重压到预算、区域这些注册信息上。4.2 用户偏好画像怎么来注册、浏览、收藏三个入口推荐模型没数据就是空转所以要先设计数据入口。第一个入口是注册表单用户在“找房意向”里填预算区间、选择意向商圈和户型这些字段直接写进user表。第二个入口是房源详情页的浏览事件每次打开详情都要向后端发一条日志“userId houseId”。第三个入口是收藏和约看动作权重最大因为这是强意向信号。我会为推荐单独建一张临时画像表user_preference字段如下CREATE TABLE user_preference ( id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL, preferred_area varchar(64) COMMENT 高频浏览区域取浏览记录中次数最多的商圈, preferred_layout varchar(16) COMMENT 高频户型, budget_min int DEFAULT NULL, budget_max int DEFAULT NULL, preferred_orientation varchar(8) DEFAULT 南北 COMMENT 通过注册和收藏统计, max_subway_minutes int DEFAULT 30 COMMENT 用户最远接受的地铁时间, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;更新画像的时机很重要。我的习惯是放在 recommend 接口被调用之前先跑一次增量计算。因为毕设阶段没有消息队列每次定时全量刷太慢增量刷库的方式更容易讲清楚。// 根据用户最近20条行为记录刷新用户偏好画像 public void refreshPreference(Long userId) { ListHouseBrowseLog logs browseLogMapper.findRecentByUser(userId, 20); if (logs.isEmpty()) { log.info(用户 {} 无行为记录使用注册默认画像, userId); return; } // 统计高频区域、高频户型以及行为所在房源的平均价格 String topArea logs.stream() .map(log - houseMapper.findById(log.getHouseId()).getArea()) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .entrySet().stream() .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) .orElse(null); preferenceMapper.updateArea(userId, topArea); }这里需要提醒如果用户行为记录只有一两条不要急着更新区域因为噪声太大。我会加上同步规则“少于 5 次的行为记录只累加收藏行为不覆盖注册时的偏好”。4.3 多因子评分代码价格、区域、户型、面积、朝向五维度推荐的核心是一个RecommendScorer类。我把五个维度的打分规则列成表格方便你直接抄因子计算方式分数范围价格匹配用户在预算区间内给 100超出但不超过 15% 给 60否则给 00~100区域匹配命中偏好区域给 100同区相邻商圈给 70其他给 3030~100户型匹配完全一致 100居室数一致但厅数不同 70否则 4040~100面积匹配用户在注册时填过面积偏好时面积在±10 平米内给 1000~100行为热度近 7 天被收藏次数 top10 房源给 100top30 给 80其余按比例40~100Java 实现就按这套规则计算总得分// 多因子加权评分分数越高越靠前权重来自配置不要写死 public class RecommendScorer { /** * 计算单套房源对某个用户的推荐分值 */ public static double score(UserPreference pref, House house) { double weightPrice 0.4; double weightArea 0.25; double weightLayout 0.15; double weightAreaSize 0.1; double weightHot 0.1; double priceScore calcPriceScore(pref.getBudgetMin(), pref.getBudgetMax(), house.getRentPrice()); double areaScore calcAreaScore(pref.getPreferredArea(), house.getArea()); double layoutScore calcLayoutScore(pref.getPreferredLayout(), house.getLayout()); double sizeScore calcSizeScore(pref.getAreaSize(), house.getAreaSize()); double hotScore calcHotScore(house.getViewCount(), house.getCollectCount()); // 总分 各因子线性加权权重越高对排序影响越明显 double total priceScore * weightPrice areaScore * weightArea layoutScore * weightLayout sizeScore * weightAreaSize hotScore * weightHot; log.debug(房源 {} 得分: 价格{} 区域{} 户型{} 面积{} 热度{} - {}, house.getId(), priceScore, areaScore, layoutScore, sizeScore, hotScore, total); return total; } }值得强调一下“线性加权”在毕设里够用但如果你把价格权重调到 0.6排序结果会不稳定。我一般把价格、区域之和控制在 0.6 以内剩下的留给户型、面积和行为热度这样推荐结果不会每一套房都在价格上最优称为“太挤”。4.4 推荐列表生成让 SQL 先过滤再用 Java 排序这是最常见的实现误区。有人会让推荐模块遍历全表房源每套都调 score 方法数据量到几千条时接口响应就明显变慢。正确姿势是先用 SQL 粗过滤掉完全不符合基本条件的房源再用 Java 打分排序。我一般用三条 SQL 规则-- 先粗过滤状态上架、价格不超过用户上限的1.5倍、已绑定区域必填 SELECT * FROM house WHERE status 1 AND IF(#{maxPrice} IS NOT NULL, rent_price #{maxPrice} * 1.5, 11) ORDER BY rent_price ASC LIMIT 100;为什么前面用 setMaxPrice 时约束是 “预算 500”到了推荐模块却放宽到 1.5 倍因为问答是“用户有明确指令”必须严格推荐是可以“探索”的你让用户看到一点超出预算但其他方面极好的房子购买意向可能更高。这两个模块边界不一样代码要分开写。粗过滤完成后再执行打分排序// 过滤出候选后用评分器打分并倒序返回 public ListHouse recommendForUser(Long userId, int topN) { UserPreference pref preferenceMapper.findByUserId(userId); if (pref null) { // 新用户无画像返回默认房源按热度排序 return houseMapper.findHotHouses(topN); } ListHouse candidates houseMapper.findFilteredCandidates(pref.getBudgetMax()); ListScoredHouse scored candidates.stream() .map(house - new ScoredHouse(house, RecommendScorer.score(pref, house))) .collect(Collectors.toList()); // 从大到小排取前 topN分数相同的按收藏数再排 scored.sort(Comparator.comparing(ScoredHouse::getScore).reversed() .thenComparing(ScoredHouse::getCollectCount).reversed()); // 一定要用 ArrayList 包一下否则返回的不可变列表不能加推荐原因 ListHouse result scored.stream() .limit(topN) .map(ScoredHouse::box) .collect(Collectors.toList()); // 写日志每个用户每次推荐都留痕 recommendLogMapper.insert(userId, houseIdsToString(result)); return result; }排序代码里有几个细节thenComparing后还要再reversed()是因为前一个排序已经倒序后面想按收藏数倒序但 Java 里二次排序比较器天然是升序所以必须再反转一次否则会出现相同的分数但收藏数最少排前面。这属于“代码看着好看实际顺序反了”的经典坑。4.5 权重标定与人机对比验证推荐参数不是拍脑袋定的。我建议做法是把评分字段单独做成一张rec_weight配置表里面有weight_price等字段界面上加一个后台管理页面方便答辩时现场改参数、前后对比列表变化。展示效果是最好的答辩素材你点一下“价格权重从0.4调到0.6”推荐列表立刻重新排序整个过程都发生在数据库配置层面不需要重新部署。5. 避坑指南6 个高频问题附现场排查步骤5.1 中文提问分词全错“两室一厅”被切成“两室”、“一厅”现象用户输入“推荐两室一厅的房子”后端日志里分词结果是“推荐”“两室”“一厅”“的房子”户型匹配永远失败。原因你不改 HanLP 自带的词典它默认认为“两室一厅”不属于常用词。解决在自定义词典rental.txt里加“两室一厅”“三室两厅”“整租”“押一付三”并且注意词典文件编码选 UTF-8否则一加载全是乱码。另外如果你用的是 Spring Boot 内嵌 Tomcat 跑 jar修改了 resources 下的词典文件一定要重新打包否则命中的还是旧的。5.2 推荐结果永远返回同一批房源用户收藏没起作用现象用户收藏了几套高价位房源后推荐列表依旧全是便宜房。原因收藏行为没有写进偏好画像或者画像刷新只在refreshPreference里统计了浏览记录没有统计收藏。解决把收藏行为的权重提升到浏览行为的 3 倍在user_preference表加一个preferred_tag把收藏房源的tags字段拆出来统计词频按词频刷新画像。推荐列表里还要再用一条id NOT IN (SELECT house_id FROM user_action WHERE user_id? AND action_type IN (dislike,already_looked))排除用户已经看过的房源。不排除的话用户每次看推荐都是那几套很快就会被发现“智能推荐不智能”。5.3 MySQL 数据表字符集导致中文回复乱码现象问答接口返回的中文在 Postman 里正常到前端页面变成问号。原因数据库连接 URL 没加characterEncodingutf8或者表不是utf8mb4。解决在application.yml里检查数据库连接串jdbc:mysql://localhost:3306/rental?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai同时把表改成 utf8mb4 或建表时指定默认字符集。特别注意“千万不能只改 server 端环境变量”你本地数据库系统默认可能是 latin1不改上面的连接串照样乱码。修改完成后重启应用然后先查SHOW CREATE TABLE user;确认 charset 字段不要查个SELECT就以为没问题。5.4 高德地图 key 在前端请求里裸奔答辩被追问现象前端 Vue 项目把AMap.key直接写死在main.js里然后把源码提交到 GitHub 仓库。答辩老师问“你这个 key 泄露了怎么办”答不上来。原因前端页面必须拿到 key 才能加载地图但 key 如果一直写在代码里别人可以在控制台里看到你的安全凭证。解决把 key 放进 Spring Boot 后端配置提供一个/api/config/mapKey接口返回给前端前端从后端拿 key 后再初始化地图。这样虽然前端控制台还是能看到以后端返回的 key但至少源码仓库里不裸奔。更稳妥的做法是限制 key 的域名白名单毕设阶段做到“key 不硬编码、接口统一从后端取”就能说服答辩老师。5.5 兜底话术太死用户连续问几次就问崩了现象用户输入“有没有性价比高的房子”后端识别成 unknown返回“我不明白”用户连续问三句系统回三句“我不明白”体验极差。原因兜底逻辑只用了一个话术也没有把 unknown 的问题收集起来形成数据。解决兜底话术最少写 5 种用随机数切换遇到 unknown 意图时把用户原话写入unresolved_question表后续你可以定时在后台看这些 “bad case”。搞定这些 bad case 后问答命中率才会真实提升这也是答辩里能讲“闭环迭代”的资本。5.6 Spring Boot 启动失败或端口被占用项目跑不起来现象双击运行主类控制台报Port 8080 was already in use。原因本地有微信开发者工具或者别的 Java 服务占用了端口。解决临时改端口用启动参数--server.port8081如果要在 IDE 里长期配置可以在application.yml里改server.port。要注意的问题是前端 axios 请求的 baseURL 如果写成http://localhost:8080后端一改端口就要联调发现跨域和连不上的问题。建议在 dev 环境直接用 8080遇到占用就查出对应进程关掉。Windows 命令是netstat -ano | findstr 8080再taskkill /pid 进程号 /F这条命令应写在你的项目 README 排错里。6. 从“能跑”到“能答辩”加日志、做验证、给推荐一个解释6.1 把问答和推荐的内部日志打开让老师看到你做了什么我在做毕设辅导时最常听到的问题是“老师觉得我这个项目太简单”。解决办法不是加更多功能而是把已有的功能做深。比如问答模块处理完问题后写一条“message_log”把用户原话、分词结果、命中意图、命中关键词、兜底是否触发、回复内容都记录下来。答辩时打开后端 log 页面按条件查一查现场演示“你看这句『想租浦东大面积』被切成了什么词又怎么匹配到了 search_house”。没有日志你的智能逻辑全是黑匣子有了日志老师才会觉得你是真的把系统做通了。6.2 用 JUnit 给推荐算法写一个回归测试推荐算法改参数很容易把结果改疯所以至少要写一个简单的 JUnit 测试保护核心逻辑。初始化几套 mock 数据验证“预算越接近、得分越高”和“户型不一致时得分低于一致”这两条规则。// 回归测试保证价格因子和区域因子的排序方向不会错 class RecommendScorerTest { Test void priceOverBudgetShouldScoreLowerThanInBudget() { // 给定人预算 max5000 UserPreference pref new UserPreference(); pref.setBudgetMax(5000); // A 房月租4800B 房月租5200 House h1 new House(); h1.setRentPrice(4800); House h2 new House(); h2.setRentPrice(5200); double score1 RecommendScorer.score(pref, h1); double score2 RecommendScorer.score(pref, h2); // 注意这里不能只断言score10要断言score1score2这才是价格因子的作用 assertTrue(score1 score2); } }这类测试跑起来很快也是告诉面试官“我做推荐时不只是调参还有回归观念”的直观证据。写断言时尽量用“比较方向”而不是“等于某个值”因为权重一改具体数值就变了但大小关系不应该变。6.3 给推荐结果配一句“推荐理由”让你从技术实现者变成产品思考者最后是我的压箱底技巧推荐列表里返回房源列表的同时返回一个recommendReason字符串。它由得分最高的因子组合而成比如“该房源靠近地铁站且价格低于您设定的预算 10%”“该房源属于您常浏览的浦东板块户型也匹配您的选择”。代码里是这样生成的// 根据因子明细生成面向用户的一句话推荐理由用于前端展示 public String buildRecommendReason(ScoredHouse scored, UserPreference pref) { House house scored.getHouse(); ListString reasons new ArrayList(); if (house.getRentPrice() pref.getBudgetMax()) { reasons.add(价格在您的预算范围内); } if (house.getSubwayDistance() pref.getMaxSubwayMinutes()) { reasons.add(距地铁步行 house.getSubwayDistance() 分钟); } if (house.getArea().equals(pref.getPreferredArea())) { reasons.add(位于您常看的 house.getArea() 板块); } // 最多取两个原因避免话术太长 return reasons.size() 2 ? String.join(, reasons.subList(0, 2)) 推荐指数较高 : String.join(, reasons) 综合排序靠前; }这样做至少是在向“推荐解释”这个工业级方向思考。答辩老师问“你和别人有什么区别”你就能说“别人只返回一套结果我能告诉用户为什么是这个结果也能让用户根据规则反馈调整推荐”。我自己以前带过的毕设小组靠再加上这一个功能系统在展示效果上拔高了一个档次而且它几乎没有增加多少工作量。说到底毕设项目源码下载下来不代表你完成了真正值钱的是你把其中几个模块拆开、改坏、再修好的过程。把问答的日志留好把推荐的分数打出来把启动排错写进 README答辩时你会比我那时候稳得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表