ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue自驾游攻略查询系统开发实战

SpringBoot+Vue自驾游攻略查询系统开发实战 做自驾游攻略查询系统这个项目说实话是我自己的一次自找麻烦。当时我想在节假日出去跑一趟长途自驾结果发现查攻略这件事比开车还累马蜂窝上有游记但路线信息过期小红书上照片好看但没法一次性规划完整路线携程上的景点介绍又偏商业化。与其东拼西凑不如自己写一个攻略查询系统。技术栈我选的是SpringBoot Vue这也是目前前后端分离项目里最主流、最适合练手的一套组合。这篇文章就把我从需求分析、数据库设计、接口开发、前端联调到部署上线的完整过程拆开讲一遍尤其是那些不跑一遍根本不会知道的坑给正在做类似毕设或自己练手项目的朋友做个参考。1. 需求与架构先把查询这件事想清楚1.1 自驾游的核心场景与用户痛点很多人在做这类系统时第一反应就是做一个景点CRUD也就是增删改查。但如果你真的把车开到路上就会发现自驾游和普通旅游有本质区别普通旅游关心这个景点有什么自驾游更关心怎么去、路好不好走、沿途还有什么、一天能跑多少公里。所以我在需求分析阶段没有直接画界面原型而是先列了几个真实场景用户出发前想找一条从成都出发、3天、全程不走回头路、能看到雪山和草原的路线用户到了某个服务区想查附近200公里内有哪些小众景点以及有没有加油站和露营地用户看完一篇攻略后想直接收藏路线并下载离线用的PDF版行程单管理员后台需要能管理景点信息、攻略内容和推荐路线避免所有数据靠手工录入SQL。基于这些场景我把系统核心功能拆成四块景点信息查询、攻略内容管理、自驾路线推荐、个人收藏夹。这个系统的名字叫自驾游攻略查询系统重点在查询两个字上所以我在设计时特意把检索能力做得比普通增删改查更深入后面会专门讲。1.2 技术选型SpringBoot Vue的组合逻辑选SpringBoot Vue不是因为它们流行而是因为它们各自解决了这个项目里最麻烦的问题。先说后端为什么用SpringBoot。这个项目从一开始就不想碰繁琐的XML配置。如果用传统的SSM框架光配置数据源、事务管理器、MyBatis会话工厂就能把人绕晕SpringBoot通过自动配置把这些基础工作全部接管了我只需要在application.yml里写几行配置就能得到一个可以独立运行的内嵌Tomcat服务。对于这种中等规模的信息管理系统SpringBoot的开发效率是最高的。再说前端为什么用Vue。自驾游攻略系统的页面交互并不复杂但有一个显著特点大量的列表渲染和状态联动。比如用户在地图上拖到一个新城市右侧攻略列表要同步刷新用户选择亲子游标签路线推荐的权重要重新计算。这种数据驱动视图的场景正好是Vue的响应式系统擅长的。而且Vue的组件化开发方式能把攻略卡片、景点详情弹窗、路线时间轴这些UI拆成独立模块后续维护成本低很多。前后端分离还有一个隐藏好处我可以把接口文档和前端Mock数据分开并行开发后端写接口的同时前端用Mock数据先做页面最后联调时再对接真实API。整个项目节奏比传统JSP开发快了一倍不止。1.3 整体架构与数据流向这个项目的架构很简单但简单不代表没有设计。我画了一条很清晰的数据流转链路前端Vue发起HTTP请求通过Axios调用后端接口后端SpringBoot的Controller接收请求做参数校验后交给Service层Service层处理业务逻辑调用Mapper层访问MySQL数据库对于热点数据比如首页推荐的路线列表先在Redis里查查不到再走数据库并回填缓存查询结果统一封装成ResultT对象以JSON格式返回前端前端拿到数据后更新Vue组件的响应式状态驱动页面重新渲染。这里有一个很多人忽略的点统一响应结构。我见过不少项目有的接口返回{code: 0, data: [...]}有的直接返回数组有的出错时返回一个纯字符串前端联调时苦不堪言。我自己的做法是定义了一个统一的Result类包含code、message、data三个字段成功时code200业务异常时code为自定义错误码。这个习惯在前后端分离项目中非常重要强烈建议从一开始就定好。2. 后端SpringBoot实现数据模型、检索接口与性能优化2.1 数据库设计四张核心表的字段与关系这个系统的数据库我设计了四张核心表景点表、攻略表、路线表、用户表。如果做的是毕设项目这个规模已经足够体现设计能力了如果要扩展到生产级可以再加收藏关系表、标签表、评论表但核心查询链路不变。景点表是基础数据字段包括景点ID、名称、所在城市、经度纬度、景点类型、门票价格、开放时间、简介、封面图URL。这里要特别注意经纬度的字段类型我用的是DECIMAL(10, 6)因为高德和腾讯地图返回的经纬度精度到小数点后6位能精确到米级如果用FLOAT会出现坐标偏移的问题别问我怎么知道的。攻略表是核心内容表字段包括攻略ID、标题、作者、出发城市、目的地城市、游玩天数、人均预算、路线标签、封面图、正文内容、发布时间、浏览量。正文内容我用的是TEXT类型但如果在生产环境我会建议把正文拆到单独的表或者用对象存储避免一次查询把所有大字段都捞出来。路线表负责存储推荐的自驾路线字段包括路线ID、路线名称、起点、终点、途经点JSON格式存储、总里程、预计耗时、适合季节、难度等级、路线简介。途经点用JSON存储是一个比较取巧的做法因为每条路线的途经点数量不一样单独建一张关联表当然更规范但对于查询系统来说JSON字段配合前端解析反而更高效。用户表就是常规设计用户名、密码BCrypt加密存储、昵称、头像、角色普通用户/管理员。这个项目里用户表主要服务于收藏功能和后台登录不需要太复杂。2.2 攻略多条件查询接口的实现这个系统的核心接口是攻略查询我把它做成一个支持多条件组合的通用接口请求参数包括keyword搜索关键词、city出发城市、days游玩天数、tag路线标签、pageNum和pageSize分页参数。以下是Service层的核心实现逻辑我用了MyBatis-Plus的LambdaQueryWrapper来构建动态条件Override public PageResultStrategyVO searchStrategies(StrategyQuery query) { // 构建分页对象 PageStrategy page new Page(query.getPageNum(), query.getPageSize()); // 动态条件构造器 LambdaQueryWrapperStrategy wrapper new LambdaQueryWrapper(); // 关键词匹配标题或简介 if (StringUtils.hasText(query.getKeyword())) { wrapper.and(w - w .like(Strategy::getTitle, query.getKeyword()) .or() .like(Strategy::getSummary, query.getKeyword())); } // 出发城市精确匹配 if (StringUtils.hasText(query.getCity())) { wrapper.eq(Strategy::getDepartureCity, query.getCity()); } // 游玩天数范围匹配 if (query.getDays() ! null query.getDays() 0) { wrapper.le(Strategy::getTravelDays, query.getDays()); } // 标签匹配 if (StringUtils.hasText(query.getTag())) { wrapper.like(Strategy::getTags, query.getTag()); } // 按浏览量倒序排列 wrapper.orderByDesc(Strategy::getViewCount); PageStrategy result strategyMapper.selectPage(page, wrapper); // 组装返回结果把实体类转成VO隐藏不需要的字段 ListStrategyVO records result.getRecords().stream() .map(strategy - { StrategyVO vo new StrategyVO(); BeanUtils.copyProperties(strategy, vo); return vo; }).collect(Collectors.toList()); return new PageResult(result.getTotal(), records); }这里有三个容易被忽略的细节。第一关键词匹配不能只匹配标题还要匹配简介和正文摘要否则用户搜川西小环线却只搜到标题里带川西的帖子体验很差。第二分页查询一定要返回total总数前端分页组件要是没有总数就没法渲染页码。第三实体类Strategy和视图对象StrategyVO一定要分开因为数据库实体类里可能包含content这个大字段直接传给前端会拉低接口响应速度而且正文里的HTML标签还可能引发XSS注入用VO做一层脱敏很有必要。2.3 检索性能优化索引策略与Redis缓存查询系统的命根子就是检索速度。攻略表的数据量虽然不至于到百万级但如果没有合理的索引联合查询照样能把MySQL拖慢。我在这张表上做了两个关键索引一个是在departure_city和travel_days上建联合索引因为这两个字段的组合查询非常频繁另一个是在view_count上建普通索引用于日常的热门攻略排序。这里要特别说明一个MySQL索引失效的场景在keyword搜索时我用了LIKE %关键词%这类前后通配符的模糊查询是没办法走B树索引的只能全表扫描。如果攻略数据量真的涨到几十万条就必须引入Elasticsearch做全文检索这个我在第5章会展开讲。缓存层的设计我花了不少心思。首页的热门路线推荐和热门景点榜单是访问最集中的数据我直接用Redis做了缓存缓存key设计了两个strategy:hot:list热门攻略榜单缓存时间30分钟route:recommend:{city}指定城市推荐路线缓存时间60分钟。读取逻辑上先查Redis缓存命中直接返回没命中就查数据库然后把结果写回Redis设置过期时间。这里有一个必须注意的坑缓存穿透。如果有用户恶意用不存在的城市名反复请求每次都会穿透缓存打到数据库为了解决这个问题我加了一个简单粗暴的办法——即使查询结果为空也把一个空对象缓存30秒避免恶意请求把数据库打死。查询接口的完整时序大概是这样的请求进来→查Redis→命中直接返回→未命中查MySQL→回填Redis→返回结果。这种缓存辅助数据库的模式虽然简单但对于攻略查询这类读多写少的场景效果立竿见影。我做了一个简单压测加了Redis之后首页推荐接口的平均响应时间从380毫秒降到了60毫秒左右。3. 前端Vue实战地图联动、路由与组件化3.1 环境准备与项目初始化前端部分我用的是Vue 2.7版本很多初学者一上来就装Vue 3结果发现插件生态不熟、踩坑没人能帮上忙。我的建议是如果是做课程设计或毕设选Vue 2是稳妥路线因为中文社区教程多、组件库成熟、遇到的问题基本都能搜到答案如果是从零学新项目可以直接Vue 3 Vite TypeScript。环境配置方面有三个关键点。第一Node.js版本不建议追新太高的版本可能会让一些老依赖编译失败我当时用的是16.x LTS版本。第二npm镜像建议换成国内源不然装依赖能等到怀疑人生一条命令就行npm config set registry https://registry.npmmirror.com。第三项目脚手架我推荐直接用Vue CLI创建虽然现在Vite是趋势但Vue CLI生成的webpack配置对新手来说更直观等跑通整个流程再换Vite也不晚。# 安装Vue CLI脚手架 npm install -g vue/cli # 创建项目 vue create travel-guide-frontend # 选择配置时勾选 Router Vuex Axios # 进入项目目录后安装UI组件库和图表库 cd travel-guide-frontend npm install element-ui axios core-js初始目录结构我一般这样安排src/api放接口请求封装src/router放路由配置src/views放页面级组件src/components放可复用组件src/store放Vuex状态管理。这个结构在项目小的时候感觉有点过度设计但一旦页面超过10个它的优势就出来了每个页面的代码改动都能快速定位。3.2 地图集成自驾路线绘制与景点标注地图是自驾游系统里最有辨识度的功能也是我踩坑最多的模块。主流的地图方案有三种高德地图JS API、腾讯地图JS API、Mapbox GL JS。我最终选了腾讯地图因为它的JS API对中文地点的解析更好逆地理编码坐标转地址一次申请Key就能用而且在国内访问稳定。地图组件需要实现三个核心功能自驾路线绘制、景点标注、地图与列表联动。路线绘制的核心代码核心思路是拿到路线途经点的经纬度数组用Polyline组件把点串起来// 初始化地图 this.map new TMap.Map(mapContainer, { center: new TMap.LatLng(this.startPoint.lat, this.startPoint.lng), zoom: 8 }); // 绘制自驾路线折线 const line new TMap.MultiPolyline({ map: this.map, styles: { route-line: new TMap.PolylineStyle({ color: #ff7e00, width: 6, borderWidth: 2, borderColor: #ffffff }) }, geometries: [{ paths: this.routePoints.map(p new TMap.LatLng(p.lat, p.lng)), styleId: route-line }] });景点标注用的是MultiMarker点击Marker时弹出景点卡片。这里有个实践上的小技巧Marker的点击事件里除了弹出景点详情外还要触发地图平移让Marker移动到视口中心不然用户点击屏幕边缘的Marker时弹窗会被地图边界切掉。地图与列表联动是这个功能里最考验前端逻辑的部分。用户点击地图上的Marker触发事件→更新当前选中景点ID→Vue watcher监听到ID变化→调用fetchNearbyStrategies接口拉取附近的攻略列表。整个链路是数据驱动的避免了手动操作DOM的各种诡异bug。这个功能做完之后我自己开车模拟了一遍把地图拖到青海湖附近旁边的攻略列表确实在1秒内刷新出了环湖攻略效果相当满意。3.3 路由设计列表页、详情页与参数传递这个系统的路由结构非常典型可以作为前端路由设计的标准案例/首页包含热门目的地推荐和攻略搜索入口/strategy/list攻略列表页支持多条件筛选/strategy/detail/:id攻略详情页通过动态路由参数传递攻略ID/route/plan路线规划页承载地图联动功能/favorite个人收藏夹。Vue Router里列表页跳到详情页有两种传参方式很多人搞不清区别。query方式传参URL里会显示?id123刷新页面参数还在params方式配动态路由/detail/:id参数在路径里语义更清晰。我的建议是详情页用params因为攻略ID是URL的一部分方便分享和SEO收录筛选条件用query因为用户刷新页面后希望筛选条件还在。// 路由配置 const routes [ { path: /, component: HomeView }, { path: /strategy/list, component: StrategyListView }, { path: /strategy/detail/:id, component: StrategyDetailView } ]; // 跳转详情页 this.$router.push({ path: /strategy/detail/${row.id} }); // 详情页接收参数 const strategyId this.$route.params.id;我在这里踩过一个比较隐蔽的坑从详情页返回列表页时列表页的滚动位置和筛选条件全丢了。解决思路很简单用Vuex把筛选条件存一份或者在beforeRouteLeave里把选择的筛选条件暂时存到sessionStorage。所以即便是一个简单的查询系统路由设计时也要把用户在页面间来回跳转的场景考虑进去。3.4 攻略卡片与详情页的组件化实现攻略列表页的UI我把它拆成了两个可复用组件StrategyCard攻略卡片和TagGroup标签组。这里组件化最大的收益是首页的热门推荐、列表页的搜索结果、个人收藏夹三处都能渲染同一个StrategyCard组件只是数据源不同。StrategyCard组件里大量使用了Vue的插槽机制。以卡片底部的操作按钮为例不同的使用场景需要的按钮不一样首页要查看详情列表页要收藏查看详情收藏夹要取消收藏查看详情。如果不用插槽就得在组件里写一堆v-if判断用了插槽之后父组件可以灵活控制按钮区域的内容div classstrategy-card img :srcstrategy.coverUrl alt攻略封面 / h3 classcard-title{{ strategy.title }}/h3 p classcard-summary{{ strategy.summary }}/p !-- 操作区域由父组件通过插槽自定义 -- div classcard-actions slot nameactions/slot /div /div攻略正文页还有一个常见的交互需求长篇内容折叠展开。一篇文章几千字如果全部展示出来页面会变得特别长而且无法快速定位到费用或路况这些关键信息。我用了两种方案结合正文分段渲染每个段落前面加一个锚点标题超过一定长度的段落默认折叠点击展开全文才显示完整内容。这个折叠展开功能在Vue里实现很简单本质上就是控制一个expanded状态难点在折叠时要保留段落的结构和样式不要因为折叠导致CSS错乱。图片处理上攻略列表用了图片懒加载v-lazy指令或者Element UI的懒加载组件避免首屏加载几十张大图把性能拖垮。这个优化在真机上效果很明显列表页首屏加载时间从2秒多降到1秒以内。4. 联调、部署与版本坑从开发到上线的完整路径4.1 跨域前后端联调的第一道坎前后端分离项目第一次联调时十有八九会遇到跨域报错。现象是浏览器控制台出现Access-Control-Allow-Origin相关的红色报错接口请求发出去但收不到响应。这个问题的本质是浏览器的同源策略在管闲事前端运行在http://localhost:8080后端运行在http://localhost:9090端口不同就属于跨域。解决方案有三种我分别说下适用场景。第一种后端CORS配置。在SpringBoot里加一个配置类允许跨域请求访问后端接口。这种方案适合开发阶段的临时联调因为配置是全局生效的上线后如果前后端域名不同也能用但要注意不能设置allowCredentials(true)的同时又用通配符*这在浏览器里是非法配置。我用的具体配置如下Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); // 生产环境这里要改成具体域名不要用* config.addAllowedOriginPattern(http://localhost:*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); // 暴露响应头给前端 config.addExposedHeader(Authorization); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/api/**, config); return new CorsFilter(source); } }第二种前端开发代理。在vue.config.js里配置devServer的proxy选项让前端开发服务器帮忙转发请求浏览器认为自己请求的是同源地址就不会有跨域问题。这种方式只在开发阶段有效打包后就不行了。第三种Nginx反向代理。生产环境最推荐的方案把前后端部署在同一域名下通过路径前缀区分请求从根源上消解跨域问题。比如前端页面在根路径后端接口在/api前缀Nginx配置一个转发规则就够了。4.2 打包集成Vue放进SpringBoot的两种方式这个问题在热词里出现了说明很多人在项目做完要部署时卡在这里。Vue项目打包后生成的是静态资源文件HTML、JS、CSSSpringBoot本身可以托管静态资源把它们放在一起部署是完全可行的而且能省去单独配置Nginx的步骤。方式一直接把Vue的dist目录下的所有文件拷贝到SpringBoot的src/main/resources/static目录下重新打包SpringBoot的jar包。这样访问http://服务器IP:端口/时SpringBoot会自动把静态资源返回给浏览器。这个方式有个大坑Vue Router如果用history模式用户在浏览器里直接访问/strategy/detail/3这类深层路径时SpringBoot找不到对应的Controller或静态资源会返回404。解决办法是在SpringBoot里加一个路由兜底让所有非/api的请求都跳转回首页index.htmlController public class PageForwardController { // 排除接口路径和静态资源路径其余全部转发到Vue入口 RequestMapping(value {/strategy/**, /route/**, /favorite/**}) public String forwardPage() { return forward:/index.html; } }方式二前后端完全分离部署前端放在Nginx上后端保留为独立jar包Nginx负责分发。我个人更推荐这种方式因为只要服务器内存足够、操作方便前后端独立扩展、独立更新都更灵活。Nginx样例配置server { listen 80; server_name your-domain.com; # 前端静态资源 location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files $uri $uri/ /index.html这行配置特别重要它能解决history模式下刷新404的问题。Nginx发现请求的路径在磁盘上找不到对应文件时会回退到index.html让Vue Router自己去解析路由。4.3 版本选型教训SpringBoot版本太高引发的连锁问题这是我这个项目里踩得最深的一个坑写出来希望大家不要重蹈覆辙。我之前一直用SpringBoot 2.x系列项目开发到一半看到Spring Boot 3.2发布了心想着新版本性能更好、安全补丁更新于是手一滑把版本升了上去。结果就是下面这一连串的麻烦第一个麻烦是javax包名迁移。SpringBoot 3.x把基础包从javax.*迁移到了jakarta.*这意味着所有引用javax.servlet的地方都要改。如果只是自己手写的类还好问题是很多第三方依赖内部还在用老包名比如旧版MyBatis-Plus、Druid连接池直接不兼容。第二个麻烦是MyBatis-Plus版本必须跟着升。SpringBoot 3.x需要MyBatis-Plus 3.5.3的适配版本而当时这个版本刚发布不久社区反馈还有一些小问题。第三个麻烦是SpringBoot 3.x默认使用CGLIB代理而不是JDK动态代理。这本来是官方设计上的优化方向但如果你在某些老项目中依赖了JDK动态代理的特性就会出现接口注入失败的问题。最后我花了一个晚上把SpringBoot版本从3.2.0回退到2.7.14所有问题迎刃而解。这件事给我的教训是在商用项目或课设项目中稳定版本永远优先于最新版本。如果你的项目核心是CRUD、查询、缓存这类常规业务SpringBoot 2.7.x MyBatis-Plus 3.5.x Vue 2.7 这一套组合已经非常成熟足够支撑一个完整系统的开发。除非你有明确的理由需要用到SpringBoot 3.x的新特性否则不要轻易做版本大升级。5. 从Demo到生产扩展方向与性能加固5.1 检索与推荐升级Elasticsearch与Flink的应用思路基础版本的攻略查询系统关键词搜索是用MySQL的LIKE %词%实现的数据量几千条时跑得挺欢但一旦数据量涨到几十万条、用户并发量上来这个方案就会成为瓶颈。生产级系统里我建议引入Elasticsearch做全文检索引擎。Elasticsearch的思路很简单把攻略的标题、正文、城市、标签等字段同步到ES索引里查询时直接走ES的match和multi_match查询不仅支持分词、拼音搜索、搜索建议还能按相关度打分排序。举个例子用户搜索川西 自驾 攻略ES会把这句话拆成川西、自驾、攻略几个词然后按相关性评分返回结果这比MySQL的简单通配符匹配体验好太多了。SpringBoot整合ES的标准姿势是使用RestHighLevelClient或者Spring Data Elasticsearch这里不展开代码但有一个架构细节值得注意ES和MySQL的同步策略。最简单的方案是每次攻略更新后在Service层主动调ES的更新接口更稳妥的方案是用消息队列异步同步或者直接上Flink CDCChange Data Capture监控MySQL的binlog自动同步数据变化到ES。说到Flink很多人在热词里搜springboot整合flink实际上Flink并不适合嵌入到SpringBoot应用里运行它通常是独立部署的流处理集群SpringBoot只是作为数据生产方或结果消费方。放在自驾游攻略系统里Flink可以做的事情很明确实时统计用户搜索热词、实时计算热门路线排行榜、根据用户实时行为做个性化推荐。比如用户在几个小时内反复查看某个区域的攻略Flink可以实时把这个信号汇总推送到推荐系统里。这个属于后期演进的方向入门阶段不建议直接上容易陷入分布式技术的泥潭。5.2 富媒体攻略图片、PDF与视频播放方案自驾游攻略如果只有文字和几张封面图说服力会打折扣。我在做扩展规划时研究了三个富媒体方向图片、PDF附件、视频。图片层面的核心痛点是体积。用户上传一张单反照片动辄十几MB直接传给后端数据库和带宽都扛不住。常规做法是前端上传前先压缩利用Canvas把图片宽高限制在1920px以内压缩质量调到0.8再把压缩后的数据传到后端后端用OSS存储或本地磁盘存储数据库只保存URL路径。这里可以用到热词里的canvas 2d vue其实就是canvas实现图片尺寸缩放和压缩导出的场景。PDF附件用于下载离线攻略很适合自驾游没网的情况。Vue里显示PDF最简单的是vue-pdf组件或浏览器的iframe内嵌预览但这两个方案都有兼容性坑。我的建议是后端把PDF转成图片再展示预览或者直接用PDF.js处理。热词里那个vue image能显示pdf吗很多人确实天真地以为img能直接放PDF路径实际上大部分浏览器不支持必须走PDF.js这类渲染方案。视频攻略是很多真实自驾博主需要的功能但视频播放是个巨大的坑。手机录制的视频动辄几百MB直接HTTP下载播放体验很差。更专业的做法是用FFmpeg把视频转成HLS协议m3u8格式切片然后前端用video.js或hls.js播放。这就是热词里vue播放m3u8免安装的实现基础m3u8本质是一个索引文件浏览器本身不能直接播放需要通过hls.js把TS分片拉取并解码渲染。后端可以用Java调用FFmpeg命令做转码也可以直接用云服务商的转码产品。5.3 接口安全与稳定性加固开发阶段接口随便调没问题但如果这个系统要上线哪怕只是给朋友用接口安全也马虎不得。最低成本的加固是参数校验。SpringBoot自带的ValidatedNotBlankRange这些注解能在进入Controller之前就把非法参数拦下来避免脏数据打到Service和数据库。很多初学者喜欢在Service里写一堆if (xxx null)的判断完全可以用校验注解替代代码干净还省心。更高一级的加固是接口签名认证。前后端约定一个签名规则比如把timestamp token 请求参数用MD5或HMAC加密生成一个sign字段后端验证签名合法后才处理请求。签名认证能有效防止接口被恶意刷量但要注意时间戳过期校验防止重放攻击。还有一层是限流。用Redis Lua脚本实现一个最简单的计数器限流单IP每秒最多请求10次超过就直接返回请求过于频繁。代码量不大但对系统稳定性的提升是质变级的。我自己的项目目前做到了参数校验和简单的IP限流签名认证属于下一步的规划。我的体会是安全加固是一个渐进的过程不要想着一步到位先从最扎手的问题入手比堆一堆华而不实的安全框架更有效。最后再分享一个实用小技巧整个项目做完之后我最大的感受是一个查询系统能不能打动用户比的不是功能多少而是查得快、查得准、查得爽。把列表页的首屏加载时间从2秒优化到1秒以内把搜索结果的相关性排好把地图联动做得跟手这些细节比多做十个管理功能都更有价值。另一个真实经验是开发过程中一定要学会慢下来先把接口文档和数据库设计定好再动手写代码。我前期就因为边写边改导致前端字段名和后端接口不一致联调时浪费了不少时间。如果你也打算做SpringBoot Vue的全栈项目建议从一开始就严格定义接口返回结构和字段命名规范这会帮你省掉至少一天的联调时间。最后留一个小问题供大家自己验证这个系统的攻略收藏功能如果要在未登录状态下也能使用你会怎么设计前端状态和后端接口想清楚这个问题你对前后端分离项目的理解就能再上一个台阶。
返回列表