docs(deploy): 添加部署文档和安全设计规范
- 新增 XuqmGroup 部署文档,包含部署方案、架构建议和部署步骤 - 添加安全设计规范,涵盖密码安全、AppSecret验证和服务端API认证 - 补充平台REST API规范,定义Server-to-Server调用接口和错误码 - 创建Java IM服务端SDK计划文档,规划Maven包发布和接口实现
这个提交包含在:
父节点
0385b2010a
当前提交
dc1ada94ea
@ -1 +1 @@
|
||||
17
|
||||
21
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
ARG SERVICE_MODULE=tenant-service
|
||||
|
||||
FROM maven:3.9.9-eclipse-temurin-21 AS build
|
||||
@ -5,6 +6,7 @@ ARG SERVICE_MODULE
|
||||
WORKDIR /workspace
|
||||
|
||||
COPY pom.xml ./pom.xml
|
||||
COPY maven-settings.xml ./maven-settings.xml
|
||||
COPY common ./common
|
||||
COPY im-sdk ./im-sdk
|
||||
COPY tenant-service ./tenant-service
|
||||
@ -14,7 +16,8 @@ COPY update-service ./update-service
|
||||
COPY demo-service ./demo-service
|
||||
COPY file-service ./file-service
|
||||
|
||||
RUN mvn -pl ${SERVICE_MODULE} -am -DskipTests package
|
||||
RUN --mount=type=cache,target=/root/.m2,sharing=locked \
|
||||
mvn -s /workspace/maven-settings.xml -pl ${SERVICE_MODULE} -am -DskipTests package
|
||||
|
||||
FROM eclipse-temurin:21-jre-jammy
|
||||
WORKDIR /app
|
||||
|
||||
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@ -15,6 +15,7 @@ pipeline {
|
||||
PROD_HOST = '106.54.23.149'
|
||||
PROD_USER = 'ubuntu'
|
||||
COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml'
|
||||
DOCKER_BUILDKIT = '1'
|
||||
}
|
||||
|
||||
stages {
|
||||
@ -29,7 +30,8 @@ pipeline {
|
||||
def imageName = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${params.SERVICE}:${params.IMAGE_TAG}"
|
||||
bat """
|
||||
docker login ${ACR_REGISTRY} -u ${ACR_USERNAME} -p %ACR_PASS%
|
||||
docker build --build-arg SERVICE_MODULE=${params.SERVICE} -t ${imageName} .
|
||||
docker pull ${imageName} || exit 0
|
||||
docker build --build-arg SERVICE_MODULE=${params.SERVICE} --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${imageName} -t ${imageName} .
|
||||
docker push ${imageName}
|
||||
docker rmi ${imageName}
|
||||
"""
|
||||
|
||||
47
README.md
47
README.md
@ -138,15 +138,15 @@ cd update-service && mvn spring-boot:run &
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:SDK 和 demo 侧传入的 `appId` 实际按 `appKey` 解析。当前默认值是 `ak_demo_chat`,如果数据库里没有这条记录,tenant-service 会在启动时自动创建。
|
||||
> 说明:SDK 和 demo 侧统一传 `appKey`。当前默认值是 `ak_demo_chat`,如果数据库里没有这条记录,tenant-service 会在启动时自动创建。
|
||||
|
||||
#### 功能服务(需 Token)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/apps/{appId}/services` | 获取应用下所有功能服务 |
|
||||
| POST | `/api/apps/{appId}/services/toggle?platform=&serviceType=&enable=` | 开启/关闭功能服务 |
|
||||
| POST | `/api/apps/{appId}/services/{id}/regenerate-key` | 重新生成 secretKey |
|
||||
| GET | `/api/apps/{appKey}/services` | 获取应用下所有功能服务 |
|
||||
| POST | `/api/apps/{appKey}/services/toggle?platform=&serviceType=&enable=` | 开启/关闭功能服务 |
|
||||
| POST | `/api/apps/{appKey}/services/{id}/regenerate-key` | 重新生成 secretKey |
|
||||
|
||||
**platform 枚举**:`ANDROID` / `IOS` / `HARMONY`
|
||||
**serviceType 枚举**:`IM` / `PUSH` / `UPDATE`
|
||||
@ -216,7 +216,7 @@ cd update-service && mvn spring-boot:run &
|
||||
|
||||
```
|
||||
POST /api/im/auth/login
|
||||
?appId=ak_xxx
|
||||
?appKey=ak_xxx
|
||||
&userId=user_001
|
||||
&nickname=张三 (可选,仅首次注册时存入外部系统)
|
||||
&avatar=https://... (可选)
|
||||
@ -230,9 +230,9 @@ POST /api/im/auth/login
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/api/im/messages/send?appId=` | 发送消息 |
|
||||
| POST | `/api/im/messages/{id}/revoke?appId=` | 撤回消息 |
|
||||
| GET | `/api/im/messages/history/{toId}?appId=&page=&size=` | 查询历史消息 |
|
||||
| POST | `/api/im/messages/send?appKey=` | 发送消息 |
|
||||
| POST | `/api/im/messages/{id}/revoke?appKey=` | 撤回消息 |
|
||||
| GET | `/api/im/messages/history/{toId}?appKey=&page=&size=` | 查询历史消息 |
|
||||
|
||||
**发送消息请求体**
|
||||
```json
|
||||
@ -276,7 +276,7 @@ ws://localhost:8082/ws/im?token=<im_jwt>
|
||||
{
|
||||
"destination": "/app/chat.send",
|
||||
"payload": {
|
||||
"appId": "ak_xxx",
|
||||
"appKey": "ak_xxx",
|
||||
"toId": "user_002",
|
||||
"chatType": "SINGLE",
|
||||
"msgType": "TEXT",
|
||||
@ -310,7 +310,7 @@ Frame 格式:
|
||||
```json
|
||||
{
|
||||
"destination": "/app/chat.revoke",
|
||||
"payload": { "appId": "ak_xxx", "messageId": "msg-uuid" }
|
||||
"payload": { "appKey": "ak_xxx", "messageId": "msg-uuid" }
|
||||
}
|
||||
```
|
||||
|
||||
@ -332,7 +332,7 @@ Frame 格式:
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"appId": "ak_xxx",
|
||||
"appKey": "ak_xxx",
|
||||
"message": { ...消息对象... }
|
||||
}
|
||||
```
|
||||
@ -345,11 +345,11 @@ Frame 格式:
|
||||
|
||||
| 表名 | 说明 |
|
||||
|------|------|
|
||||
| `push_device_token` | 设备推送 token(appId + userId + vendor 唯一) |
|
||||
| `push_device_token` | 设备推送 token(appKey + userId + vendor 唯一) |
|
||||
|
||||
### 支持厂商
|
||||
|
||||
`HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`(iOS)/ `FCM`
|
||||
`HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`(iOS)
|
||||
|
||||
### 接口
|
||||
|
||||
@ -366,7 +366,7 @@ Frame 格式:
|
||||
**注册 token**
|
||||
```
|
||||
POST /api/push/register
|
||||
?appId=ak_xxx
|
||||
?appKey=ak_xxx
|
||||
&userId=user_001
|
||||
&vendor=HUAWEI
|
||||
&token=device_push_token
|
||||
@ -375,7 +375,7 @@ POST /api/push/register
|
||||
**发送推送**
|
||||
```
|
||||
POST /api/push/send
|
||||
?appId=ak_xxx
|
||||
?appKey=ak_xxx
|
||||
&userId=user_001
|
||||
&title=新消息
|
||||
&body=张三: Hello!
|
||||
@ -385,7 +385,7 @@ POST /api/push/send
|
||||
**开关接收推送**
|
||||
```
|
||||
POST /api/push/receive-push
|
||||
?appId=ak_xxx
|
||||
?appKey=ak_xxx
|
||||
&userId=user_001
|
||||
&enabled=false
|
||||
```
|
||||
@ -393,7 +393,7 @@ POST /api/push/receive-push
|
||||
**内部通知**
|
||||
```json
|
||||
{
|
||||
"appId": "ak_xxx",
|
||||
"appKey": "ak_xxx",
|
||||
"userIds": ["user_001", "user_002"],
|
||||
"title": "群聊消息",
|
||||
"body": "张三: Hello!",
|
||||
@ -437,11 +437,12 @@ push:
|
||||
| POST | `/api/v1/updates/app/upload` | 上传版本(先传 file-service,再把 `apkUrl` 交给 update-service) |
|
||||
| POST | `/api/v1/updates/app/{id}/publish` | 发布版本 |
|
||||
| GET | `/api/v1/updates/app/list` | 版本列表 |
|
||||
| POST | `/api/v1/updates/store/app/{id}/execute-submit` | 批量提交应用市场,过程会写入批次日志与逐市场状态 |
|
||||
|
||||
**检查更新**
|
||||
```
|
||||
GET /api/v1/updates/app/check
|
||||
?appId=ak_xxx
|
||||
?appKey=ak_xxx
|
||||
&platform=ANDROID
|
||||
¤tVersionCode=10
|
||||
```
|
||||
@ -464,6 +465,8 @@ GET /api/v1/updates/app/check
|
||||
|
||||
**platform 枚举**:`ANDROID` / `IOS` / `HARMONY`
|
||||
|
||||
**市场提审状态**:`PENDING` / `SUBMITTING` / `UNDER_REVIEW` / `APPROVED` / `REJECTED`
|
||||
|
||||
**上传 APK(两段式)**
|
||||
```
|
||||
POST /api/file/upload
|
||||
@ -474,7 +477,7 @@ POST /api/file/upload
|
||||
|
||||
```
|
||||
POST /api/v1/updates/app/upload
|
||||
appId=ak_xxx
|
||||
appKey=ak_xxx
|
||||
platform=ANDROID
|
||||
versionName=1.1.0
|
||||
versionCode=11
|
||||
@ -494,7 +497,7 @@ POST /api/v1/updates/app/upload
|
||||
**检查 RN 更新**
|
||||
```
|
||||
GET /api/v1/rn/update/check
|
||||
?appId=ak_xxx
|
||||
?appKey=ak_xxx
|
||||
&moduleId=main
|
||||
&platform=android
|
||||
¤tVersion=1.0.0
|
||||
@ -517,7 +520,7 @@ GET /api/v1/rn/update/check
|
||||
**上传 Bundle(multipart/form-data)**
|
||||
```
|
||||
POST /api/v1/rn/upload
|
||||
appId=ak_xxx
|
||||
appKey=ak_xxx
|
||||
moduleId=main
|
||||
platform=ANDROID
|
||||
version=1.1.0
|
||||
@ -526,7 +529,7 @@ POST /api/v1/rn/upload
|
||||
bundle=<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
|
||||
@Table(
|
||||
name = "demo_user",
|
||||
uniqueConstraints = @UniqueConstraint(name = "uq_demo_user_appid_userid", columnNames = {"app_id", "user_id"})
|
||||
uniqueConstraints = @UniqueConstraint(name = "uq_demo_user_appkey_userid", columnNames = {"app_key", "user_id"})
|
||||
)
|
||||
public class DemoUserEntity {
|
||||
|
||||
@ -18,8 +18,8 @@ public class DemoUserEntity {
|
||||
@Column(name = "id", length = 36, nullable = false, updatable = false)
|
||||
private String id;
|
||||
|
||||
@Column(name = "app_id", length = 64, nullable = false)
|
||||
private String appId;
|
||||
@Column(name = "app_key", length = 64, nullable = false)
|
||||
private String appKey;
|
||||
|
||||
@Column(name = "user_id", length = 128, nullable = false)
|
||||
private String userId;
|
||||
@ -43,8 +43,8 @@ public class DemoUserEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -10,14 +10,14 @@ import java.util.Optional;
|
||||
|
||||
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.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
|
||||
public AuthResult register(String appKey, String userId, String password, String nickname) {
|
||||
if (userRepository.existsByAppIdAndUserId(appKey, userId)) {
|
||||
if (userRepository.existsByAppKeyAndUserId(appKey, userId)) {
|
||||
throw new BusinessException(409, "User already exists: " + userId);
|
||||
}
|
||||
|
||||
DemoUserEntity user = new DemoUserEntity();
|
||||
user.setId(UUID.randomUUID().toString());
|
||||
user.setAppId(appKey);
|
||||
user.setAppKey(appKey);
|
||||
user.setUserId(userId);
|
||||
user.setPasswordHash(passwordEncoder.encode(password));
|
||||
user.setNickname(nickname != null ? nickname : userId);
|
||||
@ -84,7 +84,7 @@ public class DemoAuthService {
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public AuthResult login(String appKey, String userId, String password) {
|
||||
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
|
||||
DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseThrow(() -> new BusinessException(401, "Invalid credentials"));
|
||||
|
||||
if (!passwordEncoder.matches(password, user.getPasswordHash())) {
|
||||
@ -103,7 +103,7 @@ public class DemoAuthService {
|
||||
|
||||
@Transactional
|
||||
public void resetPassword(String appKey, String userId, String newPassword) {
|
||||
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
|
||||
DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseThrow(() -> new BusinessException(404, "User not found: " + userId));
|
||||
user.setPasswordHash(passwordEncoder.encode(newPassword));
|
||||
userRepository.save(user);
|
||||
@ -161,7 +161,7 @@ public class DemoAuthService {
|
||||
|
||||
private UserProfile toProfile(DemoUserEntity user) {
|
||||
return new UserProfile(
|
||||
user.getAppId(),
|
||||
user.getAppKey(),
|
||||
user.getUserId(),
|
||||
user.getNickname(),
|
||||
user.getAvatar(),
|
||||
|
||||
@ -24,14 +24,14 @@ public class DemoUserService {
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserProfile getProfile(String appKey, String userId) {
|
||||
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
|
||||
DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseThrow(() -> new BusinessException(404, "User not found"));
|
||||
return toProfile(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserProfile updateProfile(String appKey, String userId, String nickname, String avatar, String gender) {
|
||||
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
|
||||
DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseThrow(() -> new BusinessException(404, "User not found"));
|
||||
|
||||
if (nickname != null && !nickname.isBlank()) {
|
||||
@ -54,7 +54,7 @@ public class DemoUserService {
|
||||
|
||||
@Transactional
|
||||
public void resetPassword(String appKey, String userId, String oldPassword, String newPassword) {
|
||||
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
|
||||
DemoUserEntity user = userRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseThrow(() -> new BusinessException(404, "User not found"));
|
||||
|
||||
if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) {
|
||||
@ -81,7 +81,7 @@ public class DemoUserService {
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<UserProfile> listMembers(String appKey) {
|
||||
return userRepository.findAllByAppIdOrderByCreatedAtAsc(appKey)
|
||||
return userRepository.findAllByAppKeyOrderByCreatedAtAsc(appKey)
|
||||
.stream()
|
||||
.map(this::toProfile)
|
||||
.toList();
|
||||
@ -89,7 +89,7 @@ public class DemoUserService {
|
||||
|
||||
private UserProfile toProfile(DemoUserEntity user) {
|
||||
return new UserProfile(
|
||||
user.getAppId(),
|
||||
user.getAppKey(),
|
||||
user.getUserId(),
|
||||
user.getNickname(),
|
||||
user.getAvatar(),
|
||||
|
||||
@ -20,14 +20,14 @@
|
||||
| 名称 | 含义 | 常见位置 |
|
||||
|------|------|----------|
|
||||
| `tenant appKey` | 租户平台应用唯一标识,`tenant-service` 的 `/api/apps/{appKey}`、`/api/apps/{appKey}/services` 使用它 | 租户平台、服务配置页 |
|
||||
| `IM appKey` | IM 业务域作用域标识,`im-service` 的管理接口和消息接口虽然历史参数名仍叫 `appId`,但实际传的是这个值 | IM 管理页、IM HTTP 接口 |
|
||||
| `IM appKey` | IM 业务域作用域标识,`im-service` 的管理接口和消息接口统一使用它 | IM 管理页、IM HTTP 接口 |
|
||||
|
||||
结论:
|
||||
|
||||
- `tenant-platform` 的“服务配置”只认租户 `appKey`
|
||||
- `tenant-platform` 的“IM 管理”必须带 `appKey`
|
||||
- `tenant-platform` 的“离线推送 / 版本管理”只需要一次开通,平台差异配置在各自的管理页里维护,不再按 Android / iOS / 鸿蒙分别申请开通
|
||||
- `im-service` 代码里沿用旧参数名 `appId`,但这是历史命名,调用方传的是 `appKey`
|
||||
- `im-service` 的对外协议统一使用 `appKey`
|
||||
|
||||
## 初始化管理员账号
|
||||
|
||||
@ -184,6 +184,8 @@
|
||||
- RN bundle 建议打成 zip 后再上传,zip 内至少包含 `rn-manifest.json`、bundle 文件和资源文件;update-service 会优先从 manifest 自动读取 `moduleId`、`version` / `bundleVersion`、`minCommonVersion` 和 `packageName`。
|
||||
- 租户平台里的“发布配置”标签页保存灰度默认模式、成员目录同步回调和成员选择回调;当默认模式切到成员灰度时,至少要配置一个回调才允许保存,保存前也会做连通性校验。
|
||||
- 上下架、上传、发布、灰度、市场提交、商店配置变更都会写入 `update_operation_log`,可通过 `GET /api/v1/updates/ops/logs?appKey=...` 查询。
|
||||
- 市场提交增加了批次级日志和逐市场阶段日志,常见 action 包括 `STORE_SUBMIT_REQUEST`、`STORE_SUBMIT_BATCH_START`、`STORE_SUBMIT_STORE_START`、`STORE_SUBMIT_STORE_STAGE`、`STORE_SUBMIT_STORE_SUCCESS`、`STORE_SUBMIT_STORE_FAILED`、`STORE_SUBMIT_BATCH_END`。
|
||||
- `storeReviewStatus` 的单市场状态除了 `PENDING / UNDER_REVIEW / APPROVED / REJECTED` 之外,还会出现 `SUBMITTING`,表示当前已经进入提审流程但尚未返回最终结果;详情页会额外显示阶段、提交时间和批次 ID。
|
||||
- 提交应用市场会真实调用已实现的厂商接口。小米、OPPO、vivo 和华为/荣耀当前支持服务端提交;App Store、Google Play、鸿蒙仍以跳转页和人工流程为主。
|
||||
- 租户平台控制台新增 `GET /api/dashboard/stats`,返回当前租户的应用数、已开通服务数和子账号数,同时会写一条 `CONSOLE / DASHBOARD / VIEW_DASHBOARD` 操作日志。
|
||||
- 租户平台“操作日志”菜单现在集中查看租户平台与版本管理两类日志;版本管理日志继续按 `appKey` 查询,控制台访问日志则落在 `t_operation_log`。
|
||||
@ -343,7 +345,7 @@ IM 服务会在消息、好友和已读等事件发生后,以 `POST` 方式向
|
||||
请求头:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
- `X-App-Id`: 应用 `appId`
|
||||
- `X-App-Key`: 应用 `appKey`
|
||||
- `X-App-Timestamp`: 请求时间戳
|
||||
- `X-App-Nonce`: 随机串
|
||||
- `X-App-Signature`: 签名结果
|
||||
@ -351,7 +353,7 @@ IM 服务会在消息、好友和已读等事件发生后,以 `POST` 方式向
|
||||
签名规则:
|
||||
|
||||
```text
|
||||
HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(body))
|
||||
HMAC-SHA256(appSecret, appKey + "\n" + timestamp + "\n" + nonce + "\n" + sha256(body))
|
||||
```
|
||||
|
||||
统一请求体结构:
|
||||
@ -364,7 +366,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
|
||||
"requestTime": 1714360000000,
|
||||
"payload": {},
|
||||
"signature": null,
|
||||
"appId": "ak_demo_chat"
|
||||
"appKey": "ak_demo_chat"
|
||||
}
|
||||
```
|
||||
|
||||
@ -378,7 +380,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
|
||||
| `requestTime` | 毫秒时间戳 |
|
||||
| `payload` | 事件数据,结构随事件变化 |
|
||||
| `signature` | 预留字段,当前由请求头承载 |
|
||||
| `appId` | 回调所属应用 ID |
|
||||
| `appKey` | 回调所属应用唯一标识 |
|
||||
|
||||
`payload` 会根据事件类型变化:
|
||||
|
||||
@ -403,7 +405,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
|
||||
|
||||
```json
|
||||
{
|
||||
"appId": "ak_demo_chat",
|
||||
"appKey": "ak_demo_chat",
|
||||
"readerId": "user_001",
|
||||
"peerId": "user_002",
|
||||
"groupId": null,
|
||||
@ -417,7 +419,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
|
||||
|
||||
```json
|
||||
{
|
||||
"appId": "ak_demo_chat",
|
||||
"appKey": "ak_demo_chat",
|
||||
"requestId": "req_001",
|
||||
"fromUserId": "user_001",
|
||||
"toUserId": "user_002",
|
||||
@ -431,7 +433,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
|
||||
|
||||
```json
|
||||
{
|
||||
"appId": "ak_demo_chat",
|
||||
"appKey": "ak_demo_chat",
|
||||
"requestId": "req_001",
|
||||
"groupId": "group_001",
|
||||
"groupName": "技术群",
|
||||
@ -446,7 +448,7 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
|
||||
|
||||
```json
|
||||
{
|
||||
"appId": "ak_demo_chat",
|
||||
"appKey": "ak_demo_chat",
|
||||
"id": "blk_001",
|
||||
"userId": "user_001",
|
||||
"blockedUserId": "user_002",
|
||||
@ -469,14 +471,14 @@ HMAC-SHA256(appSecret, appId + "\n" + timestamp + "\n" + nonce + "\n" + sha256(b
|
||||
import crypto from 'crypto'
|
||||
|
||||
export function verifyWebhook({
|
||||
appId,
|
||||
appKey,
|
||||
timestamp,
|
||||
nonce,
|
||||
body,
|
||||
signature,
|
||||
appSecret,
|
||||
}: {
|
||||
appId: string
|
||||
appKey: string
|
||||
timestamp: string
|
||||
nonce: string
|
||||
body: string
|
||||
@ -484,7 +486,7 @@ export function verifyWebhook({
|
||||
appSecret: string
|
||||
}) {
|
||||
const bodyHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex')
|
||||
const payload = `${appId}\n${timestamp}\n${nonce}\n${bodyHash}`
|
||||
const payload = `${appKey}\n${timestamp}\n${nonce}\n${bodyHash}`
|
||||
const expected = crypto.createHmac('sha256', appSecret).update(payload, 'utf8').digest('hex')
|
||||
return expected === signature
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public class FriendController {
|
||||
public ResponseEntity<ApiResponse<List<String>>> listFriends(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestParam String appKey) {
|
||||
List<String> friendIds = friendRepository.findByAppIdAndUserId(appKey, userId)
|
||||
List<String> friendIds = friendRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.stream()
|
||||
.map(ImFriendEntity::getFriendId)
|
||||
.toList();
|
||||
@ -68,8 +68,8 @@ public class FriendController {
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String friendId,
|
||||
@RequestParam String appKey) {
|
||||
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId);
|
||||
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, userId, friendId);
|
||||
friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
return ResponseEntity.ok(ApiResponse.success(null));
|
||||
}
|
||||
|
||||
@ -77,8 +77,8 @@ public class FriendController {
|
||||
public ResponseEntity<ApiResponse<Void>> removeAllFriends(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestParam String appKey) {
|
||||
friendRepository.deleteByAppIdAndUserId(appKey, userId);
|
||||
friendRepository.deleteByAppIdAndFriendId(appKey, userId);
|
||||
friendRepository.deleteByAppKeyAndUserId(appKey, userId);
|
||||
friendRepository.deleteByAppKeyAndFriendId(appKey, userId);
|
||||
return ResponseEntity.ok(ApiResponse.ok());
|
||||
}
|
||||
|
||||
@ -91,8 +91,8 @@ public class FriendController {
|
||||
if (friendId == null || friendId.isBlank() || userId.equals(friendId)) {
|
||||
continue;
|
||||
}
|
||||
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId);
|
||||
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, userId, friendId);
|
||||
friendRepository.deleteByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success(null));
|
||||
}
|
||||
@ -103,7 +103,7 @@ public class FriendController {
|
||||
@PathVariable String friendId,
|
||||
@RequestParam String appKey,
|
||||
@RequestParam(required = false) String groupName) {
|
||||
ImFriendEntity link = friendRepository.findByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
ImFriendEntity link = friendRepository.findByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
.orElseGet(() -> addFriendLink(appKey, userId, friendId));
|
||||
link.setFriendGroup(normalizeGroup(groupName));
|
||||
return ResponseEntity.ok(ApiResponse.success(friendRepository.save(link)));
|
||||
@ -113,7 +113,7 @@ public class FriendController {
|
||||
public ResponseEntity<ApiResponse<List<String>>> listFriendGroups(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestParam String appKey) {
|
||||
List<String> groups = friendRepository.findByAppIdAndUserId(appKey, userId).stream()
|
||||
List<String> groups = friendRepository.findByAppKeyAndUserId(appKey, userId).stream()
|
||||
.map(ImFriendEntity::getFriendGroup)
|
||||
.filter(group -> group != null && !group.isBlank())
|
||||
.distinct()
|
||||
@ -128,7 +128,7 @@ public class FriendController {
|
||||
@RequestParam String appKey,
|
||||
@PathVariable String groupName) {
|
||||
List<String> friendIds = friendRepository
|
||||
.findByAppIdAndUserIdAndFriendGroup(appKey, userId, normalizeGroup(groupName))
|
||||
.findByAppKeyAndUserIdAndFriendGroup(appKey, userId, normalizeGroup(groupName))
|
||||
.stream()
|
||||
.map(ImFriendEntity::getFriendId)
|
||||
.toList();
|
||||
@ -137,19 +137,19 @@ public class FriendController {
|
||||
|
||||
private ImFriendEntity addFriendLink(String appKey, String userId, String friendId) {
|
||||
ImFriendEntity forward = friendRepository
|
||||
.findByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
.findByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
.orElseGet(() -> {
|
||||
ImFriendEntity e = new ImFriendEntity();
|
||||
e.setAppId(appKey);
|
||||
e.setAppKey(appKey);
|
||||
e.setUserId(userId);
|
||||
e.setFriendId(friendId);
|
||||
return friendRepository.save(e);
|
||||
});
|
||||
|
||||
friendRepository.findByAppIdAndUserIdAndFriendId(appKey, friendId, userId)
|
||||
friendRepository.findByAppKeyAndUserIdAndFriendId(appKey, friendId, userId)
|
||||
.orElseGet(() -> {
|
||||
ImFriendEntity e = new ImFriendEntity();
|
||||
e.setAppId(appKey);
|
||||
e.setAppKey(appKey);
|
||||
e.setUserId(friendId);
|
||||
e.setFriendId(userId);
|
||||
return friendRepository.save(e);
|
||||
@ -172,8 +172,8 @@ public class FriendController {
|
||||
@RequestBody FriendCheckRequest req) {
|
||||
List<FriendCheckResult> results = new ArrayList<>();
|
||||
for (String friendId : req.friendIds() == null ? List.<String>of() : req.friendIds()) {
|
||||
boolean isFriend = friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
boolean isFriend = friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
|| friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
results.add(new FriendCheckResult(friendId, isFriend));
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success(results));
|
||||
|
||||
@ -109,7 +109,7 @@ public class ImAdminController {
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
return ResponseEntity.ok(ApiResponse.success(
|
||||
accountRepository.findByAppId(appKey, PageRequest.of(page, size))));
|
||||
accountRepository.findByAppKey(appKey, PageRequest.of(page, size))));
|
||||
}
|
||||
|
||||
/** Ban or unban a user. */
|
||||
@ -119,7 +119,7 @@ public class ImAdminController {
|
||||
@PathVariable String userId,
|
||||
@AuthenticationPrincipal String operatorId,
|
||||
@RequestBody Map<String, String> body) {
|
||||
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId)
|
||||
ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseThrow(() -> new BusinessException(404, "账号不存在"));
|
||||
String status = body.get("status");
|
||||
if (status == null || status.isBlank()) {
|
||||
@ -152,7 +152,7 @@ public class ImAdminController {
|
||||
/** List all groups for the given appKey. */
|
||||
@GetMapping("/groups")
|
||||
public ResponseEntity<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). */
|
||||
@ -253,10 +253,10 @@ public class ImAdminController {
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> stats(
|
||||
@RequestParam String appKey,
|
||||
@AuthenticationPrincipal String operatorId) {
|
||||
long totalMessages = messageRepository.countByAppId(appKey);
|
||||
long totalUsers = accountRepository.countByAppId(appKey);
|
||||
long totalGroups = groupRepository.countByAppId(appKey);
|
||||
long todayMessages = messageRepository.countTodayByAppId(appKey);
|
||||
long totalMessages = messageRepository.countByAppKey(appKey);
|
||||
long totalUsers = accountRepository.countByAppKey(appKey);
|
||||
long totalGroups = groupRepository.countByAppKey(appKey);
|
||||
long todayMessages = messageRepository.countTodayByAppKey(appKey);
|
||||
operationLogService.record(appKey, operatorId, "VIEW_STATS", "STATS", null, "summary");
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(Map.of(
|
||||
@ -551,11 +551,11 @@ public class ImAdminController {
|
||||
Page<WebhookDeliveryEntity> result;
|
||||
PageRequest pageable = PageRequest.of(page, size);
|
||||
if (callbackEvent != null && !callbackEvent.isBlank()) {
|
||||
result = webhookDeliveryRepository.findByAppIdAndCallbackEvent(appKey, callbackEvent, pageable);
|
||||
result = webhookDeliveryRepository.findByAppKeyAndCallbackEvent(appKey, callbackEvent, pageable);
|
||||
} else if (success != null) {
|
||||
result = webhookDeliveryRepository.findByAppIdAndSuccess(appKey, success, pageable);
|
||||
result = webhookDeliveryRepository.findByAppKeyAndSuccess(appKey, success, pageable);
|
||||
} else {
|
||||
result = webhookDeliveryRepository.findByAppId(appKey, pageable);
|
||||
result = webhookDeliveryRepository.findByAppKey(appKey, pageable);
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
@ -569,9 +569,9 @@ public class ImAdminController {
|
||||
Page<WebhookAlertEntity> result;
|
||||
PageRequest pageable = PageRequest.of(page, size);
|
||||
if (acknowledged != null) {
|
||||
result = webhookAlertRepository.findByAppIdAndAcknowledged(appKey, acknowledged, pageable);
|
||||
result = webhookAlertRepository.findByAppKeyAndAcknowledged(appKey, acknowledged, pageable);
|
||||
} else {
|
||||
result = webhookAlertRepository.findByAppId(appKey, pageable);
|
||||
result = webhookAlertRepository.findByAppKey(appKey, pageable);
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
@ -583,7 +583,7 @@ public class ImAdminController {
|
||||
@AuthenticationPrincipal String operatorId) {
|
||||
WebhookAlertEntity alert = webhookAlertRepository.findById(id)
|
||||
.orElseThrow(() -> new BusinessException(404, "告警不存在"));
|
||||
if (!alert.getAppId().equals(appKey)) {
|
||||
if (!alert.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
alert.setAcknowledged(true);
|
||||
@ -604,7 +604,7 @@ public class ImAdminController {
|
||||
health.put("enabled", webhook.isEnabled());
|
||||
health.put("consecutiveFailures", webhook.getConsecutiveFailures());
|
||||
health.put("lastFailureAt", webhook.getLastFailureAt());
|
||||
long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppId(appKey);
|
||||
long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppKey(appKey);
|
||||
health.put("unacknowledgedAlerts", unacknowledgedAlerts);
|
||||
return ResponseEntity.ok(ApiResponse.success(health));
|
||||
}
|
||||
|
||||
@ -27,10 +27,10 @@ public class StatisticsController {
|
||||
@RequestParam(required = false) Integer days) {
|
||||
int d = days == null ? 7 : days;
|
||||
LocalDateTime since = LocalDateTime.now().minusDays(d);
|
||||
long total = messageRepository.countByAppIdAndCreatedAtAfter(appKey, since);
|
||||
long single = messageRepository.countByAppIdAndChatTypeAndCreatedAtAfter(
|
||||
long total = messageRepository.countByAppKeyAndCreatedAtAfter(appKey, since);
|
||||
long single = messageRepository.countByAppKeyAndChatTypeAndCreatedAtAfter(
|
||||
appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.SINGLE, since);
|
||||
long group = messageRepository.countByAppIdAndChatTypeAndCreatedAtAfter(
|
||||
long group = messageRepository.countByAppKeyAndChatTypeAndCreatedAtAfter(
|
||||
appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.GROUP, since);
|
||||
return ApiResponse.success(Map.of(
|
||||
"totalMessages", total,
|
||||
@ -46,9 +46,9 @@ public class StatisticsController {
|
||||
@RequestParam(required = false) Integer days) {
|
||||
int d = days == null ? 7 : days;
|
||||
LocalDateTime since = LocalDateTime.now().minusDays(d);
|
||||
long success = webhookDeliveryRepository.countSuccessfulByAppIdSince(appKey, since);
|
||||
long failed = webhookDeliveryRepository.countFailedByAppIdSince(appKey, since);
|
||||
List<Object[]> raw = webhookDeliveryRepository.statsByAppIdSince(appKey, since);
|
||||
long success = webhookDeliveryRepository.countSuccessfulByAppKeySince(appKey, since);
|
||||
long failed = webhookDeliveryRepository.countFailedByAppKeySince(appKey, since);
|
||||
List<Object[]> raw = webhookDeliveryRepository.statsByAppKeySince(appKey, since);
|
||||
List<Map<String, Object>> eventStats = new ArrayList<>();
|
||||
for (Object[] row : raw) {
|
||||
eventStats.add(Map.of(
|
||||
|
||||
@ -13,7 +13,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "im_account",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId"}))
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId"}))
|
||||
public class ImAccountEntity {
|
||||
|
||||
public enum Gender { UNKNOWN, MALE, FEMALE }
|
||||
@ -23,7 +23,7 @@ public class ImAccountEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String userId;
|
||||
@ -49,8 +49,8 @@ public class ImAccountEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -11,13 +11,13 @@ import java.time.LocalDateTime;
|
||||
@Entity
|
||||
@Table(
|
||||
name = "im_blacklist",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "blockedUserId"}),
|
||||
indexes = @Index(name = "idx_blacklist_app_user", columnList = "appId,userId")
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "blockedUserId"}),
|
||||
indexes = @Index(name = "idx_blacklist_app_user", columnList = "appKey,userId")
|
||||
)
|
||||
public class ImBlacklistEntity extends BaseIdEntity {
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String userId;
|
||||
@ -28,8 +28,8 @@ public class ImBlacklistEntity extends BaseIdEntity {
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -10,16 +10,16 @@ import java.time.LocalDateTime;
|
||||
@Entity
|
||||
@Table(
|
||||
name = "im_conversation_state",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "targetId", "chatType"}),
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "targetId", "chatType"}),
|
||||
indexes = {
|
||||
@Index(name = "idx_conv_state_app_user", columnList = "appId,userId"),
|
||||
@Index(name = "idx_conv_state_app_target", columnList = "appId,targetId")
|
||||
@Index(name = "idx_conv_state_app_user", columnList = "appKey,userId"),
|
||||
@Index(name = "idx_conv_state_app_target", columnList = "appKey,targetId")
|
||||
}
|
||||
)
|
||||
public class ImConversationStateEntity extends BaseIdEntity {
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String userId;
|
||||
@ -53,8 +53,8 @@ public class ImConversationStateEntity extends BaseIdEntity {
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -13,7 +13,7 @@ import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@Table(name = "im_friends",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "friendId"}))
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "friendId"}))
|
||||
public class ImFriendEntity {
|
||||
|
||||
@Id
|
||||
@ -21,7 +21,7 @@ public class ImFriendEntity {
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String userId;
|
||||
@ -39,8 +39,8 @@ public class ImFriendEntity {
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -13,15 +13,15 @@ import java.time.LocalDateTime;
|
||||
@Entity
|
||||
@Table(
|
||||
name = "im_friend_request",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "fromUserId", "toUserId"}),
|
||||
indexes = @Index(name = "idx_friend_request_app_to", columnList = "appId,toUserId")
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "fromUserId", "toUserId"}),
|
||||
indexes = @Index(name = "idx_friend_request_app_to", columnList = "appKey,toUserId")
|
||||
)
|
||||
public class ImFriendRequestEntity extends BaseIdEntity {
|
||||
|
||||
public enum Status { PENDING, ACCEPTED, REJECTED }
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String fromUserId;
|
||||
@ -40,8 +40,8 @@ public class ImFriendRequestEntity extends BaseIdEntity {
|
||||
|
||||
private LocalDateTime reviewedAt;
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getFromUserId() { return fromUserId; }
|
||||
public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; }
|
||||
|
||||
@ -17,7 +17,7 @@ public class ImGlobalMuteEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64, unique = true)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean enabled;
|
||||
@ -33,8 +33,8 @@ public class ImGlobalMuteEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
|
||||
@ -16,7 +16,7 @@ public class ImGroupEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String name;
|
||||
@ -49,8 +49,8 @@ public class ImGroupEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
@ -12,14 +12,14 @@ import java.time.LocalDateTime;
|
||||
@Entity
|
||||
@Table(
|
||||
name = "im_group_join_request",
|
||||
indexes = @Index(name = "idx_group_join_request_app_group", columnList = "appId,groupId")
|
||||
indexes = @Index(name = "idx_group_join_request_app_group", columnList = "appKey,groupId")
|
||||
)
|
||||
public class ImGroupJoinRequestEntity extends BaseIdEntity {
|
||||
|
||||
public enum Status { PENDING, ACCEPTED, REJECTED }
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String groupId;
|
||||
@ -38,8 +38,8 @@ public class ImGroupJoinRequestEntity extends BaseIdEntity {
|
||||
|
||||
private LocalDateTime reviewedAt;
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getGroupId() { return groupId; }
|
||||
public void setGroupId(String groupId) { this.groupId = groupId; }
|
||||
|
||||
@ -16,8 +16,8 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "im_message", indexes = {
|
||||
@Index(name = "idx_app_from", columnList = "appId,fromUserId"),
|
||||
@Index(name = "idx_app_to", columnList = "appId,toId")
|
||||
@Index(name = "idx_app_from", columnList = "appKey,fromUserId"),
|
||||
@Index(name = "idx_app_to", columnList = "appKey,toId")
|
||||
})
|
||||
public class ImMessageEntity {
|
||||
|
||||
@ -32,7 +32,7 @@ public class ImMessageEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String fromUserId;
|
||||
@ -74,8 +74,8 @@ public class ImMessageEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getFromUserId() { return fromUserId; }
|
||||
public void setFromUserId(String fromUserId) { this.fromUserId = fromUserId; }
|
||||
|
||||
@ -14,7 +14,7 @@ public class ImOfflineMessageEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String userId;
|
||||
@ -31,8 +31,8 @@ public class ImOfflineMessageEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -11,13 +11,13 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "im_operation_log", indexes = {
|
||||
@Index(name = "idx_op_log_app_time", columnList = "appId,createdAt"),
|
||||
@Index(name = "idx_op_log_app_operator", columnList = "appId,operatorId")
|
||||
@Index(name = "idx_op_log_app_time", columnList = "appKey,createdAt"),
|
||||
@Index(name = "idx_op_log_app_operator", columnList = "appKey,operatorId")
|
||||
})
|
||||
public class ImOperationLogEntity extends BaseIdEntity {
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String operatorId;
|
||||
@ -38,8 +38,8 @@ public class ImOperationLogEntity extends BaseIdEntity {
|
||||
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getOperatorId() { return operatorId; }
|
||||
public void setOperatorId(String operatorId) { this.operatorId = operatorId; }
|
||||
|
||||
@ -18,7 +18,7 @@ public class KeywordFilterEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 512)
|
||||
private String pattern;
|
||||
@ -39,8 +39,8 @@ public class KeywordFilterEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getPattern() { return pattern; }
|
||||
public void setPattern(String pattern) { this.pattern = pattern; }
|
||||
|
||||
@ -16,7 +16,7 @@ public class WebhookAlertEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String webhookId;
|
||||
@ -43,8 +43,8 @@ public class WebhookAlertEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getWebhookId() { return webhookId; }
|
||||
public void setWebhookId(String webhookId) { this.webhookId = webhookId; }
|
||||
|
||||
@ -16,7 +16,7 @@ public class WebhookConfigEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 512)
|
||||
private String url;
|
||||
@ -40,8 +40,8 @@ public class WebhookConfigEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUrl() { return url; }
|
||||
public void setUrl(String url) { this.url = url; }
|
||||
|
||||
@ -14,7 +14,7 @@ public class WebhookDeliveryEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String callbackId;
|
||||
@ -46,8 +46,8 @@ public class WebhookDeliveryEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getCallbackId() { return callbackId; }
|
||||
public void setCallbackId(String callbackId) { this.callbackId = callbackId; }
|
||||
|
||||
@ -10,12 +10,12 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ImAccountRepository extends JpaRepository<ImAccountEntity, String> {
|
||||
Optional<ImAccountEntity> findByAppIdAndUserId(String appId, String userId);
|
||||
boolean existsByAppIdAndUserId(String appId, String userId);
|
||||
Page<ImAccountEntity> findByAppId(String appId, Pageable pageable);
|
||||
long countByAppId(String appId);
|
||||
Optional<ImAccountEntity> findByAppKeyAndUserId(String appKey, String userId);
|
||||
boolean existsByAppKeyAndUserId(String appKey, String userId);
|
||||
Page<ImAccountEntity> findByAppKey(String appKey, Pageable pageable);
|
||||
long countByAppKey(String appKey);
|
||||
|
||||
@Query("SELECT a FROM ImAccountEntity a WHERE a.appId = :appId AND a.status = 'ACTIVE' AND " +
|
||||
@Query("SELECT a FROM ImAccountEntity a WHERE a.appKey = :appKey AND a.status = 'ACTIVE' AND " +
|
||||
"(LOWER(a.userId) LIKE LOWER(CONCAT('%',:kw,'%')) OR LOWER(a.nickname) LIKE LOWER(CONCAT('%',:kw,'%')))")
|
||||
List<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;
|
||||
|
||||
public interface ImBlacklistRepository extends JpaRepository<ImBlacklistEntity, String> {
|
||||
List<ImBlacklistEntity> findByAppId(String appId);
|
||||
List<ImBlacklistEntity> findByAppIdAndUserId(String appId, String userId);
|
||||
List<ImBlacklistEntity> findByAppKey(String appKey);
|
||||
List<ImBlacklistEntity> findByAppKeyAndUserId(String appKey, String userId);
|
||||
|
||||
Optional<ImBlacklistEntity> findByAppIdAndUserIdAndBlockedUserId(
|
||||
String appId, String userId, String blockedUserId);
|
||||
Optional<ImBlacklistEntity> findByAppKeyAndUserIdAndBlockedUserId(
|
||||
String appKey, String userId, String blockedUserId);
|
||||
|
||||
boolean existsByAppIdAndUserIdAndBlockedUserId(String appId, String userId, String blockedUserId);
|
||||
boolean existsByAppKeyAndUserIdAndBlockedUserId(String appKey, String userId, String blockedUserId);
|
||||
|
||||
void deleteByAppIdAndUserIdAndBlockedUserId(String appId, String userId, String blockedUserId);
|
||||
void deleteByAppKeyAndUserIdAndBlockedUserId(String appKey, String userId, String blockedUserId);
|
||||
}
|
||||
|
||||
@ -7,16 +7,16 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ImConversationStateRepository extends JpaRepository<ImConversationStateEntity, String> {
|
||||
Optional<ImConversationStateEntity> findByAppIdAndUserIdAndTargetIdAndChatType(
|
||||
String appId, String userId, String targetId, String chatType);
|
||||
Optional<ImConversationStateEntity> findByAppKeyAndUserIdAndTargetIdAndChatType(
|
||||
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(
|
||||
String appId, String userId, String conversationGroup);
|
||||
List<ImConversationStateEntity> findByAppKeyAndUserIdAndConversationGroup(
|
||||
String appKey, String userId, String conversationGroup);
|
||||
|
||||
void deleteByAppIdAndUserIdAndTargetIdAndChatType(
|
||||
String appId, String userId, String targetId, String chatType);
|
||||
void deleteByAppKeyAndUserIdAndTargetIdAndChatType(
|
||||
String appKey, String userId, String targetId, String chatType);
|
||||
}
|
||||
|
||||
@ -9,20 +9,20 @@ import java.util.Optional;
|
||||
|
||||
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
|
||||
void deleteByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId);
|
||||
void deleteByAppKeyAndUserIdAndFriendId(String appKey, String userId, String friendId);
|
||||
|
||||
@Transactional
|
||||
void deleteByAppIdAndUserId(String appId, String userId);
|
||||
void deleteByAppKeyAndUserId(String appKey, String userId);
|
||||
|
||||
@Transactional
|
||||
void deleteByAppIdAndFriendId(String appId, String friendId);
|
||||
void deleteByAppKeyAndFriendId(String appKey, String friendId);
|
||||
}
|
||||
|
||||
@ -7,10 +7,10 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ImFriendRequestRepository extends JpaRepository<ImFriendRequestEntity, String> {
|
||||
List<ImFriendRequestEntity> findByAppId(String appId);
|
||||
Optional<ImFriendRequestEntity> findByAppIdAndFromUserIdAndToUserId(
|
||||
String appId, String fromUserId, String toUserId);
|
||||
List<ImFriendRequestEntity> findByAppKey(String appKey);
|
||||
Optional<ImFriendRequestEntity> findByAppKeyAndFromUserIdAndToUserId(
|
||||
String appKey, String fromUserId, String toUserId);
|
||||
|
||||
List<ImFriendRequestEntity> findByAppIdAndToUserId(String appId, String toUserId);
|
||||
List<ImFriendRequestEntity> findByAppIdAndFromUserId(String appId, String fromUserId);
|
||||
List<ImFriendRequestEntity> findByAppKeyAndToUserId(String appKey, String toUserId);
|
||||
List<ImFriendRequestEntity> findByAppKeyAndFromUserId(String appKey, String fromUserId);
|
||||
}
|
||||
|
||||
@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.Optional;
|
||||
|
||||
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;
|
||||
|
||||
public interface ImGroupJoinRequestRepository extends JpaRepository<ImGroupJoinRequestEntity, String> {
|
||||
List<ImGroupJoinRequestEntity> findByAppId(String appId);
|
||||
Optional<ImGroupJoinRequestEntity> findByAppIdAndGroupIdAndRequesterId(
|
||||
String appId, String groupId, String requesterId);
|
||||
List<ImGroupJoinRequestEntity> findByAppKey(String appKey);
|
||||
Optional<ImGroupJoinRequestEntity> findByAppKeyAndGroupIdAndRequesterId(
|
||||
String appKey, String groupId, String requesterId);
|
||||
|
||||
List<ImGroupJoinRequestEntity> findByAppIdAndGroupId(String appId, String groupId);
|
||||
List<ImGroupJoinRequestEntity> findByAppIdAndRequesterId(String appId, String requesterId);
|
||||
List<ImGroupJoinRequestEntity> findByAppKeyAndGroupId(String appKey, String groupId);
|
||||
List<ImGroupJoinRequestEntity> findByAppKeyAndRequesterId(String appKey, String requesterId);
|
||||
}
|
||||
|
||||
@ -8,22 +8,22 @@ import org.springframework.data.repository.query.Param;
|
||||
import java.util.List;
|
||||
|
||||
public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String> {
|
||||
List<ImGroupEntity> findByAppId(String appId);
|
||||
long countByAppId(String appId);
|
||||
List<ImGroupEntity> findByAppKey(String appKey);
|
||||
long countByAppKey(String appKey);
|
||||
|
||||
@Query(value = """
|
||||
SELECT * FROM im_group
|
||||
WHERE app_id = :appId
|
||||
WHERE app_key = :appKey
|
||||
AND JSON_CONTAINS(member_ids, JSON_QUOTE(:userId))
|
||||
ORDER BY created_at DESC
|
||||
""", nativeQuery = true)
|
||||
List<ImGroupEntity> findUserGroups(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId);
|
||||
|
||||
@Query("""
|
||||
select g from ImGroupEntity g
|
||||
where g.appId = :appId
|
||||
where g.appKey = :appKey
|
||||
and (:keyword is null or :keyword = '' or
|
||||
lower(g.id) like lower(concat('%', :keyword, '%')) or
|
||||
lower(g.name) like lower(concat('%', :keyword, '%')) or
|
||||
@ -32,7 +32,7 @@ public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String>
|
||||
order by g.createdAt desc
|
||||
""")
|
||||
List<ImGroupEntity> searchByKeyword(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("keyword") String keyword,
|
||||
Pageable pageable);
|
||||
}
|
||||
|
||||
@ -25,16 +25,16 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
chat_type,
|
||||
MAX(created_at) AS last_time
|
||||
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)
|
||||
GROUP BY target_id, chat_type
|
||||
UNION ALL
|
||||
SELECT to_id AS target_id, chat_type, MAX(created_at) AS last_time
|
||||
FROM im_message
|
||||
WHERE app_id = :appId AND chat_type = 'GROUP'
|
||||
WHERE app_key = :appKey AND chat_type = 'GROUP'
|
||||
AND to_id IN (
|
||||
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
|
||||
) combined
|
||||
@ -42,31 +42,31 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
LIMIT :size
|
||||
""", nativeQuery = true)
|
||||
List<ConversationSummary> findConversations(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId,
|
||||
@Param("size") int size);
|
||||
Page<ImMessageEntity> findByAppIdAndToIdOrderByCreatedAtDesc(
|
||||
String appId, String toId, Pageable pageable);
|
||||
Page<ImMessageEntity> findByAppIdAndFromUserIdAndToIdOrderByCreatedAtDesc(
|
||||
String appId, String fromUserId, String toId, Pageable pageable);
|
||||
Page<ImMessageEntity> findByAppKeyAndToIdOrderByCreatedAtDesc(
|
||||
String appKey, String toId, Pageable pageable);
|
||||
Page<ImMessageEntity> findByAppKeyAndFromUserIdAndToIdOrderByCreatedAtDesc(
|
||||
String appKey, String fromUserId, String toId, Pageable pageable);
|
||||
|
||||
@Query("""
|
||||
select m from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
|
||||
and ((m.fromUserId = :userId and m.toId = :peerId)
|
||||
or (m.fromUserId = :peerId and m.toId = :userId))
|
||||
order by m.createdAt desc
|
||||
""")
|
||||
Page<ImMessageEntity> findSingleConversation(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId,
|
||||
@Param("peerId") String peerId,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("""
|
||||
select m from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
|
||||
and ((m.fromUserId = :userId and m.toId = :peerId)
|
||||
or (m.fromUserId = :peerId and m.toId = :userId))
|
||||
@ -81,7 +81,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
order by m.createdAt desc
|
||||
""")
|
||||
Page<ImMessageEntity> findSingleConversationFiltered(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId,
|
||||
@Param("peerId") String peerId,
|
||||
@Param("msgType") ImMessageEntity.MsgType msgType,
|
||||
@ -90,33 +90,33 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
@Param("endTime") LocalDateTime endTime,
|
||||
Pageable pageable);
|
||||
|
||||
List<ImMessageEntity> findByAppIdAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
String appId,
|
||||
List<ImMessageEntity> findByAppKeyAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
String appKey,
|
||||
String fromUserId,
|
||||
String toId,
|
||||
LocalDateTime createdAt);
|
||||
|
||||
List<ImMessageEntity> findByAppIdAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
String appId,
|
||||
List<ImMessageEntity> findByAppKeyAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
String appKey,
|
||||
String toId,
|
||||
ImMessageEntity.ChatType chatType,
|
||||
LocalDateTime createdAt);
|
||||
|
||||
@Query("""
|
||||
select m from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
|
||||
and m.toId = :groupId
|
||||
order by m.createdAt desc
|
||||
""")
|
||||
Page<ImMessageEntity> findGroupHistory(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("groupId") String groupId,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("""
|
||||
select m from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
|
||||
and m.toId = :groupId
|
||||
and (:msgType is null or m.msgType = :msgType)
|
||||
@ -129,7 +129,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
order by m.createdAt desc
|
||||
""")
|
||||
Page<ImMessageEntity> findGroupHistoryFiltered(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("groupId") String groupId,
|
||||
@Param("msgType") ImMessageEntity.MsgType msgType,
|
||||
@Param("keyword") String keyword,
|
||||
@ -139,7 +139,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
|
||||
@Query("""
|
||||
select m from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and (:chatType is null or m.chatType = :chatType)
|
||||
and (:msgType is null or m.msgType = :msgType)
|
||||
and (:keyword is null or :keyword = '' or
|
||||
@ -152,7 +152,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
order by m.createdAt desc
|
||||
""")
|
||||
Page<ImMessageEntity> searchByKeyword(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("chatType") ImMessageEntity.ChatType chatType,
|
||||
@Param("msgType") ImMessageEntity.MsgType msgType,
|
||||
@Param("keyword") String keyword,
|
||||
@ -162,7 +162,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
|
||||
@Query("""
|
||||
select m from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and (:chatType is null or m.chatType = :chatType)
|
||||
and (:msgType is null or m.msgType = :msgType)
|
||||
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
|
||||
and m.toId in (
|
||||
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
|
||||
)
|
||||
)
|
||||
@ -186,7 +186,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
order by m.createdAt desc
|
||||
""")
|
||||
Page<ImMessageEntity> searchByKeywordForUser(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId,
|
||||
@Param("chatType") ImMessageEntity.ChatType chatType,
|
||||
@Param("msgType") ImMessageEntity.MsgType msgType,
|
||||
@ -197,7 +197,7 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
|
||||
@Query("""
|
||||
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.fromUserId = :userId and m.toId = :peerId)
|
||||
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)
|
||||
""")
|
||||
long countUnreadSingleConversation(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId,
|
||||
@Param("peerId") String peerId,
|
||||
@Param("since") LocalDateTime since);
|
||||
|
||||
@Query("""
|
||||
select count(m) from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
|
||||
and m.toId = :groupId
|
||||
and m.fromUserId <> :userId
|
||||
and (:since is null or m.createdAt > :since)
|
||||
""")
|
||||
long countUnreadGroupConversation(
|
||||
@Param("appId") String appId,
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId,
|
||||
@Param("groupId") String groupId,
|
||||
@Param("since") LocalDateTime since);
|
||||
|
||||
long countByAppId(String appId);
|
||||
long countByAppKey(String appKey);
|
||||
|
||||
@Query("select count(m) from ImMessageEntity m where m.appId = :appId and m.createdAt >= :since")
|
||||
long countByAppIdAndCreatedAtAfter(@Param("appId") String appId, @Param("since") LocalDateTime since);
|
||||
@Query("select count(m) from ImMessageEntity m where m.appKey = :appKey and m.createdAt >= :since")
|
||||
long countByAppKeyAndCreatedAtAfter(@Param("appKey") String appKey, @Param("since") LocalDateTime since);
|
||||
|
||||
@Query("select count(m) from ImMessageEntity m where m.appId = :appId and m.chatType = :chatType and m.createdAt >= :since")
|
||||
long countByAppIdAndChatTypeAndCreatedAtAfter(
|
||||
@Param("appId") String appId,
|
||||
@Query("select count(m) from ImMessageEntity m where m.appKey = :appKey and m.chatType = :chatType and m.createdAt >= :since")
|
||||
long countByAppKeyAndChatTypeAndCreatedAtAfter(
|
||||
@Param("appKey") String appKey,
|
||||
@Param("chatType") ImMessageEntity.ChatType chatType,
|
||||
@Param("since") LocalDateTime since);
|
||||
|
||||
default long countTodayByAppId(String appId) {
|
||||
return countByAppIdAndCreatedAtAfter(appId, LocalDateTime.now().toLocalDate().atStartOfDay());
|
||||
default long countTodayByAppKey(String appKey) {
|
||||
return countByAppKeyAndCreatedAtAfter(appKey, LocalDateTime.now().toLocalDate().atStartOfDay());
|
||||
}
|
||||
|
||||
@Query("""
|
||||
select m from ImMessageEntity m
|
||||
where m.appId = :appId
|
||||
where m.appKey = :appKey
|
||||
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
|
||||
and m.toId = :userId
|
||||
and m.status <> com.xuqm.im.entity.ImMessageEntity$MsgStatus.READ
|
||||
""")
|
||||
List<ImMessageEntity> findUnreadByAppIdAndToId(
|
||||
@Param("appId") String appId,
|
||||
List<ImMessageEntity> findUnreadByAppKeyAndToId(
|
||||
@Param("appKey") String appKey,
|
||||
@Param("userId") String userId);
|
||||
}
|
||||
|
||||
@ -11,14 +11,14 @@ import java.util.List;
|
||||
@Repository
|
||||
public interface ImOfflineMessageRepository extends JpaRepository<ImOfflineMessageEntity, String> {
|
||||
|
||||
List<ImOfflineMessageEntity> findByAppIdAndUserIdAndDeliveredFalse(String appId, String userId);
|
||||
List<ImOfflineMessageEntity> findByAppKeyAndUserIdAndDeliveredFalse(String appKey, String userId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE ImOfflineMessageEntity o SET o.delivered = true WHERE o.id IN ?1")
|
||||
void markDeliveredByIds(List<String> ids);
|
||||
|
||||
@Query("SELECT COUNT(o) FROM ImOfflineMessageEntity o WHERE o.appId = ?1 AND o.userId = ?2 AND o.delivered = false")
|
||||
long countUndeliveredByAppIdAndUserId(String appId, String userId);
|
||||
@Query("SELECT COUNT(o) FROM ImOfflineMessageEntity o WHERE o.appKey = ?1 AND o.userId = ?2 AND o.delivered = false")
|
||||
long countUndeliveredByAppKeyAndUserId(String appKey, String userId);
|
||||
|
||||
void deleteByAppIdAndUserIdAndDeliveredTrue(String appId, String userId);
|
||||
void deleteByAppKeyAndUserIdAndDeliveredTrue(String appKey, String userId);
|
||||
}
|
||||
|
||||
@ -6,5 +6,5 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
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;
|
||||
|
||||
public interface KeywordFilterRepository extends JpaRepository<KeywordFilterEntity, String> {
|
||||
List<KeywordFilterEntity> findByAppIdAndEnabledTrue(String appId);
|
||||
List<KeywordFilterEntity> findByAppId(String appId);
|
||||
java.util.Optional<KeywordFilterEntity> findByIdAndAppId(String id, String appId);
|
||||
List<KeywordFilterEntity> findByAppKeyAndEnabledTrue(String appKey);
|
||||
List<KeywordFilterEntity> findByAppKey(String appKey);
|
||||
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> {
|
||||
|
||||
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")
|
||||
long countUnacknowledgedByAppId(String appId);
|
||||
@Query("SELECT COUNT(a) FROM WebhookAlertEntity a WHERE a.appKey = ?1 AND a.acknowledged = false")
|
||||
long countUnacknowledgedByAppKey(String appKey);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.List;
|
||||
|
||||
public interface WebhookConfigRepository extends JpaRepository<WebhookConfigEntity, String> {
|
||||
List<WebhookConfigEntity> findByAppIdAndEnabledTrue(String appId);
|
||||
List<WebhookConfigEntity> findByAppId(String appId);
|
||||
java.util.Optional<WebhookConfigEntity> findByIdAndAppId(String id, String appId);
|
||||
List<WebhookConfigEntity> findByAppKeyAndEnabledTrue(String appKey);
|
||||
List<WebhookConfigEntity> findByAppKey(String appKey);
|
||||
java.util.Optional<WebhookConfigEntity> findByIdAndAppId(String id, String appKey);
|
||||
}
|
||||
|
||||
@ -10,21 +10,21 @@ import java.util.List;
|
||||
|
||||
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);
|
||||
|
||||
@Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appId = ?1 AND d.success = true AND d.createdAt >= ?2")
|
||||
long countSuccessfulByAppIdSince(String appId, LocalDateTime since);
|
||||
@Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.success = true AND d.createdAt >= ?2")
|
||||
long countSuccessfulByAppKeySince(String appKey, LocalDateTime since);
|
||||
|
||||
@Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appId = ?1 AND d.success = false AND d.createdAt >= ?2")
|
||||
long countFailedByAppIdSince(String appId, LocalDateTime since);
|
||||
@Query("SELECT COUNT(d) FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.success = false AND d.createdAt >= ?2")
|
||||
long countFailedByAppKeySince(String appKey, LocalDateTime since);
|
||||
|
||||
@Query("SELECT d.callbackEvent, COUNT(d), SUM(CASE WHEN d.success = true THEN 1 ELSE 0 END) " +
|
||||
"FROM WebhookDeliveryEntity d WHERE d.appId = ?1 AND d.createdAt >= ?2 GROUP BY d.callbackEvent")
|
||||
List<Object[]> statsByAppIdSince(String appId, LocalDateTime since);
|
||||
"FROM WebhookDeliveryEntity d WHERE d.appKey = ?1 AND d.createdAt >= ?2 GROUP BY d.callbackEvent")
|
||||
List<Object[]> statsByAppKeySince(String appKey, LocalDateTime since);
|
||||
}
|
||||
|
||||
@ -23,11 +23,11 @@ public class BlacklistService {
|
||||
|
||||
@Transactional
|
||||
public ImBlacklistEntity add(String appKey, String userId, String blockedUserId) {
|
||||
return repository.findByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId)
|
||||
return repository.findByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId)
|
||||
.orElseGet(() -> {
|
||||
ImBlacklistEntity entity = new ImBlacklistEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setUserId(userId);
|
||||
entity.setBlockedUserId(blockedUserId);
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
@ -39,24 +39,24 @@ public class BlacklistService {
|
||||
|
||||
@Transactional
|
||||
public void remove(String appKey, String userId, String blockedUserId) {
|
||||
ImBlacklistEntity entity = repository.findByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId)
|
||||
ImBlacklistEntity entity = repository.findByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId)
|
||||
.orElse(null);
|
||||
repository.deleteByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId);
|
||||
repository.deleteByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId);
|
||||
if (entity != null) {
|
||||
dispatchWebhook(entity, "blacklist.removed");
|
||||
}
|
||||
}
|
||||
|
||||
public List<ImBlacklistEntity> list(String appKey, String userId) {
|
||||
return repository.findByAppIdAndUserId(appKey, userId);
|
||||
return repository.findByAppKeyAndUserId(appKey, userId);
|
||||
}
|
||||
|
||||
public List<ImBlacklistEntity> listByApp(String appKey) {
|
||||
return repository.findByAppId(appKey);
|
||||
return repository.findByAppKey(appKey);
|
||||
}
|
||||
|
||||
public boolean isBlocked(String appKey, String userId, String blockedUserId) {
|
||||
return repository.existsByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId);
|
||||
return repository.existsByAppKeyAndUserIdAndBlockedUserId(appKey, userId, blockedUserId);
|
||||
}
|
||||
|
||||
public boolean isEitherBlocked(String appKey, String userId, String targetUserId) {
|
||||
@ -65,11 +65,11 @@ public class BlacklistService {
|
||||
|
||||
private void dispatchWebhook(ImBlacklistEntity entity, String callbackEvent) {
|
||||
webhookDispatchService.dispatch(
|
||||
entity.getAppId(),
|
||||
entity.getAppKey(),
|
||||
"blacklist",
|
||||
callbackEvent,
|
||||
new BlacklistCallbackPayload(
|
||||
entity.getAppId(),
|
||||
entity.getAppKey(),
|
||||
entity.getId(),
|
||||
entity.getUserId(),
|
||||
entity.getBlockedUserId(),
|
||||
|
||||
@ -87,7 +87,7 @@ public class ConversationStateService {
|
||||
|
||||
@Transactional
|
||||
public void deleteConversationState(String appKey, String userId, String targetId, String chatType) {
|
||||
repository.deleteByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType);
|
||||
repository.deleteByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -107,7 +107,7 @@ public class ConversationStateService {
|
||||
public void clearHiddenForUsers(String appKey, String targetId, String chatType, Collection<String> userIds) {
|
||||
for (String userId : userIds) {
|
||||
ImConversationStateEntity state = repository
|
||||
.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
|
||||
.findByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
|
||||
.orElse(null);
|
||||
if (state != null && state.isHidden()) {
|
||||
state.setHidden(false);
|
||||
@ -118,11 +118,11 @@ public class ConversationStateService {
|
||||
}
|
||||
|
||||
public ImConversationStateEntity getOrCreate(String appKey, String userId, String targetId, String chatType) {
|
||||
return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
|
||||
return repository.findByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
|
||||
.orElseGet(() -> {
|
||||
ImConversationStateEntity entity = new ImConversationStateEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setUserId(userId);
|
||||
entity.setTargetId(targetId);
|
||||
entity.setChatType(chatType);
|
||||
@ -139,16 +139,16 @@ public class ConversationStateService {
|
||||
}
|
||||
|
||||
public ImConversationStateEntity find(String appKey, String userId, String targetId, String chatType) {
|
||||
return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
|
||||
return repository.findByAppKeyAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public List<ImConversationStateEntity> listVisible(String appKey, String userId) {
|
||||
return repository.findByAppIdAndUserIdAndHiddenFalse(appKey, userId);
|
||||
return repository.findByAppKeyAndUserIdAndHiddenFalse(appKey, userId);
|
||||
}
|
||||
|
||||
public List<String> listConversationGroups(String appKey, String userId) {
|
||||
return repository.findByAppIdAndUserId(appKey, userId).stream()
|
||||
return repository.findByAppKeyAndUserId(appKey, userId).stream()
|
||||
.map(ImConversationStateEntity::getConversationGroup)
|
||||
.filter(group -> group != null && !group.isBlank())
|
||||
.distinct()
|
||||
@ -157,7 +157,7 @@ public class ConversationStateService {
|
||||
}
|
||||
|
||||
public List<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) {
|
||||
|
||||
@ -51,12 +51,12 @@ public class FriendRequestService {
|
||||
throw new BusinessException(403, "当前应用未开放好友申请");
|
||||
}
|
||||
final boolean[] created = {false};
|
||||
ImFriendRequestEntity saved = requestRepository.findByAppIdAndFromUserIdAndToUserId(appKey, fromUserId, toUserId)
|
||||
ImFriendRequestEntity saved = requestRepository.findByAppKeyAndFromUserIdAndToUserId(appKey, fromUserId, toUserId)
|
||||
.orElseGet(() -> {
|
||||
created[0] = true;
|
||||
ImFriendRequestEntity entity = new ImFriendRequestEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setFromUserId(fromUserId);
|
||||
entity.setToUserId(toUserId);
|
||||
entity.setRemark(remark);
|
||||
@ -94,10 +94,10 @@ public class FriendRequestService {
|
||||
request.setReviewedAt(LocalDateTime.now());
|
||||
requestRepository.save(request);
|
||||
friendRepository
|
||||
.findByAppIdAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId())
|
||||
.findByAppKeyAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId())
|
||||
.orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId()));
|
||||
friendRepository
|
||||
.findByAppIdAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId())
|
||||
.findByAppKeyAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId())
|
||||
.orElseGet(() -> friendEntity(appKey, request.getToUserId(), request.getFromUserId()));
|
||||
dispatchWebhook(request, "friend.request.accepted");
|
||||
publishNotification(
|
||||
@ -148,17 +148,17 @@ public class FriendRequestService {
|
||||
}
|
||||
|
||||
public List<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()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<ImFriendRequestEntity> outgoing(String appKey, String userId) {
|
||||
return requestRepository.findByAppIdAndFromUserId(appKey, userId);
|
||||
return requestRepository.findByAppKeyAndFromUserId(appKey, userId);
|
||||
}
|
||||
|
||||
public List<ImFriendRequestEntity> listByApp(String appKey) {
|
||||
return requestRepository.findByAppId(appKey);
|
||||
return requestRepository.findByAppKey(appKey);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -176,7 +176,7 @@ public class FriendRequestService {
|
||||
private ImFriendRequestEntity getRequest(String appKey, String requestId, String operatorId) {
|
||||
ImFriendRequestEntity request = requestRepository.findById(requestId)
|
||||
.orElseThrow(() -> new BusinessException(404, "好友申请不存在"));
|
||||
if (!request.getAppId().equals(appKey) || !request.getToUserId().equals(operatorId)) {
|
||||
if (!request.getAppKey().equals(appKey) || !request.getToUserId().equals(operatorId)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
return request;
|
||||
@ -185,7 +185,7 @@ public class FriendRequestService {
|
||||
private ImFriendRequestEntity getRequest(String appKey, String requestId) {
|
||||
ImFriendRequestEntity request = requestRepository.findById(requestId)
|
||||
.orElseThrow(() -> new BusinessException(404, "好友申请不存在"));
|
||||
if (!request.getAppId().equals(appKey)) {
|
||||
if (!request.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
return request;
|
||||
@ -201,11 +201,11 @@ public class FriendRequestService {
|
||||
request.setReviewedAt(LocalDateTime.now());
|
||||
ImFriendRequestEntity saved = requestRepository.save(request);
|
||||
friendRepository
|
||||
.findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getFromUserId(), request.getToUserId())
|
||||
.orElseGet(() -> friendEntity(request.getAppId(), request.getFromUserId(), request.getToUserId()));
|
||||
.findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getFromUserId(), request.getToUserId())
|
||||
.orElseGet(() -> friendEntity(request.getAppKey(), request.getFromUserId(), request.getToUserId()));
|
||||
friendRepository
|
||||
.findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getToUserId(), request.getFromUserId())
|
||||
.orElseGet(() -> friendEntity(request.getAppId(), request.getToUserId(), request.getFromUserId()));
|
||||
.findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getToUserId(), request.getFromUserId())
|
||||
.orElseGet(() -> friendEntity(request.getAppKey(), request.getToUserId(), request.getFromUserId()));
|
||||
dispatchWebhook(saved, "friend.request.accepted");
|
||||
publishNotification(
|
||||
request,
|
||||
@ -225,7 +225,7 @@ public class FriendRequestService {
|
||||
|
||||
private com.xuqm.im.entity.ImFriendEntity friendEntity(String appKey, String userId, String friendId) {
|
||||
com.xuqm.im.entity.ImFriendEntity entity = new com.xuqm.im.entity.ImFriendEntity();
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setUserId(userId);
|
||||
entity.setFriendId(friendId);
|
||||
return friendRepository.save(entity);
|
||||
@ -245,7 +245,7 @@ public class FriendRequestService {
|
||||
) {
|
||||
ImMessageEntity message = new ImMessageEntity();
|
||||
message.setId(UUID.randomUUID().toString());
|
||||
message.setAppId(request.getAppId());
|
||||
message.setAppKey(request.getAppKey());
|
||||
message.setFromUserId(fromUserId);
|
||||
message.setToId(toUserId);
|
||||
message.setChatType(ImMessageEntity.ChatType.SINGLE);
|
||||
@ -302,11 +302,11 @@ public class FriendRequestService {
|
||||
|
||||
private void dispatchWebhook(ImFriendRequestEntity request, String callbackEvent) {
|
||||
webhookDispatchService.dispatch(
|
||||
request.getAppId(),
|
||||
request.getAppKey(),
|
||||
"friend_request",
|
||||
callbackEvent,
|
||||
new FriendRequestCallbackPayload(
|
||||
request.getAppId(),
|
||||
request.getAppKey(),
|
||||
request.getId(),
|
||||
request.getFromUserId(),
|
||||
request.getToUserId(),
|
||||
|
||||
@ -17,14 +17,14 @@ public class GlobalMuteService {
|
||||
}
|
||||
|
||||
public boolean isEnabled(String appKey) {
|
||||
return repository.findByAppId(appKey).map(ImGlobalMuteEntity::isEnabled).orElse(false);
|
||||
return repository.findByAppKey(appKey).map(ImGlobalMuteEntity::isEnabled).orElse(false);
|
||||
}
|
||||
|
||||
public ImGlobalMuteEntity get(String appKey) {
|
||||
return repository.findByAppId(appKey).orElseGet(() -> {
|
||||
return repository.findByAppKey(appKey).orElseGet(() -> {
|
||||
ImGlobalMuteEntity entity = new ImGlobalMuteEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setEnabled(false);
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
@ -33,10 +33,10 @@ public class GlobalMuteService {
|
||||
}
|
||||
|
||||
public ImGlobalMuteEntity setEnabled(String appKey, boolean enabled) {
|
||||
ImGlobalMuteEntity entity = repository.findByAppId(appKey).orElseGet(() -> {
|
||||
ImGlobalMuteEntity entity = repository.findByAppKey(appKey).orElseGet(() -> {
|
||||
ImGlobalMuteEntity created = new ImGlobalMuteEntity();
|
||||
created.setId(UUID.randomUUID().toString());
|
||||
created.setAppId(appKey);
|
||||
created.setAppKey(appKey);
|
||||
created.setCreatedAt(LocalDateTime.now());
|
||||
return created;
|
||||
});
|
||||
|
||||
@ -47,11 +47,11 @@ public class ImAccountService {
|
||||
}
|
||||
|
||||
public LoginResult loginOrRegister(String appKey, String userId) {
|
||||
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId)
|
||||
ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseGet(() -> {
|
||||
ImAccountEntity e = new ImAccountEntity();
|
||||
e.setId(UUID.randomUUID().toString());
|
||||
e.setAppId(appKey);
|
||||
e.setAppKey(appKey);
|
||||
e.setUserId(userId);
|
||||
e.setGender(ImAccountEntity.Gender.UNKNOWN);
|
||||
e.setStatus(ImAccountEntity.Status.ACTIVE);
|
||||
@ -67,7 +67,7 @@ public class ImAccountService {
|
||||
}
|
||||
|
||||
public ImAccountEntity getAccount(String appKey, String userId) {
|
||||
return accountRepository.findByAppIdAndUserId(appKey, userId)
|
||||
return accountRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseThrow(() -> new BusinessException(404, "账号不存在"));
|
||||
}
|
||||
|
||||
@ -98,11 +98,11 @@ public class ImAccountService {
|
||||
public ImAccountEntity importAccount(String appKey, String userId, String nickname,
|
||||
String avatar, ImAccountEntity.Gender gender,
|
||||
ImAccountEntity.Status status) {
|
||||
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId)
|
||||
ImAccountEntity account = accountRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.orElseGet(() -> {
|
||||
ImAccountEntity entity = new ImAccountEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setUserId(userId);
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
return entity;
|
||||
@ -122,12 +122,12 @@ public class ImAccountService {
|
||||
}
|
||||
|
||||
public void deleteAccount(String appKey, String userId) {
|
||||
accountRepository.findByAppIdAndUserId(appKey, userId)
|
||||
accountRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
.ifPresent(accountRepository::delete);
|
||||
}
|
||||
|
||||
public boolean exists(String appKey, String userId) {
|
||||
return accountRepository.existsByAppIdAndUserId(appKey, userId);
|
||||
return accountRepository.existsByAppKeyAndUserId(appKey, userId);
|
||||
}
|
||||
|
||||
public List<ImAccountEntity> searchAccounts(String appKey, String keyword, int size) {
|
||||
|
||||
@ -75,7 +75,7 @@ public class ImGroupService {
|
||||
|
||||
ImGroupEntity group = new ImGroupEntity();
|
||||
group.setId(UUID.randomUUID().toString());
|
||||
group.setAppId(appKey);
|
||||
group.setAppKey(appKey);
|
||||
group.setName(name);
|
||||
group.setGroupType(normalizeGroupType(groupType));
|
||||
group.setCreatorId(creatorId);
|
||||
@ -135,7 +135,7 @@ public class ImGroupService {
|
||||
if (changed) {
|
||||
group.setMemberIds(toJson(members));
|
||||
ImGroupEntity saved = groupRepository.save(group);
|
||||
webhookDispatchService.dispatch(saved.getAppId(), "group", "group.member_added",
|
||||
webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.member_added",
|
||||
java.util.Map.of("groupId", saved.getId(), "addedUserIds", userIds, "operatorId", operatorId));
|
||||
return saved;
|
||||
}
|
||||
@ -153,7 +153,7 @@ public class ImGroupService {
|
||||
members.remove(userId);
|
||||
group.setMemberIds(toJson(members));
|
||||
ImGroupEntity saved = groupRepository.save(group);
|
||||
webhookDispatchService.dispatch(saved.getAppId(), "group", "group.member_removed",
|
||||
webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.member_removed",
|
||||
java.util.Map.of("groupId", saved.getId(), "removedUserId", userId, "operatorId", operatorId));
|
||||
return saved;
|
||||
}
|
||||
@ -176,7 +176,7 @@ public class ImGroupService {
|
||||
if (changed) {
|
||||
group.setMemberIds(toJson(members));
|
||||
ImGroupEntity saved = groupRepository.save(group);
|
||||
webhookDispatchService.dispatch(saved.getAppId(), "group", "group.member_removed",
|
||||
webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.member_removed",
|
||||
java.util.Map.of("groupId", saved.getId(), "removedUserIds", userIds, "operatorId", operatorId));
|
||||
return saved;
|
||||
}
|
||||
@ -197,7 +197,7 @@ public class ImGroupService {
|
||||
group.setAnnouncement(announcement);
|
||||
}
|
||||
ImGroupEntity saved = groupRepository.save(group);
|
||||
webhookDispatchService.dispatch(saved.getAppId(), "group", "group.updated",
|
||||
webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.updated",
|
||||
java.util.Map.of("groupId", saved.getId(), "name", saved.getName(), "operatorId", operatorId));
|
||||
return saved;
|
||||
}
|
||||
@ -268,7 +268,7 @@ public class ImGroupService {
|
||||
}
|
||||
|
||||
public List<ImGroupEntity> listByApp(String appKey) {
|
||||
return groupRepository.findByAppId(appKey);
|
||||
return groupRepository.findByAppKey(appKey);
|
||||
}
|
||||
|
||||
public List<ImGroupEntity> listUserGroups(String appKey, String userId) {
|
||||
@ -277,7 +277,7 @@ public class ImGroupService {
|
||||
|
||||
public List<ImGroupEntity> listPublicGroups(String appKey, String keyword) {
|
||||
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase();
|
||||
return groupRepository.findByAppId(appKey).stream()
|
||||
return groupRepository.findByAppKey(appKey).stream()
|
||||
.filter(group -> "PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType())))
|
||||
.filter(group -> normalizedKeyword.isBlank()
|
||||
|| group.getName().toLowerCase().contains(normalizedKeyword)
|
||||
@ -313,7 +313,7 @@ public class ImGroupService {
|
||||
throw new BusinessException(403, "当前应用未开放群加入申请");
|
||||
}
|
||||
ImGroupEntity group = get(groupId);
|
||||
if (!group.getAppId().equals(appKey)) {
|
||||
if (!group.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
if (!"PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType()))) {
|
||||
@ -322,11 +322,11 @@ public class ImGroupService {
|
||||
if (memberIds(group).contains(requesterId)) {
|
||||
throw new BusinessException(400, "已经在群内");
|
||||
}
|
||||
return joinRequestRepository.findByAppIdAndGroupIdAndRequesterId(appKey, groupId, requesterId)
|
||||
return joinRequestRepository.findByAppKeyAndGroupIdAndRequesterId(appKey, groupId, requesterId)
|
||||
.orElseGet(() -> {
|
||||
ImGroupJoinRequestEntity entity = new ImGroupJoinRequestEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setGroupId(groupId);
|
||||
entity.setRequesterId(requesterId);
|
||||
entity.setRemark(remark);
|
||||
@ -350,7 +350,7 @@ public class ImGroupService {
|
||||
public List<ImGroupJoinRequestEntity> listJoinRequests(String appKey, String groupId, String operatorId) {
|
||||
ImGroupEntity group = get(groupId);
|
||||
ensureCanManage(group, operatorId);
|
||||
return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId);
|
||||
return joinRequestRepository.findByAppKeyAndGroupId(appKey, groupId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -442,7 +442,7 @@ public class ImGroupService {
|
||||
}
|
||||
|
||||
private void ensureAppMatches(ImGroupEntity group, String appKey) {
|
||||
if (!group.getAppId().equals(appKey)) {
|
||||
if (!group.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
}
|
||||
@ -614,7 +614,7 @@ public class ImGroupService {
|
||||
public List<ImGroupJoinRequestEntity> adminListJoinRequests(String appKey, String groupId) {
|
||||
ImGroupEntity group = get(groupId);
|
||||
ensureAppMatches(group, appKey);
|
||||
return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId);
|
||||
return joinRequestRepository.findByAppKeyAndGroupId(appKey, groupId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -667,7 +667,7 @@ public class ImGroupService {
|
||||
group.setCreatorId(newOwnerId);
|
||||
group.setAdminIds(toJson(admins));
|
||||
ImGroupEntity saved = groupRepository.save(group);
|
||||
webhookDispatchService.dispatch(saved.getAppId(), "group", "group.owner_transferred",
|
||||
webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.owner_transferred",
|
||||
java.util.Map.of("groupId", saved.getId(), "newOwnerId", newOwnerId));
|
||||
return saved;
|
||||
}
|
||||
@ -683,7 +683,7 @@ public class ImGroupService {
|
||||
}
|
||||
group.setExtAttributes(toJson(current));
|
||||
ImGroupEntity saved = groupRepository.save(group);
|
||||
webhookDispatchService.dispatch(saved.getAppId(), "group", "group.attributes_updated",
|
||||
webhookDispatchService.dispatch(saved.getAppKey(), "group", "group.attributes_updated",
|
||||
java.util.Map.of("groupId", saved.getId(), "attributes", current));
|
||||
return saved;
|
||||
}
|
||||
@ -760,7 +760,7 @@ public class ImGroupService {
|
||||
if (recipient == null || recipient.isBlank() || recipient.equals(fromUserId)) continue;
|
||||
ImMessageEntity message = new ImMessageEntity();
|
||||
message.setId(UUID.randomUUID().toString());
|
||||
message.setAppId(group.getAppId());
|
||||
message.setAppKey(group.getAppKey());
|
||||
message.setFromUserId(fromUserId);
|
||||
message.setToId(recipient);
|
||||
message.setChatType(ImMessageEntity.ChatType.SINGLE);
|
||||
@ -775,11 +775,11 @@ public class ImGroupService {
|
||||
|
||||
private void dispatchJoinRequestWebhook(ImGroupEntity group, ImGroupJoinRequestEntity request, String callbackEvent) {
|
||||
webhookDispatchService.dispatch(
|
||||
group.getAppId(),
|
||||
group.getAppKey(),
|
||||
"group_join_request",
|
||||
callbackEvent,
|
||||
new GroupJoinRequestCallbackPayload(
|
||||
group.getAppId(),
|
||||
group.getAppKey(),
|
||||
request.getId(),
|
||||
request.getGroupId(),
|
||||
group.getName(),
|
||||
@ -832,7 +832,7 @@ public class ImGroupService {
|
||||
private ImGroupJoinRequestEntity getJoinRequest(String appKey, String requestId) {
|
||||
ImGroupJoinRequestEntity request = joinRequestRepository.findById(requestId)
|
||||
.orElseThrow(() -> new BusinessException(404, "加群申请不存在"));
|
||||
if (!request.getAppId().equals(appKey)) {
|
||||
if (!request.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
return request;
|
||||
@ -894,7 +894,7 @@ public class ImGroupService {
|
||||
private List<ImAccountEntity> resolveMembers(String appKey, List<String> ids) {
|
||||
List<ImAccountEntity> members = new ArrayList<>();
|
||||
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;
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ public class KeywordFilterService {
|
||||
}
|
||||
|
||||
public String filter(String appKey, String content) {
|
||||
List<KeywordFilterEntity> filters = repository.findByAppIdAndEnabledTrue(appKey);
|
||||
List<KeywordFilterEntity> filters = repository.findByAppKeyAndEnabledTrue(appKey);
|
||||
String result = content;
|
||||
for (KeywordFilterEntity f : filters) {
|
||||
try {
|
||||
@ -42,7 +42,7 @@ public class KeywordFilterService {
|
||||
public KeywordFilterEntity add(String appKey, String pattern, String replacement, String action) {
|
||||
KeywordFilterEntity entity = new KeywordFilterEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setPattern(pattern);
|
||||
entity.setReplacement(replacement);
|
||||
entity.setAction(action);
|
||||
@ -52,7 +52,7 @@ public class KeywordFilterService {
|
||||
}
|
||||
|
||||
public List<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) {
|
||||
|
||||
@ -116,7 +116,7 @@ public class MessageService {
|
||||
message.setId(req.messageId() != null && !req.messageId().isBlank()
|
||||
? req.messageId()
|
||||
: UUID.randomUUID().toString());
|
||||
message.setAppId(appKey);
|
||||
message.setAppKey(appKey);
|
||||
message.setFromUserId(fromUserId);
|
||||
message.setToId(req.toId());
|
||||
message.setChatType(req.chatType());
|
||||
@ -184,14 +184,14 @@ public class MessageService {
|
||||
}
|
||||
|
||||
private boolean isFriend(String appKey, String userId, String friendId) {
|
||||
return friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
return friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, userId, friendId)
|
||||
|| friendRepository.existsByAppKeyAndUserIdAndFriendId(appKey, friendId, userId);
|
||||
}
|
||||
|
||||
public ImMessageEntity revoke(String appKey, String messageId, String requestUserId) {
|
||||
ImMessageEntity message = messageRepository.findById(messageId)
|
||||
.orElseThrow(() -> new BusinessException(404, "消息不存在"));
|
||||
if (!message.getAppId().equals(appKey)) {
|
||||
if (!message.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
if (!message.getFromUserId().equals(requestUserId)) {
|
||||
@ -223,7 +223,7 @@ public class MessageService {
|
||||
public ImMessageEntity edit(String appKey, String messageId, String requestUserId, EditMessageRequest req) {
|
||||
ImMessageEntity message = messageRepository.findById(messageId)
|
||||
.orElseThrow(() -> new BusinessException(404, "消息不存在"));
|
||||
if (!message.getAppId().equals(appKey)) {
|
||||
if (!message.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
if (!message.getFromUserId().equals(requestUserId)) {
|
||||
@ -278,7 +278,7 @@ public class MessageService {
|
||||
public ImMessageEntity adminRevoke(String appKey, String messageId) {
|
||||
ImMessageEntity message = messageRepository.findById(messageId)
|
||||
.orElseThrow(() -> new BusinessException(404, "消息不存在"));
|
||||
if (!message.getAppId().equals(appKey)) {
|
||||
if (!message.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
message.setStatus(ImMessageEntity.MsgStatus.REVOKED);
|
||||
@ -341,7 +341,7 @@ public class MessageService {
|
||||
return;
|
||||
}
|
||||
List<ImMessageEntity> messages = messageRepository
|
||||
.findByAppIdAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
.findByAppKeyAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
appKey, peerId, readerId, readAt);
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
@ -374,7 +374,7 @@ public class MessageService {
|
||||
return;
|
||||
}
|
||||
List<ImMessageEntity> messages = messageRepository
|
||||
.findByAppIdAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
.findByAppKeyAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
|
||||
appKey, groupId, ImMessageEntity.ChatType.GROUP, readAt);
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
@ -491,12 +491,12 @@ public class MessageService {
|
||||
|
||||
public List<GroupReadReceiptSummary> groupReadReceipts(String appKey, String groupId, List<String> messageIds) {
|
||||
ImGroupEntity group = groupService.get(groupId);
|
||||
if (!group.getAppId().equals(appKey)) {
|
||||
if (!group.getAppKey().equals(appKey)) {
|
||||
throw new BusinessException(403, "无权操作");
|
||||
}
|
||||
List<String> members = groupService.memberIds(group);
|
||||
return messageRepository.findAllById(messageIds == null ? List.of() : messageIds).stream()
|
||||
.filter(message -> appKey.equals(message.getAppId()))
|
||||
.filter(message -> appKey.equals(message.getAppKey()))
|
||||
.filter(message -> groupId.equals(message.getToId()))
|
||||
.filter(message -> message.getChatType() == ImMessageEntity.ChatType.GROUP)
|
||||
.map(message -> {
|
||||
@ -520,7 +520,7 @@ public class MessageService {
|
||||
try {
|
||||
Map<String, Object> payload = new java.util.LinkedHashMap<>();
|
||||
payload.put("messageId", message.getId());
|
||||
payload.put("appKey", message.getAppId());
|
||||
payload.put("appKey", message.getAppKey());
|
||||
payload.put("fromUserId", message.getFromUserId());
|
||||
payload.put("toId", message.getToId());
|
||||
payload.put("chatType", message.getChatType().name());
|
||||
@ -598,7 +598,7 @@ public class MessageService {
|
||||
public ImMessageEntity adminSend(String appKey, String fromUserId, String toId, ImMessageEntity.MsgType msgType, String content) {
|
||||
ImMessageEntity message = new ImMessageEntity();
|
||||
message.setId(UUID.randomUUID().toString());
|
||||
message.setAppId(appKey);
|
||||
message.setAppKey(appKey);
|
||||
message.setFromUserId(fromUserId);
|
||||
message.setToId(toId);
|
||||
message.setChatType(ImMessageEntity.ChatType.SINGLE);
|
||||
@ -624,7 +624,7 @@ public class MessageService {
|
||||
}
|
||||
|
||||
public void adminSetMsgRead(String appKey, String userId) {
|
||||
List<ImMessageEntity> messages = messageRepository.findUnreadByAppIdAndToId(appKey, userId);
|
||||
List<ImMessageEntity> messages = messageRepository.findUnreadByAppKeyAndToId(appKey, userId);
|
||||
for (ImMessageEntity message : messages) {
|
||||
message.setStatus(ImMessageEntity.MsgStatus.READ);
|
||||
messageRepository.save(message);
|
||||
@ -642,7 +642,7 @@ public class MessageService {
|
||||
message.setId(req.messageId() != null && !req.messageId().isBlank()
|
||||
? req.messageId()
|
||||
: UUID.randomUUID().toString());
|
||||
message.setAppId(appKey);
|
||||
message.setAppKey(appKey);
|
||||
message.setFromUserId(req.fromUserId());
|
||||
message.setToId(req.toId());
|
||||
message.setChatType(req.chatType() != null ? req.chatType() : ImMessageEntity.ChatType.SINGLE);
|
||||
|
||||
@ -36,7 +36,7 @@ public class OfflineMessageSyncService {
|
||||
public void storeOfflineMessage(String appKey, String userId, String messageId) {
|
||||
ImOfflineMessageEntity entity = new ImOfflineMessageEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setUserId(userId);
|
||||
entity.setMessageId(messageId);
|
||||
entity.setDelivered(false);
|
||||
@ -48,7 +48,7 @@ public class OfflineMessageSyncService {
|
||||
@Transactional
|
||||
public void syncAndDeliver(String appKey, String userId) {
|
||||
List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository
|
||||
.findByAppIdAndUserIdAndDeliveredFalse(appKey, userId);
|
||||
.findByAppKeyAndUserIdAndDeliveredFalse(appKey, userId);
|
||||
if (offlineMessages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -68,17 +68,17 @@ public class OfflineMessageSyncService {
|
||||
log.info("Synced {} offline messages for appKey={} userId={}", deliveredIds.size(), appKey, userId);
|
||||
}
|
||||
|
||||
offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appKey, userId);
|
||||
offlineMessageRepository.deleteByAppKeyAndUserIdAndDeliveredTrue(appKey, userId);
|
||||
}
|
||||
|
||||
public long countUndelivered(String appKey, String userId) {
|
||||
return offlineMessageRepository.countUndeliveredByAppIdAndUserId(appKey, userId);
|
||||
return offlineMessageRepository.countUndeliveredByAppKeyAndUserId(appKey, userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<ImMessageEntity> syncAndReturn(String appKey, String userId) {
|
||||
List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository
|
||||
.findByAppIdAndUserIdAndDeliveredFalse(appKey, userId);
|
||||
.findByAppKeyAndUserIdAndDeliveredFalse(appKey, userId);
|
||||
List<ImMessageEntity> result = new ArrayList<>();
|
||||
List<String> deliveredIds = new ArrayList<>();
|
||||
for (ImOfflineMessageEntity offline : offlineMessages) {
|
||||
@ -91,7 +91,7 @@ public class OfflineMessageSyncService {
|
||||
if (!deliveredIds.isEmpty()) {
|
||||
offlineMessageRepository.markDeliveredByIds(deliveredIds);
|
||||
}
|
||||
offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appKey, userId);
|
||||
offlineMessageRepository.deleteByAppKeyAndUserIdAndDeliveredTrue(appKey, userId);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ public class OperationLogService {
|
||||
String detail) {
|
||||
ImOperationLogEntity entity = new ImOperationLogEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setOperatorId(operatorId == null || operatorId.isBlank() ? "system" : operatorId);
|
||||
entity.setAction(action);
|
||||
entity.setResourceType(resourceType);
|
||||
@ -38,6 +38,6 @@ public class OperationLogService {
|
||||
}
|
||||
|
||||
public Page<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) {
|
||||
return repository.findByAppId(appKey);
|
||||
return repository.findByAppKey(appKey);
|
||||
}
|
||||
|
||||
public WebhookConfigEntity get(String appKey, String id) {
|
||||
@ -30,7 +30,7 @@ public class WebhookConfigService {
|
||||
public WebhookConfigEntity create(String appKey, String url, String secret, Boolean enabled) {
|
||||
WebhookConfigEntity entity = new WebhookConfigEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setUrl(url);
|
||||
entity.setSecret(secret);
|
||||
entity.setEnabled(enabled == null || enabled);
|
||||
|
||||
@ -61,7 +61,7 @@ public class WebhookDispatchService {
|
||||
|
||||
@Async
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
@ -100,7 +100,7 @@ public class WebhookDispatchService {
|
||||
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||
WebhookDeliveryEntity delivery = new WebhookDeliveryEntity();
|
||||
delivery.setId(UUID.randomUUID().toString());
|
||||
delivery.setAppId(appKey);
|
||||
delivery.setAppKey(appKey);
|
||||
delivery.setCallbackId(callbackId);
|
||||
delivery.setCallbackEvent(callbackEvent);
|
||||
delivery.setUrl(webhook.getUrl());
|
||||
@ -112,7 +112,7 @@ public class WebhookDispatchService {
|
||||
.uri(URI.create(webhook.getUrl()))
|
||||
.timeout(Duration.ofMillis(webhookTimeoutMs))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-App-Id", appKey)
|
||||
.header("X-App-Key", appKey)
|
||||
.header("X-App-Timestamp", String.valueOf(requestTime))
|
||||
.header("X-App-Nonce", nonce)
|
||||
.header("X-App-Signature", signature)
|
||||
@ -182,7 +182,7 @@ public class WebhookDispatchService {
|
||||
|
||||
WebhookAlertEntity alert = new WebhookAlertEntity();
|
||||
alert.setId(UUID.randomUUID().toString());
|
||||
alert.setAppId(appKey);
|
||||
alert.setAppKey(appKey);
|
||||
alert.setWebhookId(webhook.getId());
|
||||
alert.setWebhookUrl(webhook.getUrl());
|
||||
alert.setAlertType("AUTO_DISABLED");
|
||||
|
||||
1
pom.xml
1
pom.xml
@ -15,7 +15,6 @@
|
||||
<module>common</module>
|
||||
<module>tenant-service</module>
|
||||
<module>im-service</module>
|
||||
<module>im-sdk</module>
|
||||
<module>push-service</module>
|
||||
<module>update-service</module>
|
||||
<module>demo-service</module>
|
||||
|
||||
@ -11,7 +11,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "push_device_login_log", indexes = {
|
||||
@Index(name = "idx_push_device_log_user_time", columnList = "appId,userId,createdAt"),
|
||||
@Index(name = "idx_push_device_log_user_time", columnList = "appKey,userId,createdAt"),
|
||||
@Index(name = "idx_push_device_log_token", columnList = "tokenHash")
|
||||
})
|
||||
public class DeviceLoginLogEntity {
|
||||
@ -24,7 +24,7 @@ public class DeviceLoginLogEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String userId;
|
||||
@ -70,8 +70,8 @@ public class DeviceLoginLogEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -11,18 +11,18 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "push_device_token",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appId", "userId", "deviceId"}))
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"appKey", "userId", "deviceId"}))
|
||||
public class DeviceTokenEntity {
|
||||
|
||||
public enum Vendor {
|
||||
HUAWEI, XIAOMI, OPPO, VIVO, HONOR, HARMONY, FCM, APNS
|
||||
HUAWEI, XIAOMI, OPPO, VIVO, HONOR, HARMONY, APNS
|
||||
}
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String userId;
|
||||
@ -67,8 +67,8 @@ public class DeviceTokenEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
|
||||
@ -6,5 +6,5 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
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;
|
||||
|
||||
public interface DeviceTokenRepository extends JpaRepository<DeviceTokenEntity, String> {
|
||||
List<DeviceTokenEntity> findByAppIdAndUserIdAndReceivePushTrue(String appId, String userId);
|
||||
Optional<DeviceTokenEntity> findFirstByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(
|
||||
String appId, String userId);
|
||||
List<DeviceTokenEntity> findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(String appId, String userId);
|
||||
Optional<DeviceTokenEntity> findByAppIdAndUserIdAndVendor(
|
||||
String appId, String userId, DeviceTokenEntity.Vendor vendor);
|
||||
Optional<DeviceTokenEntity> findByAppIdAndUserIdAndDeviceId(String appId, String userId, String deviceId);
|
||||
List<DeviceTokenEntity> findByAppKeyAndUserIdAndReceivePushTrue(String appKey, String userId);
|
||||
Optional<DeviceTokenEntity> findFirstByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(
|
||||
String appKey, String userId);
|
||||
List<DeviceTokenEntity> findByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(String appKey, String userId);
|
||||
Optional<DeviceTokenEntity> findByAppKeyAndUserIdAndVendor(
|
||||
String appKey, String userId, DeviceTokenEntity.Vendor vendor);
|
||||
Optional<DeviceTokenEntity> findByAppKeyAndUserIdAndDeviceId(String appKey, String userId, String deviceId);
|
||||
Optional<DeviceTokenEntity> findFirstByToken(String token);
|
||||
List<DeviceTokenEntity> findByAppIdAndUserId(String appId, String userId);
|
||||
List<DeviceTokenEntity> findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(String appId, String userId);
|
||||
void deleteByAppIdAndUserIdAndVendor(String appId, String userId, DeviceTokenEntity.Vendor vendor);
|
||||
void deleteByAppIdAndUserIdAndDeviceId(String appId, String userId, String deviceId);
|
||||
List<DeviceTokenEntity> findByAppKeyAndUserId(String appKey, String userId);
|
||||
List<DeviceTokenEntity> findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(String appKey, String userId);
|
||||
void deleteByAppKeyAndUserIdAndVendor(String appKey, String userId, DeviceTokenEntity.Vendor vendor);
|
||||
void deleteByAppKeyAndUserIdAndDeviceId(String appKey, String userId, String deviceId);
|
||||
}
|
||||
|
||||
@ -35,15 +35,15 @@ public class PushDiagnosticsService {
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
public PushTokenDiagnostics searchByToken(String token, String appIdHint) {
|
||||
public PushTokenDiagnostics searchByToken(String token, String appKeyHint) {
|
||||
Optional<DeviceTokenEntity> tokenMatch = tokenRepository.findFirstByToken(token);
|
||||
String appKey = appIdHint;
|
||||
String appKey = appKeyHint;
|
||||
String userId = null;
|
||||
String tokenType = "UNKNOWN";
|
||||
|
||||
if (tokenMatch.isPresent()) {
|
||||
DeviceTokenEntity device = tokenMatch.get();
|
||||
appKey = device.getAppId();
|
||||
appKey = device.getAppKey();
|
||||
userId = device.getUserId();
|
||||
tokenType = "PUSH";
|
||||
} else {
|
||||
@ -56,8 +56,8 @@ public class PushDiagnosticsService {
|
||||
try {
|
||||
Claims claims = jwtUtil.parse(token);
|
||||
userId = claims.getSubject();
|
||||
String claimAppId = claims.get("appKey", String.class);
|
||||
appKey = claimAppId == null || claimAppId.isBlank() ? appIdHint : claimAppId;
|
||||
String claimAppKey = claims.get("appKey", String.class);
|
||||
appKey = claimAppKey == null || claimAppKey.isBlank() ? appKeyHint : claimAppKey;
|
||||
tokenType = "IM";
|
||||
} catch (Exception ignored) {
|
||||
// Keep UNKNOWN and return an empty diagnostic.
|
||||
@ -71,7 +71,7 @@ public class PushDiagnosticsService {
|
||||
|
||||
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId)
|
||||
.orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L));
|
||||
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
||||
List<DeviceTokenEntity> devices = tokenRepository.findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
||||
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
|
||||
.stream()
|
||||
.map(DeviceInfo::from)
|
||||
@ -93,7 +93,7 @@ public class PushDiagnosticsService {
|
||||
public PushTokenDiagnostics searchByUserId(String appKey, String userId) {
|
||||
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId)
|
||||
.orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L));
|
||||
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
||||
List<DeviceTokenEntity> devices = tokenRepository.findByAppKeyAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
||||
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
|
||||
.stream()
|
||||
.map(DeviceInfo::from)
|
||||
@ -126,7 +126,7 @@ public class PushDiagnosticsService {
|
||||
public Page<DeviceLoginLogEntity> deviceLogs(String appKey, String userId, int page, int size) {
|
||||
int safePage = Math.max(page, 0);
|
||||
int safeSize = Math.min(Math.max(size, 1), 200);
|
||||
return logRepository.findByAppIdAndUserIdOrderByCreatedAtDesc(appKey, userId, PageRequest.of(safePage, safeSize));
|
||||
return logRepository.findByAppKeyAndUserIdOrderByCreatedAtDesc(appKey, userId, PageRequest.of(safePage, safeSize));
|
||||
}
|
||||
|
||||
public record PushTokenDiagnostics(
|
||||
|
||||
@ -68,17 +68,22 @@ public class PushDispatcher {
|
||||
String routeType = routeType(payload);
|
||||
String platform = platformForVendor(vendor, payload);
|
||||
return pushConfigClient.loadServiceConfig(appKey, platform, "PUSH")
|
||||
.map(config -> {
|
||||
JsonNode route = config.path("routing").path(routeType);
|
||||
String channelKey = route.path("channel").asText("");
|
||||
String channelId = effectiveChannelId(config.path("channels"), channelKey);
|
||||
return new PushSendOptions(
|
||||
routeType,
|
||||
channelId,
|
||||
route.path("category").asText(""),
|
||||
route.path("priority").asText(""));
|
||||
})
|
||||
.orElseGet(() -> new PushSendOptions(routeType, "", "", ""));
|
||||
.map(config -> profileFor(config, vendor.name(), routeType)
|
||||
.map(profile -> new PushSendOptions(
|
||||
profile.path("key").asText(""),
|
||||
routeType,
|
||||
profile.path("channelId").asText(""),
|
||||
profile.path("category").asText(""),
|
||||
profile.path("threadIdentifier").asText(""),
|
||||
profile.path("interruptionLevel").asText(""),
|
||||
profile.path("importance").asText(""),
|
||||
readBoolean(profile, "badge"),
|
||||
readBoolean(profile, "sound"),
|
||||
readBoolean(profile, "vibration"),
|
||||
readInteger(profile, "notifyType"),
|
||||
mapPriority(profile.path("importance").asText(""))))
|
||||
.orElseGet(() -> new PushSendOptions("", routeType, "", "", "", "", "", null, null, null, null, "")))
|
||||
.orElseGet(() -> new PushSendOptions("", routeType, "", "", "", "", "", null, null, null, null, ""));
|
||||
}
|
||||
|
||||
private String platformForVendor(DeviceTokenEntity.Vendor vendor, String payload) {
|
||||
@ -121,18 +126,73 @@ public class PushDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private String effectiveChannelId(JsonNode channels, String channelKey) {
|
||||
if (channels == null || !channels.isArray() || channelKey == null || channelKey.isBlank()) {
|
||||
return "";
|
||||
private java.util.Optional<JsonNode> profileFor(JsonNode config, String vendor, String routeType) {
|
||||
JsonNode profiles = config.path("profiles");
|
||||
if (profiles == null || !profiles.isArray()) {
|
||||
return java.util.Optional.empty();
|
||||
}
|
||||
for (JsonNode channel : channels) {
|
||||
if (channelKey.equals(channel.path("key").asText(""))) {
|
||||
String base = channel.path("channelId").asText(channelKey);
|
||||
int version = Math.max(channel.path("version").asInt(1), 1);
|
||||
return base + "_v" + version;
|
||||
JsonNode fallback = null;
|
||||
for (JsonNode profile : profiles) {
|
||||
if (!profile.path("enabled").asBoolean(true)) {
|
||||
continue;
|
||||
}
|
||||
if (!vendor.equalsIgnoreCase(profile.path("vendor").asText(""))) {
|
||||
continue;
|
||||
}
|
||||
String profileRouteType = profile.path("routeType").asText("");
|
||||
if (profileRouteType.isBlank() || "DEFAULT".equalsIgnoreCase(profileRouteType)) {
|
||||
if (fallback == null) {
|
||||
fallback = profile;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!routeType.equalsIgnoreCase(profileRouteType)) {
|
||||
continue;
|
||||
}
|
||||
return java.util.Optional.of(profile);
|
||||
}
|
||||
return "";
|
||||
return java.util.Optional.ofNullable(fallback);
|
||||
}
|
||||
|
||||
private Boolean readBoolean(JsonNode node, String key) {
|
||||
if (node == null || node.isNull()) {
|
||||
return null;
|
||||
}
|
||||
JsonNode value = node.get(key);
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
return value.asBoolean();
|
||||
}
|
||||
|
||||
private Integer readInteger(JsonNode node, String key) {
|
||||
if (node == null || node.isNull()) {
|
||||
return null;
|
||||
}
|
||||
JsonNode value = node.get(key);
|
||||
if (value == null || value.isNull()) {
|
||||
return null;
|
||||
}
|
||||
if (value.isInt() || value.isLong() || value.isNumber()) {
|
||||
return value.asInt();
|
||||
}
|
||||
String text = value.asText("");
|
||||
if (text.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(text.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String mapPriority(String importance) {
|
||||
return switch (importance == null ? "" : importance.trim().toUpperCase()) {
|
||||
case "HIGH", "MAX" -> "HIGH";
|
||||
case "LOW", "MIN" -> "LOW";
|
||||
default -> "DEFAULT";
|
||||
};
|
||||
}
|
||||
|
||||
public List<DeviceTokenEntity> selectedPushTargets(String appKey, String userId) {
|
||||
@ -150,7 +210,7 @@ public class PushDispatcher {
|
||||
|
||||
private List<DeviceTokenEntity> selectTargets(String appKey, String userId) {
|
||||
List<DeviceTokenEntity> devices = tokenRepository
|
||||
.findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
||||
.findByAppKeyAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
|
||||
if (devices.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
@ -185,15 +245,15 @@ public class PushDispatcher {
|
||||
String osVersion,
|
||||
String appVersion) {
|
||||
String resolvedDeviceId = normalizeDeviceId(deviceId, vendor, token);
|
||||
Optional<DeviceTokenEntity> existing = tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId);
|
||||
Optional<DeviceTokenEntity> existing = tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId);
|
||||
if (existing.isEmpty() && (deviceId == null || deviceId.isBlank())) {
|
||||
existing = tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor);
|
||||
existing = tokenRepository.findByAppKeyAndUserIdAndVendor(appKey, userId, vendor);
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
DeviceTokenEntity entity = existing.orElseGet(() -> {
|
||||
DeviceTokenEntity e = new DeviceTokenEntity();
|
||||
e.setId(UUID.randomUUID().toString());
|
||||
e.setAppId(appKey);
|
||||
e.setAppKey(appKey);
|
||||
e.setUserId(userId);
|
||||
e.setVendor(vendor);
|
||||
e.setCreatedAt(now);
|
||||
@ -216,8 +276,8 @@ public class PushDispatcher {
|
||||
|
||||
public void setReceivePush(String appKey, String userId, String deviceId, boolean enabled) {
|
||||
List<DeviceTokenEntity> tokens = deviceId == null || deviceId.isBlank()
|
||||
? tokenRepository.findByAppIdAndUserId(appKey, userId)
|
||||
: tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId).stream().toList();
|
||||
? tokenRepository.findByAppKeyAndUserId(appKey, userId)
|
||||
: tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId).stream().toList();
|
||||
for (DeviceTokenEntity token : tokens) {
|
||||
token.setReceivePush(enabled);
|
||||
token.setUpdatedAt(LocalDateTime.now());
|
||||
@ -228,20 +288,20 @@ public class PushDispatcher {
|
||||
|
||||
public void unregisterToken(String appKey, String userId, DeviceTokenEntity.Vendor vendor, String deviceId) {
|
||||
if (deviceId != null && !deviceId.isBlank()) {
|
||||
tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId)
|
||||
tokenRepository.findByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId)
|
||||
.ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER));
|
||||
tokenRepository.deleteByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId);
|
||||
tokenRepository.deleteByAppKeyAndUserIdAndDeviceId(appKey, userId, deviceId);
|
||||
return;
|
||||
}
|
||||
tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor)
|
||||
tokenRepository.findByAppKeyAndUserIdAndVendor(appKey, userId, vendor)
|
||||
.ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER));
|
||||
tokenRepository.deleteByAppIdAndUserIdAndVendor(appKey, userId, vendor);
|
||||
tokenRepository.deleteByAppKeyAndUserIdAndVendor(appKey, userId, vendor);
|
||||
}
|
||||
|
||||
private void recordLog(DeviceTokenEntity token, DeviceLoginLogEntity.EventType eventType) {
|
||||
DeviceLoginLogEntity entity = new DeviceLoginLogEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(token.getAppId());
|
||||
entity.setAppKey(token.getAppKey());
|
||||
entity.setUserId(token.getUserId());
|
||||
entity.setVendor(token.getVendor());
|
||||
entity.setTokenHash(hashToken(token.getToken()));
|
||||
|
||||
@ -82,16 +82,31 @@ public class ApnsPushProvider implements PushProvider {
|
||||
String url = (production ? productionPushUrl : sandboxPushUrl).replace("{token}", token);
|
||||
Map<String, Object> aps = new java.util.LinkedHashMap<>();
|
||||
aps.put("alert", Map.of("title", title, "body", body));
|
||||
aps.put("sound", "default");
|
||||
if (options == null || options.sound() == null || options.sound()) {
|
||||
aps.put("sound", "default");
|
||||
}
|
||||
if (options != null) {
|
||||
if (options.category() != null && !options.category().isBlank()) {
|
||||
aps.put("category", options.category());
|
||||
}
|
||||
if (options.routeType() != null && !options.routeType().isBlank()) {
|
||||
if (options.threadIdentifier() != null && !options.threadIdentifier().isBlank()) {
|
||||
aps.put("thread-id", options.threadIdentifier());
|
||||
} else if (options.routeType() != null && !options.routeType().isBlank()) {
|
||||
aps.put("thread-id", options.routeType());
|
||||
}
|
||||
if ("HIGH".equalsIgnoreCase(options.priority())) {
|
||||
if ("HIGH".equalsIgnoreCase(options.priority()) || "HIGH".equalsIgnoreCase(options.interruptionLevel())) {
|
||||
aps.put("interruption-level", "time-sensitive");
|
||||
} else if ("CRITICAL".equalsIgnoreCase(options.interruptionLevel())) {
|
||||
aps.put("interruption-level", "critical");
|
||||
} else if ("PASSIVE".equalsIgnoreCase(options.interruptionLevel())) {
|
||||
aps.put("interruption-level", "passive");
|
||||
} else if ("ACTIVE".equalsIgnoreCase(options.interruptionLevel())) {
|
||||
aps.put("interruption-level", "active");
|
||||
}
|
||||
if (options.badge() != null) {
|
||||
if (Boolean.TRUE.equals(options.badge())) {
|
||||
aps.put("badge", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<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) {
|
||||
JsonNode config = configClient.loadServiceConfig(appKey, "IOS", "PUSH")
|
||||
.map(node -> node.path("apns"))
|
||||
.map(node -> node.path("vendors").path("apns"))
|
||||
.orElse(null);
|
||||
if (config == null) {
|
||||
return fallback == null ? "" : fallback;
|
||||
|
||||
@ -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()) {
|
||||
notification.put("channel_id", options.channelId());
|
||||
}
|
||||
if (options.badge() != null && options.badge()) {
|
||||
notification.put("badge", 1);
|
||||
}
|
||||
}
|
||||
Map<String, Object> message = Map.of(
|
||||
"message", Map.of(
|
||||
@ -115,7 +118,7 @@ public class HarmonyPushProvider implements PushProvider {
|
||||
|
||||
private String resolveConfig(String appKey, String key, String fallback) {
|
||||
JsonNode config = configClient.loadServiceConfig(appKey, "HARMONY", "PUSH")
|
||||
.map(node -> node.path("harmony"))
|
||||
.map(node -> node.path("vendors").path("harmony"))
|
||||
.orElse(null);
|
||||
if (config == null) {
|
||||
return fallback == null ? "" : fallback;
|
||||
|
||||
@ -46,6 +46,11 @@ public class HonorPushProvider implements PushProvider {
|
||||
|
||||
@Override
|
||||
public boolean send(String appKey, String token, String title, String body, String payload) {
|
||||
return send(appKey, token, title, body, payload, PushSendOptions.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) {
|
||||
String resolvedAppId = resolveConfig(appKey, "appId", envAppId);
|
||||
String resolvedAppSecret = resolveConfig(appKey, "clientSecret", envAppSecret);
|
||||
if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) {
|
||||
@ -58,10 +63,21 @@ public class HonorPushProvider implements PushProvider {
|
||||
try {
|
||||
String accessToken = getAccessToken(resolvedAppId, resolvedAppSecret);
|
||||
String url = pushUrl.replace("{appId}", resolvedAppId);
|
||||
java.util.Map<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(
|
||||
"message", Map.of(
|
||||
"token", new String[]{token},
|
||||
"notification", Map.of("title", title, "body", body),
|
||||
"notification", notification,
|
||||
"data", payload != null ? payload : "{}"
|
||||
)
|
||||
);
|
||||
@ -94,7 +110,7 @@ public class HonorPushProvider implements PushProvider {
|
||||
|
||||
private String resolveConfig(String appKey, String key, String fallback) {
|
||||
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
|
||||
.map(node -> node.path("honor"))
|
||||
.map(node -> node.path("vendors").path("honor"))
|
||||
.orElse(null);
|
||||
if (config == null) {
|
||||
return fallback == null ? "" : fallback;
|
||||
|
||||
@ -68,7 +68,9 @@ public class HuaweiPushProvider implements PushProvider {
|
||||
Map<String, Object> androidNotification = new LinkedHashMap<>();
|
||||
String channelId = options != null && options.channelId() != null && !options.channelId().isBlank()
|
||||
? options.channelId() : "";
|
||||
String category = resolveConfig(appKey, "category", "");
|
||||
String category = options != null && options.category() != null && !options.category().isBlank()
|
||||
? options.category()
|
||||
: resolveConfig(appKey, "category", "");
|
||||
if (!channelId.isBlank()) androidNotification.put("channel_id", channelId);
|
||||
if (!category.isBlank()) androidNotification.put("category", category);
|
||||
if (!androidNotification.isEmpty()) {
|
||||
@ -104,7 +106,7 @@ public class HuaweiPushProvider implements PushProvider {
|
||||
|
||||
private String resolveConfig(String appKey, String key, String fallback) {
|
||||
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
|
||||
.map(node -> node.path("huawei"))
|
||||
.map(node -> node.path("vendors").path("huawei"))
|
||||
.orElse(null);
|
||||
if (config == null) {
|
||||
return fallback == null ? "" : fallback;
|
||||
|
||||
@ -40,6 +40,11 @@ public class OppoPushProvider implements PushProvider {
|
||||
|
||||
@Override
|
||||
public boolean send(String appKey, String token, String title, String body, String payload) {
|
||||
return send(appKey, token, title, body, payload, PushSendOptions.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) {
|
||||
String vendorAppKey = resolveConfig(appKey, "appKey");
|
||||
String masterSecret = resolveConfig(appKey, "masterSecret");
|
||||
if (vendorAppKey.isBlank() || masterSecret.isBlank()) {
|
||||
@ -55,7 +60,9 @@ public class OppoPushProvider implements PushProvider {
|
||||
inner.put("content", body);
|
||||
inner.put("target_type", 2);
|
||||
inner.put("target_value", token);
|
||||
String channelId = resolveConfig(appKey, "channelId");
|
||||
String channelId = options != null && options.channelId() != null && !options.channelId().isBlank()
|
||||
? options.channelId()
|
||||
: resolveConfig(appKey, "channelId");
|
||||
if (!channelId.isBlank()) inner.put("channel_id", channelId);
|
||||
Map<String, Object> message = Map.of("message", inner);
|
||||
String requestBody = objectMapper.writeValueAsString(message);
|
||||
@ -98,7 +105,7 @@ public class OppoPushProvider implements PushProvider {
|
||||
|
||||
private String resolveConfig(String appKey, String key) {
|
||||
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
|
||||
.map(node -> node.path("oppo"))
|
||||
.map(node -> node.path("vendors").path("oppo"))
|
||||
.orElse(null);
|
||||
if (config == null) {
|
||||
return "";
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
package com.xuqm.push.service.provider;
|
||||
|
||||
public record PushSendOptions(
|
||||
String profileKey,
|
||||
String routeType,
|
||||
String channelId,
|
||||
String category,
|
||||
String threadIdentifier,
|
||||
String interruptionLevel,
|
||||
String importance,
|
||||
Boolean badge,
|
||||
Boolean sound,
|
||||
Boolean vibration,
|
||||
Integer notifyType,
|
||||
String priority
|
||||
) {
|
||||
public static PushSendOptions empty() {
|
||||
return new PushSendOptions("", "", "", "");
|
||||
return new PushSendOptions("", "", "", "", "", "", "", null, null, null, null, "");
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,11 @@ public class VivoPushProvider implements PushProvider {
|
||||
|
||||
@Override
|
||||
public boolean send(String appKey, String token, String title, String body, String payload) {
|
||||
return send(appKey, token, title, body, payload, PushSendOptions.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) {
|
||||
String vendorAppKey = resolveConfig(appKey, "appKey");
|
||||
String appIdConfig = resolveConfig(appKey, "appId");
|
||||
if (vendorAppKey.isBlank() || appIdConfig.isBlank()) {
|
||||
@ -53,11 +58,17 @@ public class VivoPushProvider implements PushProvider {
|
||||
message.put("title", title);
|
||||
message.put("content", body);
|
||||
message.put("notifyType", 1);
|
||||
String category = resolveConfig(appKey, "category");
|
||||
String category = options != null && options.category() != null && !options.category().isBlank()
|
||||
? options.category()
|
||||
: resolveConfig(appKey, "category");
|
||||
String receiptId = resolveConfig(appKey, "receiptId");
|
||||
if (!category.isBlank()) {
|
||||
// vivo classification: 0=operation, 1=IM/system
|
||||
message.put("classification", "IM".equalsIgnoreCase(category) ? 1 : 0);
|
||||
message.put("classification", switch (category.toUpperCase()) {
|
||||
case "MESSAGE", "IM" -> 1;
|
||||
case "SOCIAL" -> 2;
|
||||
case "SYSTEM" -> 3;
|
||||
default -> 0;
|
||||
});
|
||||
}
|
||||
if (!receiptId.isBlank()) {
|
||||
message.put("requestId", receiptId + "_" + System.currentTimeMillis());
|
||||
@ -102,7 +113,7 @@ public class VivoPushProvider implements PushProvider {
|
||||
|
||||
private String resolveConfig(String appKey, String key) {
|
||||
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
|
||||
.map(node -> node.path("vivo"))
|
||||
.map(node -> node.path("vendors").path("vivo"))
|
||||
.orElse(null);
|
||||
if (config == null) {
|
||||
return "";
|
||||
|
||||
@ -70,8 +70,9 @@ public class XiaomiPushProvider implements PushProvider {
|
||||
.append("&title=").append(URLEncoder.encode(title, StandardCharsets.UTF_8))
|
||||
.append("&description=").append(URLEncoder.encode(body, StandardCharsets.UTF_8))
|
||||
.append("&restricted_package_name=").append(URLEncoder.encode(packageName, StandardCharsets.UTF_8))
|
||||
.append("¬ify_type=1")
|
||||
.append("&extra.notify_foreground=1");
|
||||
Integer notifyType = options != null ? options.notifyType() : null;
|
||||
form.append("¬ify_type=").append(notifyType == null ? 1 : Math.max(notifyType, 0));
|
||||
if (!channelId.isBlank()) {
|
||||
form.append("&channel_id=").append(URLEncoder.encode(channelId, StandardCharsets.UTF_8));
|
||||
}
|
||||
@ -110,7 +111,7 @@ public class XiaomiPushProvider implements PushProvider {
|
||||
|
||||
private String resolveVendorConfig(String appKey, String key, String fallback) {
|
||||
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
|
||||
.map(node -> node.path("xiaomi"))
|
||||
.map(node -> node.path("vendors").path("xiaomi"))
|
||||
.orElse(null);
|
||||
if (config == null) {
|
||||
return fallback == null ? "" : fallback;
|
||||
|
||||
@ -34,11 +34,6 @@ push:
|
||||
xiaomi:
|
||||
app-secret: ${XIAOMI_APP_SECRET:}
|
||||
push-url: https://api.xmpush.xiaomi.com/v3/message/regid
|
||||
fcm:
|
||||
project-id: ${FCM_PROJECT_ID:}
|
||||
service-account-json: ${FCM_SERVICE_ACCOUNT_JSON:}
|
||||
token-url: https://oauth2.googleapis.com/token
|
||||
push-url: https://fcm.googleapis.com/v1/projects/{projectId}/messages:send
|
||||
apns:
|
||||
team-id: ${APNS_TEAM_ID:}
|
||||
key-id: ${APNS_KEY_ID:}
|
||||
|
||||
@ -20,6 +20,11 @@
|
||||
<groupId>com.xuqm</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xuqm</groupId>
|
||||
<artifactId>im-sdk</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
||||
@ -112,35 +112,7 @@ public class FeatureServiceController {
|
||||
case PUSH -> featureServiceManager.buildPushConfig(
|
||||
appKey,
|
||||
platform,
|
||||
req == null ? null : req.huaweiAppIdValue(),
|
||||
req == null ? null : req.huaweiAppSecretValue(),
|
||||
req == null ? null : req.huaweiCategoryValue(),
|
||||
req == null ? null : req.xiaomiAppIdValue(),
|
||||
req == null ? null : req.xiaomiAppKeyValue(),
|
||||
req == null ? null : req.xiaomiAppSecretValue(),
|
||||
req == null ? null : req.xiaomiChannelIdValue(),
|
||||
req == null ? null : req.oppoAppIdValue(),
|
||||
req == null ? null : req.oppoAppKeyValue(),
|
||||
req == null ? null : req.oppoMasterSecretValue(),
|
||||
req == null ? null : req.oppoChannelIdValue(),
|
||||
req == null ? null : req.vivoAppIdValue(),
|
||||
req == null ? null : req.vivoAppKeyValue(),
|
||||
req == null ? null : req.vivoAppSecretValue(),
|
||||
req == null ? null : req.vivoCategoryValue(),
|
||||
req == null ? null : req.vivoReceiptIdValue(),
|
||||
req == null ? null : req.honorAppIdValue(),
|
||||
req == null ? null : req.honorClientIdValue(),
|
||||
req == null ? null : req.honorClientSecretValue(),
|
||||
req == null ? null : req.harmonyAppIdValue(),
|
||||
req == null ? null : req.harmonyAppSecretValue(),
|
||||
req == null ? null : req.apnsTeamIdValue(),
|
||||
req == null ? null : req.apnsKeyIdValue(),
|
||||
req == null ? null : req.apnsBundleIdValue(),
|
||||
req == null ? null : req.apnsKeyPathValue(),
|
||||
req != null && req.apnsSandboxValue(),
|
||||
req == null ? null : req.fcmServiceAccountJsonValue(),
|
||||
req == null ? null : req.channels(),
|
||||
req == null ? null : req.routing());
|
||||
req == null ? null : req.pushConfig());
|
||||
};
|
||||
FeatureServiceEntity saved = featureServiceManager.updateConfig(
|
||||
appKey, platform, serviceType, config);
|
||||
@ -215,102 +187,7 @@ public class FeatureServiceController {
|
||||
String defaultPackageName,
|
||||
String defaultAppStoreUrl,
|
||||
String defaultMarketUrl,
|
||||
String huaweiAppId,
|
||||
String huaweiAppSecret,
|
||||
String huaweiCategory,
|
||||
String xiaomiAppId,
|
||||
String xiaomiAppKey,
|
||||
String xiaomiAppSecret,
|
||||
String xiaomiChannelId,
|
||||
String oppoAppId,
|
||||
String oppoAppKey,
|
||||
String oppoMasterSecret,
|
||||
String oppoChannelId,
|
||||
String vivoAppId,
|
||||
String vivoAppKey,
|
||||
String vivoAppSecret,
|
||||
String vivoCategory,
|
||||
String vivoReceiptId,
|
||||
String honorAppId,
|
||||
String honorClientId,
|
||||
String honorClientSecret,
|
||||
String harmonyAppId,
|
||||
String harmonyAppSecret,
|
||||
String apnsTeamId,
|
||||
String apnsKeyId,
|
||||
String apnsBundleId,
|
||||
String apnsKeyPath,
|
||||
Boolean apnsSandbox,
|
||||
String fcmServiceAccountJson,
|
||||
PushVendorConfig huawei,
|
||||
PushVendorConfig xiaomi,
|
||||
PushVendorConfig oppo,
|
||||
PushVendorConfig vivo,
|
||||
PushVendorConfig honor,
|
||||
PushVendorConfig harmony,
|
||||
PushVendorConfig apns,
|
||||
PushVendorConfig fcm,
|
||||
JsonNode channels,
|
||||
JsonNode routing
|
||||
JsonNode pushConfig
|
||||
) {
|
||||
public String huaweiAppIdValue() { return firstText(huaweiAppId, huawei == null ? null : huawei.appId()); }
|
||||
public String huaweiAppSecretValue() { return firstText(huaweiAppSecret, huawei == null ? null : huawei.appSecret()); }
|
||||
public String huaweiCategoryValue() { return firstText(huaweiCategory, huawei == null ? null : huawei.category()); }
|
||||
public String xiaomiAppIdValue() { return firstText(xiaomiAppId, xiaomi == null ? null : xiaomi.appId()); }
|
||||
public String xiaomiAppKeyValue() { return firstText(xiaomiAppKey, xiaomi == null ? null : xiaomi.appKey()); }
|
||||
public String xiaomiAppSecretValue() { return firstText(xiaomiAppSecret, xiaomi == null ? null : xiaomi.appSecret()); }
|
||||
public String xiaomiChannelIdValue() { return firstText(xiaomiChannelId, xiaomi == null ? null : xiaomi.channelId()); }
|
||||
public String oppoAppIdValue() { return firstText(oppoAppId, oppo == null ? null : oppo.appId()); }
|
||||
public String oppoAppKeyValue() { return firstText(oppoAppKey, oppo == null ? null : oppo.appKey()); }
|
||||
public String oppoMasterSecretValue() { return firstText(oppoMasterSecret, oppo == null ? null : oppo.masterSecret()); }
|
||||
public String oppoChannelIdValue() { return firstText(oppoChannelId, oppo == null ? null : oppo.channelId()); }
|
||||
public String vivoAppIdValue() { return firstText(vivoAppId, vivo == null ? null : vivo.appId()); }
|
||||
public String vivoAppKeyValue() { return firstText(vivoAppKey, vivo == null ? null : vivo.appKey()); }
|
||||
public String vivoAppSecretValue() { return firstText(vivoAppSecret, vivo == null ? null : vivo.appSecret()); }
|
||||
public String vivoCategoryValue() { return firstText(vivoCategory, vivo == null ? null : vivo.category()); }
|
||||
public String vivoReceiptIdValue() { return firstText(vivoReceiptId, vivo == null ? null : vivo.receiptId()); }
|
||||
public String honorAppIdValue() { return firstText(honorAppId, honor == null ? null : honor.appId()); }
|
||||
public String honorClientIdValue() { return firstText(honorClientId, honor == null ? null : honor.clientId()); }
|
||||
public String honorClientSecretValue() { return firstText(honorClientSecret, honor == null ? null : honor.clientSecret()); }
|
||||
public String harmonyAppIdValue() { return firstText(harmonyAppId, harmony == null ? null : harmony.appId()); }
|
||||
public String harmonyAppSecretValue() { return firstText(harmonyAppSecret, harmony == null ? null : harmony.appSecret()); }
|
||||
public String apnsTeamIdValue() { return firstText(apnsTeamId, apns == null ? null : apns.teamId()); }
|
||||
public String apnsKeyIdValue() { return firstText(apnsKeyId, apns == null ? null : apns.keyId()); }
|
||||
public String apnsBundleIdValue() { return firstText(apnsBundleId, apns == null ? null : apns.bundleId()); }
|
||||
public String apnsKeyPathValue() { return firstText(apnsKeyPath, apns == null ? null : apns.keyPath()); }
|
||||
public boolean apnsSandboxValue() {
|
||||
if (apnsSandbox != null) {
|
||||
return apnsSandbox;
|
||||
}
|
||||
return apns != null && Boolean.TRUE.equals(apns.sandbox());
|
||||
}
|
||||
public String fcmServiceAccountJsonValue() {
|
||||
return firstText(fcmServiceAccountJson, fcm == null ? null : fcm.serviceAccountJson());
|
||||
}
|
||||
|
||||
private static String firstText(String first, String second) {
|
||||
if (first != null) {
|
||||
return first;
|
||||
}
|
||||
return second;
|
||||
}
|
||||
}
|
||||
|
||||
public record PushVendorConfig(
|
||||
String appId,
|
||||
String appKey,
|
||||
String appSecret,
|
||||
String masterSecret,
|
||||
String clientId,
|
||||
String clientSecret,
|
||||
String teamId,
|
||||
String keyId,
|
||||
String bundleId,
|
||||
String keyPath,
|
||||
Boolean sandbox,
|
||||
String serviceAccountJson,
|
||||
String channelId,
|
||||
String category,
|
||||
String receiptId
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -56,22 +56,22 @@ public class SdkConfigController {
|
||||
@RequestParam(required = false, defaultValue = "ANDROID") FeatureServiceEntity.Platform platform) {
|
||||
|
||||
AppEntity app = sdkAppProvisioningService.resolveApp(appKey);
|
||||
List<FeatureServiceEntity> features = featureServiceRepository.findByAppId(app.getAppKey());
|
||||
List<FeatureServiceEntity> features = featureServiceRepository.findByAppKey(app.getAppKey());
|
||||
|
||||
boolean imEnabled = features.stream()
|
||||
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.IM && f.isEnabled());
|
||||
boolean pushEnabled = features.stream()
|
||||
.anyMatch(f -> f.getServiceType() == FeatureServiceEntity.ServiceType.PUSH && f.isEnabled());
|
||||
JsonNode updateConfig = featureServiceRepository
|
||||
.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
||||
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
||||
.map(feature -> parseConfig(feature.getConfig()))
|
||||
.orElseGet(objectMapper::createObjectNode);
|
||||
JsonNode pushConfig = featureServiceRepository
|
||||
.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH)
|
||||
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.PUSH)
|
||||
.map(feature -> parseConfig(feature.getConfig()))
|
||||
.orElseGet(objectMapper::createObjectNode);
|
||||
boolean updateEnabled = featureServiceRepository
|
||||
.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
||||
.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, FeatureServiceEntity.ServiceType.UPDATE)
|
||||
.map(FeatureServiceEntity::isEnabled)
|
||||
.orElse(false);
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ public class FeatureServiceEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 16)
|
||||
@ -52,8 +52,8 @@ public class FeatureServiceEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public Platform getPlatform() { return platform; }
|
||||
public void setPlatform(Platform platform) { this.platform = platform; }
|
||||
|
||||
@ -19,7 +19,7 @@ public class ServiceActivationRequestEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 16)
|
||||
@ -47,14 +47,11 @@ public class ServiceActivationRequestEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
@JsonProperty("appKey")
|
||||
public String getAppKey() { return appKey; }
|
||||
|
||||
@JsonProperty("appKey")
|
||||
public String getAppKey() { return appId; }
|
||||
|
||||
@JsonProperty("appKey")
|
||||
public void setAppKey(String appKey) { this.appId = appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public FeatureServiceEntity.Platform getPlatform() { return platform; }
|
||||
public void setPlatform(FeatureServiceEntity.Platform platform) { this.platform = platform; }
|
||||
|
||||
@ -7,10 +7,10 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface FeatureServiceRepository extends JpaRepository<FeatureServiceEntity, String> {
|
||||
List<FeatureServiceEntity> findByAppId(String appId);
|
||||
List<FeatureServiceEntity> findByAppIdAndServiceType(String appId, FeatureServiceEntity.ServiceType serviceType);
|
||||
Optional<FeatureServiceEntity> findByAppIdAndPlatformAndServiceType(
|
||||
String appId,
|
||||
List<FeatureServiceEntity> findByAppKey(String appKey);
|
||||
List<FeatureServiceEntity> findByAppKeyAndServiceType(String appKey, FeatureServiceEntity.ServiceType serviceType);
|
||||
Optional<FeatureServiceEntity> findByAppKeyAndPlatformAndServiceType(
|
||||
String appKey,
|
||||
FeatureServiceEntity.Platform platform,
|
||||
FeatureServiceEntity.ServiceType serviceType);
|
||||
|
||||
|
||||
@ -12,13 +12,13 @@ import java.util.Optional;
|
||||
|
||||
public interface ServiceActivationRequestRepository extends JpaRepository<ServiceActivationRequestEntity, String> {
|
||||
|
||||
Optional<ServiceActivationRequestEntity> findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc(
|
||||
String appId, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType);
|
||||
Optional<ServiceActivationRequestEntity> findFirstByAppKeyAndPlatformAndServiceTypeOrderByCreatedAtDesc(
|
||||
String appKey, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType);
|
||||
|
||||
Optional<ServiceActivationRequestEntity> findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(
|
||||
String appId, FeatureServiceEntity.ServiceType serviceType);
|
||||
Optional<ServiceActivationRequestEntity> findFirstByAppKeyAndServiceTypeOrderByCreatedAtDesc(
|
||||
String appKey, FeatureServiceEntity.ServiceType serviceType);
|
||||
|
||||
List<ServiceActivationRequestEntity> findByAppIdOrderByCreatedAtDesc(String appId);
|
||||
List<ServiceActivationRequestEntity> findByAppKeyOrderByCreatedAtDesc(String appKey);
|
||||
|
||||
Page<ServiceActivationRequestEntity> findByStatusOrderByCreatedAtDesc(Status status, Pageable pageable);
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ public class DashboardService {
|
||||
List<AppEntity> apps = appRepository.findByTenantId(tenantId);
|
||||
long serviceCount = 0;
|
||||
for (AppEntity app : apps) {
|
||||
serviceCount += featureServiceRepository.findByAppId(app.getId()).stream()
|
||||
serviceCount += featureServiceRepository.findByAppKey(app.getId()).stream()
|
||||
.filter(FeatureServiceEntity::isEnabled)
|
||||
.count();
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ public class FeatureServiceManager {
|
||||
}
|
||||
|
||||
public List<FeatureServiceEntity> listByApp(String appKey) {
|
||||
List<FeatureServiceEntity> services = repository.findByAppId(appKey);
|
||||
List<FeatureServiceEntity> services = repository.findByAppKey(appKey);
|
||||
if (services.isEmpty()) {
|
||||
return services;
|
||||
}
|
||||
@ -68,7 +68,7 @@ public class FeatureServiceManager {
|
||||
String applyReason) {
|
||||
|
||||
if (isAppWideService(serviceType)) {
|
||||
requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(appKey, serviceType)
|
||||
requestRepository.findFirstByAppKeyAndServiceTypeOrderByCreatedAtDesc(appKey, serviceType)
|
||||
.ifPresent(req -> {
|
||||
if (req.getStatus() == Status.PENDING) {
|
||||
throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理");
|
||||
@ -78,7 +78,7 @@ public class FeatureServiceManager {
|
||||
|
||||
ServiceActivationRequestEntity req = new ServiceActivationRequestEntity();
|
||||
req.setId(UUID.randomUUID().toString());
|
||||
req.setAppId(appKey);
|
||||
req.setAppKey(appKey);
|
||||
req.setPlatform(platform);
|
||||
req.setServiceType(serviceType);
|
||||
req.setStatus(Status.PENDING);
|
||||
@ -94,7 +94,7 @@ public class FeatureServiceManager {
|
||||
public FeatureServiceEntity disable(String appKey, FeatureServiceEntity.Platform platform,
|
||||
FeatureServiceEntity.ServiceType serviceType) {
|
||||
if (isAppWideService(serviceType)) {
|
||||
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType);
|
||||
List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(appKey, serviceType);
|
||||
if (services.isEmpty()) {
|
||||
throw new BusinessException(404, "服务未开通");
|
||||
}
|
||||
@ -104,7 +104,7 @@ public class FeatureServiceManager {
|
||||
}
|
||||
|
||||
FeatureServiceEntity entity = repository
|
||||
.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
|
||||
.findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType)
|
||||
.orElseThrow(() -> new BusinessException(404, "服务未开通"));
|
||||
entity.setEnabled(false);
|
||||
return repository.save(entity);
|
||||
@ -126,17 +126,17 @@ public class FeatureServiceManager {
|
||||
requestRepository.save(req);
|
||||
|
||||
// Normalize to appKey so SdkConfigController queries are consistent
|
||||
String normalizedAppId = appRepository.findById(req.getAppId())
|
||||
String normalizedAppId = appRepository.findById(req.getAppKey())
|
||||
.map(app -> app.getAppKey())
|
||||
.orElse(req.getAppId());
|
||||
.orElse(req.getAppKey());
|
||||
|
||||
if (isAppWideService(req.getServiceType())) {
|
||||
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(normalizedAppId, req.getServiceType());
|
||||
List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(normalizedAppId, req.getServiceType());
|
||||
if (services.isEmpty()) {
|
||||
for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) {
|
||||
FeatureServiceEntity created = new FeatureServiceEntity();
|
||||
created.setId(UUID.randomUUID().toString());
|
||||
created.setAppId(normalizedAppId);
|
||||
created.setAppKey(normalizedAppId);
|
||||
created.setPlatform(platform);
|
||||
created.setServiceType(req.getServiceType());
|
||||
created.setEnabled(true);
|
||||
@ -151,11 +151,11 @@ public class FeatureServiceManager {
|
||||
}
|
||||
|
||||
FeatureServiceEntity entity = repository
|
||||
.findByAppIdAndPlatformAndServiceType(normalizedAppId, req.getPlatform(), req.getServiceType())
|
||||
.findByAppKeyAndPlatformAndServiceType(normalizedAppId, req.getPlatform(), req.getServiceType())
|
||||
.orElseGet(() -> {
|
||||
FeatureServiceEntity e = new FeatureServiceEntity();
|
||||
e.setId(UUID.randomUUID().toString());
|
||||
e.setAppId(normalizedAppId);
|
||||
e.setAppKey(normalizedAppId);
|
||||
e.setPlatform(req.getPlatform());
|
||||
e.setServiceType(req.getServiceType());
|
||||
e.setCreatedAt(LocalDateTime.now());
|
||||
@ -183,18 +183,18 @@ public class FeatureServiceManager {
|
||||
}
|
||||
|
||||
public List<ServiceActivationRequestEntity> listRequestsByApp(String appKey) {
|
||||
return requestRepository.findByAppIdOrderByCreatedAtDesc(appKey);
|
||||
return requestRepository.findByAppKeyOrderByCreatedAtDesc(appKey);
|
||||
}
|
||||
|
||||
public FeatureServiceEntity getOrFail(String appKey, FeatureServiceEntity.Platform platform,
|
||||
FeatureServiceEntity.ServiceType serviceType) {
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
|
||||
return repository.findByAppIdAndServiceType(appKey, serviceType)
|
||||
return repository.findByAppKeyAndServiceType(appKey, serviceType)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
|
||||
}
|
||||
return repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
|
||||
return repository.findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType)
|
||||
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
|
||||
}
|
||||
|
||||
@ -204,7 +204,7 @@ public class FeatureServiceManager {
|
||||
FeatureServiceEntity.ServiceType serviceType,
|
||||
String config) {
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
|
||||
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType);
|
||||
List<FeatureServiceEntity> services = repository.findByAppKeyAndServiceType(appKey, serviceType);
|
||||
if (services.isEmpty()) {
|
||||
throw new BusinessException(404, "服务未配置");
|
||||
}
|
||||
@ -477,127 +477,25 @@ public class FeatureServiceManager {
|
||||
|
||||
public String buildPushConfig(String appKey,
|
||||
FeatureServiceEntity.Platform platform,
|
||||
String huaweiAppId,
|
||||
String huaweiAppSecret,
|
||||
String huaweiCategory,
|
||||
String xiaomiAppId,
|
||||
String xiaomiAppKey,
|
||||
String xiaomiAppSecret,
|
||||
String xiaomiChannelId,
|
||||
String oppoAppId,
|
||||
String oppoAppKey,
|
||||
String oppoMasterSecret,
|
||||
String oppoChannelId,
|
||||
String vivoAppId,
|
||||
String vivoAppKey,
|
||||
String vivoAppSecret,
|
||||
String vivoCategory,
|
||||
String vivoReceiptId,
|
||||
String honorAppId,
|
||||
String honorClientId,
|
||||
String honorClientSecret,
|
||||
String harmonyAppId,
|
||||
String harmonyAppSecret,
|
||||
String apnsTeamId,
|
||||
String apnsKeyId,
|
||||
String apnsBundleId,
|
||||
String apnsKeyPath,
|
||||
boolean apnsSandbox,
|
||||
String fcmServiceAccountJson,
|
||||
JsonNode channels,
|
||||
JsonNode routing) {
|
||||
ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.PUSH).deepCopy();
|
||||
ensureObjectNode(node, "huawei");
|
||||
ensureObjectNode(node, "xiaomi");
|
||||
ensureObjectNode(node, "oppo");
|
||||
ensureObjectNode(node, "vivo");
|
||||
ensureObjectNode(node, "honor");
|
||||
ensureObjectNode(node, "harmony");
|
||||
ensureObjectNode(node, "apns");
|
||||
ensureObjectNode(node, "fcm");
|
||||
|
||||
putText(node.with("huawei"), "appId", huaweiAppId);
|
||||
putText(node.with("huawei"), "appSecret", huaweiAppSecret);
|
||||
putText(node.with("huawei"), "category", huaweiCategory);
|
||||
|
||||
putText(node.with("xiaomi"), "appId", xiaomiAppId);
|
||||
putText(node.with("xiaomi"), "appKey", xiaomiAppKey);
|
||||
putText(node.with("xiaomi"), "appSecret", xiaomiAppSecret);
|
||||
putText(node.with("xiaomi"), "channelId", xiaomiChannelId);
|
||||
|
||||
putText(node.with("oppo"), "appId", oppoAppId);
|
||||
putText(node.with("oppo"), "appKey", oppoAppKey);
|
||||
putText(node.with("oppo"), "masterSecret", oppoMasterSecret);
|
||||
putText(node.with("oppo"), "channelId", oppoChannelId);
|
||||
|
||||
putText(node.with("vivo"), "appId", vivoAppId);
|
||||
putText(node.with("vivo"), "appKey", vivoAppKey);
|
||||
putText(node.with("vivo"), "appSecret", vivoAppSecret);
|
||||
putText(node.with("vivo"), "category", vivoCategory);
|
||||
putText(node.with("vivo"), "receiptId", vivoReceiptId);
|
||||
|
||||
putText(node.with("honor"), "appId", honorAppId);
|
||||
putText(node.with("honor"), "clientId", honorClientId);
|
||||
putText(node.with("honor"), "clientSecret", honorClientSecret);
|
||||
|
||||
putText(node.with("harmony"), "appId", harmonyAppId);
|
||||
putText(node.with("harmony"), "appSecret", harmonyAppSecret);
|
||||
|
||||
putText(node.with("apns"), "teamId", apnsTeamId);
|
||||
putText(node.with("apns"), "keyId", apnsKeyId);
|
||||
putText(node.with("apns"), "bundleId", apnsBundleId);
|
||||
putText(node.with("apns"), "keyPath", apnsKeyPath);
|
||||
node.with("apns").put("sandbox", apnsSandbox);
|
||||
|
||||
putText(node.with("fcm"), "serviceAccountJson", fcmServiceAccountJson);
|
||||
if (channels != null && channels.isArray()) {
|
||||
node.set("channels", channels);
|
||||
} else if (!node.has("channels")) {
|
||||
node.set("channels", defaultPushChannels());
|
||||
JsonNode pushConfig) {
|
||||
ObjectNode root = objectMapper.createObjectNode();
|
||||
root.put("schemaVersion", 2);
|
||||
root.put("updatedAt", java.time.LocalDateTime.now().toString());
|
||||
ObjectNode vendors = objectMapper.createObjectNode();
|
||||
ArrayNode profiles = objectMapper.createArrayNode();
|
||||
if (pushConfig != null && pushConfig.isObject()) {
|
||||
JsonNode inputVendors = pushConfig.path("vendors");
|
||||
if (inputVendors != null && inputVendors.isObject()) {
|
||||
vendors.setAll((ObjectNode) inputVendors);
|
||||
}
|
||||
JsonNode inputProfiles = pushConfig.path("profiles");
|
||||
if (inputProfiles != null && inputProfiles.isArray()) {
|
||||
inputProfiles.forEach(profile -> profiles.add(profile.deepCopy()));
|
||||
}
|
||||
}
|
||||
if (routing != null && routing.isObject()) {
|
||||
node.set("routing", routing);
|
||||
} else if (!node.has("routing")) {
|
||||
node.set("routing", defaultPushRouting());
|
||||
}
|
||||
return node.toString();
|
||||
}
|
||||
|
||||
private ArrayNode defaultPushChannels() {
|
||||
ArrayNode channels = objectMapper.createArrayNode();
|
||||
channels.add(defaultPushChannel("im_message", "xuqm_im_message", "聊天消息", "单聊、群聊和好友消息", "HIGH"));
|
||||
channels.add(defaultPushChannel("system_notice", "xuqm_system_notice", "系统通知", "系统通知和业务提醒", "DEFAULT"));
|
||||
return channels;
|
||||
}
|
||||
|
||||
private ObjectNode defaultPushChannel(String key, String channelId, String name, String description, String importance) {
|
||||
ObjectNode channel = objectMapper.createObjectNode();
|
||||
channel.put("key", key);
|
||||
channel.put("channelId", channelId);
|
||||
channel.put("version", 1);
|
||||
channel.put("name", name);
|
||||
channel.put("description", description);
|
||||
channel.put("importance", importance);
|
||||
channel.put("sound", true);
|
||||
channel.put("vibration", true);
|
||||
channel.put("badge", true);
|
||||
return channel;
|
||||
}
|
||||
|
||||
private ObjectNode defaultPushRouting() {
|
||||
ObjectNode routing = objectMapper.createObjectNode();
|
||||
routing.set("IM_MESSAGE", defaultPushRoute("im_message", "MESSAGE", "HIGH"));
|
||||
routing.set("FRIEND_REQUEST", defaultPushRoute("im_message", "SOCIAL", "HIGH"));
|
||||
routing.set("SYSTEM_NOTICE", defaultPushRoute("system_notice", "SYSTEM", "DEFAULT"));
|
||||
return routing;
|
||||
}
|
||||
|
||||
private ObjectNode defaultPushRoute(String channel, String category, String priority) {
|
||||
ObjectNode route = objectMapper.createObjectNode();
|
||||
route.put("channel", channel);
|
||||
route.put("category", category);
|
||||
route.put("priority", priority);
|
||||
return route;
|
||||
root.set("vendors", vendors);
|
||||
root.set("profiles", profiles);
|
||||
return root.toString();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -649,12 +547,12 @@ public class FeatureServiceManager {
|
||||
FeatureServiceEntity.ServiceType serviceType) {
|
||||
FeatureServiceEntity entity;
|
||||
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
|
||||
entity = repository.findByAppIdAndServiceType(appKey, serviceType)
|
||||
entity = repository.findByAppKeyAndServiceType(appKey, serviceType)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
entity = repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
|
||||
entity = repository.findByAppKeyAndPlatformAndServiceType(appKey, platform, serviceType)
|
||||
.orElse(null);
|
||||
}
|
||||
if (entity == null || entity.getConfig() == null || entity.getConfig().isBlank()) {
|
||||
|
||||
@ -80,7 +80,7 @@ public class OpsService {
|
||||
List<AppEntity> apps = appRepository.findByTenantId(tenantId);
|
||||
long subAccountCount = tenantRepository.countByParentId(tenantId);
|
||||
long activeServiceCount = apps.stream()
|
||||
.flatMap(app -> featureServiceRepository.findByAppId(app.getAppKey()).stream())
|
||||
.flatMap(app -> featureServiceRepository.findByAppKey(app.getAppKey()).stream())
|
||||
.filter(FeatureServiceEntity::isEnabled)
|
||||
.count();
|
||||
|
||||
@ -207,7 +207,7 @@ public class OpsService {
|
||||
public Map<String, Object> getAppDetail(String appKey) {
|
||||
AppEntity app = appRepository.findByAppKey(appKey)
|
||||
.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();
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("app", app);
|
||||
@ -221,7 +221,7 @@ public class OpsService {
|
||||
public List<FeatureServiceEntity> listAppServices(String appKey) {
|
||||
appRepository.findByAppKey(appKey)
|
||||
.orElseThrow(() -> new IllegalArgumentException("应用不存在"));
|
||||
return featureServiceRepository.findByAppId(appKey);
|
||||
return featureServiceRepository.findByAppKey(appKey);
|
||||
}
|
||||
|
||||
public Page<OperationLogEntity> listOperationLogs(int page, int size) {
|
||||
|
||||
@ -117,13 +117,13 @@ public class SdkAppProvisioningService {
|
||||
}
|
||||
|
||||
private void ensureFeatureDefaults(AppEntity app) {
|
||||
featureServiceRepository.findByAppIdAndServiceType(app.getAppKey(), FeatureServiceEntity.ServiceType.IM)
|
||||
featureServiceRepository.findByAppKeyAndServiceType(app.getAppKey(), FeatureServiceEntity.ServiceType.IM)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
FeatureServiceEntity feature = new FeatureServiceEntity();
|
||||
feature.setId(UUID.randomUUID().toString());
|
||||
feature.setAppId(app.getAppKey());
|
||||
feature.setAppKey(app.getAppKey());
|
||||
feature.setPlatform(FeatureServiceEntity.Platform.ANDROID);
|
||||
feature.setServiceType(FeatureServiceEntity.ServiceType.IM);
|
||||
feature.setEnabled(true);
|
||||
@ -141,11 +141,11 @@ public class SdkAppProvisioningService {
|
||||
for (FeatureServiceEntity.ServiceType serviceType : List.of(
|
||||
FeatureServiceEntity.ServiceType.PUSH,
|
||||
FeatureServiceEntity.ServiceType.UPDATE)) {
|
||||
featureServiceRepository.findByAppIdAndPlatformAndServiceType(app.getAppKey(), platform, serviceType)
|
||||
featureServiceRepository.findByAppKeyAndPlatformAndServiceType(app.getAppKey(), platform, serviceType)
|
||||
.orElseGet(() -> {
|
||||
FeatureServiceEntity feature = new FeatureServiceEntity();
|
||||
feature.setId(UUID.randomUUID().toString());
|
||||
feature.setAppId(app.getAppKey());
|
||||
feature.setAppKey(app.getAppKey());
|
||||
feature.setPlatform(platform);
|
||||
feature.setServiceType(serviceType);
|
||||
feature.setEnabled(true);
|
||||
|
||||
@ -88,3 +88,4 @@ sdk:
|
||||
im-ws-url: ${SDK_IM_WS_URL:wss://im.dev.xuqinmin.com/ws/im}
|
||||
file-service-url: ${SDK_FILE_SERVICE_URL:https://file.dev.xuqinmin.com}
|
||||
im-api-url: ${SDK_IM_API_URL:https://im.dev.xuqinmin.com}
|
||||
im-platform-events-user-prefix: ${SDK_IM_PLATFORM_EVENTS_USER_PREFIX:platform-events:}
|
||||
|
||||
@ -58,10 +58,10 @@ public class AppVersionController {
|
||||
}
|
||||
|
||||
Optional<AppVersionEntity> latest = versionRepository
|
||||
.findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc(
|
||||
.findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc(
|
||||
appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
|
||||
Optional<AppVersionEntity> forcedHigher = versionRepository
|
||||
.findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc(
|
||||
.findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc(
|
||||
appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
|
||||
|
||||
if (latest.isEmpty()) {
|
||||
@ -146,7 +146,7 @@ public class AppVersionController {
|
||||
}
|
||||
AppVersionEntity entity = new AppVersionEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setPlatform(platform);
|
||||
entity.setVersionName(resolvedVersionName);
|
||||
entity.setVersionCode(resolvedVersionCode);
|
||||
@ -189,7 +189,7 @@ public class AppVersionController {
|
||||
}
|
||||
AppVersionEntity saved = versionRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"APP_VERSION",
|
||||
saved.getId(),
|
||||
"UPLOAD",
|
||||
@ -246,7 +246,7 @@ public class AppVersionController {
|
||||
entity.setGrayMemberIds(null);
|
||||
AppVersionEntity saved = versionRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"APP_VERSION",
|
||||
saved.getId(),
|
||||
publishAction(previousStatus, saved.getPublishStatus(), publishImmediately),
|
||||
@ -273,7 +273,7 @@ public class AppVersionController {
|
||||
entity.setPublishStatus(AppVersionEntity.PublishStatus.DEPRECATED);
|
||||
AppVersionEntity saved = versionRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"APP_VERSION",
|
||||
saved.getId(),
|
||||
"UNPUBLISH",
|
||||
@ -290,7 +290,7 @@ public class AppVersionController {
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> body) throws Exception {
|
||||
AppVersionEntity entity = versionRepository.findById(id).orElseThrow();
|
||||
if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppId())) {
|
||||
if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppKey())) {
|
||||
throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布");
|
||||
}
|
||||
boolean enabled = Boolean.TRUE.equals(body.get("enabled"));
|
||||
@ -305,7 +305,7 @@ public class AppVersionController {
|
||||
String selectionSource = body.get("selectionSource") == null ? "LOCAL"
|
||||
: body.get("selectionSource").toString().trim().toUpperCase();
|
||||
if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) {
|
||||
memberIds = publishConfigService.resolveGrayMembers(entity.getAppId(), body);
|
||||
memberIds = publishConfigService.resolveGrayMembers(entity.getAppKey(), body);
|
||||
}
|
||||
entity.setGrayMode("MEMBERS");
|
||||
entity.setGrayMemberIds(toJson(memberIds));
|
||||
@ -318,7 +318,7 @@ public class AppVersionController {
|
||||
entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED);
|
||||
AppVersionEntity saved = versionRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"APP_VERSION",
|
||||
saved.getId(),
|
||||
"GRAY_UPDATE",
|
||||
@ -336,7 +336,7 @@ public class AppVersionController {
|
||||
public ResponseEntity<ApiResponse<List<AppVersionEntity>>> list(
|
||||
@RequestParam String appKey, @RequestParam AppVersionEntity.Platform platform) {
|
||||
return ResponseEntity.ok(ApiResponse.success(
|
||||
versionRepository.findByAppIdAndPlatformOrderByVersionCodeDesc(appKey, platform)));
|
||||
versionRepository.findByAppKeyAndPlatformOrderByVersionCodeDesc(appKey, platform)));
|
||||
}
|
||||
|
||||
private String publishAction(AppVersionEntity.PublishStatus previousStatus,
|
||||
|
||||
@ -56,7 +56,7 @@ public class RnBundleController {
|
||||
|
||||
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
|
||||
Optional<RnBundleEntity> latest = bundleRepository
|
||||
.findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
|
||||
.findTopByAppKeyAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
|
||||
appKey, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED);
|
||||
|
||||
if (latest.isEmpty()) {
|
||||
@ -115,7 +115,7 @@ public class RnBundleController {
|
||||
|
||||
RnBundleEntity entity = new RnBundleEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setModuleId(resolvedModuleId);
|
||||
entity.setPlatform(resolvedPlatform);
|
||||
entity.setVersion(resolvedVersion);
|
||||
@ -132,7 +132,7 @@ public class RnBundleController {
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
RnBundleEntity saved = bundleRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"RN_BUNDLE",
|
||||
saved.getId(),
|
||||
"UPLOAD",
|
||||
@ -160,11 +160,11 @@ public class RnBundleController {
|
||||
List<RnBundleEntity> result;
|
||||
if (moduleId != null && platform != null) {
|
||||
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
|
||||
result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p);
|
||||
result = bundleRepository.findByAppKeyAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p);
|
||||
} else if (moduleId != null) {
|
||||
result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appKey, moduleId);
|
||||
result = bundleRepository.findByAppKeyAndModuleIdOrderByCreatedAtDesc(appKey, moduleId);
|
||||
} else {
|
||||
result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appKey);
|
||||
result = bundleRepository.findByAppKeyOrderByCreatedAtDesc(appKey);
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
@ -194,7 +194,7 @@ public class RnBundleController {
|
||||
entity.setGrayMemberIds(null);
|
||||
RnBundleEntity saved = bundleRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"RN_BUNDLE",
|
||||
saved.getId(),
|
||||
publishImmediately && (scheduledPublishAt == null || scheduledPublishAt.isBlank()) ? "PUBLISH" : "SCHEDULE_PUBLISH",
|
||||
@ -220,7 +220,7 @@ public class RnBundleController {
|
||||
entity.setPublishStatus(RnBundleEntity.PublishStatus.DEPRECATED);
|
||||
RnBundleEntity saved = bundleRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"RN_BUNDLE",
|
||||
saved.getId(),
|
||||
"UNPUBLISH",
|
||||
@ -237,7 +237,7 @@ public class RnBundleController {
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> body) throws Exception {
|
||||
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
|
||||
if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppId())) {
|
||||
if (publishConfigService.allowAnonymousUpdateCheck(entity.getAppKey())) {
|
||||
throw new com.xuqm.common.exception.BusinessException(400, "允许免登录检查更新的应用不支持灰度发布");
|
||||
}
|
||||
boolean enabled = Boolean.TRUE.equals(body.get("enabled"));
|
||||
@ -252,7 +252,7 @@ public class RnBundleController {
|
||||
String selectionSource = body.get("selectionSource") == null ? "LOCAL"
|
||||
: body.get("selectionSource").toString().trim().toUpperCase();
|
||||
if (memberIds.isEmpty() && "CALLBACK".equals(selectionSource)) {
|
||||
memberIds = publishConfigService.resolveGrayMembers(entity.getAppId(), body);
|
||||
memberIds = publishConfigService.resolveGrayMembers(entity.getAppKey(), body);
|
||||
}
|
||||
entity.setGrayMode("MEMBERS");
|
||||
entity.setGrayMemberIds(toJson(memberIds));
|
||||
@ -265,7 +265,7 @@ public class RnBundleController {
|
||||
entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED);
|
||||
RnBundleEntity saved = bundleRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"RN_BUNDLE",
|
||||
saved.getId(),
|
||||
"GRAY_UPDATE",
|
||||
|
||||
@ -69,7 +69,7 @@ public class UnifiedReleaseController {
|
||||
}
|
||||
AppVersionEntity entity = new AppVersionEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setPlatform(item.platform());
|
||||
entity.setVersionName(item.versionName());
|
||||
entity.setVersionCode(item.versionCode());
|
||||
@ -90,7 +90,7 @@ public class UnifiedReleaseController {
|
||||
}
|
||||
AppVersionEntity saved = appVersionRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"APP_VERSION",
|
||||
saved.getId(),
|
||||
"UPLOAD",
|
||||
@ -117,7 +117,7 @@ public class UnifiedReleaseController {
|
||||
|
||||
RnBundleEntity entity = new RnBundleEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setModuleId(item.moduleId());
|
||||
entity.setPlatform(item.platform());
|
||||
entity.setVersion(item.version());
|
||||
@ -130,7 +130,7 @@ public class UnifiedReleaseController {
|
||||
entity.setCreatedAt(LocalDateTime.now());
|
||||
RnBundleEntity saved = rnBundleRepository.save(entity);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"RN_BUNDLE",
|
||||
saved.getId(),
|
||||
"UPLOAD",
|
||||
|
||||
@ -9,7 +9,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "update_gray_member", uniqueConstraints = {
|
||||
@jakarta.persistence.UniqueConstraint(columnNames = {"appId", "groupName", "userId"})
|
||||
@jakarta.persistence.UniqueConstraint(columnNames = {"appKey", "groupName", "userId"})
|
||||
})
|
||||
public class AppGrayMemberEntity {
|
||||
|
||||
@ -17,7 +17,7 @@ public class AppGrayMemberEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(length = 64)
|
||||
private String groupName;
|
||||
@ -37,8 +37,8 @@ public class AppGrayMemberEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getGroupName() { return groupName; }
|
||||
public void setGroupName(String groupName) { this.groupName = groupName; }
|
||||
|
||||
@ -9,7 +9,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "update_publish_config", uniqueConstraints = {
|
||||
@jakarta.persistence.UniqueConstraint(columnNames = {"appId"})
|
||||
@jakarta.persistence.UniqueConstraint(columnNames = {"appKey"})
|
||||
})
|
||||
public class AppPublishConfigEntity {
|
||||
|
||||
@ -17,7 +17,7 @@ public class AppPublishConfigEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String configJson;
|
||||
@ -28,8 +28,8 @@ public class AppPublishConfigEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getConfigJson() { return configJson; }
|
||||
public void setConfigJson(String configJson) { this.configJson = configJson; }
|
||||
|
||||
@ -5,7 +5,7 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "update_store_config", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"appId", "storeType"})
|
||||
@UniqueConstraint(columnNames = {"appKey", "storeType"})
|
||||
})
|
||||
public class AppStoreConfigEntity {
|
||||
|
||||
@ -27,7 +27,7 @@ public class AppStoreConfigEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 16)
|
||||
@ -58,8 +58,8 @@ public class AppStoreConfigEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public StoreType getStoreType() { return storeType; }
|
||||
public void setStoreType(StoreType storeType) { this.storeType = storeType; }
|
||||
|
||||
@ -15,13 +15,13 @@ public class AppVersionEntity {
|
||||
public enum Platform { ANDROID, IOS, HARMONY }
|
||||
public enum PublishStatus { DRAFT, PUBLISHED, DEPRECATED }
|
||||
/** Per-store review state used in storeReviewStatus JSON values. */
|
||||
public enum StoreReviewState { PENDING, UNDER_REVIEW, APPROVED, REJECTED }
|
||||
public enum StoreReviewState { PENDING, SUBMITTING, UNDER_REVIEW, APPROVED, REJECTED }
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 16)
|
||||
@ -107,8 +107,8 @@ public class AppVersionEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public Platform getPlatform() { return platform; }
|
||||
public void setPlatform(Platform platform) { this.platform = platform; }
|
||||
|
||||
@ -19,7 +19,7 @@ public class RnBundleEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String moduleId;
|
||||
@ -73,8 +73,8 @@ public class RnBundleEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getModuleId() { return moduleId; }
|
||||
public void setModuleId(String moduleId) { this.moduleId = moduleId; }
|
||||
|
||||
@ -15,7 +15,7 @@ public class UpdateOperationLogEntity {
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false, length = 64)
|
||||
private String appId;
|
||||
private String appKey;
|
||||
|
||||
@Column(nullable = false, length = 32)
|
||||
private String resourceType;
|
||||
@ -41,8 +41,8 @@ public class UpdateOperationLogEntity {
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppKey() { return appKey; }
|
||||
public void setAppKey(String appKey) { this.appKey = appKey; }
|
||||
|
||||
public String getResourceType() { return resourceType; }
|
||||
public void setResourceType(String resourceType) { this.resourceType = resourceType; }
|
||||
|
||||
@ -6,5 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
|
||||
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> {
|
||||
|
||||
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;
|
||||
|
||||
public interface AppVersionRepository extends JpaRepository<AppVersionEntity, String> {
|
||||
List<AppVersionEntity> findByAppIdAndPlatformOrderByVersionCodeDesc(
|
||||
String appId, AppVersionEntity.Platform platform);
|
||||
Optional<AppVersionEntity> findTopByAppIdAndPlatformAndPublishStatusOrderByVersionCodeDesc(
|
||||
String appId, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status);
|
||||
Optional<AppVersionEntity> findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc(
|
||||
String appId,
|
||||
List<AppVersionEntity> findByAppKeyAndPlatformOrderByVersionCodeDesc(
|
||||
String appKey, AppVersionEntity.Platform platform);
|
||||
Optional<AppVersionEntity> findTopByAppKeyAndPlatformAndPublishStatusOrderByVersionCodeDesc(
|
||||
String appKey, AppVersionEntity.Platform platform, AppVersionEntity.PublishStatus status);
|
||||
Optional<AppVersionEntity> findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc(
|
||||
String appKey,
|
||||
AppVersionEntity.Platform platform,
|
||||
AppVersionEntity.PublishStatus status,
|
||||
int versionCode);
|
||||
|
||||
Optional<AppVersionEntity> findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc(
|
||||
String appId,
|
||||
Optional<AppVersionEntity> findTopByAppKeyAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc(
|
||||
String appKey,
|
||||
AppVersionEntity.Platform platform,
|
||||
AppVersionEntity.PublishStatus status,
|
||||
int versionCode);
|
||||
|
||||
@ -8,12 +8,12 @@ import java.util.Optional;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public interface RnBundleRepository extends JpaRepository<RnBundleEntity, String> {
|
||||
List<RnBundleEntity> findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(
|
||||
String appId, String moduleId, RnBundleEntity.Platform platform);
|
||||
List<RnBundleEntity> findByAppIdAndModuleIdOrderByCreatedAtDesc(String appId, String moduleId);
|
||||
List<RnBundleEntity> findByAppIdOrderByCreatedAtDesc(String appId);
|
||||
Optional<RnBundleEntity> findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
|
||||
String appId, String moduleId, RnBundleEntity.Platform platform, RnBundleEntity.PublishStatus status);
|
||||
List<RnBundleEntity> findByAppKeyAndModuleIdAndPlatformOrderByCreatedAtDesc(
|
||||
String appKey, String moduleId, RnBundleEntity.Platform platform);
|
||||
List<RnBundleEntity> findByAppKeyAndModuleIdOrderByCreatedAtDesc(String appKey, String moduleId);
|
||||
List<RnBundleEntity> findByAppKeyOrderByCreatedAtDesc(String appKey);
|
||||
Optional<RnBundleEntity> findTopByAppKeyAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
|
||||
String appKey, String moduleId, RnBundleEntity.Platform platform, RnBundleEntity.PublishStatus status);
|
||||
|
||||
List<RnBundleEntity> findByPublishStatusAndScheduledPublishAtBefore(
|
||||
RnBundleEntity.PublishStatus status, LocalDateTime before);
|
||||
|
||||
@ -7,5 +7,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.List;
|
||||
|
||||
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 RnBundleRepository rnBundleRepository;
|
||||
private final UpdateOperationLogService operationLogService;
|
||||
private final StoreReviewImNotifier storeReviewImNotifier;
|
||||
|
||||
public AppStoreService(AppStoreConfigRepository configRepo,
|
||||
AppVersionRepository versionRepo,
|
||||
RnBundleRepository rnBundleRepository,
|
||||
UpdateOperationLogService operationLogService) {
|
||||
UpdateOperationLogService operationLogService,
|
||||
StoreReviewImNotifier storeReviewImNotifier) {
|
||||
this.configRepo = configRepo;
|
||||
this.versionRepo = versionRepo;
|
||||
this.rnBundleRepository = rnBundleRepository;
|
||||
this.operationLogService = operationLogService;
|
||||
this.storeReviewImNotifier = storeReviewImNotifier;
|
||||
}
|
||||
|
||||
// ── Store config CRUD ────────────────────────────────────────────────────
|
||||
|
||||
public List<AppStoreConfigEntity> getConfigs(String appKey) {
|
||||
return configRepo.findByAppId(appKey);
|
||||
return configRepo.findByAppKey(appKey);
|
||||
}
|
||||
|
||||
public AppStoreConfigEntity saveConfig(String appKey,
|
||||
AppStoreConfigEntity.StoreType storeType,
|
||||
String configJson,
|
||||
boolean enabled) {
|
||||
boolean isCreate = configRepo.findByAppIdAndStoreType(appKey, storeType).isEmpty();
|
||||
boolean isCreate = configRepo.findByAppKeyAndStoreType(appKey, storeType).isEmpty();
|
||||
AppStoreConfigEntity entity = configRepo
|
||||
.findByAppIdAndStoreType(appKey, storeType)
|
||||
.findByAppKeyAndStoreType(appKey, storeType)
|
||||
.orElseGet(AppStoreConfigEntity::new);
|
||||
|
||||
if (entity.getId() == null) {
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setStoreType(storeType);
|
||||
}
|
||||
entity.setConfigJson(configJson);
|
||||
@ -81,7 +84,7 @@ public class AppStoreService {
|
||||
}
|
||||
|
||||
public void deleteConfig(String appKey, AppStoreConfigEntity.StoreType storeType) {
|
||||
configRepo.findByAppIdAndStoreType(appKey, storeType).ifPresent(cfg -> {
|
||||
configRepo.findByAppKeyAndStoreType(appKey, storeType).ifPresent(cfg -> {
|
||||
configRepo.delete(cfg);
|
||||
operationLogService.record(
|
||||
appKey,
|
||||
@ -118,7 +121,13 @@ public class AppStoreService {
|
||||
|
||||
Map<String, Object> reviewMap = new LinkedHashMap<>();
|
||||
for (String store : storeTypes) {
|
||||
reviewMap.put(store, reviewPayload(AppVersionEntity.StoreReviewState.PENDING.name(), null));
|
||||
reviewMap.put(store, reviewPayload(
|
||||
AppVersionEntity.StoreReviewState.PENDING.name(),
|
||||
null,
|
||||
"QUEUED",
|
||||
null,
|
||||
null,
|
||||
LocalDateTime.now().toString()));
|
||||
}
|
||||
v.setStoreSubmitTargets(mapper.writeValueAsString(storeTypes));
|
||||
v.setStoreReviewStatus(mapper.writeValueAsString(reviewMap));
|
||||
@ -129,10 +138,10 @@ public class AppStoreService {
|
||||
}
|
||||
AppVersionEntity saved = versionRepo.save(v);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"APP_VERSION",
|
||||
saved.getId(),
|
||||
"STORE_SUBMIT",
|
||||
"STORE_SUBMIT_REQUEST",
|
||||
null,
|
||||
Map.of(
|
||||
"storeTypes", storeTypes,
|
||||
@ -140,6 +149,16 @@ public class AppStoreService {
|
||||
"scheduledAt", saved.getStoreSubmitScheduledAt() == null ? "" : saved.getStoreSubmitScheduledAt().toString(),
|
||||
"autoPublishAfterReview", saved.isAutoPublishAfterReview()
|
||||
));
|
||||
storeReviewImNotifier.notifyStoreReviewChange(
|
||||
saved.getAppKey(),
|
||||
saved.getId(),
|
||||
null,
|
||||
AppVersionEntity.StoreReviewState.PENDING.name(),
|
||||
null,
|
||||
"QUEUED",
|
||||
null,
|
||||
saved.getPublishStatus().name(),
|
||||
"store_submit_requested");
|
||||
return saved;
|
||||
}
|
||||
|
||||
@ -152,7 +171,7 @@ public class AppStoreService {
|
||||
* Returns a map of storeType -> configJson (as parsed map, not raw string).
|
||||
*/
|
||||
public Map<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<>();
|
||||
for (AppStoreConfigEntity cfg : configs) {
|
||||
if (cfg.getStoreType() == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) {
|
||||
@ -167,7 +186,7 @@ public class AppStoreService {
|
||||
}
|
||||
|
||||
public Map<String, String> getReviewWebhookConfig(String appKey) throws Exception {
|
||||
AppStoreConfigEntity cfg = configRepo.findByAppIdAndStoreType(
|
||||
AppStoreConfigEntity cfg = configRepo.findByAppKeyAndStoreType(
|
||||
appKey, AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK).orElse(null);
|
||||
if (cfg == null || !cfg.isEnabled() || cfg.getConfigJson() == null || cfg.getConfigJson().isBlank()) {
|
||||
return Map.of();
|
||||
@ -179,7 +198,7 @@ public class AppStoreService {
|
||||
if (storeType == null || storeType == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) {
|
||||
return "";
|
||||
}
|
||||
return configRepo.findByAppIdAndStoreType(appKey, storeType)
|
||||
return configRepo.findByAppKeyAndStoreType(appKey, storeType)
|
||||
.filter(AppStoreConfigEntity::isEnabled)
|
||||
.map(AppStoreConfigEntity::getConfigJson)
|
||||
.map(this::extractJumpUrl)
|
||||
@ -205,14 +224,24 @@ public class AppStoreService {
|
||||
AppVersionEntity v = versionRepo.findById(versionId).orElseThrow();
|
||||
|
||||
Map<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));
|
||||
|
||||
if (v.isAutoPublishAfterReview() && allApproved(v, reviewMap)) {
|
||||
v.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED);
|
||||
log.info("Auto-published version {} after all stores approved", versionId);
|
||||
operationLogService.record(
|
||||
v.getAppId(),
|
||||
v.getAppKey(),
|
||||
"APP_VERSION",
|
||||
v.getId(),
|
||||
"AUTO_PUBLISH",
|
||||
@ -225,7 +254,7 @@ public class AppStoreService {
|
||||
|
||||
AppVersionEntity saved = versionRepo.save(v);
|
||||
operationLogService.record(
|
||||
saved.getAppId(),
|
||||
saved.getAppKey(),
|
||||
"APP_VERSION",
|
||||
saved.getId(),
|
||||
"STORE_REVIEW",
|
||||
@ -233,9 +262,68 @@ public class AppStoreService {
|
||||
Map.of(
|
||||
"storeType", storeType,
|
||||
"reviewState", state.name(),
|
||||
"stage", stage,
|
||||
"batchId", batchId,
|
||||
"publishStatus", saved.getPublishStatus().name()
|
||||
));
|
||||
sendWebhook(saved, storeType, state, reason);
|
||||
storeReviewImNotifier.notifyStoreReviewChange(
|
||||
saved.getAppKey(),
|
||||
saved.getId(),
|
||||
storeType,
|
||||
state.name(),
|
||||
reason,
|
||||
stage,
|
||||
batchId,
|
||||
saved.getPublishStatus().name(),
|
||||
"store_review_changed");
|
||||
return saved;
|
||||
}
|
||||
|
||||
public AppVersionEntity updateStoreSubmissionStage(String versionId,
|
||||
String storeType,
|
||||
String stage,
|
||||
String reason,
|
||||
String batchId) throws Exception {
|
||||
AppVersionEntity v = versionRepo.findById(versionId).orElseThrow();
|
||||
|
||||
Map<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;
|
||||
}
|
||||
|
||||
@ -252,7 +340,7 @@ public class AppStoreService {
|
||||
versionRepo.save(v);
|
||||
log.info("Scheduled publish executed for version {}", v.getId());
|
||||
operationLogService.record(
|
||||
v.getAppId(),
|
||||
v.getAppKey(),
|
||||
"APP_VERSION",
|
||||
v.getId(),
|
||||
"SCHEDULE_PUBLISH",
|
||||
@ -269,7 +357,7 @@ public class AppStoreService {
|
||||
rnBundleRepository.save(bundle);
|
||||
log.info("Scheduled publish executed for RN bundle {}", bundle.getId());
|
||||
operationLogService.record(
|
||||
bundle.getAppId(),
|
||||
bundle.getAppKey(),
|
||||
"RN_BUNDLE",
|
||||
bundle.getId(),
|
||||
"SCHEDULE_PUBLISH",
|
||||
@ -284,7 +372,7 @@ public class AppStoreService {
|
||||
String url = v.getWebhookUrl();
|
||||
if (url == null || url.isBlank()) {
|
||||
try {
|
||||
Map<String, String> shared = getReviewWebhookConfig(v.getAppId());
|
||||
Map<String, String> shared = getReviewWebhookConfig(v.getAppKey());
|
||||
url = shared.get("webhookUrl");
|
||||
} catch (Exception ignored) {
|
||||
url = null;
|
||||
@ -296,7 +384,7 @@ public class AppStoreService {
|
||||
String body = mapper.writeValueAsString(Map.of(
|
||||
"event", "store_review_update",
|
||||
"versionId", v.getId(),
|
||||
"appKey", v.getAppId(),
|
||||
"appKey", v.getAppKey(),
|
||||
"versionName", v.getVersionName(),
|
||||
"storeType", storeType,
|
||||
"reviewState", state.name(),
|
||||
@ -304,7 +392,7 @@ public class AppStoreService {
|
||||
"publishStatus", v.getPublishStatus().name(),
|
||||
"timestamp", System.currentTimeMillis()
|
||||
));
|
||||
String secret = resolveWebhookSecret(v.getAppId());
|
||||
String secret = resolveWebhookSecret(v.getAppKey());
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("Content-Type", "application/json")
|
||||
@ -354,12 +442,47 @@ public class AppStoreService {
|
||||
}
|
||||
|
||||
private Map<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<>();
|
||||
payload.put("state", state);
|
||||
payload.put("reason", reason == null ? "" : reason);
|
||||
payload.put("stage", stage == null ? "" : stage);
|
||||
payload.put("batchId", batchId == null ? "" : batchId);
|
||||
payload.put("submittedAt", submittedAt == null ? "" : submittedAt);
|
||||
payload.put("updatedAt", updatedAt == null ? "" : updatedAt);
|
||||
return payload;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<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) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
|
||||
@ -45,10 +45,10 @@ public class PublishConfigService {
|
||||
}
|
||||
|
||||
public AppPublishConfigEntity getConfig(String appKey) {
|
||||
return configRepository.findByAppId(appKey).orElseGet(() -> {
|
||||
return configRepository.findByAppKey(appKey).orElseGet(() -> {
|
||||
AppPublishConfigEntity entity = new AppPublishConfigEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setConfigJson(defaultConfigJson());
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
return entity;
|
||||
@ -56,10 +56,10 @@ public class PublishConfigService {
|
||||
}
|
||||
|
||||
public AppPublishConfigEntity saveConfig(String appKey, Map<String, Object> body) {
|
||||
AppPublishConfigEntity entity = configRepository.findByAppId(appKey).orElseGet(AppPublishConfigEntity::new);
|
||||
AppPublishConfigEntity entity = configRepository.findByAppKey(appKey).orElseGet(AppPublishConfigEntity::new);
|
||||
if (entity.getId() == null) {
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
}
|
||||
try {
|
||||
entity.setConfigJson(objectMapper.writeValueAsString(body == null ? Map.of() : body));
|
||||
@ -71,7 +71,7 @@ public class PublishConfigService {
|
||||
}
|
||||
|
||||
public JsonNode getConfigNode(String appKey) {
|
||||
AppPublishConfigEntity entity = configRepository.findByAppId(appKey).orElse(null);
|
||||
AppPublishConfigEntity entity = configRepository.findByAppKey(appKey).orElse(null);
|
||||
if (entity == null || entity.getConfigJson() == null || entity.getConfigJson().isBlank()) {
|
||||
try {
|
||||
return objectMapper.readTree(defaultConfigJson());
|
||||
@ -91,7 +91,7 @@ public class PublishConfigService {
|
||||
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(Locale.ROOT);
|
||||
String normalizedGroup = groupName == null ? "" : groupName.trim().toLowerCase(Locale.ROOT);
|
||||
|
||||
List<AppGrayMemberEntity> members = grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appKey)
|
||||
List<AppGrayMemberEntity> members = grayMemberRepository.findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(appKey)
|
||||
.stream()
|
||||
.filter(item -> normalizedGroup.isBlank()
|
||||
|| safe(item.getGroupName()).toLowerCase(Locale.ROOT).contains(normalizedGroup))
|
||||
@ -170,7 +170,7 @@ public class PublishConfigService {
|
||||
throw new IllegalStateException("Invalid gray member payload");
|
||||
}
|
||||
grayMemberRepository.deleteAll(
|
||||
grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appKey));
|
||||
grayMemberRepository.findByAppKeyOrderByGroupNameAscNameAscUserIdAsc(appKey));
|
||||
|
||||
List<AppGrayMemberEntity> saved = new ArrayList<>();
|
||||
for (GrayMemberGroupPayload group : groups) {
|
||||
@ -181,7 +181,7 @@ public class PublishConfigService {
|
||||
}
|
||||
AppGrayMemberEntity entity = new AppGrayMemberEntity();
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setAppId(appKey);
|
||||
entity.setAppKey(appKey);
|
||||
entity.setGroupName(groupName);
|
||||
entity.setUserId(member.userId().trim());
|
||||
entity.setName(member.name() == null ? "" : member.name().trim());
|
||||
|
||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
正在加载...
在新工单中引用
屏蔽一个用户