fix(push): implement OPPO server API protocol

这个提交包含在:
XuqmGroup 2026-07-13 16:37:12 +08:00
父节点 e655ab9fd4
当前提交 d18d3f8c57

查看文件

@ -8,9 +8,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -54,29 +57,39 @@ public class OppoPushProvider implements PushProvider {
try { try {
String authToken = getAccessToken(vendorAppKey, masterSecret); String authToken = getAccessToken(vendorAppKey, masterSecret);
String messageId = appKey + "_" + System.currentTimeMillis(); String messageId = appKey + "_" + System.currentTimeMillis();
Map<String, Object> inner = new java.util.LinkedHashMap<>(); Map<String, Object> notification = new java.util.LinkedHashMap<>();
inner.put("app_message_id", messageId); notification.put("app_message_id", messageId);
inner.put("title", title); notification.put("title", title);
inner.put("content", body); notification.put("content", body);
inner.put("target_type", 2); notification.put("click_action_type", 0);
inner.put("target_value", token); notification.put("off_line", true);
notification.put("off_line_ttl", 259200);
if (payload != null && !payload.isBlank()) {
notification.put("action_parameters", payload);
}
String channelId = options != null && options.channelId() != null && !options.channelId().isBlank() String channelId = options != null && options.channelId() != null && !options.channelId().isBlank()
? options.channelId() ? options.channelId()
: resolveConfig(appKey, "channelId"); : resolveConfig(appKey, "channelId");
if (!channelId.isBlank()) inner.put("channel_id", channelId); if (!channelId.isBlank()) notification.put("channel_id", channelId);
Map<String, Object> message = Map.of("message", inner); Map<String, Object> message = new java.util.LinkedHashMap<>();
String requestBody = objectMapper.writeValueAsString(message); message.put("target_type", 2);
message.put("target_value", token);
message.put("notification", notification);
String requestBody = form(Map.of("message", objectMapper.writeValueAsString(message)));
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(PUSH_URL)) .uri(URI.create(PUSH_URL))
.header("Content-Type", "application/json") .header("Content-Type", "application/x-www-form-urlencoded")
.header("auth_token", authToken) .header("auth_token", authToken)
.POST(HttpRequest.BodyPublishers.ofString(requestBody)) .POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build(); .build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
JsonNode json = objectMapper.readTree(response.body()); JsonNode json = objectMapper.readTree(response.body());
return json.path("code").asInt(-1) == 0; int code = json.path("code").asInt(-1);
if (response.statusCode() == 200 && code == 0) {
return true;
} }
log.error("OPPO push rejected: httpStatus={} code={} message={}",
response.statusCode(), code, json.path("message").asText(""));
return false; return false;
} catch (Exception e) { } catch (Exception e) {
log.error("OPPO push failed: {}", e.getMessage()); log.error("OPPO push failed: {}", e.getMessage());
@ -89,20 +102,43 @@ public class OppoPushProvider implements PushProvider {
if (cached != null && cached.expiresAt > Instant.now().getEpochSecond()) { if (cached != null && cached.expiresAt > Instant.now().getEpochSecond()) {
return cached.token; return cached.token;
} }
Map<String, String> body = Map.of("app_key", appKey, "master_secret", masterSecret); long timestamp = System.currentTimeMillis();
String sign = sha256Hex(appKey + timestamp + masterSecret);
String body = form(Map.of(
"app_key", appKey,
"timestamp", Long.toString(timestamp),
"sign", sign
));
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(AUTH_URL)) .uri(URI.create(AUTH_URL))
.header("Content-Type", "application/json") .header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body))) .POST(HttpRequest.BodyPublishers.ofString(body))
.build(); .build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode json = objectMapper.readTree(response.body()); JsonNode json = objectMapper.readTree(response.body());
int code = json.path("code").asInt(-1);
String token = json.path("data").path("auth_token").asText(""); String token = json.path("data").path("auth_token").asText("");
long createTime = json.path("data").path("create_time").asLong(Instant.now().getEpochSecond()); if (response.statusCode() != 200 || code != 0 || token.isBlank()) {
tokenCache.put(appKey, new TokenCache(token, createTime + 86400)); throw new IllegalStateException("OPPO auth rejected: httpStatus=" + response.statusCode()
+ ", code=" + code + ", message=" + json.path("message").asText(""));
}
tokenCache.put(appKey, new TokenCache(token, Instant.now().getEpochSecond() + 23 * 3600));
return token; return token;
} }
private static String form(Map<String, String> values) {
return values.entrySet().stream()
.map(entry -> URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "="
+ URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
.collect(java.util.stream.Collectors.joining("&"));
}
private static String sha256Hex(String value) throws Exception {
byte[] digest = MessageDigest.getInstance("SHA-256")
.digest(value.getBytes(StandardCharsets.UTF_8));
return java.util.HexFormat.of().formatHex(digest);
}
private String resolveConfig(String appKey, String key) { private String resolveConfig(String appKey, String key) {
JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH") JsonNode config = configClient.loadServiceConfig(appKey, "ANDROID", "PUSH")
.map(node -> node.path("vendors").path("oppo")) .map(node -> node.path("vendors").path("oppo"))