232 lines
17 KiB
JavaScript
232 lines
17 KiB
JavaScript
export const CONTEXTS = Object.freeze(['REQUEST', 'BACKGROUND', 'INFRASTRUCTURE', 'EXTERNAL'])
|
|
export const ACCESS_MODES = Object.freeze(['READ', 'WRITE', 'READ_WRITE'])
|
|
export const IDEMPOTENCY = Object.freeze([
|
|
'SAFE_READ', 'IDEMPOTENCY_KEY', 'BUSINESS_KEY', 'EVENT_ID', 'STATE_CONVERGENT', 'NONE', 'MIXED'
|
|
])
|
|
|
|
const feign = (path, source, target, contexts, access, idempotency, note) => ({
|
|
id: `FEIGN:${path}`,
|
|
transport: 'FEIGN', path, source, target, contexts, access, idempotency, note
|
|
})
|
|
|
|
const http = (path, source, target, contexts, access, idempotency, note) => ({
|
|
id: `HTTP:${path}`,
|
|
transport: 'HTTP', path, source, target, contexts, access, idempotency, note
|
|
})
|
|
|
|
export const SYNCHRONOUS_CLIENTS = Object.freeze([
|
|
feign('auth/src/main/java/com/baiyue/auth/client/IamAuthorizationClient.java',
|
|
'auth-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', 'POST 读取授权快照,不写 IAM'),
|
|
feign('auth/src/main/java/com/baiyue/auth/client/IamOnboardingClient.java',
|
|
'auth-service', 'iam-service', ['REQUEST'], 'WRITE', 'BUSINESS_KEY', '注册/补偿以 authUserId 收敛,不自动重试'),
|
|
|
|
feign('file/src/main/java/com/baiyue/file/client/FinanceExportProviderClient.java',
|
|
'file-service', 'finance-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
|
feign('file/src/main/java/com/baiyue/file/client/FirstlegExportProviderClient.java',
|
|
'file-service', 'firstleg-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
|
feign('file/src/main/java/com/baiyue/file/client/IamExportProviderClient.java',
|
|
'file-service', 'iam-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
|
feign('file/src/main/java/com/baiyue/file/client/IamQualificationClient.java',
|
|
'file-service', 'iam-service', ['REQUEST'], 'WRITE', 'STATE_CONVERGENT', '资质登记/替换按客户与资质类型收敛,无通用幂等头'),
|
|
feign('file/src/main/java/com/baiyue/file/client/OrderExportProviderClient.java',
|
|
'file-service', 'order-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
|
feign('file/src/main/java/com/baiyue/file/client/WarehouseExportProviderClient.java',
|
|
'file-service', 'warehouse-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
|
feign('file/src/main/java/com/baiyue/file/client/WeightOcrClient.java',
|
|
'file-service', 'integration-service', ['REQUEST'], 'READ', 'NONE', '上传线程内调用 OCR;第三方调用不可假定免费幂等'),
|
|
|
|
feign('finance/src/main/java/com/baiyue/finance/client/FinanceFileClient.java',
|
|
'finance-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '元数据 GET 可重复,凭证上传无幂等键'),
|
|
feign('finance/src/main/java/com/baiyue/finance/client/FirstlegCostSnapshotClient.java',
|
|
'finance-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取整柜成本分摊快照'),
|
|
feign('finance/src/main/java/com/baiyue/finance/client/FirstlegFeeSourceClient.java',
|
|
'finance-service', 'firstleg-service', ['REQUEST', 'BACKGROUND'], 'READ_WRITE', 'MIXED',
|
|
'事件消费回读快照;平台作废命令携带 Idempotency-Key 与请求哈希'),
|
|
feign('finance/src/main/java/com/baiyue/finance/client/IamCustomerClient.java',
|
|
'finance-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取客户主档快照'),
|
|
feign('finance/src/main/java/com/baiyue/finance/client/WarehouseSupplierClient.java',
|
|
'finance-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取并校验供应商快照'),
|
|
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/CarrierScheduleIntegrationClient.java',
|
|
'firstleg-service', 'integration-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '船节点定时任务查询,不属于用户请求'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/EventBridgeClient.java',
|
|
'firstleg-service', 'event-bridge-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', '装柜/船节点 Outbox 按 eventId 投递'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/FeePaymentVoucherFileClient.java',
|
|
'firstleg-service', 'file-service', ['REQUEST'], 'READ', 'SAFE_READ', '校验付款凭证元数据'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/FinanceFeeSourceEventClient.java',
|
|
'firstleg-service', 'finance-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', '费用 Outbox 按事件幂等键投递'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/IamCustomerSnapshotClient.java',
|
|
'firstleg-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取客户名称与状态快照'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/LoadingDocumentFileClient.java',
|
|
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '上传无幂等键,元数据/下载可重复'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/LoadingEvidenceFileClient.java',
|
|
'firstleg-service', 'file-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取装柜证据元数据/内容'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/MeasurementImageClient.java',
|
|
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '图片上传/作废非安全重放,查询可重复'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/OrderCargoClient.java',
|
|
'firstleg-service', 'order-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取订单货物/来源快照'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/OrderFulfillmentSplitClient.java',
|
|
'firstleg-service', 'order-service', ['REQUEST'], 'WRITE', 'IDEMPOTENCY_KEY', 'PDA 结算拆单携带 idempotencyKey/operationId'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PriceBookFileClient.java',
|
|
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '价目文件上传无幂等键,读取可重复'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PriceBookWarehouseClient.java',
|
|
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取仓库/渠道主数据'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PrintPdfClient.java',
|
|
'firstleg-service', 'print-service', ['REQUEST'], 'READ', 'SAFE_READ', '无状态 PDF 渲染,POST 为计算型读取'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/TruckAppointmentFileClient.java',
|
|
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '附件上传无幂等键,元数据读取可重复'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseMasterDataClient.java',
|
|
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取仓库主数据'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/WarehousePalletLocationClient.java',
|
|
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ_WRITE', 'MIXED',
|
|
'查询可重复;预占/选择释放/转移/结算使用业务幂等键,普通确认/释放按状态收敛'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseSupplierClient.java',
|
|
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取供应商快照'),
|
|
feign('firstleg/src/main/java/com/baiyue/firstleg/customs/client/CustomsFileClient.java',
|
|
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '报关文件上传无幂等键,读取可重复'),
|
|
|
|
feign('iam/src/main/java/com/baiyue/iam/client/AuthAccountClient.java',
|
|
'iam-service', 'auth-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '账号查询可重复,写操作按账号/命令业务键收敛'),
|
|
feign('iam/src/main/java/com/baiyue/iam/client/CustomerDimensionRuleClient.java',
|
|
'iam-service', 'firstleg-service', ['BACKGROUND'], 'WRITE', 'BUSINESS_KEY',
|
|
'IAM 持久化任务租约投递;初始化/补偿复用稳定 initializationKey,不在客户创建请求内调用'),
|
|
feign('iam/src/main/java/com/baiyue/iam/client/OverseasStorageFeeClient.java',
|
|
'iam-service', 'warehouse-service', ['REQUEST'], 'WRITE', 'BUSINESS_KEY', '初始化/补偿使用 initializationKey'),
|
|
feign('iam/src/main/java/com/baiyue/iam/client/SecurityEventBridgeClient.java',
|
|
'iam-service', 'event-bridge-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'IAM 安全 Outbox 按 eventId 投递'),
|
|
|
|
feign('lastleg-service/src/main/java/com/baiyue/lastleg/client/EventBridgeClient.java',
|
|
'lastleg-service', 'event-bridge-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', '卸柜完成 Outbox 按 eventId 投递'),
|
|
feign('lastleg-service/src/main/java/com/baiyue/lastleg/masterdata/quote/client/DomesticPartnerWarehouseClient.java',
|
|
'lastleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '报价创建时读取国内合作仓快照'),
|
|
|
|
feign('order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java',
|
|
'order-service', 'firstleg-service', ['REQUEST', 'BACKGROUND'], 'READ_WRITE', 'BUSINESS_KEY',
|
|
'列表查询/缺失修复在线执行;审核建单由 Outbox 投递,按 orderId/inboundCode 收敛'),
|
|
feign('order/src/main/java/com/baiyue/order/client/FirstlegPricingClient.java',
|
|
'order-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ', 'POST 校验计价渠道,不写履约状态'),
|
|
feign('order/src/main/java/com/baiyue/order/client/FirstlegUnifiedOrderClient.java',
|
|
'order-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ', 'POST 查询统一订单投影'),
|
|
feign('order/src/main/java/com/baiyue/order/client/ProductImageClient.java',
|
|
'order-service', 'file-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取商品图片元数据'),
|
|
feign('order/src/main/java/com/baiyue/order/client/TenantEligibilityClient.java',
|
|
'order-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取客户下单资格'),
|
|
feign('order/src/main/java/com/baiyue/order/client/WarehouseClient.java',
|
|
'order-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取仓库主数据'),
|
|
|
|
http('common-audit/src/main/java/com/baiyue/common/audit/HttpAuditSink.java',
|
|
'common-audit', 'audit-service', ['INFRASTRUCTURE'], 'WRITE', 'NONE', '同步审计投递失败当前仅记录告警'),
|
|
http('event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFirstlegEventClient.java',
|
|
'event-bridge-service', 'firstleg-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'Inbox 租约投递,目标按 eventId 幂等'),
|
|
http('event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java',
|
|
'event-bridge-service', 'lastleg-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'Inbox 租约投递,目标按 eventId 幂等'),
|
|
http('event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpSecurityProjectionEventClient.java',
|
|
'event-bridge-service', 'lastleg-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'IAM 安全事件投影按 eventId/版本单调幂等'),
|
|
http('gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpOverseasSecurityValidationClient.java',
|
|
'gateway-overseas', 'lastleg-service', ['INFRASTRUCTURE'], 'READ', 'SAFE_READ', 'POST 实时校验,失败关闭'),
|
|
http('gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpPdaSessionValidationClient.java',
|
|
'gateway-overseas', 'auth-service', ['INFRASTRUCTURE'], 'READ', 'SAFE_READ', 'POST PDA 会话校验,失败关闭'),
|
|
http('lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpUnloadPhotoFileGateway.java',
|
|
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '上传无幂等键,元数据校验可重复'),
|
|
http('lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java',
|
|
'lastleg-service', 'warehouse-overseas-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '查询可重复;写操作按 business/reservation key 或状态收敛'),
|
|
http('lastleg-service/src/main/java/com/baiyue/lastleg/palletops/gateway/HttpPalletPhysicalGateway.java',
|
|
'lastleg-service', 'warehouse-overseas-service', ['REQUEST'], 'READ_WRITE', 'IDEMPOTENCY_KEY', '托盘变更/补偿使用 operationKey 与请求哈希'),
|
|
http('lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/gateway/HttpRelabelPhotoFileGateway.java',
|
|
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '上传无幂等键,元数据校验可重复'),
|
|
http('integration-service/src/main/java/com/baiyue/integration/provider/EvergreenTrackingClient.java',
|
|
'integration-service', 'external-evergreen', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
|
http('integration-service/src/main/java/com/baiyue/integration/provider/HedeTrackingClient.java',
|
|
'integration-service', 'external-hede', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
|
http('integration-service/src/main/java/com/baiyue/integration/provider/MatsonTrackingClient.java',
|
|
'integration-service', 'external-matson', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
|
http('integration-service/src/main/java/com/baiyue/integration/provider/SmlTrackingClient.java',
|
|
'integration-service', 'external-sml', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
|
http('integration-service/src/main/java/com/baiyue/integration/provider/ZimTrackingClient.java',
|
|
'integration-service', 'external-zim', ['EXTERNAL'], 'READ', 'SAFE_READ', '令牌获取与轨迹查询不改变平台业务事实'),
|
|
http('integration-service/src/main/java/com/baiyue/integration/provider/ocr/BaiduMeterClient.java',
|
|
'integration-service', 'external-baidu-ocr', ['EXTERNAL'], 'READ', 'NONE', 'OCR 可能计费,不由 HTTP 层自动重试')
|
|
])
|
|
|
|
export const USER_ENTRIES = Object.freeze([
|
|
{ name: '认证 API', service: 'auth-service' },
|
|
{ name: 'IAM API', service: 'iam-service' },
|
|
{ name: '订单 API', service: 'order-service' },
|
|
{ name: '头程 API/PDA', service: 'firstleg-service' },
|
|
{ name: '财务 API', service: 'finance-service' },
|
|
{ name: '文件 API', service: 'file-service' },
|
|
{ name: '海外尾程 API/PDA', service: 'lastleg-service' },
|
|
{ name: '国内仓储 API', service: 'warehouse-service' },
|
|
{ name: '海外仓储 API', service: 'warehouse-overseas-service' }
|
|
])
|
|
|
|
export function graphEdges(context) {
|
|
return [...new Set(SYNCHRONOUS_CLIENTS
|
|
.filter(client => context === 'ALL' || client.contexts.includes(context))
|
|
.map(client => `${client.source}->${client.target}`))].sort()
|
|
}
|
|
|
|
export function stronglyConnectedComponents(edgeNames) {
|
|
const adjacency = new Map()
|
|
for (const edge of edgeNames) {
|
|
const [from, to] = edge.split('->')
|
|
if (!adjacency.has(from)) adjacency.set(from, new Set())
|
|
if (!adjacency.has(to)) adjacency.set(to, new Set())
|
|
adjacency.get(from).add(to)
|
|
}
|
|
let index = 0
|
|
const indexes = new Map()
|
|
const lowLinks = new Map()
|
|
const stack = []
|
|
const onStack = new Set()
|
|
const components = []
|
|
function connect(node) {
|
|
indexes.set(node, index)
|
|
lowLinks.set(node, index++)
|
|
stack.push(node)
|
|
onStack.add(node)
|
|
for (const target of adjacency.get(node)) {
|
|
if (!indexes.has(target)) {
|
|
connect(target)
|
|
lowLinks.set(node, Math.min(lowLinks.get(node), lowLinks.get(target)))
|
|
} else if (onStack.has(target)) lowLinks.set(node, Math.min(lowLinks.get(node), indexes.get(target)))
|
|
}
|
|
if (lowLinks.get(node) !== indexes.get(node)) return
|
|
const component = []
|
|
let current
|
|
do {
|
|
current = stack.pop()
|
|
onStack.delete(current)
|
|
component.push(current)
|
|
} while (current !== node)
|
|
components.push(component.sort())
|
|
}
|
|
for (const node of adjacency.keys()) if (!indexes.has(node)) connect(node)
|
|
return components.filter(component => component.length > 1)
|
|
.sort((left, right) => left.join().localeCompare(right.join()))
|
|
}
|
|
|
|
export function longestSimplePaths(entryService, edgeNames = graphEdges('REQUEST')) {
|
|
const adjacency = new Map()
|
|
for (const edge of edgeNames) {
|
|
const [from, to] = edge.split('->')
|
|
if (!adjacency.has(from)) adjacency.set(from, new Set())
|
|
adjacency.get(from).add(to)
|
|
}
|
|
let longest = []
|
|
function visit(node, path) {
|
|
const targets = [...(adjacency.get(node) ?? [])].sort()
|
|
let extended = false
|
|
for (const target of targets) {
|
|
if (path.includes(target)) continue
|
|
extended = true
|
|
visit(target, [...path, target])
|
|
}
|
|
if (extended) return
|
|
if (longest.length === 0 || path.length > longest[0].length) longest = [path]
|
|
else if (path.length === longest[0].length) longest.push(path)
|
|
}
|
|
visit(entryService, [entryService])
|
|
return longest.sort((left, right) => left.join('->').localeCompare(right.join('->')))
|
|
}
|