网易云信-新增自定义消息(iOS版)

前言

公司业务需要,PC端,移动端都用到了第三方 网易云信 IM 来实现在线客服咨询。
在这当中难免遇到一些需求是网易云信没有提供,需要自行编码进行扩展的。写此篇文章的目的正是因业务需要,需要在网易云信的基础上进行消息类型的扩展。

此篇文章里的代码是基于 网易云信 NIM_iOS_Demo_v4.5.0 版 进行修改的

如下图所示的消息类型

带图片和文字,并且可点击的消息类型,(注意收到的消息和发送的消息文本颜色不一样)

标题是iOS版,可想而知,肯定还有其他如 Android版,Web版等,不可能此类型的消息(我称它为图文消息)只支持iOS,而在Android或Web端无法显示问题。以下附上其他版本扩展的链接


正文

  1. 下载demo后,双击 NIMDemo/NIM.xcworkspace 打开项目,然后运行,确保下载下来的demo能正确运行起来。

  2. 运行没有问题后,修改以下几个文件配置,将demo修改为自己所用。

    • 修改 Classes/Util/NTESDemoConfig.m 中的_appKey,填入自己的appKey
- (instancetype)init
{
    if (self = [super init])
    {
        _appKey = @"填入自己的appKey";
        _apiURL = @"https://app.netease.im/api";
        _apnsCername = @"ENTERPRISE";
        _pkCername = @"DEMO_PUSH_KIT";
        
        _redPacketConfig = [[NTESRedPacketConfig alloc] init];        
    }
    return self;
}
  • 修改
- (NSString *)tokenByPassword
{
    //demo直接使用username作为account,md5(password)作为token
    //接入应用开发需要根据自己的实际情况来获取 account和token
    //return [[NIMSDK sharedSDK] isUsingDemoAppKey] ? [self MD5String] : self;
    return [self MD5String];
}

修改上述代码后,重新运行,即可使用自己的账号密码登录了。

  1. 添加测试发送图文链接的按钮,点击即发送图文链接消息

编辑NTESCellLayoutConfig.m文件,在init函数中 _types 增加一条

- (instancetype)init
{
    if (self = [super init])
    {
        _types =  @[
                   @"NTESJanKenPonAttachment",
                   @"NTESSnapchatAttachment",
                   @"NTESChartletAttachment",
                   @"NTESWhiteboardAttachment",
                   @"NTESRedPacketAttachment",
                   @"NTESRedPacketTipAttachment",
                   // 添加图文链接消息
                   @"NTESLinkAttachment"
                   ];
        _sessionCustomconfig = [[NTESSessionCustomContentConfig alloc] init];
        _chatroomTextConfig  = [[NTESChatroomTextContentConfig alloc] init];
        _chatroomRobotConfig = [[NTESChatroomRobotContentConfig alloc] init];
    }
    return self;
}

编辑 NTESCustomAttachmentDecoder.m文件,checkAttachment函数中添加如下代码

//头部导入
#import "NTESLinkAttachment.h"
//...

- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content
{
    id<NIMCustomAttachment> attachment = nil;

    NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
                                                             options:0
                                                               error:nil];
        if ([dict isKindOfClass:[NSDictionary class]])
        {
            NSInteger type     = [dict jsonInteger:CMType];
            NSDictionary *data = [dict jsonDict:CMData];
            switch (type) {
                //...
                // 添加图文链接 case
                case CustomMessageTypeLink:
                {
                    attachment = [[NTESLinkAttachment alloc] init];
                    ((NTESLinkAttachment *)attachment).title        = [data jsonString:CMLinkPacketTitle];
                    ((NTESLinkAttachment *)attachment).linkUrl      = [data jsonString:CMLinkPacketLinkUrl];
                    ((NTESLinkAttachment *)attachment).imageUrl     = [data jsonString:CMLinkPacketImageUrl];
                    ((NTESLinkAttachment *)attachment).describe     = [data jsonString:CMLinkPacketDescribe];
                }
                    break;
                default:
                    break;
            }
            attachment = [self checkAttachment:attachment] ? attachment : nil;
        }
    }
    return attachment;
}

- (BOOL)checkAttachment:(id<NIMCustomAttachment>)attachment
{

    // ... 省略前面的 if  else if 块
    
    // 添加如下代码
    else if ([attachment isKindOfClass:[NTESLinkAttachment class]])
    {
        check = YES;
    }
    return check;
}

编辑NTESSessionConfig.m文件,在mediaItems函数中添加如下代码

//...
// 添加图文链接测试按钮,此处的 onTapMediaItemLinkPacket 在
 NTESSessionViewController.m 中添加 
NIMMediaItem *linkPacket  = [NIMMediaItem item:@"onTapMediaItemLinkPacket:"
                                   normalImage:[UIImage imageNamed:@"icon_redpacket_normal"]
                                 selectedImage:[UIImage imageNamed:@"icon_redpacket_pressed"]
                                         title:@"图文链接"];
//...
if (isMe)
{
    items = @[janKenPon,fileTrans,tip];
}
else if(_session.sessionType == NIMSessionTypeTeam)
{
    // 在群组消息里添加
    items = @[janKenPon,teamMeeting,fileTrans,tip,redPacket,linkPacket];
}
else
{
    // 添加图文链接测试按钮
    items = @[janKenPon,audioChat,videoChat,fileTrans,snapChat,whiteBoard,tip,redPacket,linkPacket];
}

Classes/Sections/Session/Object/Attach 目录下创建 NTESLinkAttachment文件,继承 NSObject 类,实现 NIMCustomAttachment,NTESCustomAttachmentInfo 协议

创建Cocoa Touch Class文件 NTESLinkAttachment,命名规则尽量遵循云信命名规则

创建完成后,添加响应的属性值 标题title,跳转的链接linkUrl,图片imageUrl,描述describe

NTESLinkAttachment.h 文件内容如下

#import <Foundation/Foundation.h>
#import "NTESCustomAttachmentDefines.h"

@interface NTESLinkAttachment : NSObject<NIMCustomAttachment,NTESCustomAttachmentInfo>

// 标题
@property (nonatomic, copy) NSString *title;

// 点击跳转的链接地址
@property (nonatomic, copy) NSString *linkUrl;

// 图片
@property (nonatomic, copy) NSString *imageUrl;

// 描述
@property (nonatomic, copy) NSString *describe;

@end

NTESLinkAttachment.m文件内容如下

复制之后,会有报错如 NTESSessionLinkContentView.h 找不到,和 CMLinkPacket***未定义等相关错误,先别急,后面会讲到,如果看不顺眼可以先注释掉,回头再过来放开注释也行。(ps:本人非iOS开发,所以代码部分不做详细讲解)

#import "NTESLinkAttachment.h"
#import "NTESSessionLinkContentView.h"

@implementation NTESLinkAttachment

- (NSString *)encodeAttachment
{
    NSDictionary *dict = @{
                           CMType : @(CustomMessageTypeRedPacket),
                           CMData : @{
                              CMLinkPacketTitle    : self.title,
                              CMLinkPacketLinkUrl  : self.linkUrl,
                              CMLinkPacketImageUrl : self.imageUrl,
                              CMLinkPacketDescribe : self.describe
                           }
                       };
    NSData *data = [NSJSONSerialization dataWithJSONObject:dict
                                                   options:0
                                                     error:nil];
    NSString *content = nil;
    if (data) {
        content = [[NSString alloc] initWithData:data
                                        encoding:NSUTF8StringEncoding];
    }
    return content;
}


- (NSString *)cellContent:(NIMMessage *)message{
    return @"NTESSessionLinkContentView";
}

- (CGSize)contentSize:(NIMMessage *)message cellWidth:(CGFloat)width{
    CGFloat w = 240.0f;
    CGFloat h = 40.0f;
    CGFloat padding = 3.0f * 3;
    if (self.imageUrl != nil) {
        h += 140.f;
    }
    if (self.describe != nil) {
        UIFont *font = [UIFont systemFontOfSize:12.0];
        CGFloat height = [NTESSessionLinkContentView getHeightByWidth:w - padding title:self.describe font:font];
        h += height + padding;
    }
    
    return CGSizeMake(w, h);
}

- (UIEdgeInsets)contentViewInsets:(NIMMessage *)message
{
    CGFloat bubblePaddingForImage    = 3.f;
    CGFloat bubbleArrowWidthForImage = 5.f;
    if (message.isOutgoingMsg) {
        return  UIEdgeInsetsMake(bubblePaddingForImage,bubblePaddingForImage,bubblePaddingForImage,bubblePaddingForImage + bubbleArrowWidthForImage);
    }else{
        return  UIEdgeInsetsMake(bubblePaddingForImage,bubblePaddingForImage + bubbleArrowWidthForImage, bubblePaddingForImage,bubblePaddingForImage);
    }
}

- (BOOL)canBeRevoked
{
    return YES;
}

- (BOOL)canBeForwarded
{
    return YES;
}

@end

现在再来补充上面缺失的部分。
NTESCustomAttachmentDefines.h 文件中定义如下四个字段。打开这个文件可以看到这个里面还定义了一些其他消息需要用到的字段,所以遵循人家的游戏规则,也在此处定义。

//...省略

typedef NS_ENUM(NSInteger,NTESCustomMessageType){
    CustomMessageTypeJanKenPon  = 1, //剪子石头布
    CustomMessageTypeSnapchat   = 2, //阅后即焚
    CustomMessageTypeChartlet   = 3, //贴图表情
    CustomMessageTypeWhiteboard = 4, //白板会话
    // (由于我其他平台图文消息type是5,刚好我们业务不需要发红包功能,这里我只好把5变成我的图文消息,把红包类型的消息去除)
    CustomMessageTypeRedPacket  = 5, //红包消息
    CustomMessageTypeRedPacketTip = 6, //红包提示消息
};

//...省略


//红包
#define CMRedPacketTitle   @"title"        //红包标题
#define CMRedPacketContent @"content"      //红包内容
#define CMRedPacketId      @"redPacketId"  //红包ID
//红包详情
#define CMRedPacketSendId     @"sendPacketId"
#define CMRedPacketOpenId     @"openPacketId"
#define CMRedPacketDone       @"isGetDone"
// 添加此处四个字段用于图文链接消息使用
#define CMLinkPacketTitle       @"title"        //标题
#define CMLinkPacketLinkUrl     @"link_url"      //跳转链接
#define CMLinkPacketImageUrl    @"image_url"     //图片链接
#define CMLinkPacketDescribe    @"describe"     //描述
//...省略

Classes/Sections/Session/View/SessionCell/SessionContentView目录下创建Cocoach Touch Class文件 NIMSessionMessageContentView,此文件主要用来做图文链接消息的显示。

NIMSessionMessageContentView.h文件内容如下

#import "NIMSessionMessageContentView.h"

static NSString *const NIMDemoEventNameLinkingPacket = @"NIMDemoEventNameLinkingPacket";

@interface NTESSessionLinkContentView : NIMSessionMessageContentView

// 根据宽度,字体和文本内容获取高度
+ (CGFloat)getHeightByWidth:(CGFloat)width title:(NSString *)title font:(UIFont *)font;

@end

NIMSessionMessageContentView.m文件内容如下

#import "NTESSessionLinkContentView.h"
#import "UIView+NTES.h"
#import "NTESLinkAttachment.h"
#import "NTESSessionUtil.h"
#import "UIImageView+WebCache.h"

CGFloat titleHeight = 40.f; // title高度
CGFloat imageHeight = 120.f;// 图片高度

@interface NTESSessionLinkContentView()

// 图文链接消息附件
@property (nonatomic,strong) NTESLinkAttachment *attachment;

@property (nonatomic,strong) UILabel *titleLabel;

@property (nonatomic,strong) UIImageView *imageView;

@property (nonatomic,strong) UILabel *describeLabel;

@end

@implementation NTESSessionLinkContentView

- (instancetype)initSessionMessageContentView{
    self = [super initSessionMessageContentView];
    if (self) {
        self.opaque = YES;
        
        _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _imageView  = [[UIImageView alloc] initWithFrame:CGRectZero];
        _describeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    }
    return self;
}

- (void)refresh:(NIMMessageModel *)data
{
    [super refresh:data];
    NIMCustomObject *customObject = (NIMCustomObject*)data.message.messageObject;
    id attach = customObject.attachment;
    
    if ([attach isKindOfClass:[NTESLinkAttachment class]]) {
        self.attachment = (NTESLinkAttachment *)attach;
        
        self.titleLabel.text = self.attachment.title;
        [self addSubview:_titleLabel];
        
        if (self.attachment.imageUrl != nil) {
            NSURL *url = [NSURL URLWithString:self.attachment.imageUrl];
            // 默认图片 default_image,记得在 Images.xcassets 中添加
            [self.imageView sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"default_image"]];
            [self.imageView sizeToFit];
            [self addSubview:_imageView];
        }
        if (self.attachment.describe != nil) {
            self.describeLabel.text = self.attachment.describe;
            [self addSubview:_describeLabel];
        }
    }
}

- (void)layoutSubviews{
    [super layoutSubviews];
    BOOL outgoing = self.model.message.isOutgoingMsg;
    
    UIEdgeInsets contentInsets = self.model.contentViewInsets;
    CGSize contentSize = [self.model contentSize:self.superview.width];
    CGFloat padding = 15;
    
    self.titleLabel.frame = CGRectMake(padding, contentInsets.left, contentSize.width - padding, titleHeight);
    self.titleLabel.font = [UIFont systemFontOfSize:14.0];
    self.titleLabel.numberOfLines = 1;
    
    // 详情描述距离
    CGFloat describeY = titleHeight;
    
    if (self.attachment != nil && self.attachment.imageUrl != nil) {
        self.imageView.frame = CGRectMake(
                                          contentInsets.left + contentInsets.right,
                                          titleHeight + contentInsets.top + 5,
                                          contentSize.width - (contentInsets.left + contentInsets.right), imageHeight);
        self.imageView.contentMode = UIViewContentModeScaleAspectFit;
        [self setBorderWithImageView:self.imageView top:TRUE left:FALSE bottom:TRUE right:FALSE borderColor:[UIColor lightGrayColor] borderWidth:0.3f];
        describeY += imageHeight + contentInsets.top * 3 + 5 ;
    }
    
    if (self.attachment != nil && self.attachment.describe != nil) {
        UIFont *font = [UIFont systemFontOfSize:12.0];
        self.describeLabel.font = font;
        self.describeLabel.numberOfLines = 3;
        CGFloat height = [NTESSessionLinkContentView getHeightByWidth:self.describeLabel.frame.size.width title:self.attachment.describe font:font];
        self.describeLabel.frame = CGRectMake(padding, describeY, contentSize.width - padding, height + padding);
    }
    
    // 发出去的消息
    if (outgoing)
    {
        self.titleLabel.textColor = [UIColor whiteColor];
        self.describeLabel.textColor = [UIColor whiteColor];
    }
    else
    {
        self.titleLabel.textColor = [UIColor blackColor];
        self.describeLabel.textColor = [UIColor grayColor];
    }
}

// 根据宽动态获取高度
+ (CGFloat)getHeightByWidth:(CGFloat)width title:(NSString *)title font:(UIFont *)font
{
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
    label.text = title;
    label.font = font;
    label.numberOfLines = 0;
    [label sizeToFit];
    CGFloat height = label.frame.size.height;
    return height;
}

// 设置元素边框
-(void)setBorderWithImageView:(UIImageView *) imageView top:(BOOL)top left:(BOOL)left bottom:(BOOL)bottom right:(BOOL)right borderColor:(UIColor *)color borderWidth:(CGFloat)width
{
    // 垂直内边距
    CGFloat verticalPadding = 5.0f;
    if (top)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(0, -verticalPadding, imageView.frame.size.width, width);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
    if (left)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(0, 0, width, imageView.frame.size.height);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
    if (bottom)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(0, imageView.frame.size.height - width + verticalPadding, imageView.frame.size.width, width);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
    if (right)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(imageView.frame.size.width - width, 0, width, imageView.frame.size.height);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
}

- (void)onTouchUpInside:(id)sender
{
    if ([self.delegate respondsToSelector:@selector(onCatchEvent:)]) {
        NIMKitEvent *event = [[NIMKitEvent alloc] init];
        event.eventName = NIMDemoEventNameLinkingPacket;
        event.messageModel = self.model;
        event.data = self;
        [self.delegate onCatchEvent:event];
    }
}

@end

接下来我们添加图文按钮的点击事件处理。

下面代码添加此处按钮点击处理事件

打开文件 NTESSessionViewController.m, 编辑函数 onTapCell
在 if else if 代码块后面添加如下代码

// 头部需导入
#import "NTESLinkAttachment.h"
#import "NTESSessionLinkContentView.h"
#import "NTESWebViewController.h"

// ...

// 添加图文链接消息点击事件
else if ([eventName isEqualToString:NIMDemoEventNameLinkingPacket]) {
   NIMCustomObject *object = event.messageModel.message.messageObject;
   NTESLinkAttachment *attachment = (NTESLinkAttachment *)object.attachment;
   [self onOpenWebView:attachment];
   handled = YES;
}
// ....

// 添加上面调用的 onOpenWebView 函数 
- (void)onOpenWebView:(NTESLinkAttachment *)attachment {
    // NTESWebViewController 是点击显示的图文消息后要跳转的页面,在构造函数添加跳转时传入 linkUrl
    NTESWebViewController *vc = [[NTESWebViewController alloc] initWithUrl:attachment.linkUrl];
    // 设置title
    if (attachment && attachment.title != nil) {
        vc.title = attachment.title;
    }
    [self.navigationController pushViewController:vc animated:YES];
}

//...
#pragma mark - 图文链接
- (void)onTapMediaItemLinkPacket:(NIMMediaItem *)item
{
    // 此处模拟测试数据
    NTESLinkAttachment *attachment = [[NTESLinkAttachment alloc] init];
    [attachment setTitle:@"暖冬季欢乐送"];
    [attachment setLinkUrl:@"https://www.jianshu.com/u/bd57ade96e8a"];
    [attachment setImageUrl:@"https://www.baidu.com/img/bd_logo1.png"];
    [attachment setDescribe:@"家具满1000元减100元再返100元现金券!点击查看详情!"];
    NIMMessage *message = [NTESSessionMsgConverter msgWithLink:attachment];
    [self sendMessage:message];
}
//...

在目录 Classes/Sections/Session/ViewController 添加上面使用到的 NTESWebViewController,用来显示点击后的网页
NTESWebViewController.h内容如下

#import <UIKit/UIKit.h>

@interface NTESWebViewController : UIViewController<UIWebViewDelegate>
{
    UIWebView *webView;
}

- (instancetype)initWithUrl:(NSString *)url;

@end

NTESWebViewController.m内容如下

#import "NTESWebViewController.h"

@interface NTESWebViewController ()<UINavigationControllerDelegate>

@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@property (nonatomic, strong) NSString *url;

@end

@implementation NTESWebViewController

- (instancetype)initWithUrl:(NSString *)url
{
    self = [super init];
    if (self)
    {
        _url = url;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // app 尺寸,去掉状态栏
    CGRect mainScreen = [UIScreen mainScreen].applicationFrame;
    // 1.创建webview,并设置大小
    webView = [[UIWebView alloc] initWithFrame:CGRectMake(mainScreen.origin.x, mainScreen.origin.y, mainScreen.size.width, mainScreen.size.height)];
    // 2.创建请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
    // 3.加载网页
    [webView loadRequest:request];
    // 4.将webview添加到界面
    [self.view addSubview:webView];
    [webView setDelegate:self];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)webViewDidStartLoad:(UIWebView *)webView {
    // 创建UIActivityIndicatorView背底半透明View
    CGRect mainScreen = [UIScreen mainScreen].applicationFrame;
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(mainScreen.origin.x, mainScreen.origin.y, mainScreen.size.width, mainScreen.size.height)];
    [view setTag:108];
    [view setBackgroundColor:[UIColor whiteColor]];
    [self.view addSubview:view];
    
    self.activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)];
    [self.activityIndicator setCenter:view.center];
    [self.activityIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
    [view addSubview:self.activityIndicator];
    
    [self.activityIndicator startAnimating];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [self.activityIndicator stopAnimating];
    UIView *view = (UIView *)[self.view viewWithTag:108];
    [view removeFromSuperview];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    [self.activityIndicator stopAnimating];
    UIView *view = (UIView *)[self.view viewWithTag:108];
    [view removeFromSuperview];
}

@end
  1. 添加显示自定义的图文消息
    上面第3个步骤其实已经做了大部分自定义的图文链接消息的显示工作了,此处添加图文链接消息的转换代码,
    编辑NTESSessionMsgConverter.h 头文件
// ...
@class NTESLinkAttachment

@interface NTESSessionMsgConverter : NSObject
// ...
// 添加链接消息
+ (NIMMessage *)msgWithLink:(NTESLinkAttachment *)attachment;
@end

在实现文件NTESSessionMsgConverter.m 添加以下代码

//...
#import "NTESLinkAttachment.h"

@implementation NTESSessionMsgConverter
//...
+ (NIMMessage *)msgWithLink:(NTESLinkAttachment *)attachment
{
    NIMMessage *message               = [[NIMMessage alloc] init];
    NIMCustomObject *customObject     = [[NIMCustomObject alloc] init];
    customObject.attachment           = attachment;
    message.messageObject             = customObject;
    message.apnsContent = @"发来了链接信息";
    return message;
}

@end

5.修改消息列表中,显示的缩略文字

添加显示[图文链接]字样,如果不添加,默认显示的是[未知消息]

编辑 NTESSessionListViewController.m, 在contentForRecentSession中添加一条逻辑判断

// ...
#import "NTESLinkAttachment.h"

// ...
- (NSAttributedString *)contentForRecentSession:(NIMRecentSession *)recent{
    //...
    else if ([object.attachment isKindOfClass:[NTESLinkAttachment class]]) {
        text = @"[图文链接]";
     } else {
        text = @"[未知消息]";
     }
    //...
}
// ...

尾篇

到此,云信iOS端的扩展自定义消息已经完成。当然,这只是iOS的显示正常了,其他如web,Android,pc等客户端收到此类的消息,显示有问题,也是需要扩展调整的。此篇文章其他端的文章我会陆续更新,如果有需要的同学可以关注下。

以下附上其他版本扩展的链接

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

推荐阅读更多精彩内容