提交图

90 次代码提交

作者 SHA1 备注 提交日期
XuqmGroup
9084831b2a fix(安全中心): 修复健康检查误判 force-recreate 旧容器为失败
force-recreate 会先停掉旧容器(status=exited),若此时健康检查
轮询到旧容器的 exited 状态,会误判新容器失败并触发不必要的回滚。

修复方式:
- 新增 getNewestContainerId() 在 compose up 后立即拿到新容器 ID
- waitForServiceStable 接受 containerId 参数,通过 docker inspect
  精确轮询新容器状态,完全隔离旧容器的干扰
- 退化路径(containerId=null)保留原有服务名轮询逻辑

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 01:18:01 +08:00
XuqmGroup
9a9524ac07 feat(安全中心): 一键更新增加健康检查与自动回滚
每个服务重建后轮询容器状态最长 60s:
- 容器保持 running 10s 以上 → 更新成功
- 容器已 exited → 立即触发回滚(retag 旧镜像 ID 重建容器)
- 超时未就绪 → 同样触发回滚

tenant-service 的自更新助手容器也包含相同逻辑:
60s 内不健康则 retag 旧镜像并重建,保证平台始终可访问。

拉取镜像前统一保存各服务旧镜像 ID(captureCurrentImageIds),
回滚时通过 docker tag <old-id> <image:tag> 恢复旧版本。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 00:54:02 +08:00
XuqmGroup
ffdb7c56fe fix(flyway): 各服务配置独立历史表名,解决共享数据库冲突
私有化部署所有服务共用同一 MySQL 库,Flyway 默认都写
flyway_schema_history,导致不同服务的 V1 checksum 互相
覆盖/冲突。改为每个服务使用独立的表名:
flyway_history_{tenant|im|file|update|license|push|demo}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 00:05:31 +08:00
XuqmGroup
200f8ae54a feat: 引入 Flyway 数据库版本管理,替换 ddl-auto: update
所有服务(tenant/im/file/update/license/push/demo)统一:
- 添加 flyway-core + flyway-mysql 依赖
- ddl-auto 从 update 改为 validate
- 添加 V1__init.sql 基线(CREATE TABLE IF NOT EXISTS,幂等)
- baseline-on-migrate=true 兼容已有部署,存量库不重跑 V1

后续 Entity 变更须同步写 V(n+1) 迁移 SQL,Flyway 在容器
启动时自动按版本顺序执行,update.sh 无需任何改动。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 23:28:12 +08:00
XuqmGroup
ede000eac1 fix(dashboard): 统计应用数量时排除系统应用(is_default=1)
DashboardService.stats() 中过滤 isDefault=true 的系统应用,
与 AppService.listByTenant() 保持一致,避免控制台 dashboard
与应用列表页显示数量不符。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 23:09:26 +08:00
XuqmGroup
ce64c8fa60 fix: 修复私有化一键更新三个问题
1. Jenkinsfile: versions.json 补充 platformVersion 字段(取 tenant-service 版本),
   并为每个服务添加 changed 标记;私有部署 checkForUpdates 依赖此字段判断是否有新版本,
   缺失时始终返回 hasUpdate=false。

2. SystemUpdateService: dockerLogin 改为返回 boolean,凭据缺失/登录失败时
   中止更新流程(原来失败后继续,导致 docker compose pull 静默失败仍用旧镜像)。
   readRemoteVersions 新增 VERSIONS_MANIFEST_URL 远端拉取支持,
   私有服务器可在 .env 中配置后自动同步公有端版本清单。
   新增 migrate_v20260610_gray_mode_simplify_bookmark 迁移标记(实际 SQL 由 update-service 执行)。

3. SystemUpdateController: 新增 GET /api/system/versions/manifest 公开端点,
   公有服务器部署后即可作为私有服务器的 VERSIONS_MANIFEST_URL 目标地址。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 20:04:47 +08:00
XuqmGroup
167d403da6 feat(system): 添加系统更新管理和版本控制功能
- 新增私有化部署系统更新API接口(检查更新、选择性更新、重置等)
- 实现版本管理系统,支持平台版本和服务版本对比检查
- 集成Jenkinsfile自动化构建流程,支持多种版本策略
- 添加Docker镜像版本标签管理和自动注入功能
- 实现选择性更新机制,可指定服务进行增量更新
- 完善版本日志记录和更新历史追踪功能
2026-06-11 13:30:41 +08:00
XuqmGroup
77553cd105 feat(app): 支持多平台包名配置和应用信息编辑功能
- 后端增加至少填写一个平台包名的验证逻辑
- 前端调整应用数据模型,将包名字段改为可选类型
- 添加应用详情页的编辑功能和表单验证
- 优化应用列表页包名显示逻辑,支持多平台包名展示
- 重构应用配置指引页面,按平台分类展示商店配置指南
- 在版本管理页面增加包名配置检查和相应提示
- 新增应用信息编辑弹窗组件和相关业务逻辑
2026-06-11 13:04:28 +08:00
XuqmGroup
3e2db6441e feat(update): 添加 API Key 管理和 WebSocket 实时通知功能
- 新增 API Key 管理功能,支持外部工具认证调用平台 API
- 实现 WebSocket 实时通知,版本发布时推送轻量通知给客户端
- 添加 APK 文件哈希校验,支持已下载检测和直接安装
- 支持外部 APK 上传使用 API Key 认证
- 优化私有化部署自动注入 nginx WebSocket 代理配置
- 扩展 SDK 功能包括已下载检测、直接安装和实时通知监听
2026-06-11 12:25:16 +08:00
XuqmGroup
e3d7fbd591 feat(app): 添加重新生成应用配置文件功能
- 在AppController中新增regenerateConfigFile接口
- 在AppService中实现重新生成配置文件的业务逻辑
- 记录重新生成配置文件的操作日志
- 在前端API中添加重新生成功能调用方法
- 在应用详情页面添加重新生成按钮和确认对话框
- 实现重新生成配置文件的前端交互逻辑
2026-06-02 17:43:36 +08:00
XuqmGroup
596927c1c6 refactor(app): 将许可证文件功能替换为配置文件功能
- 替换 LicenseFileCrypto 为 ConfigFileCrypto 加密类
- 将 /license-file 相关接口重命名为 /config-file
- 修改数据库实体中的 licenseFileContent 字段为 configFileContent
- 更新前端 API 调用从 downloadLicenseFile 改为 downloadConfigFile
- 将安全中心的 License 文件解析功能改为 Config 文件解析
- 更新文件扩展名从 .xuqmlicense 改为 .xuqmconfig
- 修改后端服务方法 ensureLicenseFile 为 ensureConfigFile
- 调整加密解密逻辑以支持新的配置文件格式
2026-06-02 17:35:29 +08:00
XuqmGroup
eb8bc70ff5 feat(deploy): 优化版本管理和多租户合并逻辑
- 修改 readCurrentVersion 方法优先读取镜像内的 /app/VERSION 文件
- 添加对宿主机挂载目录 VERSION 文件的兼容性支持
- 移除 bumpVersionFile 方法,不再在更新后写入版本号
- 重构多租户合并逻辑,优化数据库查询和更新操作
- 简化孤儿数据修复逻辑,直接更新为保留租户ID
- 在 Dockerfile 中复制 VERSION 文件到镜像内部
- 在 Jenkinsfile 中添加自动递增构建号功能
2026-05-27 19:25:50 +08:00
XuqmGroup
898597d6b6 ```
refactor(tenant): 优化系统更新服务和租户数据修复逻辑

- 添加版本文件更新功能,在更新完成后写入新版本号到 VERSION 文件
- 重命名租户数据修复方法,从 consolidate_private_tenants 改为 fix_orphan_tenant_data
- 重构租户数据修复逻辑,支持多租户合并和孤儿数据修复两种场景
- 优化数据库操作,使用更精确的查询条件修复孤儿租户数据
- 改进迁移过程的日志输出和状态记录机制
```
2026-05-27 19:14:45 +08:00
XuqmGroup
e3e16352d5 ```
fix(tenant): 修复私有化部署下的租户注册和引导配置逻辑

- 修改 PrivateDeploymentProperties 中的 tenantRegisterEnabled 方法,
  在私有化模式下始终返回 false
- 修改 PrivateDeploymentProperties 中的 tenantBootstrapEnabled 方法,
  在私有化模式下始终返回 true
- 在 SystemUpdateService 中注入 PrivateDeploymentProperties 依赖
- 添加 migrate_v20260527_consolidate_private_tenants 数据库迁移方法
- 实现私有化部署下合并多租户功能,保留最早租户并替换其余租户ID
- 迁移涉及 t_app、t_operation_log、t_migrate_key 等表的数据
- 更新子账号的 parent_id 指向保留的租户
- 删除合并后多余的租户记录
```
2026-05-27 18:57:21 +08:00
XuqmGroup
db986808f2 feat(database): 新增 push 和 license 操作日志表
- 在 tenant-service 的系统更新服务中添加新的数据库迁移方法
- 为 push-service 创建 PushSchemaMigrationService 并实现数据库迁移逻辑
- 为 license-service 创建 LicenseSchemaMigrationService 并实现数据库迁移逻辑
- 创建 push_operation_log 表用于记录推送服务操作日志
- 创建 license_operation_log 表用于记录授权服务操作日志
- 实现数据库迁移记录表 _schema_migrations 以跟踪迁移状态
- 添加迁移验证和错误处理机制确保迁移过程可靠性
2026-05-27 18:01:31 +08:00
XuqmGroup
73dd4814f2 feat(logs): 添加操作日志功能支持推送和授权模块
- 在JwtAuthFilter中设置认证详情到claims
- 为license-service添加LicenseOperationLog相关实体、仓库和服务
- 为push-service添加PushOperationLog相关实体、仓库和服务
- 在LicenseAdminController中注入并使用操作日志记录授权变更
- 在PushManagementController中注入并使用操作日志记录推送操作
- 更新OperationLogService以支持从JWT claims获取用户信息
- 扩展OperationLogService支持推送和授权操作日志查询
- 在前端OperationLogView中添加推送和授权日志选项卡
- 添加LicenseOperationLog和PushOperationLog接口定义
- 实现推送和授权日志的数据加载和分页功能
- 添加操作类型和资源类型的标签映射支持
2026-05-27 13:36:16 +08:00
XuqmGroup
f9ad40cb98 feat(log): 优化操作日志记录和展示功能
- 在OperationLogEntity实体中新增summary和ipAddress字段存储摘要和IP信息
- 修改operationLogService.record方法支持传入操作摘要信息
- 实现客户端IP地址解析功能,支持X-Forwarded-For和X-Real-IP头
- 更新系统更新服务中的数据库表结构迁移逻辑,增加NOT NULL列处理
- 优化前端操作日志页面展示,添加标签分类和详情弹窗功能
- 在系统更新流式响应中增加网络连接异常处理机制
- 添加Nginx代理配置中的缓冲区设置以支持实时日志流式传输
2026-05-27 12:27:42 +08:00
XuqmGroup
50da70d580 fix(core): 统一全局异常处理器并添加数据库管理功能
- 在所有服务的GlobalExceptionHandler中添加HttpServletRequest参数以记录请求上下文
- 统一异常响应格式为ResponseEntity<ApiResponse<Void>>并改进错误日志记录
- 添加对多种异常类型的处理包括参数验证、请求方法不支持、权限拒绝等
- 为业务异常添加不同级别的日志记录(warn/error)和状态码映射
- 在前端系统API中新增数据库表管理相关接口定义和实现
- 添加数据库表列表、列信息和数据查询的API调用函数
2026-05-27 11:51:19 +08:00
XuqmGroup
67da05dadc fix: remove @NotBlank from Java records, add manual validation + reset with data preservation
- Remove @Valid/@NotBlank/@Size/@Email/@NotNull from all Java record DTOs
  (incompatible with Jackson deserialization in Spring Boot 3.x)
- Add manual validation in controllers instead
- Add database reset with data preservation to reset container feature
  (exports core config tables, drops all tables, Hibernate recreates on startup,
  then restores preserved data)
- Update nginx timeout regex to cover all system endpoints

Affected services: tenant-service, license-service, im-service, push-service

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 02:43:35 +08:00
XuqmGroup
26261263a0 fix: use docker ps labels to list services and fetch logs
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>
2026-05-22 23:43:39 +08:00
XuqmGroup
5e788fe26b feat(system): 添加服务日志查看功能及版本化数据库迁移机制
- SystemUpdateService: 引入 _schema_migrations 迁移表,启动时自动执行,替换
  原 docker exec 方式;新增 getRunningServices / getServiceLogs 供日志查看使用
- SystemUpdateController: 新增 GET /api/system/services、/logs/{service}、/version
- OpsController: 新增 GET /api/ops/system/services、/logs/{service}(ROLE_OPS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:22:46 +08:00
XuqmGroup
0e5558116c feat(system): 添加系统版本查询和数据库迁移功能
- 移除 license-service 中 DeviceEntity 的 device_id 唯一约束注解
- 添加 /api/system/version 接口用于查询当前部署版本
- 实现数据库 schema 版本化迁移机制
- 添加自动执行数据库迁移的功能
- 在前端安全中心界面显示当前版本和迁移状态
- 优化配置文件修复逻辑和代码结构
2026-05-22 23:04:36 +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
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
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
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
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
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
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
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
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
4e54737e72 feat: webhook shows app name; auto-withdraw superseded approved stores
- Webhook notification body shows app display name (resolved from
  tenant-service via internal API with in-memory cache) instead of appKey
- When re-uploading a package with the same versionCode, automatically
  withdraw APPROVED store entries from the older entity before submitting
  the new entity, preventing duplicate active submissions
- tenant-service /internal/sdk/apps/{appKey}/platform-info now includes
  the app 'name' field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:31:31 +08:00
XuqmGroup
3bc8a39d0f fix: return 401 (not 403) for unauthenticated requests across all services
Spring Security's default Http403ForbiddenEntryPoint was returning 403
for all auth failures. Frontend clients treat 403 as a permission error
(not an auth error), so silent loops occurred instead of proper re-login.
Adding a custom AuthenticationEntryPoint that returns 401 makes clients
handle auth failures correctly (show login page on 401).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:31:24 +08:00
XuqmGroup
f7dbce7268 fix HONOR poll endpoint and status mapping; improve all store polling reliability
- HONOR: use get-app-current-release endpoint (correct), auditResult field (0=review,1=approved,2=rejected)
- HONOR: assertHonorSuccess now accepts both "0" and "0000" success codes
- OPPO: add integer status mapping (111=approved, 444=rejected) from reference impl
- All stores: add full response body logging for diagnosing poll issues

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:15:19 +08:00
XuqmGroup
4136cd57b6 feat: add app transfer API, parallel store upload, DingTalk/WeCom/Feishu webhook formats
- OpsController/OpsService: POST /api/ops/apps/{appKey}/transfer to move app between tenants
- StoreSubmissionService: read parallelStoreUpload from publish config; conditional parallel vs sequential submission
- AppStoreService: support DINGTALK/WECOM/FEISHU/CUSTOM notify formats in sendWebhook()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:34:59 +08:00
XuqmGroup
57ad8f7f25 fix: app_key backfill on re-registration + notify after tx commit
- LicenseDeviceService: update app_key on re-registration if blank,
  fixing devices that registered before the app_key column was added
- FeatureServiceManager: send activation IM notification in afterCommit()
  hook so the frontend refresh sees the committed DB state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 15:04:57 +08:00