refactor(market): pom 坐标收口 market→member-api(facade 移入 member-api)+ tenant 依赖诚实化

承 bc-boundaries §三遗留(更深一层 BC 收口)。market-server pom 此前依赖
muse-module-member-server(可见 member 全部 internal),根因是 market 唯一引用的
member-server 类型 MarketAccountProjectionFacade(单方法读校验端口、无 DTO)。

- 接口 MarketAccountProjectionFacade 从 member-server application.account.facade
  移到 member-api api.account(对称写端口 MuseAccountRecordProjectionApi:member 定义端口、
  market 提供适配器 MarketAccountProjectionProvider、member-alone 回落 Unavailable*,
  符合 bc-boundaries §一"本域定义端口、他域提供适配器")
- market-server pom: muse-module-member-server → muse-module-member-api
- market-server 直接声明 muse-spring-boot-starter-biz-tenant(此前经 member-server 传递,
  收口暴露的隐性传递耦合 → 依赖诚实化)
- 顺带修预存潜伏 test 编译破损:AppMuseMarketPublishControllerTest 的 9 参
  PublishRecordItem 构造(record 自 commit a00c758 加 marketAssetId/appealStatus 变 10 参、
  test 未同步;因 CI 从未在本特性分支运行而潜伏)→补 marketAssetId=null

验收:member-api/member-server/market-server scoped reactor test-compile BUILD SUCCESS;
AccountMarketRecordServiceTest 8/0、MarketAccountProjectionFacadeConfigurationTest 3/0、
MarketAccountProjectionProviderTest 5/0、AppMuseMarketPublishControllerTest 10/0;
BcBoundaryArchTest 1/0(0 Architecture Violation)。同步 bc-boundaries §三 + 台账 §一 TODO。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-17 14:08:19 -07:00
parent 053caf192f
commit 4e9923ce68
14 changed files with 45 additions and 30 deletions

View File

@ -42,7 +42,7 @@
> 豁免一律**临时、显式、可见**(测试源码点名)。违例消除后必须从 `KNOWN_VIOLATION_EXEMPTIONS` 删除该类——**收紧即整改验收**。新违例不在豁免内,一律红。
> 遗留(更深一层 BC 收口,不在本次范围):market-server 的 pom 仍依赖 `muse-module-member-server`(可见 member 全部 internal);本次只消除 member.dal 的**代码 import**(ArchUnit 校验 import、不校验 pom 坐标),pom 坐标收口可登记后续。
> ✅ **pom 坐标收口已完成(2026-06-17)**:market-server pom 由 `muse-module-member-server`(可见 member 全部 internal)收窄为 `muse-module-member-api`。前置整改:market 唯一引用的 member-server 类型 `MarketAccountProjectionFacade`(单方法读校验端口、无 DTO)移到 member-api `api.account`——对称于写端口 `MuseAccountRecordProjectionApi`(member 定义端口、market 提供适配器 `MarketAccountProjectionProvider`、member-alone 回落 `UnavailableMarketAccountProjectionFacade`,符合 §一"本域定义端口、他域提供适配器");并把 market-server 此前**经 member-server 传递**获得的 `muse-spring-boot-starter-biz-tenant` 显式直接声明(依赖诚实化,收口暴露的隐性传递耦合)。验收:member-api/member-server/market-server scoped reactor `test-compile` BUILD SUCCESS;AccountMarketRecordServiceTest 8/0、MarketAccountProjectionFacadeConfigurationTest 3/0、MarketAccountProjectionProviderTest 5/0、AppMuseMarketPublishControllerTest 10/0;`BcBoundaryArchTest` 1/0(0 违例)。**顺带修**预存潜伏 test 编译破损:`AppMuseMarketPublishControllerTest` 的 9 参 `PublishRecordItem` 构造(record 自 commit a00c758 加 marketAssetId/appealStatus 变 10 参、该 test 未同步,因 CI 从未在本特性分支运行而潜伏)。
---

View File

@ -22,7 +22,7 @@
**market 写路径整改(2026-06-14,ultracode)**:member 暴露写端口 `MuseAccountRecordProjectionApi` + DTO(member-server 实现读写自有 DAL、tenantId 由实现侧从上下文注入防伪造、事务沿用调用方),market 5 类改消费端口、移除 member.dal 依赖 → `KNOWN_VIOLATION_EXEMPTIONS` 清空、BC 门全绿。**附带修复**:round-2 重构 `ContentKnowledgeWorkOwnerFacade` 时遗留的旧测试 `KnowledgeWorkOwnerFacadeTest`(仍断言旧 WorkMapper 行为)已删除,其装配守卫/兜底两用例并入 `ContentKnowledgeWorkOwnerFacadeTest`(5/0F)——此为 round-2 一处假绿(当时构建在平台时区用例处中止、未真正跑到 knowledge),现已补正。
**后续基建 TODO**:openapi-diff CI 首跑验证(🔧 检测逻辑+循环聚合已本地实证 2026-06-17:oasdiff v1.19.1 对 7 域真实契约 5/5 拦真破坏/放行安全演化,镜像钉版本+复现脚本已落地;**仅余 GHA 触发管道待真实 PR 首跑**)、`completed=测试证据`兜底(台账加 testFiles 字段 + 门禁)、`dev-baseline` 收敛进 rules、跨域 `.application` 边界门、market-server→member-server pom 坐标收口(本次只消除 member.dal 代码 import,见 bc-boundaries §三遗留)、**ai/平台预存红测试整改**(CI 接电后将暴露;均非本轮改动引入):`MuseAiTaskServiceTest` 缺 `eventPublishOutboxService`/`candidateReviewService` 桩致 11 例 NPE **✅ 已整改 40/40(2026-06-15,补桩 + review lenient,零产代码改动)**;`QiniuSmsClientTest` 时区硬编码 **✅ 已整改 5/5(2026-06-15,断言改用与生产同款 `LocalDateTimeUtil.of` 由系统时区换算→机器无关)**;`MuseAiEventPublishOutboxMapperTest` **✅ 真实 PG 实跑 5/5(2026-06-15,mini-infra PG 100.64.0.8:5433;跑法=`source infra.env` + `-DargLine='-DsocksProxyHost= -DsocksProxyPort='`;隔离 schema 自建自清、零代码改动——原"红"仅缺真 PG 环境)**。**至此 ai/平台预存红三项全清。**
**后续基建 TODO**:openapi-diff CI 首跑验证(🔧 检测逻辑+循环聚合已本地实证 2026-06-17:oasdiff v1.19.1 对 7 域真实契约 5/5 拦真破坏/放行安全演化,镜像钉版本+复现脚本已落地;**仅余 GHA 触发管道待真实 PR 首跑**)、`completed=测试证据`兜底(台账加 testFiles 字段 + 门禁)、`dev-baseline` 收敛进 rules、跨域 `.application` 边界门、market-server→member-server pom 坐标收口(✅ **已完成 2026-06-17**:接口 `MarketAccountProjectionFacade` 移入 member-api→market-server pom 收窄至 member-api + 直接声明 tenant starter[依赖诚实化];编译/4 测试 26 用例/BcBoundaryArchTest 0 违例全绿;顺带修预存潜伏 test 编译破损 AppMuseMarketPublishControllerTest;详见 bc-boundaries §三)、**ai/平台预存红测试整改**(CI 接电后将暴露;均非本轮改动引入):`MuseAiTaskServiceTest` 缺 `eventPublishOutboxService`/`candidateReviewService` 桩致 11 例 NPE **✅ 已整改 40/40(2026-06-15,补桩 + review lenient,零产代码改动)**;`QiniuSmsClientTest` 时区硬编码 **✅ 已整改 5/5(2026-06-15,断言改用与生产同款 `LocalDateTimeUtil.of` 由系统时区换算→机器无关)**;`MuseAiEventPublishOutboxMapperTest` **✅ 真实 PG 实跑 5/5(2026-06-15,mini-infra PG 100.64.0.8:5433;跑法=`source infra.env` + `-DargLine='-DsocksProxyHost= -DsocksProxyPort='`;隔离 schema 自建自清、零代码改动——原"红"仅缺真 PG 环境)**。**至此 ai/平台预存红三项全清。**
---

View File

@ -29,9 +29,15 @@
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>muse-module-member-server</artifactId>
<artifactId>muse-module-member-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- market-server 直接使用 tenant 框架类型(TenantBaseDO/TenantContextHolder/TenantIgnore);
此前经 member-server 传递获得,pom 收口去 member-server 后须显式直接声明(依赖诚实化)。 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>muse-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>muse-spring-boot-starter-security</artifactId>

View File

@ -5,7 +5,7 @@ import cn.iocoder.muse.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketAccountProjectionDO;
import cn.iocoder.muse.module.member.api.account.MuseAccountRecordProjectionApi;
import cn.iocoder.muse.module.member.api.account.dto.MuseAccountRecordProjectionSaveReqDTO;
import cn.iocoder.muse.module.member.application.account.facade.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.api.account.MarketAccountProjectionFacade;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -17,7 +17,7 @@ import static cn.iocoder.muse.module.market.enums.ErrorCodeConstants.MARKET_ACCO
/**
* Market 提供给 member Account 的真实投影 provider。
*
* <p>该 Bean 实现 member 模块既有 {@link MarketAccountProjectionFacade}(该端口在 member-server, 非 .dal, 合规),
* <p>该 Bean 实现 member 模块既有 {@link MarketAccountProjectionFacade}(该端口在 member-api, 非 .dal, 合规),
* 用于替换默认 fail-closed unavailable facade,让读侧"投影已接入"判定不变。Account 查询仍只读 member 的
* muse_account_record_projection。</p>
*

View File

@ -6,7 +6,7 @@ import cn.iocoder.muse.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketAccountProjectionDO;
import cn.iocoder.muse.module.member.api.account.MuseAccountRecordProjectionApi;
import cn.iocoder.muse.module.member.api.account.dto.MuseAccountRecordProjectionSaveReqDTO;
import cn.iocoder.muse.module.member.application.account.facade.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.api.account.MarketAccountProjectionFacade;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;

View File

@ -185,7 +185,7 @@ class AppMuseMarketPublishControllerTest extends BaseMockitoUnitTest {
setLoginUser(9001L);
when(publishService.listMyPublishRecords(9001L, "1", 1, 20, "work", "draft"))
.thenReturn(new PageResult<>(List.of(new MarketPublishService.PublishRecordItem("8101",
"可发布作品", "work", "2", "draft", "run_check", null, null, "publisher_only")), 1L));
"可发布作品", "work", "2", "draft", "run_check", null, null, "publisher_only", null)), 1L));
mockMvc.perform(get("/muse/marketplace/my-publish-records")
.header("X-API-Version", "1")

View File

@ -0,0 +1,25 @@
package cn.iocoder.muse.module.member.api.account;
/**
* Account 市场记录投影可用性边界(BC 对外 SPI 契约)。
*
* <p>Purchase / License / Publish records 在 Account 域只能读取
* {@code muse_account_record_projection}。该 Facade 用来表达外部 Market 投影同步是否已经接入,
* 防止 Account 在上游投影不可用时返回伪造的空数据。</p>
*
* <p>端口归属 member(读侧 fail-closed 判定, 由 member-server 的 {@code AccountMarketRecordServiceImpl} 消费);
* 适配器由 Market owner 提供(market-server 的 {@code MarketAccountProjectionProvider} 实现),
* member-alone(无 Market 接入)时回落 member-server 的 {@code UnavailableMarketAccountProjectionFacade}。
* 置于 member-api 以便 market 在【不依赖 member-server internal】前提下提供适配器(对称于写端口
* {@code MuseAccountRecordProjectionApi} 的布局, 使 market-server 的 pom 仅需依赖 member-api)。
* 与【写】端口职责不同、刻意保持分离。</p>
*/
public interface MarketAccountProjectionFacade {
/**
* 确认指定记录类型的 Market 投影已经可用。
*
* @param recordType Account 投影记录类型,只允许 service 内部传入固定值
*/
void requireProjectionAvailable(String recordType);
}

View File

@ -17,8 +17,8 @@ import cn.iocoder.muse.module.member.api.account.dto.MuseAccountRecordProjection
* (由 ArchUnit BcBoundaryArchTest 机械约束)。</p>
*
* <p>注:故意不把 member 既有【读校验】端口 {@code MarketAccountProjectionFacade.requireProjectionAvailable}
* 合并进来 —— 那是 member 读侧 fail-closed 判定(member-server 内部用, 见 AccountMarketRecordServiceImpl),
* 与本【写】端口职责不同, 保持分离。</p>
* 合并进来 —— 那是 member 读侧 fail-closed 判定(同置本 member-api 包, 由 AccountMarketRecordServiceImpl 消费、
* Market 适配器实现), 与本【写】端口职责不同, 保持分离。</p>
*/
public interface MuseAccountRecordProjectionApi {

View File

@ -1,6 +1,6 @@
package cn.iocoder.muse.autoconfigure.member.account;
import cn.iocoder.muse.module.member.application.account.facade.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.api.account.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.application.account.facade.UnavailableMarketAccountProjectionFacade;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

View File

@ -3,7 +3,7 @@ package cn.iocoder.muse.module.member.application.account;
import cn.iocoder.muse.framework.common.exception.ServiceException;
import cn.iocoder.muse.framework.common.pojo.PageParam;
import cn.iocoder.muse.framework.common.pojo.PageResult;
import cn.iocoder.muse.module.member.application.account.facade.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.api.account.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.controller.account.vo.AccountPageResult;
import cn.iocoder.muse.module.member.controller.admin.account.vo.AdminPurchaseRecordRespVO;
import cn.iocoder.muse.module.member.controller.app.account.vo.AppLicenseRecordRespVO;

View File

@ -1,18 +0,0 @@
package cn.iocoder.muse.module.member.application.account.facade;
/**
* Account 市场记录投影可用性边界。
*
* <p>Purchase / License / Publish records 在 Account 域只能读取
* {@code muse_account_record_projection}。该 Facade 用来表达外部 Market 投影同步是否已经接入,
* 防止 Account 在上游投影不可用时返回伪造的空数据。</p>
*/
public interface MarketAccountProjectionFacade {
/**
* 确认指定记录类型的 Market 投影已经可用。
*
* @param recordType Account 投影记录类型,只允许 service 内部传入固定值
*/
void requireProjectionAvailable(String recordType);
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.muse.module.member.application.account.facade;
import cn.iocoder.muse.framework.common.exception.ServiceException;
import cn.iocoder.muse.module.member.api.account.MarketAccountProjectionFacade;
import static cn.iocoder.muse.module.member.enums.ErrorCodeConstants.ACCOUNT_MARKET_PROJECTION_UNAVAILABLE;

View File

@ -4,7 +4,7 @@ import cn.iocoder.muse.framework.common.exception.ServiceException;
import cn.iocoder.muse.framework.common.pojo.PageParam;
import cn.iocoder.muse.framework.common.pojo.PageResult;
import cn.iocoder.muse.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.muse.module.member.application.account.facade.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.api.account.MarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.application.account.facade.UnavailableMarketAccountProjectionFacade;
import cn.iocoder.muse.module.member.controller.account.vo.AccountPageResult;
import cn.iocoder.muse.module.member.controller.admin.account.vo.AdminPurchaseRecordRespVO;

View File

@ -2,6 +2,7 @@ package cn.iocoder.muse.module.member.application.account.facade;
import cn.iocoder.muse.autoconfigure.member.account.MarketAccountProjectionFacadeAutoConfiguration;
import cn.iocoder.muse.framework.common.exception.ServiceException;
import cn.iocoder.muse.module.member.api.account.MarketAccountProjectionFacade;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfiguration;