iOS 原生推送+PushKit+本地推送(iOS8-iOS12)

iOS推送原生实现方法,本文主要实现iOS端iOS8+系统的代码内容,另外还有iOS10之后新的框架pushKit使用方法。

注意

  • 常规推送证书和pushKit推送证书是一样的;
  • 他们后台推送方法是一样的,接口通用,代码通用;
  • 但是生成的token不同,手机端接收一个是有界面提示,一个是后台提示;
  • 项目中可以混用,或用pushKit+本地通知,实现同样效果;
  • 特别强调:这些证书是后台使用,给前台xcode,打包,上线,调试证书没有半毛钱关系,xcode9后可以用自动管理,什么都不需要动,不懂的自行搜索。

1.证书创建

  • 传统推送证书:Apple Push Notification service SSL (Sandbox & Production)(自行搜索)
  • pushKit证书:VoIP Services Certificate(只有一个生产证书,自行百度)
  • 证书创建好,下载安装到钥匙串里面,(这些证书是后台使用,给前台xcode,打包,上线,调试证书没有半毛钱关系)

2.证书导出p12文件,或pem。

  • 由于两个证书操作方式一模一样,使用介绍一个就可以;
  • 导出p12,Java后台使用,或导出两个p12(公钥,密钥,导出pem PHP后台使用)
  • 生成方式,请自行搜索,下面介绍pem证书生成方式
//在桌面创建一个临时文件夹
cd ~/Desktop;
mkdir Temp_Pem;
cd ~/Desktop/Temp_Pem

//cert.p12  key.p12 是推送证书导出的 公钥 秘钥文件,记得文件copy到Temp_Pem文件夹下
openssl pkcs12 -clcerts -nokeys -out ~/Desktop/Temp_Pem/apns-dev-cert.pem -in cert.p12;
openssl pkcs12 -nocerts -out ~/Desktop/Temp_Pem/apns-dev-key.pem -in key.p12;
# 移除密码(不想移除可以不用执行这个)
# openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem
# 合成最终pem文件
cat apns-dev-cert.pem apns-dev-key.pem > apns-dev.pem

3.后台PHP推送代码

本文介绍只使用PHP代码推送,我封装一个推送类,参数是token,当然你改成你的token就可以,剩下一个改动就是,推送证书文件名和密码,传统推送和pushKit是一样,你只需要调整推送文件即可,现在必须是推送地址tls

<?php
header("Content-type: text/html; charset=utf-8");


//设备的token 通过get post  方法传递deviceToken参数,也可以写死你手机的固定APP生成的token
$deviceToken=$_GET['deviceToken'];
if ($deviceToken ==null) {
    $deviceToken=$_POST['deviceToken'];
}
if ($deviceToken ==null) {

    $deviceToken='ec4403a9eff627bf6193d3986a74286f2f41acbdf452081f5564362af4d585c4';//ipad4 pushkit
    // $deviceToken='fff60ad0f161d76b2696c0c853a7ce015da294da97095346231e9ab326697545';//ipod pushkit


    // $deviceToken='18c0c97fd42b29d62db2e6498a7debdf87d3df3f9fced55584f0d9c4df817252';//ipod push
    // $deviceToken='9159b36630e0799ed902510c071c14eff21d81a238bbb2812ff41dd998b74713';//ipad4 push
}

ApnsPush::push($deviceToken,'推送类测试推送!');


/**
* iOS push  和pushKit
*/
class ApnsPush
{
    
    function __construct()
    {
        # code...
    }

    //iOS10之后,才支持title,subtitle
    public static function push($deviceToken='',$message)
    {
        return self::push_message($deviceToken,$message,'','');
    }
    public static function push_title($deviceToken='',$message,$title)
    {
        return self::push_message($deviceToken,$message,$title,'');
    }
    public static function push_message($deviceToken='',$message='',$title='',$subtitle='')
    {
        $aps = array(
            "alert"=> array(
              "title"=>$title,
              "subtitle"=>$subtitle,
              "body"=>$message
            ),
            'sound' => 'default', #$sound = "ping1.caf";
            'badge' => 1
        );
        return self::push_apns($deviceToken,$aps);
    }
    public static function push_apns($deviceToken='',$aps = array('alert' =>'' ,'sound' => 'default','badge' =>0 ))
    {
        if (strlen($deviceToken)<=0) {
            return;
        }

        // $pass="12345678";//密码必须是证书的密码
        // $pemPath = dirname(__FILE__) .'/'.'apns_dev.pem'; 

        $pass="123456@keyi";//密码必须是证书的密码
        $pemPath = dirname(__FILE__) .'/'.'apns_dev_pushkit.pem'; 

        /* End of Configurable Items */
        $ctx = stream_context_create();
        // $ctx = stream_context_create([
        //  'ssl'=>[
        //      'verify_peer'=>false,
        //      'verify_peer_name'=>false
        //  ]
        // ]);

        // anps_dev_club是在同文件夹下的pem证书(配置证书)
        stream_context_set_option($ctx, 'ssl', 'local_cert', $pemPath);
        // assume the private key passphase was removed.(输入密码)
        stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
        // ssl://gateway.sandbox.push.apple.com:2195 这个是苹果开发测试地址
        // ssl://gateway.push.apple.com:2195 苹果发布运行地址
        // $apnsHost='tls://gateway.sandbox.push.apple.com:2195';
        $apnsHost='tls://gateway.sandbox.push.apple.com:2195';
         #好像这个用发布和调试都可以
        $fp = stream_socket_client($apnsHost, $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
        #发布
        // $fp = stream_socket_client($apnsHost, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);

        if (!$fp) {
            // print "Failed to connect $err $errstrn";
            return;
        }
        else {
            //print "Connection OK-----";
        }

        // Construct the notification payload
        // array() php的数组和字典
        // $body['aps'] = array(
        //  'alert' => '推送测试!',#推送的消息
        //  'sound' => 'default', #$sound = "ping1.caf";
        //  'badge' => 4
        // );
        $body['aps']=$aps;
        # 把字典转化成 json字符串
        $payload = json_encode($body);
        // 这是去掉空格,什么的,因为token里面含有一些不用的符号
        $msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;

        // print "sending message :" . $payload . "n".$msg;
        // 发生推送
        $result=fwrite($fp, $msg,strlen($msg));
        fclose($fp);
    }

}

?>

4.Xcode项目配置(Xcode9为例)

1.传统通知配置

  • 在设置中Capabilities 打开 Push NotificationsBackground Modes
  • Background Modes中打开 Remote notificationsBackground fetch

2.pushKit配置

由于xcode9之后,取消了VoIP的设置,可以手动在info.plist文件中加入,在Required background modes里面添加一项 App provides Voice over IP services

5.项目实现

我自己封装了一个单利类FanPushManager用来管理iOS8+的推送,只有3个代理需要在appdelgate里面实现,另外还能发送本地通知,封装了一些情况!

1.头文件 FanPushManager.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif

#import <PushKit/PushKit.h>


@interface FanPushManager : NSObject<PKPushRegistryDelegate,UNUserNotificationCenterDelegate>


+(instancetype)defaultManager;
-(void)registerNotification;//push传统推送
-(void)registerPKPush;//pushKit  VoIP
///关闭通知栏和Icon角标
-(void)cancelAllPush;


#pragma mark - 发送本地通知
+(void)fan_sendLocalNotificationBody:(NSString *)body;
//几秒后发生本地通知
+(void)fan_sendLocalNotificationBody:(NSString *)body intervalTime:(NSTimeInterval)intervalTime;
+(void)fan_sendLocalNotificationTitle:(NSString *)title body:(NSString *)body;
+(void)fan_sendLocalNotificationTitle:(NSString *)title subtitle:(NSString *)subtitle body:(NSString *)body;
+(void)fan_sendLocalNotificationTitle:(NSString *)title subtitle:(NSString *)subtitle body:(NSString *)body intervalTime:(NSTimeInterval)intervalTime;

@end

2. FanPushManager.m 含//Mark:标签的需要自己实现自己的业务逻辑

#import "FanPushManager.h"
@implementation FanPushManager

+(instancetype)defaultManager{
    static FanPushManager *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager=[[FanPushManager alloc]init];
    });
    return manager;
}

-(void)registerNotification{
    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        //必须写代理,不然无法监听通知的接收与点击事件
        center.delegate = self;
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (!error && granted) {
                //用户点击允许
                NSLog(@"iOS10注册通知成功");
                // 可以通过 getNotificationSettingsWithCompletionHandler 获取权限设置
                //之前注册推送服务,用户点击了同意还是不同意,以及用户之后又做了怎样的更改我们都无从得知,现在 apple 开放了这个 API,我们可以直接获取到用户的设定信息了。注意UNNotificationSettings是只读对象哦,不能直接修改!
                [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
                    //                NSLog(@"========%@",settings);
                    if(settings.authorizationStatus!=UNAuthorizationStatusAuthorized){
                        //用户没有授权或拒绝权限
                    }
                }];
            }else{
                //用户点击不允许
                NSLog(@"iOS10注册通知失败");
                //提示用户是否打开或设备支持授权
            }
        }];
        
        
    } else {
        // Fallback on earlier versions
        if (@available(iOS 8.0, *)) {
            //iOS8+
            UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
            [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
            
        }
    }
    //iOS8+,包括iOS10+都是需要这个方法,才能获取
    [[UIApplication sharedApplication]registerForRemoteNotifications];
}
#pragma mark - PushKit  和 PKPushRegistryDelegate
-(void)registerPKPush{
    PKPushRegistry *pushRegistry=[[PKPushRegistry alloc]initWithQueue:dispatch_get_main_queue()];
    pushRegistry.delegate=self;
    pushRegistry.desiredPushTypes=[NSSet setWithObject:PKPushTypeVoIP];
}
-(void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type{
    //获取token,这个token需要上传到服务器
    NSData *data=pushCredentials.token;
    NSString *token=[NSString stringWithFormat:@"%@",data];
    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
    token = [token stringByTrimmingCharactersInSet:set];
    token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"PushKit Token:%@",token);
    
    //MARK: 在这里补充自己的pushkit token上传到服务器的逻辑
}
//收到pushkit的通知时会调用这个方法,但是不会有UI上的显示
-(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type{
    //iOS8.0-11.0
    [FanPushManager fan_sendLocalNotificationBody:@"iOS8 PushKit发送的推送!"];
    NSDictionary *dic=payload.dictionaryPayload[@"aps"];
    
    //MARK: 解析远程推送的消息,并做处理和跳转

}
//收到pushkit的通知时会调用这个方法,但是不会有UI上的显示
-(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion{
    //iOS11.0+
    [FanPushManager fan_sendLocalNotificationBody:@"iOS11 PushKit发送的推送!"];
    
}


#pragma mark -  正常的推送代理,iOS 10++, iOS8的代理直接在APPdelegate里面
//iOS10+在前台模式下接受消息,正常推送
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler API_AVAILABLE(ios(10.0)){
    UNNotificationContent * content = notification.request.content;//通知消息体内容  title subtitle  body
    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]
        ]) {
        //远程通知
        
    }else{
        //本地通知
        
    }
    //MARK:解析推送的消息,并做处理和跳转

    
    
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);
    //这里是有点问题 的,如果用户关闭通知,但是角标不会消失,
    [UIApplication sharedApplication].applicationIconBadgeNumber=0;
//    if([UIApplication sharedApplication].applicationIconBadgeNumber>0){
//        [UIApplication sharedApplication].applicationIconBadgeNumber--;
//    }
    
}
//iOS10+在后台模式下打开消息(3DTouch不会触发次方法)
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)){
    UNNotificationContent * content = response.notification.request.content;//通知消息体内容  title subtitle  body
    if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        //打开远程通知
    }else{
        //打开本地通知
        
    }
    
    //MARK: 解析推送的消息,并做处理和跳转

    completionHandler(); // 系统要求执 这个 法
    [UIApplication sharedApplication].applicationIconBadgeNumber=0;
//    if([UIApplication sharedApplication].applicationIconBadgeNumber>0){
//        [UIApplication sharedApplication].applicationIconBadgeNumber--;
//    }

}

#pragma mark - 关闭通知
-(void)cancelAllPush{
    [UIApplication sharedApplication].applicationIconBadgeNumber=0;
    if (@available(iOS 10.0, *)) {
        [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
    }else{
        [[UIApplication sharedApplication] cancelAllLocalNotifications];
    }
}

/**

#pragma mark - iOS8-iOS9的推送相关
//iOS8.0-10.0
-(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
    //    [[UIApplication sharedApplication]registerForRemoteNotifications];
    NSLog(@"%s",__func__);
}
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    //获取token,这个token需要上传到服务器
    NSString *token=[NSString stringWithFormat:@"%@",deviceToken];
    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
    token = [token stringByTrimmingCharactersInSet:set];
    token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"PushiOS10以下 Token:%@",token);
    //MARK: 上传token到服务器
}
//iOS7-iOS9之后,接收远程推送支持的方法,在前台接收,或者后台点击进入,会走此方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    // Required, iOS 7 Support
    completionHandler(UIBackgroundFetchResultNewData);
    //原生推送(前台后台都进入该方法)
    [UIApplication sharedApplication].applicationIconBadgeNumber=0;
}

-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
    //接收了本地通知iOS11不设置userNotificationCenter会走也会执行这个方法
    NSLog(@"888888");
    
}
*/
#pragma mark - 发送本地通知
+(void)fan_sendLocalNotificationBody:(NSString *)body{
    [FanPushManager fan_sendLocalNotificationTitle:nil subtitle:nil body:body intervalTime:0.0f];
}
//几秒后发生本地通知
+(void)fan_sendLocalNotificationBody:(NSString *)body intervalTime:(NSTimeInterval)intervalTime{
    [FanPushManager fan_sendLocalNotificationTitle:nil subtitle:nil body:body intervalTime:intervalTime];
}
+(void)fan_sendLocalNotificationTitle:(NSString *)title body:(NSString *)body{
    [FanPushManager fan_sendLocalNotificationTitle:title subtitle:nil body:body intervalTime:0.0f];
}
+(void)fan_sendLocalNotificationTitle:(NSString *)title subtitle:(NSString *)subtitle body:(NSString *)body{
    [FanPushManager fan_sendLocalNotificationTitle:title subtitle:subtitle body:body intervalTime:0.0f];
}
+(void)fan_sendLocalNotificationTitle:(NSString *)title subtitle:(NSString *)subtitle body:(NSString *)body intervalTime:(NSTimeInterval)intervalTime{
    if (@available(iOS 10.0, *)) {
        // 设置触发条件 UNNotificationTrigger
        UNTimeIntervalNotificationTrigger *timeTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:(intervalTime?intervalTime:0.1) repeats:NO];
        
        // 创建通知内容 UNMutableNotificationContent, 注意不是 UNNotificationContent ,此对象为不可变对象。
        UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
        content.title =title;
        content.subtitle = subtitle;
        content.body = body;
        NSInteger badge=[UIApplication sharedApplication].applicationIconBadgeNumber;
        content.badge = @(badge+1);
        content.sound = [UNNotificationSound defaultSound];
//        content.userInfo = @{@"key1":@"value1",@"key2":@"value2"};//方便撤销时使用
        
        // 创建通知标示,是不是通知的标签啊
        NSString *requestIdentifier = @"Dely.X.time";
        
        // 创建通知请求 UNNotificationRequest 将触发条件和通知内容添加到请求中
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier content:content trigger:timeTrigger];
        
        UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
        // 将通知请求 add 到 UNUserNotificationCenter
        [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
            if (!error) {
                NSLog(@"推送已添加成功 %@", requestIdentifier);
                //你自己的需求例如下面:
            }
        }];
        
    }else{
        NSDate *date = [NSDate dateWithTimeIntervalSinceNow:intervalTime];
        UILocalNotification *notification = [[UILocalNotification alloc] init];
        //设置推送时间
        notification.fireDate = date;
        //设置时区
        notification.timeZone = [NSTimeZone localTimeZone];
        //设置重复间隔
        //        notification.repeatInterval = NSWeekCalendarUnit;
        //推送声音
        notification.soundName = UILocalNotificationDefaultSoundName;
        //    notification.soundName = @"Default";
        //内容
        notification.alertBody = body;
        notification.alertTitle=title;
        //显示在icon上的红色圈中的数子
        NSInteger badge=[UIApplication sharedApplication].applicationIconBadgeNumber;
        notification.applicationIconBadgeNumber = badge+1;
        //设置userinfo 方便在之后需要撤销的时候使用
//        NSDictionary *infoDic = [NSDictionary dictionaryWithObject:@"name" forKey:@"key"];
//        notification.userInfo = infoDic;
        
        [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
        //    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
    }
    
}
@end

3.APPDelegate实现,引入头文件,实现3个代理

//是storyBoard创建的项目
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[FanPushManager defaultManager]registerNotification];//如果只想用传统推送就直接注册一个
    [[FanPushManager defaultManager]registerPKPush];//pushKit框架使用
    return YES;
}

#pragma mark - iOS8-iOS9的推送相关
//iOS8.0-10.0
-(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
//    [[UIApplication sharedApplication]registerForRemoteNotifications];
    NSLog(@"%s",__func__);
}
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    //获取token,这个token需要上传到服务器
    NSString *token=[NSString stringWithFormat:@"%@",deviceToken];
    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
    token = [token stringByTrimmingCharactersInSet:set];
    token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"Push Token:%@",token);
    //MARK: 上传token到服务器
}
//iOS7-iOS9之后,接收远程推送支持的方法,在前台接收,或者后台点击进入,会走此方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    // Required, iOS 7 Support
    completionHandler(UIBackgroundFetchResultNewData);
    //原生推送(前台后台都进入该方法)
    [UIApplication sharedApplication].applicationIconBadgeNumber=0;
}

-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
    //接收了本地通知iOS11不设置userNotificationCenter会走也会执行这个方法
        NSLog(@"888888");
    
}

6.总结

后期会把含有Socket的demo和推送发到GitHub上面,欢迎小伙伴关注!
其实实现原生的推送也不是很麻烦,主要有两点问题目前不能解决,就是用户关掉通知后,脚标不消失。还有就是建议你们要么用传统推送,要么用pushKit+本地推送,实现效果也非常不错!

Like(喜欢)

有问题请直接在文章下面留言,喜欢就给个分享或者👍吧!

Email:fqsyfan@gmail.com

163邮箱:fanxiangyang_heda@163.com

github:https://github.com/fanxiangyang

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容