feat(meta): D1-1 MetaSchemaQueryApi 对外 schema 查询端口 + 单测[P1]

D1(work 绑定 schema)落地链第一环:消费域 content 经此端口发现/校验可用 schema,不跨 BC 查 meta.dal(BcBoundaryArchTest 门禁)。meta-api 新增 MetaSchemaQueryApi(listActiveSchemasByTargetType 供 work 创建选择 + getActiveSchemaById 供绑定写入前校验)+ MetaSchemaBriefDTO(最小投影);meta-server MetaSchemaQueryApiImpl(@Service 读自有 schemaMapper,仅 active 可作绑定目标)。

验证:单测 5/5(targetType 过滤/全列/按 id 查/非 active null/不存在或 null id)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-21 00:42:59 -07:00
parent a39c630940
commit be907fbb15
4 changed files with 162 additions and 0 deletions

View File

@ -0,0 +1,13 @@
package cn.iocoder.muse.module.meta.api.schema;
/**
* Meta Schema 摘要(对外最小投影:标识 + 展示名 + 目标类型,不含内部版本/字段细节)。
*
* <p>供消费域(如 content work 创建)发现可用 schema、按 id 校验绑定。
*/
public record MetaSchemaBriefDTO(
Long id,
String schemaKey,
String displayName,
String targetType) {
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.muse.module.meta.api.schema;
import java.util.List;
/**
* Meta Schema 对外查询端口(meta-api,供消费域发现/校验可用 schema)。
*
* <p>消费域(如 content)不能跨 BC 查 meta.dal(BcBoundaryArchTest 门禁),经此端口获取 schema 标识用于绑定:
* work 创建时拉取可选 schema、写入前按 id 校验有效。
*/
public interface MetaSchemaQueryApi {
/** 列出指定 targetType 的 active schema(targetType 为 null 则列全部 active),供创建时选择。 */
List<MetaSchemaBriefDTO> listActiveSchemasByTargetType(String targetType);
/** 按 id 查 active schema;不存在 / 非 active / id 为 null 均返回 null(供绑定写入前校验)。 */
MetaSchemaBriefDTO getActiveSchemaById(Long schemaId);
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.muse.module.meta.api.schema;
import cn.iocoder.muse.module.meta.dal.dataobject.MetaSchemaDO;
import cn.iocoder.muse.module.meta.dal.mysql.MetaSchemaMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
/**
* {@link MetaSchemaQueryApi} 实现(meta-server,读 meta 自有 DAL,合规跨 BC 接缝)。
*/
@Service
public class MetaSchemaQueryApiImpl implements MetaSchemaQueryApi {
private static final String STATUS_ACTIVE = "active";
@Resource
private MetaSchemaMapper schemaMapper;
@Override
public List<MetaSchemaBriefDTO> listActiveSchemasByTargetType(String targetType) {
return schemaMapper.selectListByStatus(STATUS_ACTIVE).stream()
.filter(schema -> targetType == null || Objects.equals(schema.getTargetType(), targetType))
.map(this::toBrief)
.toList();
}
@Override
public MetaSchemaBriefDTO getActiveSchemaById(Long schemaId) {
if (schemaId == null) {
return null;
}
MetaSchemaDO schema = schemaMapper.selectById(schemaId);
// 仅 active schema 可作绑定目标:不存在或非 active 一律视为无效(返回 null,由调用方拒绝)。
if (schema == null || !STATUS_ACTIVE.equals(schema.getStatus())) {
return null;
}
return toBrief(schema);
}
private MetaSchemaBriefDTO toBrief(MetaSchemaDO schema) {
return new MetaSchemaBriefDTO(schema.getId(), schema.getSchemaKey(),
schema.getDisplayName(), schema.getTargetType());
}
}

View File

@ -0,0 +1,84 @@
package cn.iocoder.muse.module.meta.api.schema;
import cn.iocoder.muse.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.muse.module.meta.dal.dataobject.MetaSchemaDO;
import cn.iocoder.muse.module.meta.dal.mysql.MetaSchemaMapper;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.when;
/**
* MetaSchemaQueryApi 实现单测:按 targetType 过滤 active、按 id 校验(非 active/不存在/null 返回 null)。
*/
class MetaSchemaQueryApiImplTest extends BaseMockitoUnitTest {
@InjectMocks
private MetaSchemaQueryApiImpl queryApi;
@Mock
private MetaSchemaMapper schemaMapper;
@Test
void should_listActiveSchemasFilteredByTargetType() {
when(schemaMapper.selectListByStatus("active")).thenReturn(List.of(
schema(1L, "work_core", "作品核心", "work"),
schema(2L, "kb_core", "知识库", "knowledge_base")));
List<MetaSchemaBriefDTO> result = queryApi.listActiveSchemasByTargetType("work");
assertEquals(1, result.size());
assertEquals("work_core", result.getFirst().schemaKey());
assertEquals(1L, result.getFirst().id());
}
@Test
void should_returnAllActive_whenTargetTypeNull() {
when(schemaMapper.selectListByStatus("active")).thenReturn(List.of(
schema(1L, "work_core", "作品核心", "work"),
schema(2L, "kb_core", "知识库", "knowledge_base")));
assertEquals(2, queryApi.listActiveSchemasByTargetType(null).size());
}
@Test
void should_getActiveSchemaById() {
when(schemaMapper.selectById(1L)).thenReturn(schema(1L, "work_core", "作品核心", "work"));
MetaSchemaBriefDTO dto = queryApi.getActiveSchemaById(1L);
assertEquals("work_core", dto.schemaKey());
assertEquals("work", dto.targetType());
}
@Test
void should_returnNull_whenSchemaNotActive() {
MetaSchemaDO draft = schema(1L, "work_core", "作品核心", "work");
draft.setStatus("draft");
when(schemaMapper.selectById(1L)).thenReturn(draft);
assertNull(queryApi.getActiveSchemaById(1L));
}
@Test
void should_returnNull_whenSchemaNotFoundOrNullId() {
when(schemaMapper.selectById(99L)).thenReturn(null);
assertNull(queryApi.getActiveSchemaById(99L));
assertNull(queryApi.getActiveSchemaById(null));
}
private MetaSchemaDO schema(Long id, String key, String displayName, String targetType) {
MetaSchemaDO schema = new MetaSchemaDO();
schema.setId(id);
schema.setSchemaKey(key);
schema.setDisplayName(displayName);
schema.setTargetType(targetType);
schema.setStatus("active");
return schema;
}
}