diff --git a/docs-site/docs/.vitepress/config.ts b/docs-site/docs/.vitepress/config.ts index 644442a..5696739 100644 --- a/docs-site/docs/.vitepress/config.ts +++ b/docs-site/docs/.vitepress/config.ts @@ -40,6 +40,7 @@ export default defineConfig({ { text: '快速开始', link: '/guide/quickstart' }, { text: '平台概念', link: '/guide/concepts' }, { text: '接入流程', link: '/guide/flow' }, + { text: '应用商店监测', link: '/guide/store-monitoring' }, ], '/android/': [ { text: '概览', link: '/android/' }, diff --git a/docs-site/docs/guide/store-monitoring.md b/docs-site/docs/guide/store-monitoring.md new file mode 100644 index 0000000..b10a3ae --- /dev/null +++ b/docs-site/docs/guide/store-monitoring.md @@ -0,0 +1,65 @@ +# iOS 与 HarmonyOS 应用商店监测 + +租户平台的 Update 服务可以监测 App Store Connect 和华为 AppGallery Connect +中的正式应用版本。当前能力是只读监测,不会代替开发者上传 IPA/HAP、提交审核、 +立即发布或撤回审核。 + +## 能监测什么 + +- 当前已上架版本 +- 等待审核和审核中版本 +- 审核通过、等待开发者发布的版本 +- 审核拒绝版本 +- 中国大陆地区是否实际可下载 +- 官方后台直接提交、并非通过 Xuqm 创建的外部版本 + +“审核通过”不代表“已上架”。只有中国大陆用户已经能够从对应应用商店下载时, +租户平台才会显示“已上架”。 + +## 配置监测 + +1. 进入目标应用的 **Update 服务 > 应用商店监测**。 +2. 选择 Apple App Store 或 HarmonyOS 应用市场。 +3. 填写 Bundle ID/包名和商店链接。 +4. 新增只读监测凭据。 +5. 保存后点击 **立即同步** 验证连接和读取权限。 + +凭据保存成功后不会再次显示原文。更换凭据时直接提交一份新凭据即可。 + +## 通知 + +应用商店通知默认关闭。可以按应用选择: + +- 邮件 +- 签名 Webhook + +没有启用通知时,平台仍会同步商店状态并保留事件历史。Update 服务不依赖 IM。 + +## 配置已上架版本为更新 + +从商店发现的版本不会自动成为客户端更新。确认版本已经在中国大陆上架后: + +1. 在商店版本记录中点击 **创建 Xuqm 更新草稿**。 +2. 平台创建一条“跳转应用商店”类型的更新草稿。 +3. 配置更新说明、是否强制更新、灰度范围和发布时间。 +4. 人工确认发布。 + +同一个商店版本只能关联一条有效更新记录。审核中、待发布、部分地区可用或状态 +未知的版本不能创建客户端更新。 + +## 常见状态 + +| 状态 | 说明 | +| --- | --- | +| 等待审核 | 已准备提交或等待官方开始审核 | +| 审核中 | 官方正在审核 | +| 审核拒绝 | 需要开发者在官方后台处理 | +| 审核通过待发布 | 已通过,但尚未向用户发布 | +| 商店处理中 | 已进入分发处理,暂未确认可下载 | +| 已上架 | 中国大陆地区已经实际可下载 | +| 同步失败 | 本次无法读取官方状态,历史已验证状态不会被覆盖 | + +## 当前边界 + +iOS/HarmonyOS 的构建上传、自动提审和审核后立即/定时发布正在进行正式设计, +当前页面不会显示无法执行的操作。Android 应用商店发布能力按 Android 文档执行。 diff --git a/tenant-platform/src/api/update.ts b/tenant-platform/src/api/update.ts index ac93163..a899136 100644 --- a/tenant-platform/src/api/update.ts +++ b/tenant-platform/src/api/update.ts @@ -126,6 +126,105 @@ export interface StoreConfig { updatedAt: string } +export type StoreMonitoringType = 'APP_STORE' | 'HARMONY_APP' +export type StoreMonitoringPlatform = 'IOS' | 'HARMONY' +export type StoreVersionState = + | 'DISCOVERED' + | 'PREPARING' + | 'SUBMITTED' + | 'WAITING_FOR_REVIEW' + | 'IN_REVIEW' + | 'REJECTED' + | 'APPROVED_PENDING_RELEASE' + | 'RELEASE_SCHEDULED' + | 'RELEASE_REQUESTED' + | 'PROCESSING_DISTRIBUTION' + | 'PARTIALLY_AVAILABLE' + | 'AVAILABLE' + | 'WITHDRAWN' + | 'REMOVED' + | 'UNKNOWN' + | 'SYNC_FAILED' + +export interface StoreMonitoringBinding { + id: string + appKey: string + platform: StoreMonitoringPlatform + storeType: StoreMonitoringType + packageIdentifier: string + officialAppId?: string + marketUrl?: string + targetRegion: 'CHN' + enabled: boolean + monitorCredentialConfigured: boolean + lastSyncStatus?: string + lastSyncError?: string + lastSyncedAt?: string +} + +export interface StoreMonitoringBindingInput { + packageIdentifier: string + marketUrl?: string + enabled: boolean + credentialDisplayName?: string + credential?: Record +} + +export interface StoreMonitoringVersion { + id: string + officialVersionId: string + versionName: string + buildVersion: string + source: 'XUQM_PLATFORM' | 'EXTERNAL_STORE' + state: StoreVersionState + rawState?: string + chinaAvailable: boolean + releaseSequence?: number + stateOccurredAt?: string + lastSyncedAt?: string +} + +export interface StoreMonitoringEvent { + id: string + eventType: string + source: 'WEBHOOK' | 'POLL' | 'MANUAL' | 'IMPORT' + previousState?: StoreVersionState + currentState?: StoreVersionState + rawState?: string + occurredAt?: string + receivedAt?: string +} + +export interface StoreUpdateDraft { + storeVersionId: string + appVersionId: string + platform: StoreMonitoringPlatform + versionName: string + buildVersion: string + releaseSequence: number + publishStatus: 'DRAFT' | 'PUBLISHED' | 'DEPRECATED' + storeUrl?: string +} + +export interface StoreNotificationPolicy { + emailEnabled: boolean + emailRecipients: string[] + webhookEnabled: boolean + webhookUrl?: string + webhookSecretConfigured: boolean + eventTypes: string[] + updatedAt?: string +} + +export interface StoreNotificationPolicyInput { + emailEnabled: boolean + emailRecipients: string[] + webhookEnabled: boolean + webhookUrl?: string + webhookSecret?: string + eventTypes: string[] +} + export interface AppVersion { id: string appKey: string @@ -351,6 +450,72 @@ export const updateAdminApi = { return updateClient.delete(`/api/v1/updates/store/configs/${storeType}`, { params: { appKey } }) }, + // ── iOS / Harmony official store read-only monitoring ────────────────── + + listStoreMonitoringBindings(appKey: string) { + return updateClient.get<{ data: StoreMonitoringBinding[] }>( + '/api/v1/updates/store-monitoring/bindings', + { params: { appKey } }, + ) + }, + + saveStoreMonitoringBinding( + appKey: string, + storeType: StoreMonitoringType, + body: StoreMonitoringBindingInput, + ) { + return updateClient.put<{ data: StoreMonitoringBinding }>( + `/api/v1/updates/store-monitoring/bindings/${storeType}`, + body, + { params: { appKey } }, + ) + }, + + synchronizeStoreMonitoring(appKey: string, storeType: StoreMonitoringType) { + return updateClient.post<{ data: StoreMonitoringVersion[] }>( + `/api/v1/updates/store-monitoring/bindings/${storeType}/sync`, + null, + { params: { appKey } }, + ) + }, + + listStoreMonitoringVersions(appKey: string, bindingId: string) { + return updateClient.get<{ data: StoreMonitoringVersion[] }>( + `/api/v1/updates/store-monitoring/bindings/${bindingId}/versions`, + { params: { appKey } }, + ) + }, + + listStoreMonitoringEvents(appKey: string, bindingId: string) { + return updateClient.get<{ data: StoreMonitoringEvent[] }>( + `/api/v1/updates/store-monitoring/bindings/${bindingId}/events`, + { params: { appKey } }, + ) + }, + + createStoreUpdateDraft(appKey: string, bindingId: string, storeVersionId: string) { + return updateClient.post<{ data: StoreUpdateDraft }>( + `/api/v1/updates/store-monitoring/bindings/${bindingId}/versions/${storeVersionId}/update-draft`, + null, + { params: { appKey } }, + ) + }, + + getStoreNotificationPolicy(appKey: string) { + return updateClient.get<{ data: StoreNotificationPolicy }>( + '/api/v1/updates/store-monitoring/notification-policy', + { params: { appKey } }, + ) + }, + + saveStoreNotificationPolicy(appKey: string, body: StoreNotificationPolicyInput) { + return updateClient.put<{ data: StoreNotificationPolicy }>( + '/api/v1/updates/store-monitoring/notification-policy', + body, + { params: { appKey } }, + ) + }, + executeSubmitToStores( versionId: string, storeTypes: StoreType[], diff --git a/tenant-platform/src/views/update/StoreMonitoringPanel.vue b/tenant-platform/src/views/update/StoreMonitoringPanel.vue new file mode 100644 index 0000000..98514c1 --- /dev/null +++ b/tenant-platform/src/views/update/StoreMonitoringPanel.vue @@ -0,0 +1,816 @@ + + + + + diff --git a/tenant-platform/src/views/update/VersionManagementView.vue b/tenant-platform/src/views/update/VersionManagementView.vue index c937b15..a7b362b 100644 --- a/tenant-platform/src/views/update/VersionManagementView.vue +++ b/tenant-platform/src/views/update/VersionManagementView.vue @@ -156,6 +156,10 @@ + + + +
@@ -218,9 +222,9 @@ - +
- -
- - 🤖 Android - 🍎 iOS - 🔷 鸿蒙 - -
@@ -1010,6 +1006,7 @@ import miGuideImage from '@/assets/update-store/mi/01.png' import oppoGuideImage from '@/assets/update-store/oppo/01.png' import vivoGuideImage from '@/assets/update-store/vivo/01.png' import honorGuideImage from '@/assets/update-store/honor/01.png' +import StoreMonitoringPanel from './StoreMonitoringPanel.vue' const route = useRoute() const router = useRouter() @@ -1411,32 +1408,11 @@ const STORE_DEFS: StoreDef[] = [ }, ] -const GUIDE_GROUPS = computed(() => [ - { - label: 'Android 应用商店', - stores: STORE_DEFS.filter(s => ['HUAWEI', 'MI', 'OPPO', 'VIVO', 'HONOR', 'GOOGLE_PLAY'].includes(s.type)), - }, - { - label: 'iOS / 鸿蒙', - stores: STORE_DEFS.filter(s => ['APP_STORE', 'HARMONY_APP'].includes(s.type)), - }, - { - label: '通知配置', - stores: STORE_DEFS.filter(s => s.type === 'REVIEW_WEBHOOK'), - }, -]) - -const guidePlatform = ref('android') +const ANDROID_STORE_DEFS = STORE_DEFS.filter( + store => ['HUAWEI', 'MI', 'OPPO', 'VIVO', 'HONOR', 'GOOGLE_PLAY'].includes(store.type), +) const guideExpandedStores = ref([]) -const guideFilteredStores = computed(() => { - const map: Record = { - android: ['HUAWEI', 'MI', 'OPPO', 'VIVO', 'HONOR', 'GOOGLE_PLAY'], - ios: ['APP_STORE'], - harmony: ['HARMONY_APP'], - } - const types = map[guidePlatform.value] ?? [] - return STORE_DEFS.filter(s => types.includes(s.type)) -}) +const guideFilteredStores = computed(() => ANDROID_STORE_DEFS) function getStoreConfig(type: StoreType): StoreConfig | undefined { return storeConfigs.value.find(c => c.storeType === type)