您现在的位置是:首页 >技术交流 >Tauri应用开发(四):前后端通信网站首页技术交流

Tauri应用开发(四):前后端通信

勇敢牛牛_ 2024-06-17 11:27:01
简介Tauri应用开发(四):前后端通信

tauri作为客户端应用,使用rust作为后端,我们可以使用rust来编写后端功能。然而rust的学习成本相对来说还是比较高的,并且我们希望不是所有的功能都在客户端实现,所以我们还是需要和后端程序(Java、Node应用等)进行通信。

我们可以使用以下3种方式进行通信:

  • 使用ajax或者axios
  • 使用tuari提供的API(基于rust)
  • 使用rust自己封装

本文使用第二种方式,因为对于客户端应用,访问后端服务时,如果使用axios存在跨域问题,如果在后端不允许跨域或者不方便配置跨域时,tuari提供的API便可以很好的解决这个问题。

1. 配置后端接口名单

我们在allowlist配置项中添加http相关配置:

 "http": {
   "all": true,
   "request": true,
   "scope": [
     "https://your_host:your_port/*"
   ]
 }

其中your_hostyour_port为后端接口地址和端口。

需要注意的是,后端服务需要https协议才能在生产环境(即打包后)访问,开发环境http和https都可以。

2. 使用Tauri提供的API调用后端接口

官方文档:https://tauri.app/v1/api/js/http

具体使用方式官方文档中已经提供了详细的说明和示例,这里提供一个“代码盒子”中使用到的请求封装工具,如有需要,可自行修改后使用。

import {fetch, Body, ResponseType} from '@tauri-apps/api/http';
import mime from 'mime';
import qs from 'qs';
import {ElLoading, ElMessage, ElMessageBox, ElNotification} from 'element-plus'
import {U} from "./util";
import store from "@/store";
import router from "../router/index.js";

const DefaultParam = {loading: false, repeatable: false};

const API = import.meta.env.VITE_API

export const R = {
  PREFIX: API,
  requestingApi: new Set(),
  target: {
    userApiPrefix: '/api/auth',
  },
  getDict(code, showAll, u) {
    let url = u ? u + '/common/dic' : '/common/dic';
    return this.get(url, {
      code: code,
      showAll: (showAll === null || showAll === undefined)
    });
  },
  extractUrl: function (url) {
    return url ? url.split('?')[0] : '';
  },
  isRequesting: function (url) {
    let api = this.extractUrl(url);
    return this.requestingApi.has(api);
  },
  addRequest: function (url) {
    let api = this.extractUrl(url);
    this.requestingApi.add(api);
  },
  deleteRequest: function (url) {
    let api = this.extractUrl(url);
    this.requestingApi.delete(api);
  },
  get: function (url, param, extendParam) {
    let params = {
      url,
      method: 'GET'
    };
    if (param) {
      //tauri的query只支持string参数作为value
      for (const k in param) {
        param[k] = '' + param[k]
      }
      params.query = param;
    }
    return this.ajax(params, extendParam);
  },
  post: function (url, param, extendParam) {
    const params = {
      url,
      method: 'POST',
      formData: param
    };
    //if (param) params.data = qs.stringify(param);
    return this.ajax(params, extendParam);
  },
  postJson: function (url, paramJson, extendParam) {
    return this.ajax({
      url,
      method: 'POST',
      data: paramJson
    }, extendParam);
  },
  patchJson: function (url, paramJson, dataType, extendParam) {
    return this.ajax({
      url,
      method: 'PATCH',
      data: paramJson
    }, extendParam);
  },
  delete: function (url, paramJson, extendParam) {
    return this.ajax({
      url: url,
      method: 'DELETE',
      data: paramJson
    }, extendParam);
  },
  upload(url, filePath,paramJson) {
    const params = {
      url,
      method: 'POST',
      formData: {
        file: {
          file: filePath
        },
        ...(paramJson || {})
      },

    };
    return this.ajax(params, {});
  },
  ajax: function (param, extendParam) {
    let params = this.extend({}, DefaultParam, param, extendParam || {});
    params.crossDomain = params.url.indexOf('http') === 0;
    let url = params.url;
    if (!params.crossDomain) {
      url = params.url = this.PREFIX + params.url;
    }

    if (params.method !== 'GET') {
      if (this.isRequesting(url)) {
        return new Promise((resolve, reject) => {
          resolve({ok: false, msg: 'Duplicate request'});
        });
      }
      if (params.repeatable === false && !((extendParam || {}).ignoreRepeatable)) {
        this.addRequest(url);
      }
    }
    let header = {
      Authorization: 'Bearer ' + (store.state.user.token || localStorage.getItem('token') || '')
    };
    let defaultParam = {
      headers: header,
      responseType: ResponseType.JSON,
    };
    if (params.data) {
      params.body = Body.json(params.data)
    } else if (params.formData) {
      header['Content-Type'] = "multipart/form-data"
      const formData = {}
      for (let k in params.formData) {
        if(k==='file'){
          const v = params.formData[k]
          if (typeof (v) === 'string') {
            formData[k] = v
          } else if (typeof (v) === 'object') {
            const filePath = v.file
            const type = mime.getType(filePath)
            let idx = filePath.lastIndexOf('/')
            if (idx === -1) {
              idx = filePath.lastIndexOf('\')
            }
            const fileName = filePath.substring(idx + 1)
            v['mime'] = type
            v['fileName'] = fileName
          }
        }
      }
      params.body = Body.form(params.formData)
    }
    if (params.crossDomain) {
      defaultParam.headers = {};
    }
    let that = this;
    if (params.loading)
      ElLoading.service({text: "Loading..."});

    params = this.extend({}, defaultParam, params);

    console.debug('请求参数:', params)
    return new Promise((resolve) => {
      return fetch(url, params).then((response) => {
        console.debug('响应参数:', response)
        that.deleteRequest(params.url);
        let data = response.data;
        let status = response.status;
        if (status > 300) {
          let errMsg = `${status} ${response.statusText} : ${JSON.stringify(data)}`;
          if (status === 302) {
            window.location.href = response.header.location;
          } else if (status === 404) {
            //errMsg = `${status} ${response.statusText} : ${url}`
            router.push('/404')
          } else if (status === 401) {
            store.commit('user/resetToken')
            PubSub.publish('NEED_LOGIN')
            //errMsg = `${status} 认证失败:${data.msg}`
            throw new Error(errMsg);
          } else if (status === 403) {
            errMsg = `${status} 未授权访问:${data.msg}`
          } else if (status === 429) {
            errMsg = `${status} 访问过于频繁,请稍后再试`
          } else if (status === 503) {
            errMsg = (`${status} 服务暂不可用`)
          } else if (status === 502) {
            errMsg = (`${status} 系统升级中`)
          } else if (data && data.code === 998) {
            errMsg = (`${data.msg}${params.url}`)
          }
          ElNotification.error({
            title: '请求异常',
            message: errMsg,
            customClass: 'notification',
            offset:50,
          });
          throw new Error(errMsg);
        } else {
          //服务端统一返回码
          if (data.code === 6) {
            ElMessage.error(`${data.msg}`);
            throw Error(`${data.msg}`);
          }else if(data.code === 601){
            router.push('/404')
            return
          } else if (data.code) {
            ElMessage.error(`${data.msg}`);
            throw Error(`${data.msg}`);
          }

          if (typeof data == 'object') {
            data.ok = true;
          } else {
            data = {};
            data.ok = true;
          }
        }
        resolve(data);
      }).catch((err) => {
        console.error(err)
        that.deleteRequest(params.url);
        if (params.loading) {
          ElLoading.service().close();
        }
        resolve({
          ok: false,
          msg: err
        });
      });
    });
  },
  extend() {
    let options, name, src, copy, copyIsArray, clone,
      target = arguments[0] || {},
      i = 1,
      length = arguments.length,
      deep = false;

    if (typeof target === "boolean") {
      deep = target;
      target = arguments[1] || {};

      i = 2;
    }
    if (typeof target !== "object" && !jQuery.isFunction(target)) {
      target = {};
    }
    if (length === i) {
      target = this;

      --i;
    }
    for (; i < length; i++) {
      if ((options = arguments[i]) != null) {
        for (name in options) {
          src = target[name];
          copy = options[name];

          if (target === copy) {
            continue;
          }

          if (deep && copy && (U.isPlainObject(copy) || (copyIsArray = U.isArray(copy)))) {
            if (copyIsArray) {
              copyIsArray = false;
              clone = src && U.isArray(src) ? src : [];
            } else {
              clone = src && U.isPlainObject(src) ? src : {};
            }

            target[name] = this.extend(deep, clone, copy);

          } else if (copy !== undefined) {
            target[name] = copy;
          }
        }
      }
    }
    return target;
  },
};


3.注意点:

  • query查询参数只支持key/value格式,其中value只支持字符串,如果传入数字或者其他类型将会报错。
  • 由tauri发出的请求,在调式工具的network中是看不到的,所以,可以通过log的形式,将请求参数/响应参数以及地址信息输出到console,方便查看。
  • 文件上传仅支持路径作为参数或者Uint8Array类型。文件上传示例请参照上方封装的工具中的upload方法。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。