修改底层请求逻辑,封装请求方法
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
(function (root, factory) {
|
||||
// PC 接口客户端同时支持浏览器页面和 Node 单元测试加载。
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory(root);
|
||||
return;
|
||||
}
|
||||
|
||||
root.GenealogyApi = factory(root);
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||
'use strict';
|
||||
|
||||
var DEFAULT_CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f';
|
||||
var DEFAULT_TENANT_ID = '000000';
|
||||
var DEFAULT_TOKEN_KEY = 'genealogy_auth_token';
|
||||
var DEFAULT_TOKEN_HEADER_NAME = 'Authorization';
|
||||
|
||||
function loadNodeModule(path) {
|
||||
if (typeof require !== 'function') return null;
|
||||
|
||||
try {
|
||||
return require(path);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigApi() {
|
||||
if (root.GenealogyConfig) return root.GenealogyConfig;
|
||||
|
||||
var configFactory = loadNodeModule('../config.js');
|
||||
return configFactory ? configFactory(root) : null;
|
||||
}
|
||||
|
||||
function getStorageUtil() {
|
||||
return root.StorageUtil || loadNodeModule('./StorageUtil.js');
|
||||
}
|
||||
|
||||
function getAxiosRequestUtil() {
|
||||
return root.AxiosRequestUtil || loadNodeModule('./AxiosRequestUtil.js');
|
||||
}
|
||||
|
||||
function getStorage(store) {
|
||||
if (store) return store;
|
||||
|
||||
try {
|
||||
return root.localStorage;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getTokenFromResponse(data) {
|
||||
// 登录响应兼容后端已声明的多种 token 字段。
|
||||
if (!data) return '';
|
||||
return data.token || data.accessToken || data.tokenValue || '';
|
||||
}
|
||||
|
||||
function ApiError(message, options) {
|
||||
this.name = 'ApiError';
|
||||
this.message = message || '接口请求失败';
|
||||
this.status = options && options.status;
|
||||
this.code = options && options.code;
|
||||
this.data = options && options.data;
|
||||
}
|
||||
|
||||
ApiError.prototype = Object.create(Error.prototype);
|
||||
ApiError.prototype.constructor = ApiError;
|
||||
|
||||
function createUnavailableMethod(name) {
|
||||
return function () {
|
||||
var error = new Error('PC 接口文档未提供该业务接口:' + name);
|
||||
error.code = 'PC_API_NOT_AVAILABLE';
|
||||
throw error;
|
||||
};
|
||||
}
|
||||
|
||||
function createFormData(fileOrFormData) {
|
||||
var FormDataCtor = root.FormData;
|
||||
|
||||
if (FormDataCtor && fileOrFormData instanceof FormDataCtor) return fileOrFormData;
|
||||
if (!FormDataCtor) throw new ApiError('当前环境不支持文件上传');
|
||||
|
||||
var formData = new FormDataCtor();
|
||||
formData.append('file', fileOrFormData);
|
||||
return formData;
|
||||
}
|
||||
|
||||
function createChunkFormData(body) {
|
||||
var FormDataCtor = root.FormData;
|
||||
var source = body || {};
|
||||
|
||||
if (FormDataCtor && body instanceof FormDataCtor) return body;
|
||||
if (!FormDataCtor) throw new ApiError('当前环境不支持文件上传');
|
||||
|
||||
var formData = new FormDataCtor();
|
||||
formData.append('uploadId', source.uploadId);
|
||||
formData.append('chunkIndex', source.chunkIndex);
|
||||
formData.append('chunkMd5', source.chunkMd5);
|
||||
formData.append('file', source.file);
|
||||
return formData;
|
||||
}
|
||||
|
||||
function buildApiUrl(baseUrl, path, query) {
|
||||
var url = String(baseUrl || '').replace(/\/+$/g, '') + '/' + String(path || '').replace(/^\/+/, '');
|
||||
var params = new URLSearchParams();
|
||||
|
||||
Object.keys(query || {}).forEach(function (key) {
|
||||
var value = query[key];
|
||||
if (value !== undefined && value !== null && value !== '') params.append(key, value);
|
||||
});
|
||||
|
||||
return params.toString() ? url + '?' + params.toString() : url;
|
||||
}
|
||||
|
||||
function createClient(options) {
|
||||
var settings = options || {};
|
||||
var configApi = getConfigApi();
|
||||
var config = configApi && configApi.getConfig ? configApi.getConfig() : {};
|
||||
var storageUtil = getStorageUtil();
|
||||
var axiosRequestUtil = getAxiosRequestUtil();
|
||||
var store = getStorage(settings.tokenStore || settings.storage);
|
||||
var clientId = settings.clientId || config.clientId || DEFAULT_CLIENT_ID;
|
||||
var tenantId = settings.tenantId || config.tenantId || DEFAULT_TENANT_ID;
|
||||
var tokenKey = settings.tokenKey || config.tokenKey || DEFAULT_TOKEN_KEY;
|
||||
var tokenHeaderName = settings.tokenHeaderName || config.tokenHeaderName || DEFAULT_TOKEN_HEADER_NAME;
|
||||
var baseUrl = settings.baseUrl || config.apiBaseUrl;
|
||||
|
||||
if (!axiosRequestUtil || !axiosRequestUtil.createRequester) {
|
||||
throw new ApiError('缺少 AxiosRequestUtil.createRequester 公共请求工具');
|
||||
}
|
||||
|
||||
if (!baseUrl) {
|
||||
throw new ApiError('缺少接口基础地址,请检查根目录 config.js');
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
return storageUtil && storageUtil.read ? storageUtil.read(store, tokenKey) || '' : '';
|
||||
}
|
||||
|
||||
function setToken(token) {
|
||||
if (token && storageUtil && storageUtil.write) storageUtil.write(store, tokenKey, token);
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
if (storageUtil && storageUtil.remove) storageUtil.remove(store, tokenKey);
|
||||
}
|
||||
|
||||
var requester = axiosRequestUtil.createRequester({
|
||||
baseUrl: baseUrl,
|
||||
clientId: clientId,
|
||||
tokenHeaderName: tokenHeaderName,
|
||||
getToken: getToken,
|
||||
axiosInstance: settings.axiosInstance || root.axios
|
||||
});
|
||||
|
||||
function request(method, path, requestOptions) {
|
||||
return requester(method, path, requestOptions || {});
|
||||
}
|
||||
|
||||
function withTenant(body) {
|
||||
return Object.assign({
|
||||
tenantId: tenantId,
|
||||
clientId: clientId
|
||||
}, body || {});
|
||||
}
|
||||
|
||||
async function login(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/login', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'password' }, withTenant(body))
|
||||
});
|
||||
setToken(getTokenFromResponse(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
async function register(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/register', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'password', registerSource: 'PC' }, withTenant(body))
|
||||
});
|
||||
setToken(getTokenFromResponse(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
async function loginBySms(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/login/sms', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'sms' }, withTenant(body))
|
||||
});
|
||||
setToken(getTokenFromResponse(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
function sendSmsCode(body) {
|
||||
return request('POST', '/genealogy/pc/auth/sms/code', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'sms' }, withTenant(body))
|
||||
});
|
||||
}
|
||||
|
||||
function captchaRequire(body) {
|
||||
var source = body || {};
|
||||
|
||||
return request('GET', '/captcha/require', {
|
||||
auth: false,
|
||||
query: {
|
||||
tenantId: source.tenantId || tenantId,
|
||||
clientId: source.clientId || clientId,
|
||||
sceneCode: source.sceneCode,
|
||||
subject: source.subject
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var client = {
|
||||
clientId: clientId,
|
||||
tenantId: tenantId,
|
||||
tokenKey: tokenKey,
|
||||
getToken: getToken,
|
||||
setToken: setToken,
|
||||
clearToken: clearToken,
|
||||
request: request,
|
||||
buildApiUrl: function (path, query) {
|
||||
return buildApiUrl(baseUrl, path, query);
|
||||
},
|
||||
get: function (path, query) {
|
||||
return request('GET', path, { query: query });
|
||||
},
|
||||
post: function (path, body, requestOptions) {
|
||||
return request('POST', path, Object.assign({}, requestOptions || {}, { body: body }));
|
||||
},
|
||||
put: function (path, body, requestOptions) {
|
||||
return request('PUT', path, Object.assign({}, requestOptions || {}, { body: body }));
|
||||
},
|
||||
delete: function (path, requestOptions) {
|
||||
return request('DELETE', path, requestOptions || {});
|
||||
},
|
||||
login: login,
|
||||
register: register,
|
||||
sendSmsCode: sendSmsCode,
|
||||
loginBySms: loginBySms,
|
||||
smsLogin: loginBySms,
|
||||
getProfile: function () {
|
||||
return request('GET', '/genealogy/pc/auth/profile');
|
||||
},
|
||||
currentProfile: function () {
|
||||
return request('GET', '/genealogy/pc/auth/profile');
|
||||
},
|
||||
updateProfile: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/profile', { body: body });
|
||||
},
|
||||
changePassword: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/password', { body: body });
|
||||
},
|
||||
resetPassword: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/password/reset', {
|
||||
auth: false,
|
||||
body: Object.assign({ tenantId: tenantId }, body || {})
|
||||
});
|
||||
},
|
||||
changePhone: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/phone', { body: body });
|
||||
},
|
||||
deactivateAccount: async function (body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/account/deactivate', { body: body });
|
||||
clearToken();
|
||||
return data;
|
||||
},
|
||||
logout: async function () {
|
||||
var data = await request('DELETE', '/genealogy/pc/auth/logout');
|
||||
clearToken();
|
||||
return data;
|
||||
},
|
||||
uploadFile: function (fileOrFormData) {
|
||||
return request('POST', '/genealogy/pc/files/upload', { body: createFormData(fileOrFormData) });
|
||||
},
|
||||
initResumableUpload: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/resumable/init', { body: body });
|
||||
},
|
||||
uploadChunk: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/resumable/chunk', { body: createChunkFormData(body) });
|
||||
},
|
||||
uploadResumableChunk: function (body) {
|
||||
return this.uploadChunk(body);
|
||||
},
|
||||
completeResumableUpload: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/resumable/complete', { body: body });
|
||||
},
|
||||
bindFileReference: function (body) {
|
||||
return request('POST', '/genealogy/pc/files/reference', { body: body });
|
||||
},
|
||||
releaseFileReference: function (query) {
|
||||
return request('DELETE', '/genealogy/pc/files/reference', { query: query });
|
||||
},
|
||||
getRegionChildren: function (parentCode) {
|
||||
return request('GET', '/genealogy/region/children', { query: { parentCode: parentCode } });
|
||||
},
|
||||
regionChildren: function (parentCode) {
|
||||
return this.getRegionChildren(parentCode);
|
||||
},
|
||||
getRegionPath: function (regionCode) {
|
||||
return request('GET', '/genealogy/region/path/' + encodeURIComponent(regionCode));
|
||||
},
|
||||
regionPath: function (regionCode) {
|
||||
return this.getRegionPath(regionCode);
|
||||
},
|
||||
searchRegions: function (query) {
|
||||
return request('GET', '/genealogy/region/search', { query: query });
|
||||
},
|
||||
regionSearch: function (query) {
|
||||
return this.searchRegions(query);
|
||||
},
|
||||
getRegion: function (regionCode) {
|
||||
return request('GET', '/genealogy/region/' + encodeURIComponent(regionCode));
|
||||
},
|
||||
regionDetail: function (regionCode) {
|
||||
return this.getRegion(regionCode);
|
||||
},
|
||||
captchaRequire: captchaRequire,
|
||||
captchaRequirement: captchaRequire,
|
||||
captchaChallenge: function (body) {
|
||||
return request('POST', '/captcha/challenge', { auth: false, body: body });
|
||||
},
|
||||
captchaVerify: function (body) {
|
||||
return request('POST', '/captcha/verify', { auth: false, body: body });
|
||||
},
|
||||
legacyCaptcha: function () {
|
||||
return request('GET', '/auth/code', { auth: false });
|
||||
}
|
||||
};
|
||||
|
||||
[
|
||||
'listGenealogies', 'createGenealogy', 'publicGenealogies', 'myGenealogies', 'genealogyDetail', 'genealogyOverview',
|
||||
'applyJoinGenealogy', 'myJoinApplies', 'pendingJoinApplies', 'auditJoinApply', 'cancelJoinApply', 'genealogyMembers',
|
||||
'genealogyMemberOptions', 'updateGenealogyMember', 'removeGenealogyMember', 'leaveGenealogy', 'transferGenealogyOwner',
|
||||
'articleCategories', 'articles', 'articleDetail', 'createArticle', 'updateArticle', 'feeds', 'feedsPage', 'feedDetail',
|
||||
'createFeed', 'updateFeed', 'likeFeed', 'unlikeFeed', 'feedComments', 'feedCommentsPage', 'createFeedComment',
|
||||
'deleteFeedComment', 'albums', 'createAlbum', 'updateAlbum', 'albumPhotos', 'createAlbumPhoto', 'ceremonies',
|
||||
'ceremonyDetail', 'createCeremony', 'updateCeremony', 'ceremonyGifts', 'createCeremonyGift', 'meritRecords',
|
||||
'createMeritRecord', 'growthRecords', 'growthRecordDetail', 'createGrowthRecord', 'updateGrowthRecord', 'memos',
|
||||
'memoDetail', 'createMemo', 'updateMemo', 'vipPackages', 'vipOrders', 'createVipOrder', 'generationPoems',
|
||||
'createGenerationPoem', 'updateGenerationPoem', 'previewGenerationPoems', 'saveGenerationPoemsBatch', 'lineageTree',
|
||||
'lineagePersons', 'lineagePersonsPage', 'lineagePersonOptions', 'lineagePersonDetail', 'createLineagePerson',
|
||||
'updateLineagePerson', 'deleteLineagePerson', 'addLineageRelation', 'notifications', 'markNotificationRead',
|
||||
'markAllNotificationsRead', 'helpArticles', 'helpArticleDetail', 'promotions', 'feedbackList', 'submitFeedback'
|
||||
].forEach(function (name) {
|
||||
client[name] = createUnavailableMethod(name);
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
function createDefaultClient() {
|
||||
// Node 测试环境可无全局 Axios,浏览器页面在加载顺序正确时会创建默认客户端。
|
||||
try {
|
||||
return createClient();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
DEFAULT_CLIENT_ID: DEFAULT_CLIENT_ID,
|
||||
DEFAULT_TENANT_ID: DEFAULT_TENANT_ID,
|
||||
TOKEN_KEY: DEFAULT_TOKEN_KEY,
|
||||
ApiError: ApiError,
|
||||
createClient: createClient,
|
||||
defaultClient: createDefaultClient()
|
||||
};
|
||||
});
|
||||
@@ -1,62 +1,32 @@
|
||||
(function (root, factory) {
|
||||
// 请求工具封装 URL、Header 和响应解包,业务路径由 api-client 统一提供。
|
||||
// Axios 请求工具同时支持浏览器页面和 Node 单元测试加载。
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = factory(root);
|
||||
return;
|
||||
}
|
||||
|
||||
root.RequestUtil = factory(root);
|
||||
root.AxiosRequestUtil = factory(root);
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
|
||||
'use strict';
|
||||
|
||||
function trimSlashes(value) {
|
||||
return String(value || '').replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
|
||||
function joinUrl(baseUrl, path) {
|
||||
var base = String(baseUrl || '').replace(/\/+$/g, '');
|
||||
var cleanPath = '/' + trimSlashes(path);
|
||||
return base + cleanPath;
|
||||
}
|
||||
|
||||
function appendQuery(url, query) {
|
||||
if (!query) return url;
|
||||
|
||||
var params = new URLSearchParams();
|
||||
Object.keys(query).forEach(function (key) {
|
||||
var value = query[key];
|
||||
if (value === undefined || value === null || value === '') return;
|
||||
params.append(key, value);
|
||||
});
|
||||
|
||||
var queryText = params.toString();
|
||||
return queryText ? url + '?' + queryText : url;
|
||||
}
|
||||
|
||||
function isFormData(body) {
|
||||
return typeof FormData !== 'undefined' && body instanceof FormData;
|
||||
}
|
||||
|
||||
function createHttpError(message, detail) {
|
||||
var error = new Error(message);
|
||||
|
||||
Object.keys(detail || {}).forEach(function (key) {
|
||||
error[key] = detail[key];
|
||||
});
|
||||
return error;
|
||||
}
|
||||
|
||||
async function parseJson(response) {
|
||||
try {
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
function unwrapResponse(payload) {
|
||||
if (!payload || typeof payload !== 'object') return payload;
|
||||
|
||||
if (payload.code !== undefined && payload.code !== 200) {
|
||||
if (payload.code !== undefined && Number(payload.code) !== 200) {
|
||||
throw createHttpError(payload.msg || '接口请求失败', {
|
||||
code: payload.code,
|
||||
response: payload
|
||||
@@ -74,47 +44,66 @@
|
||||
return payload;
|
||||
}
|
||||
|
||||
function getAxiosInstance(options) {
|
||||
var settings = options || {};
|
||||
var axios = settings.axiosInstance || root.axios;
|
||||
|
||||
if (!axios || typeof axios.request !== 'function' && typeof axios.create !== 'function') {
|
||||
throw new Error('缺少 Axios 公共依赖');
|
||||
}
|
||||
|
||||
if (typeof axios.create === 'function') {
|
||||
return axios.create({
|
||||
baseURL: settings.baseUrl
|
||||
});
|
||||
}
|
||||
|
||||
return axios;
|
||||
}
|
||||
|
||||
function createRequester(options) {
|
||||
var settings = options || {};
|
||||
var fetchImpl = settings.fetchImpl || root.fetch;
|
||||
var axiosInstance = getAxiosInstance(settings);
|
||||
|
||||
return async function request(method, path, requestOptions) {
|
||||
var req = requestOptions || {};
|
||||
var headers = Object.assign({}, req.headers || {});
|
||||
var token = settings.getToken ? settings.getToken() : '';
|
||||
var tokenHeaderName = settings.tokenHeaderName || 'Authorization';
|
||||
var url = appendQuery(joinUrl(settings.baseUrl, path), req.query);
|
||||
var body = req.body;
|
||||
var response;
|
||||
|
||||
headers.clientid = settings.clientId;
|
||||
if (token && req.auth !== false) headers[tokenHeaderName] = 'Bearer ' + token;
|
||||
|
||||
if (body !== undefined && body !== null && !isFormData(body)) {
|
||||
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
|
||||
body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
var response = await fetchImpl(url, {
|
||||
method: method,
|
||||
headers: headers,
|
||||
body: body
|
||||
});
|
||||
var payload = await parseJson(response);
|
||||
try {
|
||||
response = await axiosInstance.request({
|
||||
method: String(method || 'GET').toLowerCase(),
|
||||
url: path,
|
||||
params: req.query,
|
||||
data: body,
|
||||
headers: headers
|
||||
});
|
||||
} catch (error) {
|
||||
var responseData = error.response && error.response.data;
|
||||
|
||||
if (!response.ok) {
|
||||
throw createHttpError((payload && payload.msg) || '接口请求失败', {
|
||||
status: response.status,
|
||||
response: payload
|
||||
throw createHttpError((responseData && responseData.msg) || error.message || '接口请求失败', {
|
||||
status: error.response && error.response.status,
|
||||
response: responseData,
|
||||
cause: error
|
||||
});
|
||||
}
|
||||
|
||||
return unwrapResponse(payload);
|
||||
return unwrapResponse(response.data);
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
createRequester: createRequester,
|
||||
joinUrl: joinUrl,
|
||||
appendQuery: appendQuery
|
||||
unwrapResponse: unwrapResponse
|
||||
};
|
||||
});
|
||||
+4288
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user