412 lines
9.9 KiB
Markdown
412 lines
9.9 KiB
Markdown
|
|
# Monisuo 推送功能实现指南
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
本文档详细说明如何在Monisuo虚拟货币交易平台中实现推送通知功能。
|
|||
|
|
|
|||
|
|
## 架构设计
|
|||
|
|
|
|||
|
|
### 1. 推送服务选择
|
|||
|
|
|
|||
|
|
**推荐方案**: 极光推送 (JPush)
|
|||
|
|
|
|||
|
|
**理由**:
|
|||
|
|
- 国内推送到达率高(>95%)
|
|||
|
|
- 支持iOS和Android双平台
|
|||
|
|
- 提供完整的Flutter SDK
|
|||
|
|
- 免费版支持100万条/月
|
|||
|
|
- 提供REST API和SDK两种方式
|
|||
|
|
|
|||
|
|
**备选方案**: 个推 (Getui)
|
|||
|
|
|
|||
|
|
## 2. 系统架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[管理员审批] → [后端服务] → [JPush服务器] → [用户手机]
|
|||
|
|
↓ ↓ ↓ ↓
|
|||
|
|
创建推送任务 调用JPush API 推送消息 显示通知
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 实现步骤
|
|||
|
|
|
|||
|
|
### 步骤1: 注册极光推送
|
|||
|
|
|
|||
|
|
1. 访问 https://www.jiguang.cn/
|
|||
|
|
2. 注册账号并登录
|
|||
|
|
3. 创建应用
|
|||
|
|
4. 获取AppKey和Master Secret
|
|||
|
|
|
|||
|
|
### 步骤2: 后端集成
|
|||
|
|
|
|||
|
|
#### 2.1 添加依赖
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<dependency>
|
|||
|
|
<groupId>cn.jpush</groupId>
|
|||
|
|
<artifactId>jpush-client</artifactId>
|
|||
|
|
<version>3.5.8</version>
|
|||
|
|
</dependency>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2 配置文件
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# application.yml
|
|||
|
|
jpush:
|
|||
|
|
app-key: your-app-key
|
|||
|
|
master-secret: your-master-secret
|
|||
|
|
production: false # 开发环境使用false
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.3 推送服务类
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.it.rattan.monisuo.service;
|
|||
|
|
|
|||
|
|
import cn.jiguang.common.resp.DefaultResult;
|
|||
|
|
import cn.jpush.api.JPushClient;
|
|||
|
|
import cn.jpush.api.push.PushResult;
|
|||
|
|
import cn.jpush.api.push.model.Message;
|
|||
|
|
import cn.jpush.api.push.model.Notification;
|
|||
|
|
import cn.jpush.api.push.model.Platform;
|
|||
|
|
import cn.jpush.api.push.model.PushPayload;
|
|||
|
|
import cn.jpush.api.push.model.audience.Audience;
|
|||
|
|
import org.springframework.beans.factory.annotation.Value;
|
|||
|
|
import org.springframework.stereotype.Service;
|
|||
|
|
|
|||
|
|
import java.util.Map;
|
|||
|
|
|
|||
|
|
@Service
|
|||
|
|
public class JPushService {
|
|||
|
|
|
|||
|
|
@Value("${jpush.app-key}")
|
|||
|
|
private String appKey;
|
|||
|
|
|
|||
|
|
@Value("${jpush.master-secret}")
|
|||
|
|
private String masterSecret;
|
|||
|
|
|
|||
|
|
@Value("${jpush.production}")
|
|||
|
|
private boolean production;
|
|||
|
|
|
|||
|
|
private JPushClient jPushClient;
|
|||
|
|
|
|||
|
|
public JPushService() {
|
|||
|
|
jPushClient = new JPushClient(masterSecret, appKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 向指定用户发送推送
|
|||
|
|
*/
|
|||
|
|
public void sendToUser(String userId, String title, String content, Map<String, String> extras) {
|
|||
|
|
try {
|
|||
|
|
PushPayload payload = PushPayload.newBuilder()
|
|||
|
|
.setPlatform(Platform.all())
|
|||
|
|
.setAudience(Audience.alias(userId))
|
|||
|
|
.setNotification(Notification.alert(content))
|
|||
|
|
.setMessage(Message.content(content).setExtras(extras))
|
|||
|
|
.setOptions(cn.jpush.api.push.model.Options.newBuilder()
|
|||
|
|
.setApnsProduction(production)
|
|||
|
|
.build())
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
PushResult result = jPushClient.sendPush(payload);
|
|||
|
|
|
|||
|
|
if (result.statusCode == 200) {
|
|||
|
|
System.out.println("推送成功: " + result.msgId);
|
|||
|
|
} else {
|
|||
|
|
System.err.println("推送失败: " + result.statusCode);
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
e.printStackTrace();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 向所有用户发送推送
|
|||
|
|
*/
|
|||
|
|
public void sendToAll(String title, String content) {
|
|||
|
|
try {
|
|||
|
|
PushPayload payload = PushPayload.newBuilder()
|
|||
|
|
.setPlatform(Platform.all())
|
|||
|
|
.setAudience(Audience.all())
|
|||
|
|
.setNotification(Notification.alert(content))
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
jPushClient.sendPush(payload);
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
e.printStackTrace();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.4 推送控制器
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package com.it.rattan.monisuo.controller;
|
|||
|
|
|
|||
|
|
import com.it.rattan.monisuo.common.Result;
|
|||
|
|
import com.it.rattan.monisuo.service.JPushService;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.web.bind.annotation.*;
|
|||
|
|
|
|||
|
|
import java.util.HashMap;
|
|||
|
|
import java.util.Map;
|
|||
|
|
|
|||
|
|
@RestController
|
|||
|
|
@RequestMapping("/api/push")
|
|||
|
|
public class PushController {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private JPushService jPushService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送充值审批通知
|
|||
|
|
*/
|
|||
|
|
@PostMapping("/deposit/approval")
|
|||
|
|
public Result<Void> sendDepositApproval(
|
|||
|
|
@RequestParam String userId,
|
|||
|
|
@RequestParam String orderNo,
|
|||
|
|
@RequestParam String amount,
|
|||
|
|
@RequestParam boolean approved) {
|
|||
|
|
|
|||
|
|
String title = approved ? "充值审批通过" : "充值审批驳回";
|
|||
|
|
String content = approved
|
|||
|
|
? "您的充值订单已审批通过,金额: " + amount + " USDT"
|
|||
|
|
: "您的充值订单已被驳回,金额: " + amount + " USDT";
|
|||
|
|
|
|||
|
|
Map<String, String> extras = new HashMap<>();
|
|||
|
|
extras.put("type", "order");
|
|||
|
|
extras.put("orderNo", orderNo);
|
|||
|
|
extras.put("approved", String.valueOf(approved));
|
|||
|
|
|
|||
|
|
jPushService.sendToUser(userId, title, content, extras);
|
|||
|
|
|
|||
|
|
return Result.success("推送成功");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送提现审批通知
|
|||
|
|
*/
|
|||
|
|
@PostMapping("/withdraw/approval")
|
|||
|
|
public Result<Void> sendWithdrawApproval(
|
|||
|
|
@RequestParam String userId,
|
|||
|
|
@RequestParam String orderNo,
|
|||
|
|
@RequestParam String amount,
|
|||
|
|
@RequestParam boolean approved) {
|
|||
|
|
|
|||
|
|
String title = approved ? "提现审批通过" : "提现审批驳回";
|
|||
|
|
String content = approved
|
|||
|
|
? "您的提现申请已处理,金额: " + amount + " USDT"
|
|||
|
|
: "您的提现申请已被驳回,金额: " + amount + " USDT";
|
|||
|
|
|
|||
|
|
Map<String, String> extras = new HashMap<>();
|
|||
|
|
extras.put("type", "order");
|
|||
|
|
extras.put("orderNo", orderNo);
|
|||
|
|
extras.put("approved", String.valueOf(approved));
|
|||
|
|
|
|||
|
|
jPushService.sendToUser(userId, title, content, extras);
|
|||
|
|
|
|||
|
|
return Result.success("推送成功");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 步骤3: 在审批流程中集成推送
|
|||
|
|
|
|||
|
|
修改 `FundService.approve()` 方法:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Transactional
|
|||
|
|
public void approve(...) {
|
|||
|
|
// ... 原有审批逻辑 ...
|
|||
|
|
|
|||
|
|
// 审批成功后发送推送
|
|||
|
|
if (order.getType() == 1) {
|
|||
|
|
// 充值审批
|
|||
|
|
jPushService.sendToUser(
|
|||
|
|
order.getUserId().toString(),
|
|||
|
|
status == 2 ? "充值审批通过" : "充值审批驳回",
|
|||
|
|
"订单号: " + orderNo + ", 金额: " + order.getAmount() + " USDT",
|
|||
|
|
Map.of("type", "order", "orderNo", orderNo)
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
// 提现审批
|
|||
|
|
jPushService.sendToUser(
|
|||
|
|
order.getUserId().toString(),
|
|||
|
|
status == 2 ? "提现审批通过" : "提现审批驳回",
|
|||
|
|
"订单号: " + orderNo + ", 金额: " + order.getAmount() + " USDT",
|
|||
|
|
Map.of("type", "order", "orderNo", orderNo)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 推送场景设计
|
|||
|
|
|
|||
|
|
### 场景1: 充值审批通过
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"userId": "5",
|
|||
|
|
"title": "充值审批通过",
|
|||
|
|
"content": "您的充值订单已审批通过,金额: 100 USDT",
|
|||
|
|
"extras": {
|
|||
|
|
"type": "order",
|
|||
|
|
"orderNo": "F20260324001",
|
|||
|
|
"approved": "true"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**触发时机**: 管理员点击"审批通过"按钮后
|
|||
|
|
|
|||
|
|
### 场景2: 提现审批驳回
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"userId": "5",
|
|||
|
|
"title": "提现审批驳回",
|
|||
|
|
"content": "您的提现申请已被驳回,金额: 50 USDT",
|
|||
|
|
"extras": {
|
|||
|
|
"type": "order",
|
|||
|
|
"orderNo": "F20260324002",
|
|||
|
|
"approved": "false",
|
|||
|
|
"reason": "余额不足"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**触发时机**: 管理员点击"审批驳回"按钮后
|
|||
|
|
|
|||
|
|
### 场景3: 系统公告
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"title": "系统维护通知",
|
|||
|
|
"content": "系统将于今晚22:00-23:00进行维护,届时暂停服务",
|
|||
|
|
"extras": {
|
|||
|
|
"type": "announcement"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**触发时机**: 后台管理页面发布系统公告
|
|||
|
|
|
|||
|
|
## 5. 测试方法
|
|||
|
|
|
|||
|
|
### 5.1 本地测试
|
|||
|
|
|
|||
|
|
1. **启动后端服务**
|
|||
|
|
```bash
|
|||
|
|
cd ~/Desktop/projects/monisuo
|
|||
|
|
mvn spring-boot:run
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **发送测试推送**
|
|||
|
|
```bash
|
|||
|
|
curl -X POST http://localhost:5010/api/push/deposit/approval \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d "userId=5&orderNo=F20260324001&amount=100&approved=true"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **检查推送日志**
|
|||
|
|
```bash
|
|||
|
|
tail -f logs/app.log | grep "推送"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 极光推送控制台测试
|
|||
|
|
|
|||
|
|
1. 登录极光推送控制台
|
|||
|
|
2. 选择应用
|
|||
|
|
3. 点击"推送" -> "发送通知"
|
|||
|
|
4. 选择"别名"推送
|
|||
|
|
5. 输入用户别名(userId)
|
|||
|
|
6. 填写标题和内容
|
|||
|
|
7. 点击"立即发送"
|
|||
|
|
|
|||
|
|
## 6. 监控与统计
|
|||
|
|
|
|||
|
|
### 6.1 推送统计
|
|||
|
|
|
|||
|
|
在极光推送控制台可以查看:
|
|||
|
|
- 推送到达率
|
|||
|
|
- 推送打开率
|
|||
|
|
- 推送失败原因
|
|||
|
|
- 用户活跃度
|
|||
|
|
|
|||
|
|
### 6.2 日志记录
|
|||
|
|
|
|||
|
|
在 `application.yml` 中添加:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
logging:
|
|||
|
|
level:
|
|||
|
|
com.it.rattan.monisuo.service.JPushService: DEBUG
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 7. 费用估算
|
|||
|
|
|
|||
|
|
### 7.1 免费版
|
|||
|
|
- 100万条推送/月
|
|||
|
|
- 适用于中小规模应用
|
|||
|
|
- 基本统计功能
|
|||
|
|
|
|||
|
|
### 7.2 付费版
|
|||
|
|
- 无限制推送
|
|||
|
|
- 高级统计功能
|
|||
|
|
- 优先技术支持
|
|||
|
|
|
|||
|
|
**推荐**: 初期使用免费版,用户量增长后再考虑升级
|
|||
|
|
|
|||
|
|
## 8. 安全性考虑
|
|||
|
|
|
|||
|
|
### 8.1 API密钥保护
|
|||
|
|
- AppKey和Master Secret不要提交到Git
|
|||
|
|
- 使用环境变量或配置中心管理
|
|||
|
|
|
|||
|
|
### 8.2 推送内容审核
|
|||
|
|
- 敏感词过滤
|
|||
|
|
- 内容长度限制
|
|||
|
|
|
|||
|
|
### 8.3 推送频率限制
|
|||
|
|
- 避免频繁推送打扰用户
|
|||
|
|
- 设置推送间隔(如1分钟内不重复推送)
|
|||
|
|
|
|||
|
|
## 9. 最佳实践
|
|||
|
|
|
|||
|
|
### 9.1 推送时机
|
|||
|
|
- 充值/提现审批:立即推送
|
|||
|
|
- 系统公告:定时推送
|
|||
|
|
- 资产变动:延迟推送(汇总后推送)
|
|||
|
|
|
|||
|
|
### 9.2 推送内容
|
|||
|
|
- 标题简洁明了(<20字)
|
|||
|
|
- 内容包含关键信息(订单号、金额等)
|
|||
|
|
- 避免过度营销
|
|||
|
|
|
|||
|
|
### 9.3 错误处理
|
|||
|
|
- 推送失败时记录日志
|
|||
|
|
- 重大通知支持重试
|
|||
|
|
- 提供站内信作为备选
|
|||
|
|
|
|||
|
|
## 10. 后续优化
|
|||
|
|
|
|||
|
|
### 10.1 站内信系统
|
|||
|
|
- 推送失败时存入数据库
|
|||
|
|
- 用户可以在应用内查看历史消息
|
|||
|
|
|
|||
|
|
### 10.2 推送模板
|
|||
|
|
- 使用模板管理推送内容
|
|||
|
|
- 支持多语言推送
|
|||
|
|
|
|||
|
|
### 10.3 推送策略
|
|||
|
|
- 根据用户活跃度调整推送频率
|
|||
|
|
- 支持用户自定义推送设置
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档版本**: V1.0
|
|||
|
|
**最后更新**: 2026-03-24
|
|||
|
|
**作者**: Monisuo开发团队
|