rxd-wf-api
Version:
WF API Client- WeForward API请求封装
1,271 lines (1,216 loc) • 43.3 kB
JavaScript
/**
* 该包装插件基于axios包装,用于请求Weforward网关数据
* 主要功能:简化业务的请求参数和配置,提供统一的登录和退出登录方法,实现简单的负载均衡
*/
import axios from 'axios';
import extend from './es6extend.js'
import cookie from './cookie.js'
import sha256 from './sha256.js';
import storage from './storage.js'
import hexToBase64 from './hexToBase64.js';
import refreshPlugin from'./getData.js'
refreshPlugin.getPluginData()
/**
* weforward配置
* 如:
* window._WEFORWARD_CONFIG={
* "hosts": [
* "//lgateway.navboy.com"
* ]
* };
*/
//通道允许上传的单个文件大小
const ALLOWSINGLEFILESIZE = 1024 * 1024 * 3;
//异常监听事件
const EVENTS = {};
//业务层全局参数
const GLOBAL = {};
//全局配置参数
const GLOBALCONFIG = {};
//服务tag
const SERVICE_TAGS = storage.tem.getObj('__WF_SERVICE_TAGS') || {};
//开发环境服务名映射
const SERVICENAMEMAP = {};
//环境服务名版本
const SERVICE_VERSIONS = {};
//自定义事件
const CUSTOMEVENTS = [];
//全局配置
const NOAUTHDEFCONFIG = {
withoutaccessid: true,
resDataMode: 0,
resDataAttrNameStyle: 1,
reqDataAttrNameStyle: 1
};
//记录失败的接口调用信息
const API_FAIL_RECORD = {};
//鉴权链接
let baseURL = '';
//默认服务名
let baseServiceName = '';
//域名列表
let baseUrls = [];
//当前域名
let baseUrl = '';
//允许重试最大次数
let _maxretrycount = 1;
//成功回调时是否轮流切换url
let ISRECYCLEURLS = false;
//默认1,0:不转换,1、下划线转驼峰,2、兼容默认,转驼峰的同时保留原属性
let defalutResDataAttrNameStyle = 1;
//默认1,0:不转换驼峰,1、下划线转驼峰
let defalutReqDataAttrNameStyle = 1;
//默认0,0:纯业务数据,1:包含code等信息的包装数据(需要登录或者无访问权限时不受该参数控制)
let defalutResDataMode = 0;
//请求次数计数器
let requestCounter = 0;
const WF_CONFIG = window._WEFORWARD_CONFIG;
if (WF_CONFIG) {
setBaseHosts.apply(null, WF_CONFIG.hosts);
let developServiceName = WF_CONFIG.serviceName;
if (developServiceName) {
if (developServiceName.match(/,|:/)) {
developServiceName.split(',').forEach(item => {
if (!item) {
return;
}
let kv = item.split(':');
if (kv.length == 1) {
SERVICENAMEMAP[kv[0]] = kv[0];
return;
}
SERVICENAMEMAP[kv[0]] = kv[1];
});
developServiceName = '';
}
setBaseService.apply(null, [developServiceName]);
}
} else {
let developServiceName
//插件实例在vite环境获取不到环境变量,请在插件上调用setBaseHosts方法
if (process.env) {
setBaseHosts.apply(null, exchangeHostToHosts(process.env.VUE_APP_WF_HOST))
developServiceName = process.env.VUE_APP_DEV_SERVICENAME
}
if (developServiceName) {
if (developServiceName.match(/,|:/)) {
developServiceName.split(',').forEach(item => {
if (!item) {
return;
}
let kv = item.split(':');
if (kv.length == 1) {
SERVICENAMEMAP[kv[0]] = kv[0];
return;
}
SERVICENAMEMAP[kv[0]] = kv[1];
});
developServiceName = '';
}
setBaseService.apply(null, [developServiceName]);
}
}
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8';
/**
* 配置服务请求域名(请求服务的urls)
* @param {String} urls 支持多个参数url用于负载均衡
*/
function setBaseHosts(...urls) {
let _urls = [];
for (let url of urls) {
if (!url) {
continue;
}
checkHost(url, _urls);
_urls.push(url);
}
baseUrls = _urls;
if (baseUrls.length > 0) {
baseUrl = _urls[0];
}
}
/**
* 配置基础服务名称
* @param {Object} serviceName 服务名称,至少一位及以上,数字,字母,下划线
*/
function setBaseService(serviceName) {
checkServiceName(serviceName);
baseServiceName = serviceName;
}
/**
* 获取链接列表
*/
function getBaseUrls() {
return baseUrls;
}
/**
* 获取当前链接
*/
function getBaseUrl() {
return localStorage.getItem('WF_API_BASE_URL');
}
/**
* 获取基础服务名
*/
function getBaseService() {
return baseServiceName;
}
/**
* 获取服务对象的WF-TAG
* @param {Object} serviceName 服务名
*/
function getTag(serviceName) {
return SERVICE_TAGS[serviceName] || '';
}
/**
* 设置服务对象的WF-TAG
* @param {Object}serviceName 服务名
*/
function setTag(serviceName, tag = '') {
if (SERVICE_TAGS[serviceName] !== tag) {
SERVICE_TAGS[serviceName] = tag;
storage.tem.setObj('__WF_SERVICE_TAGS', SERVICE_TAGS);
}
return SERVICE_TAGS[serviceName] = tag || '';
}
/**
* 分隔host字符串
* @param {Object} hostdesc 主机描述,多个使用,隔开
*/
function exchangeHostToHosts(hostdesc) {
let _host = (hostdesc || '').split(',');
return _host;
}
/**
* 执行回调
* @param {String} event
* @param {Object} data
*/
function fireEvents(event, data) {
let events = EVENTS[event];
if (events && events.length > 0) {
for (let item of events) {
item(data);
}
}
}
//转换返回的数据值
function exchangeBackDataValues(data) {
if (!data) {
return data;
}
if (typeof data == 'string' && data.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}z$/gi)) {
return new Date(data);
}
if (Array.isArray(data)) {
let array = [];
data.forEach(item => {
array.push(exchangeBackDataValues(item));
});
return array;
}
if (!isPlainObject(data)) {
return data;
}
let _data = {};
for (let key in data) {
let oldData = data[key];
_data[key] = exchangeBackDataValues(data[key]);
}
return _data;
}
//转换返回的数据
function exchangeBackData(data, mode) {
//数据转换
data = exchangeBackDataValues(data);
let globalMode = defalutResDataAttrNameStyle;
let _mode = globalMode;
if (mode >= 0 && mode <= 2) {
_mode = mode;
}
return exchangeObjAttrNameLineToHump(data, _mode);
}
/**
* 自定义处理全局异常响应码事件
* @param {Object} eventName 事件名
* @param {Object} matchedHandler 匹配函数,函数接受一个参数(错误码),用于匹配,返回的结果应为true或false
*/
function customResErrorEvent(eventName, matchedHandler) {
CUSTOMEVENTS.push({
event: eventName,
handler: matchedHandler
});
}
/**协议前缀*/
function getProtocolPre(str = '') {
return ((GLOBALCONFIG.protocolPre || 'WF') + '').toLowerCase() + str;
}
/**协议头前缀*/
function getProtocolHeaderPre(str = '') {
return getProtocolPre('-').toUpperCase() + str;
}
/**响应前缀*/
function getProtocolResParams(str = '') {
return getProtocolPre('_') + str;
}
/**
* 循环使用baseUrl
* @param {Object} baseUrl
*/
function recycleUrl(baseUrl) {
let urls = getBaseUrls();
//有1一个以上的url时有效
if (urls.length < 2) {
return baseUrl;
}
let curindex = urls.indexOf(baseUrl);
let nextIndex = curindex + 1;
if (nextIndex >= urls.length) {
//一轮完成,从第一个开始
nextIndex = 0;
}
let newurl = urls[nextIndex];
saveBaseUrl(newurl);
return newurl;
}
/**
* 更新默认的基础浩云链接
*/
function updateBaseUrl(baseUrl) {
if (!baseUrl) {
return;
}
API_FAIL_RECORD[baseUrl] = Date.now();
let mintime = 0;
let fiturl = null;
let urls = getBaseUrls();
for (let url of urls) {
let recordtime = API_FAIL_RECORD[url] || 0;
if (!fiturl || mintime > recordtime) {
fiturl = url;
mintime = recordtime;
}
}
//距离最旧切换的链接的时间较短时,基本说明全部链接都有问题,暂不切换
if (Date.now() - mintime < 10 * 1000) {
return;
}
saveBaseUrl(fiturl);
return fiturl;
}
function saveBaseUrl(url) {
baseUrl = url;
}
/**
* @param {Object} obj是否文件类型对象(包括Blob和File类型)
*/
function isTypeFile(obj) {
return isObjectTypeOf(obj, 'File') || isObjectTypeOf(obj, 'Blob');
}
/**
* 是否文件集合
* @param {Object} obj
*/
function isTypeFiles(obj) {
return isObjectTypeOf(obj, 'FileList');
}
/**
* 是否为指定类型的对象
* @param {Object} obj
* @param {Object} type
*/
function isObjectTypeOf(obj, type) {
return null !== obj && typeof obj == 'object' && obj.constructor.name == type;
}
/**
* @param {Object} obj 是否纯数据对象
*/
function isPlainObject(obj) {
return Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && !obj.length;
}
/**
*
* @param {Object} data 按照递归的方式
* 对属性值进行转换,File对象转base64字串,Date对象转ISO字串
*/
function exchangeFormValues(data) {
if (data instanceof Date) {
return data.toISOString();
} else if (isPlainObject(data)) {
for (let key in data) {
let ret = exchangeFormValues(data[key]);
data[key] = ret;
}
} else if (Array.isArray(data)) {
for (let i = 0; i < data.length; i++) {
let ret = exchangeFormValues(data[i]);
data[i] = ret;
}
} else if (isTypeFiles(data)) {
let list = [];
for (let file of data) {
let ret = exchangeFormValues(file);
list.push(ret);
}
return list;
}
return data;
}
//驼峰转下划线
function humpToLine(name) {
if (!name) {
return name;
}
return name.replace(/([A-Z])/g, "_$1").toLowerCase();
}
//对象的属性名由驼峰转下划线
function exchangeObjAttrNameHumpToLine(params) {
if (Array.isArray(params)) {
let array = [];
params.forEach(item => {
array.push(exchangeObjAttrNameHumpToLine(item));
});
return array;
}
if (!isPlainObject(params)) {
return params;
}
let _params = {};
for (let key in params) {
let _key = humpToLine(key);
_params[_key] = exchangeObjAttrNameHumpToLine(params[key]);
}
return _params;
}
//下划线转驼峰
function lineToHub(name) {
if (!name) {
return name;
}
return name.replace(/\_([a-z])/g, (all, letter) => {
return letter.toUpperCase();
});
}
//对象的属性名由下划线转驼峰
function exchangeObjAttrNameLineToHump(params, mode) {
if (!mode) {
return params;
}
if (Array.isArray(params)) {
let array = [];
params.forEach(item => {
array.push(exchangeObjAttrNameLineToHump(item, mode));
});
return array;
}
if (!isPlainObject(params)) {
return params;
}
let _params = {};
for (let key in params) {
let _key = lineToHub(key);
let oldData = params[key];
_params[_key] = exchangeObjAttrNameLineToHump(params[key], mode);
if (key == 2) {
//兼容模式,保留原属性
_params[key] = oldData;
}
}
return _params;
}
function checkHost(url, existurls) {
var reg = new RegExp(/^((\w+):)*\/\/([^/:]+)(:\d*)?(\/)*$/, 'g')
if (!url || !url.match(reg)) {
throw new Error('配置的域名:\n' + url + '\n无效,\n\n正确的域名格式示例:\nhttps://abc.cn\nhttps://abc.cn:8081\n//abc.cn:8088');
}
if (existurls.indexOf(url) != -1) {
throw new Error('不能配置重复的域名:' + url);
}
}
function checkServiceName(serviceName) {
if (!serviceName) {
return;
}
if (!serviceName.match(/^[\d|a-z|_]{1,}$/i)) {
throw new Error('服务名称匹配字符为数字、字母、下划线');
}
}
function removeLoginInfo() {
cookie.remove('_WF_ACCESSID', '/');
cookie.remove('_WF_ACCESSKEY', '/');
cookie.remove('_WF_ACCESSEXPIRE', '/');
cookie.remove('omni-ssss', '/');
storage.tem.remove('__WF_LAST_LOGIN_TIME');
storage.tem.remove('__WF_LAST_FIRE_OAUTH_TIME');
}
function onlogined(data, resove) {
console.log(data, '存储的')
cookie.set('_WF_ACCESSID', data.accessId, 0, '/');
cookie.set('_WF_ACCESSKEY', data.accessKey, 0, '/');
cookie.set('_WF_ACCESSEXPIRE', data.accessExpire, 0, '/');
localStorage.setItem('WF_ACCESS_ID', data.accessId)
localStorage.setItem('WF_ACCESS_KEY', data.accessKey)
localStorage.setItem('WF_ACCESS_EXPIRE', data.accessExpire)
if (data.sessionId) {
cookie.set('omni-ssss', data.sessionId, 0, '/');
}
if (typeof resove == 'function') {
resove(data);
}
}
function fixAccessKeyToBase64(key) {
if (!key) {
return '';
}
if (key.length == 64) {
//64长度的为16进制的
return hexToBase64(key);
} else {
//其他的当做base64格式的
return key;
}
}
//刷新access
function refreshAccess() {
// const access_id = instance.getAccessId();
// const access_key = instance.getAccessKey();
const access_id = localStorage.getItem('WF_ACCESS_ID');
const access_key = localStorage.getItem('WF_ACCESS_KEY');
// let url = GLOBALCONFIG['refreshAccessUrl'] || 'jscf_access_service?method=us/refresh_access_one';
let url = 'jscf_access_service?method=us/refresh_access_one';
return new Promise((resolve, reject) => {
post(url, {
access_id: access_id,
access_key: access_key,
}).then(data => {
const paramsData = {
accessId: data.accessId,
accessKey: data.accessKey,
accessExpire: data.accessExpire
}
//刷新成功
onlogined(paramsData);
resolve(data)
}).catch(e => {
//刷新失败,删掉过期时间
cookie.remove('_WF_ACCESSEXPIRE', '/');
reject('access刷新失败')
});
});
}
//定时器,检查检查是否需要更新access
setInterval(function () {
// let expire = cookie.get('_WF_ACCESSEXPIRE');
let expire = localStorage.getItem('WF_ACCESS_EXPIRE');
if (!expire) {
return;
}
if (Number(expire) - Date.now() < 5 * 60 * 1000) {
//当有效期小于5分钟时刷新
// refreshAccess();
refreshPlugin.getPluginData()
}
}, 2 * 60 * 1000);
/**
* 浩云服务数据请求接口(如果包含图片文件,一律转为base64字串)
* @param {String} url 由域名(如果配置了baseUrl,域名可以省略)+服务名+'?'+参数键值对 例如:'http:192.168.0.44:6562/ias?method=accountinfo'
* @param {Object} params {"param1": "value1",File:file,blob:Blod,fileList:FileList}
* @param {Object} config 其他的配置项,浩云的常用配置放在config.wfconfig下,
* wfconfig的常用属性:
* resDataAttrNameStyle:Number类型,用于配置响应数据属性名的风格, 不指定时默认读取全局配置,指定时可选值有:0表示不转换,1表示下划线转驼峰,2、表示兼容默认,即转驼峰模式的同时保留原属性】
* resDataMode:Number类型,不指定时默认读取全局配置,0,纯业务数据,1、包含code等信息的包装数据,注意:需要登录或者无访问权限时返回数据不受该参数控制,2,返回原始请求信息
* reqDataAttrNameStyle:Number类型,用于配置请求数据属性名的风格,不指定时默认读取全局配置,0表示不作处理,1表示下划线装驼峰
*/
function post(url, params, config) {
//请求数据前的监听事件触发
fireEvents('beforerequest');
let _config = extend(true, {}, config);
let headers = extend(true, {}, _config.headers);
//浩云层的配置
let wfconfig = _config.wfconfig = extend(true, {
url: url,
//网关域名
baseURL: getBaseUrl(),
//返回数据模式
resDataMode: -1,
//返回数据属性名风格
resDataAttrNameStyle: -1,
//请输入数据属性名风格
reqDataAttrNameStyle: -1,
//自定义头
headers: headers,
//网关请求参数,可选
wfReq: {
resId: '',
traceId: '',
tenatId: '',
ver: '',
waitTimeout: -1
},
// accessId: instance.getAccessId(),
// accessKey: instance.getAccessKey(),
accessId: localStorage.getItem('WF_ACCESS_ID'),
accessKey: localStorage.getItem('WF_ACCESS_KEY'),
}, _config.wfconfig);
_config.baseURL = wfconfig.baseURL;
if (!_config.baseURL) {
throw new Error('参数异常:未指定网关域名');
}
let accessId = wfconfig.accessId;
let accessKey = fixAccessKeyToBase64(wfconfig.accessKey);
//值转换
let _params = exchangeFormValues(extend(true, {}, params));
//全局参数
_params._global = extend(true, {}, GLOBAL, _params._global);
let _global = exchangeFormValues(_params._global);
_params._global = _global;
let splitIndex = url.indexOf('?');
let baseService = getBaseService();
if (SERVICENAMEMAP && SERVICENAMEMAP[baseService]) {
baseService = SERVICENAMEMAP[baseService];
}
let _url = baseService;
let wfextparams = '';
let _pms = {
method: ''
};
if (-1 !== splitIndex) {
_url = url.substring(0, splitIndex) || baseService;
if (SERVICENAMEMAP && SERVICENAMEMAP[_url]) {
_url = SERVICENAMEMAP[_url];
}
wfextparams = url.substring(splitIndex + 1, url.length);
for (let item of wfextparams.split('&')) {
let keyvalue = item.split('=');
_pms[keyvalue[0]] = keyvalue[1] || '';
}
}
let serviceName = _url;
if (!serviceName) {
throw new Error('参数异常:未指定请求服务名');
}
if (_pms.method) {
_pms.method = humpToLine(_pms.method);
} else {
throw new Error('参数异常:未指定请求方法名');
}
let wfReq = {
//微服务版本
ver: SERVICE_VERSIONS[serviceName] || '',
traceId: '',
resId: '',
waitTimeout: -1
};
if (wfconfig.wfReq) {
wfReq = extend(true, wfReq, wfconfig.wfReq);
}
wfReq = exchangeFormValues(wfReq);
let waitTimeout = wfReq['waitTimeout'];
if (typeof waitTimeout != 'number' || waitTimeout <= 0) {
waitTimeout = GLOBALCONFIG['waitTimeout'] || 0;
}
if (typeof waitTimeout == 'number' && waitTimeout > 0) {
wfReq['waitTimeout'] = waitTimeout;
_config.timeout = waitTimeout * 1000;
} else {
delete wfReq['waitTimeout'];
}
let wfReqName = getProtocolPre('Req');
_params = {
[wfReqName]: wfReq,
invoke: extend(_pms, {
//业务层参数
params: _params,
})
};
//属性名转换
let reqDataAttrNameStyle = wfconfig.reqDataAttrNameStyle;
if (reqDataAttrNameStyle == -1) {
reqDataAttrNameStyle = defalutReqDataAttrNameStyle;
}
if (reqDataAttrNameStyle === 1) {
//请求参数转驼峰模式
_params = exchangeObjAttrNameHumpToLine(_params);
}
let wftag = headers[getProtocolHeaderPre('Tag')] || getTag(serviceName);
headers[getProtocolHeaderPre('Tag')] = wftag;
if (accessId && accessKey && wfconfig.ignoreauth !== true && wfconfig.withoutaccessid != true) {
//唯一字串
let wfnoise = Number(Date.now() + (++requestCounter)).toString(16);
let content = JSON.stringify(_params);
//签名:参数内容+唯一字串+鉴权账号
let wfContentSign = new sha256().update(content).digestBase64();
headers[getProtocolHeaderPre('Content-Sign')] = wfContentSign;
let signDataStr = serviceName + accessId + accessKey + wfnoise + wftag + wfContentSign;
let sign = new sha256().update(signDataStr).digestBase64();
headers['Authorization'] = getProtocolHeaderPre('SHA2') + ' ' + accessId + ':' + sign;
headers[getProtocolHeaderPre('Noise')] = wfnoise;
} else {
headers['Authorization'] = getProtocolHeaderPre('None');
}
_config.headers = headers;
return axios.post(_url, _params, _config);
}
/**
* 无Acesses请求数据,用法同post,默认忽略access获取数据
* @param {Object} url 请求链接
* @param {Object} params 请求参数
* @param {Object} config 请求配置
*/
function noAccessPost(url, params, config) {
let _config = extend(true, {
wfconfig: NOAUTHDEFCONFIG
}, config);
return post(url, params, _config);
}
/**
* 配置全局参数
* @param {String} key 参数名
* @param {String,Number,Boolean} value 参数值
*/
function putGlobalParam(key, value) {
if (value == null || value === undefined) {
value = '';
}
if (typeof key != 'string' || !key) {
throw new Error('key:' + key + '必须为非空字串');
}
if (typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean') {
GLOBAL[key] = value;
} else {
throw new Error('value可选类型为String,Number,Boolean');
}
}
/**
* 获取全局参数值
* @param {String} key
*/
function getGlobalParam(key) {
return GLOBAL[key];
}
/**
* 删除全局参数值
* @param {String} key
*/
function removeGlobalParam(key) {
delete GLOBAL[key];
}
//返回的异常数据统一处理
axios.interceptors.response.use(res => {
let data = res.data;
if (!data) {
return res;
}
//来自weforward的数据
let reqwfconfig = res.config.wfconfig;
if (reqwfconfig) {
let resDataMode = reqwfconfig.resDataMode;
if (resDataMode == -1) {
resDataMode = defalutResDataMode;
}
//返回原始结果
if (resDataMode == 2) {
return res;
}
let serviceName = reqwfconfig.url.split('?')[0] || getBaseService();
if (SERVICENAMEMAP && SERVICENAMEMAP[serviceName]) {
serviceName = SERVICENAMEMAP[serviceName];
}
let wftag = res.headers[getProtocolHeaderPre('Tag').toLowerCase()] || '';
setTag(serviceName, wftag);
if (ISRECYCLEURLS) {
//轮流切换url
recycleUrl(getBaseUrl());
}
let resDataAttrNameStyle = reqwfconfig.resDataAttrNameStyle;
if (resDataAttrNameStyle == -1) {
resDataAttrNameStyle = defalutResDataAttrNameStyle;
}
let loginErrorMsg = GLOBALCONFIG['loginErrorMsg'];
//浩云层异常码处理
let wfresp = data[getProtocolResParams('resp')];
let wfcode = wfresp[getProtocolResParams('code')];
if (wfcode !== 0) {
let errorMsg = wfresp[getProtocolResParams('msg')];
if ([1001, 1002, 1501].indexOf(wfcode) != -1) {
if (wfcode == 1501 && !!reqwfconfig.accessId) {
//有登陆过但是无法调用,提示无访问权限
fireEvents('visitforbidden', data.result);
throw new Error(errorMsg);
}
let isNeedLogin = true;
if ((wfcode == 1001 || wfcode == 1002) && !!reqwfconfig.accessId) {
let logincounter = Number(storage.tem.get('__WF_LAST_LOGIN_TIME') || 0);
//短时间登录过一次了,直接抛错
if (Date.now() - logincounter < 3000) {
isNeedLogin = false;
}
}
if (isNeedLogin) {
//需要验证登录
if (loginErrorMsg) {
errorMsg = loginErrorMsg;
}
removeLoginInfo();
fireEvents('requireauth', errorMsg);
}
}
throw new Error(errorMsg);
}
//上传或者下载业务处理
if (wfresp.res_url) {
return exchangeBackData(data, resDataAttrNameStyle);
}
//业务层异常码处理
let result = data.result;
let code = result.code;
if (code !== 0) {
let haslogininfo = !!reqwfconfig.accessId;
if ([10002, 10003].indexOf(code) != -1) {
let errorMsg = result.msg;
if (code == 10002 && haslogininfo) {
//有登陆过但是无法调用,提示无访问权限
fireEvents('visitforbidden', result);
throw new Error(errorMsg);
}
let isNeedLogin = true;
if (code == 10003 && haslogininfo) {
let logincounter = Number(storage.tem.get('__WF_LAST_LOGIN_TIME') || 0);
//短时间内登录过一次了,直接抛错
if (Date.now() - logincounter < 3000) {
isNeedLogin = false;
}
}
if (isNeedLogin) {
//需要验证登录
if (loginErrorMsg) {
errorMsg = loginErrorMsg;
}
removeLoginInfo();
fireEvents('requireauth', errorMsg);
}
throw new Error(errorMsg);
}
//自定义错误码拦截
for (let item of CUSTOMEVENTS) {
if (item.handler(code)) {
fireEvents(item.event, result);
throw new Error(result.msg);
}
}
if (resDataMode == 1) {
return exchangeBackData(result, resDataAttrNameStyle);
} else {
throw new Error(result.msg);
}
}
let returnret = resDataMode == 1 ? result : result.content;
return exchangeBackData(returnret, resDataAttrNameStyle);
}
//非浩宁云的服务请求直接返回业务数据
return data;
}, err => {
if (err && err.config && err.config.wfconfig && err.request) {
//浩云异常处理,负载均衡实现
let axioscfg = err.config;
let reqcode = err.request.readyState || -1;
let res = err.response;
let rescode = res ? res.status : 0;
let wfcfg = axioscfg.wfconfig;
//开启切换url并且,不是每次请求都指定baseURL的情况下,执行轮流切换url
if (!ISRECYCLEURLS && !wfcfg.baseURL) {
if (rescode >= 500 || (!res && reqcode >= 0)) {
let baseUrl = getBaseUrl();
let newbaseUrl = updateBaseUrl(baseUrl);
if (newbaseUrl) {
wfcfg.retryedcount = wfcfg.retryedcount || 0;
if (wfcfg.retryable === true || wfcfg.retryedcount < _maxretrycount) {
let _reqparams = JSON.parse(axioscfg.data).invoke.params;
let _wfcfg = extend(true, {}, wfcfg);
_wfcfg.retryedcount++;
return post(wfcfg.url, _reqparams, {
wfconfig: _wfcfg
});
}
}
}
}
}
throw err;
});
function WeforwradProtocol() {
}
let instance;
/**
* 对外提供的方法
*/
WeforwradProtocol.prototype = {
/**
* 是否已登录
*/
isLogined() {
return cookie.get('_WF_ACCESSID') && cookie.get('_WF_ACCESSKEY');
},
/**
* 获取鉴权id
*/
getAccessId() {
return cookie.get('_WF_ACCESSID');
},
/**
* 获取鉴权key
*/
getAccessKey() {
return cookie.get('_WF_ACCESSKEY');
},
/**
* 设置浩云数据请求异常(非业务异常,例如:网络链接错误或者,response的status为500,502,503,504之类的)时,单个请求可重试的次数
* 【注意:仅当配置了多个可选的浩云接口域名,并且距离最久没有调用过的域名的时间超过了10秒时才生效】
* @param {Number} count 可选数值0~3,默认1
*/
setMaxRetryCount(count) {
if (typeof count == 'number' && count >= 0 && count <= 3) {
_maxretrycount = count;
} else {
throw new Error('可重试次数值应为为0-3之间(包含)的数字');
}
},
/**
* 配置生产服务请求域名(请求服务的urls)【优先级高于通过wfconfig.json的配置】
* @param {String} urls 支持多个参数url用于负载均衡
*/
setBaseHosts,
/**
* 配置基础服务名称【优先级高于通过wfconfig.json的配置】
* @param {String} serverName
*/
setBaseService,
//原始的axios,可用于普通的数据请求
axios: axios,
/**
*
* @param {Object} data 按照递归的方式
* 对属性值进行转换,File对象转base64字串,Date对象转ISO字串
*/
exchangeFormValues,
/**
* 浩云服务数据请求接口(如果包含图片文件,一律转为base64字串)
* @param {String} url 由域名(如果配置了baseUrl,域名可以省略)+服务名+'?'+参数键值对 例如:'http:192.168.0.44:6562/ias?method=accountinfo'
* @param {Object} params {"param1": "value1",File:file,blob:Blod,fileList:FileList}
* @param {Object} config 其他的配置项,浩云的常用配置放在config.wfconfig下,
* wfconfig的常用属性:
* resDataAttrNameStyle:Number类型,用于配置响应数据属性名的风格, 不指定时默认读取全局配置,指定时可选值有:0表示不转换,1表示下划线转驼峰,2、表示兼容默认,即转驼峰模式的同时保留原属性】
* resDataMode:Number类型,不指定时默认读取全局配置,0,纯业务数据,1、包含code等信息的包装数据,注意:需要登录或者无访问权限时返回数据不受该参数控制,2,返回原始请求信息
* reqDataAttrNameStyle:Number类型,用于配置请求数据属性名的风格,不指定时默认读取全局配置,0表示不作处理,1表示下划线装驼峰
*/
post,
/**
* 无Acesses请求数据,用法同post,默认忽略access获取数据
* @param {Object} url 请求链接
* @param {Object} params 请求参数
* @param {Object} config 请求配置
*/
noAccessPost,
/**
* @param {Object} url 请求上传的微服务所需要url
* @param {Object} params 请求上传的微服务所需要的参数
* @param {Object} file 需要上传文件,file必须为是Blob或File对象,每次只能上传一个
* @param {Object} config,如果配置了wfconfig,resDataAttrNameStyle此处配置不生效,强制驼峰模式
* 关于监听上传进度的配置,请配置wfconfig的onUploadProgress属性:function(e){
}
* {wfconfig:{onUploadProgress:onUploadProgress}}
*
*/
upload(url, params, file, config) {
if (!file) {
throw new Error('参数异常:未指定要上传的文件‘file’');
}
if (!isTypeFile(file)) {
throw new Error('参数异常:file必须为是Blob或File对象');
}
let _config = extend(true, config, {
wfconfig: {
//返回数据强制下划线转驼峰
resDataAttrNameStyle: 1,
},
});
return new Promise((resoved, reject) => {
let backData = null;
post(url, params, _config).then(data => {
//处理返回的数据
let resDataMode = _config.wfconfig.resDataMode === undefined ? -1 : _config.wfconfig
.resDataMode;
if (resDataMode == -1) {
resDataMode = defalutResDataMode;
}
if (resDataMode == 1) {
backData = data.result;
} else {
backData = data.result.content;
}
let wfresp = data[getProtocolPre('Resp')];
if (!wfresp.resUrl) {
reject({
message: '上传异常:未返回上传通道',
data: backData
});
return;
}
let fileFormData = new FormData();
fileFormData.append('file', file);
return axios.post(wfresp.resUrl, fileFormData, {
onUploadProgress: _config.wfconfig.onUploadProgress
});
}).then(data => {
let resData = data.request ? data.data : data;
if (resData && resData.code != 0) {
reject({
message: resData.msg || resData || '上传失败',
data: backData
});
return;
}
resoved(backData);
}).catch(e => {
if (backData) {
reject({
message: e.message,
data: backData
});
return;
}
reject(e);
});
});
},
/**
* @param {Object} url 请求下载的微服务所需要url
* @param {Object} params 请求下载的微服务所需要的参数
* @param {Object} config 如果配置了wfconfig,resDataAttrNameStyle此处配置不生效,强制驼峰模式,
* 下载接口的wfconfig配置,此处新增一个notAutoDownload属性用于配置是否不自动打开连接Boolean类型,默认为false,也就是默认自动打开,特殊情况可能不需要自动打开,例如上传文件后
*/
download(url, params, config) {
let _config = extend(true, config, {
wfconfig: {
resDataAttrNameStyle: 1
}
});
return new Promise((resoved, reject) => {
post(url, params, _config).then(data => {
let wfresp = data[getProtocolPre('Resp')];
if (!wfresp.resUrl) {
reject(new Error('下载异常:未返回下载通道'));
}
resoved(data);
if (!_config.wfconfig.notAutoDownload) {
location.href = wfresp.resUrl;
}
}).catch(e => {
reject(e);
});
});
},
/**
* 浩云统一登录
* @param {String} username 用户名
* @param {String} password 密码
* @return {Promise}
*/
login(username, password) {
return new Promise((resove, reject) => {
let url = GLOBALCONFIG['loginUrl'] || 'zuoche_user?method=login';
noAccessPost(url, {
userName: username,
password: password,
}).then(data => {
if (data.accessId && data.accessKey) {
onlogined(data, resove);
return;
}
reject(new Error('登录异常:' + JSON.stringify(data)));
}).catch(e => {
reject(e);
});
});
},
/**
* 穿越登录功能
* @param {String} username 穿越用户名
* @param {String} password 穿越用户密码
* @param {String} suloginname 被穿越者登录名
*/
su(username, password, suloginname) {
return new Promise((resove, reject) => {
let url = GLOBALCONFIG['loginUrl'] || 'zuoche_user?method=login';
noAccessPost(url, {
userName: username,
password: password,
}).then(data => {
let suurl = GLOBALCONFIG['suUrl'] || 'zuoche_user?method=su';
return noAccessPost(suurl, {
userName: suloginname,
suId: data.sessionId
});
}).then(data => {
if (data.accessId && data.accessKey) {
onlogined(data, resove);
return;
}
reject(new Error('登录异常:' + JSON.stringify(data)));
}).catch(e => {
reject(e);
});
});
},
//退出登录,不会有失败的情况
logout() {
let promise = new Promise((resove, reject) => {
let url = GLOBALCONFIG['logoutUrl'] || 'zuoche_user?method=logout';
post(url).finally(() => {
removeLoginInfo();
resove();
})
});
return promise;
},
/**
* 添加浩宁云请求事件监听
* @param {String} event 监听的事件名,可选事件:requireauth--表示需要登录监听事件,visitforbidden--表示无访问权限监听事件,beforerequest--表示请求数据前的时间
* @param {Function} handler
*/
addEventListener(event, handler) {
if (!event) {
return;
}
if (typeof handler != 'function') {
return;
}
let events = EVENTS[event];
if (!events) {
EVENTS[event] = [];
} else if (EVENTS[event].indexOf(handler) !== -1) {
//不允许有重复的
return;
}
EVENTS[event].push(handler);
},
/**
* 移除请求异常的事件监听
* @param {String} event 事件名称
* @param {Function} handler 待移除的监听事件,不指定时移除全部监听
*/
removeEventListener(event, handler) {
if (!event) {
return;
}
let events = EVENTS[event];
if (!handler) {
delete EVENTS[event];
return;
}
let index = -1;
if (events && (index == events.indexOf(handler) != -1)) {
events.splice(index, 1);
}
},
/**
* 移除全部监听
*/
removeAllEventListener() {
for (let key in EVENTS) {
delete EVENTS[key];
}
},
/**
* 配置微服务版本
* @param {String} serviceName 正式环境服务名
* @param {String} version 大版本号例如:1.0,不配置默认为最新版
*/
setServiceVersion: function (serviceName, version) {
if (!serviceName) {
return;
}
if (version) {
SERVICE_VERSIONS[serviceName] = version;
} else {
delete SERVICE_VERSIONS[serviceName];
}
},
/**
* 更新全局属性
* @param {String} name 可以选的全局属性名
*
* resDataMode:默认0,0:纯业务数据,1:包含code等信息的包装数据(需要登录或者无访问权限时不受该参数控制),2、表示返回原始请求数据,此时不针对数据做任何加工处理
* resDataAttrNameStyle: 返回数据属性名转换模式,默认1,0:不转换,1、下划线转驼峰,2、兼容默认,转驼峰的同时保留原属性
* reqDataAttrNameStyle:Number类型,用于配置请求数据属性名的风格,不指定时默认读取全局配置,0表示不作处理,1表示下划线转驼峰
* loginErrorMsg:String类型,用于配置需要登录的提示
* loginUrl:String类型,用于配置需要自定义登录的url
* logoutUrl:String类型,用于配置需要自定义退出登录的url
* suUrl:String类型,模拟登陆 url
* waitTimeout:Number网关超时时间 单位秒
* protocolPre:String 协议头
*
*/
updateGlobalConfig: function (name, value) {
if (name == 'resDataMode') {
let val = Number(value);
if (typeof value != 'number' || (val < 0 || val > 2)) {
throw new Error(name + '的可选数值为0~2');
}
defalutResDataMode = val;
return;
}
if (name == 'resDataAttrNameStyle') {
let val = Number(value);
if (typeof value != 'number' || (val < 0 || val > 2)) {
throw new Error(name + '的可选数值为0~2');
}
defalutResDataAttrNameStyle = val;
return;
}
if (name == 'reqDataAttrNameStyle') {
let val = Number(value);
if (typeof value != 'number' || (val !== 0 && val !== 1)) {
throw new Error(name + '的可选数值为0~2');
}
defalutReqDataAttrNameStyle = val;
return;
}
GLOBALCONFIG[name] = value;
},
/**
* 配置全局参数
* @param {String} key 参数名
* @param {String,Number,Boolean} value 参数值
*/
putGlobalParam,
/**
* 获取全局参数值
* @param {String} key
*/
getGlobalParam,
/**
* 删除全局参数值
* @param {String} key
*/
removeGlobalParam,
// 自定义全局响应错误码拦截事件
customResErrorEvent,
// 保存登录信息(内部或插件使用)
onlogined,
// 刷新key和id
refreshAccess,
// 获得编译版本信息
getBuildInfo() {
return extend(true, {
version: '',
buildTime: '',
buildType: '',
buildPluginName: ''
}, window._WEFORWARD_VERSION);
},
//获取当前的基础url
getBaseUrl
};
instance = new WeforwradProtocol();
export default instance;