ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue公交线路查询系统源码解析:从数据库设计到前后端联调

SpringBoot+Vue公交线路查询系统源码解析:从数据库设计到前后端联调 网上打着“完整版”旗号的Java项目源码一抓一大把但很多下载下来不是缺依赖就是启动报错能拿来直接跑、还能看得懂核心逻辑的其实不多。这套公交线路查询管理系统采用的是SpringBoot Vue MyBatis MySQL这套Java Web开发里最经典的技术组合前后端分离功能覆盖线路查询、站点管理、车辆排班、公告发布等企业级管理系统常见的业务模块。无论你是做课程设计、毕业设计还是想拿一个练手项目研究主流框架之间的协作方式这套源码都很值得花时间走一遍。我拿到之后从数据库初始化、后端启动到前端联调完整跑通了这篇就把整理出的核心设计和几个容易踩的坑写出来。1. 项目概述与核心功能拆解很多人看到“企业级”三个字就下意识觉得项目一定很庞大其实这个词在开源项目里已经被用滥了。这个项目的定位更准确地说是“具备企业级管理系统通用规范的Java Web项目”。它有后台管理系统的标配元素登录鉴权、统一异常处理、分页查询、角色区分、操作日志记录。业务上围绕公交线路做了线路查询、站点维护、线路站点绑定、车辆分配、班次排班、公告管理。数据之间不是简单的单表操作而是典型的一对多和多对多关系比如一条线路包含多个站点一辆车固定跑一条线一个站点可以有多条线路经过。这些关系正好是MyBatis关联查询发挥价值的地方。我拿到源码后先把数据库脚本执行了总共8张核心表。默认管理员账号可以直接登录前端页面里就能看到线路列表和站点地图。整个流程走下来比起那种纯CRUD演示项目还是要复杂一些的这也是我认为它值得写一篇的原因。1.1 这个“企业级”到底指什么先说说我对这个项目“企业级”的理解。它没有上Spring Cloud没有用分布式事务没有引入消息队列但它确实具备了企业级管理系统最重要的几个特征第一权限隔离。系统区分管理员和普通用户后端用拦截器校验登录状态前端用路由守卫控制页面访问管理端和查询端页面是分开的。第二数据关系复杂。公交线路不是一张单表就能表达的线路和站点是多对多车辆和线路是一对多排班和日期是多天数据这种关系模型是真实业务系统的典型形态。第三代码结构分层清晰。Controller、Service、Mapper三层各司其职统一返回结果、全局异常处理、通用分页对象这些规范在正规团队里都是强制要求。这套源码最值得学习的就是第二个特征。课程设计常见的图书管理、学生管理系统基本都是单表CRUD写不出复杂SQL。而公交系统天然带有多表关联、排序、分组、统计这些操作学一遍能顶三个简单的管理系统。1.2 功能模块与核心业务关系整个系统按业务可以拆成七大模块我整理了一张表模块核心功能涉及的主要表登录与权限登录鉴权、角色区分、登录状态拦截sys_user线路管理线路增删改查、启用停用、首末班时间维护bus_line站点管理站点增删改查、经纬度信息维护bus_station线路站点绑定维护线路经停站点顺序、里程与票价line_station车辆管理车牌号、座位数、上线状态、所属线路bus_vehicle排班管理车辆每日班次、发车时间、运行日期bus_schedule公告与日志公告发布展示、用户操作日志记录sys_notice、sys_log这些模块之间最核心的业务关系是一条线路按顺序经过多个站点一辆车固定运行在某条线路上每天产生若干个班次。用一句话概括就是“线路—站点—车辆—班次”四层结构。前端用户查询时输入线路名或站点名后端拼装条件查询返回经过的站点列表和当天班次信息。管理员维护时主要工作集中在线路和站点的关联维护上。1.3 技术选型的底层逻辑为什么不是微服务有些同学会问都用SpringBoot了为什么不用Spring Cloud都用ORM了为什么不用MyBatis-Plus都用缓存了为什么不用Redis我在实际开发中见过不少项目一上来就上微服务结果一个公交查询系统拆了五个服务部署脚本写半天线上排查问题要在几个服务日志里来回跳。这个项目用SpringBoot Vue MyBatis MySQL恰恰是绝大多数中小型团队最稳的组合。SpringBoot负责快速整合内嵌Tomcat、自动配置、starter机制一个jar包就能跑起来部署成本极低。Vue负责前端交互组件化开发路由和状态管理生态成熟和后端只通过JSON交互。MyBatis负责SQL半自动ORM适合复杂查询尤其是公交线路这种多表关联频繁的场景手写SQL反而更可控。MySQL负责存储开源免费运维简单性能对中小项目绰绰有余。这套组合最大的好处是学习曲线平缓、资料多、招人容易。技术选型不是越新越好而是和业务匹配这是这套源码给的一个很好的示范。2. 后端设计与MyBatis核心实现后端是整个系统的重心也是我这次跑通过程中花时间最多的地方。SpringBoot的自动配置帮我们省了大量XML配置但MyBatis的Mapper接口和XML映射文件还是得一个个写。这一章按我实际梳理代码的顺序来讲先看工程结构和配置再拆核心SQL最后讲分页、缓存和接口安全。2.1 项目结构与关键配置一次说清一个标准的SpringBoot项目包结构基本是这样的com.example.bus ├── config // 跨域配置、拦截器、MyBatis配置 ├── controller // REST接口层 ├── service // 业务逻辑层 ├── mapper // MyBatis接口 ├── entity // 数据库实体类 ├── vo // 视图对象接口返回用 ├── common // 统一返回结果、异常处理、分页对象 └── utils这里有个细节很多人不注意entity和vo要分开。数据库表字段可能包含password这类敏感字段直接映射返回给前端不合适。这个项目里的做法是查询结果映射到VO对象只暴露需要的字段这是一个很规范的习惯。核心配置文件application.yml里这几个配置是从无数坑里爬出来的spring: datasource: url: jdbc:mysql://localhost:3306/bus_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 10000 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.bus.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl一定要说清楚的是URL里的参数serverTimezoneAsia/Shanghai。MySQL驱动8.x对时区要求很严格不写这个参数启动直接报“The server time zone value”错误。characterEncodingutf8保证中文不乱码。map-underscore-to-camel-case决定了数据库字段line_name能不能自动映射成实体类的lineName没配的话查询结果里一堆属性是null。log-impl配成StdOutImpl控制台直接打印完整SQL和参数排查问题比debug还快。2.2 线路详情的resultMap与多表查询线路上最核心的接口是“线路详情”根据线路ID查出线路基本信息同时带着所有经停站点和每个站点的上一站里程、票价。这个需求在表结构上就对应三张表bus_line、bus_station、line_station。如果不用MyBatis的关联映射常规做法是先查线路再查站点然后在Service里循环组装。数据量小还好站点一多就会产生N1查询问题。这个项目用的是resultMap collection联合查询一条SQL把数据一次拿回来。核心XML长这样resultMap idLineDetailMap typecom.example.bus.entity.vo.LineDetailVO id propertyid columnline_id/ result propertylineName columnline_name/ result propertyfirstTime columnfirst_time/ result propertylastTime columnlast_time/ collection propertystations ofTypecom.example.bus.entity.vo.LineStationVO id propertyid columnls_id/ result propertystationName columnstation_name/ result propertysortOrder columnsort_order/ result propertymileage columnmileage/ result propertyprice columnprice/ /collection /resultMap select idselectLineDetail resultMapLineDetailMap SELECT l.id AS line_id, l.line_name, l.first_time, l.last_time, ls.id AS ls_id, ls.sort_order, ls.mileage, ls.price, s.name AS station_name FROM bus_line l LEFT JOIN line_station ls ON l.id ls.line_id LEFT JOIN bus_station s ON ls.station_id s.id WHERE l.id #{id} ORDER BY ls.sort_order /select这里有个关键点LEFT JOIN查出来的结果集是“一行线路一行站点”的笛卡尔展开MyBatis的collection会把同一个line_id的多条站点记录自动组装到同一个线路对象里的stations列表。这样前端一次请求就能拿到完整线路图数据。但这样写有一个隐患如果某个线路有30个站点线路主表的字段会在结果集里重复30次网络传输有冗余。数据量小的时候无所谓数据量大了建议改成两条SQL查两次再在Service层组装代码可读性也更好。另外注意排序必须放在SQL里做ORDER BY ls.sort_order否则站点顺序在组装时是不确定的前端画线路图就会乱。2.3 分页插件、缓存机制与常见面试考点分页是后台管理系统的标配功能。这个项目用的是PageHelper插件用法非常固定第一步引入依赖dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency第二步加配置pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true第三步在Service方法里调用PageHelper.startPage(pageNum, pageSize); ListLineVO list lineMapper.selectLinePage(condition); PageInfoLineVO pageInfo new PageInfo(list);调用时有一个非常容易踩的坑startPage方法必须放在要分页的那条查询语句的前一行中间不能有任何其他数据库操作。因为PageHelper的原理是把startPage后的第一条SQL拦截下来拼接上LIMIT和COUNT语句。如果中间又执行了另外一条查询分页就被加到错误的SQL上了而且还不报错只会在日志里看到SQL变成SELECT count(0) FROM xxx数据却对不上。再说MyBatis的缓存这也是“mybatis面试题”里高频出现的内容。MyBatis自带一级缓存和二级缓存。一级缓存是SqlSession级别的默认开启同一个SqlSession里执行同一条SQL只访问一次数据库。二级缓存是Mapper级别的默认关闭在XML里加一行cache/就能启用。公交线路这种变动频率低的数据开二级缓存是合理的车辆状态这种高频更新的数据就不要开否则会出现缓存脏读。我自己做的时候只对线路基础表开了二级缓存排班和车辆查询保证每次走数据库。配置打印SQL之后MyBatis会把预编译的参数值也打印出来联调阶段非常好用。生产环境记得把log-impl关掉否则控制台日志会爆炸。2.4 全局XSS过滤器与接口安全接口安全这部分这个项目做了一件很多教程都不提的事用全局过滤器处理XSS攻击。思路并不复杂用一个HttpServletRequestWrapper包装原始请求重写getParameter方法把参数里的script、onerror这些危险关键字做转义或剔除再在WebConfig里注册Filterpublic class XssFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { XssHttpServletRequestWrapper wrapper new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(wrapper, response); } }这样做的好处是所有Controller接口自动获得了防XSS能力不用在每个业务方法里做参数清洗。但有两个问题必须处理好。第一文件上传接口要放行否则上传的二进制文件会被过滤器整个读一遍轻则文件损坏重则内存溢出。第二入库前转义和展示前转义要区分清楚。如果数据在入库前已经转义了查询出来再放在表单里编辑时需要对内容做反向处理否则用户看到的是一堆lt;scriptgt;乱码。这两个坑我在联调的时候都遇到过。3. 前端Vue实现与联调细节公交项目的前端没有太多花哨页面但基础工程搭建、路由参数传递、Axios封装、动态表格展示都是Vue项目里通用的硬功夫。我从前端环境搭建一路讲到接口联调把几个关键设计讲透。3.1 前端工程化依赖安装、插件与目录规范Vue项目的第一步就是环境。npm install慢到怀疑人生是每个前端新手的必经之路解决办法很简单把源切到国内镜像npm config set registry https://registry.npmmirror.com装依赖的时候还有个经典坑node-sass在Node高版本下编译屡屡失败。现在的Vue项目建议直接装sass和sass-loader也就是dart-sass不需要node-sass那套原生编译兼容性好很多。装完之后推荐装两个Vue生态必不可少的开发工具。一个是Vue DevTools浏览器扩展调试组件状态、检查路由跳转、查看Pinia/Vuex里的数据流没有它几乎没法高效调试。另一个是IDEA里的MyBatisX插件虽然它是后端工具但前端工程里如果同时打开后端源码它能实现Controller和Mapper之间的方法跳转XML文件还会高亮提示查代码效率翻倍。前端目录我习惯这样规划src ├── api // 接口请求模块按业务拆文件 ├── router // 路由表与路由守卫 ├── store // 状态管理 ├── views // 页面组件 ├── components // 通用组件 └── utils // 工具函数如request.js这里有一个容易被忽略的原则API层和组件层要分开。不要在页面上直接写axios请求而是把每个接口封装成api/line.js里的函数页面里只管调用。这个项目的前端代码就是这么组织的好处是所有接口地址集中管理后端改了URL只动一个文件。3.2 路由拦截与query、params参数玄机系统区分管理端和查询端路由就需要做登录控制。Vue Router提供了全局前置守卫这是最规范的做法router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } });配置路由表时给需要登录才能访问的页面加一个meta: { requiresAuth: true }然后在守卫里检查token。这个设计很多课程设计项目根本没有页面直接裸奔。热搜词里有“vue路由参数”这里展开一个最常见的困惑query和params的区别。query参数在URL中以?keyvalue形式出现刷新后依然存在适合传搜索条件、分页信息。params参数通常配合路由配置中的name使用URL上看不到值但页面一刷新params会被清空。很多同学第一次用params跳详情页后一刷新页面就白屏就是因为没搞明白这个机制。所以像线路ID这种关键业务参数推荐用query传递或者存在store里而不是依赖params。3.3 Axios封装与统一响应处理前后端分离项目里Axios封装是必须做的一道工序。这个项目后端所有接口返回的都是统一结构{code, msg, data}前端request.js就围绕这个结构做全局处理const service axios.create({ baseURL: /api, timeout: 10000 }); service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { Message.error(res.msg); if (res.code 401) { router.push(/login); } return Promise.reject(new Error(res.msg)); } return res; }, error { Message.error(网络异常请稍后重试); return Promise.reject(error); } );三个关键设计。第一baseURL用/api不写完整IP和端口部署时通过Nginx反向代理转给后端这样前端代码和真实后端地址解耦切环境只需要改代理配置。第二401统一跳登录用户登录态过期时无论哪个接口返回都在拦截器里处理掉每个页面不用重复写判断。第三业务错误用Message提示后端返回的msg直接展示给用户比如“线路名称已存在”体验比浏览器默认的英文报错好得多。3.4 查询列表、分页与线路图展示用户查询线路是这个系统的核心页面。前端布局是“搜索表单 表格 分页”搜索条件包括线路名称关键字和运营状态。分页组件和后端PageInfo对象直接对接PageInfo里有total、pageNum、pageSize、list前端拿到这几项渲染表格和控制分页按钮。值得说明的是搜索条件如何和URL参数联动。我把搜索词和当前页码都放到query里比如/line/list?keyword1路pageNum2。这样一来用户刷新页面后搜索状态还在把链接发给别人也能看到同样的筛选结果。这个交互细节很多后台管理系统都没做到但这个项目做到了。线路经停站点的展示上如果假设源码中没有引入高德地图API比较务实的方式是用ECharts的lines特效和散点坐标画线路走向图。后端返回每个站点的经纬度数组前端把这些坐标点按顺序连成折线站名做标注效果类似地铁线路图。相比直接上地图API这个方案不依赖高德key和配额也不涉及地图初始化失败的问题。如果后续想升级成真实地图把ECharts的坐标系换成高德JS API的polyline就能平滑迁移数据模型完全不用改。4. 数据库设计与MySQL落地细节数据库设计是整个系统的基础也是区分“课程设计作业”和“像样业务系统”的关键。这一章从表结构、索引、连接参数三个维度展开。4.1 八张表的业务含义与关联设计后端部分我提到过这个系统的8张核心表这里把每张表的设计意图说清楚。sys_user用户表字段有id、username、password、real_name、role、status。密码必须加密存储不能明文入库。bus_line线路表字段有id、line_name、start_station、end_station、first_time、last_time、interval_minutes、status。首末班时间和发车间隔是排班计算的依据。bus_station站点表字段有id、name、address、lng、lat。经纬度字段直接支撑线路图展示。line_station线路站点关联表字段有id、line_id、station_id、sort_order、mileage、price。这是整个业务设计的核心公交行业里“一条线路按一定顺序经过若干站点”这个关系就是靠sort_order字段表达的。bus_vehicle车辆表字段有id、plate_no、line_id、capacity、status、driver_name。bus_schedule排班表字段有id、vehicle_id、line_id、depart_time、run_date。车辆和日期的组合每天生成一批新班次。sys_notice公告表字段有id、title、content、publish_time、status。sys_log操作日志表字段有id、user_id、action、ip、create_time。其中line_station这张表值得重点理解。它是业务里典型的关联表也可以叫“中间表”不仅存了外键关系还额外存了这条线路上站点的顺序、里程和票价。如果删掉这张表线路“经过哪些站点”的信息就完全丢失了。这种设计思路在电商项目里的商品和规格、权限系统里的角色和菜单是同一个套路。初始化数据时有一个很容易出问题的地方sort_order必须从1开始连续递增否则线路图上站点顺序会乱。我在初始化脚本里发现过sort_order和station_id不匹配的数据这种脏数据不会报错但前端展示非常诡异站点跳来跳去。数据库层面可以给(line_id, sort_order)加一个联合唯一索引从根上挡住重复数据。4.2 针对公交场景的索引与SQL优化公交查询系统的查询模式非常固定就三类第一按线路名称模糊查对应SQL是line_name LIKE %关键字%。这种查询在数据量小时全表扫描没问题但线路表超过几万条后性能会明显下降。LIKE以%开头的条件无法走索引能做的优化是改成前缀匹配或者引入Elasticsearch。对中小项目来说数据量在几万条以内完全能承受不必过度设计。第二按站点反查线路SQL会从line_station表出发通过station_id查到所有line_id再关联bus_line。这时候line_station表上station_id字段必须加索引否则每次反查都是全表扫描。我在本地模拟过10万条关联数据的场景加索引前后的查询耗时差了近百倍。第三分页深翻页问题。前端表格翻到第10000页时MySQL会把前面9990条数据全部查一遍再扔掉效率极低。优化方式通常用延迟关联SELECT l.* FROM bus_line l INNER JOIN ( SELECT id FROM bus_line ORDER BY id LIMIT 10000, 10 ) tmp ON l.id tmp.id;先用覆盖索引只查ID列定位目标数据再回表查详情性能能提升一个量级。这是我在数据量超过十万后实测过的方案配合PageHelper使用时需要在XML里手写这条子查询。4.3 MySQL安装、连接池与账号权限热搜词里有“mysql安装教程”“mysql安装配置教程”“linux安装mysql”说明环境问题卡住了很多人。Windows下装MySQL 8.x最省心的方式就是下载MSI安装包一步步点下去注意数据目录要选择非系统盘字符集选utf8mb4而不是默认的latin1。Linux服务器如果自己编译太折腾直接用包管理器或者官方tar包但有一个容易踩的坑如果之前用root用户初始化过数据目录后面运行MySQL必须用专用mysql用户权限不对会导致启动失败。装好后顺手把root密码改掉并且不要在生产环境继续用rootALTER USER rootlocalhost IDENTIFIED BY 你的密码; CREATE USER bus_app% IDENTIFIED BY xxxxxx; GRANT SELECT, INSERT, UPDATE, DELETE ON bus_system.* TO bus_app%;这样业务系统只拥有操作本项目库的DML权限就算被注入了也拿不到其他库。连接池方面SpringBoot 2.x默认的HikariCP已经是性能很强的连接池了不用额外引Druid。参数上maximum-pool-size默认10对公交项目来说足够了改成20意义不大反而多占数据库连接资源。connection-timeout默认30秒局域网内建议改成10秒数据库出问题的时候前端能更快收到错误而不是傻等半分钟。idle-timeout不要低于10分钟否则连接频繁重建会浪费性能。5. 完整运行与问题排查实录拿到源码之后最重要的事情就是把项目跑起来。这一章我用实际操作顺序来讲从环境准备到启动步骤再到高频问题和扩展方向。5.1 从零跑通环境准备与启动步骤要跑通这套源码环境需要JDK 8、Maven 3.6、Node 14、MySQL 8.0。我按步骤列出来第一步初始化数据库。用Navicat或者命令行执行sql目录下的init.sql脚本里建库、建表、插入基础数据一次性完成。执行完后确认bus_system库下有8张表sys_user表里能看到默认管理员账号。第二步修改后端配置。打开application.yml把数据库连接改成自己的账号密码。这一步最常见的错误是密码写错、时区没配、字符集不对对照2.1节的URL模板逐项检查。第三步启动后端。在项目根目录执行mvn spring-boot:run或者先mvn clean package -DskipTests打包成jar再运行。启动成功后控制台会看到SpringBoot的Banner图案。想换Banner的话网上有“springboot banner生成器”粘贴一段文字就能生成自定义图案纯属仪式感功能不影响运行。第四步启动前端。前端目录下执行npm install装依赖然后npm run dev启动开发服务器。浏览器自动打开用默认管理员账号登录。第五步联调验证。进入线路管理页面执行一次查询打开浏览器开发者工具的Network面板确认分页查询接口返回了正确数据。如果接口404优先检查vue.config.js里的devServer代理配置是否和后端端口一致。5.2 高频问题排查速查表我在跑通和后续修改的过程中把遇到的高频问题整理成了速查表不少是社区里反复被问到的现象原因解决方式前端访问接口404devServer代理未配置或跨域被拦vue.config.js里配置proxy或在后端加CorsConfig后端启动报时区错误MySQL驱动8.x要求指定时区JDBC URL加serverTimezoneAsia/Shanghai中文数据乱码数据库表或连接字符集不是utf8mb4库表统一用utf8mb4连接串加characterEncodingutf8PageHelper分页不生效startPage和查询之间隔了其他操作startPage放在目标Mapper调用前一行登录成功但接口提示无权限拦截器未放行登录接口和静态资源在WebConfig白名单里加入 /login、/api/notices 等npm install一直报错node-sass编译失败或网络问题换用sass切换npmmirror镜像源刷新页面404history路由没有配fallbackNginx配置 try_files $uri $uri/ /index.htmlMyBatis查询结果全是null没开map-underscore-to-camel-caseapplication.yml里加配置或使用resultMap前端传日期差8小时前后端时区不一致后端统一Asia/Shanghai前端用时间戳或UTC转换这里补充一个排查分页问题的土办法把log-impl打开观察控制台打印的SQL。如果PageHelper生效会看到查询被改写成带LIMIT的形式同时还有一条SELECT count(0)的统计SQL。如果只看到普通查询说明startPage没有对目标SQL生效。5.3 从这套源码还能扩展做什么公交线路查询只是一个业务载体它背后的技术点能迁移到很多系统把line_station的关联思想迁移到商品分类、课程章节、角色菜单几乎所有树形或列表关联的场景都能用。把车辆排班的日期批量生成逻辑学一遍就能理解定时任务和批量插入的写法。把“统计线路客流量”做成报表从排班表和日志表做group by就是一个标准的报表开发入门案例。如果公司项目需要做地图轨迹展示把站点经纬度换成GPS坐标点同样的一套数据模型就能升级成实时车辆监控。很多人误以为源码项目就是“换个名字交作业”但真正有价值的是把设计思路抽出来。比如我后面给一个物流公司做配送线路管理业务表和这个系统几乎一一对应配送线路对应bus_line配送点对应bus_station配送顺序对应sort_order直接把关联查询的resultMap拿过来改了改表名就用了。这就是这套源码最大的学习价值。说句实在话这个项目我拿到手第一反应是“又一个CRUD后台”但真正跑通之后发现价值恰恰藏在那些看起来普通的CRUD背后。线路站点的多对多关系、PageHelper分页的边界场景、Vue路由参数的刷新陷阱、MySQL连接串的时区问题每一个都能让一个初级开发卡上一整天。我后来带新人时也经常拿这套代码当入门教材因为它足够简单到能看懂全貌又足够复杂到能暴露真实问题。如果你也是准备把一套源码真正吃透而不是下载完就吃灰建议按我这个顺序走一遍先看数据库脚本再看MyBatis的resultMap然后打开前端路由把请求链路串起来最后自己动手加一个“线路收藏”功能。做完这一圈你对SpringBootVue全家桶的工程化理解会比看十遍教程都管用。
返回列表