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

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

查看文件

@ -1 +1 @@
17
21

查看文件

@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1.7
ARG SERVICE_MODULE=tenant-service
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
查看文件

@ -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}
"""

查看文件

@ -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` | 设备推送 tokenappId + userId + vendor 唯一) |
| `push_device_token` | 设备推送 tokenappKey + userId + vendor 唯一) |
### 支持厂商
`HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`iOS/ `FCM`
`HUAWEI` / `XIAOMI` / `OPPO` / `VIVO` / `HONOR` / `APNS`iOS
### 接口
@ -366,7 +366,7 @@ Frame 格式:
**注册 token**
```
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
&currentVersionCode=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
&currentVersion=1.0.0
@ -517,7 +520,7 @@ GET /api/v1/rn/update/check
**上传 Bundlemultipart/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");

查看文件

@ -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(
.map(config -> profileFor(config, vendor.name(), routeType)
.map(profile -> new PushSendOptions(
profile.path("key").asText(""),
routeType,
channelId,
route.path("category").asText(""),
route.path("priority").asText(""));
})
.orElseGet(() -> new PushSendOptions(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 java.util.Optional.ofNullable(fallback);
}
private Boolean readBoolean(JsonNode node, String key) {
if (node == null || node.isNull()) {
return null;
}
JsonNode value = node.get(key);
if (value == null || value.isNull()) {
return null;
}
return value.asBoolean();
}
private Integer readInteger(JsonNode node, String key) {
if (node == null || node.isNull()) {
return null;
}
JsonNode value = node.get(key);
if (value == null || value.isNull()) {
return null;
}
if (value.isInt() || value.isLong() || value.isNumber()) {
return value.asInt();
}
String text = value.asText("");
if (text.isBlank()) {
return null;
}
try {
return Integer.parseInt(text.trim());
} catch (NumberFormatException e) {
return null;
}
}
return "";
private String mapPriority(String importance) {
return switch (importance == null ? "" : importance.trim().toUpperCase()) {
case "HIGH", "MAX" -> "HIGH";
case "LOW", "MIN" -> "LOW";
default -> "DEFAULT";
};
}
public List<DeviceTokenEntity> selectedPushTargets(String appKey, String userId) {
@ -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));
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("&notify_type=1")
.append("&extra.notify_foreground=1");
Integer notifyType = options != null ? options.notifyType() : null;
form.append("&notify_type=").append(notifyType == null ? 1 : Math.max(notifyType, 0));
if (!channelId.isBlank()) {
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);
}
if (routing != null && routing.isObject()) {
node.set("routing", routing);
} else if (!node.has("routing")) {
node.set("routing", defaultPushRouting());
JsonNode inputProfiles = pushConfig.path("profiles");
if (inputProfiles != null && inputProfiles.isArray()) {
inputProfiles.forEach(profile -> profiles.add(profile.deepCopy()));
}
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 中更改的文件太多 显示更多