拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue前后端分离项目实战:旅游景点导游平台开发详解

SpringBoot+Vue前后端分离项目实战:旅游景点导游平台开发详解

每年到了毕业季,总能看到一堆人抱着“SpringBoot+Vue”的组合在论坛里蹲源码、找毕设。说实话,这个组合早就成了Java Web方向毕业设计的标准答案,但真正的问题是:源码拿到手之后,你能不能讲清楚、改得动、跑得起来。这篇东西我按一个完整可交付的“旅游景点导游平台”项目来拆,围绕SpringBoot、Vue、SQL脚本和接口文档这几块,把从数据库设计到前后端联调的完整链路捋一遍,既适合拿来当毕设参考,也适合想正经学一遍前后端分离项目的同学。

1. 项目整体设计与选题思路

1.1 这个平台到底解决了什么问题

你说一个旅游景点导游平台,核心用户其实是两类人:一类是到桂林旅游、想找景点信息、想看导游讲解的游客;另一类是管理景点资料、发布导游信息的平台运营方。游客端的需求很直白——打开页面能看到景点列表、景点详情、导游介绍,甚至能根据景点名称或地区搜索;运营端的需求则是要把这些数据管起来,增删改查、上下架、录入导游信息。

很多同学在做这类毕设的时候容易犯一个毛病,就是把“导游平台”做成一个只展示静态景点介绍的网站。这其实等于没做。真正的导游平台,至少要有一个“导游资源”的概念:每个景点可以关联一个或多个导游,导游有简介、有擅长路线、有讲解风格标签。游客可以看景点,也可以看“哪位导游负责这个景点”。这套关系在数据库里就是一张关联表的事,但在功能上,它决定了你系统里到底有没有“平台”的味儿。

别小看这一点。答辩的时候老师问“你的系统业务是什么”,你能说出“游客通过平台获取景点信息并选择合适的导游讲解”,和你只能说“游客看景点介绍”,完全是两个档次。

1.2 为什么选SpringBoot+Vue这套组合

SpringBoot + Vue 前后端分离,几乎已经成了Java Web毕业设计的默认模板。原因其实很实在:SpringBoot把Spring MVC那一大堆XML配置全部简化掉了,你写一个@RestController就能吐接口;Vue则有完整的前端生态,组件化开发让页面组织起来很清晰。对于一个人要在一个学期内完成整套系统的学生来说,这是学习成本最低、出活最快、而且招聘市场上最认可的组合。

从后端角度看,SpringBoot 2.x版本配MyBatis-Plus是现阶段最主流的搭配。MyBatis-Plus的BaseMapper让你连SQL都少写一大半,单表CRUD真的就只是几行代码的事。当然,不要因为有了它就连SQL都不学,这个概念你一定要清楚:MyBatis-Plus解决的是单表通用操作,真正复杂的多表联查、分组统计,该写SQL还是要写。

前端Vue这边,我用的是Vue 2 + Element UI。虽然Vue 3和Element Plus已经很成熟了,但你要考虑一个现实问题:网上的教程、论坛里的报错帖子、甚至你导师手里可能存着的参考代码,绝大多数还是Vue 2的。毕设求稳,Vue 2不是技术落后,而是生态成熟,你卡住的时候能搜到答案比什么都强。

前后端分离还有一个好处:接口文档变得非常重要。这也是这个项目里为什么要专门准备一份接口文档的原因。前后端分离之后,前端同学和后端同学是并行开发的,如果没有一份统一的接口约定,两边各自写完一对接,简直是一场灾难。

1.3 系统核心模块拆解

一个完整的景点导游平台,按我的习惯会拆成下面这几个模块:

  • 用户模块:游客注册登录、个人信息管理,后端用JWT做无状态登录认证。
  • 景点模块:景点列表分页展示、景点详情、按名称/地区搜索、景点图片管理。
  • 导游模块:导游信息维护、导游与景点关联、按景点查询导游。
  • 评论收藏模块:游客可以对景点评论、收藏景点(好评率是加分项)。
  • 管理后台:管理员对景点、导游、用户进行统一管理,包含数据统计看板。

模块划分的意义在于,它直接影响你数据库的表设计和接口粒度。很多同学上来就建表,建到一半发现字段关系理不清,就是因为没有先在脑子里面把模块边界画出来。模块定下来,表结构才能定下来,接口才能定下来。

2. 数据库设计与SQL脚本要点

2.1 表结构设计思路

数据库是整套系统的地基。地基没打好,后面写接口的时候到处补字段、改结构,那体验别提多难受了。按照上面拆的模块,我会把数据库设计成7张表:

表名用途关键字段
user用户表id, username, password, nickname, avatar, role, create_time
scenic景点表id, name, region, level, price, description, cover_image, status
guide导游表id, name, avatar, language, years, tags, introduction, scenic_id
comment评论表id, user_id, scenic_id, content, rate, create_time
favorite收藏表id, user_id, scenic_id, create_time
admin管理员表id, username, password, real_name, create_time
notice公告表id, title, content, create_time

核心关系我解释一下。guide表里直接放一个scenic_id,表示这个导游属于哪个景点。这是典型的一对多关系:一个景点有多个导游,一个导游只属于一个景点。如果你想做得更灵活一点,比如一个导游可以带多条线路,那就要拆第三张关联表guide_scenic出来。毕设级别用直接外键就可以了,但思路要能说清楚。

user表里的role字段我之前犹豫过,后来决定保留。虽然一般系统会把管理员单独放一张表,但通过role字段区分游客和管理员,能让登录逻辑变得非常统一——同一个登录接口,查user表拿到角色,如果是管理员就直接跳后台。这个做法在中小型系统里非常常见,省一张表,也省一套登录逻辑。

2.2 SQL脚本编写细节与初始化数据

SQL脚本是整个项目交付物里最容易被人忽略、但实际最要命的一个文件。老师要验收项目的时候,第一步就是看你能否把数据库跑起来。如果SQL脚本一执行就报错,或者字段对不上,那就算你代码写得再漂亮也白搭。

我写SQL脚本时有几个习惯,都在这个项目中用上了:

第一,建库建表语句必须带IF NOT EXISTS,插入数据前先查重。这样脚本重复执行不会报错。别笑,真有同学交上去的脚本执行到一半崩了,就是因为第二次跑的时候主键冲突。

CREATE DATABASE IF NOT EXISTS guide_platform DEFAULT CHARACTER SET utf8mb4; USE guide_platform; CREATE TABLE IF NOT EXISTS scenic ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT '景点名称', region VARCHAR(50) COMMENT '所在区域', level VARCHAR(10) COMMENT '景区等级', price DECIMAL(10,2) DEFAULT 0 COMMENT '门票价格', description TEXT COMMENT '景点介绍', cover_image VARCHAR(255) COMMENT '封面图地址', status TINYINT DEFAULT 1 COMMENT '1上架 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

第二,表结构里必须写COMMENT。注释这东西,平时写代码你可能嫌烦,但写SQL脚本的时候,每张表、每个关键字段的注释,都是答辩时的救命稻草。老师一看你的注释就知道你懂这个字段是干什么的。

第三,初始化数据要贴近真实场景。既然是桂林旅游平台,那像漓江、象鼻山、龙脊梯田、两江四湖、银子岩这些景点必须出现在初始化脚本里。导游数据也一样,名字、语种、服务年限、擅长路线都要像模像样。真实感的初始数据在演示系统的时候特别加分,一眼就能看出你是认真做过功课的。

2.3 字段设计中的几个坑

第一坑:密码字段。很多同学的user表里,password字段就定义一个VARCHAR(20),然后明文存进去。不是我看不起这种写法,是这种行为放到答辩现场真的会被老师抓住不放。正确做法是用BCrypt加密,加密后的字符串长度是60个字符左右,所以VARCHAR(100)才是安全的长度。明文存密码这种问题,属于教师答辩时最典型的“安全性追问”。

第二坑:金额字段。景点的门票价格,千万不要用FLOAT或者DOUBLE。二进制浮点数在比较和计算的时候会出现0.1 + 0.2 = 0.30000000000000004这种问题。数据库里关于钱的正确字段只有DECIMAL,也就是定点数。

第三坑:时间字段。统一用DATETIME,配合DEFAULT CURRENT_TIMESTAMP自动填充创建时间。更新时间的字段加上ON UPDATE CURRENT_TIMESTAMP属性,这样每次更新记录时时间自动变,不需要在Java代码里手动赋值。

3. 后端核心接口实现

3.1 项目结构分层与接口规范

SpringBoot后端项目的目录结构,我推荐按“controller / service / mapper / entity”四层来组织。这是最经典的Java Web分层方式,也是绝大多数毕设会采用的方式。模块化分包建议用common存放统一返回结果、统一异常处理、JWT工具类,用config放跨域和拦截器配置,把业务代码和基础设施分开,看着清爽,也方便老师检查代码质量。

统一返回结果这块是很多新手容易忽略的。如果不做统一封装,每个接口的返回格式都不一样,前端取值的时候就非常痛苦。我的做法是定义一个Result类,包含code、message、data三个字段:

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

这个类写好之后,所有接口返回Result.success(xxx),前端统一判断code === 200再取data,联调的时候能少吵一半的架。这也是我在接口文档里重点约定的一条:所有接口统一返回格式,前端不管拿到什么接口,解析方式都是一样的。

3.2 用户认证与JWT登录

登录认证这块,我用的方案是JWT,全称JSON Web Token。它的核心思想是:用户登录成功后,后端生成一个加密的Token字符串返回给前端,前端把Token存下来,之后每一次请求都在请求头里带上Authorization: Bearer <token>,后端验证这个Token是否有效。

为什么不用Session?因为前后端分离之后,后端不再View渲染页面,Session依赖的Cookie跨域很麻烦。而JWT是无状态的,后端不需要存任何会话数据,验签通过就放行。这对以后部署成多实例服务也是有好处。

JWT的结构分三段:Header(头部)、Payload(载荷)、Signature(签名)。Payload里可以放用户ID、用户名、角色等信息。要注意的是,JWT默认只是Base64编码,不是加密,所以不要往Payload里放密码之类的敏感信息。关键的签名是用HMAC SHA256算法做的,密钥放在后端的配置文件中,不能泄露。

具体到项目里,我用一个JwtUtil类负责生成和解析Token,用Spring Boot的拦截器HandlerInterceptor统一做登录校验。放行的路径(比如首页景点列表、游客注册登录)通过WebMvcConfigurer里的addInterceptors方法配置,其余接口全部拦截:

@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private JwtInterceptor jwtInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register", "/api/scenic/**"); } }

这里的/api/scenic/**放行也是有意为之的:景点信息属于公开数据,游客未登录也应该能看;但评论、收藏这类写操作就必须登录。

3.3 景点与导游核心接口示例

景点模块的接口,最核心的就是分页查询和详情查询。分页查询一定要支持三个参数:当前页pageNum、每页大小pageSize、搜索关键字keyword。我用MyBatis-Plus的Page对象来实现,示例代码如下:

@GetMapping("/api/scenic/page") public Result<IPage<Scenic>> getScenicPage( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword) { LambdaQueryWrapper<Scenic> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Scenic::getStatus, 1); if (keyword != null && !keyword.isEmpty()) { wrapper.like(Scenic::getName, keyword) .or().like(Scenic::getRegion, keyword); } wrapper.orderByDesc(Scenic::getCreateTime); return Result.success(scenicService.page(new Page<>(pageNum, pageSize), wrapper)); }

这里有个你想不到的细节:为什么分页查询里景点状态要写死为status = 1?因为游客端只能看到上架的景点,而管理后台需要看到全部,包括下架的。同一个接口,我在管理后台的Controller里单独写了一个/api/admin/scenic/page,不加status条件,两套接口各管各的。如果你只用一个接口,靠传参控制,也不是不行,但那样逻辑耦合,后面改起来容易出bug。

导游接口同理。按景点查导游的接口路径我定义为GET /api/guide/listByScenicId?scenicId=1,并且要求scenicId不能为空,否则直接返回错误。这种防御性校验虽然简单,但它是接口健壮性的体现,接口文档里必须写明哪些参数是必填的。

3.4 接口文档如何整理

接口文档是项目交付物的重要部分,很多同学把这个当成“写文档”,随便弄个Word文件把接口路径一列就完了。这完全不行。一份能用的接口文档,每个接口至少要包含:接口路径、请求方式、请求参数(名称、类型、是否必填、说明)、返回示例、错误码说明。

我拿到这个项目的时候,整理接口文档用的是Markdown格式,因为GitHub和Gitee都能直接渲染,浏览起来特别方便。文档结构按照模块分章节,每个模块下逐个列接口。比如景点模块的文档片段:

### 3.1 景点分页列表 - 请求地址:GET /api/scenic/page - 请求参数: | 参数名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | pageNum | int | 否 | 当前页码,默认1 | | pageSize | int | 否 | 每页数量,默认10 | | keyword | String | 否 | 景点名称或地区 | - 返回示例: { "code": 200, "message": "success", "data": { "records": [], "total": 20, "size": 10, "current": 1 } }

这里有个技巧:接口文档里的返回示例,不要手写,直接从Swagger或者实际接口调用里复制真实的JSON返回。手写很容易漏字段或者写错类型,坑了前端不说,答辩被问到也说不清。

4. 前端Vue页面开发与联调

4.1 前端工程搭建与路由设计

前端项目我用Vue CLI脚手架生成,项目结构是标准的:src/views放页面组件、src/router放路由配置、src/api放接口请求封装、src/utils放axios实例和工具函数。页面方面,游客端我规划了首页、景点列表、景点详情、导游列表、个人中心五个主页面;管理后台则是景点管理、导游管理、评论管理、用户管理、数据概览五个页面。

路由设计这里要展开说。Vue Router有两种模式:hash和history。毕设项目我推荐用hash模式,因为history模式刷新页面时后端必须有对应的路由重写规则,否则页面会404。部署到Tomcat里尤其要注意,history模式需要特殊配置,而hash模式什么都不用管,URL里多个#符号而已,无所谓。

路由守卫是另一个值得写的点。管理后台的页面,必须要登录且角色是管理员才能访问。我用Vue Router的beforeEach全局守卫来做控制:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); const role = localStorage.getItem('role'); if (to.path.startsWith('/admin') && (!token || role !== 'admin')) { next('/login'); } else { next(); } });

前端路由守卫和后端拦截器是双重保险。哪怕有人绕过前端守卫直接发请求,后端的JWT拦截器也会把没有Token的请求拦下来。这个逻辑要做到后端为主、前端为辅,思路要清晰。

4.2 页面核心组件实现

景点列表页是整个系统的门面,也是老师最先看到的功能页面。我用Element UI的Card卡片组件做景点展示,每个卡片包含封面图、景点名称、等级、地区、门票价格。搜索框放在页面上方,输入关键字回车或者点击搜索按钮调用分页接口。整套逻辑其实不复杂,但有几个细节能体现你的专业度:

图片地址的拼接要有统一规范。数据库里存的是相对路径/images/guilin/xiangbishan.jpg,前端请求接口返回的原始数据里不能直接拿来用,因为主机地址部分是缺失的。我在utils里写了一个方法,用环境变量里的VITE_BASE_URL来拼完整地址。这个做法在Vue项目里很常见,比在前端写死一个IP强得多。

分页组件的current-page和page-size要与后端分页接口的返回数据绑定。Element UI的Pagination组件的total要赋值为接口返回的total字段,否则分页不能正确显示总页数。这些看起来是小事,但是前后端字段对不上也会导致分页失效。

景点详情页的设计上,我用了一个el-tabs组件,分成“景点介绍”“导游信息”“用户评论”三个Tab。导游信息Tab里,前端通过scenicId调/api/guide/listByScenicId接口;用户评论Tab里则调/api/comment/list?scenicId=xxx接口。Tab加接口的组合,能让详情页的信息层次非常清楚,也方便扩展。

4.3 前后端联调与跨域问题

前后端联调最经典的问题就是跨域。前端开发时跑在localhost:8080,后端跑在localhost:9090,浏览器会见两个端口不同,直接判定为跨域请求,然后把响应拦掉。解决办法有几种:

  • 后端加@CrossOrigin注解(单接口级,麻烦);
  • 后端全局CORS配置类(推荐,一次配完);
  • 前端Vite或Webpack配proxy代理(用于生产环境部署)。

我的做法是在后端写一个全局配置类:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("http://localhost:*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

注意一个细节:在allowedOriginPatterns里,不要写*,因为allowCredentials(true)和通配符*是冲突的,会被浏览器拒绝。localhost:*表示允许所有端口,比一个一个写要省事。

联调时我还常遇到一个问题:前端请求能到达后端,但返回不了。排查思路是:先打开浏览器F12的Network面板,看请求有没有发出;再看请求的响应状态码,如果是401说明是拦截器拦了,403说明是跨域配置不对,404说明是路径写错了——尤其是Controller里的@RequestMapping类级路径加/api后,前端请求路径必须完全一致。这种排查思路要刻在脑子里,它能帮你省下来大量跟同学互相甩锅的时间。

5. 常见问题与排查技巧实录

5.1 数据库连不上与时区问题

这个项目的数据库连接报错,99%的原因集中在三个方面:URL写错、驱动版本不匹配、时区不对。SpringBoot 2.x默认用的MySQL驱动是com.mysql.cj.jdbc.Driver,这个驱动对时区要求很严格,连接串里不带时区参数会直接报Server returns invalid timezone的异常。

解决办法是在application.yml里加上serverTimezone=Asia/Shanghai:

spring: datasource: url: jdbc:mysql://localhost:3306/guide_platform?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4 username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver

还有个容易踩的坑:password直接写在application.yml里。如果是自己学习无所谓,但如果是交付项目,建议把密码改成环境变量占位符${DB_PASSWORD},然后在启动参数里传入。这个细节在答辩的时候提出来,老师会认为你有生产环境的意识。

5.2 接口返回中文乱码

中文乱码这个问题的根源是编码不一致。数据库表用了utf8mb4,但连接串没指定characterEncoding,或者后端接口返回时没有设置produces为application/json;charset=UTF-8。

SpringBoot里,最简单的处理方式是在application.yml里强制设置响应编码:

server: servlet: encoding: charset: UTF-8 force: true

force: true这个配置很关键,它会让所有响应强制使用UTF-8,不管请求头里写的什么编码。如果你已经配了这个还是会乱码,那就把目光转向数据库连接串,检查是不是没有加characterEncoding=utf8mb4。把URL里加上这个参数,重启,基本能解决。

5.3 打包部署时遇到的坑

前后端分离项目的打包部署,只要遇到了,十有八九是静态资源路径问题。前端执行npm run build后生成dist目录,你可以选择两种方式部署:一是把dist放到Nginx里,配置反向代理转发/api请求到后端;二是把dist直接放到SpringBoot的src/main/resources/static目录下,打成一个大Jar包。

毕设项目我推荐第二种,因为省事——一个Jar包解决全部。但有个问题要注意:SpringBoot默认只能识别static目录下的index.html,如果你用了Vue Router的history模式,刷新任意非首页路径都会404。解决办法就是在后端加一个路由重定向:

@Controller public class IndexController { @RequestMapping({"/{path:[^\\.]*}", "/**/{path:[^\\.]*}"}) public String forward() { return "forward:/index.html"; } }

这段代码的意思是:所有不带文件后缀的路径,全部转发到index.html,让前端路由自己去解析。[^\\.]*这个正则过滤了带点号的路径资源(比如logo.png),避免把静态资源请求也转发出去。

5.4 开发过程中最经典的三个报错

第一个是Whitelabel Error Page。后端启动成功,但访问某个路径返回这个默认错误页,意味着Controller没有匹配到这个路径。排查重点是看@RequestMapping的完整路径,尤其注意类级和方法级路径的拼接是否有歧义。

第二个是Invalid bound statement (not found)。Mapper接口定义了方法,但对应的XML文件里没有这个id的SQL,或者XML文件路径与Mapper接口路径不一致。我的习惯是让XML文件和Mapper接口放在同一个包目录下,并且开启mapper-locations通配扫描:

mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml

第三个是Consider defining a bean of type 'xxxMapper' in your configuration。启动时提示找不到Mapper的Bean,99%的原因是没有在主启动类上加@MapperScan注解,或者注解包的路径写错了。加上它就好。

5.5 问题排查的通用方法论

把所有问题收敛到最后,我发现一个通用排查链条:先看控制台日志报错信息,再看前端Network面板请求状态,最后看数据库里的数据状态。Log记录后端异常、Network记录接口交互、数据库记录数据结果,三层定位就是做项目调试的核心方法。

后端日志看什么?看堆栈第一行的异常类型和描述,NullPointerException就是空指针、DuplicateKeyException就是主键冲突、BadSqlGrammarException就是SQL写错。把这些常见异常的名字和含义记住了,以后看日志就不用一行一行读了,扫一眼就知道大致方向。

前端Network面板看什么?看HTTP状态码和响应内容。200不代表一定成功,还要看响应体里的code字段;500看后端日志;401看Token是否过期;404首先检查地址拼写。这套方法熟练之后,联调速度能提升一倍。

6. 项目答辩要点与扩展方向

6.1 答辩时容易被问到的问题

毕设答辩的套路其实很固定,老师翻来覆去就那几个问题,但答不好的话,项目做得再好也容易翻车。我列几个高频问题,你可以提前想好答案:

“你的系统安全性怎么考虑?”——从密码加密(BCrypt)、登录认证(JWT)、后端参数校验、SQL注入预防(用MyBatis-Plus预编译,拒绝字符串拼接)这几个角度答,基本滴水不漏。

“为什么用Redis做缓存没?你的系统有哪里需要缓存。”——如果你项目里没有用到Redis,要能坦诚地说系统当前的数据量不需要缓存,但要能说出在什么场景下需要加(比如景点详情页热点数据)。加了这个回答,老师会觉得你有架构思维。

“你的系统有哪些创新点?”——这个问题是送分题,也是送命题。别说什么“使用前后端分离技术”这种话。你可以说“系统把景点、导游、评论、收藏整合成闭环,游客能在一个平台完成信息获取到服务选择的完整链路”,这才是你说得清楚的创新。

“项目都有哪些角色?权限是怎么控制的?”——务必把你的用户角色(游客、管理员)说清楚,然后把后端拦截器如何拦截、前端路由守卫如何控制、管理员接口如何独立开这三个层次答出来。

在我看来,答辩的关键不在于把每个问题都答成标准答案,而在于你对自己项目代码的熟悉程度。每一行核心代码都要能解释为什么这么写,这才算真正掌握了这个毕设。

6.2 项目还能怎么扩展

如果你只想及格,做到上面的内容已经够了;如果你想拿个高分,那我建议你在这个基础上有针对性地做一点扩展。

第一个扩展方向是推荐系统。毕设级别的推荐系统不需要什么深度算法,简单的“基于用户浏览历史推荐同地区景点”就能支撑一篇很有说服力的论文。算分逻辑可以用“用户最近浏览的景点地区标签 + 同地区其他热门景点排序”,实现思路清晰、可解释性强。

第二个扩展方向是地图可视化。接入像Leaflet或高德地图JS API,把景点列表渲染到地图上,用户在地图上直接点击景点标记查看详情。这项工作的技术难度不大,但视觉效果非常出彩,答辩现场算是很有冲击力的演示素材。

第三个扩展方向是统计看板。管理后台加一些简单的统计报表——按地区统计景点数量、按月统计访问量、热门景点Top10。用ECharts画折线图、柱状图、饼图,不需要多复杂,但能让你的项目从“功能型系统”变成“数据型系统”,这个层次感是完全不同的。

第四个扩展方向是提升非功能性能。把景点详情页做成静态化处理,或者给热点数据加一层本地缓存;把上传的图片存到OSS而不是本地磁盘。这些都属于“一句话就能解释清楚价值”的优化,适合在论文的“不足与展望”部分写,也适合在答辩时回答问题。

6.3 我在实际开发中的几点感受

最后说点掏心窝子的话。就是这个项目,我在实际复盘的时候发现,最花时间的根本不是写代码,而是“改代码”——就是那种今天写完明天觉得不好推倒重来的过程。数据库字段命名不统一,后端返给前端的字段名没和文档对齐,前端在页面上把后端字段名的scenicId写成了scenic_id,联调时报错半天,结果是字段名不一致。这些教训印在脑子里以后,我再做项目就养成了一个习惯:先写接口文档,再定数据库字段,最后才写代码。

另外补充一个很实用的小建议:做这种带管理后台的系统,一定提前准备一套演示用的“排练脚本”,包括几条路径:游客注册登录 → 浏览桂林景点列表 → 查看漓江详情 → 查看导游信息 → 提交评论;管理员登录 → 新增景点 → 编辑导游信息 → 查看用户列表。演示的时候按这套脚本走,每一步都要提前确认数据是存在的、按钮是可点的。答辩现场的突发状况,九成都是因为演示时点了某个按钮发现数据为空或者页面报错。

做毕设这件事,说辛苦是真辛苦,但回头看,它其实是你把课堂上学的所有东西串成一条线的机会。把这个项目从头到尾啃下来,SpringBoot和Vue怎么配合、数据库表怎么设计、接口怎么定义,你就都有了切身的体感,这份体感比代码本身更值钱。

返回列表