个推官方文档:https://docs.getui.com/getui/server/rest_v2/push/
1、编写配置文件
修改.yml文件
getui: AppID: OokKLlwRjU7tJMccVVra72AppKey: f8C6lK7OGu1115ckOfVxD8MasterSecret: aTxslPiUJy9kzzZaPONL26AppSecret: sAoJ9sQ66S13P0PG3c1y02
编写映射文件
GeTuiConfig
package com.common.disposition;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "getui")
public class GeTuiConfig {private String appId;private String appKey;private String masterSecret;private String appSecret;
}
2、编写工具类
GeTuiUtil
package com.common.util;import com.common.disposition.GeTuiConfig;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.concurrent.TimeUnit;@Component
public class GeTuiUtil {private static String APP_ID;private static String APP_KEY;private static String MASTER_SECRET;// 在构造函数中初始化配置@Autowiredpublic GeTuiUtil(GeTuiConfig config) {APP_ID = config.getAppId();APP_KEY = config.getAppKey();MASTER_SECRET = config.getMasterSecret();}private static final String REDIS_TOKEN_KEY = "GETUI_ACCESS_TOKEN";private static RedisTemplate<String, String> redisTemplate;private static ObjectMapper objectMapper = new ObjectMapper(); // 用于解析 JSON 响应// 初始化 RedisTemplate,手动注入@Autowiredpublic void setRedisTemplate(@Qualifier("stringRedisTemplate") RedisTemplate<String, String> redisTemplate) {GeTuiUtil.redisTemplate = redisTemplate;}// 获取 access_token,优先从 Redis 中获取public static String getAccessToken() throws Exception {String accessToken = redisTemplate.opsForValue().get(REDIS_TOKEN_KEY);if (accessToken == null || accessToken.isEmpty()) {System.out.println("在Redis中找不到访问令牌,正在从GeTui获取...");accessToken = getAccessTokenFromGeTui();} else {System.out.println("从Redis检索到的访问令牌。");}return accessToken;}// 从个推获取 access_tokenprivate static String getAccessTokenFromGeTui() throws Exception {long timestamp = System.currentTimeMillis();String sign = sha256(APP_KEY + timestamp + MASTER_SECRET);String jsonPayload = String.format("{\"sign\": \"%s\", \"timestamp\": \"%s\", \"appkey\": \"%s\"}",sign, timestamp, APP_KEY);String response = postRequest("https://restapi.getui.com/v2/" + APP_ID + "/auth", jsonPayload);String accessToken = parseAccessTokenFromResponse(response);long expireTime = parseExpireTimeFromResponse(response); // 获取过期时间// 将 token 存入 Redis,并设置过期时间redisTemplate.opsForValue().set(REDIS_TOKEN_KEY, accessToken, expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);return accessToken;}// 发送cid单推消息 (toSingle)public static void sendPushMessageCid(String clientId, String title, String body) throws Exception {String token = getAccessToken();String jsonPayload = String.format("{\"request_id\": \"unique_request_id\", \"audience\": {\"cid\": [\"%s\"]}, \"push_message\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}, \"push_channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}}}}",clientId, title, body, title, body);postRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/single/cid", jsonPayload, token);}// 发送别名(alias)单推消息 (toSingle)public static void sendPushMessageAlias(String clientId, String title, String body) throws Exception {String token = getAccessToken();String jsonPayload = String.format("{\"request_id\": \"unique_request_id\", \"audience\": {\"alias\": [\"%s\"]}, \"push_message\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}, \"push_channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}}}}",clientId, title, body, title, body);postRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/single/cid", jsonPayload, token);}// 发送cid批量推消息 (toList)public static void sendBatchPushMessageCid(String[] clientIds, String title, String body) throws Exception {String token = getAccessToken();StringBuilder cidArray = new StringBuilder();for (String clientId : clientIds) {cidArray.append("\"").append(clientId).append("\",");}// Remove trailing commaString cidList = cidArray.substring(0, cidArray.length() - 1);String jsonPayload = String.format("{\"audience\": {\"cid\": [%s]}, \"push_message\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}, \"push_channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}}}}",cidList, title, body, title, body);postRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/list/cid", jsonPayload, token);}// 发送别名(alias)批量推消息 (toList)public static void sendBatchPushMessageAlias(String[] clientIds, String title, String body) throws Exception {String token = getAccessToken();StringBuilder cidArray = new StringBuilder();for (String clientId : clientIds) {cidArray.append("\"").append(clientId).append("\",");}// Remove trailing commaString cidList = cidArray.substring(0, cidArray.length() - 1);String jsonPayload = String.format("{\"audience\": {\"alias\": [%s]}, \"push_message\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}, \"push_channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}}}}",cidList, title, body, title, body);postRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/list/cid", jsonPayload, token);}// 发送群推消息 (toApp)public static void sendPushToApp(String title, String body) throws Exception {String token = getAccessToken();String jsonPayload = String.format("{\"request_id\": \"unique_request_id\", \"audience\": \"all\", \"push_message\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}, \"push_channel\": {\"ios\": {\"type\": \"notify\"}, \"android\": {\"ups\": {\"notification\": {\"title\": \"%s\", \"body\": \"%s\", \"click_type\": \"none\"}}}}}",title, body, title, body);postRequest("https://restapi.getui.com/v2/" + APP_ID + "/push/all", jsonPayload, token);}// 通用的 POST 请求,带 Tokenprivate static String postRequest(String requestUrl, String payload, String token) throws Exception {URL url = new URL(requestUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("POST");conn.setRequestProperty("Content-Type", "application/json");conn.setRequestProperty("token", token);conn.setDoOutput(true);try (OutputStream os = conn.getOutputStream()) {byte[] input = payload.getBytes(StandardCharsets.UTF_8);os.write(input, 0, input.length);}int responseCode = conn.getResponseCode();if (responseCode == 200) {System.out.println("推送消息发送成功。");} else {System.out.println("发送推送消息失败。响应代码: " + responseCode);}return new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8);}// 重载的 POST 请求,不带 Tokenprivate static String postRequest(String requestUrl, String payload) throws Exception {URL url = new URL(requestUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("POST");conn.setRequestProperty("Content-Type", "application/json");conn.setDoOutput(true);try (OutputStream os = conn.getOutputStream()) {byte[] input = payload.getBytes(StandardCharsets.UTF_8);os.write(input, 0, input.length);}int responseCode = conn.getResponseCode();if (responseCode == 200) {System.out.println("Request successful.");} else {System.out.println("发送请求失败。响应代码: " + responseCode);}return new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8);}// 解析获取到的 access_tokenprivate static String parseAccessTokenFromResponse(String response) throws Exception {JsonNode rootNode = objectMapper.readTree(response);return rootNode.path("data").path("token").asText();}// 解析过期时间private static long parseExpireTimeFromResponse(String response) throws Exception {JsonNode rootNode = objectMapper.readTree(response);return rootNode.path("data").path("expire_time").asLong();}// 生成 SHA-256 签名private static String sha256(String input) throws Exception {MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));StringBuilder hexString = new StringBuilder();for (byte b : hash) {String hex = Integer.toHexString(0xff & b);if (hex.length() == 1){hexString.append('0');}hexString.append(hex);}return hexString.toString();}
}
3、调用示例
单推送消息
// 单推送消息@PostMapping("sendBatchPushMessage")@Operation(summary = "单推送消息")public ApiResult<String> sendBatchPushMessage(@RequestParam("clientId") String clientId,@RequestParam("title") String title,@RequestParam("body") String body) {try {GeTuiUtil.sendPushMessageCid(clientId, title, body);return ApiResult.success("推送成功");} catch (Exception e) {// 打印详细的错误信息以便调试e.printStackTrace();return ApiResult.fail("推送失败,内部错误:" + e.getMessage());}}