feat(api): 完善家谱系统API客户端契约

- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
This commit is contained in:
fizzleaf
2026-07-29 16:57:27 +08:00
parent 59a72fb22b
commit fb1743aa2a
74 changed files with 13166 additions and 1320 deletions
+223 -14
View File
@@ -163,6 +163,9 @@
}
function toRequiredPathId(value, label) {
if (typeof value === 'number' && !Number.isSafeInteger(value)) {
throw new Error('无效' + label);
}
var text = String(value === undefined || value === null ? '' : value).trim();
if (!text) throw new Error('缺少' + label);
@@ -238,38 +241,65 @@
}
function buildMeritRecordPath(genealogyId, meritId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') +
'/merit-records/' + toRequiredPathId(meritId, '功德记录编号');
var path = '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/merit-records';
return meritId === undefined || meritId === null || meritId === ''
? path
: path + '/' + toRequiredPathId(meritId, '功德记录编号');
}
function buildArticleCollectionPath(genealogyId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/articles';
}
function buildArticlePath(genealogyId, articleId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '谱编号') +
'/articles/' + toRequiredPathId(articleId, '谱文编号');
return buildArticleCollectionPath(genealogyId) + '/' + toRequiredPathId(articleId, '谱编号');
}
function buildAlbumCollectionPath(genealogyId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/albums';
}
function buildAlbumPath(genealogyId, albumId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') +
'/albums/' + toRequiredPathId(albumId, '相册编号');
return buildAlbumCollectionPath(genealogyId) + '/' + toRequiredPathId(albumId, '相册编号');
}
function buildAlbumPhotoCollectionPath(genealogyId, albumId) {
return buildAlbumPath(genealogyId, albumId) + '/photos';
}
function buildAlbumPhotoPath(genealogyId, albumId, photoId) {
return buildAlbumPath(genealogyId, albumId) + '/photos/' + toRequiredPathId(photoId, '相册照片编号');
return buildAlbumPhotoCollectionPath(genealogyId, albumId) + '/' + toRequiredPathId(photoId, '相册照片编号');
}
function buildVideoCollectionPath(genealogyId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/videos';
}
function buildVideoPath(genealogyId, videoId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') +
'/videos/' + toRequiredPathId(videoId, '视频编号');
return buildVideoCollectionPath(genealogyId) + '/' + toRequiredPathId(videoId, '视频编号');
}
function buildCeremonyCollectionPath(genealogyId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/ceremonies';
}
function buildCeremonyPath(genealogyId, ceremonyId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') +
'/ceremonies/' + toRequiredPathId(ceremonyId, '祭祀活动编号');
return buildCeremonyCollectionPath(genealogyId) + '/' + toRequiredPathId(ceremonyId, '祭祀活动编号');
}
function buildCeremonyGiftPath(genealogyId, ceremonyId, giftId) {
return buildCeremonyPath(genealogyId, ceremonyId) + '/gifts/' + toRequiredPathId(giftId, '祭品编号');
}
function buildGenealogyMemberCollectionPath(genealogyId) {
return '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/members';
}
function buildGenealogyMemberPath(genealogyId, memberId) {
return buildGenealogyMemberCollectionPath(genealogyId) + '/' + toRequiredPathId(memberId, '成员编号');
}
async function login(body) {
var data = await request('POST', '/genealogy/pc/auth/login', {
auth: false,
@@ -437,8 +467,57 @@
genealogyQuota: function () {
return request('GET', '/genealogy/pc/genealogies/quota');
},
genealogiesMine: function () {
return request('GET', '/genealogy/pc/genealogies/mine');
},
genealogyOptions: function (query) {
return request('GET', '/genealogy/pc/genealogies/options', { query: query });
},
genealogyDetail: function (genealogyId) {
return request('GET', '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号'));
},
genealogyOverview: function (genealogyId) {
return request('GET', '/genealogy/pc/genealogies/' + toRequiredPathId(genealogyId, '家谱编号') + '/overview');
},
createGenealogy: function (body) {
return request('POST', '/genealogy/pc/genealogies', {
body: pickDefined(body, [
'genealogyName', 'surname', 'regionCode', 'ancestralHall', 'originPlace',
'addressDetail', 'coverOssId', 'intro', 'visibility', 'joinMode'
])
});
},
applyToGenealogy: function (genealogyId, body) {
return request('POST', '/genealogy/pc/genealogies/' +
toRequiredPathId(genealogyId, '家谱编号') + '/join-applies', {
body: pickDefined(body, ['applicantName', 'phone', 'relationDesc', 'applyReason'])
});
},
myGenealogyJoinApplies: function () {
return request('GET', '/genealogy/pc/genealogies/join-applies/mine');
},
pendingGenealogyJoinApplies: function (genealogyId) {
return request('GET', '/genealogy/pc/genealogies/' +
toRequiredPathId(genealogyId, '家谱编号') + '/join-applies/pending');
},
auditGenealogyJoinApply: function (genealogyId, applyId, body) {
return request('PUT', '/genealogy/pc/genealogies/' +
toRequiredPathId(genealogyId, '家谱编号') + '/join-applies/' +
toRequiredPathId(applyId, '申请编号') + '/audit', {
body: pickDefined(body, ['status', 'auditRemark'])
});
},
cancelGenealogyJoinApply: function (applyId) {
return request('DELETE', '/genealogy/pc/genealogies/join-applies/' +
toRequiredPathId(applyId, '申请编号'));
},
notifications: function (query) {
return request('GET', '/genealogy/pc/notifications', { query: query });
return request('GET', '/genealogy/pc/notifications', {
query: pickDefined(query, ['readStatus'])
});
},
notificationDetail: function (notificationId) {
return request('GET', '/genealogy/pc/notifications/' + toRequiredPathId(notificationId, '通知编号'));
},
unreadNotificationCount: function () {
return request('GET', '/genealogy/pc/notifications/unread-count');
@@ -590,23 +669,131 @@
deleteMemo: function (genealogyId, memoId) {
return request('DELETE', buildMemoPath(genealogyId, memoId));
},
meritRecords: function (genealogyId) {
return request('GET', buildMeritRecordPath(genealogyId));
},
createMeritRecord: function (genealogyId, body) {
return request('POST', buildMeritRecordPath(genealogyId), {
body: pickDefined(body, ['donorName', 'meritType', 'meritTitle', 'meritContent', 'amount', 'meritTime', 'sortOrder', 'status'])
});
},
meritRecordDetail: function (genealogyId, meritId) {
return request('GET', buildMeritRecordPath(genealogyId, meritId));
},
updateMeritRecord: function (genealogyId, meritId, body) {
return request('PUT', buildMeritRecordPath(genealogyId, meritId), {
body: pickDefined(body, ['donorName', 'meritType', 'meritTitle', 'meritContent', 'amount', 'meritTime', 'sortOrder', 'status'])
});
},
deleteMeritRecord: function (genealogyId, meritId) {
return request('DELETE', buildMeritRecordPath(genealogyId, meritId));
},
articles: function (genealogyId) {
return request('GET', buildArticleCollectionPath(genealogyId));
},
createArticle: function (genealogyId, body) {
return request('POST', buildArticleCollectionPath(genealogyId), {
body: pickDefined(body, [
'categoryId', 'articleTitle', 'articleSummary', 'coverOssId',
'articleContent', 'authorName', 'sortOrder', 'status'
])
});
},
articleDetail: function (genealogyId, articleId) {
return request('GET', buildArticlePath(genealogyId, articleId));
},
updateArticle: function (genealogyId, articleId, body) {
return request('PUT', buildArticlePath(genealogyId, articleId), {
body: pickDefined(body, [
'categoryId', 'articleTitle', 'articleSummary', 'coverOssId',
'articleContent', 'authorName', 'sortOrder', 'status'
])
});
},
deleteArticle: function (genealogyId, articleId) {
return request('DELETE', buildArticlePath(genealogyId, articleId));
},
albums: function (genealogyId) {
return request('GET', buildAlbumCollectionPath(genealogyId));
},
createAlbum: function (genealogyId, body) {
return request('POST', buildAlbumCollectionPath(genealogyId), {
body: pickDefined(body, ['albumName', 'albumDesc', 'coverOssId', 'sortOrder', 'status'])
});
},
updateAlbum: function (genealogyId, albumId, body) {
return request('PUT', buildAlbumPath(genealogyId, albumId), {
body: pickDefined(body, ['albumName', 'albumDesc', 'coverOssId', 'sortOrder', 'status'])
});
},
albumPhotos: function (genealogyId, albumId) {
return request('GET', buildAlbumPhotoCollectionPath(genealogyId, albumId));
},
createAlbumPhoto: function (genealogyId, albumId, body) {
return request('POST', buildAlbumPhotoCollectionPath(genealogyId, albumId), {
body: pickDefined(body, [
'ossId', 'photoTitle', 'photoDesc', 'photographer',
'shootTime', 'sortOrder', 'status'
])
});
},
deleteAlbum: function (genealogyId, albumId) {
return request('DELETE', buildAlbumPath(genealogyId, albumId));
},
deleteAlbumPhoto: function (genealogyId, albumId, photoId) {
return request('DELETE', buildAlbumPhotoPath(genealogyId, albumId, photoId));
},
videos: function (genealogyId) {
return request('GET', buildVideoCollectionPath(genealogyId));
},
createVideo: function (genealogyId, body) {
return request('POST', buildVideoCollectionPath(genealogyId), { body: body });
},
videoDetail: function (genealogyId, videoId) {
return request('GET', buildVideoPath(genealogyId, videoId));
},
updateVideo: function (genealogyId, videoId, body) {
return request('PUT', buildVideoPath(genealogyId, videoId), { body: body });
},
deleteVideo: function (genealogyId, videoId) {
return request('DELETE', buildVideoPath(genealogyId, videoId));
},
replaceCeremonyInvitees: function (genealogyId, ceremonyId, body) {
return request('PUT', buildCeremonyPath(genealogyId, ceremonyId) + '/invitees', { body: body });
ceremonies: function (genealogyId) {
return request('GET', buildCeremonyCollectionPath(genealogyId));
},
createCeremony: function (genealogyId, body) {
return request('POST', buildCeremonyCollectionPath(genealogyId), {
body: pickDefined(body, [
'ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime',
'location', 'locationAddress', 'longitude', 'latitude',
'coverOssId', 'sortOrder', 'status'
])
});
},
ceremonyDetail: function (genealogyId, ceremonyId) {
return request('GET', buildCeremonyPath(genealogyId, ceremonyId));
},
updateCeremony: function (genealogyId, ceremonyId, body) {
return request('PUT', buildCeremonyPath(genealogyId, ceremonyId), {
body: pickDefined(body, [
'ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime',
'location', 'locationAddress', 'longitude', 'latitude',
'coverOssId', 'sortOrder', 'status'
])
});
},
ceremonyGifts: function (genealogyId, ceremonyId) {
return request('GET', buildCeremonyPath(genealogyId, ceremonyId) + '/gifts');
},
createCeremonyGift: function (genealogyId, ceremonyId, body) {
return request('POST', buildCeremonyPath(genealogyId, ceremonyId) + '/gifts', {
body: pickDefined(body, ['giverName', 'giftAmount', 'giftMessage'])
});
},
replaceCeremonyInvitees: function (genealogyId, ceremonyId, inviteeUserIds) {
return request('PUT', buildCeremonyPath(genealogyId, ceremonyId) + '/invitees', {
body: { inviteeUserIds: Array.isArray(inviteeUserIds) ? inviteeUserIds : [] }
});
},
ceremonyInvitations: function (genealogyId, ceremonyId) {
return request('GET', buildCeremonyPath(genealogyId, ceremonyId) + '/invitations');
@@ -622,6 +809,28 @@
},
deleteCeremonyGift: function (genealogyId, ceremonyId, giftId) {
return request('DELETE', buildCeremonyGiftPath(genealogyId, ceremonyId, giftId));
},
genealogyMembers: function (genealogyId) {
return request('GET', buildGenealogyMemberCollectionPath(genealogyId));
},
genealogyMemberOptions: function (genealogyId) {
return request('GET', buildGenealogyMemberCollectionPath(genealogyId) + '/options');
},
updateGenealogyMember: function (genealogyId, memberId, body) {
return request('PUT', buildGenealogyMemberPath(genealogyId, memberId), {
body: pickDefined(body, ['memberName', 'relationName', 'roleType', 'lineagePersonId'])
});
},
removeGenealogyMember: function (genealogyId, memberId) {
return request('DELETE', buildGenealogyMemberPath(genealogyId, memberId));
},
leaveGenealogy: function (genealogyId) {
return request('DELETE', buildGenealogyMemberCollectionPath(genealogyId) + '/me');
},
transferGenealogyOwner: function (genealogyId, body) {
return request('PUT', buildGenealogyMemberCollectionPath(genealogyId) + '/owner-transfer', {
body: pickDefined(body, ['targetMemberId'])
});
}
};
}