docs(deploy): 添加部署文档和安全设计规范

- 新增 XuqmGroup 部署文档,包含部署方案、架构建议和部署步骤
- 添加安全设计规范,涵盖密码安全、AppSecret验证和服务端API认证
- 补充平台REST API规范,定义Server-to-Server调用接口和错误码
- 创建Java IM服务端SDK计划文档,规划Maven包发布和接口实现
这个提交包含在:
XuqmGroup 2026-05-08 18:32:00 +08:00
父节点 0385b2010a
当前提交 dc1ada94ea
共有 103 个文件被更改,包括 1144 次插入1014 次删除

查看文件

@ -1 +1 @@
17 21

查看文件

@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1.7
ARG SERVICE_MODULE=tenant-service ARG SERVICE_MODULE=tenant-service
FROM maven:3.9.9-eclipse-temurin-21 AS build FROM maven:3.9.9-eclipse-temurin-21 AS build
@ -5,6 +6,7 @@ ARG SERVICE_MODULE
WORKDIR /workspace WORKDIR /workspace
COPY pom.xml ./pom.xml COPY pom.xml ./pom.xml
COPY maven-settings.xml ./maven-settings.xml
COPY common ./common COPY common ./common
COPY im-sdk ./im-sdk COPY im-sdk ./im-sdk
COPY tenant-service ./tenant-service COPY tenant-service ./tenant-service
@ -14,7 +16,8 @@ COPY update-service ./update-service
COPY demo-service ./demo-service COPY demo-service ./demo-service
COPY file-service ./file-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 FROM eclipse-temurin:21-jre-jammy
WORKDIR /app WORKDIR /app

4
Jenkinsfile vendored
查看文件

@ -15,6 +15,7 @@ pipeline {
PROD_HOST = '106.54.23.149' PROD_HOST = '106.54.23.149'
PROD_USER = 'ubuntu' PROD_USER = 'ubuntu'
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml' COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
DOCKER_BUILDKIT = '1'
} }
stages { stages {
@ -29,7 +30,8 @@ pipeline {
def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${params.SERVICE}:${params.IMAGE_TAG}" def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${params.SERVICE}:${params.IMAGE_TAG}"
bat """ bat """
docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS% 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 push ${imageName}
docker rmi ${imageName} docker rmi ${imageName}
""" """

查看文件

@ -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 #### 功能服务(需 Token
| 方法 | 路径 | 说明 | | 方法 | 路径 | 说明 |
|------|------|------| |------|------|------|
| GET | `/api/apps/{appId}/services` | 获取应用下所有功能服务 | | GET | `/api/apps/{appKey}/services` | 获取应用下所有功能服务 |
| POST | `/api/apps/{appId}/services/toggle?platform=&serviceType=&enable=` | 开启/关闭功能服务 | | POST | `/api/apps/{appKey}/services/toggle?platform=&serviceType=&enable=` | 开启/关闭功能服务 |
| POST | `/api/apps/{appId}/services/{id}/regenerate-key` | 重新生成 secretKey | | POST | `/api/apps/{appKey}/services/{id}/regenerate-key` | 重新生成 secretKey |
**platform 枚举**`ANDROID` / `IOS` / `HARMONY` **platform 枚举**`ANDROID` / `IOS` / `HARMONY`
**serviceType 枚举**`IM` / `PUSH` / `UPDATE` **serviceType 枚举**`IM` / `PUSH` / `UPDATE`
@ -216,7 +216,7 @@ cd update-service && mvn spring-boot:run &
``` ```
POST /api/im/auth/login POST /api/im/auth/login
?appId=ak_xxx ?appKey=ak_xxx
&userId=user_001 &userId=user_001
&nickname=张三 (可选,仅首次注册时存入外部系统) &nickname=张三 (可选,仅首次注册时存入外部系统)
&avatar=https://... (可选) &avatar=https://... (可选)
@ -230,9 +230,9 @@ POST /api/im/auth/login
| 方法 | 路径 | 说明 | | 方法 | 路径 | 说明 |
|------|------|------| |------|------|------|
| POST | `/api/im/messages/send?appId=` | 发送消息 | | POST | `/api/im/messages/send?appKey=` | 发送消息 |
| POST | `/api/im/messages/{id}/revoke?appId=` | 撤回消息 | | POST | `/api/im/messages/{id}/revoke?appKey=` | 撤回消息 |
| GET | `/api/im/messages/history/{toId}?appId=&page=&size=` | 查询历史消息 | | GET | `/api/im/messages/history/{toId}?appKey=&page=&size=` | 查询历史消息 |
**发送消息请求体** **发送消息请求体**
```json ```json
@ -276,7 +276,7 @@ ws://localhost:8082/ws/im?token=<im_jwt>
{ {
"destination": "/app/chat.send", "destination": "/app/chat.send",
"payload": { "payload": {
"appId": "ak_xxx", "appKey": "ak_xxx",
"toId": "user_002", "toId": "user_002",
"chatType": "SINGLE", "chatType": "SINGLE",
"msgType": "TEXT", "msgType": "TEXT",
@ -310,7 +310,7 @@ Frame 格式:
```json ```json
{ {
"destination": "/app/chat.revoke", "destination": "/app/chat.revoke",
"payload": { "appId": "ak_xxx", "messageId": "msg-uuid" } "payload": { "appKey": "ak_xxx", "messageId": "msg-uuid" }
} }
``` ```
@ -332,7 +332,7 @@ Frame 格式:
```json ```json
{ {
"event": "message", "event": "message",
"appId": "ak_xxx", "appKey": "ak_xxx",
"message": { ...消息对象... } "message": { ...消息对象... }
} }
``` ```
@ -345,11 +345,11 @@ Frame 格式:
| 表名 | 说明 | | 表名 | 说明 |
|------|------| |------|------|
| `push_device_token` | 设备推送 tokenappId + userId + vendor 唯一) | | `push_device_token` | 设备推送 tokenappKey + userId + vendor 唯一) |
### 支持厂商 ### 支持厂商
`HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`iOS/ `FCM` `HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`iOS
### 接口 ### 接口
@ -366,7 +366,7 @@ Frame 格式:
**注册 token** **注册 token**
``` ```
POST /api/push/register POST /api/push/register
?appId=ak_xxx ?appKey=ak_xxx
&userId=user_001 &userId=user_001
&vendor=HUAWEI &vendor=HUAWEI
&token=device_push_token &token=device_push_token
@ -375,7 +375,7 @@ POST /api/push/register
**发送推送** **发送推送**
``` ```
POST /api/push/send POST /api/push/send
?appId=ak_xxx ?appKey=ak_xxx
&userId=user_001 &userId=user_001
&title=新消息 &title=新消息
&body=张三: Hello! &body=张三: Hello!
@ -385,7 +385,7 @@ POST /api/push/send
**开关接收推送** **开关接收推送**
``` ```
POST /api/push/receive-push POST /api/push/receive-push
?appId=ak_xxx ?appKey=ak_xxx
&userId=user_001 &userId=user_001
&enabled=false &enabled=false
``` ```
@ -393,7 +393,7 @@ POST /api/push/receive-push
**内部通知** **内部通知**
```json ```json
{ {
"appId": "ak_xxx", "appKey": "ak_xxx",
"userIds": ["user_001", "user_002"], "userIds": ["user_001", "user_002"],
"title": "群聊消息", "title": "群聊消息",
"body": "张三: Hello!", "body": "张三: Hello!",
@ -437,11 +437,12 @@ push:
| POST | `/api/v1/updates/app/upload` | 上传版本(先传 file-service,再把 `apkUrl` 交给 update-service | | POST | `/api/v1/updates/app/upload` | 上传版本(先传 file-service,再把 `apkUrl` 交给 update-service |
| POST | `/api/v1/updates/app/{id}/publish` | 发布版本 | | POST | `/api/v1/updates/app/{id}/publish` | 发布版本 |
| GET | `/api/v1/updates/app/list` | 版本列表 | | GET | `/api/v1/updates/app/list` | 版本列表 |
| POST | `/api/v1/updates/store/app/{id}/execute-submit` | 批量提交应用市场,过程会写入批次日志与逐市场状态 |
**检查更新** **检查更新**
``` ```
GET /api/v1/updates/app/check GET /api/v1/updates/app/check
?appId=ak_xxx ?appKey=ak_xxx
&platform=ANDROID &platform=ANDROID
&currentVersionCode=10 &currentVersionCode=10
``` ```
@ -464,6 +465,8 @@ GET /api/v1/updates/app/check
**platform 枚举**`ANDROID` / `IOS` / `HARMONY` **platform 枚举**`ANDROID` / `IOS` / `HARMONY`
**市场提审状态**`PENDING` / `SUBMITTING` / `UNDER_REVIEW` / `APPROVED` / `REJECTED`
**上传 APK两段式** **上传 APK两段式**
``` ```
POST /api/file/upload POST /api/file/upload
@ -474,7 +477,7 @@ POST /api/file/upload
``` ```
POST /api/v1/updates/app/upload POST /api/v1/updates/app/upload
appId=ak_xxx appKey=ak_xxx
platform=ANDROID platform=ANDROID
versionName=1.1.0 versionName=1.1.0
versionCode=11 versionCode=11
@ -494,7 +497,7 @@ POST /api/v1/updates/app/upload
**检查 RN 更新** **检查 RN 更新**
``` ```
GET /api/v1/rn/update/check GET /api/v1/rn/update/check
?appId=ak_xxx ?appKey=ak_xxx
&moduleId=main &moduleId=main
&platform=android &platform=android
&currentVersion=1.0.0 &currentVersion=1.0.0
@ -517,7 +520,7 @@ GET /api/v1/rn/update/check
**上传 Bundlemultipart/form-data** **上传 Bundlemultipart/form-data**
``` ```
POST /api/v1/rn/upload POST /api/v1/rn/upload
appId=ak_xxx appKey=ak_xxx
moduleId=main moduleId=main
platform=ANDROID platform=ANDROID
version=1.1.0 version=1.1.0
@ -526,7 +529,7 @@ POST /api/v1/rn/upload
bundle=<binary .bundle 文件> bundle=<binary .bundle 文件>
``` ```
服务端自动计算 MD5,存储至 `{upload-dir}/rn/{appId}/{platform}/{moduleId}/`。 服务端自动计算 MD5,存储至 `{upload-dir}/rn/{appKey}/{platform}/{moduleId}/`。
### 环境变量配置 ### 环境变量配置

查看文件

@ -6,7 +6,7 @@ import java.time.Instant;
@Entity @Entity
@Table( @Table(
name = "demo_user", 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 { public class DemoUserEntity {
@ -18,8 +18,8 @@ public class DemoUserEntity {
@Column(name = "id", length = 36, nullable = false, updatable = false) @Column(name = "id", length = 36, nullable = false, updatable = false)
private String id; private String id;
@Column(name = "app_id", length = 64, nullable = false) @Column(name = "app_key", length = 64, nullable = false)
private String appId; private String appKey;
@Column(name = "user_id", length = 128, nullable = false) @Column(name = "user_id", length = 128, nullable = false)
private String userId; private String userId;
@ -43,8 +43,8 @@ public class DemoUserEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -10,14 +10,14 @@ import java.util.Optional;
public interface DemoUserRepository extends JpaRepository<DemoUserEntity, String> { public interface DemoUserRepository extends JpaRepository<DemoUserEntity, String> {
Optional<DemoUserEntity> findByAppIdAndUserId(String appId, String userId); Optional<DemoUserEntity> 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.userId) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " +
"LOWER(u.nickname) LIKE LOWER(CONCAT('%', :keyword, '%')))") "LOWER(u.nickname) LIKE LOWER(CONCAT('%', :keyword, '%')))")
List<DemoUserEntity> searchByKeyword(@Param("appId") String appId, @Param("keyword") String keyword); List<DemoUserEntity> searchByKeyword(@Param("appKey") String appKey, @Param("keyword") String keyword);
List<DemoUserEntity> findAllByAppIdOrderByCreatedAtAsc(String appId); List<DemoUserEntity> findAllByAppKeyOrderByCreatedAtAsc(String appKey);
} }

查看文件

@ -58,13 +58,13 @@ public class DemoAuthService {
@Transactional @Transactional
public AuthResult register(String appKey, String userId, String password, String nickname) { 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); throw new BusinessException(409, "User already exists: " + userId);
} }
DemoUserEntity user = new DemoUserEntity(); DemoUserEntity user = new DemoUserEntity();
user.setId(UUID.randomUUID().toString()); user.setId(UUID.randomUUID().toString());
user.setAppId(appKey); user.setAppKey(appKey);
user.setUserId(userId); user.setUserId(userId);
user.setPasswordHash(passwordEncoder.encode(password)); user.setPasswordHash(passwordEncoder.encode(password));
user.setNickname(nickname != null ? nickname : userId); user.setNickname(nickname != null ? nickname : userId);
@ -84,7 +84,7 @@ public class DemoAuthService {
@Transactional(readOnly = true) @Transactional(readOnly = true)
public AuthResult login(String appKey, String userId, String password) { 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")); .orElseThrow(() -> new BusinessException(401, "Invalid credentials"));
if (!passwordEncoder.matches(password, user.getPasswordHash())) { if (!passwordEncoder.matches(password, user.getPasswordHash())) {
@ -103,7 +103,7 @@ public class DemoAuthService {
@Transactional @Transactional
public void resetPassword(String appKey, String userId, String newPassword) { 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)); .orElseThrow(() -> new BusinessException(404, "User not found: " + userId));
user.setPasswordHash(passwordEncoder.encode(newPassword)); user.setPasswordHash(passwordEncoder.encode(newPassword));
userRepository.save(user); userRepository.save(user);
@ -161,7 +161,7 @@ public class DemoAuthService {
private UserProfile toProfile(DemoUserEntity user) { private UserProfile toProfile(DemoUserEntity user) {
return new UserProfile( return new UserProfile(
user.getAppId(), user.getAppKey(),
user.getUserId(), user.getUserId(),
user.getNickname(), user.getNickname(),
user.getAvatar(), user.getAvatar(),

查看文件

@ -24,14 +24,14 @@ public class DemoUserService {
@Transactional(readOnly = true) @Transactional(readOnly = true)
public UserProfile getProfile(String appKey, String userId) { 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")); .orElseThrow(() -> new BusinessException(404, "User not found"));
return toProfile(user); return toProfile(user);
} }
@Transactional @Transactional
public UserProfile updateProfile(String appKey, String userId, String nickname, String avatar, String gender) { 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")); .orElseThrow(() -> new BusinessException(404, "User not found"));
if (nickname != null && !nickname.isBlank()) { if (nickname != null && !nickname.isBlank()) {
@ -54,7 +54,7 @@ public class DemoUserService {
@Transactional @Transactional
public void resetPassword(String appKey, String userId, String oldPassword, String newPassword) { 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")); .orElseThrow(() -> new BusinessException(404, "User not found"));
if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) { if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) {
@ -81,7 +81,7 @@ public class DemoUserService {
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<UserProfile> listMembers(String appKey) { public List<UserProfile> listMembers(String appKey) {
return userRepository.findAllByAppIdOrderByCreatedAtAsc(appKey) return userRepository.findAllByAppKeyOrderByCreatedAtAsc(appKey)
.stream() .stream()
.map(this::toProfile) .map(this::toProfile)
.toList(); .toList();
@ -89,7 +89,7 @@ public class DemoUserService {
private UserProfile toProfile(DemoUserEntity user) { private UserProfile toProfile(DemoUserEntity user) {
return new UserProfile( return new UserProfile(
user.getAppId(), user.getAppKey(),
user.getUserId(), user.getUserId(),
user.getNickname(), user.getNickname(),
user.getAvatar(), user.getAvatar(),

查看文件

@ -20,14 +20,14 @@
| 名称 | 含义 | 常见位置 | | 名称 | 含义 | 常见位置 |
|------|------|----------| |------|------|----------|
| `tenant appKey` | 租户平台应用唯一标识,`tenant-service` 的 `/api/apps/{appKey}`、`/api/apps/{appKey}/services` 使用它 | 租户平台、服务配置页 | | `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` 的“服务配置”只认租户 `appKey`
- `tenant-platform` 的“IM 管理”必须带 `appKey` - `tenant-platform` 的“IM 管理”必须带 `appKey`
- `tenant-platform` 的“离线推送 / 版本管理”只需要一次开通,平台差异配置在各自的管理页里维护,不再按 Android / iOS / 鸿蒙分别申请开通 - `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` - 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=...` 查询。 - 上下架、上传、发布、灰度、市场提交、商店配置变更都会写入 `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、鸿蒙仍以跳转页和人工流程为主。 - 提交应用市场会真实调用已实现的厂商接口。小米、OPPO、vivo 和华为/荣耀当前支持服务端提交;App Store、Google Play、鸿蒙仍以跳转页和人工流程为主。
- 租户平台控制台新增 `GET /api/dashboard/stats`,返回当前租户的应用数、已开通服务数和子账号数,同时会写一条 `CONSOLE / DASHBOARD / VIEW_DASHBOARD` 操作日志。 - 租户平台控制台新增 `GET /api/dashboard/stats`,返回当前租户的应用数、已开通服务数和子账号数,同时会写一条 `CONSOLE / DASHBOARD / VIEW_DASHBOARD` 操作日志。
- 租户平台“操作日志”菜单现在集中查看租户平台与版本管理两类日志;版本管理日志继续按 `appKey` 查询,控制台访问日志则落在 `t_operation_log` - 租户平台“操作日志”菜单现在集中查看租户平台与版本管理两类日志;版本管理日志继续按 `appKey` 查询,控制台访问日志则落在 `t_operation_log`
@ -343,7 +345,7 @@ IM 服务会在消息、好友和已读等事件发生后,以 `POST` 方式向
请求头: 请求头:
- `Content-Type: application/json` - `Content-Type: application/json`
- `X-App-Id`: 应用 `appId` - `X-App-Key`: 应用 `appKey`
- `X-App-Timestamp`: 请求时间戳 - `X-App-Timestamp`: 请求时间戳
- `X-App-Nonce`: 随机串 - `X-App-Nonce`: 随机串
- `X-App-Signature`: 签名结果 - `X-App-Signature`: 签名结果
@ -351,7 +353,7 @@ IM 服务会在消息、好友和已读等事件发生后,以 `POST` 方式向
签名规则: 签名规则:
```text ```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, "requestTime": 1714360000000,
"payload": {}, "payload": {},
"signature": null, "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` | 毫秒时间戳 | | `requestTime` | 毫秒时间戳 |
| `payload` | 事件数据,结构随事件变化 | | `payload` | 事件数据,结构随事件变化 |
| `signature` | 预留字段,当前由请求头承载 | | `signature` | 预留字段,当前由请求头承载 |
| `appId` | 回调所属应用 ID | | `appKey` | 回调所属应用唯一标识 |
`payload` 会根据事件类型变化: `payload` 会根据事件类型变化:
@ -403,7 +405,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
```json ```json
{ {
"appId": "ak_demo_chat", "appKey": "ak_demo_chat",
"readerId": "user_001", "readerId": "user_001",
"peerId": "user_002", "peerId": "user_002",
"groupId": null, "groupId": null,
@ -417,7 +419,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
```json ```json
{ {
"appId": "ak_demo_chat", "appKey": "ak_demo_chat",
"requestId": "req_001", "requestId": "req_001",
"fromUserId": "user_001", "fromUserId": "user_001",
"toUserId": "user_002", "toUserId": "user_002",
@ -431,7 +433,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
```json ```json
{ {
"appId": "ak_demo_chat", "appKey": "ak_demo_chat",
"requestId": "req_001", "requestId": "req_001",
"groupId": "group_001", "groupId": "group_001",
"groupName": "技术群", "groupName": "技术群",
@ -446,7 +448,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
```json ```json
{ {
"appId": "ak_demo_chat", "appKey": "ak_demo_chat",
"id": "blk_001", "id": "blk_001",
"userId": "user_001", "userId": "user_001",
"blockedUserId": "user_002", "blockedUserId": "user_002",
@ -469,14 +471,14 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
import crypto from 'crypto' import crypto from 'crypto'
export function verifyWebhook({ export function verifyWebhook({
appId, appKey,
timestamp, timestamp,
nonce, nonce,
body, body,
signature, signature,
appSecret, appSecret,
}: { }: {
appId: string appKey: string
timestamp: string timestamp: string
nonce: string nonce: string
body: string body: string
@ -484,7 +486,7 @@ export function verifyWebhook({
appSecret: string appSecret: string
}) { }) {
const bodyHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex') 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') const expected = crypto.createHmac('sha256', appSecret).update(payload, 'utf8').digest('hex')
return expected === signature return expected === signature
} }

查看文件

@ -33,7 +33,7 @@ public class FriendController {
public ResponseEntity<ApiResponse<List<String>>> listFriends( public ResponseEntity<ApiResponse<List<String>>> listFriends(
@AuthenticationPrincipal String userId, @AuthenticationPrincipal String userId,
@RequestParam String appKey) { @RequestParam String appKey) {
List<String> friendIds = friendRepository.findByAppIdAndUserId(appKey, userId) List<String> friendIds = friendRepository.findByAppKeyAndUserId(appKey, userId)
.stream() .stream()
.map(ImFriendEntity::getFriendId) .map(ImFriendEntity::getFriendId)
.toList(); .toList();
@ -68,8 +68,8 @@ public class FriendController {
@AuthenticationPrincipal String userId, @AuthenticationPrincipal String userId,
@PathVariable String friendId, @PathVariable String friendId,
@RequestParam String appKey) { @RequestParam String appKey) {
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId); friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, userId, friendId);
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId); friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
return ResponseEntity.ok(ApiResponse.success(null)); return ResponseEntity.ok(ApiResponse.success(null));
} }
@ -77,8 +77,8 @@ public class FriendController {
public ResponseEntity<ApiResponse<Void>> removeAllFriends( public ResponseEntity<ApiResponse<Void>> removeAllFriends(
@AuthenticationPrincipal String userId, @AuthenticationPrincipal String userId,
@RequestParam String appKey) { @RequestParam String appKey) {
friendRepository.deleteByAppIdAndUserId(appKey, userId); friendRepository.deleteByAppKeyAndUserId(appKey, userId);
friendRepository.deleteByAppIdAndFriendId(appKey, userId); friendRepository.deleteByAppKeyAndFriendId(appKey, userId);
return ResponseEntity.ok(ApiResponse.ok()); return ResponseEntity.ok(ApiResponse.ok());
} }
@ -91,8 +91,8 @@ public class FriendController {
if (friendId == null || friendId.isBlank() || userId.equals(friendId)) { if (friendId == null || friendId.isBlank() || userId.equals(friendId)) {
continue; continue;
} }
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId); friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, userId, friendId);
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId); friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
} }
return ResponseEntity.ok(ApiResponse.success(null)); return ResponseEntity.ok(ApiResponse.success(null));
} }
@ -103,7 +103,7 @@ public class FriendController {
@PathVariable String friendId, @PathVariable String friendId,
@RequestParam String appKey, @RequestParam String appKey,
@RequestParam(required = false) String groupName) { @RequestParam(required = false) String groupName) {
ImFriendEntity link = friendRepository.findByAppIdAndUserIdAndFriendId(appKey, userId, friendId) ImFriendEntity link = friendRepository.findByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
.orElseGet(() -> addFriendLink(appKey, userId, friendId)); .orElseGet(() -> addFriendLink(appKey, userId, friendId));
link.setFriendGroup(normalizeGroup(groupName)); link.setFriendGroup(normalizeGroup(groupName));
return ResponseEntity.ok(ApiResponse.success(friendRepository.save(link))); return ResponseEntity.ok(ApiResponse.success(friendRepository.save(link)));
@ -113,7 +113,7 @@ public class FriendController {
public ResponseEntity<ApiResponse<List<String>>> listFriendGroups( public ResponseEntity<ApiResponse<List<String>>> listFriendGroups(
@AuthenticationPrincipal String userId, @AuthenticationPrincipal String userId,
@RequestParam String appKey) { @RequestParam String appKey) {
List<String> groups = friendRepository.findByAppIdAndUserId(appKey, userId).stream() List<String> groups = friendRepository.findByAppKeyAndUserId(appKey, userId).stream()
.map(ImFriendEntity::getFriendGroup) .map(ImFriendEntity::getFriendGroup)
.filter(group -> group != null && !group.isBlank()) .filter(group -> group != null && !group.isBlank())
.distinct() .distinct()
@ -128,7 +128,7 @@ public class FriendController {
@RequestParam String appKey, @RequestParam String appKey,
@PathVariable String groupName) { @PathVariable String groupName) {
List<String> friendIds = friendRepository List<String> friendIds = friendRepository
.findByAppIdAndUserIdAndFriendGroup(appKey, userId, normalizeGroup(groupName)) .findByAppKeyAndUserIdAndFriendGroup(appKey, userId, normalizeGroup(groupName))
.stream() .stream()
.map(ImFriendEntity::getFriendId) .map(ImFriendEntity::getFriendId)
.toList(); .toList();
@ -137,19 +137,19 @@ public class FriendController {
private ImFriendEntity addFriendLink(String appKey, String userId, String friendId) { private ImFriendEntity addFriendLink(String appKey, String userId, String friendId) {
ImFriendEntity forward = friendRepository ImFriendEntity forward = friendRepository
.findByAppIdAndUserIdAndFriendId(appKey, userId, friendId) .findByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
.orElseGet(() -> { .orElseGet(() -> {
ImFriendEntity e = new ImFriendEntity(); ImFriendEntity e = new ImFriendEntity();
e.setAppId(appKey); e.setAppKey(appKey);
e.setUserId(userId); e.setUserId(userId);
e.setFriendId(friendId); e.setFriendId(friendId);
return friendRepository.save(e); return friendRepository.save(e);
}); });
friendRepository.findByAppIdAndUserIdAndFriendId(appKey, friendId, userId) friendRepository.findByAppKeyAndUserIdAndFriendId(appKey, friendId, userId)
.orElseGet(() -> { .orElseGet(() -> {
ImFriendEntity e = new ImFriendEntity(); ImFriendEntity e = new ImFriendEntity();
e.setAppId(appKey); e.setAppKey(appKey);
e.setUserId(friendId); e.setUserId(friendId);
e.setFriendId(userId); e.setFriendId(userId);
return friendRepository.save(e); return friendRepository.save(e);
@ -172,8 +172,8 @@ public class FriendController {
@RequestBody FriendCheckRequest req) { @RequestBody FriendCheckRequest req) {
List<FriendCheckResult> results = new ArrayList<>(); List<FriendCheckResult> results = new ArrayList<>();
for (String friendId : req.friendIds() == null ? List.<String>of() : req.friendIds()) { for (String friendId : req.friendIds() == null ? List.<String>of() : req.friendIds()) {
boolean isFriend = friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId) boolean isFriend = friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId); || friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
results.add(new FriendCheckResult(friendId, isFriend)); results.add(new FriendCheckResult(friendId, isFriend));
} }
return ResponseEntity.ok(ApiResponse.success(results)); return ResponseEntity.ok(ApiResponse.success(results));

查看文件

@ -109,7 +109,7 @@ public class ImAdminController {
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) { @RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success( return ResponseEntity.ok(ApiResponse.success(
accountRepository.findByAppId(appKey, PageRequest.of(page, size)))); accountRepository.findByAppKey(appKey, PageRequest.of(page, size))));
} }
/** Ban or unban a user. */ /** Ban or unban a user. */
@ -119,7 +119,7 @@ public class ImAdminController {
@PathVariable String userId, @PathVariable String userId,
@AuthenticationPrincipal String operatorId, @AuthenticationPrincipal String operatorId,
@RequestBody Map<String, String> body) { @RequestBody Map<String, String> body) {
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId) ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "账号不存在")); .orElseThrow(() -> new BusinessException(404, "账号不存在"));
String status = body.get("status"); String status = body.get("status");
if (status == null || status.isBlank()) { if (status == null || status.isBlank()) {
@ -152,7 +152,7 @@ public class ImAdminController {
/** List all groups for the given appKey. */ /** List all groups for the given appKey. */
@GetMapping("/groups") @GetMapping("/groups")
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> listGroups(@RequestParam String appKey) { public ResponseEntity<ApiResponse<List<ImGroupEntity>>> 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). */ /** Admin registers a new IM user (or returns existing). */
@ -253,10 +253,10 @@ public class ImAdminController {
public ResponseEntity<ApiResponse<Map<String, Object>>> stats( public ResponseEntity<ApiResponse<Map<String, Object>>> stats(
@RequestParam String appKey, @RequestParam String appKey,
@AuthenticationPrincipal String operatorId) { @AuthenticationPrincipal String operatorId) {
long totalMessages = messageRepository.countByAppId(appKey); long totalMessages = messageRepository.countByAppKey(appKey);
long totalUsers = accountRepository.countByAppId(appKey); long totalUsers = accountRepository.countByAppKey(appKey);
long totalGroups = groupRepository.countByAppId(appKey); long totalGroups = groupRepository.countByAppKey(appKey);
long todayMessages = messageRepository.countTodayByAppId(appKey); long todayMessages = messageRepository.countTodayByAppKey(appKey);
operationLogService.record(appKey, operatorId, "VIEW_STATS", "STATS", null, "summary"); operationLogService.record(appKey, operatorId, "VIEW_STATS", "STATS", null, "summary");
return ResponseEntity.ok(ApiResponse.success(Map.of( return ResponseEntity.ok(ApiResponse.success(Map.of(
@ -551,11 +551,11 @@ public class ImAdminController {
Page<WebhookDeliveryEntity> result; Page<WebhookDeliveryEntity> result;
PageRequest pageable = PageRequest.of(page, size); PageRequest pageable = PageRequest.of(page, size);
if (callbackEvent != null && !callbackEvent.isBlank()) { if (callbackEvent != null && !callbackEvent.isBlank()) {
result = webhookDeliveryRepository.findByAppIdAndCallbackEvent(appKey, callbackEvent, pageable); result = webhookDeliveryRepository.findByAppKeyAndCallbackEvent(appKey, callbackEvent, pageable);
} else if (success != null) { } else if (success != null) {
result = webhookDeliveryRepository.findByAppIdAndSuccess(appKey, success, pageable); result = webhookDeliveryRepository.findByAppKeyAndSuccess(appKey, success, pageable);
} else { } else {
result = webhookDeliveryRepository.findByAppId(appKey, pageable); result = webhookDeliveryRepository.findByAppKey(appKey, pageable);
} }
return ResponseEntity.ok(ApiResponse.success(result)); return ResponseEntity.ok(ApiResponse.success(result));
} }
@ -569,9 +569,9 @@ public class ImAdminController {
Page<WebhookAlertEntity> result; Page<WebhookAlertEntity> result;
PageRequest pageable = PageRequest.of(page, size); PageRequest pageable = PageRequest.of(page, size);
if (acknowledged != null) { if (acknowledged != null) {
result = webhookAlertRepository.findByAppIdAndAcknowledged(appKey, acknowledged, pageable); result = webhookAlertRepository.findByAppKeyAndAcknowledged(appKey, acknowledged, pageable);
} else { } else {
result = webhookAlertRepository.findByAppId(appKey, pageable); result = webhookAlertRepository.findByAppKey(appKey, pageable);
} }
return ResponseEntity.ok(ApiResponse.success(result)); return ResponseEntity.ok(ApiResponse.success(result));
} }
@ -583,7 +583,7 @@ public class ImAdminController {
@AuthenticationPrincipal String operatorId) { @AuthenticationPrincipal String operatorId) {
WebhookAlertEntity alert = webhookAlertRepository.findById(id) WebhookAlertEntity alert = webhookAlertRepository.findById(id)
.orElseThrow(() -> new BusinessException(404, "告警不存在")); .orElseThrow(() -> new BusinessException(404, "告警不存在"));
if (!alert.getAppId().equals(appKey)) { if (!alert.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
alert.setAcknowledged(true); alert.setAcknowledged(true);
@ -604,7 +604,7 @@ public class ImAdminController {
health.put("enabled", webhook.isEnabled()); health.put("enabled", webhook.isEnabled());
health.put("consecutiveFailures", webhook.getConsecutiveFailures()); health.put("consecutiveFailures", webhook.getConsecutiveFailures());
health.put("lastFailureAt", webhook.getLastFailureAt()); health.put("lastFailureAt", webhook.getLastFailureAt());
long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppId(appKey); long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppKey(appKey);
health.put("unacknowledgedAlerts", unacknowledgedAlerts); health.put("unacknowledgedAlerts", unacknowledgedAlerts);
return ResponseEntity.ok(ApiResponse.success(health)); return ResponseEntity.ok(ApiResponse.success(health));
} }

查看文件

@ -27,10 +27,10 @@ public class StatisticsController {
@RequestParam(required = false) Integer days) { @RequestParam(required = false) Integer days) {
int d = days == null ? 7 : days; int d = days == null ? 7 : days;
LocalDateTime since = LocalDateTime.now().minusDays(d); LocalDateTime since = LocalDateTime.now().minusDays(d);
long total = messageRepository.countByAppIdAndCreatedAtAfter(appKey, since); long total = messageRepository.countByAppKeyAndCreatedAtAfter(appKey, since);
long single = messageRepository.countByAppIdAndChatTypeAndCreatedAtAfter( long single = messageRepository.countByAppKeyAndChatTypeAndCreatedAtAfter(
appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.SINGLE, since); 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); appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.GROUP, since);
return ApiResponse.success(Map.of( return ApiResponse.success(Map.of(
"totalMessages", total, "totalMessages", total,
@ -46,9 +46,9 @@ public class StatisticsController {
@RequestParam(required = false) Integer days) { @RequestParam(required = false) Integer days) {
int d = days == null ? 7 : days; int d = days == null ? 7 : days;
LocalDateTime since = LocalDateTime.now().minusDays(d); LocalDateTime since = LocalDateTime.now().minusDays(d);
long success = webhookDeliveryRepository.countSuccessfulByAppIdSince(appKey, since); long success = webhookDeliveryRepository.countSuccessfulByAppKeySince(appKey, since);
long failed = webhookDeliveryRepository.countFailedByAppIdSince(appKey, since); long failed = webhookDeliveryRepository.countFailedByAppKeySince(appKey, since);
List<Object[]> raw = webhookDeliveryRepository.statsByAppIdSince(appKey, since); List<Object[]> raw = webhookDeliveryRepository.statsByAppKeySince(appKey, since);
List<Map<String, Object>> eventStats = new ArrayList<>(); List<Map<String, Object>> eventStats = new ArrayList<>();
for (Object[] row : raw) { for (Object[] row : raw) {
eventStats.add(Map.of( eventStats.add(Map.of(

查看文件

@ -13,7 +13,7 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "im_account", @Table(name = "im_account",
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId"})) uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId"}))
public class ImAccountEntity { public class ImAccountEntity {
public enum Gender { UNKNOWN, MALE, FEMALE } public enum Gender { UNKNOWN, MALE, FEMALE }
@ -23,7 +23,7 @@ public class ImAccountEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String userId; private String userId;
@ -49,8 +49,8 @@ public class ImAccountEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -11,13 +11,13 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table( @Table(
name = "im_blacklist", name = "im_blacklist",
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "blockedUserId"}), uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "blockedUserId"}),
indexes = @Index(name = "idx_blacklist_app_user", columnList = "appId,userId") indexes = @Index(name = "idx_blacklist_app_user", columnList = "appKey,userId")
) )
public class ImBlacklistEntity extends BaseIdEntity { public class ImBlacklistEntity extends BaseIdEntity {
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String userId; private String userId;
@ -28,8 +28,8 @@ public class ImBlacklistEntity extends BaseIdEntity {
@Column(nullable = false) @Column(nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -10,16 +10,16 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table( @Table(
name = "im_conversation_state", name = "im_conversation_state",
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "targetId", "chatType"}), uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "targetId", "chatType"}),
indexes = { indexes = {
@Index(name = "idx_conv_state_app_user", columnList = "appId,userId"), @Index(name = "idx_conv_state_app_user", columnList = "appKey,userId"),
@Index(name = "idx_conv_state_app_target", columnList = "appId,targetId") @Index(name = "idx_conv_state_app_target", columnList = "appKey,targetId")
} }
) )
public class ImConversationStateEntity extends BaseIdEntity { public class ImConversationStateEntity extends BaseIdEntity {
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String userId; private String userId;
@ -53,8 +53,8 @@ public class ImConversationStateEntity extends BaseIdEntity {
@Column(nullable = false) @Column(nullable = false)
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -13,7 +13,7 @@ import java.time.Instant;
@Entity @Entity
@Table(name = "im_friends", @Table(name = "im_friends",
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "friendId"})) uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "friendId"}))
public class ImFriendEntity { public class ImFriendEntity {
@Id @Id
@ -21,7 +21,7 @@ public class ImFriendEntity {
private Long id; private Long id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String userId; private String userId;
@ -39,8 +39,8 @@ public class ImFriendEntity {
public Long getId() { return id; } public Long getId() { return id; }
public void setId(Long id) { this.id = id; } public void setId(Long id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -13,15 +13,15 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table( @Table(
name = "im_friend_request", name = "im_friend_request",
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "fromUserId", "toUserId"}), uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "fromUserId", "toUserId"}),
indexes = @Index(name = "idx_friend_request_app_to", columnList = "appId,toUserId") indexes = @Index(name = "idx_friend_request_app_to", columnList = "appKey,toUserId")
) )
public class ImFriendRequestEntity extends BaseIdEntity { public class ImFriendRequestEntity extends BaseIdEntity {
public enum Status { PENDING, ACCEPTED, REJECTED } public enum Status { PENDING, ACCEPTED, REJECTED }
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String fromUserId; private String fromUserId;
@ -40,8 +40,8 @@ public class ImFriendRequestEntity extends BaseIdEntity {
private LocalDateTime reviewedAt; private LocalDateTime reviewedAt;
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getFromUserId() { return fromUserId; } public String getFromUserId() { return fromUserId; }
public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; } public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; }

查看文件

@ -17,7 +17,7 @@ public class ImGlobalMuteEntity {
private String id; private String id;
@Column(nullable = false, length = 64, unique = true) @Column(nullable = false, length = 64, unique = true)
private String appId; private String appKey;
@Column(nullable = false) @Column(nullable = false)
private boolean enabled; private boolean enabled;
@ -33,8 +33,8 @@ public class ImGlobalMuteEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public boolean isEnabled() { return enabled; } public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; }

查看文件

@ -16,7 +16,7 @@ public class ImGroupEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String name; private String name;
@ -49,8 +49,8 @@ public class ImGroupEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getName() { return name; } public String getName() { return name; }
public void setName(String name) { this.name = name; } public void setName(String name) { this.name = name; }

查看文件

@ -12,14 +12,14 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table( @Table(
name = "im_group_join_request", 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 class ImGroupJoinRequestEntity extends BaseIdEntity {
public enum Status { PENDING, ACCEPTED, REJECTED } public enum Status { PENDING, ACCEPTED, REJECTED }
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String groupId; private String groupId;
@ -38,8 +38,8 @@ public class ImGroupJoinRequestEntity extends BaseIdEntity {
private LocalDateTime reviewedAt; private LocalDateTime reviewedAt;
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getGroupId() { return groupId; } public String getGroupId() { return groupId; }
public void setGroupId(String groupId) { this.groupId = groupId; } public void setGroupId(String groupId) { this.groupId = groupId; }

查看文件

@ -16,8 +16,8 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "im_message", indexes = { @Table(name = "im_message", indexes = {
@Index(name = "idx_app_from", columnList = "appId,fromUserId"), @Index(name = "idx_app_from", columnList = "appKey,fromUserId"),
@Index(name = "idx_app_to", columnList = "appId,toId") @Index(name = "idx_app_to", columnList = "appKey,toId")
}) })
public class ImMessageEntity { public class ImMessageEntity {
@ -32,7 +32,7 @@ public class ImMessageEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String fromUserId; private String fromUserId;
@ -74,8 +74,8 @@ public class ImMessageEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getFromUserId() { return fromUserId; } public String getFromUserId() { return fromUserId; }
public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; } public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; }

查看文件

@ -14,7 +14,7 @@ public class ImOfflineMessageEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String userId; private String userId;
@ -31,8 +31,8 @@ public class ImOfflineMessageEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -11,13 +11,13 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "im_operation_log", indexes = { @Table(name = "im_operation_log", indexes = {
@Index(name = "idx_op_log_app_time", columnList = "appId,createdAt"), @Index(name = "idx_op_log_app_time", columnList = "appKey,createdAt"),
@Index(name = "idx_op_log_app_operator", columnList = "appId,operatorId") @Index(name = "idx_op_log_app_operator", columnList = "appKey,operatorId")
}) })
public class ImOperationLogEntity extends BaseIdEntity { public class ImOperationLogEntity extends BaseIdEntity {
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String operatorId; private String operatorId;
@ -38,8 +38,8 @@ public class ImOperationLogEntity extends BaseIdEntity {
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class) @JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
private LocalDateTime createdAt; private LocalDateTime createdAt;
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getOperatorId() { return operatorId; } public String getOperatorId() { return operatorId; }
public void setOperatorId(String operatorId) { this.operatorId = operatorId; } public void setOperatorId(String operatorId) { this.operatorId = operatorId; }

查看文件

@ -18,7 +18,7 @@ public class KeywordFilterEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 512) @Column(nullable = false, length = 512)
private String pattern; private String pattern;
@ -39,8 +39,8 @@ public class KeywordFilterEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getPattern() { return pattern; } public String getPattern() { return pattern; }
public void setPattern(String pattern) { this.pattern = pattern; } public void setPattern(String pattern) { this.pattern = pattern; }

查看文件

@ -16,7 +16,7 @@ public class WebhookAlertEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String webhookId; private String webhookId;
@ -43,8 +43,8 @@ public class WebhookAlertEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getWebhookId() { return webhookId; } public String getWebhookId() { return webhookId; }
public void setWebhookId(String webhookId) { this.webhookId = webhookId; } public void setWebhookId(String webhookId) { this.webhookId = webhookId; }

查看文件

@ -16,7 +16,7 @@ public class WebhookConfigEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 512) @Column(nullable = false, length = 512)
private String url; private String url;
@ -40,8 +40,8 @@ public class WebhookConfigEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUrl() { return url; } public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; } public void setUrl(String url) { this.url = url; }

查看文件

@ -14,7 +14,7 @@ public class WebhookDeliveryEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String callbackId; private String callbackId;
@ -46,8 +46,8 @@ public class WebhookDeliveryEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getCallbackId() { return callbackId; } public String getCallbackId() { return callbackId; }
public void setCallbackId(String callbackId) { this.callbackId = callbackId; } public void setCallbackId(String callbackId) { this.callbackId = callbackId; }

查看文件

@ -10,12 +10,12 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface ImAccountRepository extends JpaRepository<ImAccountEntity, String> { public interface ImAccountRepository extends JpaRepository<ImAccountEntity, String> {
Optional<ImAccountEntity> findByAppIdAndUserId(String appId, String userId); Optional<ImAccountEntity> findByAppKeyAndUserId(String appKey, String userId);
boolean existsByAppIdAndUserId(String appId, String userId); boolean existsByAppKeyAndUserId(String appKey, String userId);
Page<ImAccountEntity> findByAppId(String appId, Pageable pageable); Page<ImAccountEntity> findByAppKey(String appKey, Pageable pageable);
long countByAppId(String appId); 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,'%')))") "(LOWER(a.userId) LIKE LOWER(CONCAT('%',:kw,'%')) OR LOWER(a.nickname) LIKE LOWER(CONCAT('%',:kw,'%')))")
List<ImAccountEntity> searchByKeyword(@Param("appId") String appId, @Param("kw") String keyword, Pageable pageable); List<ImAccountEntity> searchByKeyword(@Param("appKey") String appKey, @Param("kw") String keyword, Pageable pageable);
} }

查看文件

@ -7,13 +7,13 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface ImBlacklistRepository extends JpaRepository<ImBlacklistEntity, String> { public interface ImBlacklistRepository extends JpaRepository<ImBlacklistEntity, String> {
List<ImBlacklistEntity> findByAppId(String appId); List<ImBlacklistEntity> findByAppKey(String appKey);
List<ImBlacklistEntity> findByAppIdAndUserId(String appId, String userId); List<ImBlacklistEntity> findByAppKeyAndUserId(String appKey, String userId);
Optional<ImBlacklistEntity> findByAppIdAndUserIdAndBlockedUserId( Optional<ImBlacklistEntity> findByAppKeyAndUserIdAndBlockedUserId(
String appId, String userId, String blockedUserId); 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);
} }

查看文件

@ -7,16 +7,16 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface ImConversationStateRepository extends JpaRepository<ImConversationStateEntity, String> { public interface ImConversationStateRepository extends JpaRepository<ImConversationStateEntity, String> {
Optional<ImConversationStateEntity> findByAppIdAndUserIdAndTargetIdAndChatType( Optional<ImConversationStateEntity> findByAppKeyAndUserIdAndTargetIdAndChatType(
String appId, String userId, String targetId, String chatType); String appKey, String userId, String targetId, String chatType);
List<ImConversationStateEntity> findByAppIdAndUserId(String appId, String userId); List<ImConversationStateEntity> findByAppKeyAndUserId(String appKey, String userId);
List<ImConversationStateEntity> findByAppIdAndUserIdAndHiddenFalse(String appId, String userId); List<ImConversationStateEntity> findByAppKeyAndUserIdAndHiddenFalse(String appKey, String userId);
List<ImConversationStateEntity> findByAppIdAndUserIdAndConversationGroup( List<ImConversationStateEntity> findByAppKeyAndUserIdAndConversationGroup(
String appId, String userId, String conversationGroup); String appKey, String userId, String conversationGroup);
void deleteByAppIdAndUserIdAndTargetIdAndChatType( void deleteByAppKeyAndUserIdAndTargetIdAndChatType(
String appId, String userId, String targetId, String chatType); String appKey, String userId, String targetId, String chatType);
} }

查看文件

@ -9,20 +9,20 @@ import java.util.Optional;
public interface ImFriendRepository extends JpaRepository<ImFriendEntity, Long> { public interface ImFriendRepository extends JpaRepository<ImFriendEntity, Long> {
List<ImFriendEntity> findByAppIdAndUserId(String appId, String userId); List<ImFriendEntity> findByAppKeyAndUserId(String appKey, String userId);
List<ImFriendEntity> findByAppIdAndUserIdAndFriendGroup(String appId, String userId, String friendGroup); List<ImFriendEntity> findByAppKeyAndUserIdAndFriendGroup(String appKey, String userId, String friendGroup);
Optional<ImFriendEntity> findByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId); Optional<ImFriendEntity> findByAppKeyAndUserIdAndFriendId(String appKey, String userId, String friendId);
boolean existsByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId); boolean existsByAppKeyAndUserIdAndFriendId(String appKey, String userId, String friendId);
@Transactional @Transactional
void deleteByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId); void deleteByAppKeyAndUserIdAndFriendId(String appKey, String userId, String friendId);
@Transactional @Transactional
void deleteByAppIdAndUserId(String appId, String userId); void deleteByAppKeyAndUserId(String appKey, String userId);
@Transactional @Transactional
void deleteByAppIdAndFriendId(String appId, String friendId); void deleteByAppKeyAndFriendId(String appKey, String friendId);
} }

查看文件

@ -7,10 +7,10 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface ImFriendRequestRepository extends JpaRepository<ImFriendRequestEntity, String> { public interface ImFriendRequestRepository extends JpaRepository<ImFriendRequestEntity, String> {
List<ImFriendRequestEntity> findByAppId(String appId); List<ImFriendRequestEntity> findByAppKey(String appKey);
Optional<ImFriendRequestEntity> findByAppIdAndFromUserIdAndToUserId( Optional<ImFriendRequestEntity> findByAppKeyAndFromUserIdAndToUserId(
String appId, String fromUserId, String toUserId); String appKey, String fromUserId, String toUserId);
List<ImFriendRequestEntity> findByAppIdAndToUserId(String appId, String toUserId); List<ImFriendRequestEntity> findByAppKeyAndToUserId(String appKey, String toUserId);
List<ImFriendRequestEntity> findByAppIdAndFromUserId(String appId, String fromUserId); List<ImFriendRequestEntity> findByAppKeyAndFromUserId(String appKey, String fromUserId);
} }

查看文件

@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional; import java.util.Optional;
public interface ImGlobalMuteRepository extends JpaRepository<ImGlobalMuteEntity, String> { public interface ImGlobalMuteRepository extends JpaRepository<ImGlobalMuteEntity, String> {
Optional<ImGlobalMuteEntity> findByAppId(String appId); Optional<ImGlobalMuteEntity> findByAppKey(String appKey);
} }

查看文件

@ -7,10 +7,10 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface ImGroupJoinRequestRepository extends JpaRepository<ImGroupJoinRequestEntity, String> { public interface ImGroupJoinRequestRepository extends JpaRepository<ImGroupJoinRequestEntity, String> {
List<ImGroupJoinRequestEntity> findByAppId(String appId); List<ImGroupJoinRequestEntity> findByAppKey(String appKey);
Optional<ImGroupJoinRequestEntity> findByAppIdAndGroupIdAndRequesterId( Optional<ImGroupJoinRequestEntity> findByAppKeyAndGroupIdAndRequesterId(
String appId, String groupId, String requesterId); String appKey, String groupId, String requesterId);
List<ImGroupJoinRequestEntity> findByAppIdAndGroupId(String appId, String groupId); List<ImGroupJoinRequestEntity> findByAppKeyAndGroupId(String appKey, String groupId);
List<ImGroupJoinRequestEntity> findByAppIdAndRequesterId(String appId, String requesterId); List<ImGroupJoinRequestEntity> findByAppKeyAndRequesterId(String appKey, String requesterId);
} }

查看文件

@ -8,22 +8,22 @@ import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String> { public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String> {
List<ImGroupEntity> findByAppId(String appId); List<ImGroupEntity> findByAppKey(String appKey);
long countByAppId(String appId); long countByAppKey(String appKey);
@Query(value = """ @Query(value = """
SELECT * FROM im_group SELECT * FROM im_group
WHERE app_id = :appId WHERE app_key = :appKey
AND JSON_CONTAINS(member_ids, JSON_QUOTE(:userId)) AND JSON_CONTAINS(member_ids, JSON_QUOTE(:userId))
ORDER BY created_at DESC ORDER BY created_at DESC
""", nativeQuery = true) """, nativeQuery = true)
List<ImGroupEntity> findUserGroups( List<ImGroupEntity> findUserGroups(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId); @Param("userId") String userId);
@Query(""" @Query("""
select g from ImGroupEntity g select g from ImGroupEntity g
where g.appId = :appId where g.appKey = :appKey
and (:keyword is null or :keyword = '' or and (:keyword is null or :keyword = '' or
lower(g.id) like lower(concat('%', :keyword, '%')) or lower(g.id) like lower(concat('%', :keyword, '%')) or
lower(g.name) like lower(concat('%', :keyword, '%')) or lower(g.name) like lower(concat('%', :keyword, '%')) or
@ -32,7 +32,7 @@ public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String>
order by g.createdAt desc order by g.createdAt desc
""") """)
List<ImGroupEntity> searchByKeyword( List<ImGroupEntity> searchByKeyword(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("keyword") String keyword, @Param("keyword") String keyword,
Pageable pageable); Pageable pageable);
} }

查看文件

@ -25,16 +25,16 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
chat_type, chat_type,
MAX(created_at) AS last_time MAX(created_at) AS last_time
FROM im_message FROM im_message
WHERE app_id = :appId AND chat_type = 'SINGLE' WHERE app_key = :appKey AND chat_type = 'SINGLE'
AND (from_user_id = :userId OR to_id = :userId) AND (from_user_id = :userId OR to_id = :userId)
GROUP BY target_id, chat_type GROUP BY target_id, chat_type
UNION ALL UNION ALL
SELECT to_id AS target_id, chat_type, MAX(created_at) AS last_time SELECT to_id AS target_id, chat_type, MAX(created_at) AS last_time
FROM im_message FROM im_message
WHERE app_id = :appId AND chat_type = 'GROUP' WHERE app_key = :appKey AND chat_type = 'GROUP'
AND to_id IN ( AND to_id IN (
SELECT id FROM im_group SELECT id FROM im_group
WHERE app_id = :appId AND JSON_CONTAINS(member_ids, JSON_QUOTE(:userId)) WHERE app_key = :appKey AND JSON_CONTAINS(member_ids, JSON_QUOTE(:userId))
) )
GROUP BY to_id, chat_type GROUP BY to_id, chat_type
) combined ) combined
@ -42,31 +42,31 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
LIMIT :size LIMIT :size
""", nativeQuery = true) """, nativeQuery = true)
List<ConversationSummary> findConversations( List<ConversationSummary> findConversations(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId, @Param("userId") String userId,
@Param("size") int size); @Param("size") int size);
Page<ImMessageEntity> findByAppIdAndToIdOrderByCreatedAtDesc( Page<ImMessageEntity> findByAppKeyAndToIdOrderByCreatedAtDesc(
String appId, String toId, Pageable pageable); String appKey, String toId, Pageable pageable);
Page<ImMessageEntity> findByAppIdAndFromUserIdAndToIdOrderByCreatedAtDesc( Page<ImMessageEntity> findByAppKeyAndFromUserIdAndToIdOrderByCreatedAtDesc(
String appId, String fromUserId, String toId, Pageable pageable); String appKey, String fromUserId, String toId, Pageable pageable);
@Query(""" @Query("""
select m from ImMessageEntity m 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.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
and ((m.fromUserId = :userId and m.toId = :peerId) and ((m.fromUserId = :userId and m.toId = :peerId)
or (m.fromUserId = :peerId and m.toId = :userId)) or (m.fromUserId = :peerId and m.toId = :userId))
order by m.createdAt desc order by m.createdAt desc
""") """)
Page<ImMessageEntity> findSingleConversation( Page<ImMessageEntity> findSingleConversation(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId, @Param("userId") String userId,
@Param("peerId") String peerId, @Param("peerId") String peerId,
Pageable pageable); Pageable pageable);
@Query(""" @Query("""
select m from ImMessageEntity m 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.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
and ((m.fromUserId = :userId and m.toId = :peerId) and ((m.fromUserId = :userId and m.toId = :peerId)
or (m.fromUserId = :peerId and m.toId = :userId)) or (m.fromUserId = :peerId and m.toId = :userId))
@ -81,7 +81,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
order by m.createdAt desc order by m.createdAt desc
""") """)
Page<ImMessageEntity> findSingleConversationFiltered( Page<ImMessageEntity> findSingleConversationFiltered(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId, @Param("userId") String userId,
@Param("peerId") String peerId, @Param("peerId") String peerId,
@Param("msgType") ImMessageEntity.MsgType msgType, @Param("msgType") ImMessageEntity.MsgType msgType,
@ -90,33 +90,33 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
@Param("endTime") LocalDateTime endTime, @Param("endTime") LocalDateTime endTime,
Pageable pageable); Pageable pageable);
List<ImMessageEntity> findByAppIdAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc( List<ImMessageEntity> findByAppKeyAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
String appId, String appKey,
String fromUserId, String fromUserId,
String toId, String toId,
LocalDateTime createdAt); LocalDateTime createdAt);
List<ImMessageEntity> findByAppIdAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc( List<ImMessageEntity> findByAppKeyAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
String appId, String appKey,
String toId, String toId,
ImMessageEntity.ChatType chatType, ImMessageEntity.ChatType chatType,
LocalDateTime createdAt); LocalDateTime createdAt);
@Query(""" @Query("""
select m from ImMessageEntity m 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.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
and m.toId = :groupId and m.toId = :groupId
order by m.createdAt desc order by m.createdAt desc
""") """)
Page<ImMessageEntity> findGroupHistory( Page<ImMessageEntity> findGroupHistory(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("groupId") String groupId, @Param("groupId") String groupId,
Pageable pageable); Pageable pageable);
@Query(""" @Query("""
select m from ImMessageEntity m 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.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
and m.toId = :groupId and m.toId = :groupId
and (:msgType is null or m.msgType = :msgType) and (:msgType is null or m.msgType = :msgType)
@ -129,7 +129,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
order by m.createdAt desc order by m.createdAt desc
""") """)
Page<ImMessageEntity> findGroupHistoryFiltered( Page<ImMessageEntity> findGroupHistoryFiltered(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("groupId") String groupId, @Param("groupId") String groupId,
@Param("msgType") ImMessageEntity.MsgType msgType, @Param("msgType") ImMessageEntity.MsgType msgType,
@Param("keyword") String keyword, @Param("keyword") String keyword,
@ -139,7 +139,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
@Query(""" @Query("""
select m from ImMessageEntity m select m from ImMessageEntity m
where m.appId = :appId where m.appKey = :appKey
and (:chatType is null or m.chatType = :chatType) and (:chatType is null or m.chatType = :chatType)
and (:msgType is null or m.msgType = :msgType) and (:msgType is null or m.msgType = :msgType)
and (:keyword is null or :keyword = '' or and (:keyword is null or :keyword = '' or
@ -152,7 +152,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
order by m.createdAt desc order by m.createdAt desc
""") """)
Page<ImMessageEntity> searchByKeyword( Page<ImMessageEntity> searchByKeyword(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("chatType") ImMessageEntity.ChatType chatType, @Param("chatType") ImMessageEntity.ChatType chatType,
@Param("msgType") ImMessageEntity.MsgType msgType, @Param("msgType") ImMessageEntity.MsgType msgType,
@Param("keyword") String keyword, @Param("keyword") String keyword,
@ -162,7 +162,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
@Query(""" @Query("""
select m from ImMessageEntity m select m from ImMessageEntity m
where m.appId = :appId where m.appKey = :appKey
and (:chatType is null or m.chatType = :chatType) and (:chatType is null or m.chatType = :chatType)
and (:msgType is null or m.msgType = :msgType) and (:msgType is null or m.msgType = :msgType)
and (:keyword is null or :keyword = '' or and (:keyword is null or :keyword = '' or
@ -178,7 +178,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
or (m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP or (m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
and m.toId in ( and m.toId in (
select g.id from ImGroupEntity g select g.id from ImGroupEntity g
where g.appId = :appId where g.appKey = :appKey
and function('JSON_CONTAINS', g.memberIds, function('JSON_QUOTE', :userId)) = 1 and function('JSON_CONTAINS', g.memberIds, function('JSON_QUOTE', :userId)) = 1
) )
) )
@ -186,7 +186,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
order by m.createdAt desc order by m.createdAt desc
""") """)
Page<ImMessageEntity> searchByKeywordForUser( Page<ImMessageEntity> searchByKeywordForUser(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId, @Param("userId") String userId,
@Param("chatType") ImMessageEntity.ChatType chatType, @Param("chatType") ImMessageEntity.ChatType chatType,
@Param("msgType") ImMessageEntity.MsgType msgType, @Param("msgType") ImMessageEntity.MsgType msgType,
@ -197,7 +197,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
@Query(""" @Query("""
select count(m) from ImMessageEntity m select count(m) from ImMessageEntity m
where m.appId = :appId where m.appKey = :appKey
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
and ((m.fromUserId = :userId and m.toId = :peerId) and ((m.fromUserId = :userId and m.toId = :peerId)
or (m.fromUserId = :peerId and m.toId = :userId)) or (m.fromUserId = :peerId and m.toId = :userId))
@ -205,48 +205,48 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
and (:since is null or m.createdAt > :since) and (:since is null or m.createdAt > :since)
""") """)
long countUnreadSingleConversation( long countUnreadSingleConversation(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId, @Param("userId") String userId,
@Param("peerId") String peerId, @Param("peerId") String peerId,
@Param("since") LocalDateTime since); @Param("since") LocalDateTime since);
@Query(""" @Query("""
select count(m) from ImMessageEntity m 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.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
and m.toId = :groupId and m.toId = :groupId
and m.fromUserId <> :userId and m.fromUserId <> :userId
and (:since is null or m.createdAt > :since) and (:since is null or m.createdAt > :since)
""") """)
long countUnreadGroupConversation( long countUnreadGroupConversation(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId, @Param("userId") String userId,
@Param("groupId") String groupId, @Param("groupId") String groupId,
@Param("since") LocalDateTime since); @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") @Query("select count(m) from ImMessageEntity m where m.appKey = :appKey and m.createdAt >= :since")
long countByAppIdAndCreatedAtAfter(@Param("appId") String appId, @Param("since") LocalDateTime 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") @Query("select count(m) from ImMessageEntity m where m.appKey = :appKey and m.chatType = :chatType and m.createdAt >= :since")
long countByAppIdAndChatTypeAndCreatedAtAfter( long countByAppKeyAndChatTypeAndCreatedAtAfter(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("chatType") ImMessageEntity.ChatType chatType, @Param("chatType") ImMessageEntity.ChatType chatType,
@Param("since") LocalDateTime since); @Param("since") LocalDateTime since);
default long countTodayByAppId(String appId) { default long countTodayByAppKey(String appKey) {
return countByAppIdAndCreatedAtAfter(appId, LocalDateTime.now().toLocalDate().atStartOfDay()); return countByAppKeyAndCreatedAtAfter(appKey, LocalDateTime.now().toLocalDate().atStartOfDay());
} }
@Query(""" @Query("""
select m from ImMessageEntity m 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.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
and m.toId = :userId and m.toId = :userId
and m.status <> com.xuqm.im.entity.ImMessageEntity$MsgStatus.READ and m.status <> com.xuqm.im.entity.ImMessageEntity$MsgStatus.READ
""") """)
List<ImMessageEntity> findUnreadByAppIdAndToId( List<ImMessageEntity> findUnreadByAppKeyAndToId(
@Param("appId") String appId, @Param("appKey") String appKey,
@Param("userId") String userId); @Param("userId") String userId);
} }

查看文件

@ -11,14 +11,14 @@ import java.util.List;
@Repository @Repository
public interface ImOfflineMessageRepository extends JpaRepository<ImOfflineMessageEntity, String> { public interface ImOfflineMessageRepository extends JpaRepository<ImOfflineMessageEntity, String> {
List<ImOfflineMessageEntity> findByAppIdAndUserIdAndDeliveredFalse(String appId, String userId); List<ImOfflineMessageEntity> findByAppKeyAndUserIdAndDeliveredFalse(String appKey, String userId);
@Modifying @Modifying
@Query("UPDATE ImOfflineMessageEntity o SET o.delivered = true WHERE o.id IN ?1") @Query("UPDATE ImOfflineMessageEntity o SET o.delivered = true WHERE o.id IN ?1")
void markDeliveredByIds(List<String> ids); void markDeliveredByIds(List<String> ids);
@Query("SELECT COUNT(o) FROM ImOfflineMessageEntity o WHERE o.appId = ?1 AND o.userId = ?2 AND o.delivered = false") @Query("SELECT COUNT(o) FROM ImOfflineMessageEntity o WHERE o.appKey = ?1 AND o.userId = ?2 AND o.delivered = false")
long countUndeliveredByAppIdAndUserId(String appId, String userId); long countUndeliveredByAppKeyAndUserId(String appKey, String userId);
void deleteByAppIdAndUserIdAndDeliveredTrue(String appId, String userId); void deleteByAppKeyAndUserIdAndDeliveredTrue(String appKey, String userId);
} }

查看文件

@ -6,5 +6,5 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface ImOperationLogRepository extends JpaRepository<ImOperationLogEntity, String> { public interface ImOperationLogRepository extends JpaRepository<ImOperationLogEntity, String> {
Page<ImOperationLogEntity> findByAppIdOrderByCreatedAtDesc(String appId, Pageable pageable); Page<ImOperationLogEntity> findByAppKeyOrderByCreatedAtDesc(String appKey, Pageable pageable);
} }

查看文件

@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;
public interface KeywordFilterRepository extends JpaRepository<KeywordFilterEntity, String> { public interface KeywordFilterRepository extends JpaRepository<KeywordFilterEntity, String> {
List<KeywordFilterEntity> findByAppIdAndEnabledTrue(String appId); List<KeywordFilterEntity> findByAppKeyAndEnabledTrue(String appKey);
List<KeywordFilterEntity> findByAppId(String appId); List<KeywordFilterEntity> findByAppKey(String appKey);
java.util.Optional<KeywordFilterEntity> findByIdAndAppId(String id, String appId); java.util.Optional<KeywordFilterEntity> findByIdAndAppId(String id, String appKey);
} }

查看文件

@ -8,10 +8,10 @@ import org.springframework.data.jpa.repository.Query;
public interface WebhookAlertRepository extends JpaRepository<WebhookAlertEntity, String> { public interface WebhookAlertRepository extends JpaRepository<WebhookAlertEntity, String> {
Page<WebhookAlertEntity> findByAppId(String appId, Pageable pageable); Page<WebhookAlertEntity> findByAppKey(String appKey, Pageable pageable);
Page<WebhookAlertEntity> findByAppIdAndAcknowledged(String appId, boolean acknowledged, Pageable pageable); Page<WebhookAlertEntity> findByAppKeyAndAcknowledged(String appKey, boolean acknowledged, Pageable pageable);
@Query("SELECT COUNT(a) FROM WebhookAlertEntity a WHERE a.appId = ?1 AND a.acknowledged = false") @Query("SELECT COUNT(a) FROM WebhookAlertEntity a WHERE a.appKey = ?1 AND a.acknowledged = false")
long countUnacknowledgedByAppId(String appId); long countUnacknowledgedByAppKey(String appKey);
} }

查看文件

@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;
public interface WebhookConfigRepository extends JpaRepository<WebhookConfigEntity, String> { public interface WebhookConfigRepository extends JpaRepository<WebhookConfigEntity, String> {
List<WebhookConfigEntity> findByAppIdAndEnabledTrue(String appId); List<WebhookConfigEntity> findByAppKeyAndEnabledTrue(String appKey);
List<WebhookConfigEntity> findByAppId(String appId); List<WebhookConfigEntity> findByAppKey(String appKey);
java.util.Optional<WebhookConfigEntity> findByIdAndAppId(String id, String appId); java.util.Optional<WebhookConfigEntity> findByIdAndAppId(String id, String appKey);
} }

查看文件

@ -10,21 +10,21 @@ import java.util.List;
public interface WebhookDeliveryRepository extends JpaRepository<WebhookDeliveryEntity, String> { public interface WebhookDeliveryRepository extends JpaRepository<WebhookDeliveryEntity, String> {
Page<WebhookDeliveryEntity> findByAppIdAndCallbackEvent(String appId, String callbackEvent, Pageable pageable); Page<WebhookDeliveryEntity> findByAppKeyAndCallbackEvent(String appKey, String callbackEvent, Pageable pageable);
Page<WebhookDeliveryEntity> findByAppId(String appId, Pageable pageable); Page<WebhookDeliveryEntity> findByAppKey(String appKey, Pageable pageable);
Page<WebhookDeliveryEntity> findByAppIdAndSuccess(String appId, boolean success, Pageable pageable); Page<WebhookDeliveryEntity> findByAppKeyAndSuccess(String appKey, boolean success, Pageable pageable);
List<WebhookDeliveryEntity> findByCallbackId(String callbackId); List<WebhookDeliveryEntity> findByCallbackId(String callbackId);
@Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appId = ?1 AND d.success = true AND d.createdAt >= ?2") @Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.success = true AND d.createdAt >= ?2")
long countSuccessfulByAppIdSince(String appId, LocalDateTime since); 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") @Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.success = false AND d.createdAt >= ?2")
long countFailedByAppIdSince(String appId, LocalDateTime since); long countFailedByAppKeySince(String appKey, LocalDateTime since);
@Query("SELECT d.callbackEvent, COUNT(d), SUM(CASE WHEN d.success = true THEN 1 ELSE 0 END) " + @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") "FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.createdAt >= ?2 GROUP BY d.callbackEvent")
List<Object[]> statsByAppIdSince(String appId, LocalDateTime since); List<Object[]> statsByAppKeySince(String appKey, LocalDateTime since);
} }

查看文件

@ -23,11 +23,11 @@ public class BlacklistService {
@Transactional @Transactional
public ImBlacklistEntity add(String appKey, String userId, String blockedUserId) { public ImBlacklistEntity add(String appKey, String userId, String blockedUserId) {
return repository.findByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId) return repository.findByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId)
.orElseGet(() -> { .orElseGet(() -> {
ImBlacklistEntity entity = new ImBlacklistEntity(); ImBlacklistEntity entity = new ImBlacklistEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setUserId(userId); entity.setUserId(userId);
entity.setBlockedUserId(blockedUserId); entity.setBlockedUserId(blockedUserId);
entity.setCreatedAt(LocalDateTime.now()); entity.setCreatedAt(LocalDateTime.now());
@ -39,24 +39,24 @@ public class BlacklistService {
@Transactional @Transactional
public void remove(String appKey, String userId, String blockedUserId) { 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); .orElse(null);
repository.deleteByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId); repository.deleteByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId);
if (entity != null) { if (entity != null) {
dispatchWebhook(entity, "blacklist.removed"); dispatchWebhook(entity, "blacklist.removed");
} }
} }
public List<ImBlacklistEntity> list(String appKey, String userId) { public List<ImBlacklistEntity> list(String appKey, String userId) {
return repository.findByAppIdAndUserId(appKey, userId); return repository.findByAppKeyAndUserId(appKey, userId);
} }
public List<ImBlacklistEntity> listByApp(String appKey) { public List<ImBlacklistEntity> listByApp(String appKey) {
return repository.findByAppId(appKey); return repository.findByAppKey(appKey);
} }
public boolean isBlocked(String appKey, String userId, String blockedUserId) { 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) { public boolean isEitherBlocked(String appKey, String userId, String targetUserId) {
@ -65,11 +65,11 @@ public class BlacklistService {
private void dispatchWebhook(ImBlacklistEntity entity, String callbackEvent) { private void dispatchWebhook(ImBlacklistEntity entity, String callbackEvent) {
webhookDispatchService.dispatch( webhookDispatchService.dispatch(
entity.getAppId(), entity.getAppKey(),
"blacklist", "blacklist",
callbackEvent, callbackEvent,
new BlacklistCallbackPayload( new BlacklistCallbackPayload(
entity.getAppId(), entity.getAppKey(),
entity.getId(), entity.getId(),
entity.getUserId(), entity.getUserId(),
entity.getBlockedUserId(), entity.getBlockedUserId(),

查看文件

@ -87,7 +87,7 @@ public class ConversationStateService {
@Transactional @Transactional
public void deleteConversationState(String appKey, String userId, String targetId, String chatType) { public void deleteConversationState(String appKey, String userId, String targetId, String chatType) {
repository.deleteByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType); repository.deleteByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType);
} }
@Transactional @Transactional
@ -107,7 +107,7 @@ public class ConversationStateService {
public void clearHiddenForUsers(String appKey, String targetId, String chatType, Collection<String> userIds) { public void clearHiddenForUsers(String appKey, String targetId, String chatType, Collection<String> userIds) {
for (String userId : userIds) { for (String userId : userIds) {
ImConversationStateEntity state = repository ImConversationStateEntity state = repository
.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType) .findByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
.orElse(null); .orElse(null);
if (state != null && state.isHidden()) { if (state != null && state.isHidden()) {
state.setHidden(false); state.setHidden(false);
@ -118,11 +118,11 @@ public class ConversationStateService {
} }
public ImConversationStateEntity getOrCreate(String appKey, String userId, String targetId, String chatType) { 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(() -> { .orElseGet(() -> {
ImConversationStateEntity entity = new ImConversationStateEntity(); ImConversationStateEntity entity = new ImConversationStateEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setUserId(userId); entity.setUserId(userId);
entity.setTargetId(targetId); entity.setTargetId(targetId);
entity.setChatType(chatType); entity.setChatType(chatType);
@ -139,16 +139,16 @@ public class ConversationStateService {
} }
public ImConversationStateEntity find(String appKey, String userId, String targetId, String chatType) { 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); .orElse(null);
} }
public List<ImConversationStateEntity> listVisible(String appKey, String userId) { public List<ImConversationStateEntity> listVisible(String appKey, String userId) {
return repository.findByAppIdAndUserIdAndHiddenFalse(appKey, userId); return repository.findByAppKeyAndUserIdAndHiddenFalse(appKey, userId);
} }
public List<String> listConversationGroups(String appKey, String userId) { public List<String> listConversationGroups(String appKey, String userId) {
return repository.findByAppIdAndUserId(appKey, userId).stream() return repository.findByAppKeyAndUserId(appKey, userId).stream()
.map(ImConversationStateEntity::getConversationGroup) .map(ImConversationStateEntity::getConversationGroup)
.filter(group -> group != null && !group.isBlank()) .filter(group -> group != null && !group.isBlank())
.distinct() .distinct()
@ -157,7 +157,7 @@ public class ConversationStateService {
} }
public List<ImConversationStateEntity> listByConversationGroup(String appKey, String userId, String conversationGroup) { public List<ImConversationStateEntity> 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) { private void touch(ImConversationStateEntity entity) {

查看文件

@ -51,12 +51,12 @@ public class FriendRequestService {
throw new BusinessException(403, "当前应用未开放好友申请"); throw new BusinessException(403, "当前应用未开放好友申请");
} }
final boolean[] created = {false}; final boolean[] created = {false};
ImFriendRequestEntity saved = requestRepository.findByAppIdAndFromUserIdAndToUserId(appKey, fromUserId, toUserId) ImFriendRequestEntity saved = requestRepository.findByAppKeyAndFromUserIdAndToUserId(appKey, fromUserId, toUserId)
.orElseGet(() -> { .orElseGet(() -> {
created[0] = true; created[0] = true;
ImFriendRequestEntity entity = new ImFriendRequestEntity(); ImFriendRequestEntity entity = new ImFriendRequestEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setFromUserId(fromUserId); entity.setFromUserId(fromUserId);
entity.setToUserId(toUserId); entity.setToUserId(toUserId);
entity.setRemark(remark); entity.setRemark(remark);
@ -94,10 +94,10 @@ public class FriendRequestService {
request.setReviewedAt(LocalDateTime.now()); request.setReviewedAt(LocalDateTime.now());
requestRepository.save(request); requestRepository.save(request);
friendRepository friendRepository
.findByAppIdAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId()) .findByAppKeyAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId())
.orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId())); .orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId()));
friendRepository friendRepository
.findByAppIdAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId()) .findByAppKeyAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId())
.orElseGet(() -> friendEntity(appKey, request.getToUserId(), request.getFromUserId())); .orElseGet(() -> friendEntity(appKey, request.getToUserId(), request.getFromUserId()));
dispatchWebhook(request, "friend.request.accepted"); dispatchWebhook(request, "friend.request.accepted");
publishNotification( publishNotification(
@ -148,17 +148,17 @@ public class FriendRequestService {
} }
public List<ImFriendRequestEntity> incoming(String appKey, String userId) { public List<ImFriendRequestEntity> 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())) .filter(request -> ImFriendRequestEntity.Status.PENDING.name().equals(request.getStatus()))
.toList(); .toList();
} }
public List<ImFriendRequestEntity> outgoing(String appKey, String userId) { public List<ImFriendRequestEntity> outgoing(String appKey, String userId) {
return requestRepository.findByAppIdAndFromUserId(appKey, userId); return requestRepository.findByAppKeyAndFromUserId(appKey, userId);
} }
public List<ImFriendRequestEntity> listByApp(String appKey) { public List<ImFriendRequestEntity> listByApp(String appKey) {
return requestRepository.findByAppId(appKey); return requestRepository.findByAppKey(appKey);
} }
@Transactional @Transactional
@ -176,7 +176,7 @@ public class FriendRequestService {
private ImFriendRequestEntity getRequest(String appKey, String requestId, String operatorId) { private ImFriendRequestEntity getRequest(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = requestRepository.findById(requestId) ImFriendRequestEntity request = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "好友申请不存在")); .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, "无权操作"); throw new BusinessException(403, "无权操作");
} }
return request; return request;
@ -185,7 +185,7 @@ public class FriendRequestService {
private ImFriendRequestEntity getRequest(String appKey, String requestId) { private ImFriendRequestEntity getRequest(String appKey, String requestId) {
ImFriendRequestEntity request = requestRepository.findById(requestId) ImFriendRequestEntity request = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "好友申请不存在")); .orElseThrow(() -> new BusinessException(404, "好友申请不存在"));
if (!request.getAppId().equals(appKey)) { if (!request.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
return request; return request;
@ -201,11 +201,11 @@ public class FriendRequestService {
request.setReviewedAt(LocalDateTime.now()); request.setReviewedAt(LocalDateTime.now());
ImFriendRequestEntity saved = requestRepository.save(request); ImFriendRequestEntity saved = requestRepository.save(request);
friendRepository friendRepository
.findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getFromUserId(), request.getToUserId()) .findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getFromUserId(), request.getToUserId())
.orElseGet(() -> friendEntity(request.getAppId(), request.getFromUserId(), request.getToUserId())); .orElseGet(() -> friendEntity(request.getAppKey(), request.getFromUserId(), request.getToUserId()));
friendRepository friendRepository
.findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getToUserId(), request.getFromUserId()) .findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getToUserId(), request.getFromUserId())
.orElseGet(() -> friendEntity(request.getAppId(), request.getToUserId(), request.getFromUserId())); .orElseGet(() -> friendEntity(request.getAppKey(), request.getToUserId(), request.getFromUserId()));
dispatchWebhook(saved, "friend.request.accepted"); dispatchWebhook(saved, "friend.request.accepted");
publishNotification( publishNotification(
request, request,
@ -225,7 +225,7 @@ public class FriendRequestService {
private com.xuqm.im.entity.ImFriendEntity friendEntity(String appKey, String userId, String friendId) { 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(); com.xuqm.im.entity.ImFriendEntity entity = new com.xuqm.im.entity.ImFriendEntity();
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setUserId(userId); entity.setUserId(userId);
entity.setFriendId(friendId); entity.setFriendId(friendId);
return friendRepository.save(entity); return friendRepository.save(entity);
@ -245,7 +245,7 @@ public class FriendRequestService {
) { ) {
ImMessageEntity message = new ImMessageEntity(); ImMessageEntity message = new ImMessageEntity();
message.setId(UUID.randomUUID().toString()); message.setId(UUID.randomUUID().toString());
message.setAppId(request.getAppId()); message.setAppKey(request.getAppKey());
message.setFromUserId(fromUserId); message.setFromUserId(fromUserId);
message.setToId(toUserId); message.setToId(toUserId);
message.setChatType(ImMessageEntity.ChatType.SINGLE); message.setChatType(ImMessageEntity.ChatType.SINGLE);
@ -302,11 +302,11 @@ public class FriendRequestService {
private void dispatchWebhook(ImFriendRequestEntity request, String callbackEvent) { private void dispatchWebhook(ImFriendRequestEntity request, String callbackEvent) {
webhookDispatchService.dispatch( webhookDispatchService.dispatch(
request.getAppId(), request.getAppKey(),
"friend_request", "friend_request",
callbackEvent, callbackEvent,
new FriendRequestCallbackPayload( new FriendRequestCallbackPayload(
request.getAppId(), request.getAppKey(),
request.getId(), request.getId(),
request.getFromUserId(), request.getFromUserId(),
request.getToUserId(), request.getToUserId(),

查看文件

@ -17,14 +17,14 @@ public class GlobalMuteService {
} }
public boolean isEnabled(String appKey) { 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) { public ImGlobalMuteEntity get(String appKey) {
return repository.findByAppId(appKey).orElseGet(() -> { return repository.findByAppKey(appKey).orElseGet(() -> {
ImGlobalMuteEntity entity = new ImGlobalMuteEntity(); ImGlobalMuteEntity entity = new ImGlobalMuteEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setEnabled(false); entity.setEnabled(false);
entity.setCreatedAt(LocalDateTime.now()); entity.setCreatedAt(LocalDateTime.now());
entity.setUpdatedAt(LocalDateTime.now()); entity.setUpdatedAt(LocalDateTime.now());
@ -33,10 +33,10 @@ public class GlobalMuteService {
} }
public ImGlobalMuteEntity setEnabled(String appKey, boolean enabled) { public ImGlobalMuteEntity setEnabled(String appKey, boolean enabled) {
ImGlobalMuteEntity entity = repository.findByAppId(appKey).orElseGet(() -> { ImGlobalMuteEntity entity = repository.findByAppKey(appKey).orElseGet(() -> {
ImGlobalMuteEntity created = new ImGlobalMuteEntity(); ImGlobalMuteEntity created = new ImGlobalMuteEntity();
created.setId(UUID.randomUUID().toString()); created.setId(UUID.randomUUID().toString());
created.setAppId(appKey); created.setAppKey(appKey);
created.setCreatedAt(LocalDateTime.now()); created.setCreatedAt(LocalDateTime.now());
return created; return created;
}); });

查看文件

@ -47,11 +47,11 @@ public class ImAccountService {
} }
public LoginResult loginOrRegister(String appKey, String userId) { public LoginResult loginOrRegister(String appKey, String userId) {
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId) ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId)
.orElseGet(() -> { .orElseGet(() -> {
ImAccountEntity e = new ImAccountEntity(); ImAccountEntity e = new ImAccountEntity();
e.setId(UUID.randomUUID().toString()); e.setId(UUID.randomUUID().toString());
e.setAppId(appKey); e.setAppKey(appKey);
e.setUserId(userId); e.setUserId(userId);
e.setGender(ImAccountEntity.Gender.UNKNOWN); e.setGender(ImAccountEntity.Gender.UNKNOWN);
e.setStatus(ImAccountEntity.Status.ACTIVE); e.setStatus(ImAccountEntity.Status.ACTIVE);
@ -67,7 +67,7 @@ public class ImAccountService {
} }
public ImAccountEntity getAccount(String appKey, String userId) { public ImAccountEntity getAccount(String appKey, String userId) {
return accountRepository.findByAppIdAndUserId(appKey, userId) return accountRepository.findByAppKeyAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "账号不存在")); .orElseThrow(() -> new BusinessException(404, "账号不存在"));
} }
@ -98,11 +98,11 @@ public class ImAccountService {
public ImAccountEntity importAccount(String appKey, String userId, String nickname, public ImAccountEntity importAccount(String appKey, String userId, String nickname,
String avatar, ImAccountEntity.Gender gender, String avatar, ImAccountEntity.Gender gender,
ImAccountEntity.Status status) { ImAccountEntity.Status status) {
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId) ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId)
.orElseGet(() -> { .orElseGet(() -> {
ImAccountEntity entity = new ImAccountEntity(); ImAccountEntity entity = new ImAccountEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setUserId(userId); entity.setUserId(userId);
entity.setCreatedAt(LocalDateTime.now()); entity.setCreatedAt(LocalDateTime.now());
return entity; return entity;
@ -122,12 +122,12 @@ public class ImAccountService {
} }
public void deleteAccount(String appKey, String userId) { public void deleteAccount(String appKey, String userId) {
accountRepository.findByAppIdAndUserId(appKey, userId) accountRepository.findByAppKeyAndUserId(appKey, userId)
.ifPresent(accountRepository::delete); .ifPresent(accountRepository::delete);
} }
public boolean exists(String appKey, String userId) { public boolean exists(String appKey, String userId) {
return accountRepository.existsByAppIdAndUserId(appKey, userId); return accountRepository.existsByAppKeyAndUserId(appKey, userId);
} }
public List<ImAccountEntity> searchAccounts(String appKey, String keyword, int size) { public List<ImAccountEntity> searchAccounts(String appKey, String keyword, int size) {

查看文件

@ -75,7 +75,7 @@ public class ImGroupService {
ImGroupEntity group = new ImGroupEntity(); ImGroupEntity group = new ImGroupEntity();
group.setId(UUID.randomUUID().toString()); group.setId(UUID.randomUUID().toString());
group.setAppId(appKey); group.setAppKey(appKey);
group.setName(name); group.setName(name);
group.setGroupType(normalizeGroupType(groupType)); group.setGroupType(normalizeGroupType(groupType));
group.setCreatorId(creatorId); group.setCreatorId(creatorId);
@ -135,7 +135,7 @@ public class ImGroupService {
if (changed) { if (changed) {
group.setMemberIds(toJson(members)); group.setMemberIds(toJson(members));
ImGroupEntity saved = groupRepository.save(group); 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)); java.util.Map.of("groupId", saved.getId(), "addedUserIds", userIds, "operatorId", operatorId));
return saved; return saved;
} }
@ -153,7 +153,7 @@ public class ImGroupService {
members.remove(userId); members.remove(userId);
group.setMemberIds(toJson(members)); group.setMemberIds(toJson(members));
ImGroupEntity saved = groupRepository.save(group); 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)); java.util.Map.of("groupId", saved.getId(), "removedUserId", userId, "operatorId", operatorId));
return saved; return saved;
} }
@ -176,7 +176,7 @@ public class ImGroupService {
if (changed) { if (changed) {
group.setMemberIds(toJson(members)); group.setMemberIds(toJson(members));
ImGroupEntity saved = groupRepository.save(group); 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)); java.util.Map.of("groupId", saved.getId(), "removedUserIds", userIds, "operatorId", operatorId));
return saved; return saved;
} }
@ -197,7 +197,7 @@ public class ImGroupService {
group.setAnnouncement(announcement); group.setAnnouncement(announcement);
} }
ImGroupEntity saved = groupRepository.save(group); 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)); java.util.Map.of("groupId", saved.getId(), "name", saved.getName(), "operatorId", operatorId));
return saved; return saved;
} }
@ -268,7 +268,7 @@ public class ImGroupService {
} }
public List<ImGroupEntity> listByApp(String appKey) { public List<ImGroupEntity> listByApp(String appKey) {
return groupRepository.findByAppId(appKey); return groupRepository.findByAppKey(appKey);
} }
public List<ImGroupEntity> listUserGroups(String appKey, String userId) { public List<ImGroupEntity> listUserGroups(String appKey, String userId) {
@ -277,7 +277,7 @@ public class ImGroupService {
public List<ImGroupEntity> listPublicGroups(String appKey, String keyword) { public List<ImGroupEntity> listPublicGroups(String appKey, String keyword) {
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(); 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 -> "PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType())))
.filter(group -> normalizedKeyword.isBlank() .filter(group -> normalizedKeyword.isBlank()
|| group.getName().toLowerCase().contains(normalizedKeyword) || group.getName().toLowerCase().contains(normalizedKeyword)
@ -313,7 +313,7 @@ public class ImGroupService {
throw new BusinessException(403, "当前应用未开放群加入申请"); throw new BusinessException(403, "当前应用未开放群加入申请");
} }
ImGroupEntity group = get(groupId); ImGroupEntity group = get(groupId);
if (!group.getAppId().equals(appKey)) { if (!group.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
if (!"PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType()))) { if (!"PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType()))) {
@ -322,11 +322,11 @@ public class ImGroupService {
if (memberIds(group).contains(requesterId)) { if (memberIds(group).contains(requesterId)) {
throw new BusinessException(400, "已经在群内"); throw new BusinessException(400, "已经在群内");
} }
return joinRequestRepository.findByAppIdAndGroupIdAndRequesterId(appKey, groupId, requesterId) return joinRequestRepository.findByAppKeyAndGroupIdAndRequesterId(appKey, groupId, requesterId)
.orElseGet(() -> { .orElseGet(() -> {
ImGroupJoinRequestEntity entity = new ImGroupJoinRequestEntity(); ImGroupJoinRequestEntity entity = new ImGroupJoinRequestEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setGroupId(groupId); entity.setGroupId(groupId);
entity.setRequesterId(requesterId); entity.setRequesterId(requesterId);
entity.setRemark(remark); entity.setRemark(remark);
@ -350,7 +350,7 @@ public class ImGroupService {
public List<ImGroupJoinRequestEntity> listJoinRequests(String appKey, String groupId, String operatorId) { public List<ImGroupJoinRequestEntity> listJoinRequests(String appKey, String groupId, String operatorId) {
ImGroupEntity group = get(groupId); ImGroupEntity group = get(groupId);
ensureCanManage(group, operatorId); ensureCanManage(group, operatorId);
return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId); return joinRequestRepository.findByAppKeyAndGroupId(appKey, groupId);
} }
@Transactional @Transactional
@ -442,7 +442,7 @@ public class ImGroupService {
} }
private void ensureAppMatches(ImGroupEntity group, String appKey) { private void ensureAppMatches(ImGroupEntity group, String appKey) {
if (!group.getAppId().equals(appKey)) { if (!group.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
} }
@ -614,7 +614,7 @@ public class ImGroupService {
public List<ImGroupJoinRequestEntity> adminListJoinRequests(String appKey, String groupId) { public List<ImGroupJoinRequestEntity> adminListJoinRequests(String appKey, String groupId) {
ImGroupEntity group = get(groupId); ImGroupEntity group = get(groupId);
ensureAppMatches(group, appKey); ensureAppMatches(group, appKey);
return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId); return joinRequestRepository.findByAppKeyAndGroupId(appKey, groupId);
} }
@Transactional @Transactional
@ -667,7 +667,7 @@ public class ImGroupService {
group.setCreatorId(newOwnerId); group.setCreatorId(newOwnerId);
group.setAdminIds(toJson(admins)); group.setAdminIds(toJson(admins));
ImGroupEntity saved = groupRepository.save(group); 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)); java.util.Map.of("groupId", saved.getId(), "newOwnerId", newOwnerId));
return saved; return saved;
} }
@ -683,7 +683,7 @@ public class ImGroupService {
} }
group.setExtAttributes(toJson(current)); group.setExtAttributes(toJson(current));
ImGroupEntity saved = groupRepository.save(group); 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)); java.util.Map.of("groupId", saved.getId(), "attributes", current));
return saved; return saved;
} }
@ -760,7 +760,7 @@ public class ImGroupService {
if (recipient == null || recipient.isBlank() || recipient.equals(fromUserId)) continue; if (recipient == null || recipient.isBlank() || recipient.equals(fromUserId)) continue;
ImMessageEntity message = new ImMessageEntity(); ImMessageEntity message = new ImMessageEntity();
message.setId(UUID.randomUUID().toString()); message.setId(UUID.randomUUID().toString());
message.setAppId(group.getAppId()); message.setAppKey(group.getAppKey());
message.setFromUserId(fromUserId); message.setFromUserId(fromUserId);
message.setToId(recipient); message.setToId(recipient);
message.setChatType(ImMessageEntity.ChatType.SINGLE); message.setChatType(ImMessageEntity.ChatType.SINGLE);
@ -775,11 +775,11 @@ public class ImGroupService {
private void dispatchJoinRequestWebhook(ImGroupEntity group, ImGroupJoinRequestEntity request, String callbackEvent) { private void dispatchJoinRequestWebhook(ImGroupEntity group, ImGroupJoinRequestEntity request, String callbackEvent) {
webhookDispatchService.dispatch( webhookDispatchService.dispatch(
group.getAppId(), group.getAppKey(),
"group_join_request", "group_join_request",
callbackEvent, callbackEvent,
new GroupJoinRequestCallbackPayload( new GroupJoinRequestCallbackPayload(
group.getAppId(), group.getAppKey(),
request.getId(), request.getId(),
request.getGroupId(), request.getGroupId(),
group.getName(), group.getName(),
@ -832,7 +832,7 @@ public class ImGroupService {
private ImGroupJoinRequestEntity getJoinRequest(String appKey, String requestId) { private ImGroupJoinRequestEntity getJoinRequest(String appKey, String requestId) {
ImGroupJoinRequestEntity request = joinRequestRepository.findById(requestId) ImGroupJoinRequestEntity request = joinRequestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "加群申请不存在")); .orElseThrow(() -> new BusinessException(404, "加群申请不存在"));
if (!request.getAppId().equals(appKey)) { if (!request.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
return request; return request;
@ -894,7 +894,7 @@ public class ImGroupService {
private List<ImAccountEntity> resolveMembers(String appKey, List<String> ids) { private List<ImAccountEntity> resolveMembers(String appKey, List<String> ids) {
List<ImAccountEntity> members = new ArrayList<>(); List<ImAccountEntity> members = new ArrayList<>();
for (String userId : ids == null ? List.<String>of() : ids) { for (String userId : ids == null ? List.<String>of() : ids) {
accountRepository.findByAppIdAndUserId(appKey, userId).ifPresent(members::add); accountRepository.findByAppKeyAndUserId(appKey, userId).ifPresent(members::add);
} }
return members; return members;
} }

查看文件

@ -20,7 +20,7 @@ public class KeywordFilterService {
} }
public String filter(String appKey, String content) { public String filter(String appKey, String content) {
List<KeywordFilterEntity> filters = repository.findByAppIdAndEnabledTrue(appKey); List<KeywordFilterEntity> filters = repository.findByAppKeyAndEnabledTrue(appKey);
String result = content; String result = content;
for (KeywordFilterEntity f : filters) { for (KeywordFilterEntity f : filters) {
try { try {
@ -42,7 +42,7 @@ public class KeywordFilterService {
public KeywordFilterEntity add(String appKey, String pattern, String replacement, String action) { public KeywordFilterEntity add(String appKey, String pattern, String replacement, String action) {
KeywordFilterEntity entity = new KeywordFilterEntity(); KeywordFilterEntity entity = new KeywordFilterEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setPattern(pattern); entity.setPattern(pattern);
entity.setReplacement(replacement); entity.setReplacement(replacement);
entity.setAction(action); entity.setAction(action);
@ -52,7 +52,7 @@ public class KeywordFilterService {
} }
public List<KeywordFilterEntity> list(String appKey) { public List<KeywordFilterEntity> 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) { public KeywordFilterEntity update(String appKey, String id, String pattern, String replacement, String action, Boolean enabled) {

查看文件

@ -116,7 +116,7 @@ public class MessageService {
message.setId(req.messageId() != null && !req.messageId().isBlank() message.setId(req.messageId() != null && !req.messageId().isBlank()
? req.messageId() ? req.messageId()
: UUID.randomUUID().toString()); : UUID.randomUUID().toString());
message.setAppId(appKey); message.setAppKey(appKey);
message.setFromUserId(fromUserId); message.setFromUserId(fromUserId);
message.setToId(req.toId()); message.setToId(req.toId());
message.setChatType(req.chatType()); message.setChatType(req.chatType());
@ -184,14 +184,14 @@ public class MessageService {
} }
private boolean isFriend(String appKey, String userId, String friendId) { private boolean isFriend(String appKey, String userId, String friendId) {
return friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId) return friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId); || friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
} }
public ImMessageEntity revoke(String appKey, String messageId, String requestUserId) { public ImMessageEntity revoke(String appKey, String messageId, String requestUserId) {
ImMessageEntity message = messageRepository.findById(messageId) ImMessageEntity message = messageRepository.findById(messageId)
.orElseThrow(() -> new BusinessException(404, "消息不存在")); .orElseThrow(() -> new BusinessException(404, "消息不存在"));
if (!message.getAppId().equals(appKey)) { if (!message.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
if (!message.getFromUserId().equals(requestUserId)) { if (!message.getFromUserId().equals(requestUserId)) {
@ -223,7 +223,7 @@ public class MessageService {
public ImMessageEntity edit(String appKey, String messageId, String requestUserId, EditMessageRequest req) { public ImMessageEntity edit(String appKey, String messageId, String requestUserId, EditMessageRequest req) {
ImMessageEntity message = messageRepository.findById(messageId) ImMessageEntity message = messageRepository.findById(messageId)
.orElseThrow(() -> new BusinessException(404, "消息不存在")); .orElseThrow(() -> new BusinessException(404, "消息不存在"));
if (!message.getAppId().equals(appKey)) { if (!message.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
if (!message.getFromUserId().equals(requestUserId)) { if (!message.getFromUserId().equals(requestUserId)) {
@ -278,7 +278,7 @@ public class MessageService {
public ImMessageEntity adminRevoke(String appKey, String messageId) { public ImMessageEntity adminRevoke(String appKey, String messageId) {
ImMessageEntity message = messageRepository.findById(messageId) ImMessageEntity message = messageRepository.findById(messageId)
.orElseThrow(() -> new BusinessException(404, "消息不存在")); .orElseThrow(() -> new BusinessException(404, "消息不存在"));
if (!message.getAppId().equals(appKey)) { if (!message.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
message.setStatus(ImMessageEntity.MsgStatus.REVOKED); message.setStatus(ImMessageEntity.MsgStatus.REVOKED);
@ -341,7 +341,7 @@ public class MessageService {
return; return;
} }
List<ImMessageEntity> messages = messageRepository List<ImMessageEntity> messages = messageRepository
.findByAppIdAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc( .findByAppKeyAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
appKey, peerId, readerId, readAt); appKey, peerId, readerId, readAt);
if (messages.isEmpty()) { if (messages.isEmpty()) {
return; return;
@ -374,7 +374,7 @@ public class MessageService {
return; return;
} }
List<ImMessageEntity> messages = messageRepository List<ImMessageEntity> messages = messageRepository
.findByAppIdAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc( .findByAppKeyAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
appKey, groupId, ImMessageEntity.ChatType.GROUP, readAt); appKey, groupId, ImMessageEntity.ChatType.GROUP, readAt);
if (messages.isEmpty()) { if (messages.isEmpty()) {
return; return;
@ -491,12 +491,12 @@ public class MessageService {
public List<GroupReadReceiptSummary> groupReadReceipts(String appKey, String groupId, List<String> messageIds) { public List<GroupReadReceiptSummary> groupReadReceipts(String appKey, String groupId, List<String> messageIds) {
ImGroupEntity group = groupService.get(groupId); ImGroupEntity group = groupService.get(groupId);
if (!group.getAppId().equals(appKey)) { if (!group.getAppKey().equals(appKey)) {
throw new BusinessException(403, "无权操作"); throw new BusinessException(403, "无权操作");
} }
List<String> members = groupService.memberIds(group); List<String> members = groupService.memberIds(group);
return messageRepository.findAllById(messageIds == null ? List.of() : messageIds).stream() 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 -> groupId.equals(message.getToId()))
.filter(message -> message.getChatType() == ImMessageEntity.ChatType.GROUP) .filter(message -> message.getChatType() == ImMessageEntity.ChatType.GROUP)
.map(message -> { .map(message -> {
@ -520,7 +520,7 @@ public class MessageService {
try { try {
Map<String, Object> payload = new java.util.LinkedHashMap<>(); Map<String, Object> payload = new java.util.LinkedHashMap<>();
payload.put("messageId", message.getId()); payload.put("messageId", message.getId());
payload.put("appKey", message.getAppId()); payload.put("appKey", message.getAppKey());
payload.put("fromUserId", message.getFromUserId()); payload.put("fromUserId", message.getFromUserId());
payload.put("toId", message.getToId()); payload.put("toId", message.getToId());
payload.put("chatType", message.getChatType().name()); 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) { public ImMessageEntity adminSend(String appKey, String fromUserId, String toId, ImMessageEntity.MsgType msgType, String content) {
ImMessageEntity message = new ImMessageEntity(); ImMessageEntity message = new ImMessageEntity();
message.setId(UUID.randomUUID().toString()); message.setId(UUID.randomUUID().toString());
message.setAppId(appKey); message.setAppKey(appKey);
message.setFromUserId(fromUserId); message.setFromUserId(fromUserId);
message.setToId(toId); message.setToId(toId);
message.setChatType(ImMessageEntity.ChatType.SINGLE); message.setChatType(ImMessageEntity.ChatType.SINGLE);
@ -624,7 +624,7 @@ public class MessageService {
} }
public void adminSetMsgRead(String appKey, String userId) { public void adminSetMsgRead(String appKey, String userId) {
List<ImMessageEntity> messages = messageRepository.findUnreadByAppIdAndToId(appKey, userId); List<ImMessageEntity> messages = messageRepository.findUnreadByAppKeyAndToId(appKey, userId);
for (ImMessageEntity message : messages) { for (ImMessageEntity message : messages) {
message.setStatus(ImMessageEntity.MsgStatus.READ); message.setStatus(ImMessageEntity.MsgStatus.READ);
messageRepository.save(message); messageRepository.save(message);
@ -642,7 +642,7 @@ public class MessageService {
message.setId(req.messageId() != null && !req.messageId().isBlank() message.setId(req.messageId() != null && !req.messageId().isBlank()
? req.messageId() ? req.messageId()
: UUID.randomUUID().toString()); : UUID.randomUUID().toString());
message.setAppId(appKey); message.setAppKey(appKey);
message.setFromUserId(req.fromUserId()); message.setFromUserId(req.fromUserId());
message.setToId(req.toId()); message.setToId(req.toId());
message.setChatType(req.chatType() != null ? req.chatType() : ImMessageEntity.ChatType.SINGLE); message.setChatType(req.chatType() != null ? req.chatType() : ImMessageEntity.ChatType.SINGLE);

查看文件

@ -36,7 +36,7 @@ public class OfflineMessageSyncService {
public void storeOfflineMessage(String appKey, String userId, String messageId) { public void storeOfflineMessage(String appKey, String userId, String messageId) {
ImOfflineMessageEntity entity = new ImOfflineMessageEntity(); ImOfflineMessageEntity entity = new ImOfflineMessageEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setUserId(userId); entity.setUserId(userId);
entity.setMessageId(messageId); entity.setMessageId(messageId);
entity.setDelivered(false); entity.setDelivered(false);
@ -48,7 +48,7 @@ public class OfflineMessageSyncService {
@Transactional @Transactional
public void syncAndDeliver(String appKey, String userId) { public void syncAndDeliver(String appKey, String userId) {
List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository
.findByAppIdAndUserIdAndDeliveredFalse(appKey, userId); .findByAppKeyAndUserIdAndDeliveredFalse(appKey, userId);
if (offlineMessages.isEmpty()) { if (offlineMessages.isEmpty()) {
return; return;
} }
@ -68,17 +68,17 @@ public class OfflineMessageSyncService {
log.info("Synced {} offline messages for appKey={} userId={}", deliveredIds.size(), appKey, userId); 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) { public long countUndelivered(String appKey, String userId) {
return offlineMessageRepository.countUndeliveredByAppIdAndUserId(appKey, userId); return offlineMessageRepository.countUndeliveredByAppKeyAndUserId(appKey, userId);
} }
@Transactional @Transactional
public List<ImMessageEntity> syncAndReturn(String appKey, String userId) { public List<ImMessageEntity> syncAndReturn(String appKey, String userId) {
List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository
.findByAppIdAndUserIdAndDeliveredFalse(appKey, userId); .findByAppKeyAndUserIdAndDeliveredFalse(appKey, userId);
List<ImMessageEntity> result = new ArrayList<>(); List<ImMessageEntity> result = new ArrayList<>();
List<String> deliveredIds = new ArrayList<>(); List<String> deliveredIds = new ArrayList<>();
for (ImOfflineMessageEntity offline : offlineMessages) { for (ImOfflineMessageEntity offline : offlineMessages) {
@ -91,7 +91,7 @@ public class OfflineMessageSyncService {
if (!deliveredIds.isEmpty()) { if (!deliveredIds.isEmpty()) {
offlineMessageRepository.markDeliveredByIds(deliveredIds); offlineMessageRepository.markDeliveredByIds(deliveredIds);
} }
offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appKey, userId); offlineMessageRepository.deleteByAppKeyAndUserIdAndDeliveredTrue(appKey, userId);
return result; return result;
} }
} }

查看文件

@ -27,7 +27,7 @@ public class OperationLogService {
String detail) { String detail) {
ImOperationLogEntity entity = new ImOperationLogEntity(); ImOperationLogEntity entity = new ImOperationLogEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setOperatorId(operatorId == null || operatorId.isBlank() ? "system" : operatorId); entity.setOperatorId(operatorId == null || operatorId.isBlank() ? "system" : operatorId);
entity.setAction(action); entity.setAction(action);
entity.setResourceType(resourceType); entity.setResourceType(resourceType);
@ -38,6 +38,6 @@ public class OperationLogService {
} }
public Page<ImOperationLogEntity> list(String appKey, Pageable pageable) { public Page<ImOperationLogEntity> list(String appKey, Pageable pageable) {
return repository.findByAppIdOrderByCreatedAtDesc(appKey, pageable); return repository.findByAppKeyOrderByCreatedAtDesc(appKey, pageable);
} }
} }

查看文件

@ -19,7 +19,7 @@ public class WebhookConfigService {
} }
public List<WebhookConfigEntity> list(String appKey) { public List<WebhookConfigEntity> list(String appKey) {
return repository.findByAppId(appKey); return repository.findByAppKey(appKey);
} }
public WebhookConfigEntity get(String appKey, String id) { 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) { public WebhookConfigEntity create(String appKey, String url, String secret, Boolean enabled) {
WebhookConfigEntity entity = new WebhookConfigEntity(); WebhookConfigEntity entity = new WebhookConfigEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setUrl(url); entity.setUrl(url);
entity.setSecret(secret); entity.setSecret(secret);
entity.setEnabled(enabled == null || enabled); entity.setEnabled(enabled == null || enabled);

查看文件

@ -61,7 +61,7 @@ public class WebhookDispatchService {
@Async @Async
public void dispatch(String appKey, String callbackType, String callbackEvent, Object payload) { public void dispatch(String appKey, String callbackType, String callbackEvent, Object payload) {
List<WebhookConfigEntity> webhooks = webhookRepository.findByAppIdAndEnabledTrue(appKey); List<WebhookConfigEntity> webhooks = webhookRepository.findByAppKeyAndEnabledTrue(appKey);
if (webhooks.isEmpty()) { if (webhooks.isEmpty()) {
return; return;
} }
@ -100,7 +100,7 @@ public class WebhookDispatchService {
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
WebhookDeliveryEntity delivery = new WebhookDeliveryEntity(); WebhookDeliveryEntity delivery = new WebhookDeliveryEntity();
delivery.setId(UUID.randomUUID().toString()); delivery.setId(UUID.randomUUID().toString());
delivery.setAppId(appKey); delivery.setAppKey(appKey);
delivery.setCallbackId(callbackId); delivery.setCallbackId(callbackId);
delivery.setCallbackEvent(callbackEvent); delivery.setCallbackEvent(callbackEvent);
delivery.setUrl(webhook.getUrl()); delivery.setUrl(webhook.getUrl());
@ -112,7 +112,7 @@ public class WebhookDispatchService {
.uri(URI.create(webhook.getUrl())) .uri(URI.create(webhook.getUrl()))
.timeout(Duration.ofMillis(webhookTimeoutMs)) .timeout(Duration.ofMillis(webhookTimeoutMs))
.header("Content-Type", "application/json") .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-Timestamp", String.valueOf(requestTime))
.header("X-App-Nonce", nonce) .header("X-App-Nonce", nonce)
.header("X-App-Signature", signature) .header("X-App-Signature", signature)
@ -182,7 +182,7 @@ public class WebhookDispatchService {
WebhookAlertEntity alert = new WebhookAlertEntity(); WebhookAlertEntity alert = new WebhookAlertEntity();
alert.setId(UUID.randomUUID().toString()); alert.setId(UUID.randomUUID().toString());
alert.setAppId(appKey); alert.setAppKey(appKey);
alert.setWebhookId(webhook.getId()); alert.setWebhookId(webhook.getId());
alert.setWebhookUrl(webhook.getUrl()); alert.setWebhookUrl(webhook.getUrl());
alert.setAlertType("AUTO_DISABLED"); alert.setAlertType("AUTO_DISABLED");

查看文件

@ -15,7 +15,6 @@
<module>common</module> <module>common</module>
<module>tenant-service</module> <module>tenant-service</module>
<module>im-service</module> <module>im-service</module>
<module>im-sdk</module>
<module>push-service</module> <module>push-service</module>
<module>update-service</module> <module>update-service</module>
<module>demo-service</module> <module>demo-service</module>

查看文件

@ -11,7 +11,7 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "push_device_login_log", indexes = { @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") @Index(name = "idx_push_device_log_token", columnList = "tokenHash")
}) })
public class DeviceLoginLogEntity { public class DeviceLoginLogEntity {
@ -24,7 +24,7 @@ public class DeviceLoginLogEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String userId; private String userId;
@ -70,8 +70,8 @@ public class DeviceLoginLogEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -11,18 +11,18 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "push_device_token", @Table(name = "push_device_token",
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "deviceId"})) uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "deviceId"}))
public class DeviceTokenEntity { public class DeviceTokenEntity {
public enum Vendor { public enum Vendor {
HUAWEI, XIAOMI, OPPO, VIVO, HONOR, HARMONY, FCM, APNS HUAWEI, XIAOMI, OPPO, VIVO, HONOR, HARMONY, APNS
} }
@Id @Id
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 128) @Column(nullable = false, length = 128)
private String userId; private String userId;
@ -67,8 +67,8 @@ public class DeviceTokenEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getUserId() { return userId; } public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; } public void setUserId(String userId) { this.userId = userId; }

查看文件

@ -6,5 +6,5 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface DeviceLoginLogRepository extends JpaRepository<DeviceLoginLogEntity, String> { public interface DeviceLoginLogRepository extends JpaRepository<DeviceLoginLogEntity, String> {
Page<DeviceLoginLogEntity> findByAppIdAndUserIdOrderByCreatedAtDesc(String appId, String userId, Pageable pageable); Page<DeviceLoginLogEntity> findByAppKeyAndUserIdOrderByCreatedAtDesc(String appKey, String userId, Pageable pageable);
} }

查看文件

@ -7,16 +7,16 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface DeviceTokenRepository extends JpaRepository<DeviceTokenEntity, String> { public interface DeviceTokenRepository extends JpaRepository<DeviceTokenEntity, String> {
List<DeviceTokenEntity> findByAppIdAndUserIdAndReceivePushTrue(String appId, String userId); List<DeviceTokenEntity> findByAppKeyAndUserIdAndReceivePushTrue(String appKey, String userId);
Optional<DeviceTokenEntity> findFirstByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc( Optional<DeviceTokenEntity> findFirstByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(
String appId, String userId); String appKey, String userId);
List<DeviceTokenEntity> findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(String appId, String userId); List<DeviceTokenEntity> findByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(String appKey, String userId);
Optional<DeviceTokenEntity> findByAppIdAndUserIdAndVendor( Optional<DeviceTokenEntity> findByAppKeyAndUserIdAndVendor(
String appId, String userId, DeviceTokenEntity.Vendor vendor); String appKey, String userId, DeviceTokenEntity.Vendor vendor);
Optional<DeviceTokenEntity> findByAppIdAndUserIdAndDeviceId(String appId, String userId, String deviceId); Optional<DeviceTokenEntity> findByAppKeyAndUserIdAndDeviceId(String appKey, String userId, String deviceId);
Optional<DeviceTokenEntity> findFirstByToken(String token); Optional<DeviceTokenEntity> findFirstByToken(String token);
List<DeviceTokenEntity> findByAppIdAndUserId(String appId, String userId); List<DeviceTokenEntity> findByAppKeyAndUserId(String appKey, String userId);
List<DeviceTokenEntity> findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(String appId, String userId); List<DeviceTokenEntity> findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(String appKey, String userId);
void deleteByAppIdAndUserIdAndVendor(String appId, String userId, DeviceTokenEntity.Vendor vendor); void deleteByAppKeyAndUserIdAndVendor(String appKey, String userId, DeviceTokenEntity.Vendor vendor);
void deleteByAppIdAndUserIdAndDeviceId(String appId, String userId, String deviceId); void deleteByAppKeyAndUserIdAndDeviceId(String appKey, String userId, String deviceId);
} }

查看文件

@ -35,15 +35,15 @@ public class PushDiagnosticsService {
this.jwtUtil = jwtUtil; this.jwtUtil = jwtUtil;
} }
public PushTokenDiagnostics searchByToken(String token, String appIdHint) { public PushTokenDiagnostics searchByToken(String token, String appKeyHint) {
Optional<DeviceTokenEntity> tokenMatch = tokenRepository.findFirstByToken(token); Optional<DeviceTokenEntity> tokenMatch = tokenRepository.findFirstByToken(token);
String appKey = appIdHint; String appKey = appKeyHint;
String userId = null; String userId = null;
String tokenType = "UNKNOWN"; String tokenType = "UNKNOWN";
if (tokenMatch.isPresent()) { if (tokenMatch.isPresent()) {
DeviceTokenEntity device = tokenMatch.get(); DeviceTokenEntity device = tokenMatch.get();
appKey = device.getAppId(); appKey = device.getAppKey();
userId = device.getUserId(); userId = device.getUserId();
tokenType = "PUSH"; tokenType = "PUSH";
} else { } else {
@ -56,8 +56,8 @@ public class PushDiagnosticsService {
try { try {
Claims claims = jwtUtil.parse(token); Claims claims = jwtUtil.parse(token);
userId = claims.getSubject(); userId = claims.getSubject();
String claimAppId = claims.get("appKey", String.class); String claimAppKey = claims.get("appKey", String.class);
appKey = claimAppId == null || claimAppId.isBlank() ? appIdHint : claimAppId; appKey = claimAppKey == null || claimAppKey.isBlank() ? appKeyHint : claimAppKey;
tokenType = "IM"; tokenType = "IM";
} catch (Exception ignored) { } catch (Exception ignored) {
// Keep UNKNOWN and return an empty diagnostic. // Keep UNKNOWN and return an empty diagnostic.
@ -71,7 +71,7 @@ public class PushDiagnosticsService {
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId) ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId)
.orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L)); .orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L));
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); List<DeviceTokenEntity> devices = tokenRepository.findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId) List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
.stream() .stream()
.map(DeviceInfo::from) .map(DeviceInfo::from)
@ -93,7 +93,7 @@ public class PushDiagnosticsService {
public PushTokenDiagnostics searchByUserId(String appKey, String userId) { public PushTokenDiagnostics searchByUserId(String appKey, String userId) {
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId) ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId)
.orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L)); .orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L));
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); List<DeviceTokenEntity> devices = tokenRepository.findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId) List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
.stream() .stream()
.map(DeviceInfo::from) .map(DeviceInfo::from)
@ -126,7 +126,7 @@ public class PushDiagnosticsService {
public Page<DeviceLoginLogEntity> deviceLogs(String appKey, String userId, int page, int size) { public Page<DeviceLoginLogEntity> deviceLogs(String appKey, String userId, int page, int size) {
int safePage = Math.max(page, 0); int safePage = Math.max(page, 0);
int safeSize = Math.min(Math.max(size, 1), 200); 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( public record PushTokenDiagnostics(

查看文件

@ -68,17 +68,22 @@ public class PushDispatcher {
String routeType = routeType(payload); String routeType = routeType(payload);
String platform = platformForVendor(vendor, payload); String platform = platformForVendor(vendor, payload);
return pushConfigClient.loadServiceConfig(appKey, platform, "PUSH") return pushConfigClient.loadServiceConfig(appKey, platform, "PUSH")
.map(config -> { .map(config -> profileFor(config, vendor.name(), routeType)
JsonNode route = config.path("routing").path(routeType); .map(profile -> new PushSendOptions(
String channelKey = route.path("channel").asText(""); profile.path("key").asText(""),
String channelId = effectiveChannelId(config.path("channels"), channelKey);
return new PushSendOptions(
routeType, routeType,
channelId, profile.path("channelId").asText(""),
route.path("category").asText(""), profile.path("category").asText(""),
route.path("priority").asText("")); profile.path("threadIdentifier").asText(""),
}) profile.path("interruptionLevel").asText(""),
.orElseGet(() -> new PushSendOptions(routeType, "", "", "")); 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) { private String platformForVendor(DeviceTokenEntity.Vendor vendor, String payload) {
@ -121,18 +126,73 @@ public class PushDispatcher {
} }
} }
private String effectiveChannelId(JsonNode channels, String channelKey) { private java.util.Optional<JsonNode> profileFor(JsonNode config, String vendor, String routeType) {
if (channels == null || !channels.isArray() || channelKey == null || channelKey.isBlank()) { JsonNode profiles = config.path("profiles");
return ""; if (profiles == null || !profiles.isArray()) {
return java.util.Optional.empty();
} }
for (JsonNode channel : channels) { JsonNode fallback = null;
if (channelKey.equals(channel.path("key").asText(""))) { for (JsonNode profile : profiles) {
String base = channel.path("channelId").asText(channelKey); if (!profile.path("enabled").asBoolean(true)) {
int version = Math.max(channel.path("version").asInt(1), 1); continue;
return base + "_v" + version; }
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 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;
} }
} }
return "";
private String mapPriority(String importance) {
return switch (importance == null ? "" : importance.trim().toUpperCase()) {
case "HIGH", "MAX" -> "HIGH";
case "LOW", "MIN" -> "LOW";
default -> "DEFAULT";
};
} }
public List<DeviceTokenEntity> selectedPushTargets(String appKey, String userId) { public List<DeviceTokenEntity> selectedPushTargets(String appKey, String userId) {
@ -150,7 +210,7 @@ public class PushDispatcher {
private List<DeviceTokenEntity> selectTargets(String appKey, String userId) { private List<DeviceTokenEntity> selectTargets(String appKey, String userId) {
List<DeviceTokenEntity> devices = tokenRepository List<DeviceTokenEntity> devices = tokenRepository
.findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId); .findByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
if (devices.isEmpty()) { if (devices.isEmpty()) {
return List.of(); return List.of();
} }
@ -185,15 +245,15 @@ public class PushDispatcher {
String osVersion, String osVersion,
String appVersion) { String appVersion) {
String resolvedDeviceId = normalizeDeviceId(deviceId, vendor, token); String resolvedDeviceId = normalizeDeviceId(deviceId, vendor, token);
Optional<DeviceTokenEntity> existing = tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId); Optional<DeviceTokenEntity> existing = tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId);
if (existing.isEmpty() && (deviceId == null || deviceId.isBlank())) { if (existing.isEmpty() && (deviceId == null || deviceId.isBlank())) {
existing = tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor); existing = tokenRepository.findByAppKeyAndUserIdAndVendor(appKey, userId, vendor);
} }
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
DeviceTokenEntity entity = existing.orElseGet(() -> { DeviceTokenEntity entity = existing.orElseGet(() -> {
DeviceTokenEntity e = new DeviceTokenEntity(); DeviceTokenEntity e = new DeviceTokenEntity();
e.setId(UUID.randomUUID().toString()); e.setId(UUID.randomUUID().toString());
e.setAppId(appKey); e.setAppKey(appKey);
e.setUserId(userId); e.setUserId(userId);
e.setVendor(vendor); e.setVendor(vendor);
e.setCreatedAt(now); e.setCreatedAt(now);
@ -216,8 +276,8 @@ public class PushDispatcher {
public void setReceivePush(String appKey, String userId, String deviceId, boolean enabled) { public void setReceivePush(String appKey, String userId, String deviceId, boolean enabled) {
List<DeviceTokenEntity> tokens = deviceId == null || deviceId.isBlank() List<DeviceTokenEntity> tokens = deviceId == null || deviceId.isBlank()
? tokenRepository.findByAppIdAndUserId(appKey, userId) ? tokenRepository.findByAppKeyAndUserId(appKey, userId)
: tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId).stream().toList(); : tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId).stream().toList();
for (DeviceTokenEntity token : tokens) { for (DeviceTokenEntity token : tokens) {
token.setReceivePush(enabled); token.setReceivePush(enabled);
token.setUpdatedAt(LocalDateTime.now()); token.setUpdatedAt(LocalDateTime.now());
@ -228,20 +288,20 @@ public class PushDispatcher {
public void unregisterToken(String appKey, String userId, DeviceTokenEntity.Vendor vendor, String deviceId) { public void unregisterToken(String appKey, String userId, DeviceTokenEntity.Vendor vendor, String deviceId) {
if (deviceId != null && !deviceId.isBlank()) { if (deviceId != null && !deviceId.isBlank()) {
tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId) tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId)
.ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER)); .ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER));
tokenRepository.deleteByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId); tokenRepository.deleteByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId);
return; return;
} }
tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor) tokenRepository.findByAppKeyAndUserIdAndVendor(appKey, userId, vendor)
.ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER)); .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) { private void recordLog(DeviceTokenEntity token, DeviceLoginLogEntity.EventType eventType) {
DeviceLoginLogEntity entity = new DeviceLoginLogEntity(); DeviceLoginLogEntity entity = new DeviceLoginLogEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(token.getAppId()); entity.setAppKey(token.getAppKey());
entity.setUserId(token.getUserId()); entity.setUserId(token.getUserId());
entity.setVendor(token.getVendor()); entity.setVendor(token.getVendor());
entity.setTokenHash(hashToken(token.getToken())); entity.setTokenHash(hashToken(token.getToken()));

查看文件

@ -82,16 +82,31 @@ public class ApnsPushProvider implements PushProvider {
String url = (production ? productionPushUrl : sandboxPushUrl).replace("{token}", token); String url = (production ? productionPushUrl : sandboxPushUrl).replace("{token}", token);
Map<String, Object> aps = new java.util.LinkedHashMap<>(); Map<String, Object> aps = new java.util.LinkedHashMap<>();
aps.put("alert", Map.of("title", title, "body", body)); aps.put("alert", Map.of("title", title, "body", body));
if (options == null || options.sound() == null || options.sound()) {
aps.put("sound", "default"); aps.put("sound", "default");
}
if (options != null) { if (options != null) {
if (options.category() != null && !options.category().isBlank()) { if (options.category() != null && !options.category().isBlank()) {
aps.put("category", options.category()); 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()); 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"); 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<String, Object> message = new java.util.LinkedHashMap<>(); Map<String, Object> message = new java.util.LinkedHashMap<>();
@ -153,7 +168,7 @@ public class ApnsPushProvider implements PushProvider {
private String resolveConfig(String appKey, String key, String fallback) { private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "IOS", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "IOS", "PUSH")
.map(node -> node.path("apns")) .map(node -> node.path("vendors").path("apns"))
.orElse(null); .orElse(null);
if (config == null) { if (config == null) {
return fallback == null ? "" : fallback; return fallback == null ? "" : fallback;

查看文件

@ -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<String, TokenCache> 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<String, Object> 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<String, Object> 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<String> 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<String> 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) {}
}

查看文件

@ -78,6 +78,9 @@ public class HarmonyPushProvider implements PushProvider {
if (options.channelId() != null && !options.channelId().isBlank()) { if (options.channelId() != null && !options.channelId().isBlank()) {
notification.put("channel_id", options.channelId()); notification.put("channel_id", options.channelId());
} }
if (options.badge() != null && options.badge()) {
notification.put("badge", 1);
}
} }
Map<String, Object> message = Map.of( Map<String, Object> message = Map.of(
"message", Map.of( "message", Map.of(
@ -115,7 +118,7 @@ public class HarmonyPushProvider implements PushProvider {
private String resolveConfig(String appKey, String key, String fallback) { private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "HARMONY", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "HARMONY", "PUSH")
.map(node -> node.path("harmony")) .map(node -> node.path("vendors").path("harmony"))
.orElse(null); .orElse(null);
if (config == null) { if (config == null) {
return fallback == null ? "" : fallback; return fallback == null ? "" : fallback;

查看文件

@ -46,6 +46,11 @@ public class HonorPushProvider implements PushProvider {
@Override @Override
public boolean send(String appKey, String token, String title, String body, String payload) { 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 resolvedAppId = resolveConfig(appKey, "appId", envAppId);
String resolvedAppSecret = resolveConfig(appKey, "clientSecret", envAppSecret); String resolvedAppSecret = resolveConfig(appKey, "clientSecret", envAppSecret);
if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) { if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) {
@ -58,10 +63,21 @@ public class HonorPushProvider implements PushProvider {
try { try {
String accessToken = getAccessToken(resolvedAppId, resolvedAppSecret); String accessToken = getAccessToken(resolvedAppId, resolvedAppSecret);
String url = pushUrl.replace("{appId}", resolvedAppId); String url = pushUrl.replace("{appId}", resolvedAppId);
java.util.Map<String, Object> 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<String, Object> message = Map.of( Map<String, Object> message = Map.of(
"message", Map.of( "message", Map.of(
"token", new String[]{token}, "token", new String[]{token},
"notification", Map.of("title", title, "body", body), "notification", notification,
"data", payload != null ? payload : "{}" "data", payload != null ? payload : "{}"
) )
); );
@ -94,7 +110,7 @@ public class HonorPushProvider implements PushProvider {
private String resolveConfig(String appKey, String key, String fallback) { private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("honor")) .map(node -> node.path("vendors").path("honor"))
.orElse(null); .orElse(null);
if (config == null) { if (config == null) {
return fallback == null ? "" : fallback; return fallback == null ? "" : fallback;

查看文件

@ -68,7 +68,9 @@ public class HuaweiPushProvider implements PushProvider {
Map<String, Object> androidNotification = new LinkedHashMap<>(); Map<String, Object> androidNotification = new LinkedHashMap<>();
String channelId = options != null && options.channelId() != null && !options.channelId().isBlank() String channelId = options != null && options.channelId() != null && !options.channelId().isBlank()
? options.channelId() : ""; ? 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 (!channelId.isBlank()) androidNotification.put("channel_id", channelId);
if (!category.isBlank()) androidNotification.put("category", category); if (!category.isBlank()) androidNotification.put("category", category);
if (!androidNotification.isEmpty()) { if (!androidNotification.isEmpty()) {
@ -104,7 +106,7 @@ public class HuaweiPushProvider implements PushProvider {
private String resolveConfig(String appKey, String key, String fallback) { private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("huawei")) .map(node -> node.path("vendors").path("huawei"))
.orElse(null); .orElse(null);
if (config == null) { if (config == null) {
return fallback == null ? "" : fallback; return fallback == null ? "" : fallback;

查看文件

@ -40,6 +40,11 @@ public class OppoPushProvider implements PushProvider {
@Override @Override
public boolean send(String appKey, String token, String title, String body, String payload) { 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 vendorAppKey = resolveConfig(appKey, "appKey");
String masterSecret = resolveConfig(appKey, "masterSecret"); String masterSecret = resolveConfig(appKey, "masterSecret");
if (vendorAppKey.isBlank() || masterSecret.isBlank()) { if (vendorAppKey.isBlank() || masterSecret.isBlank()) {
@ -55,7 +60,9 @@ public class OppoPushProvider implements PushProvider {
inner.put("content", body); inner.put("content", body);
inner.put("target_type", 2); inner.put("target_type", 2);
inner.put("target_value", token); 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); if (!channelId.isBlank()) inner.put("channel_id", channelId);
Map<String, Object> message = Map.of("message", inner); Map<String, Object> message = Map.of("message", inner);
String requestBody = objectMapper.writeValueAsString(message); String requestBody = objectMapper.writeValueAsString(message);
@ -98,7 +105,7 @@ public class OppoPushProvider implements PushProvider {
private String resolveConfig(String appKey, String key) { private String resolveConfig(String appKey, String key) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("oppo")) .map(node -> node.path("vendors").path("oppo"))
.orElse(null); .orElse(null);
if (config == null) { if (config == null) {
return ""; return "";

查看文件

@ -1,12 +1,20 @@
package com.xuqm.push.service.provider; package com.xuqm.push.service.provider;
public record PushSendOptions( public record PushSendOptions(
String profileKey,
String routeType, String routeType,
String channelId, String channelId,
String category, String category,
String threadIdentifier,
String interruptionLevel,
String importance,
Boolean badge,
Boolean sound,
Boolean vibration,
Integer notifyType,
String priority String priority
) { ) {
public static PushSendOptions empty() { public static PushSendOptions empty() {
return new PushSendOptions("", "", "", ""); return new PushSendOptions("", "", "", "", "", "", "", null, null, null, null, "");
} }
} }

查看文件

@ -40,6 +40,11 @@ public class VivoPushProvider implements PushProvider {
@Override @Override
public boolean send(String appKey, String token, String title, String body, String payload) { 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 vendorAppKey = resolveConfig(appKey, "appKey");
String appIdConfig = resolveConfig(appKey, "appId"); String appIdConfig = resolveConfig(appKey, "appId");
if (vendorAppKey.isBlank() || appIdConfig.isBlank()) { if (vendorAppKey.isBlank() || appIdConfig.isBlank()) {
@ -53,11 +58,17 @@ public class VivoPushProvider implements PushProvider {
message.put("title", title); message.put("title", title);
message.put("content", body); message.put("content", body);
message.put("notifyType", 1); 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"); String receiptId = resolveConfig(appKey, "receiptId");
if (!category.isBlank()) { if (!category.isBlank()) {
// vivo classification: 0=operation, 1=IM/system message.put("classification", switch (category.toUpperCase()) {
message.put("classification", "IM".equalsIgnoreCase(category) ? 1 : 0); case "MESSAGE", "IM" -> 1;
case "SOCIAL" -> 2;
case "SYSTEM" -> 3;
default -> 0;
});
} }
if (!receiptId.isBlank()) { if (!receiptId.isBlank()) {
message.put("requestId", receiptId + "_" + System.currentTimeMillis()); message.put("requestId", receiptId + "_" + System.currentTimeMillis());
@ -102,7 +113,7 @@ public class VivoPushProvider implements PushProvider {
private String resolveConfig(String appKey, String key) { private String resolveConfig(String appKey, String key) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("vivo")) .map(node -> node.path("vendors").path("vivo"))
.orElse(null); .orElse(null);
if (config == null) { if (config == null) {
return ""; return "";

查看文件

@ -70,8 +70,9 @@ public class XiaomiPushProvider implements PushProvider {
.append("&title=").append(URLEncoder.encode(title, StandardCharsets.UTF_8)) .append("&title=").append(URLEncoder.encode(title, StandardCharsets.UTF_8))
.append("&description=").append(URLEncoder.encode(body, StandardCharsets.UTF_8)) .append("&description=").append(URLEncoder.encode(body, StandardCharsets.UTF_8))
.append("&restricted_package_name=").append(URLEncoder.encode(packageName, StandardCharsets.UTF_8)) .append("&restricted_package_name=").append(URLEncoder.encode(packageName, StandardCharsets.UTF_8))
.append("&notify_type=1")
.append("&extra.notify_foreground=1"); .append("&extra.notify_foreground=1");
Integer notifyType = options != null ? options.notifyType() : null;
form.append("&notify_type=").append(notifyType == null ? 1 : Math.max(notifyType, 0));
if (!channelId.isBlank()) { if (!channelId.isBlank()) {
form.append("&channel_id=").append(URLEncoder.encode(channelId, StandardCharsets.UTF_8)); 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) { private String resolveVendorConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("xiaomi")) .map(node -> node.path("vendors").path("xiaomi"))
.orElse(null); .orElse(null);
if (config == null) { if (config == null) {
return fallback == null ? "" : fallback; return fallback == null ? "" : fallback;

查看文件

@ -34,11 +34,6 @@ push:
xiaomi: xiaomi:
app-secret: ${XIAOMI_APP_SECRET:} app-secret: ${XIAOMI_APP_SECRET:}
push-url: https://api.xmpush.xiaomi.com/v3/message/regid 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: apns:
team-id: ${APNS_TEAM_ID:} team-id: ${APNS_TEAM_ID:}
key-id: ${APNS_KEY_ID:} key-id: ${APNS_KEY_ID:}

查看文件

@ -20,6 +20,11 @@
<groupId>com.xuqm</groupId> <groupId>com.xuqm</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.xuqm</groupId>
<artifactId>im-sdk</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>

查看文件

@ -112,35 +112,7 @@ public class FeatureServiceController {
case PUSH -> featureServiceManager.buildPushConfig( case PUSH -> featureServiceManager.buildPushConfig(
appKey, appKey,
platform, platform,
req == null ? null : req.huaweiAppIdValue(), req == null ? null : req.pushConfig());
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());
}; };
FeatureServiceEntity saved = featureServiceManager.updateConfig( FeatureServiceEntity saved = featureServiceManager.updateConfig(
appKey, platform, serviceType, config); appKey, platform, serviceType, config);
@ -215,102 +187,7 @@ public class FeatureServiceController {
String defaultPackageName, String defaultPackageName,
String defaultAppStoreUrl, String defaultAppStoreUrl,
String defaultMarketUrl, String defaultMarketUrl,
String huaweiAppId, JsonNode pushConfig
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
) { ) {
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
) {}
}

查看文件

@ -56,22 +56,22 @@ public class SdkConfigController {
@RequestParam(required = false, defaultValue = "ANDROID") FeatureServiceEntity.Platform platform) { @RequestParam(required = false, defaultValue = "ANDROID") FeatureServiceEntity.Platform platform) {
AppEntity app = sdkAppProvisioningService.resolveApp(appKey); AppEntity app = sdkAppProvisioningService.resolveApp(appKey);
List<FeatureServiceEntity> features = featureServiceRepository.findByAppId(app.getAppKey()); List<FeatureServiceEntity> features = featureServiceRepository.findByAppKey(app.getAppKey());
boolean imEnabled = features.stream() boolean imEnabled = features.stream()
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.IM && f.isEnabled()); .anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.IM && f.isEnabled());
boolean pushEnabled = features.stream() boolean pushEnabled = features.stream()
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.PUSH && f.isEnabled()); .anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.PUSH && f.isEnabled());
JsonNode updateConfig = featureServiceRepository JsonNode updateConfig = featureServiceRepository
.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE) .findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
.map(feature -> parseConfig(feature.getConfig())) .map(feature -> parseConfig(feature.getConfig()))
.orElseGet(objectMapper::createObjectNode); .orElseGet(objectMapper::createObjectNode);
JsonNode pushConfig = featureServiceRepository JsonNode pushConfig = featureServiceRepository
.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH) .findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH)
.map(feature -> parseConfig(feature.getConfig())) .map(feature -> parseConfig(feature.getConfig()))
.orElseGet(objectMapper::createObjectNode); .orElseGet(objectMapper::createObjectNode);
boolean updateEnabled = featureServiceRepository boolean updateEnabled = featureServiceRepository
.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE) .findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
.map(FeatureServiceEntity::isEnabled) .map(FeatureServiceEntity::isEnabled)
.orElse(false); .orElse(false);

查看文件

@ -26,7 +26,7 @@ public class FeatureServiceEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(nullable = false, length = 16) @Column(nullable = false, length = 16)
@ -52,8 +52,8 @@ public class FeatureServiceEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public Platform getPlatform() { return platform; } public Platform getPlatform() { return platform; }
public void setPlatform(Platform platform) { this.platform = platform; } public void setPlatform(Platform platform) { this.platform = platform; }

查看文件

@ -19,7 +19,7 @@ public class ServiceActivationRequestEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(nullable = false, length = 16) @Column(nullable = false, length = 16)
@ -47,14 +47,11 @@ public class ServiceActivationRequestEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } @JsonProperty("appKey")
public void setAppId(String appId) { this.appId = appId; } public String getAppKey() { return appKey; }
@JsonProperty("appKey") @JsonProperty("appKey")
public String getAppKey() { return appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
@JsonProperty("appKey")
public void setAppKey(String appKey) { this.appId = appKey; }
public FeatureServiceEntity.Platform getPlatform() { return platform; } public FeatureServiceEntity.Platform getPlatform() { return platform; }
public void setPlatform(FeatureServiceEntity.Platform platform) { this.platform = platform; } public void setPlatform(FeatureServiceEntity.Platform platform) { this.platform = platform; }

查看文件

@ -7,10 +7,10 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface FeatureServiceRepository extends JpaRepository<FeatureServiceEntity, String> { public interface FeatureServiceRepository extends JpaRepository<FeatureServiceEntity, String> {
List<FeatureServiceEntity> findByAppId(String appId); List<FeatureServiceEntity> findByAppKey(String appKey);
List<FeatureServiceEntity> findByAppIdAndServiceType(String appId, FeatureServiceEntity.ServiceType serviceType); List<FeatureServiceEntity> findByAppKeyAndServiceType(String appKey, FeatureServiceEntity.ServiceType serviceType);
Optional<FeatureServiceEntity> findByAppIdAndPlatformAndServiceType( Optional<FeatureServiceEntity> findByAppKeyAndPlatformAndServiceType(
String appId, String appKey,
FeatureServiceEntity.Platform platform, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType); FeatureServiceEntity.ServiceType serviceType);

查看文件

@ -12,13 +12,13 @@ import java.util.Optional;
public interface ServiceActivationRequestRepository extends JpaRepository<ServiceActivationRequestEntity, String> { public interface ServiceActivationRequestRepository extends JpaRepository<ServiceActivationRequestEntity, String> {
Optional<ServiceActivationRequestEntity> findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc( Optional<ServiceActivationRequestEntity> findFirstByAppKeyAndPlatformAndServiceTypeOrderByCreatedAtDesc(
String appId, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType); String appKey, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType);
Optional<ServiceActivationRequestEntity> findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc( Optional<ServiceActivationRequestEntity> findFirstByAppKeyAndServiceTypeOrderByCreatedAtDesc(
String appId, FeatureServiceEntity.ServiceType serviceType); String appKey, FeatureServiceEntity.ServiceType serviceType);
List<ServiceActivationRequestEntity> findByAppIdOrderByCreatedAtDesc(String appId); List<ServiceActivationRequestEntity> findByAppKeyOrderByCreatedAtDesc(String appKey);
Page<ServiceActivationRequestEntity> findByStatusOrderByCreatedAtDesc(Status status, Pageable pageable); Page<ServiceActivationRequestEntity> findByStatusOrderByCreatedAtDesc(Status status, Pageable pageable);

查看文件

@ -33,7 +33,7 @@ public class DashboardService {
List<AppEntity> apps = appRepository.findByTenantId(tenantId); List<AppEntity> apps = appRepository.findByTenantId(tenantId);
long serviceCount = 0; long serviceCount = 0;
for (AppEntity app : apps) { for (AppEntity app : apps) {
serviceCount += featureServiceRepository.findByAppId(app.getId()).stream() serviceCount += featureServiceRepository.findByAppKey(app.getId()).stream()
.filter(FeatureServiceEntity::isEnabled) .filter(FeatureServiceEntity::isEnabled)
.count(); .count();
} }

查看文件

@ -38,7 +38,7 @@ public class FeatureServiceManager {
} }
public List<FeatureServiceEntity> listByApp(String appKey) { public List<FeatureServiceEntity> listByApp(String appKey) {
List<FeatureServiceEntity> services = repository.findByAppId(appKey); List<FeatureServiceEntity> services = repository.findByAppKey(appKey);
if (services.isEmpty()) { if (services.isEmpty()) {
return services; return services;
} }
@ -68,7 +68,7 @@ public class FeatureServiceManager {
String applyReason) { String applyReason) {
if (isAppWideService(serviceType)) { if (isAppWideService(serviceType)) {
requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(appKey, serviceType) requestRepository.findFirstByAppKeyAndServiceTypeOrderByCreatedAtDesc(appKey, serviceType)
.ifPresent(req -> { .ifPresent(req -> {
if (req.getStatus() == Status.PENDING) { if (req.getStatus() == Status.PENDING) {
throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理"); throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理");
@ -78,7 +78,7 @@ public class FeatureServiceManager {
ServiceActivationRequestEntity req = new ServiceActivationRequestEntity(); ServiceActivationRequestEntity req = new ServiceActivationRequestEntity();
req.setId(UUID.randomUUID().toString()); req.setId(UUID.randomUUID().toString());
req.setAppId(appKey); req.setAppKey(appKey);
req.setPlatform(platform); req.setPlatform(platform);
req.setServiceType(serviceType); req.setServiceType(serviceType);
req.setStatus(Status.PENDING); req.setStatus(Status.PENDING);
@ -94,7 +94,7 @@ public class FeatureServiceManager {
public FeatureServiceEntity disable(String appKey, FeatureServiceEntity.Platform platform, public FeatureServiceEntity disable(String appKey, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) { FeatureServiceEntity.ServiceType serviceType) {
if (isAppWideService(serviceType)) { if (isAppWideService(serviceType)) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType); List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(appKey, serviceType);
if (services.isEmpty()) { if (services.isEmpty()) {
throw new BusinessException(404, "服务未开通"); throw new BusinessException(404, "服务未开通");
} }
@ -104,7 +104,7 @@ public class FeatureServiceManager {
} }
FeatureServiceEntity entity = repository FeatureServiceEntity entity = repository
.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType) .findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType)
.orElseThrow(() -> new BusinessException(404, "服务未开通")); .orElseThrow(() -> new BusinessException(404, "服务未开通"));
entity.setEnabled(false); entity.setEnabled(false);
return repository.save(entity); return repository.save(entity);
@ -126,17 +126,17 @@ public class FeatureServiceManager {
requestRepository.save(req); requestRepository.save(req);
// Normalize to appKey so SdkConfigController queries are consistent // Normalize to appKey so SdkConfigController queries are consistent
String normalizedAppId = appRepository.findById(req.getAppId()) String normalizedAppId = appRepository.findById(req.getAppKey())
.map(app -> app.getAppKey()) .map(app -> app.getAppKey())
.orElse(req.getAppId()); .orElse(req.getAppKey());
if (isAppWideService(req.getServiceType())) { if (isAppWideService(req.getServiceType())) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(normalizedAppId, req.getServiceType()); List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(normalizedAppId, req.getServiceType());
if (services.isEmpty()) { if (services.isEmpty()) {
for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) { for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) {
FeatureServiceEntity created = new FeatureServiceEntity(); FeatureServiceEntity created = new FeatureServiceEntity();
created.setId(UUID.randomUUID().toString()); created.setId(UUID.randomUUID().toString());
created.setAppId(normalizedAppId); created.setAppKey(normalizedAppId);
created.setPlatform(platform); created.setPlatform(platform);
created.setServiceType(req.getServiceType()); created.setServiceType(req.getServiceType());
created.setEnabled(true); created.setEnabled(true);
@ -151,11 +151,11 @@ public class FeatureServiceManager {
} }
FeatureServiceEntity entity = repository FeatureServiceEntity entity = repository
.findByAppIdAndPlatformAndServiceType(normalizedAppId, req.getPlatform(), req.getServiceType()) .findByAppKeyAndPlatformAndServiceType(normalizedAppId, req.getPlatform(), req.getServiceType())
.orElseGet(() -> { .orElseGet(() -> {
FeatureServiceEntity e = new FeatureServiceEntity(); FeatureServiceEntity e = new FeatureServiceEntity();
e.setId(UUID.randomUUID().toString()); e.setId(UUID.randomUUID().toString());
e.setAppId(normalizedAppId); e.setAppKey(normalizedAppId);
e.setPlatform(req.getPlatform()); e.setPlatform(req.getPlatform());
e.setServiceType(req.getServiceType()); e.setServiceType(req.getServiceType());
e.setCreatedAt(LocalDateTime.now()); e.setCreatedAt(LocalDateTime.now());
@ -183,18 +183,18 @@ public class FeatureServiceManager {
} }
public List<ServiceActivationRequestEntity> listRequestsByApp(String appKey) { public List<ServiceActivationRequestEntity> listRequestsByApp(String appKey) {
return requestRepository.findByAppIdOrderByCreatedAtDesc(appKey); return requestRepository.findByAppKeyOrderByCreatedAtDesc(appKey);
} }
public FeatureServiceEntity getOrFail(String appKey, FeatureServiceEntity.Platform platform, public FeatureServiceEntity getOrFail(String appKey, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) { FeatureServiceEntity.ServiceType serviceType) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) { if (serviceType == FeatureServiceEntity.ServiceType.IM) {
return repository.findByAppIdAndServiceType(appKey, serviceType) return repository.findByAppKeyAndServiceType(appKey, serviceType)
.stream() .stream()
.findFirst() .findFirst()
.orElseThrow(() -> new BusinessException(404, "服务未配置")); .orElseThrow(() -> new BusinessException(404, "服务未配置"));
} }
return repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType) return repository.findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType)
.orElseThrow(() -> new BusinessException(404, "服务未配置")); .orElseThrow(() -> new BusinessException(404, "服务未配置"));
} }
@ -204,7 +204,7 @@ public class FeatureServiceManager {
FeatureServiceEntity.ServiceType serviceType, FeatureServiceEntity.ServiceType serviceType,
String config) { String config) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) { if (serviceType == FeatureServiceEntity.ServiceType.IM) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType); List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(appKey, serviceType);
if (services.isEmpty()) { if (services.isEmpty()) {
throw new BusinessException(404, "服务未配置"); throw new BusinessException(404, "服务未配置");
} }
@ -477,127 +477,25 @@ public class FeatureServiceManager {
public String buildPushConfig(String appKey, public String buildPushConfig(String appKey,
FeatureServiceEntity.Platform platform, FeatureServiceEntity.Platform platform,
String huaweiAppId, JsonNode pushConfig) {
String huaweiAppSecret, ObjectNode root = objectMapper.createObjectNode();
String huaweiCategory, root.put("schemaVersion", 2);
String xiaomiAppId, root.put("updatedAt", java.time.LocalDateTime.now().toString());
String xiaomiAppKey, ObjectNode vendors = objectMapper.createObjectNode();
String xiaomiAppSecret, ArrayNode profiles = objectMapper.createArrayNode();
String xiaomiChannelId, if (pushConfig != null && pushConfig.isObject()) {
String oppoAppId, JsonNode inputVendors = pushConfig.path("vendors");
String oppoAppKey, if (inputVendors != null && inputVendors.isObject()) {
String oppoMasterSecret, vendors.setAll((ObjectNode) inputVendors);
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());
} }
if (routing != null && routing.isObject()) { JsonNode inputProfiles = pushConfig.path("profiles");
node.set("routing", routing); if (inputProfiles != null && inputProfiles.isArray()) {
} else if (!node.has("routing")) { inputProfiles.forEach(profile -> profiles.add(profile.deepCopy()));
node.set("routing", defaultPushRouting());
} }
return node.toString();
} }
root.set("vendors", vendors);
private ArrayNode defaultPushChannels() { root.set("profiles", profiles);
ArrayNode channels = objectMapper.createArrayNode(); return root.toString();
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;
} }
@Transactional @Transactional
@ -649,12 +547,12 @@ public class FeatureServiceManager {
FeatureServiceEntity.ServiceType serviceType) { FeatureServiceEntity.ServiceType serviceType) {
FeatureServiceEntity entity; FeatureServiceEntity entity;
if (serviceType == FeatureServiceEntity.ServiceType.IM) { if (serviceType == FeatureServiceEntity.ServiceType.IM) {
entity = repository.findByAppIdAndServiceType(appKey, serviceType) entity = repository.findByAppKeyAndServiceType(appKey, serviceType)
.stream() .stream()
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} else { } else {
entity = repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType) entity = repository.findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType)
.orElse(null); .orElse(null);
} }
if (entity == null || entity.getConfig() == null || entity.getConfig().isBlank()) { if (entity == null || entity.getConfig() == null || entity.getConfig().isBlank()) {

查看文件

@ -80,7 +80,7 @@ public class OpsService {
List<AppEntity> apps = appRepository.findByTenantId(tenantId); List<AppEntity> apps = appRepository.findByTenantId(tenantId);
long subAccountCount = tenantRepository.countByParentId(tenantId); long subAccountCount = tenantRepository.countByParentId(tenantId);
long activeServiceCount = apps.stream() long activeServiceCount = apps.stream()
.flatMap(app -> featureServiceRepository.findByAppId(app.getAppKey()).stream()) .flatMap(app -> featureServiceRepository.findByAppKey(app.getAppKey()).stream())
.filter(FeatureServiceEntity::isEnabled) .filter(FeatureServiceEntity::isEnabled)
.count(); .count();
@ -207,7 +207,7 @@ public class OpsService {
public Map<String, Object> getAppDetail(String appKey) { public Map<String, Object> getAppDetail(String appKey) {
AppEntity app = appRepository.findByAppKey(appKey) AppEntity app = appRepository.findByAppKey(appKey)
.orElseThrow(() -> new IllegalArgumentException("应用不存在")); .orElseThrow(() -> new IllegalArgumentException("应用不存在"));
List<FeatureServiceEntity> services = featureServiceRepository.findByAppId(app.getAppKey()); List<FeatureServiceEntity> services = featureServiceRepository.findByAppKey(app.getAppKey());
long enabledCount = services.stream().filter(FeatureServiceEntity::isEnabled).count(); long enabledCount = services.stream().filter(FeatureServiceEntity::isEnabled).count();
Map<String, Object> result = new LinkedHashMap<>(); Map<String, Object> result = new LinkedHashMap<>();
result.put("app", app); result.put("app", app);
@ -221,7 +221,7 @@ public class OpsService {
public List<FeatureServiceEntity> listAppServices(String appKey) { public List<FeatureServiceEntity> listAppServices(String appKey) {
appRepository.findByAppKey(appKey) appRepository.findByAppKey(appKey)
.orElseThrow(() -> new IllegalArgumentException("应用不存在")); .orElseThrow(() -> new IllegalArgumentException("应用不存在"));
return featureServiceRepository.findByAppId(appKey); return featureServiceRepository.findByAppKey(appKey);
} }
public Page<OperationLogEntity> listOperationLogs(int page, int size) { public Page<OperationLogEntity> listOperationLogs(int page, int size) {

查看文件

@ -117,13 +117,13 @@ public class SdkAppProvisioningService {
} }
private void ensureFeatureDefaults(AppEntity app) { private void ensureFeatureDefaults(AppEntity app) {
featureServiceRepository.findByAppIdAndServiceType(app.getAppKey(), FeatureServiceEntity.ServiceType.IM) featureServiceRepository.findByAppKeyAndServiceType(app.getAppKey(), FeatureServiceEntity.ServiceType.IM)
.stream() .stream()
.findFirst() .findFirst()
.orElseGet(() -> { .orElseGet(() -> {
FeatureServiceEntity feature = new FeatureServiceEntity(); FeatureServiceEntity feature = new FeatureServiceEntity();
feature.setId(UUID.randomUUID().toString()); feature.setId(UUID.randomUUID().toString());
feature.setAppId(app.getAppKey()); feature.setAppKey(app.getAppKey());
feature.setPlatform(FeatureServiceEntity.Platform.ANDROID); feature.setPlatform(FeatureServiceEntity.Platform.ANDROID);
feature.setServiceType(FeatureServiceEntity.ServiceType.IM); feature.setServiceType(FeatureServiceEntity.ServiceType.IM);
feature.setEnabled(true); feature.setEnabled(true);
@ -141,11 +141,11 @@ public class SdkAppProvisioningService {
for (FeatureServiceEntity.ServiceType serviceType : List.of( for (FeatureServiceEntity.ServiceType serviceType : List.of(
FeatureServiceEntity.ServiceType.PUSH, FeatureServiceEntity.ServiceType.PUSH,
FeatureServiceEntity.ServiceType.UPDATE)) { FeatureServiceEntity.ServiceType.UPDATE)) {
featureServiceRepository.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, serviceType) featureServiceRepository.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, serviceType)
.orElseGet(() -> { .orElseGet(() -> {
FeatureServiceEntity feature = new FeatureServiceEntity(); FeatureServiceEntity feature = new FeatureServiceEntity();
feature.setId(UUID.randomUUID().toString()); feature.setId(UUID.randomUUID().toString());
feature.setAppId(app.getAppKey()); feature.setAppKey(app.getAppKey());
feature.setPlatform(platform); feature.setPlatform(platform);
feature.setServiceType(serviceType); feature.setServiceType(serviceType);
feature.setEnabled(true); feature.setEnabled(true);

查看文件

@ -88,3 +88,4 @@ sdk:
im-ws-url: ${SDK_IM_WS_URL:wss://im.dev.xuqinmin.com/ws/im} 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} 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-api-url: ${SDK_IM_API_URL:https://im.dev.xuqinmin.com}
im-platform-events-user-prefix: ${SDK_IM_PLATFORM_EVENTS_USER_PREFIX:platform-events:}

查看文件

@ -58,10 +58,10 @@ public class AppVersionController {
} }
Optional<AppVersionEntity> latest = versionRepository Optional<AppVersionEntity> latest = versionRepository
.findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc( .findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc(
appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode); appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
Optional<AppVersionEntity> forcedHigher = versionRepository Optional<AppVersionEntity> forcedHigher = versionRepository
.findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc( .findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc(
appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode); appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
if (latest.isEmpty()) { if (latest.isEmpty()) {
@ -146,7 +146,7 @@ public class AppVersionController {
} }
AppVersionEntity entity = new AppVersionEntity(); AppVersionEntity entity = new AppVersionEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setPlatform(platform); entity.setPlatform(platform);
entity.setVersionName(resolvedVersionName); entity.setVersionName(resolvedVersionName);
entity.setVersionCode(resolvedVersionCode); entity.setVersionCode(resolvedVersionCode);
@ -189,7 +189,7 @@ public class AppVersionController {
} }
AppVersionEntity saved = versionRepository.save(entity); AppVersionEntity saved = versionRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"APP_VERSION", "APP_VERSION",
saved.getId(), saved.getId(),
"UPLOAD", "UPLOAD",
@ -246,7 +246,7 @@ public class AppVersionController {
entity.setGrayMemberIds(null); entity.setGrayMemberIds(null);
AppVersionEntity saved = versionRepository.save(entity); AppVersionEntity saved = versionRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"APP_VERSION", "APP_VERSION",
saved.getId(), saved.getId(),
publishAction(previousStatus, saved.getPublishStatus(), publishImmediately), publishAction(previousStatus, saved.getPublishStatus(), publishImmediately),
@ -273,7 +273,7 @@ public class AppVersionController {
entity.setPublishStatus(AppVersionEntity.PublishStatus.DEPRECATED); entity.setPublishStatus(AppVersionEntity.PublishStatus.DEPRECATED);
AppVersionEntity saved = versionRepository.save(entity); AppVersionEntity saved = versionRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"APP_VERSION", "APP_VERSION",
saved.getId(), saved.getId(),
"UNPUBLISH", "UNPUBLISH",
@ -290,7 +290,7 @@ public class AppVersionController {
@PathVariable String id, @PathVariable String id,
@RequestBody Map<String, Object> body) throws Exception { @RequestBody Map<String, Object> body) throws Exception {
AppVersionEntity entity = versionRepository.findById(id).orElseThrow(); AppVersionEntity entity = versionRepository.findById(id).orElseThrow();
if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppId())) { if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppKey())) {
throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布"); throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布");
} }
boolean enabled = Boolean.TRUE.equals(body.get("enabled")); boolean enabled = Boolean.TRUE.equals(body.get("enabled"));
@ -305,7 +305,7 @@ public class AppVersionController {
String selectionSource = body.get("selectionSource") == null ? "LOCAL" String selectionSource = body.get("selectionSource") == null ? "LOCAL"
: body.get("selectionSource").toString().trim().toUpperCase(); : body.get("selectionSource").toString().trim().toUpperCase();
if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) { if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) {
memberIds = publishConfigService.resolveGrayMembers(entity.getAppId(), body); memberIds = publishConfigService.resolveGrayMembers(entity.getAppKey(), body);
} }
entity.setGrayMode("MEMBERS"); entity.setGrayMode("MEMBERS");
entity.setGrayMemberIds(toJson(memberIds)); entity.setGrayMemberIds(toJson(memberIds));
@ -318,7 +318,7 @@ public class AppVersionController {
entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED); entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED);
AppVersionEntity saved = versionRepository.save(entity); AppVersionEntity saved = versionRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"APP_VERSION", "APP_VERSION",
saved.getId(), saved.getId(),
"GRAY_UPDATE", "GRAY_UPDATE",
@ -336,7 +336,7 @@ public class AppVersionController {
public ResponseEntity<ApiResponse<List<AppVersionEntity>>> list( public ResponseEntity<ApiResponse<List<AppVersionEntity>>> list(
@RequestParam String appKey, @RequestParam AppVersionEntity.Platform platform) { @RequestParam String appKey, @RequestParam AppVersionEntity.Platform platform) {
return ResponseEntity.ok(ApiResponse.success( return ResponseEntity.ok(ApiResponse.success(
versionRepository.findByAppIdAndPlatformOrderByVersionCodeDesc(appKey, platform))); versionRepository.findByAppKeyAndPlatformOrderByVersionCodeDesc(appKey, platform)));
} }
private String publishAction(AppVersionEntity.PublishStatus previousStatus, private String publishAction(AppVersionEntity.PublishStatus previousStatus,

查看文件

@ -56,7 +56,7 @@ public class RnBundleController {
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase()); RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
Optional<RnBundleEntity> latest = bundleRepository Optional<RnBundleEntity> latest = bundleRepository
.findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc( .findTopByAppKeyAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
appKey, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED); appKey, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED);
if (latest.isEmpty()) { if (latest.isEmpty()) {
@ -115,7 +115,7 @@ public class RnBundleController {
RnBundleEntity entity = new RnBundleEntity(); RnBundleEntity entity = new RnBundleEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setModuleId(resolvedModuleId); entity.setModuleId(resolvedModuleId);
entity.setPlatform(resolvedPlatform); entity.setPlatform(resolvedPlatform);
entity.setVersion(resolvedVersion); entity.setVersion(resolvedVersion);
@ -132,7 +132,7 @@ public class RnBundleController {
entity.setCreatedAt(LocalDateTime.now()); entity.setCreatedAt(LocalDateTime.now());
RnBundleEntity saved = bundleRepository.save(entity); RnBundleEntity saved = bundleRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"RN_BUNDLE", "RN_BUNDLE",
saved.getId(), saved.getId(),
"UPLOAD", "UPLOAD",
@ -160,11 +160,11 @@ public class RnBundleController {
List<RnBundleEntity> result; List<RnBundleEntity> result;
if (moduleId != null && platform != null) { if (moduleId != null && platform != null) {
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase()); RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p); result = bundleRepository.findByAppKeyAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p);
} else if (moduleId != null) { } else if (moduleId != null) {
result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appKey, moduleId); result = bundleRepository.findByAppKeyAndModuleIdOrderByCreatedAtDesc(appKey, moduleId);
} else { } else {
result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appKey); result = bundleRepository.findByAppKeyOrderByCreatedAtDesc(appKey);
} }
return ResponseEntity.ok(ApiResponse.success(result)); return ResponseEntity.ok(ApiResponse.success(result));
} }
@ -194,7 +194,7 @@ public class RnBundleController {
entity.setGrayMemberIds(null); entity.setGrayMemberIds(null);
RnBundleEntity saved = bundleRepository.save(entity); RnBundleEntity saved = bundleRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"RN_BUNDLE", "RN_BUNDLE",
saved.getId(), saved.getId(),
publishImmediately && (scheduledPublishAt == null || scheduledPublishAt.isBlank()) ? "PUBLISH" : "SCHEDULE_PUBLISH", publishImmediately && (scheduledPublishAt == null || scheduledPublishAt.isBlank()) ? "PUBLISH" : "SCHEDULE_PUBLISH",
@ -220,7 +220,7 @@ public class RnBundleController {
entity.setPublishStatus(RnBundleEntity.PublishStatus.DEPRECATED); entity.setPublishStatus(RnBundleEntity.PublishStatus.DEPRECATED);
RnBundleEntity saved = bundleRepository.save(entity); RnBundleEntity saved = bundleRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"RN_BUNDLE", "RN_BUNDLE",
saved.getId(), saved.getId(),
"UNPUBLISH", "UNPUBLISH",
@ -237,7 +237,7 @@ public class RnBundleController {
@PathVariable String id, @PathVariable String id,
@RequestBody Map<String, Object> body) throws Exception { @RequestBody Map<String, Object> body) throws Exception {
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow(); RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppId())) { if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppKey())) {
throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布"); throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布");
} }
boolean enabled = Boolean.TRUE.equals(body.get("enabled")); boolean enabled = Boolean.TRUE.equals(body.get("enabled"));
@ -252,7 +252,7 @@ public class RnBundleController {
String selectionSource = body.get("selectionSource") == null ? "LOCAL" String selectionSource = body.get("selectionSource") == null ? "LOCAL"
: body.get("selectionSource").toString().trim().toUpperCase(); : body.get("selectionSource").toString().trim().toUpperCase();
if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) { if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) {
memberIds = publishConfigService.resolveGrayMembers(entity.getAppId(), body); memberIds = publishConfigService.resolveGrayMembers(entity.getAppKey(), body);
} }
entity.setGrayMode("MEMBERS"); entity.setGrayMode("MEMBERS");
entity.setGrayMemberIds(toJson(memberIds)); entity.setGrayMemberIds(toJson(memberIds));
@ -265,7 +265,7 @@ public class RnBundleController {
entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED); entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED);
RnBundleEntity saved = bundleRepository.save(entity); RnBundleEntity saved = bundleRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"RN_BUNDLE", "RN_BUNDLE",
saved.getId(), saved.getId(),
"GRAY_UPDATE", "GRAY_UPDATE",

查看文件

@ -69,7 +69,7 @@ public class UnifiedReleaseController {
} }
AppVersionEntity entity = new AppVersionEntity(); AppVersionEntity entity = new AppVersionEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setPlatform(item.platform()); entity.setPlatform(item.platform());
entity.setVersionName(item.versionName()); entity.setVersionName(item.versionName());
entity.setVersionCode(item.versionCode()); entity.setVersionCode(item.versionCode());
@ -90,7 +90,7 @@ public class UnifiedReleaseController {
} }
AppVersionEntity saved = appVersionRepository.save(entity); AppVersionEntity saved = appVersionRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"APP_VERSION", "APP_VERSION",
saved.getId(), saved.getId(),
"UPLOAD", "UPLOAD",
@ -117,7 +117,7 @@ public class UnifiedReleaseController {
RnBundleEntity entity = new RnBundleEntity(); RnBundleEntity entity = new RnBundleEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setModuleId(item.moduleId()); entity.setModuleId(item.moduleId());
entity.setPlatform(item.platform()); entity.setPlatform(item.platform());
entity.setVersion(item.version()); entity.setVersion(item.version());
@ -130,7 +130,7 @@ public class UnifiedReleaseController {
entity.setCreatedAt(LocalDateTime.now()); entity.setCreatedAt(LocalDateTime.now());
RnBundleEntity saved = rnBundleRepository.save(entity); RnBundleEntity saved = rnBundleRepository.save(entity);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"RN_BUNDLE", "RN_BUNDLE",
saved.getId(), saved.getId(),
"UPLOAD", "UPLOAD",

查看文件

@ -9,7 +9,7 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "update_gray_member", uniqueConstraints = { @Table(name = "update_gray_member", uniqueConstraints = {
@jakarta.persistence.UniqueConstraint(columnNames = {"appId", "groupName", "userId"}) @jakarta.persistence.UniqueConstraint(columnNames = {"appKey", "groupName", "userId"})
}) })
public class AppGrayMemberEntity { public class AppGrayMemberEntity {
@ -17,7 +17,7 @@ public class AppGrayMemberEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(length = 64) @Column(length = 64)
private String groupName; private String groupName;
@ -37,8 +37,8 @@ public class AppGrayMemberEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getGroupName() { return groupName; } public String getGroupName() { return groupName; }
public void setGroupName(String groupName) { this.groupName = groupName; } public void setGroupName(String groupName) { this.groupName = groupName; }

查看文件

@ -9,7 +9,7 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "update_publish_config", uniqueConstraints = { @Table(name = "update_publish_config", uniqueConstraints = {
@jakarta.persistence.UniqueConstraint(columnNames = {"appId"}) @jakarta.persistence.UniqueConstraint(columnNames = {"appKey"})
}) })
public class AppPublishConfigEntity { public class AppPublishConfigEntity {
@ -17,7 +17,7 @@ public class AppPublishConfigEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String configJson; private String configJson;
@ -28,8 +28,8 @@ public class AppPublishConfigEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getConfigJson() { return configJson; } public String getConfigJson() { return configJson; }
public void setConfigJson(String configJson) { this.configJson = configJson; } public void setConfigJson(String configJson) { this.configJson = configJson; }

查看文件

@ -5,7 +5,7 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "update_store_config", uniqueConstraints = { @Table(name = "update_store_config", uniqueConstraints = {
@UniqueConstraint(columnNames = {"appId", "storeType"}) @UniqueConstraint(columnNames = {"appKey", "storeType"})
}) })
public class AppStoreConfigEntity { public class AppStoreConfigEntity {
@ -27,7 +27,7 @@ public class AppStoreConfigEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(nullable = false, length = 16) @Column(nullable = false, length = 16)
@ -58,8 +58,8 @@ public class AppStoreConfigEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public StoreType getStoreType() { return storeType; } public StoreType getStoreType() { return storeType; }
public void setStoreType(StoreType storeType) { this.storeType = storeType; } public void setStoreType(StoreType storeType) { this.storeType = storeType; }

查看文件

@ -15,13 +15,13 @@ public class AppVersionEntity {
public enum Platform { ANDROID, IOS, HARMONY } public enum Platform { ANDROID, IOS, HARMONY }
public enum PublishStatus { DRAFT, PUBLISHED, DEPRECATED } public enum PublishStatus { DRAFT, PUBLISHED, DEPRECATED }
/** Per-store review state used in storeReviewStatus JSON values. */ /** 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 @Id
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(nullable = false, length = 16) @Column(nullable = false, length = 16)
@ -107,8 +107,8 @@ public class AppVersionEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public Platform getPlatform() { return platform; } public Platform getPlatform() { return platform; }
public void setPlatform(Platform platform) { this.platform = platform; } public void setPlatform(Platform platform) { this.platform = platform; }

查看文件

@ -19,7 +19,7 @@ public class RnBundleEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String moduleId; private String moduleId;
@ -73,8 +73,8 @@ public class RnBundleEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getModuleId() { return moduleId; } public String getModuleId() { return moduleId; }
public void setModuleId(String moduleId) { this.moduleId = moduleId; } public void setModuleId(String moduleId) { this.moduleId = moduleId; }

查看文件

@ -15,7 +15,7 @@ public class UpdateOperationLogEntity {
private String id; private String id;
@Column(nullable = false, length = 64) @Column(nullable = false, length = 64)
private String appId; private String appKey;
@Column(nullable = false, length = 32) @Column(nullable = false, length = 32)
private String resourceType; private String resourceType;
@ -41,8 +41,8 @@ public class UpdateOperationLogEntity {
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getAppId() { return appId; } public String getAppKey() { return appKey; }
public void setAppId(String appId) { this.appId = appId; } public void setAppKey(String appKey) { this.appKey = appKey; }
public String getResourceType() { return resourceType; } public String getResourceType() { return resourceType; }
public void setResourceType(String resourceType) { this.resourceType = resourceType; } public void setResourceType(String resourceType) { this.resourceType = resourceType; }

查看文件

@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;
public interface AppGrayMemberRepository extends JpaRepository<AppGrayMemberEntity, String> { public interface AppGrayMemberRepository extends JpaRepository<AppGrayMemberEntity, String> {
List<AppGrayMemberEntity> findByAppIdOrderByGroupNameAscNameAscUserIdAsc(String appId); List<AppGrayMemberEntity> findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(String appKey);
} }

查看文件

@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional; import java.util.Optional;
public interface AppPublishConfigRepository extends JpaRepository<AppPublishConfigEntity, String> { public interface AppPublishConfigRepository extends JpaRepository<AppPublishConfigEntity, String> {
Optional<AppPublishConfigEntity> findByAppId(String appId); Optional<AppPublishConfigEntity> findByAppKey(String appKey);
} }

查看文件

@ -8,9 +8,9 @@ import java.util.Optional;
public interface AppStoreConfigRepository extends JpaRepository<AppStoreConfigEntity, String> { public interface AppStoreConfigRepository extends JpaRepository<AppStoreConfigEntity, String> {
List<AppStoreConfigEntity> findByAppId(String appId); List<AppStoreConfigEntity> findByAppKey(String appKey);
List<AppStoreConfigEntity> findByAppIdAndEnabled(String appId, boolean enabled); List<AppStoreConfigEntity> findByAppKeyAndEnabled(String appKey, boolean enabled);
Optional<AppStoreConfigEntity> findByAppIdAndStoreType(String appId, AppStoreConfigEntity.StoreType storeType); Optional<AppStoreConfigEntity> findByAppKeyAndStoreType(String appKey, AppStoreConfigEntity.StoreType storeType);
} }

查看文件

@ -8,18 +8,18 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface AppVersionRepository extends JpaRepository<AppVersionEntity, String> { public interface AppVersionRepository extends JpaRepository<AppVersionEntity, String> {
List<AppVersionEntity> findByAppIdAndPlatformOrderByVersionCodeDesc( List<AppVersionEntity> findByAppKeyAndPlatformOrderByVersionCodeDesc(
String appId, AppVersionEntity.Platform platform); String appKey, AppVersionEntity.Platform platform);
Optional<AppVersionEntity> findTopByAppIdAndPlatformAndPublishStatusOrderByVersionCodeDesc( Optional<AppVersionEntity> findTopByAppKeyAndPlatformAndPublishStatusOrderByVersionCodeDesc(
String appId, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status); String appKey, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status);
Optional<AppVersionEntity> findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc( Optional<AppVersionEntity> findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc(
String appId, String appKey,
AppVersionEntity.Platform platform, AppVersionEntity.Platform platform,
AppVersionEntity.PublishStatus status, AppVersionEntity.PublishStatus status,
int versionCode); int versionCode);
Optional<AppVersionEntity> findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc( Optional<AppVersionEntity> findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc(
String appId, String appKey,
AppVersionEntity.Platform platform, AppVersionEntity.Platform platform,
AppVersionEntity.PublishStatus status, AppVersionEntity.PublishStatus status,
int versionCode); int versionCode);

查看文件

@ -8,12 +8,12 @@ import java.util.Optional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public interface RnBundleRepository extends JpaRepository<RnBundleEntity, String> { public interface RnBundleRepository extends JpaRepository<RnBundleEntity, String> {
List<RnBundleEntity> findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc( List<RnBundleEntity> findByAppKeyAndModuleIdAndPlatformOrderByCreatedAtDesc(
String appId, String moduleId, RnBundleEntity.Platform platform); String appKey, String moduleId, RnBundleEntity.Platform platform);
List<RnBundleEntity> findByAppIdAndModuleIdOrderByCreatedAtDesc(String appId, String moduleId); List<RnBundleEntity> findByAppKeyAndModuleIdOrderByCreatedAtDesc(String appKey, String moduleId);
List<RnBundleEntity> findByAppIdOrderByCreatedAtDesc(String appId); List<RnBundleEntity> findByAppKeyOrderByCreatedAtDesc(String appKey);
Optional<RnBundleEntity> findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc( Optional<RnBundleEntity> findTopByAppKeyAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
String appId, String moduleId, RnBundleEntity.Platform platform, RnBundleEntity.PublishStatus status); String appKey, String moduleId, RnBundleEntity.Platform platform, RnBundleEntity.PublishStatus status);
List<RnBundleEntity> findByPublishStatusAndScheduledPublishAtBefore( List<RnBundleEntity> findByPublishStatusAndScheduledPublishAtBefore(
RnBundleEntity.PublishStatus status, LocalDateTime before); RnBundleEntity.PublishStatus status, LocalDateTime before);

查看文件

@ -7,5 +7,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List; import java.util.List;
public interface UpdateOperationLogRepository extends JpaRepository<UpdateOperationLogEntity, String> { public interface UpdateOperationLogRepository extends JpaRepository<UpdateOperationLogEntity, String> {
List<UpdateOperationLogEntity> findByAppIdOrderByCreatedAtDesc(String appId, Pageable pageable); List<UpdateOperationLogEntity> findByAppKeyOrderByCreatedAtDesc(String appKey, Pageable pageable);
} }

查看文件

@ -32,35 +32,38 @@ public class AppStoreService {
private final AppVersionRepository versionRepo; private final AppVersionRepository versionRepo;
private final RnBundleRepository rnBundleRepository; private final RnBundleRepository rnBundleRepository;
private final UpdateOperationLogService operationLogService; private final UpdateOperationLogService operationLogService;
private final StoreReviewImNotifier storeReviewImNotifier;
public AppStoreService(AppStoreConfigRepository configRepo, public AppStoreService(AppStoreConfigRepository configRepo,
AppVersionRepository versionRepo, AppVersionRepository versionRepo,
RnBundleRepository rnBundleRepository, RnBundleRepository rnBundleRepository,
UpdateOperationLogService operationLogService) { UpdateOperationLogService operationLogService,
StoreReviewImNotifier storeReviewImNotifier) {
this.configRepo = configRepo; this.configRepo = configRepo;
this.versionRepo = versionRepo; this.versionRepo = versionRepo;
this.rnBundleRepository = rnBundleRepository; this.rnBundleRepository = rnBundleRepository;
this.operationLogService = operationLogService; this.operationLogService = operationLogService;
this.storeReviewImNotifier = storeReviewImNotifier;
} }
// Store config CRUD // Store config CRUD
public List<AppStoreConfigEntity> getConfigs(String appKey) { public List<AppStoreConfigEntity> getConfigs(String appKey) {
return configRepo.findByAppId(appKey); return configRepo.findByAppKey(appKey);
} }
public AppStoreConfigEntity saveConfig(String appKey, public AppStoreConfigEntity saveConfig(String appKey,
AppStoreConfigEntity.StoreType storeType, AppStoreConfigEntity.StoreType storeType,
String configJson, String configJson,
boolean enabled) { boolean enabled) {
boolean isCreate = configRepo.findByAppIdAndStoreType(appKey, storeType).isEmpty(); boolean isCreate = configRepo.findByAppKeyAndStoreType(appKey, storeType).isEmpty();
AppStoreConfigEntity entity = configRepo AppStoreConfigEntity entity = configRepo
.findByAppIdAndStoreType(appKey, storeType) .findByAppKeyAndStoreType(appKey, storeType)
.orElseGet(AppStoreConfigEntity::new); .orElseGet(AppStoreConfigEntity::new);
if (entity.getId() == null) { if (entity.getId() == null) {
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setStoreType(storeType); entity.setStoreType(storeType);
} }
entity.setConfigJson(configJson); entity.setConfigJson(configJson);
@ -81,7 +84,7 @@ public class AppStoreService {
} }
public void deleteConfig(String appKey, AppStoreConfigEntity.StoreType storeType) { public void deleteConfig(String appKey, AppStoreConfigEntity.StoreType storeType) {
configRepo.findByAppIdAndStoreType(appKey, storeType).ifPresent(cfg -> { configRepo.findByAppKeyAndStoreType(appKey, storeType).ifPresent(cfg -> {
configRepo.delete(cfg); configRepo.delete(cfg);
operationLogService.record( operationLogService.record(
appKey, appKey,
@ -118,7 +121,13 @@ public class AppStoreService {
Map<String, Object> reviewMap = new LinkedHashMap<>(); Map<String, Object> reviewMap = new LinkedHashMap<>();
for (String store : storeTypes) { 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.setStoreSubmitTargets(mapper.writeValueAsString(storeTypes));
v.setStoreReviewStatus(mapper.writeValueAsString(reviewMap)); v.setStoreReviewStatus(mapper.writeValueAsString(reviewMap));
@ -129,10 +138,10 @@ public class AppStoreService {
} }
AppVersionEntity saved = versionRepo.save(v); AppVersionEntity saved = versionRepo.save(v);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"APP_VERSION", "APP_VERSION",
saved.getId(), saved.getId(),
"STORE_SUBMIT", "STORE_SUBMIT_REQUEST",
null, null,
Map.of( Map.of(
"storeTypes", storeTypes, "storeTypes", storeTypes,
@ -140,6 +149,16 @@ public class AppStoreService {
"scheduledAt", saved.getStoreSubmitScheduledAt() == null ? "" : saved.getStoreSubmitScheduledAt().toString(), "scheduledAt", saved.getStoreSubmitScheduledAt() == null ? "" : saved.getStoreSubmitScheduledAt().toString(),
"autoPublishAfterReview", saved.isAutoPublishAfterReview() "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; return saved;
} }
@ -152,7 +171,7 @@ public class AppStoreService {
* Returns a map of storeType -> configJson (as parsed map, not raw string). * Returns a map of storeType -> configJson (as parsed map, not raw string).
*/ */
public Map<String, Object> getStoreCredentials(String appKey) throws Exception { public Map<String, Object> getStoreCredentials(String appKey) throws Exception {
List<AppStoreConfigEntity> configs = configRepo.findByAppIdAndEnabled(appKey, true); List<AppStoreConfigEntity> configs = configRepo.findByAppKeyAndEnabled(appKey, true);
Map<String, Object> result = new LinkedHashMap<>(); Map<String, Object> result = new LinkedHashMap<>();
for (AppStoreConfigEntity cfg : configs) { for (AppStoreConfigEntity cfg : configs) {
if (cfg.getStoreType() == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) { if (cfg.getStoreType() == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) {
@ -167,7 +186,7 @@ public class AppStoreService {
} }
public Map<String, String> getReviewWebhookConfig(String appKey) throws Exception { public Map<String, String> getReviewWebhookConfig(String appKey) throws Exception {
AppStoreConfigEntity cfg = configRepo.findByAppIdAndStoreType( AppStoreConfigEntity cfg = configRepo.findByAppKeyAndStoreType(
appKey, AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK).orElse(null); appKey, AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK).orElse(null);
if (cfg == null || !cfg.isEnabled() || cfg.getConfigJson() == null || cfg.getConfigJson().isBlank()) { if (cfg == null || !cfg.isEnabled() || cfg.getConfigJson() == null || cfg.getConfigJson().isBlank()) {
return Map.of(); return Map.of();
@ -179,7 +198,7 @@ public class AppStoreService {
if (storeType == null || storeType == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) { if (storeType == null || storeType == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) {
return ""; return "";
} }
return configRepo.findByAppIdAndStoreType(appKey, storeType) return configRepo.findByAppKeyAndStoreType(appKey, storeType)
.filter(AppStoreConfigEntity::isEnabled) .filter(AppStoreConfigEntity::isEnabled)
.map(AppStoreConfigEntity::getConfigJson) .map(AppStoreConfigEntity::getConfigJson)
.map(this::extractJumpUrl) .map(this::extractJumpUrl)
@ -205,14 +224,24 @@ public class AppStoreService {
AppVersionEntity v = versionRepo.findById(versionId).orElseThrow(); AppVersionEntity v = versionRepo.findById(versionId).orElseThrow();
Map<String, Object> reviewMap = parseReviewStatus(v.getStoreReviewStatus()); Map<String, Object> reviewMap = parseReviewStatus(v.getStoreReviewStatus());
reviewMap.put(storeType, reviewPayload(state.name(), reason)); Map<String, Object> 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)); v.setStoreReviewStatus(mapper.writeValueAsString(reviewMap));
if (v.isAutoPublishAfterReview() && allApproved(v, reviewMap)) { if (v.isAutoPublishAfterReview() && allApproved(v, reviewMap)) {
v.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED); v.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED);
log.info("Auto-published version {} after all stores approved", versionId); log.info("Auto-published version {} after all stores approved", versionId);
operationLogService.record( operationLogService.record(
v.getAppId(), v.getAppKey(),
"APP_VERSION", "APP_VERSION",
v.getId(), v.getId(),
"AUTO_PUBLISH", "AUTO_PUBLISH",
@ -225,7 +254,7 @@ public class AppStoreService {
AppVersionEntity saved = versionRepo.save(v); AppVersionEntity saved = versionRepo.save(v);
operationLogService.record( operationLogService.record(
saved.getAppId(), saved.getAppKey(),
"APP_VERSION", "APP_VERSION",
saved.getId(), saved.getId(),
"STORE_REVIEW", "STORE_REVIEW",
@ -233,9 +262,68 @@ public class AppStoreService {
Map.of( Map.of(
"storeType", storeType, "storeType", storeType,
"reviewState", state.name(), "reviewState", state.name(),
"stage", stage,
"batchId", batchId,
"publishStatus", saved.getPublishStatus().name() "publishStatus", saved.getPublishStatus().name()
)); ));
sendWebhook(saved, storeType, state, reason); 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<String, Object> reviewMap = parseReviewStatus(v.getStoreReviewStatus());
Map<String, Object> 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; return saved;
} }
@ -252,7 +340,7 @@ public class AppStoreService {
versionRepo.save(v); versionRepo.save(v);
log.info("Scheduled publish executed for version {}", v.getId()); log.info("Scheduled publish executed for version {}", v.getId());
operationLogService.record( operationLogService.record(
v.getAppId(), v.getAppKey(),
"APP_VERSION", "APP_VERSION",
v.getId(), v.getId(),
"SCHEDULE_PUBLISH", "SCHEDULE_PUBLISH",
@ -269,7 +357,7 @@ public class AppStoreService {
rnBundleRepository.save(bundle); rnBundleRepository.save(bundle);
log.info("Scheduled publish executed for RN bundle {}", bundle.getId()); log.info("Scheduled publish executed for RN bundle {}", bundle.getId());
operationLogService.record( operationLogService.record(
bundle.getAppId(), bundle.getAppKey(),
"RN_BUNDLE", "RN_BUNDLE",
bundle.getId(), bundle.getId(),
"SCHEDULE_PUBLISH", "SCHEDULE_PUBLISH",
@ -284,7 +372,7 @@ public class AppStoreService {
String url = v.getWebhookUrl(); String url = v.getWebhookUrl();
if (url == null || url.isBlank()) { if (url == null || url.isBlank()) {
try { try {
Map<String, String> shared = getReviewWebhookConfig(v.getAppId()); Map<String, String> shared = getReviewWebhookConfig(v.getAppKey());
url = shared.get("webhookUrl"); url = shared.get("webhookUrl");
} catch (Exception ignored) { } catch (Exception ignored) {
url = null; url = null;
@ -296,7 +384,7 @@ public class AppStoreService {
String body = mapper.writeValueAsString(Map.of( String body = mapper.writeValueAsString(Map.of(
"event", "store_review_update", "event", "store_review_update",
"versionId", v.getId(), "versionId", v.getId(),
"appKey", v.getAppId(), "appKey", v.getAppKey(),
"versionName", v.getVersionName(), "versionName", v.getVersionName(),
"storeType", storeType, "storeType", storeType,
"reviewState", state.name(), "reviewState", state.name(),
@ -304,7 +392,7 @@ public class AppStoreService {
"publishStatus", v.getPublishStatus().name(), "publishStatus", v.getPublishStatus().name(),
"timestamp", System.currentTimeMillis() "timestamp", System.currentTimeMillis()
)); ));
String secret = resolveWebhookSecret(v.getAppId()); String secret = resolveWebhookSecret(v.getAppKey());
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url)) .uri(URI.create(url))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
@ -354,12 +442,47 @@ public class AppStoreService {
} }
private Map<String, Object> reviewPayload(String state, String reason) { private Map<String, Object> reviewPayload(String state, String reason) {
return reviewPayload(state, reason, null, null, null, LocalDateTime.now().toString());
}
private Map<String, Object> reviewPayload(String state,
String reason,
String stage,
String batchId,
String submittedAt,
String updatedAt) {
Map<String, Object> payload = new LinkedHashMap<>(); Map<String, Object> payload = new LinkedHashMap<>();
payload.put("state", state); payload.put("state", state);
payload.put("reason", reason == null ? "" : reason); 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; return payload;
} }
@SuppressWarnings("unchecked")
private Map<String, Object> asReviewPayload(Object value) {
if (value instanceof Map<?, ?> map) {
return new LinkedHashMap<>((Map<String, Object>) 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) { private String readReviewState(Object value) {
if (value == null) { if (value == null) {
return ""; return "";

查看文件

@ -45,10 +45,10 @@ public class PublishConfigService {
} }
public AppPublishConfigEntity getConfig(String appKey) { public AppPublishConfigEntity getConfig(String appKey) {
return configRepository.findByAppId(appKey).orElseGet(() -> { return configRepository.findByAppKey(appKey).orElseGet(() -> {
AppPublishConfigEntity entity = new AppPublishConfigEntity(); AppPublishConfigEntity entity = new AppPublishConfigEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setConfigJson(defaultConfigJson()); entity.setConfigJson(defaultConfigJson());
entity.setUpdatedAt(LocalDateTime.now()); entity.setUpdatedAt(LocalDateTime.now());
return entity; return entity;
@ -56,10 +56,10 @@ public class PublishConfigService {
} }
public AppPublishConfigEntity saveConfig(String appKey, Map<String, Object> body) { public AppPublishConfigEntity saveConfig(String appKey, Map<String, Object> body) {
AppPublishConfigEntity entity = configRepository.findByAppId(appKey).orElseGet(AppPublishConfigEntity::new); AppPublishConfigEntity entity = configRepository.findByAppKey(appKey).orElseGet(AppPublishConfigEntity::new);
if (entity.getId() == null) { if (entity.getId() == null) {
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
} }
try { try {
entity.setConfigJson(objectMapper.writeValueAsString(body == null ? Map.of() : body)); entity.setConfigJson(objectMapper.writeValueAsString(body == null ? Map.of() : body));
@ -71,7 +71,7 @@ public class PublishConfigService {
} }
public JsonNode getConfigNode(String appKey) { 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()) { if (entity == null || entity.getConfigJson() == null || entity.getConfigJson().isBlank()) {
try { try {
return objectMapper.readTree(defaultConfigJson()); return objectMapper.readTree(defaultConfigJson());
@ -91,7 +91,7 @@ public class PublishConfigService {
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(Locale.ROOT); String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(Locale.ROOT);
String normalizedGroup = groupName == null ? "" : groupName.trim().toLowerCase(Locale.ROOT); String normalizedGroup = groupName == null ? "" : groupName.trim().toLowerCase(Locale.ROOT);
List<AppGrayMemberEntity> members = grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appKey) List<AppGrayMemberEntity> members = grayMemberRepository.findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(appKey)
.stream() .stream()
.filter(item -> normalizedGroup.isBlank() .filter(item -> normalizedGroup.isBlank()
|| safe(item.getGroupName()).toLowerCase(Locale.ROOT).contains(normalizedGroup)) || safe(item.getGroupName()).toLowerCase(Locale.ROOT).contains(normalizedGroup))
@ -170,7 +170,7 @@ public class PublishConfigService {
throw new IllegalStateException("Invalid gray member payload"); throw new IllegalStateException("Invalid gray member payload");
} }
grayMemberRepository.deleteAll( grayMemberRepository.deleteAll(
grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appKey)); grayMemberRepository.findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(appKey));
List<AppGrayMemberEntity> saved = new ArrayList<>(); List<AppGrayMemberEntity> saved = new ArrayList<>();
for (GrayMemberGroupPayload group : groups) { for (GrayMemberGroupPayload group : groups) {
@ -181,7 +181,7 @@ public class PublishConfigService {
} }
AppGrayMemberEntity entity = new AppGrayMemberEntity(); AppGrayMemberEntity entity = new AppGrayMemberEntity();
entity.setId(UUID.randomUUID().toString()); entity.setId(UUID.randomUUID().toString());
entity.setAppId(appKey); entity.setAppKey(appKey);
entity.setGroupName(groupName); entity.setGroupName(groupName);
entity.setUserId(member.userId().trim()); entity.setUserId(member.userId().trim());
entity.setName(member.name() == null ? "" : member.name().trim()); entity.setName(member.name() == null ? "" : member.name().trim());

某些文件未显示,因为此 diff 中更改的文件太多 显示更多