This commit is contained in:
2026-08-31 10:17:03 +08:00
parent aa583efa4a
commit bf1dc1f209
17 changed files with 354 additions and 142 deletions
@@ -0,0 +1,47 @@
package com.baiyue.firstleg.loading.coload.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.InternalBatchQueryRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.InternalOrderGroupInfo;
import com.baiyue.firstleg.loading.coload.service.ColoadGroupService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.List;
/** Order 聚合使用的内部同柜组端口,不是 Platform 页面协议。 */
@RestController
@RequestMapping("/internal/firstleg/v1/coload-groups")
public class InternalColoadGroupController {
private final ColoadGroupService service;
@Value("${baiyue.security.internal-service-token}")
private String expectedToken;
public InternalColoadGroupController(ColoadGroupService service) {
this.service = service;
}
@PostMapping("/batch-query")
public ApiResponse<List<InternalOrderGroupInfo>> batchQuery(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody InternalBatchQueryRequest request) {
requireToken(token);
return ApiResponse.success(service.queryInternalByOrderIds(request.orderIds()));
}
private void requireToken(String token) {
if (token == null || !MessageDigest.isEqual(token.getBytes(StandardCharsets.UTF_8),
expectedToken.getBytes(StandardCharsets.UTF_8))) {
throw new BizException(ResultCode.FORBIDDEN, "内部服务令牌无效");
}
}
}
@@ -24,6 +24,12 @@ public final class ColoadGroupModels {
) {
}
/** 仅供 Order 服务聚合使用,不属于 Platform 页面识别契约。 */
public record InternalBatchQueryRequest(
@NotEmpty @Size(max = 200) List<@NotNull @Min(1) Long> orderIds
) {
}
public record Member(
long tenantId,
long orderId,
@@ -74,4 +80,8 @@ public final class ColoadGroupModels {
memberCount = members.size();
}
}
/** 内部服务聚合投影;技术标识不得透传为页面主展示。 */
public record InternalOrderGroupInfo(long orderId, GroupSnapshot group) {
}
}
@@ -108,6 +108,16 @@ public interface ColoadGroupMapper {
""")
List<MemberCodeRow> effectiveMemberCodes(@Param("groupId") long groupId);
@Select({"<script>", """
SELECT member.order_id orderId,member.group_id groupId
FROM fl_coload_group_member member
JOIN fl_coload_group group_row ON group_row.id=member.group_id
WHERE member.order_id IN
""", "<foreach collection='orderIds' item='orderId' open='(' separator=',' close=')'>",
"#{orderId}", "</foreach>",
"AND group_row.group_status&lt;&gt;'CANCELLED' ORDER BY member.order_id,group_row.created_at DESC", "</script>"})
List<OrderGroupLinkRow> linksByOrderIds(@Param("orderIds") List<Long> orderIds);
@Update("""
UPDATE fl_coload_group
SET group_status='CANCELLED',cancelled_by=#{operatorId},
@@ -60,6 +60,13 @@ public final class ColoadPersistenceRows {
private String inboundCode;
}
@Getter
@Setter
public static class OrderGroupLinkRow {
private Long orderId;
private Long groupId;
}
@Getter
@Setter
public static class CommandRow {
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.GroupSnapshot;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.Member;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BusinessMember;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.BusinessSummary;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.InternalOrderGroupInfo;
import com.baiyue.firstleg.loading.coload.persistence.ColoadGroupMapper;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.CommandRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.GroupRow;
@@ -114,6 +115,20 @@ public class ColoadGroupService implements ColoadGroupAssignmentPort {
return new BusinessSummary(status, members.size(), members);
}
@Transactional(readOnly = true)
public List<InternalOrderGroupInfo> queryInternalByOrderIds(List<Long> requestedOrderIds) {
List<Long> orderIds = normalizedOrderIds(requestedOrderIds, 1, 200);
Map<Long, InternalOrderGroupInfo> latest = new LinkedHashMap<>();
Map<Long, GroupSnapshot> groups = new LinkedHashMap<>();
for (var link : mapper.linksByOrderIds(orderIds)) {
if (latest.containsKey(link.getOrderId())) continue;
GroupSnapshot group = groups.computeIfAbsent(link.getGroupId(), this::requiredSnapshot);
if (!CURRENT_GROUP_STATUSES.contains(group.status())) continue;
latest.put(link.getOrderId(), new InternalOrderGroupInfo(link.getOrderId(), group));
}
return orderIds.stream().map(latest::get).filter(Objects::nonNull).toList();
}
@Override
@Transactional(readOnly = true)
public GroupSnapshot findByInbound(long tenantId, long inboundId) {
@@ -2,8 +2,10 @@ package com.baiyue.firstleg.loading.coload;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.loading.coload.controller.PlatformColoadGroupController;
import com.baiyue.firstleg.loading.coload.controller.InternalColoadGroupController;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CancelRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.CreateRequest;
import com.baiyue.firstleg.loading.coload.model.ColoadGroupModels.InternalBatchQueryRequest;
import com.baiyue.firstleg.loading.coload.port.ColoadGroupAssignmentPort;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -51,6 +53,16 @@ class ColoadGroupContractTest {
long.class, String.class)).isNotNull();
}
@Test
void orderAggregationUsesSeparateInternalServiceTokenEndpoint() throws Exception {
RequestMapping base = InternalColoadGroupController.class.getAnnotation(RequestMapping.class);
assertThat(base.value()).containsExactly("/internal/firstleg/v1/coload-groups");
Method method = InternalColoadGroupController.class.getDeclaredMethod(
"batchQuery", String.class, InternalBatchQueryRequest.class);
assertThat(method.getAnnotation(PostMapping.class).value()).containsExactly("/batch-query");
assertThat(method.getAnnotation(PreAuthorize.class)).isNull();
}
private void assertEndpoint(String methodName, String path, Class<?>... parameterTypes) throws Exception {
Method method = PlatformColoadGroupController.class.getDeclaredMethod(methodName, parameterTypes);
if (path.isEmpty()) {
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.Inbo
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.MemberRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.InboundGroupLookupRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.MemberCodeRow;
import com.baiyue.firstleg.loading.coload.persistence.ColoadPersistenceRows.OrderGroupLinkRow;
import com.baiyue.firstleg.loading.coload.service.ColoadGroupService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
@@ -83,6 +84,23 @@ class ColoadGroupServiceTest {
.isInstanceOf(BizException.class).hasMessageContaining("多个有效");
}
@Test
void internalOrderAggregationKeepsTechnicalGroupSnapshotOffPlatformContract() {
when(mapper.linksByOrderIds(List.of(11L, 12L))).thenReturn(List.of(
link(11L, 500L), link(12L, 500L)));
when(mapper.group(500L)).thenReturn(group(500L, "LOCKED", 700L, 1));
when(mapper.members(500L)).thenReturn(List.of(
member(101L, 11L, 1001L, 30), member(202L, 12L, 1002L, 50)));
var result = service.queryInternalByOrderIds(List.of(12L, 11L));
assertThat(result).extracting("orderId").containsExactly(11L, 12L);
assertThat(result).allSatisfy(info -> {
assertThat(info.group().groupId()).isEqualTo(500L);
assertThat(info.group().status()).isEqualTo("LOCKED");
});
}
@Test
void createsCrossCustomerGroupAndFreezesAllAvailableFragmentsForEveryOrder() {
when(mapper.lockCandidates(List.of(11L, 12L))).thenReturn(List.of(
@@ -272,6 +290,13 @@ class ColoadGroupServiceTest {
return value;
}
private static OrderGroupLinkRow link(long orderId, long groupId) {
OrderGroupLinkRow value = new OrderGroupLinkRow();
value.setOrderId(orderId);
value.setGroupId(groupId);
return value;
}
private static org.assertj.core.groups.Tuple tuple(Object... values) {
return org.assertj.core.groups.Tuple.tuple(values);
}
@@ -75,6 +75,16 @@ class ColoadGroupSqlContractTest {
.doesNotContain("member.active_flag=1", "inbound_role", "split_status", "split_purpose");
}
@Test
void internalOrderAggregationExcludesCancelledGroupsWithoutChangingPlatformReadKey() throws Exception {
Method method = ColoadGroupMapper.class.getDeclaredMethod("linksByOrderIds", java.util.List.class);
String sql = String.join(" ", method.getAnnotation(Select.class).value());
assertThat(sql)
.contains("member.order_id IN")
.contains("group_row.group_status&lt;&gt;'CANCELLED'")
.doesNotContain("inbound_code=#{inboundCode}");
}
private static String tableDefinition(String source, String tableName) {
int start = source.indexOf("CREATE TABLE IF NOT EXISTS " + tableName + " (");
int end = source.indexOf(';', start);