图1 个推推送服务框架图
原创作者http://www.jianshu.com/p/a9acc30e9ba6

首先,按照个推SDK集成指南配置好一个完整的工程。或者直接下载现有工程(需要修改bundle identifier、kGtAppId、kGtAppKey、kGtAppSecret)。
如有错误和待完善的地方,还请指正。

新建推送:

图2 注:Badge参数为icon的角标

本文将介绍对推送消息的两种处理方式。
在接收到推送消息时,分为3种情况,本文还将后两者细分为两种情况:

  1. APP处于前台
    1.1 APP接收到推送后推送后首先弹出一个Alert提示是否跳转页面
  2. APP处于后台
    2.1 点击通知栏使APP进入前台后,直接跳转页面
    2.2 点击icon图标使APP进入前台后,不作操作
  3. APP处于关闭状态
    3.1 点击通知栏启动APP,直接跳转页面
    3.2 点击icon图标启动APP,不作操作

方式一:

首先为AppDelegate添加一个属性,

// 用来判断是否是通过点击通知栏开启(唤醒)APP
@property (nonatomic) BOOL isLaunchedByNotification;

当通过点击通知栏来启动或唤醒APP时,会调用didReceiveRemoteNotification:方法,在该方法里将isLaunchedByNotification的值置为YES:

/** APP已经接收到“远程”通知(推送) - 透传推送消息  */
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {// 当APP处于后台或者关闭状态,点击通知栏就会先走这个方法,再使得个推SDK收到透传消息回调// 处理APNs代码,通过userInfo可以取到推送的信息(包括内容,角标,自定义参数等)。如果需要弹窗等其他操作,则需要自行编码。NSLog(@"\n>>>APP已经接收到“远程”通知(推送)[Receive RemoteNotification - Background Fetch]:%@\n\n",userInfo);completionHandler(UIBackgroundFetchResultNewData);self.isLaunchedByNotification = YES;
}

然后会调用以下方法(当APP处于前台时会直接调用此方法),其中payloadData的值转为NSString对象即为图2中的消息内容里的JSON数据:

/** SDK收到透传消息回调 */
- (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId {// 收到个推消息NSString *payloadMsg = nil;if (payloadData) {payloadMsg = [[NSString alloc] initWithBytes:payloadData.byteslength:payloadData.lengthencoding:NSUTF8StringEncoding];}// 当app不在前台时,接收到的推送消息offLine值均为YES// 判断app是否是点击通知栏消息进行唤醒或开启// 如果是点击icon图标使得app进入前台,则不做操作,并且同一条推送通知,此方法只执行一次if (offLine) {// 离线消息,说明app接收推送时不在前台if (self.isLaunchedByNotification) {// app是通过点击通知栏进入前台[[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_PUSH object:nil userInfo:@{kNOTIFICATION_PUSH : payloadMsg}];self.isLaunchedByNotification = NO;} else {// app是通过点击icon进入前台,在这里不做操作}} else if(!self.isLaunchedByNotification) {// app已经处于前台,提示框提示[[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_ALERT object:nil userInfo:@{kNOTIFICATION_ALERT : payloadMsg}];}elf.isLaunchedByNotification = NO;}
}

2.2和3.2情况下的问题

这两种情况下,如果用户不点击通知栏而是点击桌面icon图标启动或唤起APP,会直接调用GeTuiSdkDidReceivePayloadData:方法,根据本文的方式处理的话确实不会有任何操作,但是通知栏的消息仍然存在,如果再次点击通知栏消息,仍会调动didReceiveRemoteNotification:方法,但是不会再调用GeTuiSdkDidReceivePayloadData:方法,这样的话isLaunchedByNotification的值会被置为YES,而且无法再被置为NO。
解决办法:在app进入前台后通过将Badge角标置为0来移除通知栏信息,代码如下:

- (void)applicationDidBecomeActive:(UIApplication *)application {// 这里的写法是为了在app进入前台后,清除通知栏消息NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;badge = badge == 1 ? 2 : 1;// 这里经过两次赋值才可以移除通知栏消息[UIApplication sharedApplication].applicationIconBadgeNumber = badge;[UIApplication sharedApplication].applicationIconBadgeNumber = 0;//    // 下面这个方法只有Badge角标不为0时才执行,如果个推推送时Badge为0,那么不会走下面的方法
//    if (badge) {//
//        badge = badge == 1 ? 2 : 1;
//        [UIApplication sharedApplication].applicationIconBadgeNumber = badge;
//        [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
//    }
}

方式二:

这种方式默认在通过点击icon使app进入前台时不做操作。
当通过点击通知栏来启动或唤醒APP时,会调用didReceiveRemoteNotification:方法,接收到的推送内容包含在userInfo参数里,可以在此方法里对推送消息进行操作:

/** APP已经接收到“远程”通知(推送) - 透传推送消息  */
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {// 当APP处于后台或者关闭状态,点击通知栏就会先走这个方法,再使得个推SDK收到透传消息回调// 处理APNs代码,通过userInfo可以取到推送的信息(包括内容,角标,自定义参数等)。如果需要弹窗等其他操作,则需要自行编码。NSLog(@"\n>>>APP已经接收到“远程”通知(推送)[Receive RemoteNotification - Background Fetch]:%@\n\n",userInfo);completionHandler(UIBackgroundFetchResultNewData);// app是通过点击通知栏进入前台[[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_PUSH object:nil userInfo:@{kNOTIFICATION_PUSH : payloadMsg}];
}

同时,由于系统方法调用完成后,个推仍会调用一次GeTuiSdkDidReceivePayloadData:方法,需要在GeTuiSdkDidReceivePayloadData:方法里判断当前消息是否为offLine离线消息,如果是离线消息则不做任何处理:

/** SDK收到透传消息回调 */
- (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId {// 收到个推消息NSString *payloadMsg = nil;if (payloadData) {payloadMsg = [[NSString alloc] initWithBytes:payloadData.byteslength:payloadData.lengthencoding:NSUTF8StringEncoding];}// 当app在前台时,接收到的推送消息offLine值均为NO// 对于离线消息,这里不做操作if (!offLine) {// app已经处于前台,提示框提示[[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_ALERT object:nil userInfo:@{kNOTIFICATION_ALERT : payloadMsg}];}
}

对于角标的处理可以参考方式一中的处理方法。

前言 在去年的苹果大会上,苹果带来的iOS 10 系统中将之前繁杂的推送通知统一成UserNotifications.framework 来集中管理和使用通知功能,还增加一些实用的功能——撤回单条通知、更新已展示通知、中途修改通知内容、在通知中显示多媒体资源、自定义UI等功能。

前言

在去年的苹果大会上,苹果带来的iOS 10 系统中将之前繁杂的推送通知统一成UserNotifications.framework 来集中管理和使用通知功能,还增加一些实用的功能——撤回单条通知、更新已展示通知、中途修改通知内容、在通知中显示多媒体资源、自定义UI等功能。
那么在ios10之前,ios的消息推送是怎么分类的呢?

ios 10之前

在ios之前,iOS推送分为Local Notifications(本地推送) 和 Remote Notifications(远程推送)。

本地推送

不需要服务器支持(无需联网)就能发出的推送通知,app本地创建通知,加入到系统的Schedule里,如果触发器条件达成时会推送相应的消息内容,如常见的定时任务闹钟等。

使用上也是非常简单。

/*@property(nonatomic,copy) NSDate *fireDate;@property(nonatomic,copy) NSTimeZone *timeZone; 时区@property(nonatomic) NSCalendarUnit repeatInterval; 重复间隔(枚举)@property(nonatomic,copy) NSCalendar *repeatCalendar; 重复日期(NSCalendar)@property(nonatomic,copy) CLRegion *region 设置区域(设置当进入某一个区域时,发出一个通知)@property(nonatomic,assign) BOOL regionTriggersOnce YES,只会在第一次进入某一个区域时发出通知.NO,每次进入该区域都会发通知@property(nonatomic,copy) NSString *alertBody;      @property(nonatomic) BOOL hasAction;                是否隐藏锁屏界面设置的alertAction@property(nonatomic,copy) NSString *alertAction;    设置锁屏界面一个文字@property(nonatomic,copy) NSString *alertLaunchImage;   启动图片@property(nonatomic,copy) NSString *alertTitle@property(nonatomic,copy) NSString *soundName;@property(nonatomic) NSInteger applicationIconBadgeNumber;@property(nonatomic,copy) NSDictionary *userInfo; // 设置通知的额外的数据*/- (IBAction)addLocalNote:(id)sender {// 创建一个本地通知UILocalNotification *localNote = [[UILocalNotification alloc] init];// 设置本地通知的一些属性(通知发出的时间/通知的内容)// 设置通知发出的时间localNote.fireDate = [NSDate dateWithTimeIntervalSinceNow:5.0];//设置通知的内容localNote.alertBody = @"吃饭了吗?";//设置锁屏界面的文字localNote.alertAction = @"查看具体的消息";//设置锁屏界面alertAction是否有效localNote.hasAction = YES;//设置通过点击通知打开APP的时候的启动图片(无论字符串设置成什么内容,都是显示应用程序的启动图片)localNote.alertLaunchImage = @"111";//设置通知中心通知的标题localNote.alertTitle = @"222222222222";//设置音效localNote.soundName = @"buyao.wav";//设置应用程序图标右上角的数字localNote.applicationIconBadgeNumber = 1;//设置通知之后的属性localNote.userInfo = @{@"name" : @"张三", @"toName" : @"李四"};//调度通知[[UIApplication sharedApplication] scheduleLocalNotification:localNote];
}

当用户点击本地推送通知的时候,会自动打开app,这里有2种情况:app在后台运行,或者被系统进程杀死,对于这两种情况,我们怎么处理呢?

app后台运行


这时候我们只需要调用下AppDelegate方法即可。代码实现

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{// 跳转逻辑if (application.applicationState == UIApplicationStateActive) return;if (application.applicationState == UIApplicationStateInactive) {// 当应用在后台收到本地通知时执行的跳转代码[self jumpToSession];}NSLog(@"local notifacation %@", notification);
}- (void)jumpToSession
{UILabel *redView = [[UILabel alloc] init];redView.backgroundColor = [UIColor redColor];redView.frame = CGRectMake(0, 100, 300, 400);redView.numberOfLines = 0;// redView.text = [NSString stringWithFormat:@"%@", launchOptions];[self.window.rootViewController.view addSubview:redView];
}

app被杀死


对于app被杀死的情况,要先启动app,启动完毕会调用AppDelegate方法。
需要特别注意的是:在iOS8.0以后本地通知有了一些变化,如果要使用本地通知,需要得到用户的许可。
部分代码实现:

#define IS_iOS8 ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)@interface AppDelegate ()@end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {/*UIUserNotificationTypeNone    = 0,      不发出通知UIUserNotificationTypeBadge   = 1 << 0, 改变应用程序图标右上角的数字UIUserNotificationTypeSound   = 1 << 1, 播放音效UIUserNotificationTypeAlert   = 1 << 2, 是否运行显示横幅*/[application setApplicationIconBadgeNumber:0];if (IS_iOS8) {UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound categories:nil];[application registerUserNotificationSettings:settings];}// 如果是正常启动应用程序,那么launchOptions参数是null,其他方式需要对launchOptions设置if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {// 当被杀死状态收到本地通知时执行的跳转代码// [self jumpToSession];UILabel *redView = [[UILabel alloc] init];redView.backgroundColor = [UIColor redColor];redView.frame = CGRectMake(0, 100, 300, 400);redView.numberOfLines = 0;redView.text = [NSString stringWithFormat:@"%@", launchOptions];[self.window.rootViewController.view addSubview:redView];}return YES;
}

远程推送

远程推送指从远程服务器推送给客户端的通知(需要联网),远程推送服务一般采用苹果的APNS (Apple Push Notification Service)。

要实现远程推送,一般会涉及到三个阶段:

  1. APNS Pusher应用程序把要发送的消息、目的iPhone的标识打包,发给APNS。
  2. APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone。
  3. iPhone把发来的消息传递给相应的应用程序, 并且按照设定弹出Push通知。

基本配置

条件:新建一个对应你bundle的push 证书,打开Push Notifications 开关(XCode7不打开也可以正常使用,XCode8以后必须打开)。

代码实现:
注册接受APNs通知。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) {// 1.注册UserNotification,以获取推送通知的权限UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge categories:nil];[application registerUserNotificationSettings:settings];// 2.注册远程推送[application registerForRemoteNotifications];} else {[application registerForRemoteNotificationTypes:UIRemoteNotificationTypeNewsstandContentAvailability | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];}return YES;
}

调用AppDelegate方法,获取到用户的deviceToken。

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{// <32e7cf5f 8af9a8d4 2a3aaa76 7f3e9f8e 1f7ea8ff 39f50a2a e383528d 7ee9a4ea>// <32e7cf5f 8af9a8d4 2a3aaa76 7f3e9f8e 1f7ea8ff 39f50a2a e383528d 7ee9a4ea>NSLog(@"%@", deviceToken.description);
}

推送通知,和本地通知一样有两种状况。

// 当接受到远程退职时会执行该方法(当进入前台或者应用程序在前台)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{NSLog(@"%@", userInfo);UIView *redView = [[UIView alloc] init];redView.backgroundColor = [UIColor redColor];redView.frame = CGRectMake(100, 100, 100, 100);[self.window.rootViewController.view addSubview:redView];
}

苹果建议使用方法

/*1.开启后台模式2.调用completionHandler,告诉系统你现在是否有新的数据更新3.userInfo添加一个字段:"content-available" : "1" : 只要添加了该字段,接受到通知都会在后台运行*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{NSLog(@"%@", userInfo);UIView *redView = [[UIView alloc] init];redView.backgroundColor = [UIColor redColor];redView.frame = CGRectMake(100, 100, 100, 100);[self.window.rootViewController.view addSubview:redView];completionHandler(UIBackgroundFetchResultNewData);
}

UserNotitfication

iOS10 中统一了本地推送和远程推送的 API,在 UserNotifications.framework 来统一处理与推送相关任务,并增加了图片、音频、视频,自定义通知 UI 等新特性。

通知界面

多媒体

在此次版本中,iOS10 不仅新增消息的3dtouch等,还对图片、音频、视频等多媒体做了改进和优化。

类型 限制大小
图片  10M
音频  5M
视频  50M

多媒体推送代码:

if #available(iOS 10.0, *) {let content = UNMutableNotificationContent()content.title = "iOS10 推送测试"content.body = "附件"content.userInfo = ["icon":"1","mutable-content":1]content.categoryIdentifier = "InputSomething"let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)let requestIdentifier = "imageLocal"if let imageURL = Bundle.main.url(forResource: "avatar@2x", withExtension: "png"), let attachment = try? UNNotificationAttachment(identifier: "imageAttachment", url: imageURL, options: nil) {content.attachments = [attachment]}let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) inif (error != nil) {print("error: \(error.debugDescription)")}})}

通常在做多媒体自定义推送的时候,一般会用到UNNotificationServiceExtension应用扩展,通过在 payload 中增加 mutable-content 字段来触发扩展。

{"aps":{"alert":"IOS10 推送测试","sound":"default","badge":1,"mutable-content":1,"category":"InputSomething"},"image":"https://ws1.sinaimg.cn/mw690/934b5ef8gw1fapg2ssteej20oz0oz420.jpg"
}

当推送达到 app 时,会启动扩展并回调 didReceive 方法。在该方法里面可以对推送的 UNMutableNotificationContent 做出相应的修改。在 didReceive 回调方法中的 request 包含了推送的具体信息,可以通过其 userInfo 属性来解析出多媒体的 url。

let imageURL = Bundle.main.url(forResource: "lufei", withExtension: "jpg")

值得注意的是这里 Bundle 指的是扩展的沙盒,不是 app 的沙盒,所以资源的路径要正确。

而读取远程资源比读取本地资源一般要多一步保存操作。

private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?) -> Void) {let task = URLSession.shared.dataTask(with: url, completionHandler: {data, res, error invar localURL: URL? = 下载完之后保存到本地并返回本地的 urlhandler(localURL)})task.resume()}

得到本地的 url 之后操作就一样了,都是通过 url 来生成一个 UNNotificationAttachment 对象。一切都操作完之后将这个 UNMutableNotificationContent 对象返还 contentHandler(bestAttemptContent)。

自定义界面

除了上述功能外,苹果还新增了自定义界面,这个绝对是大招。

其中上面的黄色区域可以理解成一个 ViewController 操作,下面绿色部分就是 Title 之类的显示内容。这部分是可以隐藏的。在扩展的目录下的 info.plist 编辑一些界面相关的东西。

说明:

  • UNNotificationExtensionCategory 触发 Extension 的 category 这里需要在注册才能有效的触发 字符串类型
  • UNNotificationExtensionInitialContentSizeRatio 上图黄色区域的长宽比,float 类型
  • UNNotificationExtensionDefaultContentHidden 默认内容是否隐藏,Bool 类型

个推推送,APP接收到推送后的操作(前台与非前台的处理不同)相关推荐

  1. iOS 当APP接收到推送消息时,对推送消息进行处理

    结合最近做的项目来总结一下,在我们接收到推送通知后我们将要怎么处理这条推送消息,也为以后再做相关推送的时候能够更快处理. 首先要说明两个概念:本地通知和远程推送.本地通知是由本地应用触发的,接收会比较 ...

  2. android极光推送声音,解决极光推送后台接收到推送消息,无提示音不震动

    ----以下方法前提确保你的设备,打开了此APP的通知提示. 正题------ 我遇到的情况是,安卓客户端是有提示音和振动提示的. iOS端在极光的web后台编辑消息 推送到我的手机有提示音和振动,通 ...

  3. C#微信公众号开发系列教程五(接收事件推送与消息排重)

    C#微信公众号开发系列教程五(接收事件推送与消息排重) 原文:C#微信公众号开发系列教程五(接收事件推送与消息排重) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续 ...

  4. APP消息智能推送有什么特点

    虽然在这个互联网飞速发展的时代,越来越多的APP被安装在用户手机上.但除了一些极其频繁的APP,如微信和淘宝,大多数APP每天都静静地躺在移动桌面的小角落里,被动地等待用户醒来. 要唤醒这些客户,信息 ...

  5. vue-admin websocket接收消息推送+语音提示(详细代码)

    websocket接收消息推送+语音提示 这个是同事的代码,我拿来记录一下,希望以后可以看得懂-- utils/websocket.js const audioUrl = require('@/ass ...

  6. PHP7.0微信公众平台开发4: 实例一:接收普通消息和接收事件推送

    PHP7.0微信公众平台开发4: 实例一:接收普通消息和接收事件推送 1. API接口的声明 2. PHP脚本代码 3. 运行结果 由于消息类型有多钟,本文中,"接收普通消息"我以 ...

  7. 微信公众号的二次开发(三、接收事件推送获取用户信息)

    在上篇<微信公众号的二次开发(二 自定义菜单的创建)>中我们介绍了自定义菜单的创建.本篇文章将介绍如何通过接收事件推送来获取用户信息.首先我们阅读官方文档: 根据官方文档的介绍 微信公众号 ...

  8. 微信公众号接收事件推送

    功能 当开发者在微信开放平台启用了服务器配置后,在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信 ...

  9. C#公众平台(二)—— 接收事件推送之关注回调

    在C#公众平台(一)-- 接入配置 写了公众平台的基本配置,这篇文章讲的是关注回调事件. 基础帮助类 接收事件推送文档 接入指南 /// <summary>/// WeChatEvent ...

最新文章

  1. 小眼睛适合大框还是小框眼镜_北京潘家园眼镜城良心商家推荐
  2. 【机器学习PAI实践六】金融贷款发放预测
  3. PHP的pathinfo()
  4. OKWatchDog 打造一个安全的容器类
  5. 手把手教你用直方图、饼图和条形图做数据分析(Python代码)
  6. 阶段3 3.SpringMVC·_06.异常处理及拦截器_5 SpringMVC拦截器之编写controller
  7. IIS Rewrite配置与 Rewrite.dll下载
  8. 该死的配置系统未能初始化
  9. 3种Flink State Backed| 你该用哪个?
  10. 武汉java软谋教育坑吗_软谋在线教育诚招php,java,.net,设计师讲师(可兼职)...
  11. 【MQ-2 可燃气体和烟雾传感器与 Arduino 配合使用】
  12. python爬虫详解(七)——使用爬虫查看实时疫情状况
  13. Codeforces Round #548 (Div. 2) C. Edgy Trees(并查集+快速幂)
  14. anaconda linux环境变量,配置anaconda环境(linux)
  15. 【华为机试真题详解】开心消消乐【2022 Q4 | 100分】
  16. 微信公众号发送小程序卡片_微信公众号群发文章支持添加小程序卡片
  17. 玩转数据可视化之R语言ggplot2:(三)ggplot2实现将多张图放在一起,包括并排和插图绘制(快速入门)
  18. linux桌面环境占用内存对比,很轻很强大:轻量级桌面环境比较
  19. Matlab代理模型工具箱汇总及下载
  20. Dockerharbor安装 镜像私有仓库上床和拉取

热门文章

  1. 教你如何用duilib实现控件可拖动,可拖拽
  2. matplotlib模块的pyplot子模块绘制余切函数
  3. nginx的rewrite详解
  4. 【计算机网络】实验2:常见网络命令操作
  5. couchbase的使用(springboot连接couchbase,node.js连接couchbase)
  6. 附指南原文下载-《GB/T 39725-2020 信息安全技术 健康医疗数据安全指南》解读(一)
  7. PCIe扫盲——高级错误报告AER(一)
  8. 思科防火墙多端口映射多端口
  9. 牛客挑战赛42 A.小睿睿的数列
  10. matlab2019使用simulink对控制系统进行仿真