https://www.jianshu.com/p/2bfb1c4e9f21

前言

公司业务需要,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等客户端收到此类的消息,显示有问题,也是需要扩展调整的。此篇文章其他端的文章我会陆续更新,如果有需要的同学可以关注下。

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

作者:醉生夢死
链接:https://www.jianshu.com/p/2bfb1c4e9f21
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

网易云信-新增自定义消息(iOS版)的更多相关文章

  1. 网易云信,发送验证码短信C#版代码

    网易云信发送短信代码(C# 版)....需要注意SHA1 String有转换小写!!!! using System; using System.Collections.Generic; using S ...

  2. 模板短信接口调用java,pythoy版(一) 网易云信

    说明 短信服务平台有很多,我只是个人需求,首次使用,算是测试用的,故选个网易(大公司). 稳定性:我只测试了15条短信... 不过前3条短信5分钟左右的延时,后面就比较快.... 我只是需要发短信,等 ...

  3. 网易新闻iOS版使用的18个开源组件

    转载来自:http://www.jianshu.com/p/8952944f7566  原文最后编辑时间:2015.05.19 网易新闻iOS版在开发过程中曾经使用过的第三方开源类库.组件 1.AFN ...

  4. 微信小程序开发中的二三事之网易云信IMSDK DEMO

    本文由作者邹永胜授权网易云社区发布. 简介 为了更好的展示我们即时通讯SDK强悍的能力,网易云信IM SDK微信小程序DEMO的开发就提上了日程.用产品的话说就是: 云信 IM 小程序 SDK 的能力 ...

  5. 【网易云信】H5 容器技术方案

    Native 开发原生应用是手机操作系统厂商(目前主要是苹果的 iOS 和 Google 的 Android)对外界提供的标准化的开发模式,他们对于 Native 开发提供了一套标准化实现和优化方案. ...

  6. 中国首个 SaaS 模式的云告警平台 iOS 版 APP 上线

    今天上午,国内首个 SaaS 模式的云告警平台 OneAlert 正式发布 ios 版 APP,每个 ios 用户,无需电脑,都可以通过手机全程跟踪所有告警,并且可以和每一个成员一键式电话沟通,团队协 ...

  7. 视频直播SDK-ios版

    IOS视频直播接入说明 一.名词解释 分辨率:用于计算机视频处理的图像,以水平和垂直方向上所能显示的像素数来表示分辨率.常见视频分辨率的有1080P即1920x1080,720P即1080x720,6 ...

  8. 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

    本文原文内容来自InfoQ的技术分享,本次有修订.勘误和加工,感谢原作者的分享. 1.前言 自从2018年8月20日子弹短信在锤子发布会露面之后(详见<老罗最新发布了“子弹短信”这款IM,主打熟 ...

  9. 通过微信Android和iOS版,看两大系统的差异

    由于设计师或者产品经理使用的移动设备大部分是iPhone,所以在做设计时,容易忽略Android和iOS的差异,按照自己的使用习惯进行设计,导致大部分设计师或产品经理做出的设计都是基于iOS规范或习惯 ...

随机推荐

  1. C语言与汇编的嵌入式编程:求100以内素数

    写汇编之前,需要搞清楚C语言代码的写法,这里以最简单的算法举例说明 C代码如下: #include <stdio.h> void main(){ int i,j; ; ;i<=;i+ ...

  2. RT_THREAD之nano学习

    nona版本为精简版本,只保留FISH(选配)最小内核,可以适配STM的STD.HAL/LL库,需要手动进行移植:现在可以在KEIL MDK/CUBEMX中进行集成,也可以RT-Thread Nano ...

  3. EF Expression 扩展

    using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; na ...

  4. MySQL8.0.11安装后,使用CMD无法启动mysql服务

    首先,先把mysql的bin路径添加到系统环境变量 这样做可以,直接进入CMD后执行mysql服务,不需要进入mysql的bin文件路径去执行. 第一步:在MySQL的安装文件的bin目录(例如:C: ...

  5. Java电子书高清PDF集合免费下载

    这份资源是我经过多年积累才整理归类出来,有很多电子书我觉质量还是非常高的,由于电子书太多我也是用业余时间挑着看的,这么多资源自己保存着也是浪费,就想着现在把资源分享出来,希望能真正帮到大家: 资源我都 ...

  6. Druid数据源SQL数据库与Spring监控

    Druid监控概要说明 为什么要监控? Druid是什么?德鲁伊 URL监控配置说明 配置步骤 步骤 配置 第一步 web.xml 配置 WebStatFilter 第二步 WebStatFilter ...

  7. 测试环境docker-swarm安装部署

    测试环境swarm安装部署 部署前增加监听docker2375端口 centos 增加tcp监听端口 修改/lib/systemd/system/docker.service sed -i ‘s/Ex ...

  8. Deeplearning.ai课程笔记-结构化机器学习项目

    目录 一. 正交化 二. 指标 1. 单一数字评估指标 2. 优化指标.满足指标 三. 训练集.验证集.测试集 1. 数据集划分 2. 验证集.测试集分布 3. 验证集.测试集大小 四. 比较人类表现 ...

  9. Spring Boot RestApi 测试教程 Mock 的使用

    测试 Spring Boot Web 的时候,我们需要用到 MockMvc,即系统伪造一个 mvc 环境.本章主要编写一个基于 RESTful API 正删改查操作的测试用例.本章最终测试用例运行结果 ...

  10. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 网格系统实例:中型和大型设备

    <!DOCTYPE html> <html> <head> <title>Bootstrap 实例 - 中型和大型设备</title> &l ...