前后端分离解决什么问题,痛点在哪里?

在不使用前后端分离的年代,前端人员写好静态的HTML页面后给到后端开发人员(所谓的静态页面是指数据都是固定死的页面),后端开发人员需要把静态的HTML页面转为动态的页面。怎么转为动态的页面呢?方式有很多种,比如通过模板技术转为JSP、Thymeleaf或者Freemarker。因为这些模板技术可以对静态的HTML进行改造,先从数据库查询数据,利用模板技术把查询后的数据和静态的html进行合并,然后再返回给客户端。
前后端分离 - 图1

服务端开发人员需要使用模板技术对静态的HTML页面进行改造,会向HTML页面里面插入很多标签(模板技术的标签),如果遇到问题,就需要找前端人员来一起排查解决,但此时前端看到的代码已经不是他之前写的HTML了,是混合了一大堆标签的HTML代码,而前端也不懂这些模板技术。类似这样的场景:
后端:你写的页面有问题啊,不显示数据。
前端:不可能,我这边都是好的。
后端:你自己来看啊。
前端:你写的这是什么玩意?我给你的代码不是这样的。
后端:我得把数据加到你的HTML页面里面呀。
前端:我又不懂这些标签,你再把标签摘出来吧,我帮你看看问题。
后端:……

这样直接导致开发效率非常低,遇到问题也不好排查,需要双方一起协商一起查问题,并且还非常浪费用户流量,每次请求都会返回整个HTML页面,很多HTML标签如div是不需要返回的,只需要返回div所需要的数据就可以了。为了解决这些问题前后端分离的模式就诞生了。

什么是前后端分离

顾名思义,前后端分离,就是把一个应用的前端代码和后端代码分离开来,拆分成两个应用,一个纯前端应用,只负责数据展示和用户交互,一个后端应用,只负责提供接口返回数据(如JSON)给前端应用。前端应用使用静态HTML页面,通过异步请求(如Ajax)调用后端接口,返回给客户端(浏览器)数据后,浏览器解析服务端返回的数据,动态生成HTML标签展示到用户界面上。最终的结构是这样的
前后端分离 - 图2

如上图,其实就是拆分成两个应用,前端应用和后端应用,部署的时候也分开部署。前端应用使用Vue、React、Angular等开发,后端应用使用Spring boot框架进行开发。最流行的方案就是Spring boot + Vue啦。

项目案例

我们一步一步演进、实现前后端分离项目。

先让静态页面动起来

我们使用Spring boot搭建后端项目,因为它能减少很多配置。前端使用Vue编写(不用害怕,只是非常简单的页面),为了演示我们不搞复杂的东西。项目如下图所示
Snipaste_2021-12-25_17-54-24.png
是的,我们仍然把静态HTML页面放到后端项目里面。HTML页面大致如下

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div id="app">
  11. </div>
  12. <!-- 本地引入Vue库 -->
  13. <script src="js/vue.js"></script>
  14. <!-- 引入axios发送网络请求 -->
  15. <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  16. <template id='temp-test'>
  17. <div>这是一个简单的HTML页面</div>
  18. <button @click="fetchData">获取服务端数据,进行展示</button>
  19. <ul>
  20. <li v-for="item in listData" :key="index">
  21. 姓名: {{item.name}} 年龄:{{item.age}}
  22. </li>
  23. </ul>
  24. </template>
  25. <script>
  26. const xxx = {
  27. template: "#temp-test",
  28. data() {
  29. return {
  30. listData:[]
  31. }
  32. },
  33. methods: {
  34. fetchData(){
  35. axios.get("http://localhost:8080/getPersons").then(response => (
  36. this.listData = response.data
  37. ))
  38. }
  39. },
  40. }
  41. const app = Vue.createApp(xxx);
  42. app.mount("#app")
  43. </script>
  44. </body>
  45. </html>

我们启动后端应用,访问http://localhost:8080/test.html。如下图所示
image.png
点击获取服务数据按钮,如下所示
image.png
此时此刻静态的HTML页面真的变成动态的了。这种结构如下图
前后端分离 - 图6

这种模式还是存在问题,前端和后端代码还是放在同一个工程中,前端人员还需要下载IDEA进行开发吗?这样做肯定不行,前端人员肯定要用前端工具进行开发,比如vscode、webstorm等,所以我们需要创建两个项目,一个后端项目,一个前端项目。

彻底前后端分离

后端项目保持不变,只是把资源文件里面的内容放到了前端项目里面,如下图所示
Snipaste_2021-12-25_18-36-45.png
启动后端项目,服务地址为:http://localhost:8080/getPersons
前端可以使用vscode或者webstorm等,这里我们使用vscode
Snipaste_2021-12-25_18-41-13.png
如下图所示
image.png
前端项目,本地服务的地址为:http://127.0.0.1:5501/test.html。此时的结构图如下所示
前后端分离 - 图10

当点击获取服务端数据按钮时,发现控制台报错
image.png
这就是传说的跨域问题,这里我们先刹车讲解一下跨域问题。

同源策略的限制

关于CORS的理解,阮一峰这篇文章感觉不错

  • 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致,默认情况下Ajax请求只能发送给同源的URL

这一点MDN上做了一个例子比较好
image.png

  • 有些HTML标签是不受同源策略影响的,比如以下标签

    1. <script src="..."></script>
    2. <link rel="stylesheet" href="...">
    3. <img>
    4. <video>
    5. <audio>
    6. <iframe>
  • 跨域只存在于浏览器端,但是像iOS、Android等移动端因为会有webview加载HTML页面的情况,也会存在跨域,这时客户端和服务器都需要做处理,我们目前只讨论PC浏览器以及Spring boot的解决方案。

  • 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

    如何解决跨域访问

    使用CORS跨域请求,CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出请求。
    客户端支持
    默认所有浏览器都支持,如果是IE浏览器至少IE10

服务端Spring Boot配置

服务端处理有很多种方式,我们这里先用简单粗暴的方式直接在Controller上或者Controller里面的方法上加注解即可

  1. @RestController
  2. @CrossOrigin({"http://127.0.0.1:5501"}) //允许哪些源跨域,传一个数组即可
  3. public class TestController {
  4. @RequestMapping("/getPersons")
  5. public List<Person> getUser(){
  6. List<Person> list = new ArrayList<>(10);
  7. for (int i = 0; i < 10; i++) {
  8. list.add(new Person("jack"+i,i));
  9. }
  10. return list;
  11. }
  12. }

点击获取服务端数据按钮
image.png
到这里前后端分离就讲完了,我们画一个最终的结构图
前后端分离 - 图14