HarmonyOS NEXT Push Kit + Spring Boot 实现华为推送服务完整教程
文章主要讲述的华为push kit的消息推送能力
HarmonyOS NEXT + Spring Boot 实现华为推送服务完整教程
前言
本文详细介绍如何在 HarmonyOS NEXT 应用中集成华为 Push Kit 推送服务,实现服务端向客户端发送推送通知的完整流程。
本教程包含:
- 客户端获取 Push Token
- 客户端上传 Token 到服务器
- 服务端调用华为推送 API 发送通知
一、整体架构
推送服务的整体流程分为两个阶段:
阶段一:Token 注册
- App 启动时,调用华为 Push Kit SDK 获取设备的 Push Token
- 将 Push Token 上传到自己的服务器保存
阶段二:发送推送
- 服务端需要推送时,从数据库查询目标用户的 Push Token
- 服务端调用华为推送 API,将消息推送到指定设备
二、客户端开发(HarmonyOS NEXT)
2.1 配置 Push Kit
首先在 AGC 控制台开通 Push Kit 服务,下载 agconnect-services.json 配置文件,放到项目的 entry/src/main/resources/rawfile/ 目录。
2.2 获取 Push Token
在应用的 EntryAbility 中初始化推送服务并获取 Token。
EntryAbility.ets
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { pushService } from '@kit.PushKit';
import { preferences } from '@kit.ArkData';
import { http } from '@kit.NetworkKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'EntryAbility';
export default class EntryAbility extends UIAbility {
private pushToken: string = '';
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, TAG, 'onCreate');
// 初始化推送服务
this.initPushService();
}
/**
* 初始化推送服务,获取 Push Token
*/
private initPushService(): void {
hilog.info(0x0000, TAG, 'initPushService: Starting...');
pushService.getToken()
.then((token: string) => {
if (!token || token.length === 0) {
hilog.warn(0x0000, TAG, 'initPushService: Got empty token');
return;
}
hilog.info(0x0000, TAG, 'initPushService: Got push token successfully');
this.pushToken = token;
// 保存到本地存储
this.saveTokenToLocal(token);
// 上传到服务器
this.uploadTokenToServer(token);
})
.catch((err: Error) => {
hilog.error(0x0000, TAG, `initPushService: Failed: ${err.message}`);
});
}
/**
* 保存 Token 到本地存储
*/
private async saveTokenToLocal(token: string): Promise<void> {
try {
const options: preferences.Options = { name: 'app_preferences' };
const dataPreferences = await preferences.getPreferences(this.context, options);
await dataPreferences.put('push_token', token);
await dataPreferences.flush();
hilog.info(0x0000, TAG, 'saveTokenToLocal: Success');
} catch (e) {
hilog.error(0x0000, TAG, `saveTokenToLocal: Failed: ${e}`);
}
}
/**
* 上传 Token 到服务器
*/
private async uploadTokenToServer(token: string): Promise<void> {
try {
// 从本地获取用户登录的 auth token
const options: preferences.Options = { name: 'app_preferences' };
const dataPreferences = await preferences.getPreferences(this.context, options);
const authToken = await dataPreferences.get('auth_token', '') as string;
if (!authToken || authToken.length === 0) {
hilog.warn(0x0000, TAG, 'uploadTokenToServer: User not logged in');
return;
}
// 发送 HTTP 请求上传 Push Token
const httpRequest = http.createHttp();
const response = await httpRequest.request(
'https://your-server.com/api/user/push-token',
{
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authToken}`
},
extraData: JSON.stringify({ pushToken: token })
}
);
hilog.info(0x0000, TAG, `uploadTokenToServer: Response code=${response.responseCode}`);
httpRequest.destroy();
} catch (e) {
hilog.error(0x0000, TAG, `uploadTokenToServer: Failed: ${e}`);
}
}
// ... 其他生命周期方法
}
2.3 关键说明
pushService.getToken()是异步方法,返回设备唯一的 Push Token- Push Token 需要保存到本地,方便后续使用
- 用户登录后,需要将 Push Token 上传到服务器与用户关联
- 如果用户未登录,可以先保存 Token,等登录后再上传
三、服务端开发(Spring Boot)
3.1 用户实体类
首先在用户表中添加 pushToken 字段,用于存储用户设备的 Push Token。
User.java
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String nickname;
// Push Token 字段
@Column(length = 512)
private String pushToken;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getPushToken() { return pushToken; }
public void setPushToken(String pushToken) { this.pushToken = pushToken; }
// ... 其他字段的 getter/setter
}
3.2 接收 Push Token 的接口
UserController.java
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@Autowired
private JwtUtil jwtUtil;
/**
* 更新用户的 Push Token
*/
@PostMapping("/push-token")
public ResponseEntity<?> updatePushToken(
@RequestHeader("Authorization") String authorization,
@RequestBody Map<String, String> request) {
try {
// 从 JWT 中解析用户 ID
String token = authorization.replace("Bearer ", "");
Long userId = jwtUtil.getUserIdFromToken(token);
// 获取请求中的 Push Token
String pushToken = request.get("pushToken");
if (pushToken == null || pushToken.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of(
"code", 400,
"message", "pushToken 不能为空"
));
}
// 更新用户的 Push Token
Optional<User> userOpt = userRepository.findById(userId);
if (userOpt.isEmpty()) {
return ResponseEntity.status(404).body(Map.of(
"code", 404,
"message", "用户不存在"
));
}
User user = userOpt.get();
user.setPushToken(pushToken);
userRepository.save(user);
return ResponseEntity.ok(Map.of(
"code", 200,
"message", "Push Token 更新成功"
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"code", 500,
"message", "更新失败: " + e.getMessage()
));
}
}
}
3.3 华为推送服务配置
在 application.properties 中添加华为推送的配置:
# 华为推送配置(从 AGC 控制台获取)
huawei.push.app-id=你的AppID
huawei.push.client-id=你的ClientID
huawei.push.client-secret=你的ClientSecret
huawei.push.project-id=你的ProjectID
创建配置类读取这些配置:
HuaweiPushConfig.java
@Configuration
@ConfigurationProperties(prefix = "huawei.push")
public class HuaweiPushConfig {
private String appId;
private String clientId;
private String clientSecret;
private String projectId;
// Getters and Setters
public String getAppId() { return appId; }
public void setAppId(String appId) { this.appId = appId; }
public String getClientId() { return clientId; }
public void setClientId(String clientId) { this.clientId = clientId; }
public String getClientSecret() { return clientSecret; }
public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; }
public String getProjectId() { return projectId; }
public void setProjectId(String projectId) { this.projectId = projectId; }
}
3.4 华为推送服务实现
这是核心的推送服务类,负责调用华为推送 API。
HuaweiPushService.java
@Service
public class HuaweiPushService {
private static final Logger log = LoggerFactory.getLogger(HuaweiPushService.class);
// 华为 OAuth2 Token 获取地址
private static final String TOKEN_URL = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
// 华为推送 API 地址(v3 版本,用于 HarmonyOS NEXT)
private static final String PUSH_URL_TEMPLATE = "https://push-api.cloud.huawei.com/v3/%s/messages:send";
@Autowired
private HuaweiPushConfig config;
@Autowired
private UserRepository userRepository;
@Autowired
private ObjectMapper objectMapper;
// 缓存 Access Token
private String accessToken;
private long tokenExpireTime = 0;
/**
* 发送推送通知给指定用户
* @param userId 目标用户ID
* @param title 通知标题
* @param body 通知内容
* @param data 自定义数据(可选)
* @return 是否发送成功
*/
public boolean sendNotificationToUser(Long userId, String title, String body, Map<String, Object> data) {
log.info("sendNotificationToUser: userId={}, title={}", userId, title);
try {
// 1. 查询用户的 Push Token
Optional<User> userOpt = userRepository.findById(userId);
if (userOpt.isEmpty()) {
log.warn("User not found: {}", userId);
return false;
}
String pushToken = userOpt.get().getPushToken();
if (pushToken == null || pushToken.isEmpty()) {
log.warn("User {} has no push token", userId);
return false;
}
// 2. 发送推送
return sendPushNotification(pushToken, title, body, data);
} catch (Exception e) {
log.error("sendNotificationToUser failed", e);
return false;
}
}
/**
* 发送推送通知
* @param pushToken 设备 Push Token
* @param title 通知标题
* @param body 通知内容
* @param data 自定义数据
* @return 是否发送成功
*/
public boolean sendPushNotification(String pushToken, String title, String body, Map<String, Object> data) {
log.info("sendPushNotification: title={}, body={}", title, body);
// 检查配置
if (!isConfigured()) {
log.warn("Huawei Push is not configured");
return false;
}
try {
// 1. 获取 Access Token
refreshAccessTokenIfNeeded();
// 2. 构建推送消息
Map<String, Object> message = buildPushMessage(pushToken, title, body, data);
String messageJson = objectMapper.writeValueAsString(message);
log.info("Push message: {}", messageJson);
// 3. 发送推送请求
String pushUrl = String.format(PUSH_URL_TEMPLATE, config.getProjectId());
String response = sendHttpPost(pushUrl, messageJson, accessToken);
log.info("Push response: {}", response);
// 4. 解析响应
Map<String, Object> result = objectMapper.readValue(response, Map.class);
String code = String.valueOf(result.get("code"));
// 华为推送成功码是 "80000000"
boolean success = "80000000".equals(code);
if (!success) {
log.error("Push failed: code={}, msg={}", code, result.get("msg"));
}
return success;
} catch (Exception e) {
log.error("sendPushNotification failed", e);
return false;
}
}
// ... 后续方法见下文
}
3.5 构建推送消息体
华为推送 v3 API 的消息格式如下:
/**
* 构建推送消息体(v3 API 格式)
*/
private Map<String, Object> buildPushMessage(String pushToken, String title, String body, Map<String, Object> data) {
Map<String, Object> root = new HashMap<>();
// target: 推送目标
Map<String, Object> target = new HashMap<>();
target.put("token", new String[]{pushToken});
root.put("target", target);
// payload: 推送内容
Map<String, Object> payload = new HashMap<>();
// notification: 通知消息
Map<String, Object> notification = new HashMap<>();
notification.put("title", title);
notification.put("body", body);
notification.put("category", "IM"); // 消息类别
// clickAction: 点击动作
Map<String, Object> clickAction = new HashMap<>();
clickAction.put("actionType", 0); // 0: 打开应用首页
notification.put("clickAction", clickAction);
payload.put("notification", notification);
// data: 自定义数据(点击通知时传递给应用)
if (data != null && !data.isEmpty()) {
try {
payload.put("data", objectMapper.writeValueAsString(data));
} catch (Exception e) {
log.warn("Failed to serialize data", e);
}
}
// pushOptions: 推送选项
Map<String, Object> pushOptions = new HashMap<>();
pushOptions.put("testMessage", false);
payload.put("pushOptions", pushOptions);
root.put("payload", payload);
return root;
}
生成的 JSON 格式示例:
{
"target": {
"token": ["IQAAAACy0T5MAAC..."]
},
"payload": {
"notification": {
"title": "您有一条新消息",
"body": "张三给您发送了一条消息",
"category": "IM",
"clickAction": {
"actionType": 0
}
},
"data": "{\"type\":\"chat\",\"senderId\":123}",
"pushOptions": {
"testMessage": false
}
}
}
3.6 获取华为 Access Token
调用华为推送 API 需要先获取 Access Token,Token 有效期为 1 小时。
/**
* 刷新 Access Token(如果过期)
*/
private void refreshAccessTokenIfNeeded() throws Exception {
// 检查 Token 是否仍然有效
if (accessToken != null && System.currentTimeMillis() < tokenExpireTime) {
return;
}
log.info("Refreshing access token...");
// 构建 OAuth2 请求参数
String params = "grant_type=client_credentials"
+ "&client_id=" + URLEncoder.encode(config.getAppId(), StandardCharsets.UTF_8)
+ "&client_secret=" + URLEncoder.encode(config.getClientSecret(), StandardCharsets.UTF_8);
// 发送请求
String response = sendHttpPostForm(TOKEN_URL, params);
log.info("Token response: {}", response);
// 解析响应
Map<String, Object> result = objectMapper.readValue(response, Map.class);
if (result.containsKey("access_token")) {
accessToken = (String) result.get("access_token");
int expiresIn = ((Number) result.get("expires_in")).intValue();
// 设置过期时间(提前 5 分钟刷新)
tokenExpireTime = System.currentTimeMillis() + (expiresIn - 300) * 1000L;
log.info("Access token obtained, expires in {} seconds", expiresIn);
} else {
String error = String.valueOf(result.get("error"));
String errorDesc = String.valueOf(result.get("error_description"));
throw new RuntimeException("Failed to get access token: " + error + " - " + errorDesc);
}
}
3.7 HTTP 请求工具方法
/**
* 发送 HTTP POST 请求(JSON 格式)
*/
private String sendHttpPost(String urlStr, String body, String bearerToken) throws Exception {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
try {
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 设置请求头
conn.setRequestProperty("Authorization", "Bearer " + bearerToken);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestProperty("push-type", "0"); // v3 API 需要此 header
// 发送请求体
try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
}
// 读取响应
int responseCode = conn.getResponseCode();
InputStream is = responseCode >= 400 ? conn.getErrorStream() : conn.getInputStream();
if (is == null) {
return "{\"code\":\"" + responseCode + "\"}";
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
} finally {
conn.disconnect();
}
}
/**
* 发送 HTTP POST 请求(表单格式,用于获取 Token)
*/
private String sendHttpPostForm(String urlStr, String params) throws Exception {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
try {
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
try (OutputStream os = conn.getOutputStream()) {
os.write(params.getBytes(StandardCharsets.UTF_8));
}
int responseCode = conn.getResponseCode();
InputStream is = responseCode >= 400 ? conn.getErrorStream() : conn.getInputStream();
if (is == null) {
return "{\"error\":\"" + responseCode + "\"}";
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
} finally {
conn.disconnect();
}
}
/**
* 检查推送服务是否已配置
*/
private boolean isConfigured() {
return config.getAppId() != null && !config.getAppId().isEmpty()
&& config.getClientSecret() != null && !config.getClientSecret().isEmpty()
&& config.getProjectId() != null && !config.getProjectId().isEmpty();
}
四、使用示例
4.1 在业务代码中发送推送
例如,当用户收到新消息时发送推送通知:
MessageService.java
@Service
public class MessageService {
@Autowired
private HuaweiPushService pushService;
@Autowired
private MessageRepository messageRepository;
/**
* 发送消息
*/
public Message sendMessage(Long senderId, Long receiverId, String content) {
// 1. 保存消息到数据库
Message message = new Message();
message.setSenderId(senderId);
message.setReceiverId(receiverId);
message.setContent(content);
message.setCreateTime(System.currentTimeMillis());
messageRepository.save(message);
// 2. 发送推送通知给接收者
String title = "您有一条新消息";
String body = content.length() > 50 ? content.substring(0, 50) + "..." : content;
// 自定义数据,用于点击通知后跳转
Map<String, Object> data = new HashMap<>();
data.put("type", "chat");
data.put("senderId", senderId);
pushService.sendNotificationToUser(receiverId, title, body, data);
return message;
}
}
4.2 测试推送接口
可以创建一个测试接口来验证推送功能:
TestController.java
@RestController
@RequestMapping("/api/test")
public class TestController {
@Autowired
private HuaweiPushService pushService;
/**
* 测试推送(仅用于开发测试)
*/
@PostMapping("/push")
public ResponseEntity<?> testPush(@RequestBody Map<String, Object> request) {
Long userId = ((Number) request.get("userId")).longValue();
String title = (String) request.get("title");
String body = (String) request.get("body");
boolean success = pushService.sendNotificationToUser(userId, title, body, null);
return ResponseEntity.ok(Map.of(
"code", success ? 200 : 500,
"message", success ? "推送成功" : "推送失败"
));
}
}
测试请求:
curl -X POST http://localhost:8080/api/test/push \
-H "Content-Type: application/json" \
-d '{
"userId": 1,
"title": "测试推送",
"body": "这是一条测试消息"
}'
五、AGC 控制台配置说明
5.1 开通 Push Kit 服务
- 登录 华为 AGC 控制台
- 选择或创建你的项目
- 在左侧菜单选择「增长」→「推送服务」
- 点击「开通服务」
5.2 获取配置参数
-
进入「项目设置」→「常规」
-
记录以下信息:
- App ID
- Client ID
- Project ID
-
进入「项目设置」→「Server SDK」
-
创建或查看 Client Secret
5.3 下载配置文件
- 在「项目设置」→「常规」页面
- 下载
agconnect-services.json文件 - 将文件放到 HarmonyOS 项目的
entry/src/main/resources/rawfile/目录
六、常见问题
问题 1:获取 Push Token 失败
可能原因:
- AGC 配置文件未正确放置
- Push Kit 服务未开通
- 设备网络异常
解决方案:
- 检查
agconnect-services.json文件是否存在 - 确认 AGC 控制台已开通 Push Kit
- 检查设备网络连接
问题 2:Access Token 获取失败
可能原因:
- App ID 或 Client Secret 配置错误
- 网络无法访问华为服务器
解决方案:
- 核对 AGC 控制台的配置信息
- 检查服务器网络是否能访问
oauth-login.cloud.huawei.com
问题 3:推送发送成功但收不到通知
可能原因:
- Push Token 已过期或无效
- 设备通知权限未开启
- 应用被系统限制后台运行
解决方案:
- 让用户重新打开 App 获取新的 Push Token
- 检查设备的通知权限设置
- 检查应用的后台运行权限
问题 4:华为推送返回错误码
常见错误码说明:
80000000:成功80100000:参数错误,检查请求格式80100001:Access Token 无效,需要重新获取80300007:Push Token 无效,设备需要重新获取 Token80300008:消息体过大,减少内容长度
七、总结
本文介绍了 HarmonyOS NEXT 应用集成华为推送服务的完整流程:
- 客户端:通过 Push Kit SDK 获取设备 Token,上传到服务器
- 服务端:保存用户的 Push Token,需要推送时调用华为 API 发送通知
- 华为云:负责将通知推送到目标设备
关键点:
- Push Token 是设备唯一标识,需要与用户关联保存
- Access Token 有效期 1 小时,需要缓存并定时刷新
- v3 API 用于 HarmonyOS NEXT,消息格式与 v1 略有不同
希望本文对你有所帮助!
更多推荐


所有评论(0)