SpringBoot+微信小程序+AI智能校园导航系统毕业设计全解析
2026/9/7 14:28:00 网站建设 项目流程

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 或 11,SpringBoot 3.x 用 Java 17
SpringBoot2.7.x 或 3.2.x毕业设计建议从 2.7.x 起步,资料多,依赖冲突少
MyBatis-Plus3.5.x需检查与 SpringBoot 的兼容性
MySQL5.7 / 8.0本机开发可用 5.7,生产建议 8.0
Redis可选用于缓存 POI 列表或存储登录态,不是必须项

这里要特别注意 SpringBoot 版本带来的包名差异。SpringBoot 2.7.x 使用javax.servletjavax命名空间,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-urlapi-keymodel三项,就能切换不同服务商。论文里也可以写清楚“采用 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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>

注意:MyBatis-Plus 的 starter 版本要和 SpringBoot 主版本匹配。SpringBoot 3.x 需要 MyBatis-Plus 3.5.3 以上版本,否则启动会报错。

统一返回结构建议写成Result<T>

@Data public class Result<T> { private Integer code; private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "success"; r.data = data; return r; } public static <T> Result<T> error(String msg) { Result<T> r = new Result<>(); r.code = 500; r.msg = msg; return r; } }

所有接口统一返回这个结构,小程序端只需要判断code是否为 200。

4.2 微信登录 code2session 流程

小程序端调用wx.login()得到临时code,后端拿code调用微信接口换取openid。具体流程:

  1. 小程序调用wx.login获取code
  2. 小程序把code传给后端。
  3. 后端请求https://api.weixin.qq.com/sns/jscode2session
  4. 微信返回openidsession_key
  5. 后端用openid查询用户,不存在则注册,然后生成自定义token返回小程序。

后端示例:

@RestController @RequestMapping("/api/auth") public class AuthController { @Resource private RestTemplate restTemplate; @Resource private WxConfig wxConfig; @Resource private UserService userService; @PostMapping("/login") public Result<LoginVO> login(@RequestBody LoginRequest request) { String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + wxConfig.getAppid() + "&secret=" + wxConfig.getSecret() + "&js_code=" + request.getCode() + "&grant_type=authorization_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 Result<List<PoiVO>> search(@RequestParam String keyword) { LambdaQueryWrapper<Poi> wrapper = new LambdaQueryWrapper<>(); wrapper.like(Poi::getName, keyword) .or().like(Poi::getType, keyword) .or().like(Poi::getKeywords, keyword) .eq(Poi::getStatus, 1); List<Poi> list = poiService.list(wrapper); return Result.success(list); } @GetMapping("/list") public Result<List<Poi>> list() { LambdaQueryWrapper<Poi> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Poi::getStatus, 1); return Result.success(poiService.list(wrapper)); } }

路线规划接口使用 Dijkstra 算法在route_edge表构造的图上求最短路径:

public RouteVO planRoute(Long startPoiId, Long endPoiId) { List<RouteEdge> edges = routeEdgeMapper.selectList(null); Map<Long, List<RouteEdge>> graph = buildGraph(edges); Map<Long, Long> prev = new HashMap<>(); Map<Long, Integer> dist = new HashMap<>(); PriorityQueue<long[]> 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 对话接口是系统的重点。整体处理流程分成四步:

  1. 接收用户问题。
  2. 先做轻量级本地检索:从 POI 表查候选地点,从campus_knowledge表查知识库内容。
  3. 把候选地点和知识库信息拼进提示词。
  4. 调用大模型接口,返回文本回复和可选的地点对象。

核心代码如下:

@RestController @RequestMapping("/api/ai") public class AiChatController { @Resource private PoiService poiService; @Resource private CampusKnowledgeService knowledgeService; @Resource private AiService aiService; @PostMapping("/chat") public Result<AiReplyVO> chat(@RequestBody ChatRequest request) { String question = request.getQuestion(); // 1. 本地检索候选 POI List<Poi> poiCandidates = poiService.searchByNameOrKeyword(question); // 2. 检索知识库 List<CampusKnowledge> 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, List<Poi> poiList, List<CampusKnowledge> knowledgeList) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(aiConfig.getApiKey()); String systemPrompt = buildSystemPrompt(poiList, knowledgeList); Map<String, 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) )); HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(body), headers); ResponseEntity<String> 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 found

AI 接口需要处理几类异常:

  • 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 style="width: 100%; height: 100vh;" latitude="{{latitude}}" longitude="{{longitude}}" scale="{{scale}}" markers="{{markers}}" polyline="{{polyline}}" show-location ></map>

show-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.jsonLaunch中触发:

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?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/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 小程序真机调试

小程序在开发者工具里能跑通,不一定在真机上能跑通。主要差异在三个方面:

  1. 网络地址。真机不能访问电脑 localhost,需要改为局域网 IP 或已备案域名。
  2. 合法域名。开发者工具可以勾选“不校验合法域名”,真机预览时如果后端不是 HTTPS 域名,必须也开启调试模式,否则请求会被拦截。
  3. 地理位置。真机定位比模拟器准,但需要在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 但看不到日志缺少日志配置查看控制台异常堆栈加全局异常处理和 logback
AI 返回 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 接入有四个高频坑:

  1. API Key 泄露。不要把 key 写在小程序前端或 GitHub 仓库里,后端用环境变量或配置文件管理。
  2. 上下文无限增长。每次请求只传当前问题和系统提示词即可,不要反复累积历史消息,否则 token 会越来越大。
  3. 知识库检索为空时模型乱答。检索不到内容时,要在提示词里明确写“如果知识库中没有相关信息,请直接告知用户未知,不要编造”。
  4. 结果不可解析。如果让模型返回 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.js,token 自动附带。
  • 地图 marker 坐标与路线 polyline 坐标系一致。
  • AI 接口关闭时,小程序显示友好提示,不白屏。
  • 核心接口用 curl 或 Postman 跑通,留存测试截图。
  • 论文中的表结构和代码与实际项目一致,避免交错。
  • 演示至少准备 5 到 10 分钟脚本,覆盖地图、搜索、路线、AI 问答和数据维护五条操作。

校园导航系统的核心并不在于 AI 调用本身,而在于地图数据是否准确、路线计算是否可靠、AI 是否能把用户问题转换成有效的导航动作。把这三层打通,项目就真正完整了。对于初学者,建议先把地图数据和路线接口做到可演示,再接入 AI 对话,最后再考虑流式输出、路线分享这类扩展功能。这个顺序能保证项目在最简形态下已经跑通,后面每一步都是在已有基础上加分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询