跳至主要内容

Aile 帳號身份體系規格說明書

版本: v1.0
最後更新: 2026-06-13
服務範圍: aile-service-accountaile-service-tenantaile-service-room


1. 概述

Aile 的身份體系採用分層設計,以 Account(帳號) 為頂層統一身份,向下關聯 AppAccount(應用帳號)TenantEmployee(員工)TenantContact(客戶/聯繫人) 等租戶級身份。系統支援匿名訪客到實名用戶的平滑過渡,並透過 Scope 機制管理渠道級身份映射。

1.1 核心設計原則

原則說明
Account 是統一身份錨點所有租戶級身份(員工、客戶)都歸屬於一個 Account
租戶隔離同一 Account 在不同租戶中有獨立的 Employee 或 Contact 記錄
渠道多身份同一 Account 可透過多個 channel+scopeId 訪問同一服務號
匿名可合併匿名訪客的 Contact 資料可在實名登入後合併到實名 Account

2. 身份層級總覽

┌─────────────────────────────────────────────────────────┐
│ AccountModel │
│ (aile.account) — 頂層統一身份 │
│ 欄位: mobile, channel, status, isSystem, personRoomId │
│ 類型: Anonymous | NonRealName | RealName | System │
└────────────┬──────────────┬──────────────────────────────┘
│ │
┌──────▼──────┐ ┌───▼──────────────────────────┐
│ AppAccount │ │ AccountScopeModel │
│ Model │ │ (aile.account.scope) │
│ (aile. │ │ 帳號在渠道上的 scope 映射 │
│ account │ │ accountId ↔ channel ↔ scopeId│
│ .app) │ └───────────────────────────────┘
│ 應用帳號 │
└─────────────┘

┌──────────────────────────────────────────────────────────┐
│ 租戶層 (Tenant) │
│ │
│ ┌─────────────────────┐ ┌────────────────────────────┐ │
│ │ TenantEmployeeModel │ │ TenantContactModel │ │
│ │ (aile.tenant. │ │ (aile.tenant.contact) │ │
│ │ employee) │ │ 客戶 / 聯繫人 │ │
│ │ 員工 │ │ type: RealName | │ │
│ │ joinType: Guarantor │ │ Anonymous | │ │
│ │ | Invitation │ │ Independent │ │
│ └─────────┬───────────┘ └─────────────┬──────────────┘ │
│ │ │ │
└────────────┼─────────────────────────────┼────────────────┘
│ │
┌──────▼──────┐ ┌───────▼──────────────┐
│ServiceMember│ │ ServiceNumberScope │
│ Model │ │ Model │
│ 服務號成員 │ │ 服務號進線 scope │
│ privilege: │ │ customerId ↔ │
│ Owner / │ │ serviceNumberId ↔ │
│ Manager / │ │ channel ↔ scopeId │
│ Common │ │ ↔ roomId │
└─────────────┘ └───────────────────────┘

┌───────▼──────────────┐
│ServiceNumberRelation │
│ Model │
│ 服務號訂閱關係 │
│ customerId ↔ │
│ serviceNumberId ↔ │
│ accountId │
└───────────────────────┘

3. AccountModel(頂層帳號)

3.1 資料結構

MongoDB 集合:aile.account

// aile-api/aile-account-api/.../model/AccountModel.java
@Document("aile.account")
public class AccountModel extends BaseModel {
String mobile; // 手機號碼
String countryCode; // 國碼
Channel channel; // 註冊渠道
Status status; // Enable / Disable(Saga 歸併可標記為 Deleted)
Boolean isSystem; // 是否為系統帳號
String personRoomId; // 個人聊天室 ID(系統級)
}

3.2 帳號類型(推導)

帳號類型透過 AccountType.get(AccountModel) 推導,非持久化欄位:

// aile-api/aile-account-api/.../enums/AccountType.java
public enum AccountType {
Anonymous, // 匿名用戶(Channel.isAnonymous() == true)
NonRealName, // 未實名用戶(無手機號)
RealName, // 實名用戶(有手機號)
System // 系統用戶(isSystem == true)
}

3.3 登入方式

// aile-api/aile-account-api/.../constant/LoginType.java
LINE(1), // LINE 登入
THIRD(2), // 第三方登入
OTP(3), // OTP 登入
PWD(4), // 密碼登入
ONCE_TOKEN(5), // 一次性 Token 登入
DEVICE(6), // 裝置登入
QRCODE(7), // 掃描登入
ACCESS(8), // 授權登入
SERVICE(9), // 服務登入
AUTO(10), // 自動登入
ANONYMOUS(11) // 匿名登入

3.4 帳號服務

位置:aile-service/aile-service-account/.../service/impl/AccountServiceImpl.java

方法說明
register(dto)註冊新帳號(手機號 + 渠道)
login(dto)登入(支援多種登入方式)
syncCreate(dto)同步建立(供跨服務調用)
update(dto)更新帳號資訊

4. AppAccountModel(應用帳號)

4.1 資料結構

MongoDB 集合:aile.account.app

// aile-api/aile-account-api/.../model/AppAccountModel.java
@Document("aile.account.app")
@CompoundIndexes({
@CompoundIndex(name = "accountId_1_channel_1", def = "{'accountId': 1, 'channel': 1}"),
@CompoundIndex(name = "accountId_1_channel_1_status_1", def = "{'accountId': 1, 'channel': 1, 'status': 1}"),
@CompoundIndex(name = "accountId_1_updateTime_1", def = "{'accountId': 1, 'updateTime': 1}")
})
public class AppAccountModel extends BaseModel {
String accountId; // 關聯的 Account ID
String name; // 顯示名稱
Integer age; // 年齡
Gender gender; // 性別
String birthday; // 生日
List<String> interests;// 興趣標籤
Channel channel; // 渠道
String password; // 密碼(加密)
String avatarId; // 頭像 ID
Status status; // Enable / Disable
Boolean androidMute; // Android 靜音
Boolean iosMute; // iOS 靜音
Boolean pcMute; // PC 靜音
// ...
}

4.2 設計意圖

  • 一個 Account 可有多個 AppAccount:每個 accountId + channel 組合對應一個 AppAccount
  • AppAccount 儲存該渠道下的個人資料(頭像、暱稱、靜音設定等)
  • 與 AccountModel 的關係為 多對一

5. AccountScopeModel(帳號渠道 Scope)

5.1 資料結構

MongoDB 集合:aile.account.scope

// aile-api/aile-account-api/.../model/AccountScopeModel.java
@Document("aile.account.scope")
@CompoundIndexes({
@CompoundIndex(name = "scopeId_1_channel_1", def = "{'scopeId': 1, 'channel': 1}"),
@CompoundIndex(name = "accountId_1_channel_1_scopeId_1", def = "{'accountId': 1, 'channel': 1, 'scopeId': 1}")
})
public class AccountScopeModel extends BaseModel {
String scopeId; // 渠道側唯一 ID(如 LINE userId)
Channel channel; // 渠道
String accountId; // 關聯的 Account ID
String name; // 渠道側顯示名稱
}

5.2 用途

AccountScopeModel 是 Account 層級的渠道映射,與 Tenant 層級的 ServiceNumberScopeModel 互補:

層級模型用途
Account 層AccountScopeModelAccount ↔ channel ↔ scopeId(登入級)
Tenant 層ServiceNumberScopeModelContact ↔ serviceNumberId ↔ channel ↔ scopeId(服務號級)

6. TenantEmployeeModel(員工)

6.1 資料結構

MongoDB 集合:aile.tenant.employee

// aile-api/aile-tenant-api/.../model/TenantEmployeeModel.java
@Document("aile.tenant.employee")
public class TenantEmployeeModel extends BaseModel {
String name; // 姓名
String avatarId; // 頭像 ID
String mood; // 個性簽名
Integer age; // 年齡
Gender gender; // 性別
String birthday; // 生日
Status status; // Enable / Disable
String accountId; // 關聯的 Account ID
String tenantId; // 所屬租戶 ID
Channel channel; // 渠道
String personRoomId; // 個人聊天室 ID
String systemRoomId; // 系統聊天室 ID
TenantJoinType joinType; // 加入方式:Guarantor / Invitation
String openId; // 外部 OpenID
Boolean isJoinAile; // 是否已加入 Aile 生態
Boolean isBindAile; // 是否已綁定 Aile 生態
Boolean isCollectInfo; // 是否已收集資訊
String homePagePicId; // 主頁背景圖 ID
}

6.2 加入方式

// aile-api/aile-tenant-api/.../enums/TenantJoinType.java
public enum TenantJoinType {
Guarantor, // 擔保人邀請
Invitation // 邀請碼加入
}

6.3 與 Account 的關係

  • accountId 關聯 AccountModel
  • 一個 Account 可以在多個 Tenant 中作為 Employee
  • 同一 Account 在同一 Tenant 中只能有一條 Employee 記錄

6.4 加入生態流程

Account 登入

└── TenantBindService.send(TenantBindDto)

├── 向 Account 的個人聊天室發送「加入生態」卡片
├── Account 接受 → TenantBindService.collectInfo()
│ ├── 建立 TenantEmployeeModel
│ ├── isJoinAile = true
│ └── isBindAile = true

└── TenantBindService.check()
└── 檢查 Account 是否已加入該租戶

7. TenantContactModel(客戶 / 聯繫人)

7.1 資料結構

MongoDB 集合:aile.tenant.contact

// aile-api/aile-tenant-api/.../model/TenantContactModel.java
@Document("aile.tenant.contact")
public class TenantContactModel extends BaseModel {
// 基本資料
String name; // 姓名
Integer age;
Gender gender;
String birthday;
String phone;
String email;
// 公司資料
String company;
Boolean isAileCompany;
String companyAddress;
String companyPhone;
String companyEmail;
String companyDuty;
String companyDepartment;
// 其他
BloodType bloodType;
MaritalStatus maritalStatus;
List<Language> languages;
String interests;
String alias; // 備註名
String description; // 描述
String businessCardId; // 名片 ID
String openId; // 外部 OpenID
String avatarId; // 頭像 ID
// 關聯
Status status;
String accountId; // 關聯的 Account ID
String tenantId; // 所屬租戶 ID
Channel channel; // 渠道
Boolean isJoinAile; // 是否已加入 Aile
Boolean isBindAile; // 是否已綁定
Boolean isCollectInfo; // 是否已收集資訊
// 類型
ContactType type; // RealName / Anonymous / Independent
}

7.2 客戶類型

// aile-api/aile-tenant-api/.../enums/contact/ContactType.java
public enum ContactType {
RealName, // 實名會員(關聯了 Account)
Anonymous, // 匿名會員(未關聯 Account,僅有渠道 scopeId)
Independent // 獨立客戶(如 LineGroup 帶入的客戶,不屬於任何 Account)
}

7.3 關鍵規則

  • 客戶屬於租戶,不屬於服務號:TenantContactModel 只有 tenantId,沒有 serviceNumberId
  • 同一 Account 在同一 Tenant 中只能有一條 Contact 記錄
  • type = Anonymous 的客戶在實名登入後可被合併到 type = RealName 的客戶

8. ServiceMemberModel(服務號成員)

8.1 資料結構

MongoDB 集合:aile.tenant.servicenumber.member

// aile-api/aile-tenant-api/.../model/servicenumber/ServiceMemberModel.java
@Document("aile.tenant.servicenumber.member")
public class ServiceMemberModel extends BaseModel {
String serviceNumberId; // 服務號 ID
String memberId; // 成員 ID(即 TenantEmployeeModel.id)
String accountId; // 關聯 Account
String tenantId; // 租戶 ID
ServiceNumberType serviceNumberType; // 服務號類型
ServiceMemberPrivilege privilege; // Owner / Manager / Common
Status status; // Enable / Disable
}

8.2 權限層級

// aile-api/aile-tenant-api/.../enums/ServiceMemberPrivilege.java
public enum ServiceMemberPrivilege {
Owner, // 擁有者
Manager, // 管理員
Common // 一般成員
}

8.3 關係鏈

Account ──→ TenantEmployee ──→ ServiceMember

├── serviceNumberId
└── privilege (Owner / Manager / Common)

一個員工可以同時是多個服務號的成員,每個服務號中有獨立的權限。


9. ServiceNumberScopeModel(服務號進線 Scope)

9.1 資料結構

MongoDB 集合:aile.tenant.servicenumber.scope

// aile-api/aile-tenant-api/.../model/servicenumber/ServiceNumberScopeModel.java
@Document("aile.tenant.servicenumber.scope")
public class ServiceNumberScopeModel extends BaseModel {
String tenantId; // 租戶 ID
String accountId; // 帳號 ID
String customerId; // 客戶 ID(TenantContactModel.id)
Channel channel; // 渠道
String scopeId; // 渠道側唯一 ID(如 LINE userId 或 chat.code)
String serviceNumberId; // 服務號 ID
String code; // 渠道側主體 code
String name; // 渠道側顯示名稱
String avatarId; // 頭像 ID
String pictureUrl; // 頭像 URL
String roomId; // 對應的 Services Room ID
Status status; // Enable / Disable
}

9.2 核心作用

ServiceNumberScopeModel 是系統中最關鍵的橋接模型:

客戶 ──→ ServiceNumberScopeModel ──→ 服務號
│ │ │
│ ┌──────┴──────────┐ │
│ │ customerId │ │
│ │ serviceNumberId │ │
│ │ channel │ │
│ │ scopeId │ │
│ │ roomId │ │
│ └─────────────────┘ │
│ │
└── TenantContactModel ───────────────┘

一個客戶可以在同一服務號下有多條 Scope 記錄(不同 channel),但同一 channel + scopeId + serviceNumberId 組合唯一。


10. ServiceNumberRelationModel(服務號訂閱關係)

10.1 資料結構

MongoDB 集合:aile.tenant.servicenumber.relation

// aile-api/aile-tenant-api/.../model/servicenumber/ServiceNumberRelationModel.java
@Document("aile.tenant.servicenumber.relation")
public class ServiceNumberRelationModel extends BaseModel {
String serviceNumberId;
ServiceNumberType serviceNumberType;
String accountId; // 帳號 ID
String customerId; // 客戶 ID
String tenantId; // 租戶 ID
String roomId; // 聊天室 ID
ServiceOpenType openType;// 公開類型
ServiceNumberRelationStatus status; // 訂閱狀態
String alias; // 備註名
}

10.2 與 Scope 的區別

模型用途粒度
ServiceNumberScopeModel渠道進線路由(實際收發消息)channel + scopeId + serviceNumberId
ServiceNumberRelationModel業務層訂閱/關注關係customerId + serviceNumberId

11. 匿名訪客與實名合併

11.1 匿名訪客的產生

1. 用戶透過 LINE 匿名進入
→ Gateway Webhook 事件
→ 若無對應 Account:建立 AccountModel(type=Anonymous)
→ 建立 TenantContactModel(type=Anonymous)
→ 建立 ServiceNumberScopeModel
→ 建立 ServiceNumberRelationModel

11.2 匿名訪客 ContactType 標記

// TenantContactModel.type 可為:
ContactType.Anonymous // 匿名訪客,未關聯實名 Account
ContactType.RealName // 實名客戶,關聯了實名 Account
ContactType.Independent // 獨立客戶(如 LineGroup 帶入,不經過 Account)

11.3 實名登入合併流程

當匿名訪客進行實名登入(如輸入手機號)時:

AnonymousVisitorMergePlanAdapter.resolve(anonymousAccountId, targetAccountId)

├── 查詢 anonymousAccountId 下的所有 TenantContactModel(status=Enable)

├── 逐個客戶生成 ContactMergePlan:
│ ├── 查找目標 Account 在該租戶是否已有 Contact(targetContact)
│ ├── 收集匿名客戶的 ServiceNumberRelation 列表
│ ├── 收集目標客戶的 ServiceNumberRelation 列表
│ └── 生成合併計劃(標記源客戶刪除、遷移關聯)

└── Saga 編排執行:
├── MergeAnonymousVisitorContactDataStep(合併客戶資料)
├── ProcessServiceSessionMergeStep(合併會話)
├── MergeLineGroupContactRelationStep(合併 LineGroup 關聯)
└── 其他相關 Step

11.4 Saga 步驟清單

Step說明
ResolveContactMergePlanStep解析合併計劃
MergeAnonymousVisitorContactDataStep合併匿名訪客客戶資料
ProcessServiceSessionMergeStep合併會話記錄
MergeLineGroupContactRelationStep合併 LineGroup 關聯
MarkSourceServiceSessionDeletedStep標記源會話刪除
GatewayCustomerDeleteStep通知 Gateway 清理舊客戶

12. 房間成員類型

// aile-api/aile-room-api/.../enums/RoomMemberType.java
public enum RoomMemberType {
User, // 一般用戶(員工或客戶)
ServiceNumber, // 服務號
Robot, // 機器人
Assistant, // AI 助手
Room // 聊天室本身(系統消息)
}
成員類型對應實體說明
UserTenantEmployeeModelTenantContactModel房間中的人類成員
ServiceNumberServiceNumberModel服務號作為房間成員
Robot機器人帳號自動回覆機器人
AssistantTenantAssistantModelAI 助手

13. 完整關係圖

                          ┌──────────────────┐
│ AccountModel │
│ (aile.account) │
│ mobile, channel │
│ status, isSystem│
└───┬──────┬───────┘
│ │
┌───────────────┘ └───────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ AppAccountModel │ │ AccountScopeModel │
│ (aile.account. │ │ (aile.account. │
│ app) │ │ scope) │
│ name, avatar │ │ channel, scopeId │
│ channel, mute │ └──────────────────────┘
└──────────────────┘

│ accountId

┌──────────┴─────────────────────────────────────┐
│ Tenant (租戶) │
│ │
│ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ TenantEmployeeModel │ │TenantContactModel│ │
│ │ (aile.tenant. │ │(aile.tenant. │ │
│ │ employee) │ │ contact) │ │
│ │ id → memberId │ │ id → customerId │ │
│ └─────────┬────────────┘ └────────┬─────────┘ │
│ │ │ │
└────────────┼────────────────────────┼───────────┘
│ │
┌──────▼──────┐ ┌──────▼───────────────┐
│ServiceMember│ │ServiceNumberScopeModel│
│ Model │ │ (aile.tenant. │
│ memberId │ │ servicenumber.scope) │
│ privilege │ │ customerId │
│ (Owner / │ │ serviceNumberId │
│ Manager / │ │ channel ↔ scopeId │
│ Common) │ │ roomId │
└─────────────┘ └──────────┬────────────┘

┌────────▼────────────┐
│ServiceNumberRelation │
│ Model │
│ (aile.tenant. │
│ servicenumber. │
│ relation) │
│ customerId │
│ serviceNumberId │
│ accountId │
│ status │
└──────────────────────┘

14. 關鍵身份流轉場景

14.1 新用戶首次進線

LINE 用戶掃碼進入服務號

├── 無 Account → 建立 Anonymous Account
├── 無 TenantContact → 建立 Contact(type=Anonymous)
├── 建立 ServiceNumberScope(customerId + serviceNumberId + channel + scopeId)
├── 建立 ServiceNumberRelation(customerId + serviceNumberId + accountId)
└── 建立 Services Room → 開始會話

14.2 匿名轉實名

Anonymous Account 輸入手機號進行實名登入

├── Account 升級:type: Anonymous → RealName(填入手機號)
├── 觸發 Saga:AnonymousVisitorMergePlanAdapter
│ ├── 查找該 Account 的所有 Anonymous Contact
│ ├── 對每個 Contact 生成合併計劃
│ └── Saga 編排執行(合併 Contact、合併 Session、合併 Relation)
└── Contact 升級:type: Anonymous → RealName

14.3 已有 Account 進線新租戶

已實名 Account 透過 LINE 進入新租戶的服務號

├── 查找 Account(依 channel + scopeId → AccountScope)
├── 在該租戶建立 TenantContactModel(type=RealName)
├── 建立 ServiceNumberScope
└── 建立 ServiceNumberRelation

14.4 員工加入生態

用戶登入 → 通過邀請/擔保加入租戶

├── 若無 Account → 建立 Account
├── 建立 TenantEmployeeModel(joinType=Guarantor/Invitation)
├── TenantBindService.send() → 發送加入生態卡片
├── 用戶接受 → isJoinAile=true, isBindAile=true
└── 可被加入 ServiceMember(成為服務號客服)

14.5 LineGroup 獨立客戶

LINE 群組事件(MemberJoin / Messaging)

├── 群組成員可能沒有 Account
├── 建立 TenantContactModel(type=Independent)
│ └── Independent:不關聯 Account,僅作為群組內的用戶身份
├── 建立 LineGroupContactRelationModel
└── 加入 LineGroup Room 為成員

15. 關鍵檔案索引

層級檔案說明
API Modelaile-api/aile-account-api/.../model/AccountModel.java頂層帳號
API Modelaile-api/aile-account-api/.../model/AppAccountModel.java應用帳號
API Modelaile-api/aile-account-api/.../model/AccountScopeModel.java帳號渠道 Scope
API Enumaile-api/aile-account-api/.../enums/AccountType.java帳號類型(Anonymous/NonRealName/RealName/System)
API Constaile-api/aile-account-api/.../constant/LoginType.java11 種登入方式
API Modelaile-api/aile-tenant-api/.../model/TenantEmployeeModel.java員工
API Modelaile-api/aile-tenant-api/.../model/TenantContactModel.java客戶/聯繫人
API Enumaile-api/aile-tenant-api/.../enums/contact/ContactType.java客戶類型(RealName/Anonymous/Independent)
API Enumaile-api/aile-tenant-api/.../enums/TenantJoinType.java加入方式
API Modelaile-api/aile-tenant-api/.../model/servicenumber/ServiceMemberModel.java服務號成員
API Enumaile-api/aile-tenant-api/.../enums/ServiceMemberPrivilege.java服務號權限
API Modelaile-api/aile-tenant-api/.../model/servicenumber/ServiceNumberScopeModel.java服務號進線 Scope
API Modelaile-api/aile-tenant-api/.../model/servicenumber/ServiceNumberRelationModel.java服務號訂閱
API Enumaile-api/aile-room-api/.../enums/RoomMemberType.java房間成員類型
API Enumaile-api/aile-job-api/.../enums/UserTypeEnums.java用戶類型(Employee/Contact/Visitor)
Serviceaile-service/aile-service-account/.../service/impl/AccountServiceImpl.java帳號服務
Serviceaile-service/aile-service-tenant/.../service/impl/TenantBindServiceImpl.java租戶綁定服務
Mergeaile-service/aile-service-tenant/.../merge/adapter/AnonymousVisitorMergePlanAdapter.java匿名訪客合併
Sagaaile-service/aile-service-tenant/.../saga/step/MergeAnonymousVisitorContactDataStep.javaSaga 合併步驟
Sagaaile-service/aile-service-tenant/.../saga/step/ResolveContactMergePlanStep.java合併計劃解析