chore: sync local changes

这个提交包含在:
XuqmGroup 2026-05-07 19:39:42 +08:00
父节点 9cb352bb99
当前提交 77dafd76bf
共有 85 个文件被更改,包括 1343 次插入1212 次删除

查看文件

@ -13,11 +13,11 @@ public final class AppRequestSignatureUtil {
private AppRequestSignatureUtil() {
}
public static String payload(String appId,
public static String payload(String appKey,
String userId,
long timestamp,
String nonce) {
return normalize(appId) + '\n'
return normalize(appKey) + '\n'
+ normalize(userId) + '\n'
+ timestamp + '\n'
+ normalize(nonce);

查看文件

@ -19,8 +19,8 @@ public class DemoAuthController {
@PostMapping("/register")
public ApiResponse<Map<String, Object>> register(@RequestBody RegisterRequest body) {
if (body.appId() == null || body.appId().isBlank()) {
return ApiResponse.badRequest("appId is required");
if (body.appKey() == null || body.appKey().isBlank()) {
return ApiResponse.badRequest("appKey is required");
}
if (body.userId() == null || body.userId().isBlank()) {
return ApiResponse.badRequest("userId is required");
@ -30,15 +30,15 @@ public class DemoAuthController {
}
DemoAuthService.AuthResult result = authService.register(
body.appId(), body.userId(), body.password(), body.nickname());
body.appKey(), body.userId(), body.password(), body.nickname());
return ApiResponse.success(buildResponse(result));
}
@PostMapping("/login")
public ApiResponse<Map<String, Object>> login(@RequestBody LoginRequest body) {
if (body.appId() == null || body.appId().isBlank()) {
return ApiResponse.badRequest("appId is required");
if (body.appKey() == null || body.appKey().isBlank()) {
return ApiResponse.badRequest("appKey is required");
}
if (body.userId() == null || body.userId().isBlank()) {
return ApiResponse.badRequest("userId is required");
@ -48,7 +48,7 @@ public class DemoAuthController {
}
DemoAuthService.AuthResult result = authService.login(
body.appId(), body.userId(), body.password());
body.appKey(), body.userId(), body.password());
return ApiResponse.success(buildResponse(result));
}
@ -63,8 +63,8 @@ public class DemoAuthController {
@PostMapping("/reset-password")
public ApiResponse<Void> resetPassword(@RequestBody ResetPasswordRequest body) {
if (body.appId() == null || body.appId().isBlank()) {
return ApiResponse.badRequest("appId is required");
if (body.appKey() == null || body.appKey().isBlank()) {
return ApiResponse.badRequest("appKey is required");
}
if (body.userId() == null || body.userId().isBlank()) {
return ApiResponse.badRequest("userId is required");
@ -72,11 +72,11 @@ public class DemoAuthController {
if (body.newPassword() == null || body.newPassword().length() < 6) {
return ApiResponse.badRequest("password must be at least 6 characters");
}
authService.resetPassword(body.appId(), body.userId(), body.newPassword());
authService.resetPassword(body.appKey(), body.userId(), body.newPassword());
return ApiResponse.ok();
}
public record RegisterRequest(String appId, String userId, String password, String nickname) {}
public record LoginRequest(String appId, String userId, String password) {}
public record ResetPasswordRequest(String appId, String userId, String newPassword) {}
public record RegisterRequest(String appKey, String userId, String password, String nickname) {}
public record LoginRequest(String appKey, String userId, String password) {}
public record ResetPasswordRequest(String appKey, String userId, String newPassword) {}
}

查看文件

@ -20,46 +20,46 @@ public class DemoUserController {
@GetMapping("/user/profile")
public ApiResponse<DemoUserService.UserProfile> getProfile(
@RequestParam String appId,
@RequestParam String appKey,
Authentication auth) {
String userId = resolveUserId(auth);
return ApiResponse.success(userService.getProfile(appId, userId));
return ApiResponse.success(userService.getProfile(appKey, userId));
}
@PutMapping("/user/profile")
public ApiResponse<DemoUserService.UserProfile> updateProfile(
@RequestParam String appId,
@RequestParam String appKey,
Authentication auth,
@RequestBody UpdateProfileRequest body) {
String userId = resolveUserId(auth);
return ApiResponse.success(
userService.updateProfile(appId, userId, body.nickname(), body.avatar(), body.gender()));
userService.updateProfile(appKey, userId, body.nickname(), body.avatar(), body.gender()));
}
@PostMapping("/user/change-password")
public ApiResponse<Void> changePassword(
@RequestParam String appId,
@RequestParam String appKey,
Authentication auth,
@RequestBody ResetPasswordRequest body) {
String userId = resolveUserId(auth);
if (body.oldPassword() == null || body.newPassword() == null) {
return ApiResponse.badRequest("oldPassword and newPassword are required");
}
userService.resetPassword(appId, userId, body.oldPassword(), body.newPassword());
userService.resetPassword(appKey, userId, body.oldPassword(), body.newPassword());
return ApiResponse.ok();
}
@GetMapping("/users/search")
public ApiResponse<List<DemoUserService.UserProfile>> searchUsers(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String keyword) {
return ApiResponse.success(userService.searchUsers(appId, keyword));
return ApiResponse.success(userService.searchUsers(appKey, keyword));
}
@GetMapping("/users/members")
public ApiResponse<List<DemoUserService.UserProfile>> listMembers(
@RequestParam String appId) {
return ApiResponse.success(userService.listMembers(appId));
@RequestParam String appKey) {
return ApiResponse.success(userService.listMembers(appKey));
}
private String resolveUserId(Authentication auth) {

查看文件

@ -31,14 +31,14 @@ public class DemoAppSecretClient {
this.restTemplate = restTemplate;
}
public String getAppSecret(String appId) {
return cache.computeIfAbsent(appId, this::fetchAppSecret);
public String getAppSecret(String appKey) {
return cache.computeIfAbsent(appKey, this::fetchAppSecret);
}
private String fetchAppSecret(String appId) {
private String fetchAppSecret(String appKey) {
String url = UriComponentsBuilder.fromHttpUrl(tenantServiceUrl)
.path("/api/internal/sdk/apps/{appId}/secret")
.buildAndExpand(appId)
.path("/api/internal/sdk/apps/{appKey}/secret")
.buildAndExpand(appKey)
.toUriString();
HttpHeaders headers = new HttpHeaders();
headers.set("X-Internal-Token", internalToken);
@ -58,6 +58,6 @@ public class DemoAppSecretClient {
} catch (RestClientException e) {
throw new BusinessException(502, "Failed to resolve app secret: " + e.getMessage());
}
throw new BusinessException(502, "Failed to resolve app secret for appId: " + appId);
throw new BusinessException(502, "Failed to resolve app secret for appKey: " + appKey);
}
}

查看文件

@ -54,17 +54,17 @@ public class DemoAuthService {
public record AuthResult(String demoToken, String imToken, UserProfile profile) {}
public record ImCredential(String token) {}
public record UserProfile(String appId, String userId, String nickname, String avatar, String gender) {}
public record UserProfile(String appKey, String userId, String nickname, String avatar, String gender) {}
@Transactional
public AuthResult register(String appId, String userId, String password, String nickname) {
if (userRepository.existsByAppIdAndUserId(appId, userId)) {
public AuthResult register(String appKey, String userId, String password, String nickname) {
if (userRepository.existsByAppIdAndUserId(appKey, userId)) {
throw new BusinessException(409, "User already exists: " + userId);
}
DemoUserEntity user = new DemoUserEntity();
user.setId(UUID.randomUUID().toString());
user.setAppId(appId);
user.setAppId(appKey);
user.setUserId(userId);
user.setPasswordHash(passwordEncoder.encode(password));
user.setNickname(nickname != null ? nickname : userId);
@ -72,8 +72,8 @@ public class DemoAuthService {
user.setCreatedAt(Instant.now());
userRepository.save(user);
String demoToken = generateDemoToken(appId, userId);
ImCredential imCredential = callImServiceLogin(appId, userId);
String demoToken = generateDemoToken(appKey, userId);
ImCredential imCredential = callImServiceLogin(appKey, userId);
return new AuthResult(
demoToken,
@ -83,16 +83,16 @@ public class DemoAuthService {
}
@Transactional(readOnly = true)
public AuthResult login(String appId, String userId, String password) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
public AuthResult login(String appKey, String userId, String password) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(401, "Invalid credentials"));
if (!passwordEncoder.matches(password, user.getPasswordHash())) {
throw new BusinessException(401, "Invalid credentials");
}
String demoToken = generateDemoToken(appId, userId);
ImCredential imCredential = callImServiceLogin(appId, userId);
String demoToken = generateDemoToken(appKey, userId);
ImCredential imCredential = callImServiceLogin(appKey, userId);
return new AuthResult(
demoToken,
@ -102,30 +102,30 @@ public class DemoAuthService {
}
@Transactional
public void resetPassword(String appId, String userId, String newPassword) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
public void resetPassword(String appKey, String userId, String newPassword) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "User not found: " + userId));
user.setPasswordHash(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
private String generateDemoToken(String appId, String userId) {
return jwtUtil.generate(userId, Map.of("appId", appId, "role", "USER"));
private String generateDemoToken(String appKey, String userId) {
return jwtUtil.generate(userId, Map.of("appKey", appKey, "role", "USER"));
}
/**
* Calls im-service to ensure the IM account exists and obtain an IM token.
*/
private ImCredential callImServiceLogin(String appId, String userId) {
private ImCredential callImServiceLogin(String appKey, String userId) {
long timestamp = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString();
String appSecret = appSecretClient.getAppSecret(appId);
String payload = AppRequestSignatureUtil.payload(appId, userId, timestamp, nonce);
String appSecret = appSecretClient.getAppSecret(appKey);
String payload = AppRequestSignatureUtil.payload(appKey, userId, timestamp, nonce);
String signature = AppRequestSignatureUtil.sign(appSecret, payload);
URI uri = UriComponentsBuilder.fromHttpUrl(imServiceUrl)
.path("/api/im/auth/login")
.queryParam("appId", appId)
.queryParam("appKey", appKey)
.queryParam("userId", userId)
.encode()
.build()
@ -151,10 +151,10 @@ public class DemoAuthService {
}
return new ImCredential(token);
}
log.warn("im-service login returned unexpected response for appId={} userId={}: {}", appId, userId, body);
log.warn("im-service login returned unexpected response for appKey={} userId={}: {}", appKey, userId, body);
throw new BusinessException(502, "Failed to acquire IM token");
} catch (RestClientException e) {
log.error("Failed to call im-service login for appId={} userId={}: {}", appId, userId, e.getMessage());
log.error("Failed to call im-service login for appKey={} userId={}: {}", appKey, userId, e.getMessage());
throw new BusinessException(502, "Failed to acquire IM token");
}
}

查看文件

@ -20,18 +20,18 @@ public class DemoUserService {
this.passwordEncoder = passwordEncoder;
}
public record UserProfile(String appId, String userId, String nickname, String avatar, String gender) {}
public record UserProfile(String appKey, String userId, String nickname, String avatar, String gender) {}
@Transactional(readOnly = true)
public UserProfile getProfile(String appId, String userId) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
public UserProfile getProfile(String appKey, String userId) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "User not found"));
return toProfile(user);
}
@Transactional
public UserProfile updateProfile(String appId, String userId, String nickname, String avatar, String gender) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
public UserProfile updateProfile(String appKey, String userId, String nickname, String avatar, String gender) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "User not found"));
if (nickname != null && !nickname.isBlank()) {
@ -53,8 +53,8 @@ public class DemoUserService {
}
@Transactional
public void resetPassword(String appId, String userId, String oldPassword, String newPassword) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
public void resetPassword(String appKey, String userId, String oldPassword, String newPassword) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "User not found"));
if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) {
@ -69,19 +69,19 @@ public class DemoUserService {
}
@Transactional(readOnly = true)
public List<UserProfile> searchUsers(String appId, String keyword) {
public List<UserProfile> searchUsers(String appKey, String keyword) {
if (keyword == null || keyword.isBlank()) {
throw new BusinessException(400, "Search keyword must not be blank");
}
return userRepository.searchByKeyword(appId, keyword.trim())
return userRepository.searchByKeyword(appKey, keyword.trim())
.stream()
.map(this::toProfile)
.toList();
}
@Transactional(readOnly = true)
public List<UserProfile> listMembers(String appId) {
return userRepository.findAllByAppIdOrderByCreatedAtAsc(appId)
public List<UserProfile> listMembers(String appKey) {
return userRepository.findAllByAppIdOrderByCreatedAtAsc(appKey)
.stream()
.map(this::toProfile)
.toList();

查看文件

@ -35,7 +35,7 @@ jwt:
expiration: 3153600000000
demo:
tenant-service-url: ${TENANT_SERVICE_URL:http://127.0.0.1:8081}
tenant-service-url: ${TENANT_SERVICE_URL:http://127.0.0.1:9001}
internal-token: ${SDK_INTERNAL_TOKEN:xuqm-internal-token}
im-service-url: ${IM_SERVICE_URL:http://127.0.0.1:8082}

查看文件

@ -41,7 +41,7 @@ public final class XuqmImServerSdk {
private final String baseUrl;
private final String pushBaseUrl;
private final String updateBaseUrl;
private final String appId;
private final String appKey;
private final String appSecret;
private final Supplier<String> bearerTokenSupplier;
@ -53,7 +53,7 @@ public final class XuqmImServerSdk {
this.baseUrl = trimTrailingSlash(builder.baseUrl);
this.pushBaseUrl = trimTrailingSlash(builder.pushBaseUrl == null ? builder.baseUrl : builder.pushBaseUrl);
this.updateBaseUrl = trimTrailingSlash(builder.updateBaseUrl == null ? builder.baseUrl : builder.updateBaseUrl);
this.appId = Objects.requireNonNull(builder.appId, "appId");
this.appKey = Objects.requireNonNull(builder.appKey, "appKey");
this.appSecret = Objects.requireNonNull(builder.appSecret, "appSecret");
this.bearerTokenSupplier = builder.bearerTokenSupplier;
}
@ -65,7 +65,7 @@ public final class XuqmImServerSdk {
public ImMessage sendMessage(SendMessageRequest request) {
ApiResponse<ImMessage> response = request(
"POST",
buildUri("/api/im/messages/send", Map.of("appId", appId)),
buildUri("/api/im/messages/send", Map.of("appKey", appKey)),
request,
authorizedHeaders(),
new TypeReference<>() {}
@ -76,7 +76,7 @@ public final class XuqmImServerSdk {
public ImMessage revokeMessage(String messageId) {
ApiResponse<ImMessage> response = request(
"POST",
buildUri("/api/im/messages/" + encode(messageId) + "/revoke", Map.of("appId", appId)),
buildUri("/api/im/messages/" + encode(messageId) + "/revoke", Map.of("appKey", appKey)),
null,
authorizedHeaders(),
new TypeReference<>() {}
@ -87,7 +87,7 @@ public final class XuqmImServerSdk {
public ImMessage editMessage(String messageId, String content) {
ApiResponse<ImMessage> response = request(
"PUT",
buildUri("/api/im/messages/" + encode(messageId), Map.of("appId", appId)),
buildUri("/api/im/messages/" + encode(messageId), Map.of("appKey", appKey)),
Map.of("content", content),
authorizedHeaders(),
new TypeReference<>() {}
@ -142,7 +142,7 @@ public final class XuqmImServerSdk {
public List<ConversationView> listConversations(int size) {
ApiResponse<List<ConversationView>> response = request(
"GET",
buildUri("/api/im/conversations", Map.of("appId", appId, "page", "0", "size", String.valueOf(size))),
buildUri("/api/im/conversations", Map.of("appKey", appKey, "page", "0", "size", String.valueOf(size))),
null,
authorizedHeaders(),
new TypeReference<>() {}
@ -318,7 +318,7 @@ public final class XuqmImServerSdk {
if (Math.abs(now - ts) > 5 * 60 * 1000L) {
return false;
}
String payload = appId + "\n" + timestamp + "\n" + normalize(nonce) + "\n" + sha256Hex(body);
String payload = appKey + "\n" + timestamp + "\n" + normalize(nonce) + "\n" + sha256Hex(body);
String expected = hmacSha256Hex(appSecret, payload);
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
@ -336,7 +336,7 @@ public final class XuqmImServerSdk {
longValue(root, "requestTime"),
root.get("payload"),
text(root, "signature"),
text(root, "appId")
text(root, "appKey")
);
} catch (JsonProcessingException e) {
throw new ImSdkException("Invalid callback body", e);
@ -399,7 +399,7 @@ public final class XuqmImServerSdk {
request(
"POST",
buildUri(pushBaseUrl, "/api/push/register", Map.of(
"appId", appId,
"appKey", appKey,
"userId", userId,
"vendor", vendor,
"token", token
@ -412,7 +412,7 @@ public final class XuqmImServerSdk {
public void sendPush(String userId, String title, String body, String payload) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
query.put("userId", userId);
query.put("title", title);
query.put("body", body);
@ -432,7 +432,7 @@ public final class XuqmImServerSdk {
ApiResponse<Map<String, Object>> response = request(
"GET",
buildUri(updateBaseUrl, "/api/v1/updates/app/check", Map.of(
"appId", appId,
"appKey", appKey,
"platform", platform,
"currentVersionCode", String.valueOf(currentVersionCode)
)),
@ -451,7 +451,7 @@ public final class XuqmImServerSdk {
boolean forceUpdate,
Path apkFile) {
Map<String, String> form = new LinkedHashMap<>();
form.put("appId", appId);
form.put("appKey", appKey);
form.put("platform", platform);
form.put("versionName", versionName);
form.put("versionCode", String.valueOf(versionCode));
@ -473,7 +473,7 @@ public final class XuqmImServerSdk {
public UnifiedReleaseResult uploadUnifiedRelease(UnifiedReleaseManifest manifest, Map<String, Path> files) {
Map<String, String> form = new LinkedHashMap<>();
form.put("appId", appId);
form.put("appKey", appKey);
try {
form.put("manifest", objectMapper.writeValueAsString(manifest));
} catch (JsonProcessingException e) {
@ -529,7 +529,7 @@ public final class XuqmImServerSdk {
public List<AppVersionView> listAppVersions(String platform) {
ApiResponse<List<AppVersionView>> response = request(
"GET",
buildUri(updateBaseUrl, "/api/v1/updates/app/list", Map.of("appId", appId, "platform", platform)),
buildUri(updateBaseUrl, "/api/v1/updates/app/list", Map.of("appKey", appKey, "platform", platform)),
null,
publicHeaders(),
new TypeReference<>() {}
@ -541,7 +541,7 @@ public final class XuqmImServerSdk {
ApiResponse<RnBundleView> response = request(
"GET",
buildUri(updateBaseUrl, "/api/v1/rn/update/check", Map.of(
"appId", appId,
"appKey", appKey,
"moduleId", moduleId,
"platform", platform,
"currentVersion", currentVersion
@ -561,7 +561,7 @@ public final class XuqmImServerSdk {
String note,
Path bundle) {
Map<String, String> form = new LinkedHashMap<>();
form.put("appId", appId);
form.put("appKey", appKey);
form.put("moduleId", moduleId);
form.put("platform", platform);
form.put("version", version);
@ -607,7 +607,7 @@ public final class XuqmImServerSdk {
public List<RnBundleView> listRnBundles(String moduleId, String platform) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
if (moduleId != null) {
query.put("moduleId", moduleId);
}
@ -1146,9 +1146,9 @@ public final class XuqmImServerSdk {
return response.data();
}
public void kickUsers(String appId, List<String> userIds) {
public void kickUsers(String appKey, List<String> userIds) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
request(
"POST",
buildUri("/api/im/admin/users/kick", query),
@ -1158,9 +1158,9 @@ public final class XuqmImServerSdk {
);
}
public List<ImMessage> batchSendMessage(String appId, List<String> toIds, String msgType, String content) {
public List<ImMessage> batchSendMessage(String appKey, List<String> toIds, String msgType, String content) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
ApiResponse<List<ImMessage>> response = request(
"POST",
buildUri("/api/im/admin/messages/batch-send", query),
@ -1171,9 +1171,9 @@ public final class XuqmImServerSdk {
return response.data();
}
public void adminSetMsgRead(String appId, String userId) {
public void adminSetMsgRead(String appKey, String userId) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
request(
"POST",
buildUri("/api/im/admin/messages/read", query),
@ -1183,9 +1183,9 @@ public final class XuqmImServerSdk {
);
}
public List<ImMessage> importMessages(String appId, List<ImportMessageRequest> requests) {
public List<ImMessage> importMessages(String appKey, List<ImportMessageRequest> requests) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
ApiResponse<List<ImMessage>> response = request(
"POST",
buildUri("/api/im/admin/messages/import", query),
@ -1196,9 +1196,9 @@ public final class XuqmImServerSdk {
return response.data();
}
public List<FriendCheckResult> checkFriends(String appId, List<String> friendIds) {
public List<FriendCheckResult> checkFriends(String appKey, List<String> friendIds) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
ApiResponse<List<FriendCheckResult>> response = request(
"POST",
buildUri("/api/im/friends/check", query),
@ -1648,7 +1648,7 @@ public final class XuqmImServerSdk {
private Map<String, String> appQuery() {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
return query;
}
@ -1668,7 +1668,7 @@ public final class XuqmImServerSdk {
private Map<String, String> queryParams(String msgType, String keyword, LocalDateTime startTime, LocalDateTime endTime, int page, int size) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
if (msgType != null && !msgType.isBlank()) {
query.put("msgType", msgType);
}
@ -1696,7 +1696,7 @@ public final class XuqmImServerSdk {
int size
) {
Map<String, String> query = new LinkedHashMap<>();
query.put("appId", appId);
query.put("appKey", appKey);
if (chatType != null && !chatType.isBlank()) {
query.put("chatType", chatType);
}
@ -1741,7 +1741,7 @@ public final class XuqmImServerSdk {
private String baseUrl = DEFAULT_BASE_URL;
private String pushBaseUrl;
private String updateBaseUrl;
private String appId;
private String appKey;
private String appSecret;
private Supplier<String> bearerTokenSupplier;
@ -1762,8 +1762,8 @@ public final class XuqmImServerSdk {
return this;
}
public Builder appId(String appId) {
this.appId = appId;
public Builder appKey(String appKey) {
this.appKey = appKey;
return this;
}
@ -1778,7 +1778,7 @@ public final class XuqmImServerSdk {
}
public XuqmImServerSdk build() {
Objects.requireNonNull(this.appId, "appId is required");
Objects.requireNonNull(this.appKey, "appKey is required");
Objects.requireNonNull(this.appSecret, "appSecret is required");
return new XuqmImServerSdk(this);
}
@ -1806,7 +1806,7 @@ public final class XuqmImServerSdk {
public record AccountView(
String id,
String appId,
String appKey,
String userId,
String nickname,
String gender,
@ -1825,7 +1825,7 @@ public final class XuqmImServerSdk {
public record FriendLinkView(
Long id,
String appId,
String appKey,
String userId,
String friendId,
String friendGroup,
@ -1836,7 +1836,7 @@ public final class XuqmImServerSdk {
public record FriendRequestView(
String id,
String appId,
String appKey,
String fromUserId,
String toUserId,
String remark,
@ -1847,7 +1847,7 @@ public final class XuqmImServerSdk {
public record BlacklistView(
String id,
String appId,
String appKey,
String userId,
String blockedUserId,
LocalDateTime createdAt
@ -1862,7 +1862,7 @@ public final class XuqmImServerSdk {
public record GroupView(
String id,
String appId,
String appKey,
String name,
String groupType,
String creatorId,
@ -1892,7 +1892,7 @@ public final class XuqmImServerSdk {
public record GroupJoinRequestView(
String id,
String appId,
String appKey,
String groupId,
String requesterId,
String remark,
@ -1908,7 +1908,7 @@ public final class XuqmImServerSdk {
long requestTime,
JsonNode payload,
String signature,
String appId
String appKey
) {
public boolean isType(String type) {
return type != null && callbackType != null && callbackType.equalsIgnoreCase(type);
@ -1941,7 +1941,7 @@ public final class XuqmImServerSdk {
public record AppVersionView(
String id,
String appId,
String appKey,
String platform,
String versionName,
int versionCode,
@ -1958,7 +1958,7 @@ public final class XuqmImServerSdk {
public record RnBundleView(
String id,
String appId,
String appKey,
String moduleId,
String platform,
String version,
@ -2013,7 +2013,7 @@ public final class XuqmImServerSdk {
public record ImMessage(
String id,
String appId,
String appKey,
String fromUserId,
String toId,
String chatType,
@ -2043,7 +2043,7 @@ public final class XuqmImServerSdk {
public record WebhookConfigView(
String id,
String appId,
String appKey,
String url,
String secret,
boolean enabled,
@ -2058,7 +2058,7 @@ public final class XuqmImServerSdk {
) {}
public record AppVersionUploadRequest(
String appId,
String appKey,
String platform,
String versionName,
int versionCode,
@ -2067,7 +2067,7 @@ public final class XuqmImServerSdk {
) {}
public record RnBundleUploadRequest(
String appId,
String appKey,
String moduleId,
String platform,
String version,
@ -2076,7 +2076,7 @@ public final class XuqmImServerSdk {
) {}
public record MessageReadCallbackPayload(
String appId,
String appKey,
String readerId,
String peerId,
String groupId,

查看文件

@ -63,15 +63,15 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
token = token.substring(7);
if (jwtUtil.isValid(token)) {
String userId = jwtUtil.getSubject(token);
String appId = "";
String appKey = "";
try {
Object claim = jwtUtil.parse(token).get("appId");
if (claim != null) appId = claim.toString();
Object claim = jwtUtil.parse(token).get("appKey");
if (claim != null) appKey = claim.toString();
} catch (Exception ignored) {}
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userId, null,
List.of(new SimpleGrantedAuthority("ROLE_USER")));
auth.setDetails(java.util.Map.of("appId", appId));
auth.setDetails(java.util.Map.of("appKey", appKey));
accessor.setUser(auth);
userPresenceService.markOnline(userId);
}

查看文件

@ -33,15 +33,15 @@ public class AccountController {
@GetMapping("/{userId}")
public ResponseEntity<ApiResponse<ImAccountEntity>> get(
@AuthenticationPrincipal String currentUserId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String userId) {
return ResponseEntity.ok(ApiResponse.success(accountService.getAccount(appId, userId)));
return ResponseEntity.ok(ApiResponse.success(accountService.getAccount(appKey, userId)));
}
@PutMapping("/{userId}")
public ResponseEntity<ApiResponse<ImAccountEntity>> update(
@AuthenticationPrincipal String currentUserId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String userId,
@RequestParam(required = false) String nickname,
@RequestParam(required = false) String avatar,
@ -50,31 +50,31 @@ public class AccountController {
throw new BusinessException(403, "Only the account owner can update profile");
}
return ResponseEntity.ok(ApiResponse.success(
accountService.updateAccount(appId, userId, nickname, avatar, gender)));
accountService.updateAccount(appKey, userId, nickname, avatar, gender)));
}
@GetMapping("/search")
public ResponseEntity<ApiResponse<List<ImAccountEntity>>> search(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String keyword,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(accountService.searchAccounts(appId, keyword, size)));
return ResponseEntity.ok(ApiResponse.success(accountService.searchAccounts(appKey, keyword, size)));
}
@PostMapping("/import")
public ResponseEntity<ApiResponse<ImAccountEntity>> importAccount(
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody ImportAccountRequest req) {
return ResponseEntity.ok(ApiResponse.success(
accountService.importAccount(appId, req.userId(), req.nickname(), req.avatar(), req.gender(), req.status())));
accountService.importAccount(appKey, req.userId(), req.nickname(), req.avatar(), req.gender(), req.status())));
}
@PostMapping("/import/batch")
public ResponseEntity<ApiResponse<List<ImAccountEntity>>> importAccounts(
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody List<ImportAccountRequest> req) {
return ResponseEntity.ok(ApiResponse.success(accountService.importAccounts(
appId,
appKey,
req == null ? List.of() : req.stream()
.map(item -> new ImAccountService.ImportAccountRequest(
item.userId(), item.nickname(), item.avatar(), item.gender(), item.status()))
@ -83,17 +83,17 @@ public class AccountController {
@DeleteMapping("/{userId}")
public ResponseEntity<ApiResponse<Void>> delete(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String userId) {
accountService.deleteAccount(appId, userId);
accountService.deleteAccount(appKey, userId);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/{userId}/exists")
public ResponseEntity<ApiResponse<Map<String, Boolean>>> exists(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String userId) {
return ResponseEntity.ok(ApiResponse.success(Map.of("exists", accountService.exists(appId, userId))));
return ResponseEntity.ok(ApiResponse.success(Map.of("exists", accountService.exists(appKey, userId))));
}
public record ImportAccountRequest(

查看文件

@ -24,7 +24,7 @@ public class AuthController {
@PostMapping("/login")
public ResponseEntity<ApiResponse<Map<String, Object>>> login(
@RequestParam @NotBlank String appId,
@RequestParam @NotBlank String appKey,
@RequestParam @NotBlank String userId,
@RequestHeader(value = "X-App-Timestamp", required = false) String timestamp,
@RequestHeader(value = "X-App-Nonce", required = false) String nonce,
@ -32,8 +32,8 @@ public class AuthController {
if (timestamp == null || nonce == null || signature == null) {
return ResponseEntity.status(401).body(ApiResponse.error(401, "Missing app signature"));
}
accountService.validateSignature(appId, userId, timestamp, nonce, signature);
ImAccountService.LoginResult result = accountService.loginOrRegister(appId, userId);
accountService.validateSignature(appKey, userId, timestamp, nonce, signature);
ImAccountService.LoginResult result = accountService.loginOrRegister(appKey, userId);
return ResponseEntity.ok(ApiResponse.success(Map.of("token", result.token())));
}
}

查看文件

@ -27,34 +27,34 @@ public class BlacklistController {
@GetMapping
public ResponseEntity<ApiResponse<List<ImBlacklistEntity>>> list(
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(blacklistService.list(appId, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(blacklistService.list(appKey, userId)));
}
@PostMapping
public ResponseEntity<ApiResponse<ImBlacklistEntity>> add(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String blockedUserId) {
return ResponseEntity.ok(ApiResponse.success(blacklistService.add(appId, userId, blockedUserId)));
return ResponseEntity.ok(ApiResponse.success(blacklistService.add(appKey, userId, blockedUserId)));
}
@DeleteMapping
public ResponseEntity<ApiResponse<Void>> remove(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String blockedUserId) {
blacklistService.remove(appId, userId, blockedUserId);
blacklistService.remove(appKey, userId, blockedUserId);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/check")
public ResponseEntity<ApiResponse<BlacklistCheckResult>> check(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String targetUserId) {
boolean blockedByMe = blacklistService.isBlocked(appId, userId, targetUserId);
boolean blockedByTarget = blacklistService.isBlocked(appId, targetUserId, userId);
boolean blockedByMe = blacklistService.isBlocked(appKey, userId, targetUserId);
boolean blockedByTarget = blacklistService.isBlocked(appKey, targetUserId, userId);
return ResponseEntity.ok(ApiResponse.success(
new BlacklistCheckResult(targetUserId, blockedByMe, blockedByTarget, blockedByMe || blockedByTarget)));
}

查看文件

@ -37,45 +37,45 @@ public class ConversationController {
@GetMapping("/conversations")
public ResponseEntity<ApiResponse<List<ConversationView>>> conversations(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(messageService.conversationViews(appId, userId, size)));
return ResponseEntity.ok(ApiResponse.success(messageService.conversationViews(appKey, userId, size)));
}
@PutMapping("/conversations/{targetId}/pinned")
public ResponseEntity<ApiResponse<Void>> setPinned(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String targetId,
@RequestParam String chatType,
@RequestParam boolean pinned) {
conversationStateService.setPinned(appId, userId, targetId, chatType, pinned);
conversationStateService.setPinned(appKey, userId, targetId, chatType, pinned);
return ResponseEntity.ok(ApiResponse.ok());
}
@PutMapping("/conversations/{targetId}/muted")
public ResponseEntity<ApiResponse<Void>> setMuted(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String targetId,
@RequestParam String chatType,
@RequestParam boolean muted) {
conversationStateService.setMuted(appId, userId, targetId, chatType, muted);
conversationStateService.setMuted(appKey, userId, targetId, chatType, muted);
return ResponseEntity.ok(ApiResponse.ok());
}
@PutMapping("/conversations/{targetId}/read")
public ResponseEntity<ApiResponse<Void>> markRead(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String targetId,
@RequestParam String chatType) {
var state = conversationStateService.markRead(appId, userId, targetId, chatType);
var state = conversationStateService.markRead(appKey, userId, targetId, chatType);
if ("GROUP".equalsIgnoreCase(chatType)) {
messageService.syncGroupReadReceipt(appId, userId, targetId, state.getLastReadAt());
messageService.syncGroupReadReceipt(appKey, userId, targetId, state.getLastReadAt());
} else {
messageService.syncReadReceipt(appId, userId, targetId, chatType, state.getLastReadAt());
messageService.syncReadReceipt(appKey, userId, targetId, chatType, state.getLastReadAt());
}
return ResponseEntity.ok(ApiResponse.ok());
}
@ -83,50 +83,50 @@ public class ConversationController {
@PutMapping("/conversations/{targetId}/draft")
public ResponseEntity<ApiResponse<Void>> setDraft(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String targetId,
@RequestParam String chatType,
@RequestParam(required = false) String draft) {
conversationStateService.setDraft(appId, userId, targetId, chatType, draft);
conversationStateService.setDraft(appKey, userId, targetId, chatType, draft);
return ResponseEntity.ok(ApiResponse.ok());
}
@PutMapping("/conversations/{targetId}/hidden")
public ResponseEntity<ApiResponse<Void>> setHidden(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String targetId,
@RequestParam String chatType,
@RequestParam boolean hidden) {
conversationStateService.setHidden(appId, userId, targetId, chatType, hidden);
conversationStateService.setHidden(appKey, userId, targetId, chatType, hidden);
return ResponseEntity.ok(ApiResponse.ok());
}
@PutMapping("/conversations/{targetId}/group")
public ResponseEntity<ApiResponse<Void>> setConversationGroup(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String targetId,
@RequestParam String chatType,
@RequestParam(required = false) String groupName) {
conversationStateService.setConversationGroup(appId, userId, targetId, chatType, groupName);
conversationStateService.setConversationGroup(appKey, userId, targetId, chatType, groupName);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/conversation-groups")
public ResponseEntity<ApiResponse<List<String>>> listConversationGroups(
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(conversationStateService.listConversationGroups(appId, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(conversationStateService.listConversationGroups(appKey, userId)));
}
@GetMapping("/conversation-groups/{groupName}")
public ResponseEntity<ApiResponse<List<Map<String, String>>>> listConversationGroupItems(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupName) {
List<Map<String, String>> items = conversationStateService
.listByConversationGroup(appId, userId, groupName)
.listByConversationGroup(appKey, userId, groupName)
.stream()
.map(state -> Map.of(
"targetId", state.getTargetId(),
@ -140,11 +140,11 @@ public class ConversationController {
@DeleteMapping("/conversations/{targetId}")
public ResponseEntity<ApiResponse<Void>> deleteConversation(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String targetId,
@RequestParam String chatType) {
boolean syncAcrossClients = featureConfigClient.multiClientConversationDeleteSync(appId);
conversationStateService.deleteConversation(appId, userId, targetId, chatType, syncAcrossClients);
boolean syncAcrossClients = featureConfigClient.multiClientConversationDeleteSync(appKey);
conversationStateService.deleteConversation(appKey, userId, targetId, chatType, syncAcrossClients);
return ResponseEntity.ok(ApiResponse.ok());
}
}

查看文件

@ -32,8 +32,8 @@ public class FriendController {
@GetMapping
public ResponseEntity<ApiResponse<List<String>>> listFriends(
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
List<String> friendIds = friendRepository.findByAppIdAndUserId(appId, userId)
@RequestParam String appKey) {
List<String> friendIds = friendRepository.findByAppIdAndUserId(appKey, userId)
.stream()
.map(ImFriendEntity::getFriendId)
.toList();
@ -43,22 +43,22 @@ public class FriendController {
@PostMapping
public ResponseEntity<ApiResponse<ImFriendEntity>> addFriend(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String friendId) {
return ResponseEntity.ok(ApiResponse.success(addFriendLink(appId, userId, friendId)));
return ResponseEntity.ok(ApiResponse.success(addFriendLink(appKey, userId, friendId)));
}
@PostMapping("/batch")
public ResponseEntity<ApiResponse<List<ImFriendEntity>>> addFriends(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody FriendBatchRequest req) {
List<ImFriendEntity> links = new ArrayList<>();
for (String friendId : unique(req.friendIds())) {
if (friendId == null || friendId.isBlank() || userId.equals(friendId)) {
continue;
}
links.add(addFriendLink(appId, userId, friendId));
links.add(addFriendLink(appKey, userId, friendId));
}
return ResponseEntity.ok(ApiResponse.success(links));
}
@ -67,32 +67,32 @@ public class FriendController {
public ResponseEntity<ApiResponse<Void>> removeFriend(
@AuthenticationPrincipal String userId,
@PathVariable String friendId,
@RequestParam String appId) {
friendRepository.deleteByAppIdAndUserIdAndFriendId(appId, userId, friendId);
friendRepository.deleteByAppIdAndUserIdAndFriendId(appId, friendId, userId);
@RequestParam String appKey) {
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId);
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
return ResponseEntity.ok(ApiResponse.success(null));
}
@DeleteMapping
public ResponseEntity<ApiResponse<Void>> removeAllFriends(
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
friendRepository.deleteByAppIdAndUserId(appId, userId);
friendRepository.deleteByAppIdAndFriendId(appId, userId);
@RequestParam String appKey) {
friendRepository.deleteByAppIdAndUserId(appKey, userId);
friendRepository.deleteByAppIdAndFriendId(appKey, userId);
return ResponseEntity.ok(ApiResponse.ok());
}
@PostMapping("/batch/remove")
public ResponseEntity<ApiResponse<Void>> removeFriends(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody FriendBatchRequest req) {
for (String friendId : unique(req.friendIds())) {
if (friendId == null || friendId.isBlank() || userId.equals(friendId)) {
continue;
}
friendRepository.deleteByAppIdAndUserIdAndFriendId(appId, userId, friendId);
friendRepository.deleteByAppIdAndUserIdAndFriendId(appId, friendId, userId);
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, userId, friendId);
friendRepository.deleteByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
}
return ResponseEntity.ok(ApiResponse.success(null));
}
@ -101,10 +101,10 @@ public class FriendController {
public ResponseEntity<ApiResponse<ImFriendEntity>> setFriendGroup(
@AuthenticationPrincipal String userId,
@PathVariable String friendId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) String groupName) {
ImFriendEntity link = friendRepository.findByAppIdAndUserIdAndFriendId(appId, userId, friendId)
.orElseGet(() -> addFriendLink(appId, userId, friendId));
ImFriendEntity link = friendRepository.findByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
.orElseGet(() -> addFriendLink(appKey, userId, friendId));
link.setFriendGroup(normalizeGroup(groupName));
return ResponseEntity.ok(ApiResponse.success(friendRepository.save(link)));
}
@ -112,8 +112,8 @@ public class FriendController {
@GetMapping("/groups")
public ResponseEntity<ApiResponse<List<String>>> listFriendGroups(
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
List<String> groups = friendRepository.findByAppIdAndUserId(appId, userId).stream()
@RequestParam String appKey) {
List<String> groups = friendRepository.findByAppIdAndUserId(appKey, userId).stream()
.map(ImFriendEntity::getFriendGroup)
.filter(group -> group != null && !group.isBlank())
.distinct()
@ -125,31 +125,31 @@ public class FriendController {
@GetMapping("/groups/{groupName}")
public ResponseEntity<ApiResponse<List<String>>> listFriendsByGroup(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupName) {
List<String> friendIds = friendRepository
.findByAppIdAndUserIdAndFriendGroup(appId, userId, normalizeGroup(groupName))
.findByAppIdAndUserIdAndFriendGroup(appKey, userId, normalizeGroup(groupName))
.stream()
.map(ImFriendEntity::getFriendId)
.toList();
return ResponseEntity.ok(ApiResponse.success(friendIds));
}
private ImFriendEntity addFriendLink(String appId, String userId, String friendId) {
private ImFriendEntity addFriendLink(String appKey, String userId, String friendId) {
ImFriendEntity forward = friendRepository
.findByAppIdAndUserIdAndFriendId(appId, userId, friendId)
.findByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
.orElseGet(() -> {
ImFriendEntity e = new ImFriendEntity();
e.setAppId(appId);
e.setAppId(appKey);
e.setUserId(userId);
e.setFriendId(friendId);
return friendRepository.save(e);
});
friendRepository.findByAppIdAndUserIdAndFriendId(appId, friendId, userId)
friendRepository.findByAppIdAndUserIdAndFriendId(appKey, friendId, userId)
.orElseGet(() -> {
ImFriendEntity e = new ImFriendEntity();
e.setAppId(appId);
e.setAppId(appKey);
e.setUserId(friendId);
e.setFriendId(userId);
return friendRepository.save(e);
@ -168,12 +168,12 @@ public class FriendController {
@PostMapping("/check")
public ResponseEntity<ApiResponse<List<FriendCheckResult>>> checkFriends(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody FriendCheckRequest req) {
List<FriendCheckResult> results = new ArrayList<>();
for (String friendId : req.friendIds() == null ? List.<String>of() : req.friendIds()) {
boolean isFriend = friendRepository.existsByAppIdAndUserIdAndFriendId(appId, userId, friendId)
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appId, friendId, userId);
boolean isFriend = friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
results.add(new FriendCheckResult(friendId, isFriend));
}
return ResponseEntity.ok(ApiResponse.success(results));

查看文件

@ -28,53 +28,53 @@ public class FriendRequestController {
@GetMapping
public ResponseEntity<ApiResponse<List<ImFriendRequestEntity>>> list(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(defaultValue = "incoming") String direction) {
List<ImFriendRequestEntity> list = "outgoing".equalsIgnoreCase(direction)
? friendRequestService.outgoing(appId, userId)
: friendRequestService.incoming(appId, userId);
? friendRequestService.outgoing(appKey, userId)
: friendRequestService.incoming(appKey, userId);
return ResponseEntity.ok(ApiResponse.success(list));
}
@PostMapping
public ResponseEntity<ApiResponse<ImFriendRequestEntity>> send(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String toUserId,
@RequestParam(required = false) String remark) {
return ResponseEntity.ok(ApiResponse.success(friendRequestService.send(appId, userId, toUserId, remark)));
return ResponseEntity.ok(ApiResponse.success(friendRequestService.send(appKey, userId, toUserId, remark)));
}
@PostMapping("/{requestId}/accept")
public ResponseEntity<ApiResponse<ImFriendRequestEntity>> accept(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String requestId) {
return ResponseEntity.ok(ApiResponse.success(friendRequestService.accept(appId, requestId, userId)));
return ResponseEntity.ok(ApiResponse.success(friendRequestService.accept(appKey, requestId, userId)));
}
@PostMapping("/{requestId}/reject")
public ResponseEntity<ApiResponse<ImFriendRequestEntity>> reject(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String requestId) {
return ResponseEntity.ok(ApiResponse.success(friendRequestService.reject(appId, requestId, userId)));
return ResponseEntity.ok(ApiResponse.success(friendRequestService.reject(appKey, requestId, userId)));
}
@PostMapping("/batch/accept")
public ResponseEntity<ApiResponse<List<ImFriendRequestEntity>>> acceptBatch(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody BatchRequest req) {
return ResponseEntity.ok(ApiResponse.success(friendRequestService.acceptBatch(appId, req.requestIds(), userId)));
return ResponseEntity.ok(ApiResponse.success(friendRequestService.acceptBatch(appKey, req.requestIds(), userId)));
}
@PostMapping("/batch/reject")
public ResponseEntity<ApiResponse<List<ImFriendRequestEntity>>> rejectBatch(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody BatchRequest req) {
return ResponseEntity.ok(ApiResponse.success(friendRequestService.rejectBatch(appId, req.requestIds(), userId)));
return ResponseEntity.ok(ApiResponse.success(friendRequestService.rejectBatch(appKey, req.requestIds(), userId)));
}
public record BatchRequest(List<String> requestIds) {}

查看文件

@ -33,49 +33,49 @@ public class GroupController {
public ResponseEntity<ApiResponse<ImGroupEntity>> create(
@RequestBody CreateGroupRequest req,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(
groupService.create(appId, req.name(), userId, req.memberIds(), req.groupType(), null)));
groupService.create(appKey, req.name(), userId, req.memberIds(), req.groupType(), null)));
}
@GetMapping
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> list(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String userId) {
return ResponseEntity.ok(ApiResponse.success(groupService.listUserGroups(appId, userId)));
return ResponseEntity.ok(ApiResponse.success(groupService.listUserGroups(appKey, userId)));
}
@GetMapping("/public")
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> listPublic(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) String keyword) {
return ResponseEntity.ok(ApiResponse.success(groupService.listPublicGroups(appId, keyword)));
return ResponseEntity.ok(ApiResponse.success(groupService.listPublicGroups(appKey, keyword)));
}
@GetMapping("/search")
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> search(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String keyword,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(groupService.searchGroups(appId, keyword, size)));
return ResponseEntity.ok(ApiResponse.success(groupService.searchGroups(appKey, keyword, size)));
}
@GetMapping("/{groupId}/members")
public ResponseEntity<ApiResponse<List<ImAccountEntity>>> listMembers(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(groupService.listMembers(appId, groupId, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(groupService.listMembers(appKey, groupId, userId)));
}
@GetMapping("/{groupId}/members/search")
public ResponseEntity<ApiResponse<List<ImAccountEntity>>> searchMembers(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String keyword,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(groupService.searchMembers(appId, groupId, userId, keyword, size)));
return ResponseEntity.ok(ApiResponse.success(groupService.searchMembers(appKey, groupId, userId, keyword, size)));
}
@PutMapping("/{groupId}")
@ -176,17 +176,17 @@ public class GroupController {
public ResponseEntity<ApiResponse<ImGroupJoinRequestEntity>> sendJoinRequest(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) String remark) {
return ResponseEntity.ok(ApiResponse.success(groupService.sendJoinRequest(appId, groupId, userId, remark)));
return ResponseEntity.ok(ApiResponse.success(groupService.sendJoinRequest(appKey, groupId, userId, remark)));
}
@GetMapping("/{groupId}/join-requests")
public ResponseEntity<ApiResponse<List<ImGroupJoinRequestEntity>>> listJoinRequests(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(groupService.listJoinRequests(appId, groupId, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(groupService.listJoinRequests(appKey, groupId, userId)));
}
@PostMapping("/{groupId}/join-requests/{requestId}/accept")
@ -194,8 +194,8 @@ public class GroupController {
@PathVariable String groupId,
@PathVariable String requestId,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(groupService.acceptJoinRequest(appId, requestId, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(groupService.acceptJoinRequest(appKey, requestId, userId)));
}
@PostMapping("/{groupId}/join-requests/{requestId}/reject")
@ -203,28 +203,28 @@ public class GroupController {
@PathVariable String groupId,
@PathVariable String requestId,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(groupService.rejectJoinRequest(appId, requestId, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(groupService.rejectJoinRequest(appKey, requestId, userId)));
}
@PostMapping("/{groupId}/join-requests/batch/accept")
public ResponseEntity<ApiResponse<List<ImGroupJoinRequestEntity>>> acceptJoinRequests(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody RequestBatch req) {
return ResponseEntity.ok(ApiResponse.success(
groupService.acceptJoinRequests(appId, groupId, req.requestIds(), userId)));
groupService.acceptJoinRequests(appKey, groupId, req.requestIds(), userId)));
}
@PostMapping("/{groupId}/join-requests/batch/reject")
public ResponseEntity<ApiResponse<List<ImGroupJoinRequestEntity>>> rejectJoinRequests(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody RequestBatch req) {
return ResponseEntity.ok(ApiResponse.success(
groupService.rejectJoinRequests(appId, groupId, req.requestIds(), userId)));
groupService.rejectJoinRequests(appKey, groupId, req.requestIds(), userId)));
}
@PutMapping("/{groupId}/members/{userId}/info")

查看文件

@ -102,24 +102,24 @@ public class ImAdminController {
this.userPresenceService = userPresenceService;
}
/** List all registered IM users for the given appId. */
/** List all registered IM users for the given appKey. */
@GetMapping("/users")
public ResponseEntity<ApiResponse<Page<ImAccountEntity>>> listUsers(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(
accountRepository.findByAppId(appId, PageRequest.of(page, size))));
accountRepository.findByAppId(appKey, PageRequest.of(page, size))));
}
/** Ban or unban a user. */
@PutMapping("/users/{userId}/status")
public ResponseEntity<ApiResponse<ImAccountEntity>> updateUserStatus(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String userId,
@AuthenticationPrincipal String operatorId,
@RequestBody Map<String, String> body) {
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appId, userId)
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "账号不存在"));
String status = body.get("status");
if (status == null || status.isBlank()) {
@ -127,72 +127,72 @@ public class ImAdminController {
}
account.setStatus(ImAccountEntity.Status.valueOf(status.toUpperCase()));
ImAccountEntity saved = accountRepository.save(account);
operationLogService.record(appId, operatorId, "UPDATE_USER_STATUS", "ACCOUNT", userId, status);
operationLogService.record(appKey, operatorId, "UPDATE_USER_STATUS", "ACCOUNT", userId, status);
return ResponseEntity.ok(ApiResponse.success(saved));
}
/** Update a registered IM user profile without changing userId. */
@PutMapping("/users/{userId}")
public ResponseEntity<ApiResponse<ImAccountEntity>> updateUser(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String userId,
@AuthenticationPrincipal String operatorId,
@RequestBody UpdateUserRequest req) {
ImAccountEntity saved = accountService.updateAccount(
appId,
appKey,
userId,
req.nickname(),
req.avatar(),
req.gender(),
req.status());
operationLogService.record(appId, operatorId, "UPDATE_USER", "ACCOUNT", userId, req.nickname());
operationLogService.record(appKey, operatorId, "UPDATE_USER", "ACCOUNT", userId, req.nickname());
return ResponseEntity.ok(ApiResponse.success(saved));
}
/** List all groups for the given appId. */
/** List all groups for the given appKey. */
@GetMapping("/groups")
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> listGroups(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(groupRepository.findByAppId(appId)));
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> listGroups(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(groupRepository.findByAppId(appKey)));
}
/** Admin registers a new IM user (or returns existing). */
@PostMapping("/users")
public ResponseEntity<ApiResponse<ImAccountEntity>> registerUser(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody RegisterUserRequest req) {
ImAccountEntity account = accountService.importAccount(
appId,
appKey,
req.userId(),
req.nickname(),
req.avatar(),
req.gender(),
req.status());
operationLogService.record(appId, operatorId, "REGISTER_USER", "ACCOUNT", req.userId(), req.nickname());
operationLogService.record(appKey, operatorId, "REGISTER_USER", "ACCOUNT", req.userId(), req.nickname());
return ResponseEntity.ok(ApiResponse.success(account));
}
/** Admin creates a group. */
@PostMapping("/groups")
public ResponseEntity<ApiResponse<ImGroupEntity>> createGroup(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody CreateGroupRequest req) {
ImGroupEntity group = groupService.create(
appId,
appKey,
req.name(),
req.creatorId(),
req.memberIds(),
req.groupType(),
req.announcement());
operationLogService.record(appId, operatorId, "CREATE_GROUP", "GROUP", group.getId(), group.getName());
operationLogService.record(appKey, operatorId, "CREATE_GROUP", "GROUP", group.getId(), group.getName());
return ResponseEntity.ok(ApiResponse.success(group));
}
/** Admin updates a group without changing its id. */
@PutMapping("/groups/{groupId}")
public ResponseEntity<ApiResponse<ImGroupEntity>> updateGroup(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody UpdateGroupRequest req) {
@ -202,38 +202,38 @@ public class ImAdminController {
req.name(),
req.groupType(),
req.announcement());
operationLogService.record(appId, operatorId, "UPDATE_GROUP", "GROUP", groupId, req.name());
operationLogService.record(appKey, operatorId, "UPDATE_GROUP", "GROUP", groupId, req.name());
return ResponseEntity.ok(ApiResponse.success(group));
}
/** Fuzzy search users by userId or nickname. */
@GetMapping("/users/search")
public ResponseEntity<ApiResponse<List<ImAccountEntity>>> searchUsers(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestParam String keyword,
@RequestParam(defaultValue = "20") int size) {
List<ImAccountEntity> results = accountRepository.searchByKeyword(appId, keyword, PageRequest.of(0, size));
operationLogService.record(appId, operatorId, "SEARCH_USERS", "ACCOUNT", null, keyword);
List<ImAccountEntity> results = accountRepository.searchByKeyword(appKey, keyword, PageRequest.of(0, size));
operationLogService.record(appKey, operatorId, "SEARCH_USERS", "ACCOUNT", null, keyword);
return ResponseEntity.ok(ApiResponse.success(results));
}
/** Fuzzy search groups by id, name, creator or announcement. */
@GetMapping("/groups/search")
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> searchGroups(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestParam String keyword,
@RequestParam(defaultValue = "20") int size) {
List<ImGroupEntity> results = groupRepository.searchByKeyword(appId, keyword, PageRequest.of(0, size));
operationLogService.record(appId, operatorId, "SEARCH_GROUPS", "GROUP", null, keyword);
List<ImGroupEntity> results = groupRepository.searchByKeyword(appKey, keyword, PageRequest.of(0, size));
operationLogService.record(appKey, operatorId, "SEARCH_GROUPS", "GROUP", null, keyword);
return ResponseEntity.ok(ApiResponse.success(results));
}
/** Search messages across the application. */
@GetMapping("/messages/search")
public ResponseEntity<ApiResponse<Page<ImMessageEntity>>> searchMessages(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestParam(required = false) ImMessageEntity.ChatType chatType,
@RequestParam(required = false) ImMessageEntity.MsgType msgType,
@ -242,22 +242,22 @@ public class ImAdminController {
@RequestParam(required = false) LocalDateTime endTime,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
operationLogService.record(appId, operatorId, "SEARCH_MESSAGES", "MESSAGE", null, keyword);
operationLogService.record(appKey, operatorId, "SEARCH_MESSAGES", "MESSAGE", null, keyword);
return ResponseEntity.ok(ApiResponse.success(
messageRepository.searchByKeyword(
appId, chatType, msgType, keyword, startTime, endTime, PageRequest.of(page, size))));
appKey, chatType, msgType, keyword, startTime, endTime, PageRequest.of(page, size))));
}
/** Message statistics for the given appId. */
/** Message statistics for the given appKey. */
@GetMapping("/stats")
public ResponseEntity<ApiResponse<Map<String, Object>>> stats(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId) {
long totalMessages = messageRepository.countByAppId(appId);
long totalUsers = accountRepository.countByAppId(appId);
long totalGroups = groupRepository.countByAppId(appId);
long todayMessages = messageRepository.countTodayByAppId(appId);
operationLogService.record(appId, operatorId, "VIEW_STATS", "STATS", null, "summary");
long totalMessages = messageRepository.countByAppId(appKey);
long totalUsers = accountRepository.countByAppId(appKey);
long totalGroups = groupRepository.countByAppId(appKey);
long todayMessages = messageRepository.countTodayByAppId(appKey);
operationLogService.record(appKey, operatorId, "VIEW_STATS", "STATS", null, "summary");
return ResponseEntity.ok(ApiResponse.success(Map.of(
"totalMessages", totalMessages,
@ -270,7 +270,7 @@ public class ImAdminController {
/** Admin queries conversation history between any two users. */
@GetMapping("/messages")
public ResponseEntity<ApiResponse<Page<com.xuqm.im.entity.ImMessageEntity>>> history(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestParam String userA,
@RequestParam String userB,
@ -280,270 +280,270 @@ public class ImAdminController {
@RequestParam(required = false) LocalDateTime endTime,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
operationLogService.record(appId, operatorId, "VIEW_HISTORY", "MESSAGE", userA + "," + userB, keyword);
operationLogService.record(appKey, operatorId, "VIEW_HISTORY", "MESSAGE", userA + "," + userB, keyword);
return ResponseEntity.ok(ApiResponse.success(
messageRepository.findSingleConversationFiltered(
appId, userA, userB, msgType, keyword, startTime, endTime, PageRequest.of(page, size))));
appKey, userA, userB, msgType, keyword, startTime, endTime, PageRequest.of(page, size))));
}
/** Admin revokes an arbitrary message. */
@PostMapping("/messages/{messageId}/revoke")
public ResponseEntity<ApiResponse<com.xuqm.im.entity.ImMessageEntity>> adminRevoke(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@PathVariable String messageId) {
ImMessageEntity revoked = messageService.adminRevoke(appId, messageId);
operationLogService.record(appId, operatorId, "ADMIN_REVOKE_MESSAGE", "MESSAGE", messageId, null);
ImMessageEntity revoked = messageService.adminRevoke(appKey, messageId);
operationLogService.record(appKey, operatorId, "ADMIN_REVOKE_MESSAGE", "MESSAGE", messageId, null);
return ResponseEntity.ok(ApiResponse.success(revoked));
}
/** Admin force dismisses a group. */
@DeleteMapping("/groups/{groupId}")
public ResponseEntity<ApiResponse<Void>> adminDismissGroup(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@PathVariable String groupId) {
groupService.adminDismiss(groupId);
operationLogService.record(appId, operatorId, "ADMIN_DISMISS_GROUP", "GROUP", groupId, null);
operationLogService.record(appKey, operatorId, "ADMIN_DISMISS_GROUP", "GROUP", groupId, null);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/friend-requests")
public ResponseEntity<ApiResponse<List<ImFriendRequestEntity>>> listFriendRequests(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(friendRequestService.listByApp(appId)));
public ResponseEntity<ApiResponse<List<ImFriendRequestEntity>>> listFriendRequests(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(friendRequestService.listByApp(appKey)));
}
@PostMapping("/friend-requests")
public ResponseEntity<ApiResponse<ImFriendRequestEntity>> createFriendRequest(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody FriendRequestCreateRequest req) {
ImFriendRequestEntity saved = friendRequestService.send(appId, req.fromUserId(), req.toUserId(), req.remark());
operationLogService.record(appId, operatorId, "CREATE_FRIEND_REQUEST", "FRIEND_REQUEST", saved.getId(), req.toUserId());
ImFriendRequestEntity saved = friendRequestService.send(appKey, req.fromUserId(), req.toUserId(), req.remark());
operationLogService.record(appKey, operatorId, "CREATE_FRIEND_REQUEST", "FRIEND_REQUEST", saved.getId(), req.toUserId());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/friend-requests/{requestId}/accept")
public ResponseEntity<ApiResponse<ImFriendRequestEntity>> acceptFriendRequest(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String requestId,
@AuthenticationPrincipal String operatorId) {
ImFriendRequestEntity saved = friendRequestService.adminAccept(appId, requestId);
operationLogService.record(appId, operatorId, "ACCEPT_FRIEND_REQUEST", "FRIEND_REQUEST", requestId, null);
ImFriendRequestEntity saved = friendRequestService.adminAccept(appKey, requestId);
operationLogService.record(appKey, operatorId, "ACCEPT_FRIEND_REQUEST", "FRIEND_REQUEST", requestId, null);
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/friend-requests/{requestId}/reject")
public ResponseEntity<ApiResponse<ImFriendRequestEntity>> rejectFriendRequest(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String requestId,
@AuthenticationPrincipal String operatorId) {
ImFriendRequestEntity saved = friendRequestService.adminReject(appId, requestId);
operationLogService.record(appId, operatorId, "REJECT_FRIEND_REQUEST", "FRIEND_REQUEST", requestId, null);
ImFriendRequestEntity saved = friendRequestService.adminReject(appKey, requestId);
operationLogService.record(appKey, operatorId, "REJECT_FRIEND_REQUEST", "FRIEND_REQUEST", requestId, null);
return ResponseEntity.ok(ApiResponse.success(saved));
}
@GetMapping("/blacklist")
public ResponseEntity<ApiResponse<List<ImBlacklistEntity>>> listBlacklist(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(blacklistService.listByApp(appId)));
public ResponseEntity<ApiResponse<List<ImBlacklistEntity>>> listBlacklist(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(blacklistService.listByApp(appKey)));
}
@PostMapping("/blacklist")
public ResponseEntity<ApiResponse<ImBlacklistEntity>> addBlacklist(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody BlacklistRequest req) {
ImBlacklistEntity saved = blacklistService.add(appId, req.userId(), req.blockedUserId());
operationLogService.record(appId, operatorId, "ADD_BLACKLIST", "BLACKLIST", saved.getId(), req.blockedUserId());
ImBlacklistEntity saved = blacklistService.add(appKey, req.userId(), req.blockedUserId());
operationLogService.record(appKey, operatorId, "ADD_BLACKLIST", "BLACKLIST", saved.getId(), req.blockedUserId());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@DeleteMapping("/blacklist")
public ResponseEntity<ApiResponse<Void>> removeBlacklist(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String userId,
@RequestParam String blockedUserId,
@AuthenticationPrincipal String operatorId) {
blacklistService.remove(appId, userId, blockedUserId);
operationLogService.record(appId, operatorId, "REMOVE_BLACKLIST", "BLACKLIST", userId, blockedUserId);
blacklistService.remove(appKey, userId, blockedUserId);
operationLogService.record(appKey, operatorId, "REMOVE_BLACKLIST", "BLACKLIST", userId, blockedUserId);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/groups/{groupId}/members")
public ResponseEntity<ApiResponse<List<ImAccountEntity>>> listGroupMembers(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId) {
return ResponseEntity.ok(ApiResponse.success(groupService.adminListMembers(appId, groupId)));
return ResponseEntity.ok(ApiResponse.success(groupService.adminListMembers(appKey, groupId)));
}
@GetMapping("/groups/{groupId}/members/search")
public ResponseEntity<ApiResponse<List<ImAccountEntity>>> searchGroupMembers(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@RequestParam String keyword,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(groupService.adminSearchMembers(appId, groupId, keyword, size)));
return ResponseEntity.ok(ApiResponse.success(groupService.adminSearchMembers(appKey, groupId, keyword, size)));
}
@PostMapping("/groups/{groupId}/members")
public ResponseEntity<ApiResponse<ImGroupEntity>> addGroupMember(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody MemberRequest req) {
ImGroupEntity saved = groupService.adminAddMember(appId, groupId, req.userId());
operationLogService.record(appId, operatorId, "ADMIN_ADD_GROUP_MEMBER", "GROUP", groupId, req.userId());
ImGroupEntity saved = groupService.adminAddMember(appKey, groupId, req.userId());
operationLogService.record(appKey, operatorId, "ADMIN_ADD_GROUP_MEMBER", "GROUP", groupId, req.userId());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@DeleteMapping("/groups/{groupId}/members/{userId}")
public ResponseEntity<ApiResponse<ImGroupEntity>> removeGroupMember(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@PathVariable String userId,
@AuthenticationPrincipal String operatorId) {
ImGroupEntity saved = groupService.adminRemoveMember(appId, groupId, userId);
operationLogService.record(appId, operatorId, "ADMIN_REMOVE_GROUP_MEMBER", "GROUP", groupId, userId);
ImGroupEntity saved = groupService.adminRemoveMember(appKey, groupId, userId);
operationLogService.record(appKey, operatorId, "ADMIN_REMOVE_GROUP_MEMBER", "GROUP", groupId, userId);
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/groups/{groupId}/roles")
public ResponseEntity<ApiResponse<ImGroupEntity>> setGroupRole(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody SetRoleRequest req) {
ImGroupEntity saved = groupService.adminSetRole(appId, groupId, req.userId(), req.role());
operationLogService.record(appId, operatorId, "ADMIN_SET_GROUP_ROLE", "GROUP", groupId, req.userId() + ":" + req.role());
ImGroupEntity saved = groupService.adminSetRole(appKey, groupId, req.userId(), req.role());
operationLogService.record(appKey, operatorId, "ADMIN_SET_GROUP_ROLE", "GROUP", groupId, req.userId() + ":" + req.role());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/groups/{groupId}/owner")
public ResponseEntity<ApiResponse<ImGroupEntity>> transferGroupOwner(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody TransferOwnerRequest req) {
ImGroupEntity saved = groupService.adminTransferOwner(appId, groupId, req.newOwnerId());
operationLogService.record(appId, operatorId, "ADMIN_TRANSFER_GROUP_OWNER", "GROUP", groupId, req.newOwnerId());
ImGroupEntity saved = groupService.adminTransferOwner(appKey, groupId, req.newOwnerId());
operationLogService.record(appKey, operatorId, "ADMIN_TRANSFER_GROUP_OWNER", "GROUP", groupId, req.newOwnerId());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PutMapping("/groups/{groupId}/attributes")
public ResponseEntity<ApiResponse<ImGroupEntity>> updateGroupAttributes(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody Map<String, Object> attributes) {
ImGroupEntity saved = groupService.adminUpdateExtAttributes(appId, groupId, attributes);
operationLogService.record(appId, operatorId, "ADMIN_UPDATE_GROUP_ATTRIBUTES", "GROUP", groupId, null);
ImGroupEntity saved = groupService.adminUpdateExtAttributes(appKey, groupId, attributes);
operationLogService.record(appKey, operatorId, "ADMIN_UPDATE_GROUP_ATTRIBUTES", "GROUP", groupId, null);
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/groups/{groupId}/attributes/delete")
public ResponseEntity<ApiResponse<ImGroupEntity>> removeGroupAttributes(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody AttributeKeysRequest req) {
ImGroupEntity saved = groupService.adminRemoveExtAttributes(appId, groupId, req.keys());
operationLogService.record(appId, operatorId, "ADMIN_REMOVE_GROUP_ATTRIBUTES", "GROUP", groupId,
ImGroupEntity saved = groupService.adminRemoveExtAttributes(appKey, groupId, req.keys());
operationLogService.record(appKey, operatorId, "ADMIN_REMOVE_GROUP_ATTRIBUTES", "GROUP", groupId,
String.join(",", req.keys() == null ? List.of() : req.keys()));
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/groups/{groupId}/mute")
public ResponseEntity<ApiResponse<ImGroupEntity>> muteGroupMember(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody MuteMemberRequest req) {
ImGroupEntity saved = groupService.adminMuteMember(appId, groupId, req.userId(), req.minutes());
operationLogService.record(appId, operatorId, "ADMIN_MUTE_GROUP_MEMBER", "GROUP", groupId, req.userId() + ":" + req.minutes());
ImGroupEntity saved = groupService.adminMuteMember(appKey, groupId, req.userId(), req.minutes());
operationLogService.record(appKey, operatorId, "ADMIN_MUTE_GROUP_MEMBER", "GROUP", groupId, req.userId() + ":" + req.minutes());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/groups/{groupId}/read-receipts")
public ResponseEntity<ApiResponse<List<MessageService.GroupReadReceiptSummary>>> groupReadReceipts(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@AuthenticationPrincipal String operatorId,
@RequestBody GroupReadReceiptRequest req) {
List<MessageService.GroupReadReceiptSummary> receipts =
messageService.groupReadReceipts(appId, groupId, req.messageIds());
operationLogService.record(appId, operatorId, "QUERY_GROUP_READ_RECEIPTS", "GROUP", groupId,
messageService.groupReadReceipts(appKey, groupId, req.messageIds());
operationLogService.record(appKey, operatorId, "QUERY_GROUP_READ_RECEIPTS", "GROUP", groupId,
"count=" + receipts.size());
return ResponseEntity.ok(ApiResponse.success(receipts));
}
@GetMapping("/groups/{groupId}/join-requests")
public ResponseEntity<ApiResponse<List<ImGroupJoinRequestEntity>>> listGroupJoinRequests(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId) {
return ResponseEntity.ok(ApiResponse.success(groupService.adminListJoinRequests(appId, groupId)));
return ResponseEntity.ok(ApiResponse.success(groupService.adminListJoinRequests(appKey, groupId)));
}
@PostMapping("/groups/{groupId}/join-requests/{requestId}/accept")
public ResponseEntity<ApiResponse<ImGroupJoinRequestEntity>> acceptGroupJoinRequest(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@PathVariable String requestId,
@AuthenticationPrincipal String operatorId) {
ImGroupJoinRequestEntity saved = groupService.adminAcceptJoinRequest(appId, groupId, requestId);
operationLogService.record(appId, operatorId, "ADMIN_ACCEPT_GROUP_JOIN_REQUEST", "GROUP_JOIN_REQUEST", requestId, null);
ImGroupJoinRequestEntity saved = groupService.adminAcceptJoinRequest(appKey, groupId, requestId);
operationLogService.record(appKey, operatorId, "ADMIN_ACCEPT_GROUP_JOIN_REQUEST", "GROUP_JOIN_REQUEST", requestId, null);
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PostMapping("/groups/{groupId}/join-requests/{requestId}/reject")
public ResponseEntity<ApiResponse<ImGroupJoinRequestEntity>> rejectGroupJoinRequest(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String groupId,
@PathVariable String requestId,
@AuthenticationPrincipal String operatorId) {
ImGroupJoinRequestEntity saved = groupService.adminRejectJoinRequest(appId, groupId, requestId);
operationLogService.record(appId, operatorId, "ADMIN_REJECT_GROUP_JOIN_REQUEST", "GROUP_JOIN_REQUEST", requestId, null);
ImGroupJoinRequestEntity saved = groupService.adminRejectJoinRequest(appKey, groupId, requestId);
operationLogService.record(appKey, operatorId, "ADMIN_REJECT_GROUP_JOIN_REQUEST", "GROUP_JOIN_REQUEST", requestId, null);
return ResponseEntity.ok(ApiResponse.success(saved));
}
@GetMapping("/webhooks")
public ResponseEntity<ApiResponse<List<WebhookConfigEntity>>> listWebhooks(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(webhookConfigService.list(appId)));
public ResponseEntity<ApiResponse<List<WebhookConfigEntity>>> listWebhooks(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(webhookConfigService.list(appKey)));
}
@PostMapping("/webhooks")
public ResponseEntity<ApiResponse<WebhookConfigEntity>> createWebhook(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody WebhookConfigRequest req) {
WebhookConfigEntity saved = webhookConfigService.create(appId, req.url(), req.secret(), req.enabled());
operationLogService.record(appId, operatorId, "CREATE_WEBHOOK", "WEBHOOK", saved.getId(), req.url());
WebhookConfigEntity saved = webhookConfigService.create(appKey, req.url(), req.secret(), req.enabled());
operationLogService.record(appKey, operatorId, "CREATE_WEBHOOK", "WEBHOOK", saved.getId(), req.url());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PutMapping("/webhooks/{id}")
public ResponseEntity<ApiResponse<WebhookConfigEntity>> updateWebhook(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String id,
@AuthenticationPrincipal String operatorId,
@RequestBody WebhookConfigRequest req) {
WebhookConfigEntity saved = webhookConfigService.update(appId, id, req.url(), req.secret(), req.enabled());
operationLogService.record(appId, operatorId, "UPDATE_WEBHOOK", "WEBHOOK", id, req.url());
WebhookConfigEntity saved = webhookConfigService.update(appKey, id, req.url(), req.secret(), req.enabled());
operationLogService.record(appKey, operatorId, "UPDATE_WEBHOOK", "WEBHOOK", id, req.url());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@DeleteMapping("/webhooks/{id}")
public ResponseEntity<ApiResponse<Void>> deleteWebhook(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@PathVariable String id) {
webhookConfigService.delete(appId, id);
operationLogService.record(appId, operatorId, "DELETE_WEBHOOK", "WEBHOOK", id, null);
webhookConfigService.delete(appKey, id);
operationLogService.record(appKey, operatorId, "DELETE_WEBHOOK", "WEBHOOK", id, null);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/webhook-deliveries")
public ResponseEntity<ApiResponse<Page<WebhookDeliveryEntity>>> listWebhookDeliveries(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) String callbackEvent,
@RequestParam(required = false) Boolean success,
@RequestParam(defaultValue = "0") int page,
@ -551,122 +551,122 @@ public class ImAdminController {
Page<WebhookDeliveryEntity> result;
PageRequest pageable = PageRequest.of(page, size);
if (callbackEvent != null && !callbackEvent.isBlank()) {
result = webhookDeliveryRepository.findByAppIdAndCallbackEvent(appId, callbackEvent, pageable);
result = webhookDeliveryRepository.findByAppIdAndCallbackEvent(appKey, callbackEvent, pageable);
} else if (success != null) {
result = webhookDeliveryRepository.findByAppIdAndSuccess(appId, success, pageable);
result = webhookDeliveryRepository.findByAppIdAndSuccess(appKey, success, pageable);
} else {
result = webhookDeliveryRepository.findByAppId(appId, pageable);
result = webhookDeliveryRepository.findByAppId(appKey, pageable);
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/webhook-alerts")
public ResponseEntity<ApiResponse<Page<WebhookAlertEntity>>> listWebhookAlerts(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) Boolean acknowledged,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Page<WebhookAlertEntity> result;
PageRequest pageable = PageRequest.of(page, size);
if (acknowledged != null) {
result = webhookAlertRepository.findByAppIdAndAcknowledged(appId, acknowledged, pageable);
result = webhookAlertRepository.findByAppIdAndAcknowledged(appKey, acknowledged, pageable);
} else {
result = webhookAlertRepository.findByAppId(appId, pageable);
result = webhookAlertRepository.findByAppId(appKey, pageable);
}
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping("/webhook-alerts/{id}/acknowledge")
public ResponseEntity<ApiResponse<WebhookAlertEntity>> acknowledgeWebhookAlert(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String id,
@AuthenticationPrincipal String operatorId) {
WebhookAlertEntity alert = webhookAlertRepository.findById(id)
.orElseThrow(() -> new BusinessException(404, "告警不存在"));
if (!alert.getAppId().equals(appId)) {
if (!alert.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
alert.setAcknowledged(true);
alert.setAcknowledgedAt(LocalDateTime.now());
WebhookAlertEntity saved = webhookAlertRepository.save(alert);
operationLogService.record(appId, operatorId, "ACK_WEBHOOK_ALERT", "WEBHOOK_ALERT", id, null);
operationLogService.record(appKey, operatorId, "ACK_WEBHOOK_ALERT", "WEBHOOK_ALERT", id, null);
return ResponseEntity.ok(ApiResponse.success(saved));
}
@GetMapping("/webhooks/{id}/health")
public ResponseEntity<ApiResponse<Map<String, Object>>> getWebhookHealth(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String id) {
WebhookConfigEntity webhook = webhookConfigService.get(appId, id);
WebhookConfigEntity webhook = webhookConfigService.get(appKey, id);
Map<String, Object> health = new LinkedHashMap<>();
health.put("webhookId", webhook.getId());
health.put("url", webhook.getUrl());
health.put("enabled", webhook.isEnabled());
health.put("consecutiveFailures", webhook.getConsecutiveFailures());
health.put("lastFailureAt", webhook.getLastFailureAt());
long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppId(appId);
long unacknowledgedAlerts = webhookAlertRepository.countUnacknowledgedByAppId(appKey);
health.put("unacknowledgedAlerts", unacknowledgedAlerts);
return ResponseEntity.ok(ApiResponse.success(health));
}
@GetMapping("/keyword-filters")
public ResponseEntity<ApiResponse<List<KeywordFilterEntity>>> listKeywordFilters(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(keywordFilterService.list(appId)));
public ResponseEntity<ApiResponse<List<KeywordFilterEntity>>> listKeywordFilters(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(keywordFilterService.list(appKey)));
}
@PostMapping("/keyword-filters")
public ResponseEntity<ApiResponse<KeywordFilterEntity>> createKeywordFilter(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody KeywordFilterRequest req) {
KeywordFilterEntity saved = keywordFilterService.add(appId, req.pattern(), req.replacement(), req.action());
operationLogService.record(appId, operatorId, "CREATE_KEYWORD_FILTER", "KEYWORD_FILTER", saved.getId(), req.pattern());
KeywordFilterEntity saved = keywordFilterService.add(appKey, req.pattern(), req.replacement(), req.action());
operationLogService.record(appKey, operatorId, "CREATE_KEYWORD_FILTER", "KEYWORD_FILTER", saved.getId(), req.pattern());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@PutMapping("/keyword-filters/{id}")
public ResponseEntity<ApiResponse<KeywordFilterEntity>> updateKeywordFilter(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String id,
@AuthenticationPrincipal String operatorId,
@RequestBody KeywordFilterRequest req) {
KeywordFilterEntity saved = keywordFilterService.update(appId, id, req.pattern(), req.replacement(), req.action(), req.enabled());
operationLogService.record(appId, operatorId, "UPDATE_KEYWORD_FILTER", "KEYWORD_FILTER", id, req.pattern());
KeywordFilterEntity saved = keywordFilterService.update(appKey, id, req.pattern(), req.replacement(), req.action(), req.enabled());
operationLogService.record(appKey, operatorId, "UPDATE_KEYWORD_FILTER", "KEYWORD_FILTER", id, req.pattern());
return ResponseEntity.ok(ApiResponse.success(saved));
}
@DeleteMapping("/keyword-filters/{id}")
public ResponseEntity<ApiResponse<Void>> deleteKeywordFilter(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@PathVariable String id) {
keywordFilterService.delete(appId, id);
operationLogService.record(appId, operatorId, "DELETE_KEYWORD_FILTER", "KEYWORD_FILTER", id, null);
keywordFilterService.delete(appKey, id);
operationLogService.record(appKey, operatorId, "DELETE_KEYWORD_FILTER", "KEYWORD_FILTER", id, null);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/global-mute")
public ResponseEntity<ApiResponse<ImGlobalMuteEntity>> getGlobalMute(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(globalMuteService.get(appId)));
public ResponseEntity<ApiResponse<ImGlobalMuteEntity>> getGlobalMute(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(globalMuteService.get(appKey)));
}
@PutMapping("/global-mute")
public ResponseEntity<ApiResponse<ImGlobalMuteEntity>> setGlobalMute(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestParam boolean enabled) {
ImGlobalMuteEntity saved = globalMuteService.setEnabled(appId, enabled);
operationLogService.record(appId, operatorId, "SET_GLOBAL_MUTE", "GLOBAL_MUTE", saved.getId(), String.valueOf(enabled));
ImGlobalMuteEntity saved = globalMuteService.setEnabled(appKey, enabled);
operationLogService.record(appKey, operatorId, "SET_GLOBAL_MUTE", "GLOBAL_MUTE", saved.getId(), String.valueOf(enabled));
return ResponseEntity.ok(ApiResponse.success(saved));
}
@GetMapping("/operation-logs")
public ResponseEntity<ApiResponse<Page<com.xuqm.im.entity.ImOperationLogEntity>>> operationLogs(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(
operationLogService.list(appId, PageRequest.of(page, size))));
operationLogService.list(appKey, PageRequest.of(page, size))));
}
@GetMapping("/users/state")
@ -689,15 +689,15 @@ public class ImAdminController {
@PostMapping("/users/kick")
@PreAuthorize("hasAnyAuthority('ROLE_OPS', 'ROLE_TENANT')")
public ResponseEntity<ApiResponse<Void>> kickUsers(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody KickRequest req) {
for (String userId : req.userIds()) {
messagingTemplate.convertAndSendToUser(userId, "/queue/kick",
Map.of("type", "KICK", "reason", "管理员强制下线"));
redisTemplate.opsForValue().set("im:kick:" + appId + ":" + userId, "1", Duration.ofMinutes(5));
redisTemplate.opsForValue().set("im:kick:" + appKey + ":" + userId, "1", Duration.ofMinutes(5));
}
operationLogService.record(appId, operatorId, "KICK_USERS", "ACCOUNT", null,
operationLogService.record(appKey, operatorId, "KICK_USERS", "ACCOUNT", null,
String.join(",", req.userIds()));
return ResponseEntity.ok(ApiResponse.ok());
}
@ -705,15 +705,15 @@ public class ImAdminController {
@PostMapping("/messages/batch-send")
@PreAuthorize("hasAnyAuthority('ROLE_OPS', 'ROLE_TENANT')")
public ResponseEntity<ApiResponse<List<ImMessageEntity>>> batchSendMsg(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody BatchSendRequest req) {
List<ImMessageEntity> result = new ArrayList<>();
for (String toId : req.toIds()) {
ImMessageEntity sent = messageService.adminSend(appId, operatorId, toId, req.msgType(), req.content());
ImMessageEntity sent = messageService.adminSend(appKey, operatorId, toId, req.msgType(), req.content());
result.add(sent);
}
operationLogService.record(appId, operatorId, "BATCH_SEND_MESSAGE", "MESSAGE", null,
operationLogService.record(appKey, operatorId, "BATCH_SEND_MESSAGE", "MESSAGE", null,
"count=" + result.size());
return ResponseEntity.ok(ApiResponse.success(result));
}
@ -721,22 +721,22 @@ public class ImAdminController {
@PostMapping("/messages/read")
@PreAuthorize("hasAnyAuthority('ROLE_OPS', 'ROLE_TENANT')")
public ResponseEntity<ApiResponse<Void>> adminSetMsgRead(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody SetMsgReadRequest req) {
messageService.adminSetMsgRead(appId, req.userId());
operationLogService.record(appId, operatorId, "ADMIN_SET_MSG_READ", "MESSAGE", req.userId(), null);
messageService.adminSetMsgRead(appKey, req.userId());
operationLogService.record(appKey, operatorId, "ADMIN_SET_MSG_READ", "MESSAGE", req.userId(), null);
return ResponseEntity.ok(ApiResponse.ok());
}
@PostMapping("/messages/import")
@PreAuthorize("hasAnyAuthority('ROLE_OPS', 'ROLE_TENANT')")
public ResponseEntity<ApiResponse<List<ImMessageEntity>>> importMessages(
@RequestParam String appId,
@RequestParam String appKey,
@AuthenticationPrincipal String operatorId,
@RequestBody List<MessageService.ImportMessageRequest> req) {
List<ImMessageEntity> result = messageService.importMessages(appId, req);
operationLogService.record(appId, operatorId, "IMPORT_MESSAGES", "MESSAGE", null,
List<ImMessageEntity> result = messageService.importMessages(appKey, req);
operationLogService.record(appKey, operatorId, "IMPORT_MESSAGES", "MESSAGE", null,
"count=" + result.size());
return ResponseEntity.ok(ApiResponse.success(result));
}

查看文件

@ -40,8 +40,8 @@ public class InternalPresenceController {
try {
Claims claims = jwtUtil.parse(request.token());
String userId = claims.getSubject();
String appId = claims.get("appId", String.class);
return ResponseEntity.ok(ApiResponse.success(status(appId, userId)));
String appKey = claims.get("appKey", String.class);
return ResponseEntity.ok(ApiResponse.success(status(appKey, userId)));
} catch (Exception e) {
return ResponseEntity.badRequest().body(ApiResponse.error(400, "Invalid IM token"));
}
@ -50,17 +50,17 @@ public class InternalPresenceController {
@GetMapping("/users/{userId}")
public ResponseEntity<ApiResponse<PresenceStatus>> userStatus(
@RequestHeader(value = "X-Internal-Token", required = false) String token,
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable String userId) {
if (!isAllowed(token)) {
return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden"));
}
return ResponseEntity.ok(ApiResponse.success(status(appId, userId)));
return ResponseEntity.ok(ApiResponse.success(status(appKey, userId)));
}
private PresenceStatus status(String appId, String userId) {
private PresenceStatus status(String appKey, String userId) {
boolean online = presenceService.isOnline(userId);
return new PresenceStatus(appId, userId, online, presenceService.lastSeenAt(userId));
return new PresenceStatus(appKey, userId, online, presenceService.lastSeenAt(userId));
}
private boolean isAllowed(String token) {
@ -69,5 +69,5 @@ public class InternalPresenceController {
public record TokenRequest(String token) {}
public record PresenceStatus(String appId, String userId, boolean online, long lastSeenAt) {}
public record PresenceStatus(String appKey, String userId, boolean online, long lastSeenAt) {}
}

查看文件

@ -45,16 +45,16 @@ public class MessageController {
public ResponseEntity<ApiResponse<ImMessageEntity>> send(
@Valid @RequestBody SendMessageRequest req,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(messageService.send(appId, userId, req)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(messageService.send(appKey, userId, req)));
}
@PostMapping("/{id}/revoke")
public ResponseEntity<ApiResponse<ImMessageEntity>> revoke(
@PathVariable String id,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(messageService.revoke(appId, id, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(messageService.revoke(appKey, id, userId)));
}
@PutMapping("/{id}")
@ -62,15 +62,15 @@ public class MessageController {
@PathVariable String id,
@Valid @RequestBody EditMessageRequest req,
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(messageService.edit(appId, id, userId, req)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(messageService.edit(appKey, id, userId, req)));
}
@GetMapping("/history/{toId}")
public ResponseEntity<ApiResponse<?>> history(
@PathVariable String toId,
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) ImMessageEntity.MsgType msgType,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
@ -78,14 +78,14 @@ public class MessageController {
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(
messageService.history(appId, userId, toId, msgType, keyword, startTime, endTime, page, size)));
messageService.history(appKey, userId, toId, msgType, keyword, startTime, endTime, page, size)));
}
@GetMapping("/group-history/{groupId}")
public ResponseEntity<ApiResponse<?>> groupHistory(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) ImMessageEntity.MsgType msgType,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime,
@ -93,13 +93,13 @@ public class MessageController {
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(
messageService.groupHistory(appId, groupId, userId, msgType, keyword, startTime, endTime, page, size)));
messageService.groupHistory(appKey, groupId, userId, msgType, keyword, startTime, endTime, page, size)));
}
@GetMapping("/search")
public ResponseEntity<ApiResponse<Page<ImMessageEntity>>> search(
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) ImMessageEntity.ChatType chatType,
@RequestParam(required = false) ImMessageEntity.MsgType msgType,
@RequestParam(required = false) String keyword,
@ -109,21 +109,21 @@ public class MessageController {
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(
messageRepository.searchByKeywordForUser(
appId, userId, chatType, msgType, keyword, startTime, endTime, PageRequest.of(page, size))));
appKey, userId, chatType, msgType, keyword, startTime, endTime, PageRequest.of(page, size))));
}
@GetMapping("/offline/count")
public ResponseEntity<ApiResponse<Map<String, Object>>> offlineMessageCount(
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
long count = offlineMessageSyncService.countUndelivered(appId, userId);
@RequestParam String appKey) {
long count = offlineMessageSyncService.countUndelivered(appKey, userId);
return ResponseEntity.ok(ApiResponse.success(Map.of("count", count)));
}
@PostMapping("/offline")
public ResponseEntity<ApiResponse<List<ImMessageEntity>>> syncOfflineMessages(
@AuthenticationPrincipal String userId,
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(offlineMessageSyncService.syncAndReturn(appId, userId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(offlineMessageSyncService.syncAndReturn(appKey, userId)));
}
}

查看文件

@ -23,15 +23,15 @@ public class StatisticsController {
@GetMapping("/messages")
public ApiResponse<Map<String, Object>> messageStats(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) Integer days) {
int d = days == null ? 7 : days;
LocalDateTime since = LocalDateTime.now().minusDays(d);
long total = messageRepository.countByAppIdAndCreatedAtAfter(appId, since);
long total = messageRepository.countByAppIdAndCreatedAtAfter(appKey, since);
long single = messageRepository.countByAppIdAndChatTypeAndCreatedAtAfter(
appId, com.xuqm.im.entity.ImMessageEntity.ChatType.SINGLE, since);
appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.SINGLE, since);
long group = messageRepository.countByAppIdAndChatTypeAndCreatedAtAfter(
appId, com.xuqm.im.entity.ImMessageEntity.ChatType.GROUP, since);
appKey, com.xuqm.im.entity.ImMessageEntity.ChatType.GROUP, since);
return ApiResponse.success(Map.of(
"totalMessages", total,
"singleMessages", single,
@ -42,13 +42,13 @@ public class StatisticsController {
@GetMapping("/webhooks")
public ApiResponse<Map<String, Object>> webhookStats(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) Integer days) {
int d = days == null ? 7 : days;
LocalDateTime since = LocalDateTime.now().minusDays(d);
long success = webhookDeliveryRepository.countSuccessfulByAppIdSince(appId, since);
long failed = webhookDeliveryRepository.countFailedByAppIdSince(appId, since);
List<Object[]> raw = webhookDeliveryRepository.statsByAppIdSince(appId, since);
long success = webhookDeliveryRepository.countSuccessfulByAppIdSince(appKey, since);
long failed = webhookDeliveryRepository.countFailedByAppIdSince(appKey, since);
List<Object[]> raw = webhookDeliveryRepository.statsByAppIdSince(appKey, since);
List<Map<String, Object>> eventStats = new ArrayList<>();
for (Object[] row : raw) {
eventStats.add(Map.of(

查看文件

@ -1,7 +1,7 @@
package com.xuqm.im.model;
public record BlacklistCallbackPayload(
String appId,
String appKey,
String id,
String userId,
String blockedUserId,

查看文件

@ -1,7 +1,7 @@
package com.xuqm.im.model;
public record FriendRequestCallbackPayload(
String appId,
String appKey,
String requestId,
String fromUserId,
String toUserId,

查看文件

@ -1,7 +1,7 @@
package com.xuqm.im.model;
public record GroupJoinRequestCallbackPayload(
String appId,
String appKey,
String requestId,
String groupId,
String groupName,

查看文件

@ -3,7 +3,7 @@ package com.xuqm.im.model;
import java.util.List;
public record MessageReadCallbackPayload(
String appId,
String appKey,
String readerId,
String peerId,
String groupId,

查看文件

@ -9,5 +9,5 @@ public record WebhookCallbackEnvelope(
long requestTime,
JsonNode payload,
String signature,
String appId
String appKey
) {}

查看文件

@ -22,12 +22,12 @@ public class BlacklistService {
}
@Transactional
public ImBlacklistEntity add(String appId, String userId, String blockedUserId) {
return repository.findByAppIdAndUserIdAndBlockedUserId(appId, userId, blockedUserId)
public ImBlacklistEntity add(String appKey, String userId, String blockedUserId) {
return repository.findByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId)
.orElseGet(() -> {
ImBlacklistEntity entity = new ImBlacklistEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setUserId(userId);
entity.setBlockedUserId(blockedUserId);
entity.setCreatedAt(LocalDateTime.now());
@ -38,29 +38,29 @@ public class BlacklistService {
}
@Transactional
public void remove(String appId, String userId, String blockedUserId) {
ImBlacklistEntity entity = repository.findByAppIdAndUserIdAndBlockedUserId(appId, userId, blockedUserId)
public void remove(String appKey, String userId, String blockedUserId) {
ImBlacklistEntity entity = repository.findByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId)
.orElse(null);
repository.deleteByAppIdAndUserIdAndBlockedUserId(appId, userId, blockedUserId);
repository.deleteByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId);
if (entity != null) {
dispatchWebhook(entity, "blacklist.removed");
}
}
public List<ImBlacklistEntity> list(String appId, String userId) {
return repository.findByAppIdAndUserId(appId, userId);
public List<ImBlacklistEntity> list(String appKey, String userId) {
return repository.findByAppIdAndUserId(appKey, userId);
}
public List<ImBlacklistEntity> listByApp(String appId) {
return repository.findByAppId(appId);
public List<ImBlacklistEntity> listByApp(String appKey) {
return repository.findByAppId(appKey);
}
public boolean isBlocked(String appId, String userId, String blockedUserId) {
return repository.existsByAppIdAndUserIdAndBlockedUserId(appId, userId, blockedUserId);
public boolean isBlocked(String appKey, String userId, String blockedUserId) {
return repository.existsByAppIdAndUserIdAndBlockedUserId(appKey, userId, blockedUserId);
}
public boolean isEitherBlocked(String appId, String userId, String targetUserId) {
return isBlocked(appId, userId, targetUserId) || isBlocked(appId, targetUserId, userId);
public boolean isEitherBlocked(String appKey, String userId, String targetUserId) {
return isBlocked(appKey, userId, targetUserId) || isBlocked(appKey, targetUserId, userId);
}
private void dispatchWebhook(ImBlacklistEntity entity, String callbackEvent) {

查看文件

@ -20,24 +20,24 @@ public class ConversationStateService {
}
@Transactional
public ImConversationStateEntity setPinned(String appId, String userId, String targetId, String chatType, boolean pinned) {
ImConversationStateEntity state = getOrCreate(appId, userId, targetId, chatType);
public ImConversationStateEntity setPinned(String appKey, String userId, String targetId, String chatType, boolean pinned) {
ImConversationStateEntity state = getOrCreate(appKey, userId, targetId, chatType);
state.setPinned(pinned);
touch(state);
return repository.save(state);
}
@Transactional
public ImConversationStateEntity setMuted(String appId, String userId, String targetId, String chatType, boolean muted) {
ImConversationStateEntity state = getOrCreate(appId, userId, targetId, chatType);
public ImConversationStateEntity setMuted(String appKey, String userId, String targetId, String chatType, boolean muted) {
ImConversationStateEntity state = getOrCreate(appKey, userId, targetId, chatType);
state.setMuted(muted);
touch(state);
return repository.save(state);
}
@Transactional
public ImConversationStateEntity markRead(String appId, String userId, String targetId, String chatType) {
ImConversationStateEntity state = getOrCreate(appId, userId, targetId, chatType);
public ImConversationStateEntity markRead(String appKey, String userId, String targetId, String chatType) {
ImConversationStateEntity state = getOrCreate(appKey, userId, targetId, chatType);
state.setLastReadAt(LocalDateTime.now());
state.setHidden(false);
touch(state);
@ -45,16 +45,16 @@ public class ConversationStateService {
}
@Transactional
public ImConversationStateEntity setDraft(String appId, String userId, String targetId, String chatType, String draft) {
ImConversationStateEntity state = getOrCreate(appId, userId, targetId, chatType);
public ImConversationStateEntity setDraft(String appKey, String userId, String targetId, String chatType, String draft) {
ImConversationStateEntity state = getOrCreate(appKey, userId, targetId, chatType);
state.setDraft(draft);
touch(state);
return repository.save(state);
}
@Transactional
public ImConversationStateEntity setHidden(String appId, String userId, String targetId, String chatType, boolean hidden) {
ImConversationStateEntity state = getOrCreate(appId, userId, targetId, chatType);
public ImConversationStateEntity setHidden(String appKey, String userId, String targetId, String chatType, boolean hidden) {
ImConversationStateEntity state = getOrCreate(appKey, userId, targetId, chatType);
state.setHidden(hidden);
if (hidden) {
state.setDraft(null);
@ -65,20 +65,20 @@ public class ConversationStateService {
@Transactional
public ImConversationStateEntity setConversationGroup(
String appId,
String appKey,
String userId,
String targetId,
String chatType,
String conversationGroup) {
ImConversationStateEntity state = getOrCreate(appId, userId, targetId, chatType);
ImConversationStateEntity state = getOrCreate(appKey, userId, targetId, chatType);
state.setConversationGroup(normalizeGroup(conversationGroup));
touch(state);
return repository.save(state);
}
@Transactional
public ImConversationStateEntity hideConversation(String appId, String userId, String targetId, String chatType) {
ImConversationStateEntity state = getOrCreate(appId, userId, targetId, chatType);
public ImConversationStateEntity hideConversation(String appKey, String userId, String targetId, String chatType) {
ImConversationStateEntity state = getOrCreate(appKey, userId, targetId, chatType);
state.setHidden(true);
state.setDraft(null);
touch(state);
@ -86,28 +86,28 @@ public class ConversationStateService {
}
@Transactional
public void deleteConversationState(String appId, String userId, String targetId, String chatType) {
repository.deleteByAppIdAndUserIdAndTargetIdAndChatType(appId, userId, targetId, chatType);
public void deleteConversationState(String appKey, String userId, String targetId, String chatType) {
repository.deleteByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType);
}
@Transactional
public void deleteConversation(String appId,
public void deleteConversation(String appKey,
String userId,
String targetId,
String chatType,
boolean syncAcrossClients) {
if (syncAcrossClients) {
deleteConversationState(appId, userId, targetId, chatType);
deleteConversationState(appKey, userId, targetId, chatType);
return;
}
hideConversation(appId, userId, targetId, chatType);
hideConversation(appKey, userId, targetId, chatType);
}
@Transactional
public void clearHiddenForUsers(String appId, String targetId, String chatType, Collection<String> userIds) {
public void clearHiddenForUsers(String appKey, String targetId, String chatType, Collection<String> userIds) {
for (String userId : userIds) {
ImConversationStateEntity state = repository
.findByAppIdAndUserIdAndTargetIdAndChatType(appId, userId, targetId, chatType)
.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
.orElse(null);
if (state != null && state.isHidden()) {
state.setHidden(false);
@ -117,12 +117,12 @@ public class ConversationStateService {
}
}
public ImConversationStateEntity getOrCreate(String appId, String userId, String targetId, String chatType) {
return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appId, userId, targetId, chatType)
public ImConversationStateEntity getOrCreate(String appKey, String userId, String targetId, String chatType) {
return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
.orElseGet(() -> {
ImConversationStateEntity entity = new ImConversationStateEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setUserId(userId);
entity.setTargetId(targetId);
entity.setChatType(chatType);
@ -138,17 +138,17 @@ public class ConversationStateService {
});
}
public ImConversationStateEntity find(String appId, String userId, String targetId, String chatType) {
return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appId, userId, targetId, chatType)
public ImConversationStateEntity find(String appKey, String userId, String targetId, String chatType) {
return repository.findByAppIdAndUserIdAndTargetIdAndChatType(appKey, userId, targetId, chatType)
.orElse(null);
}
public List<ImConversationStateEntity> listVisible(String appId, String userId) {
return repository.findByAppIdAndUserIdAndHiddenFalse(appId, userId);
public List<ImConversationStateEntity> listVisible(String appKey, String userId) {
return repository.findByAppIdAndUserIdAndHiddenFalse(appKey, userId);
}
public List<String> listConversationGroups(String appId, String userId) {
return repository.findByAppIdAndUserId(appId, userId).stream()
public List<String> listConversationGroups(String appKey, String userId) {
return repository.findByAppIdAndUserId(appKey, userId).stream()
.map(ImConversationStateEntity::getConversationGroup)
.filter(group -> group != null && !group.isBlank())
.distinct()
@ -156,8 +156,8 @@ public class ConversationStateService {
.toList();
}
public List<ImConversationStateEntity> listByConversationGroup(String appId, String userId, String conversationGroup) {
return repository.findByAppIdAndUserIdAndConversationGroup(appId, userId, normalizeGroup(conversationGroup));
public List<ImConversationStateEntity> listByConversationGroup(String appKey, String userId, String conversationGroup) {
return repository.findByAppIdAndUserIdAndConversationGroup(appKey, userId, normalizeGroup(conversationGroup));
}
private void touch(ImConversationStateEntity entity) {

查看文件

@ -45,18 +45,18 @@ public class FriendRequestService {
}
@Transactional
public ImFriendRequestEntity send(String appId, String fromUserId, String toUserId, String remark) {
String mode = featureConfigClient.friendRequestMode(appId);
public ImFriendRequestEntity send(String appKey, String fromUserId, String toUserId, String remark) {
String mode = featureConfigClient.friendRequestMode(appKey);
if ("DISALLOW".equals(mode)) {
throw new BusinessException(403, "当前应用未开放好友申请");
}
final boolean[] created = {false};
ImFriendRequestEntity saved = requestRepository.findByAppIdAndFromUserIdAndToUserId(appId, fromUserId, toUserId)
ImFriendRequestEntity saved = requestRepository.findByAppIdAndFromUserIdAndToUserId(appKey, fromUserId, toUserId)
.orElseGet(() -> {
created[0] = true;
ImFriendRequestEntity entity = new ImFriendRequestEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setFromUserId(fromUserId);
entity.setToUserId(toUserId);
entity.setRemark(remark);
@ -88,17 +88,17 @@ public class FriendRequestService {
}
@Transactional
public ImFriendRequestEntity accept(String appId, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appId, requestId, operatorId);
public ImFriendRequestEntity accept(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId);
request.setStatus(ImFriendRequestEntity.Status.ACCEPTED.name());
request.setReviewedAt(LocalDateTime.now());
requestRepository.save(request);
friendRepository
.findByAppIdAndUserIdAndFriendId(appId, request.getFromUserId(), request.getToUserId())
.orElseGet(() -> friendEntity(appId, request.getFromUserId(), request.getToUserId()));
.findByAppIdAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId())
.orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId()));
friendRepository
.findByAppIdAndUserIdAndFriendId(appId, request.getToUserId(), request.getFromUserId())
.orElseGet(() -> friendEntity(appId, request.getToUserId(), request.getFromUserId()));
.findByAppIdAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId())
.orElseGet(() -> friendEntity(appKey, request.getToUserId(), request.getFromUserId()));
dispatchWebhook(request, "friend.request.accepted");
publishNotification(
request,
@ -112,8 +112,8 @@ public class FriendRequestService {
}
@Transactional
public ImFriendRequestEntity reject(String appId, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appId, requestId, operatorId);
public ImFriendRequestEntity reject(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId);
request.setStatus(ImFriendRequestEntity.Status.REJECTED.name());
request.setReviewedAt(LocalDateTime.now());
ImFriendRequestEntity saved = requestRepository.save(request);
@ -130,69 +130,69 @@ public class FriendRequestService {
}
@Transactional
public List<ImFriendRequestEntity> acceptBatch(String appId, List<String> requestIds, String operatorId) {
public List<ImFriendRequestEntity> acceptBatch(String appKey, List<String> requestIds, String operatorId) {
List<ImFriendRequestEntity> result = new java.util.ArrayList<>();
for (String requestId : unique(requestIds)) {
result.add(acceptInternal(appId, requestId, operatorId));
result.add(acceptInternal(appKey, requestId, operatorId));
}
return result;
}
@Transactional
public List<ImFriendRequestEntity> rejectBatch(String appId, List<String> requestIds, String operatorId) {
public List<ImFriendRequestEntity> rejectBatch(String appKey, List<String> requestIds, String operatorId) {
List<ImFriendRequestEntity> result = new java.util.ArrayList<>();
for (String requestId : unique(requestIds)) {
result.add(rejectInternal(appId, requestId, operatorId));
result.add(rejectInternal(appKey, requestId, operatorId));
}
return result;
}
public List<ImFriendRequestEntity> incoming(String appId, String userId) {
return requestRepository.findByAppIdAndToUserId(appId, userId).stream()
public List<ImFriendRequestEntity> incoming(String appKey, String userId) {
return requestRepository.findByAppIdAndToUserId(appKey, userId).stream()
.filter(request -> ImFriendRequestEntity.Status.PENDING.name().equals(request.getStatus()))
.toList();
}
public List<ImFriendRequestEntity> outgoing(String appId, String userId) {
return requestRepository.findByAppIdAndFromUserId(appId, userId);
public List<ImFriendRequestEntity> outgoing(String appKey, String userId) {
return requestRepository.findByAppIdAndFromUserId(appKey, userId);
}
public List<ImFriendRequestEntity> listByApp(String appId) {
return requestRepository.findByAppId(appId);
public List<ImFriendRequestEntity> listByApp(String appKey) {
return requestRepository.findByAppId(appKey);
}
@Transactional
public ImFriendRequestEntity adminAccept(String appId, String requestId) {
ImFriendRequestEntity request = getRequest(appId, requestId);
public ImFriendRequestEntity adminAccept(String appKey, String requestId) {
ImFriendRequestEntity request = getRequest(appKey, requestId);
return acceptRequest(request);
}
@Transactional
public ImFriendRequestEntity adminReject(String appId, String requestId) {
ImFriendRequestEntity request = getRequest(appId, requestId);
public ImFriendRequestEntity adminReject(String appKey, String requestId) {
ImFriendRequestEntity request = getRequest(appKey, requestId);
return rejectRequest(request);
}
private ImFriendRequestEntity getRequest(String appId, String requestId, String operatorId) {
private ImFriendRequestEntity getRequest(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "好友申请不存在"));
if (!request.getAppId().equals(appId) || !request.getToUserId().equals(operatorId)) {
if (!request.getAppId().equals(appKey) || !request.getToUserId().equals(operatorId)) {
throw new BusinessException(403, "无权操作");
}
return request;
}
private ImFriendRequestEntity getRequest(String appId, String requestId) {
private ImFriendRequestEntity getRequest(String appKey, String requestId) {
ImFriendRequestEntity request = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "好友申请不存在"));
if (!request.getAppId().equals(appId)) {
if (!request.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
return request;
}
private ImFriendRequestEntity acceptInternal(String appId, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appId, requestId, operatorId);
private ImFriendRequestEntity acceptInternal(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId);
return acceptRequest(request);
}
@ -218,14 +218,14 @@ public class FriendRequestService {
return saved;
}
private ImFriendRequestEntity rejectInternal(String appId, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appId, requestId, operatorId);
private ImFriendRequestEntity rejectInternal(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId);
return rejectRequest(request);
}
private com.xuqm.im.entity.ImFriendEntity friendEntity(String appId, String userId, String friendId) {
private com.xuqm.im.entity.ImFriendEntity friendEntity(String appKey, String userId, String friendId) {
com.xuqm.im.entity.ImFriendEntity entity = new com.xuqm.im.entity.ImFriendEntity();
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setUserId(userId);
entity.setFriendId(friendId);
return friendRepository.save(entity);

查看文件

@ -16,15 +16,15 @@ public class GlobalMuteService {
this.repository = repository;
}
public boolean isEnabled(String appId) {
return repository.findByAppId(appId).map(ImGlobalMuteEntity::isEnabled).orElse(false);
public boolean isEnabled(String appKey) {
return repository.findByAppId(appKey).map(ImGlobalMuteEntity::isEnabled).orElse(false);
}
public ImGlobalMuteEntity get(String appId) {
return repository.findByAppId(appId).orElseGet(() -> {
public ImGlobalMuteEntity get(String appKey) {
return repository.findByAppId(appKey).orElseGet(() -> {
ImGlobalMuteEntity entity = new ImGlobalMuteEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setEnabled(false);
entity.setCreatedAt(LocalDateTime.now());
entity.setUpdatedAt(LocalDateTime.now());
@ -32,11 +32,11 @@ public class GlobalMuteService {
});
}
public ImGlobalMuteEntity setEnabled(String appId, boolean enabled) {
ImGlobalMuteEntity entity = repository.findByAppId(appId).orElseGet(() -> {
public ImGlobalMuteEntity setEnabled(String appKey, boolean enabled) {
ImGlobalMuteEntity entity = repository.findByAppId(appKey).orElseGet(() -> {
ImGlobalMuteEntity created = new ImGlobalMuteEntity();
created.setId(UUID.randomUUID().toString());
created.setAppId(appId);
created.setAppId(appKey);
created.setCreatedAt(LocalDateTime.now());
return created;
});

查看文件

@ -28,7 +28,7 @@ public class ImAccountService {
this.appSecretClient = appSecretClient;
}
public void validateSignature(String appId,
public void validateSignature(String appKey,
String userId,
String timestamp,
String nonce,
@ -39,19 +39,19 @@ public class ImAccountService {
} catch (NumberFormatException e) {
throw new BusinessException(401, "Invalid app signature");
}
String secret = appSecretClient.getAppSecret(appId);
String payload = AppRequestSignatureUtil.payload(appId, userId, ts, nonce);
String secret = appSecretClient.getAppSecret(appKey);
String payload = AppRequestSignatureUtil.payload(appKey, userId, ts, nonce);
if (!AppRequestSignatureUtil.matches(secret, payload, signature)) {
throw new BusinessException(401, "Invalid app signature");
}
}
public LoginResult loginOrRegister(String appId, String userId) {
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appId, userId)
public LoginResult loginOrRegister(String appKey, String userId) {
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId)
.orElseGet(() -> {
ImAccountEntity e = new ImAccountEntity();
e.setId(UUID.randomUUID().toString());
e.setAppId(appId);
e.setAppId(appKey);
e.setUserId(userId);
e.setGender(ImAccountEntity.Gender.UNKNOWN);
e.setStatus(ImAccountEntity.Status.ACTIVE);
@ -63,17 +63,17 @@ public class ImAccountService {
throw new BusinessException(403, "账号已被封禁");
}
return new LoginResult(jwtUtil.generate(userId, Map.of("appId", appId, "role", "USER")));
return new LoginResult(jwtUtil.generate(userId, Map.of("appKey", appKey, "role", "USER")));
}
public ImAccountEntity getAccount(String appId, String userId) {
return accountRepository.findByAppIdAndUserId(appId, userId)
public ImAccountEntity getAccount(String appKey, String userId) {
return accountRepository.findByAppIdAndUserId(appKey, userId)
.orElseThrow(() -> new BusinessException(404, "账号不存在"));
}
public ImAccountEntity updateAccount(String appId, String userId, String nickname,
public ImAccountEntity updateAccount(String appKey, String userId, String nickname,
String avatar, ImAccountEntity.Gender gender) {
ImAccountEntity account = getAccount(appId, userId);
ImAccountEntity account = getAccount(appKey, userId);
if (nickname != null) account.setNickname(nickname);
if (avatar != null) account.setAvatar(avatar);
if (gender != null) account.setGender(gender);
@ -81,13 +81,13 @@ public class ImAccountService {
}
public ImAccountEntity updateAccount(
String appId,
String appKey,
String userId,
String nickname,
String avatar,
ImAccountEntity.Gender gender,
ImAccountEntity.Status status) {
ImAccountEntity account = getAccount(appId, userId);
ImAccountEntity account = getAccount(appKey, userId);
if (nickname != null) account.setNickname(nickname);
if (avatar != null) account.setAvatar(avatar);
if (gender != null) account.setGender(gender);
@ -95,14 +95,14 @@ public class ImAccountService {
return accountRepository.save(account);
}
public ImAccountEntity importAccount(String appId, String userId, String nickname,
public ImAccountEntity importAccount(String appKey, String userId, String nickname,
String avatar, ImAccountEntity.Gender gender,
ImAccountEntity.Status status) {
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appId, userId)
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appKey, userId)
.orElseGet(() -> {
ImAccountEntity entity = new ImAccountEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setUserId(userId);
entity.setCreatedAt(LocalDateTime.now());
return entity;
@ -114,24 +114,24 @@ public class ImAccountService {
return accountRepository.save(account);
}
public List<ImAccountEntity> importAccounts(String appId, List<ImportAccountRequest> requests) {
public List<ImAccountEntity> importAccounts(String appKey, List<ImportAccountRequest> requests) {
return requests == null ? List.of() : requests.stream()
.filter(req -> req != null && req.userId() != null && !req.userId().isBlank())
.map(req -> importAccount(appId, req.userId(), req.nickname(), req.avatar(), req.gender(), req.status()))
.map(req -> importAccount(appKey, req.userId(), req.nickname(), req.avatar(), req.gender(), req.status()))
.toList();
}
public void deleteAccount(String appId, String userId) {
accountRepository.findByAppIdAndUserId(appId, userId)
public void deleteAccount(String appKey, String userId) {
accountRepository.findByAppIdAndUserId(appKey, userId)
.ifPresent(accountRepository::delete);
}
public boolean exists(String appId, String userId) {
return accountRepository.existsByAppIdAndUserId(appId, userId);
public boolean exists(String appKey, String userId) {
return accountRepository.existsByAppIdAndUserId(appKey, userId);
}
public List<ImAccountEntity> searchAccounts(String appId, String keyword, int size) {
return accountRepository.searchByKeyword(appId, keyword, PageRequest.of(0, Math.max(size, 1)));
public List<ImAccountEntity> searchAccounts(String appKey, String keyword, int size) {
return accountRepository.searchByKeyword(appKey, keyword, PageRequest.of(0, Math.max(size, 1)));
}
public record ImportAccountRequest(

查看文件

@ -27,14 +27,14 @@ public class ImAppSecretClient {
@Value("${im.internal-token:xuqm-internal-token}")
private String internalToken;
public String getAppSecret(String appId) {
return cache.computeIfAbsent(appId, this::fetchAppSecret);
public String getAppSecret(String appKey) {
return cache.computeIfAbsent(appKey, this::fetchAppSecret);
}
private String fetchAppSecret(String appId) {
private String fetchAppSecret(String appKey) {
String url = UriComponentsBuilder.fromHttpUrl(tenantServiceUrl)
.path("/api/internal/sdk/apps/{appId}/secret")
.buildAndExpand(appId)
.path("/api/internal/sdk/apps/{appKey}/secret")
.buildAndExpand(appKey)
.toUriString();
HttpHeaders headers = new HttpHeaders();
headers.set("X-Internal-Token", internalToken);
@ -54,6 +54,6 @@ public class ImAppSecretClient {
} catch (RestClientException e) {
throw new BusinessException(502, "Failed to resolve app secret: " + e.getMessage());
}
throw new BusinessException(502, "Failed to resolve app secret for appId: " + appId);
throw new BusinessException(502, "Failed to resolve app secret for appKey: " + appKey);
}
}

查看文件

@ -31,16 +31,16 @@ public class ImFeatureConfigClient {
this.restTemplate = new RestTemplate();
}
public boolean allowStrangerMessage(String appId) {
return readConfig(appId).path("allowStrangerMessage").asBoolean(false);
public boolean allowStrangerMessage(String appKey) {
return readConfig(appKey).path("allowStrangerMessage").asBoolean(false);
}
public boolean allowFriendRequest(String appId) {
return readConfig(appId).path("allowFriendRequest").asBoolean(true);
public boolean allowFriendRequest(String appKey) {
return readConfig(appKey).path("allowFriendRequest").asBoolean(true);
}
public String friendRequestMode(String appId) {
JsonNode node = readConfig(appId);
public String friendRequestMode(String appKey) {
JsonNode node = readConfig(appKey);
String mode = node.path("friendRequestMode").asText("");
String normalized = mode == null ? "" : mode.trim().toUpperCase();
return switch (normalized) {
@ -49,46 +49,46 @@ public class ImFeatureConfigClient {
};
}
public boolean allowGroupJoinRequest(String appId) {
return readConfig(appId).path("allowGroupJoinRequest").asBoolean(true);
public boolean allowGroupJoinRequest(String appKey) {
return readConfig(appKey).path("allowGroupJoinRequest").asBoolean(true);
}
public boolean blacklistSendSuccess(String appId) {
return readConfig(appId).path("blacklistSendSuccess").asBoolean(true);
public boolean blacklistSendSuccess(String appKey) {
return readConfig(appKey).path("blacklistSendSuccess").asBoolean(true);
}
public int messageRecallMinutes(String appId) {
return Math.max(readConfig(appId).path("messageRecallMinutes").asInt(2), 0);
public int messageRecallMinutes(String appKey) {
return Math.max(readConfig(appKey).path("messageRecallMinutes").asInt(2), 0);
}
public int historyRetentionDays(String appId) {
return Math.max(readConfig(appId).path("historyRetentionDays").asInt(7), 1);
public int historyRetentionDays(String appKey) {
return Math.max(readConfig(appKey).path("historyRetentionDays").asInt(7), 1);
}
public int conversationPullLimit(String appId) {
return Math.min(Math.max(readConfig(appId).path("conversationPullLimit").asInt(100), 1), 500);
public int conversationPullLimit(String appKey) {
return Math.min(Math.max(readConfig(appKey).path("conversationPullLimit").asInt(100), 1), 500);
}
public boolean multiClientConversationDeleteSync(String appId) {
return readConfig(appId).path("multiClientConversationDeleteSync").asBoolean(false);
public boolean multiClientConversationDeleteSync(String appKey) {
return readConfig(appKey).path("multiClientConversationDeleteSync").asBoolean(false);
}
public boolean allowMultiDeviceLogin(String appId) {
return readConfig(appId).path("allowMultiDeviceLogin").asBoolean(true);
public boolean allowMultiDeviceLogin(String appKey) {
return readConfig(appKey).path("allowMultiDeviceLogin").asBoolean(true);
}
public String multiDeviceLoginMode(String appId) {
String mode = readConfig(appId).path("multiDeviceLoginMode").asText("MULTI_DEVICE_FREE");
public String multiDeviceLoginMode(String appKey) {
String mode = readConfig(appKey).path("multiDeviceLoginMode").asText("MULTI_DEVICE_FREE");
return switch (mode.toUpperCase()) {
case "SAME_PLATFORM_ONE", "SINGLE_DEVICE" -> mode.toUpperCase();
default -> "MULTI_DEVICE_FREE";
};
}
private JsonNode readConfig(String appId) {
private JsonNode readConfig(String appKey) {
String url = UriComponentsBuilder.fromHttpUrl(tenantServiceUrl)
.path("/api/internal/sdk/apps/{appId}/services/{platform}/{serviceType}")
.buildAndExpand(appId, "ANDROID", "IM")
.path("/api/internal/sdk/apps/{appKey}/services/{platform}/{serviceType}")
.buildAndExpand(appKey, "ANDROID", "IM")
.toUriString();
HttpHeaders headers = new HttpHeaders();
headers.set("X-Internal-Token", internalToken);
@ -108,7 +108,7 @@ public class ImFeatureConfigClient {
}
}
} catch (Exception e) {
log.warn("Failed to read IM feature config for appId={}: {}", appId, e.getMessage());
log.warn("Failed to read IM feature config for appKey={}: {}", appKey, e.getMessage());
}
return OBJECT_MAPPER.createObjectNode();
}

查看文件

@ -64,7 +64,7 @@ public class ImGroupService {
@Transactional
public ImGroupEntity create(
String appId,
String appKey,
String name,
String creatorId,
List<String> memberIds,
@ -75,7 +75,7 @@ public class ImGroupService {
ImGroupEntity group = new ImGroupEntity();
group.setId(UUID.randomUUID().toString());
group.setAppId(appId);
group.setAppId(appKey);
group.setName(name);
group.setGroupType(normalizeGroupType(groupType));
group.setCreatorId(creatorId);
@ -85,7 +85,7 @@ public class ImGroupService {
group.setExtAttributes("{}");
group.setCreatedAt(LocalDateTime.now());
ImGroupEntity saved = groupRepository.save(group);
webhookDispatchService.dispatch(appId, "group", "group.created",
webhookDispatchService.dispatch(appKey, "group", "group.created",
java.util.Map.of("groupId", saved.getId(), "name", saved.getName(),
"creatorId", creatorId, "memberIds", members));
return saved;
@ -267,17 +267,17 @@ public class ImGroupService {
return fromJson(group.getAdminIds());
}
public List<ImGroupEntity> listByApp(String appId) {
return groupRepository.findByAppId(appId);
public List<ImGroupEntity> listByApp(String appKey) {
return groupRepository.findByAppId(appKey);
}
public List<ImGroupEntity> listUserGroups(String appId, String userId) {
return groupRepository.findUserGroups(appId, userId);
public List<ImGroupEntity> listUserGroups(String appKey, String userId) {
return groupRepository.findUserGroups(appKey, userId);
}
public List<ImGroupEntity> listPublicGroups(String appId, String keyword) {
public List<ImGroupEntity> listPublicGroups(String appKey, String keyword) {
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase();
return groupRepository.findByAppId(appId).stream()
return groupRepository.findByAppId(appKey).stream()
.filter(group -> "PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType())))
.filter(group -> normalizedKeyword.isBlank()
|| group.getName().toLowerCase().contains(normalizedKeyword)
@ -285,35 +285,35 @@ public class ImGroupService {
.toList();
}
public List<ImGroupEntity> searchGroups(String appId, String keyword, int size) {
return groupRepository.searchByKeyword(appId, keyword, PageRequest.of(0, Math.max(size, 1)));
public List<ImGroupEntity> searchGroups(String appKey, String keyword, int size) {
return groupRepository.searchByKeyword(appKey, keyword, PageRequest.of(0, Math.max(size, 1)));
}
public List<ImAccountEntity> listMembers(String appId, String groupId, String requesterId) {
public List<ImAccountEntity> listMembers(String appKey, String groupId, String requesterId) {
ImGroupEntity group = get(groupId, requesterId);
return resolveMembers(appId, memberIds(group));
return resolveMembers(appKey, memberIds(group));
}
public List<ImAccountEntity> searchMembers(String appId, String groupId, String requesterId, String keyword, int size) {
public List<ImAccountEntity> searchMembers(String appKey, String groupId, String requesterId, String keyword, int size) {
ImGroupEntity group = get(groupId, requesterId);
List<String> ids = memberIds(group);
if (keyword == null || keyword.isBlank()) {
return resolveMembers(appId, ids).stream().limit(Math.max(size, 1)).toList();
return resolveMembers(appKey, ids).stream().limit(Math.max(size, 1)).toList();
}
LinkedHashSet<String> memberIdSet = new LinkedHashSet<>(ids);
return accountRepository.searchByKeyword(appId, keyword, PageRequest.of(0, Math.max(size, 1)))
return accountRepository.searchByKeyword(appKey, keyword, PageRequest.of(0, Math.max(size, 1)))
.stream()
.filter(account -> memberIdSet.contains(account.getUserId()))
.toList();
}
@Transactional
public ImGroupJoinRequestEntity sendJoinRequest(String appId, String groupId, String requesterId, String remark) {
if (!featureConfigClient.allowGroupJoinRequest(appId)) {
public ImGroupJoinRequestEntity sendJoinRequest(String appKey, String groupId, String requesterId, String remark) {
if (!featureConfigClient.allowGroupJoinRequest(appKey)) {
throw new BusinessException(403, "当前应用未开放群加入申请");
}
ImGroupEntity group = get(groupId);
if (!group.getAppId().equals(appId)) {
if (!group.getAppId().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(appId, groupId, requesterId)
return joinRequestRepository.findByAppIdAndGroupIdAndRequesterId(appKey, groupId, requesterId)
.orElseGet(() -> {
ImGroupJoinRequestEntity entity = new ImGroupJoinRequestEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setGroupId(groupId);
entity.setRequesterId(requesterId);
entity.setRemark(remark);
@ -347,15 +347,15 @@ public class ImGroupService {
});
}
public List<ImGroupJoinRequestEntity> listJoinRequests(String appId, String groupId, String operatorId) {
public List<ImGroupJoinRequestEntity> listJoinRequests(String appKey, String groupId, String operatorId) {
ImGroupEntity group = get(groupId);
ensureCanManage(group, operatorId);
return joinRequestRepository.findByAppIdAndGroupId(appId, groupId);
return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId);
}
@Transactional
public ImGroupJoinRequestEntity acceptJoinRequest(String appId, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
public ImGroupJoinRequestEntity acceptJoinRequest(String appKey, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appKey, requestId);
ImGroupEntity group = get(request.getGroupId());
ensureCanManage(group, operatorId);
request.setStatus(ImGroupJoinRequestEntity.Status.ACCEPTED.name());
@ -376,8 +376,8 @@ public class ImGroupService {
}
@Transactional
public ImGroupJoinRequestEntity rejectJoinRequest(String appId, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
public ImGroupJoinRequestEntity rejectJoinRequest(String appKey, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appKey, requestId);
ImGroupEntity group = get(request.getGroupId());
ensureCanManage(group, operatorId);
request.setStatus(ImGroupJoinRequestEntity.Status.REJECTED.name());
@ -397,23 +397,23 @@ public class ImGroupService {
}
@Transactional
public List<ImGroupJoinRequestEntity> acceptJoinRequests(String appId, String groupId, List<String> requestIds, String operatorId) {
public List<ImGroupJoinRequestEntity> acceptJoinRequests(String appKey, String groupId, List<String> requestIds, String operatorId) {
ImGroupEntity group = get(groupId);
ensureCanManage(group, operatorId);
List<ImGroupJoinRequestEntity> result = new ArrayList<>();
for (String requestId : unique(requestIds)) {
result.add(acceptJoinRequestInternal(appId, group, requestId, operatorId));
result.add(acceptJoinRequestInternal(appKey, group, requestId, operatorId));
}
return result;
}
@Transactional
public List<ImGroupJoinRequestEntity> rejectJoinRequests(String appId, String groupId, List<String> requestIds, String operatorId) {
public List<ImGroupJoinRequestEntity> rejectJoinRequests(String appKey, String groupId, List<String> requestIds, String operatorId) {
ImGroupEntity group = get(groupId);
ensureCanManage(group, operatorId);
List<ImGroupJoinRequestEntity> result = new ArrayList<>();
for (String requestId : unique(requestIds)) {
result.add(rejectJoinRequestInternal(appId, group, requestId, operatorId));
result.add(rejectJoinRequestInternal(appKey, group, requestId, operatorId));
}
return result;
}
@ -441,27 +441,27 @@ public class ImGroupService {
}
}
private void ensureAppMatches(ImGroupEntity group, String appId) {
if (!group.getAppId().equals(appId)) {
private void ensureAppMatches(ImGroupEntity group, String appKey) {
if (!group.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
}
public List<ImAccountEntity> adminListMembers(String appId, String groupId) {
public List<ImAccountEntity> adminListMembers(String appKey, String groupId) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
return resolveMembers(appId, memberIds(group));
ensureAppMatches(group, appKey);
return resolveMembers(appKey, memberIds(group));
}
public List<ImAccountEntity> adminSearchMembers(String appId, String groupId, String keyword, int size) {
public List<ImAccountEntity> adminSearchMembers(String appKey, String groupId, String keyword, int size) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
List<String> ids = memberIds(group);
if (keyword == null || keyword.isBlank()) {
return resolveMembers(appId, ids).stream().limit(Math.max(size, 1)).toList();
return resolveMembers(appKey, ids).stream().limit(Math.max(size, 1)).toList();
}
LinkedHashSet<String> memberIdSet = new LinkedHashSet<>(ids);
return accountRepository.searchByKeyword(appId, keyword, PageRequest.of(0, Math.max(size, 1)))
return accountRepository.searchByKeyword(appKey, keyword, PageRequest.of(0, Math.max(size, 1)))
.stream()
.filter(account -> memberIdSet.contains(account.getUserId()))
.toList();
@ -477,9 +477,9 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminTransferOwner(String appId, String groupId, String newOwnerId) {
public ImGroupEntity adminTransferOwner(String appKey, String groupId, String newOwnerId) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
return transferOwnerInternal(group, newOwnerId);
}
@ -491,9 +491,9 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminUpdateExtAttributes(String appId, String groupId, Map<String, Object> attributes) {
public ImGroupEntity adminUpdateExtAttributes(String appKey, String groupId, Map<String, Object> attributes) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
return updateExtAttributesInternal(group, attributes);
}
@ -505,24 +505,24 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminRemoveExtAttributes(String appId, String groupId, List<String> keys) {
public ImGroupEntity adminRemoveExtAttributes(String appKey, String groupId, List<String> keys) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
return removeExtAttributesInternal(group, keys);
}
@Transactional
public ImGroupEntity adminAddMember(String appId, String groupId, String userId) {
public ImGroupEntity adminAddMember(String appKey, String groupId, String userId) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
addMemberInternal(group, userId);
return group;
}
@Transactional
public ImGroupEntity adminAddMembers(String appId, String groupId, List<String> userIds) {
public ImGroupEntity adminAddMembers(String appKey, String groupId, List<String> userIds) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
List<String> members = new ArrayList<>(fromJson(group.getMemberIds()));
boolean changed = false;
for (String userId : userIds == null ? List.<String>of() : userIds) {
@ -542,9 +542,9 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminRemoveMember(String appId, String groupId, String userId) {
public ImGroupEntity adminRemoveMember(String appKey, String groupId, String userId) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
List<String> members = new ArrayList<>(fromJson(group.getMemberIds()));
if (members.remove(userId)) {
group.setMemberIds(toJson(members));
@ -554,9 +554,9 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminRemoveMembers(String appId, String groupId, List<String> userIds) {
public ImGroupEntity adminRemoveMembers(String appKey, String groupId, List<String> userIds) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
List<String> members = new ArrayList<>(fromJson(group.getMemberIds()));
boolean changed = false;
for (String userId : userIds == null ? List.<String>of() : userIds) {
@ -573,9 +573,9 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminSetRole(String appId, String groupId, String userId, String role) {
public ImGroupEntity adminSetRole(String appKey, String groupId, String userId, String role) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
List<String> admins = new ArrayList<>(fromJson(group.getAdminIds()));
if ("ADMIN".equalsIgnoreCase(role)) {
if (!admins.contains(userId)) {
@ -592,9 +592,9 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminMuteMember(String appId, String groupId, String userId, long minutes) {
public ImGroupEntity adminMuteMember(String appKey, String groupId, String userId, long minutes) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
ImGroupMuteEntity mute = muteRepository
.findByGroupIdAndUserIdAndMutedUntilAfter(groupId, userId, LocalDateTime.now())
.orElseGet(() -> {
@ -611,10 +611,10 @@ public class ImGroupService {
return group;
}
public List<ImGroupJoinRequestEntity> adminListJoinRequests(String appId, String groupId) {
public List<ImGroupJoinRequestEntity> adminListJoinRequests(String appKey, String groupId) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
return joinRequestRepository.findByAppIdAndGroupId(appId, groupId);
ensureAppMatches(group, appKey);
return joinRequestRepository.findByAppIdAndGroupId(appKey, groupId);
}
@Transactional
@ -634,9 +634,9 @@ public class ImGroupService {
}
@Transactional
public ImGroupEntity adminModifyMemberInfo(String appId, String groupId, String userId, String nickName) {
public ImGroupEntity adminModifyMemberInfo(String appKey, String groupId, String userId, String nickName) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ensureAppMatches(group, appKey);
return modifyMemberInfo(groupId, userId, nickName);
}
@ -711,10 +711,10 @@ public class ImGroupService {
}
@Transactional
public ImGroupJoinRequestEntity adminAcceptJoinRequest(String appId, String groupId, String requestId) {
public ImGroupJoinRequestEntity adminAcceptJoinRequest(String appKey, String groupId, String requestId) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
ensureAppMatches(group, appKey);
ImGroupJoinRequestEntity request = getJoinRequest(appKey, requestId);
if (!group.getId().equals(request.getGroupId())) {
throw new BusinessException(400, "加群申请不属于当前群");
}
@ -727,10 +727,10 @@ public class ImGroupService {
}
@Transactional
public ImGroupJoinRequestEntity adminRejectJoinRequest(String appId, String groupId, String requestId) {
public ImGroupJoinRequestEntity adminRejectJoinRequest(String appKey, String groupId, String requestId) {
ImGroupEntity group = get(groupId);
ensureAppMatches(group, appId);
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
ensureAppMatches(group, appKey);
ImGroupJoinRequestEntity request = getJoinRequest(appKey, requestId);
if (!group.getId().equals(request.getGroupId())) {
throw new BusinessException(400, "加群申请不属于当前群");
}
@ -829,17 +829,17 @@ public class ImGroupService {
}
}
private ImGroupJoinRequestEntity getJoinRequest(String appId, String requestId) {
private ImGroupJoinRequestEntity getJoinRequest(String appKey, String requestId) {
ImGroupJoinRequestEntity request = joinRequestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "加群申请不存在"));
if (!request.getAppId().equals(appId)) {
if (!request.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
return request;
}
private ImGroupJoinRequestEntity acceptJoinRequestInternal(String appId, ImGroupEntity group, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
private ImGroupJoinRequestEntity acceptJoinRequestInternal(String appKey, ImGroupEntity group, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appKey, requestId);
if (!group.getId().equals(request.getGroupId())) {
throw new BusinessException(400, "加群申请不属于当前群");
}
@ -861,8 +861,8 @@ public class ImGroupService {
return saved;
}
private ImGroupJoinRequestEntity rejectJoinRequestInternal(String appId, ImGroupEntity group, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
private ImGroupJoinRequestEntity rejectJoinRequestInternal(String appKey, ImGroupEntity group, String requestId, String operatorId) {
ImGroupJoinRequestEntity request = getJoinRequest(appKey, requestId);
if (!group.getId().equals(request.getGroupId())) {
throw new BusinessException(400, "加群申请不属于当前群");
}
@ -891,10 +891,10 @@ public class ImGroupService {
return values == null ? List.of() : new ArrayList<>(new LinkedHashSet<>(values));
}
private List<ImAccountEntity> resolveMembers(String appId, List<String> ids) {
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(appId, userId).ifPresent(members::add);
accountRepository.findByAppIdAndUserId(appKey, userId).ifPresent(members::add);
}
return members;
}

查看文件

@ -34,14 +34,14 @@ public class ImPushBridge {
this.objectMapper = objectMapper;
}
public void sendOfflinePush(String appId, String userId, String title, String body, String payload) {
public void sendOfflinePush(String appKey, String userId, String title, String body, String payload) {
if (isOnline(userId)) {
return;
}
sendOfflinePushToUsers(appId, List.of(userId), title, body, payload);
sendOfflinePushToUsers(appKey, List.of(userId), title, body, payload);
}
public void sendOfflinePushToUsers(String appId, List<String> userIds, String title, String body, String payload) {
public void sendOfflinePushToUsers(String appKey, List<String> userIds, String title, String body, String payload) {
if (userIds == null || userIds.isEmpty()) {
return;
}
@ -56,7 +56,7 @@ public class ImPushBridge {
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-Internal-Token", internalToken);
Map<String, Object> bodyMap = Map.of(
"appId", appId,
"appKey", appKey,
"userIds", offlineUserIds,
"title", title,
"body", body,
@ -64,9 +64,9 @@ public class ImPushBridge {
);
HttpEntity<String> request = new HttpEntity<>(objectMapper.writeValueAsString(bodyMap), headers);
restTemplate.postForEntity(pushServiceUrl + "/api/push/internal/notify", request, String.class);
log.debug("Sync offline push sent appId={} users={} title={}", appId, offlineUserIds.size(), title);
log.debug("Sync offline push sent appKey={} users={} title={}", appKey, offlineUserIds.size(), title);
} catch (Exception e) {
log.warn("Failed to send offline push appId={} users={}: {}", appId, offlineUserIds.size(), e.getMessage());
log.warn("Failed to send offline push appKey={} users={}: {}", appKey, offlineUserIds.size(), e.getMessage());
}
}

查看文件

@ -32,13 +32,13 @@ public class ImPushBridgeClient {
this.objectMapper = objectMapper;
}
public void notifyUsers(String appId, List<String> userIds, String title, String body, String payload) {
public void notifyUsers(String appKey, List<String> userIds, String title, String body, String payload) {
if (userIds == null || userIds.isEmpty()) {
return;
}
try {
Map<String, Object> bodyMap = new LinkedHashMap<>();
bodyMap.put("appId", appId);
bodyMap.put("appKey", appKey);
bodyMap.put("userIds", userIds);
bodyMap.put("title", title);
bodyMap.put("body", body);
@ -52,7 +52,7 @@ public class ImPushBridgeClient {
.build();
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
log.warn("Failed to notify push-service for appId={}, userIds={}: {}", appId, userIds, e.getMessage());
log.warn("Failed to notify push-service for appKey={}, userIds={}: {}", appKey, userIds, e.getMessage());
}
}
}

查看文件

@ -19,8 +19,8 @@ public class KeywordFilterService {
this.repository = repository;
}
public String filter(String appId, String content) {
List<KeywordFilterEntity> filters = repository.findByAppIdAndEnabledTrue(appId);
public String filter(String appKey, String content) {
List<KeywordFilterEntity> filters = repository.findByAppIdAndEnabledTrue(appKey);
String result = content;
for (KeywordFilterEntity f : filters) {
try {
@ -39,10 +39,10 @@ public class KeywordFilterService {
return result;
}
public KeywordFilterEntity add(String appId, String pattern, String replacement, String action) {
public KeywordFilterEntity add(String appKey, String pattern, String replacement, String action) {
KeywordFilterEntity entity = new KeywordFilterEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setPattern(pattern);
entity.setReplacement(replacement);
entity.setAction(action);
@ -51,12 +51,12 @@ public class KeywordFilterService {
return repository.save(entity);
}
public List<KeywordFilterEntity> list(String appId) {
return repository.findByAppId(appId);
public List<KeywordFilterEntity> list(String appKey) {
return repository.findByAppId(appKey);
}
public KeywordFilterEntity update(String appId, String id, String pattern, String replacement, String action, Boolean enabled) {
KeywordFilterEntity entity = repository.findByIdAndAppId(id, appId)
public KeywordFilterEntity update(String appKey, String id, String pattern, String replacement, String action, Boolean enabled) {
KeywordFilterEntity entity = repository.findByIdAndAppId(id, appKey)
.orElseThrow(() -> new BusinessException(404, "关键词过滤规则不存在"));
if (pattern != null) {
entity.setPattern(pattern);
@ -73,8 +73,8 @@ public class KeywordFilterService {
return repository.save(entity);
}
public void delete(String appId, String id) {
KeywordFilterEntity entity = repository.findByIdAndAppId(id, appId)
public void delete(String appKey, String id) {
KeywordFilterEntity entity = repository.findByIdAndAppId(id, appKey)
.orElseThrow(() -> new BusinessException(404, "关键词过滤规则不存在"));
repository.delete(entity);
}

查看文件

@ -76,13 +76,13 @@ public class MessageService {
this.objectMapper = objectMapper;
}
public ImMessageEntity send(String appId, String fromUserId, SendMessageRequest req) {
if (globalMuteService.isEnabled(appId)) {
public ImMessageEntity send(String appKey, String fromUserId, SendMessageRequest req) {
if (globalMuteService.isEnabled(appKey)) {
throw new BusinessException(403, "当前应用已开启全局禁言");
}
String content = req.content();
if (req.msgType() == ImMessageEntity.MsgType.TEXT) {
content = keywordFilterService.filter(appId, content);
content = keywordFilterService.filter(appKey, content);
if (content == null) {
throw new BusinessException("消息包含违禁内容");
}
@ -97,13 +97,13 @@ public class MessageService {
if (groupService.isMemberMuted(req.toId(), fromUserId)) {
throw new BusinessException(403, "当前用户已被禁言");
}
} else if (!isFriend(appId, fromUserId, req.toId())
&& !featureConfigClient.allowStrangerMessage(appId)) {
} else if (!isFriend(appKey, fromUserId, req.toId())
&& !featureConfigClient.allowStrangerMessage(appKey)) {
throw new BusinessException(403, "仅允许好友之间发送消息");
} else {
boolean senderBlocksReceiver = blacklistService.isBlocked(appId, fromUserId, req.toId());
receiverBlocksSender = blacklistService.isBlocked(appId, req.toId(), fromUserId);
boolean blacklistSendSuccess = featureConfigClient.blacklistSendSuccess(appId);
boolean senderBlocksReceiver = blacklistService.isBlocked(appKey, fromUserId, req.toId());
receiverBlocksSender = blacklistService.isBlocked(appKey, req.toId(), fromUserId);
boolean blacklistSendSuccess = featureConfigClient.blacklistSendSuccess(appKey);
if (senderBlocksReceiver && receiverBlocksSender) {
throw new BusinessException(403, "已被拉黑,无法发送消息");
}
@ -116,7 +116,7 @@ public class MessageService {
message.setId(req.messageId() != null && !req.messageId().isBlank()
? req.messageId()
: UUID.randomUUID().toString());
message.setAppId(appId);
message.setAppId(appKey);
message.setFromUserId(fromUserId);
message.setToId(req.toId());
message.setChatType(req.chatType());
@ -127,42 +127,42 @@ public class MessageService {
message.setCreatedAt(LocalDateTime.now());
ImMessageEntity saved = messageRepository.save(message);
if (req.chatType() == ImMessageEntity.ChatType.GROUP) {
saved.setGroupReadCount(groupReadCount(appId, req.toId(), saved.getCreatedAt(), saved.getFromUserId()));
saved.setGroupReadCount(groupReadCount(appKey, req.toId(), saved.getCreatedAt(), saved.getFromUserId()));
}
if (req.chatType() == ImMessageEntity.ChatType.SINGLE && !fromUserId.equals(req.toId())) {
log.debug("echo message back to sender appId={} from={} to={}",
appId, fromUserId, req.toId());
log.debug("echo message back to sender appKey={} from={} to={}",
appKey, fromUserId, req.toId());
clusterPublisher.publish("/user/" + fromUserId + "/queue/messages", saved);
if (!receiverBlocksSender) {
log.debug("deliver message to receiver appId={} from={} to={}",
appId, fromUserId, req.toId());
log.debug("deliver message to receiver appKey={} from={} to={}",
appKey, fromUserId, req.toId());
boolean receiverOnline = userPresenceService.isOnline(req.toId());
if (receiverOnline) {
clusterPublisher.publish("/user/" + req.toId() + "/queue/messages", saved);
} else {
offlineMessageSyncService.storeOfflineMessage(appId, req.toId(), saved.getId());
offlineMessageSyncService.storeOfflineMessage(appKey, req.toId(), saved.getId());
}
conversationStateService.clearHiddenForUsers(appId, req.toId(), req.chatType().name(), List.of(fromUserId, req.toId()));
conversationStateService.clearHiddenForUsers(appKey, req.toId(), req.chatType().name(), List.of(fromUserId, req.toId()));
imPushBridge.sendOfflinePushToUsers(
appId,
appKey,
List.of(req.toId()),
"新消息",
saved.getContent(),
buildPushPayload(saved)
);
} else {
conversationStateService.clearHiddenForUsers(appId, req.toId(), req.chatType().name(), List.of(fromUserId));
conversationStateService.clearHiddenForUsers(appKey, req.toId(), req.chatType().name(), List.of(fromUserId));
}
} else if (req.chatType() == ImMessageEntity.ChatType.GROUP) {
String destination = "/topic/group/" + req.toId();
log.debug("send message appId={} from={} to={} chatType={} msgType={} destination={}",
appId, fromUserId, req.toId(), req.chatType(), req.msgType(), destination);
log.debug("send message appKey={} from={} to={} chatType={} msgType={} destination={}",
appKey, fromUserId, req.toId(), req.chatType(), req.msgType(), destination);
clusterPublisher.publish(destination, saved);
List<String> memberIds = groupService.memberIds(group);
conversationStateService.clearHiddenForUsers(appId, req.toId(), req.chatType().name(), memberIds);
conversationStateService.clearHiddenForUsers(appKey, req.toId(), req.chatType().name(), memberIds);
imPushBridge.sendOfflinePushToUsers(
appId,
appKey,
memberIds.stream()
.filter(memberId -> !memberId.equals(fromUserId))
.toList(),
@ -172,32 +172,32 @@ public class MessageService {
);
} else {
String destination = "/user/" + req.toId() + "/queue/messages";
log.debug("send message appId={} from={} to={} chatType={} msgType={} destination={}",
appId, fromUserId, req.toId(), req.chatType(), req.msgType(), destination);
log.debug("send message appKey={} from={} to={} chatType={} msgType={} destination={}",
appKey, fromUserId, req.toId(), req.chatType(), req.msgType(), destination);
if (!receiverBlocksSender) {
clusterPublisher.publish(destination, saved);
}
}
dispatchWebhooks(appId, "message.sent", saved);
dispatchWebhooks(appKey, "message.sent", saved);
return saved;
}
private boolean isFriend(String appId, String userId, String friendId) {
return friendRepository.existsByAppIdAndUserIdAndFriendId(appId, userId, friendId)
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appId, friendId, userId);
private boolean isFriend(String appKey, String userId, String friendId) {
return friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, userId, friendId)
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appKey, friendId, userId);
}
public ImMessageEntity revoke(String appId, String messageId, String requestUserId) {
public ImMessageEntity revoke(String appKey, String messageId, String requestUserId) {
ImMessageEntity message = messageRepository.findById(messageId)
.orElseThrow(() -> new BusinessException(404, "消息不存在"));
if (!message.getAppId().equals(appId)) {
if (!message.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
if (!message.getFromUserId().equals(requestUserId)) {
throw new BusinessException(403, "只能撤回自己发送的消息");
}
int recallMinutes = featureConfigClient.messageRecallMinutes(appId);
int recallMinutes = featureConfigClient.messageRecallMinutes(appKey);
if (recallMinutes > 0 && message.getCreatedAt().plusMinutes(recallMinutes).isBefore(LocalDateTime.now())) {
throw new BusinessException(403, "已超过可撤回时长");
}
@ -216,14 +216,14 @@ public class MessageService {
log.debug("revoke group messageId={} groupId={}", saved.getId(), saved.getToId());
clusterPublisher.publish("/topic/group/" + saved.getToId(), saved);
}
dispatchWebhooks(appId, "message.revoked", saved);
dispatchWebhooks(appKey, "message.revoked", saved);
return saved;
}
public ImMessageEntity edit(String appId, String messageId, String requestUserId, EditMessageRequest req) {
public ImMessageEntity edit(String appKey, String messageId, String requestUserId, EditMessageRequest req) {
ImMessageEntity message = messageRepository.findById(messageId)
.orElseThrow(() -> new BusinessException(404, "消息不存在"));
if (!message.getAppId().equals(appId)) {
if (!message.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
if (!message.getFromUserId().equals(requestUserId)) {
@ -236,7 +236,7 @@ public class MessageService {
throw new BusinessException(400, "仅支持编辑文本消息");
}
String content = keywordFilterService.filter(appId, req.content());
String content = keywordFilterService.filter(appKey, req.content());
if (content == null) {
throw new BusinessException("消息包含违禁内容");
}
@ -251,7 +251,7 @@ public class MessageService {
clusterPublisher.publish("/user/" + saved.getFromUserId() + "/queue/messages", saved);
}
imPushBridge.sendOfflinePushToUsers(
appId,
appKey,
List.of(saved.getToId()),
"消息已编辑",
saved.getContent(),
@ -261,7 +261,7 @@ public class MessageService {
clusterPublisher.publish("/topic/group/" + saved.getToId(), saved);
List<String> memberIds = groupService.memberIds(groupService.get(saved.getToId()));
imPushBridge.sendOfflinePushToUsers(
appId,
appKey,
memberIds.stream()
.filter(memberId -> !memberId.equals(saved.getFromUserId()))
.toList(),
@ -271,14 +271,14 @@ public class MessageService {
);
}
dispatchWebhooks(appId, "message.edited", saved);
dispatchWebhooks(appKey, "message.edited", saved);
return saved;
}
public ImMessageEntity adminRevoke(String appId, String messageId) {
public ImMessageEntity adminRevoke(String appKey, String messageId) {
ImMessageEntity message = messageRepository.findById(messageId)
.orElseThrow(() -> new BusinessException(404, "消息不存在"));
if (!message.getAppId().equals(appId)) {
if (!message.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
message.setStatus(ImMessageEntity.MsgStatus.REVOKED);
@ -295,12 +295,12 @@ public class MessageService {
log.debug("admin revoke group messageId={} groupId={}", saved.getId(), saved.getToId());
clusterPublisher.publish("/topic/group/" + saved.getToId(), saved);
}
dispatchWebhooks(appId, "message.revoked", saved);
dispatchWebhooks(appKey, "message.revoked", saved);
return saved;
}
public Page<ImMessageEntity> history(
String appId,
String appKey,
String userId,
String toId,
ImMessageEntity.MsgType msgType,
@ -309,13 +309,13 @@ public class MessageService {
LocalDateTime endTime,
int page,
int size) {
LocalDateTime effectiveStart = applyHistoryRetention(appId, startTime);
LocalDateTime effectiveStart = applyHistoryRetention(appKey, startTime);
return messageRepository.findSingleConversationFiltered(
appId, userId, toId, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
appKey, userId, toId, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
}
public Page<ImMessageEntity> groupHistory(
String appId,
String appKey,
String groupId,
String userId,
ImMessageEntity.MsgType msgType,
@ -324,25 +324,25 @@ public class MessageService {
LocalDateTime endTime,
int page,
int size) {
LocalDateTime effectiveStart = applyHistoryRetention(appId, startTime);
LocalDateTime effectiveStart = applyHistoryRetention(appKey, startTime);
ImGroupEntity group = groupService.get(groupId);
if (!groupService.memberIds(group).contains(userId)) {
throw new BusinessException(403, "不在群内");
}
Page<ImMessageEntity> pageResult = messageRepository.findGroupHistoryFiltered(
appId, groupId, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
appKey, groupId, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
pageResult.forEach(message -> message.setGroupReadCount(
groupReadCount(appId, groupId, message.getCreatedAt(), message.getFromUserId())));
groupReadCount(appKey, groupId, message.getCreatedAt(), message.getFromUserId())));
return pageResult;
}
public void syncReadReceipt(String appId, String readerId, String peerId, String chatType, LocalDateTime readAt) {
public void syncReadReceipt(String appKey, String readerId, String peerId, String chatType, LocalDateTime readAt) {
if (!ImMessageEntity.ChatType.SINGLE.name().equals(chatType) || readerId.equals(peerId)) {
return;
}
List<ImMessageEntity> messages = messageRepository
.findByAppIdAndFromUserIdAndToIdAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
appId, peerId, readerId, readAt);
appKey, peerId, readerId, readAt);
if (messages.isEmpty()) {
return;
}
@ -357,8 +357,8 @@ public class MessageService {
clusterPublisher.publish("/user/" + peerId + "/queue/messages", saved);
messageIds.add(saved.getId());
}
dispatchWebhooks(appId, "message.read", new MessageReadCallbackPayload(
appId,
dispatchWebhooks(appKey, "message.read", new MessageReadCallbackPayload(
appKey,
readerId,
peerId,
null,
@ -368,25 +368,25 @@ public class MessageService {
));
}
public void syncGroupReadReceipt(String appId, String readerId, String groupId, LocalDateTime readAt) {
public void syncGroupReadReceipt(String appKey, String readerId, String groupId, LocalDateTime readAt) {
ImGroupEntity group = groupService.get(groupId);
if (!groupService.memberIds(group).contains(readerId)) {
return;
}
List<ImMessageEntity> messages = messageRepository
.findByAppIdAndToIdAndChatTypeAndCreatedAtLessThanEqualOrderByCreatedAtAsc(
appId, groupId, ImMessageEntity.ChatType.GROUP, readAt);
appKey, groupId, ImMessageEntity.ChatType.GROUP, readAt);
if (messages.isEmpty()) {
return;
}
List<String> messageIds = new java.util.ArrayList<>();
for (ImMessageEntity message : messages) {
message.setGroupReadCount(groupReadCount(appId, groupId, message.getCreatedAt(), message.getFromUserId()));
message.setGroupReadCount(groupReadCount(appKey, groupId, message.getCreatedAt(), message.getFromUserId()));
clusterPublisher.publish("/topic/group/" + groupId, message);
messageIds.add(message.getId());
}
dispatchWebhooks(appId, "message.read", new MessageReadCallbackPayload(
appId,
dispatchWebhooks(appKey, "message.read", new MessageReadCallbackPayload(
appKey,
readerId,
null,
groupId,
@ -397,7 +397,7 @@ public class MessageService {
}
public Page<ImMessageEntity> adminHistory(
String appId,
String appKey,
String userA,
String userB,
ImMessageEntity.MsgType msgType,
@ -406,13 +406,13 @@ public class MessageService {
LocalDateTime endTime,
int page,
int size) {
LocalDateTime effectiveStart = applyHistoryRetention(appId, startTime);
LocalDateTime effectiveStart = applyHistoryRetention(appKey, startTime);
return messageRepository.findSingleConversationFiltered(
appId, userA, userB, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
appKey, userA, userB, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
}
public Page<ImMessageEntity> adminGroupHistory(
String appId,
String appKey,
String groupId,
ImMessageEntity.MsgType msgType,
String keyword,
@ -420,30 +420,30 @@ public class MessageService {
LocalDateTime endTime,
int page,
int size) {
LocalDateTime effectiveStart = applyHistoryRetention(appId, startTime);
LocalDateTime effectiveStart = applyHistoryRetention(appKey, startTime);
Page<ImMessageEntity> pageResult = messageRepository.findGroupHistoryFiltered(
appId, groupId, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
appKey, groupId, msgType, keyword, effectiveStart, endTime, PageRequest.of(page, size));
pageResult.forEach(message -> message.setGroupReadCount(
groupReadCount(appId, groupId, message.getCreatedAt(), message.getFromUserId())));
groupReadCount(appKey, groupId, message.getCreatedAt(), message.getFromUserId())));
return pageResult;
}
public List<ImMessageRepository.ConversationSummary> conversations(String appId, String userId, int size) {
return messageRepository.findConversations(appId, userId, normalizeConversationSize(appId, size));
public List<ImMessageRepository.ConversationSummary> conversations(String appKey, String userId, int size) {
return messageRepository.findConversations(appKey, userId, normalizeConversationSize(appKey, size));
}
public List<ConversationView> conversationViews(String appId, String userId, int size) {
int cappedSize = normalizeConversationSize(appId, size);
public List<ConversationView> conversationViews(String appKey, String userId, int size) {
int cappedSize = normalizeConversationSize(appKey, size);
int fetchSize = Math.max(cappedSize * 3, cappedSize);
return messageRepository.findConversations(appId, userId, fetchSize).stream()
.map(summary -> toConversationView(appId, userId, summary))
return messageRepository.findConversations(appKey, userId, fetchSize).stream()
.map(summary -> toConversationView(appKey, userId, summary))
.filter(Objects::nonNull)
.limit(cappedSize)
.toList();
}
private LocalDateTime applyHistoryRetention(String appId, LocalDateTime requestedStart) {
int retentionDays = featureConfigClient.historyRetentionDays(appId);
private LocalDateTime applyHistoryRetention(String appKey, LocalDateTime requestedStart) {
int retentionDays = featureConfigClient.historyRetentionDays(appKey);
LocalDateTime retentionStart = LocalDateTime.now().minusDays(retentionDays);
if (requestedStart == null || requestedStart.isBefore(retentionStart)) {
return retentionStart;
@ -451,31 +451,31 @@ public class MessageService {
return requestedStart;
}
private int normalizeConversationSize(String appId, int requestedSize) {
int limit = featureConfigClient.conversationPullLimit(appId);
private int normalizeConversationSize(String appKey, int requestedSize) {
int limit = featureConfigClient.conversationPullLimit(appKey);
int safeRequested = Math.max(requestedSize, 1);
return Math.min(safeRequested, limit);
}
private ConversationView toConversationView(
String appId,
String appKey,
String userId,
ImMessageRepository.ConversationSummary summary
) {
String targetId = summary.getTargetId();
String chatType = summary.getChatType();
var state = conversationStateService.find(appId, userId, targetId, chatType);
var state = conversationStateService.find(appKey, userId, targetId, chatType);
if (state != null && state.isHidden()) {
return null;
}
Page<ImMessageEntity> page = chatType.equals("GROUP")
? messageRepository.findGroupHistory(appId, targetId, PageRequest.of(0, 1))
: messageRepository.findSingleConversation(appId, userId, targetId, PageRequest.of(0, 1));
? messageRepository.findGroupHistory(appKey, targetId, PageRequest.of(0, 1))
: messageRepository.findSingleConversation(appKey, userId, targetId, PageRequest.of(0, 1));
ImMessageEntity lastMessage = page.getContent().stream().findFirst().orElse(null);
LocalDateTime lastReadAt = state == null ? null : state.getLastReadAt();
long unreadCount = chatType.equals("GROUP")
? messageRepository.countUnreadGroupConversation(appId, userId, targetId, lastReadAt)
: messageRepository.countUnreadSingleConversation(appId, userId, targetId, lastReadAt);
? messageRepository.countUnreadGroupConversation(appKey, userId, targetId, lastReadAt)
: messageRepository.countUnreadSingleConversation(appKey, userId, targetId, lastReadAt);
return new ConversationView(
targetId,
chatType,
@ -489,18 +489,18 @@ public class MessageService {
);
}
public List<GroupReadReceiptSummary> groupReadReceipts(String appId, String groupId, List<String> messageIds) {
public List<GroupReadReceiptSummary> groupReadReceipts(String appKey, String groupId, List<String> messageIds) {
ImGroupEntity group = groupService.get(groupId);
if (!group.getAppId().equals(appId)) {
if (!group.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
List<String> members = groupService.memberIds(group);
return messageRepository.findAllById(messageIds == null ? List.of() : messageIds).stream()
.filter(message -> appId.equals(message.getAppId()))
.filter(message -> appKey.equals(message.getAppId()))
.filter(message -> groupId.equals(message.getToId()))
.filter(message -> message.getChatType() == ImMessageEntity.ChatType.GROUP)
.map(message -> {
int readCount = groupReadCount(appId, groupId, message.getCreatedAt(), message.getFromUserId());
int readCount = groupReadCount(appKey, groupId, message.getCreatedAt(), message.getFromUserId());
return new GroupReadReceiptSummary(
message.getId(),
groupId,
@ -520,7 +520,7 @@ public class MessageService {
try {
Map<String, Object> payload = new java.util.LinkedHashMap<>();
payload.put("messageId", message.getId());
payload.put("appId", message.getAppId());
payload.put("appKey", message.getAppId());
payload.put("fromUserId", message.getFromUserId());
payload.put("toId", message.getToId());
payload.put("chatType", message.getChatType().name());
@ -557,7 +557,7 @@ public class MessageService {
};
}
private int groupReadCount(String appId, String groupId, LocalDateTime createdAt, String senderId) {
private int groupReadCount(String appKey, String groupId, LocalDateTime createdAt, String senderId) {
ImGroupEntity group = groupService.get(groupId);
int count = 0;
for (String memberId : groupService.memberIds(group)) {
@ -565,7 +565,7 @@ public class MessageService {
count += 1;
continue;
}
var state = conversationStateService.find(appId, memberId, groupId, ImMessageEntity.ChatType.GROUP.name());
var state = conversationStateService.find(appKey, memberId, groupId, ImMessageEntity.ChatType.GROUP.name());
if (state != null && state.getLastReadAt() != null && !state.getLastReadAt().isBefore(createdAt)) {
count += 1;
}
@ -587,18 +587,18 @@ public class MessageService {
return java.util.Optional.empty();
}
protected void dispatchWebhooks(String appId, String callbackEvent, ImMessageEntity message) {
webhookDispatchService.dispatch(appId, "message", callbackEvent, message);
protected void dispatchWebhooks(String appKey, String callbackEvent, ImMessageEntity message) {
webhookDispatchService.dispatch(appKey, "message", callbackEvent, message);
}
protected void dispatchWebhooks(String appId, String callbackEvent, Object payload) {
webhookDispatchService.dispatch(appId, "message", callbackEvent, payload);
protected void dispatchWebhooks(String appKey, String callbackEvent, Object payload) {
webhookDispatchService.dispatch(appKey, "message", callbackEvent, payload);
}
public ImMessageEntity adminSend(String appId, String fromUserId, String toId, ImMessageEntity.MsgType msgType, String content) {
public ImMessageEntity adminSend(String appKey, String fromUserId, String toId, ImMessageEntity.MsgType msgType, String content) {
ImMessageEntity message = new ImMessageEntity();
message.setId(UUID.randomUUID().toString());
message.setAppId(appId);
message.setAppId(appKey);
message.setFromUserId(fromUserId);
message.setToId(toId);
message.setChatType(ImMessageEntity.ChatType.SINGLE);
@ -610,28 +610,28 @@ public class MessageService {
clusterPublisher.publish("/user/" + fromUserId + "/queue/messages", saved);
clusterPublisher.publish("/user/" + toId + "/queue/messages", saved);
conversationStateService.clearHiddenForUsers(appId, toId, ImMessageEntity.ChatType.SINGLE.name(), List.of(fromUserId, toId));
conversationStateService.clearHiddenForUsers(appKey, toId, ImMessageEntity.ChatType.SINGLE.name(), List.of(fromUserId, toId));
imPushBridge.sendOfflinePushToUsers(
appId,
appKey,
List.of(toId),
"新消息",
saved.getContent(),
buildPushPayload(saved)
);
dispatchWebhooks(appId, "message.sent", saved);
dispatchWebhooks(appKey, "message.sent", saved);
return saved;
}
public void adminSetMsgRead(String appId, String userId) {
List<ImMessageEntity> messages = messageRepository.findUnreadByAppIdAndToId(appId, userId);
public void adminSetMsgRead(String appKey, String userId) {
List<ImMessageEntity> messages = messageRepository.findUnreadByAppIdAndToId(appKey, userId);
for (ImMessageEntity message : messages) {
message.setStatus(ImMessageEntity.MsgStatus.READ);
messageRepository.save(message);
}
}
public List<ImMessageEntity> importMessages(String appId, List<ImportMessageRequest> requests) {
public List<ImMessageEntity> importMessages(String appKey, List<ImportMessageRequest> requests) {
List<ImMessageEntity> result = new ArrayList<>();
for (ImportMessageRequest req : requests == null ? List.<ImportMessageRequest>of() : requests) {
if (req == null || req.fromUserId() == null || req.fromUserId().isBlank()
@ -642,7 +642,7 @@ public class MessageService {
message.setId(req.messageId() != null && !req.messageId().isBlank()
? req.messageId()
: UUID.randomUUID().toString());
message.setAppId(appId);
message.setAppId(appKey);
message.setFromUserId(req.fromUserId());
message.setToId(req.toId());
message.setChatType(req.chatType() != null ? req.chatType() : ImMessageEntity.ChatType.SINGLE);

查看文件

@ -33,22 +33,22 @@ public class OfflineMessageSyncService {
}
@Transactional
public void storeOfflineMessage(String appId, String userId, String messageId) {
public void storeOfflineMessage(String appKey, String userId, String messageId) {
ImOfflineMessageEntity entity = new ImOfflineMessageEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setUserId(userId);
entity.setMessageId(messageId);
entity.setDelivered(false);
entity.setCreatedAt(LocalDateTime.now());
offlineMessageRepository.save(entity);
log.debug("Stored offline message appId={} userId={} messageId={}", appId, userId, messageId);
log.debug("Stored offline message appKey={} userId={} messageId={}", appKey, userId, messageId);
}
@Transactional
public void syncAndDeliver(String appId, String userId) {
public void syncAndDeliver(String appKey, String userId) {
List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository
.findByAppIdAndUserIdAndDeliveredFalse(appId, userId);
.findByAppIdAndUserIdAndDeliveredFalse(appKey, userId);
if (offlineMessages.isEmpty()) {
return;
}
@ -59,26 +59,26 @@ public class OfflineMessageSyncService {
if (message != null) {
clusterPublisher.publish("/user/" + userId + "/queue/messages", message);
deliveredIds.add(offline.getId());
log.debug("Delivered offline message appId={} userId={} messageId={}", appId, userId, message.getId());
log.debug("Delivered offline message appKey={} userId={} messageId={}", appKey, userId, message.getId());
}
}
if (!deliveredIds.isEmpty()) {
offlineMessageRepository.markDeliveredByIds(deliveredIds);
log.info("Synced {} offline messages for appId={} userId={}", deliveredIds.size(), appId, userId);
log.info("Synced {} offline messages for appKey={} userId={}", deliveredIds.size(), appKey, userId);
}
offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appId, userId);
offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appKey, userId);
}
public long countUndelivered(String appId, String userId) {
return offlineMessageRepository.countUndeliveredByAppIdAndUserId(appId, userId);
public long countUndelivered(String appKey, String userId) {
return offlineMessageRepository.countUndeliveredByAppIdAndUserId(appKey, userId);
}
@Transactional
public List<ImMessageEntity> syncAndReturn(String appId, String userId) {
public List<ImMessageEntity> syncAndReturn(String appKey, String userId) {
List<ImOfflineMessageEntity> offlineMessages = offlineMessageRepository
.findByAppIdAndUserIdAndDeliveredFalse(appId, userId);
.findByAppIdAndUserIdAndDeliveredFalse(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(appId, userId);
offlineMessageRepository.deleteByAppIdAndUserIdAndDeliveredTrue(appKey, userId);
return result;
}
}

查看文件

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

查看文件

@ -18,19 +18,19 @@ public class WebhookConfigService {
this.repository = repository;
}
public List<WebhookConfigEntity> list(String appId) {
return repository.findByAppId(appId);
public List<WebhookConfigEntity> list(String appKey) {
return repository.findByAppId(appKey);
}
public WebhookConfigEntity get(String appId, String id) {
return repository.findByIdAndAppId(id, appId)
public WebhookConfigEntity get(String appKey, String id) {
return repository.findByIdAndAppId(id, appKey)
.orElseThrow(() -> new BusinessException(404, "回调配置不存在"));
}
public WebhookConfigEntity create(String appId, String url, String secret, Boolean enabled) {
public WebhookConfigEntity create(String appKey, String url, String secret, Boolean enabled) {
WebhookConfigEntity entity = new WebhookConfigEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setUrl(url);
entity.setSecret(secret);
entity.setEnabled(enabled == null || enabled);
@ -38,8 +38,8 @@ public class WebhookConfigService {
return repository.save(entity);
}
public WebhookConfigEntity update(String appId, String id, String url, String secret, Boolean enabled) {
WebhookConfigEntity entity = repository.findByIdAndAppId(id, appId)
public WebhookConfigEntity update(String appKey, String id, String url, String secret, Boolean enabled) {
WebhookConfigEntity entity = repository.findByIdAndAppId(id, appKey)
.orElseThrow(() -> new BusinessException(404, "回调配置不存在"));
if (url != null) {
entity.setUrl(url);
@ -53,8 +53,8 @@ public class WebhookConfigService {
return repository.save(entity);
}
public void delete(String appId, String id) {
WebhookConfigEntity entity = repository.findByIdAndAppId(id, appId)
public void delete(String appKey, String id) {
WebhookConfigEntity entity = repository.findByIdAndAppId(id, appKey)
.orElseThrow(() -> new BusinessException(404, "回调配置不存在"));
repository.delete(entity);
}

查看文件

@ -60,13 +60,13 @@ public class WebhookDispatchService {
}
@Async
public void dispatch(String appId, String callbackType, String callbackEvent, Object payload) {
List<WebhookConfigEntity> webhooks = webhookRepository.findByAppIdAndEnabledTrue(appId);
public void dispatch(String appKey, String callbackType, String callbackEvent, Object payload) {
List<WebhookConfigEntity> webhooks = webhookRepository.findByAppIdAndEnabledTrue(appKey);
if (webhooks.isEmpty()) {
return;
}
try {
String appSecret = appSecretClient.getAppSecret(appId);
String appSecret = appSecretClient.getAppSecret(appKey);
long requestTime = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString().replace("-", "");
String callbackId = UUID.randomUUID().toString();
@ -77,20 +77,20 @@ public class WebhookDispatchService {
requestTime,
objectMapper.valueToTree(payload),
null,
appId
appKey
);
String body = objectMapper.writeValueAsString(envelope);
String signature = signWebhook(appId, appSecret, requestTime, nonce, body);
String signature = signWebhook(appKey, appSecret, requestTime, nonce, body);
for (WebhookConfigEntity webhook : webhooks) {
deliverWithRetry(appId, callbackId, callbackEvent, webhook, body, signature, requestTime, nonce);
deliverWithRetry(appKey, callbackId, callbackEvent, webhook, body, signature, requestTime, nonce);
}
} catch (Exception e) {
log.warn("Webhook dispatch prepare failed appId={} event={}: {}", appId, callbackEvent, e.getMessage());
log.warn("Webhook dispatch prepare failed appKey={} event={}: {}", appKey, callbackEvent, e.getMessage());
}
}
private void deliverWithRetry(String appId, String callbackId, String callbackEvent,
private void deliverWithRetry(String appKey, String callbackId, String callbackEvent,
WebhookConfigEntity webhook, String body, String signature,
long requestTime, String nonce) {
HttpClient client = HttpClient.newBuilder()
@ -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(appId);
delivery.setAppId(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", appId)
.header("X-App-Id", appKey)
.header("X-App-Timestamp", String.valueOf(requestTime))
.header("X-App-Nonce", nonce)
.header("X-App-Signature", signature)
@ -131,28 +131,28 @@ public class WebhookDispatchService {
webhook.setLastFailureAt(null);
webhookRepository.save(webhook);
}
log.info("Webhook delivered appId={} event={} url={} attempt={} status={}",
appId, callbackEvent, webhook.getUrl(), attempt, response.statusCode());
log.info("Webhook delivered appKey={} event={} url={} attempt={} status={}",
appKey, callbackEvent, webhook.getUrl(), attempt, response.statusCode());
return;
} else {
delivery.setSuccess(false);
delivery.setErrorMessage("HTTP " + response.statusCode());
deliveryRepository.save(delivery);
log.warn("Webhook returned non-2xx appId={} event={} url={} attempt={} status={}",
appId, callbackEvent, webhook.getUrl(), attempt, response.statusCode());
log.warn("Webhook returned non-2xx appKey={} event={} url={} attempt={} status={}",
appKey, callbackEvent, webhook.getUrl(), attempt, response.statusCode());
}
} catch (Exception e) {
delivery.setSuccess(false);
delivery.setErrorMessage(truncate(e.getMessage(), 4000));
deliveryRepository.save(delivery);
log.warn("Webhook delivery failed appId={} event={} url={} attempt={}: {}",
appId, callbackEvent, webhook.getUrl(), attempt, e.getMessage());
log.warn("Webhook delivery failed appKey={} event={} url={} attempt={}: {}",
appKey, callbackEvent, webhook.getUrl(), attempt, e.getMessage());
}
if (attempt < MAX_RETRIES) {
long delay = RETRY_DELAYS_MS[attempt - 1];
log.info("Webhook retry scheduled appId={} event={} url={} delayMs={}",
appId, callbackEvent, webhook.getUrl(), delay);
log.info("Webhook retry scheduled appKey={} event={} url={} delayMs={}",
appKey, callbackEvent, webhook.getUrl(), delay);
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
@ -160,29 +160,29 @@ public class WebhookDispatchService {
break;
}
} else {
handleMaxRetriesExceeded(appId, callbackEvent, webhook);
handleMaxRetriesExceeded(appKey, callbackEvent, webhook);
}
}
}
private void handleMaxRetriesExceeded(String appId, String callbackEvent, WebhookConfigEntity webhook) {
private void handleMaxRetriesExceeded(String appKey, String callbackEvent, WebhookConfigEntity webhook) {
int failures = webhook.getConsecutiveFailures() + 1;
webhook.setConsecutiveFailures(failures);
webhook.setLastFailureAt(LocalDateTime.now());
webhookRepository.save(webhook);
log.error("Webhook max retries exceeded appId={} event={} url={} consecutiveFailures={}",
appId, callbackEvent, webhook.getUrl(), failures);
log.error("Webhook max retries exceeded appKey={} event={} url={} consecutiveFailures={}",
appKey, callbackEvent, webhook.getUrl(), failures);
if (failures >= ALERT_THRESHOLD && webhook.isEnabled()) {
webhook.setEnabled(false);
webhookRepository.save(webhook);
log.warn("Webhook auto-disabled after {} consecutive failures appId={} url={}",
ALERT_THRESHOLD, appId, webhook.getUrl());
log.warn("Webhook auto-disabled after {} consecutive failures appKey={} url={}",
ALERT_THRESHOLD, appKey, webhook.getUrl());
WebhookAlertEntity alert = new WebhookAlertEntity();
alert.setId(UUID.randomUUID().toString());
alert.setAppId(appId);
alert.setAppId(appKey);
alert.setWebhookId(webhook.getId());
alert.setWebhookUrl(webhook.getUrl());
alert.setAlertType("AUTO_DISABLED");
@ -193,8 +193,8 @@ public class WebhookDispatchService {
}
}
private String signWebhook(String appId, String appSecret, long requestTime, String nonce, String body) {
String payload = appId + "\n" + requestTime + "\n" + nonce + "\n" + sha256Hex(body);
private String signWebhook(String appKey, String appSecret, long requestTime, String nonce, String body) {
String payload = appKey + "\n" + requestTime + "\n" + nonce + "\n" + sha256Hex(body);
return hmacSha256Hex(appSecret, payload);
}

查看文件

@ -34,13 +34,13 @@ public class ChatController {
request.messageId(), request.toId(), request.chatType(), request.msgType(),
request.content(), request.mentionedUserIds()
);
messageService.send(request.appId(), userId, req);
messageService.send(request.appKey(), userId, req);
}
@MessageMapping("/chat.revoke")
public void revoke(@Payload WsRevokeRequest request, Principal principal) {
if (principal == null) return;
messageService.revoke(request.appId(), request.messageId(), principal.getName());
messageService.revoke(request.appKey(), request.messageId(), principal.getName());
}
@MessageMapping("/chat.sync")
@ -48,17 +48,17 @@ public class ChatController {
if (principal == null) return;
String userId = principal.getName();
userPresenceService.heartbeat(userId);
offlineMessageSyncService.syncAndDeliver(request.appId(), userId);
offlineMessageSyncService.syncAndDeliver(request.appKey(), userId);
}
public record WsMessageRequest(
String appId, String messageId, String toId,
String appKey, String messageId, String toId,
ImMessageEntity.ChatType chatType,
ImMessageEntity.MsgType msgType,
String content, String mentionedUserIds
) {}
public record WsRevokeRequest(String appId, String messageId) {}
public record WsRevokeRequest(String appKey, String messageId) {}
public record WsSyncRequest(String appId) {}
public record WsSyncRequest(String appKey) {}
}

查看文件

@ -41,17 +41,17 @@ public class ImSessionKickListener {
String userId = auth.getName();
String sessionId = accessor.getSessionId();
String appId = null;
String appKey = null;
Object details = auth.getDetails();
if (details instanceof Map<?, ?> detailsMap) {
Object v = detailsMap.get("appId");
if (v != null) appId = v.toString();
Object v = detailsMap.get("appKey");
if (v != null) appKey = v.toString();
}
if (appId == null || appId.isBlank()) {
if (appKey == null || appKey.isBlank()) {
return;
}
String mode = featureConfigClient.multiDeviceLoginMode(appId);
String mode = featureConfigClient.multiDeviceLoginMode(appKey);
if ("MULTI_DEVICE_FREE".equals(mode)) {
return;
}
@ -61,7 +61,7 @@ public class ImSessionKickListener {
return;
}
log.info("Sending kick to userId={} appId={} mode={} newSessionId={}", userId, appId, mode, sessionId);
log.info("Sending kick to userId={} appKey={} mode={} newSessionId={}", userId, appKey, mode, sessionId);
Map<String, String> payload = Map.of("type", "KICKED", "sessionId", sessionId, "reason", mode.toLowerCase());
messagingTemplate.convertAndSendToUser(userId, "/queue/system", payload);
}

查看文件

@ -44,7 +44,7 @@ jwt:
expiration: 0
im:
tenant-service-url: ${TENANT_SERVICE_URL:http://127.0.0.1:8081}
tenant-service-url: ${TENANT_SERVICE_URL:http://127.0.0.1:9001}
internal-token: ${SDK_INTERNAL_TOKEN:xuqm-internal-token}
push-service-url: ${PUSH_SERVICE_URL:http://127.0.0.1:8083}
multi-login: true

查看文件

@ -39,7 +39,7 @@ public class InternalPushController {
if (token == null || !internalToken.equals(token)) {
return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden"));
}
pushDispatcher.pushToUsers(request.appId(), request.userIds(), request.title(), request.body(), request.payload());
pushDispatcher.pushToUsers(request.appKey(), request.userIds(), request.title(), request.body(), request.payload());
return ResponseEntity.ok(ApiResponse.ok());
}
@ -47,24 +47,24 @@ public class InternalPushController {
public ResponseEntity<ApiResponse<PushDiagnosticsService.PushTokenDiagnostics>> searchByToken(
@RequestHeader(value = "X-Internal-Token", required = false) String token,
@org.springframework.web.bind.annotation.RequestParam String queryToken,
@org.springframework.web.bind.annotation.RequestParam(required = false) String appId) {
@org.springframework.web.bind.annotation.RequestParam(required = false) String appKey) {
if (!isAllowed(token)) {
return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden"));
}
return ResponseEntity.ok(ApiResponse.success(diagnosticsService.searchByToken(queryToken, appId)));
return ResponseEntity.ok(ApiResponse.success(diagnosticsService.searchByToken(queryToken, appKey)));
}
@GetMapping("/device-logs")
public ResponseEntity<ApiResponse<java.util.Map<String, Object>>> deviceLogs(
@RequestHeader(value = "X-Internal-Token", required = false) String token,
@org.springframework.web.bind.annotation.RequestParam String appId,
@org.springframework.web.bind.annotation.RequestParam String appKey,
@org.springframework.web.bind.annotation.RequestParam String userId,
@org.springframework.web.bind.annotation.RequestParam(defaultValue = "0") int page,
@org.springframework.web.bind.annotation.RequestParam(defaultValue = "20") int size) {
if (!isAllowed(token)) {
return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden"));
}
Page<DeviceLoginLogEntity> result = diagnosticsService.deviceLogs(appId, userId, page, size);
Page<DeviceLoginLogEntity> result = diagnosticsService.deviceLogs(appKey, userId, page, size);
return ResponseEntity.ok(ApiResponse.success(java.util.Map.of(
"content", result.getContent(),
"total", result.getTotalElements(),
@ -80,7 +80,7 @@ public class InternalPushController {
return ResponseEntity.status(403).body(ApiResponse.error(403, "Forbidden"));
}
PushDiagnosticsService.TestPushResult result = diagnosticsService.sendTestOfflineMessage(
request.appId(),
request.appKey(),
request.userId(),
request.title(),
request.body(),
@ -93,7 +93,7 @@ public class InternalPushController {
}
public record NotifyRequest(
@NotBlank String appId,
@NotBlank String appKey,
List<@NotBlank String> userIds,
@NotBlank String title,
@NotBlank String body,
@ -101,7 +101,7 @@ public class InternalPushController {
) {}
public record TestOfflineRequest(
@NotBlank String appId,
@NotBlank String appKey,
@NotBlank String userId,
@NotBlank String title,
@NotBlank String body,

查看文件

@ -24,7 +24,7 @@ public class PushController {
@PostMapping("/register")
public ResponseEntity<ApiResponse<Void>> register(
@RequestParam @NotBlank String appId,
@RequestParam @NotBlank String appKey,
@RequestParam @NotBlank String userId,
@RequestParam @NotNull DeviceTokenEntity.Vendor vendor,
@RequestParam @NotBlank String token,
@ -34,38 +34,38 @@ public class PushController {
@RequestParam(required = false) String model,
@RequestParam(required = false) String osVersion,
@RequestParam(required = false) String appVersion) {
pushDispatcher.registerToken(appId, userId, vendor, token, platform, deviceId, brand, model, osVersion, appVersion);
pushDispatcher.registerToken(appKey, userId, vendor, token, platform, deviceId, brand, model, osVersion, appVersion);
return ResponseEntity.ok(ApiResponse.ok());
}
@PostMapping("/receive-push")
public ResponseEntity<ApiResponse<Void>> receivePush(
@RequestParam @NotBlank String appId,
@RequestParam @NotBlank String appKey,
@RequestParam @NotBlank String userId,
@RequestParam(required = false) String deviceId,
@RequestParam boolean enabled) {
pushDispatcher.setReceivePush(appId, userId, deviceId, enabled);
pushDispatcher.setReceivePush(appKey, userId, deviceId, enabled);
return ResponseEntity.ok(ApiResponse.ok());
}
@PostMapping("/send")
public ResponseEntity<ApiResponse<Void>> send(
@RequestParam @NotBlank String appId,
@RequestParam @NotBlank String appKey,
@RequestParam @NotBlank String userId,
@RequestParam @NotBlank String title,
@RequestParam @NotBlank String body,
@RequestParam(required = false) String payload) {
pushDispatcher.pushToUser(appId, userId, title, body, payload);
pushDispatcher.pushToUser(appKey, userId, title, body, payload);
return ResponseEntity.ok(ApiResponse.ok());
}
@DeleteMapping("/unregister")
public ResponseEntity<ApiResponse<Void>> unregister(
@RequestParam @NotBlank String appId,
@RequestParam @NotBlank String appKey,
@RequestParam @NotBlank String userId,
@RequestParam @NotNull DeviceTokenEntity.Vendor vendor,
@RequestParam(required = false) String deviceId) {
pushDispatcher.unregisterToken(appId, userId, vendor, deviceId);
pushDispatcher.unregisterToken(appKey, userId, vendor, deviceId);
return ResponseEntity.ok(ApiResponse.ok());
}
}

查看文件

@ -26,18 +26,18 @@ public class PushManagementController {
@GetMapping("/user-status")
public ResponseEntity<ApiResponse<PushDiagnosticsService.PushTokenDiagnostics>> userStatus(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String userId) {
return ResponseEntity.ok(ApiResponse.success(diagnosticsService.searchByUserId(appId, userId)));
return ResponseEntity.ok(ApiResponse.success(diagnosticsService.searchByUserId(appKey, userId)));
}
@GetMapping("/device-logs")
public ResponseEntity<ApiResponse<Map<String, Object>>> deviceLogs(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String userId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Page<DeviceLoginLogEntity> result = diagnosticsService.deviceLogs(appId, userId, page, size);
Page<DeviceLoginLogEntity> result = diagnosticsService.deviceLogs(appKey, userId, page, size);
return ResponseEntity.ok(ApiResponse.success(Map.of(
"content", result.getContent(),
"total", result.getTotalElements(),
@ -49,7 +49,7 @@ public class PushManagementController {
public ResponseEntity<ApiResponse<PushDiagnosticsService.TestPushResult>> testOffline(
@RequestBody TestOfflineRequest request) {
PushDiagnosticsService.TestPushResult result = diagnosticsService.sendTestOfflineMessage(
request.appId(),
request.appKey(),
request.userId(),
request.title(),
request.body(),
@ -58,7 +58,7 @@ public class PushManagementController {
}
public record TestOfflineRequest(
String appId,
String appKey,
String userId,
String title,
String body,

查看文件

@ -45,12 +45,12 @@ public class ImPresenceClient {
}
}
public Optional<PresenceStatus> userStatus(String appId, String userId) {
public Optional<PresenceStatus> userStatus(String appKey, String userId) {
try {
HttpHeaders headers = internalHeaders();
HttpEntity<Void> request = new HttpEntity<>(headers);
String uri = UriComponentsBuilder.fromHttpUrl(imServiceBaseUrl + "/api/im/internal/presence/users/{userId}")
.queryParam("appId", appId)
.queryParam("appKey", appKey)
.build(userId)
.toString();
String body = restTemplate.exchange(
@ -80,11 +80,11 @@ public class ImPresenceClient {
return Optional.empty();
}
return Optional.of(new PresenceStatus(
data.path("appId").asText(null),
data.path("appKey").asText(null),
data.path("userId").asText(null),
data.path("online").asBoolean(false),
data.path("lastSeenAt").asLong(0L)));
}
public record PresenceStatus(String appId, String userId, boolean online, long lastSeenAt) {}
public record PresenceStatus(String appKey, String userId, boolean online, long lastSeenAt) {}
}

查看文件

@ -37,27 +37,27 @@ public class PushDiagnosticsService {
public PushTokenDiagnostics searchByToken(String token, String appIdHint) {
Optional<DeviceTokenEntity> tokenMatch = tokenRepository.findFirstByToken(token);
String appId = appIdHint;
String appKey = appIdHint;
String userId = null;
String tokenType = "UNKNOWN";
if (tokenMatch.isPresent()) {
DeviceTokenEntity device = tokenMatch.get();
appId = device.getAppId();
appKey = device.getAppId();
userId = device.getUserId();
tokenType = "PUSH";
} else {
Optional<ImPresenceClient.PresenceStatus> resolved = presenceClient.resolveToken(token);
if (resolved.isPresent()) {
appId = resolved.get().appId();
appKey = resolved.get().appKey();
userId = resolved.get().userId();
tokenType = "IM";
} else {
try {
Claims claims = jwtUtil.parse(token);
userId = claims.getSubject();
String claimAppId = claims.get("appId", String.class);
appId = claimAppId == null || claimAppId.isBlank() ? appIdHint : claimAppId;
String claimAppId = claims.get("appKey", String.class);
appKey = claimAppId == null || claimAppId.isBlank() ? appIdHint : claimAppId;
tokenType = "IM";
} catch (Exception ignored) {
// Keep UNKNOWN and return an empty diagnostic.
@ -65,14 +65,14 @@ public class PushDiagnosticsService {
}
}
if (appId == null || appId.isBlank() || userId == null || userId.isBlank()) {
return new PushTokenDiagnostics(tokenType, appId, userId, false, 0L, false, null, List.of(), List.of());
if (appKey == null || appKey.isBlank() || userId == null || userId.isBlank()) {
return new PushTokenDiagnostics(tokenType, appKey, userId, false, 0L, false, null, List.of(), List.of());
}
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appId, userId)
.orElse(new ImPresenceClient.PresenceStatus(appId, userId, false, 0L));
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appId, userId);
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appId, userId)
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appKey, userId)
.orElse(new ImPresenceClient.PresenceStatus(appKey, userId, false, 0L));
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
.stream()
.map(DeviceInfo::from)
.toList();
@ -80,7 +80,7 @@ public class PushDiagnosticsService {
boolean canSendOffline = !presence.online() && !deliverableDevices.isEmpty();
return new PushTokenDiagnostics(
tokenType,
appId,
appKey,
userId,
presence.online(),
presence.lastSeenAt(),
@ -90,11 +90,11 @@ public class PushDiagnosticsService {
devices.stream().map(DeviceInfo::from).toList());
}
public PushTokenDiagnostics searchByUserId(String appId, String userId) {
ImPresenceClient.PresenceStatus presence = presenceClient.userStatus(appId, userId)
.orElse(new ImPresenceClient.PresenceStatus(appId, userId, false, 0L));
List<DeviceTokenEntity> devices = tokenRepository.findByAppIdAndUserIdOrderByLastLoginAtDescUpdatedAtDesc(appId, userId);
List<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appId, userId)
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<DeviceInfo> deliverableDevices = pushDispatcher.selectedPushTargets(appKey, userId)
.stream()
.map(DeviceInfo::from)
.toList();
@ -102,7 +102,7 @@ public class PushDiagnosticsService {
boolean canSendOffline = !presence.online() && !deliverableDevices.isEmpty();
return new PushTokenDiagnostics(
"USER",
appId,
appKey,
userId,
presence.online(),
presence.lastSeenAt(),
@ -112,26 +112,26 @@ public class PushDiagnosticsService {
devices.stream().map(DeviceInfo::from).toList());
}
public TestPushResult sendTestOfflineMessage(String appId, String userId, String title, String body, String payload) {
List<DeviceInfo> targets = pushDispatcher.selectedPushTargets(appId, userId)
public TestPushResult sendTestOfflineMessage(String appKey, String userId, String title, String body, String payload) {
List<DeviceInfo> targets = pushDispatcher.selectedPushTargets(appKey, userId)
.stream()
.map(DeviceInfo::from)
.toList();
if (!targets.isEmpty()) {
pushDispatcher.pushToUser(appId, userId, title, body, payload);
pushDispatcher.pushToUser(appKey, userId, title, body, payload);
}
return new TestPushResult(appId, userId, !targets.isEmpty(), targets.size(), targets);
return new TestPushResult(appKey, userId, !targets.isEmpty(), targets.size(), targets);
}
public Page<DeviceLoginLogEntity> deviceLogs(String appId, String userId, int page, int size) {
public Page<DeviceLoginLogEntity> deviceLogs(String appKey, String userId, int page, int size) {
int safePage = Math.max(page, 0);
int safeSize = Math.min(Math.max(size, 1), 200);
return logRepository.findByAppIdAndUserIdOrderByCreatedAtDesc(appId, userId, PageRequest.of(safePage, safeSize));
return logRepository.findByAppIdAndUserIdOrderByCreatedAtDesc(appKey, userId, PageRequest.of(safePage, safeSize));
}
public record PushTokenDiagnostics(
String tokenType,
String appId,
String appKey,
String userId,
boolean online,
long lastSeenAt,
@ -141,7 +141,7 @@ public class PushDiagnosticsService {
List<DeviceInfo> devices) {}
public record TestPushResult(
String appId,
String appKey,
String userId,
boolean sent,
int targetCount,

查看文件

@ -49,25 +49,25 @@ public class PushDispatcher {
.collect(Collectors.toMap(PushProvider::vendorName, p -> p));
}
public void pushToUser(String appId, String userId, String title, String body, String payload) {
List<DeviceTokenEntity> targets = selectTargets(appId, userId);
public void pushToUser(String appKey, String userId, String title, String body, String payload) {
List<DeviceTokenEntity> targets = selectTargets(appKey, userId);
if (targets.isEmpty()) {
log.info("Skip push to {}@{}: no receive-enabled device", userId, appId);
log.info("Skip push to {}@{}: no receive-enabled device", userId, appKey);
return;
}
for (DeviceTokenEntity t : targets) {
PushProvider provider = providers.get(t.getVendor().name());
if (provider != null) {
boolean ok = provider.send(appId, t.getToken(), title, body, payload, resolveOptions(appId, payload, t.getVendor()));
log.info("Push to {}@{} via {} deviceId={}: {}", userId, appId, t.getVendor(), t.getDeviceId(), ok ? "OK" : "FAIL");
boolean ok = provider.send(appKey, t.getToken(), title, body, payload, resolveOptions(appKey, payload, t.getVendor()));
log.info("Push to {}@{} via {} deviceId={}: {}", userId, appKey, t.getVendor(), t.getDeviceId(), ok ? "OK" : "FAIL");
}
}
}
private PushSendOptions resolveOptions(String appId, String payload, DeviceTokenEntity.Vendor vendor) {
private PushSendOptions resolveOptions(String appKey, String payload, DeviceTokenEntity.Vendor vendor) {
String routeType = routeType(payload);
String platform = platformForVendor(vendor, payload);
return pushConfigClient.loadServiceConfig(appId, platform, "PUSH")
return pushConfigClient.loadServiceConfig(appKey, platform, "PUSH")
.map(config -> {
JsonNode route = config.path("routing").path(routeType);
String channelKey = route.path("channel").asText("");
@ -135,26 +135,26 @@ public class PushDispatcher {
return "";
}
public List<DeviceTokenEntity> selectedPushTargets(String appId, String userId) {
return selectTargets(appId, userId);
public List<DeviceTokenEntity> selectedPushTargets(String appKey, String userId) {
return selectTargets(appKey, userId);
}
public void pushToUsers(String appId, List<String> userIds, String title, String body, String payload) {
public void pushToUsers(String appKey, List<String> userIds, String title, String body, String payload) {
if (userIds == null || userIds.isEmpty()) {
return;
}
for (String userId : userIds) {
pushToUser(appId, userId, title, body, payload);
pushToUser(appKey, userId, title, body, payload);
}
}
private List<DeviceTokenEntity> selectTargets(String appId, String userId) {
private List<DeviceTokenEntity> selectTargets(String appKey, String userId) {
List<DeviceTokenEntity> devices = tokenRepository
.findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appId, userId);
.findByAppIdAndUserIdAndReceivePushTrueOrderByLastLoginAtDescUpdatedAtDesc(appKey, userId);
if (devices.isEmpty()) {
return List.of();
}
String mode = imConfigClient.multiDeviceLoginMode(appId);
String mode = imConfigClient.multiDeviceLoginMode(appKey);
return switch (mode) {
case "SINGLE_DEVICE" -> List.of(devices.get(0));
case "SAME_PLATFORM_ONE" -> devices.stream()
@ -174,7 +174,7 @@ public class PushDispatcher {
};
}
public void registerToken(String appId,
public void registerToken(String appKey,
String userId,
DeviceTokenEntity.Vendor vendor,
String token,
@ -185,15 +185,15 @@ public class PushDispatcher {
String osVersion,
String appVersion) {
String resolvedDeviceId = normalizeDeviceId(deviceId, vendor, token);
Optional<DeviceTokenEntity> existing = tokenRepository.findByAppIdAndUserIdAndDeviceId(appId, userId, resolvedDeviceId);
Optional<DeviceTokenEntity> existing = tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, resolvedDeviceId);
if (existing.isEmpty() && (deviceId == null || deviceId.isBlank())) {
existing = tokenRepository.findByAppIdAndUserIdAndVendor(appId, userId, vendor);
existing = tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor);
}
LocalDateTime now = LocalDateTime.now();
DeviceTokenEntity entity = existing.orElseGet(() -> {
DeviceTokenEntity e = new DeviceTokenEntity();
e.setId(UUID.randomUUID().toString());
e.setAppId(appId);
e.setAppId(appKey);
e.setUserId(userId);
e.setVendor(vendor);
e.setCreatedAt(now);
@ -214,10 +214,10 @@ public class PushDispatcher {
recordLog(saved, DeviceLoginLogEntity.EventType.REGISTER);
}
public void setReceivePush(String appId, String userId, String deviceId, boolean enabled) {
public void setReceivePush(String appKey, String userId, String deviceId, boolean enabled) {
List<DeviceTokenEntity> tokens = deviceId == null || deviceId.isBlank()
? tokenRepository.findByAppIdAndUserId(appId, userId)
: tokenRepository.findByAppIdAndUserIdAndDeviceId(appId, userId, deviceId).stream().toList();
? tokenRepository.findByAppIdAndUserId(appKey, userId)
: tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId).stream().toList();
for (DeviceTokenEntity token : tokens) {
token.setReceivePush(enabled);
token.setUpdatedAt(LocalDateTime.now());
@ -226,16 +226,16 @@ public class PushDispatcher {
tokenRepository.saveAll(tokens);
}
public void unregisterToken(String appId, String userId, DeviceTokenEntity.Vendor vendor, String deviceId) {
public void unregisterToken(String appKey, String userId, DeviceTokenEntity.Vendor vendor, String deviceId) {
if (deviceId != null && !deviceId.isBlank()) {
tokenRepository.findByAppIdAndUserIdAndDeviceId(appId, userId, deviceId)
tokenRepository.findByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId)
.ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER));
tokenRepository.deleteByAppIdAndUserIdAndDeviceId(appId, userId, deviceId);
tokenRepository.deleteByAppIdAndUserIdAndDeviceId(appKey, userId, deviceId);
return;
}
tokenRepository.findByAppIdAndUserIdAndVendor(appId, userId, vendor)
tokenRepository.findByAppIdAndUserIdAndVendor(appKey, userId, vendor)
.ifPresent(entity -> recordLog(entity, DeviceLoginLogEntity.EventType.UNREGISTER));
tokenRepository.deleteByAppIdAndUserIdAndVendor(appId, userId, vendor);
tokenRepository.deleteByAppIdAndUserIdAndVendor(appKey, userId, vendor);
}
private void recordLog(DeviceTokenEntity token, DeviceLoginLogEntity.EventType eventType) {

查看文件

@ -30,12 +30,12 @@ public class TenantImConfigClient {
this.objectMapper = objectMapper;
}
public String multiDeviceLoginMode(String appId) {
public String multiDeviceLoginMode(String appKey) {
try {
HttpHeaders headers = new HttpHeaders();
headers.set("X-Internal-Token", internalToken);
ResponseEntity<JsonNode> response = restTemplate.exchange(
tenantServiceBaseUrl + "/api/internal/sdk/apps/" + appId + "/services/ANDROID/IM",
tenantServiceBaseUrl + "/api/internal/sdk/apps/" + appKey + "/services/ANDROID/IM",
HttpMethod.GET,
new HttpEntity<>(headers),
JsonNode.class);
@ -55,12 +55,12 @@ public class TenantImConfigClient {
return multi ? "MULTI_DEVICE_FREE" : "SINGLE_DEVICE";
}
} catch (Exception e) {
log.warn("load tenant im config failed appId={} reason={}", appId, e.getMessage());
log.warn("load tenant im config failed appKey={} reason={}", appKey, e.getMessage());
}
return "MULTI_DEVICE_FREE";
}
public boolean allowMultiDeviceLogin(String appId) {
return !"SINGLE_DEVICE".equals(multiDeviceLoginMode(appId));
public boolean allowMultiDeviceLogin(String appKey) {
return !"SINGLE_DEVICE".equals(multiDeviceLoginMode(appKey));
}
}

查看文件

@ -30,13 +30,38 @@ public class TenantPushConfigClient {
@Value("${push.internal-token:xuqm-internal-token}")
private String internalToken;
public Optional<JsonNode> loadServiceConfig(String appId, String platform, String serviceType) {
public Map<String, String> fetchPlatformInfo(String appKey) {
try {
HttpHeaders headers = new HttpHeaders();
headers.set("X-Internal-Token", internalToken);
ResponseEntity<Map> resp = restTemplate.exchange(
tenantServiceBaseUrl + "/api/internal/sdk/apps/" + appKey + "/platform-info",
HttpMethod.GET,
new HttpEntity<>(null, headers),
Map.class
);
Map<?, ?> body = resp.getBody();
if (body == null) return Map.of();
Object data = body.get("data");
if (!(data instanceof Map<?, ?> dataMap)) return Map.of();
Map<String, String> result = new java.util.HashMap<>();
dataMap.forEach((k, v) -> {
if (k instanceof String && v instanceof String) result.put((String) k, (String) v);
});
return result;
} catch (Exception e) {
log.warn("fetch platform info failed appKey={} reason={}", appKey, e.getMessage());
return Map.of();
}
}
public Optional<JsonNode> loadServiceConfig(String appKey, String platform, String serviceType) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-Internal-Token", internalToken);
ResponseEntity<Map> resp = restTemplate.exchange(
tenantServiceBaseUrl + "/api/internal/sdk/apps/" + appId + "/services/" + platform + "/" + serviceType,
tenantServiceBaseUrl + "/api/internal/sdk/apps/" + appKey + "/services/" + platform + "/" + serviceType,
HttpMethod.GET,
new HttpEntity<>(null, headers),
Map.class
@ -59,8 +84,8 @@ public class TenantPushConfigClient {
}
return Optional.of(objectMapper.readTree(json));
} catch (Exception e) {
log.warn("load tenant push config failed appId={} platform={} serviceType={} reason={}",
appId, platform, serviceType, e.getMessage());
log.warn("load tenant push config failed appKey={} platform={} serviceType={} reason={}",
appKey, platform, serviceType, e.getMessage());
return Optional.empty();
}
}

查看文件

@ -63,16 +63,16 @@ public class ApnsPushProvider implements PushProvider {
}
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
return send(appId, token, title, body, payload, PushSendOptions.empty());
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 appId, String token, String title, String body, String payload, PushSendOptions options) {
String teamId = resolveConfig(appId, "teamId", envTeamId);
String keyId = resolveConfig(appId, "keyId", envKeyId);
String bundleId = resolveConfig(appId, "bundleId", envBundleId);
String privateKeyPem = resolveConfig(appId, "privateKey", envPrivateKey);
public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) {
String teamId = resolveConfig(appKey, "teamId", envTeamId);
String keyId = resolveConfig(appKey, "keyId", envKeyId);
String bundleId = resolveConfig(appKey, "bundleId", envBundleId);
String privateKeyPem = resolveConfig(appKey, "privateKey", envPrivateKey);
if (teamId.isBlank() || keyId.isBlank() || bundleId.isBlank() || privateKeyPem.isBlank()) {
log.warn("APNS push not configured");
return false;
@ -151,8 +151,8 @@ public class ApnsPushProvider implements PushProvider {
return kf.generatePrivate(spec);
}
private String resolveConfig(String appId, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appId, "IOS", "PUSH")
private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "IOS", "PUSH")
.map(node -> node.path("apns"))
.orElse(null);
if (config == null) {

查看文件

@ -56,14 +56,14 @@ public class FcmPushProvider implements PushProvider {
}
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
return send(appId, token, title, body, payload, PushSendOptions.empty());
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 appId, String token, String title, String body, String payload, PushSendOptions options) {
String projectId = resolveConfig(appId, "projectId", envProjectId);
String serviceAccountJson = resolveConfig(appId, "serviceAccountJson", envServiceAccountJson);
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;
@ -144,8 +144,8 @@ public class FcmPushProvider implements PushProvider {
return kf.generatePrivate(spec);
}
private String resolveConfig(String appId, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appId, "ANDROID", "PUSH")
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) {

查看文件

@ -53,14 +53,14 @@ public class HarmonyPushProvider implements PushProvider {
}
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
return send(appId, token, title, body, payload, PushSendOptions.empty());
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 appId, String token, String title, String body, String payload, PushSendOptions options) {
String resolvedAppId = resolveConfig(appId, "appId", envAppId);
String resolvedAppSecret = resolveConfig(appId, "appSecret", envAppSecret);
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, "appSecret", envAppSecret);
if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) {
log.warn("Harmony push not configured");
return false;
@ -113,8 +113,8 @@ public class HarmonyPushProvider implements PushProvider {
return (String) json.get("access_token");
}
private String resolveConfig(String appId, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appId, "HARMONY", "PUSH")
private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "HARMONY", "PUSH")
.map(node -> node.path("harmony"))
.orElse(null);
if (config == null) {

查看文件

@ -45,11 +45,11 @@ public class HonorPushProvider implements PushProvider {
}
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
String resolvedAppId = resolveConfig(appId, "appId", envAppId);
String resolvedAppSecret = resolveConfig(appId, "clientSecret", envAppSecret);
public boolean send(String appKey, String token, String title, String body, String payload) {
String resolvedAppId = resolveConfig(appKey, "appId", envAppId);
String resolvedAppSecret = resolveConfig(appKey, "clientSecret", envAppSecret);
if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) {
resolvedAppSecret = resolveConfig(appId, "appSecret", envAppSecret);
resolvedAppSecret = resolveConfig(appKey, "appSecret", envAppSecret);
}
if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) {
log.warn("Honor push not configured");
@ -92,8 +92,8 @@ public class HonorPushProvider implements PushProvider {
return (String) json.get("access_token");
}
private String resolveConfig(String appId, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appId, "ANDROID", "PUSH")
private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("honor"))
.orElse(null);
if (config == null) {

查看文件

@ -46,14 +46,14 @@ public class HuaweiPushProvider implements PushProvider {
}
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
return send(appId, token, title, body, payload, PushSendOptions.empty());
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 appId, String token, String title, String body, String payload, PushSendOptions options) {
String resolvedAppId = resolveConfig(appId, "appId", envAppId);
String resolvedAppSecret = resolveConfig(appId, "appSecret", envAppSecret);
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, "appSecret", envAppSecret);
if (resolvedAppId.isBlank() || resolvedAppSecret.isBlank()) {
log.warn("Huawei push not configured");
return false;
@ -65,8 +65,14 @@ public class HuaweiPushProvider implements PushProvider {
bodyMap.put("token", new String[]{token});
bodyMap.put("notification", Map.of("title", title, "body", body));
bodyMap.put("data", payload != null ? payload : "{}");
if (options != null && options.channelId() != null && !options.channelId().isBlank()) {
bodyMap.put("android", Map.of("notification", Map.of("channel_id", options.channelId())));
Map<String, Object> androidNotification = new LinkedHashMap<>();
String channelId = options != null && options.channelId() != null && !options.channelId().isBlank()
? options.channelId() : "";
String category = resolveConfig(appKey, "category", "");
if (!channelId.isBlank()) androidNotification.put("channel_id", channelId);
if (!category.isBlank()) androidNotification.put("category", category);
if (!androidNotification.isEmpty()) {
bodyMap.put("android", Map.of("notification", androidNotification));
}
Map<String, Object> message = Map.of("message", bodyMap);
String requestBody = objectMapper.writeValueAsString(message);
@ -96,8 +102,8 @@ public class HuaweiPushProvider implements PushProvider {
return (String) json.get("access_token");
}
private String resolveConfig(String appId, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appId, "ANDROID", "PUSH")
private String resolveConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("huawei"))
.orElse(null);
if (config == null) {

查看文件

@ -39,25 +39,25 @@ public class OppoPushProvider implements PushProvider {
}
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
String appKey = resolveConfig(appId, "appKey");
String masterSecret = resolveConfig(appId, "masterSecret");
if (appKey.isBlank() || masterSecret.isBlank()) {
public boolean send(String appKey, String token, String title, String body, String payload) {
String vendorAppKey = resolveConfig(appKey, "appKey");
String masterSecret = resolveConfig(appKey, "masterSecret");
if (vendorAppKey.isBlank() || masterSecret.isBlank()) {
log.warn("OPPO push not configured");
return false;
}
try {
String authToken = getAccessToken(appKey, masterSecret);
String messageId = appId + "_" + System.currentTimeMillis();
Map<String, Object> message = Map.of(
"message", Map.of(
"app_message_id", messageId,
"title", title,
"content", body,
"target_type", 2,
"target_value", token
)
);
String authToken = getAccessToken(vendorAppKey, masterSecret);
String messageId = appKey + "_" + System.currentTimeMillis();
Map<String, Object> inner = new java.util.LinkedHashMap<>();
inner.put("app_message_id", messageId);
inner.put("title", title);
inner.put("content", body);
inner.put("target_type", 2);
inner.put("target_value", token);
String 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);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(PUSH_URL))
@ -96,8 +96,8 @@ public class OppoPushProvider implements PushProvider {
return token;
}
private String resolveConfig(String appId, String key) {
JsonNode config = configClient.loadServiceConfig(appId, "ANDROID", "PUSH")
private String resolveConfig(String appKey, String key) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("oppo"))
.orElse(null);
if (config == null) {

查看文件

@ -2,9 +2,9 @@ package com.xuqm.push.service.provider;
public interface PushProvider {
String vendorName();
boolean send(String appId, String token, String title, String body, String payload);
boolean send(String appKey, String token, String title, String body, String payload);
default boolean send(String appId, String token, String title, String body, String payload, PushSendOptions options) {
return send(appId, token, title, body, payload);
default boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) {
return send(appKey, token, title, body, payload);
}
}

查看文件

@ -39,21 +39,29 @@ public class VivoPushProvider implements PushProvider {
}
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
String appKey = resolveConfig(appId, "appKey");
String appIdConfig = resolveConfig(appId, "appId");
if (appKey.isBlank() || appIdConfig.isBlank()) {
public boolean send(String appKey, String token, String title, String body, String payload) {
String vendorAppKey = resolveConfig(appKey, "appKey");
String appIdConfig = resolveConfig(appKey, "appId");
if (vendorAppKey.isBlank() || appIdConfig.isBlank()) {
log.warn("Vivo push not configured");
return false;
}
try {
String authToken = getAccessToken(appIdConfig, appKey);
Map<String, Object> message = Map.of(
"regId", token,
"title", title,
"content", body,
"notifyType", 1
);
String authToken = getAccessToken(appIdConfig, vendorAppKey);
Map<String, Object> message = new java.util.LinkedHashMap<>();
message.put("regId", token);
message.put("title", title);
message.put("content", body);
message.put("notifyType", 1);
String 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);
}
if (!receiptId.isBlank()) {
message.put("requestId", receiptId + "_" + System.currentTimeMillis());
}
String requestBody = objectMapper.writeValueAsString(message);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(PUSH_URL))
@ -92,8 +100,8 @@ public class VivoPushProvider implements PushProvider {
return token;
}
private String resolveConfig(String appId, String key) {
JsonNode config = configClient.loadServiceConfig(appId, "ANDROID", "PUSH")
private String resolveConfig(String appKey, String key) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("vivo"))
.orElse(null);
if (config == null) {

查看文件

@ -13,6 +13,7 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@Component
public class XiaomiPushProvider implements PushProvider {
@ -36,50 +37,79 @@ public class XiaomiPushProvider implements PushProvider {
public String vendorName() { return "XIAOMI"; }
@Override
public boolean send(String appId, String token, String title, String body, String payload) {
return send(appId, token, title, body, payload, PushSendOptions.empty());
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 appId, String token, String title, String body, String payload, PushSendOptions options) {
String appSecret = resolveConfig(appId, "appSecret", envAppSecret);
public boolean send(String appKey, String token, String title, String body, String payload, PushSendOptions options) {
String appSecret = resolveVendorConfig(appKey, "appSecret", envAppSecret);
if (appSecret.isBlank()) {
appSecret = resolveConfig(appId, "appKey", envAppSecret);
appSecret = resolveVendorConfig(appKey, "appKey", envAppSecret);
}
if (appSecret.isBlank()) {
log.warn("Xiaomi push not configured");
return false;
}
String packageName = resolveConfig(appId, "packageName", "");
Map<String, String> platformInfo = configClient.fetchPlatformInfo(appKey);
String packageName = platformInfo.getOrDefault("androidPackageName", "");
if (packageName.isBlank()) {
log.warn("Xiaomi push skipped: packageName not configured for appId={}", appId);
log.warn("Xiaomi push skipped: Android packageName not set in app config for appKey={}", appKey);
return false;
}
try {
String form = "registration_id=" + URLEncoder.encode(token, StandardCharsets.UTF_8)
+ "&title=" + URLEncoder.encode(title, StandardCharsets.UTF_8)
+ "&description=" + URLEncoder.encode(body, StandardCharsets.UTF_8)
+ "&restricted_package_name=" + URLEncoder.encode(packageName, StandardCharsets.UTF_8)
+ "&notify_type=1";
String channelId = resolveVendorConfig(appKey, "channelId", "");
if (options != null && options.channelId() != null && !options.channelId().isBlank()) {
form += "&channel_id=" + URLEncoder.encode(options.channelId(), StandardCharsets.UTF_8);
channelId = options.channelId();
}
StringBuilder form = new StringBuilder();
form.append("registration_id=").append(URLEncoder.encode(token, StandardCharsets.UTF_8))
.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");
if (!channelId.isBlank()) {
form.append("&channel_id=").append(URLEncoder.encode(channelId, StandardCharsets.UTF_8));
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(pushUrl))
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Authorization", "key=" + appSecret)
.POST(HttpRequest.BodyPublishers.ofString(form))
.POST(HttpRequest.BodyPublishers.ofString(form.toString()))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return response.statusCode() == 200;
log.debug("Xiaomi push response status={} body={}", response.statusCode(), response.body());
if (response.statusCode() != 200) {
log.warn("Xiaomi push HTTP error status={} body={}", response.statusCode(), response.body());
return false;
}
// Xiaomi returns 200 even on error; check result field
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
JsonNode json = mapper.readTree(response.body());
String result = json.path("result").asText("");
if (!"ok".equalsIgnoreCase(result)) {
log.warn("Xiaomi push rejected result={} description={} body={}",
result, json.path("description").asText(""), response.body());
return false;
}
} catch (Exception e) {
log.warn("Xiaomi push response parse error: {}", e.getMessage());
}
return true;
} catch (Exception e) {
log.error("Xiaomi push failed: {}", e.getMessage());
return false;
}
}
private String resolveConfig(String appId, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appId, "ANDROID", "PUSH")
private String resolveVendorConfig(String appKey, String key, String fallback) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("xiaomi"))
.orElse(null);
if (config == null) {

查看文件

@ -48,4 +48,4 @@ push:
push-url: https://api.push.apple.com/3/device/{token}
sandbox-push-url: https://api.sandbox.push.apple.com/3/device/{token}
tenant-service-base-url: ${TENANT_SERVICE_BASE_URL:http://tenant-service:8081}
tenant-service-base-url: ${TENANT_SERVICE_BASE_URL:http://tenant-service:9001}

查看文件

@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/apps/{appId}/services")
@RequestMapping("/api/apps/{appKey}/services")
public class FeatureServiceController {
private final FeatureServiceManager featureServiceManager;
@ -37,34 +37,34 @@ public class FeatureServiceController {
@GetMapping
public ResponseEntity<ApiResponse<List<FeatureServiceEntity>>> list(
@PathVariable String appId, @AuthenticationPrincipal String tenantId) {
appService.getById(appId, tenantId);
return ResponseEntity.ok(ApiResponse.success(featureServiceManager.listByApp(appId)));
@PathVariable String appKey, @AuthenticationPrincipal String tenantId) {
appService.getById(appKey, tenantId);
return ResponseEntity.ok(ApiResponse.success(featureServiceManager.listByApp(appKey)));
}
@GetMapping("/item")
public ResponseEntity<ApiResponse<FeatureServiceEntity>> get(
@PathVariable String appId,
@PathVariable String appKey,
@RequestParam FeatureServiceEntity.Platform platform,
@RequestParam FeatureServiceEntity.ServiceType serviceType,
@AuthenticationPrincipal String tenantId) {
appService.getById(appId, tenantId);
return ResponseEntity.ok(ApiResponse.success(featureServiceManager.getByPlatform(appId, platform, serviceType)));
appService.getById(appKey, tenantId);
return ResponseEntity.ok(ApiResponse.success(featureServiceManager.getByPlatform(appKey, platform, serviceType)));
}
/** Disable a service (enable=false only; enabling requires ops approval via request-activation). */
@PostMapping("/toggle")
public ResponseEntity<ApiResponse<FeatureServiceEntity>> toggle(
@PathVariable String appId,
@PathVariable String appKey,
@RequestParam FeatureServiceEntity.Platform platform,
@RequestParam FeatureServiceEntity.ServiceType serviceType,
@RequestParam boolean enable,
@AuthenticationPrincipal String tenantId) {
appService.getById(appId, tenantId);
appService.getById(appKey, tenantId);
if (enable) {
throw new com.xuqm.common.exception.BusinessException(400, "开启服务请通过 request-activation 申请");
}
FeatureServiceEntity saved = featureServiceManager.disable(appId, platform, serviceType);
FeatureServiceEntity saved = featureServiceManager.disable(appKey, platform, serviceType);
operationLogService.record(tenantId, "SERVICE", "FEATURE_SERVICE", saved.getId(), "DISABLE_SERVICE", java.util.Map.of(
"platform", platform.name(),
"serviceType", serviceType.name()
@ -74,15 +74,15 @@ public class FeatureServiceController {
@PutMapping("/config")
public ResponseEntity<ApiResponse<FeatureServiceEntity>> updateConfig(
@PathVariable String appId,
@PathVariable String appKey,
@RequestParam FeatureServiceEntity.Platform platform,
@RequestParam FeatureServiceEntity.ServiceType serviceType,
@RequestBody FeatureServiceConfigRequest req,
@AuthenticationPrincipal String tenantId) {
appService.getById(appId, tenantId);
appService.getById(appKey, tenantId);
String config = switch (serviceType) {
case IM -> featureServiceManager.buildImConfig(
appId,
appKey,
platform,
req == null ? null : req.allowStrangerMessage(),
req == null ? null : req.allowFriendRequest(),
@ -95,7 +95,7 @@ public class FeatureServiceController {
req == null ? null : req.allowMultiDeviceLogin(),
req == null ? null : req.multiClientConversationDeleteSync());
case UPDATE -> featureServiceManager.buildUpdateConfig(
appId,
appKey,
platform,
req == null ? null : req.defaultStoreTargets(),
req == null ? null : req.defaultPublishMode(),
@ -110,19 +110,24 @@ public class FeatureServiceController {
req == null ? null : req.defaultAppStoreUrl(),
req == null ? null : req.defaultMarketUrl());
case PUSH -> featureServiceManager.buildPushConfig(
appId,
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(),
@ -138,7 +143,7 @@ public class FeatureServiceController {
req == null ? null : req.routing());
};
FeatureServiceEntity saved = featureServiceManager.updateConfig(
appId, platform, serviceType, config);
appKey, platform, serviceType, config);
operationLogService.record(tenantId, "SERVICE", "FEATURE_SERVICE", saved.getId(), "UPDATE_SERVICE_CONFIG", java.util.Map.of(
"platform", platform.name(),
"serviceType", serviceType.name()
@ -149,13 +154,13 @@ public class FeatureServiceController {
/** Submit an activation request for ops approval. */
@PostMapping("/request-activation")
public ResponseEntity<ApiResponse<ServiceActivationRequestEntity>> requestActivation(
@PathVariable String appId,
@PathVariable String appKey,
@RequestParam FeatureServiceEntity.Platform platform,
@RequestParam FeatureServiceEntity.ServiceType serviceType,
@RequestParam(required = false) String applyReason,
@AuthenticationPrincipal String tenantId) {
appService.getById(appId, tenantId);
ServiceActivationRequestEntity saved = featureServiceManager.submitActivationRequest(appId, platform, serviceType, applyReason);
appService.getById(appKey, tenantId);
ServiceActivationRequestEntity saved = featureServiceManager.submitActivationRequest(appKey, platform, serviceType, applyReason);
java.util.Map<String, Object> detail = new java.util.LinkedHashMap<>();
detail.put("platform", platform.name());
detail.put("serviceType", serviceType.name());
@ -168,17 +173,17 @@ public class FeatureServiceController {
@GetMapping("/requests")
public ResponseEntity<ApiResponse<List<ServiceActivationRequestEntity>>> listRequests(
@PathVariable String appId, @AuthenticationPrincipal String tenantId) {
appService.getById(appId, tenantId);
return ResponseEntity.ok(ApiResponse.success(featureServiceManager.listRequestsByApp(appId)));
@PathVariable String appKey, @AuthenticationPrincipal String tenantId) {
appService.getById(appKey, tenantId);
return ResponseEntity.ok(ApiResponse.success(featureServiceManager.listRequestsByApp(appKey)));
}
@PostMapping("/{id}/regenerate-key")
public ResponseEntity<ApiResponse<FeatureServiceEntity>> regenerateKey(
@PathVariable String appId,
@PathVariable String appKey,
@PathVariable String id,
@AuthenticationPrincipal String tenantId) {
appService.getById(appId, tenantId);
appService.getById(appKey, tenantId);
FeatureServiceEntity updated = featureServiceManager.regenerateSecretKey(id);
operationLogService.record(tenantId, "SERVICE", "FEATURE_SERVICE", updated.getId(), "REGENERATE_KEY", java.util.Map.of(
"platform", updated.getPlatform().name(),
@ -212,15 +217,20 @@ public class FeatureServiceController {
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,
@ -243,21 +253,26 @@ public class FeatureServiceController {
JsonNode channels,
JsonNode routing
) {
public String huaweiAppIdValue() { return firstText(huaweiAppId, huawei == null ? null : huawei.appId()); }
public String huaweiAppIdValue() { return firstText(huaweiAppId, huawei == null ? null : huawei.appKey()); }
public String huaweiAppSecretValue() { return firstText(huaweiAppSecret, huawei == null ? null : huawei.appSecret()); }
public String xiaomiAppIdValue() { return firstText(xiaomiAppId, xiaomi == null ? null : xiaomi.appId()); }
public String huaweiCategoryValue() { return firstText(huaweiCategory, huawei == null ? null : huawei.category()); }
public String xiaomiAppIdValue() { return firstText(xiaomiAppId, xiaomi == null ? null : xiaomi.appKey()); }
public String xiaomiAppKeyValue() { return firstText(xiaomiAppKey, xiaomi == null ? null : xiaomi.appKey()); }
public String xiaomiAppSecretValue() { return firstText(xiaomiAppSecret, xiaomi == null ? null : xiaomi.appSecret()); }
public String oppoAppIdValue() { return firstText(oppoAppId, oppo == null ? null : oppo.appId()); }
public String xiaomiChannelIdValue() { return firstText(xiaomiChannelId, xiaomi == null ? null : xiaomi.channelId()); }
public String oppoAppIdValue() { return firstText(oppoAppId, oppo == null ? null : oppo.appKey()); }
public String oppoAppKeyValue() { return firstText(oppoAppKey, oppo == null ? null : oppo.appKey()); }
public String oppoMasterSecretValue() { return firstText(oppoMasterSecret, oppo == null ? null : oppo.masterSecret()); }
public String vivoAppIdValue() { return firstText(vivoAppId, vivo == null ? null : vivo.appId()); }
public String oppoChannelIdValue() { return firstText(oppoChannelId, oppo == null ? null : oppo.channelId()); }
public String vivoAppIdValue() { return firstText(vivoAppId, vivo == null ? null : vivo.appKey()); }
public String vivoAppKeyValue() { return firstText(vivoAppKey, vivo == null ? null : vivo.appKey()); }
public String vivoAppSecretValue() { return firstText(vivoAppSecret, vivo == null ? null : vivo.appSecret()); }
public String honorAppIdValue() { return firstText(honorAppId, honor == null ? null : honor.appId()); }
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.appKey()); }
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 harmonyAppIdValue() { return firstText(harmonyAppId, harmony == null ? null : harmony.appKey()); }
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()); }
@ -282,7 +297,7 @@ public class FeatureServiceController {
}
public record PushVendorConfig(
String appId,
String appKey,
String appKey,
String appSecret,
String masterSecret,
@ -293,6 +308,9 @@ public class FeatureServiceController {
String bundleId,
String keyPath,
Boolean sandbox,
String serviceAccountJson
String serviceAccountJson,
String channelId,
String category,
String receiptId
) {}
}

查看文件

@ -32,24 +32,40 @@ public class InternalSdkController {
this.featureServiceManager = featureServiceManager;
}
@GetMapping("/apps/{appId}/secret")
@GetMapping("/apps/{appKey}/secret")
public ResponseEntity<ApiResponse<Map<String, String>>> getAppSecret(
@PathVariable String appId,
@PathVariable String appKey,
@RequestHeader(value = "X-Internal-Token", required = false) String token) {
if (token == null || !internalToken.equals(token)) {
return ResponseEntity.status(403)
.body(ApiResponse.error(403, "Forbidden"));
}
AppEntity app = provisioningService.resolveApp(appId);
AppEntity app = provisioningService.resolveApp(appKey);
return ResponseEntity.ok(ApiResponse.success(Map.of(
"appId", app.getAppKey(),
"appKey", app.getAppKey(),
"appSecret", app.getAppSecret()
)));
}
@GetMapping("/apps/{appId}/services/{platform}/{serviceType}")
@GetMapping("/apps/{appKey}/platform-info")
public ResponseEntity<ApiResponse<Map<String, String>>> getPlatformInfo(
@PathVariable String appKey,
@RequestHeader(value = "X-Internal-Token", required = false) String token) {
if (token == null || !internalToken.equals(token)) {
return ResponseEntity.status(403)
.body(ApiResponse.error(403, "Forbidden"));
}
AppEntity app = provisioningService.resolveApp(appKey);
return ResponseEntity.ok(ApiResponse.success(Map.of(
"androidPackageName", app.getPackageName() == null ? "" : app.getPackageName(),
"iosBundleId", app.getIosBundleId() == null ? "" : app.getIosBundleId(),
"harmonyBundleName", app.getHarmonyBundleName() == null ? "" : app.getHarmonyBundleName()
)));
}
@GetMapping("/apps/{appKey}/services/{platform}/{serviceType}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getService(
@PathVariable String appId,
@PathVariable String appKey,
@PathVariable FeatureServiceEntity.Platform platform,
@PathVariable FeatureServiceEntity.ServiceType serviceType,
@RequestHeader(value = "X-Internal-Token", required = false) String token) {
@ -57,8 +73,8 @@ public class InternalSdkController {
return ResponseEntity.status(403)
.body(ApiResponse.error(403, "Forbidden"));
}
provisioningService.resolveApp(appId);
FeatureServiceEntity service = featureServiceManager.getOrFail(appId, platform, serviceType);
provisioningService.resolveApp(appKey);
FeatureServiceEntity service = featureServiceManager.getOrFail(appKey, platform, serviceType);
return ResponseEntity.ok(ApiResponse.success(Map.of(
"enabled", service.isEnabled(),
"config", service.getConfig() == null ? "" : service.getConfig()

查看文件

@ -168,25 +168,25 @@ public class OpsController {
@PreAuthorize("hasAuthority('ROLE_OPS')")
public ResponseEntity<ApiResponse<Map<String, Object>>> searchPushByToken(
@RequestParam String token,
@RequestParam(required = false) String appId) {
return ResponseEntity.ok(ApiResponse.success(pushDiagnosticsClient.searchByToken(token, appId)));
@RequestParam(required = false) String appKey) {
return ResponseEntity.ok(ApiResponse.success(pushDiagnosticsClient.searchByToken(token, appKey)));
}
@GetMapping("/api/ops/push/device-logs")
@PreAuthorize("hasAuthority('ROLE_OPS')")
public ResponseEntity<ApiResponse<Map<String, Object>>> pushDeviceLogs(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String userId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(pushDiagnosticsClient.deviceLogs(appId, userId, page, size)));
return ResponseEntity.ok(ApiResponse.success(pushDiagnosticsClient.deviceLogs(appKey, userId, page, size)));
}
@PostMapping("/api/ops/push/test-offline")
@PreAuthorize("hasAuthority('ROLE_OPS')")
public ResponseEntity<ApiResponse<Map<String, Object>>> sendPushTestOffline(@RequestBody Map<String, String> body) {
return ResponseEntity.ok(ApiResponse.success(pushDiagnosticsClient.sendTestOffline(
body.get("appId"),
body.get("appKey"),
body.get("userId"),
body.getOrDefault("title", "XuqmGroup Push 测试"),
body.getOrDefault("body", "这是一条离线推送测试消息"),

查看文件

@ -43,19 +43,19 @@ public class SdkConfigController {
}
/**
* GET /api/sdk/config?appId=XXX&platform=ANDROID public, no auth required.
* GET /api/sdk/config?appKey=XXX&platform=ANDROID public, no auth required.
*
* Returns SDK configuration URLs and enabled feature flags for the given appId/appKey.
* Returns SDK configuration URLs and enabled feature flags for the given appKey/appKey.
* The demo app (`ak_demo_chat`) is auto-provisioned if it does not exist.
* For update releases, the platform-specific UPDATE row drives both the enabled flag and
* the default release configuration.
*/
@GetMapping("/config")
public ResponseEntity<ApiResponse<SdkConfigResponse>> getConfig(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false, defaultValue = "ANDROID") FeatureServiceEntity.Platform platform) {
AppEntity app = sdkAppProvisioningService.resolveApp(appId);
AppEntity app = sdkAppProvisioningService.resolveApp(appKey);
List<FeatureServiceEntity> features = featureServiceRepository.findByAppId(app.getAppKey());
boolean imEnabled = features.stream()

查看文件

@ -5,6 +5,8 @@ import jakarta.validation.constraints.Size;
public record CreateAppRequest(
@NotBlank @Size(max = 128) String packageName,
@Size(max = 128) String iosBundleId,
@Size(max = 128) String harmonyBundleName,
@NotBlank @Size(max = 128) String name,
@Size(max = 512) String description,
String iconUrl

查看文件

@ -19,6 +19,12 @@ public class AppEntity {
@Column(nullable = false, length = 128)
private String packageName;
@Column(length = 128)
private String iosBundleId;
@Column(length = 128)
private String harmonyBundleName;
@Column(nullable = false, length = 128)
private String name;
@ -46,6 +52,12 @@ public class AppEntity {
public String getPackageName() { return packageName; }
public void setPackageName(String packageName) { this.packageName = packageName; }
public String getIosBundleId() { return iosBundleId; }
public void setIosBundleId(String iosBundleId) { this.iosBundleId = iosBundleId; }
public String getHarmonyBundleName() { return harmonyBundleName; }
public void setHarmonyBundleName(String harmonyBundleName) { this.harmonyBundleName = harmonyBundleName; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }

查看文件

@ -47,6 +47,8 @@ public class AppService {
app.setId(UUID.randomUUID().toString());
app.setTenantId(tenantId);
app.setPackageName(req.packageName());
app.setIosBundleId(req.iosBundleId());
app.setHarmonyBundleName(req.harmonyBundleName());
app.setName(req.name());
app.setDescription(req.description());
app.setIconUrl(req.iconUrl());
@ -69,6 +71,8 @@ public class AppService {
before.put("packageName", app.getPackageName());
before.put("description", app.getDescription());
before.put("iconUrl", app.getIconUrl());
app.setIosBundleId(req.iosBundleId());
app.setHarmonyBundleName(req.harmonyBundleName());
app.setName(req.name());
app.setDescription(req.description());
app.setIconUrl(req.iconUrl());

查看文件

@ -37,8 +37,8 @@ public class FeatureServiceManager {
this.objectMapper = objectMapper;
}
public List<FeatureServiceEntity> listByApp(String appId) {
List<FeatureServiceEntity> services = repository.findByAppId(appId);
public List<FeatureServiceEntity> listByApp(String appKey) {
List<FeatureServiceEntity> services = repository.findByAppId(appKey);
if (services.isEmpty()) {
return services;
}
@ -62,13 +62,13 @@ public class FeatureServiceManager {
*/
@Transactional
public ServiceActivationRequestEntity submitActivationRequest(
String appId,
String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType,
String applyReason) {
if (isAppWideService(serviceType)) {
requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(appId, serviceType)
requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(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(appId);
req.setAppId(appKey);
req.setPlatform(platform);
req.setServiceType(serviceType);
req.setStatus(Status.PENDING);
@ -91,10 +91,10 @@ public class FeatureServiceManager {
* Disable a service immediately (no approval needed).
*/
@Transactional
public FeatureServiceEntity disable(String appId, FeatureServiceEntity.Platform platform,
public FeatureServiceEntity disable(String appKey, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
if (isAppWideService(serviceType)) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appId, serviceType);
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType);
if (services.isEmpty()) {
throw new BusinessException(404, "服务未开通");
}
@ -104,7 +104,7 @@ public class FeatureServiceManager {
}
FeatureServiceEntity entity = repository
.findByAppIdAndPlatformAndServiceType(appId, platform, serviceType)
.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
.orElseThrow(() -> new BusinessException(404, "服务未开通"));
entity.setEnabled(false);
return repository.save(entity);
@ -182,29 +182,29 @@ public class FeatureServiceManager {
return requestRepository.save(req);
}
public List<ServiceActivationRequestEntity> listRequestsByApp(String appId) {
return requestRepository.findByAppIdOrderByCreatedAtDesc(appId);
public List<ServiceActivationRequestEntity> listRequestsByApp(String appKey) {
return requestRepository.findByAppIdOrderByCreatedAtDesc(appKey);
}
public FeatureServiceEntity getOrFail(String appId, FeatureServiceEntity.Platform platform,
public FeatureServiceEntity getOrFail(String appKey, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
return repository.findByAppIdAndServiceType(appId, serviceType)
return repository.findByAppIdAndServiceType(appKey, serviceType)
.stream()
.findFirst()
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
}
return repository.findByAppIdAndPlatformAndServiceType(appId, platform, serviceType)
return repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
.orElseThrow(() -> new BusinessException(404, "服务未配置"));
}
@Transactional
public FeatureServiceEntity updateConfig(String appId,
public FeatureServiceEntity updateConfig(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType,
String config) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appId, serviceType);
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appKey, serviceType);
if (services.isEmpty()) {
throw new BusinessException(404, "服务未配置");
}
@ -213,73 +213,73 @@ public class FeatureServiceManager {
return services.get(0);
}
FeatureServiceEntity entity = getOrFail(appId, platform, serviceType);
FeatureServiceEntity entity = getOrFail(appKey, platform, serviceType);
entity.setConfig(config);
return repository.save(entity);
}
public FeatureServiceEntity getByPlatform(String appId,
public FeatureServiceEntity getByPlatform(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return getOrFail(appId, platform, serviceType);
return getOrFail(appKey, platform, serviceType);
}
public boolean allowStrangerMessage(String appId,
public boolean allowStrangerMessage(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("allowStrangerMessage").asBoolean(false);
return readConfigNode(appKey, platform, serviceType).path("allowStrangerMessage").asBoolean(false);
}
public boolean allowFriendRequest(String appId,
public boolean allowFriendRequest(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("allowFriendRequest").asBoolean(true);
return readConfigNode(appKey, platform, serviceType).path("allowFriendRequest").asBoolean(true);
}
public boolean allowGroupJoinRequest(String appId,
public boolean allowGroupJoinRequest(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("allowGroupJoinRequest").asBoolean(true);
return readConfigNode(appKey, platform, serviceType).path("allowGroupJoinRequest").asBoolean(true);
}
public String friendRequestMode(String appId,
public String friendRequestMode(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
String mode = readConfigNode(appId, platform, serviceType).path("friendRequestMode").asText("");
String mode = readConfigNode(appKey, platform, serviceType).path("friendRequestMode").asText("");
return switch (mode == null ? "" : mode.trim().toUpperCase()) {
case "DIRECT_ACCEPT", "DISALLOW" -> mode.trim().toUpperCase();
default -> "REQUIRE_CONFIRM";
};
}
public boolean blacklistSendSuccess(String appId,
public boolean blacklistSendSuccess(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
return readConfigNode(appId, platform, serviceType).path("blacklistSendSuccess").asBoolean(true);
return readConfigNode(appKey, platform, serviceType).path("blacklistSendSuccess").asBoolean(true);
}
public int messageRecallMinutes(String appId,
public int messageRecallMinutes(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
int minutes = readConfigNode(appId, platform, serviceType).path("messageRecallMinutes").asInt(2);
int minutes = readConfigNode(appKey, platform, serviceType).path("messageRecallMinutes").asInt(2);
return Math.max(minutes, 0);
}
public int conversationPullLimit(String appId,
public int conversationPullLimit(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
int limit = readConfigNode(appId, platform, serviceType).path("conversationPullLimit").asInt(100);
int limit = readConfigNode(appKey, platform, serviceType).path("conversationPullLimit").asInt(100);
return Math.min(Math.max(limit, 1), 500);
}
public int historyRetentionDays(String appId,
public int historyRetentionDays(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
int days = readConfigNode(appId, platform, serviceType).path("historyRetentionDays").asInt(7);
int days = readConfigNode(appKey, platform, serviceType).path("historyRetentionDays").asInt(7);
return Math.max(days, 1);
}
public String buildImConfig(String appId,
public String buildImConfig(String appKey,
FeatureServiceEntity.Platform platform,
Boolean allowStrangerMessage,
Boolean allowFriendRequest,
@ -291,7 +291,7 @@ public class FeatureServiceManager {
Integer conversationPullLimit,
Boolean allowMultiDeviceLogin,
Boolean multiClientConversationDeleteSync) {
ObjectNode node = readConfigNode(appId, platform, FeatureServiceEntity.ServiceType.IM).deepCopy();
ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.IM).deepCopy();
if (!node.has("allowStrangerMessage")) {
node.put("allowStrangerMessage", false);
}
@ -379,7 +379,7 @@ public class FeatureServiceManager {
return node.toString();
}
public String buildUpdateConfig(String appId,
public String buildUpdateConfig(String appKey,
FeatureServiceEntity.Platform platform,
List<String> defaultStoreTargets,
String defaultPublishMode,
@ -393,7 +393,7 @@ public class FeatureServiceManager {
String defaultPackageName,
String defaultAppStoreUrl,
String defaultMarketUrl) {
ObjectNode node = readConfigNode(appId, platform, FeatureServiceEntity.ServiceType.UPDATE).deepCopy();
ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.UPDATE).deepCopy();
if (!node.has("defaultStoreTargets")) {
node.putArray("defaultStoreTargets");
}
@ -475,19 +475,24 @@ public class FeatureServiceManager {
return node.toString();
}
public String buildPushConfig(String appId,
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,
@ -501,7 +506,7 @@ public class FeatureServiceManager {
String fcmServiceAccountJson,
JsonNode channels,
JsonNode routing) {
ObjectNode node = readConfigNode(appId, platform, FeatureServiceEntity.ServiceType.PUSH).deepCopy();
ObjectNode node = readConfigNode(appKey, platform, FeatureServiceEntity.ServiceType.PUSH).deepCopy();
ensureObjectNode(node, "huawei");
ensureObjectNode(node, "xiaomi");
ensureObjectNode(node, "oppo");
@ -513,18 +518,23 @@ public class FeatureServiceManager {
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);
@ -634,17 +644,17 @@ public class FeatureServiceManager {
|| serviceType == FeatureServiceEntity.ServiceType.UPDATE;
}
private JsonNode readConfigNode(String appId,
private JsonNode readConfigNode(String appKey,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
FeatureServiceEntity entity;
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
entity = repository.findByAppIdAndServiceType(appId, serviceType)
entity = repository.findByAppIdAndServiceType(appKey, serviceType)
.stream()
.findFirst()
.orElse(null);
} else {
entity = repository.findByAppIdAndPlatformAndServiceType(appId, platform, serviceType)
entity = repository.findByAppIdAndPlatformAndServiceType(appKey, platform, serviceType)
.orElse(null);
}
if (entity == null || entity.getConfig() == null || entity.getConfig().isBlank()) {

查看文件

@ -30,18 +30,18 @@ public class OpsPushDiagnosticsClient {
this.objectMapper = objectMapper;
}
public Map<String, Object> searchByToken(String token, String appId) {
public Map<String, Object> searchByToken(String token, String appKey) {
String uri = UriComponentsBuilder.fromHttpUrl(pushServiceBaseUrl + "/api/push/internal/diagnostics/search")
.queryParam("queryToken", token)
.queryParamIfPresent("appId", appId == null || appId.isBlank() ? java.util.Optional.empty() : java.util.Optional.of(appId))
.queryParamIfPresent("appKey", appKey == null || appKey.isBlank() ? java.util.Optional.empty() : java.util.Optional.of(appKey))
.build()
.toUriString();
return dataMap(exchange(uri));
}
public Map<String, Object> deviceLogs(String appId, String userId, int page, int size) {
public Map<String, Object> deviceLogs(String appKey, String userId, int page, int size) {
String uri = UriComponentsBuilder.fromHttpUrl(pushServiceBaseUrl + "/api/push/internal/device-logs")
.queryParam("appId", appId)
.queryParam("appKey", appKey)
.queryParam("userId", userId)
.queryParam("page", page)
.queryParam("size", size)
@ -50,13 +50,13 @@ public class OpsPushDiagnosticsClient {
return dataMap(exchange(uri));
}
public Map<String, Object> sendTestOffline(String appId, String userId, String title, String body, String payload) {
public Map<String, Object> sendTestOffline(String appKey, String userId, String title, String body, String payload) {
String uri = pushServiceBaseUrl + "/api/push/internal/test-offline";
HttpHeaders headers = new HttpHeaders();
headers.set("X-Internal-Token", internalToken);
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> request = new java.util.LinkedHashMap<>();
request.put("appId", appId);
request.put("appKey", appKey);
request.put("userId", userId);
request.put("title", title);
request.put("body", body);

查看文件

@ -163,10 +163,10 @@ public class OpsService {
return appRepository.findByNameContainingIgnoreCaseOrAppKeyContainingIgnoreCase(keyword, keyword, pageable);
}
public Map<String, Object> getAppDetail(String appId) {
AppEntity app = appRepository.findById(appId)
public Map<String, Object> getAppDetail(String appKey) {
AppEntity app = appRepository.findById(appKey)
.orElseThrow(() -> new IllegalArgumentException("应用不存在"));
List<FeatureServiceEntity> services = featureServiceRepository.findByAppId(appId);
List<FeatureServiceEntity> services = featureServiceRepository.findByAppId(appKey);
long enabledCount = services.stream().filter(FeatureServiceEntity::isEnabled).count();
Map<String, Object> result = new LinkedHashMap<>();
result.put("app", app);
@ -177,10 +177,10 @@ public class OpsService {
return result;
}
public List<FeatureServiceEntity> listAppServices(String appId) {
appRepository.findById(appId)
public List<FeatureServiceEntity> listAppServices(String appKey) {
appRepository.findById(appKey)
.orElseThrow(() -> new IllegalArgumentException("应用不存在"));
return featureServiceRepository.findByAppId(appId);
return featureServiceRepository.findByAppId(appKey);
}
public Page<OperationLogEntity> listOperationLogs(int page, int size) {

查看文件

@ -56,14 +56,14 @@ public class SdkAppProvisioningService {
}
@Transactional
public AppEntity resolveApp(String appId) {
return appRepository.findByAppKey(appId)
.or(() -> appRepository.findById(appId))
public AppEntity resolveApp(String appKey) {
return appRepository.findByAppKey(appKey)
.or(() -> appRepository.findById(appKey))
.orElseGet(() -> {
if (!bootstrapAppKey.equals(appId)) {
throw new BusinessException(404, "App not found: " + appId);
if (!bootstrapAppKey.equals(appKey)) {
throw new BusinessException(404, "App not found: " + appKey);
}
return ensureApp(appId, true);
return ensureApp(appKey, true);
});
}

查看文件

@ -1,5 +1,5 @@
server:
port: 8081
port: 9001
spring:
application:

查看文件

@ -41,8 +41,8 @@ public class AppStoreController {
// Store credential config
@GetMapping("/configs")
public ResponseEntity<ApiResponse<List<AppStoreConfigEntity>>> getConfigs(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(storeService.getConfigs(appId)));
public ResponseEntity<ApiResponse<List<AppStoreConfigEntity>>> getConfigs(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(storeService.getConfigs(appKey)));
}
/**
@ -52,7 +52,7 @@ public class AppStoreController {
*/
@PutMapping("/configs/{storeType}")
public ResponseEntity<ApiResponse<AppStoreConfigEntity>> saveConfig(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable AppStoreConfigEntity.StoreType storeType,
@RequestBody Map<String, Object> body) {
@ -60,24 +60,24 @@ public class AppStoreController {
boolean enabled = !Boolean.FALSE.equals(body.get("enabled"));
validateStoreConfig(storeType, configJson);
return ResponseEntity.ok(ApiResponse.success(
storeService.saveConfig(appId, storeType, configJson, enabled)));
storeService.saveConfig(appKey, storeType, configJson, enabled)));
}
@DeleteMapping("/configs/{storeType}")
public ResponseEntity<ApiResponse<Void>> deleteConfig(
@RequestParam String appId,
@RequestParam String appKey,
@PathVariable AppStoreConfigEntity.StoreType storeType) {
storeService.deleteConfig(appId, storeType);
storeService.deleteConfig(appKey, storeType);
return ResponseEntity.ok(ApiResponse.success(null));
}
/**
* Returns enabled store credentials for the given appId.
* Returns enabled store credentials for the given appKey.
* Called by the release script so it can submit to stores without storing secrets locally.
*/
@GetMapping("/credentials")
public ResponseEntity<ApiResponse<Map<String, Object>>> getCredentials(@RequestParam String appId) throws Exception {
return ResponseEntity.ok(ApiResponse.success(storeService.getStoreCredentials(appId)));
public ResponseEntity<ApiResponse<Map<String, Object>>> getCredentials(@RequestParam String appKey) throws Exception {
return ResponseEntity.ok(ApiResponse.success(storeService.getStoreCredentials(appKey)));
}
// Version store submission

查看文件

@ -47,17 +47,17 @@ public class AppVersionController {
@GetMapping("/app/check")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkUpdate(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam AppVersionEntity.Platform platform,
@RequestParam int currentVersionCode,
@RequestParam(required = false) String userId) {
Optional<AppVersionEntity> latest = versionRepository
.findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanOrderByVersionCodeDesc(
appId, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
Optional<AppVersionEntity> forcedHigher = versionRepository
.findTopByAppIdAndPlatformAndPublishStatusAndVersionCodeGreaterThanAndForceUpdateTrueOrderByVersionCodeDesc(
appId, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
appKey, platform, AppVersionEntity.PublishStatus.PUBLISHED, currentVersionCode);
if (latest.isEmpty()) {
return ResponseEntity.ok(ApiResponse.success(Map.of("needsUpdate", false)));
@ -82,10 +82,10 @@ public class AppVersionController {
String appStoreJumpUrl = hasText(v.getAppStoreUrl())
? v.getAppStoreUrl()
: appStoreService.getStoreJumpUrl(appId, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.APP_STORE);
: appStoreService.getStoreJumpUrl(appKey, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.APP_STORE);
String harmonyJumpUrl = hasText(v.getMarketUrl())
? v.getMarketUrl()
: appStoreService.getStoreJumpUrl(appId, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.HARMONY_APP);
: appStoreService.getStoreJumpUrl(appKey, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.HARMONY_APP);
return ResponseEntity.ok(ApiResponse.success(Map.of(
"needsUpdate", true,
"versionName", v.getVersionName(),
@ -100,7 +100,7 @@ public class AppVersionController {
@PostMapping("/app/upload")
public ResponseEntity<ApiResponse<AppVersionEntity>> upload(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam AppVersionEntity.Platform platform,
@RequestParam(required = false) String versionName,
@RequestParam(required = false) Integer versionCode,
@ -124,8 +124,8 @@ public class AppVersionController {
? updateAssetService.inspectAppPackage(apkUrl)
: (apkFile != null && !apkFile.isEmpty() ? updateAssetService.inspectAppPackage(apkFile) : null);
} catch (Exception ex) {
log.warn("Unable to inspect upload package for appId={}, platform={}, source={}, fallback to manual version fields: {}",
appId, platform, hasText(apkUrl) ? apkUrl : (apkFile != null ? apkFile.getOriginalFilename() : null), ex.getMessage());
log.warn("Unable to inspect upload package for appKey={}, platform={}, source={}, fallback to manual version fields: {}",
appKey, platform, hasText(apkUrl) ? apkUrl : (apkFile != null ? apkFile.getOriginalFilename() : null), ex.getMessage());
}
String resolvedVersionName = hasText(versionName) ? versionName : (inspected != null ? inspected.versionName() : null);
Integer resolvedVersionCode = versionCode != null ? versionCode : (inspected != null ? inspected.versionCode() : null);
@ -141,7 +141,7 @@ public class AppVersionController {
}
AppVersionEntity entity = new AppVersionEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setPlatform(platform);
entity.setVersionName(resolvedVersionName);
entity.setVersionCode(resolvedVersionCode);
@ -170,12 +170,12 @@ public class AppVersionController {
entity.setAppStoreUrl(hasText(appStoreUrl)
? appStoreUrl
: (platform == AppVersionEntity.Platform.IOS
? appStoreService.getStoreJumpUrl(appId, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.APP_STORE)
? appStoreService.getStoreJumpUrl(appKey, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.APP_STORE)
: null));
entity.setMarketUrl(hasText(marketUrl)
? marketUrl
: (platform == AppVersionEntity.Platform.HARMONY
? appStoreService.getStoreJumpUrl(appId, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.HARMONY_APP)
? appStoreService.getStoreJumpUrl(appKey, com.xuqm.update.entity.AppStoreConfigEntity.StoreType.HARMONY_APP)
: null));
if (publishImmediately) {
entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED);
@ -326,9 +326,9 @@ public class AppVersionController {
@GetMapping("/app/list")
public ResponseEntity<ApiResponse<List<AppVersionEntity>>> list(
@RequestParam String appId, @RequestParam AppVersionEntity.Platform platform) {
@RequestParam String appKey, @RequestParam AppVersionEntity.Platform platform) {
return ResponseEntity.ok(ApiResponse.success(
versionRepository.findByAppIdAndPlatformOrderByVersionCodeDesc(appId, platform)));
versionRepository.findByAppIdAndPlatformOrderByVersionCodeDesc(appKey, platform)));
}
private String publishAction(AppVersionEntity.PublishStatus previousStatus,

查看文件

@ -20,38 +20,38 @@ public class PublishConfigController {
}
@GetMapping("/publish/config")
public ResponseEntity<ApiResponse<AppPublishConfigEntity>> getConfig(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(publishConfigService.getConfig(appId)));
public ResponseEntity<ApiResponse<AppPublishConfigEntity>> getConfig(@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(publishConfigService.getConfig(appKey)));
}
@PutMapping("/publish/config")
public ResponseEntity<ApiResponse<AppPublishConfigEntity>> saveConfig(
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody Map<String, Object> body) {
validateCallbacks(body);
return ResponseEntity.ok(ApiResponse.success(publishConfigService.saveConfig(appId, body)));
return ResponseEntity.ok(ApiResponse.success(publishConfigService.saveConfig(appKey, body)));
}
@GetMapping("/gray/members")
public ResponseEntity<ApiResponse<List<PublishConfigService.GrayMemberGroupView>>> listMembers(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String groupName) {
return ResponseEntity.ok(ApiResponse.success(
publishConfigService.listGrayMembers(appId, keyword, groupName)));
publishConfigService.listGrayMembers(appKey, keyword, groupName)));
}
@PostMapping("/gray/members/sync")
public ResponseEntity<ApiResponse<List<PublishConfigService.GrayMemberGroupView>>> syncMembers(
@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(publishConfigService.syncGrayMembers(appId)));
@RequestParam String appKey) {
return ResponseEntity.ok(ApiResponse.success(publishConfigService.syncGrayMembers(appKey)));
}
@PostMapping("/gray/members/import")
public ResponseEntity<ApiResponse<List<PublishConfigService.GrayMemberGroupView>>> importMembers(
@RequestParam String appId,
@RequestParam String appKey,
@RequestBody String payload) {
return ResponseEntity.ok(ApiResponse.success(publishConfigService.replaceGrayMembers(appId, payload)));
return ResponseEntity.ok(ApiResponse.success(publishConfigService.replaceGrayMembers(appKey, payload)));
}
private void validateCallbacks(Map<String, Object> body) {

查看文件

@ -42,7 +42,7 @@ public class RnBundleController {
@GetMapping("/update/check")
public ResponseEntity<ApiResponse<Map<String, Object>>> checkUpdate(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String moduleId,
@RequestParam String platform,
@RequestParam String currentVersion,
@ -51,7 +51,7 @@ public class RnBundleController {
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
Optional<RnBundleEntity> latest = bundleRepository
.findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
appId, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED);
appKey, moduleId, p, RnBundleEntity.PublishStatus.PUBLISHED);
if (latest.isEmpty()) {
return ResponseEntity.ok(ApiResponse.success(Map.of("needsUpdate", false)));
@ -63,7 +63,7 @@ public class RnBundleController {
"needsUpdate", needsUpdate,
"bundleVersion", parseBundleVersion(b.getVersion()),
"latestVersion", b.getVersion(),
"downloadUrl", resolvePublicBaseUrl() + "/api/v1/rn/files/" + appId + "/" + platform.toLowerCase() + "/" + moduleId,
"downloadUrl", resolvePublicBaseUrl() + "/api/v1/rn/files/" + appKey + "/" + platform.toLowerCase() + "/" + moduleId,
"md5", b.getMd5(),
"minCommonVersion", b.getMinCommonVersion() != null ? b.getMinCommonVersion() : "0.0.0",
"note", b.getNote() != null ? b.getNote() : "",
@ -75,7 +75,7 @@ public class RnBundleController {
@PostMapping("/upload")
public ResponseEntity<ApiResponse<RnBundleEntity>> upload(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) String moduleId,
@RequestParam(required = false) RnBundleEntity.Platform platform,
@RequestParam(required = false) String version,
@ -93,11 +93,11 @@ public class RnBundleController {
throw new IllegalArgumentException("moduleId, version and platform are required or must be readable from the bundle name");
}
UpdateAssetService.StoredRnBundle stored = updateAssetService.storeRnBundle(
appId, resolvedPlatform.name(), resolvedModuleId, bundle);
appKey, resolvedPlatform.name(), resolvedModuleId, bundle);
RnBundleEntity entity = new RnBundleEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setModuleId(resolvedModuleId);
entity.setPlatform(resolvedPlatform);
entity.setVersion(resolvedVersion);
@ -136,17 +136,17 @@ public class RnBundleController {
@GetMapping("/list")
public ResponseEntity<ApiResponse<List<RnBundleEntity>>> list(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(required = false) String moduleId,
@RequestParam(required = false) String platform) {
List<RnBundleEntity> result;
if (moduleId != null && platform != null) {
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appId, moduleId, p);
result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appKey, moduleId, p);
} else if (moduleId != null) {
result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appId, moduleId);
result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appKey, moduleId);
} else {
result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appId);
result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appKey);
}
return ResponseEntity.ok(ApiResponse.success(result));
}

查看文件

@ -50,7 +50,7 @@ public class UnifiedReleaseController {
@PostMapping("/unified/upload")
public ResponseEntity<ApiResponse<UnifiedReleaseResult>> upload(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam String manifest,
HttpServletRequest request) throws Exception {
@ -69,7 +69,7 @@ public class UnifiedReleaseController {
}
AppVersionEntity entity = new AppVersionEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setPlatform(item.platform());
entity.setVersionName(item.versionName());
entity.setVersionCode(item.versionCode());
@ -110,14 +110,14 @@ public class UnifiedReleaseController {
for (UnifiedReleaseManifest.RnBundleUploadItem item : safeList(unifiedReleaseManifest.rnBundles())) {
MultipartFile file = multipartRequest.getFile(item.fileKey());
UpdateAssetService.StoredRnBundle stored = updateAssetService.storeRnBundle(
appId,
appKey,
item.platform().name(),
item.moduleId(),
file);
RnBundleEntity entity = new RnBundleEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setModuleId(item.moduleId());
entity.setPlatform(item.platform());
entity.setVersion(item.version());

查看文件

@ -29,12 +29,12 @@ public class UpdateFileController {
return serveFile(file, MediaType.parseMediaType("application/vnd.android.package-archive"));
}
@GetMapping("/api/v1/rn/files/{appId}/{platform}/{moduleId}")
@GetMapping("/api/v1/rn/files/{appKey}/{platform}/{moduleId}")
public ResponseEntity<Resource> downloadRnBundle(
@PathVariable String appId,
@PathVariable String appKey,
@PathVariable String platform,
@PathVariable String moduleId) throws Exception {
Path file = Paths.get(uploadDir, "rn", appId, platform, moduleId,
Path file = Paths.get(uploadDir, "rn", appKey, platform, moduleId,
moduleId + "." + platform + ".bundle").normalize();
return serveFile(file, MediaType.APPLICATION_OCTET_STREAM);
}

查看文件

@ -23,8 +23,8 @@ public class UpdateOperationLogController {
@GetMapping("/logs")
public ResponseEntity<ApiResponse<List<UpdateOperationLogEntity>>> list(
@RequestParam String appId,
@RequestParam String appKey,
@RequestParam(defaultValue = "100") int limit) {
return ResponseEntity.ok(ApiResponse.success(operationLogService.list(appId, limit)));
return ResponseEntity.ok(ApiResponse.success(operationLogService.list(appKey, limit)));
}
}

查看文件

@ -45,22 +45,22 @@ public class AppStoreService {
// Store config CRUD
public List<AppStoreConfigEntity> getConfigs(String appId) {
return configRepo.findByAppId(appId);
public List<AppStoreConfigEntity> getConfigs(String appKey) {
return configRepo.findByAppId(appKey);
}
public AppStoreConfigEntity saveConfig(String appId,
public AppStoreConfigEntity saveConfig(String appKey,
AppStoreConfigEntity.StoreType storeType,
String configJson,
boolean enabled) {
boolean isCreate = configRepo.findByAppIdAndStoreType(appId, storeType).isEmpty();
boolean isCreate = configRepo.findByAppIdAndStoreType(appKey, storeType).isEmpty();
AppStoreConfigEntity entity = configRepo
.findByAppIdAndStoreType(appId, storeType)
.findByAppIdAndStoreType(appKey, storeType)
.orElseGet(AppStoreConfigEntity::new);
if (entity.getId() == null) {
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setStoreType(storeType);
}
entity.setConfigJson(configJson);
@ -68,7 +68,7 @@ public class AppStoreService {
entity.setUpdatedAt(LocalDateTime.now());
AppStoreConfigEntity saved = configRepo.save(entity);
operationLogService.record(
appId,
appKey,
"STORE_CONFIG",
saved.getId(),
isCreate ? "CREATE_STORE_CONFIG" : "UPDATE_STORE_CONFIG",
@ -80,11 +80,11 @@ public class AppStoreService {
return saved;
}
public void deleteConfig(String appId, AppStoreConfigEntity.StoreType storeType) {
configRepo.findByAppIdAndStoreType(appId, storeType).ifPresent(cfg -> {
public void deleteConfig(String appKey, AppStoreConfigEntity.StoreType storeType) {
configRepo.findByAppIdAndStoreType(appKey, storeType).ifPresent(cfg -> {
configRepo.delete(cfg);
operationLogService.record(
appId,
appKey,
"STORE_CONFIG",
cfg.getId(),
"DELETE_STORE_CONFIG",
@ -151,8 +151,8 @@ public class AppStoreService {
* Fetch enabled store credentials for use by the release script.
* Returns a map of storeType -> configJson (as parsed map, not raw string).
*/
public Map<String, Object> getStoreCredentials(String appId) throws Exception {
List<AppStoreConfigEntity> configs = configRepo.findByAppIdAndEnabled(appId, true);
public Map<String, Object> getStoreCredentials(String appKey) throws Exception {
List<AppStoreConfigEntity> configs = configRepo.findByAppIdAndEnabled(appKey, true);
Map<String, Object> result = new LinkedHashMap<>();
for (AppStoreConfigEntity cfg : configs) {
if (cfg.getStoreType() == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) {
@ -166,20 +166,20 @@ public class AppStoreService {
return result;
}
public Map<String, String> getReviewWebhookConfig(String appId) throws Exception {
public Map<String, String> getReviewWebhookConfig(String appKey) throws Exception {
AppStoreConfigEntity cfg = configRepo.findByAppIdAndStoreType(
appId, AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK).orElse(null);
appKey, AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK).orElse(null);
if (cfg == null || !cfg.isEnabled() || cfg.getConfigJson() == null || cfg.getConfigJson().isBlank()) {
return Map.of();
}
return mapper.readValue(cfg.getConfigJson(), new TypeReference<>() {});
}
public String getStoreJumpUrl(String appId, AppStoreConfigEntity.StoreType storeType) {
public String getStoreJumpUrl(String appKey, AppStoreConfigEntity.StoreType storeType) {
if (storeType == null || storeType == AppStoreConfigEntity.StoreType.REVIEW_WEBHOOK) {
return "";
}
return configRepo.findByAppIdAndStoreType(appId, storeType)
return configRepo.findByAppIdAndStoreType(appKey, storeType)
.filter(AppStoreConfigEntity::isEnabled)
.map(AppStoreConfigEntity::getConfigJson)
.map(this::extractJumpUrl)
@ -296,7 +296,7 @@ public class AppStoreService {
String body = mapper.writeValueAsString(Map.of(
"event", "store_review_update",
"versionId", v.getId(),
"appId", v.getAppId(),
"appKey", v.getAppId(),
"versionName", v.getVersionName(),
"storeType", storeType,
"reviewState", state.name(),
@ -332,9 +332,9 @@ public class AppStoreService {
AppVersionEntity.StoreReviewState.APPROVED.name().equals(readReviewState(reviewMap.get(t))));
}
private String resolveWebhookSecret(String appId) {
private String resolveWebhookSecret(String appKey) {
try {
return getReviewWebhookConfig(appId).getOrDefault("secret", "");
return getReviewWebhookConfig(appKey).getOrDefault("secret", "");
} catch (Exception e) {
return "";
}

查看文件

@ -44,22 +44,22 @@ public class PublishConfigService {
this.objectMapper = objectMapper;
}
public AppPublishConfigEntity getConfig(String appId) {
return configRepository.findByAppId(appId).orElseGet(() -> {
public AppPublishConfigEntity getConfig(String appKey) {
return configRepository.findByAppId(appKey).orElseGet(() -> {
AppPublishConfigEntity entity = new AppPublishConfigEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setConfigJson(defaultConfigJson());
entity.setUpdatedAt(LocalDateTime.now());
return entity;
});
}
public AppPublishConfigEntity saveConfig(String appId, Map<String, Object> body) {
AppPublishConfigEntity entity = configRepository.findByAppId(appId).orElseGet(AppPublishConfigEntity::new);
public AppPublishConfigEntity saveConfig(String appKey, Map<String, Object> body) {
AppPublishConfigEntity entity = configRepository.findByAppId(appKey).orElseGet(AppPublishConfigEntity::new);
if (entity.getId() == null) {
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
}
try {
entity.setConfigJson(objectMapper.writeValueAsString(body == null ? Map.of() : body));
@ -70,8 +70,8 @@ public class PublishConfigService {
return configRepository.save(entity);
}
public JsonNode getConfigNode(String appId) {
AppPublishConfigEntity entity = configRepository.findByAppId(appId).orElse(null);
public JsonNode getConfigNode(String appKey) {
AppPublishConfigEntity entity = configRepository.findByAppId(appKey).orElse(null);
if (entity == null || entity.getConfigJson() == null || entity.getConfigJson().isBlank()) {
try {
return objectMapper.readTree(defaultConfigJson());
@ -87,11 +87,11 @@ public class PublishConfigService {
}
}
public List<GrayMemberGroupView> listGrayMembers(String appId, String keyword, String groupName) {
public List<GrayMemberGroupView> listGrayMembers(String appKey, String keyword, String groupName) {
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase(Locale.ROOT);
String normalizedGroup = groupName == null ? "" : groupName.trim().toLowerCase(Locale.ROOT);
List<AppGrayMemberEntity> members = grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appId)
List<AppGrayMemberEntity> members = grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appKey)
.stream()
.filter(item -> normalizedGroup.isBlank()
|| safe(item.getGroupName()).toLowerCase(Locale.ROOT).contains(normalizedGroup))
@ -116,15 +116,15 @@ public class PublishConfigService {
.toList();
}
public List<GrayMemberGroupView> syncGrayMembers(String appId) {
JsonNode config = getConfigNode(appId);
public List<GrayMemberGroupView> syncGrayMembers(String appKey) {
JsonNode config = getConfigNode(appKey);
String url = config.path("grayDirectorySyncCallbackUrl").asText("");
if (url == null || url.isBlank()) {
throw new IllegalStateException("grayDirectorySyncCallbackUrl is not configured");
}
Map<String, Object> requestBody = Map.of(
"appId", appId,
"appKey", appKey,
"timestamp", System.currentTimeMillis(),
"action", "sync"
);
@ -137,11 +137,11 @@ public class PublishConfigService {
ResponseEntity<String> response = restTemplate.exchange(
URI.create(url), org.springframework.http.HttpMethod.POST,
new org.springframework.http.HttpEntity<>(requestBody, headers), String.class);
return replaceGrayMembers(appId, response.getBody());
return replaceGrayMembers(appKey, response.getBody());
}
public List<String> resolveGrayMembers(String appId, Map<String, Object> requestBody) {
JsonNode config = getConfigNode(appId);
public List<String> resolveGrayMembers(String appKey, Map<String, Object> requestBody) {
JsonNode config = getConfigNode(appKey);
String url = config.path("graySelectCallbackUrl").asText("");
if (url == null || url.isBlank()) {
throw new IllegalStateException("graySelectCallbackUrl is not configured");
@ -150,7 +150,7 @@ public class PublishConfigService {
if (requestBody != null) {
payload.putAll(requestBody);
}
payload.put("appId", appId);
payload.put("appKey", appKey);
payload.put("timestamp", System.currentTimeMillis());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
@ -164,13 +164,13 @@ public class PublishConfigService {
return extractMemberIds(response.getBody());
}
public List<GrayMemberGroupView> replaceGrayMembers(String appId, String responseBody) {
public List<GrayMemberGroupView> replaceGrayMembers(String appKey, String responseBody) {
List<GrayMemberGroupPayload> groups = parseGroups(responseBody);
if (groups == null) {
throw new IllegalStateException("Invalid gray member payload");
}
grayMemberRepository.deleteAll(
grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(appId));
grayMemberRepository.findByAppIdOrderByGroupNameAscNameAscUserIdAsc(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(appId);
entity.setAppId(appKey);
entity.setGroupName(groupName);
entity.setUserId(member.userId().trim());
entity.setName(member.name() == null ? "" : member.name().trim());
@ -191,7 +191,7 @@ public class PublishConfigService {
}
}
grayMemberRepository.saveAll(saved);
return listGrayMembers(appId, null, null);
return listGrayMembers(appKey, null, null);
}
private List<String> extractMemberIds(String responseBody) {

查看文件

@ -136,12 +136,12 @@ public class UpdateAssetService {
return inspectRnBundleName(fileName);
}
public StoredRnBundle storeRnBundle(String appId, String platform, String moduleId, MultipartFile bundle) throws Exception {
public StoredRnBundle storeRnBundle(String appKey, String platform, String moduleId, MultipartFile bundle) throws Exception {
if (bundle == null || bundle.isEmpty()) {
throw new IllegalArgumentException("bundle file is required");
}
String filename = resolveBundleFilename(moduleId, platform, bundle.getOriginalFilename());
Path dir = Paths.get(uploadDir, "rn", appId, platform.toLowerCase(), moduleId);
Path dir = Paths.get(uploadDir, "rn", appKey, platform.toLowerCase(), moduleId);
Files.createDirectories(dir);
Path dest = dir.resolve(filename);

查看文件

@ -25,7 +25,7 @@ public class UpdateOperationLogService {
this.objectMapper = objectMapper;
}
public void record(String appId,
public void record(String appKey,
String resourceType,
String resourceId,
String action,
@ -33,7 +33,7 @@ public class UpdateOperationLogService {
Map<String, Object> detail) {
UpdateOperationLogEntity entity = new UpdateOperationLogEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setAppId(appKey);
entity.setResourceType(resourceType);
entity.setResourceId(resourceId);
entity.setAction(action);
@ -44,10 +44,10 @@ public class UpdateOperationLogService {
repository.save(entity);
}
public List<UpdateOperationLogEntity> list(String appId, int limit) {
public List<UpdateOperationLogEntity> list(String appKey, int limit) {
int size = Math.min(Math.max(limit, 1), 200);
return repository.findByAppIdOrderByCreatedAtDesc(
appId,
appKey,
PageRequest.of(0, size));
}