329 lines
7.6 KiB
Markdown
329 lines
7.6 KiB
Markdown
|
|
# Flutter前端推送通知集成方案
|
|||
|
|
|
|||
|
|
## 技术选型
|
|||
|
|
|
|||
|
|
### 推送服务选择
|
|||
|
|
考虑到国内环境,推荐使用 **极光推送 (JPush)** 或 **个推 (Getui)**
|
|||
|
|
|
|||
|
|
**优势**:
|
|||
|
|
- 国内推送到达率高
|
|||
|
|
- 支持iOS和Android
|
|||
|
|
- 提供完善的Flutter SDK
|
|||
|
|
- 免费版本足够使用
|
|||
|
|
|
|||
|
|
## 集成步骤
|
|||
|
|
|
|||
|
|
### 1. 添加依赖
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
dependencies:
|
|||
|
|
# 极光推送
|
|||
|
|
jpush_flutter: ^2.5.0
|
|||
|
|
|
|||
|
|
# 本地通知
|
|||
|
|
flutter_local_notifications: ^16.0.0
|
|||
|
|
|
|||
|
|
# 权限管理
|
|||
|
|
permission_handler: ^11.0.0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Android配置
|
|||
|
|
|
|||
|
|
**android/app/build.gradle**:
|
|||
|
|
```gradle
|
|||
|
|
android {
|
|||
|
|
defaultConfig {
|
|||
|
|
manifestPlaceholders = [
|
|||
|
|
JPUSH_PKGNAME: applicationId,
|
|||
|
|
JPUSH_APPKEY: "你的APPKEY",
|
|||
|
|
JPUSH_CHANNEL: "developer-default"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**android/app/src/main/AndroidManifest.xml**:
|
|||
|
|
```xml
|
|||
|
|
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT"/>
|
|||
|
|
<uses-permission android:name="android.permission.INTERNET"/>
|
|||
|
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
|||
|
|
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
|||
|
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
|||
|
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
|||
|
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
|||
|
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
|||
|
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. iOS配置
|
|||
|
|
|
|||
|
|
**ios/Runner/Info.plist**:
|
|||
|
|
```xml
|
|||
|
|
<key>UIBackgroundModes</key>
|
|||
|
|
<array>
|
|||
|
|
<string>remote-notification</string>
|
|||
|
|
</array>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 初始化推送
|
|||
|
|
|
|||
|
|
**lib/services/push_service.dart**:
|
|||
|
|
```dart
|
|||
|
|
import 'package:jpush_flutter/jpush_flutter.dart';
|
|||
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|||
|
|
|
|||
|
|
class PushService {
|
|||
|
|
static final PushService _instance = PushService._internal();
|
|||
|
|
factory PushService() => _instance;
|
|||
|
|
PushService._internal();
|
|||
|
|
|
|||
|
|
final JPush jpush = JPush();
|
|||
|
|
final FlutterLocalNotificationsPlugin localNotifications =
|
|||
|
|
FlutterLocalNotificationsPlugin();
|
|||
|
|
|
|||
|
|
Future<void> init() async {
|
|||
|
|
// 初始化极光推送
|
|||
|
|
jpush.addEventHandler(
|
|||
|
|
onReceiveNotification: (Map<String, dynamic> message) async {
|
|||
|
|
print('收到推送: $message');
|
|||
|
|
_handleNotification(message);
|
|||
|
|
},
|
|||
|
|
onOpenNotification: (Map<String, dynamic> message) async {
|
|||
|
|
print('点击推送: $message');
|
|||
|
|
_handleNotificationTap(message);
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
jpush.setup(
|
|||
|
|
appKey: '你的APPKEY',
|
|||
|
|
channel: 'developer-default',
|
|||
|
|
production: false,
|
|||
|
|
debug: true,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 初始化本地通知
|
|||
|
|
await _initLocalNotifications();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _initLocalNotifications() async {
|
|||
|
|
const AndroidInitializationSettings initializationSettingsAndroid =
|
|||
|
|
AndroidInitializationSettings('@mipmap/ic_launcher');
|
|||
|
|
|
|||
|
|
final DarwinInitializationSettings initializationSettingsDarwin =
|
|||
|
|
DarwinInitializationSettings();
|
|||
|
|
|
|||
|
|
final InitializationSettings initializationSettings =
|
|||
|
|
InitializationSettings(
|
|||
|
|
android: initializationSettingsAndroid,
|
|||
|
|
iOS: initializationSettingsDarwin,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
await localNotifications.initialize(
|
|||
|
|
initializationSettings,
|
|||
|
|
onDidReceiveNotificationResponse: (NotificationResponse response) {
|
|||
|
|
_handleLocalNotificationTap(response.payload);
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _handleNotification(Map<String, dynamic> message) {
|
|||
|
|
// 处理推送消息
|
|||
|
|
final title = message['title'] ?? '新消息';
|
|||
|
|
final body = message['alert'] ?? '';
|
|||
|
|
|
|||
|
|
_showLocalNotification(title, body, message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _handleNotificationTap(Map<String, dynamic> message) {
|
|||
|
|
// 处理推送点击
|
|||
|
|
final type = message['extras']['type'];
|
|||
|
|
|
|||
|
|
switch (type) {
|
|||
|
|
case 'order':
|
|||
|
|
// 跳转到订单详情
|
|||
|
|
break;
|
|||
|
|
case 'asset':
|
|||
|
|
// 跳转到资产页面
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
// 跳转到首页
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future<void> _showLocalNotification(
|
|||
|
|
String title,
|
|||
|
|
String body,
|
|||
|
|
Map<String, dynamic> data,
|
|||
|
|
) async {
|
|||
|
|
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
|||
|
|
AndroidNotificationDetails(
|
|||
|
|
'monisuo_channel',
|
|||
|
|
'模拟所通知',
|
|||
|
|
channelDescription: '模拟所应用通知',
|
|||
|
|
importance: Importance.max,
|
|||
|
|
priority: Priority.high,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const NotificationDetails platformChannelSpecifics =
|
|||
|
|
NotificationDetails(android: androidPlatformChannelSpecifics);
|
|||
|
|
|
|||
|
|
await localNotifications.show(
|
|||
|
|
DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
|||
|
|
title,
|
|||
|
|
body,
|
|||
|
|
platformChannelSpecifics,
|
|||
|
|
payload: jsonEncode(data),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void _handleLocalNotificationTap(String? payload) {
|
|||
|
|
if (payload != null) {
|
|||
|
|
final data = jsonDecode(payload);
|
|||
|
|
_handleNotificationTap(data);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置别名(通常是用户ID)
|
|||
|
|
Future<void> setAlias(String userId) async {
|
|||
|
|
jpush.setAlias(userId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除别名
|
|||
|
|
Future<void> deleteAlias() async {
|
|||
|
|
jpush.deleteAlias();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置标签
|
|||
|
|
Future<void> setTags(List<String> tags) async {
|
|||
|
|
jpush.setTags(tags);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 在main.dart中初始化
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
void main() async {
|
|||
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|||
|
|
|
|||
|
|
// 初始化推送服务
|
|||
|
|
await PushService().init();
|
|||
|
|
|
|||
|
|
runApp(const MyApp());
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. 登录后设置别名
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// 用户登录成功后
|
|||
|
|
await PushService().setAlias(userId);
|
|||
|
|
|
|||
|
|
// 用户退出登录后
|
|||
|
|
await PushService().deleteAlias();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 后端推送接口
|
|||
|
|
|
|||
|
|
**后端需要提供推送接口**:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@PostMapping("/api/push/send")
|
|||
|
|
public Result<Void> sendPush(
|
|||
|
|
@RequestParam String userId,
|
|||
|
|
@RequestParam String title,
|
|||
|
|
@RequestParam String body,
|
|||
|
|
@RequestParam(required = false) Map<String, Object> extras
|
|||
|
|
) {
|
|||
|
|
// 调用极光推送API
|
|||
|
|
JPushClient jpushClient = new JPushClient(appKey, masterSecret);
|
|||
|
|
|
|||
|
|
PushPayload payload = PushPayload.newBuilder()
|
|||
|
|
.setPlatform(Platform.all())
|
|||
|
|
.setAudience(Audience.alias(userId))
|
|||
|
|
.setNotification(Notification.alert(body))
|
|||
|
|
.setMessage(Message.content(body))
|
|||
|
|
.setOptions(Options.newBuilder().setApnsProduction(false).build())
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
jpushClient.sendPush(payload);
|
|||
|
|
|
|||
|
|
return Result.success("推送成功");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 推送场景
|
|||
|
|
|
|||
|
|
### 1. 充值审批通知
|
|||
|
|
```dart
|
|||
|
|
{
|
|||
|
|
"title": "充值审批通过",
|
|||
|
|
"body": "您的充值订单已审批通过,金额: 100 USDT",
|
|||
|
|
"extras": {
|
|||
|
|
"type": "order",
|
|||
|
|
"orderNo": "F20260324001"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 提现审批通知
|
|||
|
|
```dart
|
|||
|
|
{
|
|||
|
|
"title": "提现审批完成",
|
|||
|
|
"body": "您的提现申请已处理,金额: 100 USDT",
|
|||
|
|
"extras": {
|
|||
|
|
"type": "order",
|
|||
|
|
"orderNo": "F20260324002"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 资产变动通知
|
|||
|
|
```dart
|
|||
|
|
{
|
|||
|
|
"title": "资产变动",
|
|||
|
|
"body": "您的账户余额发生变动",
|
|||
|
|
"extras": {
|
|||
|
|
"type": "asset",
|
|||
|
|
"userId": "123"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 测试
|
|||
|
|
|
|||
|
|
### 1. 测试推送
|
|||
|
|
```dart
|
|||
|
|
// 在开发环境中测试
|
|||
|
|
await PushService()._showLocalNotification(
|
|||
|
|
'测试标题',
|
|||
|
|
'测试内容',
|
|||
|
|
{'type': 'test'},
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 测试极光推送
|
|||
|
|
- 在极光推送控制台发送测试推送
|
|||
|
|
- 检查应用是否收到推送
|
|||
|
|
|
|||
|
|
## 注意事项
|
|||
|
|
|
|||
|
|
1. **权限申请**: Android需要申请通知权限
|
|||
|
|
2. **iOS证书**: 需要配置APNs证书
|
|||
|
|
3. **测试环境**: 开发时使用开发环境
|
|||
|
|
4. **生产环境**: 上线前切换到生产环境
|
|||
|
|
5. **用户隐私**: 首次启动时请求推送权限
|
|||
|
|
|
|||
|
|
## 成本估算
|
|||
|
|
|
|||
|
|
- 极光推送免费版:每月100万条推送
|
|||
|
|
- 对于中小规模应用完全够用
|
|||
|
|
- 如需更多,可以升级付费版本
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**集成时间**: 预计2-3小时
|
|||
|
|
**难度**: 中等
|
|||
|
|
**维护成本**: 低
|