Replace compose-file-path-dependent `docker compose -f <path>` calls
with label-based `docker ps` queries so the ops log viewer works on
both public cloud and private deployments regardless of compose file
location.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enhance the universal cleanup in refreshStoreReviewStatus to also clear
stale nonCurrentRelease/preExisting marks when onlineVersionCode is blank
but currentSubmissionLive is false. This handles OPPO and other stores
that may not return a versionCode in their API response.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add AppService.ensureLicenseFile() that generates and persists a license
file when the app doesn't have one yet. Update AppController.downloadLicenseFile
to use it instead of throwing "License file not generated yet".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous fix only cleared stale APPROVED+nonCurrentRelease states when
poll returned mappedState==APPROVED. If the store API returned UNDER_REVIEW
(for the newly-submitted version) while the DB still held a stale
APPROVED+nonCurrentRelease from the old online version, the stale state
was never cleared.
Add universal cleanup: before any mappedState branching, if onlineVersionCode
< submittedCode and the DB entry has nonCurrentRelease/preExisting flags,
immediately clear the store review state.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add AppStoreService.clearStoreReview() to remove a store's review entry
from storeReviewStatus JSON (used for false-positive cleanup)
- refreshStoreReviewStatus: when existing state is APPROVED but polled
onlineVersionCode < submittedCode, clear the stale state instead of
leaving the misleading nonCurrentRelease flag in place
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Require @AuthenticationPrincipal tenantId in parseLicenseFile endpoint
and verify the decrypted appKey belongs to the current tenant before
returning license contents. Returns 403 "权限不足无法展示" for
cross-tenant license files.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add compareVersionCodes() helper to compare numeric versionCodes safely
- preflightStoreSubmission: block only when onlineVersionCode >= submittedCode;
allow submit when online < submitted (normal new release)
- refreshStoreReviewStatus: only write preExisting=true when online >= submitted
- pollStoreReviewStatus: same guard for UNDER_REVIEW/REJECTED → ONLINE transitions
- All per-store query methods (Huawei, Xiaomi, OPPO, VIVO, Honor): only set
nonCurrentRelease=true when onlineVersionCode > submittedCode
- Fix pre-existing compilation errors: replace findByAppKeyAndStoreType with
findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add composite unique constraint (app_key, device_id) on DeviceEntity
- Remove global unique constraint from device_id column
- Update DeviceRepository: findByAppKeyAndDeviceId returns Optional,
findByDeviceId returns List for multi-app lookups
- Update DeviceService.register/verify to scope lookups by appKey
so same physical device can register independently for each app
- Update LicenseInternalController.getDevice to return list
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- DeviceService.register(): update appKey when device switches to a different app
and adjust registered device counters for old/new appKey
- LicenseAdminController: fix updateAppLicense parameter count mismatch
- AppController: add POST /api/apps/license/parse endpoint for license file decryption
- SecurityCenterView: add License file parser UI with upload and paste support
- appApi: add parseLicenseFile() method
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- AppEntity: add licenseFileContent field to store pre-generated encrypted license
- AppService: generate license file content on create/update with normalized baseUrl
- AppController: read license file content from entity instead of generating on-the-fly
- Web: remove license download v-if serviceEnabled check, always show download button
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SdkConfigController: require packageName param; reject with 403 if it doesn't
match the platform-specific name registered for the app (skipped when app has
no name configured yet).
LicensePublicController: add required packageName to register/verify requests.
DeviceService: validatePackageName() checks against android/ios/harmony names
stored on AppLicenseEntity; rejects if any are configured and none match.
AppLicenseEntity: add android_package_name, ios_bundle_id, harmony_bundle_name
columns (auto-migrated via ddl-auto=update).
LicenseInternalController/AppLicenseService: accept and persist package names
via upsert endpoint.
LicenseServiceClient/FeatureServiceManager: pass app package names when syncing
license records to license-service.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UpdateAssetService: add FILE_BASE_URL / FILE_SERVICE_INTERNAL_URL config; any URL
starting with FILE_BASE_URL is rewritten to the internal file-service address instead
of going through the external domain, fixing APK inspect timeout on private deployments.
SystemUpdateService: add patchDockerComposeUpdateService() to inject FILE_BASE_URL and
FILE_SERVICE_INTERNAL_URL into existing customers' docker-compose.yml on update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
update-service:
- AppPublishConfigRepository/AppStoreConfigRepository: change Optional-returning
findBy methods to findTopBy...OrderByUpdatedAtDesc to tolerate duplicate rows in
public DB and avoid IncorrectResultSizeDataAccessException
- Revert GlobalExceptionHandler to safe "服务器内部错误" (debug details removed)
tenant-service:
- SdkAppInitializer: skip Demo Chat creation on DEPLOYMENT_MODE=PRIVATE;
migrate existing system apps (ak_demo_chat, IM platform app) to is_default=true
- SdkAppProvisioningService.ensureApp: mark all platform-provisioned apps as
is_default=true, deletable=false so they don't appear in user's app list
- PrivateTenantBootstrapInitializer: migrate existing private bootstrap apps to
is_default=true on upgrade
- AppService.listByTenant: filter out is_default=true system apps from the list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OPPO returns audit_status=5 for versions currently under review (审核中);
previously this was mapped to REJECTED causing a state mismatch. Only 444
is now treated as the rejected aggregate code.
Also extend the scheduled poll to restore REJECTED→UNDER_REVIEW for any
store (not just Xiaomi) when the vendor API reports the version is under
review, covering cases where the submission succeeded after our platform
recorded an exception.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OPPO server includes all received body params in server-side signature computation;
sending empty-string optional params while excluding them from the client-side sign
produces a mismatch. Remove empty entries from params map before building both the
api_sign and the POST body so both sides operate on identical param sets.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OPPO: re-obtain access token after APK upload (prevents expiry during long uploads)
- OPPO: exclude empty-string params from api_sign computation per OPPO spec
- OPPO: include errno in error message for easier debugging
- MI: detect already-live version via packageInfo.versionCode before upload;
throw VersionAlreadyLiveException → executor marks state APPROVED instead of REJECTED
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
application.yml — all services:
- Replace hardcoded jdbc:mysql://39.107.53.187 with ${SPRING_DATASOURCE_URL:fallback}
- Same for SPRING_DATASOURCE_USERNAME/PASSWORD
- im-service: replace hardcoded redisdev.xuqinmin.com with ${SPRING_DATA_REDIS_*}
This ensures docker-compose environment overrides take effect; without these
placeholders, Spring Boot's relaxed binding couldn't override the YAML values
and the private deployment connected to production databases.
StoreSubmissionService.refreshStoreReviewStatus — two bugs fixed:
1. MI/UNDER_REVIEW_XIAOMI branch now guards against downgrading APPROVED state.
Xiaomi's poll API returns UNDER_REVIEW_XIAOMI when the submitted version is
not yet the live version, even after the store approves it. Previously this
caused the manual refresh to overwrite a webhook-confirmed APPROVED with
UNDER_REVIEW on every click.
2. When the poll returns APPROVED but currentSubmissionLive=false (another version
is live on the store), no longer overwrite an existing APPROVED (from webhook)
with nonCurrentRelease=true. The webhook is authoritative; the live version
difference just means distribution is pending, not that this is a non-current
release. Only adds nonCurrentRelease when transitioning FROM a non-APPROVED
state (true pre-existing detection).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/api/private/migrate/import → /api/private/deployment/migrate/import
to match PrivateDeploymentController's @RequestMapping("/api/private/deployment")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
updateStoreReviewLive was calling IM notifier but not sendWebhook,
so polling-detected APPROVED events (e.g. Xiaomi going live) never
triggered the tenant's configured webhook URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PrivateDeploymentProperties: DEPLOYMENT_MODE/ENABLE_*/TENANT_BOOTSTRAP_ENABLED config binding
- PrivateTenantBootstrapInitializer: auto-create main tenant and app from env vars when PRIVATE mode, idempotent
- AuthService: block registration with XUQM_PRIVATE_2001 when TENANT_REGISTER_ENABLED=false
- EmailService: block REGISTER email verification in private mode
- SdkConfigController: intersect DB feature flags with ENABLE_* deployment flags for runtime degradation
- PrivateDeploymentController: GET /api/private/deployment/status public endpoint
- SecurityConfig: permit /api/private/deployment/status without auth
- application.yml: add deployment.* and tenant.bootstrap.* config sections with env var bindings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>