From 83cf9541e7e4f4f7c7d264a51e2c34a66ba1454a Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Fri, 1 May 2026 22:18:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E5=8C=96=20IM=20=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=8E=A5=E5=8F=A3=EF=BC=8CJWT=20=E6=B0=B8=E4=B9=85?= =?UTF-8?q?=E6=9C=89=E6=95=88=EF=BC=8C=E7=A6=BB=E7=BA=BF=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/im/auth/login 移除 nickname/avatar 参数 - AppRequestSignatureUtil.payload 简化签名(不含 nickname/avatar) - JwtUtil 默认过期时间改为极大值(≈100年) - ImAccountService.validateSignature/loginOrRegister 适配 - DemoAuthService.callImServiceLogin 不传 nickname - XuqmImServerSdk.login() 签名方法同步简化 - ImPushBridge 改为批量同步调用 /api/push/internal/notify - PushDispatcher 移除 @Async --- .../security/AppRequestSignatureUtil.java | 4 -- .../com/xuqm/common/security/JwtUtil.java | 12 +++-- .../xuqm/demo/service/DemoAuthService.java | 14 +++--- .../java/com/xuqm/im/sdk/XuqmImServerSdk.java | 14 ++---- .../xuqm/im/controller/AuthController.java | 6 +-- .../com/xuqm/im/service/ImAccountService.java | 12 ++--- .../com/xuqm/im/service/ImPushBridge.java | 50 +++++++++++-------- .../com/xuqm/push/service/PushDispatcher.java | 3 -- 8 files changed, 52 insertions(+), 63 deletions(-) diff --git a/common/src/main/java/com/xuqm/common/security/AppRequestSignatureUtil.java b/common/src/main/java/com/xuqm/common/security/AppRequestSignatureUtil.java index bd7f2cd..b620b49 100644 --- a/common/src/main/java/com/xuqm/common/security/AppRequestSignatureUtil.java +++ b/common/src/main/java/com/xuqm/common/security/AppRequestSignatureUtil.java @@ -15,14 +15,10 @@ public final class AppRequestSignatureUtil { public static String payload(String appId, String userId, - String nickname, - String avatar, long timestamp, String nonce) { return normalize(appId) + '\n' + normalize(userId) + '\n' - + normalize(nickname) + '\n' - + normalize(avatar) + '\n' + timestamp + '\n' + normalize(nonce); } diff --git a/common/src/main/java/com/xuqm/common/security/JwtUtil.java b/common/src/main/java/com/xuqm/common/security/JwtUtil.java index 1aa4983..a609c03 100644 --- a/common/src/main/java/com/xuqm/common/security/JwtUtil.java +++ b/common/src/main/java/com/xuqm/common/security/JwtUtil.java @@ -17,7 +17,7 @@ public class JwtUtil { @Value("${jwt.secret}") private String secret; - @Value("${jwt.expiration:86400000}") + @Value("${jwt.expiration:3153600000000}") private long expiration; public long getExpirationMillis() { @@ -30,13 +30,15 @@ public class JwtUtil { } public String generate(String subject, Map claims) { - return Jwts.builder() + var builder = Jwts.builder() .subject(subject) .claims(claims) .issuedAt(new Date()) - .expiration(new Date(System.currentTimeMillis() + expiration)) - .signWith(getSigningKey()) - .compact(); + .signWith(getSigningKey()); + if (expiration > 0) { + builder.expiration(new Date(System.currentTimeMillis() + expiration)); + } + return builder.compact(); } public String generate(String subject) { diff --git a/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java b/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java index f4516be..1068c46 100644 --- a/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java +++ b/demo-service/src/main/java/com/xuqm/demo/service/DemoAuthService.java @@ -73,7 +73,7 @@ public class DemoAuthService { userRepository.save(user); String demoToken = generateDemoToken(appId, userId); - ImCredential imCredential = callImServiceLogin(appId, userId, user.getNickname()); + ImCredential imCredential = callImServiceLogin(appId, userId); return new AuthResult( demoToken, @@ -94,7 +94,7 @@ public class DemoAuthService { } String demoToken = generateDemoToken(appId, userId); - ImCredential imCredential = callImServiceLogin(appId, userId, user.getNickname()); + ImCredential imCredential = callImServiceLogin(appId, userId); return new AuthResult( demoToken, @@ -124,27 +124,25 @@ public class DemoAuthService { public ImCredential refreshImToken(String appId, String userId) { DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId) .orElseThrow(() -> new BusinessException(404, "User not found: " + userId)); - return callImServiceLogin(appId, userId, user.getNickname()); + return callImServiceLogin(appId, userId); } /** * Calls im-service to ensure the IM account exists and obtain an IM token. - * POST {imServiceUrl}/api/im/auth/login?appId={appId}&userId={userId}&nickname={nickname} + * POST {imServiceUrl}/api/im/auth/login?appId={appId}&userId={userId} * Response: {"code":200,"data":{"token":"..."}} */ - private ImCredential callImServiceLogin(String appId, String userId, String nickname) { + private ImCredential callImServiceLogin(String appId, String userId) { long timestamp = System.currentTimeMillis(); String nonce = UUID.randomUUID().toString(); - String effectiveNickname = nickname != null ? nickname : userId; String appSecret = appSecretClient.getAppSecret(appId); - String payload = AppRequestSignatureUtil.payload(appId, userId, effectiveNickname, null, timestamp, nonce); + String payload = AppRequestSignatureUtil.payload(appId, userId, timestamp, nonce); String signature = AppRequestSignatureUtil.sign(appSecret, payload); URI uri = UriComponentsBuilder.fromHttpUrl(imServiceUrl) .path("/api/im/auth/login") .queryParam("appId", appId) .queryParam("userId", userId) - .queryParam("nickname", effectiveNickname) .encode() .build() .toUri(); diff --git a/im-sdk/src/main/java/com/xuqm/im/sdk/XuqmImServerSdk.java b/im-sdk/src/main/java/com/xuqm/im/sdk/XuqmImServerSdk.java index 289900f..bef01f0 100644 --- a/im-sdk/src/main/java/com/xuqm/im/sdk/XuqmImServerSdk.java +++ b/im-sdk/src/main/java/com/xuqm/im/sdk/XuqmImServerSdk.java @@ -62,14 +62,14 @@ public final class XuqmImServerSdk { return new Builder(); } - public LoginResponse login(String userId, String nickname, String avatar) { + public LoginResponse login(String userId) { long timestamp = System.currentTimeMillis(); String nonce = UUID.randomUUID().toString().replace("-", ""); - String payload = AppRequestSignatureUtil.payload(appId, userId, nickname, avatar, timestamp, nonce); + String payload = AppRequestSignatureUtil.payload(appId, userId, timestamp, nonce); String signature = AppRequestSignatureUtil.sign(appSecret, payload); URI uri = buildUri( "/api/im/auth/login", - loginQuery(userId, nickname, avatar, timestamp, nonce) + loginQuery(userId, timestamp, nonce) ); ApiResponse response = request( "POST", @@ -1379,18 +1379,12 @@ public final class XuqmImServerSdk { return URI.create(builder.toString()); } - private Map loginQuery(String userId, String nickname, String avatar, long timestamp, String nonce) { + private Map loginQuery(String userId, long timestamp, String nonce) { Map query = new LinkedHashMap<>(); query.put("appId", appId); query.put("userId", userId); query.put("timestamp", String.valueOf(timestamp)); query.put("nonce", nonce); - if (nickname != null && !nickname.isBlank()) { - query.put("nickname", nickname); - } - if (avatar != null && !avatar.isBlank()) { - query.put("avatar", avatar); - } return query; } diff --git a/im-service/src/main/java/com/xuqm/im/controller/AuthController.java b/im-service/src/main/java/com/xuqm/im/controller/AuthController.java index 9caad10..6cde15c 100644 --- a/im-service/src/main/java/com/xuqm/im/controller/AuthController.java +++ b/im-service/src/main/java/com/xuqm/im/controller/AuthController.java @@ -26,16 +26,14 @@ public class AuthController { public ResponseEntity>> login( @RequestParam @NotBlank String appId, @RequestParam @NotBlank String userId, - @RequestParam(required = false) String nickname, - @RequestParam(required = false) String avatar, @RequestHeader(value = "X-App-Timestamp", required = false) String timestamp, @RequestHeader(value = "X-App-Nonce", required = false) String nonce, @RequestHeader(value = "X-App-Signature", required = false) String signature) { if (timestamp == null || nonce == null || signature == null) { return ResponseEntity.status(401).body(ApiResponse.error(401, "Missing app signature")); } - accountService.validateSignature(appId, userId, nickname, avatar, timestamp, nonce, signature); - ImAccountService.LoginResult result = accountService.loginOrRegister(appId, userId, nickname, avatar); + accountService.validateSignature(appId, userId, timestamp, nonce, signature); + ImAccountService.LoginResult result = accountService.loginOrRegister(appId, userId); return ResponseEntity.ok(ApiResponse.success(Map.of( "token", result.token(), "expiresAt", result.expiresAt() diff --git a/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java b/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java index f61e310..2288733 100644 --- a/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java +++ b/im-service/src/main/java/com/xuqm/im/service/ImAccountService.java @@ -31,8 +31,6 @@ public class ImAccountService { public void validateSignature(String appId, String userId, - String nickname, - String avatar, String timestamp, String nonce, String signature) { @@ -47,21 +45,19 @@ public class ImAccountService { throw new BusinessException(401, "App signature expired"); } String secret = appSecretClient.getAppSecret(appId); - String payload = AppRequestSignatureUtil.payload(appId, userId, nickname, avatar, ts, nonce); + String payload = AppRequestSignatureUtil.payload(appId, userId, ts, nonce); if (!AppRequestSignatureUtil.matches(secret, payload, signature)) { throw new BusinessException(401, "Invalid app signature"); } } - public LoginResult loginOrRegister(String appId, String userId, String nickname, String avatar) { + public LoginResult loginOrRegister(String appId, String userId) { ImAccountEntity account = accountRepository.findByAppIdAndUserId(appId, userId) .orElseGet(() -> { ImAccountEntity e = new ImAccountEntity(); e.setId(UUID.randomUUID().toString()); e.setAppId(appId); e.setUserId(userId); - e.setNickname(nickname); - e.setAvatar(avatar); e.setGender(ImAccountEntity.Gender.UNKNOWN); e.setStatus(ImAccountEntity.Status.ACTIVE); e.setCreatedAt(LocalDateTime.now()); @@ -72,7 +68,9 @@ public class ImAccountService { throw new BusinessException(403, "账号已被封禁"); } - long expiresAt = Instant.now().toEpochMilli() + jwtUtil.getExpirationMillis(); + long expiresAt = jwtUtil.getExpirationMillis() > 0 + ? Instant.now().toEpochMilli() + jwtUtil.getExpirationMillis() + : Long.MAX_VALUE; return new LoginResult(jwtUtil.generate(userId, Map.of("appId", appId, "role", "USER")), expiresAt); } diff --git a/im-service/src/main/java/com/xuqm/im/service/ImPushBridge.java b/im-service/src/main/java/com/xuqm/im/service/ImPushBridge.java index f8d7400..c9b1593 100644 --- a/im-service/src/main/java/com/xuqm/im/service/ImPushBridge.java +++ b/im-service/src/main/java/com/xuqm/im/service/ImPushBridge.java @@ -1,5 +1,6 @@ package com.xuqm.im.service; +import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -8,11 +9,10 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.messaging.simp.user.SimpUserRegistry; import org.springframework.stereotype.Component; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import java.util.List; +import java.util.Map; @Component public class ImPushBridge { @@ -21,6 +21,7 @@ public class ImPushBridge { private final SimpUserRegistry userRegistry; private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; @Value("${im.push-service-url:http://127.0.0.1:8083}") private String pushServiceUrl; @@ -28,40 +29,45 @@ public class ImPushBridge { @Value("${im.internal-token:xuqm-internal-token}") private String internalToken; - public ImPushBridge(SimpUserRegistry userRegistry) { + public ImPushBridge(SimpUserRegistry userRegistry, ObjectMapper objectMapper) { this.userRegistry = userRegistry; this.restTemplate = new RestTemplate(); + this.objectMapper = objectMapper; } public void sendOfflinePush(String appId, String userId, String title, String body, String payload) { if (isOnline(userId)) { return; } - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.set("X-Internal-Token", internalToken); - MultiValueMap map = new LinkedMultiValueMap<>(); - map.add("appId", appId); - map.add("userId", userId); - map.add("title", title); - map.add("body", body); - if (payload != null) { - map.add("payload", payload); - } - HttpEntity> request = new HttpEntity<>(map, headers); - restTemplate.postForEntity(pushServiceUrl + "/api/push/send", request, String.class); - } catch (Exception e) { - log.warn("Failed to send offline push appId={} userId={}: {}", appId, userId, e.getMessage()); - } + sendOfflinePushToUsers(appId, List.of(userId), title, body, payload); } public void sendOfflinePushToUsers(String appId, List userIds, String title, String body, String payload) { if (userIds == null || userIds.isEmpty()) { return; } - for (String userId : userIds) { - sendOfflinePush(appId, userId, title, body, payload); + List offlineUserIds = userIds.stream() + .filter(userId -> !isOnline(userId)) + .toList(); + if (offlineUserIds.isEmpty()) { + return; + } + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-Internal-Token", internalToken); + Map bodyMap = Map.of( + "appId", appId, + "userIds", offlineUserIds, + "title", title, + "body", body, + "payload", payload + ); + HttpEntity 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); + } catch (Exception e) { + log.warn("Failed to send offline push appId={} users={}: {}", appId, offlineUserIds.size(), e.getMessage()); } } diff --git a/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java b/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java index 8ee2dd5..cdd8d3c 100644 --- a/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java +++ b/push-service/src/main/java/com/xuqm/push/service/PushDispatcher.java @@ -5,7 +5,6 @@ import com.xuqm.push.repository.DeviceTokenRepository; import com.xuqm.push.service.provider.PushProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -29,7 +28,6 @@ public class PushDispatcher { .collect(Collectors.toMap(PushProvider::vendorName, p -> p)); } - @Async public void pushToUser(String appId, String userId, String title, String body, String payload) { List tokens = tokenRepository.findByAppIdAndUserIdAndReceivePushTrue(appId, userId); for (DeviceTokenEntity t : tokens) { @@ -41,7 +39,6 @@ public class PushDispatcher { } } - @Async public void pushToUsers(String appId, List userIds, String title, String body, String payload) { if (userIds == null || userIds.isEmpty()) { return;