uniapp.uploadFile封装并解决移动端上传时header头时间戳丢失问题

使用HMAC-SHA256进行文件上传的安全实现
这段代码展示了如何使用HMAC-SHA256算法结合时间戳生成安全令牌,用于在uni-app中进行文件上传。通过设置HTTP头,确保了上传过程的安全性,并提供了上传文件的基本配置选项。

话不多说,上代码

function uploadFile(obj) {
	var time = new Date().getTime();
	var secret = Base64.stringify(hmacSHA256(token + "" + time, "0Q4icRHjJY2BRdTJdJrCFixD6OMhhd01"));
	var url = obj.url; //服务器地址
	var filePath = obj.filePath; //要上传文件资源的路径。
	var formData = obj.formData;
	var success = obj.success;
	var name = obj.name; //文件对应的 key 
	var method = obj.method || 'POST'; //默认post请求
	uni.uploadFile({
		url: url,
		filePath: filePath,
		name: name,
		formData: formData,
		header: {
			'token': token,
			'time': time.toString(),   //H5端不转没问题,移动端必须转string类型
			'secret': secret
		},
		method: method,
		success: function(res) {
			console.log(obj)
			success(res)
		},
		fail: function(res) {}
	})
}

 

import { isRefreshTokenRequest, refreshToken as postRefreshToken } from '@/api/common' import { useUserInfo } from '@/stores/userInfo' import { getTabbarPages, navigate } from './common' export interface RequestOptions extends UniApp.RequestOptions { showLoading?: boolean // 是否开启 loading 层效果,默认 false showErrorMessage?: boolean // 是否开启接口错误信息展示,默认 true showCodeMessage?: boolean // 是否开启code不为1的信息提示,默认 true showSuccessMessage?: boolean // 是否开启code为1的信息提示,默认 false __isRefreshToken?: boolean [key: string]: any } export interface UploadFileOption extends UniApp.UploadFileOption { showLoading?: boolean showErrorMessage?: boolean showCodeMessage?: boolean showSuccessMessage?: boolean __isRefreshToken?: boolean [key: string]: any } /** * 根据运行环境获取基础请求URL */ export const getBaseUrl = (): string => { return import.meta.env.VITE_REQUEST_BASE_URL } /** * 拦截器(目前已用于网络请求、上传文件) */ export const interceptor = { invoke(options: RequestOptions) { // 按需拼接域名 if (!options.url.startsWith('http')) { options.url = getBaseUrl() + options.url } // header 处理 const userInfo = useUserInfo() options.header = { server: true, 'think-lang': 'zh-cn', 'ba-user-token': userInfo.getToken(), ...options.header, } // loading if (getOptionValue(options, 'showLoading')) { uni.showLoading({ title: '加载中...', }) } }, success(res: UniApp.RequestSuccessCallbackResult, options: RequestOptions) { const data = res.data as ApiResponse if (data && data.code === 1 && getOptionValue(options, 'showSuccessMessage')) { uni.showToast({ title: data.msg ? data.msg : '操作成功', icon: 'none', }) } else if (data && data.code !== 1) { if (data.code == 409) { /** * 409 token 过期,无感刷新 token * 在拦截器中处理不便,封装到了网络请求函数(request)中 */ return } if (getOptionValue(options, 'showCodeMessage')) { uni.showToast({ title: data.msg ? data.msg : '操作失败', icon: 'none', }) } // 跳转至登录页或会员中心 if (data.code == 303) { let newNavUrl = '/pages/user/user' let newNavQuery = '' // 需要登录 if (data.data.type == 'need login') { newNavUrl = '/pages/user/login' const pages = getCurrentPages() const lastPage: any = pages[pages.length - 1] newNavQuery = `?navUrl=${encodeURIComponent(lastPage.$page.fullPath)}` } setTimeout(() => { const newNavType: NavigateType = getNavigateType(newNavUrl) navigate(newNavType, { url: newNavUrl + newNavQuery }) }, 1500) } } }, complete(res: UniApp.RequestSuccessCallbackResult, options: RequestOptions) { if (getOptionValue(options, 'showLoading')) { uni.hideLoading() } // 错误提示 if (getOptionValue(options, 'showErrorMessage') && res.statusCode != 200) { httpErrorStatusHandle(res) } }, } /** * 网络请求 */ const request = <Data = any, T extends ApiResponse = ApiResponse<Data>>(options: RequestOptions) => { return new Promise<T>((resolve, reject) => { uni.request<RequestOptions>({ ...options, success: async (res) => { const data = res.data as T if (data && data.code === 1) { resolve(data) } else if (data.code === 409 && !isRefreshTokenRequest(options)) { await refreshToken() request(options) .then((res) => resolve(res as T)) .catch((err) => reject(err)) } else { reject(data) } }, fail: (err) => { reject(err) }, }) }) } /** * 文件上传请求 */ export const uploadFile = <Data = any, T extends ApiResponse = ApiResponse<Data>>(options: UploadFileOption) => { return new Promise<T>((resolve, reject) => { uni.uploadFile<UploadFileOption>({ ...options, success: async (res) => { const response = JSON.parse(res.data) as T if (response.code == 1) { resolve(response) } else if (response.code === 409 && !isRefreshTokenRequest(options)) { await refreshToken() uploadFile(options) .then((res) => resolve(res as T)) .catch((err) => reject(err)) } else { reject(response) } }, fail: (err) => { reject(err) }, }) }) } // 参数二的 complete 类型定义不全,断言为 any uni.addInterceptor('request', interceptor as any) uni.addInterceptor('uploadFile', interceptor as any) /** * 处理异常 */ function httpErrorStatusHandle(error: UniApp.RequestSuccessCallbackResult) { let message = '未知错误' if (error) { if (error.statusCode) { switch (error.statusCode) { case 302: message = '接口重定向了!' break case 400: message = '参数不正确!' break case 401: message = '您没有权限操作!' break case 403: message = '您没有权限操作!' break case 404: message = '请求地址出错!' break case 408: message = '请求超!' break case 409: message = '系统已存在相同数据!' break case 500: message = '服务器内部错误!' break case 501: message = '服务未实现!' break case 502: message = '网关错误!' break case 503: message = '服务不可用!' break case 504: message = '服务暂无法访问,请稍后再试!' break case 505: message = 'HTTP版本不受支持!' break default: message = '异常问题,请联系网站管理员!' break } } if (error.errMsg) { if (error.errMsg.includes('timeout')) { message = '请求超!' } message = error.errMsg } } uni.showModal({ title: '提示', content: message, showCancel: false, }) } /** * 获取请求配置值(无值返回默认值) */ export const getOptionValue = (options: RequestOptions, name: 'showLoading' | 'showErrorMessage' | 'showCodeMessage' | 'showSuccessMessage') => { const customOptions = { showLoading: false, showErrorMessage: true, showCodeMessage: true, showSuccessMessage: false, } return options[name] ?? customOptions[name] } const getNavigateType = (path: string) => { return getTabbarPages().includes(path) ? 'switchTab' : 'redirectTo' } let refreshTokenPromise: Promise<void> | null = null /** * 获取唯一化的刷新 token promise * PS:多个请求,若刷新 token 操作刚完成,立即又收到之前已经发出的会响应 409 的请求,则可能会重复刷新,不影响业务,介意可考虑请求前拦截等 */ const refreshToken = () => { if (refreshTokenPromise) { return refreshTokenPromise } const userInfo = useUserInfo() refreshTokenPromise = new Promise((resolve, reject) => { postRefreshToken() .then((res) => { if (res.code == 1 && res.data.type == 'user-refresh') { userInfo.setToken(res.data.token, 'auth') resolve() } else { reject(res) } }) .catch((err) => { userInfo.removeToken() uni.redirectTo({ url: '/pages/user/login', }) reject(err) }) }) refreshTokenPromise.finally(() => { refreshTokenPromise = null }) return refreshTokenPromise } export default request 这段代码存在问题
09-05
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值