---
doc_id: broadcast-inboundreason-spec
title: 群發與進線原因規格說明書
description: Aile 群發任務、目標受眾、消息發送、進線原因配置、會話關聯、統計與 API 規格，供開發團隊參考。
slug: /specifications/broadcast-inboundreason-spec
product: Aile
category: specification
audience:
  - developer
  - architect
  - qa
visibility: public
status: published
version: 1.0.0
owner: aile-platform
updated_at: 2026-06-13
tags:
  - Aile
  - Broadcast
  - InboundReason
  - ServiceNumber
  - 規格說明書
rendered_html: /rendered/specifications/broadcast-inboundreason-spec/
download: true
sidebar_position: 8
---

# 群發（Broadcast）與進線原因（InboundReason）規格說明書

**版本：** v1.0  
**最後更新：** 2026-06-13  
**服務範圍：** `aile-service-tenant`（核心）、`aile-service-room`（協同）  

---

## 第一部分：群發（Broadcast）

### 1. 概述

群發（Broadcast）是 Aile 提供的批量消息推送能力，允許服務號向符合特定條件（標籤篩選或指定客戶列表）的客戶群發送訊息。群發支援定時發送、渠道過濾和結果統計。

---

### 2. BroadcastModel（群發持久化模型）

#### 2.1 資料結構

MongoDB 集合：`aile.tenant.servicenumber.broadcast`

```java
// aile-api/aile-tenant-api/.../model/servicenumber/BroadcastModel.java
@Document("aile.tenant.servicenumber.broadcast")
@CompoundIndexes({
    @CompoundIndex(name = "serviceNumberId", def = "{'serviceNumberId': 1}"),
    @CompoundIndex(name = "serviceNumberId_1_status_1_updateTime_1", 
        def = "{'serviceNumberId': 1, 'status': 1, 'updateTime': 1}")
})
public class BroadcastModel extends BaseModel {
    String name;                   // 任務標題
    String remark;                 // 備註
    String tenantId;               // 租戶 ID
    String accountId;              // 操作者帳號 ID
    String memberId;               // 操作者客服 memberId
    String serviceNumberId;        // 服務號 ID
    Long broadcastTime;            // 預定發送時間
    List<String> labelIds;         // 目標標籤 ID 列表（選填）
    List<String> customerIds;      // 目標客戶 ID 列表（選填）
    LabelMatchLogic matchLogic;    // 標籤匹配邏輯（AND / OR）
    List<BroadcastBody> content;   // 群發內容
    BroadcastStatus status;        // 狀態
    Channel channel;               // 目標渠道
    Integer totalCount;            // 目標總數
    Integer successCount;          // 成功數
    Integer failCount;             // 失敗數
}
```

#### 2.2 狀態機

```java
// aile-api/aile-tenant-api/.../enums/servicenumber/BroadcastStatus.java
public enum BroadcastStatus {
    Will,    // 待發送
    Doing,   // 發送中
    Done,    // 已完成
    Delete   // 已刪除
}
```

```
Will ──→ Doing ──→ Done
  │                 │
  └──→ Delete       └──→ Delete
```

---

### 3. 群發服務實現

#### 3.1 BroadcastServiceImpl

位置：`aile-service/aile-service-tenant/.../service/servicenumber/impl/BroadcastServiceImpl.java`

核心方法：

| 方法 | 說明 |
|------|------|
| `create(dto)` | 建立群發任務 |
| `update(dto)` | 更新任務（僅 Will 狀態可更新） |
| `start(broadcastId)` | 啟動發送（Will → Doing） |
| `complete(broadcastId)` | 完成發送（Doing → Done） |
| `delete(broadcastId)` | 刪除任務（軟刪除） |
| `list(dto)` | 分頁查詢群發任務 |

#### 3.2 目標受眾計算

```
BroadcastServiceImpl.start()
  │
  ├── 若有 labelIds
  │     ├── 依 matchLogic（AND/OR）查詢符合標籤的客戶列表
  │     └── 交叉過濾 customerIds（如有指定）
  │
  ├── 若僅有 customerIds
  │     └── 直接使用指定客戶列表
  │
  ├── 渠道過濾
  │     └── 僅向指定 channel 的客戶發送
  │
  ├── 計算 totalCount = 目標客戶數
  │
  └── 逐個客戶發送消息
        ├── 成功 → successCount++
        └── 失敗 → failCount++（記錄日誌）
```

---

### 4. 消息發送機制

#### 4.1 BroadcastMessageUtil

位置：`aile-service/aile-service-room/.../message/BroadcastMessageUtil.java`

群發消息透過 Room 服務發送：

```
BroadcastServiceImpl
  │
  └── MessageFeign.sendServiceMessage(dto)  [跨服務調用]
        │
        └── aile-service-room
              │
              └── BroadcastMessageUtil.sendBroadcastMessage()
                    ├── 建立 MessageModel
                    ├── 寫入對應 Services Room
                    └── 透過 GatewayFeign 外發
```

#### 4.2 廣播內容格式

```java
// BroadcastBody（內嵌於 BroadcastModel）
public class BroadcastBody {
    MessageType type;    // 消息類型（Text / Image / Template 等）
    String text;         // 文字內容
    String fileUrl;      // 媒體文件 URL
    // Template 消息
    TemplateMessage template; // 模板消息（包含 Action 按鈕等）
}
```

---

### 5. 群發 API

路由：`/servicenumber/broadcast/`

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/broadcast/create` | 建立群發任務 |
| `POST` | `/broadcast/update` | 更新群發任務 |
| `POST` | `/broadcast/start` | 啟動發送 |
| `POST` | `/broadcast/complete` | 標記完成 |
| `POST` | `/broadcast/delete` | 刪除任務 |
| `POST` | `/broadcast/list` | 查詢任務列表 |
| `GET` | `/broadcast/detail?broadcastId=` | 查詢任務詳情 |
| `POST` | `/broadcast/target-batch` | 批量查詢目標受眾 |

---

### 6. 定時發送

群發支援預約發送，透過 `broadcastTime` 欄位指定未來時間：

```
BroadcastModel.broadcastTime
  │
  └── 定時任務掃描 (Cron / OnceScheduleManager)
        ├── 檢查 broadcastTime ≤ now
        ├── 檢查 status = Will
        └── 自動調用 start()
```

---

## 第二部分：進線原因（InboundReason）

### 7. 概述

進線原因（InboundReason）是 Aile 提供的客戶進線來源標記系統，用於追蹤客戶是從哪個業務場景進入客服對話的。支援兩種模式：

| 模式 | 說明 |
|------|------|
| **預設來源（Preset）** | 管理員預先配置的進線原因，客戶進線時透過 URL 參數或 API 指定 |
| **即時原因（RealTime）** | 客戶進線時動態傳入的進線原因資料 |

---

### 8. ServiceInboundReasonModel（進線原因配置）

#### 8.1 資料結構

MongoDB 集合：`aile.tenant.inbound.reason`

```java
// aile-api/aile-tenant-api/.../model/inboundreason/ServiceInboundReasonModel.java
@Document("aile.tenant.inbound.reason")
@CompoundIndexes({
    @CompoundIndex(name = "tenantId", def = "{'tenantId': 1}")
})
public class ServiceInboundReasonModel extends BaseModel {
    String tenantId;                          // 租戶 ID
    String sourceName;                        // 來源名稱（如「官網首頁」）
    ServiceInboundReasonTypeEnum sourceType;  // 來源類型
    String description;                       // 進線原因描述
    String businessId;                        // 業務 ID（關聯特定業務場景）
    Long validityPeriod;                      // 有效期（null 或 0 表示永久有效）
    Boolean status;                           // 啟用/禁用
}
```

#### 8.2 進線原因類型枚舉

```java
// aile-api/aile-tenant-api/.../enums/ServiceInboundReasonTypeEnum.java
public enum ServiceInboundReasonTypeEnum {
    SCAN_QR_CODE("qrcode", "掃碼進線"),       // 門店、海報、線下活動
    PRODUCT_CONSULT("product", "商品咨詢"),    // 商品詳情頁、商品列表
    PROMOTION_CONSULT("activity", "活動咨詢"), // 促銷活動卡片、活動頁面
    SIGNATURE("signature", "簽名檔進線"),      // 員工郵件簽名檔連接
    CUSTOMER_SUPPORT("support", "客服支持"),   // 幫助中心、聯繫我們
    PRICING_CONSULT("pricing", "定價咨詢"),    // 定價頁面、價格表
    CUSTOM("custom", "自定義")                 // 業務自定義場景
}
```

---

### 9. ServiceInboundReasonSessionModel（進線原因-會話關聯）

#### 9.1 資料結構

MongoDB 集合：`aile.tenant.servicenumber.inbound.reason.session`

```java
// aile-api/aile-tenant-api/.../model/inboundreason/ServiceInboundReasonSessionModel.java
@Document("aile.tenant.servicenumber.inbound.reason.session")
@CompoundIndexes({
    @CompoundIndex(name = "sourceId", def = "{'sourceId': 1}"),
    @CompoundIndex(name = "sessionId", def = "{'sessionId': 1}"),
    @CompoundIndex(name = "serviceNumberId_sourceId", def = "{'serviceNumberId': 1, 'sourceId': 1}"),
    @CompoundIndex(name = "serviceNumberId_sourceId_transferToAgent", 
        def = "{'serviceNumberId': 1, 'sourceId': 1, 'transferToAgent': 1}")
})
public class ServiceInboundReasonSessionModel extends BaseModel {
    String tenantId;                          // 租戶 ID
    String serviceNumberId;                   // 服務號 ID
    String sessionId;                         // 會話 ID
    String roomId;                            // 聊天室 ID
    String customerId;                        // 客戶 ID
    String sourceId;                          // 進線原因 ID（關聯 ServiceInboundReasonModel）
    Boolean transferToAgent;                  // 是否轉人工
    ServiceInboundReasonOrigin originType;    // 來源類型：Preset / RealTime
    String sourceData;                        // 即時原因資料（JSON 字串，originType=RealTime 時使用）
    String sourceName;                        // 來源名稱快照（建立關聯時的來源名稱）
    ServiceInboundReasonTypeEnum sourceType;  // 來源類型快照
}
```

---

### 10. 進線原因來源類型

```java
// aile-api/aile-tenant-api/.../enums/ServiceInboundReasonOrigin.java
public enum ServiceInboundReasonOrigin {
    Preset,   // 預設來源（管理員配置的進線原因）
    RealTime  // 即時原因（客戶進線時動態傳入）
}
```

| 類型 | 說明 | 使用場景 |
|------|------|----------|
| `Preset` | 預先配置好的進線原因 | 掃碼進線、商品頁面進線、活動頁面進線 |
| `RealTime` | 進線時動態指定的原因 | 自定義業務場景、外部系統傳入的臨時原因 |

---

### 11. 進線原因服務

#### 11.1 ServiceInboundReasonService

位置：`aile-service/aile-service-tenant/.../service/inboundreason/ServiceInboundReasonService.java`

提供進線原因配置的 CRUD：

| 方法 | 說明 |
|------|------|
| `create(dto)` | 建立進線原因配置 |
| `update(dto)` | 更新配置 |
| `delete(id)` | 刪除配置 |
| `list(dto)` | 分頁查詢配置列表 |
| `enable(id)` / `disable(id)` | 啟用/停用 |

#### 11.2 ServiceInboundReasonSessionService

位置：`aile-service/aile-service-tenant/.../service/inboundreason/ServiceInboundReasonSessionService.java`

提供進線原因與會話的綁定：

```java
public interface ServiceInboundReasonSessionService extends BaseMongoService<ServiceInboundReasonSessionModel> {
    void bindSession(ServiceInboundReasonSessionBindDto bindDto);
    void markTransferToAgent(String sessionId);
}
```

---

### 12. 進線原因綁定流程

```
客戶進線（攜帶進線原因參數）
  │
  ├── 透過 URL 參數 ?sourceId=xxx 指定預設原因
  │     或
  ├── 透過 API 傳入即時原因資料
  │
  └── ServiceInboundReasonServiceImpl
        │
        ├── 查詢 ServiceInboundReasonModel（若 sourceId 有效）
        ├── 建立 ServiceSession（會話）
        └── ServiceInboundReasonSessionService.bindSession(bindDto)
              │
              ├── 建立 ServiceInboundReasonSessionModel
              │     ├── originType = Preset（若使用預設原因）
              │     │            = RealTime（若動態傳入）
              │     ├── sourceName = 快照當前的來源名稱
              │     └── sourceData = 即時原因的 JSON 資料（RealTime 時）
              │
              └── 關聯 sessionId ↔ sourceId
```

---

### 13. 轉人工標記

```java
ServiceInboundReasonSessionService.markTransferToAgent(sessionId)
  │
  └── 更新 ServiceInboundReasonSessionModel.transferToAgent = true
```

此標記用於統計：哪些進線原因的客戶最終轉了人工客服。

---

### 14. 進線原因統計

基於 `ServiceInboundReasonSessionModel` 的聚合查詢：

| 統計維度 | 說明 |
|----------|------|
| 按來源類型 | 各來源類型的進線量、轉人工量 |
| 按預設原因 | 每個預設配置的進線量 |
| 按即時原因 | 按 sourceName 分組的即時原因進線量 |
| 轉人工率 | `transferToAgent=true` 的比例 |

---

### 15. 進線原因 API

#### 15.1 進線原因配置 API

路由：`/inboundreason/`

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/inboundreason/create` | 建立進線原因 |
| `POST` | `/inboundreason/update` | 更新進線原因 |
| `POST` | `/inboundreason/delete` | 刪除進線原因 |
| `POST` | `/inboundreason/list` | 查詢進線原因列表 |
| `GET` | `/inboundreason/detail?inboundReasonId=` | 查詢詳情 |

#### 15.2 進線原因關聯查詢 API

| 方法 | 路徑 | 說明 |
|------|------|------|
| `GET` | `/inboundreason/session/list?sessionId=` | 查詢會話的進線原因 |
| `GET` | `/inboundreason/session/statistics` | 進線原因統計 |

---

### 16. 業務場景示例

#### 16.1 掃碼進線

```
1. 管理員配置進線原因：
   sourceName = "台北門市"，sourceType = SCAN_QR_CODE
2. 生成帶參數的二維碼 URL：
   https://line.me/R/ti/p/@xxx?sourceId=INB_001
3. 客戶掃碼後進線：
   → 自動綁定 sourceId="INB_001"
   → originType=Preset
   → 記錄進線原因快照
```

#### 16.2 即時原因

```
1. 外部系統呼叫進線 API，附帶：
   { sourceName: "訂單 #12345 查詢", sourceType: "custom", sourceData: { orderId: "12345" } }
2. 系統建立進線原因關聯：
   → originType=RealTime
   → sourceData = {"orderId":"12345"}
   → 客服端顯示「進線原因：訂單 #12345 查詢」
```

---

### 17. 群發與進線原因的關聯

群發任務可以透過進線原因數據進行精準投放：

```
BroadcastServiceImpl（選擇目標受眾時）
  │
  ├── 可選：篩選有特定進線原因的客戶
  │     ├── 查詢 ServiceInboundReasonSessionModel
  │     │     └── sourceId = "INB_001"（掃碼進線的客戶）
  │     └── 取得 customerId 列表
  │
  └── 將篩選結果作為群發目標
```

---

### 18. 關鍵檔案索引

#### 群發（Broadcast）

| 層級 | 檔案 | 說明 |
|------|------|------|
| API Model | `aile-api/aile-tenant-api/.../model/servicenumber/BroadcastModel.java` | 群發持久化模型 |
| API Enum | `aile-api/aile-tenant-api/.../enums/servicenumber/BroadcastStatus.java` | 群發狀態枚舉 |
| Service | `aile-service/aile-service-tenant/.../service/servicenumber/impl/BroadcastServiceImpl.java` | 群發服務實現 |
| Room Util | `aile-service/aile-service-room/.../message/BroadcastMessageUtil.java` | 群發消息工具 |

#### 進線原因（InboundReason）

| 層級 | 檔案 | 說明 |
|------|------|------|
| API Model | `aile-api/aile-tenant-api/.../model/inboundreason/ServiceInboundReasonModel.java` | 進線原因配置模型 |
| API Model | `aile-api/aile-tenant-api/.../model/inboundreason/ServiceInboundReasonSessionModel.java` | 進線原因-會話關聯模型 |
| API Enum | `aile-api/aile-tenant-api/.../enums/ServiceInboundReasonTypeEnum.java` | 進線原因類型枚舉 |
| API Enum | `aile-api/aile-tenant-api/.../enums/ServiceInboundReasonOrigin.java` | 來源類型枚舉 |
| API DTO | `aile-api/aile-tenant-api/.../dto/inboundreason/ServiceInboundReasonSessionBindDto.java` | 會話綁定 DTO |
| Service | `aile-service/aile-service-tenant/.../service/inboundreason/ServiceInboundReasonService.java` | 進線原因服務接口 |
| Service | `aile-service/aile-service-tenant/.../service/inboundreason/ServiceInboundReasonSessionService.java` | 關聯服務接口 |
| Service Impl | `aile-service/aile-service-tenant/.../service/inboundreason/impl/ServiceInboundReasonServiceImpl.java` | 進線原因服務實現 |
| Service Impl | `aile-service/aile-service-tenant/.../service/inboundreason/impl/ServiceInboundReasonSessionServiceImpl.java` | 關聯服務實現 |
| Redis Key | `aile-api/aile-tenant-api/.../constant/ServiceInboundReasonRedisKey.java` | 進線原因 Redis Key |
