Files
biz-mobile-cloud-zax/src/sheep/utils/jg-push/README.md
T
dzc ea8468e3ef feat:学生家长导入
feat:增加饭卡列表查询
feat:极光推送过期数据清理
2026-08-13 11:35:25 +08:00

18 KiB
Raw Blame History

极光推送使用说明

本文档说明当前项目中 jg-push 模块的设计、接入方式、常用 API、状态管理方式以及业务侧推荐用法。

适用范围:

  • HBuilderX 原生 App 环境
  • uni-app + Vue 3 + Pinia
  • 当前项目目录:src/sheep/utils/jg-push

目录结构

当前推送模块已经拆分为目录形式,便于长期维护:

src/sheep/utils/jg-push/
├── index.ts
├── service.ts
├── types.ts
└── README.md

职责说明:

  • index.ts:统一导出入口,对外保持稳定引用路径
  • service.ts:极光推送工具类实现、事件监听、状态同步、官方 API 封装
  • types.ts:对外暴露的类型声明、错误类型、原生插件接口定义
  • README.md:使用说明文档

模块设计

当前模块采用“两层结构”:

  1. jg-push 工具层
  2. push-store 状态层

对应文件:

  • 工具层:src/sheep/utils/jg-push/index.ts
  • 状态层:src/sheep/stores/push-store.ts

这样设计的好处:

  • 页面、组件、业务逻辑都只需要调用统一工具实例
  • 推送运行状态由全局 store 统一维护
  • 消息事件格式统一,降低业务接入成本
  • 非 APP-PLUS 环境下有兜底,不会直接调用原生插件报错

导入方式

默认导入单例:

import jgPush from "@@/utils/jg-push"

按需导入类或类型:

import jgPush, { JgPushService, type PushInitializeOptions, type JPushNotificationPayload, type PushStatusSnapshot } from "@@/utils/jg-push"

说明:

  • default export 是单例对象,推荐业务侧直接使用
  • JgPushService 是类,一般不建议业务层重复 new
  • 所有类型都已通过 index.ts 统一导出

运行前提

在使用本模块前,需要先确认原生端已经正确集成极光 HBuilderX 插件:

  • 插件名称:JG-JPush
  • 文档来源:
    • API.md
    • ANDROID.md
    • IOS.md

如果原生插件未正确集成:

  • uni.requireNativePlugin("JG-JPush") 将不可用
  • 工具类初始化会报出统一错误
  • H5 环境下会自动走不支持分支,不会崩溃

状态管理说明

全局状态由 usePushStore() 维护。

核心状态包括:

  • 当前运行平台
  • 插件是否可用
  • 是否已初始化
  • 是否正在初始化
  • 当前连接状态
  • 推送是否被停止
  • 通知权限状态
  • 系统通知总开关状态
  • registrationId
  • 当前 alias
  • 当前 tags
  • deviceToken
  • 最近一次通知/点击通知/自定义消息/应用内消息
  • 最近一次错误
  • 待消费跳转队列
  • 轻量事件历史

读取方式示例:

import { usePushStore } from "@@/stores/push-store"

const pushStore = usePushStore()

console.log(pushStore.registrationId)
console.log(pushStore.alias)
console.log(pushStore.tags)
console.log(pushStore.connected)
console.log(pushStore.notificationPermission)

最小接入流程

推荐的接入顺序如下:

  1. 应用启动时初始化推送
  2. 登录成功后获取 registrationId 并提交后端绑定
  3. 需要时监听通知事件
  4. 退出登录时先请求后端精确解绑,再清理本地 alias 和标签

1. 应用启动初始化

推荐在 App.vue 的 onLaunch 中尽早初始化。

示例:

import jgPush from "@@/utils/jg-push"

export default {
	async onLaunch() {
		try {
			await jgPush.initialize({
				debug: import.meta.env.DEV,
				autoInitService: true,
				enableCrashHandler: true,
				requestAndroidPermission: true,
				requestIOSNotificationPermission: true,
				iosForegroundOptions: {
					alert: true,
					sound: true,
					badge: true,
				},
			})
		} catch (error) {
			console.error("[JPush] 初始化失败", error)
		}
	},
}

说明:

  • debug:开发环境建议开启
  • autoInitService:是否调用 initJPushService
  • enableCrashHandler:是否开启极光 Crash 收集
  • requestAndroidPermission:Android 是否主动申请推送相关权限
  • requestIOSNotificationPermission:iOS 是否主动查询或申请通知授权
  • iosForegroundOptions:iOS 前台通知展示策略

2. 登录成功后绑定设备

当前项目由后端将业务 userId 作为极光 alias,App 不再直接调用 jgPush.setAlias()。

原因:

  • 后端可以在绑定前统一清理过期和超量设备
  • userId 从登录 Token 获取,客户端不能绑定其他用户
  • 密码、微信和 Apple 登录共用同一条推送同步路径
  • 推送失败属于附属能力失败,不会改变登录认证结果

绑定逻辑已统一放在 src/sheep/stores/user.ts 的 disposeLogin() 中。各登录页面只需在保存 Token 后调用该方法,不要再追加别名操作。

示例:

import { useUserStore } from "@@/stores/user"

const userStore = useUserStore()

const onLoginSuccess = async () => {
	// disposeLogin 内部会获取 registrationId 并调用后端绑定接口;推送失败只记录警告。
	await userStore.disposeLogin()
}

3. 退出登录时解绑

disposeLogOut() 按以下顺序执行:

  1. Token 有效时获取当前 registrationId
  2. 调用后端接口精确解绑当前设备
  3. 调用 jgPush.deleteAlias() 作为本地兜底
  4. 调用 jgPush.cleanTags() 清理本地标签
  5. 调用服务端 Token 注销接口
  6. 在 finally 中清除本地登录态

上述步骤相互隔离,任何一步失败都不能阻止本地退出。强杀 App 或系统回收进程时无法保证执行解绑,后端会通过过期设备策略兜底。

示例:

import { useUserStore } from "@@/stores/user"

async function handleLogout() {
	const userStore = useUserStore()
	await userStore.disposeLogOut()
}

常用初始化配置

基础初始化

await jgPush.initialize({
	debug: true,
	autoInitService: true,
})

Android 初始化示例

await jgPush.initialize({
	debug: true,
	autoInitService: true,
	requestAndroidPermission: true,
	androidChannel: {
		channel: "default",
	},
})

Android 自定义渠道和铃声

await jgPush.initialize({
	androidChannel: {
		channel: "notice",
		channel_id: "notice_channel",
		sound: "push.caf",
	},
})

iOS 初始化示例

await jgPush.initialize({
	autoInitService: true,
	requestIOSNotificationPermission: true,
	iosForegroundOptions: {
		alert: true,
		sound: true,
		badge: true,
	},
})

常用状态查询

获取完整状态快照

const status = jgPush.getStatus()
console.log(status)

返回结构示例:

{
  platform: "android",
  pluginAvailable: true,
  initialized: true,
  initializing: false,
  connected: true,
  pushStopped: false,
  notificationPermission: "granted",
  notificationEnabled: true,
  registrationId: "xxxxxx",
  alias: "10001",
  tags: ["merchant", "rider"],
  deviceToken: "",
}

主动刷新状态

await jgPush.refreshStatus()

适用场景:

  • 应用从后台回到前台
  • 登录后想重新同步 registrationId
  • 权限变化后想重新读取通知状态

获取注册 ID

const registrationId = await jgPush.getRegistrationId()
console.log(registrationId)

说明:

  • 极光刚初始化完成时,registrationId 可能暂时为空
  • 这种情况属于正常现象,可以稍后重试

事件监听用法

本模块提供统一的事件订阅接口:

const off = jgPush.on("notification", (event) => {
	console.log(event.type)
	console.log(event.payload)
	console.log(event.raw)
})

off()

支持的事件:

  • connect
  • notification
  • customMessage
  • inAppMessage
  • localNotification
  • tagAlias
  • mobileNumber
  • deviceToken
  • geofence
  • voip
  • error

监听连接状态

const offConnect = jgPush.on("connect", (event) => {
	console.log("是否连接成功:", event.payload.connectEnable)
})

监听通知事件

const offNotification = jgPush.on("notification", (event) => {
	const payload = event.payload
	console.log("通知标题:", payload.title)
	console.log("通知内容:", payload.content)
	console.log("通知附加字段:", payload.extras)
	console.log("通知事件类型:", payload.notificationEventType)
})

说明:

  • notificationArrived:收到通知
  • notificationOpened:点击通知

如果是点击通知,工具类会尝试从 extras 中提取跳转信息并写入 push-store 的待消费路由队列。

监听自定义消息

const offCustom = jgPush.on("customMessage", (event) => {
	console.log("自定义消息内容:", event.payload.content)
	console.log("扩展字段:", event.payload.extras)
})

监听错误

const offError = jgPush.on("error", (event) => {
	console.error("推送模块错误:", event.payload.code, event.payload.message)
})

清空监听器

jgPush.clearListeners()

只清理某一种事件:

jgPush.clearListeners("notification")

别名与标签管理

底层设置别名

setAlias() 仅作为原生插件底层能力保留,当前项目的登录流程禁止直接调用。业务登录必须提交 registrationId 给后端统一绑定。

await jgPush.setAlias("10001")

本地删除别名

deleteAlias() 只用于退出登录时的本地兜底,不能替代后端精确解绑接口。

await jgPush.deleteAlias()

查询别名

const result = await jgPush.queryAlias()
console.log(result.alias)

新增标签

await jgPush.addTags(["merchant", "canteen"])

覆盖标签

await jgPush.updateTags(["merchant", "vip"])

删除指定标签

await jgPush.deleteTags(["vip"])

清空所有标签

await jgPush.cleanTags()

查询单个标签

const result = await jgPush.queryTag("merchant")
console.log(result.tag, result.tagEnable)

查询全部标签

const result = await jgPush.getAllTags()
console.log(result.tags)

说明:

  • 标签/别名接口内部已做 Promise 封装
  • 工具类内部会通过 sequence 自动关联原生回调
  • 回调超时会抛出统一错误

通知相关能力

打开系统通知设置页

await jgPush.openSettingsForNotification()

查询通知权限状态

const result = await jgPush.queryNotificationState()
console.log(result.permission)
console.log(result.enabled)

权限状态说明:

  • unknown
  • granted
  • denied
  • not_determined
  • provisional
  • disabled

查询 Android 通知总开关

const enabled = await jgPush.isNotificationEnabled()

查询 iOS 通知授权

const result = await jgPush.requestNotificationAuthorization()
console.log(result.status)

本地通知能力

本模块不仅支持接收服务端推送,也支持主动发本地通知,适合:

  • 关键业务提醒
  • 前台即时提醒
  • 应用内手动触发通知场景

新增本地通知

await jgPush.addLocalNotification({
	messageID: "order_10001",
	title: "新订单提醒",
	content: "您有一笔新的订单,请及时处理",
	extras: {
		path: "/pages/order/detail",
		query: {
			id: "10001",
		},
	},
})

删除指定本地通知

await jgPush.removeLocalNotification("order_10001")

清空本地通知

await jgPush.clearLocalNotifications()

Android 专属能力

停止推送

await jgPush.stopPush()

恢复推送

await jgPush.resumePush()

查询推送是否已停止

const stopped = await jgPush.isPushStopped()

设置通知渠道

await jgPush.setChannel({
	channel: "default",
})

设置通知渠道与铃声

await jgPush.setChannelAndSound({
	channel: "notice",
	channel_id: "notice_channel",
	sound: "push.caf",
})

设置允许推送时间

await jgPush.setPushTime({
	pushTimeDays: [1, 2, 3, 4, 5],
	pushTimeStartHour: 8,
	pushTimeEndHour: 22,
})

设置静默时间

await jgPush.setSilenceTime({
	silenceTimeStartHour: 22,
	silenceTimeStartMinute: 0,
	silenceTimeEndHour: 8,
	silenceTimeEndMinute: 0,
})

设置最近保留通知数

await jgPush.setLatestNotificationNumber(10)

请求 Android 权限

await jgPush.requestAndroidPermission()

页面统计

await jgPush.onResume()
await jgPush.onPause()

清除通知

await jgPush.clearNotificationById("10001")
await jgPush.clearAllNotifications()

iOS 专属能力

设置前台通知展示策略

await jgPush.setNotiForegroundPresentationOptions({
	alert: true,
	sound: true,
	badge: true,
})

查询系统定位服务

const enabled = await jgPush.locationServicesEnabled()

查询定位授权状态

const status = await jgPush.getLocationAuthorizationStatus()

请求定位授权

await jgPush.requestLocationAuthorization()

初始化 VoIP

await jgPush.initVoipService()

设置位置

await jgPush.setLocation({
	latitude: 30.2741,
	longitude: 120.1551,
})

设置定位能力开关

await jgPush.setLocationEnable(true)

页面进入与离开

await jgPush.pageEnterTo("订单详情页")
await jgPush.pageLeave("订单详情页")

设置地理围栏最大数量

await jgPush.setMaxGeofenceNumber(10)

删除指定地理围栏

await jgPush.deleteGeofence("beijing")

错误处理方式

本模块统一使用 PushServiceError 作为异常类型。

推荐写法:

import jgPush, { PushServiceError } from "@@/utils/jg-push"

try {
	await jgPush.updateTags(["canteen"])
} catch (error) {
	if (error instanceof PushServiceError) {
		console.error(error.code, error.message, error.detail)
	} else {
		console.error(error)
	}
}

常见错误码:

  • PUSH_UNSUPPORTED_PLATFORM
  • PUSH_PLUGIN_UNAVAILABLE
  • PUSH_METHOD_UNAVAILABLE
  • PUSH_NOT_INITIALIZED
  • PUSH_CALLBACK_TIMEOUT
  • PUSH_NATIVE_ERROR

与当前项目结合的接入方案

当前项目已经按以下方式接入:

App 启动时初始化

初始化位于 src/App.vue 的 onLaunch() 内,并使用独立异常捕获,插件或权限初始化失败不会阻断 App 启动。

建议写法:

import jgPush from "@@/utils/jg-push"

onLaunch() {
  this._initTabbar()
  this._initGlobalWs()

  // #ifdef APP-PLUS
  jgPush.initialize({
    debug: import.meta.env.DEV,
    autoInitService: true,
    enableCrashHandler: true,
    requestAndroidPermission: true,
    requestIOSNotificationPermission: true,
    iosForegroundOptions: {
      alert: true,
      sound: true,
      badge: true,
    },
  }).catch((error) => {
    console.error("[JPush] init failed", error)
  })
  // #endif
}

登录成功后同步设备

密码、微信和 Apple 登录成功后都会调用 userStore.disposeLogin()。该方法获取当前 registrationId,调用 /thirdparty/jg-push/device/bind,然后独立更新标签。

登录调用方只保留:

await userStore.disposeLogin()

后端绑定或标签更新失败只记录警告,不向登录页面抛出,也不触发统一错误 Toast。

退出登录时精确解绑并清理本地状态

退出逻辑统一位于 src/sheep/stores/user.ts 的 disposeLogOut()。

页面只需调用:

await userStore.disposeLogOut()

App 原生推送逻辑均位于 APP-PLUS 条件编译分支,H5 不调用极光原生插件。

待跳转路由说明

如果通知点击事件的 extras 中带有以下字段:

  • path
  • query
  • routePath
  • routeQuery
  • params
  • url

工具类会尝试自动提取并写入 push-store.pendingRoutes。

业务层可以这样消费:

import { usePushStore } from "@@/stores/push-store"
import { router } from "@@/index"

const pushStore = usePushStore()

const nextRoute = pushStore.shiftPendingRoute()
if (nextRoute) {
	router.jump(nextRoute.path, nextRoute.query || {})
}

适合场景:

  • 冷启动后延迟跳转
  • 首页 ready 后统一处理通知跳转
  • 避免通知点击过早发生时页面尚未挂载

常见问题

1. H5 为什么不能用?

因为极光 HBuilderX 插件是原生插件,依赖:

  • uni.requireNativePlugin("JG-JPush")

这不是 H5 能直接使用的能力,所以 H5 会自动走不支持分支。

2. 为什么 registrationId 有时为空?

这是正常现象,常见原因:

  • SDK 刚初始化
  • 网络尚未连上
  • 极光注册流程尚未完成

建议做法:

  • 初始化后稍后重试
  • 在 onShow 或登录后再调用一次 refreshStatus()

3. 为什么调用标签或别名接口会超时?

常见原因:

  • 原生插件未正确初始化
  • 当前平台不支持该方法
  • 原生层没有正确回调
  • 网络或设备状态异常

模块内部会抛出:

  • PUSH_CALLBACK_TIMEOUT

4. 推送状态在哪里看?

可以直接看 push-store:

const pushStore = usePushStore()
console.log(pushStore)

也可以调用:

console.log(jgPush.getStatus())

业务接入顺序

新增登录方式或推送业务时按这个顺序接入:

  1. 在 App.vue 初始化推送,并独立捕获初始化异常
  2. 登录成功后统一调用 userStore.disposeLogin(),由后端绑定设备
  3. 退出登录统一调用 userStore.disposeLogOut(),由后端精确解绑当前设备
  4. 在首页或业务层加通知事件监听
  5. 根据业务需要再补 tags、本地通知、跳转队列处理

总结

当前 jg-push 模块已经具备以下能力:

  • 极光原生插件初始化
  • Android / iOS 差异化处理
  • 推送状态全局维护
  • 通知、自定义消息、应用内消息统一监听
  • 别名、标签、角标、本地通知、权限、地理能力完整封装
  • 错误统一包装
  • 与当前项目的登录态和全局状态体系兼容
  • 后端统一管理 alias 容量、过期设备和精确解绑
  • 登录认证与推送同步相互隔离