您现在的位置是:首页 >技术交流 >Tauri应用开发(四):前后端通信网站首页技术交流
Tauri应用开发(四):前后端通信
简介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_host
和your_port
为后端接口地址和端口。
需要注意的是,后端服务需要
https
协议才能在生产环境(即打包后)访问,开发环境http和https都可以。
2. 使用Tauri提供的API调用后端接口
具体使用方式官方文档中已经提供了详细的说明和示例,这里提供一个“代码盒子”中使用到的请求封装工具,如有需要,可自行修改后使用。
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
方法。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。