提交图

161 次代码提交

作者 SHA1 备注 提交日期
XuqmGroup
0e5558116c feat(system): 添加系统版本查询和数据库迁移功能
- 移除 license-service 中 DeviceEntity 的 device_id 唯一约束注解
- 添加 /api/system/version 接口用于查询当前部署版本
- 实现数据库 schema 版本化迁移机制
- 添加自动执行数据库迁移的功能
- 在前端安全中心界面显示当前版本和迁移状态
- 优化配置文件修复逻辑和代码结构
2026-05-22 23:04:36 +08:00
XuqmGroup
c6ab1b9244 fix(update): 修复应用版本审核状态轮询逻辑
- 扩展查询条件以包含非当前发布和预存在标记的应用版本
- 更新轮询日志信息以反映新的状态检查范围
- 添加对已批准商店中陈旧非当前发布/预存在标记的清理逻辑
- 实现在线版本码比较来清除错误的状态标记
- 优化轮询流程以处理审核状态和陈旧标记的组合情况
2026-05-22 22:18:50 +08:00
XuqmGroup
362dbcc638 fix: clear stale nonCurrentRelease even when onlineVersionCode is blank
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>
2026-05-22 19:55:49 +08:00
XuqmGroup
23390570ef feat: auto-generate license file on download if missing
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>
2026-05-22 19:47:53 +08:00
XuqmGroup
619e822d85 fix: clear stale nonCurrentRelease regardless of polled review state
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>
2026-05-22 19:44:53 +08:00
XuqmGroup
0c9fd338eb fix: clear stale APPROVED state when online version is older than submitted
- 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>
2026-05-22 19:20:48 +08:00
XuqmGroup
8f2f29170e feat: add tenant ownership check to license file parser
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>
2026-05-22 19:09:27 +08:00
XuqmGroup
94fda7ad6e fix: allow store submission when online version is older than submitted
- 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>
2026-05-22 19:09:20 +08:00
XuqmGroup
1a0ef7d886 support multi-app device registration per device
- 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>
2026-05-22 18:47:17 +08:00
XuqmGroup
843ed69f3c license: fix device re-register appKey update, add license file parser
- 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>
2026-05-22 18:37:46 +08:00
XuqmGroup
ccb976c605 tenant: auto-generate license file on app creation, decouple from license service
- 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>
2026-05-22 17:56:12 +08:00
XuqmGroup
8c9bfb6acd feat: license 文件作为通用凭证支持所有服务 SDK 初始化
- LicenseFileCrypto 移至 common 模块并新增 decrypt() 方法
- LicenseFileCrypto.LicensePayload 携带 appKey / packageName / iosBundleId / harmonyBundleName,matchesPackageName() 支持三端包名任一匹配
- tenant-service downloadLicenseFile:去掉"License 服务已开通"限制,app 创建即可下载;payload 新增 iosBundleId / harmonyBundleName
- im / push / update / license 四个服务 SDK 初始化端点均支持双模式:
  · licenseFile 模式:解密文件取 appKey,比对 packageName(无需调 tenant-service)
  · appKey 模式:调 tenant-service 取 platformInfo 比对 packageName(原有逻辑)
- appKey 参数由必填改为可选(与 licenseFile 二选一)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 16:47:30 +08:00
XuqmGroup
0a267c5f70 feat: 校验 SDK 初始化时 packageName 与平台配置的 appKey 是否匹配
- im/push/update 三个服务登录/检查更新接口新增必填参数 packageName
- 调用对应服务的 tenant-service 内部接口获取 platformInfo,与传入包名比对,不匹配返回 403
- update 服务按 platform 字段精确匹配(ANDROID/IOS/HARMONY 各用对应字段)
- im/push 服务对三端包名任一匹配即通过
- ImAppSecretClient / PushAppSecretClient 新增 getPlatformInfo 缓存方法
- 新增 UpdateTenantClient 用于 update-service 调用 tenant-service platformInfo 接口

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 16:41:17 +08:00
XuqmGroup
4c0db6e9b7 feat: validate packageName against appKey on SDK and license init
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>
2026-05-22 16:31:50 +08:00
XuqmGroup
138360b760 fix(update): rewrite file-service URL to internal address for private deployments
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>
2026-05-22 16:03:09 +08:00
XuqmGroup
32aa3c0eef feat(tenant): split update/reset ops, remove bootstrap app auto-creation
- SystemUpdateService: split runUpdate() (pull+recreate) and runReset() (recreate only)
- SystemUpdateController: add POST /api/system/reset endpoint
- SdkAppProvisioningService: remove ensureBootstrapApp/ensureApp/ensureFeatureDefaults; resolveApp now throws 404 instead of auto-creating
- SdkAppInitializer: remove ensureBootstrapApp call; only runs one-time migration marking existing system apps as isDefault=true
- PrivateTenantBootstrapInitializer: remove bootstrap app creation; only ensures admin tenant account exists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 15:33:20 +08:00
XuqmGroup
9728dbb002 fix: suppress duplicate-result errors and hide system apps from private deployment
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>
2026-05-22 14:24:33 +08:00
XuqmGroup
855b17ef0e fix(update): 修复版本列表排序并改进Android包上传功能
- 将版本查询排序从versionCode改为createdAt以正确显示最新版本
- 为Android包上传表单添加已上传文件显示区域
- 实现删除已上传文件的功能以便重新上传
- 添加上传组件引用以支持文件清除操作
- 增加Document和Delete图标导入用于文件管理界面
- 添加已上传文件信息的样式和布局支持
2026-05-21 18:20:06 +08:00
XuqmGroup
24e11794bc refactor(update): 移除版本重复检查并修改发布状态逻辑
- 移除了应用包名和版本号的重复上传检查逻辑
- 修改了发布立即生效时的状态变更机制,改为将其他已发布版本标记为废弃状态
- 新增了按应用键、平台和发布状态查询的方法
- 简化了版本上传时的验证流程,移除了APK文件MD5比较相关代码
2026-05-21 18:08:21 +08:00
XuqmGroup
e5d9e0da0c debug(update-service): 在错误响应中暴露异常信息(临时)
用于排查上传接口 500 错误的根本原因,确认后将恢复。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:47:51 +08:00
XuqmGroup
d49d0297cf fix(update-service): 非灰度版本对匿名用户可见
原逻辑在 allowAnonymousCheck=false 且 userId=null 时直接返回
needsUpdate=false,导致无登录流程的应用(如 clinical-android)
永远收不到更新提示。

修正为:只有灰度版本才需要 userId;非灰度已发布版本对所有调用
方可见,allowAnonymousCheck=false 仅在非灰度场景下补充拦截。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:22:59 +08:00
XuqmGroup
c9c50038bf fix(tenant-service): 自动修复 nginx 更新接口 60s 超时
patchNginxUpdateTimeout 为 /api/system/update 注入精确匹配 location,
proxy_read_timeout 设为 600s,避免 docker pull 静默期断连。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:19:55 +08:00
XuqmGroup
4a38147cb9 feat(tenant-service): 一键更新自动修复配置文件
- 更新前执行幂等配置修复:nginx location /file/ → /api/file/,
  docker-compose.yml 补齐 FILE_UPLOAD_DIR 和 FILE_BASE_URL
- nginx 移至 OTHER_SERVICES 末尾,最后重启以应用修复后的配置
- docker login 读取 .env 中的仓库凭据,解决私有镜像拉取 403

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:08:01 +08:00
XuqmGroup
7a530eb35b fix(license): 新设备注册时初始化 lastVerifiedAt 字段
新设备首次注册成功后,lastVerifiedAt 原本为 null,
导致控制台"最后验证时间"始终为空直到缓存过期后的首次 verify 调用。
注册即视为首次验证,同步写入 lastVerifiedAt。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:39:30 +08:00
XuqmGroup
b0e7f198db feat(license): 支持修改 License 过期时间 + 修复一键更新三个问题
License 过期时间:
- LicenseAdminController PATCH 接口增加 expiresAt 字段
- AppLicenseService.update() 移除"一旦设置不可修改"限制,支持清空(永久)或更新日期

一键更新 (SystemUpdateService) 修复:
1. 改用 docker compose (v2) 替换 docker-compose (v1)
2. isRunning/getCurrentImage 去掉 project=xuqm 标签过滤
   (deploy.sh 不传 -p 参数,实际 project 标签为目录名)
3. 拉取前读取 deployRoot/.env 中的 REGISTRY 凭据并执行 docker login

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:26:01 +08:00
XuqmGroup
aece1fd08d fix(system-update): 用 compose label 查询容器,修复 isRunning 和自更新助手镜像
- isRunning() 改用 docker ps --filter label=com.docker.compose.service
  兼容 Compose v1 (xuqm_svc_1) 和 v2 (xuqm-svc-1) 命名格式
- 自更新助手镜像改用 getCurrentImage() 从运行中容器的 label 获取,
  不再依赖容器环境变量 REGISTRY/IMAGE_TAG(容器内未注入这两个变量)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:46:39 +08:00
XuqmGroup
cc132c7ce7 feat(license): license 文件新增 serverUrl 字段,私有化部署自动写入
私有化模式下生成的 license 文件包含 serverUrl,SDK 通过
XuqmSDK.autoInitialize() 读取后可自动配置所有服务端点,
无需在 App 层硬编码 appKey 或 serverUrl。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:25:13 +08:00
XuqmGroup
a98dbca26d fix(system-update): 用独立助手容器替代 CompletableFuture 实现 tenant-service 自重建
原方案:CompletableFuture 延迟调用 docker-compose up。
问题:docker-compose 发出 stop 指令后,容器内全部进程(含 CompletableFuture 线程)
     被立即杀死,rm/create/start 步骤永远不会执行,tenant-service 停在停止状态。

新方案:先用 docker run -d 启动独立助手容器(xuqm-self-updater),
     它不依附于 tenant-service,不会随之终止;8 秒后执行 force-recreate。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:52:36 +08:00
XuqmGroup
f2e126e2d0 feat(tenant-service): 一键更新接口 + Dockerfile 添加 docker-compose
- 新增 SystemUpdateController POST /api/system/update(PRIVATE 模式)
- SystemUpdateService 通过 docker-compose 拉镜像并逐服务重建容器
- Dockerfile 添加 docker-cli + docker-compose(用于容器内调用 Docker API)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:46:40 +08:00
XuqmGroup
8a3c41d5ff feat(license): 租户自主管理最大设备数,ops 彻底移除 license 管理
license-service:
- LicenseAdminController: 新增 PATCH /api/license/admin/apps/{appKey},
  租户可直接修改 maxDevices / isActive / remark

tenant-service:
- OpsController: 移除 GET /api/ops/apps/{appKey}/license 和
  PUT /api/ops/apps/{appKey}/license/max-devices 两个端点,
  同时移除 licenseServiceClient 字段注入
- LicenseServiceClient: 移除 updateMaxDevices() 和 getAppLicenseStatus()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:45:33 +08:00
XuqmGroup
af922ae420 fix(ci): 串行化生产部署防止并发 docker pull 竞争
多服务同时构建时 Deploy 阶段并发向同一台生产机 docker pull,
containerd content store 写入共享 layer 产生文件竞争导致 rename 失败。
加 lock('prod-deploy') 确保所有服务按序部署,并加 retry(3) 容忍偶发抖动。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:58:08 +08:00
XuqmGroup
25e9bef745 chore(version): 更新版本号到 2026.05.20-private.3
- 添加新版本号文件 VERSION
- 版本号设置为 2026.05.20-private.3
2026-05-21 10:44:59 +08:00
XuqmGroup
02ad5aad06 fix(private): 私有化部署 CORS 放开所有 Origin
私有化部署时客户使用自定义域名,原硬编码的 *.xuqinmin.com 白名单
导致 WebSocket 握手和跨域请求被 Spring Security CORS 过滤器拒绝(426/403)。
检测 DEPLOYMENT_MODE=PRIVATE 环境变量,私有化模式下允许所有 Origin。
影响范围:im-service / file-service / license-service / update-service。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 10:44:33 +08:00
XuqmGroup
897326ff0f feat(private): 新增内部维护接口自动处理积压 PENDING 申请
- SecurityConfig: 放开 /api/private/admin/** 无需 JWT
- FeatureServiceManager.autoApproveAllPending(): 批量审批所有 PENDING 记录
- OpsController: POST /api/private/admin/approve-pending-requests
  仅私有化模式可用,upgrade.sh 重启后自动调用,无需手动操作

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:45:07 +08:00
XuqmGroup
6ca0dcbe74 fix(private): 私有化模式下存量 PENDING 服务申请自动开通
之前的自动开通逻辑在重复申请检查之后,导致已有 PENDING 记录时
直接抛 400 而不进入自动开通流程。
现在私有化模式下检测到 PENDING 记录时直接 approveRequest,
不再返回"请等待运营人员处理"错误。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:32:32 +08:00
XuqmGroup
f9957143da feat(private): 私有化部署增强 — 服务自动开通、屏蔽 Ops 功能
- FeatureServiceManager: 私有化模式下服务开通申请跳过审核,直接自动激活
- OpsController: 私有化模式下 /api/auth/ops/login 返回 404,屏蔽运营登录
- OpsAdminInitializer: 私有化模式下跳过默认运营管理员账号的初始化

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:24:43 +08:00
XuqmGroup
4432c7dc28 fix(oppo): remap audit_status=5 to UNDER_REVIEW; restore REJECTED→UNDER_REVIEW in poll
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>
2026-05-20 10:59:52 +08:00
XuqmGroup
4d2faa33de fix(oppo): strip empty params from submit body to fix sign mismatch (errno=800004)
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>
2026-05-20 10:16:41 +08:00
XuqmGroup
501d7e09ab fix(update): fix OPPO token expiry, sign empty params, and MI already-live detection
- 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>
2026-05-19 19:11:13 +08:00
XuqmGroup
450a44de68 fix: isolate private deployment databases and fix store review refresh bugs
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>
2026-05-19 18:25:50 +08:00
XuqmGroup
9771663f00 fix(tenant): correct import endpoint path in SecurityConfig
/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>
2026-05-19 16:07:39 +08:00
XuqmGroup
d007072ea9 fix(update): send webhook on store live detection (Xiaomi 已上架)
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>
2026-05-19 15:57:51 +08:00
XuqmGroup
f97201e3e3 feat(tenant): API-based tenant migration for private deployment
- Add MigrateController: request-code / generate-key / export endpoints
  with one-time pmk_ key (SHA-256 hashed, 24h expiry)
- Add PrivateDeploymentController import endpoint for private mode only
- Add MigrateKeyEntity / MigrateKeyRepository for key lifecycle
- Add MigrateExportData DTO (tenant + apps + feature services)
- Add AppEntity.isDefault / deletable fields
- Add AppRepository.deleteAllExcept / FeatureServiceRepository.deleteAllExcept
- Permit /api/migrate/export and /api/private/migrate/import in SecurityConfig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 15:11:49 +08:00
XuqmGroup
e5f0e7faea feat: private deployment server-side capabilities (P2)
- 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>
2026-05-18 20:49:46 +08:00
XuqmGroup
4d54d2a4a4 docs(private): 更新私有化SDK设计文档添加配置管理和错误码规范
- Flutter包名格式从xuqm_private_flutter_*改为xuqm_private_*
- 添加configVersion字段用于追踪配置变更,格式为YYYY.MM.DD-序号
- 添加配置版本管理说明,包含schemaVersion兼容性策略
- 添加配置热更新机制,支持冷加载和热加载两种方式
- 添加多环境配置支持,可通过activeProfile指定生效环境
- 添加统一错误码规范,覆盖配置缺失、格式错误、版本不兼容等场景
- 更新验收标准,包含热更新、多环境切换、错误码一致性要求
- 添加私有化开发计划中的错误码规范、文档同步、数据迁移等任务
- 补充MySQL运维细节、证书自动续期、监控告警、日志收集等部署要求
- 添加性能基准指标,包含HTTP API、WebSocket、文件传输等性能要求
- 修复应用商店审核状态轮询中的逻辑错误,添加小米商店特殊处理
- 更新前端界面显示审核版本信息,优化状态刷新逻辑
2026-05-18 19:17:44 +08:00
XuqmGroup
93fdb31cdc docs(private): 完善私有化部署开发计划和设计规范
- 增加实时进度和交接规则,定义任务状态枚举和更新格式
- 创建任务进度台账,涵盖P0-P5阶段全部开发任务
- 补充部署仓库交付边界确认和进度审计规范
- 完善MySQL/Redis双模式支持,增加external/managed选项
- 增加离线部署、安全治理、可观测性等完整交付能力
- 更新仓库结构设计,增加secrets.env、observability、data目录
- 补充健康检查、诊断脚本、升级回滚、备份恢复详细要求
- 优化应用商店审核状态查询逻辑,增加手动刷新接口
- 修复小米和VIVO商店状态查询中的版本匹配逻辑错误
- 增加缓存键版本隔离,防止不同版本状态混淆
- 优化厂商API连通性检查和审核状态轮询机制
2026-05-18 19:00:38 +08:00
XuqmGroup
87edb316a5 feat(private-deploy): 支持 MySQL/Redis 外部连接和托管模式部署
- 添加 external 和 managed 两种数据库/缓存模式支持
- 实现 MySQL/Redis 托管安装脚本和配置向导
- 支持客户自备连接或部署脚本新建基础设施
- 更新部署文档说明不同模式的配置和验证要求
- 添加应用版本防重复上传和删除功能
- 实现应用商店预提交检查和发布计划功能
2026-05-18 18:37:10 +08:00
XuqmGroup
e309a41ed0 docs(deploy): 移除 Jenkins 配置和 Android Demo 计划文档
- 删除 jenkins-setup.md 完整的 Jenkins 服务配置指南
- 更新 README.md 部署文档标题为公有化部署文档
- 添加私有化部署说明章节和相关设计文档链接
- 从 REST API 设计文档中移除 demo-service 相关描述
- 更新推送架构图中业务服务端描述为客户端服务器
- 删除 android-demo-plan.md Android Demo 开发计划文档
- 删除 multi-platform-im-roadmap.md 多平台 IM 路线图文档
- 删除 java-im-server-sdk-plan.md Java IM 服务端 SDK 计划文档
2026-05-18 17:57:05 +08:00
XuqmGroup
b3b33dbb7b fix: remove unreliable post-failure live-check; restrict REJECTED poll to HUAWEI
Post-failure live check called pollStoreSingleReviewState on submission
failure, but for MI/VIVO/OPPO/HONOR the poll returns APPROVED for ANY
live version (not version-specific). This caused false positives: if an
older version was live on Xiaomi, the poll would return APPROVED and
incorrectly mark the new submission as 已上线(直接上传).

Changes:
- Remove post-failure live check from executeSinglePlan and sequential
  failure handler — submission failures are just marked REJECTED
- Restrict the REJECTED-store poll to HUAWEI only, since it is the only
  store where pollStoreSingleReviewState is version-specific
  (compares onShelfVersionCode against v.getVersionCode())
- Log full VIVO data response to capture versionCode field name for
  future version-specific detection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 17:52:53 +08:00
XuqmGroup
8d46d21726 fix: store resubmission, Xiaomi curl, and live-on-store detection
- Dockerfile: add curl to Alpine runtime image (required by Xiaomi/Vivo
  APK upload which uses ProcessBuilder curl for HTTP/1.1 multipart)
- AppStoreService: mark previously-APPROVED stores as WITHDRAWN on
  resubmission so executeSubmitAsync calls the store cancel API first;
  guard updateStoreReview from stale APPROVED webhooks on PENDING/WITHDRAWN
  stores; add updateStoreReviewLive() to record pre-existing live versions
  with liveOnStore/preExisting metadata
- StoreSubmissionService: call cancelAtStore for WITHDRAWN stores before
  resubmitting; expand poll query to include REJECTED stores and call
  updateStoreReviewLive when a REJECTED store is found live; after any
  submission failure check if the store already has the version live
  (catches Huawei/Vivo pre-existing direct uploads at submission time)
- AppVersionRepository: add findAllWithUnderReviewOrRejectedStores query

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 17:30:26 +08:00