一直在帮人看毕设项目,最近问得最多的一个就是"旅游自驾游攻略分享系统",SpringBoot + Vue这个组合几乎快成Java毕设的标配了。网上各种版本的源码倒是不少,但真正能讲清楚"为什么这么设计"的资料反而不多。这篇文章我就拿这个自驾游攻略查询与分享系统当例子,把从需求拆解、数据库设计、后端接口、前端页面到最终部署的完整链路过一遍。不管是你已经拿到了完整源码想看懂它,还是准备自己从零写一个类似的系统,这篇都能给你一些实际能用的东西。
项目本身的业务不复杂:用户注册登录、浏览自驾游攻略、按目的地或分类筛选、发布自己的攻略、收藏和评论。技术栈是SpringBoot做后端接口,Vue做前端页面,MySQL存数据。这种结构在毕设里非常典型,因为它把一个完整项目拆成了两块:前端管交互展示,后端管业务逻辑和数据,两边通过JSON格式的接口通信,分工清楚,也好排查问题。
我遇到的很多人一开始看到整套源码就懵了,代码太多不知道从哪里看起。其实只要抓住一条主线:数据是怎么从MySQL里取出来,经过后端处理,传到前端页面渲染出来的。顺着这条线读代码,整个系统就串起来了。
1. 需求拆解与功能模块划分
1.1 从标题看核心需求
"自驾游攻略方案分享系统"和"攻略查询系统",这两个关键词实际上点出了系统的两个核心方向:
分享意味着用户不仅仅是游客,还能产生内容。也就是说系统里一定要有"发布攻略"这个功能,涉及内容提交、图片上传、草稿保存这些操作。查询意味着要能高效地找到想要的内容,涉及攻略列表展示、按目的地/标题/分类搜索、分页加载这些逻辑。
再加上"自驾游"这个领域约束,攻略内容里通常要包含路线规划、加油点、住宿推荐、景点门票、路况信息、总里程和预计费用这类字段,这些都会直接影响数据库表的设计。
1.2 角色与功能范围
按照毕设的常见要求和实际使用场景,我把系统拆成三种角色:
- 游客(未登录):浏览攻略列表、查看攻略详情、搜索攻略、查看目的地分类。不需要登录就能看内容,这符合大多数内容平台的做法。
- 注册用户(已登录):在游客权限之外,可以发布攻略、编辑删除自己发布的攻略、收藏攻略、发表评论、修改个人资料、查看自己的收藏和发布记录。
- 管理员:用户管理,可以禁用违规账号;攻略审核与删除,处理不良内容;分类管理,添加/修改/删除目的地分类;查看系统统计数据,比如用户数量、攻略数量、总浏览量和评论量。
功能范围定到这个程度就够了。做得太简单,比如没有用户体系只有攻略展示,答辩时老师会问你"用户体系为什么没有";做得太复杂,比如加订单支付优惠券,那又超出了"攻略分享系统"的边界,反而是给自己挖坑。
1.3 技术选型的几个关键考量
技术选型这块我多说几句,因为答辩时候老师一定会问"你为什么选这个技术栈"。
为什么是SpringBoot而不是SSM?SpringBoot最大的价值是简化了配置。传统SSM要写一堆XML配置文件,SpringBoot用自动配置和Starter机制把这些都封装好了,一个注解就能启动一个Web应用。目前企业里新项目基本都是SpringBoot,学这个离实际工作需求更近,也更好讲。
为什么是Vue而不是JSP?这是个核心问题。旧的项目用JSP做服务端渲染,前后端代码混在一起,改前端就要重启后端。Vue把前端独立出来,通过Axios发请求向后端要数据,前后端完全分离。开发时还可以用Vue的脚手架独立调试页面,体验好很多。答辩时一定要说清楚"前后端分离"这个点,这是整个项目架构的灵魂。
持久层框架选什么?常见的方案是MyBatis-Plus。它在MyBatis基础上封装了通用的CRUD方法,大多数单表操作不用自己写SQL,通过LambdaQueryWrapper就能完成条件查询。同时它也保留了自定义SQL的能力,复杂查询写在XML里,灵活度和开发效率都有了。
版本选择要注意什么?这块网上踩坑的人特别多。SpringBoot目前有2.x和3.x两个大版本,3.x要求JDK17以上,很多学校的实验环境还停留在JDK8,所以我个人建议毕设选SpringBoot 2.7.x版本,搭配JDK8,兼容性最好。前端Vue推荐用2.x版本配Element-UI组件库,Vue3虽然新,但很多同学对Composition API不熟悉,用Option API写更容易上手,网上资料也多。记住:毕设求稳不求新,用自己最熟悉、资料最多的组合才是正确的。
2. 数据库设计与数据表关系梳理
2.1 核心表结构设计
数据库是项目的基石,表设计好了,后面的代码写起来就很顺。这个系统的核心表我列一下,每张表说明它的作用和关键字段。
用户表(user)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 用户名,唯一 |
| password | varchar(255) | 密码(MD5加盐或BCrypt加密) |
| nickname | varchar(50) | 昵称 |
| avatar | varchar(255) | 头像地址 |
| role | varchar(20) | 角色(USER/ADMIN) |
| status | tinyint | 状态(1正常/0禁用) |
| create_time | datetime | 注册时间 |
密码必须加密存储,明文存密码是很严重的低级错误。BCrypt加密比MD5安全性高很多,Spring Security里有现成的BCryptPasswordEncoder可以用。
攻略表(strategy)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| title | varchar(100) | 攻略标题 |
| destination | varchar(100) | 目的地 |
| cover_image | varchar(255) | 封面图 |
| category_id | bigint | 所属分类 |
| user_id | bigint | 发布者 |
| content | text | 攻略正文(富文本内容) |
| mileage | int | 总里程(公里) |
| days | int | 行程天数 |
| budget | decimal(10,2) | 人均预算 |
| view_count | int | 浏览量 |
| like_count | int | 点赞数 |
| status | tinyint | 状态(1待审核/2已发布/3已驳回) |
| create_time | datetime | 发布时间 |
destination、mileage、days、budget这几个字段就是"自驾游"场景的差异化体现,纯旅游攻略网站一般不会有这些字段。答辩时能说出"这些字段来源于自驾游用户在做路线规划时的真实关注点",比空谈技术加分很多。
分类表(category)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| name | varchar(50) | 分类名(如"川西环线""新疆独库公路") |
| description | varchar(255) | 分类描述 |
| sort_order | int | 排序权重 |
目的地分类的设计有两种思路:一种是把目的地直接写死在代码里,简单但扩展差;另一种是设计分类表,由管理员维护。毕设建议用表,理由还是那句话:答辩好讲,"满足分类动态扩展的需求"。
收藏表(favorite)和评论表(comment)
收藏表字段简单:id、user_id、strategy_id、create_time,联合唯一索引保证一个用户对一篇攻略只能收藏一次。评论表同样关联用户和攻略,加一条parent_id字段支持楼中楼回复(可做可不做,做了是亮点),还有content和create_time。
2.2 表关系的理解方式
这四张核心表的关系就一句话:用户与攻略是一对多(一个用户可以发布多篇攻略),用户与收藏是多对多(通过收藏表关联),攻略与评论是一对多(一篇攻略下面有多条评论),分类与攻略是一对多(一个分类下有多篇攻略)。在MySQL里,多对多关系是通过一张中间表实现的,收藏表就是那个中间表。
我看过不少同学的源码,数据库这块最容易出现的问题有两个:一是字段没有统一的命名规范,userName和user_name混着用,后端映射的时候特别容易出bug;二是没有设计status审核状态字段。很多毕设系统用户发完攻略直接上架,老师如果问"如何控制内容质量""有没有审核机制",就会答不上来。加一个状态字段就能回答"通过审核机制避免垃圾内容",成本几乎为零,效果却很好。
3. 后端接口与核心业务实现
3.1 后端项目结构规划
拿到源码以后,先看包结构。标准的SpringBoot项目包结构应该是这样的:
com.example.travel ├── TravelApplication.java ├── controller // 控制层 │ ├── AuthController.java │ ├── StrategyController.java │ ├── CategoryController.java │ ├── FavoriteController.java │ └── CommentController.java ├── service // 业务层 │ ├── UserService.java │ ├── StrategyService.java │ ├── CategoryService.java │ └── CommentService.java ├── mapper // 持久层(MyBatis-Plus的Mapper接口) │ ├── UserMapper.java │ ├── StrategyMapper.java │ └── CommentMapper.java ├── entity // 实体类 │ ├── User.java │ ├── Strategy.java │ └── Comment.java ├── dto // 数据传输对象 │ ├── LoginDTO.java │ ├── StrategyDTO.java │ └── PageResult.java ├── common // 通用响应封装、异常处理、工具类 │ ├── R.java │ └── GlobalExceptionHandler.java └── config // 配置类 └── WebConfig.java这套结构就是典型的三层架构:Controller接收请求,Service处理业务逻辑,Mapper操作数据库。如果你拿到的源码没有按这个结构分层,无论代码能不能跑,都建议你按这个思路重构一下,因为这是答辩时老师最熟悉的架构,你讲起来顺,他听起来也顺。
3.2 登录认证与JWT设计
认证方案最常用的是JWT(JSON Web Token)。原理很简单:用户登录成功后,后端验证用户名密码正确,生成一个包含用户ID和角色信息的加密token返回给前端。前端把token存在localStorage里,每次请求在header里带上,后端拦截器验证token有效就放行。
生成JWT的核心代码不复杂:
// 生成token,有效期设置为24小时 String token = Jwts.builder() .setSubject(user.getUsername()) .claim("userId", user.getId()) .claim("role", user.getRole()) .setExpiration(new Date(System.currentTimeMillis() + 86400000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();对应的拦截器逻辑就是:从请求头拿到token,如果解析失败或过期就返回401状态码,前端收到401自动跳转登录页。这里有个细节容易忽略:拦截器要排除登录接口、注册接口和攻略列表/详情查询接口,因为这些是游客也能访问的。判断逻辑不是简单"全部拦截",而是维护一个白名单列表,放行白名单路径,其余的校验token。
3.3 攻略发布的保存逻辑
发布攻略这块涉及一个"联合保存"的问题:用户提交的攻略,除了基本信息(标题、目的地、天数、预算等),还有正文内容。前端富文本编辑器生成的是HTML片段,后端把它当作一个大字符串存到content字段即可,不需要特殊处理。
接口代码逻辑大概是这样的:
@PostMapping("/strategy") public R saveStrategy(@RequestBody StrategyDTO dto) { // 从token中获取当前登录用户ID Long userId = JwtUtil.getUserId(request); Strategy strategy = new Strategy(); BeanUtils.copyProperties(dto, strategy); strategy.setUserId(userId); strategy.setStatus(1); // 待审核 strategy.setViewCount(0); strategy.setLikeCount(0); strategyService.save(strategy); return R.success("发布成功,等待审核"); }注意status字段默认值是"待审核",这是前面设计审核机制时的落地代码。如果你做的是免审核版本,这里状态直接设为"已发布"也行,但建议保留待审核这个逻辑,答辩的时候把它当作一个完整流程中的重要防控点来说。
3.4 攻略列表的条件分页查询
攻略首页是系统的核心展示页面,查询条件比较多:按关键词搜索标题、按目的地/分类筛选、按热度排序、按时间排序、还有分页。MyBatis-Plus的分页查询写起来很简洁:
Page<Strategy> page = new Page<>(current, size); LambdaQueryWrapper<Strategy> wrapper = new LambdaQueryWrapper<>(); // 关键词搜索 if (StringUtils.hasText(keyword)) { wrapper.like(Strategy::getTitle, keyword); } // 分类筛选 if (categoryId != null) { wrapper.eq(Strategy::getCategoryId, categoryId); } // 排序:按浏览量和时间降序 wrapper.orderByDesc(Strategy::getViewCount) .orderByDesc(Strategy::getCreateTime); // 仅查询已发布数据 wrapper.eq(Strategy::getStatus, 2); Page<Strategy> result = strategyMapper.selectPage(page, wrapper);这里有两个优化点值得写进论文里:第一是排序字段加索引,否则数据量大到一定程度,order by会走文件排序,性能会下降;第二是避免select *,MyBatis-Plus的selectPage默认会把所有字段查出来,包括很长的content文本。攻略列表页只显示标题、封面、目的地、预算这些信息,根本不展示正文,把content查出来白白增加网络传输和内存开销。优化方案是在实体类的content字段上加@TableField(select = false)注解,需要正文时再用单独的方法查询,这样列表接口就轻量多了。
4. 前端Vue页面与交互实现重点
4.1 前端工程初始化
Vue前端项目的目录结构我按功能模块组织,这套结构做中大项目也不乱:
src ├── api // 接口请求封装 │ ├── auth.js │ ├── strategy.js │ └── user.js ├── assets // 静态资源 ├── components // 公共组件 │ ├── Header.vue │ ├── Footer.vue │ └── StrategyCard.vue ├── router // 路由配置 │ └── index.js ├── store // 状态管理(简单项目用Vuex即可) │ └── index.js ├── views // 页面 │ ├── Home.vue │ ├── StrategyDetail.vue │ ├── StrategyEdit.vue │ ├── Login.vue │ ├── Register.vue │ └── admin │ ├── UserManage.vue │ └── StrategyManage.vue ├── utils // 工具函数,如axios封装 │ └── request.js ├── App.vue └── main.js很多人都卡在"环境启动不了"这一步。如果你也是,建议用以下几个命令先确认Vue相关环境是否就绪,检查node版本(建议16以上)、确认npm镜像源可以正常访问。拿到的源码一般自带package.json,在项目根目录执行npm install安装依赖,然后npm run serve启动开发服务器,默认端口8080,和SpringBoot的8080会冲突,所以记得在vue.config.js里把前端端口改成8081。
# 查看node版本 node -v # 安装项目依赖(第一次会比较慢) npm install # 启动开发服务 npm run serve4.2 axios封装与请求拦截
前端的核心是axios请求封装。在utils/request.js里创建一个axios实例,设置baseURL指向后端地址(开发环境一般是http://localhost:8080/api),然后加请求拦截器和响应拦截器:
import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const request = axios.create({ baseURL: '/api', // 通过vue.config.js的proxy代理,避免跨域 timeout: 10000 }) // 请求拦截器:带上token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }) // 响应拦截器:统一处理错误码和401跳转 request.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } Message.error(error.response?.data?.message || '请求失败') return Promise.reject(error) } )跨域问题是前后端分离项目里必踩的坑。解决方式有两种,一种是在后端写CORS跨域配置,另一种是前端开发服务器用proxy代理。我建议用proxy代理,因为生产部署时前后端通常同域部署,不需要跨域;只有开发环境才需要代理,这样配置更贴近生产环境:
// vue.config.js module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }这样前端所有以/api开头的请求都会被转发到后端的http://localhost:8080,并且自动去掉/api前缀,后端Controller里就不需要加统一前缀了。
4.3 攻略列表页和详情页实现
首页列表的设计上,用卡片式布局展示攻略:封面图大图展示、标题醒目、目的地标签、里程天数预算信息清晰。卡片按钮点击跳转详情页。数据加载用el-pagination组件实现分页。
比较关键的优化点是列表页的骨架屏和Loading状态。数据没回来时先显示"加载中",避免用户看到空白页。用Element-UI的v-loading指令即可实现:
<div v-loading="loading"> <el-row :gutter="20"> <el-col :span="8" v-for="item in strategyList" :key="item.id"> <strategy-card :data="item" /> </el-col> </el-row> </div>详情页在mounted里根据路由参数中的id去请求详情接口,同时需要判断当前用户是否已收藏该攻略,已收藏则收藏按钮置灰。这个页面的核心就是"把攻略的全部信息展示出来,同时处理交互状态"。攻略正文用的是v-html直接渲染HTML内容,这里有个安全注意点:v-html存在XSS注入风险,如果内容是由系统自己管理的(管理员审核后再发布),风险相对可控,但绝不能在前端直接渲染用户输入的原始内容而不做任何过滤。
4.4 富文本编辑器的集成
发布攻略的页面是另一个难点。标题、目的地、天数、预算这些字段用普通表单即可,但正文需要一个富文本编辑器。比较常用的方案有WangEditor和TinyMCE。WangEditor是国产组件,中文文档完善,集成方式也很简单:
import E from 'wangeditor' mounted() { this.editor = new E('#editor') this.editor.config.placeholder = '分享你的自驾游路线和心路历程...' this.editor.create() }富文本编辑器的图片上传需要单独处理。编辑器的图片是通过base64变成很长一串字符串传到后端,还是先上传到服务器拿回URL再插入?对于图片比较多的攻略,base64会导致接口报文特别大,请求会超时。建议在editor.config里配置uploadImgServer指向后端的图片上传接口,后端用MultipartFile接收文件,保存到本地指定目录,返回可访问的URL,编辑器自动把URL插入正文。
这里就牵扯出一个后端配置:静态资源映射。SpringBoot默认只处理代码内的静态资源,你上传的图片如果放在服务器某个目录(比如/home/travel/images),需要写一个配置类把/images/**映射到那个目录,否则图片URL访问不到:
@Configuration public class WebConfig implements WebMvcConfigurer { @Value("${file.upload-path}") private String uploadPath; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceHandler("file:" + uploadPath); } }5. 从本地到部署:完整运行指南与踩坑记录
5.1 环境准备清单
这套系统跑起来需要的环境,直接列个清单对照着准备:
- JDK 8(对应SpringBoot 2.x版本)
- Maven 3.6+
- MySQL 5.7或8.0
- Node.js 16+
- 可选:Navicat或任意MySQL客户端工具
- 可选:Redis(如果项目里用了缓存/验证码功能)
数据库部分我提醒一个初学容易翻车的点:MySQL 8.0的驱动配置和5.7略有不同。MySQL 8.0以上用com.mysql.cj.jdbc.Driver,5.7及以前用com.mysql.jdbc.Driver。另外连接字符串最好加上时区参数serverTimezone=Asia/Shanghai,否则可能会报时区错误。
5.2 配置文件解读
拿到源码后,先看application.yml或application.properties,把数据库账号密码、Redis地址(如果有)、文件上传路径改成自己本地的配置。这就是整个后端能启动的基础:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/travel?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 自己的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 100MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0log-impl配置成StdOutImpl会在控制台打印所有SQL语句,开发时强烈建议开启,能帮你排查很多数据库操作的问题。部署到生产前再关掉。
5.3 前后端启动顺序和测试流程
启动顺序一般是这样:
- 先启动MySQL,确认可以正常连接。用客户端工具把项目提供的sql脚本导入,建立数据库和表结构,顺便看看基础数据。
- 启动后端。在项目根目录执行
mvn spring-boot:run,看到"Started TravelApplication"的日志表示启动成功。这时用Postman或者浏览器访问http://localhost:8080/api/strategy/list这样的接口,看有没有数据返回。 - 启动前端。进入前端目录执行
npm install和npm run serve,打开浏览器访问http://localhost:8081,能正常显示首页列表数据,说明整套系统已经通了。
部署到服务器的话,后端一般打成jar包:mvn clean package -DskipTests,然后用nohup java -jar xxx.jar > log.txt 2>&1 &在后台运行。前端执行npm run build生成dist目录,把dist里的静态文件用Nginx托管,然后配置Nginx把/api开头的请求反向代理到后端的8080端口。这里再强调一次:[bat]Nginx的配置核心是location /api的实现,因为采用的是前端代理方案,生产环境的转发规则需与开发环境保持一致,确保接口路径一致。
5.4 高频踩坑记录
坑1:端口被占用。启动后端时报"Port 8080 was already in use",说明有别的进程占用了8080。Windows下用netstat -aon|findstr "8080"查进程号,然后taskkill /PID 进程号 /F 强制结束。或者直接改后端端口。
坑2:前端接口404。页面能打开但数据加载不出来,F12看Network面板,接口返回404。这种情况一般是baseURL配置和后端路径不匹配,或者是代理没生效,先确认vue.config.js里的proxy配置对不对,再确认后端Controller的@RequestMapper路径。把这两边对齐,问题基本就解决了。
坑3:数据库中文乱码。MySQL的连接字符串一定要加characterEncoding=utf8,创建数据库时要指定字符集DEFAULT CHARSET=utf8mb4,创建表的时候同理。utf8mb4比utf8更好,因为它支持表情符号,现在这个环境下谁不发点表情包呢。
坑4:Maven依赖下载慢或失败。把Maven的中央仓库镜像换成阿里云镜像,在settings.xml里加:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/central</url> </mirror>Node的npm也是一样,用npm config set registry https://registry.npmmirror.com换成国内镜像,装依赖能快很多。
6. 从代码到论文:毕设文档的写作思路
6.1 论文结构怎么组织
源码跑通了,论文也是大头。毕设论文的结构通常是这样:
- 第1章 绪论:写研究背景和意义,从自驾游市场的发展、用户对攻略信息的需求切入,过渡到"开发一个攻略分享系统"这个主题,再写国内外研究现状。
- 第2章 关键技术介绍:SpringBoot、Vue、MyBatis-Plus、MySQL,每样介绍基本情况、核心特性、为什么选它。这一章是凑篇幅的主力,但也要写得有信息量,不能光抄百度百科。
- 第3章 系统分析:可行性分析(技术、经济、操作三个维度)、需求分析(功能需求+非功能需求)、用例图、业务流程。
- 第4章 系统设计:总体架构图、功能模块划分、数据库表设计(每张表写清楚字段、类型、说明、主外键关系)。
- 第5章 系统实现:按功能模块写,每个模块放核心代码、界截图、实现说明。
- 第6章 系统测试:测试方法说明,写一些测试用例表,比如"输入正确用户名密码登录成功的用例""输入错误密码提示失败的用例",再分析测试结果。
- 第7章 总结与展望:写项目完成的内容、遇到的问题、不足和后续改进方向。
6.2 让论文更有分量的几个细节
论文最怕的就是看起来像把代码贴一遍。我建议从这几个方面加深深度:
第一,把"为什么"讲清楚。比如选择JWT而不是Session,要说Session在集群环境下存在session共享问题,JWT无状态、天然支持分布式;选择Vue而不是JSP,要说前后端分离提高开发效率、降低维护成本;富文本编辑器为什么选WangEditor,因为它文档全、社区活跃、集成简单。每个选择都有自己的理由,写出来就是深度。
第二,把测试章节做实。很多人的测试章节就写"系统能正常运行",这等于没写。好的做法是:先用黑盒测试的等价类划分法设计用例,比如登录名"已注册用户""未注册用户""空值""超长字符串"各设计一条用例,预期结果分别是什么;然后用表格把所有用例汇总,最后写一个"实测结果",每条用例是否通过。这一章其实不难写,但工作量摆在那里,认认真真做的人不多,做完了论文的质量就上来了。
第三,画图要规范。用例图、E-R图、系统架构图、流程图,这些图是论文的门面。Windows下用Visio或ProcessOn画就行,免费版够用,前提是要画得规范,不要截图代码代替。
7. 答辩常见问题与项目亮点提炼
7.1 老师最爱问的几个问题
答辩时老师问的问题其实是有限的,就集中在几个方向。我列一下高频问题清单:
- "你这个系统的角色权限是怎么控制的?"回答思路:后端用拦截器校验JWT里的角色信息,管理员接口有单独的权限判断,前端用路由守卫控制页面访问权限。双端都控制,防止绕过前端直接调接口。
- "数据库表为什么这么设计?为什么用这个字段?"回答思路:从业务场景出发,比如攻略表增加里程、天数、预算字段,是因为自驾游用户做攻略时最关心这三个数据,这也是区别于普通旅游网站的地方。
- "查询性能怎么优化?"回答思路:分页查询、索引、避免查询不必要的字段(content字段不select)、查询条件动态拼装。数据量大时可以考虑Redis缓存攻略详情,这些点提前想好。
- "这么多个表之间是什么关系?"回答思路:画出E-R图,说明一对一、一对多、多对多的关系,以及多对多如何通过中间表来实现。
- "这个项目你遇到的最大困难是什么?怎么解决的?"回答思路:选一个真实的问题来讲,比如富文本图片上传导致接口超时、跨域配置调试了很久、数据量大了之后列表页加载变慢。讲完问题一定要讲解决方案,体现独立解决问题的能力。
7.2 项目经验的延伸方向
如果你学有余力,想让这个项目在简历或答辩中更有竞争力,有几个低成本高性价比的改进方向:
Elasticsearch全文检索。攻略系统的搜索场景和ES的全文检索天然契合。把攻略数据同步到ES,搜索时按关键词的匹配度排序,比MySQL的like模糊查询快不少。这个改动要写的代码量不小,但对项目含金量提升很明显。
Redis缓存优化。把攻略详情、热门攻略列表缓存到Redis,减少对MySQL的直接访问压力。设好合理的过期时间,再加缓存穿透、缓存击穿的简单处理,这一套操作能体现出你懂性能和并发,这些都是加分项。
评论的敏感词过滤。在评论发布接口加一个简单的DFA(确定性有限自动机)敏感词过滤算法,把命中的词替换成*,既简单又实用,也很容易讲明白原理。
Spring Security做更细粒度的权限控制。如果只是想满足基本要求,JWT已经够了。但如果你想展示自己会主流的权限框架,把Spring Security集成进来,尤其是理解它的过滤器链机制,这个项目就更接近企业级水平了。
7.3 个人实操中的一些心得
最后分享几点我在帮人调试这类项目时的真实体会。第一条,任何毕设项目拿到手,先跑通再研究。环境变量、数据库密码这些配置问题能卡一个新手好几天,如果一套源码你调了三四个小时还没起来,建议先彻底清理Maven本地仓库的缓存,再把错误日志复制到搜索引擎按关键词找答案,大部分坑别人都踩过,不存在只有你遇到的神奇bug。第二条,数据库的SQL脚本是第一文档。表设计合理不合理、数据字段齐不齐,看一遍SQL全知道,比看一堆代码文档都直观。第三条,如果你的时间不够充裕,按我之前说的版本组合来搭环境,别一上来就追求最新技术,把一个自己能讲清楚、能跑得通、逻辑完整的系统做到位,比什么都有说服力。
这个自驾游攻略系统本身的技术含量不算顶尖,但它承载了一个完整项目从需求到设计、从代码到部署、从测试到论文的全部流程。你把它彻底吃透了,无论是应付答辩,还是借着它入门企业里真实的Java开发流程,都会收获不小。照着这篇文章的思路从数据库开始一步步过代码,遇到不懂的再回头查资料,整体过两三遍之后你会发现,它没那么神秘,这就是一次扎扎实实的全栈实战训练。