ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot+微信小程序+AI智能校园导航系统毕业设计全解析

2026/9/7 14:28:06 拓冰建站 浏览量
SpringBoot+微信小程序+AI智能校园导航系统毕业设计全解析 SpringBoot 微信小程序 AI 智能校园导航系统的毕业设计项目看起来功能很多实际拆开只有三条业务线校园地图数据管理、路线规划、AI 自然语言问答。只要把这三条线想清楚源码、论文、PPT 和演示讲解都可以围绕它们组织项目也才不会做成一张“只带几个标记点的地图”。校园导航和普通地图导航有一个本质差异普通地图软件面对的是成熟的市政路网而校园导航面对的是“最后一公里”问题。新生报到要知道自己住哪栋宿舍楼访客要知道图书馆入口朝哪边开学生想知道第一食堂三层有没有开放。这些问题只有校内数据才能回答。加进 AI 大模型之后用户不需要逐条筛选目的地直接输入“我想去图书馆还书怎么走”系统就能理解意图、检索校内地点并返回路线。这篇文章按这个系统的真实开发顺序展开分为技术选型、数据库设计、SpringBoot 后端接口、微信小程序页面、AI 接入、联调验证、排错路径和答辩准备几个部分。文中代码用于说明实现思路实际项目落地时要根据自己的包名、依赖版本、数据库字段和 AI 服务商配置做调整。1. 先弄清楚智能校园导航系统要解决什么问题1.1 校园导航和普通城市导航的本质差异普通地图软件有完整的路网数据导航时系统会在道路级别计算最短路径。校园导航不同校园内的道路等级低、楼栋密集、小路多很多地点不在市政路网上甚至同一栋楼的不同入口坐标都不一样。这时需要自己维护一份“校园 POI兴趣点”数据把教学楼、食堂、宿舍、图书馆、体育馆、行政楼都记录成带经纬度的地点再在 POI 之间建立可通行的路径边。这条数据链是导航系统的基础。数据准确路线才可信数据不准AI 回答得再流畅也没有用。所以设计系统时第一个要做的不是写 AI 接口而是把校园地图数据模型设计好。还需要注意坐标系问题。微信小程序地图组件默认使用 WGS-84 或 GCJ-02 坐标不同来源的坐标如果混用会出现 marker 偏移几十米的现象。项目中应该统一记录并使用同一套坐标系常见做法是统一使用 GCJ-02 坐标入库前端直接展示。1.2 AI 大模型在系统里的真实定位AI 在校园导航系统里不是核心计算引擎而是一个“自然语言入口层”。路线计算仍然靠后端图算法完成AI 负责解决用户怎么把自己的需求表达出来的问题。用户输入“我要去图书馆”传统系统要用户先选择目的地类型再在地图上点选。接入大模型后系统可以把这句话拆解为意图navigation目标地点图书馆可能存在的附加信息还书、自习、开放时间然后 AI 从 POI 表里检索出候选地点把候选地点信息和校园知识库内容组织成一句可读的回复同时返回一个结构化的地点对象小程序端拿到这个对象就能直接画路线。在毕业设计里这样的定位更容易写清楚。论文里可以写“本系统采用大模型完成意图识别与对话生成路径规划由基于 Dijstra 算法的后端模块实现”逻辑清楚也避免了把大模型硬塞进路径计算导致的不可控。1.3 角色、功能边界与毕业设计加分点系统常见的角色划分是普通用户和管理员。普通用户在小程序端使用地图浏览、POI 检索、路线规划、AI 问答、收藏和反馈功能管理员在 Web 管理端维护楼宇、POI、路线边和校园知识库。模块普通用户管理员地图浏览查看楼宇与 POI 标记查看、编辑全部数据POI 检索按名称或关键词搜索新增、修改、上下架 POI路线规划选择起点终点查看路线维护路线边数据AI 问答自然语言提问、跳转导航维护知识库内容历史记录查看历史导航与 AI 提问查看统计和反馈加分功能通常不是做大而全的管理后台而是把一两个细节做扎实比如 AI 回答后能直接跳转到地图路线路线可分享给好友用户在 AI 答案下能给“有用/没用”反馈管理员维护知识库后 AI 回答能立即更新。这些功能实现成本不高却能在演示和答辩时形成亮点。2. 技术选型与项目结构设计2.1 后端技术栈SpringBoot MyBatis-Plus MySQL后端使用 SpringBoot这是目前毕业设计中最稳妥的选择生态资料多、社区问题多、遇到问题容易查到解决方案。持久层使用 MyBatis-Plus可以少写很多单表 CRUD。数据库使用 MySQL 即可。组件推荐范围说明Java8 / 11 / 17SpringBoot 2.7.x 用 Java 8 或 11SpringBoot 3.x 用 Java 17SpringBoot2.7.x 或 3.2.x毕业设计建议从 2.7.x 起步资料多依赖冲突少MyBatis-Plus3.5.x需检查与 SpringBoot 的兼容性MySQL5.7 / 8.0本机开发可用 5.7生产建议 8.0Redis可选用于缓存 POI 列表或存储登录态不是必须项这里要特别注意 SpringBoot 版本带来的包名差异。SpringBoot 2.7.x 使用javax.servlet等javax命名空间SpringBoot 3.x 已经切换到jakarta命名空间。如果网上找的资料用的还是javax而本地项目是 SpringBoot 3.x会直接编译报错。建议先固定一个版本再统一所有依赖版本。2.2 小程序端原生框架还是 uni-app微信小程序端有两种常见选择原生小程序和 uni-app。对比项原生小程序uni-app上手难度低语法直接中等要理解 Vue 语法和编译链路调试体验微信开发者工具直接调需要区分 H5、微信、其他端差异地图组件直接使用map组件通过 uni-app 封装调用毕业设计适配好也常见但不建议为跨端额外增加复杂度如果项目只需要微信端推荐使用原生小程序。它本身有完整的地图组件、wx.request网络请求和wx.login登录流程学习成本低调试时问题定位直接。如果论文里特别强调“一套代码多端运行”才考虑 uni-app。2.3 AI 接入的三种方式AI 大模型接入方式常见有三类接入方式优点缺点适合场景直接调用 HTTP 接口代码直观模型可切换需要自己处理鉴权、超时、错误毕业设计首选Spring AI抽象统一支持流式版本演进快中文资料少熟悉框架后再用规则 模型混合离线可用成本低意图识别弱扩展差演示时网络不稳的兜底毕业设计里最推荐第一种。大模型厂商通常提供兼容的 chat/completions HTTP 接口只需要配置base-url、api-key、model三项就能切换不同服务商。论文里也可以写清楚“采用 HTTP 方式接入大模型接口下面示例以 OpenAI 兼容协议为例”既清晰又不绑定具体厂商。2.4 项目目录结构建议把前后端分成两个工程目录便于分别启动和打包。campus-nav-server/ src/main/java/com/example/campusnav/ controller/ # 接口层 service/ # 业务层 mapper/ # MyBatis-Plus Mapper entity/ # 数据库实体 dto/ # 请求和响应对象 config/ # 微信配置、AI 配置、Web 配置 common/ # 统一返回 Result、异常处理 src/main/resources/ mapper/ # XML 文件如果使用 application.yml pom.xml campus-nav-miniapp/ app.js app.json app.wxss pages/ map/ # 地图页 search/ # 搜索页 chat/ # AI 对话页 mine/ # 个人中心页 utils/ request.js # 请求封装 auth.js # 登录态处理前后端分离后小程序通过 HTTP 请求后端接口后端负责业务逻辑和数据持久化结构清楚论文画系统架构图也方便。3. 数据库设计地图数据先准确AI 才有意义3.1 核心表结构数据库至少要包含楼宇表、POI 表、路线边表、导航记录表和 AI 问答反馈表。先用楼宇表保存建筑主体信息再用 POI 表保存楼内具体地点这样导航可以精确到“第三教学楼 201 教室”级别。CREATE TABLE building ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 楼宇名称, code VARCHAR(50) COMMENT 楼宇编码, description VARCHAR(500) COMMENT 楼宇介绍, latitude DECIMAL(10,6) NOT NULL COMMENT 纬度, longitude DECIMAL(10,6) NOT NULL COMMENT 经度, floor_count INT DEFAULT 1 COMMENT 楼层数, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE poi ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 地点名称, type VARCHAR(50) COMMENT 类型教学楼/食堂/图书馆/宿舍/行政楼, building_id BIGINT COMMENT 所属楼宇, floor INT COMMENT 所在楼层, latitude DECIMAL(10,6) NOT NULL, longitude DECIMAL(10,6) NOT NULL, keywords VARCHAR(255) COMMENT 检索关键词如自习室、复印、还书, description VARCHAR(1000) COMMENT 地点介绍, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );POI 表的keywords字段很重要。用户可能搜索“自习”也可能搜索“借书”这些词不一定出现在地点名称里但可以提前维护在关键词字段中。检索时同时匹配名称和关键词召回率会高很多。3.2 路径与经纬度数据怎么组织路线数据使用边表保存这也是图算法的基础CREATE TABLE route_edge ( id BIGINT PRIMARY KEY AUTO_INCREMENT, start_poi_id BIGINT NOT NULL, end_poi_id BIGINT NOT NULL, distance_meters INT COMMENT 距离单位米, walk_time_seconds INT COMMENT 步行预计时间单位秒, path_json TEXT COMMENT 中间经过点坐标 JSON 数组, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_start (start_poi_id), KEY idx_end (end_poi_id) );path_json可以保存一串中间坐标点例如[{lat:30.51,lng:114.41}, {lat:30.51,lng:114.42}]。前端画路线时把两端的 POI 坐标和中间点组合成polyline即可。路线边数据不需要一开始就完整录入。可以先录入校园主干道路网保证相邻 POI 之间有边再逐步补充细节。演示时只需要覆盖校园核心区域就能看出效果。3.3 导航记录和反馈表导航记录表用于保存用户请求过的路线答辩时可以说明“系统支持历史导航查询”。CREATE TABLE navigation_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT, start_poi_id BIGINT, end_poi_id BIGINT, path_json TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE ai_feedback ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT, question TEXT, answer TEXT, is_helpful TINYINT DEFAULT -1 COMMENT -1未反馈 1有用 0无用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );ai_feedback表可以记录每次 AI 问答的内容和用户的反馈结果。这既能作为功能展示也是后续优化知识库的重要依据。3.4 校园知识库数据准备AI 问答要稳定不能完全依赖模型训练数据因为模型并不知道你学校的图书馆几点关门。正确做法是准备一张校园知识库表CREATE TABLE campus_knowledge ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category VARCHAR(50) COMMENT 分类开放时间/食堂/图书馆/宿舍/行政, title VARCHAR(200), content TEXT, keywords VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );示例数据INSERT INTO campus_knowledge (category, title, content, keywords) VALUES (开放时间, 图书馆开放时间, 图书馆周一至周日 8:00-22:00节假日另行通知。, 图书馆,开放时间,几点关门), (食堂, 第一食堂, 第一食堂位于图书馆南侧 200 米共三层营业时间 6:30-20:30。, 第一食堂,饭,食堂);AI 收到用户问题后先从这张表检索相关内容再把检索结果组装进提示词让模型基于知识库内容回答。这样做的好处是知识更新后不需要重新训练模型只需要改数据库内容。4. SpringBoot 后端核心接口实现4.1 依赖配置和统一返回结构pom.xml中核心依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意MyBatis-Plus 的 starter 版本要和 SpringBoot 主版本匹配。SpringBoot 3.x 需要 MyBatis-Plus 3.5.3 以上版本否则启动会报错。统一返回结构建议写成ResultTData public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }所有接口统一返回这个结构小程序端只需要判断code是否为 200。4.2 微信登录 code2session 流程小程序端调用wx.login()得到临时code后端拿code调用微信接口换取openid。具体流程小程序调用wx.login获取code。小程序把code传给后端。后端请求https://api.weixin.qq.com/sns/jscode2session。微信返回openid和session_key。后端用openid查询用户不存在则注册然后生成自定义token返回小程序。后端示例RestController RequestMapping(/api/auth) public class AuthController { Resource private RestTemplate restTemplate; Resource private WxConfig wxConfig; Resource private UserService userService; PostMapping(/login) public ResultLoginVO login(RequestBody LoginRequest request) { String url https://api.weixin.qq.com/sns/jscode2session ?appid wxConfig.getAppid() secret wxConfig.getSecret() js_code request.getCode() grant_typeauthorization_code; String resp restTemplate.getForObject(url, String.class); // 解析返回 JSON得到 openid JsonNode node new ObjectMapper().readTree(resp); if (node.has(errcode) node.get(errcode).asInt() ! 0) { return Result.error(微信登录失败: node.get(errmsg).asText()); } String openid node.get(openid).asText(); // 查询或注册用户 User user userService.findOrCreateByOpenid(openid); // 生成自定义 token这里可以用 UUID生产环境建议用 JWT String token UUID.randomUUID().toString().replace(-, ); LoginVO vo new LoginVO(); vo.setToken(token); vo.setUserId(user.getId()); vo.setNickName(user.getNickName()); return Result.success(vo); } }这里的自定义token是简化做法。毕业设计可以只把 token 存到 Redis 或内存里但最好说明白生产环境要用带过期时间的 JWT不能只靠随机字符串。4.3 POI 查询与路径规划接口POI 搜索接口不复杂关键是要同时匹配名称、类型和关键词RestController RequestMapping(/api/poi) public class PoiController { Resource private PoiService poiService; GetMapping(/search) public ResultListPoiVO search(RequestParam String keyword) { LambdaQueryWrapperPoi wrapper new LambdaQueryWrapper(); wrapper.like(Poi::getName, keyword) .or().like(Poi::getType, keyword) .or().like(Poi::getKeywords, keyword) .eq(Poi::getStatus, 1); ListPoi list poiService.list(wrapper); return Result.success(list); } GetMapping(/list) public ResultListPoi list() { LambdaQueryWrapperPoi wrapper new LambdaQueryWrapper(); wrapper.eq(Poi::getStatus, 1); return Result.success(poiService.list(wrapper)); } }路线规划接口使用 Dijkstra 算法在route_edge表构造的图上求最短路径public RouteVO planRoute(Long startPoiId, Long endPoiId) { ListRouteEdge edges routeEdgeMapper.selectList(null); MapLong, ListRouteEdge graph buildGraph(edges); MapLong, Long prev new HashMap(); MapLong, Integer dist new HashMap(); PriorityQueuelong[] queue new PriorityQueue(Comparator.comparingLong(a - a[1])); dist.put(startPoiId, 0); queue.offer(new long[]{startPoiId, 0}); while (!queue.isEmpty()) { long[] cur queue.poll(); long curId cur[0]; int curDist (int) cur[1]; if (curDist dist.getOrDefault(curId, Integer.MAX_VALUE)) { continue; } if (curId endPoiId) { break; } for (RouteEdge edge : graph.getOrDefault(curId, Collections.emptyList())) { int newDist curDist edge.getDistanceMeters(); if (newDist dist.getOrDefault(edge.getEndPoiId(), Integer.MAX_VALUE)) { dist.put(edge.getEndPoiId(), newDist); prev.put(edge.getEndPoiId(), curId); queue.offer(new long[]{edge.getEndPoiId(), newDist}); } } } // 回溯 prev得到完整 POI 序列再从 POI 表查坐标组装 polyline return buildRouteVO(prev, startPoiId, endPoiId); }Dijkstra 算法适合 POI 数量在几百到几千级别的校园场景。如果以后扩展到整个城市级别再考虑改用 A* 算法或接入专业地图引擎。4.4 AI 对话接口意图识别、知识检索、结果生成AI 对话接口是系统的重点。整体处理流程分成四步接收用户问题。先做轻量级本地检索从 POI 表查候选地点从campus_knowledge表查知识库内容。把候选地点和知识库信息拼进提示词。调用大模型接口返回文本回复和可选的地点对象。核心代码如下RestController RequestMapping(/api/ai) public class AiChatController { Resource private PoiService poiService; Resource private CampusKnowledgeService knowledgeService; Resource private AiService aiService; PostMapping(/chat) public ResultAiReplyVO chat(RequestBody ChatRequest request) { String question request.getQuestion(); // 1. 本地检索候选 POI ListPoi poiCandidates poiService.searchByNameOrKeyword(question); // 2. 检索知识库 ListCampusKnowledge knowledgeList knowledgeService.search(question); // 3. 构造提示词 String replyText aiService.chat(question, poiCandidates, knowledgeList); // 4. 尝试从回复中解析结构化地点 AiReplyVO vo new AiReplyVO(); vo.setReply(replyText); vo.setPoiList(poiCandidates); return Result.success(vo); } }AiService里真正调用大模型的部分可以直接用 RestTemplate 请求兼容 chat/completions 协议的接口public String chat(String question, ListPoi poiList, ListCampusKnowledge knowledgeList) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(aiConfig.getApiKey()); String systemPrompt buildSystemPrompt(poiList, knowledgeList); MapString, Object body new HashMap(); body.put(model, aiConfig.getModel()); body.put(temperature, 0.3); body.put(messages, Arrays.asList( Map.of(role, system, content, systemPrompt), Map.of(role, user, content, question) )); HttpEntityString entity new HttpEntity(objectMapper.writeValueAsString(body), headers); ResponseEntityString response restTemplate.postForEntity( aiConfig.getBaseUrl() /chat/completions, entity, String.class); return parseContent(response.getBody()); }buildSystemPrompt方法要把检索结果放进提示词你是校园智能导航助手。只能基于下列校园知识库和地点信息回答不要编造不存在的建筑或地点。 校园地点 - 图书馆坐标30.5100, 114.4100类型图书馆关键词自习,借书,还书 - 第一食堂坐标30.5080, 114.4120类型食堂关键词饭,早餐 校园知识库 - 图书馆开放时间周一至周日 8:00-22:00。 - 第一食堂共三层营业时间 6:30-20:30。 用户问题 我想去图书馆还书怎么走这样模型就能基于真实数据回答而不是依靠训练时习得的通用知识。4.5 AI 请求的关键参数与错误处理调用大模型接口时几个参数要特别注意参数推荐值说明temperature0.2 - 0.5导航场景希望答案稳定温度过高会乱答max_tokens500 - 1000控制回复长度避免超限报错connectTimeout5 秒连接超时readTimeout30 秒读取超时大模型生成时间较长model按服务商模型名模型名写错会返回 404 或 model not foundAI 接口需要处理几类异常API Key 无效返回 401需要在日志里记录不要把 Key 返回给前端。模型名错误返回 404 或 model not found检查配置项。限流部分服务商返回 429应该提示“AI 服务繁忙请稍后再试”。网络超时设置好 RestTemplate 的 connectTimeout 和 readTimeout。Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(30000); return new RestTemplate(factory); }实际部署时api-key 只能放在后端配置里绝不允许在小程序前端写入。小程序端直接调用大模型接口会泄露密钥而且微信小程序要求所有请求域名必须备案且配置合法域名直接把第三方 AI 地址写成请求地址会触发域名校验问题。5. 微信小程序端实现5.1 页面结构与 app.json 配置小程序端页面分四个地图页、搜索页、AI 对话页、个人中心页。{ pages: [ pages/map/index, pages/search/index, pages/chat/index, pages/mine/index ], window: { navigationBarTitleText: AI 校园导航, navigationBarBackgroundColor: #2196F3, navigationBarTextStyle: white }, tabBar: { list: [ { pagePath: pages/map/index, text: 地图 }, { pagePath: pages/chat/index, text: AI 助手 }, { pagePath: pages/mine/index, text: 我的 } ] }, permission: { scope.userLocation: { desc: 用于展示你在地图上的位置 } } }注意permission字段。小程序申请地理位置权限时必须配置用途说明否则在部分平台会被拒绝。5.2 地图组件markers polyline 画路线地图页的核心逻辑加载 POI 列表显示标记点击 POI 后请求后端路线接口用polyline画出路线。Page({ data: { markers: [], polyline: [], latitude: 30.5110, longitude: 114.4160, scale: 16 }, onLoad() { this.loadPoiList(); }, loadPoiList() { request.get(/poi/list).then(data { const markers data.map(p ({ id: p.id, latitude: p.latitude, longitude: p.longitude, title: p.name, callout: { content: p.name, display: ALWAYS, fontSize: 12, borderRadius: 6, padding: 4, bgColor: #ffffff } })); this.setData({ markers }); }); }, planRoute(e) { const { startId, endId } e.detail; request.get(/route/plan, { startId, endId }).then(data { this.setData({ polyline: [{ points: data.points, color: #2196F3, width: 4, arrowLine: true }] }); }); } });地图组件用法map stylewidth: 100%; height: 100vh; latitude{{latitude}} longitude{{longitude}} scale{{scale}} markers{{markers}} polyline{{polyline}} show-location /mapshow-location会在地图上显示用户当前位置但实测需要用户授权。坐标系如果不统一marker 会偏移这是地图页最常见的问题。5.3 AI 对话页面与流式回复处理AI 对话页用消息列表展示用户和 AI 的对话。Page({ data: { messages: [], inputValue: }, onInput(e) { this.setData({ inputValue: e.detail.value }); }, sendMessage() { const text this.data.inputValue.trim(); if (!text) return; const messages [...this.data.messages, { role: user, content: text }]; this.setData({ messages, inputValue: }); request.post(/ai/chat, { question: text }).then(data { this.setData({ messages: [...this.data.messages, { role: assistant, content: data.reply }] }); // 如果回复里带了 poiList 或路线可以提示跳转地图 if (data.poiList data.poiList.length 0) { this.setData({ suggestPoi: data.poiList[0] }); } }).catch(() { wx.showToast({ title: AI 服务暂不可用, icon: none }); }); } });这里的对话是请求-响应模式不是流式。毕业设计用非流式已经足够。如果论文想写“流式输出”需要后端改用 SSE 或 WebSocket小程序端也要相应处理复杂度会明显上升非必需不推荐。5.4 登录态和请求封装小程序所有请求统一走request.js自动携带 token// utils/request.js const BASE_URL http://127.0.0.1:8080/api; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method, data, header: { Content-Type: application/json, token: wx.getStorageSync(token) || }, success(res) { if (res.statusCode 200 res.data.code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail(err) { reject(err); } }); }); } module.exports { request, get(path, data) { return request(path, GET, data); }, post(path, data) { return request(path, POST, data); }, BASE_URL };登录一般在app.js的onLaunch中触发App({ onLaunch() { wx.login({ success: (res) { wx.request({ url: http://127.0.0.1:8080/api/auth/login, method: POST, data: { code: res.code }, success: (resp) { wx.setStorageSync(token, resp.data.data.token); } }); } }); } });本地调试时BASE_URL可以使用http://127.0.0.1:8080但真机预览时手机不能直接用电脑的 localhost需要改成电脑在局域网中的 IP比如http://192.168.1.100:8080。上线发布时则必须换成备案过的 HTTPS 域名。6. 联调运行与验证6.1 后端本地启动后端启动前先确认 MySQL 建好库application.yml配置正确server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/campus_nav?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl wx: mini-app: appid: 你的appid secret: 你的secret ai: api-key: 你的APIKey base-url: https://api.example.com/v1 model: your-model-name启动命令mvn spring-boot:run启动后用 curl 验证接口curl http://localhost:8080/api/poi/search?keyword图书馆 curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {code:test_code_from_devtools}注意wx.login得到的 code 只能使用一次而且有效期很短。用 curl 测试登录接口时需要从小程序开发者工具里取真实 code。6.2 小程序真机调试小程序在开发者工具里能跑通不一定在真机上能跑通。主要差异在三个方面网络地址。真机不能访问电脑 localhost需要改为局域网 IP 或已备案域名。合法域名。开发者工具可以勾选“不校验合法域名”真机预览时如果后端不是 HTTPS 域名必须也开启调试模式否则请求会被拦截。地理位置。真机定位比模拟器准但需要在app.json配置permission并在真机上授权。推荐流程先在开发者工具中调通接口再开“不校验合法域名”用真机预览调试最后再配置 HTTPS 域名做发布前验证。6.3 核心场景自查清单场景操作预期结果登录打开小程序自动获得 token个人中心显示用户标识地图进入地图页所有启用状态的 POI 显示标记搜索输入“图书馆”返回图书馆 POI可选中路线选择起点和终点地图画出路线显示距离或时间AI 问答输入“图书馆几点关门”返回知识库中的开放时间AI 转导航输入“去第一食堂”回答中点名第一食堂附带 POI可跳转路线异常AI 接口关闭显示“AI 服务暂不可用”不白屏反馈AI 回答下点“有用”数据库中 feedback 表新增记录这个清单既是联调检查项也是答辩现场演示脚本的雏形。7. 常见问题与排查路径7.1 错误现象对照表现象常见原因检查方式处理建议小程序请求显示 url not in domain list合法域名未配置开发者工具 Network 面板本地勾选“不校验合法域名”上线配置 HTTPS 域名code2session 返回 40013appid 或 secret 错误与小程序后台核对重新复制配置登录接口拼接 URL 报错参数顺序或编码问题打印完整 URL使用 URIBuilder 或 encode 参数地图 marker 位置偏移坐标系不一致对比坐标来源统一入库和展示都为 GCJ-02后端返回 500 但看不到日志缺少日志配置查看控制台异常堆栈加全局异常处理和 logbackAI 返回 model not found模型名错误用 curl 手动调用 AI 接口替换为服务商可用模型名AI 接口超时超时时间太短或网络慢检查 RestTemplate 配置设置 readTimeout 为 30 秒真机请求失败而开发者工具正常IP 或域名不可达手机浏览器访问后端地址使用局域网 IP 或 HTTPS 域名排查顺序建议先确认请求是否发出再确认后端是否收到再确认数据库查询结果最后确认返回数据格式是否被前端正确解析。7.2 排查链路请求层、服务层、数据层一个问题从现象倒推根因一般按三层查。请求层打开微信开发者工具的 Network 面板看请求是否发出、状态码是多少、响应体是什么。常见结果是 404路径错、500后端异常、403域名或权限问题。服务层看后端控制台或日志文件。SpringBoot 默认会打印异常堆栈先找第一行Caused by通常是真正的根因。如果接口被拦截还要查拦截器或过滤器是否放行了/api/auth/login。数据层如果接口返回空列表直接在数据库里执行同样的 SQL确认数据是否存在、状态字段是否为 1、关键词是否匹配。很多“AI 找不到地点”的问题实际不是模型问题而是 POI 表没数据。7.3 AI 接口接入的典型坑AI 接入有四个高频坑API Key 泄露。不要把 key 写在小程序前端或 GitHub 仓库里后端用环境变量或配置文件管理。上下文无限增长。每次请求只传当前问题和系统提示词即可不要反复累积历史消息否则 token 会越来越大。知识库检索为空时模型乱答。检索不到内容时要在提示词里明确写“如果知识库中没有相关信息请直接告知用户未知不要编造”。结果不可解析。如果让模型返回 JSON 结构要设置temperature偏低并在代码里做异常捕获解析失败时降级为普通文本展示。7.4 SpringBoot 版本和依赖冲突SpringBoot 3.x 项目使用javax.*包名会直接编译失败。常见错误是import javax.servlet.*无法解析。解决办法是改成jakarta.servlet.*或者把项目降到 SpringBoot 2.7.x。另一个典型问题是 MyBatis-Plus 与 SpringBoot 3.x 不兼容。MyBatis-Plus 3.5.3 之前的部分版本对 SpringBoot 3.x 支持不完整会出现Invalid value type for attribute factoryBeanObjectType这样的报错。遇到时优先升级 MyBatis-Plus 版本。如果本地创建 SpringBoot 项目时一直卡在下载依赖可以检查 Maven 镜像源是否配置为国内仓库地址同时确认 Java 版本和 Maven 版本是否匹配。8. 毕业设计演示、论文与答辩建议8.1 演示脚本要覆盖的三条业务线答辩演示不要只讲“系统能登录”要按业务线讲每条线都要有操作和结果。第一条线是地图导航。登录后进入地图页展示所有楼宇 POI搜索“图书馆”选择目的地系统画出路线。重点说明路线数据来自route_edge表路径计算使用 Dijkstra 算法。第二条线是 AI 问答。输入“图书馆几点关门”AI 基于知识库回答再输入“我要去第一食堂吃饭”AI 识别出导航意图并返回第一食堂 POI点击后跳转路线。这里要突出 AI 与传统表单搜索的区别。第三条线是数据管理。在管理端新增一个 POI比如“咖啡厅”完善名称、坐标、关键词回到小程序刷新新 POI 立即显示再问 AI“校园里哪里能买咖啡”AI 能检索到新地点。这条线能证明系统不是静态演示管理员维护数据后 AI 能力随之更新。8.2 论文里要写清楚的部分论文在“系统设计”部分要重点写清楚三块。一是数据库设计。给出 E-R 图说明 building、poi、route_edge 三张表如何支撑导航能力campus_knowledge 表如何支撑 AI 问答。二是接口设计。列出核心接口表格包括登录、POI 搜索、路线规划、AI 对话四个接口写明请求参数、返回结构和处理流程。三是 AI 接入设计。说明提示词如何构造知识库如何检索模型返回如何解析以及为什么不能让模型凭空回答未知信息。测试部分的重点不是“系统能跑”而是“系统在异常情况下如何处理”。至少写清 AI 接口超时、POI 无数据、数据库连接失败这三种场景下的表现。8.3 答辩高频问题与回复思路问题回复思路为什么使用大模型用户意图表达灵活大模型负责自然语言理解路径计算仍由后端算法完成两者分工明确大模型接口收费怎么办演示环境可以使用开发额度也可以设计成模型可配置切换论文中说明成本控制方案如果模型乱答怎么办系统设置知识库兜底和提示词约束检索不到内容时模型必须回答“未知”不编造路线规划为什么用 Dijkstra校园 POI 规模小Dijkstra 简单可解释能保证最短路径规模扩大后可扩展为 A*用户量大了怎么办POI 列表加缓存AI 接口异步化数据库加索引论文中给出扩展方向即可8.4 发布前检查清单写论文前和提交前最后做一次清单检查。数据库建表脚本完整POI 和知识库有至少 20 条能演示的数据。微信 appid 和 secret 配置正确登录接口能返回 token。AI 接口的 api-key 放后端没有提交到前端代码或 GitHub。小程序所有请求都走统一 request.jstoken 自动附带。地图 marker 坐标与路线 polyline 坐标系一致。AI 接口关闭时小程序显示友好提示不白屏。核心接口用 curl 或 Postman 跑通留存测试截图。论文中的表结构和代码与实际项目一致避免交错。演示至少准备 5 到 10 分钟脚本覆盖地图、搜索、路线、AI 问答和数据维护五条操作。校园导航系统的核心并不在于 AI 调用本身而在于地图数据是否准确、路线计算是否可靠、AI 是否能把用户问题转换成有效的导航动作。把这三层打通项目就真正完整了。对于初学者建议先把地图数据和路线接口做到可演示再接入 AI 对话最后再考虑流式输出、路线分享这类扩展功能。这个顺序能保证项目在最简形态下已经跑通后面每一步都是在已有基础上加分。