Java 项目实战: 外卖平台优化-前后端分离开发模式与工程拆分

前后端分离: 开发模式、协作流程与工程结构拆分

纲要

前后端分离开发,解决当前工程的一个结构性问题:

  • 现状问题 :reggie_take_out 一个 Maven 工程里既有 Java 代码,又有 backend/front 两个目录的前端页面
  • 四个痛点:分工不明确、开发效率低、不便于管理、招聘困难
  • 分离后的变化 :工程拆成前端工程与后端工程;部署时后端打 jar 上 Tomcat,前端打包上 Nginx
  • 协作流程:定制接口 → 并行开发 → 前后端自测 → 联调 → 提测
  • 什么是"接口" :一个 HTTP 请求的完整约定(地址、方法、参数、响应)
  • 前端技术栈 :VSCode/HBuilder、NodeJS、Vue、ElementUI、Mock、Webpack

#mermaid-svg-qom9rTMfawkKXbvB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qom9rTMfawkKXbvB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qom9rTMfawkKXbvB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qom9rTMfawkKXbvB .error-icon{fill:#552222;}#mermaid-svg-qom9rTMfawkKXbvB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qom9rTMfawkKXbvB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qom9rTMfawkKXbvB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qom9rTMfawkKXbvB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qom9rTMfawkKXbvB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qom9rTMfawkKXbvB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qom9rTMfawkKXbvB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qom9rTMfawkKXbvB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qom9rTMfawkKXbvB .marker.cross{stroke:#333333;}#mermaid-svg-qom9rTMfawkKXbvB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qom9rTMfawkKXbvB p{margin:0;}#mermaid-svg-qom9rTMfawkKXbvB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qom9rTMfawkKXbvB .cluster-label text{fill:#333;}#mermaid-svg-qom9rTMfawkKXbvB .cluster-label span{color:#333;}#mermaid-svg-qom9rTMfawkKXbvB .cluster-label span p{background-color:transparent;}#mermaid-svg-qom9rTMfawkKXbvB .label text,#mermaid-svg-qom9rTMfawkKXbvB span{fill:#333;color:#333;}#mermaid-svg-qom9rTMfawkKXbvB .node rect,#mermaid-svg-qom9rTMfawkKXbvB .node circle,#mermaid-svg-qom9rTMfawkKXbvB .node ellipse,#mermaid-svg-qom9rTMfawkKXbvB .node polygon,#mermaid-svg-qom9rTMfawkKXbvB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qom9rTMfawkKXbvB .rough-node .label text,#mermaid-svg-qom9rTMfawkKXbvB .node .label text,#mermaid-svg-qom9rTMfawkKXbvB .image-shape .label,#mermaid-svg-qom9rTMfawkKXbvB .icon-shape .label{text-anchor:middle;}#mermaid-svg-qom9rTMfawkKXbvB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qom9rTMfawkKXbvB .rough-node .label,#mermaid-svg-qom9rTMfawkKXbvB .node .label,#mermaid-svg-qom9rTMfawkKXbvB .image-shape .label,#mermaid-svg-qom9rTMfawkKXbvB .icon-shape .label{text-align:center;}#mermaid-svg-qom9rTMfawkKXbvB .node.clickable{cursor:pointer;}#mermaid-svg-qom9rTMfawkKXbvB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qom9rTMfawkKXbvB .arrowheadPath{fill:#333333;}#mermaid-svg-qom9rTMfawkKXbvB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qom9rTMfawkKXbvB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qom9rTMfawkKXbvB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qom9rTMfawkKXbvB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qom9rTMfawkKXbvB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qom9rTMfawkKXbvB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qom9rTMfawkKXbvB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qom9rTMfawkKXbvB .cluster text{fill:#333;}#mermaid-svg-qom9rTMfawkKXbvB .cluster span{color:#333;}#mermaid-svg-qom9rTMfawkKXbvB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qom9rTMfawkKXbvB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qom9rTMfawkKXbvB rect.text{fill:none;stroke-width:0;}#mermaid-svg-qom9rTMfawkKXbvB .icon-shape,#mermaid-svg-qom9rTMfawkKXbvB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qom9rTMfawkKXbvB .icon-shape p,#mermaid-svg-qom9rTMfawkKXbvB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qom9rTMfawkKXbvB .icon-shape .label rect,#mermaid-svg-qom9rTMfawkKXbvB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qom9rTMfawkKXbvB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qom9rTMfawkKXbvB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qom9rTMfawkKXbvB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
定制接口

★ 最关键环节
并行开发
后端自测

Postman / Swagger
前端自测

Mock 模拟数据
前后端联调

校验数据格式
格式是否符合约定?
提交测试

一、当前工程的问题

现状

打开 reggie_take_out 工程,目录构成是:

txt 复制代码
reggie_take_out/
├── src/main/java/          # 后端 Java 代码
├── src/main/resources/
│   ├── application.yml
│   ├── mapper/
│   ├── static/             # 静态资源
│   │   ├── backend/        # 后台管理页面(HTML/JS/CSS)
│   │   └── front/          # 移动端页面
│   └── templates/
└── pom.xml

src/main/resources/static 下的 backend 与 front 两个目录,装着全部前端代码------HTML、JS、CSS、图片。

前后端代码混合在同一个 Maven 工程里。

四个痛点

分工不明确 。同一个开发人员既要写 Java 后端,又要写 HTML/JS 前端。职责边界模糊,出了问题容易互相甩锅。

开发效率低。一会儿写前端,一会儿写后端,上下文频繁切换。人的大脑切换任务是有成本的,来回跳转会显著降低效率。

不便于管理 。很多时候只改了前端的一个按钮样式,后端一行没动,却必须打开整个 Maven 工程,重新打包部署整个应用。一个小改动引发一次全量发布,这在小版本中尤其浪费。

招聘困难 。要求一个人既精通 Java 生态(Spring Boot、MyBatis、Redis、MySQL)又精通前端(Vue、Webpack、CSS),这样的人极少,即便有也成本高昂。

混合开发的其他问题

补充课程未展开但实践中同样突出的几点:

  • 构建耦合 :前端改一个字也要跑一遍 Maven 打包,构建时间从秒级变成分钟级
  • 无法独立扩缩容 :前端是静态资源,本该由 Nginx 高效处理,现在却要占用 Tomcat 的连接与 JVM 内存
  • 技术栈升级困难:想换前端框架就得动后端工程,风险外溢

二、前后端分离开发

定义

前端代码交给专门的前端开发人员负责,后端代码交给专门的后端开发人员负责。分工明确,各司其职。

三个收益

提高效率------让合适的人做擅长的事。前端人员专注交互与体验,后端人员专注业务与数据。

可以并行开发 ------这是最大的收益。接口定好后,前端与后端同时开工,而不是前端等后端接口写完才能动。项目周期显著缩短。

便于管理与维护------两个工程独立版本控制、独立构建、独立部署。

工程结构的变化

分离后从"一个 Maven 工程"拆成两个工程:

txt 复制代码
拆分前:                              拆分后:
reggie_take_out/                     reggie-backend/              reggie-front/
├── src/main/java/                   ├── src/main/java/           ├── public/
├── src/main/resources/              ├── src/main/resources/      ├── src/
│   ├── static/                      │   ├── application.yml      │   ├── api/
│   │   ├── backend/    ← 前端       │   ├── mapper/              │   ├── views/
│   │   └── front/      ← 前端       │   └── static/(已清空)     │   ├── router/
│   └── templates/                   └── pom.xml                  │   └── utils/
└── pom.xml                                                       ├── package.json
                                                                  └── vue.config.js

后端工程 :只有 Java 代码与配置文件,不再包含任何 HTML/CSS/JS。

前端工程 :只有前端代码,没有任何 Java。

部署方式的变化

这是容易被忽略但非常重要的一点:
#mermaid-svg-YJPSoakIBxqqpray{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YJPSoakIBxqqpray .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YJPSoakIBxqqpray .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YJPSoakIBxqqpray .error-icon{fill:#552222;}#mermaid-svg-YJPSoakIBxqqpray .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YJPSoakIBxqqpray .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YJPSoakIBxqqpray .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YJPSoakIBxqqpray .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YJPSoakIBxqqpray .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YJPSoakIBxqqpray .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YJPSoakIBxqqpray .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YJPSoakIBxqqpray .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YJPSoakIBxqqpray .marker.cross{stroke:#333333;}#mermaid-svg-YJPSoakIBxqqpray svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YJPSoakIBxqqpray p{margin:0;}#mermaid-svg-YJPSoakIBxqqpray .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YJPSoakIBxqqpray .cluster-label text{fill:#333;}#mermaid-svg-YJPSoakIBxqqpray .cluster-label span{color:#333;}#mermaid-svg-YJPSoakIBxqqpray .cluster-label span p{background-color:transparent;}#mermaid-svg-YJPSoakIBxqqpray .label text,#mermaid-svg-YJPSoakIBxqqpray span{fill:#333;color:#333;}#mermaid-svg-YJPSoakIBxqqpray .node rect,#mermaid-svg-YJPSoakIBxqqpray .node circle,#mermaid-svg-YJPSoakIBxqqpray .node ellipse,#mermaid-svg-YJPSoakIBxqqpray .node polygon,#mermaid-svg-YJPSoakIBxqqpray .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YJPSoakIBxqqpray .rough-node .label text,#mermaid-svg-YJPSoakIBxqqpray .node .label text,#mermaid-svg-YJPSoakIBxqqpray .image-shape .label,#mermaid-svg-YJPSoakIBxqqpray .icon-shape .label{text-anchor:middle;}#mermaid-svg-YJPSoakIBxqqpray .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YJPSoakIBxqqpray .rough-node .label,#mermaid-svg-YJPSoakIBxqqpray .node .label,#mermaid-svg-YJPSoakIBxqqpray .image-shape .label,#mermaid-svg-YJPSoakIBxqqpray .icon-shape .label{text-align:center;}#mermaid-svg-YJPSoakIBxqqpray .node.clickable{cursor:pointer;}#mermaid-svg-YJPSoakIBxqqpray .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YJPSoakIBxqqpray .arrowheadPath{fill:#333333;}#mermaid-svg-YJPSoakIBxqqpray .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YJPSoakIBxqqpray .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YJPSoakIBxqqpray .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YJPSoakIBxqqpray .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YJPSoakIBxqqpray .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YJPSoakIBxqqpray .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YJPSoakIBxqqpray .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YJPSoakIBxqqpray .cluster text{fill:#333;}#mermaid-svg-YJPSoakIBxqqpray .cluster span{color:#333;}#mermaid-svg-YJPSoakIBxqqpray div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YJPSoakIBxqqpray .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YJPSoakIBxqqpray rect.text{fill:none;stroke-width:0;}#mermaid-svg-YJPSoakIBxqqpray .icon-shape,#mermaid-svg-YJPSoakIBxqqpray .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YJPSoakIBxqqpray .icon-shape p,#mermaid-svg-YJPSoakIBxqqpray .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YJPSoakIBxqqpray .icon-shape .label rect,#mermaid-svg-YJPSoakIBxqqpray .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YJPSoakIBxqqpray .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YJPSoakIBxqqpray .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YJPSoakIBxqqpray :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 分离前
Maven 工程

前后端混合
打成一个 jar
部署到 Tomcat
Tomcat 同时处理

静态资源 + 动态请求
#mermaid-svg-RyPHa0SF0drQ0fz8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RyPHa0SF0drQ0fz8 .error-icon{fill:#552222;}#mermaid-svg-RyPHa0SF0drQ0fz8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RyPHa0SF0drQ0fz8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .marker.cross{stroke:#333333;}#mermaid-svg-RyPHa0SF0drQ0fz8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RyPHa0SF0drQ0fz8 p{margin:0;}#mermaid-svg-RyPHa0SF0drQ0fz8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .cluster-label text{fill:#333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .cluster-label span{color:#333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .cluster-label span p{background-color:transparent;}#mermaid-svg-RyPHa0SF0drQ0fz8 .label text,#mermaid-svg-RyPHa0SF0drQ0fz8 span{fill:#333;color:#333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .node rect,#mermaid-svg-RyPHa0SF0drQ0fz8 .node circle,#mermaid-svg-RyPHa0SF0drQ0fz8 .node ellipse,#mermaid-svg-RyPHa0SF0drQ0fz8 .node polygon,#mermaid-svg-RyPHa0SF0drQ0fz8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RyPHa0SF0drQ0fz8 .rough-node .label text,#mermaid-svg-RyPHa0SF0drQ0fz8 .node .label text,#mermaid-svg-RyPHa0SF0drQ0fz8 .image-shape .label,#mermaid-svg-RyPHa0SF0drQ0fz8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-RyPHa0SF0drQ0fz8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RyPHa0SF0drQ0fz8 .rough-node .label,#mermaid-svg-RyPHa0SF0drQ0fz8 .node .label,#mermaid-svg-RyPHa0SF0drQ0fz8 .image-shape .label,#mermaid-svg-RyPHa0SF0drQ0fz8 .icon-shape .label{text-align:center;}#mermaid-svg-RyPHa0SF0drQ0fz8 .node.clickable{cursor:pointer;}#mermaid-svg-RyPHa0SF0drQ0fz8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .arrowheadPath{fill:#333333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RyPHa0SF0drQ0fz8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RyPHa0SF0drQ0fz8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RyPHa0SF0drQ0fz8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RyPHa0SF0drQ0fz8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RyPHa0SF0drQ0fz8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RyPHa0SF0drQ0fz8 .cluster text{fill:#333;}#mermaid-svg-RyPHa0SF0drQ0fz8 .cluster span{color:#333;}#mermaid-svg-RyPHa0SF0drQ0fz8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RyPHa0SF0drQ0fz8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RyPHa0SF0drQ0fz8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-RyPHa0SF0drQ0fz8 .icon-shape,#mermaid-svg-RyPHa0SF0drQ0fz8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RyPHa0SF0drQ0fz8 .icon-shape p,#mermaid-svg-RyPHa0SF0drQ0fz8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RyPHa0SF0drQ0fz8 .icon-shape .label rect,#mermaid-svg-RyPHa0SF0drQ0fz8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RyPHa0SF0drQ0fz8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RyPHa0SF0drQ0fz8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RyPHa0SF0drQ0fz8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 分离后
后端工程
打包成 jar
前端工程
Webpack 打包

dist 目录
部署到 Tomcat/Spring Boot
部署到 Nginx
只处理动态请求
只处理静态资源

后端代码打包部署到 Tomcat 服务器 (或 Spring Boot 内置容器);前端工程打包后部署到 Nginx。

前端产物(HTML/CSS/JS)本质就是静态资源,而 Nginx 处理静态资源的能力远强于 Tomcat(sendfile 零拷贝、epoll 高并发、不占 JVM)。这正是上一篇 Nginx 部署静态资源要解决的问题。

注意 :分离后前端请求后端会涉及跨域问题,需要配 CORS 或 Nginx 反向代理。这是下一篇 Swagger 与后续部署章节要处理的。

三、协作流程

完整流程

分离后最核心的问题是:前后端人员如何配合。标准流程五步:

第一步:定制接口

前后端坐下来(或通过接口管理平台)约定每个功能的请求细节。

第二步:并行开发

双方按约定各自写代码,互不阻塞。

第三步:各自自测

  • 后端用 Postman、Swagger 测试自己的接口
  • 前端用 Mock 造模拟数据验证页面渲染

第四步:联调

前端请求真正打到后端,主要校验数据格式是否符合约定。

第五步:提测

交给专门的测试人员做完整验证。

为什么"定制接口"是关键

课程里特别强调了这个环节------如果接口没定义好就开始开发,很可能联调时对接不上:请求地址不对、参数不符合要求,导致返工。

联调阶段的典型事故:约定返回 JSON,后端实际返回了 XML 或纯文本,前端完全无法解析。这类问题在接口约定清晰时根本不会发生。

接口的具体含义

这里的"接口"不是 Java 的 interface ,而是一个 HTTP 请求的完整约定,包括:

要素 说明 示例
接口名称 功能描述 新增用户
请求方法 GET/POST/PUT/DELETE POST
请求路径 URL 路径 /user/add
请求头 Header 要求 Content-Type: application/json
请求参数 请求体或 Query 参数 name(String)、age(Integer)
参数约束 是否必填、类型、示例 name 必填,示例"张三"
响应数据 返回结构与示例 {"code":1,"message":"ok"}

完整接口示例

新增用户接口:

txt 复制代码
接口名称:新增用户
请求方式:POST
请求路径:/user/add

请求头:
  Content-Type: application/json

请求体(application/json):
  {
    "name": "张三",      // String,必填
    "age": 25           // Integer,必填
  }

响应数据(application/json):
  {
    "code": 1,
    "message": "ok"
  }

对应到外卖平台的实际接口,例如菜品分页查询:

txt 复制代码
接口名称:菜品分页查询
请求方式:GET
请求路径:/dish/page

请求参数(Query):
  page      Integer  必填   示例 1
  pageSize  Integer  必填   示例 10
  name      String   非必填 示例 鱼香肉丝

响应数据(application/json):
  {
    "code": 1,
    "msg": null,
    "data": {
      "records": [
        {
          "id": "1397849739276890114",
          "name": "鱼香肉丝",
          "categoryId": "1397844263642378242",
          "categoryName": "川菜",
          "price": 3800,
          "image": "dish-xxx.jpg",
          "status": 1
        }
      ],
      "total": 24,
      "size": 10,
      "current": 1,
      "pages": 3
    },
    "map": {}
  }

注意 id 是字符串 而非数字------这正是前面"雪花 ID 精度丢失"那一节的结果:JacksonObjectMapper 把 Long 序列化成 String,避免 JS 的 2^53 截断。

这类细节必须在接口文档里写清楚 ,否则前端按数字类型解析 ID 会踩坑。

前端为什么能用 Mock 并行开发

Mock 即模拟数据。前端开发一个分页列表页面时,不需要后端真的就绪,只要按接口约定造一份假数据:

javascript 复制代码
// mock/dish.js
export default {
  'GET /dish/page': {
    code: 1,
    msg: null,
    data: {
      records: [
        {
          id: '1397849739276890114',
          name: '鱼香肉丝',
          categoryName: '川菜',
          price: 3800,
          status: 1
        },
        {
          id: '1397849739276890115',
          name: '宫保鸡丁',
          categoryName: '川菜',
          price: 3600,
          status: 1
        }
      ],
      total: 24,
      size: 10,
      current: 1,
      pages: 3
    },
    map: {}
  }
}

关键前提 :Mock 数据的结构必须与接口约定完全一致 。这样页面在 Mock 下能正常渲染,切到真实后端时因为数据格式一致,自然也能渲染。

这也是为什么接口约定必须先于开发------Mock 数据就是接口约定的第一份可执行实现。

四、前端技术栈

后端工程师不需要写前端代码,但要了解对方用什么,才能高效协作。

开发工具

工具 说明
Visual Studio Code(VSCode) 目前最主流的前端编辑器,插件生态丰富
HBuilder 国产前端 IDE,对 uni-app 支持好

技术框架

技术 作用 后端类比
NodeJS 前端运行基石,类似 JDK JDK
Vue 前端 MVVM 框架 Spring MVC
ElementUI UI 组件库(表格、下拉框、菜单) Bootstrap / 各种 UI 库
Mock 造模拟数据,支持并行开发 Postman Mock Server
Webpack 前端打包工具 Maven

NodeJS 的定位值得一说 :它之于前端就像 JDK 之于 Java。前端的现代构建工具(Webpack、Vite)、开发服务器、包管理器(npm、yarn)全部运行在 NodeJS 之上。但它只用于开发期和构建期 ------最终产物是纯静态文件,运行时不需要 NodeJS。

Webpack 的产物不是 jar/war ,而是一堆经过压缩、混淆、合并的 JS/CSS/HTML,通常输出到 dist/ 目录。这个 dist 目录就是部署到 Nginx 的东西。

前后端技术对照

环节 前端 后端
运行环境 NodeJS JDK
包管理 npm / yarn Maven / Gradle
依赖声明 package.json pom.xml
主框架 Vue Spring Boot
UI 组件 ElementUI ---
构建工具 Webpack Maven
构建产物 dist/(静态文件) *.jar
部署位置 Nginx Tomcat / Spring Boot
自测工具 Mock Postman / Swagger

五、分离后的跨域问题

分离开发带来一个新问题:前端在 Nginx(80 端口),后端在 Tomcat(8080 端口),域名或端口不同即构成跨域,浏览器的同源策略会拦截请求。

解法一:后端配置 CORS

java 复制代码
package com.itheima.reggie.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 跨域配置
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

注意 allowCredentials(true) 与 allowedOrigins("*") 不能同时使用 ------CORS 规范规定,允许携带凭证时 Origin 不能用通配符。所以上面用的是 allowedOriginPatterns("*")(Spring 5.3+ 支持),或者显式列出允许的域名。

解法二:Nginx 反向代理(推荐)

让前后端对外同一个域名同一个端口,从根上消除跨域:

nginx 复制代码
server {
    listen       80;
    server_name  localhost;

    # 前端静态资源
    location / {
        root   html;
        index  index.html;
        try_files $uri $uri/ /index.html;
    }

    # 后端接口,转发到 8080
    location /api/ {
        proxy_pass http://192.168.138.101:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

前端所有请求都发到 /api/**(同源),由 Nginx 转发到后端。浏览器看到的是同源请求,不存在跨域。

生产推荐这种方案 ------同源不仅避免了 CORS 的复杂配置,还省去了预检请求(OPTIONS)带来的额外往返。

六、接口文档的三种形态

形态 工具 特点
独立管理平台 YApi 需自行部署,支持团队协作、Mock、测试
代码自动生成 Swagger / Knife4j 注解驱动,与代码同步,后端友好
静态文件 Markdown / HTML 离线可看,但易与代码脱节

下一篇讲 YApi 的具体使用,再下一篇讲 Swagger------后者是后端工程师更常用的方式,因为它用注解写在代码里,代码改了文档自动跟着变。

API 速览

概念 / 工具 作用
接口(API) 一个 HTTP 请求的完整约定:方法、路径、参数、响应
Mock 模拟数据,让前端在后端未就绪时也能开发
NodeJS 前端构建期运行环境,类比 JDK
Vue 前端 MVVM 框架
ElementUI Vue 的 UI 组件库
Webpack 前端打包工具,产物在 dist/ 目录
VSCode / HBuilder 前端开发 IDE
CORS 跨域资源共享,分离部署后必须处理
allowedOriginPatterns Spring 5.3+ 的跨域来源匹配,可配合 allowCredentials
try_files Nginx 指令,SPA 前端路由回退到 index.html

官方文档

总结

前后端混合开发的本质问题是耦合:分工、构建、部署、扩缩容全绑在一起。分离后前端工程与后端工程独立演进,各自用最合适的工具链。

分离不只是目录拆分,部署方式也变了 :后端打 jar 上 Tomcat/Spring Boot,前端 Webpack 打包成 dist 上 Nginx。前端产物本质是静态资源,交给 Nginx 处理能获得 sendfile 零拷贝和 epoll 高并发的收益,同时不占用宝贵的 JVM 资源。

协作流程中最关键的是"定制接口" 。这里的接口不是 Java 的 interface,而是一个 HTTP 请求的完整约定------方法、路径、请求头、参数(含是否必填与示例)、响应结构与示例。接口定清楚了,前后端才能并行开发;定不清楚,联调阶段必然返工。

前端能并行开发靠的是 Mock ,而 Mock 数据必须与接口约定结构完全一致------所以接口文档实际上是 Mock 数据、前端代码、后端代码三方共同的输入。

分离后出现跨域问题 ,推荐用 Nginx 反向代理让前后端同源(/api/** 转发到后端),比配置 CORS 更彻底:既不用处理预检请求,也避免了 allowCredentials 与通配符 Origin 的冲突。

下一篇讲 YApi------API 管理平台的部署与使用,包括创建项目、添加分类、定义接口、导出与批量导入接口文档。

相关推荐
Dovis(誓平步青云)2 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
HEJOO92 小时前
指针的算术运算详解
java·数据结构·算法
sunshine22 girl2 小时前
Java学习六 Java常见API-2--String-3-字符串的截取替换和其他方法
java·开发语言·学习
LOVE️YOU2 小时前
Python 函数名、函数对象与“把函数作为参数传递”
开发语言·python
EatFan2 小时前
从“框架混战“到“运行时收敛“:2026 年 AI Agent 开发框架的三条路线之争
java·数据库·人工智能·多智能体·ai agent·mcp·agent 框架
小蒜学长2 小时前
基于SpringBoot的佳新超市管理系统设计与实现系统(代码+数据库+LW)
java·数据库·spring boot·后端·佳新超市管理系统
sunshine22 girl3 小时前
Java学习六 Java常见API-1-Radom随机数
java·开发语言·学习
可乐鸡翅yeah_3 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
潼心1412o3 小时前
C++初阶(长期更新)第6讲:类和对象(下)
开发语言·c++·算法