chore: sync local changes
这个提交包含在:
父节点
9cb352bb99
当前提交
77dafd76bf
@ -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)
|
||||
+ "¬ify_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("¬ify_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));
|
||||
}
|
||||
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户