fix(p1r): 对齐 Account 市场记录状态过滤
This commit is contained in:
parent
ac81708f61
commit
68313e6efe
@ -56,24 +56,24 @@ public class AccountMarketRecordServiceImpl implements AccountMarketRecordServic
|
||||
public AccountPageResult<AdminPurchaseRecordRespVO> adminListPurchaseRecords(PageParam pageParam,
|
||||
Long accountUserId,
|
||||
String status) {
|
||||
String normalizedStatus = normalizeFilter(status, ADMIN_PURCHASE_STATUSES, "status");
|
||||
List<String> storageStatuses = toAdminPurchaseStorageStatuses(status);
|
||||
marketProjectionFacade.requireProjectionAvailable(RECORD_TYPE_PURCHASE);
|
||||
if (NON_NUMERIC_USER_ID_MARKER.equals(accountUserId)) {
|
||||
// 非数字 userId 代表合同 ID 当前无法映射到 member_user.id;保留过滤语义,不能退化成全量查询。
|
||||
return AccountPageResult.of(Collections.emptyList(), 0L, pageParam);
|
||||
}
|
||||
requireUserIfPresent(accountUserId);
|
||||
return pageByProjection(pageParam, RECORD_TYPE_PURCHASE, accountUserId, normalizedStatus, null,
|
||||
return pageByProjection(pageParam, RECORD_TYPE_PURCHASE, accountUserId, storageStatuses, null,
|
||||
AccountConvert.INSTANCE::convertAdminPurchaseRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountPageResult<AppPurchaseRecordRespVO> appListPurchases(Long accountUserId, PageParam pageParam,
|
||||
String status) {
|
||||
String normalizedStatus = normalizeFilter(status, APP_PURCHASE_STATUSES, "status");
|
||||
List<String> storageStatuses = toAppPurchaseStorageStatuses(status);
|
||||
marketProjectionFacade.requireProjectionAvailable(RECORD_TYPE_PURCHASE);
|
||||
requireUser(accountUserId);
|
||||
return pageByProjection(pageParam, RECORD_TYPE_PURCHASE, accountUserId, normalizedStatus, null,
|
||||
return pageByProjection(pageParam, RECORD_TYPE_PURCHASE, accountUserId, storageStatuses, null,
|
||||
AccountConvert.INSTANCE::convertAppPurchaseRecord);
|
||||
}
|
||||
|
||||
@ -81,10 +81,10 @@ public class AccountMarketRecordServiceImpl implements AccountMarketRecordServic
|
||||
public AccountPageResult<AppLicenseRecordRespVO> appListLicenses(Long accountUserId, PageParam pageParam,
|
||||
String assetType, String licenseStatus) {
|
||||
String normalizedAssetType = normalizeFilter(assetType, ASSET_TYPES, "assetType");
|
||||
String normalizedStatus = normalizeFilter(licenseStatus, LICENSE_STATUSES, "licenseStatus");
|
||||
List<String> storageStatuses = toSingleStorageStatus(licenseStatus, LICENSE_STATUSES, "licenseStatus");
|
||||
marketProjectionFacade.requireProjectionAvailable(RECORD_TYPE_LICENSE);
|
||||
requireUser(accountUserId);
|
||||
return pageByProjection(pageParam, RECORD_TYPE_LICENSE, accountUserId, normalizedStatus, normalizedAssetType,
|
||||
return pageByProjection(pageParam, RECORD_TYPE_LICENSE, accountUserId, storageStatuses, normalizedAssetType,
|
||||
AccountConvert.INSTANCE::convertAppLicenseRecord);
|
||||
}
|
||||
|
||||
@ -92,19 +92,19 @@ public class AccountMarketRecordServiceImpl implements AccountMarketRecordServic
|
||||
public AccountPageResult<AppPublishRecordRespVO> appListPublishRecords(Long accountUserId, PageParam pageParam,
|
||||
String assetType, String reviewStatus) {
|
||||
String normalizedAssetType = normalizeFilter(assetType, ASSET_TYPES, "assetType");
|
||||
String normalizedStatus = normalizeFilter(reviewStatus, REVIEW_STATUSES, "reviewStatus");
|
||||
List<String> storageStatuses = toPublishStorageStatuses(reviewStatus);
|
||||
marketProjectionFacade.requireProjectionAvailable(RECORD_TYPE_PUBLISH);
|
||||
requireUser(accountUserId);
|
||||
return pageByProjection(pageParam, RECORD_TYPE_PUBLISH, accountUserId, normalizedStatus, normalizedAssetType,
|
||||
return pageByProjection(pageParam, RECORD_TYPE_PUBLISH, accountUserId, storageStatuses, normalizedAssetType,
|
||||
AccountConvert.INSTANCE::convertAppPublishRecord);
|
||||
}
|
||||
|
||||
private <T> AccountPageResult<T> pageByProjection(PageParam pageParam, String recordType, Long accountUserId,
|
||||
String status, String assetType,
|
||||
List<String> storageStatuses, String assetType,
|
||||
Function<AccountRecordProjectionDO, T> converter) {
|
||||
// recordType 只能由各入口硬编码传入,不能暴露给请求参数,避免跨类型读取其它投影。
|
||||
PageResult<AccountRecordProjectionDO> page = recordProjectionMapper.selectPageByFilters(pageParam,
|
||||
recordType, accountUserId, status, assetType);
|
||||
recordType, accountUserId, storageStatuses, assetType);
|
||||
List<T> list = page.getList() == null ? Collections.emptyList()
|
||||
: page.getList().stream().map(converter).toList();
|
||||
return AccountPageResult.of(list, page.getTotal(), pageParam);
|
||||
@ -121,6 +121,47 @@ public class AccountMarketRecordServiceImpl implements AccountMarketRecordServic
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private List<String> toAdminPurchaseStorageStatuses(String status) {
|
||||
String normalizedStatus = normalizeFilter(status, ADMIN_PURCHASE_STATUSES, "status");
|
||||
if (normalizedStatus == null) {
|
||||
return null;
|
||||
}
|
||||
if ("processing".equals(normalizedStatus)) {
|
||||
// Admin OpenAPI 没有 external_pending;所有会响应为 processing 的上游状态都必须一起命中过滤。
|
||||
return List.of("processing", "pending", "external_pending");
|
||||
}
|
||||
return List.of(normalizedStatus);
|
||||
}
|
||||
|
||||
private List<String> toAppPurchaseStorageStatuses(String status) {
|
||||
String normalizedStatus = normalizeFilter(status, APP_PURCHASE_STATUSES, "status");
|
||||
if (normalizedStatus == null) {
|
||||
return null;
|
||||
}
|
||||
if ("external_pending".equals(normalizedStatus)) {
|
||||
// Market purchase 投影历史上可能写 pending;App 合同把它归一为 external_pending。
|
||||
return List.of("external_pending", "pending");
|
||||
}
|
||||
return List.of(normalizedStatus);
|
||||
}
|
||||
|
||||
private List<String> toPublishStorageStatuses(String reviewStatus) {
|
||||
String normalizedStatus = normalizeFilter(reviewStatus, REVIEW_STATUSES, "reviewStatus");
|
||||
if (normalizedStatus == null) {
|
||||
return null;
|
||||
}
|
||||
if ("under_review".equals(normalizedStatus)) {
|
||||
// muse_market_publish_request 的默认状态是 pending_review;旧投影可能仍使用 pending/processing。
|
||||
return List.of("under_review", "pending_review", "pending", "processing");
|
||||
}
|
||||
return List.of(normalizedStatus);
|
||||
}
|
||||
|
||||
private List<String> toSingleStorageStatus(String status, Set<String> allowedValues, String fieldName) {
|
||||
String normalizedStatus = normalizeFilter(status, allowedValues, fieldName);
|
||||
return normalizedStatus == null ? null : List.of(normalizedStatus);
|
||||
}
|
||||
|
||||
private void requireUser(Long accountUserId) {
|
||||
if (accountUserId == null || memberUserMapper.selectById(accountUserId) == null) {
|
||||
throw new ServiceException(ACCOUNT_USER_NOT_EXISTS);
|
||||
|
||||
@ -321,7 +321,7 @@ public interface AccountConvert {
|
||||
}
|
||||
|
||||
default String toReviewStatus(String status) {
|
||||
if ("pending".equals(status) || "processing".equals(status)) {
|
||||
if ("pending_review".equals(status) || "pending".equals(status) || "processing".equals(status)) {
|
||||
return "under_review";
|
||||
}
|
||||
return statusOrFallback(status, REVIEW_STATUSES, "needs_supplement");
|
||||
|
||||
@ -30,13 +30,13 @@ public interface AccountRecordProjectionMapper extends BaseMapperX<AccountRecord
|
||||
}
|
||||
|
||||
default PageResult<AccountRecordProjectionDO> selectPageByFilters(PageParam pageParam, String recordType,
|
||||
Long accountUserId, String sourceStatus,
|
||||
Long accountUserId, List<String> sourceStatuses,
|
||||
String assetType) {
|
||||
LambdaQueryWrapperX<AccountRecordProjectionDO> query = new LambdaQueryWrapperX<AccountRecordProjectionDO>()
|
||||
// record_type 是 Account 侧隔离 purchase/license/publish 的硬边界,不能由请求方自由控制。
|
||||
.eq(AccountRecordProjectionDO::getRecordType, recordType)
|
||||
.eqIfPresent(AccountRecordProjectionDO::getAccountUserId, accountUserId)
|
||||
.eqIfPresent(AccountRecordProjectionDO::getSourceStatus, sourceStatus)
|
||||
.inIfPresent(AccountRecordProjectionDO::getSourceStatus, sourceStatuses)
|
||||
.orderByDesc(AccountRecordProjectionDO::getOccurredAt)
|
||||
.orderByDesc(AccountRecordProjectionDO::getCreateTime);
|
||||
if (StringUtils.hasText(assetType)) {
|
||||
|
||||
@ -51,7 +51,7 @@ class AccountMarketRecordServiceTest extends BaseMockitoUnitTest {
|
||||
void should_adminListPurchaseRecords_queryPurchaseProjectionAndMaskExternalRef() {
|
||||
PageParam pageParam = page(2, 20);
|
||||
when(memberUserMapper.selectById(1001L)).thenReturn(MemberUserDO.builder().id(1001L).build());
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "purchase", 1001L, "completed", null))
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "purchase", 1001L, List.of("completed"), null))
|
||||
.thenReturn(new PageResult<>(List.of(purchaseProjection("purchase-1", 1001L,
|
||||
"EXT-ORDER-SECRET-123456", "completed")), 3L));
|
||||
|
||||
@ -68,14 +68,31 @@ class AccountMarketRecordServiceTest extends BaseMockitoUnitTest {
|
||||
assertEquals("completed", record.getStatus());
|
||||
assertFalse(record.getExternalOrderRef().contains("SECRET"));
|
||||
verify(marketProjectionFacade).requireProjectionAvailable("purchase");
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "purchase", 1001L, "completed", null);
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "purchase", 1001L, List.of("completed"), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_adminListPurchaseRecords_mapProcessingFilterToRawPendingProcessingStatuses() {
|
||||
PageParam pageParam = page(1, 20);
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "purchase", null,
|
||||
List.of("processing", "pending", "external_pending"), null))
|
||||
.thenReturn(new PageResult<>(List.of(purchaseProjection("purchase-pending", 1001L,
|
||||
"EXT-ORDER-SECRET-222222", "pending")), 1L));
|
||||
|
||||
AccountPageResult<AdminPurchaseRecordRespVO> page =
|
||||
service.adminListPurchaseRecords(pageParam, null, "processing");
|
||||
|
||||
assertEquals("processing", page.getList().get(0).getStatus());
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "purchase", null,
|
||||
List.of("processing", "pending", "external_pending"), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_appListPurchases_useLoginOwnerAndPurchaseTypeOnly() {
|
||||
PageParam pageParam = page(1, 20);
|
||||
when(memberUserMapper.selectById(1001L)).thenReturn(MemberUserDO.builder().id(1001L).build());
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "purchase", 1001L, "external_pending", null))
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "purchase", 1001L,
|
||||
List.of("external_pending", "pending"), null))
|
||||
.thenReturn(new PageResult<>(List.of(purchaseProjection("purchase-2", 1001L,
|
||||
"EXT-ORDER-SECRET-999999", "pending")), 1L));
|
||||
|
||||
@ -86,15 +103,17 @@ class AccountMarketRecordServiceTest extends BaseMockitoUnitTest {
|
||||
assertEquals("purchase-2", record.getRecordId());
|
||||
assertEquals("external_pending", record.getStatus());
|
||||
assertFalse(record.getExternalOrderRef().contains("SECRET"));
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "purchase", 1001L, "external_pending", null);
|
||||
verify(recordProjectionMapper, never()).selectPageByFilters(pageParam, "license", 1001L, "external_pending", null);
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "purchase", 1001L,
|
||||
List.of("external_pending", "pending"), null);
|
||||
verify(recordProjectionMapper, never()).selectPageByFilters(pageParam, "license", 1001L,
|
||||
List.of("external_pending", "pending"), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_appListLicenses_filterByAssetAndStatusWithoutMutatingLicenseStatus() {
|
||||
PageParam pageParam = page(1, 20);
|
||||
when(memberUserMapper.selectById(1001L)).thenReturn(MemberUserDO.builder().id(1001L).build());
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "license", 1001L, "bound", "agent"))
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "license", 1001L, List.of("bound"), "agent"))
|
||||
.thenReturn(new PageResult<>(List.of(licenseProjection()), 1L));
|
||||
|
||||
AccountPageResult<AppLicenseRecordRespVO> page =
|
||||
@ -106,14 +125,14 @@ class AccountMarketRecordServiceTest extends BaseMockitoUnitTest {
|
||||
assertEquals("bound", record.getLicenseStatus());
|
||||
assertEquals("allowed", record.getActionPolicy().getInstallPolicy());
|
||||
assertEquals(List.of("source_version_changed"), record.getActionPolicy().getRecheckReasons());
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "license", 1001L, "bound", "agent");
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "license", 1001L, List.of("bound"), "agent");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_appListPublishRecords_filterByAssetAndReviewStatus() {
|
||||
PageParam pageParam = page(1, 20);
|
||||
when(memberUserMapper.selectById(1001L)).thenReturn(MemberUserDO.builder().id(1001L).build());
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "publish", 1001L, "submitted", "work"))
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "publish", 1001L, List.of("submitted"), "work"))
|
||||
.thenReturn(new PageResult<>(List.of(publishProjection()), 1L));
|
||||
|
||||
AccountPageResult<AppPublishRecordRespVO> page =
|
||||
@ -124,7 +143,23 @@ class AccountMarketRecordServiceTest extends BaseMockitoUnitTest {
|
||||
assertEquals("work", record.getAssetType());
|
||||
assertEquals("submitted", record.getReviewStatus());
|
||||
assertEquals("not_listed", record.getMarketStatus());
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "publish", 1001L, "submitted", "work");
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "publish", 1001L, List.of("submitted"), "work");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_appListPublishRecords_mapUnderReviewFilterToRawPendingReviewStatuses() {
|
||||
PageParam pageParam = page(1, 20);
|
||||
when(memberUserMapper.selectById(1001L)).thenReturn(MemberUserDO.builder().id(1001L).build());
|
||||
when(recordProjectionMapper.selectPageByFilters(pageParam, "publish", 1001L,
|
||||
List.of("under_review", "pending_review", "pending", "processing"), "work"))
|
||||
.thenReturn(new PageResult<>(List.of(publishProjection("pending_review")), 1L));
|
||||
|
||||
AccountPageResult<AppPublishRecordRespVO> page =
|
||||
service.appListPublishRecords(1001L, pageParam, "work", "under_review");
|
||||
|
||||
assertEquals("under_review", page.getList().get(0).getReviewStatus());
|
||||
verify(recordProjectionMapper).selectPageByFilters(pageParam, "publish", 1001L,
|
||||
List.of("under_review", "pending_review", "pending", "processing"), "work");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -192,12 +227,16 @@ class AccountMarketRecordServiceTest extends BaseMockitoUnitTest {
|
||||
}
|
||||
|
||||
private static AccountRecordProjectionDO publishProjection() {
|
||||
return publishProjection("submitted");
|
||||
}
|
||||
|
||||
private static AccountRecordProjectionDO publishProjection(String sourceStatus) {
|
||||
AccountRecordProjectionDO projection = AccountRecordProjectionDO.builder()
|
||||
.accountUserId(1001L)
|
||||
.recordType("publish")
|
||||
.recordId("publish-1")
|
||||
.title("Work Pro")
|
||||
.sourceStatus("submitted")
|
||||
.sourceStatus(sourceStatus)
|
||||
.projectionSnapshot("{\"assetType\":\"work\",\"version\":\"0.1.0\","
|
||||
+ "\"marketStatus\":\"not_listed\",\"blockingReason\":\"等待审核\"}")
|
||||
.build();
|
||||
|
||||
@ -2,6 +2,7 @@ package cn.iocoder.muse.module.member.convert.account;
|
||||
|
||||
import cn.iocoder.muse.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.muse.module.member.controller.admin.account.vo.AdminPurchaseRecordRespVO;
|
||||
import cn.iocoder.muse.module.member.controller.app.account.vo.AppPublishRecordRespVO;
|
||||
import cn.iocoder.muse.module.member.controller.app.account.vo.AppProfileRespVO;
|
||||
import cn.iocoder.muse.module.member.controller.app.account.vo.SecurityEventDetailRespVO;
|
||||
import cn.iocoder.muse.module.member.dal.dataobject.account.AccountProfileDO;
|
||||
@ -110,6 +111,25 @@ class AccountConvertTest {
|
||||
assertEquals("Shanghai", vo.getDeviceInfo().getLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_convertPublishPendingReviewToOpenApiUnderReview() {
|
||||
AccountRecordProjectionDO projection = AccountRecordProjectionDO.builder()
|
||||
.accountUserId(1001L)
|
||||
.recordType("publish")
|
||||
.recordId("publish-1")
|
||||
.title("Work Pro")
|
||||
.sourceStatus("pending_review")
|
||||
.projectionSnapshot("{\"assetType\":\"work\",\"version\":\"0.1.0\",\"marketStatus\":\"not_listed\"}")
|
||||
.build();
|
||||
projection.setOccurredAt(LocalDateTime.of(2026, 5, 28, 12, 0));
|
||||
|
||||
AppPublishRecordRespVO vo = convert.convertAppPublishRecord(projection);
|
||||
|
||||
assertEquals("publish-1", vo.getRecordId());
|
||||
assertEquals("under_review", vo.getReviewStatus());
|
||||
assertEquals("not_listed", vo.getMarketStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_applySecurityAcknowledgementFields_when_ackExists() {
|
||||
LocalDateTime acknowledgedAt = LocalDateTime.of(2026, 5, 28, 13, 0);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user