依 plan-plus-api分析.html 的方案,把 public/plans-plus.html 單頁訂閱流程從 mock 切換到 AML 後端(iCON.Abp.AMLPortal)真實 API,並在本地 docker-compose-local-dev 環境跑通、逐流程實測(支付除外)。本文記錄實際落地內容與驗證結果。
plans-plus 依賴的 3 條只讀目錄(editions / plans/catalog / countries)、2 條交互查詢(agents / tenants/lookup)、下單提交(subscribe,含 new/renew/topup)與 聯絡我們(subscribe-offline → CreateFeedback)均已對接真實後端並實測通過。單次查詢與支付本輪除外,仍走 mock 兜底。
| 前端端點 | 對應後端 | 狀態 | 實測結果 |
|---|---|---|---|
GET /editions | Order/portal/GetEditionList | 完成 | 9 行業(過濾 Standard/DC、Others 置末);jQSeparatedEditions 對齊真實 CPA GUID |
GET /plans/catalog | plan/portal/GetPlanList | 完成 | HKG·HKD 目錄;standard/cpa/addons 全帶 planDetailId;KYC 月租 + jQuota 配套 |
GET /countries | Order/getCategoryByTypes | 完成 | 249 國;已改免 token(原用 OAuth,本地無憑證會失敗) |
GET /agents/:code | SearchUserByCodeAndType + GetPlanList(agentUserId) | 完成 | 返回 {code,name,tiers,email,phone,agentorId};tiers 經二次 GetPlanList 推導,空=不過濾 |
GET /tenants/lookup | 新增 Order/portal/queryRenewableTenantByEmail | 完成 | 後端新端點按郵箱聚合 currentSubscription+referrer;cpa1 唯一命中 |
POST /subscribe new | Order/portal/CreateOrder | 完成 | 空 BR / 個人主體 / 加值項 / agent 均建單入庫(BR/CI 已放開) |
POST /subscribe renew·topup | Order/portal/TenantRenewal | 完成 | cpa1 續費 / jQuota 加購均建單;修復內部 token URL 後通過 |
POST /subscribe-offline(聯絡我們/推薦人) | customer/CreateFeedback | 完成 | 訂單摘要序列化為 message;有推薦人則寫 AssignedAgentUserId 路由收件人 |
/single-query · /payments/* | ConsumerPortal(待後端)· 支付方(未定) | 本輪除外 | 保持 mock 兜底;文檔明確除外 |
用 docs/AML_Backend/docker-compose-local-dev/AML-local-dev.bak(201MB,SQL Server 備份,內部庫名 AML-local-dev)恢復真實開發數據,取代原「補種子 SQL」路線——恢復後目錄 / 租戶 / 代理計劃齊備,直接可對接。
恢復為 AbpAML(host 連接的庫),MOVE 邏輯文件到 /var/opt/mssql/data/AbpAML.mdf/_log.ldf;恢復前停 host 釋放連接、置 SINGLE_USER 取得獨佔。
| 表 | 恢復後 | 用途 |
|---|---|---|
AMLPortal_Plans / PlanDetails | 123 / 123 | 方案目錄(按國家分兩套:HKG-HKD 與 JPN-USD,靠 systemCode/countryCode 區分) |
SaasEditions | 12(含真實 CPA 3A1A296B-…) | 所屬行業 |
SaasTenants / AMLPortal_TenantPropertys | 42 / 98 | 續費 / 加購測試數據 |
AMLPortal_AgentUserPlans | 24 | agent tiers 過濾 |
AMLPortal_Orders / AbpUsers | 142 / 131 | 訂單反查 / 推薦人 |
catalog 國家過濾:services/catalog.js 默認過濾 HKG(香港市場·HKD,與舊 mock 幣種一致);/plans-jp 的 PAYG 定價另走 JPN。
共 3 項代碼改動 + 2 項配置,全部重建 httpapi-host 鏡像後實測通過。
注釋 OrderService.cs CreateOrder 的兩處 BR/CI 必填校驗(前置校驗處 + Organization BR/CI required 處),保留唯一性校驗 ExistsByOrganizationBRCI(對 BR、CI 皆空返回 false,空值安全)。個人主體 / 空 BR 可下單。
queryRenewableTenantByEmail 端點 新增解決舊 queryRenewableTenant 只能按租戶名 DB 搜、無法按郵箱過濾的維度錯位。新端點維度反轉:
UserName=='admin' && Email==email 定位租戶管理員(禁多租戶過濾,全庫掃描可接受);IsActive 的 TenantProperty(含 Order→OrderDetails→Plan),按 CreationTime 取最新;currentSubscription:基礎方案(B)→planId/名稱/月數/價;AdlU→addons.users(PCS 之和);jQ/j→jquotaPackageId;EnableKYC→kyc;並解析 EditionName(由 EditionId)與 referrer(由 AgentorId 解析用戶)。新增 DTO:QueryRenewableTenantByEmailParam、RenewableTenantByEmailDto(含 CurrentSubscriptionDto / RenewableReferrerDto)。
CreateFeedback 加 AssignedAgentUserId(聯絡推薦人路由)完成Feedback 實體 + CreateFeedbackDto 加可空 AssignedAgentUserId(AutoMapper 同名自動映射);CustomerService.CreateFeedback 於有值時用 GetUsersByIDs 解析推薦人郵箱,作 SendEmailOnPortalFeedback 的收件人(無郵箱回退平台銷售),給客戶本人的確認郵件不變。
EF 遷移(部署待辦):本地以 ALTER TABLE AMLPortal_Feedbacks ADD AssignedAgentUserId uniqueidentifier NULL 加列(實體屬性映射到該列即可運行)。正式部署需在構建環境跑 dotnet ef migrations add 生成遷移(就一句 AddColumn)——原因:運行時 Database.Migrate() 會讀取遷移的 TargetModel(完整模型快照),手寫遷移不可行。
appsettings.local.json 的 Portal.jQSeparatedEditions.EditionIds 由庫中不存在的 3A1A2969-… 改為真實 CPA edition 3A1A296B-DAB4-4B94-B267-4424683B8916,否則前端選 CPA 不觸發 isCpa 方案集切換。
實測中發現的坑:AppConfig:General:ApiLocalhostUrl 出廠 https://localhost:44331/,但本地 host 只監聽 HTTP → TenantRenewal / 建租戶等內部 AbpTokenService.connect/token 調用 SSL 握手失敗(500)。docker-compose.local.yml host env 已加 AppConfig__General__ApiLocalhostUrl: "http://localhost:44331/"。(CreateOrder 走異步隊列建租戶未同步觸發,故只有 renew/topup 報錯——由此定位。)
採 BFF 範式:瀏覽器只調本站 /api/*,服務端匿名代理後端並裁剪響應。前端 plans-plus.js 零改動。
免 token:門戶端點均 [AbpAutoAuth("Portal")](SearchUserByCodeAndType 為 [AllowAnonymous])——後端 AbpAutoAuthMiddleware 服務端注入門戶訪客 token 並覆蓋 Authorization,故 BFF 匿名 POST/GET 即可,無需配 AUTH_*。統一助手 server/services/portal.js。
| 文件 | 職責 |
|---|---|
services/portal.js 新 | 匿名 POST/GET 助手(axios + 自簽證書放行 + {code,msg,data} 拆包) |
services/catalog.js 新 | GetPlanList → 按國家(默認 HKG)過濾 → filterPlan 拆 standard/cpa/addons → 產出前端契約 + planDetailId 索引;5 分鐘緩存 |
routes/editions.js 新 | GetEditionList → 多語言映射 + 過濾 Standard/DC + Others 置末 + jQSeparatedEditions 鍵名/值轉小寫 |
routes/plans.js 新 | GET /plans/catalog[?countryCode] → catalog 服務 |
routes/agents.js 新 | GET /agents/:code → SearchUser + GetPlanList(agentUserId) 推導 tiers |
routes/tenants.js 新 | GET /tenants/lookup?email= → queryRenewableTenantByEmail,字段直通 + 命中態 + 日期格式化 |
routes/subscribe.js 新 | POST /subscribe(new→CreateOrder / renew·topup→TenantRenewal,組裝 PlanList 回填 planDetailId、KYC PCS=月數) + POST /subscribe-offline(序列化摘要 + agentCode 解析→AssignedAgentUserId → CreateFeedback) |
routes/countries.js 改 | 由 OAuth 改免 token(本地無憑證),輸出不變 |
index.js 改 | 真實路由挂在 mock 之前:真實路由處理訂閱流程,mock 只兜底 single-query/options、PAYG-GetPlanList、payments、promos |
package.json 改 | 補回 start:local 腳本 |
PlanList = []
if type != 'topup': push({PlanId: plan.planId, PlanDetailId: plan.planDetailId, PCS:1})
if addons.users > 0: push({PlanId: refs.user.planId, PlanDetailId: refs.user.planDetailId, PCS: users})
if addons.kyc: push({PlanId: refs.kyc.planId, PlanDetailId: refs.kyc.planDetailId, PCS: kycRentalMonths}) // KYC 月租
if addons.jquotaPackageId: push({PlanId: jqId, PlanDetailId: planDetailByPlanId[jqId], PCS:1})
cd AML_Backend/docker-compose-local-dev
docker compose -f docker-compose.local.yml build httpapi-host # 編譯後端改動
docker compose -f docker-compose.local.yml up -d --no-deps httpapi-host # 只重建 host,不重跑 migrator
# 後端 Swagger: http://localhost:44331/swagger DB: localhost,11433 (sa / Aml@Local2026)
cd justsolutionsWebV2
npm run start:local
# = APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true PORT=8090
# 頁面: http://localhost:8090/plans-plus (日本版 /plans-jp)
PLANS_PLUS_MOCK=true 讓 single-query/payments 走 mock 兜底;真實訂閱路由挂在 mock 之前優先處理。
以下均為 APP_ENV=dev、指向本地 docker 後端 的真實調用(非 mock),並核對數據庫寫入。
| 流程 | 驗證 |
|---|---|
| 只讀目錄 | editions=9 行業(CPA 用對齊 GUID)· catalog standard/cpa/addons 全帶 planDetailId · countries=249 · agents/C99099 返回 tiers/email/agentorId |
| 租戶查詢 | tenants/lookup?email=307736951@qq.com → cpa1 唯一命中,聚合 editionId=CPA / referrer / currentSubscription(kyc=true, expiry 2027) |
| 新購(多變體) | 4 筆真實訂單入庫:基礎 / 個人主體+空 BR / 全加值項(Standard+jQuota+增加使用人+KYC) / agent;GoodsName 顯示 PlanList 組裝正確 |
| 續費 / 加購 | cpa1 續費(CPA 24 Months) + jQuota 200 加購 均建單入庫(修 token URL 後) |
| 聯絡我們/推薦人 | subscribe-offline → Feedback 寫入,AssignedAgentUserId=3A09C9F3…(推薦人)、摘要序列化 245 字 |
| 前端渲染(無頭 Chrome) | 行業下拉填充真實 editions、jQuota 真實 HKD 價渲染、無 JS 錯誤 |
結論:P1–P4 全部端到端打通,真實建單入庫,前端真實渲染。
CreateConsumerLink→CreateConsumerOrder(待後端 + 需 2C 租戶/檢測引擎),本輪保持 mock。AssignedAgentUserId 正式 EF 遷移:本地已 ALTER 加列;部署環境需 dotnet ef migrations add 生成遷移(trivial AddColumn)。test@test.com 對應 3 個租戶(郵箱非全局唯一,生產唯一);部分租戶 active TenantProperty 無關聯 Order → currentSubscription.planId 為空(前端 renewalPlanObj 兜底)。bestValue / note* / nameJP / edition 多語言由 BFF 靜態映射補足(日文暫回退英文)。modules/iCON.Abp.AMLPortal/…/OrderService.cs — BR/CI 放開 + QueryRenewableTenantByEmail…/Application.Contracts/OrderAppLayer/{IOrderService,QueryRenewableTenantByEmailParam,RenewableTenantByEmailDto}.cs…/HttpApi/Controllers/OrderController.cs — 新端點路由…/Domain/DbEntity/Feedback.cs · …/Application.Contracts/CustomerAppLayer/CreateFeedbackDto.cs · …/Application/CustomerService.cs — AssignedAgentUserIddocker-compose-local-dev/appsettings.local.json — jQSeparatedEditions GUIDdocker-compose-local-dev/docker-compose.local.yml — ApiLocalhostUrl http overrideserver/services/{portal,catalog}.js、server/routes/{editions,plans,agents,tenants,subscribe}.jsserver/routes/countries.js(免 token)、server/index.js(挂載)、package.json(start:local)本報告基於 2026-07-06 本地 docker 環境實測。配套方案文檔見 plan-plus-api分析.html。