修改底层请求逻辑,封装请求方法

This commit is contained in:
rain
2026-07-10 14:11:10 +08:00
parent 6050508144
commit 6129a9221a
66 changed files with 5861 additions and 451 deletions
+44 -35
View File
@@ -1,5 +1,5 @@
const assert = require('assert');
const GenealogyApi = require('../public/js/api-client.js');
const GenealogyApi = require('../utils/ApiClient.js');
function createStore(initial) {
const values = Object.assign({}, initial);
@@ -24,7 +24,8 @@ async function run() {
'token-key': 'abc-token'
});
assert.strictEqual(GenealogyApi.DEFAULT_BASE_URL, 'http://test-genealogy-api.ddxcjp.cn');
// 接口基础地址只能由根目录 config.js 负责,公共客户端不能重复声明。
assert.strictEqual(GenealogyApi.DEFAULT_BASE_URL, undefined);
const client = GenealogyApi.createClient({
baseUrl: 'https://test-genealogy-api.ddxcjp.cn',
@@ -33,31 +34,32 @@ async function run() {
tokenKey: 'token-key',
tokenHeaderName: 'Authorization',
tokenStore: store,
fetchImpl: async (url, options) => {
calls.push({ url, options });
axiosInstance: {
request: async (config) => {
calls.push(config);
return {
ok: true,
status: 200,
json: async () => ({
return {
status: 200,
data: {
code: 200,
msg: '操作成功',
data: { token: 'server-token', url }
})
};
data: { token: 'server-token', url: config.url }
}
};
}
}
});
await client.login({ phone: '13800000000', password: 'md5' });
assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/login');
assert.strictEqual(calls[0].options.headers.clientid, 'client-x');
assert.strictEqual(calls[0].options.headers.Authorization, undefined);
assert.strictEqual(JSON.parse(calls[0].options.body).tenantId, 'tenant-x');
assert.strictEqual(calls[0].url, '/genealogy/pc/auth/login');
assert.strictEqual(calls[0].headers.clientid, 'client-x');
assert.strictEqual(calls[0].headers.Authorization, undefined);
assert.strictEqual(calls[0].data.tenantId, 'tenant-x');
assert.strictEqual(store.values['token-key'], 'server-token');
await client.getProfile();
assert.strictEqual(calls[1].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile');
assert.strictEqual(calls[1].options.headers.Authorization, 'Bearer server-token');
assert.strictEqual(calls[1].url, '/genealogy/pc/auth/profile');
assert.strictEqual(calls[1].headers.Authorization, 'Bearer server-token');
await client.updateProfile({
nickName: '张三',
@@ -65,12 +67,12 @@ async function run() {
cityCode: '510100',
districtCode: '510104'
});
assert.strictEqual(calls[2].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile');
assert.strictEqual(calls[2].options.method, 'PUT');
assert.strictEqual(calls[2].url, '/genealogy/pc/auth/profile');
assert.strictEqual(calls[2].method, 'put');
await client.uploadFile('file-content');
assert.strictEqual(calls[3].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/files/upload');
assert.strictEqual(calls[3].options.headers['Content-Type'], undefined);
assert.strictEqual(calls[3].url, '/genealogy/pc/files/upload');
assert.strictEqual(calls[3].headers['Content-Type'], undefined);
await client.initResumableUpload({
fileName: 'cover.jpg',
@@ -79,10 +81,11 @@ async function run() {
chunkSize: 4194304,
totalChunks: 1
});
assert.strictEqual(calls[4].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/files/resumable/init');
assert.strictEqual(calls[4].url, '/genealogy/pc/files/resumable/init');
await client.getRegionChildren('51');
assert.strictEqual(calls[5].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/region/children?parentCode=51');
assert.strictEqual(calls[5].url, '/genealogy/region/children');
assert.deepStrictEqual(calls[5].params, { parentCode: '51' });
await client.captchaRequire({
sceneCode: 'WEB_H5_LOGIN',
@@ -90,31 +93,37 @@ async function run() {
});
assert.strictEqual(
calls[6].url,
'https://test-genealogy-api.ddxcjp.cn/captcha/require?tenantId=tenant-x&clientId=client-x&sceneCode=WEB_H5_LOGIN&subject=13800000000'
'/captcha/require'
);
assert.strictEqual(calls[6].options.headers.Authorization, undefined);
assert.deepStrictEqual(calls[6].params, {
tenantId: 'tenant-x',
clientId: 'client-x',
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000'
});
assert.strictEqual(calls[6].headers.Authorization, undefined);
await client.loginBySms({
phone: '13800000000',
smsCode: '6666',
validToken: 'sms-ticket'
});
assert.strictEqual(calls[7].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/login/sms');
assert.strictEqual(calls[7].options.headers.Authorization, undefined);
assert.strictEqual(JSON.parse(calls[7].options.body).grantType, 'sms');
assert.strictEqual(JSON.parse(calls[7].options.body).tenantId, 'tenant-x');
assert.strictEqual(JSON.parse(calls[7].options.body).clientId, 'client-x');
assert.strictEqual(calls[7].url, '/genealogy/pc/auth/login/sms');
assert.strictEqual(calls[7].headers.Authorization, undefined);
assert.strictEqual(calls[7].data.grantType, 'sms');
assert.strictEqual(calls[7].data.tenantId, 'tenant-x');
assert.strictEqual(calls[7].data.clientId, 'client-x');
await client.sendSmsCode({
sceneCode: 'WEB_H5_LOGIN',
phone: '13800000000',
validToken: 'captcha-ticket'
});
assert.strictEqual(calls[8].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/sms/code');
assert.strictEqual(calls[8].options.headers.Authorization, undefined);
assert.strictEqual(JSON.parse(calls[8].options.body).grantType, 'sms');
assert.strictEqual(JSON.parse(calls[8].options.body).sceneCode, 'WEB_H5_LOGIN');
assert.strictEqual(JSON.parse(calls[8].options.body).validToken, 'captcha-ticket');
assert.strictEqual(calls[8].url, '/genealogy/pc/auth/sms/code');
assert.strictEqual(calls[8].headers.Authorization, undefined);
assert.strictEqual(calls[8].data.grantType, 'sms');
assert.strictEqual(calls[8].data.sceneCode, 'WEB_H5_LOGIN');
assert.strictEqual(calls[8].data.validToken, 'captcha-ticket');
assert.throws(
() => client.listGenealogies(),
+45
View File
@@ -0,0 +1,45 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const rootDirectory = path.join(__dirname, '..');
function scriptIndex(html, source) {
return html.indexOf(`src="${source}"`);
}
function run() {
const pages = fs.readdirSync(rootDirectory).filter((fileName) => {
if (!fileName.endsWith('.html')) return false;
const html = fs.readFileSync(path.join(rootDirectory, fileName), 'utf8');
return html.includes('public/js/api-client.js') || html.includes('utils/ApiClient.js');
});
assert.strictEqual(pages.length, 45, '接口客户端加载页面数量应保持为 45 页');
pages.forEach((fileName) => {
const html = fs.readFileSync(path.join(rootDirectory, fileName), 'utf8');
const configIndex = scriptIndex(html, 'config.js');
const storageIndex = scriptIndex(html, 'utils/StorageUtil.js');
const axiosIndex = scriptIndex(html, 'utils/axios.js');
const requestIndex = scriptIndex(html, 'utils/AxiosRequestUtil.js');
const apiIndex = scriptIndex(html, 'utils/ApiClient.js');
assert.notStrictEqual(configIndex, -1, `${fileName} 应加载环境配置`);
assert.notStrictEqual(storageIndex, -1, `${fileName} 应加载 token 存储工具`);
assert.notStrictEqual(axiosIndex, -1, `${fileName} 应加载本地 Axios`);
assert.notStrictEqual(requestIndex, -1, `${fileName} 应加载 AxiosRequestUtil`);
assert.notStrictEqual(apiIndex, -1, `${fileName} 应加载 ApiClient`);
assert.ok(configIndex < storageIndex, `${fileName} 应先加载环境配置再加载存储工具`);
assert.ok(storageIndex < axiosIndex, `${fileName} 应先加载存储工具再加载 Axios`);
assert.ok(axiosIndex < requestIndex, `${fileName} 应先加载 Axios 再加载请求封装`);
assert.ok(requestIndex < apiIndex, `${fileName} 应先加载请求封装再加载接口客户端`);
assert.strictEqual(html.includes('utils/RequestUtil.js'), false, `${fileName} 不应继续加载旧 RequestUtil`);
assert.strictEqual(html.includes('public/js/api-client.js'), false, `${fileName} 不应继续加载旧 ApiClient`);
});
console.log('api-script-order tests passed');
}
run();
+6 -3
View File
@@ -16,15 +16,18 @@ function run() {
pages.forEach((fileName) => {
const html = readPage(fileName);
const formCaptchaBox = /<form[\s\S]*?data-captcha-box[\s\S]*?<\/form>/i.test(html);
const pageModalBox = /class="[^"]*\bauth-captcha-modal\b[^"]*"[^>]*data-captcha-box/.test(html)
|| /data-captcha-box[^>]*class="[^"]*\bauth-captcha-modal\b[^"]*"/.test(html);
const pageCaptchaBox = /data-captcha-box/.test(html);
const hasLayuiCss = html.includes('href="public/layui/css/layui.css"');
const hasTacCss = html.includes('href="public/tac/css/tac.css"');
const hasCaptchaModalCss = html.includes('href="public/css/captcha-modal.css"');
const layuiScriptIndex = html.indexOf('src="public/layui/layui.js"');
const authScriptIndex = html.indexOf('src="public/js/auth-pages.js"');
assert.strictEqual(formCaptchaBox, false, `${fileName} 不应把 TAC 挂载点放在表单内部`);
assert.strictEqual(pageModalBox, true, `${fileName} 应提供页面级 TAC 弹窗挂载点`);
assert.strictEqual(pageCaptchaBox, false, `${fileName} 不应保留会将 TAC 渲染到页面底部的静态挂载点`);
assert.strictEqual(hasLayuiCss, true, `${fileName} 应加载 layui.css 作为 layer 提示样式依赖`);
assert.strictEqual(hasTacCss, true, `${fileName} 应加载 TAC 自带样式`);
assert.strictEqual(hasCaptchaModalCss, false, `${fileName} 不应加载自定义验证码样式`);
assert.notStrictEqual(layuiScriptIndex, -1, `${fileName} 应加载 layui.js 使用 layer.msg`);
assert.ok(layuiScriptIndex < authScriptIndex, `${fileName} 应在 auth-pages.js 之前加载 layui.js`);
});
+44 -8
View File
@@ -1,4 +1,6 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const CaptchaPages = require('../public/js/captcha-pages.js');
const fakeApi = {
@@ -7,6 +9,14 @@ const fakeApi = {
};
function run() {
const captchaSource = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'captcha-pages.js'), 'utf8');
assert.strictEqual(captchaSource.includes('auth-captcha-modal'), false, '验证码适配层不应控制自定义弹窗样式');
assert.strictEqual(captchaSource.includes('bgUrl:'), false, '验证码适配层不应覆盖 TAC 背景样式');
assert.strictEqual(captchaSource.includes('logoUrl:'), false, '验证码适配层不应覆盖 TAC Logo 样式');
assert.strictEqual(captchaSource.includes('root.layui.layer.open'), true, '验证码适配层应使用 Layui 默认弹层承载 TAC');
assert.strictEqual(captchaSource.includes('root.layui.layer.close'), true, '验证码结束时应关闭对应 Layui 弹层');
assert.deepStrictEqual(CaptchaPages.buildChallengeBody({
sceneCode: 'WEB_H5_LOGIN',
subject: '13800000000'
@@ -81,23 +91,49 @@ function run() {
required: true
}), true);
const modalBox = { nodeName: 'modal' };
const formBox = { nodeName: 'form-box' };
const originalDocument = global.document;
const originalLayui = global.layui;
const mountedBox = {
innerHTML: '<div class="tianai-captcha-parent"></div>'
};
let layerOptions;
let closedLayerIndex;
global.document = {
querySelector(selector) {
return selector === '.auth-captcha-modal[data-captcha-box]' ? modalBox : null;
assert.strictEqual(selector, '[data-captcha-layer-box="captcha-layer-1"]');
return mountedBox;
}
};
global.layui = {
layer: {
open(options) {
layerOptions = options;
return 7;
},
close(index) {
closedLayerIndex = index;
}
}
};
assert.strictEqual(CaptchaPages.getCaptchaBox({
querySelector(selector) {
return selector === '[data-captcha-box]' ? formBox : null;
}
}), modalBox);
const captchaLayer = CaptchaPages.createCaptchaLayer();
assert.strictEqual(captchaLayer.box, mountedBox);
assert.strictEqual(captchaLayer.layerIndex, 7);
assert.strictEqual(layerOptions.type, 1);
assert.strictEqual(layerOptions.title, false);
assert.strictEqual(layerOptions.closeBtn, 0);
assert.deepStrictEqual(layerOptions.area, ['318px', '318px']);
assert.strictEqual(layerOptions.offset, 'auto');
assert.strictEqual(typeof layerOptions.content, 'string');
assert.strictEqual(layerOptions.content.includes('data-captcha-layer-box="captcha-layer-1"'), true);
CaptchaPages.closeCaptchaLayer(captchaLayer);
assert.strictEqual(mountedBox.innerHTML, '');
assert.strictEqual(closedLayerIndex, 7);
global.document = originalDocument;
global.layui = originalLayui;
console.log('captcha-pages tests passed');
}
+20 -16
View File
@@ -1,26 +1,30 @@
const assert = require('assert');
const configFactory = require('../config.js');
const store = {};
const config = configFactory({
localStorage: {
getItem: (key) => Object.prototype.hasOwnProperty.call(store, key) ? store[key] : null,
setItem: (key, value) => {
store[key] = String(value);
}
const developmentConfig = configFactory({
location: {
hostname: '127.0.0.1',
port: '5501'
}
});
assert.strictEqual(config.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn');
assert.strictEqual(config.getClientId(), 'ced7e5f0498645c6ec642dcf450b036f');
assert.strictEqual(config.getTenantId(), '000000');
assert.strictEqual(config.getTokenKey(), 'genealogy_auth_token');
assert.strictEqual(config.getConfig().tokenHeaderName, 'Authorization');
assert.deepStrictEqual(developmentConfig.getConfig(), {
environment: 'development',
apiBaseUrl: 'http://test-genealogy-api.ddxcjp.cn'
});
assert.strictEqual(developmentConfig.getClientId, undefined);
assert.strictEqual(developmentConfig.getTenantId, undefined);
assert.strictEqual(developmentConfig.getTokenKey, undefined);
assert.strictEqual(developmentConfig.getTokenHeaderName, undefined);
store.genealogy_api_base_url = 'https://custom.example.test';
assert.strictEqual(config.getApiBaseUrl(), 'https://custom.example.test');
const productionConfig = configFactory({
location: {
hostname: 'genealogy.example.test',
port: ''
}
});
store.genealogy_api_base_url = 'https://test-genealogy-api.ddxcjp.cn';
assert.strictEqual(config.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn');
assert.strictEqual(productionConfig.getEnvironment(), 'production');
assert.strictEqual(productionConfig.getApiBaseUrl(), 'http://test-genealogy-api.ddxcjp.cn');
console.log('config tests passed');
+15 -12
View File
@@ -1,7 +1,7 @@
const assert = require('assert');
const StorageUtil = require('../utils/StorageUtil.js');
const FormUtil = require('../utils/FormUtil.js');
const RequestUtil = require('../utils/RequestUtil.js');
const AxiosRequestUtil = require('../utils/AxiosRequestUtil.js');
assert.strictEqual(FormUtil.trimOrUndefined(' 张三 '), '张三');
assert.strictEqual(FormUtil.trimOrUndefined(' '), undefined);
@@ -27,19 +27,20 @@ StorageUtil.remove(storage, 'token');
assert.strictEqual(StorageUtil.read(storage, 'token'), null);
const calls = [];
const requester = RequestUtil.createRequester({
const requester = AxiosRequestUtil.createRequester({
baseUrl: 'https://test-genealogy-api.ddxcjp.cn',
clientId: 'pc-client',
tokenHeaderName: 'Authorization',
getToken: () => 'token-x',
fetchImpl: async (url, options) => {
calls.push({ url, options });
axiosInstance: {
request: async (config) => {
calls.push(config);
return {
ok: true,
status: 200,
json: async () => ({ code: 200, data: { ok: true } })
};
return {
status: 200,
data: { code: 200, data: { ok: true } }
};
}
}
});
@@ -47,8 +48,10 @@ requester('GET', '/genealogy/pc/auth/profile', {
query: { keyword: '汤 氏' }
}).then((data) => {
assert.deepStrictEqual(data, { ok: true });
assert.strictEqual(calls[0].url, 'https://test-genealogy-api.ddxcjp.cn/genealogy/pc/auth/profile?keyword=%E6%B1%A4+%E6%B0%8F');
assert.strictEqual(calls[0].options.headers.clientid, 'pc-client');
assert.strictEqual(calls[0].options.headers.Authorization, 'Bearer token-x');
assert.strictEqual(calls[0].method, 'get');
assert.strictEqual(calls[0].url, '/genealogy/pc/auth/profile');
assert.deepStrictEqual(calls[0].params, { keyword: '汤 氏' });
assert.strictEqual(calls[0].headers.clientid, 'pc-client');
assert.strictEqual(calls[0].headers.Authorization, 'Bearer token-x');
console.log('utils tests passed');
});