首发于哈德韦
前后端分离开发中前端需要克服的挑战

前后端分离开发中前端需要克服的挑战

背景:

网页前后端分离开发,好处是关注点分离,以及并行开发提高效率;原生应用的前端和后端天生分离,原生应用开发在对接后端接口上需要克服比网页前端更多的挑战。

实际工作中碰到的问题:

  • 并行不起来,前端依赖接口,在接口没好时,就只能等着。
  • 要么,前端先写死一些数据开发页面,接口好了后,对接起来要修改大量代码,效率低下。
  • 以及在后端发布之前,前端要修改大量代码去调用一个临时的终端节点,网页前端需要克服跨域问题,而原生应用往往需要解决自颁发证书的信任问题。

解决方案:

对于网页前端引入两种开发模式,而且通过良好的配置,使得在这两种开发模式可以自由切换,不用更改任何代码。

  • 第一步:mock 模式下开发( npm run mock ):在接口没好时,使用 mock 模式开发。注意,mock 与直接页面写死数据不同,mock 是模拟接口返回的数据,至于请求接口这些代码仍然要写,而且当接口好了之后,切换到请求真实接口,不需要改动任何代码
  • 第二步:代理模式下开发( npm run proxy ):在接口完成后,使用 代理 模式开发。注意,代理模式是从本地直接请求真实接口,这与运行时的请求真实接口不同,因为运行时的域名不会是 localhost。

项目发布后,是运行模式,这与以上两种模式都是有区别的。

umi 网页项目示例:

以 umi 项目为例,在 package.json 里加上两个 scripts :

{
  ...
  "scripts": {
    "mock": "umi dev",
    "proxy": "UMI_ENV=dev MOCK=none umi dev"
  }
  ...
}

可以看出, umi 项目自动开启 mock 开发模式,具体可以参考 umi 项目的 mock 配置。对于其他非 umi 项目,也可以自行配置。

而对于 proxy 模式,则需要本地通过 localhost 服务器代码去访问真实接口给前端用,这样才不会有跨域等问题。这个一般 webpack 项目都可以很简单地配置出来,如下:

proxy: {
    '/api': {
    target: 'https://your.domain.com/api',
    changeOrigin: true,
    pathRewrite: {'^/api': ''}
  }
}

前端请求接口的代码如下:

fetch(`/api/xxx`, ...)

注意,通过以上配置后,无论是 mock 开发模式,还是 proxy 模式,还是运行时模式,前端请求接口的代码都不用变。

如果涉及到接口需要用户登录的场景,那么以上 proxy 配置改成这样即可:

proxy: {
    '/api': {
    target: 'https://your.domain.com/api',
    changeOrigin: true,
    pathRewrite: {'^/api': ''},
    withCredentials: true,
    headers: { Cookie: 'YourToken=9fk_i8oJE5xoqSvi4DS9RhDZARMskBLc-pD0oHDPK0r' },
  }
}

非 umi webpack 网页项目示例:

只要引入 devServer 插件,在 setup 配置里配置好 mock 返回的数据,再通过一个 proxy 来实现在 mock 模式下向 mock 接口发起请求即可:

  devServer: {
    setup: app => {
      app.get('/api/*', (req, res) => {
        setTimeout(() => {
          res.json({ custom: 'mock response' });
        }, 3000);
      });
    },
    proxy: process.env.MOCK ? {
      '/api': {
        target: 'http://localhost:3333/api',
        changeOrigin: true
      }
    }/* mock 模式 */ : {
      '/api': {
        target: 'https://dev.example.com',
        changeOrigin: true,
        withCredentials: true,
        headers: {
          Cookie: ''
        }
      }
    } /* dev 模式 */
  }

安卓项目示例:

以上的 your.domain.com/api 终端节点,在开发过程中可能是临时的 url,并且其 https 证书往往是自颁发的证书,对于网页前端,通过恰当的 proxy 配置加上相应的浏览器设置都不会碰到问题,但是对于原生应用,就需要克服这个证书信任问题,可以通过一段比较丑的代码去强行信任所有的连接(只在开发时启用),比如对于安卓应用,可以使用下面这段代码:

// CustomTrust.java

package com.yourname.builder;

import org.jetbrains.annotations.NotNull;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.OkHttpClient;

public class CustomTrust {
    final static TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[] {};
        }
    }};

    public static OkHttpClient getUnsafeOkHttpClient() {
        try {
            // Create a trust manager that does not validate certificate chains

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
            builder.hostnameVerifier(getHostnameVerifier());

            OkHttpClient okHttpClient = builder.build();
            return okHttpClient;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @NotNull
    public static HostnameVerifier getHostnameVerifier() {
        return new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
    }
}

在正常获取到 Http 客户端后,利用上述的 CustomTrust 改写其信任设置:

import okhttp3.OkHttpClient
import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager

...
val okHttpClientBuilder = OkHttpClient.Builder()
trustAll(okHttpClientBuilder)
...

private fun trustAll(okHttpClientBuilder: OkHttpClient.Builder) {
    val sslContext = SSLContext.getInstance("SSL")
    sslContext.init(null, CustomTrust.trustAllCerts, java.security.SecureRandom())
    okHttpClientBuilder.sslSocketFactory(sslContext.socketFactory, CustomTrust.trustAllCerts[0] as X509TrustManager)
    okHttpClientBuilder.hostnameVerifier(CustomTrust.getHostnameVerifier())
}

总结:

本文针对前后端并行开发时碰到的典型问题进行了总结,并给出了可行的改进方案。这个可行是指作者亲测有效,还对于网页前端和安卓前端给出了关键代码示例。

编辑于 2022-07-08 13:44