Aile 點獲取體系 — 完整設計規格(統整修訂版 v2.0)

版本:v2.0(統整修訂版)

日期:2026-06-07

來源整合:技術設計/活動引擎/管理臺統整方案、開發規格 v1.0、使用者故事 v1.0

狀態:評審稿(已對齊三份文件衝突並給出修訂結論)

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

0. 本版說明

1. 背景與目標

現有 Aile 配額體系已覆蓋“扣點側”能力(超額扣點、套餐/延長包購買、欠費處理、AIPool 繫結與授權),但“獲點側”尚未形成完整閉環,導致運營補償、使用者自購、活動激勵與點數收支審計分散。

本規格目標:建立 Aile 點獲取體系統一開發邊界。

  1. 1. Backend 提供統一給點底座,封裝 AIPool 累點能力、冪等、審計與狀態流轉。
  2. 2. Admin 支援 Aile 官方管理員對個人租戶給點,並在個人租戶後臺提供 Aile 點內嵌購買體驗。
  3. 3. AileApp 在後續階段提供購買入口、收支明細與活動中心。
  4. 4. 活動給點系統作為獨立複雜能力,先定義事件、規則、預算與審計邊界,避免首期過度實現。

2. 資訊來源與現有 Backend 對齊

2.1 資訊來源

2.2 現有可複用基礎(只讀檢查)

結論(DRY):不新建獨立賬務底座,在現有 quota/point audit 能力上擴充套件。給點執行邏輯收斂到新的 TenantPointGrantService,審計讀寫仍復用 TenantPointAuditService

3. 範圍與階段

3.1 Phase 1:優先閉環

包含:

不包含:審批流;Aile 側給點額度上限配置;AilePro 聊天介面快捷給點;AileApp 管理員給點;完整活動中心與複雜活動規則配置。

3.2 Phase 2:體驗增強

3.3 Phase 3:活動給點系統

4. 角色與業務物件

角色/物件定義
Aile 官方管理員Aile 官方租戶下具備後臺管理許可權的運營角色,可對個人租戶執行給點。Phase 1 不做更細粒度審批與額度分級。角色編碼見第 13 節待確認。
商務人士 / 個人租戶一個自然人對應的個人租戶,在 AIPool 的 Aile 租戶下擁有 Aile 點賬戶。
AIPool 點數中心Aile 點真實賬務系統,負責點數增加、扣減、支付、餘額與交易流水(賬務真相)。
openIdAIPool 累點/扣點目標使用者標識,給點必須明確到該標識。
eventIdAile 側全域性冪等鍵,同一事件重複提交不得重複給點。
TenantPointAuditAile 側審計底稿,用於冪等、狀態追蹤、排障與報表。
SYSTEM_GLOBAL_TENANT平臺級行為(如平臺級活動)的全域性租戶常量。不可用於管理員給點的 tenantId——管理員給點的 tenantId 必須為目標個人租戶(見第 12 節衝突 C8)。

5. 總體架構

成功業務拒絕超時/通訊異常Admin 手動給點Backend 統一給點服務 TenantPointGrantService使用者自購支付成功活動引擎給點事件寫入 TenantPointAudit: PendingmarkProcessing呼叫 TenantAipoolDeductClient.increasePointAIPool 點數中心AIPool 返回結果更新審計: Success + outTraceId更新審計: Failed + failReason保留 Processing + 記錄原因, 交由補單 Job/手動重試Admin/App 查詢收支明細Admin 排障與重試

活動給點擴充套件架構:

預算充足預算不足使用者行為: 註冊/開戶/加秘書/邀請GCP Pub/Sub: User Event Topic活動規則引擎 aipool-activity-service*三階段規則匹配Redis Lua 原子預算扣減釋出 tenant-point-grant 事件活動狀態 Exhausted + 告警Backend 統一給點服務

6. Backend 規格

6.1 統一給點服務 TenantPointGrantService

新增業務語義明確的服務 TenantPointGrantService(實現類 TenantPointGrantServiceImpl),不要把給點編排流程堆入 TenantPointAuditServiceImpl。審計讀寫仍復用 TenantPointAuditService

核心職責:

  1. 1. 接收統一給點命令。
  2. 2. 校驗 eventIdopenId、點數、場景、來源業務 ID。
  3. 3. 先寫入 TenantPointAuditModel,狀態 Pending
  4. 4. 呼叫 TenantAipoolDeductClient.increasePoint(...)
  5. 5. 根據 AIPool 響應更新 Success / Failed,通訊異常保留 Processing
  6. 6. 返回可追蹤結果:eventIdstatusoutTraceId、失敗原因。

6.2 統一給點命令欄位

欄位說明
eventId全域性冪等鍵,規則 GRANT_{Scene}_{businessId}。管理員給點 businessId=ADM_TASK_{taskId};活動給點 businessId={campaignId}_{participantId};購買到賬 businessId={orderId}
tenantId目標租戶 ID。管理員給點/使用者自購:目標個人租戶 ID。平臺級活動SYSTEM_GLOBAL_TENANT。常量統一管理,不硬編碼散落。
accountId可選,Aile 賬號 ID,用於排障和前端展示。
openId必填,AIPool 累點目標。
amount必填,正整數。審計 ailePointsChange 為正數。
auditSceneAdminGrantActivityGrantUserPurchaseOrderCompensation 等。
sourceBizId來源業務主鍵(管理員任務 ID、活動參與記錄 ID、訂單 ID)。
operatorId管理員給點必填;系統/活動給點使用系統操作者。
reasonCode給點原因列舉(見 6.7)。
remark人可讀說明。原因為“其他”時必填。

6.3 狀態機(統一口徑)

通訊異常保留, 補單Job重試手動重試/補發開始PendingProcessingSuccessFailed結束

點數審計狀態列舉統一為:PendingProcessingSuccessFailed(PascalCase)。

冪等要求:

6.4 MongoDB tenant_point_audit 推薦結構


    
    
    
  {
  "bsonType"
: "object",
  "required"
: ["eventId", "tenantId", "openId", "auditScene", "sourceBizId", "ailePointsChange", "status", "occurredAt"],
  "properties"
: {
    "id"
: { "bsonType": "objectId" },
    "eventId"
: { "bsonType": "string", "description": "全域性唯一業務冪等鍵, 規則 GRANT_{Scene}_{businessId}; 唯一索引保證資料庫層防刷" },
    "tenantId"
: { "bsonType": "string", "description": "管理員給點/自購=目標個人租戶; 平臺級活動=SYSTEM_GLOBAL_TENANT" },
    "openId"
: { "bsonType": "string" },
    "auditScene"
: { "bsonType": "string", "enum": ["QuotaDeduct", "OrderPaid", "OrderCompensation", "ActivityGrant", "AdminGrant", "UserPurchase"] },
    "sourceBizId"
: { "bsonType": "string", "description": "關聯業務主鍵: 訂單ID/客訴單ID/被邀請使用者ID/加祕書事件ID" },
    "ailePointsChange"
: { "bsonType": "int", "description": "正數為給點, 負數為扣點" },
    "status"
: { "bsonType": "string", "enum": ["Pending", "Processing", "Success", "Failed"] },
    "occurredAt"
: { "bsonType": "long", "description": "毫秒時間戳" },
    "operatorId"
: { "bsonType": "string", "description": "管理員給點操作者" },
    "reasonCode"
: { "bsonType": "string" },
    "aipoolResponseCode"
: { "bsonType": "string" },
    "aipoolResponseMessage"
: { "bsonType": "string" },
    "outTraceId"
: { "bsonType": "string", "description": "AIPool 交易流水號, 跨系統對賬" },
    "remark"
: { "bsonType": "string" }
  }

}

6.5 核心給點服務實作(修訂版)


    
    
    
  @Slf4j
@Service

public
 class TenantPointGrantServiceImpl implements TenantPointGrantService {

    @Resource
 private TenantPointAuditService tenantPointAuditService;
    @Resource
 private TenantAipoolDeductClient tenantAipoolDeductClient;
    @Resource
 private AilePointGrantProperties grantProperties; // 有效期/pointType 配置化

    public
 PointGrantResult grantPoints(PointGrantCommand cmd) {
        // 1. 生成 eventId

        String
 eventId = "GRANT_" + cmd.getScene().name() + "_" + cmd.getBusinessId();

        // 2. 幂等检索

        TenantPointAuditModel
 existing = tenantPointAuditService.findAuditByEventId(eventId);
        if
 (existing != null) {
            return
 PointGrantResult.from(existing); // Success 直接返回; Processing/Failed 按 6.3 处理
        }

        // 3. 写入 Pending

        TenantPointAuditModel
 audit = TenantPointAuditModel.superBuilder()
                .eventId(eventId)
                .tenantId(cmd.getTenantId()) // 管理员给点=目标个人租户; 平台级活动=SYSTEM_GLOBAL_TENANT
                .openId(cmd.getOpenId())
                .auditScene(cmd.getScene())
                .sourceBizId(cmd.getBusinessId())
                .operatorId(cmd.getOperatorId())
                .reasonCode(cmd.getReasonCode())
                .ailePointsChange(cmd.getAmount())
                .status(TenantPointAuditStatus.Pending)
                .occurredAt(System.currentTimeMillis())
                .remark(cmd.getRemark())
                .build();
        tenantPointAuditService.insert(audit);

        // 4. 组装请求 (有效期/pointType 配置化)

        TenantAipoolPointIncreaseRequest
 request = TenantAipoolPointIncreaseRequest.builder()
                .openId(cmd.getOpenId())
                .point(cmd.getAmount())
                .code(eventId) // AIPool 侧强幂等键
                .reason(cmd.getRemark())
                .fromSystem(cmd.getScene().getFromSystem()) // AILE_ADMIN_GRANT / AILE_USER_PURCHASE / AILE_ACTIVITY_GRANT
                .fromInfo(cmd.getScene().name() + ":" + cmd.getBusinessId())
                .startTime(System.currentTimeMillis())
                .expireTime(grantProperties.resolveExpireTime(cmd.getScene())) // 不硬编码, 按场景配置
                .pointType(grantProperties.resolvePointType(cmd.getScene()))
                .build();

        try
 {
            tenantPointAuditService.markProcessing(eventId, cmd.getAmount());
            TenantAipoolDeductResult
 result = tenantAipoolDeductClient.increasePoint(request);
            if
 (result.isSuccess()) {
                tenantPointAuditService.markGrantSuccess(eventId, cmd.getAmount(),
                        result.getResponseCode(), result.getResponseMessage(), result.getOutTraceId());
                return
 PointGrantResult.success(eventId, result.getOutTraceId());
            } else {
                // 下游业务拒绝(余额超限/黑名单等): 明确失败

                tenantPointAuditService.markFailed(eventId, cmd.getAmount(), result.getResponseMessage());
                return
 PointGrantResult.failed(eventId, result.getResponseMessage());
            }
        } catch (Exception ex) {
            // 通信超时/异常: 保留 Processing, 交由补单 Job 或手动重试, 不静默失败

            log.error("[PointGrant] Communication error. eventId={}", eventId, ex);
            return
 PointGrantResult.processing(eventId, "S2S Communication Error: " + ex.getMessage());
        }
    }
}

6.6 AIPool 累點呼叫產品化

複用 TenantAipoolDeductClient.increasePoint(...),產品化補齊:

6.7 管理員給點服務

業務規則:

介面(修訂示例,tenantId 取目標租戶):


    
    
    
  @PostMapping("/admin/v1/users/{openId}/points/grant")
@PreAuthorize("hasRole('AILE_OFFICIAL_ADMIN')")
 // 角色编码待最终确认(Q5)
public
 ResponseEntity<AdminGrantResponse> manualGrantPoints(
        @PathVariable
 String openId,
        @Validated
 @RequestBody AdminGrantRequest body)
 {
    PointGrantCommand
 cmd = PointGrantCommand.builder()
            .tenantId(body.getTargetTenantId()) // 目标个人租户, 非 SYSTEM_GLOBAL_TENANT
            .openId(openId)
            .amount(body.getAmount())
            .scene(TenantPointAuditScene.AdminGrant)
            .businessId("ADM_TASK_" + body.getTaskId())
            .operatorId(getLoginAdminId())
            .reasonCode(body.getReasonCode())
            .remark("管理员[" + getLoginAdminEmail() + "]手动指派: " + body.getReason())
            .build();
    PointGrantResult
 r = tenantPointGrantService.grantPoints(cmd);
    return
 ResponseEntity.ok(AdminGrantResponse.from(r));
}

介面能力:查詢租戶列表(型別/關鍵字/狀態篩選)、發起給點、按 eventId/審計 ID 查詢結果、審計列表(複用/擴充套件 /point/audit/v1/list,增加時間範圍、操作人、來源業務 ID、正負點數篩選)。

6.8 使用者自購 Aile 點

業務定位:Aile 不替代 AIPool 支付能力,只提供內嵌購買入口與代理/回撥對接。套餐、價格、贈送比例由 Aile Admin 配置維護(非 AIPool 動態拉取,見決議 Q4);賬務真相(餘額、交易流水)仍以 AIPool 為準,Aile 不維護獨立賬務。

Backend 支援:拉取可購買套餐/檔位;建立購買訂單或獲取 AIPool 支付引數;接收/同步支付結果;支付成功寫入 TenantPointAuditauditScene=UserPurchase,以訂單 ID 冪等);Admin/App 查詢餘額與購買記錄。

已決議(見第 13 節):套餐由 Aile Admin 配置管理(Q4);首期支付支援 UUPon + 現金(混合支付,Q9);到賬以 AIPool 回撥為主、Aile 輪詢兜底對賬(Q5)。

6.9 活動給點服務(Phase 3)

邊界:活動引擎負責規則判斷、預算扣減、防刷與參與記錄;統一給點服務只接收“已判定透過”的給點事件;預算必須發點前原子扣減;活動參與記錄與 TenantPointAudit 透過 sourceBizId/eventId 關聯。

活動狀態機(統一口徑,見衝突 C3):DRAFTACTIVEPAUSED / EXHAUSTED / ENDEDARCHIVED

活動 Schema(aile.campaign):


    
    
    
  {
  "title"
: "Campaign",
  "type"
: "object",
  "properties"
: {
    "campaignId"
: { "type": "string", "description": "CAMP_{SCOPE}_{YYYYMMDD}_{NAME}" },
    "title"
: { "type": "string" },
    "campaignScope"
: { "type": "string", "enum": ["PLATFORM", "TENANT"] },
    "tenantId"
: { "type": "string", "description": "PLATFORM 时写入 SYSTEM_GLOBAL_TENANT" },
    "status"
: { "type": "string", "enum": ["DRAFT", "ACTIVE", "PAUSED", "EXHAUSTED", "ENDED", "ARCHIVED"] },
    "startTime"
: { "type": "integer" },
    "endTime"
: { "type": "integer" },
    "totalBudget"
: { "type": "integer" },
    "distributedBudget"
: { "type": "integer", "default": 0 },
    "singleGrantAmount"
: { "type": "integer" },
    "rules"
: { "type": "array", "items": { "type": "object", "properties": {
      "ruleType"
: { "type": "string", "enum": ["EventTypeCondition", "UserEligibility", "FrequencyLimit"] },
      "operator"
: { "type": "string", "enum": ["EQUALS", "IN_LIST", "LESS_THAN_OR_EQUAL"] },
      "value"
: { "type": "string" }
    }
}}
  }

}

高併發預算控制(Redis Lua 原子扣減):


    
    
    
  -- KEYS[1]: campaign:CAMP_ID:budget
-- ARGV[1]: single grant amount

local
 key = KEYS[1]
local
 decrement = tonumber(ARGV[1])
local
 current = tonumber(redis.call('get', key) or "0")
if
 current >= decrement then
    redis.call('decrby', key, decrement)
    return
 1
else

    return
 0
end

返回 1 安全獲取併發布給點事件;返回 0 由活動服務釋出 alert 並非同步更新狀態為 EXHAUSTED,杜絕超發。給點失敗需進入補償/重試佇列,不靜默吞預算。

7. Admin 介面規格

7.1 官方管理員給點

入口:Aile 官方租戶 Admin 後臺租戶列表。能力:

7.2 點數審計與報表

7.3 個人租戶內嵌購買

入口:個人租戶 Admin“我的配額/點數”區域。展示餘額、“立即購買”入口、檔位/價格/贈送/支付方式彈窗、支付狀態、成功後重新整理餘額、收支記錄出現“購買充值”。

7.4 活動管理臺(Phase 3)

核心 API(節選):


    
    
    
  paths:
  /admin/v1/campaigns:

    post:
 { summary: 创建行销活动(PLATFORM/TENANT) }
  /admin/v1/campaigns/{id}/budget:

    put:
 { summary: 追加活动预算(Thread-Safe, 同步 INCRBY Redis) }
  /admin/v1/campaigns/re-run:

    post:
 { summary: 一键补发(携带原 eventId, 强幂等) }

8. AileApp 規格

Phase 1 不強制開發。

9. 非功能性要求

10. 驗收標準(Phase 1)

  1. 1. Backend 透過統一給點服務完成一次管理員給點,並寫入完整審計記錄(tenantId 為目標個人租戶)。
  2. 2. 同一 eventId 重複請求不重複呼叫 AIPool 或重複給點。
  3. 3. AIPool 成功返回時審計 Success 並記錄 outTraceId
  4. 4. AIPool 業務拒絕時審計 Failed 並記錄失敗原因;通訊異常保留 Processing 可補單。
  5. 5. Admin 能在租戶列表篩選個人租戶併發起給點。
  6. 6. Admin 能查詢給點/扣點審計明細與基礎統計。
  7. 7. 個人租戶 Admin 能看到購買入口並完成 AIPool 購買/約定支付流程。
  8. 8. 購買成功後收支明細出現購買充值記錄。
  9. 9. 許可權不滿足者不能執行官方管理員給點。
  10. 10. 現有扣點、配額使用、欠費處理鏈路不受影響。

11. 設計原則應用

12. 三份文件衝突分析與修訂結論

編號衝突點各文件表述修訂結論
C1給點服務歸屬/命名統整方案架構圖把給點入口標為 TenantPointAuditServiceImpl,但程式碼用 TenantPointGrantServiceImpl;開發規格明確要求新建 TenantPointGrantService,不堆入審計服務統一:新建 TenantPointGrantService(Impl) 負責給點編排,審計讀寫複用 TenantPointAuditService。架構圖已更正。
C2通訊異常時的狀態處理統整方案程式碼異常分支直接 markFailed,但其註釋寫“保留 Pending/Processing 以便補單重試”——自相矛盾統一:業務拒絕→Failed;通訊超時/異常→保留 Processing,交補單 Job/手動重試。程式碼已修訂。
C3活動狀態列舉開發規格:Draft/Active/Paused/Exhausted/Ended/Archived(6 態,首字母大寫);統整方案:DRAFT/ACTIVE/PAUSED/EXHAUSTED/COMPLETED(5 態,無 Archived,用 COMPLETED);使用者故事:建立/編輯/啟用/暫停/結束統一:大寫列舉 DRAFT/ACTIVE/PAUSED/EXHAUSTED/ENDED/ARCHIVED;自然結束用 ENDED,保留 ARCHIVED
C4點數審計狀態大小寫兩份均用 Pending/Processing/Success/Failed(PascalCase),但活動態混用大寫——風格不一致統一:點數審計態 PascalCase;活動態全大寫。兩套互不混用。
C5auditScene 列舉集合統整方案含 QuotaDeduct/OrderPaid/OrderCompensation/ActivityGrant/AdminGrant/UserPurchase;開發規格僅列舉部分統一採用統整方案完整集合(見 6.4)。
C6點有效期 expireTime統整方案程式碼硬編碼 1 年;開發規格明確“不應硬編碼,需產品確認”,並列為待確認問題統一:expireTime 配置化,按場景分別配置,預設值待產品確認(Q2)。
C7pointType統整方案硬編碼 pointType=1(封閉點);開發規格未提及統一:pointType 配置化,預設值需產品確認(Q3)。
C8管理員給點的 tenantId使用者故事/開發規格:給點目標=個人租戶,tenantId=目標個人租戶 ID;統整方案管理員控制器傳 SYSTEM_GLOBAL_TENANT重要修訂:管理員給點 tenantId 寫目標個人租戶 ID(用 operatorId 記錄管理員);SYSTEM_GLOBAL_TENANT 僅用於平臺級活動。
C9許可權角色編碼統整方案硬編碼 hasRole('ADMIN');開發規格列為待確認統一:佔位 AILE_OFFICIAL_ADMIN,最終編碼待確認(Q5)。
C10活動引擎服務歸屬統整方案:獨立 aipool-activity-service;開發規格:待確認(springcloud-aile vs 獨立服務)已決議(Q8):活動引擎獨立為 aipool-activity-service
C11階段定位差異統整方案完整呈現活動引擎/管理臺(易被讀作 Phase 1);使用者故事/開發規格將活動給點列為 Phase 3統一按 Phase 1/2/3 劃分(見第 3 節);活動給點屬 Phase 3。
C12eventId 活動場景格式開發規格:GRANT_ActivityGrant_{campaignId}_{participantId};統整方案:GRANT_{Scene}_{businessId}(單一 businessId)統一:businessId={campaignId}_{participantId},套入通用規則 GRANT_{Scene}_{businessId},兩者等價。

13. 關鍵決議與外部依賴

編號問題回填答覆設計落地 / 狀態
1AIPool 累點介面契約/成功碼/冪等碼/錯誤碼需與 AIPool 確認⏳ 外部依賴:待 AIPool 提供正式欄位契約與狀態碼,封裝層預留適配與錯誤碼對映
2Aile 點預設有效期可配置,預設 1 年expireTime 配置化,預設 365 天,可按活動/管理員/購買分別覆蓋
3pointType 取值只有封閉點✅ 固定為發行租戶封閉點,首期不支援其他點種
4使用者自購套餐來源Aile Admin 配置管理✅ 套餐/檔位/價格在 Aile Admin 配置維護,App/Admin 讀 Aile 配置展示(非 AIPool 動態拉取)
5購買到賬觸發方式AIPool 回撥 + Aile 輪詢✅ 主:AIPool 支付成功回撥寫審計;輔:Aile 定時輪詢兜底對賬
6官方管理員角色編碼Aile 官方管理員✅ 角色=Aile 官方管理員;佔位編碼 AILE_OFFICIAL_ADMIN,最終編碼值隨 Admin 許可權模型確定
7個人租戶與 openId 對映需與 AIPool 協商⏳ 外部依賴:與 AIPool 確認個人租戶需提供哪些資訊,以唯一定位 AIPool 使用者並完成給點
8活動給點服務歸屬建議 aipool-activity-service✅ 已決議:活動引擎獨立為 aipool-activity-service
9支付方式支援 UUPon + 現金✅ 首期支援 UUPon + 現金(混合支付)

13.1 原始待確認問題與回填答覆

  1. 1. AIPool 累點介面的正式欄位契約、成功碼、冪等碼與錯誤碼是否已最終確認。
    1. 1. 需要和AIPool確認
  2. 2. (Q2)Aile 點預設有效期如何確定,是否按活動/管理員/購買三類分別配置。
    1. 1. 暫定可以配置。 預設1年
  3. 3. (新增)pointType 預設值與各場景取值。
    1. 1. 只有封閉點
  4. 4. 使用者自購套餐來源:AIPool 動態拉取還是 Aile Admin 配置展示。
    1. 1. AileAdmin中配置管理
  5. 5. 購買支付成功後的到賬觸發方式:AIPool 回撥、Aile 輪詢,還是現有訂單同步機制。
    1. 1. AIPool回撥+Aile輪詢
  6. 6. (Q5)Aile 官方管理員許可權在現有 Admin 許可權模型中的角色編碼。
    1. 1. Aile 官方管理員
  7. 7. 個人租戶與 openId 的穩定對映來源,以及異常缺失時的處理方式。
    1. 1. 需要和AIPool協商, 對於個人租戶, 要提供哪些資訊, 可以確認再AIPool中的一個人,並可以實現給點?
  8. 8. (Q7)活動給點服務歸屬:springcloud-aile 還是獨立 aipool-activity-service
    1. 1. 建議aipool-activity-service
  9. 9. 支付方式是否首期必須支援 UUPon + 現金混合支付。
    1. 1. 支援UUPON+現金