跳至主要内容

aile-service-platform-integration 服務設計規格 v1.0

文件說明

本文件是 aile-service-platform-integration 服務的最終設計規格,作為開發團隊的實施指導文件。文件自包含,覆蓋服務邊界、領域模型、安裝握手協議、閘道器層、事件三段鏈路、簽名規則、管理面介面與資料遷移等全部交付範圍。

[!NOTE] 本文件定位為開發參考用的臨時設計稿,協助團隊在正式規格文件發布前對齊服務設計與實作邊界;待正式規格建立後,可由正式文件取代並移除此參考稿。

閱讀物件:後端開發、測試、運維。

閱讀前提:已瞭解 Aile 多租戶生態(Aile、AIPower 等)與現有 aile-service-job 中 TenantAppModel / WebhookEventServiceImpl 的歷史實現。


一、設計目標

aile-service-platform-integration 是 Aile 生態中負責「平臺級整合底座」的獨立微服務,定位為 Aile Open Integration Layer 的工程實體——為所有外部生態應用(AIPower 及未來擴充套件)提供統一的安裝、鑑權、路由與事件出口,自身不持有任何業務領域邏輯。

核心目標:

  • 提供 IntegrationApp / TenantIntegration / TenantMapping 三張核心模型,實現「平臺級應用定義」與「租戶級安裝例項」的分離
  • 承擔 /openapi/v1/* 閘道器層:統一路由、簽名驗籤、鑑權切面,內部代理回各領域子服務,不持有任何業務領域邏輯
  • 定義統一的 EventEnvelope 結構,封裝業務事件並推入 Pub/Sub,供獨立的 webhook 服務消費
  • 實現 Aile 主導的安裝握手協議(/install / /update / /uninstall / /rotate-secret),完成租戶安裝例項的全生命週期管理
  • 統一 HMAC-SHA256 簽名規則,消除當前 SignatureUtils 中三套不一致邏輯
  • 替代當前 aile-service-job 中零散的 TenantAppModel / WebhookEventServiceImpl

二、服務邊界與責任宣告

本服務採用「Open Integration Layer」邊界,本服務自有僅代理兩類能力的劃分如下:

能力本服務定位說明
IntegrationApp / TenantIntegration / TenantMapping自有領域本服務獨佔的核心模型與狀態機
安裝握手協議(/install /update /uninstall /rotate-secret)自有介面由本服務主導呼叫 AIPower 安裝入口
統一簽名 SDK / 驗籤 Filter自有能力本服務暴露給所有 Aile 子服務複用
/openapi/v1/* 閘道器自有入口 + 代理轉發路由 + 鑑權 + 簽名驗籤,領域邏輯回原服務
業務事件封裝 + Pub/Sub 投遞 + EventLog自有職責本服務取代舊 WebhookEventServiceImpl 的「事件封裝+釋出」環節
Webhook 出站投遞 / 重試 / DLQ不在本服務由獨立 webhook 服務訂閱 Pub/Sub 完成
通知狀態事件(notice.*)封裝 + 釋出自有職責message 服務透過 EventPublishClient 推入本服務,統一封裝為 EventEnvelope 後入 Pub/Sub,與業務事件共用同一 webhookUrl 投遞
AIPower 反向能力呼叫(Aile→AIPower 執行時 API)本期不實現apiBaseUrl 欄位僅佔位,後期再設計
AIFF / 業務物件 / 同步資源等所有領域邏輯不在本服務領域程式碼與儲存保留在 auth/job/tenant/account/room 等原服務,本服務僅作為閘道器代理
SystemAppModel(內部服務間簽名)不動保留在 aile-service-job,與 IntegrationApp 互不干涉

架構定點陣圖:


三、本期關鍵設計決策

以下決策為本期實施的最終口徑,開發實施時直接遵循,不再做二次討論。

決策項最終口徑原因 / 說明
外部租戶欄位命名統一使用 externalTenantId / externalSpaceId面向未來多生態應用擴充套件提前抽象,避免與具體應用(如 AIPower)耦合
Webhook URL 設計統一為單一 webhookUrl,業務事件與 notice.* 通知事件全部投遞到該地址事件分發本質上是第三方接收側的職責(EventEnvelope 已攜帶 eventType);合併端點簡化握手協議、路由約定、遷移指令碼與接收側接入成本
通知 notice.* 事件與業務事件完全同鏈路同通道:message 服務 → EventPublishClient → 本服務 → Pub/Sub → 獨立 webhook 服務 → 統一 webhookUrl走 Pub/Sub 解耦,避免增加 message 服務的同步出站壓力;複用統一簽名 / 重試 / DLQ;下游接收側按 EventEnvelope.eventType 自行分發
反向能力呼叫(Aile → 外部應用執行時 API)本期不實現,apiBaseUrl 欄位僅佔位P0 範圍聚焦核心閉環
Scope 鑑權本期不實現,supportedScopes / grantedScopes 預設 ["*"]簡化首版,後續再做細粒度
PENDING_USER_CONFIRM 狀態列舉保留,反向回撥介面不實現本期外部應用不需要人工審批流程
Nonce 防重放本期不實現(沿用現狀)nonce 僅參與簽名計算,不快取查重,後續如有安全需求再加
Secret 儲存明文儲存 Mongo沿用現狀,後續如啟用 KMS 再遷移
金鑰輪換策略硬切換,舊金鑰立即失效實現簡單,本期可接受短暫中斷
舊介面相容不提供灰度相容期,遷移後直接下線 /tenantapp/v1/*降低長期維護成本
EventLog 用途僅作審計,不作重試源重試由下游獨立 webhook 服務負責

四、命名規範與術語

採用三層命名以消除舊 appId 二義性(全域性應用定義、租戶安裝例項、租戶實體三者明確分離)。下表為本服務的正式命名口徑,所有程式碼、介面、文件須統一使用。

正式命名含義使用位置
integrationAppId全域性生態應用定義 ID,例如 aipower。全平臺唯一,不隨租戶變化IntegrationApp.appId、EventEnvelope.integration.appId
tenantIntegrationId租戶安裝例項 ID,簽名鑑權與路由的主要依據簽名 header、TenantIntegration.integrationId、EventEnvelope.integration.integrationId
tenantIntegrationSecret租戶安裝例項簽名金鑰簽名演算法輸入,僅本服務與該安裝例項持有
aileTenantIdAile 側的租戶 IDTenantIntegration.aileTenantId、TenantMapping.aileTenantId
externalTenantId / externalSpaceId外部生態應用側的租戶/空間 ID(抽象命名,適配 AIPower 及未來其他生態應用)TenantMapping.externalTenantId、EventEnvelope.tenant.mappedExternalTenantId
ownerType / ownerId個人租戶隔離維度TenantMapping.ownerType / ownerId

簽名 Header 的正式口徑:

Authorization = "AILE " + tenantIntegrationId + ":" + signature

五、包結構設計(DDD 分層)

aile-service/
aile-service-platform-integration/
src/main/java/com/aile/service/platformintegration/
PlatformIntegrationApplication.java
config/
gateway/ ← 网关层(新增,本服务核心入口)
OpenApiRoutingFilter.java ← /openapi/v1/* 路由
SignatureVerifyFilter.java ← 入站签名验签
TenantContextResolver.java ← 根据 tenantIntegrationId 解析租户上下文
ProxyController.java ← 代理转发
controller/ ← 自有接口层
InstallController.java ← /install /update /uninstall /rotate-secret
IntegrationAppController.java ← /admin/integrations/apps
TenantIntegrationController.java
application/ ← 应用服务层
InstallHandshakeAppService.java
IntegrationManagementAppService.java
EventPublishAppService.java
domain/ ← 领域层
integrationapp/
IntegrationApp.java
IntegrationAppRepository.java
tenantintegration/
TenantIntegration.java
TenantIntegrationRepository.java
TenantIntegrationStatus.java
TenantIntegrationAuditRepository.java
InstallHandshakeService.java
tenantmapping/
TenantMapping.java
TenantMappingRepository.java
event/
EventEnvelope.java
StandardEventType.java
EventPublisher.java ← 接口,实现走 Pub/Sub
EventLogRepository.java
signature/
HmacSignatureService.java
infrastructure/ ← 基础设施层
persistence/
IntegrationAppMongoRepo.java
TenantIntegrationMongoRepo.java
TenantIntegrationAuditMongoRepo.java
TenantMappingMongoRepo.java
EventLogMongoRepo.java
pubsub/
EventPubSubPublisher.java ← Pub/Sub 实现
http/
InstallHttpClient.java ← 调用 AIPower /install
proxy/
DownstreamRouteRegistry.java ← /openapi/v1/* 路由配置

API 模型層(與其他服務共享):

aile-api/
aile-platform-integration-api/
src/main/java/com/aile/api/platformintegration/
model/
IntegrationAppModel.java
TenantIntegrationModel.java
TenantMappingModel.java
enums/
IntegrationAppStatus.java
TenantIntegrationStatus.java
StandardEventType.java
OwnerType.java
dto/
InstallRequestDto.java
InstallResponseDto.java
UpdateInstallRequestDto.java
UninstallRequestDto.java
RotateSecretRequestDto.java
EventEnvelopeDto.java

六、領域模型設計

6.1 IntegrationApp(聚合根)

平臺級全域性應用定義。一個生態應用在全平臺僅一條記錄。

@Document("aile.platform.integration.app")
@CompoundIndexes({
@CompoundIndex(name = "appId_1", def = "{'appId': 1}", unique = true)
})
public class IntegrationAppModel extends BaseModel {
private String appId; // integrationAppId,如 "aipower"
private String appName;
private String provider; // 如 "AIPOWER"
private String installBaseUrl; // 固定安装入口地址(控制面)
private List<TenantType> supportedTenantTypes; // 支持的租户类型,例如 [PERSONAL, TEAM]
private List<String> supportedScopes; // 占位,本期默认 ["*"]
private List<String> defaultScopes; // 占位,本期默认 ["*"]
private List<String> supportedEvents; // 支持的事件类型清单(资源域级,如 contact.*)
private IntegrationAppStatus status; // ACTIVE / DEPRECATED
}

6.2 TenantIntegration(聚合根,含狀態機)

租戶安裝例項。同一 aileTenantId + appId 只能存在一個非 DELETED 記錄。

@Document("aile.platform.tenant.integration")
@CompoundIndexes({
@CompoundIndex(name = "integrationId_1", def = "{'integrationId': 1}", unique = true),
@CompoundIndex(name = "tenant_app_active",
def = "{'aileTenantId': 1, 'appId': 1, 'status': 1}")
})
public class TenantIntegrationModel extends BaseModel {
private String integrationId; // tenantIntegrationId
private String aileTenantId;
private TenantType aileTenantType; // PERSONAL / TEAM
private String appId; // 关联 IntegrationApp.appId
private String appSecret; // tenantIntegrationSecret,明文存储(本期沿用现状)
private IntegrationMode integrationMode; // PERSONAL / TEAM
private String apiBaseUrl; // 占位,本期不调用
private String webhookUrl; // 统一 Webhook 接收地址,业务事件与 notice.* 通知事件全部投递到此(由独立 webhook 服务消费 Pub/Sub 后投递);接收侧自行按 eventType 分发
private List<String> grantedScopes; // 占位,本期默认 ["*"]
private List<String> subscribedEvents; // 资源域级订阅,如 ["contact.*", "user.*"]
private TenantIntegrationStatus status;
private String createdBy;
// 审计日志不内嵌,使用独立 collection aile.platform.tenant.integration.audit
}

狀態機:

狀態含義本期是否啟用
PENDINGAile 已建立安裝草稿,握手未完成
PENDING_USER_CONFIRMAIPower 側需人工確認列舉保留,不會進入此狀態
ACTIVE安裝完成,正常執行
SUSPENDED臨時停用
DISABLED租戶/平臺主動停用
DELETED已解除安裝,終態

狀態遷移圖:

關鍵規則:

  • ACTIVE 狀態不投遞事件,不接受簽名 API 呼叫(解除安裝/輪轉等控制面動作除外)
  • 所有狀態遷移須寫入獨立審計集合(見 6.4)
  • 同一 aileTenantId + appId 組合只能存在一個非 DELETED 記錄(應用層校驗 + 唯一索引)
  • DELETED 為終態,清理期結束後可物理清除 appSecret 與端點資訊

6.3 TenantMapping(實體)

跨系統租戶對映。

@Document("aile.platform.tenant.mapping")
public class TenantMappingModel extends BaseModel {
private String mappingId;
private String aileTenantId;
private TenantType aileTenantType; // PERSONAL / TEAM
private String externalTenantId; // 外部生态应用侧租户 ID(如 AIPower 内部租户 ID)
private String externalSpaceId; // 团队模式空间ID,个人模式为 null
private OwnerType ownerType; // AILE_PERSONAL / AILE_TEAM
private String ownerId; // 个人模式必填,= ailePersonalTenantId
private String integrationId; // 关联 TenantIntegration
private MappingStatus status;
}

OwnerType 列舉:

public enum OwnerType {
AILE_PERSONAL, // 个人租户模式
AILE_TEAM // 团队租户模式(预留,实际团队模式可不依赖 ownerId)
}

個人租戶 / 團隊租戶業務前提:

  • 個人租戶在 Aile 側沿用團隊租戶的資料結構,前端按功能粒度做能力限制,以便未來一鍵升級為團隊租戶
  • 一個賬號僅擁有且必擁有一個個人租戶——賬號↔個人租戶為一對一繫結關係,不存在多賬號共享同一個人租戶的場景
  • 團隊租戶可包含多個賬號與多個空間(space)

對映規則:

  • 個人租戶
    • AIPower 側不為每個 Aile 個人租戶單獨建立外部租戶例項,所有 Aile 個人租戶共用同一 externalTenantId(=publicTenantId)
    • ownerType = AILE_PERSONAL
    • ownerId = ailePersonalTenantId(= 該賬號 ID,三者一一對應),作為 AIPower 共享租戶內的資料隔離鍵
  • 團隊租戶 → 每個團隊獨立 externalTenantId,可選 externalSpaceId
  • 對映在安裝握手成功後根據 AIPower 返回資訊建立
  • 對映表是事件路由與資料隔離的核心依據

Contact ↔ ServiceNumber 關係約定(本服務不直接持有 contact / visitor / service_number 領域模型,這些保留在 auth / tenant 等下游服務,但其關係約束直接影響事件 scope 與 OpenAPI 路徑設計,在此明確口徑):

  • service_number(服務號,ServiceNumber) 是租戶內資源,一個租戶可擁有 1~N 個服務號,服務號本身歸屬唯一租戶。
  • contact / visitor(客戶/訪客) 是租戶級主體,在租戶範圍內全域性唯一(visitor 實名歸戶後晉升為 contact)。
  • 「進線」是 contact × service_number 的關係實體——同一個 contact 可以進入同租戶下的 1~N 個服務號,在每個服務號下獨立呈現為「該服務號的客戶」。
  • 由此推論:
    • 主體生命週期(contact.created / contact.updated / contact.deleted)是租戶級事件,不帶 scope.serviceNumberId
    • 進線 / 關注變更(contact.entered / contact.service_number_followed / contact.service_number_unfollowed)是服務號級事件,必填 scope.serviceNumberId;followed / unfollowed 表達客戶主動關注 / 取消關注該服務號的狀態變遷。
    • OpenAPI 路徑上 contact / visitor 資源始終巢狀在 /openapi/v1/service-numbers/&#123;snId&#125;/ 之下,因為外部生態應用看到的「客戶」始終是某個服務號下的檢視(詳見 §9.2)。

6.4 TenantIntegrationAudit(審計實體,獨立 collection)

@Document("aile.platform.tenant.integration.audit")
@CompoundIndexes({
@CompoundIndex(name = "integrationId_time",
def = "{'integrationId': 1, 'occurredAt': -1}")
})
public class TenantIntegrationAuditModel extends BaseModel {
private String integrationId;
private TenantIntegrationStatus fromStatus;
private TenantIntegrationStatus toStatus;
private String actor; // userId 或 system 标识
private String reason;
private Long occurredAt;
}

僅追加寫入,不更新不刪除,保留完整狀態遷移歷史。


七、安裝握手協議

本節定義 Aile 主導、外部生態應用配合的安裝握手全流程,涵蓋建立、更新、解除安裝、金鑰輪換四個控制面動作。

7.1 安裝時序

7.2 /install 請求欄位

請求:POST &#123;installBaseUrl&#125;/install

欄位必填說明
integrationAppId全域性應用 ID,如 aipower
tenantIntegrationId本次安裝的租戶例項 ID
tenantIntegrationSecret與上述 ID 配對的簽名金鑰,僅在安裝握手階段下發
aileTenantIdAile 租戶 ID
aileTenantTypePERSONAL / TEAM
requestedScopes本期固定為 ["*"]
aileApiBaseUrlAIPower 後續呼叫 Aile Open API 的基礎地址
installNonce冪等鍵,AIPower 據此去重同一安裝請求
installedAtISO-8601 時間戳

安全說明:本期 /install 不攜帶簽名,僅依賴 HTTPS + AIPower 側 IP 白名單保障來源合法性。tenantIntegrationSecret 在請求體中明文下發(HTTPS 傳輸層已加密)。

7.3 /install 響應欄位

欄位必填說明
integrationModePERSONAL / TEAM
apiBaseUrl佔位,本期儲存但不呼叫
webhookUrl統一 Webhook 接收地址,業務事件與 notice.* 通知事件均投遞到此(由獨立 webhook 服務消費 Pub/Sub 後投遞),必須 HTTPS;第三方在自己 handler 內按 eventType 欄位分發到對應處理邏輯
externalTenantId外部生態應用側租戶 ID(如 AIPower 內部租戶 ID)
externalSpaceId團隊模式可選,個人模式省略
ownerType個人租戶必填AILE_PERSONAL
ownerId個人租戶必填ailePersonalTenantId
acceptedScopes本期固定回傳 ["*"]
installStatus本期僅接受 ACTIVE,返回其他值視為錯誤

7.4 錯誤碼

錯誤碼HTTP說明
UNSUPPORTED_TENANT_TYPE400AIPower 不支援該 aileTenantType
DUPLICATE_INSTALL409同 installNonce 或同 (aileTenantId + integrationAppId) 已存在有效安裝
INTEGRATION_APP_NOT_FOUND404integrationAppId 未註冊或非 ACTIVE
STATUS_TRANSITION_FORBIDDEN409不允許的狀態遷移
SIGNATURE_INVALID401Open API 簽名驗籤失敗
TENANT_INTEGRATION_NOT_ACTIVE403非 ACTIVE 狀態下呼叫需簽名的 Open API
INVALID_WEBHOOK_URL400非 HTTPS URL

保留但本期不觸發:SCOPE_NOT_GRANTABLE(scope 未啟用)、REQUIRES_MANUAL_APPROVAL(人工審批未啟用)。

7.5 其他控制面介面

介面說明
POST &#123;installBaseUrl&#125;/update更新已安裝租戶的 webhook 端點等
POST &#123;installBaseUrl&#125;/uninstall解除安裝,本服務側 TenantIntegration → DELETED
POST &#123;installBaseUrl&#125;/rotate-secret金鑰輪換,硬切換,舊金鑰立即失效,無重疊期

八、簽名與驗籤

8.1 統一簽名規則

raw = tenantIntegrationId + nonce + bodyString
signature = Base64(HmacSHA256(tenantIntegrationSecret, raw))
Authorization = "AILE " + tenantIntegrationId + ":" + signature

統一適用範圍(消除當前 SignatureUtils 三套不一致):

  • /openapi/v1/* 入站驗籤
  • 控制面 /install 之後所有需鑑權呼叫
  • 測試介面

不參與簽名的欄位:serviceNumberId 僅作為 OpenAPI 路徑上的資源標識與 EventEnvelope 的 scope 欄位使用,不進入簽名 raw 串;EventEnvelope 的 scope 子結構整體同樣不參與任何簽名計算。簽名 raw 串仍嚴格遵循 §8.1 第一段定義。

8.2 HmacSignatureService

public interface HmacSignatureService {
String sign(String body, String tenantIntegrationId, String secret, String nonce);
boolean verify(String body, String authHeader, String nonce, String secret);
String buildAuthHeader(String tenantIntegrationId, String signature);
AuthHeaderParts parseAuthHeader(String authorizationHeader);
}

8.3 Nonce 與防重放

本期不實現 nonce 防重放(沿用現狀)。nonce 僅作為簽名輸入引數參與計算,不做快取查重、不校驗時間戳視窗。後續如有安全需求再加 Redis 快取 + TTL。

8.4 Secret 儲存

tenantIntegrationSecret 明文儲存在 Mongo,與當前 TenantAppModel.secretKey 一致。已知風險點,本期不升級,後續如啟用 KMS 再做遷移。


九、/openapi/v1/* 閘道器層

本服務承擔所有生態應用的入站 API 統一入口。

9.1 閘道器職責與請求流轉

處理順序:

  1. SignatureVerifyFilter:解析 Authorization header,提取 tenantIntegrationId,查庫取 tenantIntegrationSecret,按§8.1 驗籤,失敗返回 401 SIGNATURE_INVALID
  2. TenantContextResolver:根據 tenantIntegrationId 還原 aileTenantId / aileTenantType / externalTenantId / ownerType / ownerId,注入請求上下文
  3. OpenApiRoutingFilter:根據路徑查 DownstreamRouteRegistry,判斷應路由到哪個內部服務
  4. ProxyController:代理轉發(WebClient/RestTemplate),帶上呼叫側服務間鑑權(複用現有 SystemAppModel 簽名機制),將上下文以 header 的形式傳遞給下游(X-Aile-Tenant-Id / X-Aile-Owner-Id 等)
  5. 領域邏輯完全在下游服務處理,本服務不解析 body 內容

9.2 路由清單(具體 method + path 白名單)

DownstreamRouteRegistry 採用配置驅動 + 白名單:每條 Open API 必須在下表顯式註冊具體 method + path,不接受萬用字元字首;未登記路徑一律返回 404 ROUTE_NOT_FOUND。新增 API 時,本服務的路由配置與下游服務實現必須同步登記。

9.2.1 租戶級資源(扁平路徑)

方法 + 路徑下游服務資源域說明
GET /openapi/v1/tenants/meaile-service-tenant租戶查詢當前安裝例項所屬租戶資訊
GET /openapi/v1/usersaile-service-account使用者列出租戶下使用者(分頁)
GET /openapi/v1/users/&#123;userId&#125;aile-service-account使用者查詢單個使用者
GET /openapi/v1/service-numbersaile-service-tenant服務號列出當前 integration 可訪問的全部服務號
GET /openapi/v1/service-numbers/&#123;snId&#125;aile-service-tenant服務號查詢單個服務號詳情
GET /openapi/v1/groupsaile-service-room群組列出租戶群組(分頁)
GET /openapi/v1/groups/&#123;groupId&#125;aile-service-room群組查詢單個群組
GET /openapi/v1/addressbookaile-service-auth通訊錄查詢租戶通訊錄
POST /openapi/v1/aiff/configurationsaile-service-authAIFF 配置註冊 AIFF 槽位 + endpoint URL
GET /openapi/v1/aiff/configurationsaile-service-authAIFF 配置查詢當前 integration 已註冊的 AIFF 配置清單
PUT /openapi/v1/aiff/configurations/&#123;configId&#125;aile-service-authAIFF 配置更新單個 AIFF 配置
DELETE /openapi/v1/aiff/configurations/&#123;configId&#125;aile-service-authAIFF 配置刪除單個 AIFF 配置
POST /openapi/v1/entry-sourcesaile-service-room進線原因建立預設進線來源 EntrySource;閘道器自動注入 owner.integrationAppId / owner.tenantIntegrationId 標記建立方;觸發事件按 owner 路由回建立方 webhook(詳 §9.4)
GET /openapi/v1/entry-sourcesaile-service-room進線原因列表查詢(預設僅返回 owner.tenantIntegrationId = 當前 integration 建立的記錄;支援 status / channel / reason.type 過濾)
GET /openapi/v1/entry-sources/&#123;sourceId&#125;aile-service-room進線原因查詢單個 EntrySource;owner 非當前 integration 時返回 403 ENTRY_SOURCE_FORBIDDEN
PUT /openapi/v1/entry-sources/&#123;sourceId&#125;aile-service-room進線原因更新 EntrySource(name / reason.data / expiresAt / channels 等);owner 檢查同上
PUT /openapi/v1/entry-sources/&#123;sourceId&#125;/statusaile-service-room進線原因停用 / 重啟 EntrySource(activeinactive);owner 檢查同上
GET /openapi/v1/business/objectsaile-service-job業務物件列出業務物件型別(擴充套件點)
GET /openapi/v1/sync/resourcesaile-service-job同步資源列出同步資源型別(擴充套件點)

9.2.2 服務號級資源(巢狀路徑 /openapi/v1/service-numbers/&#123;snId&#125;/...)

方法 + 路徑下游服務資源域說明
GET /openapi/v1/service-numbers/&#123;snId&#125;/contactsaile-service-auth聯絡人列出該服務號下的 contact(分頁 / 篩選)
GET /openapi/v1/service-numbers/&#123;snId&#125;/contacts/&#123;contactId&#125;aile-service-auth聯絡人查詢單個 contact 在該服務號下的檢視
GET /openapi/v1/service-numbers/&#123;snId&#125;/contacts/labelsaile-service-auth聯絡人標籤列出該服務號下可用的客戶標籤清單(供整合方篩選受眾)
POST /openapi/v1/service-numbers/&#123;snId&#125;/contacts/labels:addaile-service-auth聯絡人標籤批次給 contact 打標籤(body:contactIds[]labelIds[],冪等)
POST /openapi/v1/service-numbers/&#123;snId&#125;/contacts/labels:removeaile-service-auth聯絡人標籤批次摘標籤(body:contactIds[]labelIds[],冪等)
GET /openapi/v1/service-numbers/&#123;snId&#125;/contacts/&#123;contactId&#125;/labelsaile-service-auth聯絡人標籤查詢單個 contact 在該服務號下的全部標籤
GET /openapi/v1/service-numbers/&#123;snId&#125;/visitorsaile-service-auth訪客列出該服務號下的 visitor
GET /openapi/v1/service-numbers/&#123;snId&#125;/visitors/&#123;visitorId&#125;aile-service-auth訪客查詢單個 visitor 在該服務號下的檢視
POST /openapi/v1/service-numbers/&#123;snId&#125;/broadcastsaile-service-message群發任務建立服務號群發任務(複用 Aile 現行服務號群發能力,新增 OpenAPI 入口)。body:name / note / channels[] / messages[](1..5 則 NoticeContent 卡片 / `{type:"text"
GET /openapi/v1/service-numbers/&#123;snId&#125;/broadcasts/&#123;taskId&#125;aile-service-message群發任務查詢任務狀態。返回 status(scheduled / sending / completed / cancelled / failed) / recipientCount / summary&#123;sent,delivered,failed,read&#125; / startedAt / finishedAt。MVP 階段以輪詢替代任務級 webhook
POST /openapi/v1/service-numbers/&#123;snId&#125;/broadcasts/&#123;taskId&#125;:cancelaile-service-message群發任務取消任務(僅 scheduled 狀態有效)
GET /openapi/v1/service-numbers/&#123;snId&#125;/notices/&#123;noticeId&#125;aile-service-message通知查詢單條通知詳情(狀態 / 渠道 / 接收方 / NoticeContent 快照)

路徑設計約定:

  • 頂層 /openapi/v1/service-numbers 提供「列出當前 integration 可訪問的全部服務號」能力,生態應用據此發現並下鑽。
  • 服務號級資源(contacts / visitors / broadcasts / 服務號級 notices)統一以 /openapi/v1/service-numbers/&#123;snId&#125;/... 巢狀路徑表達;閘道器從 path variable 解析 serviceNumberId,以 header X-Aile-Service-Number-Id 透傳給下游領域服務。
  • 租戶級資源(tenants / users / groups / addressbook / aiff / entry-sources / business / sync)保持扁平路徑,不引入服務號字首。
  • serviceNumberId 僅作為路由 / 上下文引數,不參與簽名計算(詳見 §8.1)。
  • 群發場景取代舊版「服務號級 notice 寫入」入口:整合方透過 /openapi/v1/service-numbers/&#123;snId&#125;/notices 直接建立通知,而是統一透過上表 POST .../broadcasts 介面提交群發任務(單條傳送視為 audience 長度為 1 的群發任務),Aile 群發流水線完成命中 / 排程 / 退訂 / 頻次 / 時間窗治理後下發;/notices/&#123;noticeId&#125; 僅保留只讀查詢語義。
  • 下游服務對映可隨架構演進調整,以 Spring @ConfigurationProperties 載入,避免硬編碼。

9.3 鑑權與狀態門控

  • tenantIntegrationId 對應的 TenantIntegration.status 必須 = ACTIVE,否則返回 403 TENANT_INTEGRATION_NOT_ACTIVE
  • subscribedEvents 僅用於事件過濾,不參與入站 API 鑑權(本期不啟用 scope)
  • 對服務號級路徑(/openapi/v1/service-numbers/&#123;snId&#125;/...),閘道器需額外校驗:tenantIntegrationId 必須有權訪問該 serviceNumberId(即該 service_number 已繫結到當前 integration),校驗不透過返回 403 SERVICE_NUMBER_FORBIDDEN
  • 對 EntrySource 資源(/openapi/v1/entry-sources/&#123;sourceId&#125;)的 GET / PUT / 狀態切換,閘道器在下游響應前由 aile-service-room 進行 owner 檢查:EntrySource.owner.tenantIntegrationId 必須 = 當前 tenantIntegrationId,否則返回 403 ENTRY_SOURCE_FORBIDDEN;列表查詢預設僅返回 owner 為當前 integration 的記錄
  • 路由失敗返回 404 ROUTE_NOT_FOUND

9.4 EntrySource 整合規範(進線原因 owner 歸屬與 webhook 路由)

EntrySource(進線來源,詳見進線原因規格設計)是租戶級共享資源,但每條記錄歸屬唯一建立方(integration / Aile 後臺管理員)。本服務在閘道器層為 EntrySource 注入 owner 標記並據此完成事件迴流,使「誰建立,誰接收」成為整合契約的硬約束。

9.4.1 EntrySource owner 欄位(由 aile-service-room 持久化)

閘道器在轉發 POST /openapi/v1/entry-sources 時,自動在請求體中注入 owner 子結構(整合方自行填寫,即便填寫也會被覆蓋):

{
"owner": {
"type": "integration",
"integrationAppId": "aireach",
"tenantIntegrationId": "ti_xxx"
}
}
欄位取值說明
owner.typeintegration / aile_admin透過 /openapi/v1/entry-sources 建立的固定為 integration;Aile 後臺管理臺手工建立的為 aile_admin
owner.integrationAppId當前 IntegrationApp.appIdaireach;aile_admin 型別為 null
owner.tenantIntegrationId當前 TenantIntegration.integrationId用於精確路由 webhook 回撥;aile_admin 型別為 null

EntrySource 在建立後,owner 欄位不可修改;解除安裝安裝例項(TenantIntegration → DELETED)時,該 integration 名下所有 EntrySource 由本服務級聯呼叫 PUT .../status (inactive) 停用,但不刪除記錄(保留歷史 Session 的進線原因可讀性)。

9.4.2 EntrySource 觸發 → 事件迴流路由規則

當使用者透過某個 EntrySource 進線建立 Session,aile-service-room 按以下規則路由 session.* 事件:

核心約束:

  • integration.integrationId 來源於 EntrySource.owner,而非「觸發使用者當前所屬租戶的全部 integration」——多個 integration 安裝到同一租戶時,只有建立該 EntrySource 的 integration 會收到迴流,其它 integration 不感知。
  • 若該 tenantIntegrationId 已不在 ACTIVE 狀態(SUSPENDED / DISABLED / DELETED),按 §6.2 狀態機規則不投遞,事件僅寫 EventLog 備查(publishStatus = FAILED,failureReason = "OWNER_INTEGRATION_NOT_ACTIVE")。
  • owner.type = aile_admin 的 EntrySource 觸發時不發整合 webhook,僅在 Aile 內部產生 Session 流轉。

9.4.3 新增 session.* 資源域

為支撐上述迴流鏈路,本期 §10.2 事件清單新增 session.* 資源域(由 aile-service-room 在 EntrySource 觸發 Session 時發出),清單詳見 §10.2。

9.4.4 整合方接入約定

  • 整合方透過 POST /openapi/v1/entry-sources 建立 EntrySource 時,無需單獨配置 webhook URL —— 事件統一走該 integration 安裝時握手協議給出的 webhookUrl,無第二條投遞通道。
  • 整合方需在 TenantIntegration.subscribedEvents 中訂閱 session.* 資源域,否則即使 owner 匹配也不會收到迴流(本服務在釋出前按 subscribedEvents 過濾)。
  • 單條 EntrySource 由單一 integration 擁有(1:1);如需多 integration 共享同一進線入口,各自建立獨立 EntrySource 並指向同一目標渠道即可。

十、事件體系

10.1 EventEnvelope 結構

統一事件信封結構如下,所有 Aile 領域服務發出的事件都必須封裝為此結構後入 Pub/Sub:

{
"eventId": "evt_xxx",
"eventType": "contact.entered",
"eventVersion": "1.0",
"occurredAt": "2026-05-20T10:00:00Z",
"source": "aile-service-auth",
"integration": {
"appId": "aipower",
"integrationId": "ti_xxx"
},
"tenant": {
"aileTenantId": "t_xxx",
"aileTenantType": "PERSONAL",
"mappedExternalTenantId": "aip_xxx",
"mappedExternalSpaceId": null,
"ownerType": "AILE_PERSONAL",
"ownerId": "pt_xxx"
},
"scope": {
"serviceNumberId": "sn_xxx"
},
"data": { ... },
"metadata": { ... }
}

關鍵設計點:

  • source 為實際發出事件的領域服務名(不再寫死為 aile)
  • tenant 子結構同時包含 Aile 側與外部生態側兩個身份,供下游 webhook 服務路由到正確生態應用租戶
  • scope 子結構承載「業務作用域」語義,與 tenant(身份維度)嚴格分離;本期只包含 serviceNumberId,後續可擴充套件 channelType 等橫切維度
  • scope.serviceNumberId服務號級事件中必填,在租戶級事件中省略——具體分類見 §10.2
  • eventVersion 事件 schema 變更時遞增
  • scope 子結構及其內的 serviceNumberId 均不參與簽名計算(詳見 §8.1)

10.2 事件型別清單(資源域級)

本期本服務需支援封裝與釋出以下資源域:

資源域中文說明舉例事件型別發出服務scope.serviceNumberId
tenant.*租戶生命週期(Aile 側租戶本身的建立、修改、停用)tenant.created / tenant.updated / tenant.disabledaile-service-tenant不填(租戶級)
user.*使用者 / 賬號(租戶下的成員,跨服務號共享)user.created / user.updated / user.disabledaile-service-account不填(租戶級)
service_number.*服務號(ServiceNumber,租戶內的客戶進線入口 / 業務號;一個租戶可擁有 1~N 個)service_number.created / service_number.updated / service_number.deletedaile-service-tenant必填(= 主體 ID,自身即服務號)
contact.*聯絡人 / 客戶(實名歸戶後的穩定主體,租戶範圍全域性唯一)主體生命週期(租戶級):contact.created / contact.updated / contact.deleted
進線 / 關注行為(服務號級):contact.entered / contact.re_entered / contact.service_number_followed / contact.service_number_unfollowedaile-service-auth主體生命週期類不填;進線 / 關注行為類必填
visitor.*訪客(未實名的臨時主體,可被合併 / 晉升為 contact)主體級:visitor.created / visitor.merged
進線行為(服務號級):visitor.enteredaile-service-auth主體級不填;進線行為類必填
group.*群組 / 聊天室(租戶級,可跨服務號組織)group.created / group.member_changedaile-service-room不填(租戶級,群組跨服務號)
addressbook.*通訊錄(租戶成員 / 聯絡人組織目錄同步)addressbook.syncedaile-service-auth不填(租戶級)
notice.*通知狀態(送達 / 失敗 / 已讀 / 點選 / 退回 / 投訴 / 任務完成 / 轉化 等狀態回撥)基礎(v1.0):notice.delivered / notice.failed / notice.read
v1.3 推播場景閉環擴充套件:notice.clicked(連結點選) / notice.bounced(投遞失敗子類:號碼無效 / 拒收 / 通道故障) / notice.complained(使用者標記騷擾) / notice.task_completed(整批傳送任務終態) / notice.converted(Aile 側歸因轉化)aile-service-message通知繫結具體服務號時必填;租戶級通知可省略
session.*會話生命週期(由 EntrySource 觸發的客戶會話)。按 EntrySource owner 精確路由回建立該來源的 integration,而非廣播給同租戶全部 integration(詳 §9.4)session.created / session.closed / session.transferredaile-service-roomEntrySource 繫結具體服務號時必填;租戶級 EntrySource 觸發時可省略

事件分類口徑:

  • 租戶級事件:主體跨服務號共享,scope.serviceNumberId 省略。包含 tenant / user / addressbook / group 全量,以及 contact / visitor 的主體生命週期事件。
  • 服務號級事件:行為發生在某個具體服務號上下文,scope.serviceNumberId 必填。包含 contact / visitor 的進線、關注 / 取消關注事件,以及繫結到具體服務號的 notice。
  • 進線行為單獨建模為 contact.entered / visitor.entered 等獨立事件型別,正確表達 contact ↔ service_number 的 N:M 關係——同一 contact 進入第二個服務號時,觸發 contact.created,而是觸發 contact.entered。
  • EventPublishClient 在封裝時按 eventType 自動校驗 scope.serviceNumberId 是否符合上述約束;違反約束直接拋錯,避免下游接收側得到含義不明的事件。

投遞約定:下游 webhook 服務統一投遞到 TenantIntegration.webhookUrl,不再按事件型別分流——本期合併 URL,所有資源域事件(含 notice.*)共用同一入口。接收側在自己 webhook handler 內根據 EventEnvelope 的 eventType 欄位分發到對應處理邏輯(業務異動 / 通知狀態 / 等)。

NoticeContent 訊息契約與 notice.clicked 觸發規則(v1.4 補充)

Aile 的「通知」是一種結構化富文字訊息格式(本服務不持有該領域邏輯,由 aile-service-message 承載),其傳送體(NoticeContent)由生態應用逐條組合後提交,Aile 不維護預審模板。為使 notice.clicked 事件能被正確路由回傳送應用,並與「進線」聯動,NoticeContent 與事件 payload 需遵以下約定:

  1. NoticeContent 結構:title / image / describe / buttons (0..N)。每個 buttonbuttonId (傳送方給定、語義化) / label / actionType / payload
  2. actionType 列舉:postback / url / aiff / action。點選捕獲路徑差異:
    • postback: 客戶端 → aile-service-message 後端 postback 端點 → 解析 payload
    • url: aile-service-message 包一層跳轉代理(形如 /notice-click/&#123;noticeId&#125;/&#123;buttonId&#125;)記錄點選 → 302 至目標 URL。
    • aiff / action: 由 AileDesktop 客戶端攔截點選後埋點上報 aile-service-message。
  3. postback button.payload 契約(本服務與傳送應用雙方約束):
    • appId (string, 必填):傳送方 IntegrationApp 的 integrationAppId,aile-service-message 據此反查該服務號下哪個 TenantIntegration 需被通知。
    • clickId (string, 必填):傳送方自定義業務語義標識(例:campaign_42_cta_redeem)。所有其他 actionType 的 payload 僅供 Aile 本身使用,不作路由依據。
    • extra (object, 可選):業務自定義欄位,原樣透傳回 webhook。
  4. **notice.clicked event payload(EventEnvelope.data 子結構)**必含:
    • noticeId / buttonId / actionType / contactId / clickedAt
    • postbackPayload:原樣透傳傳送方在 NoticeContent 中給定的按鈕 payload(含 appId / clickId / extra)
    • inbound (可選子結構,由 Aile 側進線規則決定是否攜帶):&#123; triggered: boolean, conversationId?: string, reason?: &#123; code, label, source &#125; &#125;
  5. 點選 ↔ 進線聯動由 Aile 編排:aile-service-message 攔截點選 → 依賴 aile-service-room 提供的進線規則(按 serviceNumberId / actionType / payload.clickId 等判據是否同時建立 / 複用 contact↔serviceNumber 進線) → 如命中,將 inbound 子結構填充入 event payload → EventPublishClient 封裝 EventEnvelope 交由本服務入 Pub/Sub。整合方接收一條 notice.clicked event 即可同時獲取「點選事實 + 進線事實 + 進線原因」。
  6. **inbound.reason.code**推薦格式:notice_click_<clickId> 或由 Aile 運營配置的對映表生成。

本服務不新增領域邏輯,僅在「事件封裝 + 釋出」環節保證如上結構被原樣以 EventEnvelope.data 透傳。NoticeContent 結構的儲存 / 點選捕獲路由 / 進線規則引擎均由 aile-service-message + aile-service-room 實現。

10.3 事件三段鏈路

職責邊界:

  • 本服務只負責封裝 + 釋出 + 寫 EventLog
  • 不負責重試/DLQ/最終投遞
  • EventLog 僅供審計與排障,不作為重試源
  • 上游領域服務接入方式:在該服務的應用服務層呼叫 EventPublishClient(api 模組提供的 FeignClient)

10.4 EventLog 儲存

@Document("aile.platform.event.log")
@CompoundIndexes({
@CompoundIndex(name = "eventId_1", def = "{'eventId': 1}", unique = true),
@CompoundIndex(name = "tenant_time",
def = "{'aileTenantId': 1, 'occurredAt': -1}"),
@CompoundIndex(name = "ttl_idx",
def = "{'occurredAt': 1}",
expireAfterSeconds = 2592000) // 30 天 TTL
})
public class EventLogModel extends BaseModel {
private String eventId;
private String eventType;
private String integrationId;
private String aileTenantId;
private String envelopeJson; // 完整 EventEnvelope JSON 存档
private PublishStatus publishStatus; // PUBLISHED / FAILED
private String failureReason;
private Long occurredAt;
}
  • TTL = 30 天,到期自動清理
  • publishStatus = FAILED 表示推入 Pub/Sub 本身失敗(僅本服務職責範圍),與下游 webhook 服務的重試狀態無關
  • 僅供查詢/審計,不提供重投介面(重投需走下游 webhook 服務)

十一、管理面介面

11.1 IntegrationApp 管理(平臺管理臺手工建立)

介面說明
POST /admin/integrations/apps建立生態應用定義
GET /admin/integrations/apps列表查詢
GET /admin/integrations/apps/&#123;appId&#125;查詢單個
PUT /admin/integrations/apps/&#123;appId&#125;更新(僅名稱/描述/事件清單等可變欄位)
POST /admin/integrations/apps/&#123;appId&#125;/deprecate下架,狀態 → DEPRECATED

11.2 TenantIntegration 管理

介面說明
POST /admin/integrations/tenant-integrations啟動安裝握手(走§7 協議)
GET /admin/integrations/tenant-integrations列表查詢(支援按 aileTenantId / appId / status 篩選)
GET /admin/integrations/tenant-integrations/&#123;integrationId&#125;詳情
POST /admin/integrations/tenant-integrations/&#123;integrationId&#125;/suspendACTIVE → SUSPENDED
POST /admin/integrations/tenant-integrations/&#123;integrationId&#125;/resumeSUSPENDED/DISABLED → ACTIVE
POST /admin/integrations/tenant-integrations/&#123;integrationId&#125;/disable→ DISABLED
POST /admin/integrations/tenant-integrations/&#123;integrationId&#125;/uninstall→ DELETED + 呼叫下游 /uninstall
POST /admin/integrations/tenant-integrations/&#123;integrationId&#125;/rotate-secret硬切換金鑰
GET /admin/integrations/tenant-integrations/&#123;integrationId&#125;/audits查詢審計軌跡

11.3 鑑權

  • 複用現有使用者登入體系(同 Aile 後臺管理臺),讀取當前登入使用者身份
  • 平臺管理員角色可訪問 /admin/integrations/apps/**(生態應用定義)
  • 租戶管理員可訪問本租戶的 /admin/integrations/tenant-integrations/**
  • 本服務不重複實現鑑權,複用 aile-service-auth 提供的 RBAC 攔截器

十二、資料遷移與上線

12.1 遷移範圍

當前 aile-service-job 中的 TenantAppModel 是本服務未拆分前的簡化實現,由本服務上線後一次性遷移並下線

舊實體/介面新位置處理方式
TenantAppModel(aile-service-job)TenantIntegrationModel(本服務)一次性指令碼遷移存量資料
/tenantapp/v1/* 介面/admin/integrations/tenant-integrations/openapi/v1/*舊介面直接下線(不提供相容期)
WebhookEventServiceImpl(aile-service-job)EventPublishAppService(本服務)原傳送點改為傳送到本服務 EventPublishClient
SignatureUtils 三套邏輯(各服務)HmacSignatureService(本服務 api 模組)各服務按§8.1 統一呼叫
SystemAppModel(aile-service-job)不動,保留原位內部服務間簽名繼續使用,與 IntegrationApp 互不干涉

12.2 遷移指令碼

// 偽程式碼
for each tenantApp in aile-service-job.TenantAppModel {
create IntegrationApp if not exists (provider=AIPOWER → appId=aipower)
create TenantIntegration {
integrationId = tenantApp.appId
tenantIntegrationSecret = tenantApp.secretKey // 明文沿用
aileTenantId = tenantApp.tenantId
appId = "aipower"
webhookUrl = tenantApp.webhookUrl // 旧 tenantApp.noticeUrl 不再使用,合并至唯一 webhookUrl;迁移后由租户管理员确认是否需改成 noticeUrl
status = ACTIVE
subscribedEvents = ["*"] // 存量按全订阅处理
}
create TenantMapping {
aileTenantId = tenantApp.tenantId
externalTenantId = tenantApp.aipowerTenantId
ownerType = AILE_PERSONAL // 存量推断
ownerId = tenantApp.tenantId
}
write audit log: actor=system, reason="migration from TenantAppModel"
}

12.3 上線步驟

  1. 部署本服務,建立 Mongo collection + 索引
  2. 配置 DownstreamRouteRegistry(各下游服務接入)
  3. 平臺管理員透過 /admin/integrations/apps 建立 aipower 應用定義
  4. 跑遷移指令碼匯入存量 TenantAppModelTenantIntegration + TenantMapping
  5. 切流量:AIPower 改用 /openapi/v1/*,各 Aile 子服務事件傳送點改用本服務 EventPublishClient
  6. 觀察 EventLog + 閘道器訪問日誌一週,確認無迴歸
  7. 下線 /tenantapp/v1/* 介面與 TenantAppModel

十三、階段一交付清單

類別交付物
新建服務aile-service-platform-integration(完整 DDD 分層)
新建 api 模組aile-platform-integration-api(模型+列舉+DTO+EventPublishClient+HmacSignatureService)
新建 Mongo 集合aile.platform.integration.app / aile.platform.tenant.integration / aile.platform.tenant.integration.audit / aile.platform.tenant.mapping / aile.platform.event.log
核心介面/install /update /uninstall /rotate-secret(控制面)
/openapi/v1/*(閘道器)
/admin/integrations/*(管理面)
統一簽名HmacSignatureService 替換三套 SignatureUtils
事件鏈路EventPublishAppService + Pub/Sub publisher + EventLog
遷移指令碼TenantAppModel → TenantIntegration / TenantMapping
下線項aile-service-job 中的 TenantAppModel、/tenantapp/v1/*、WebhookEventServiceImpl
釋出範圍tenant.* / user.* / service_number.* / contact.* / visitor.* / group.* / addressbook.* / notice.*(本期 notice 子事件含 delivered / failed / read / clicked / bounced / complained / task_completed / converted,詳 §10.2)
不在本期scope 鑑權、nonce 防重放、KMS 加密、AIPower 反向能力呼叫、PENDING_USER_CONFIRM 反向回撥、域名白名單、灰度相容期

十四、修改歷程

版本日期作者修訂摘要
v0.1chunlei zhu初版(已歸檔,不再維護)
v0.22026-05-20chunlei zhu系統性重寫:補齊安裝協議、閘道器層、事件三段鏈路;明確服務邊界為 Open Integration Layer(零業務領域邏輯);抽象外部租戶命名為 externalTenantId/externalSpaceId;鎖定本期不實現項
v1.02026-05-21chunlei zhu定稿為開發實施版本:移除對其他設計文件的外部引用,將「與上位方案差異」改寫為本期最終設計決策,文件自包含,作為開發團隊實施指導依據
v1.12026-05-21chunlei zhu引入服務號(serviceNumber)作為橫切上下文:§10.1 EventEnvelope 新增 scope 子結構(serviceNumberId);§10.2 事件清單按租戶級/服務號級分類並新增 contact.entered 等進線行為事件;§9.2 路由表新增 /openapi/v1/service-numbers/ 頂層入口與服務號巢狀資源路徑;§6.3 補充 contact ↔ service_number N:M 關係約定;§9.3 增加 SERVICE_NUMBER_FORBIDDEN 鑑權規則;明確 scope 與 serviceNumberId 均不參與簽名
v1.22026-05-21chunlei zhu事件清單治理:統一服務號資源域命名為 service_number(ServiceNumber),取代原 service_account;§10.2 表格新增「中文說明」列,補齊每個資源域的業務含義;§6.3 / §9.3 / §十三 同步替換 service_account 表述;service_number.* 事件按服務號自身生命週期調整為 created / updated / deleted(原 bound / unbound 不屬於服務號自身動作,移除);contact 關注類事件 service_number_linked / unlinked 重新命名為 service_number_followed / unfollowed(語義為客戶是否關注該服務號),相應分類口徑同步調為「進線 / 關注行為」
v1.32026-05-22chunlei zhu推播場景閉環擴充套件(為承接 AiReach 等推播類生態應用):§10.2 notice.* 資源域追加 5 項擴充套件事件 — notice.clicked(連結點選) / notice.bounced(投遞失敗子類:號碼無效 / 拒收 / 通道故障) / notice.complained(使用者標記騷擾) / notice.task_completed(整批傳送任務終態,可替代生態應用的輪詢) / notice.converted(Aile 側歸因轉化);§13 釋出範圍同步更新。本服務自身行為不變(仍按 EventEnvelope 封裝 → Pub/Sub → 獨立 webhook 服務投遞);由 aile-service-message 在對應業務時機發起 EventPublishClient 呼叫即可
v1.42026-05-22chunlei zhuNoticeContent 訊息契約 + notice.clicked 觸發規則固化(§10.2 補充段落):1) 明確 Aile 不維護預審模板,應用按 NoticeContent(title / image / describe + buttons[],四類 actionType)自行組合傳送;2) postback button payload 契約:appId(必填,路由接收方) + clickId(必填,業務語義) + extra(可選);3) notice.clicked payload 必含 postbackPayload(原樣透傳傳送方按鈕 payload)與可選 inbound 子結構(triggered / conversationId / reason);4) 點選 ↔ 進線聯動由 Aile 編排(aile-service-message 攔截點選 + aile-service-room 提供進線規則 → 填充 inbound 後發事件),生態應用收一條 event 即同時獲知點選 + 進線;5) 本服務僅在事件封裝環節保證上述結構透傳,NoticeContent / 點選捕獲路由 / 進線規則引擎實現在 aile-service-message + aile-service-room
v1.52026-05-26chunlei zhu§9.2 路由清單具體化 + 群發主鏈替代 notices 寫入 + EntrySource owner 整合規範:1) §9.2 由萬用字元字首表(如 /openapi/v1/service-numbers/&#123;snId&#125;/contacts/**)重寫為具體 method + path 白名單清單,拆分為 §9.2.1 租戶級與 §9.2.2 服務號級兩張表,每條 API 必須顯式註冊,未登記一律 404;2) 群發主鏈固化為 /openapi/v1/service-numbers/&#123;snId&#125;/broadcasts(A1 建立 / A2 查詢 / A3 取消),取代原「透過 /service-numbers/&#123;snId&#125;/notices 寫入」的群發入口,服務號級 /notices/&#123;noticeId&#125; 僅保留只讀語義,沿用 Aile 現行服務號群發流水線;3) 新增聯絡人標籤整合介面(GET /contacts/labels / POST /contacts/labels:add / POST /contacts/labels:remove / GET /contacts/&#123;contactId&#125;/labels)對齊 AiReach D 類用例;4) 新增進線原因 OpenAPI(/openapi/v1/entry-sources CRUD + 狀態切換)允許整合方租戶級自助登記 EntrySource;新增 §9.4「EntrySource 整合規範」明確 owner.integrationAppId / tenantIntegrationId 由閘道器自動注入、不可篡改、解除安裝時級聯停用;觸發迴流按 owner 精確路由——僅建立該 EntrySource 的 integration 收到事件,與同租戶其他 integration 隔離;5) §10.2 事件清單新增 session.* 資源域(session.created / closed / transferred)承載 EntrySource 觸發迴流