test(api): 更新API客户端契约测试以符合YAML规范
- 更新登录响应模拟数据以匹配真实的AppLoginVo结构 - 添加认证客户端租户和授权字段验证 - 增加操作码枚举验证测试用例 - 移除对旧token别名的兼容性测试 - 修复测试用例中的短信验证码长度一致性问题 - 更新区域接口路径为PC专用路径 - 调整分片上传接口参数以符合新契约定义 refactor(api): 重构API客户端实现以严格遵循YAML契约 - 添加认证操作码和短信操作码枚举验证 - 实现严格的token响应解析只接受access_token字段 - 使用pickDefined函数过滤请求体中未定义的字段 - 重构认证接口参数映射以符合契约定义 - 更新区域接口路径为PC专用路径/genealogy/pc/region/* - 优化分片上传接口参数结构与契约保持一致 - 添加操作码枚举验证函数toRequiredOperationCode - 实现请求体字段选择性提取功能 feat(auth): 优化认证页面的验证码处理流程 - 添加takeCaptchaToken函数用于一次性获取验证码票据 - 更新短信验证码长度验证从4-6位改为精确4位 - 在登录和密码重置流程中集成验证码票据处理 - 修复验证码发送后票据清理逻辑 - 更新HTML模板中的验证码输入字段属性 chore(config): 提取常量配置并扩展配置对象结构 - 将客户端ID、租户ID和令牌键提取为常量 - 扩展配置对象返回客户端配置信息 - 更新配置测试用例以验证新增配置项 docs(planning): 更新PC接口对接规划文档 - 更新契约源说明以反映YAML冻结契约 - 添加YAML与在线Apifox复核对比内容 - 更新阻断项状态表格 - 修订登录响应token字段处理规范 - 更新文件上传和行政区划接口规范说明 style(profile): 优化相册管理页面的文件上传交互 - 将封面和照片OSS ID输入改为隐藏字段 - 添加文件选择标签以改善用户体验 - 移除手动输入OSS ID的选项保持界面简洁
This commit is contained in:
+90
-19
@@ -11,6 +11,21 @@
|
||||
var DEFAULT_CLIENT_ID = 'ced7e5f0498645c6ec642dcf450b036f';
|
||||
var DEFAULT_TENANT_ID = '000000';
|
||||
var DEFAULT_TOKEN_KEY = 'genealogy_auth_token';
|
||||
var VERIFICATION_OPERATION_CODES = [
|
||||
'password-login',
|
||||
'sms-login',
|
||||
'register',
|
||||
'forgot-password',
|
||||
'phone-change',
|
||||
'account-deactivate'
|
||||
];
|
||||
var SMS_OPERATION_CODES = [
|
||||
'sms-login',
|
||||
'register',
|
||||
'forgot-password',
|
||||
'phone-change',
|
||||
'account-deactivate'
|
||||
];
|
||||
|
||||
function loadNodeModule(path) {
|
||||
if (typeof require !== 'function') return null;
|
||||
@@ -49,7 +64,17 @@
|
||||
}
|
||||
|
||||
function getTokenFromResponse(data) {
|
||||
return data && (data.token || data.accessToken || data.access_token || data.tokenValue) || '';
|
||||
return data && data.access_token || '';
|
||||
}
|
||||
|
||||
function pickDefined(source, allowedFields) {
|
||||
var input = source || {};
|
||||
var result = {};
|
||||
|
||||
allowedFields.forEach(function (field) {
|
||||
if (input[field] !== undefined) result[field] = input[field];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function createChunkFormData(bodyOrFormData) {
|
||||
@@ -112,6 +137,7 @@
|
||||
baseUrl: baseUrl,
|
||||
clientId: clientId,
|
||||
getToken: getToken,
|
||||
onUnauthorized: clearToken,
|
||||
axiosInstance: settings.axiosInstance || root.axios
|
||||
});
|
||||
|
||||
@@ -121,15 +147,19 @@
|
||||
}
|
||||
|
||||
function withTenant(body) {
|
||||
return Object.assign({ tenantId: tenantId }, body || {});
|
||||
return Object.assign({}, body || {}, { tenantId: tenantId });
|
||||
}
|
||||
|
||||
function buildVerificationPath(operationCode, suffix) {
|
||||
return '/genealogy/pc/auth/verification/' + toRequiredPathId(operationCode, 'PC 认证动作') + (suffix || '');
|
||||
return '/genealogy/pc/auth/verification/' +
|
||||
toRequiredOperationCode(operationCode, 'PC 认证动作', VERIFICATION_OPERATION_CODES) +
|
||||
(suffix || '');
|
||||
}
|
||||
|
||||
function buildSmsCodePath(operationCode) {
|
||||
return '/genealogy/pc/auth/sms/' + toRequiredPathId(operationCode, 'PC 短信认证动作') + '/code';
|
||||
return '/genealogy/pc/auth/sms/' +
|
||||
toRequiredOperationCode(operationCode, 'PC 短信认证动作', SMS_OPERATION_CODES) +
|
||||
'/code';
|
||||
}
|
||||
|
||||
function toRequiredPathId(value, label) {
|
||||
@@ -139,6 +169,14 @@
|
||||
return encodeURIComponent(text);
|
||||
}
|
||||
|
||||
function toRequiredOperationCode(value, label, allowedValues) {
|
||||
var text = String(value === undefined || value === null ? '' : value).trim();
|
||||
|
||||
if (!text) throw new Error('缺少' + label);
|
||||
if (allowedValues.indexOf(text) === -1) throw new Error('不支持的' + label + ':' + text);
|
||||
return encodeURIComponent(text);
|
||||
}
|
||||
|
||||
function buildFeedPath(genealogyId, suffix) {
|
||||
// 家族圈接口统一由家谱编号定位,避免页面拼接出未定义的请求路径。
|
||||
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/feeds' + (suffix || '');
|
||||
@@ -235,7 +273,10 @@
|
||||
async function login(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/login', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'password' }, withTenant(body))
|
||||
body: Object.assign(
|
||||
pickDefined(body, ['phone', 'password', 'validToken']),
|
||||
{ grantType: 'password', tenantId: tenantId }
|
||||
)
|
||||
});
|
||||
var token = getTokenFromResponse(data);
|
||||
|
||||
@@ -247,7 +288,10 @@
|
||||
async function loginBySms(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/login/sms', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'sms' }, withTenant(body))
|
||||
body: Object.assign(
|
||||
pickDefined(body, ['phone', 'smsCode']),
|
||||
{ grantType: 'sms', tenantId: tenantId }
|
||||
)
|
||||
});
|
||||
var token = getTokenFromResponse(data);
|
||||
|
||||
@@ -258,7 +302,7 @@
|
||||
|
||||
async function deactivateAccount(body) {
|
||||
var data = await request('POST', '/genealogy/pc/auth/account/deactivate', {
|
||||
body: body
|
||||
body: pickDefined(body, ['smsCode'])
|
||||
});
|
||||
clearToken();
|
||||
return data;
|
||||
@@ -285,7 +329,10 @@
|
||||
register: function (body) {
|
||||
return request('POST', '/genealogy/pc/auth/register', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'password', registerSource: 'PC' }, withTenant(body))
|
||||
body: Object.assign(
|
||||
pickDefined(body, ['phone', 'password', 'nickName', 'smsCode']),
|
||||
{ grantType: 'password', tenantId: tenantId, registerSource: 'PC' }
|
||||
)
|
||||
});
|
||||
},
|
||||
login: login,
|
||||
@@ -294,26 +341,38 @@
|
||||
return request('POST', buildSmsCodePath(operationCode), {
|
||||
auth: false,
|
||||
// PC 认证目录以路径 operationCode 绑定动作;clientid 只由请求头提供。
|
||||
body: Object.assign({ grantType: 'sms', tenantId: tenantId }, body || {})
|
||||
body: Object.assign(
|
||||
pickDefined(body, ['phone', 'validToken']),
|
||||
{ grantType: 'sms', tenantId: tenantId }
|
||||
)
|
||||
});
|
||||
},
|
||||
currentProfile: function () {
|
||||
return request('GET', '/genealogy/pc/auth/profile');
|
||||
},
|
||||
updateProfile: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/profile', { body: body });
|
||||
return request('PUT', '/genealogy/pc/auth/profile', {
|
||||
body: pickDefined(body, ['nickName', 'realName', 'avatar', 'sex', 'birthday', 'email'])
|
||||
});
|
||||
},
|
||||
changePassword: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/password', { body: body });
|
||||
return request('PUT', '/genealogy/pc/auth/password', {
|
||||
body: pickDefined(body, ['oldPassword', 'newPassword'])
|
||||
});
|
||||
},
|
||||
resetPassword: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/password/reset', {
|
||||
auth: false,
|
||||
body: Object.assign({ grantType: 'password' }, withTenant(body))
|
||||
body: Object.assign(
|
||||
pickDefined(body, ['phone', 'smsCode', 'newPassword']),
|
||||
{ grantType: 'password', tenantId: tenantId }
|
||||
)
|
||||
});
|
||||
},
|
||||
changePhone: function (body) {
|
||||
return request('PUT', '/genealogy/pc/auth/phone', { body: body });
|
||||
return request('PUT', '/genealogy/pc/auth/phone', {
|
||||
body: pickDefined(body, ['phone', 'smsCode'])
|
||||
});
|
||||
},
|
||||
deactivateAccount: deactivateAccount,
|
||||
logout: logout,
|
||||
@@ -340,10 +399,22 @@
|
||||
});
|
||||
},
|
||||
captchaChallenge: function (operationCode, body) {
|
||||
return request('POST', buildVerificationPath(operationCode, '/challenge'), { auth: false, body: body });
|
||||
return request('POST', buildVerificationPath(operationCode, '/challenge'), {
|
||||
auth: false,
|
||||
body: withTenant(pickDefined(body, ['subject']))
|
||||
});
|
||||
},
|
||||
captchaVerify: function (operationCode, body) {
|
||||
return request('POST', buildVerificationPath(operationCode, '/verify'), { auth: false, body: body });
|
||||
return request('POST', buildVerificationPath(operationCode, '/verify'), {
|
||||
auth: false,
|
||||
body: withTenant(pickDefined(body, [
|
||||
'subject',
|
||||
'challengeId',
|
||||
'providerCode',
|
||||
'captchaType',
|
||||
'payload'
|
||||
]))
|
||||
});
|
||||
},
|
||||
captchaChallengeUrl: function (operationCode) {
|
||||
return buildApiUrl(baseUrl, buildVerificationPath(operationCode, '/challenge'));
|
||||
@@ -352,16 +423,16 @@
|
||||
return buildApiUrl(baseUrl, buildVerificationPath(operationCode, '/verify'));
|
||||
},
|
||||
regionChildren: function (parentCode) {
|
||||
return request('GET', '/genealogy/region/children', { query: { parentCode: parentCode } });
|
||||
return request('GET', '/genealogy/pc/region/children', { query: { parentCode: parentCode } });
|
||||
},
|
||||
regionPath: function (regionCode) {
|
||||
return request('GET', '/genealogy/region/path/' + encodeURIComponent(regionCode));
|
||||
return request('GET', '/genealogy/pc/region/path/' + encodeURIComponent(regionCode));
|
||||
},
|
||||
regionSearch: function (query) {
|
||||
return request('GET', '/genealogy/region/search', { query: query });
|
||||
return request('GET', '/genealogy/pc/region/search', { query: query });
|
||||
},
|
||||
regionDetail: function (regionCode) {
|
||||
return request('GET', '/genealogy/region/' + encodeURIComponent(regionCode));
|
||||
return request('GET', '/genealogy/pc/region/' + encodeURIComponent(regionCode));
|
||||
},
|
||||
genealogyQuota: function () {
|
||||
return request('GET', '/genealogy/pc/genealogies/quota');
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
var settings = options || {};
|
||||
var axiosInstance = getAxiosInstance(settings);
|
||||
|
||||
function handleUnauthorized(status) {
|
||||
if (Number(status) === 401 && settings.onUnauthorized) settings.onUnauthorized();
|
||||
}
|
||||
|
||||
return async function request(method, path, requestOptions) {
|
||||
var req = requestOptions || {};
|
||||
var headers = Object.assign({}, req.headers || {});
|
||||
@@ -90,15 +94,23 @@
|
||||
});
|
||||
} catch (error) {
|
||||
var responseData = error.response && error.response.data;
|
||||
var status = error.response && error.response.status;
|
||||
|
||||
handleUnauthorized(status);
|
||||
|
||||
throw createHttpError((responseData && responseData.msg) || error.message || '接口请求失败', {
|
||||
status: error.response && error.response.status,
|
||||
status: status,
|
||||
response: responseData,
|
||||
cause: error
|
||||
});
|
||||
}
|
||||
|
||||
return unwrapResponse(response.data);
|
||||
try {
|
||||
return unwrapResponse(response.data);
|
||||
} catch (error) {
|
||||
handleUnauthorized(error.code);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user