资讯动态

前后端分离架构实战:从JSP到SpringBoot+Vue的演进与部署指南

发布时间:2026/9/30 7:36:53 来源:尧图企业网站定制
1. 十年前我为什么要带头把项目拆成前后端先把时间拉回到2013年左右。那时候我刚工作没几年团队接的都是企业级管理系统技术栈高度统一后端用Java页面用JSP数据库用MySQL服务器清一色Tomcat。听起来没什么问题但真正动手的时候所有人都憋着一肚子火。当时最典型的场景是这样的前端工程师改一个按钮要把整个Java项目在本地跑起来启动一次Tomcat少说也要几十秒等Spring容器加载完、等MyBatis的Mapper扫完半分钟就过去了。更难受的是JSP页面里经常混着Java代码% request.getAttribute(xxx) %这种写法到处都是后端写了一段逻辑前端想看分支判断得连Java语法一起懂。代码改完要联调那就更费劲了。一个页面上有三个区块A区块是张三做的B区块是李四做的两个人的代码都在同一个JSP文件里用SVN往上一提交冲突能吵半天。那时候我们常开玩笑说前后端没有分离分离的是人和人之间的耐心。真正的导火索是一次上线事故。电商促销页面要改版前端希望两周搞定交互后端说接口要按他的数据结构排期结果两边僵到最后一周前端用iframe嵌了一个纯HTML页面勉强把效果做出来了但数据全部写死。那一次之后我们团队做了一个很朴素的决定把控制器的返回类型从ModelAndView改成ResponseBody把JSP改成纯HTML前端自己调接口后端只负责出数据。这个决定就是前后端分离在中小团队里的雏形。回头看它解决的核心问题不是技术而是协作边界。后端能专心做业务和数据处理前端能自己做交互、样式和部分业务逻辑两边的交付节奏不必再被绑在同一个发布周期里。2. 从JSP到RESTful API前后端分离的技术路线是如何走出来的2.1 第一阶段Ajax小规模使用页面仍是多页应用其实前后端分离并不是突然出现的。2005年Gmail把Ajax大规模带到浏览器端之后很多人就发现页面局部刷新比整页提交要快得多。但那时候Ajax只是作为JSP页面的一个辅助手段用户点一个按钮页面不跳转前端用XMLHttpRequest向服务端要一段数据然后DOM操作把结果塞到某个div里。这个阶段严格说不叫分离叫异步化改造。后端依然是MVC那一套Controller返回的还是JSP页面只不过页内有一部分交互走接口。基于这种模式前端压力不大但前端代码的责任边界已经开始露头谁负责维护那些繁杂的DOM操作页面一多回调一层套一层代码很快就没法看了。2.2 第二阶段SPA爆发前端真正接管页面渲染大概在2013到2015年AngularJS、React、Vue陆续进入视野单页应用SPA的概念开始普及。SPA最大的变化是页面渲染完全交给前端后端不再返回HTML而是返回结构化数据一开始是XML后来几乎统一成JSON。这个变化是决定性的。因为一旦后端不再产HTML前端就需要自己去解决路由、状态管理、模板编译、组件通信等一系列问题。而一旦前端开始像做工程那样管理代码Node.js作为构建工具链的基底就被迅速普及。现在大家用烂了的npm、webpack、babel都是那两年沉淀下来的。我自己经历过的典型改造路径是这样的后端把Controller拆成两层一层专出JSON比如/api/user返回用户对象一层不再用JSP把原有页面静态化扔给Nginx托管前端项目单独初始化一个工程用Vue CLI之类脚手架搭好之后开发阶段用proxy插件把/api请求代理到后端端口生产阶段再交给Nginx做反向代理。2.3 RESTful API成了大家嘴上不说但必须认的规矩前后端分离走到一定规模之后大家发现一个问题接口规范如果靠口头约定迟早要出事。比如获取用户信息这个接口A团队写成/getUserB团队写成/user/queryC团队直接/user?id1联调和维护成本急剧上升。RESTful风格之所以能普及不是因为REST这个词有多高级而是它给出了一套足够简单的约定资源用名词操作用动词映射到HTTP方法。GET /user/1获取用户POST /user创建用户DELETE /user/1删除用户。这套约定大大降低了前后端沟通时的理解成本也让接口文档变得好写。当然RESTful不是万能的。企业管理系统里大量存在导出报表批量审批复杂条件分页查询这类动作如果死抠REST语义会很别扭。成熟的团队通常的做法是核心资源走RESTful风格报表、提交、批量操作等场景用RPC风格接口或加/action后缀比如POST /report/export、POST /order/batchAudit目的只有一个——让调用的人一眼看懂不产生歧义。3. SpringBoot Vue这套组合为什么成了主流若依框架解决了什么3.1 SpringBoot消灭了配置地狱Vue解决了组件复用如果只选一套技术栈来代表过去五年前后端分离的主流实践SpringBoot Vue绝对是最有代表性的。先说SpringBoot这一侧它最大的贡献是把SSM时代混乱的XML配置压缩成了零配置或极少配置。以前搭一个SpringMVC项目要写web.xml、spring-mvc.xml、mybatis-config.xml一堆bean定义看得人头皮发麻SpringBoot通过自动配置和起步依赖一套spring-boot-starter-web就搞定了大部分东西前后端对接时互相传递的接口模型也更好维护。Vue这边呢它的学习曲线比React平缓不少模板语法非常接近写HTML的心智模型模板、组件、路由、状态管理一套下来极其顺手。而且Vue对中后台项目简直天生适配表格、表单、弹窗、抽屉、分页这一套东西做一个页面几乎等于做一次组件拼装。我在SpringBoot Vue项目里最常用的目录约定是前端src/api模块统一封装axios每个后端Controller对应该目录里的一个JS文件后端这边controller、service、mapper三层严格分层接口返回统一封装成ResultT结构。3.2 一个完整接口的典型链路从前端点击到后端落库很多人刚接触前后端分离时容易被一堆框架吓到但其实整个调用链路很朴素Vue页面里用户点保存按钮触发一个methods里的方法。该方法调用src/api/user.js里封装的addUser(data)。addUser内部调用axios发起POST /api/user请求并设置请求头Content-Type: application/json。请求先到Nginx或开发时经过Vite代理被转发到SpringBoot后端的8080端口。SpringBoot的UserController.addUser(RequestBody UserDTO dto)接收JSON做参数校验调到UserService再经UserMapper执行MYSQL的INSERT语句。结果一层层返回到前端Vue组件根据返回的code字段判断成功失败成功了就关弹窗、刷新列表失败了就在页面上展示错误消息。这个链路里最值得注意的约定有两个。第一接口返回结构要全局统一。我见过很多团队一开始不统一返回格式有的接口直接返回数组有的返回{success: true}有的返回{code: 0}前端拿到不同接口要做不同的判断痛苦不堪。统一成{code: 200, data: {...}, message: success}之后前端只需封一个响应拦截器判断一次code异常提示全局处理省下大量重复劳动。第二DTO和实体类一定要分清。很多新手图省事直接把MyBatis的实体类用在Controller入参上结果有一天数据库加了一个敏感字段比如用户表的password_hash前端只要多传一个字段不小心就会把脏数据写进库里。正确的做法是Controller接收DTOService层负责把DTO转换成实体再交给Mapper操作这样接口层永远不会暴露数据库结构。3.3 若依框架中后台项目的开箱即用方案如果你接触过国内大量企业级前后端分离项目一定绕不开若依RuoYi。这个开源框架本质上是把SpringBoot Vue MyBatis这一套中后台的公共能力全部预置好了登录认证、权限管理RBAC、代码生成器、定时任务、操作日志、数据字典、多数据源配置几乎一个不落。很多新手会问若依到底解决了什么问题我的理解是它解决了新项目从零搭建时那最难熬的一周。没有若依之前做一个后台管理系统的第一周基本都在搭框架画登录页、搞用户表角色表、写token校验逻辑、写权限拦截器、做菜单管理。有若依之后你clone一下代码改个数据库连接跑起来就已经有一个能登录、能管理菜单、能分配权限的基础后台了。但用若依也有需要注意的事。第一它的代码风格偏传统很多地方用了startWith、endWith拼条件的方式写SQL如果你习惯写MyBatis-Plus的LambdaQueryWrapper刚上手可能会觉得不够现代化。第二若依的权限模型是经典RBAC也就是用户—角色—菜单三级模型如果你的业务场景需要数据权限行级控制光靠若依的默认配置是不够的得自己扩展数据权限的SQL拼接逻辑。我在几个外包和政府项目中都基于若依改过最大的体会是拿它当脚手架没问题但不要被它的结构框死。新模块尽量按你自己的业务领域去划分包结构只复用它的基础能力登录、权限、代码生成业务代码尽量独立这样后续即使框架升级你的业务代码迁移成本也有限。4. 前后端分离项目的部署真相Tomcat和Nginx到底各管哪一段4.1 一个常被问的问题SpringBoot前后端分离项目能不能丢Tomcat搜索热词里tomcat部署前后端分离项目排在很前面可见这是很多人实际会遇到的场景。先给出明确结论能但不能直接用Tomcat把前端页面和SpringBoot的API一起部署。为什么因为前后端分离之后前端产物是纯静态资源HTML、CSS、JS、图片它不需要Servlet容器来处理只需要一个静态文件服务器而后端的SpringBoot应用是一个Jar或War包虽然内嵌了Tomcat但它的职责是提供API。如果把前端静态文件也塞进Tomcat的webapps目录里不是说完全不能跑而是会带来几个问题静态资源加载路径和接口路径混在一起部署升级时改前端也要动Tomcat、动Java进程完全违背了独立部署、独立发布的初衷。更实际的问题是性能。Tomcat对静态文件的处理能力远不如NginxNginx处理静态资源用事件驱动模型高并发下占用的系统资源更少配合gzip压缩、缓存过期头、浏览器协商缓存同样一台服务器前端页面的加载速度差距非常明显。4.2 推荐的部署架构Nginx托管前端反向代理到Tomcat这家单个人最常见也最推荐的结构如下用户浏览器 ↓ Nginx80端口 ├── / → 托管前端dist静态文件 └── /api → proxy_pass 到后端Tomcat8080端口Nginx配置的核心长这样server { listen 80; server_name your-domain.com; # 前端静态资源 root /opt/ruoyi/dist; index index.html; # 兼容Vue Router的history模式 location / { try_files $uri $uri/ /index.html; } # 后端API location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有一个必须重点说的细节proxy_pass http://127.0.0.1:8080/;末尾这个斜杠。如果你写成http://127.0.0.1:8080没有结尾斜杠那么/api/user/list会被转发到后端变成/api/user/list如果你写成带斜杠的http://127.0.0.1:8080/那么/api/user/list会被替换成/user/list也就是**/api前缀被剥掉**。后端Controller如果统一要求路径是/user/list没有/api前缀就采用带斜杠的写法如果后端Controller本身就带/api/user/list前缀就别加末尾斜杠。这个坑我见过不止一次很多人前端请求栈报404检查了半天最后发现是Nginx的proxy_pass斜杠问题。4.3 Vue Router的history模式刷新就404的根因部署前后端分离项目时另一大高频踩坑点是打开首页一切正常一刷新某个子路由Nginx直接返回404。原因是Vue Router在history模式下路由是前端通过History API模拟的浏览器地址栏的/user/list在后端服务器上根本不存在对应的物理文件。如果Nginx没有配置try_files请求到达服务器后发现找不到文件自然会404。解决办法就是我上面配置里写的location / { try_files $uri $uri/ /index.html; }这行的意思是如果请求的路径能找到静态文件就直接返回文件如果找不到就回退到index.html把路由交给前端的Vue Router自己去解析。如果你的项目部署在子路径下比如http://server.com/myapp/那问题会复杂一些Vue Router的base要设成/myapp/Nginx的location也要对应调整成location /myapp/ { ... }而且try_files要写成try_files $uri $uri/ /myapp/index.html;。我第一次遇到子路径部署时在这上面折腾了整整大半天后来学乖了默认都用根路径部署实在不行再考虑子路径。4.4 前后端跨域问题Dev阶段和Prod阶段的处理不一样跨域CORS也是前后端分离初学者最容易绕晕的点。先把这个机制说透浏览器会拦截跨域请求协议不同、域名不同、端口不同都算跨域注意是浏览器拦截服务器之间的请求不受这个限制。开发阶段的解决办法非常简单在Vite或Webpack的devServer里配一个proxy代理把/api自动转发到后端的http://localhost:8080因为浏览器发出的请求是同源的都指向前端脚手架自己的端口浏览器根本感知不到跨域自然不会被拦截。生产阶段的处理思路就不同了。既然我们已经在Nginx做了反向代理前端和后端统一通过同一个域名访问前端是/后端是/api本质上是同源的跨域问题根本不存在。所以我在正式环境部署时几乎不会去后端代码里加CrossOrigin注解也不建议团队加。只有一种情况需要在后端处理跨域前端和后端的域名或端口无法统一比如前端部署在公司CMS平台上后端独立部署在某台内网服务器。这时候才需要在SpringBoot里配置全局CORS但请注意allowOrigins尽量不要写成*而是明确指定允许的来源列表否则接口会被任意第三方页面调用存在安全风险。4.5 会话与Token前后端分离后登录态该放哪JSP时代登录态靠Session Cookie服务端能直接感知用户状态。前后端分离之后页面和接口分开了Session的维护变得别扭尤其是在跨域、多实例部署的场景下。于是Token成为主流的认证方案。简单说用户登录成功后后端返回一个Token常见的是有效期内签名的JWT前端把它存在本地常用的有localStorage、sessionStorage或者内存变量之后每个请求通过请求头Authorization: Bearer token携带。后端用一个拦截器或AOP切面统一校验Token解析出用户身份再放行请求。这里有几个实践心得不要把Token放在localStorage里存死。localStorage的读取会被任何同源脚本访问如果页面存在XSS漏洞Token很容易被偷走。折中的方案是存内存变量刷新页面后丢失再去调一个刷新接口重新获取。JWT的核心是签名不是加密。JWT里塞的用户信息是Base64编码的任何拿到JWT的人都能解码看到只是无法篡改。所以敏感字段手机号、身份证号不要直接放进JWT。Token过期后要有一个平滑的刷新机制。后端接口返回401时前端响应拦截器要捕获到然后用refresh_token去换新的access_token换成功了重放原请求换失败了再跳登录页。这个链路如果没做好用户的体验就是用着用着突然被踢回登录页。5. 前后端分离的代价我们不能假装这不是一把双刃剑聊到这里如果只说前后端分离的好话那是不负责任的。这些年我在项目里也见过不少伪分离和过度分离这些坑说穿了其实都是人祸不是这个架构模式本身的问题。5.1 伪分离接口数量翻三倍前端还是在写死数据有些团队名义上做了前后端分离实际上只是把原来的JSP变成了后端生成前端JS变量。Controller返回一个JSON壳里面嵌套一段由后端拼出来的HTML字符串前端拿过来直接用v-html或dangerouslySetInnerHTML渲染。这种模式表面上是接口返回数据实际上数据里全是渲染好的HTML标签前端想改成组件化渲染根本无从下手。这种伪分离比不分离更糟因为它既有前端工程的复杂性又没有真正解放前端。要判断你们是不是伪分离有一个很简单的标准把接口返回的JSON里所有HTML标签删掉看前端还能不能正常渲染页面。如果不能那你就还是在用分离的壳做耦合的事。5.2 过度设计只是一个信息展示页硬拆成三个系统反过来另一种极端是一个很小的项目也强行搞前后端分离、微服务、消息中间件、容器编排。我见过一个只有三个功能模块的内部工具前端用了全套Vue全家桶后端拆了四个微服务每个服务配一个独立的MySQL库部署还要走K8s。项目的成本大部分花在了为了让系统显得很专业上而不是花在业务交付上。说真的前后端分离的最小可行规模至少得满足一个条件你有一个明确的接口消费者和接口提供者。如果只有一个人做全栈页面和逻辑都在同一个需求下快速迭代采用一个成熟的SSR框架比如SpringBoot Thymeleaf或者Next.js反而更省力。等团队规模上来了页面角色复杂了再切到前后端分离也不迟。5.3 接口成为新的性能瓶颈与排障难点分离之后前端看到的大部分慢已经从后端渲染慢变成前端调了一堆接口。尤其是首屏需要展示二三十个字段如果前端没有做并发请求、懒加载、字段裁剪优化很容易出现页面上每个区块都在转圈的情况。一个页面调一堆接口还有一个隐患接口失败率呈指数级放大。任何一个环节慢了或挂了整块页面就渲染不全。所以我现在做项目一定会规划一个API聚合层或者一个BFF层让页面优先请求一两个聚合接口后端内部把多个数据源的拼装做完而不是让浏览器直接打印一个接口轰炸清单。排障难也是实实在在的代价。分离之前一条请求全链路都在应用日志里好查分离之后一个请求要依次经过Nginx、网关、后端服务、数据库日志散落在多处前端发现问题时后端往往已经轮转了半天。解决这个问题没有银弹只能靠全链路追踪如SkyWalking、Zipkin和规范化的traceId透传把一次请求串起来看。6. 前后端分离之后的演进方向哪些趋势会留下哪些只是概念6.1 SSR与同构渲染向首屏体验妥协前后端分离的SPA模式有一个天然短板首屏加载慢、SEO不友好。搜索引擎爬虫虽然已经在进步但对大量异步渲染内容的SPA页面收录效果依然不如服务端渲染稳定。于是这几年SSR如Nuxt.js、Next.js重新流行起来。它的思路是首屏由服务端把JavaScript跑一遍渲染出完整HTML返回给浏览器后续页面交互再前端接管。听起来像是回到了服务端渲染其实是另一回事——渲染能力变成了同一套代码的两种执行环境也就是同构渲染。落地实践里很多团队并不会全站SSR而是选几个关键页面做SSR比如首页、落地页、详情页其余管理后台依然用SPA模式。我自己在电商项目里的做法是C端页面用Nuxt SSRB端管理后台保持Vue SPA两边尽量复用公共组件库部署分开但不互相阻塞。6.2 微前端多团队大型中后台的整合方案微前端在过去几年被提到了很高的位置。它的核心诉求是一个大型后台管理系统由多个团队各自负责一部分大家技术栈不一、发布节奏不一但最终要集成在一个统一的壳里。微前端通过路由分发把多个子应用整合成一个对外统一的应用各自独立开发、独立部署。这条路线我用下来觉得它解决的是组织问题多于技术问题。如果你的团队大了、系统复杂到一定程度微前端能让相互协作的代价变低但如果只是几个人维护一个小后台强行上微前端只会引入大量的Remote Entry、沙箱隔离、公共依赖共享等问题得不偿失。6.3 BFF层给前端定制刚刚好的接口BFFBackend For Frontend的概念其实很朴素在数据提供者和前端消费者之间加一层专门为前端服务的聚合层。前端需要什么BFF就组装什么后端各领域服务不直接暴露给前端。为什么要BFF因为真正的后端服务接口往往是为业务能力设计的字段很全而前端页面可能需要的是视图模型字段不多但跨了多个服务。我在一个商城项目里商品详情页要展示商品基本信息、库存状态、促销活动、店铺信息如果让前端分别请求4个服务接口首屏会慢得没眼看。后来我在BFF层用并行调用把4个服务的数据组装成一个详情DTO前端一次请求全部拿到首屏速度翻了近一倍。但BFF不是免费的。它多了一层代码要维护多了一层故障点。如果团队规模不大我建议先把BFF逻辑放在原生后端Service层里等确实出现一个页面需要多服务数据拼装再单独抽层。6.4 前后端分离与低代码平台的博弈最后说一个很多人没意识到的趋势低代码平台正在消灭一部分常规的前后端分离开发。在低代码平台上表单和列表通过可视化配置直接生成底层封装好了接口调用你不需要再关心数据是怎么来的。这确实让没有技术背景的业务人员也能搭页面变成现实。但低代码从来不是替代前后端分离而是把分离出来的接口复用到了更高一层。那些被低代码平台调用的数据服务恰恰还需要后端工程师以规范的方式提供。低代码解决的是页面渲染无关紧要的部分而复杂业务逻辑、数据模型、权限边界永远需要工程化的代码去兜底。7. 说句实在话面对还没开始分离的团队如果一个项目还没有做前后端分离或者正在考虑要不要做我的建议很直接分三步走。第一步把登录和权限梳理清楚。前后端分离后最痛苦的就是认证体系改造先想好Token怎么发、怎么验、怎么刷新、跨域怎么处理这比选框架重要一百倍。第二步挑一个新模块做试点。不要试图把几百个老JSP页面一次性翻新那不叫重构叫冒险。先拿一个交互复杂、变更频繁的模块做样板梳理出接口规范、前端工程结构、部署管道效果好了再复制。第三步把接口文档和状态码规范定死。前后端分离的工程能不能跑得顺很大程度取决于两边对接口的常识是否一致。状态码怎么定义、分页参数怎么传、字段命名是驼峰还是下划线、时间格式是字符串还是时间戳这些细节必须在项目初期就落成文档。从我这些年的经验看前后端分离最大的价值不是技术上的解耦而是让每个角色都能在自己的专业领域做到极致。后端不必再被前端页面逼着学习JavaScript和CSS前端也不必为了改一个按钮去重启Tomcat。它把工程协作的边界定清楚之后团队的交付速度和幸福感都会上一个台阶。当然它也逼着我们每个人走出舒适区后端要懂接口设计、懂Token、懂跨域前端要懂工程化、懂部署、懂性能。时代没有淘汰哪种语言或框架它淘汰的永远是只愿意守着自己那一亩三分地的人。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑