From dc1ada94ea4118135aaa71681e29b24dc8e1d8a4 Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Fri, 8 May 2026 18:32:00 +0800 Subject: [PATCH] =?UTF-8?q?docs(deploy):=20=E6=B7=BB=E5=8A=A0=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=96=87=E6=A1=A3=E5=92=8C=E5=AE=89=E5=85=A8=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 XuqmGroup 部署文档,包含部署方案、架构建议和部署步骤 - 添加安全设计规范,涵盖密码安全、AppSecret验证和服务端API认证 - 补充平台REST API规范,定义Server-to-Server调用接口和错误码 - 创建Java IM服务端SDK计划文档,规划Maven包发布和接口实现 --- .java-version | 2 +- Dockerfile | 5 +- Jenkinsfile | 4 +- README.md | 47 +-- .../com/xuqm/demo/entity/DemoUserEntity.java | 10 +- .../demo/repository/DemoUserRepository.java | 10 +- .../xuqm/demo/service/DemoAuthService.java | 10 +- .../xuqm/demo/service/DemoUserService.java | 10 +- docs/API_ACCESS.md | 28 +- .../xuqm/im/controller/FriendController.java | 32 +- .../xuqm/im/controller/ImAdminController.java | 28 +- .../im/controller/StatisticsController.java | 12 +- .../com/xuqm/im/entity/ImAccountEntity.java | 8 +- .../com/xuqm/im/entity/ImBlacklistEntity.java | 10 +- .../im/entity/ImConversationStateEntity.java | 12 +- .../com/xuqm/im/entity/ImFriendEntity.java | 8 +- .../xuqm/im/entity/ImFriendRequestEntity.java | 10 +- .../xuqm/im/entity/ImGlobalMuteEntity.java | 6 +- .../com/xuqm/im/entity/ImGroupEntity.java | 6 +- .../im/entity/ImGroupJoinRequestEntity.java | 8 +- .../com/xuqm/im/entity/ImMessageEntity.java | 10 +- .../im/entity/ImOfflineMessageEntity.java | 6 +- .../xuqm/im/entity/ImOperationLogEntity.java | 10 +- .../xuqm/im/entity/KeywordFilterEntity.java | 6 +- .../xuqm/im/entity/WebhookAlertEntity.java | 6 +- .../xuqm/im/entity/WebhookConfigEntity.java | 6 +- .../xuqm/im/entity/WebhookDeliveryEntity.java | 6 +- .../im/repository/ImAccountRepository.java | 12 +- .../im/repository/ImBlacklistRepository.java | 12 +- .../ImConversationStateRepository.java | 16 +- .../im/repository/ImFriendRepository.java | 14 +- .../repository/ImFriendRequestRepository.java | 10 +- .../im/repository/ImGlobalMuteRepository.java | 2 +- .../ImGroupJoinRequestRepository.java | 10 +- .../xuqm/im/repository/ImGroupRepository.java | 12 +- .../im/repository/ImMessageRepository.java | 80 ++--- .../ImOfflineMessageRepository.java | 8 +- .../repository/ImOperationLogRepository.java | 2 +- .../repository/KeywordFilterRepository.java | 6 +- .../im/repository/WebhookAlertRepository.java | 8 +- .../repository/WebhookConfigRepository.java | 6 +- .../repository/WebhookDeliveryRepository.java | 18 +- .../com/xuqm/im/service/BlacklistService.java | 18 +- .../im/service/ConversationStateService.java | 16 +- .../xuqm/im/service/FriendRequestService.java | 34 +- .../xuqm/im/service/GlobalMuteService.java | 10 +- .../com/xuqm/im/service/ImAccountService.java | 14 +- .../com/xuqm/im/service/ImGroupService.java | 40 +-- .../xuqm/im/service/KeywordFilterService.java | 6 +- .../com/xuqm/im/service/MessageService.java | 28 +- .../im/service/OfflineMessageSyncService.java | 12 +- .../xuqm/im/service/OperationLogService.java | 4 +- .../xuqm/im/service/WebhookConfigService.java | 4 +- .../im/service/WebhookDispatchService.java | 8 +- pom.xml | 1 - .../push/entity/DeviceLoginLogEntity.java | 8 +- .../xuqm/push/entity/DeviceTokenEntity.java | 10 +- .../repository/DeviceLoginLogRepository.java | 2 +- .../repository/DeviceTokenRepository.java | 22 +- .../push/service/PushDiagnosticsService.java | 16 +- .../com/xuqm/push/service/PushDispatcher.java | 122 +++++-- .../service/provider/ApnsPushProvider.java | 23 +- .../service/provider/FcmPushProvider.java | 159 --------- .../service/provider/HarmonyPushProvider.java | 5 +- .../service/provider/HonorPushProvider.java | 20 +- .../service/provider/HuaweiPushProvider.java | 6 +- .../service/provider/OppoPushProvider.java | 11 +- .../service/provider/PushSendOptions.java | 10 +- .../service/provider/VivoPushProvider.java | 19 +- .../service/provider/XiaomiPushProvider.java | 5 +- .../src/main/resources/application.yml | 5 - tenant-service/pom.xml | 5 + .../controller/FeatureServiceController.java | 127 +------- .../controller/SdkConfigController.java | 8 +- .../tenant/entity/FeatureServiceEntity.java | 6 +- .../ServiceActivationRequestEntity.java | 11 +- .../repository/FeatureServiceRepository.java | 8 +- .../ServiceActivationRequestRepository.java | 10 +- .../xuqm/tenant/service/DashboardService.java | 2 +- .../tenant/service/FeatureServiceManager.java | 172 ++-------- .../com/xuqm/tenant/service/OpsService.java | 6 +- .../service/SdkAppProvisioningService.java | 8 +- .../src/main/resources/application.yml | 1 + .../controller/AppVersionController.java | 20 +- .../update/controller/RnBundleController.java | 22 +- .../controller/UnifiedReleaseController.java | 8 +- .../update/entity/AppGrayMemberEntity.java | 8 +- .../update/entity/AppPublishConfigEntity.java | 8 +- .../update/entity/AppStoreConfigEntity.java | 8 +- .../xuqm/update/entity/AppVersionEntity.java | 8 +- .../xuqm/update/entity/RnBundleEntity.java | 6 +- .../entity/UpdateOperationLogEntity.java | 6 +- .../repository/AppGrayMemberRepository.java | 2 +- .../AppPublishConfigRepository.java | 2 +- .../repository/AppStoreConfigRepository.java | 6 +- .../repository/AppVersionRepository.java | 16 +- .../update/repository/RnBundleRepository.java | 12 +- .../UpdateOperationLogRepository.java | 2 +- .../xuqm/update/service/AppStoreService.java | 163 ++++++++-- .../update/service/PublishConfigService.java | 16 +- .../service/StoreSubmissionService.java | 303 ++++++++++++++++-- .../service/UpdateOperationLogService.java | 4 +- .../src/main/resources/application.yml | 4 + 103 files changed, 1144 insertions(+), 1014 deletions(-) delete mode 100644 push-service/src/main/java/com/xuqm/push/service/provider/FcmPushProvider.java diff --git a/.java-version b/.java-version index 98d9bcb..aabe6ec 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -17 +21 diff --git a/Dockerfile b/Dockerfile index cab9937..45c33ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1.7 ARG SERVICE_MODULE=tenant-service FROM maven:3.9.9-eclipse-temurin-21 AS build @@ -5,6 +6,7 @@ ARG SERVICE_MODULE WORKDIR /workspace COPY pom.xml ./pom.xml +COPY maven-settings.xml ./maven-settings.xml COPY common ./common COPY im-sdk ./im-sdk COPY tenant-service ./tenant-service @@ -14,7 +16,8 @@ COPY update-service ./update-service COPY demo-service ./demo-service COPY file-service ./file-service -RUN mvn -pl ${SERVICE_MODULE} -am -DskipTests package +RUN --mount=type=cache,target=/root/.m2,sharing=locked \ + mvn -s /workspace/maven-settings.xml -pl ${SERVICE_MODULE} -am -DskipTests package FROM eclipse-temurin:21-jre-jammy WORKDIR /app diff --git a/Jenkinsfile b/Jenkinsfile index 839be5d..958ac6c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,6 +15,7 @@ pipeline { PROD_HOST = '106.54.23.149' PROD_USER = 'ubuntu' COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml' + DOCKER_BUILDKIT = '1' } stages { @@ -29,7 +30,8 @@ pipeline { def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${params.SERVICE}:${params.IMAGE_TAG}" bat """ docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS% - docker build --build-arg SERVICE_MODULE=${params.SERVICE} -t ${imageName} . + docker pull ${imageName} || exit 0 + docker build --build-arg SERVICE_MODULE=${params.SERVICE} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${imageName} -t ${imageName} . docker push ${imageName} docker rmi ${imageName} """ diff --git a/README.md b/README.md index 94ce9b5..5911c28 100644 --- a/README.md +++ b/README.md @@ -138,15 +138,15 @@ cd update-service && mvn spring-boot:run & } ``` -> 说明:SDK 和 demo 侧传入的 `appId` 实际按 `appKey` 解析。当前默认值是 `ak_demo_chat`,如果数据库里没有这条记录,tenant-service 会在启动时自动创建。 +> 说明:SDK 和 demo 侧统一传 `appKey`。当前默认值是 `ak_demo_chat`,如果数据库里没有这条记录,tenant-service 会在启动时自动创建。 #### 功能服务(需 Token) | 方法 | 路径 | 说明 | |------|------|------| -| GET | `/api/apps/{appId}/services` | 获取应用下所有功能服务 | -| POST | `/api/apps/{appId}/services/toggle?platform=&serviceType=&enable=` | 开启/关闭功能服务 | -| POST | `/api/apps/{appId}/services/{id}/regenerate-key` | 重新生成 secretKey | +| GET | `/api/apps/{appKey}/services` | 获取应用下所有功能服务 | +| POST | `/api/apps/{appKey}/services/toggle?platform=&serviceType=&enable=` | 开启/关闭功能服务 | +| POST | `/api/apps/{appKey}/services/{id}/regenerate-key` | 重新生成 secretKey | **platform 枚举**:`ANDROID` / `IOS` / `HARMONY` **serviceType 枚举**:`IM` / `PUSH` / `UPDATE` @@ -216,7 +216,7 @@ cd update-service && mvn spring-boot:run & ``` POST /api/im/auth/login - ?appId=ak_xxx + ?appKey=ak_xxx &userId=user_001 &nickname=张三 (可选,仅首次注册时存入外部系统) &avatar=https://... (可选) @@ -230,9 +230,9 @@ POST /api/im/auth/login | 方法 | 路径 | 说明 | |------|------|------| -| POST | `/api/im/messages/send?appId=` | 发送消息 | -| POST | `/api/im/messages/{id}/revoke?appId=` | 撤回消息 | -| GET | `/api/im/messages/history/{toId}?appId=&page=&size=` | 查询历史消息 | +| POST | `/api/im/messages/send?appKey=` | 发送消息 | +| POST | `/api/im/messages/{id}/revoke?appKey=` | 撤回消息 | +| GET | `/api/im/messages/history/{toId}?appKey=&page=&size=` | 查询历史消息 | **发送消息请求体** ```json @@ -276,7 +276,7 @@ ws://localhost:8082/ws/im?token= { "destination": "/app/chat.send", "payload": { - "appId": "ak_xxx", + "appKey": "ak_xxx", "toId": "user_002", "chatType": "SINGLE", "msgType": "TEXT", @@ -310,7 +310,7 @@ Frame 格式: ```json { "destination": "/app/chat.revoke", - "payload": { "appId": "ak_xxx", "messageId": "msg-uuid" } + "payload": { "appKey": "ak_xxx", "messageId": "msg-uuid" } } ``` @@ -332,7 +332,7 @@ Frame 格式: ```json { "event": "message", - "appId": "ak_xxx", + "appKey": "ak_xxx", "message": { ...消息对象... } } ``` @@ -345,11 +345,11 @@ Frame 格式: | 表名 | 说明 | |------|------| -| `push_device_token` | 设备推送 token(appId + userId + vendor 唯一) | +| `push_device_token` | 设备推送 token(appKey + userId + vendor 唯一) | ### 支持厂商 -`HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`(iOS)/ `FCM` +`HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`(iOS) ### 接口 @@ -366,7 +366,7 @@ Frame 格式: **注册 token** ``` POST /api/push/register - ?appId=ak_xxx + ?appKey=ak_xxx &userId=user_001 &vendor=HUAWEI &token=device_push_token @@ -375,7 +375,7 @@ POST /api/push/register **发送推送** ``` POST /api/push/send - ?appId=ak_xxx + ?appKey=ak_xxx &userId=user_001 &title=新消息 &body=张三: Hello! @@ -385,7 +385,7 @@ POST /api/push/send **开关接收推送** ``` POST /api/push/receive-push - ?appId=ak_xxx + ?appKey=ak_xxx &userId=user_001 &enabled=false ``` @@ -393,7 +393,7 @@ POST /api/push/receive-push **内部通知** ```json { - "appId": "ak_xxx", + "appKey": "ak_xxx", "userIds": ["user_001", "user_002"], "title": "群聊消息", "body": "张三: Hello!", @@ -437,11 +437,12 @@ push: | POST | `/api/v1/updates/app/upload` | 上传版本(先传 file-service,再把 `apkUrl` 交给 update-service) | | POST | `/api/v1/updates/app/{id}/publish` | 发布版本 | | GET | `/api/v1/updates/app/list` | 版本列表 | +| POST | `/api/v1/updates/store/app/{id}/execute-submit` | 批量提交应用市场,过程会写入批次日志与逐市场状态 | **检查更新** ``` GET /api/v1/updates/app/check - ?appId=ak_xxx + ?appKey=ak_xxx &platform=ANDROID ¤tVersionCode=10 ``` @@ -464,6 +465,8 @@ GET /api/v1/updates/app/check **platform 枚举**:`ANDROID` / `IOS` / `HARMONY` +**市场提审状态**:`PENDING` / `SUBMITTING` / `UNDER_REVIEW` / `APPROVED` / `REJECTED` + **上传 APK(两段式)** ``` POST /api/file/upload @@ -474,7 +477,7 @@ POST /api/file/upload ``` POST /api/v1/updates/app/upload - appId=ak_xxx + appKey=ak_xxx platform=ANDROID versionName=1.1.0 versionCode=11 @@ -494,7 +497,7 @@ POST /api/v1/updates/app/upload **检查 RN 更新** ``` GET /api/v1/rn/update/check - ?appId=ak_xxx + ?appKey=ak_xxx &moduleId=main &platform=android ¤tVersion=1.0.0 @@ -517,7 +520,7 @@ GET /api/v1/rn/update/check **上传 Bundle(multipart/form-data)** ``` POST /api/v1/rn/upload - appId=ak_xxx + appKey=ak_xxx moduleId=main platform=ANDROID version=1.1.0 @@ -526,7 +529,7 @@ POST /api/v1/rn/upload bundle= ``` -服务端自动计算 MD5,存储至 `{upload-dir}/rn/{appId}/{platform}/{moduleId}/`。 +服务端自动计算 MD5,存储至 `{upload-dir}/rn/{appKey}/{platform}/{moduleId}/`。 ### 环境变量配置 diff --git a/demo-service/src/main/java/com/xuqm/demo/entity/DemoUserEntity.java b/demo-service/src/main/java/com/xuqm/demo/entity/DemoUserEntity.java index fcc844e..fcc564a 100644 --- a/demo-service/src/main/java/com/xuqm/demo/entity/DemoUserEntity.java +++ b/demo-service/src/main/java/com/xuqm/demo/entity/DemoUserEntity.java @@ -6,7 +6,7 @@ import java.time.Instant; @Entity @Table( name = "demo_user", - uniqueConstraints = @UniqueConstraint(name = "uq_demo_user_appid_userid", columnNames = {"app_id", "user_id"}) + uniqueConstraints = @UniqueConstraint(name = "uq_demo_user_appkey_userid", columnNames = {"app_key", "user_id"}) ) public class DemoUserEntity { @@ -18,8 +18,8 @@ public class DemoUserEntity { @Column(name = "id", length = 36, nullable = false, updatable = false) private String id; - @Column(name = "app_id", length = 64, nullable = false) - private String appId; + @Column(name = "app_key", length = 64, nullable = false) + private String appKey; @Column(name = "user_id", length = 128, nullable = false) private String userId; @@ -43,8 +43,8 @@ public class DemoUserEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/demo-service/src/main/java/com/xuqm/demo/repository/DemoUserRepository.java b/demo-service/src/main/java/com/xuqm/demo/repository/DemoUserRepository.java index 0ab3856..346f649 100644 --- a/demo-service/src/main/java/com/xuqm/demo/repository/DemoUserRepository.java +++ b/demo-service/src/main/java/com/xuqm/demo/repository/DemoUserRepository.java @@ -10,14 +10,14 @@ import java.util.Optional; public interface DemoUserRepository extends JpaRepository { - Optional findByAppIdAndUserId(String appId, String userId); + Optional findByAppKeyAndUserId(String appKey, String userId); - boolean existsByAppIdAndUserId(String appId, String userId); + boolean existsByAppKeyAndUserId(String appKey, String userId); - @Query("SELECT u FROM DemoUserEntity u WHERE u.appId = :appId AND " + + @Query("SELECT u FROM DemoUserEntity u WHERE u.appKey = :appKey AND " + "(LOWER(u.userId) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " + "LOWER(u.nickname) LIKE LOWER(CONCAT('%', :keyword, '%')))") - List searchByKeyword(@Param("appId") String appId, @Param("keyword") String keyword); + List searchByKeyword(@Param("appKey") String appKey, @Param("keyword") String keyword); - List findAllByAppIdOrderByCreatedAtAsc(String appId); + List findAllByAppKeyOrderByCreatedAtAsc(String appKey); } diff --git a/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java b/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java index 4a8fcf5..e1bb10c 100644 --- a/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java +++ b/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java @@ -58,13 +58,13 @@ public class DemoAuthService { @Transactional public AuthResult register(String appKey, String userId, String password, String nickname) { - if (userRepository.existsByAppIdAndUserId(appKey, userId)) { + if (userRepository.existsByAppKeyAndUserId(appKey, userId)) { throw new BusinessException(409, "User already exists: " + userId); } DemoUserEntity user = new DemoUserEntity(); user.setId(UUID.randomUUID().toString()); - user.setAppId(appKey); + user.setAppKey(appKey); user.setUserId(userId); user.setPasswordHash(passwordEncoder.encode(password)); user.setNickname(nickname != null ? nickname : userId); @@ -84,7 +84,7 @@ public class DemoAuthService { @Transactional(readOnly = true) public AuthResult login(String appKey, String userId, String password) { - DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId) + DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId) .orElseThrow(() -> new BusinessException(401, "Invalid credentials")); if (!passwordEncoder.matches(password, user.getPasswordHash())) { @@ -103,7 +103,7 @@ public class DemoAuthService { @Transactional public void resetPassword(String appKey, String userId, String newPassword) { - DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId) + DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId) .orElseThrow(() -> new BusinessException(404, "User not found: " + userId)); user.setPasswordHash(passwordEncoder.encode(newPassword)); userRepository.save(user); @@ -161,7 +161,7 @@ public class DemoAuthService { private UserProfile toProfile(DemoUserEntity user) { return new UserProfile( - user.getAppId(), + user.getAppKey(), user.getUserId(), user.getNickname(), user.getAvatar(), diff --git a/demo-service/src/main/java/com/xuqm/demo/service/DemoUserService.java b/demo-service/src/main/java/com/xuqm/demo/service/DemoUserService.java index 2a59f3a..a6b5732 100644 --- a/demo-service/src/main/java/com/xuqm/demo/service/DemoUserService.java +++ b/demo-service/src/main/java/com/xuqm/demo/service/DemoUserService.java @@ -24,14 +24,14 @@ public class DemoUserService { @Transactional(readOnly = true) public UserProfile getProfile(String appKey, String userId) { - DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId) + DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId) .orElseThrow(() -> new BusinessException(404, "User not found")); return toProfile(user); } @Transactional public UserProfile updateProfile(String appKey, String userId, String nickname, String avatar, String gender) { - DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId) + DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId) .orElseThrow(() -> new BusinessException(404, "User not found")); if (nickname != null && !nickname.isBlank()) { @@ -54,7 +54,7 @@ public class DemoUserService { @Transactional public void resetPassword(String appKey, String userId, String oldPassword, String newPassword) { - DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId) + DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId) .orElseThrow(() -> new BusinessException(404, "User not found")); if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) { @@ -81,7 +81,7 @@ public class DemoUserService { @Transactional(readOnly = true) public List listMembers(String appKey) { - return userRepository.findAllByAppIdOrderByCreatedAtAsc(appKey) + return userRepository.findAllByAppKeyOrderByCreatedAtAsc(appKey) .stream() .map(this::toProfile) .toList(); @@ -89,7 +89,7 @@ public class DemoUserService { private UserProfile toProfile(DemoUserEntity user) { return new UserProfile( - user.getAppId(), + user.getAppKey(), user.getUserId(), user.getNickname(), user.getAvatar(), diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md index 1c81ac8..f707b21 100644 --- a/docs/API_ACCESS.md +++ b/docs/API_ACCESS.md @@ -20,14 +20,14 @@ | 名称 | 含义 | 常见位置 | |------|------|----------| | `tenant appKey` | 租户平台应用唯一标识,`tenant-service` 的 `/api/apps/{appKey}`、`/api/apps/{appKey}/services` 使用它 | 租户平台、服务配置页 | -| `IM appKey` | IM 业务域作用域标识,`im-service` 的管理接口和消息接口虽然历史参数名仍叫 `appId`,但实际传的是这个值 | IM 管理页、IM HTTP 接口 | +| `IM appKey` | IM 业务域作用域标识,`im-service` 的管理接口和消息接口统一使用它 | IM 管理页、IM HTTP 接口 | 结论: - `tenant-platform` 的“服务配置”只认租户 `appKey` - `tenant-platform` 的“IM 管理”必须带 `appKey` - `tenant-platform` 的“离线推送 / 版本管理”只需要一次开通,平台差异配置在各自的管理页里维护,不再按 Android / iOS / 鸿蒙分别申请开通 -- `im-service` 代码里沿用旧参数名 `appId`,但这是历史命名,调用方传的是 `appKey` +- `im-service` 的对外协议统一使用 `appKey` ## 初始化管理员账号 @@ -184,6 +184,8 @@ - RN bundle 建议打成 zip 后再上传,zip 内至少包含 `rn-manifest.json`、bundle 文件和资源文件;update-service 会优先从 manifest 自动读取 `moduleId`、`version` / `bundleVersion`、`minCommonVersion` 和 `packageName`。 - 租户平台里的“发布配置”标签页保存灰度默认模式、成员目录同步回调和成员选择回调;当默认模式切到成员灰度时,至少要配置一个回调才允许保存,保存前也会做连通性校验。 - 上下架、上传、发布、灰度、市场提交、商店配置变更都会写入 `update_operation_log`,可通过 `GET /api/v1/updates/ops/logs?appKey=...` 查询。 +- 市场提交增加了批次级日志和逐市场阶段日志,常见 action 包括 `STORE_SUBMIT_REQUEST`、`STORE_SUBMIT_BATCH_START`、`STORE_SUBMIT_STORE_START`、`STORE_SUBMIT_STORE_STAGE`、`STORE_SUBMIT_STORE_SUCCESS`、`STORE_SUBMIT_STORE_FAILED`、`STORE_SUBMIT_BATCH_END`。 +- `storeReviewStatus` 的单市场状态除了 `PENDING / UNDER_REVIEW / APPROVED / REJECTED` 之外,还会出现 `SUBMITTING`,表示当前已经进入提审流程但尚未返回最终结果;详情页会额外显示阶段、提交时间和批次 ID。 - 提交应用市场会真实调用已实现的厂商接口。小米、OPPO、vivo 和华为/荣耀当前支持服务端提交;App Store、Google Play、鸿蒙仍以跳转页和人工流程为主。 - 租户平台控制台新增 `GET /api/dashboard/stats`,返回当前租户的应用数、已开通服务数和子账号数,同时会写一条 `CONSOLE / DASHBOARD / VIEW_DASHBOARD` 操作日志。 - 租户平台“操作日志”菜单现在集中查看租户平台与版本管理两类日志;版本管理日志继续按 `appKey` 查询,控制台访问日志则落在 `t_operation_log`。 @@ -343,7 +345,7 @@ IM 服务会在消息、好友和已读等事件发生后,以 `POST` 方式向 请求头: - `Content-Type: application/json` -- `X-App-Id`: 应用 `appId` +- `X-App-Key`: 应用 `appKey` - `X-App-Timestamp`: 请求时间戳 - `X-App-Nonce`: 随机串 - `X-App-Signature`: 签名结果 @@ -351,7 +353,7 @@ IM 服务会在消息、好友和已读等事件发生后,以 `POST` 方式向 签名规则: ```text -HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(body)) +HMAC-SHA256(appSecret, appKey + "\n" + timestamp + "\n" + nonce + "\n" + sha256(body)) ``` 统一请求体结构: @@ -364,7 +366,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b "requestTime": 1714360000000, "payload": {}, "signature": null, - "appId": "ak_demo_chat" + "appKey": "ak_demo_chat" } ``` @@ -378,7 +380,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b | `requestTime` | 毫秒时间戳 | | `payload` | 事件数据,结构随事件变化 | | `signature` | 预留字段,当前由请求头承载 | -| `appId` | 回调所属应用 ID | +| `appKey` | 回调所属应用唯一标识 | `payload` 会根据事件类型变化: @@ -403,7 +405,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b ```json { - "appId": "ak_demo_chat", + "appKey": "ak_demo_chat", "readerId": "user_001", "peerId": "user_002", "groupId": null, @@ -417,7 +419,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b ```json { - "appId": "ak_demo_chat", + "appKey": "ak_demo_chat", "requestId": "req_001", "fromUserId": "user_001", "toUserId": "user_002", @@ -431,7 +433,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b ```json { - "appId": "ak_demo_chat", + "appKey": "ak_demo_chat", "requestId": "req_001", "groupId": "group_001", "groupName": "技术群", @@ -446,7 +448,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b ```json { - "appId": "ak_demo_chat", + "appKey": "ak_demo_chat", "id": "blk_001", "userId": "user_001", "blockedUserId": "user_002", @@ -469,14 +471,14 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b import crypto from 'crypto' export function verifyWebhook({ - appId, + appKey, timestamp, nonce, body, signature, appSecret, }: { - appId: string + appKey: string timestamp: string nonce: string body: string @@ -484,7 +486,7 @@ export function verifyWebhook({ appSecret: string }) { const bodyHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex') - const payload = `${appId}\n${timestamp}\n${nonce}\n${bodyHash}` + const payload = `${appKey}\n${timestamp}\n${nonce}\n${bodyHash}` const expected = crypto.createHmac('sha256', appSecret).update(payload, 'utf8').digest('hex') return expected === signature } diff --git a/im-service/src/main/java/com/xuqm/im/controller/FriendController.java b/im-service/src/main/java/com/xuqm/im/controller/FriendController.java index 5a684fa..c9b94f5 100644 --- a/im-service/src/main/java/com/xuqm/im/controller/FriendController.java +++ b/im-service/src/main/java/com/xuqm/im/controller/FriendController.java @@ -33,7 +33,7 @@ public class FriendController { public ResponseEntity>> listFriends( @AuthenticationPrincipal String userId, @RequestParam String appKey) { - List friendIds = friendRepository.findByAppIdAndUserId(appKey, userId) + List friendIds = friendRepository.findByAppKeyAndUserId(appKey, userId) .stream() .map(ImFriendEntity::getFriendId) .toList(); @@ -68,8 +68,8 @@ public class FriendController { @AuthenticationPrincipal String userId, @PathVariable String friendId, @RequestParam String appKey) { - friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId); - friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId); + friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, userId, friendId); + friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, friendId, userId); return ResponseEntity.ok(ApiResponse.success(null)); } @@ -77,8 +77,8 @@ public class FriendController { public ResponseEntity> removeAllFriends( @AuthenticationPrincipal String userId, @RequestParam String appKey) { - friendRepository.deleteByAppIdAndUserId(appKey, userId); - friendRepository.deleteByAppIdAndFriendId(appKey, userId); + friendRepository.deleteByAppKeyAndUserId(appKey, userId); + friendRepository.deleteByAppKeyAndFriendId(appKey, userId); return ResponseEntity.ok(ApiResponse.ok()); } @@ -91,8 +91,8 @@ public class FriendController { if (friendId == null || friendId.isBlank() || userId.equals(friendId)) { continue; } - friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId); - friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId); + friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, userId, friendId); + friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, friendId, userId); } return ResponseEntity.ok(ApiResponse.success(null)); } @@ -103,7 +103,7 @@ public class FriendController { @PathVariable String friendId, @RequestParam String appKey, @RequestParam(required = false) String groupName) { - ImFriendEntity link = friendRepository.findByAppIdAndUserIdAndFriendId(appKey, userId, friendId) + ImFriendEntity link = friendRepository.findByAppKeyAndUserIdAndFriendId(appKey, userId, friendId) .orElseGet(() -> addFriendLink(appKey, userId, friendId)); link.setFriendGroup(normalizeGroup(groupName)); return ResponseEntity.ok(ApiResponse.success(friendRepository.save(link))); @@ -113,7 +113,7 @@ public class FriendController { public ResponseEntity>> listFriendGroups( @AuthenticationPrincipal String userId, @RequestParam String appKey) { - List groups = friendRepository.findByAppIdAndUserId(appKey, userId).stream() + List groups = friendRepository.findByAppKeyAndUserId(appKey, userId).stream() .map(ImFriendEntity::getFriendGroup) .filter(group -> group != null && !group.isBlank()) .distinct() @@ -128,7 +128,7 @@ public class FriendController { @RequestParam String appKey, @PathVariable String groupName) { List friendIds = friendRepository - .findByAppIdAndUserIdAndFriendGroup(appKey, userId, normalizeGroup(groupName)) + .findByAppKeyAndUserIdAndFriendGroup(appKey, userId, normalizeGroup(groupName)) .stream() .map(ImFriendEntity::getFriendId) .toList(); @@ -137,19 +137,19 @@ public class FriendController { private ImFriendEntity addFriendLink(String appKey, String userId, String friendId) { ImFriendEntity forward = friendRepository - .findByAppIdAndUserIdAndFriendId(appKey, userId, friendId) + .findByAppKeyAndUserIdAndFriendId(appKey, userId, friendId) .orElseGet(() -> { ImFriendEntity e = new ImFriendEntity(); - e.setAppId(appKey); + e.setAppKey(appKey); e.setUserId(userId); e.setFriendId(friendId); return friendRepository.save(e); }); - friendRepository.findByAppIdAndUserIdAndFriendId(appKey, friendId, userId) + friendRepository.findByAppKeyAndUserIdAndFriendId(appKey, friendId, userId) .orElseGet(() -> { ImFriendEntity e = new ImFriendEntity(); - e.setAppId(appKey); + e.setAppKey(appKey); e.setUserId(friendId); e.setFriendId(userId); return friendRepository.save(e); @@ -172,8 +172,8 @@ public class FriendController { @RequestBody FriendCheckRequest req) { List results = new ArrayList<>(); for (String friendId : req.friendIds() == null ? List.of() : req.friendIds()) { - boolean isFriend = friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId) - || friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId); + boolean isFriend = friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, userId, friendId) + || friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, friendId, userId); results.add(new FriendCheckResult(friendId, isFriend)); } return ResponseEntity.ok(ApiResponse.success(results)); diff --git a/im-service/src/main/java/com/xuqm/im/controller/ImAdminController.java b/im-service/src/main/java/com/xuqm/im/controller/ImAdminController.java index 790e484..4b79fd4 100644 --- a/im-service/src/main/java/com/xuqm/im/controller/ImAdminController.java +++ b/im-service/src/main/java/com/xuqm/im/controller/ImAdminController.java @@ -109,7 +109,7 @@ public class ImAdminController { @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { return ResponseEntity.ok(ApiResponse.success( - accountRepository.findByAppId(appKey, PageRequest.of(page, size)))); + accountRepository.findByAppKey(appKey, PageRequest.of(page, size)))); } /** Ban or unban a user. */ @@ -119,7 +119,7 @@ public class ImAdminController { @PathVariable String userId, @AuthenticationPrincipal String operatorId, @RequestBody Map body) { - ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId) + ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId) .orElseThrow(() -> new BusinessException(404, "账号不存在")); String status = body.get("status"); if (status == null || status.isBlank()) { @@ -152,7 +152,7 @@ public class ImAdminController { /** List all groups for the given appKey. */ @GetMapping("/groups") public ResponseEntity>> listGroups(@RequestParam String appKey) { - return ResponseEntity.ok(ApiResponse.success(groupRepository.findByAppId(appKey))); + return ResponseEntity.ok(ApiResponse.success(groupRepository.findByAppKey(appKey))); } /** Admin registers a new IM user (or returns existing). */ @@ -253,10 +253,10 @@ public class ImAdminController { public ResponseEntity>> stats( @RequestParam String appKey, @AuthenticationPrincipal String operatorId) { - long totalMessages = messageRepository.countByAppId(appKey); - long totalUsers = accountRepository.countByAppId(appKey); - long totalGroups = groupRepository.countByAppId(appKey); - long todayMessages = messageRepository.countTodayByAppId(appKey); + long totalMessages = messageRepository.countByAppKey(appKey); + long totalUsers = accountRepository.countByAppKey(appKey); + long totalGroups = groupRepository.countByAppKey(appKey); + long todayMessages = messageRepository.countTodayByAppKey(appKey); operationLogService.record(appKey, operatorId, "VIEW_STATS", "STATS", null, "summary"); return ResponseEntity.ok(ApiResponse.success(Map.of( @@ -551,11 +551,11 @@ public class ImAdminController { Page result; PageRequest pageable = PageRequest.of(page, size); if (callbackEvent != null && !callbackEvent.isBlank()) { - result = webhookDeliveryRepository.findByAppIdAndCallbackEvent(appKey, callbackEvent, pageable); + result = webhookDeliveryRepository.findByAppKeyAndCallbackEvent(appKey, callbackEvent, pageable); } else if (success != null) { - result = webhookDeliveryRepository.findByAppIdAndSuccess(appKey, success, pageable); + result = webhookDeliveryRepository.findByAppKeyAndSuccess(appKey, success, pageable); } else { - result = webhookDeliveryRepository.findByAppId(appKey, pageable); + result = webhookDeliveryRepository.findByAppKey(appKey, pageable); } return ResponseEntity.ok(ApiResponse.success(result)); } @@ -569,9 +569,9 @@ public class ImAdminController { Page result; PageRequest pageable = PageRequest.of(page, size); if (acknowledged != null) { - result = webhookAlertRepository.findByAppIdAndAcknowledged(appKey, acknowledged, pageable); + result = webhookAlertRepository.findByAppKeyAndAcknowledged(appKey, acknowledged, pageable); } else { - result = webhookAlertRepository.findByAppId(appKey, pageable); + result = webhookAlertRepository.findByAppKey(appKey, pageable); } return ResponseEntity.ok(ApiResponse.success(result)); } @@ -583,7 +583,7 @@ public class ImAdminController { @AuthenticationPrincipal String operatorId) { WebhookAlertEntity alert = webhookAlertRepository.findById(id) .orElseThrow(() -> new BusinessException(404, "告警不存在")); - if (!alert.getAppId().equals(appKey)) { + if (!alert.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } alert.setAcknowledged(true); @@ -604,7 +604,7 @@ public class ImAdminController { health.put("enabled", webhook.isEnabled()); health.put("consecutiveFailures", webhook.getConsecutiveFailures()); health.put("lastFailureAt", webhook.getLastFailureAt()); - long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppId(appKey); + long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppKey(appKey); health.put("unacknowledgedAlerts", unacknowledgedAlerts); return ResponseEntity.ok(ApiResponse.success(health)); } diff --git a/im-service/src/main/java/com/xuqm/im/controller/StatisticsController.java b/im-service/src/main/java/com/xuqm/im/controller/StatisticsController.java index 2920e89..cbb0ce3 100644 --- a/im-service/src/main/java/com/xuqm/im/controller/StatisticsController.java +++ b/im-service/src/main/java/com/xuqm/im/controller/StatisticsController.java @@ -27,10 +27,10 @@ public class StatisticsController { @RequestParam(required = false) Integer days) { int d = days == null ? 7 : days; LocalDateTime since = LocalDateTime.now().minusDays(d); - long total = messageRepository.countByAppIdAndCreatedAtAfter(appKey, since); - long single = messageRepository.countByAppIdAndChatTypeAndCreatedAtAfter( + long total = messageRepository.countByAppKeyAndCreatedAtAfter(appKey, since); + long single = messageRepository.countByAppKeyAndChatTypeAndCreatedAtAfter( appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.SINGLE, since); - long group = messageRepository.countByAppIdAndChatTypeAndCreatedAtAfter( + long group = messageRepository.countByAppKeyAndChatTypeAndCreatedAtAfter( appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.GROUP, since); return ApiResponse.success(Map.of( "totalMessages", total, @@ -46,9 +46,9 @@ public class StatisticsController { @RequestParam(required = false) Integer days) { int d = days == null ? 7 : days; LocalDateTime since = LocalDateTime.now().minusDays(d); - long success = webhookDeliveryRepository.countSuccessfulByAppIdSince(appKey, since); - long failed = webhookDeliveryRepository.countFailedByAppIdSince(appKey, since); - List raw = webhookDeliveryRepository.statsByAppIdSince(appKey, since); + long success = webhookDeliveryRepository.countSuccessfulByAppKeySince(appKey, since); + long failed = webhookDeliveryRepository.countFailedByAppKeySince(appKey, since); + List raw = webhookDeliveryRepository.statsByAppKeySince(appKey, since); List> eventStats = new ArrayList<>(); for (Object[] row : raw) { eventStats.add(Map.of( diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImAccountEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImAccountEntity.java index 2bab2d8..2d5d9c7 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImAccountEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImAccountEntity.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; @Entity @Table(name = "im_account", - uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId"})) + uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId"})) public class ImAccountEntity { public enum Gender { UNKNOWN, MALE, FEMALE } @@ -23,7 +23,7 @@ public class ImAccountEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String userId; @@ -49,8 +49,8 @@ public class ImAccountEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImBlacklistEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImBlacklistEntity.java index 928ffc8..51bc2f5 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImBlacklistEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImBlacklistEntity.java @@ -11,13 +11,13 @@ import java.time.LocalDateTime; @Entity @Table( name = "im_blacklist", - uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "blockedUserId"}), - indexes = @Index(name = "idx_blacklist_app_user", columnList = "appId,userId") + uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "blockedUserId"}), + indexes = @Index(name = "idx_blacklist_app_user", columnList = "appKey,userId") ) public class ImBlacklistEntity extends BaseIdEntity { @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String userId; @@ -28,8 +28,8 @@ public class ImBlacklistEntity extends BaseIdEntity { @Column(nullable = false) private LocalDateTime createdAt; - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImConversationStateEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImConversationStateEntity.java index 7ab3358..9225164 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImConversationStateEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImConversationStateEntity.java @@ -10,16 +10,16 @@ import java.time.LocalDateTime; @Entity @Table( name = "im_conversation_state", - uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "targetId", "chatType"}), + uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "targetId", "chatType"}), indexes = { - @Index(name = "idx_conv_state_app_user", columnList = "appId,userId"), - @Index(name = "idx_conv_state_app_target", columnList = "appId,targetId") + @Index(name = "idx_conv_state_app_user", columnList = "appKey,userId"), + @Index(name = "idx_conv_state_app_target", columnList = "appKey,targetId") } ) public class ImConversationStateEntity extends BaseIdEntity { @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String userId; @@ -53,8 +53,8 @@ public class ImConversationStateEntity extends BaseIdEntity { @Column(nullable = false) private LocalDateTime updatedAt; - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImFriendEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImFriendEntity.java index 030a04f..e048f35 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImFriendEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImFriendEntity.java @@ -13,7 +13,7 @@ import java.time.Instant; @Entity @Table(name = "im_friends", - uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "friendId"})) + uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "friendId"})) public class ImFriendEntity { @Id @@ -21,7 +21,7 @@ public class ImFriendEntity { private Long id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String userId; @@ -39,8 +39,8 @@ public class ImFriendEntity { public Long getId() { return id; } public void setId(Long id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImFriendRequestEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImFriendRequestEntity.java index 574b023..ff41db0 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImFriendRequestEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImFriendRequestEntity.java @@ -13,15 +13,15 @@ import java.time.LocalDateTime; @Entity @Table( name = "im_friend_request", - uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "fromUserId", "toUserId"}), - indexes = @Index(name = "idx_friend_request_app_to", columnList = "appId,toUserId") + uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "fromUserId", "toUserId"}), + indexes = @Index(name = "idx_friend_request_app_to", columnList = "appKey,toUserId") ) public class ImFriendRequestEntity extends BaseIdEntity { public enum Status { PENDING, ACCEPTED, REJECTED } @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String fromUserId; @@ -40,8 +40,8 @@ public class ImFriendRequestEntity extends BaseIdEntity { private LocalDateTime reviewedAt; - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getFromUserId() { return fromUserId; } public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImGlobalMuteEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImGlobalMuteEntity.java index 6d050cd..feae6b4 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImGlobalMuteEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImGlobalMuteEntity.java @@ -17,7 +17,7 @@ public class ImGlobalMuteEntity { private String id; @Column(nullable = false, length = 64, unique = true) - private String appId; + private String appKey; @Column(nullable = false) private boolean enabled; @@ -33,8 +33,8 @@ public class ImGlobalMuteEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImGroupEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImGroupEntity.java index 487a0f1..7840ba1 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImGroupEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImGroupEntity.java @@ -16,7 +16,7 @@ public class ImGroupEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String name; @@ -49,8 +49,8 @@ public class ImGroupEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getName() { return name; } public void setName(String name) { this.name = name; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImGroupJoinRequestEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImGroupJoinRequestEntity.java index 06a0735..6eecc35 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImGroupJoinRequestEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImGroupJoinRequestEntity.java @@ -12,14 +12,14 @@ import java.time.LocalDateTime; @Entity @Table( name = "im_group_join_request", - indexes = @Index(name = "idx_group_join_request_app_group", columnList = "appId,groupId") + indexes = @Index(name = "idx_group_join_request_app_group", columnList = "appKey,groupId") ) public class ImGroupJoinRequestEntity extends BaseIdEntity { public enum Status { PENDING, ACCEPTED, REJECTED } @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 64) private String groupId; @@ -38,8 +38,8 @@ public class ImGroupJoinRequestEntity extends BaseIdEntity { private LocalDateTime reviewedAt; - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImMessageEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImMessageEntity.java index cfc739c..f72b5da 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImMessageEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImMessageEntity.java @@ -16,8 +16,8 @@ import java.time.LocalDateTime; @Entity @Table(name = "im_message", indexes = { - @Index(name = "idx_app_from", columnList = "appId,fromUserId"), - @Index(name = "idx_app_to", columnList = "appId,toId") + @Index(name = "idx_app_from", columnList = "appKey,fromUserId"), + @Index(name = "idx_app_to", columnList = "appKey,toId") }) public class ImMessageEntity { @@ -32,7 +32,7 @@ public class ImMessageEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String fromUserId; @@ -74,8 +74,8 @@ public class ImMessageEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getFromUserId() { return fromUserId; } public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImOfflineMessageEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImOfflineMessageEntity.java index 0fef5b9..8195b97 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImOfflineMessageEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImOfflineMessageEntity.java @@ -14,7 +14,7 @@ public class ImOfflineMessageEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 64) private String userId; @@ -31,8 +31,8 @@ public class ImOfflineMessageEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/ImOperationLogEntity.java b/im-service/src/main/java/com/xuqm/im/entity/ImOperationLogEntity.java index 62dc7bd..fbd2cb4 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/ImOperationLogEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/ImOperationLogEntity.java @@ -11,13 +11,13 @@ import java.time.LocalDateTime; @Entity @Table(name = "im_operation_log", indexes = { - @Index(name = "idx_op_log_app_time", columnList = "appId,createdAt"), - @Index(name = "idx_op_log_app_operator", columnList = "appId,operatorId") + @Index(name = "idx_op_log_app_time", columnList = "appKey,createdAt"), + @Index(name = "idx_op_log_app_operator", columnList = "appKey,operatorId") }) public class ImOperationLogEntity extends BaseIdEntity { @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String operatorId; @@ -38,8 +38,8 @@ public class ImOperationLogEntity extends BaseIdEntity { @JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class) private LocalDateTime createdAt; - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getOperatorId() { return operatorId; } public void setOperatorId(String operatorId) { this.operatorId = operatorId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/KeywordFilterEntity.java b/im-service/src/main/java/com/xuqm/im/entity/KeywordFilterEntity.java index f24ad82..1f4ac9b 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/KeywordFilterEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/KeywordFilterEntity.java @@ -18,7 +18,7 @@ public class KeywordFilterEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 512) private String pattern; @@ -39,8 +39,8 @@ public class KeywordFilterEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/WebhookAlertEntity.java b/im-service/src/main/java/com/xuqm/im/entity/WebhookAlertEntity.java index 47e70b3..a1b1f7d 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/WebhookAlertEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/WebhookAlertEntity.java @@ -16,7 +16,7 @@ public class WebhookAlertEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 64) private String webhookId; @@ -43,8 +43,8 @@ public class WebhookAlertEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getWebhookId() { return webhookId; } public void setWebhookId(String webhookId) { this.webhookId = webhookId; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/WebhookConfigEntity.java b/im-service/src/main/java/com/xuqm/im/entity/WebhookConfigEntity.java index 6670917..971456d 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/WebhookConfigEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/WebhookConfigEntity.java @@ -16,7 +16,7 @@ public class WebhookConfigEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 512) private String url; @@ -40,8 +40,8 @@ public class WebhookConfigEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } diff --git a/im-service/src/main/java/com/xuqm/im/entity/WebhookDeliveryEntity.java b/im-service/src/main/java/com/xuqm/im/entity/WebhookDeliveryEntity.java index e5737ee..68b6907 100644 --- a/im-service/src/main/java/com/xuqm/im/entity/WebhookDeliveryEntity.java +++ b/im-service/src/main/java/com/xuqm/im/entity/WebhookDeliveryEntity.java @@ -14,7 +14,7 @@ public class WebhookDeliveryEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 64) private String callbackId; @@ -46,8 +46,8 @@ public class WebhookDeliveryEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getCallbackId() { return callbackId; } public void setCallbackId(String callbackId) { this.callbackId = callbackId; } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImAccountRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImAccountRepository.java index ca71c14..caebf44 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImAccountRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImAccountRepository.java @@ -10,12 +10,12 @@ import java.util.List; import java.util.Optional; public interface ImAccountRepository extends JpaRepository { - Optional findByAppIdAndUserId(String appId, String userId); - boolean existsByAppIdAndUserId(String appId, String userId); - Page findByAppId(String appId, Pageable pageable); - long countByAppId(String appId); + Optional findByAppKeyAndUserId(String appKey, String userId); + boolean existsByAppKeyAndUserId(String appKey, String userId); + Page findByAppKey(String appKey, Pageable pageable); + long countByAppKey(String appKey); - @Query("SELECT a FROM ImAccountEntity a WHERE a.appId = :appId AND a.status = 'ACTIVE' AND " + + @Query("SELECT a FROM ImAccountEntity a WHERE a.appKey = :appKey AND a.status = 'ACTIVE' AND " + "(LOWER(a.userId) LIKE LOWER(CONCAT('%',:kw,'%')) OR LOWER(a.nickname) LIKE LOWER(CONCAT('%',:kw,'%')))") - List searchByKeyword(@Param("appId") String appId, @Param("kw") String keyword, Pageable pageable); + List searchByKeyword(@Param("appKey") String appKey, @Param("kw") String keyword, Pageable pageable); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImBlacklistRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImBlacklistRepository.java index 6b6d9b7..e5f29a9 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImBlacklistRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImBlacklistRepository.java @@ -7,13 +7,13 @@ import java.util.List; import java.util.Optional; public interface ImBlacklistRepository extends JpaRepository { - List findByAppId(String appId); - List findByAppIdAndUserId(String appId, String userId); + List findByAppKey(String appKey); + List findByAppKeyAndUserId(String appKey, String userId); - Optional findByAppIdAndUserIdAndBlockedUserId( - String appId, String userId, String blockedUserId); + Optional findByAppKeyAndUserIdAndBlockedUserId( + String appKey, String userId, String blockedUserId); - boolean existsByAppIdAndUserIdAndBlockedUserId(String appId, String userId, String blockedUserId); + boolean existsByAppKeyAndUserIdAndBlockedUserId(String appKey, String userId, String blockedUserId); - void deleteByAppIdAndUserIdAndBlockedUserId(String appId, String userId, String blockedUserId); + void deleteByAppKeyAndUserIdAndBlockedUserId(String appKey, String userId, String blockedUserId); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImConversationStateRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImConversationStateRepository.java index 2aa628c..aee2a17 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImConversationStateRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImConversationStateRepository.java @@ -7,16 +7,16 @@ import java.util.List; import java.util.Optional; public interface ImConversationStateRepository extends JpaRepository { - Optional findByAppIdAndUserIdAndTargetIdAndChatType( - String appId, String userId, String targetId, String chatType); + Optional findByAppKeyAndUserIdAndTargetIdAndChatType( + String appKey, String userId, String targetId, String chatType); - List findByAppIdAndUserId(String appId, String userId); + List findByAppKeyAndUserId(String appKey, String userId); - List findByAppIdAndUserIdAndHiddenFalse(String appId, String userId); + List findByAppKeyAndUserIdAndHiddenFalse(String appKey, String userId); - List findByAppIdAndUserIdAndConversationGroup( - String appId, String userId, String conversationGroup); + List findByAppKeyAndUserIdAndConversationGroup( + String appKey, String userId, String conversationGroup); - void deleteByAppIdAndUserIdAndTargetIdAndChatType( - String appId, String userId, String targetId, String chatType); + void deleteByAppKeyAndUserIdAndTargetIdAndChatType( + String appKey, String userId, String targetId, String chatType); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImFriendRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImFriendRepository.java index 3d17769..d6e6a30 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImFriendRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImFriendRepository.java @@ -9,20 +9,20 @@ import java.util.Optional; public interface ImFriendRepository extends JpaRepository { - List findByAppIdAndUserId(String appId, String userId); + List findByAppKeyAndUserId(String appKey, String userId); - List findByAppIdAndUserIdAndFriendGroup(String appId, String userId, String friendGroup); + List findByAppKeyAndUserIdAndFriendGroup(String appKey, String userId, String friendGroup); - Optional findByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId); + Optional findByAppKeyAndUserIdAndFriendId(String appKey, String userId, String friendId); - boolean existsByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId); + boolean existsByAppKeyAndUserIdAndFriendId(String appKey, String userId, String friendId); @Transactional - void deleteByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId); + void deleteByAppKeyAndUserIdAndFriendId(String appKey, String userId, String friendId); @Transactional - void deleteByAppIdAndUserId(String appId, String userId); + void deleteByAppKeyAndUserId(String appKey, String userId); @Transactional - void deleteByAppIdAndFriendId(String appId, String friendId); + void deleteByAppKeyAndFriendId(String appKey, String friendId); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImFriendRequestRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImFriendRequestRepository.java index d4f75f4..b35c5e6 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImFriendRequestRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImFriendRequestRepository.java @@ -7,10 +7,10 @@ import java.util.List; import java.util.Optional; public interface ImFriendRequestRepository extends JpaRepository { - List findByAppId(String appId); - Optional findByAppIdAndFromUserIdAndToUserId( - String appId, String fromUserId, String toUserId); + List findByAppKey(String appKey); + Optional findByAppKeyAndFromUserIdAndToUserId( + String appKey, String fromUserId, String toUserId); - List findByAppIdAndToUserId(String appId, String toUserId); - List findByAppIdAndFromUserId(String appId, String fromUserId); + List findByAppKeyAndToUserId(String appKey, String toUserId); + List findByAppKeyAndFromUserId(String appKey, String fromUserId); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImGlobalMuteRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImGlobalMuteRepository.java index b84b575..2a70f6a 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImGlobalMuteRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImGlobalMuteRepository.java @@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface ImGlobalMuteRepository extends JpaRepository { - Optional findByAppId(String appId); + Optional findByAppKey(String appKey); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImGroupJoinRequestRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImGroupJoinRequestRepository.java index d75d324..1bb32ff 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImGroupJoinRequestRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImGroupJoinRequestRepository.java @@ -7,10 +7,10 @@ import java.util.List; import java.util.Optional; public interface ImGroupJoinRequestRepository extends JpaRepository { - List findByAppId(String appId); - Optional findByAppIdAndGroupIdAndRequesterId( - String appId, String groupId, String requesterId); + List findByAppKey(String appKey); + Optional findByAppKeyAndGroupIdAndRequesterId( + String appKey, String groupId, String requesterId); - List findByAppIdAndGroupId(String appId, String groupId); - List findByAppIdAndRequesterId(String appId, String requesterId); + List findByAppKeyAndGroupId(String appKey, String groupId); + List findByAppKeyAndRequesterId(String appKey, String requesterId); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImGroupRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImGroupRepository.java index 0fbfe4e..c9e8717 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImGroupRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImGroupRepository.java @@ -8,22 +8,22 @@ import org.springframework.data.repository.query.Param; import java.util.List; public interface ImGroupRepository extends JpaRepository { - List findByAppId(String appId); - long countByAppId(String appId); + List findByAppKey(String appKey); + long countByAppKey(String appKey); @Query(value = """ SELECT * FROM im_group - WHERE app_id = :appId + WHERE app_key = :appKey AND JSON_CONTAINS(member_ids, JSON_QUOTE(:userId)) ORDER BY created_at DESC """, nativeQuery = true) List findUserGroups( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("userId") String userId); @Query(""" select g from ImGroupEntity g - where g.appId = :appId + where g.appKey = :appKey and (:keyword is null or :keyword = '' or lower(g.id) like lower(concat('%', :keyword, '%')) or lower(g.name) like lower(concat('%', :keyword, '%')) or @@ -32,7 +32,7 @@ public interface ImGroupRepository extends JpaRepository order by g.createdAt desc """) List searchByKeyword( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("keyword") String keyword, Pageable pageable); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImMessageRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImMessageRepository.java index e8e7512..599ae02 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImMessageRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImMessageRepository.java @@ -25,16 +25,16 @@ public interface ImMessageRepository extends JpaRepository findConversations( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("userId") String userId, @Param("size") int size); - Page findByAppIdAndToIdOrderByCreatedAtDesc( - String appId, String toId, Pageable pageable); - Page findByAppIdAndFromUserIdAndToIdOrderByCreatedAtDesc( - String appId, String fromUserId, String toId, Pageable pageable); + Page findByAppKeyAndToIdOrderByCreatedAtDesc( + String appKey, String toId, Pageable pageable); + Page findByAppKeyAndFromUserIdAndToIdOrderByCreatedAtDesc( + String appKey, String fromUserId, String toId, Pageable pageable); @Query(""" select m from ImMessageEntity m - where m.appId = :appId + where m.appKey = :appKey and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE and ((m.fromUserId = :userId and m.toId = :peerId) or (m.fromUserId = :peerId and m.toId = :userId)) order by m.createdAt desc """) Page findSingleConversation( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("userId") String userId, @Param("peerId") String peerId, Pageable pageable); @Query(""" select m from ImMessageEntity m - where m.appId = :appId + where m.appKey = :appKey and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE and ((m.fromUserId = :userId and m.toId = :peerId) or (m.fromUserId = :peerId and m.toId = :userId)) @@ -81,7 +81,7 @@ public interface ImMessageRepository extends JpaRepository findSingleConversationFiltered( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("userId") String userId, @Param("peerId") String peerId, @Param("msgType") ImMessageEntity.MsgType msgType, @@ -90,33 +90,33 @@ public interface ImMessageRepository extends JpaRepository findByAppIdAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc( - String appId, + List findByAppKeyAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc( + String appKey, String fromUserId, String toId, LocalDateTime createdAt); - List findByAppIdAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc( - String appId, + List findByAppKeyAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc( + String appKey, String toId, ImMessageEntity.ChatType chatType, LocalDateTime createdAt); @Query(""" select m from ImMessageEntity m - where m.appId = :appId + where m.appKey = :appKey and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP and m.toId = :groupId order by m.createdAt desc """) Page findGroupHistory( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("groupId") String groupId, Pageable pageable); @Query(""" select m from ImMessageEntity m - where m.appId = :appId + where m.appKey = :appKey and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP and m.toId = :groupId and (:msgType is null or m.msgType = :msgType) @@ -129,7 +129,7 @@ public interface ImMessageRepository extends JpaRepository findGroupHistoryFiltered( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("groupId") String groupId, @Param("msgType") ImMessageEntity.MsgType msgType, @Param("keyword") String keyword, @@ -139,7 +139,7 @@ public interface ImMessageRepository extends JpaRepository searchByKeyword( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("chatType") ImMessageEntity.ChatType chatType, @Param("msgType") ImMessageEntity.MsgType msgType, @Param("keyword") String keyword, @@ -162,7 +162,7 @@ public interface ImMessageRepository extends JpaRepository searchByKeywordForUser( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("userId") String userId, @Param("chatType") ImMessageEntity.ChatType chatType, @Param("msgType") ImMessageEntity.MsgType msgType, @@ -197,7 +197,7 @@ public interface ImMessageRepository extends JpaRepository :since) """) long countUnreadSingleConversation( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("userId") String userId, @Param("peerId") String peerId, @Param("since") LocalDateTime since); @Query(""" select count(m) from ImMessageEntity m - where m.appId = :appId + where m.appKey = :appKey and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP and m.toId = :groupId and m.fromUserId <> :userId and (:since is null or m.createdAt > :since) """) long countUnreadGroupConversation( - @Param("appId") String appId, + @Param("appKey") String appKey, @Param("userId") String userId, @Param("groupId") String groupId, @Param("since") LocalDateTime since); - long countByAppId(String appId); + long countByAppKey(String appKey); - @Query("select count(m) from ImMessageEntity m where m.appId = :appId and m.createdAt >= :since") - long countByAppIdAndCreatedAtAfter(@Param("appId") String appId, @Param("since") LocalDateTime since); + @Query("select count(m) from ImMessageEntity m where m.appKey = :appKey and m.createdAt >= :since") + long countByAppKeyAndCreatedAtAfter(@Param("appKey") String appKey, @Param("since") LocalDateTime since); - @Query("select count(m) from ImMessageEntity m where m.appId = :appId and m.chatType = :chatType and m.createdAt >= :since") - long countByAppIdAndChatTypeAndCreatedAtAfter( - @Param("appId") String appId, + @Query("select count(m) from ImMessageEntity m where m.appKey = :appKey and m.chatType = :chatType and m.createdAt >= :since") + long countByAppKeyAndChatTypeAndCreatedAtAfter( + @Param("appKey") String appKey, @Param("chatType") ImMessageEntity.ChatType chatType, @Param("since") LocalDateTime since); - default long countTodayByAppId(String appId) { - return countByAppIdAndCreatedAtAfter(appId, LocalDateTime.now().toLocalDate().atStartOfDay()); + default long countTodayByAppKey(String appKey) { + return countByAppKeyAndCreatedAtAfter(appKey, LocalDateTime.now().toLocalDate().atStartOfDay()); } @Query(""" select m from ImMessageEntity m - where m.appId = :appId + where m.appKey = :appKey and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE and m.toId = :userId and m.status <> com.xuqm.im.entity.ImMessageEntity$MsgStatus.READ """) - List findUnreadByAppIdAndToId( - @Param("appId") String appId, + List findUnreadByAppKeyAndToId( + @Param("appKey") String appKey, @Param("userId") String userId); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImOfflineMessageRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImOfflineMessageRepository.java index 079dc6a..d9bf844 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImOfflineMessageRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImOfflineMessageRepository.java @@ -11,14 +11,14 @@ import java.util.List; @Repository public interface ImOfflineMessageRepository extends JpaRepository { - List findByAppIdAndUserIdAndDeliveredFalse(String appId, String userId); + List findByAppKeyAndUserIdAndDeliveredFalse(String appKey, String userId); @Modifying @Query("UPDATE ImOfflineMessageEntity o SET o.delivered = true WHERE o.id IN ?1") void markDeliveredByIds(List ids); - @Query("SELECT COUNT(o) FROM ImOfflineMessageEntity o WHERE o.appId = ?1 AND o.userId = ?2 AND o.delivered = false") - long countUndeliveredByAppIdAndUserId(String appId, String userId); + @Query("SELECT COUNT(o) FROM ImOfflineMessageEntity o WHERE o.appKey = ?1 AND o.userId = ?2 AND o.delivered = false") + long countUndeliveredByAppKeyAndUserId(String appKey, String userId); - void deleteByAppIdAndUserIdAndDeliveredTrue(String appId, String userId); + void deleteByAppKeyAndUserIdAndDeliveredTrue(String appKey, String userId); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/ImOperationLogRepository.java b/im-service/src/main/java/com/xuqm/im/repository/ImOperationLogRepository.java index 27a60d8..fe2fd64 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/ImOperationLogRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/ImOperationLogRepository.java @@ -6,5 +6,5 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface ImOperationLogRepository extends JpaRepository { - Page findByAppIdOrderByCreatedAtDesc(String appId, Pageable pageable); + Page findByAppKeyOrderByCreatedAtDesc(String appKey, Pageable pageable); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/KeywordFilterRepository.java b/im-service/src/main/java/com/xuqm/im/repository/KeywordFilterRepository.java index 578614f..8708f79 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/KeywordFilterRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/KeywordFilterRepository.java @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface KeywordFilterRepository extends JpaRepository { - List findByAppIdAndEnabledTrue(String appId); - List findByAppId(String appId); - java.util.Optional findByIdAndAppId(String id, String appId); + List findByAppKeyAndEnabledTrue(String appKey); + List findByAppKey(String appKey); + java.util.Optional findByIdAndAppId(String id, String appKey); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/WebhookAlertRepository.java b/im-service/src/main/java/com/xuqm/im/repository/WebhookAlertRepository.java index dea4194..96bdb94 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/WebhookAlertRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/WebhookAlertRepository.java @@ -8,10 +8,10 @@ import org.springframework.data.jpa.repository.Query; public interface WebhookAlertRepository extends JpaRepository { - Page findByAppId(String appId, Pageable pageable); + Page findByAppKey(String appKey, Pageable pageable); - Page findByAppIdAndAcknowledged(String appId, boolean acknowledged, Pageable pageable); + Page findByAppKeyAndAcknowledged(String appKey, boolean acknowledged, Pageable pageable); - @Query("SELECT COUNT(a) FROM WebhookAlertEntity a WHERE a.appId = ?1 AND a.acknowledged = false") - long countUnacknowledgedByAppId(String appId); + @Query("SELECT COUNT(a) FROM WebhookAlertEntity a WHERE a.appKey = ?1 AND a.acknowledged = false") + long countUnacknowledgedByAppKey(String appKey); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/WebhookConfigRepository.java b/im-service/src/main/java/com/xuqm/im/repository/WebhookConfigRepository.java index 3a06cd4..1d2c8d6 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/WebhookConfigRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/WebhookConfigRepository.java @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface WebhookConfigRepository extends JpaRepository { - List findByAppIdAndEnabledTrue(String appId); - List findByAppId(String appId); - java.util.Optional findByIdAndAppId(String id, String appId); + List findByAppKeyAndEnabledTrue(String appKey); + List findByAppKey(String appKey); + java.util.Optional findByIdAndAppId(String id, String appKey); } diff --git a/im-service/src/main/java/com/xuqm/im/repository/WebhookDeliveryRepository.java b/im-service/src/main/java/com/xuqm/im/repository/WebhookDeliveryRepository.java index a8f4a07..de0a2fc 100644 --- a/im-service/src/main/java/com/xuqm/im/repository/WebhookDeliveryRepository.java +++ b/im-service/src/main/java/com/xuqm/im/repository/WebhookDeliveryRepository.java @@ -10,21 +10,21 @@ import java.util.List; public interface WebhookDeliveryRepository extends JpaRepository { - Page findByAppIdAndCallbackEvent(String appId, String callbackEvent, Pageable pageable); + Page findByAppKeyAndCallbackEvent(String appKey, String callbackEvent, Pageable pageable); - Page findByAppId(String appId, Pageable pageable); + Page findByAppKey(String appKey, Pageable pageable); - Page findByAppIdAndSuccess(String appId, boolean success, Pageable pageable); + Page findByAppKeyAndSuccess(String appKey, boolean success, Pageable pageable); List findByCallbackId(String callbackId); - @Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appId = ?1 AND d.success = true AND d.createdAt >= ?2") - long countSuccessfulByAppIdSince(String appId, LocalDateTime since); + @Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.success = true AND d.createdAt >= ?2") + long countSuccessfulByAppKeySince(String appKey, LocalDateTime since); - @Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appId = ?1 AND d.success = false AND d.createdAt >= ?2") - long countFailedByAppIdSince(String appId, LocalDateTime since); + @Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.success = false AND d.createdAt >= ?2") + long countFailedByAppKeySince(String appKey, LocalDateTime since); @Query("SELECT d.callbackEvent, COUNT(d), SUM(CASE WHEN d.success = true THEN 1 ELSE 0 END) " + - "FROM WebhookDeliveryEntity d WHERE d.appId = ?1 AND d.createdAt >= ?2 GROUP BY d.callbackEvent") - List statsByAppIdSince(String appId, LocalDateTime since); + "FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.createdAt >= ?2 GROUP BY d.callbackEvent") + List statsByAppKeySince(String appKey, LocalDateTime since); } diff --git a/im-service/src/main/java/com/xuqm/im/service/BlacklistService.java b/im-service/src/main/java/com/xuqm/im/service/BlacklistService.java index c4db3f4..2c9a0b1 100644 --- a/im-service/src/main/java/com/xuqm/im/service/BlacklistService.java +++ b/im-service/src/main/java/com/xuqm/im/service/BlacklistService.java @@ -23,11 +23,11 @@ public class BlacklistService { @Transactional public ImBlacklistEntity add(String appKey, String userId, String blockedUserId) { - return repository.findByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId) + return repository.findByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId) .orElseGet(() -> { ImBlacklistEntity entity = new ImBlacklistEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setUserId(userId); entity.setBlockedUserId(blockedUserId); entity.setCreatedAt(LocalDateTime.now()); @@ -39,24 +39,24 @@ public class BlacklistService { @Transactional public void remove(String appKey, String userId, String blockedUserId) { - ImBlacklistEntity entity = repository.findByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId) + ImBlacklistEntity entity = repository.findByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId) .orElse(null); - repository.deleteByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId); + repository.deleteByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId); if (entity != null) { dispatchWebhook(entity, "blacklist.removed"); } } public List list(String appKey, String userId) { - return repository.findByAppIdAndUserId(appKey, userId); + return repository.findByAppKeyAndUserId(appKey, userId); } public List listByApp(String appKey) { - return repository.findByAppId(appKey); + return repository.findByAppKey(appKey); } public boolean isBlocked(String appKey, String userId, String blockedUserId) { - return repository.existsByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId); + return repository.existsByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId); } public boolean isEitherBlocked(String appKey, String userId, String targetUserId) { @@ -65,11 +65,11 @@ public class BlacklistService { private void dispatchWebhook(ImBlacklistEntity entity, String callbackEvent) { webhookDispatchService.dispatch( - entity.getAppId(), + entity.getAppKey(), "blacklist", callbackEvent, new BlacklistCallbackPayload( - entity.getAppId(), + entity.getAppKey(), entity.getId(), entity.getUserId(), entity.getBlockedUserId(), diff --git a/im-service/src/main/java/com/xuqm/im/service/ConversationStateService.java b/im-service/src/main/java/com/xuqm/im/service/ConversationStateService.java index 5763562..b309b6f 100644 --- a/im-service/src/main/java/com/xuqm/im/service/ConversationStateService.java +++ b/im-service/src/main/java/com/xuqm/im/service/ConversationStateService.java @@ -87,7 +87,7 @@ public class ConversationStateService { @Transactional public void deleteConversationState(String appKey, String userId, String targetId, String chatType) { - repository.deleteByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType); + repository.deleteByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType); } @Transactional @@ -107,7 +107,7 @@ public class ConversationStateService { public void clearHiddenForUsers(String appKey, String targetId, String chatType, Collection userIds) { for (String userId : userIds) { ImConversationStateEntity state = repository - .findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType) + .findByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType) .orElse(null); if (state != null && state.isHidden()) { state.setHidden(false); @@ -118,11 +118,11 @@ public class ConversationStateService { } public ImConversationStateEntity getOrCreate(String appKey, String userId, String targetId, String chatType) { - return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType) + return repository.findByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType) .orElseGet(() -> { ImConversationStateEntity entity = new ImConversationStateEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setUserId(userId); entity.setTargetId(targetId); entity.setChatType(chatType); @@ -139,16 +139,16 @@ public class ConversationStateService { } public ImConversationStateEntity find(String appKey, String userId, String targetId, String chatType) { - return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType) + return repository.findByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType) .orElse(null); } public List listVisible(String appKey, String userId) { - return repository.findByAppIdAndUserIdAndHiddenFalse(appKey, userId); + return repository.findByAppKeyAndUserIdAndHiddenFalse(appKey, userId); } public List listConversationGroups(String appKey, String userId) { - return repository.findByAppIdAndUserId(appKey, userId).stream() + return repository.findByAppKeyAndUserId(appKey, userId).stream() .map(ImConversationStateEntity::getConversationGroup) .filter(group -> group != null && !group.isBlank()) .distinct() @@ -157,7 +157,7 @@ public class ConversationStateService { } public List listByConversationGroup(String appKey, String userId, String conversationGroup) { - return repository.findByAppIdAndUserIdAndConversationGroup(appKey, userId, normalizeGroup(conversationGroup)); + return repository.findByAppKeyAndUserIdAndConversationGroup(appKey, userId, normalizeGroup(conversationGroup)); } private void touch(ImConversationStateEntity entity) { diff --git a/im-service/src/main/java/com/xuqm/im/service/FriendRequestService.java b/im-service/src/main/java/com/xuqm/im/service/FriendRequestService.java index 63c562e..e98dba9 100644 --- a/im-service/src/main/java/com/xuqm/im/service/FriendRequestService.java +++ b/im-service/src/main/java/com/xuqm/im/service/FriendRequestService.java @@ -51,12 +51,12 @@ public class FriendRequestService { throw new BusinessException(403, "当前应用未开放好友申请"); } final boolean[] created = {false}; - ImFriendRequestEntity saved = requestRepository.findByAppIdAndFromUserIdAndToUserId(appKey, fromUserId, toUserId) + ImFriendRequestEntity saved = requestRepository.findByAppKeyAndFromUserIdAndToUserId(appKey, fromUserId, toUserId) .orElseGet(() -> { created[0] = true; ImFriendRequestEntity entity = new ImFriendRequestEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setFromUserId(fromUserId); entity.setToUserId(toUserId); entity.setRemark(remark); @@ -94,10 +94,10 @@ public class FriendRequestService { request.setReviewedAt(LocalDateTime.now()); requestRepository.save(request); friendRepository - .findByAppIdAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId()) + .findByAppKeyAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId()) .orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId())); friendRepository - .findByAppIdAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId()) + .findByAppKeyAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId()) .orElseGet(() -> friendEntity(appKey, request.getToUserId(), request.getFromUserId())); dispatchWebhook(request, "friend.request.accepted"); publishNotification( @@ -148,17 +148,17 @@ public class FriendRequestService { } public List incoming(String appKey, String userId) { - return requestRepository.findByAppIdAndToUserId(appKey, userId).stream() + return requestRepository.findByAppKeyAndToUserId(appKey, userId).stream() .filter(request -> ImFriendRequestEntity.Status.PENDING.name().equals(request.getStatus())) .toList(); } public List outgoing(String appKey, String userId) { - return requestRepository.findByAppIdAndFromUserId(appKey, userId); + return requestRepository.findByAppKeyAndFromUserId(appKey, userId); } public List listByApp(String appKey) { - return requestRepository.findByAppId(appKey); + return requestRepository.findByAppKey(appKey); } @Transactional @@ -176,7 +176,7 @@ public class FriendRequestService { private ImFriendRequestEntity getRequest(String appKey, String requestId, String operatorId) { ImFriendRequestEntity request = requestRepository.findById(requestId) .orElseThrow(() -> new BusinessException(404, "好友申请不存在")); - if (!request.getAppId().equals(appKey) || !request.getToUserId().equals(operatorId)) { + if (!request.getAppKey().equals(appKey) || !request.getToUserId().equals(operatorId)) { throw new BusinessException(403, "无权操作"); } return request; @@ -185,7 +185,7 @@ public class FriendRequestService { private ImFriendRequestEntity getRequest(String appKey, String requestId) { ImFriendRequestEntity request = requestRepository.findById(requestId) .orElseThrow(() -> new BusinessException(404, "好友申请不存在")); - if (!request.getAppId().equals(appKey)) { + if (!request.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } return request; @@ -201,11 +201,11 @@ public class FriendRequestService { request.setReviewedAt(LocalDateTime.now()); ImFriendRequestEntity saved = requestRepository.save(request); friendRepository - .findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getFromUserId(), request.getToUserId()) - .orElseGet(() -> friendEntity(request.getAppId(), request.getFromUserId(), request.getToUserId())); + .findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getFromUserId(), request.getToUserId()) + .orElseGet(() -> friendEntity(request.getAppKey(), request.getFromUserId(), request.getToUserId())); friendRepository - .findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getToUserId(), request.getFromUserId()) - .orElseGet(() -> friendEntity(request.getAppId(), request.getToUserId(), request.getFromUserId())); + .findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getToUserId(), request.getFromUserId()) + .orElseGet(() -> friendEntity(request.getAppKey(), request.getToUserId(), request.getFromUserId())); dispatchWebhook(saved, "friend.request.accepted"); publishNotification( request, @@ -225,7 +225,7 @@ public class FriendRequestService { private com.xuqm.im.entity.ImFriendEntity friendEntity(String appKey, String userId, String friendId) { com.xuqm.im.entity.ImFriendEntity entity = new com.xuqm.im.entity.ImFriendEntity(); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setUserId(userId); entity.setFriendId(friendId); return friendRepository.save(entity); @@ -245,7 +245,7 @@ public class FriendRequestService { ) { ImMessageEntity message = new ImMessageEntity(); message.setId(UUID.randomUUID().toString()); - message.setAppId(request.getAppId()); + message.setAppKey(request.getAppKey()); message.setFromUserId(fromUserId); message.setToId(toUserId); message.setChatType(ImMessageEntity.ChatType.SINGLE); @@ -302,11 +302,11 @@ public class FriendRequestService { private void dispatchWebhook(ImFriendRequestEntity request, String callbackEvent) { webhookDispatchService.dispatch( - request.getAppId(), + request.getAppKey(), "friend_request", callbackEvent, new FriendRequestCallbackPayload( - request.getAppId(), + request.getAppKey(), request.getId(), request.getFromUserId(), request.getToUserId(), diff --git a/im-service/src/main/java/com/xuqm/im/service/GlobalMuteService.java b/im-service/src/main/java/com/xuqm/im/service/GlobalMuteService.java index 219c365..fa85ea1 100644 --- a/im-service/src/main/java/com/xuqm/im/service/GlobalMuteService.java +++ b/im-service/src/main/java/com/xuqm/im/service/GlobalMuteService.java @@ -17,14 +17,14 @@ public class GlobalMuteService { } public boolean isEnabled(String appKey) { - return repository.findByAppId(appKey).map(ImGlobalMuteEntity::isEnabled).orElse(false); + return repository.findByAppKey(appKey).map(ImGlobalMuteEntity::isEnabled).orElse(false); } public ImGlobalMuteEntity get(String appKey) { - return repository.findByAppId(appKey).orElseGet(() -> { + return repository.findByAppKey(appKey).orElseGet(() -> { ImGlobalMuteEntity entity = new ImGlobalMuteEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setEnabled(false); entity.setCreatedAt(LocalDateTime.now()); entity.setUpdatedAt(LocalDateTime.now()); @@ -33,10 +33,10 @@ public class GlobalMuteService { } public ImGlobalMuteEntity setEnabled(String appKey, boolean enabled) { - ImGlobalMuteEntity entity = repository.findByAppId(appKey).orElseGet(() -> { + ImGlobalMuteEntity entity = repository.findByAppKey(appKey).orElseGet(() -> { ImGlobalMuteEntity created = new ImGlobalMuteEntity(); created.setId(UUID.randomUUID().toString()); - created.setAppId(appKey); + created.setAppKey(appKey); created.setCreatedAt(LocalDateTime.now()); return created; }); diff --git a/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java b/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java index d000491..ff6a948 100644 --- a/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java +++ b/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java @@ -47,11 +47,11 @@ public class ImAccountService { } public LoginResult loginOrRegister(String appKey, String userId) { - ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId) + ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId) .orElseGet(() -> { ImAccountEntity e = new ImAccountEntity(); e.setId(UUID.randomUUID().toString()); - e.setAppId(appKey); + e.setAppKey(appKey); e.setUserId(userId); e.setGender(ImAccountEntity.Gender.UNKNOWN); e.setStatus(ImAccountEntity.Status.ACTIVE); @@ -67,7 +67,7 @@ public class ImAccountService { } public ImAccountEntity getAccount(String appKey, String userId) { - return accountRepository.findByAppIdAndUserId(appKey, userId) + return accountRepository.findByAppKeyAndUserId(appKey, userId) .orElseThrow(() -> new BusinessException(404, "账号不存在")); } @@ -98,11 +98,11 @@ public class ImAccountService { public ImAccountEntity importAccount(String appKey, String userId, String nickname, String avatar, ImAccountEntity.Gender gender, ImAccountEntity.Status status) { - ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId) + ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId) .orElseGet(() -> { ImAccountEntity entity = new ImAccountEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setUserId(userId); entity.setCreatedAt(LocalDateTime.now()); return entity; @@ -122,12 +122,12 @@ public class ImAccountService { } public void deleteAccount(String appKey, String userId) { - accountRepository.findByAppIdAndUserId(appKey, userId) + accountRepository.findByAppKeyAndUserId(appKey, userId) .ifPresent(accountRepository::delete); } public boolean exists(String appKey, String userId) { - return accountRepository.existsByAppIdAndUserId(appKey, userId); + return accountRepository.existsByAppKeyAndUserId(appKey, userId); } public List searchAccounts(String appKey, String keyword, int size) { diff --git a/im-service/src/main/java/com/xuqm/im/service/ImGroupService.java b/im-service/src/main/java/com/xuqm/im/service/ImGroupService.java index 648caa0..b9be33d 100644 --- a/im-service/src/main/java/com/xuqm/im/service/ImGroupService.java +++ b/im-service/src/main/java/com/xuqm/im/service/ImGroupService.java @@ -75,7 +75,7 @@ public class ImGroupService { ImGroupEntity group = new ImGroupEntity(); group.setId(UUID.randomUUID().toString()); - group.setAppId(appKey); + group.setAppKey(appKey); group.setName(name); group.setGroupType(normalizeGroupType(groupType)); group.setCreatorId(creatorId); @@ -135,7 +135,7 @@ public class ImGroupService { if (changed) { group.setMemberIds(toJson(members)); ImGroupEntity saved = groupRepository.save(group); - webhookDispatchService.dispatch(saved.getAppId(), "group", "group.member_added", + webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.member_added", java.util.Map.of("groupId", saved.getId(), "addedUserIds", userIds, "operatorId", operatorId)); return saved; } @@ -153,7 +153,7 @@ public class ImGroupService { members.remove(userId); group.setMemberIds(toJson(members)); ImGroupEntity saved = groupRepository.save(group); - webhookDispatchService.dispatch(saved.getAppId(), "group", "group.member_removed", + webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.member_removed", java.util.Map.of("groupId", saved.getId(), "removedUserId", userId, "operatorId", operatorId)); return saved; } @@ -176,7 +176,7 @@ public class ImGroupService { if (changed) { group.setMemberIds(toJson(members)); ImGroupEntity saved = groupRepository.save(group); - webhookDispatchService.dispatch(saved.getAppId(), "group", "group.member_removed", + webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.member_removed", java.util.Map.of("groupId", saved.getId(), "removedUserIds", userIds, "operatorId", operatorId)); return saved; } @@ -197,7 +197,7 @@ public class ImGroupService { group.setAnnouncement(announcement); } ImGroupEntity saved = groupRepository.save(group); - webhookDispatchService.dispatch(saved.getAppId(), "group", "group.updated", + webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.updated", java.util.Map.of("groupId", saved.getId(), "name", saved.getName(), "operatorId", operatorId)); return saved; } @@ -268,7 +268,7 @@ public class ImGroupService { } public List listByApp(String appKey) { - return groupRepository.findByAppId(appKey); + return groupRepository.findByAppKey(appKey); } public List listUserGroups(String appKey, String userId) { @@ -277,7 +277,7 @@ public class ImGroupService { public List listPublicGroups(String appKey, String keyword) { String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(); - return groupRepository.findByAppId(appKey).stream() + return groupRepository.findByAppKey(appKey).stream() .filter(group -> "PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType()))) .filter(group -> normalizedKeyword.isBlank() || group.getName().toLowerCase().contains(normalizedKeyword) @@ -313,7 +313,7 @@ public class ImGroupService { throw new BusinessException(403, "当前应用未开放群加入申请"); } ImGroupEntity group = get(groupId); - if (!group.getAppId().equals(appKey)) { + if (!group.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } if (!"PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType()))) { @@ -322,11 +322,11 @@ public class ImGroupService { if (memberIds(group).contains(requesterId)) { throw new BusinessException(400, "已经在群内"); } - return joinRequestRepository.findByAppIdAndGroupIdAndRequesterId(appKey, groupId, requesterId) + return joinRequestRepository.findByAppKeyAndGroupIdAndRequesterId(appKey, groupId, requesterId) .orElseGet(() -> { ImGroupJoinRequestEntity entity = new ImGroupJoinRequestEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setGroupId(groupId); entity.setRequesterId(requesterId); entity.setRemark(remark); @@ -350,7 +350,7 @@ public class ImGroupService { public List listJoinRequests(String appKey, String groupId, String operatorId) { ImGroupEntity group = get(groupId); ensureCanManage(group, operatorId); - return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId); + return joinRequestRepository.findByAppKeyAndGroupId(appKey, groupId); } @Transactional @@ -442,7 +442,7 @@ public class ImGroupService { } private void ensureAppMatches(ImGroupEntity group, String appKey) { - if (!group.getAppId().equals(appKey)) { + if (!group.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } } @@ -614,7 +614,7 @@ public class ImGroupService { public List adminListJoinRequests(String appKey, String groupId) { ImGroupEntity group = get(groupId); ensureAppMatches(group, appKey); - return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId); + return joinRequestRepository.findByAppKeyAndGroupId(appKey, groupId); } @Transactional @@ -667,7 +667,7 @@ public class ImGroupService { group.setCreatorId(newOwnerId); group.setAdminIds(toJson(admins)); ImGroupEntity saved = groupRepository.save(group); - webhookDispatchService.dispatch(saved.getAppId(), "group", "group.owner_transferred", + webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.owner_transferred", java.util.Map.of("groupId", saved.getId(), "newOwnerId", newOwnerId)); return saved; } @@ -683,7 +683,7 @@ public class ImGroupService { } group.setExtAttributes(toJson(current)); ImGroupEntity saved = groupRepository.save(group); - webhookDispatchService.dispatch(saved.getAppId(), "group", "group.attributes_updated", + webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.attributes_updated", java.util.Map.of("groupId", saved.getId(), "attributes", current)); return saved; } @@ -760,7 +760,7 @@ public class ImGroupService { if (recipient == null || recipient.isBlank() || recipient.equals(fromUserId)) continue; ImMessageEntity message = new ImMessageEntity(); message.setId(UUID.randomUUID().toString()); - message.setAppId(group.getAppId()); + message.setAppKey(group.getAppKey()); message.setFromUserId(fromUserId); message.setToId(recipient); message.setChatType(ImMessageEntity.ChatType.SINGLE); @@ -775,11 +775,11 @@ public class ImGroupService { private void dispatchJoinRequestWebhook(ImGroupEntity group, ImGroupJoinRequestEntity request, String callbackEvent) { webhookDispatchService.dispatch( - group.getAppId(), + group.getAppKey(), "group_join_request", callbackEvent, new GroupJoinRequestCallbackPayload( - group.getAppId(), + group.getAppKey(), request.getId(), request.getGroupId(), group.getName(), @@ -832,7 +832,7 @@ public class ImGroupService { private ImGroupJoinRequestEntity getJoinRequest(String appKey, String requestId) { ImGroupJoinRequestEntity request = joinRequestRepository.findById(requestId) .orElseThrow(() -> new BusinessException(404, "加群申请不存在")); - if (!request.getAppId().equals(appKey)) { + if (!request.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } return request; @@ -894,7 +894,7 @@ public class ImGroupService { private List resolveMembers(String appKey, List ids) { List members = new ArrayList<>(); for (String userId : ids == null ? List.of() : ids) { - accountRepository.findByAppIdAndUserId(appKey, userId).ifPresent(members::add); + accountRepository.findByAppKeyAndUserId(appKey, userId).ifPresent(members::add); } return members; } diff --git a/im-service/src/main/java/com/xuqm/im/service/KeywordFilterService.java b/im-service/src/main/java/com/xuqm/im/service/KeywordFilterService.java index 7c233cd..489cefa 100644 --- a/im-service/src/main/java/com/xuqm/im/service/KeywordFilterService.java +++ b/im-service/src/main/java/com/xuqm/im/service/KeywordFilterService.java @@ -20,7 +20,7 @@ public class KeywordFilterService { } public String filter(String appKey, String content) { - List filters = repository.findByAppIdAndEnabledTrue(appKey); + List filters = repository.findByAppKeyAndEnabledTrue(appKey); String result = content; for (KeywordFilterEntity f : filters) { try { @@ -42,7 +42,7 @@ public class KeywordFilterService { public KeywordFilterEntity add(String appKey, String pattern, String replacement, String action) { KeywordFilterEntity entity = new KeywordFilterEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setPattern(pattern); entity.setReplacement(replacement); entity.setAction(action); @@ -52,7 +52,7 @@ public class KeywordFilterService { } public List list(String appKey) { - return repository.findByAppId(appKey); + return repository.findByAppKey(appKey); } public KeywordFilterEntity update(String appKey, String id, String pattern, String replacement, String action, Boolean enabled) { diff --git a/im-service/src/main/java/com/xuqm/im/service/MessageService.java b/im-service/src/main/java/com/xuqm/im/service/MessageService.java index b4603ce..47b2f7f 100644 --- a/im-service/src/main/java/com/xuqm/im/service/MessageService.java +++ b/im-service/src/main/java/com/xuqm/im/service/MessageService.java @@ -116,7 +116,7 @@ public class MessageService { message.setId(req.messageId() != null && !req.messageId().isBlank() ? req.messageId() : UUID.randomUUID().toString()); - message.setAppId(appKey); + message.setAppKey(appKey); message.setFromUserId(fromUserId); message.setToId(req.toId()); message.setChatType(req.chatType()); @@ -184,14 +184,14 @@ public class MessageService { } private boolean isFriend(String appKey, String userId, String friendId) { - return friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId) - || friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId); + return friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, userId, friendId) + || friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, friendId, userId); } public ImMessageEntity revoke(String appKey, String messageId, String requestUserId) { ImMessageEntity message = messageRepository.findById(messageId) .orElseThrow(() -> new BusinessException(404, "消息不存在")); - if (!message.getAppId().equals(appKey)) { + if (!message.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } if (!message.getFromUserId().equals(requestUserId)) { @@ -223,7 +223,7 @@ public class MessageService { public ImMessageEntity edit(String appKey, String messageId, String requestUserId, EditMessageRequest req) { ImMessageEntity message = messageRepository.findById(messageId) .orElseThrow(() -> new BusinessException(404, "消息不存在")); - if (!message.getAppId().equals(appKey)) { + if (!message.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } if (!message.getFromUserId().equals(requestUserId)) { @@ -278,7 +278,7 @@ public class MessageService { public ImMessageEntity adminRevoke(String appKey, String messageId) { ImMessageEntity message = messageRepository.findById(messageId) .orElseThrow(() -> new BusinessException(404, "消息不存在")); - if (!message.getAppId().equals(appKey)) { + if (!message.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } message.setStatus(ImMessageEntity.MsgStatus.REVOKED); @@ -341,7 +341,7 @@ public class MessageService { return; } List messages = messageRepository - .findByAppIdAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc( + .findByAppKeyAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc( appKey, peerId, readerId, readAt); if (messages.isEmpty()) { return; @@ -374,7 +374,7 @@ public class MessageService { return; } List messages = messageRepository - .findByAppIdAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc( + .findByAppKeyAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc( appKey, groupId, ImMessageEntity.ChatType.GROUP, readAt); if (messages.isEmpty()) { return; @@ -491,12 +491,12 @@ public class MessageService { public List groupReadReceipts(String appKey, String groupId, List messageIds) { ImGroupEntity group = groupService.get(groupId); - if (!group.getAppId().equals(appKey)) { + if (!group.getAppKey().equals(appKey)) { throw new BusinessException(403, "无权操作"); } List members = groupService.memberIds(group); return messageRepository.findAllById(messageIds == null ? List.of() : messageIds).stream() - .filter(message -> appKey.equals(message.getAppId())) + .filter(message -> appKey.equals(message.getAppKey())) .filter(message -> groupId.equals(message.getToId())) .filter(message -> message.getChatType() == ImMessageEntity.ChatType.GROUP) .map(message -> { @@ -520,7 +520,7 @@ public class MessageService { try { Map payload = new java.util.LinkedHashMap<>(); payload.put("messageId", message.getId()); - payload.put("appKey", message.getAppId()); + payload.put("appKey", message.getAppKey()); payload.put("fromUserId", message.getFromUserId()); payload.put("toId", message.getToId()); payload.put("chatType", message.getChatType().name()); @@ -598,7 +598,7 @@ public class MessageService { public ImMessageEntity adminSend(String appKey, String fromUserId, String toId, ImMessageEntity.MsgType msgType, String content) { ImMessageEntity message = new ImMessageEntity(); message.setId(UUID.randomUUID().toString()); - message.setAppId(appKey); + message.setAppKey(appKey); message.setFromUserId(fromUserId); message.setToId(toId); message.setChatType(ImMessageEntity.ChatType.SINGLE); @@ -624,7 +624,7 @@ public class MessageService { } public void adminSetMsgRead(String appKey, String userId) { - List messages = messageRepository.findUnreadByAppIdAndToId(appKey, userId); + List messages = messageRepository.findUnreadByAppKeyAndToId(appKey, userId); for (ImMessageEntity message : messages) { message.setStatus(ImMessageEntity.MsgStatus.READ); messageRepository.save(message); @@ -642,7 +642,7 @@ public class MessageService { message.setId(req.messageId() != null && !req.messageId().isBlank() ? req.messageId() : UUID.randomUUID().toString()); - message.setAppId(appKey); + message.setAppKey(appKey); message.setFromUserId(req.fromUserId()); message.setToId(req.toId()); message.setChatType(req.chatType() != null ? req.chatType() : ImMessageEntity.ChatType.SINGLE); diff --git a/im-service/src/main/java/com/xuqm/im/service/OfflineMessageSyncService.java b/im-service/src/main/java/com/xuqm/im/service/OfflineMessageSyncService.java index aeacf4c..faffd61 100644 --- a/im-service/src/main/java/com/xuqm/im/service/OfflineMessageSyncService.java +++ b/im-service/src/main/java/com/xuqm/im/service/OfflineMessageSyncService.java @@ -36,7 +36,7 @@ public class OfflineMessageSyncService { public void storeOfflineMessage(String appKey, String userId, String messageId) { ImOfflineMessageEntity entity = new ImOfflineMessageEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setUserId(userId); entity.setMessageId(messageId); entity.setDelivered(false); @@ -48,7 +48,7 @@ public class OfflineMessageSyncService { @Transactional public void syncAndDeliver(String appKey, String userId) { List offlineMessages = offlineMessageRepository - .findByAppIdAndUserIdAndDeliveredFalse(appKey, userId); + .findByAppKeyAndUserIdAndDeliveredFalse(appKey, userId); if (offlineMessages.isEmpty()) { return; } @@ -68,17 +68,17 @@ public class OfflineMessageSyncService { log.info("Synced {} offline messages for appKey={} userId={}", deliveredIds.size(), appKey, userId); } - offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appKey, userId); + offlineMessageRepository.deleteByAppKeyAndUserIdAndDeliveredTrue(appKey, userId); } public long countUndelivered(String appKey, String userId) { - return offlineMessageRepository.countUndeliveredByAppIdAndUserId(appKey, userId); + return offlineMessageRepository.countUndeliveredByAppKeyAndUserId(appKey, userId); } @Transactional public List syncAndReturn(String appKey, String userId) { List offlineMessages = offlineMessageRepository - .findByAppIdAndUserIdAndDeliveredFalse(appKey, userId); + .findByAppKeyAndUserIdAndDeliveredFalse(appKey, userId); List result = new ArrayList<>(); List deliveredIds = new ArrayList<>(); for (ImOfflineMessageEntity offline : offlineMessages) { @@ -91,7 +91,7 @@ public class OfflineMessageSyncService { if (!deliveredIds.isEmpty()) { offlineMessageRepository.markDeliveredByIds(deliveredIds); } - offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appKey, userId); + offlineMessageRepository.deleteByAppKeyAndUserIdAndDeliveredTrue(appKey, userId); return result; } } diff --git a/im-service/src/main/java/com/xuqm/im/service/OperationLogService.java b/im-service/src/main/java/com/xuqm/im/service/OperationLogService.java index 03dfc6d..597df2a 100644 --- a/im-service/src/main/java/com/xuqm/im/service/OperationLogService.java +++ b/im-service/src/main/java/com/xuqm/im/service/OperationLogService.java @@ -27,7 +27,7 @@ public class OperationLogService { String detail) { ImOperationLogEntity entity = new ImOperationLogEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setOperatorId(operatorId == null || operatorId.isBlank() ? "system" : operatorId); entity.setAction(action); entity.setResourceType(resourceType); @@ -38,6 +38,6 @@ public class OperationLogService { } public Page list(String appKey, Pageable pageable) { - return repository.findByAppIdOrderByCreatedAtDesc(appKey, pageable); + return repository.findByAppKeyOrderByCreatedAtDesc(appKey, pageable); } } diff --git a/im-service/src/main/java/com/xuqm/im/service/WebhookConfigService.java b/im-service/src/main/java/com/xuqm/im/service/WebhookConfigService.java index a72559b..39e45ab 100644 --- a/im-service/src/main/java/com/xuqm/im/service/WebhookConfigService.java +++ b/im-service/src/main/java/com/xuqm/im/service/WebhookConfigService.java @@ -19,7 +19,7 @@ public class WebhookConfigService { } public List list(String appKey) { - return repository.findByAppId(appKey); + return repository.findByAppKey(appKey); } public WebhookConfigEntity get(String appKey, String id) { @@ -30,7 +30,7 @@ public class WebhookConfigService { public WebhookConfigEntity create(String appKey, String url, String secret, Boolean enabled) { WebhookConfigEntity entity = new WebhookConfigEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setUrl(url); entity.setSecret(secret); entity.setEnabled(enabled == null || enabled); diff --git a/im-service/src/main/java/com/xuqm/im/service/WebhookDispatchService.java b/im-service/src/main/java/com/xuqm/im/service/WebhookDispatchService.java index aee1832..1bb19dd 100644 --- a/im-service/src/main/java/com/xuqm/im/service/WebhookDispatchService.java +++ b/im-service/src/main/java/com/xuqm/im/service/WebhookDispatchService.java @@ -61,7 +61,7 @@ public class WebhookDispatchService { @Async public void dispatch(String appKey, String callbackType, String callbackEvent, Object payload) { - List webhooks = webhookRepository.findByAppIdAndEnabledTrue(appKey); + List webhooks = webhookRepository.findByAppKeyAndEnabledTrue(appKey); if (webhooks.isEmpty()) { return; } @@ -100,7 +100,7 @@ public class WebhookDispatchService { for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { WebhookDeliveryEntity delivery = new WebhookDeliveryEntity(); delivery.setId(UUID.randomUUID().toString()); - delivery.setAppId(appKey); + delivery.setAppKey(appKey); delivery.setCallbackId(callbackId); delivery.setCallbackEvent(callbackEvent); delivery.setUrl(webhook.getUrl()); @@ -112,7 +112,7 @@ public class WebhookDispatchService { .uri(URI.create(webhook.getUrl())) .timeout(Duration.ofMillis(webhookTimeoutMs)) .header("Content-Type", "application/json") - .header("X-App-Id", appKey) + .header("X-App-Key", appKey) .header("X-App-Timestamp", String.valueOf(requestTime)) .header("X-App-Nonce", nonce) .header("X-App-Signature", signature) @@ -182,7 +182,7 @@ public class WebhookDispatchService { WebhookAlertEntity alert = new WebhookAlertEntity(); alert.setId(UUID.randomUUID().toString()); - alert.setAppId(appKey); + alert.setAppKey(appKey); alert.setWebhookId(webhook.getId()); alert.setWebhookUrl(webhook.getUrl()); alert.setAlertType("AUTO_DISABLED"); diff --git a/pom.xml b/pom.xml index fd616b8..3c2570b 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,6 @@ common tenant-service im-service - im-sdk push-service update-service demo-service diff --git a/push-service/src/main/java/com/xuqm/push/entity/DeviceLoginLogEntity.java b/push-service/src/main/java/com/xuqm/push/entity/DeviceLoginLogEntity.java index ff8382f..73e35c3 100644 --- a/push-service/src/main/java/com/xuqm/push/entity/DeviceLoginLogEntity.java +++ b/push-service/src/main/java/com/xuqm/push/entity/DeviceLoginLogEntity.java @@ -11,7 +11,7 @@ import java.time.LocalDateTime; @Entity @Table(name = "push_device_login_log", indexes = { - @Index(name = "idx_push_device_log_user_time", columnList = "appId,userId,createdAt"), + @Index(name = "idx_push_device_log_user_time", columnList = "appKey,userId,createdAt"), @Index(name = "idx_push_device_log_token", columnList = "tokenHash") }) public class DeviceLoginLogEntity { @@ -24,7 +24,7 @@ public class DeviceLoginLogEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String userId; @@ -70,8 +70,8 @@ public class DeviceLoginLogEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/push-service/src/main/java/com/xuqm/push/entity/DeviceTokenEntity.java b/push-service/src/main/java/com/xuqm/push/entity/DeviceTokenEntity.java index 2ad77a9..0cf54f6 100644 --- a/push-service/src/main/java/com/xuqm/push/entity/DeviceTokenEntity.java +++ b/push-service/src/main/java/com/xuqm/push/entity/DeviceTokenEntity.java @@ -11,18 +11,18 @@ import java.time.LocalDateTime; @Entity @Table(name = "push_device_token", - uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "deviceId"})) + uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "deviceId"})) public class DeviceTokenEntity { public enum Vendor { - HUAWEI, XIAOMI, OPPO, VIVO, HONOR, HARMONY, FCM, APNS + HUAWEI, XIAOMI, OPPO, VIVO, HONOR, HARMONY, APNS } @Id private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 128) private String userId; @@ -67,8 +67,8 @@ public class DeviceTokenEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } diff --git a/push-service/src/main/java/com/xuqm/push/repository/DeviceLoginLogRepository.java b/push-service/src/main/java/com/xuqm/push/repository/DeviceLoginLogRepository.java index ec607e1..567e5ff 100644 --- a/push-service/src/main/java/com/xuqm/push/repository/DeviceLoginLogRepository.java +++ b/push-service/src/main/java/com/xuqm/push/repository/DeviceLoginLogRepository.java @@ -6,5 +6,5 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface DeviceLoginLogRepository extends JpaRepository { - Page findByAppIdAndUserIdOrderByCreatedAtDesc(String appId, String userId, Pageable pageable); + Page findByAppKeyAndUserIdOrderByCreatedAtDesc(String appKey, String userId, Pageable pageable); } diff --git a/push-service/src/main/java/com/xuqm/push/repository/DeviceTokenRepository.java b/push-service/src/main/java/com/xuqm/push/repository/DeviceTokenRepository.java index 20b1f58..d4f5ecc 100644 --- a/push-service/src/main/java/com/xuqm/push/repository/DeviceTokenRepository.java +++ b/push-service/src/main/java/com/xuqm/push/repository/DeviceTokenRepository.java @@ -7,16 +7,16 @@ import java.util.List; import java.util.Optional; public interface DeviceTokenRepository extends JpaRepository { - List findByAppIdAndUserIdAndReceivePushTrue(String appId, String userId); - Optional findFirstByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc( - String appId, String userId); - List findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(String appId, String userId); - Optional findByAppIdAndUserIdAndVendor( - String appId, String userId, DeviceTokenEntity.Vendor vendor); - Optional findByAppIdAndUserIdAndDeviceId(String appId, String userId, String deviceId); + List findByAppKeyAndUserIdAndReceivePushTrue(String appKey, String userId); + Optional findFirstByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc( + String appKey, String userId); + List findByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(String appKey, String userId); + Optional findByAppKeyAndUserIdAndVendor( + String appKey, String userId, DeviceTokenEntity.Vendor vendor); + Optional findByAppKeyAndUserIdAndDeviceId(String appKey, String userId, String deviceId); Optional findFirstByToken(String token); - List findByAppIdAndUserId(String appId, String userId); - List findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(String appId, String userId); - void deleteByAppIdAndUserIdAndVendor(String appId, String userId, DeviceTokenEntity.Vendor vendor); - void deleteByAppIdAndUserIdAndDeviceId(String appId, String userId, String deviceId); + List findByAppKeyAndUserId(String appKey, String userId); + List findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(String appKey, String userId); + void deleteByAppKeyAndUserIdAndVendor(String appKey, String userId, DeviceTokenEntity.Vendor vendor); + void deleteByAppKeyAndUserIdAndDeviceId(String appKey, String userId, String deviceId); } diff --git a/push-service/src/main/java/com/xuqm/push/service/PushDiagnosticsService.java b/push-service/src/main/java/com/xuqm/push/service/PushDiagnosticsService.java index 4357459..4d17f8e 100644 --- a/push-service/src/main/java/com/xuqm/push/service/PushDiagnosticsService.java +++ b/push-service/src/main/java/com/xuqm/push/service/PushDiagnosticsService.java @@ -35,15 +35,15 @@ public class PushDiagnosticsService { this.jwtUtil = jwtUtil; } - public PushTokenDiagnostics searchByToken(String token, String appIdHint) { + public PushTokenDiagnostics searchByToken(String token, String appKeyHint) { Optional tokenMatch = tokenRepository.findFirstByToken(token); - String appKey = appIdHint; + String appKey = appKeyHint; String userId = null; String tokenType = "UNKNOWN"; if (tokenMatch.isPresent()) { DeviceTokenEntity device = tokenMatch.get(); - appKey = device.getAppId(); + appKey = device.getAppKey(); userId = device.getUserId(); tokenType = "PUSH"; } else { @@ -56,8 +56,8 @@ public class PushDiagnosticsService { try { Claims claims = jwtUtil.parse(token); userId = claims.getSubject(); - String claimAppId = claims.get("appKey", String.class); - appKey = claimAppId == null || claimAppId.isBlank() ? appIdHint : claimAppId; + String claimAppKey = claims.get("appKey", String.class); + appKey = claimAppKey == null || claimAppKey.isBlank() ? appKeyHint : claimAppKey; tokenType = "IM"; } catch (Exception ignored) { // Keep UNKNOWN and return an empty diagnostic. @@ -71,7 +71,7 @@ public class PushDiagnosticsService { ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId) .orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L)); - List devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); + List devices = tokenRepository.findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); List deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId) .stream() .map(DeviceInfo::from) @@ -93,7 +93,7 @@ public class PushDiagnosticsService { public PushTokenDiagnostics searchByUserId(String appKey, String userId) { ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId) .orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L)); - List devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); + List devices = tokenRepository.findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); List deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId) .stream() .map(DeviceInfo::from) @@ -126,7 +126,7 @@ public class PushDiagnosticsService { public Page deviceLogs(String appKey, String userId, int page, int size) { int safePage = Math.max(page, 0); int safeSize = Math.min(Math.max(size, 1), 200); - return logRepository.findByAppIdAndUserIdOrderByCreatedAtDesc(appKey, userId, PageRequest.of(safePage, safeSize)); + return logRepository.findByAppKeyAndUserIdOrderByCreatedAtDesc(appKey, userId, PageRequest.of(safePage, safeSize)); } public record PushTokenDiagnostics( diff --git a/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java b/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java index a975b0c..dcbcc1f 100644 --- a/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java +++ b/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java @@ -68,17 +68,22 @@ public class PushDispatcher { String routeType = routeType(payload); String platform = platformForVendor(vendor, payload); return pushConfigClient.loadServiceConfig(appKey, platform, "PUSH") - .map(config -> { - JsonNode route = config.path("routing").path(routeType); - String channelKey = route.path("channel").asText(""); - String channelId = effectiveChannelId(config.path("channels"), channelKey); - return new PushSendOptions( - routeType, - channelId, - route.path("category").asText(""), - route.path("priority").asText("")); - }) - .orElseGet(() -> new PushSendOptions(routeType, "", "", "")); + .map(config -> profileFor(config, vendor.name(), routeType) + .map(profile -> new PushSendOptions( + profile.path("key").asText(""), + routeType, + profile.path("channelId").asText(""), + profile.path("category").asText(""), + profile.path("threadIdentifier").asText(""), + profile.path("interruptionLevel").asText(""), + profile.path("importance").asText(""), + readBoolean(profile, "badge"), + readBoolean(profile, "sound"), + readBoolean(profile, "vibration"), + readInteger(profile, "notifyType"), + mapPriority(profile.path("importance").asText("")))) + .orElseGet(() -> new PushSendOptions("", routeType, "", "", "", "", "", null, null, null, null, ""))) + .orElseGet(() -> new PushSendOptions("", routeType, "", "", "", "", "", null, null, null, null, "")); } private String platformForVendor(DeviceTokenEntity.Vendor vendor, String payload) { @@ -121,18 +126,73 @@ public class PushDispatcher { } } - private String effectiveChannelId(JsonNode channels, String channelKey) { - if (channels == null || !channels.isArray() || channelKey == null || channelKey.isBlank()) { - return ""; + private java.util.Optional profileFor(JsonNode config, String vendor, String routeType) { + JsonNode profiles = config.path("profiles"); + if (profiles == null || !profiles.isArray()) { + return java.util.Optional.empty(); } - for (JsonNode channel : channels) { - if (channelKey.equals(channel.path("key").asText(""))) { - String base = channel.path("channelId").asText(channelKey); - int version = Math.max(channel.path("version").asInt(1), 1); - return base + "_v" + version; + JsonNode fallback = null; + for (JsonNode profile : profiles) { + if (!profile.path("enabled").asBoolean(true)) { + continue; } + if (!vendor.equalsIgnoreCase(profile.path("vendor").asText(""))) { + continue; + } + String profileRouteType = profile.path("routeType").asText(""); + if (profileRouteType.isBlank() || "DEFAULT".equalsIgnoreCase(profileRouteType)) { + if (fallback == null) { + fallback = profile; + } + continue; + } + if (!routeType.equalsIgnoreCase(profileRouteType)) { + continue; + } + return java.util.Optional.of(profile); } - return ""; + return java.util.Optional.ofNullable(fallback); + } + + private Boolean readBoolean(JsonNode node, String key) { + if (node == null || node.isNull()) { + return null; + } + JsonNode value = node.get(key); + if (value == null || value.isNull()) { + return null; + } + return value.asBoolean(); + } + + private Integer readInteger(JsonNode node, String key) { + if (node == null || node.isNull()) { + return null; + } + JsonNode value = node.get(key); + if (value == null || value.isNull()) { + return null; + } + if (value.isInt() || value.isLong() || value.isNumber()) { + return value.asInt(); + } + String text = value.asText(""); + if (text.isBlank()) { + return null; + } + try { + return Integer.parseInt(text.trim()); + } catch (NumberFormatException e) { + return null; + } + } + + private String mapPriority(String importance) { + return switch (importance == null ? "" : importance.trim().toUpperCase()) { + case "HIGH", "MAX" -> "HIGH"; + case "LOW", "MIN" -> "LOW"; + default -> "DEFAULT"; + }; } public List selectedPushTargets(String appKey, String userId) { @@ -150,7 +210,7 @@ public class PushDispatcher { private List selectTargets(String appKey, String userId) { List devices = tokenRepository - .findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); + .findByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); if (devices.isEmpty()) { return List.of(); } @@ -185,15 +245,15 @@ public class PushDispatcher { String osVersion, String appVersion) { String resolvedDeviceId = normalizeDeviceId(deviceId, vendor, token); - Optional existing = tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId); + Optional existing = tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId); if (existing.isEmpty() && (deviceId == null || deviceId.isBlank())) { - existing = tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor); + existing = tokenRepository.findByAppKeyAndUserIdAndVendor(appKey, userId, vendor); } LocalDateTime now = LocalDateTime.now(); DeviceTokenEntity entity = existing.orElseGet(() -> { DeviceTokenEntity e = new DeviceTokenEntity(); e.setId(UUID.randomUUID().toString()); - e.setAppId(appKey); + e.setAppKey(appKey); e.setUserId(userId); e.setVendor(vendor); e.setCreatedAt(now); @@ -216,8 +276,8 @@ public class PushDispatcher { public void setReceivePush(String appKey, String userId, String deviceId, boolean enabled) { List tokens = deviceId == null || deviceId.isBlank() - ? tokenRepository.findByAppIdAndUserId(appKey, userId) - : tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId).stream().toList(); + ? tokenRepository.findByAppKeyAndUserId(appKey, userId) + : tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId).stream().toList(); for (DeviceTokenEntity token : tokens) { token.setReceivePush(enabled); token.setUpdatedAt(LocalDateTime.now()); @@ -228,20 +288,20 @@ public class PushDispatcher { public void unregisterToken(String appKey, String userId, DeviceTokenEntity.Vendor vendor, String deviceId) { if (deviceId != null && !deviceId.isBlank()) { - tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId) + tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId) .ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER)); - tokenRepository.deleteByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId); + tokenRepository.deleteByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId); return; } - tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor) + tokenRepository.findByAppKeyAndUserIdAndVendor(appKey, userId, vendor) .ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER)); - tokenRepository.deleteByAppIdAndUserIdAndVendor(appKey, userId, vendor); + tokenRepository.deleteByAppKeyAndUserIdAndVendor(appKey, userId, vendor); } private void recordLog(DeviceTokenEntity token, DeviceLoginLogEntity.EventType eventType) { DeviceLoginLogEntity entity = new DeviceLoginLogEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(token.getAppId()); + entity.setAppKey(token.getAppKey()); entity.setUserId(token.getUserId()); entity.setVendor(token.getVendor()); entity.setTokenHash(hashToken(token.getToken())); diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java index 935dc74..4722a16 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/ApnsPushProvider.java @@ -82,16 +82,31 @@ public class ApnsPushProvider implements PushProvider { String url = (production ? productionPushUrl : sandboxPushUrl).replace("{token}", token); Map aps = new java.util.LinkedHashMap<>(); aps.put("alert", Map.of("title", title, "body", body)); - aps.put("sound", "default"); + if (options == null || options.sound() == null || options.sound()) { + aps.put("sound", "default"); + } if (options != null) { if (options.category() != null && !options.category().isBlank()) { aps.put("category", options.category()); } - if (options.routeType() != null && !options.routeType().isBlank()) { + if (options.threadIdentifier() != null && !options.threadIdentifier().isBlank()) { + aps.put("thread-id", options.threadIdentifier()); + } else if (options.routeType() != null && !options.routeType().isBlank()) { aps.put("thread-id", options.routeType()); } - if ("HIGH".equalsIgnoreCase(options.priority())) { + if ("HIGH".equalsIgnoreCase(options.priority()) || "HIGH".equalsIgnoreCase(options.interruptionLevel())) { aps.put("interruption-level", "time-sensitive"); + } else if ("CRITICAL".equalsIgnoreCase(options.interruptionLevel())) { + aps.put("interruption-level", "critical"); + } else if ("PASSIVE".equalsIgnoreCase(options.interruptionLevel())) { + aps.put("interruption-level", "passive"); + } else if ("ACTIVE".equalsIgnoreCase(options.interruptionLevel())) { + aps.put("interruption-level", "active"); + } + if (options.badge() != null) { + if (Boolean.TRUE.equals(options.badge())) { + aps.put("badge", 1); + } } } Map message = new java.util.LinkedHashMap<>(); @@ -153,7 +168,7 @@ public class ApnsPushProvider implements PushProvider { private String resolveConfig(String appKey, String key, String fallback) { JsonNode config = configClient.loadServiceConfig(appKey, "IOS", "PUSH") - .map(node -> node.path("apns")) + .map(node -> node.path("vendors").path("apns")) .orElse(null); if (config == null) { return fallback == null ? "" : fallback; diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/FcmPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/FcmPushProvider.java deleted file mode 100644 index 02f6551..0000000 --- a/push-service/src/main/java/com/xuqm/push/service/provider/FcmPushProvider.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.xuqm.push.service.provider; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.xuqm.push.service.TenantPushConfigClient; -import io.jsonwebtoken.Jwts; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.net.URI; -import java.net.URLEncoder; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.security.KeyFactory; -import java.security.PrivateKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Component -public class FcmPushProvider implements PushProvider { - - private static final Logger log = LoggerFactory.getLogger(FcmPushProvider.class); - - @Value("${push.fcm.project-id:}") - private String envProjectId; - - @Value("${push.fcm.service-account-json:}") - private String envServiceAccountJson; - - @Value("${push.fcm.token-url:https://oauth2.googleapis.com/token}") - private String tokenUrl; - - @Value("${push.fcm.push-url:https://fcm.googleapis.com/v1/projects/{projectId}/messages:send}") - private String pushUrl; - - private final TenantPushConfigClient configClient; - private final HttpClient httpClient = HttpClient.newHttpClient(); - private final ObjectMapper objectMapper = new ObjectMapper(); - private final Map tokenCache = new ConcurrentHashMap<>(); - - public FcmPushProvider(TenantPushConfigClient configClient) { - this.configClient = configClient; - } - - @Override - public String vendorName() { - return "FCM"; - } - - @Override - public boolean send(String appKey, String token, String title, String body, String payload) { - return send(appKey, token, title, body, payload, PushSendOptions.empty()); - } - - @Override - public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) { - String projectId = resolveConfig(appKey, "projectId", envProjectId); - String serviceAccountJson = resolveConfig(appKey, "serviceAccountJson", envServiceAccountJson); - if (projectId.isBlank() || serviceAccountJson.isBlank()) { - log.warn("FCM push not configured"); - return false; - } - try { - String accessToken = getAccessToken(projectId, serviceAccountJson); - String url = pushUrl.replace("{projectId}", projectId); - Map bodyMap = new LinkedHashMap<>(); - bodyMap.put("token", token); - bodyMap.put("notification", Map.of("title", title, "body", body)); - bodyMap.put("data", payload != null ? Map.of("payload", payload) : Map.of()); - if (options != null && options.channelId() != null && !options.channelId().isBlank()) { - bodyMap.put("android", Map.of("notification", Map.of("channel_id", options.channelId()))); - } - Map message = Map.of("message", bodyMap); - String requestBody = objectMapper.writeValueAsString(message); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessToken) - .POST(HttpRequest.BodyPublishers.ofString(requestBody)) - .build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - return response.statusCode() == 200; - } catch (Exception e) { - log.error("FCM push failed: {}", e.getMessage()); - return false; - } - } - - private String getAccessToken(String projectId, String serviceAccountJson) throws Exception { - TokenCache cache = tokenCache.get(projectId); - if (cache != null && cache.expiresAt > System.currentTimeMillis() + 60_000) { - return cache.token; - } - - JsonNode sa = objectMapper.readTree(serviceAccountJson); - String clientEmail = sa.path("client_email").asText(); - String privateKeyPem = sa.path("private_key").asText(); - - PrivateKey privateKey = parseRsaPrivateKey(privateKeyPem); - - long now = System.currentTimeMillis(); - String jwt = Jwts.builder() - .subject(clientEmail) - .issuer(clientEmail) - .claim("aud", tokenUrl) - .claim("scope", "https://www.googleapis.com/auth/firebase.messaging") - .issuedAt(new Date(now)) - .expiration(new Date(now + 3600_000)) - .signWith(privateKey) - .compact(); - - String form = "grant_type=" + URLEncoder.encode("urn:ietf:params:oauth:grant-type:jwt-bearer", StandardCharsets.UTF_8) - + "&assertion=" + jwt; - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(tokenUrl)) - .header("Content-Type", "application/x-www-form-urlencoded") - .POST(HttpRequest.BodyPublishers.ofString(form)) - .build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - Map json = objectMapper.readValue(response.body(), Map.class); - String accessToken = (String) json.get("access_token"); - Integer expiresIn = (Integer) json.get("expires_in"); - long expiresAt = System.currentTimeMillis() + (expiresIn != null ? expiresIn : 3600) * 1000L; - tokenCache.put(projectId, new TokenCache(accessToken, expiresAt)); - return accessToken; - } - - private PrivateKey parseRsaPrivateKey(String pem) throws Exception { - String clean = pem.replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\s", ""); - byte[] decoded = Base64.getDecoder().decode(clean); - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded); - KeyFactory kf = KeyFactory.getInstance("RSA"); - return kf.generatePrivate(spec); - } - - private String resolveConfig(String appKey, String key, String fallback) { - JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") - .map(node -> node.path("fcm")) - .orElse(null); - if (config == null) { - return fallback == null ? "" : fallback; - } - String value = config.path(key).asText(""); - return value.isBlank() ? (fallback == null ? "" : fallback) : value; - } - - private record TokenCache(String token, long expiresAt) {} -} diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java index 952955e..aec2cb7 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/HarmonyPushProvider.java @@ -78,6 +78,9 @@ public class HarmonyPushProvider implements PushProvider { if (options.channelId() != null && !options.channelId().isBlank()) { notification.put("channel_id", options.channelId()); } + if (options.badge() != null && options.badge()) { + notification.put("badge", 1); + } } Map message = Map.of( "message", Map.of( @@ -115,7 +118,7 @@ public class HarmonyPushProvider implements PushProvider { private String resolveConfig(String appKey, String key, String fallback) { JsonNode config = configClient.loadServiceConfig(appKey, "HARMONY", "PUSH") - .map(node -> node.path("harmony")) + .map(node -> node.path("vendors").path("harmony")) .orElse(null); if (config == null) { return fallback == null ? "" : fallback; diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/HonorPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/HonorPushProvider.java index b7b3759..39c7328 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/HonorPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/HonorPushProvider.java @@ -46,6 +46,11 @@ public class HonorPushProvider implements PushProvider { @Override public boolean send(String appKey, String token, String title, String body, String payload) { + return send(appKey, token, title, body, payload, PushSendOptions.empty()); + } + + @Override + public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) { String resolvedAppId = resolveConfig(appKey, "appId", envAppId); String resolvedAppSecret = resolveConfig(appKey, "clientSecret", envAppSecret); if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) { @@ -58,10 +63,21 @@ public class HonorPushProvider implements PushProvider { try { String accessToken = getAccessToken(resolvedAppId, resolvedAppSecret); String url = pushUrl.replace("{appId}", resolvedAppId); + java.util.Map notification = new java.util.LinkedHashMap<>(); + notification.put("title", title); + notification.put("body", body); + if (options != null) { + if (options.channelId() != null && !options.channelId().isBlank()) { + notification.put("channel_id", options.channelId()); + } + if (options.category() != null && !options.category().isBlank()) { + notification.put("category", options.category()); + } + } Map message = Map.of( "message", Map.of( "token", new String[]{token}, - "notification", Map.of("title", title, "body", body), + "notification", notification, "data", payload != null ? payload : "{}" ) ); @@ -94,7 +110,7 @@ public class HonorPushProvider implements PushProvider { private String resolveConfig(String appKey, String key, String fallback) { JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") - .map(node -> node.path("honor")) + .map(node -> node.path("vendors").path("honor")) .orElse(null); if (config == null) { return fallback == null ? "" : fallback; diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/HuaweiPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/HuaweiPushProvider.java index e1a4197..d6d69b2 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/HuaweiPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/HuaweiPushProvider.java @@ -68,7 +68,9 @@ public class HuaweiPushProvider implements PushProvider { Map androidNotification = new LinkedHashMap<>(); String channelId = options != null && options.channelId() != null && !options.channelId().isBlank() ? options.channelId() : ""; - String category = resolveConfig(appKey, "category", ""); + String category = options != null && options.category() != null && !options.category().isBlank() + ? options.category() + : resolveConfig(appKey, "category", ""); if (!channelId.isBlank()) androidNotification.put("channel_id", channelId); if (!category.isBlank()) androidNotification.put("category", category); if (!androidNotification.isEmpty()) { @@ -104,7 +106,7 @@ public class HuaweiPushProvider implements PushProvider { private String resolveConfig(String appKey, String key, String fallback) { JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") - .map(node -> node.path("huawei")) + .map(node -> node.path("vendors").path("huawei")) .orElse(null); if (config == null) { return fallback == null ? "" : fallback; diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/OppoPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/OppoPushProvider.java index d31adf9..5e80990 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/OppoPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/OppoPushProvider.java @@ -40,6 +40,11 @@ public class OppoPushProvider implements PushProvider { @Override public boolean send(String appKey, String token, String title, String body, String payload) { + return send(appKey, token, title, body, payload, PushSendOptions.empty()); + } + + @Override + public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) { String vendorAppKey = resolveConfig(appKey, "appKey"); String masterSecret = resolveConfig(appKey, "masterSecret"); if (vendorAppKey.isBlank() || masterSecret.isBlank()) { @@ -55,7 +60,9 @@ public class OppoPushProvider implements PushProvider { inner.put("content", body); inner.put("target_type", 2); inner.put("target_value", token); - String channelId = resolveConfig(appKey, "channelId"); + String channelId = options != null && options.channelId() != null && !options.channelId().isBlank() + ? options.channelId() + : resolveConfig(appKey, "channelId"); if (!channelId.isBlank()) inner.put("channel_id", channelId); Map message = Map.of("message", inner); String requestBody = objectMapper.writeValueAsString(message); @@ -98,7 +105,7 @@ public class OppoPushProvider implements PushProvider { private String resolveConfig(String appKey, String key) { JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") - .map(node -> node.path("oppo")) + .map(node -> node.path("vendors").path("oppo")) .orElse(null); if (config == null) { return ""; diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/PushSendOptions.java b/push-service/src/main/java/com/xuqm/push/service/provider/PushSendOptions.java index ac9af4d..59ca431 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/PushSendOptions.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/PushSendOptions.java @@ -1,12 +1,20 @@ package com.xuqm.push.service.provider; public record PushSendOptions( + String profileKey, String routeType, String channelId, String category, + String threadIdentifier, + String interruptionLevel, + String importance, + Boolean badge, + Boolean sound, + Boolean vibration, + Integer notifyType, String priority ) { public static PushSendOptions empty() { - return new PushSendOptions("", "", "", ""); + return new PushSendOptions("", "", "", "", "", "", "", null, null, null, null, ""); } } diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/VivoPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/VivoPushProvider.java index 376c984..f4aae6f 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/VivoPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/VivoPushProvider.java @@ -40,6 +40,11 @@ public class VivoPushProvider implements PushProvider { @Override public boolean send(String appKey, String token, String title, String body, String payload) { + return send(appKey, token, title, body, payload, PushSendOptions.empty()); + } + + @Override + public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) { String vendorAppKey = resolveConfig(appKey, "appKey"); String appIdConfig = resolveConfig(appKey, "appId"); if (vendorAppKey.isBlank() || appIdConfig.isBlank()) { @@ -53,11 +58,17 @@ public class VivoPushProvider implements PushProvider { message.put("title", title); message.put("content", body); message.put("notifyType", 1); - String category = resolveConfig(appKey, "category"); + String category = options != null && options.category() != null && !options.category().isBlank() + ? options.category() + : resolveConfig(appKey, "category"); String receiptId = resolveConfig(appKey, "receiptId"); if (!category.isBlank()) { - // vivo classification: 0=operation, 1=IM/system - message.put("classification", "IM".equalsIgnoreCase(category) ? 1 : 0); + message.put("classification", switch (category.toUpperCase()) { + case "MESSAGE", "IM" -> 1; + case "SOCIAL" -> 2; + case "SYSTEM" -> 3; + default -> 0; + }); } if (!receiptId.isBlank()) { message.put("requestId", receiptId + "_" + System.currentTimeMillis()); @@ -102,7 +113,7 @@ public class VivoPushProvider implements PushProvider { private String resolveConfig(String appKey, String key) { JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") - .map(node -> node.path("vivo")) + .map(node -> node.path("vendors").path("vivo")) .orElse(null); if (config == null) { return ""; diff --git a/push-service/src/main/java/com/xuqm/push/service/provider/XiaomiPushProvider.java b/push-service/src/main/java/com/xuqm/push/service/provider/XiaomiPushProvider.java index 30d7a5a..d4a4882 100644 --- a/push-service/src/main/java/com/xuqm/push/service/provider/XiaomiPushProvider.java +++ b/push-service/src/main/java/com/xuqm/push/service/provider/XiaomiPushProvider.java @@ -70,8 +70,9 @@ public class XiaomiPushProvider implements PushProvider { .append("&title=").append(URLEncoder.encode(title, StandardCharsets.UTF_8)) .append("&description=").append(URLEncoder.encode(body, StandardCharsets.UTF_8)) .append("&restricted_package_name=").append(URLEncoder.encode(packageName, StandardCharsets.UTF_8)) - .append("¬ify_type=1") .append("&extra.notify_foreground=1"); + Integer notifyType = options != null ? options.notifyType() : null; + form.append("¬ify_type=").append(notifyType == null ? 1 : Math.max(notifyType, 0)); if (!channelId.isBlank()) { form.append("&channel_id=").append(URLEncoder.encode(channelId, StandardCharsets.UTF_8)); } @@ -110,7 +111,7 @@ public class XiaomiPushProvider implements PushProvider { private String resolveVendorConfig(String appKey, String key, String fallback) { JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") - .map(node -> node.path("xiaomi")) + .map(node -> node.path("vendors").path("xiaomi")) .orElse(null); if (config == null) { return fallback == null ? "" : fallback; diff --git a/push-service/src/main/resources/application.yml b/push-service/src/main/resources/application.yml index 94d4d23..e5db9e6 100644 --- a/push-service/src/main/resources/application.yml +++ b/push-service/src/main/resources/application.yml @@ -34,11 +34,6 @@ push: xiaomi: app-secret: ${XIAOMI_APP_SECRET:} push-url: https://api.xmpush.xiaomi.com/v3/message/regid - fcm: - project-id: ${FCM_PROJECT_ID:} - service-account-json: ${FCM_SERVICE_ACCOUNT_JSON:} - token-url: https://oauth2.googleapis.com/token - push-url: https://fcm.googleapis.com/v1/projects/{projectId}/messages:send apns: team-id: ${APNS_TEAM_ID:} key-id: ${APNS_KEY_ID:} diff --git a/tenant-service/pom.xml b/tenant-service/pom.xml index eb71fd8..1b3a89f 100644 --- a/tenant-service/pom.xml +++ b/tenant-service/pom.xml @@ -20,6 +20,11 @@ com.xuqm common + + com.xuqm + im-sdk + 0.1.0-SNAPSHOT + org.springframework.boot spring-boot-starter-web diff --git a/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java b/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java index 6481b32..c2de691 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/controller/FeatureServiceController.java @@ -112,35 +112,7 @@ public class FeatureServiceController { case PUSH -> featureServiceManager.buildPushConfig( appKey, platform, - req == null ? null : req.huaweiAppIdValue(), - req == null ? null : req.huaweiAppSecretValue(), - req == null ? null : req.huaweiCategoryValue(), - req == null ? null : req.xiaomiAppIdValue(), - req == null ? null : req.xiaomiAppKeyValue(), - req == null ? null : req.xiaomiAppSecretValue(), - req == null ? null : req.xiaomiChannelIdValue(), - req == null ? null : req.oppoAppIdValue(), - req == null ? null : req.oppoAppKeyValue(), - req == null ? null : req.oppoMasterSecretValue(), - req == null ? null : req.oppoChannelIdValue(), - req == null ? null : req.vivoAppIdValue(), - req == null ? null : req.vivoAppKeyValue(), - req == null ? null : req.vivoAppSecretValue(), - req == null ? null : req.vivoCategoryValue(), - req == null ? null : req.vivoReceiptIdValue(), - req == null ? null : req.honorAppIdValue(), - req == null ? null : req.honorClientIdValue(), - req == null ? null : req.honorClientSecretValue(), - req == null ? null : req.harmonyAppIdValue(), - req == null ? null : req.harmonyAppSecretValue(), - req == null ? null : req.apnsTeamIdValue(), - req == null ? null : req.apnsKeyIdValue(), - req == null ? null : req.apnsBundleIdValue(), - req == null ? null : req.apnsKeyPathValue(), - req != null && req.apnsSandboxValue(), - req == null ? null : req.fcmServiceAccountJsonValue(), - req == null ? null : req.channels(), - req == null ? null : req.routing()); + req == null ? null : req.pushConfig()); }; FeatureServiceEntity saved = featureServiceManager.updateConfig( appKey, platform, serviceType, config); @@ -215,102 +187,7 @@ public class FeatureServiceController { String defaultPackageName, String defaultAppStoreUrl, String defaultMarketUrl, - String huaweiAppId, - String huaweiAppSecret, - String huaweiCategory, - String xiaomiAppId, - String xiaomiAppKey, - String xiaomiAppSecret, - String xiaomiChannelId, - String oppoAppId, - String oppoAppKey, - String oppoMasterSecret, - String oppoChannelId, - String vivoAppId, - String vivoAppKey, - String vivoAppSecret, - String vivoCategory, - String vivoReceiptId, - String honorAppId, - String honorClientId, - String honorClientSecret, - String harmonyAppId, - String harmonyAppSecret, - String apnsTeamId, - String apnsKeyId, - String apnsBundleId, - String apnsKeyPath, - Boolean apnsSandbox, - String fcmServiceAccountJson, - PushVendorConfig huawei, - PushVendorConfig xiaomi, - PushVendorConfig oppo, - PushVendorConfig vivo, - PushVendorConfig honor, - PushVendorConfig harmony, - PushVendorConfig apns, - PushVendorConfig fcm, - JsonNode channels, - JsonNode routing + JsonNode pushConfig ) { - public String huaweiAppIdValue() { return firstText(huaweiAppId, huawei == null ? null : huawei.appId()); } - public String huaweiAppSecretValue() { return firstText(huaweiAppSecret, huawei == null ? null : huawei.appSecret()); } - public String huaweiCategoryValue() { return firstText(huaweiCategory, huawei == null ? null : huawei.category()); } - public String xiaomiAppIdValue() { return firstText(xiaomiAppId, xiaomi == null ? null : xiaomi.appId()); } - public String xiaomiAppKeyValue() { return firstText(xiaomiAppKey, xiaomi == null ? null : xiaomi.appKey()); } - public String xiaomiAppSecretValue() { return firstText(xiaomiAppSecret, xiaomi == null ? null : xiaomi.appSecret()); } - public String xiaomiChannelIdValue() { return firstText(xiaomiChannelId, xiaomi == null ? null : xiaomi.channelId()); } - public String oppoAppIdValue() { return firstText(oppoAppId, oppo == null ? null : oppo.appId()); } - public String oppoAppKeyValue() { return firstText(oppoAppKey, oppo == null ? null : oppo.appKey()); } - public String oppoMasterSecretValue() { return firstText(oppoMasterSecret, oppo == null ? null : oppo.masterSecret()); } - public String oppoChannelIdValue() { return firstText(oppoChannelId, oppo == null ? null : oppo.channelId()); } - public String vivoAppIdValue() { return firstText(vivoAppId, vivo == null ? null : vivo.appId()); } - public String vivoAppKeyValue() { return firstText(vivoAppKey, vivo == null ? null : vivo.appKey()); } - public String vivoAppSecretValue() { return firstText(vivoAppSecret, vivo == null ? null : vivo.appSecret()); } - public String vivoCategoryValue() { return firstText(vivoCategory, vivo == null ? null : vivo.category()); } - public String vivoReceiptIdValue() { return firstText(vivoReceiptId, vivo == null ? null : vivo.receiptId()); } - public String honorAppIdValue() { return firstText(honorAppId, honor == null ? null : honor.appId()); } - public String honorClientIdValue() { return firstText(honorClientId, honor == null ? null : honor.clientId()); } - public String honorClientSecretValue() { return firstText(honorClientSecret, honor == null ? null : honor.clientSecret()); } - public String harmonyAppIdValue() { return firstText(harmonyAppId, harmony == null ? null : harmony.appId()); } - public String harmonyAppSecretValue() { return firstText(harmonyAppSecret, harmony == null ? null : harmony.appSecret()); } - public String apnsTeamIdValue() { return firstText(apnsTeamId, apns == null ? null : apns.teamId()); } - public String apnsKeyIdValue() { return firstText(apnsKeyId, apns == null ? null : apns.keyId()); } - public String apnsBundleIdValue() { return firstText(apnsBundleId, apns == null ? null : apns.bundleId()); } - public String apnsKeyPathValue() { return firstText(apnsKeyPath, apns == null ? null : apns.keyPath()); } - public boolean apnsSandboxValue() { - if (apnsSandbox != null) { - return apnsSandbox; - } - return apns != null && Boolean.TRUE.equals(apns.sandbox()); - } - public String fcmServiceAccountJsonValue() { - return firstText(fcmServiceAccountJson, fcm == null ? null : fcm.serviceAccountJson()); - } - - private static String firstText(String first, String second) { - if (first != null) { - return first; - } - return second; - } } - - public record PushVendorConfig( - String appId, - String appKey, - String appSecret, - String masterSecret, - String clientId, - String clientSecret, - String teamId, - String keyId, - String bundleId, - String keyPath, - Boolean sandbox, - String serviceAccountJson, - String channelId, - String category, - String receiptId - ) {} } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java b/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java index 256381b..d908df0 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/controller/SdkConfigController.java @@ -56,22 +56,22 @@ public class SdkConfigController { @RequestParam(required = false, defaultValue = "ANDROID") FeatureServiceEntity.Platform platform) { AppEntity app = sdkAppProvisioningService.resolveApp(appKey); - List features = featureServiceRepository.findByAppId(app.getAppKey()); + List features = featureServiceRepository.findByAppKey(app.getAppKey()); boolean imEnabled = features.stream() .anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.IM && f.isEnabled()); boolean pushEnabled = features.stream() .anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.PUSH && f.isEnabled()); JsonNode updateConfig = featureServiceRepository - .findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE) + .findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE) .map(feature -> parseConfig(feature.getConfig())) .orElseGet(objectMapper::createObjectNode); JsonNode pushConfig = featureServiceRepository - .findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH) + .findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH) .map(feature -> parseConfig(feature.getConfig())) .orElseGet(objectMapper::createObjectNode); boolean updateEnabled = featureServiceRepository - .findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE) + .findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE) .map(FeatureServiceEntity::isEnabled) .orElse(false); diff --git a/tenant-service/src/main/java/com/xuqm/tenant/entity/FeatureServiceEntity.java b/tenant-service/src/main/java/com/xuqm/tenant/entity/FeatureServiceEntity.java index 551169d..a42086a 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/entity/FeatureServiceEntity.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/entity/FeatureServiceEntity.java @@ -26,7 +26,7 @@ public class FeatureServiceEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 16) @@ -52,8 +52,8 @@ public class FeatureServiceEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public Platform getPlatform() { return platform; } public void setPlatform(Platform platform) { this.platform = platform; } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java b/tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java index a4a75c1..ddd9b96 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java @@ -19,7 +19,7 @@ public class ServiceActivationRequestEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 16) @@ -47,14 +47,11 @@ public class ServiceActivationRequestEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + @JsonProperty("appKey") + public String getAppKey() { return appKey; } @JsonProperty("appKey") - public String getAppKey() { return appId; } - - @JsonProperty("appKey") - public void setAppKey(String appKey) { this.appId = appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public FeatureServiceEntity.Platform getPlatform() { return platform; } public void setPlatform(FeatureServiceEntity.Platform platform) { this.platform = platform; } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/repository/FeatureServiceRepository.java b/tenant-service/src/main/java/com/xuqm/tenant/repository/FeatureServiceRepository.java index 499d9f8..3b571c9 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/repository/FeatureServiceRepository.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/repository/FeatureServiceRepository.java @@ -7,10 +7,10 @@ import java.util.List; import java.util.Optional; public interface FeatureServiceRepository extends JpaRepository { - List findByAppId(String appId); - List findByAppIdAndServiceType(String appId, FeatureServiceEntity.ServiceType serviceType); - Optional findByAppIdAndPlatformAndServiceType( - String appId, + List findByAppKey(String appKey); + List findByAppKeyAndServiceType(String appKey, FeatureServiceEntity.ServiceType serviceType); + Optional findByAppKeyAndPlatformAndServiceType( + String appKey, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType); diff --git a/tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java b/tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java index a79f8a4..02ba937 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java @@ -12,13 +12,13 @@ import java.util.Optional; public interface ServiceActivationRequestRepository extends JpaRepository { - Optional findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc( - String appId, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType); + Optional findFirstByAppKeyAndPlatformAndServiceTypeOrderByCreatedAtDesc( + String appKey, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType); - Optional findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc( - String appId, FeatureServiceEntity.ServiceType serviceType); + Optional findFirstByAppKeyAndServiceTypeOrderByCreatedAtDesc( + String appKey, FeatureServiceEntity.ServiceType serviceType); - List findByAppIdOrderByCreatedAtDesc(String appId); + List findByAppKeyOrderByCreatedAtDesc(String appKey); Page findByStatusOrderByCreatedAtDesc(Status status, Pageable pageable); diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/DashboardService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/DashboardService.java index 8293fc6..27f764a 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/DashboardService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/DashboardService.java @@ -33,7 +33,7 @@ public class DashboardService { List apps = appRepository.findByTenantId(tenantId); long serviceCount = 0; for (AppEntity app : apps) { - serviceCount += featureServiceRepository.findByAppId(app.getId()).stream() + serviceCount += featureServiceRepository.findByAppKey(app.getId()).stream() .filter(FeatureServiceEntity::isEnabled) .count(); } diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java index 09d9d99..fce7f2a 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java @@ -38,7 +38,7 @@ public class FeatureServiceManager { } public List listByApp(String appKey) { - List services = repository.findByAppId(appKey); + List services = repository.findByAppKey(appKey); if (services.isEmpty()) { return services; } @@ -68,7 +68,7 @@ public class FeatureServiceManager { String applyReason) { if (isAppWideService(serviceType)) { - requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(appKey, serviceType) + requestRepository.findFirstByAppKeyAndServiceTypeOrderByCreatedAtDesc(appKey, serviceType) .ifPresent(req -> { if (req.getStatus() == Status.PENDING) { throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理"); @@ -78,7 +78,7 @@ public class FeatureServiceManager { ServiceActivationRequestEntity req = new ServiceActivationRequestEntity(); req.setId(UUID.randomUUID().toString()); - req.setAppId(appKey); + req.setAppKey(appKey); req.setPlatform(platform); req.setServiceType(serviceType); req.setStatus(Status.PENDING); @@ -94,7 +94,7 @@ public class FeatureServiceManager { public FeatureServiceEntity disable(String appKey, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType) { if (isAppWideService(serviceType)) { - List services = repository.findByAppIdAndServiceType(appKey, serviceType); + List services = repository.findByAppKeyAndServiceType(appKey, serviceType); if (services.isEmpty()) { throw new BusinessException(404, "服务未开通"); } @@ -104,7 +104,7 @@ public class FeatureServiceManager { } FeatureServiceEntity entity = repository - .findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType) + .findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType) .orElseThrow(() -> new BusinessException(404, "服务未开通")); entity.setEnabled(false); return repository.save(entity); @@ -126,17 +126,17 @@ public class FeatureServiceManager { requestRepository.save(req); // Normalize to appKey so SdkConfigController queries are consistent - String normalizedAppId = appRepository.findById(req.getAppId()) + String normalizedAppId = appRepository.findById(req.getAppKey()) .map(app -> app.getAppKey()) - .orElse(req.getAppId()); + .orElse(req.getAppKey()); if (isAppWideService(req.getServiceType())) { - List services = repository.findByAppIdAndServiceType(normalizedAppId, req.getServiceType()); + List services = repository.findByAppKeyAndServiceType(normalizedAppId, req.getServiceType()); if (services.isEmpty()) { for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) { FeatureServiceEntity created = new FeatureServiceEntity(); created.setId(UUID.randomUUID().toString()); - created.setAppId(normalizedAppId); + created.setAppKey(normalizedAppId); created.setPlatform(platform); created.setServiceType(req.getServiceType()); created.setEnabled(true); @@ -151,11 +151,11 @@ public class FeatureServiceManager { } FeatureServiceEntity entity = repository - .findByAppIdAndPlatformAndServiceType(normalizedAppId, req.getPlatform(), req.getServiceType()) + .findByAppKeyAndPlatformAndServiceType(normalizedAppId, req.getPlatform(), req.getServiceType()) .orElseGet(() -> { FeatureServiceEntity e = new FeatureServiceEntity(); e.setId(UUID.randomUUID().toString()); - e.setAppId(normalizedAppId); + e.setAppKey(normalizedAppId); e.setPlatform(req.getPlatform()); e.setServiceType(req.getServiceType()); e.setCreatedAt(LocalDateTime.now()); @@ -183,18 +183,18 @@ public class FeatureServiceManager { } public List listRequestsByApp(String appKey) { - return requestRepository.findByAppIdOrderByCreatedAtDesc(appKey); + return requestRepository.findByAppKeyOrderByCreatedAtDesc(appKey); } public FeatureServiceEntity getOrFail(String appKey, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType) { if (serviceType == FeatureServiceEntity.ServiceType.IM) { - return repository.findByAppIdAndServiceType(appKey, serviceType) + return repository.findByAppKeyAndServiceType(appKey, serviceType) .stream() .findFirst() .orElseThrow(() -> new BusinessException(404, "服务未配置")); } - return repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType) + return repository.findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType) .orElseThrow(() -> new BusinessException(404, "服务未配置")); } @@ -204,7 +204,7 @@ public class FeatureServiceManager { FeatureServiceEntity.ServiceType serviceType, String config) { if (serviceType == FeatureServiceEntity.ServiceType.IM) { - List services = repository.findByAppIdAndServiceType(appKey, serviceType); + List services = repository.findByAppKeyAndServiceType(appKey, serviceType); if (services.isEmpty()) { throw new BusinessException(404, "服务未配置"); } @@ -477,127 +477,25 @@ public class FeatureServiceManager { public String buildPushConfig(String appKey, FeatureServiceEntity.Platform platform, - String huaweiAppId, - String huaweiAppSecret, - String huaweiCategory, - String xiaomiAppId, - String xiaomiAppKey, - String xiaomiAppSecret, - String xiaomiChannelId, - String oppoAppId, - String oppoAppKey, - String oppoMasterSecret, - String oppoChannelId, - String vivoAppId, - String vivoAppKey, - String vivoAppSecret, - String vivoCategory, - String vivoReceiptId, - String honorAppId, - String honorClientId, - String honorClientSecret, - String harmonyAppId, - String harmonyAppSecret, - String apnsTeamId, - String apnsKeyId, - String apnsBundleId, - String apnsKeyPath, - boolean apnsSandbox, - String fcmServiceAccountJson, - JsonNode channels, - JsonNode routing) { - ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.PUSH).deepCopy(); - ensureObjectNode(node, "huawei"); - ensureObjectNode(node, "xiaomi"); - ensureObjectNode(node, "oppo"); - ensureObjectNode(node, "vivo"); - ensureObjectNode(node, "honor"); - ensureObjectNode(node, "harmony"); - ensureObjectNode(node, "apns"); - ensureObjectNode(node, "fcm"); - - putText(node.with("huawei"), "appId", huaweiAppId); - putText(node.with("huawei"), "appSecret", huaweiAppSecret); - putText(node.with("huawei"), "category", huaweiCategory); - - putText(node.with("xiaomi"), "appId", xiaomiAppId); - putText(node.with("xiaomi"), "appKey", xiaomiAppKey); - putText(node.with("xiaomi"), "appSecret", xiaomiAppSecret); - putText(node.with("xiaomi"), "channelId", xiaomiChannelId); - - putText(node.with("oppo"), "appId", oppoAppId); - putText(node.with("oppo"), "appKey", oppoAppKey); - putText(node.with("oppo"), "masterSecret", oppoMasterSecret); - putText(node.with("oppo"), "channelId", oppoChannelId); - - putText(node.with("vivo"), "appId", vivoAppId); - putText(node.with("vivo"), "appKey", vivoAppKey); - putText(node.with("vivo"), "appSecret", vivoAppSecret); - putText(node.with("vivo"), "category", vivoCategory); - putText(node.with("vivo"), "receiptId", vivoReceiptId); - - putText(node.with("honor"), "appId", honorAppId); - putText(node.with("honor"), "clientId", honorClientId); - putText(node.with("honor"), "clientSecret", honorClientSecret); - - putText(node.with("harmony"), "appId", harmonyAppId); - putText(node.with("harmony"), "appSecret", harmonyAppSecret); - - putText(node.with("apns"), "teamId", apnsTeamId); - putText(node.with("apns"), "keyId", apnsKeyId); - putText(node.with("apns"), "bundleId", apnsBundleId); - putText(node.with("apns"), "keyPath", apnsKeyPath); - node.with("apns").put("sandbox", apnsSandbox); - - putText(node.with("fcm"), "serviceAccountJson", fcmServiceAccountJson); - if (channels != null && channels.isArray()) { - node.set("channels", channels); - } else if (!node.has("channels")) { - node.set("channels", defaultPushChannels()); + JsonNode pushConfig) { + ObjectNode root = objectMapper.createObjectNode(); + root.put("schemaVersion", 2); + root.put("updatedAt", java.time.LocalDateTime.now().toString()); + ObjectNode vendors = objectMapper.createObjectNode(); + ArrayNode profiles = objectMapper.createArrayNode(); + if (pushConfig != null && pushConfig.isObject()) { + JsonNode inputVendors = pushConfig.path("vendors"); + if (inputVendors != null && inputVendors.isObject()) { + vendors.setAll((ObjectNode) inputVendors); + } + JsonNode inputProfiles = pushConfig.path("profiles"); + if (inputProfiles != null && inputProfiles.isArray()) { + inputProfiles.forEach(profile -> profiles.add(profile.deepCopy())); + } } - if (routing != null && routing.isObject()) { - node.set("routing", routing); - } else if (!node.has("routing")) { - node.set("routing", defaultPushRouting()); - } - return node.toString(); - } - - private ArrayNode defaultPushChannels() { - ArrayNode channels = objectMapper.createArrayNode(); - channels.add(defaultPushChannel("im_message", "xuqm_im_message", "聊天消息", "单聊、群聊和好友消息", "HIGH")); - channels.add(defaultPushChannel("system_notice", "xuqm_system_notice", "系统通知", "系统通知和业务提醒", "DEFAULT")); - return channels; - } - - private ObjectNode defaultPushChannel(String key, String channelId, String name, String description, String importance) { - ObjectNode channel = objectMapper.createObjectNode(); - channel.put("key", key); - channel.put("channelId", channelId); - channel.put("version", 1); - channel.put("name", name); - channel.put("description", description); - channel.put("importance", importance); - channel.put("sound", true); - channel.put("vibration", true); - channel.put("badge", true); - return channel; - } - - private ObjectNode defaultPushRouting() { - ObjectNode routing = objectMapper.createObjectNode(); - routing.set("IM_MESSAGE", defaultPushRoute("im_message", "MESSAGE", "HIGH")); - routing.set("FRIEND_REQUEST", defaultPushRoute("im_message", "SOCIAL", "HIGH")); - routing.set("SYSTEM_NOTICE", defaultPushRoute("system_notice", "SYSTEM", "DEFAULT")); - return routing; - } - - private ObjectNode defaultPushRoute(String channel, String category, String priority) { - ObjectNode route = objectMapper.createObjectNode(); - route.put("channel", channel); - route.put("category", category); - route.put("priority", priority); - return route; + root.set("vendors", vendors); + root.set("profiles", profiles); + return root.toString(); } @Transactional @@ -649,12 +547,12 @@ public class FeatureServiceManager { FeatureServiceEntity.ServiceType serviceType) { FeatureServiceEntity entity; if (serviceType == FeatureServiceEntity.ServiceType.IM) { - entity = repository.findByAppIdAndServiceType(appKey, serviceType) + entity = repository.findByAppKeyAndServiceType(appKey, serviceType) .stream() .findFirst() .orElse(null); } else { - entity = repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType) + entity = repository.findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType) .orElse(null); } if (entity == null || entity.getConfig() == null || entity.getConfig().isBlank()) { diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/OpsService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/OpsService.java index f6c7352..44d85e9 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/OpsService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/OpsService.java @@ -80,7 +80,7 @@ public class OpsService { List apps = appRepository.findByTenantId(tenantId); long subAccountCount = tenantRepository.countByParentId(tenantId); long activeServiceCount = apps.stream() - .flatMap(app -> featureServiceRepository.findByAppId(app.getAppKey()).stream()) + .flatMap(app -> featureServiceRepository.findByAppKey(app.getAppKey()).stream()) .filter(FeatureServiceEntity::isEnabled) .count(); @@ -207,7 +207,7 @@ public class OpsService { public Map getAppDetail(String appKey) { AppEntity app = appRepository.findByAppKey(appKey) .orElseThrow(() -> new IllegalArgumentException("应用不存在")); - List services = featureServiceRepository.findByAppId(app.getAppKey()); + List services = featureServiceRepository.findByAppKey(app.getAppKey()); long enabledCount = services.stream().filter(FeatureServiceEntity::isEnabled).count(); Map result = new LinkedHashMap<>(); result.put("app", app); @@ -221,7 +221,7 @@ public class OpsService { public List listAppServices(String appKey) { appRepository.findByAppKey(appKey) .orElseThrow(() -> new IllegalArgumentException("应用不存在")); - return featureServiceRepository.findByAppId(appKey); + return featureServiceRepository.findByAppKey(appKey); } public Page listOperationLogs(int page, int size) { diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/SdkAppProvisioningService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/SdkAppProvisioningService.java index bbb38ae..a891ad4 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/SdkAppProvisioningService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/SdkAppProvisioningService.java @@ -117,13 +117,13 @@ public class SdkAppProvisioningService { } private void ensureFeatureDefaults(AppEntity app) { - featureServiceRepository.findByAppIdAndServiceType(app.getAppKey(), FeatureServiceEntity.ServiceType.IM) + featureServiceRepository.findByAppKeyAndServiceType(app.getAppKey(), FeatureServiceEntity.ServiceType.IM) .stream() .findFirst() .orElseGet(() -> { FeatureServiceEntity feature = new FeatureServiceEntity(); feature.setId(UUID.randomUUID().toString()); - feature.setAppId(app.getAppKey()); + feature.setAppKey(app.getAppKey()); feature.setPlatform(FeatureServiceEntity.Platform.ANDROID); feature.setServiceType(FeatureServiceEntity.ServiceType.IM); feature.setEnabled(true); @@ -141,11 +141,11 @@ public class SdkAppProvisioningService { for (FeatureServiceEntity.ServiceType serviceType : List.of( FeatureServiceEntity.ServiceType.PUSH, FeatureServiceEntity.ServiceType.UPDATE)) { - featureServiceRepository.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, serviceType) + featureServiceRepository.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, serviceType) .orElseGet(() -> { FeatureServiceEntity feature = new FeatureServiceEntity(); feature.setId(UUID.randomUUID().toString()); - feature.setAppId(app.getAppKey()); + feature.setAppKey(app.getAppKey()); feature.setPlatform(platform); feature.setServiceType(serviceType); feature.setEnabled(true); diff --git a/tenant-service/src/main/resources/application.yml b/tenant-service/src/main/resources/application.yml index ca9cb49..e6c1439 100644 --- a/tenant-service/src/main/resources/application.yml +++ b/tenant-service/src/main/resources/application.yml @@ -88,3 +88,4 @@ sdk: im-ws-url: ${SDK_IM_WS_URL:wss://im.dev.xuqinmin.com/ws/im} file-service-url: ${SDK_FILE_SERVICE_URL:https://file.dev.xuqinmin.com} im-api-url: ${SDK_IM_API_URL:https://im.dev.xuqinmin.com} + im-platform-events-user-prefix: ${SDK_IM_PLATFORM_EVENTS_USER_PREFIX:platform-events:} diff --git a/update-service/src/main/java/com/xuqm/update/controller/AppVersionController.java b/update-service/src/main/java/com/xuqm/update/controller/AppVersionController.java index f336f5c..556cbec 100644 --- a/update-service/src/main/java/com/xuqm/update/controller/AppVersionController.java +++ b/update-service/src/main/java/com/xuqm/update/controller/AppVersionController.java @@ -58,10 +58,10 @@ public class AppVersionController { } Optional latest = versionRepository - .findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc( + .findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc( appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode); Optional forcedHigher = versionRepository - .findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc( + .findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc( appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode); if (latest.isEmpty()) { @@ -146,7 +146,7 @@ public class AppVersionController { } AppVersionEntity entity = new AppVersionEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setPlatform(platform); entity.setVersionName(resolvedVersionName); entity.setVersionCode(resolvedVersionCode); @@ -189,7 +189,7 @@ public class AppVersionController { } AppVersionEntity saved = versionRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "APP_VERSION", saved.getId(), "UPLOAD", @@ -246,7 +246,7 @@ public class AppVersionController { entity.setGrayMemberIds(null); AppVersionEntity saved = versionRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "APP_VERSION", saved.getId(), publishAction(previousStatus, saved.getPublishStatus(), publishImmediately), @@ -273,7 +273,7 @@ public class AppVersionController { entity.setPublishStatus(AppVersionEntity.PublishStatus.DEPRECATED); AppVersionEntity saved = versionRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "APP_VERSION", saved.getId(), "UNPUBLISH", @@ -290,7 +290,7 @@ public class AppVersionController { @PathVariable String id, @RequestBody Map body) throws Exception { AppVersionEntity entity = versionRepository.findById(id).orElseThrow(); - if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppId())) { + if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppKey())) { throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布"); } boolean enabled = Boolean.TRUE.equals(body.get("enabled")); @@ -305,7 +305,7 @@ public class AppVersionController { String selectionSource = body.get("selectionSource") == null ? "LOCAL" : body.get("selectionSource").toString().trim().toUpperCase(); if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) { - memberIds = publishConfigService.resolveGrayMembers(entity.getAppId(), body); + memberIds = publishConfigService.resolveGrayMembers(entity.getAppKey(), body); } entity.setGrayMode("MEMBERS"); entity.setGrayMemberIds(toJson(memberIds)); @@ -318,7 +318,7 @@ public class AppVersionController { entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED); AppVersionEntity saved = versionRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "APP_VERSION", saved.getId(), "GRAY_UPDATE", @@ -336,7 +336,7 @@ public class AppVersionController { public ResponseEntity>> list( @RequestParam String appKey, @RequestParam AppVersionEntity.Platform platform) { return ResponseEntity.ok(ApiResponse.success( - versionRepository.findByAppIdAndPlatformOrderByVersionCodeDesc(appKey, platform))); + versionRepository.findByAppKeyAndPlatformOrderByVersionCodeDesc(appKey, platform))); } private String publishAction(AppVersionEntity.PublishStatus previousStatus, diff --git a/update-service/src/main/java/com/xuqm/update/controller/RnBundleController.java b/update-service/src/main/java/com/xuqm/update/controller/RnBundleController.java index 07b6a04..d0164d5 100644 --- a/update-service/src/main/java/com/xuqm/update/controller/RnBundleController.java +++ b/update-service/src/main/java/com/xuqm/update/controller/RnBundleController.java @@ -56,7 +56,7 @@ public class RnBundleController { RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase()); Optional latest = bundleRepository - .findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc( + .findTopByAppKeyAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc( appKey, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED); if (latest.isEmpty()) { @@ -115,7 +115,7 @@ public class RnBundleController { RnBundleEntity entity = new RnBundleEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setModuleId(resolvedModuleId); entity.setPlatform(resolvedPlatform); entity.setVersion(resolvedVersion); @@ -132,7 +132,7 @@ public class RnBundleController { entity.setCreatedAt(LocalDateTime.now()); RnBundleEntity saved = bundleRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "RN_BUNDLE", saved.getId(), "UPLOAD", @@ -160,11 +160,11 @@ public class RnBundleController { List result; if (moduleId != null && platform != null) { RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase()); - result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p); + result = bundleRepository.findByAppKeyAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p); } else if (moduleId != null) { - result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appKey, moduleId); + result = bundleRepository.findByAppKeyAndModuleIdOrderByCreatedAtDesc(appKey, moduleId); } else { - result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appKey); + result = bundleRepository.findByAppKeyOrderByCreatedAtDesc(appKey); } return ResponseEntity.ok(ApiResponse.success(result)); } @@ -194,7 +194,7 @@ public class RnBundleController { entity.setGrayMemberIds(null); RnBundleEntity saved = bundleRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "RN_BUNDLE", saved.getId(), publishImmediately && (scheduledPublishAt == null || scheduledPublishAt.isBlank()) ? "PUBLISH" : "SCHEDULE_PUBLISH", @@ -220,7 +220,7 @@ public class RnBundleController { entity.setPublishStatus(RnBundleEntity.PublishStatus.DEPRECATED); RnBundleEntity saved = bundleRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "RN_BUNDLE", saved.getId(), "UNPUBLISH", @@ -237,7 +237,7 @@ public class RnBundleController { @PathVariable String id, @RequestBody Map body) throws Exception { RnBundleEntity entity = bundleRepository.findById(id).orElseThrow(); - if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppId())) { + if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppKey())) { throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布"); } boolean enabled = Boolean.TRUE.equals(body.get("enabled")); @@ -252,7 +252,7 @@ public class RnBundleController { String selectionSource = body.get("selectionSource") == null ? "LOCAL" : body.get("selectionSource").toString().trim().toUpperCase(); if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) { - memberIds = publishConfigService.resolveGrayMembers(entity.getAppId(), body); + memberIds = publishConfigService.resolveGrayMembers(entity.getAppKey(), body); } entity.setGrayMode("MEMBERS"); entity.setGrayMemberIds(toJson(memberIds)); @@ -265,7 +265,7 @@ public class RnBundleController { entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED); RnBundleEntity saved = bundleRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "RN_BUNDLE", saved.getId(), "GRAY_UPDATE", diff --git a/update-service/src/main/java/com/xuqm/update/controller/UnifiedReleaseController.java b/update-service/src/main/java/com/xuqm/update/controller/UnifiedReleaseController.java index d0cd03a..1d4c73e 100644 --- a/update-service/src/main/java/com/xuqm/update/controller/UnifiedReleaseController.java +++ b/update-service/src/main/java/com/xuqm/update/controller/UnifiedReleaseController.java @@ -69,7 +69,7 @@ public class UnifiedReleaseController { } AppVersionEntity entity = new AppVersionEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setPlatform(item.platform()); entity.setVersionName(item.versionName()); entity.setVersionCode(item.versionCode()); @@ -90,7 +90,7 @@ public class UnifiedReleaseController { } AppVersionEntity saved = appVersionRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "APP_VERSION", saved.getId(), "UPLOAD", @@ -117,7 +117,7 @@ public class UnifiedReleaseController { RnBundleEntity entity = new RnBundleEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setModuleId(item.moduleId()); entity.setPlatform(item.platform()); entity.setVersion(item.version()); @@ -130,7 +130,7 @@ public class UnifiedReleaseController { entity.setCreatedAt(LocalDateTime.now()); RnBundleEntity saved = rnBundleRepository.save(entity); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "RN_BUNDLE", saved.getId(), "UPLOAD", diff --git a/update-service/src/main/java/com/xuqm/update/entity/AppGrayMemberEntity.java b/update-service/src/main/java/com/xuqm/update/entity/AppGrayMemberEntity.java index 5fdaf7b..0a8dc2b 100644 --- a/update-service/src/main/java/com/xuqm/update/entity/AppGrayMemberEntity.java +++ b/update-service/src/main/java/com/xuqm/update/entity/AppGrayMemberEntity.java @@ -9,7 +9,7 @@ import java.time.LocalDateTime; @Entity @Table(name = "update_gray_member", uniqueConstraints = { - @jakarta.persistence.UniqueConstraint(columnNames = {"appId", "groupName", "userId"}) + @jakarta.persistence.UniqueConstraint(columnNames = {"appKey", "groupName", "userId"}) }) public class AppGrayMemberEntity { @@ -17,7 +17,7 @@ public class AppGrayMemberEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(length = 64) private String groupName; @@ -37,8 +37,8 @@ public class AppGrayMemberEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } diff --git a/update-service/src/main/java/com/xuqm/update/entity/AppPublishConfigEntity.java b/update-service/src/main/java/com/xuqm/update/entity/AppPublishConfigEntity.java index 963dcb1..16eaf89 100644 --- a/update-service/src/main/java/com/xuqm/update/entity/AppPublishConfigEntity.java +++ b/update-service/src/main/java/com/xuqm/update/entity/AppPublishConfigEntity.java @@ -9,7 +9,7 @@ import java.time.LocalDateTime; @Entity @Table(name = "update_publish_config", uniqueConstraints = { - @jakarta.persistence.UniqueConstraint(columnNames = {"appId"}) + @jakarta.persistence.UniqueConstraint(columnNames = {"appKey"}) }) public class AppPublishConfigEntity { @@ -17,7 +17,7 @@ public class AppPublishConfigEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(columnDefinition = "TEXT") private String configJson; @@ -28,8 +28,8 @@ public class AppPublishConfigEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getConfigJson() { return configJson; } public void setConfigJson(String configJson) { this.configJson = configJson; } diff --git a/update-service/src/main/java/com/xuqm/update/entity/AppStoreConfigEntity.java b/update-service/src/main/java/com/xuqm/update/entity/AppStoreConfigEntity.java index 9e4b514..0b7c129 100644 --- a/update-service/src/main/java/com/xuqm/update/entity/AppStoreConfigEntity.java +++ b/update-service/src/main/java/com/xuqm/update/entity/AppStoreConfigEntity.java @@ -5,7 +5,7 @@ import java.time.LocalDateTime; @Entity @Table(name = "update_store_config", uniqueConstraints = { - @UniqueConstraint(columnNames = {"appId", "storeType"}) + @UniqueConstraint(columnNames = {"appKey", "storeType"}) }) public class AppStoreConfigEntity { @@ -27,7 +27,7 @@ public class AppStoreConfigEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 16) @@ -58,8 +58,8 @@ public class AppStoreConfigEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public StoreType getStoreType() { return storeType; } public void setStoreType(StoreType storeType) { this.storeType = storeType; } diff --git a/update-service/src/main/java/com/xuqm/update/entity/AppVersionEntity.java b/update-service/src/main/java/com/xuqm/update/entity/AppVersionEntity.java index 3c9983b..f3889ff 100644 --- a/update-service/src/main/java/com/xuqm/update/entity/AppVersionEntity.java +++ b/update-service/src/main/java/com/xuqm/update/entity/AppVersionEntity.java @@ -15,13 +15,13 @@ public class AppVersionEntity { public enum Platform { ANDROID, IOS, HARMONY } public enum PublishStatus { DRAFT, PUBLISHED, DEPRECATED } /** Per-store review state used in storeReviewStatus JSON values. */ - public enum StoreReviewState { PENDING, UNDER_REVIEW, APPROVED, REJECTED } + public enum StoreReviewState { PENDING, SUBMITTING, UNDER_REVIEW, APPROVED, REJECTED } @Id private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 16) @@ -107,8 +107,8 @@ public class AppVersionEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public Platform getPlatform() { return platform; } public void setPlatform(Platform platform) { this.platform = platform; } diff --git a/update-service/src/main/java/com/xuqm/update/entity/RnBundleEntity.java b/update-service/src/main/java/com/xuqm/update/entity/RnBundleEntity.java index e897356..f2ce77b 100644 --- a/update-service/src/main/java/com/xuqm/update/entity/RnBundleEntity.java +++ b/update-service/src/main/java/com/xuqm/update/entity/RnBundleEntity.java @@ -19,7 +19,7 @@ public class RnBundleEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 64) private String moduleId; @@ -73,8 +73,8 @@ public class RnBundleEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getModuleId() { return moduleId; } public void setModuleId(String moduleId) { this.moduleId = moduleId; } diff --git a/update-service/src/main/java/com/xuqm/update/entity/UpdateOperationLogEntity.java b/update-service/src/main/java/com/xuqm/update/entity/UpdateOperationLogEntity.java index a3c6265..4bac39a 100644 --- a/update-service/src/main/java/com/xuqm/update/entity/UpdateOperationLogEntity.java +++ b/update-service/src/main/java/com/xuqm/update/entity/UpdateOperationLogEntity.java @@ -15,7 +15,7 @@ public class UpdateOperationLogEntity { private String id; @Column(nullable = false, length = 64) - private String appId; + private String appKey; @Column(nullable = false, length = 32) private String resourceType; @@ -41,8 +41,8 @@ public class UpdateOperationLogEntity { public String getId() { return id; } public void setId(String id) { this.id = id; } - public String getAppId() { return appId; } - public void setAppId(String appId) { this.appId = appId; } + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } public String getResourceType() { return resourceType; } public void setResourceType(String resourceType) { this.resourceType = resourceType; } diff --git a/update-service/src/main/java/com/xuqm/update/repository/AppGrayMemberRepository.java b/update-service/src/main/java/com/xuqm/update/repository/AppGrayMemberRepository.java index 6b226a3..f5a9b3b 100644 --- a/update-service/src/main/java/com/xuqm/update/repository/AppGrayMemberRepository.java +++ b/update-service/src/main/java/com/xuqm/update/repository/AppGrayMemberRepository.java @@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface AppGrayMemberRepository extends JpaRepository { - List findByAppIdOrderByGroupNameAscNameAscUserIdAsc(String appId); + List findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(String appKey); } diff --git a/update-service/src/main/java/com/xuqm/update/repository/AppPublishConfigRepository.java b/update-service/src/main/java/com/xuqm/update/repository/AppPublishConfigRepository.java index e6ee9cc..9544176 100644 --- a/update-service/src/main/java/com/xuqm/update/repository/AppPublishConfigRepository.java +++ b/update-service/src/main/java/com/xuqm/update/repository/AppPublishConfigRepository.java @@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface AppPublishConfigRepository extends JpaRepository { - Optional findByAppId(String appId); + Optional findByAppKey(String appKey); } diff --git a/update-service/src/main/java/com/xuqm/update/repository/AppStoreConfigRepository.java b/update-service/src/main/java/com/xuqm/update/repository/AppStoreConfigRepository.java index 550c688..1fb0e0c 100644 --- a/update-service/src/main/java/com/xuqm/update/repository/AppStoreConfigRepository.java +++ b/update-service/src/main/java/com/xuqm/update/repository/AppStoreConfigRepository.java @@ -8,9 +8,9 @@ import java.util.Optional; public interface AppStoreConfigRepository extends JpaRepository { - List findByAppId(String appId); + List findByAppKey(String appKey); - List findByAppIdAndEnabled(String appId, boolean enabled); + List findByAppKeyAndEnabled(String appKey, boolean enabled); - Optional findByAppIdAndStoreType(String appId, AppStoreConfigEntity.StoreType storeType); + Optional findByAppKeyAndStoreType(String appKey, AppStoreConfigEntity.StoreType storeType); } diff --git a/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java b/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java index aefa18c..393f77c 100644 --- a/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java +++ b/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java @@ -8,18 +8,18 @@ import java.util.List; import java.util.Optional; public interface AppVersionRepository extends JpaRepository { - List findByAppIdAndPlatformOrderByVersionCodeDesc( - String appId, AppVersionEntity.Platform platform); - Optional findTopByAppIdAndPlatformAndPublishStatusOrderByVersionCodeDesc( - String appId, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status); - Optional findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc( - String appId, + List findByAppKeyAndPlatformOrderByVersionCodeDesc( + String appKey, AppVersionEntity.Platform platform); + Optional findTopByAppKeyAndPlatformAndPublishStatusOrderByVersionCodeDesc( + String appKey, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status); + Optional findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc( + String appKey, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status, int versionCode); - Optional findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc( - String appId, + Optional findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc( + String appKey, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status, int versionCode); diff --git a/update-service/src/main/java/com/xuqm/update/repository/RnBundleRepository.java b/update-service/src/main/java/com/xuqm/update/repository/RnBundleRepository.java index 796a6b7..fe8eeb5 100644 --- a/update-service/src/main/java/com/xuqm/update/repository/RnBundleRepository.java +++ b/update-service/src/main/java/com/xuqm/update/repository/RnBundleRepository.java @@ -8,12 +8,12 @@ import java.util.Optional; import java.time.LocalDateTime; public interface RnBundleRepository extends JpaRepository { - List findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc( - String appId, String moduleId, RnBundleEntity.Platform platform); - List findByAppIdAndModuleIdOrderByCreatedAtDesc(String appId, String moduleId); - List findByAppIdOrderByCreatedAtDesc(String appId); - Optional findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc( - String appId, String moduleId, RnBundleEntity.Platform platform, RnBundleEntity.PublishStatus status); + List findByAppKeyAndModuleIdAndPlatformOrderByCreatedAtDesc( + String appKey, String moduleId, RnBundleEntity.Platform platform); + List findByAppKeyAndModuleIdOrderByCreatedAtDesc(String appKey, String moduleId); + List findByAppKeyOrderByCreatedAtDesc(String appKey); + Optional findTopByAppKeyAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc( + String appKey, String moduleId, RnBundleEntity.Platform platform, RnBundleEntity.PublishStatus status); List findByPublishStatusAndScheduledPublishAtBefore( RnBundleEntity.PublishStatus status, LocalDateTime before); diff --git a/update-service/src/main/java/com/xuqm/update/repository/UpdateOperationLogRepository.java b/update-service/src/main/java/com/xuqm/update/repository/UpdateOperationLogRepository.java index 94dc340..23c03ca 100644 --- a/update-service/src/main/java/com/xuqm/update/repository/UpdateOperationLogRepository.java +++ b/update-service/src/main/java/com/xuqm/update/repository/UpdateOperationLogRepository.java @@ -7,5 +7,5 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface UpdateOperationLogRepository extends JpaRepository { - List findByAppIdOrderByCreatedAtDesc(String appId, Pageable pageable); + List findByAppKeyOrderByCreatedAtDesc(String appKey, Pageable pageable); } diff --git a/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java b/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java index 6f74297..9387203 100644 --- a/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java +++ b/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java @@ -32,35 +32,38 @@ public class AppStoreService { private final AppVersionRepository versionRepo; private final RnBundleRepository rnBundleRepository; private final UpdateOperationLogService operationLogService; + private final StoreReviewImNotifier storeReviewImNotifier; public AppStoreService(AppStoreConfigRepository configRepo, AppVersionRepository versionRepo, RnBundleRepository rnBundleRepository, - UpdateOperationLogService operationLogService) { + UpdateOperationLogService operationLogService, + StoreReviewImNotifier storeReviewImNotifier) { this.configRepo = configRepo; this.versionRepo = versionRepo; this.rnBundleRepository = rnBundleRepository; this.operationLogService = operationLogService; + this.storeReviewImNotifier = storeReviewImNotifier; } // ── Store config CRUD ──────────────────────────────────────────────────── public List getConfigs(String appKey) { - return configRepo.findByAppId(appKey); + return configRepo.findByAppKey(appKey); } public AppStoreConfigEntity saveConfig(String appKey, AppStoreConfigEntity.StoreType storeType, String configJson, boolean enabled) { - boolean isCreate = configRepo.findByAppIdAndStoreType(appKey, storeType).isEmpty(); + boolean isCreate = configRepo.findByAppKeyAndStoreType(appKey, storeType).isEmpty(); AppStoreConfigEntity entity = configRepo - .findByAppIdAndStoreType(appKey, storeType) + .findByAppKeyAndStoreType(appKey, storeType) .orElseGet(AppStoreConfigEntity::new); if (entity.getId() == null) { entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setStoreType(storeType); } entity.setConfigJson(configJson); @@ -81,7 +84,7 @@ public class AppStoreService { } public void deleteConfig(String appKey, AppStoreConfigEntity.StoreType storeType) { - configRepo.findByAppIdAndStoreType(appKey, storeType).ifPresent(cfg -> { + configRepo.findByAppKeyAndStoreType(appKey, storeType).ifPresent(cfg -> { configRepo.delete(cfg); operationLogService.record( appKey, @@ -118,7 +121,13 @@ public class AppStoreService { Map reviewMap = new LinkedHashMap<>(); for (String store : storeTypes) { - reviewMap.put(store, reviewPayload(AppVersionEntity.StoreReviewState.PENDING.name(), null)); + reviewMap.put(store, reviewPayload( + AppVersionEntity.StoreReviewState.PENDING.name(), + null, + "QUEUED", + null, + null, + LocalDateTime.now().toString())); } v.setStoreSubmitTargets(mapper.writeValueAsString(storeTypes)); v.setStoreReviewStatus(mapper.writeValueAsString(reviewMap)); @@ -129,10 +138,10 @@ public class AppStoreService { } AppVersionEntity saved = versionRepo.save(v); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "APP_VERSION", saved.getId(), - "STORE_SUBMIT", + "STORE_SUBMIT_REQUEST", null, Map.of( "storeTypes", storeTypes, @@ -140,6 +149,16 @@ public class AppStoreService { "scheduledAt", saved.getStoreSubmitScheduledAt() == null ? "" : saved.getStoreSubmitScheduledAt().toString(), "autoPublishAfterReview", saved.isAutoPublishAfterReview() )); + storeReviewImNotifier.notifyStoreReviewChange( + saved.getAppKey(), + saved.getId(), + null, + AppVersionEntity.StoreReviewState.PENDING.name(), + null, + "QUEUED", + null, + saved.getPublishStatus().name(), + "store_submit_requested"); return saved; } @@ -152,7 +171,7 @@ public class AppStoreService { * Returns a map of storeType -> configJson (as parsed map, not raw string). */ public Map getStoreCredentials(String appKey) throws Exception { - List configs = configRepo.findByAppIdAndEnabled(appKey, true); + List configs = configRepo.findByAppKeyAndEnabled(appKey, true); Map result = new LinkedHashMap<>(); for (AppStoreConfigEntity cfg : configs) { if (cfg.getStoreType() == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) { @@ -167,7 +186,7 @@ public class AppStoreService { } public Map getReviewWebhookConfig(String appKey) throws Exception { - AppStoreConfigEntity cfg = configRepo.findByAppIdAndStoreType( + AppStoreConfigEntity cfg = configRepo.findByAppKeyAndStoreType( appKey, AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK).orElse(null); if (cfg == null || !cfg.isEnabled() || cfg.getConfigJson() == null || cfg.getConfigJson().isBlank()) { return Map.of(); @@ -179,7 +198,7 @@ public class AppStoreService { if (storeType == null || storeType == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) { return ""; } - return configRepo.findByAppIdAndStoreType(appKey, storeType) + return configRepo.findByAppKeyAndStoreType(appKey, storeType) .filter(AppStoreConfigEntity::isEnabled) .map(AppStoreConfigEntity::getConfigJson) .map(this::extractJumpUrl) @@ -205,14 +224,24 @@ public class AppStoreService { AppVersionEntity v = versionRepo.findById(versionId).orElseThrow(); Map reviewMap = parseReviewStatus(v.getStoreReviewStatus()); - reviewMap.put(storeType, reviewPayload(state.name(), reason)); + Map current = asReviewPayload(reviewMap.get(storeType)); + String batchId = readText(current.get("batchId")); + String submittedAt = readText(current.get("submittedAt")); + String stage = stageForFinalState(state); + reviewMap.put(storeType, reviewPayload( + state.name(), + reason, + stage, + batchId.isBlank() ? null : batchId, + submittedAt.isBlank() ? null : submittedAt, + LocalDateTime.now().toString())); v.setStoreReviewStatus(mapper.writeValueAsString(reviewMap)); if (v.isAutoPublishAfterReview() && allApproved(v, reviewMap)) { v.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED); log.info("Auto-published version {} after all stores approved", versionId); operationLogService.record( - v.getAppId(), + v.getAppKey(), "APP_VERSION", v.getId(), "AUTO_PUBLISH", @@ -225,7 +254,7 @@ public class AppStoreService { AppVersionEntity saved = versionRepo.save(v); operationLogService.record( - saved.getAppId(), + saved.getAppKey(), "APP_VERSION", saved.getId(), "STORE_REVIEW", @@ -233,9 +262,68 @@ public class AppStoreService { Map.of( "storeType", storeType, "reviewState", state.name(), + "stage", stage, + "batchId", batchId, "publishStatus", saved.getPublishStatus().name() )); sendWebhook(saved, storeType, state, reason); + storeReviewImNotifier.notifyStoreReviewChange( + saved.getAppKey(), + saved.getId(), + storeType, + state.name(), + reason, + stage, + batchId, + saved.getPublishStatus().name(), + "store_review_changed"); + return saved; + } + + public AppVersionEntity updateStoreSubmissionStage(String versionId, + String storeType, + String stage, + String reason, + String batchId) throws Exception { + AppVersionEntity v = versionRepo.findById(versionId).orElseThrow(); + + Map reviewMap = parseReviewStatus(v.getStoreReviewStatus()); + Map current = asReviewPayload(reviewMap.get(storeType)); + String submittedAt = readText(current.get("submittedAt")); + if (submittedAt.isBlank()) { + submittedAt = LocalDateTime.now().toString(); + } + reviewMap.put(storeType, reviewPayload( + AppVersionEntity.StoreReviewState.SUBMITTING.name(), + reason, + stage, + batchId, + submittedAt, + LocalDateTime.now().toString())); + v.setStoreReviewStatus(mapper.writeValueAsString(reviewMap)); + AppVersionEntity saved = versionRepo.save(v); + operationLogService.record( + saved.getAppKey(), + "APP_VERSION", + saved.getId(), + "STORE_SUBMIT_STAGE", + reason, + Map.of( + "storeType", storeType, + "stage", stage, + "batchId", batchId, + "reviewState", AppVersionEntity.StoreReviewState.SUBMITTING.name() + )); + storeReviewImNotifier.notifyStoreReviewChange( + saved.getAppKey(), + saved.getId(), + storeType, + AppVersionEntity.StoreReviewState.SUBMITTING.name(), + reason, + stage, + batchId, + saved.getPublishStatus().name(), + "store_submission_stage"); return saved; } @@ -252,7 +340,7 @@ public class AppStoreService { versionRepo.save(v); log.info("Scheduled publish executed for version {}", v.getId()); operationLogService.record( - v.getAppId(), + v.getAppKey(), "APP_VERSION", v.getId(), "SCHEDULE_PUBLISH", @@ -269,7 +357,7 @@ public class AppStoreService { rnBundleRepository.save(bundle); log.info("Scheduled publish executed for RN bundle {}", bundle.getId()); operationLogService.record( - bundle.getAppId(), + bundle.getAppKey(), "RN_BUNDLE", bundle.getId(), "SCHEDULE_PUBLISH", @@ -284,7 +372,7 @@ public class AppStoreService { String url = v.getWebhookUrl(); if (url == null || url.isBlank()) { try { - Map shared = getReviewWebhookConfig(v.getAppId()); + Map shared = getReviewWebhookConfig(v.getAppKey()); url = shared.get("webhookUrl"); } catch (Exception ignored) { url = null; @@ -296,7 +384,7 @@ public class AppStoreService { String body = mapper.writeValueAsString(Map.of( "event", "store_review_update", "versionId", v.getId(), - "appKey", v.getAppId(), + "appKey", v.getAppKey(), "versionName", v.getVersionName(), "storeType", storeType, "reviewState", state.name(), @@ -304,7 +392,7 @@ public class AppStoreService { "publishStatus", v.getPublishStatus().name(), "timestamp", System.currentTimeMillis() )); - String secret = resolveWebhookSecret(v.getAppId()); + String secret = resolveWebhookSecret(v.getAppKey()); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") @@ -354,12 +442,47 @@ public class AppStoreService { } private Map reviewPayload(String state, String reason) { + return reviewPayload(state, reason, null, null, null, LocalDateTime.now().toString()); + } + + private Map reviewPayload(String state, + String reason, + String stage, + String batchId, + String submittedAt, + String updatedAt) { Map payload = new LinkedHashMap<>(); payload.put("state", state); payload.put("reason", reason == null ? "" : reason); + payload.put("stage", stage == null ? "" : stage); + payload.put("batchId", batchId == null ? "" : batchId); + payload.put("submittedAt", submittedAt == null ? "" : submittedAt); + payload.put("updatedAt", updatedAt == null ? "" : updatedAt); return payload; } + @SuppressWarnings("unchecked") + private Map asReviewPayload(Object value) { + if (value instanceof Map map) { + return new LinkedHashMap<>((Map) map); + } + return new LinkedHashMap<>(); + } + + private String readText(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private String stageForFinalState(AppVersionEntity.StoreReviewState state) { + return switch (state) { + case UNDER_REVIEW -> "SUBMITTED"; + case APPROVED -> "APPROVED"; + case REJECTED -> "FAILED"; + case SUBMITTING -> "SUBMITTING"; + case PENDING -> "QUEUED"; + }; + } + private String readReviewState(Object value) { if (value == null) { return ""; diff --git a/update-service/src/main/java/com/xuqm/update/service/PublishConfigService.java b/update-service/src/main/java/com/xuqm/update/service/PublishConfigService.java index 349ab17..e103376 100644 --- a/update-service/src/main/java/com/xuqm/update/service/PublishConfigService.java +++ b/update-service/src/main/java/com/xuqm/update/service/PublishConfigService.java @@ -45,10 +45,10 @@ public class PublishConfigService { } public AppPublishConfigEntity getConfig(String appKey) { - return configRepository.findByAppId(appKey).orElseGet(() -> { + return configRepository.findByAppKey(appKey).orElseGet(() -> { AppPublishConfigEntity entity = new AppPublishConfigEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setConfigJson(defaultConfigJson()); entity.setUpdatedAt(LocalDateTime.now()); return entity; @@ -56,10 +56,10 @@ public class PublishConfigService { } public AppPublishConfigEntity saveConfig(String appKey, Map body) { - AppPublishConfigEntity entity = configRepository.findByAppId(appKey).orElseGet(AppPublishConfigEntity::new); + AppPublishConfigEntity entity = configRepository.findByAppKey(appKey).orElseGet(AppPublishConfigEntity::new); if (entity.getId() == null) { entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); } try { entity.setConfigJson(objectMapper.writeValueAsString(body == null ? Map.of() : body)); @@ -71,7 +71,7 @@ public class PublishConfigService { } public JsonNode getConfigNode(String appKey) { - AppPublishConfigEntity entity = configRepository.findByAppId(appKey).orElse(null); + AppPublishConfigEntity entity = configRepository.findByAppKey(appKey).orElse(null); if (entity == null || entity.getConfigJson() == null || entity.getConfigJson().isBlank()) { try { return objectMapper.readTree(defaultConfigJson()); @@ -91,7 +91,7 @@ public class PublishConfigService { String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(Locale.ROOT); String normalizedGroup = groupName == null ? "" : groupName.trim().toLowerCase(Locale.ROOT); - List members = grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appKey) + List members = grayMemberRepository.findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(appKey) .stream() .filter(item -> normalizedGroup.isBlank() || safe(item.getGroupName()).toLowerCase(Locale.ROOT).contains(normalizedGroup)) @@ -170,7 +170,7 @@ public class PublishConfigService { throw new IllegalStateException("Invalid gray member payload"); } grayMemberRepository.deleteAll( - grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appKey)); + grayMemberRepository.findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(appKey)); List saved = new ArrayList<>(); for (GrayMemberGroupPayload group : groups) { @@ -181,7 +181,7 @@ public class PublishConfigService { } AppGrayMemberEntity entity = new AppGrayMemberEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setGroupName(groupName); entity.setUserId(member.userId().trim()); entity.setName(member.name() == null ? "" : member.name().trim()); diff --git a/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java b/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java index 9879ff4..1b73c42 100644 --- a/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java +++ b/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java @@ -21,14 +21,18 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.io.File; +import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.InputStream; import java.math.BigInteger; import java.net.URI; import java.nio.file.Paths; import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.LocalDateTime; import java.security.MessageDigest; import java.security.PublicKey; +import java.security.interfaces.RSAKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.*; @@ -58,6 +62,7 @@ public class StoreSubmissionService { private final AppStoreConfigRepository configRepo; private final AppStoreService storeService; private final UpdateAssetService updateAssetService; + private final UpdateOperationLogService operationLogService; @Value("${update.upload-dir:/tmp/xuqm-update}") private String uploadDir; @@ -65,11 +70,13 @@ public class StoreSubmissionService { public StoreSubmissionService(AppVersionRepository versionRepo, AppStoreConfigRepository configRepo, AppStoreService storeService, - UpdateAssetService updateAssetService) { + UpdateAssetService updateAssetService, + UpdateOperationLogService operationLogService) { this.versionRepo = versionRepo; this.configRepo = configRepo; this.storeService = storeService; this.updateAssetService = updateAssetService; + this.operationLogService = operationLogService; } /** @@ -80,6 +87,8 @@ public class StoreSubmissionService { public void executeSubmitAsync(String versionId) { AppVersionEntity v = versionRepo.findById(versionId).orElse(null); if (v == null) { log.error("Version not found: {}", versionId); return; } + String batchId = UUID.randomUUID().toString(); + LocalDateTime startedAt = LocalDateTime.now(); if ("SCHEDULED".equalsIgnoreCase(v.getStoreSubmitMode())) { v.setStoreSubmitMode("AUTO_REVIEW"); @@ -88,36 +97,114 @@ public class StoreSubmissionService { } List targets = parseTargets(v.getStoreSubmitTargets()); - if (targets.isEmpty()) { log.warn("No store targets for version {}", versionId); return; } + if (targets.isEmpty()) { + log.warn("No store targets for version {}", versionId); + recordBatchEvent(v, versionId, batchId, "STORE_SUBMIT_BATCH_SKIPPED", startedAt, Map.of( + "reason", "No store targets" + )); + return; + } - File apkFile = resolveLocalFile(v.getDownloadUrl()); + log.info("Store submit batch start version={} appKey={} batchId={} targets={}", + versionId, v.getAppKey(), batchId, targets); + recordBatchEvent(v, versionId, batchId, "STORE_SUBMIT_BATCH_START", startedAt, Map.of( + "targets", targets, + "submitMode", v.getStoreSubmitMode(), + "scheduledAt", v.getStoreSubmitScheduledAt() == null ? "" : v.getStoreSubmitScheduledAt().toString(), + "downloadUrl", v.getDownloadUrl() == null ? "" : v.getDownloadUrl() + )); - for (String storeType : targets) { + File apkFile; + try { + apkFile = resolveLocalFile(v.getDownloadUrl()); + } catch (Exception e) { + String message = describeException(e); + log.error("Store submit batch failed before file resolve version={} batchId={}: {}", versionId, batchId, message, e); + recordBatchEvent(v, versionId, batchId, "STORE_SUBMIT_BATCH_FAILED", startedAt, Map.of( + "reason", message, + "phase", "RESOLVE_FILE" + )); + return; + } + + int successCount = 0; + int rejectedCount = 0; + int skippedCount = 0; + for (int index = 0; index < targets.size(); index++) { + String storeType = targets.get(index); + long storeStartedAt = System.currentTimeMillis(); + recordStoreEvent(v, versionId, batchId, storeType, "STORE_SUBMIT_STORE_START", Map.of( + "index", index + 1, + "total", targets.size(), + "versionName", v.getVersionName(), + "versionCode", v.getVersionCode(), + "packageName", v.getPackageName() == null ? "" : v.getPackageName() + ), null); try { AppStoreConfigEntity cfg = configRepo - .findByAppIdAndStoreType(v.getAppId(), AppStoreConfigEntity.StoreType.valueOf(storeType)) + .findByAppKeyAndStoreType(v.getAppKey(), AppStoreConfigEntity.StoreType.valueOf(storeType)) .orElse(null); if (cfg == null || !cfg.isEnabled()) { - log.warn("Store config not found or disabled for {}/{}", v.getAppId(), storeType); + skippedCount++; + String reason = "Store config not found or disabled"; + log.warn("Store config not found or disabled for {}/{} batchId={}", v.getAppKey(), storeType, batchId); storeService.updateStoreReview(versionId, storeType, AppVersionEntity.StoreReviewState.REJECTED, - "Store config not found or disabled"); + reason); + recordStoreEvent(v, versionId, batchId, storeType, "STORE_SUBMIT_STORE_FAILED", Map.of( + "durationMs", System.currentTimeMillis() - storeStartedAt, + "phase", "CONFIG", + "reason", reason + ), reason); continue; } Map creds = parseConfig(cfg.getConfigJson()); + try { + storeService.updateStoreSubmissionStage(versionId, storeType, "SUBMITTING", + "开始调用厂商提交接口", batchId); + } catch (Exception stageEx) { + log.warn("Failed to update submission stage for {}/{} batchId={}: {}", + v.getAppKey(), storeType, batchId, stageEx.getMessage(), stageEx); + } + recordStoreEvent(v, versionId, batchId, storeType, "STORE_SUBMIT_STORE_STAGE", Map.of( + "phase", "SUBMITTING", + "credentialKeys", new ArrayList<>(creds.keySet()) + ), null); submitToStore(storeType, v, apkFile, creds); storeService.updateStoreReview(versionId, storeType, AppVersionEntity.StoreReviewState.UNDER_REVIEW); + successCount++; + recordStoreEvent(v, versionId, batchId, storeType, "STORE_SUBMIT_STORE_SUCCESS", Map.of( + "durationMs", System.currentTimeMillis() - storeStartedAt, + "reviewState", AppVersionEntity.StoreReviewState.UNDER_REVIEW.name() + ), null); log.info("Submitted version {} to {}", versionId, storeType); } catch (Exception e) { + rejectedCount++; + String message = describeException(e); log.error("Submission to {} failed for version {}: {}", storeType, versionId, e.getMessage(), e); + recordStoreEvent(v, versionId, batchId, storeType, "STORE_SUBMIT_STORE_FAILED", Map.of( + "durationMs", System.currentTimeMillis() - storeStartedAt, + "phase", "SUBMISSION", + "errorClass", e.getClass().getName(), + "reason", message + ), message); try { storeService.updateStoreReview(versionId, storeType, AppVersionEntity.StoreReviewState.REJECTED, - e.getMessage()); + message); } catch (Exception ex) { /* best effort */ } } } + recordBatchEvent(v, versionId, batchId, "STORE_SUBMIT_BATCH_END", startedAt, Map.of( + "targets", targets, + "successCount", successCount, + "rejectedCount", rejectedCount, + "skippedCount", skippedCount, + "durationMs", Duration.between(startedAt, LocalDateTime.now()).toMillis() + )); + log.info("Store submit batch end version={} appKey={} batchId={} success={} rejected={} skipped={}", + versionId, v.getAppKey(), batchId, successCount, rejectedCount, skippedCount); } @Scheduled(fixedDelay = 60_000) @@ -196,7 +283,7 @@ public class StoreSubmissionService { "grant_type", "client_credentials"); ResponseEntity resp = rest.postForEntity( HUAWEI_API + "/api/oauth2/v1/token", new HttpEntity<>(body, headers), Map.class); - return Objects.requireNonNull(resp.getBody()).get("access_token").toString(); + return requiredText(resp.getBody(), "access_token", "Huawei token"); } @SuppressWarnings("unchecked") @@ -205,10 +292,19 @@ public class StoreSubmissionService { ResponseEntity resp = rest.exchange( HUAWEI_API + "/api/publish/v2/appid-list?packageName=" + packageName, HttpMethod.GET, new HttpEntity<>(headers), Map.class); - Map body = resp.getBody(); - List> list = (List>) body.get("appids"); - if (list == null || list.isEmpty()) throw new RuntimeException("Huawei: app not found for " + packageName); - return list.get(0).get("id").toString(); + Map body = requireBodyMap(resp.getBody(), "Huawei app id list"); + List> list = asMapList(body.get("appids")); + if (list.isEmpty()) { + list = asMapList(body.get("data")); + } + if (list.isEmpty()) { + throw new RuntimeException("Huawei: app not found for " + packageName + ", response=" + summarizeMap(body)); + } + String appId = firstText(list.get(0), "id", "appId", "app_id"); + if (appId.isBlank()) { + throw new RuntimeException("Huawei: app id missing for " + packageName + ", response=" + summarizeMap(list.get(0))); + } + return appId; } @SuppressWarnings("unchecked") @@ -217,7 +313,7 @@ public class StoreSubmissionService { String url = HUAWEI_API + "/api/publish/v2/upload-url/for-obs?appId=" + hwAppId + "&fileName=" + file.getName() + "&contentLength=" + file.length(); ResponseEntity resp = rest.exchange(url, HttpMethod.GET, new HttpEntity<>(headers), Map.class); - return resp.getBody(); + return requireBodyMap(resp.getBody(), "Huawei upload url"); } private void huaweiUploadFile(String uploadUrl, Map extraHeaders, File file) { @@ -236,8 +332,19 @@ public class StoreSubmissionService { ResponseEntity resp = rest.exchange( HUAWEI_API + "/api/publish/v2/app-file-info?appId=" + hwAppId, HttpMethod.PUT, new HttpEntity<>(body, headers), Map.class); - List> pkgList = (List>) resp.getBody().get("pkgVersion"); - return pkgList.get(0).get("id").toString(); + Map respBody = requireBodyMap(resp.getBody(), "Huawei bind apk"); + List> pkgList = asMapList(respBody.get("pkgVersion")); + if (pkgList.isEmpty()) { + pkgList = asMapList(respBody.get("data")); + } + if (pkgList.isEmpty()) { + throw new RuntimeException("Huawei: bind apk response missing pkgVersion, response=" + summarizeMap(respBody)); + } + String pkgId = firstText(pkgList.get(0), "id", "pkgId", "packageId"); + if (pkgId.isBlank()) { + throw new RuntimeException("Huawei: bind apk response missing pkg id, response=" + summarizeMap(pkgList.get(0))); + } + return pkgId; } @SuppressWarnings("unchecked") @@ -249,12 +356,16 @@ public class StoreSubmissionService { ResponseEntity resp = rest.exchange( HUAWEI_API + "/api/publish/v2/package/compile/status?appId=" + hwAppId + "&pkgIds=" + pkgId, HttpMethod.GET, new HttpEntity<>(headers), Map.class); - List> states = (List>) resp.getBody().get("pkgStateList"); + Map respBody = requireBodyMap(resp.getBody(), "Huawei compile status"); + List> states = asMapList(respBody.get("pkgStateList")); + if (states.isEmpty()) { + states = asMapList(respBody.get("data")); + } if (states != null && !states.isEmpty()) { Object compileStatus = states.get(0).get("compileStatus"); if ("2".equals(String.valueOf(compileStatus))) return; // 2 = success if ("3".equals(String.valueOf(compileStatus))) - throw new RuntimeException("Huawei compile failed"); + throw new RuntimeException("Huawei compile failed: " + summarizeMap(states.get(0))); } } throw new RuntimeException("Huawei compile timeout"); @@ -495,7 +606,7 @@ public class StoreSubmissionService { MultiValueMap body = new LinkedMultiValueMap<>(); body.add("RequestData", asJsonString(requestData)); - body.add("SIG", rsaEncryptHex(asJsonString(sig), publicKey)); + body.add("SIG", rsaEncryptHexChunked(asJsonString(sig), publicKey)); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); @@ -536,7 +647,7 @@ public class StoreSubmissionService { MultiValueMap body = new LinkedMultiValueMap<>(); body.add("apk", new FileSystemResource(apkFile)); body.add("RequestData", asJsonString(requestData)); - body.add("SIG", rsaEncryptHex(asJsonString(sig), publicKey)); + body.add("SIG", rsaEncryptHexChunked(asJsonString(sig), publicKey)); ResponseEntity response = rest.postForEntity( "https://api.developer.xiaomi.com/devupload/dev/push", @@ -778,15 +889,25 @@ public class StoreSubmissionService { } } - private String rsaEncryptHex(String content, String publicKeyPem) throws Exception { + private String rsaEncryptHexChunked(String content, String publicKeyPem) throws Exception { CertificateFactory factory = CertificateFactory.getInstance("X.509"); try (InputStream inputStream = new java.io.ByteArrayInputStream(publicKeyPem.getBytes(StandardCharsets.UTF_8))) { X509Certificate certificate = (X509Certificate) factory.generateCertificate(inputStream); PublicKey publicKey = certificate.getPublicKey(); javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, publicKey); - byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(encrypted); + byte[] plain = content.getBytes(StandardCharsets.UTF_8); + int keySizeBytes = publicKey instanceof RSAKey rsaKey + ? (rsaKey.getModulus().bitLength() + 7) / 8 + : 128; + int maxBlockSize = Math.max(1, keySizeBytes - 11); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (int offset = 0; offset < plain.length; offset += maxBlockSize) { + int len = Math.min(maxBlockSize, plain.length - offset); + byte[] encrypted = cipher.doFinal(plain, offset, len); + out.write(encrypted); + } + return HexFormat.of().formatHex(out.toByteArray()); } } @@ -867,4 +988,140 @@ public class StoreSubmissionService { throw new IllegalStateException(store + " credential missing: " + key); return v; } + + @SuppressWarnings("unchecked") + private Map requireBodyMap(Map body, String action) { + if (body == null) { + throw new IllegalStateException(action + " failed: empty response body"); + } + return (Map) body; + } + + @SuppressWarnings("unchecked") + private List> asMapList(Object value) { + if (!(value instanceof List list)) { + return List.of(); + } + List> result = new ArrayList<>(); + for (Object item : list) { + if (item instanceof Map map) { + result.add((Map) map); + } + } + return result; + } + + private String firstText(Map map, String... keys) { + if (map == null || keys == null) return ""; + for (String key : keys) { + Object value = map.get(key); + if (value != null) { + String text = String.valueOf(value); + if (!text.isBlank()) { + return text; + } + } + } + return ""; + } + + private String requiredText(Map map, String key, String action) { + if (map == null) { + throw new IllegalStateException(action + " failed: empty response body"); + } + Object value = map.get(key); + if (value == null || String.valueOf(value).isBlank()) { + throw new IllegalStateException(action + " failed: missing " + key + ", response=" + summarizeMap(map)); + } + return String.valueOf(value); + } + + private String summarizeMap(Map map) { + if (map == null) return "{}"; + Map summary = new LinkedHashMap<>(); + for (Map.Entry entry : map.entrySet()) { + String key = String.valueOf(entry.getKey()); + Object value = entry.getValue(); + if ("data".equals(key) && value instanceof List list) { + summary.put(key, "list[" + list.size() + "]"); + } else { + summary.put(key, value); + } + } + try { + return mapper.writeValueAsString(summary); + } catch (Exception e) { + return summary.toString(); + } + } + + private void recordBatchEvent(AppVersionEntity v, + String versionId, + String batchId, + String action, + LocalDateTime startedAt, + Map detail) { + recordEvent(v, versionId, action, null, startedAt == null ? null : startedAt.toString(), batchId, detail, null); + } + + private void recordStoreEvent(AppVersionEntity v, + String versionId, + String batchId, + String storeType, + String action, + Map detail, + String reason) { + Map payload = new LinkedHashMap<>(); + if (detail != null) { + payload.putAll(detail); + } + payload.put("storeType", storeType); + payload.put("batchId", batchId); + recordEvent(v, versionId, action, storeType, null, batchId, payload, reason); + } + + private void recordEvent(AppVersionEntity v, + String versionId, + String action, + String storeType, + String startedAt, + String batchId, + Map detail, + String reason) { + Map payload = new LinkedHashMap<>(); + if (detail != null) { + payload.putAll(detail); + } + if (batchId != null) { + payload.put("batchId", batchId); + } + if (startedAt != null) { + payload.putIfAbsent("startedAt", startedAt); + } + if (storeType != null) { + payload.putIfAbsent("storeType", storeType); + } + operationLogService.record( + v.getAppKey(), + "APP_VERSION", + versionId, + action, + reason, + payload); + } + + private String describeException(Throwable throwable) { + if (throwable == null) { + return "unknown error"; + } + Throwable root = throwable; + while (root.getCause() != null && root.getCause() != root) { + root = root.getCause(); + } + String message = root.getMessage(); + if (message == null || message.isBlank()) { + message = root.getClass().getSimpleName(); + } + return root.getClass().getSimpleName() + ": " + message; + } } diff --git a/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java b/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java index c04ef6d..0772ca5 100644 --- a/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java +++ b/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java @@ -33,7 +33,7 @@ public class UpdateOperationLogService { Map detail) { UpdateOperationLogEntity entity = new UpdateOperationLogEntity(); entity.setId(UUID.randomUUID().toString()); - entity.setAppId(appKey); + entity.setAppKey(appKey); entity.setResourceType(resourceType); entity.setResourceId(resourceId); entity.setAction(action); @@ -46,7 +46,7 @@ public class UpdateOperationLogService { public List list(String appKey, int limit) { int size = Math.min(Math.max(limit, 1), 200); - return repository.findByAppIdOrderByCreatedAtDesc( + return repository.findByAppKeyOrderByCreatedAtDesc( appKey, PageRequest.of(0, size)); } diff --git a/update-service/src/main/resources/application.yml b/update-service/src/main/resources/application.yml index 3124803..6a6d6b5 100644 --- a/update-service/src/main/resources/application.yml +++ b/update-service/src/main/resources/application.yml @@ -32,6 +32,10 @@ update: upload-dir: ${UPDATE_UPLOAD_DIR:/tmp/xuqm-update} base-url: ${UPDATE_BASE_URL:https://update.dev.xuqinmin.com} +sdk: + tenant-service-url: ${SDK_TENANT_SERVICE_URL:http://xuqm-tenant-service:9001} + internal-token: ${SDK_INTERNAL_TOKEN:xuqm-internal-token} + management: endpoints: web: