程序员

程序员

一把双刃剑 -- 融云即时通讯sdk中的自定义消息使用心得&指南 (下)

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 16 次浏览 • 3 天前 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20210224192919.png

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).

他家的官网和文档地址: 官网:https://www.rongcloud.cn/ 文档:https://docs.rongcloud.cn/v4

这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面).

心得 (下)

自定义小视频消息

接上篇对自定义消息的开发心得哈.

因为融云家自带的小视频消息是需要收费的, 需要在服务端开通小视频服务后, 同时在端上做一下配置, 才可以使用小视频消息. 我一看这还得了, 想方设法收我钱呢不是. 不过他家只是对小视频类型的消息在服务端做了限制, 而不是完全不让在消息中携带视频链接. 自定义消息是随便自定义的, 那么我自定义一个小视频消息不就好啦.

大概实现思路如下:

自定义小视频消息继承MediaMessageContent,其中mLocalPath是小视频文件本地的存放路径,mMediaUrl是小视频文件上传到文件服务器后的http/https地址。

小视频的拍摄,播放我们RongCloud SDK没有接口,开发者自己实现。

当拍摄完成,发送小视频消息时使用方法

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallback callback)或者

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallbackWithUploader callback)

这两个方法的不同是后者开发者负责小视频文件的上传到指定的服务器,前者使用我们RongCloud默认的文件服务器

以上是大致步骤,小视频开发过程中可能遇到的问题,说明如下:

1.关于缩略图的处理,我们SDK没有直接上传一张图片返回一个url地址的接口,开发者可以把缩略图上传到自己的服务器,这样缩略图跟mMediaUrl类似,小视频消息展示显示缩略图时加载一张网络图片即可。

另一种缩略图处理方式类似我们SDK发送图片消息时的缩略图处理,把缩略图做base64编码,放到自定义消息体中直接传输,这种方式涉及到消息发送时把缩略图转化为base64数据和接收到消息时还原为缩略图,在我们SDK内部使用的是MessageHandler。

关于MessageHandler,我们RongCloud的每个消息都有一个MessageHandler,此前我们文档从没有介绍过这个

MessageHandler,对用户透明的,用户的自定义消息没有指定它是因为有个默认的DefaultMessageHandler。

自定义消息时可以指定自己的MessageHandler,例如图片消息的定义如下

ImageMessage.png

MessageHandler在消息发送和接收时在IPC进程中会被自动调用,它有两个方法,encodeMessage

和decodeMessage,在消息接收后调用decodeMessage时开发者可以把base64对应的数据转化为缩略图url,这样在展示缩略图时直接使用url即可。

/**
 * 解码 {@link MessageContent} 到 {@link Message} 中。
 *
 * @param message 用于存放 MessageContent 的消息实体。
 * @param content 将要被解码的 MessageContent。
 */
public abstract void decodeMessage(Message message, T content);

/**
 * 对 {@link Message} 编码。
 *
 * @param message 将要被编码的 Message 实体。
 */
public abstract void encodeMessage(Message message);

此文档包含了两个附件分别为自定义小视频消息和对应的小视频消息MessageHandler,供开发者参考

2.开发中可能还会遇到小视频文件上传时进度更新的问题,如果开发者自定义的小视频消息不继承自MediaMessageContent而是MessageContent,需要自己在UI上维护上传进度


【Android开发】如何使用融云的消息扩展

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 14 次浏览 • 3 天前 • 来自相关话题

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html总结文... ...查看全部

微信截图_20210224192550.png

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html

总结文档中的几个重点:

  • 4.0.3之前的版本无法使用。

  • 消息扩展是 Message类的属性,也就是说自定义消息也可以使用。

  • 单条消息只能设置300个kv,(某些场景不可用,比如在群里发400个人可领的红包)。

  • 仅支持单聊、群聊。

  • key和value都是字符串类型,并且key不能是中文,否则会报错INVALID_PARAMETER。

  • 用IMKit的UI发送消息时需要拦截一下,设置消息扩展开关。

使用步骤:

  1. 消息的发送端必须给需要消息扩展的消息开开关,没有全局设置,只能单条消息设置。分以下两种情况:

  2. 如果使用的是 IMKit 提供的UI发送消息,在 ConversationFragment中的 onSendToggleClick 方法发送消息,在 sendMessage 前调用下面的方法

    /**
     * 设置消息扩展信息列表
     * 

扩展信息只支持单聊和群组,其它会话类型不能设置扩展信息。


     *
     * @param expansion 消息扩展信息列表
     */
    public void setExpansion(HashMap<String, String> expansion) {
        this.expansion = expansion;
    }

还需要设置 canIncludeExpansion 和 expansionDic 。

  • 如果是自己调用 RongIM 和 RongIMClient 接口发的消息,用 MessageContent 类的对象构造一个 Message 对象,然后设置 canIncludeExpansion 和 expansionDic 再调用RCIM中对应的 send 方法将消息发送出去即可。

  • 更新和删除扩展的操作可以参考官方文档。

  • 想要实时捕获消息扩展的更改和删除,就需要设置回调,在回调中更新UI。设置回调可参考官方文档。

融云官网:https://www.rongcloud.cn/

文档频道:https://docs.rongcloud.cn/v4


Spring Initializr中生成的mvnw是干吗的?

科技创新王叫兽 发表了文章 • 0 个评论 • 50 次浏览 • 2021-02-04 10:11 • 来自相关话题

来源于公众号-程序猿DD当我们使用Spring Initializr来创建Spring Boot工程的时候,有没有发现在工程根目录下有两个名为mvnw的文件:从命名、图标、扩展名来猜测,这两个文件的作用应该是一样的,只是cmd文件应该是用在windows下跑的... ...查看全部

来源于公众号-程序猿DD

当我们使用Spring Initializr来创建Spring Boot工程的时候,有没有发现在工程根目录下有两个名为mvnw的文件:

微信图片_20210203173520.jpg

从命名、图标、扩展名来猜测,这两个文件的作用应该是一样的,只是cmd文件应该是用在windows下跑的,而另外一个则是用于linux环境下跑的。

那么这个文件到底是用来做什么呢?下面我们一起尝试了解一下:

第一步:打开读一下

微信图片_20210203173526-1024x1531.png

因为内容较多,我这里就不放出来了。内容也非常易懂,只要你了解shell和maven,就能知道这个脚本主要做这几件事:

  1. 检测你是否有安装Maven,如果没有,就自动下载一个(这样才能完成后续的构建任务)

  2. 检查你是否有安装Java或者配置是否正确,这个无法自己完成,如果报错了,就要自己处理一下,比如JAVA_HOME没有,那就自己配置下。

  3. 检查否存在版本不兼容的情况,如果不兼容他会下载合适的版本来帮助你完成构建

更多检查的细节可以自行打开查看和学习

第二步:执行验证下

执行命令:mvnw install

微信图片_20210203173531.png等待构建完成,我们再看看:微信图片_20210203173534.png完美!轻松简单的完成了一个Spring Boot项目的构建!

拒绝白嫖!开源模式的反击:向不要脸的云服务商收费!

科技创新王叫兽 发表了文章 • 0 个评论 • 73 次浏览 • 2021-02-04 10:11 • 来自相关话题

年底将至,又到了大多数打工人开始编写年终小结的时候,但是总有那么一群人,平时碌碌无为,一等到年底,就到处打听到处收集各种成效数字,然后各种不要脸的洋洋洒洒的写在自己的年终小结里,仿佛那些完全没参与过的项目都是他一手打造的,彷佛那些别人辛苦一年才做出的成绩理应给... ...查看全部

年底将至,又到了大多数打工人开始编写年终小结的时候,但是总有那么一群人,平时碌碌无为,一等到年底,就到处打听到处收集各种成效数字,然后各种不要脸的洋洋洒洒的写在自己的年终小结里,仿佛那些完全没参与过的项目都是他一手打造的,彷佛那些别人辛苦一年才做出的成绩理应给他一样。

遇到这种人,气不气?

其实这种人不在少数,也正是有这样的人,才会有拿来主义,才会有做事毫无底线的云服务商,才导致了开源商业模式一再萎靡。

当然,老实人并不代表着一定要被欺负!就在1月15号,Elastic 公司 官方宣布:改变 Elasticsearch 和 Kibana 的开源协议,由 Apache 2.0 变更为 SSPL 与 Elastic License。

这次变更的主要针对目的,就是那些毫无底线的云服务商!

微信图片_20210203172125 (1).jpgElastic 公司 的CEO Shay Banon在他的博文中解释到:

虽然,从Apache 2.0 变更为 SSPL 与 Elastic License会改变一部分源代码,但是对于绝大多数的使用免费版本的用户及Elastic 公司的云客户以及自主管理软件客户也不会受到影响。唯一会受影响的,就是云服务商。

SSPL 允许用户以自由且不受限制的方式使用并修改代码成果,但是SSPL也有自己的要求:那就是如果将产品以作为一种服务进行交付,那么必须同时公开发布所有关于修改及 SSPL 之下管理层的源代码。

由此将限制云服务商在不对项目做出任何贡献的前提下,发布云服务商自己的 Elasticsearch 与 Kibana 服务,从而达到保护Elastic 公司的权益,亦可以让公司在开发免费产品方面持续投入的热情和资源,避免恶性循环。

微信图片_20210203172149.png

对于为什么会这么做,Shay Banon也给出了自己的解释:

长久以来,云服务商不断地肆意的将免费的开源软件集成到自己的云产品中,同时将其作为自己的云服务解决方案给予客户使用,俗称白嫖~造成了许多客户越来越多的放弃这些开源厂商的付费版本,直接断绝了开源厂商的经济来源!

早在Elastic之前,就有不少开源公司进行了变更:

Redis、MongDB、OpenCV、Google,Elastic绝不会是最后一个。

此次Elastic主要是借鉴了MongoDB的类似经验。

微信图片_20210203172152.png早在2018 年 10 月,MongoDB 就已经宣布将开源协议从 GNU AGPLv3切换到 SSPL,无论当时如何引起部分用户的不满,MongoDB 至少目前还很健康的继续经营,股价也从 2018 年的不足 100 美元 / 股涨到现在的 361 美元 / 股。可以说是开源公司学习的楷模。微信图片_20210203172155.jpg

SSPL 就是由 MongoDB 最初创建的可提供源代码的许可证,旨在体现开放源码理想的许可证,允许自由和不受限制地使用,修改和重新分发。

SSPL最简单也是最核心的一点就是前文提过的:如果将产品以作为一种服务进行交付,那么必须同时公开发布所有关于修改及 SSPL 之下管理层的源代码。

SSPL 是基于GPLv3 的 copyleft 许可证。并没有经过 OSI(Open Source Initiative,开源促进会,批准开源协议的机构) 批准,因此官方还特意声明:为了避免混淆,我们暂时不将两个产品称为开源,而使用“免费和开放” 进行描述。

有时候甚至会觉得,SSPL纯粹就是被那些习惯了白嫖的云服务商逼出来的产物,就像当每个人做事都只顾着看别人的成效数字,又有几个人能真正做好事呢?


融云 IMKit 音频录制参数

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 215 次浏览 • 2021-01-11 15:14 • 来自相关话题

场景:使用融云自带的界面进行语音消息的播放。自己进行音频录制。使用的融云的 RCHQMessage问题:语音消息 iOS 和 Android 不互通,接收到消息之后无法播放。解决方案:经过与融云开发者的确认,使用时必须保证如下录制参数:iOS AVA... ...查看全部

场景:

  1. 使用融云自带的界面进行语音消息的播放。

  2. 自己进行音频录制。

  3. 使用的融云的 RCHQMessage

问题:

  1. 语音消息 iOS 和 Android 不互通,接收到消息之后无法播放。

解决方案:

经过与融云开发者的确认,使用时必须保证如下录制参数:

iOS AVAudioRecorder 录制参数如下设置:

AVFormatIDKey : @(kAudioFormatMPEG4AAC_HE),
AVSampleRateKey : @(44100.0),
AVNumberOfChannelsKey : @1,
AVEncoderBitRateKey : @(16000)

Android MediaRecorder 录制参数如下:

setAudioSamplingRate(44100);
setAudioEncodingBitRate(16000);
setAudioChannels(1);
setAudioSource(MediaRecorder.AudioSource.MIC);
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);

其他一些内容的使用可以自己去官网文档搜索:

融云文档:https://docs.rongcloud.cn/v4/


干货分享——使用融云通讯能力库 IMLib 实现单群聊的阅读回执

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 219 次浏览 • 2021-01-11 15:14 • 来自相关话题

今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 SDK 去实现,还是有些坑在等着我们的,下面就开始分(bì)享(kēng)喽~分享之前先做一些准备工作,先找到我们需要调用的接口文档文档:https://docs.rongclou... ...查看全部

今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 SDK 去实现,还是有些坑在等着我们的,下面就开始分(bì)享(kēng)喽~

  1. 分享之前先做一些准备工作,先找到我们需要调用的接口文档

  2. 根据不同的会话类型以及消息的发送方和接收方,要分别处理

    • 单聊

      接收方 :在阅读消息后,调用 RCIMClient 类的发送阅读回执接口,参数如下:

      conversationType 单聊会话类型

      targetId 消息的会话 ID

      time 会话最后一条消息的发送时间(sentTime)

/*!
 发送某个会话中消息阅读的回执

 @param conversationType    会话类型
 @param targetId            会话 ID
 @param timestamp           该会话中已阅读的最后一条消息的发送时间戳
 @param successBlock        发送成功的回调
 @param errorBlock          发送失败的回调[nErrorCode: 失败的错误码]

 @discussion 此接口只支持单聊, 如果使用 IMLib 可以注册监听
 RCLibDispatchReadReceiptNotification 通知,使用 IMKit 直接设置RCIM.h
 中的 enabledReadReceiptConversationTypeList。

 @warning 目前仅支持单聊。

 @remarks 高级功能
 */
- (void)sendReadReceiptMessage:(RCConversationType)conversationType
                      targetId:(NSString *)targetId
                          time:(long long)timestamp
                       success:(void (^)(void))successBlock
                         error:(void (^)(RCErrorCode nErrorCode))errorBlock;

发送方:监听下面这个通知,在接收后修改消息的展示

/*!
 @const 收到已读回执的 Notification

 @discussion 收到消息已读回执之后,IMLib 会分发此通知。

 Notification 的 object 为 nil,userInfo 为 NSDictionary 对象,
 其中 key 值分别为 @"cType"、@"tId"、@"messageTime",
 对应的 value 为会话类型的 NSNumber 对象 、会话的 targetId 、已阅读的最后一条消息的 sendTime。
 如:
 NSNumber *ctype = [notification.userInfo objectForKey:@"cType"];
 NSNumber *time = [notification.userInfo objectForKey:@"messageTime"];
 NSString *targetId = [notification.userInfo objectForKey:@"tId"];
 NSString *fromUserId = [notification.userInfo objectForKey:@"fId"];

 收到这个消息之后可以更新这个会话中 messageTime 以前的消息 UI 为已读(底层数据库消息状态已经改为已读)。

 @remarks 事件监听
 */
FOUNDATION_EXPORT NSString *const RCLibDispatchReadReceiptNotification;

群聊

发送方

在发送消息 A 后,需要针对该消息发送回执请求,message 传之前发的消息 A

/*!
 请求消息阅读回执

 @param message      要求阅读回执的消息
 @param successBlock 请求成功的回调
 @param errorBlock   请求失败的回调[nErrorCode: 失败的错误码]

 @discussion 通过此接口,可以要求阅读了这条消息的用户发送阅读回执。

 @remarks 高级功能
 */
- (void)sendReadReceiptRequest:(RCMessage *)message
                       success:(void (^)(void))successBlock
                         error:(void (^)(RCErrorCode nErrorCode))errorBlock;

设置下面代理函数,在接收到发送方发来的阅读回执响应后,修改消息的展示

/*!
 IMlib消息接收的监听器

 @discussion
 设置IMLib的消息接收监听器请参考RCIMClient的setReceiveMessageDelegate:object:方法。

 @warning 如果您使用IMlib,可以设置并实现此Delegate监听消息接收;
 如果您使用IMKit,请使用RCIM中的RCIMReceiveMessageDelegate监听消息接收,而不要使用此监听器,否则会导致IMKit中无法自动更新UI!
 */
@protocol RCIMClientReceiveMessageDelegate <NSObject>

/*!
 消息已读回执响应(收到阅读回执响应,可以按照 messageUId 更新消息的阅读数)
 @param messageUId       请求已读回执的消息ID
 @param conversationType conversationType
 @param targetId         targetId
 @param userIdList 已读userId列表
 */
- (void)onMessageReceiptResponse:(RCConversationType)conversationType
                        targetId:(NSString *)targetId
                      messageUId:(NSString *)messageUId
                      readerList:(NSMutableDictionary *)userIdList;

接收方

设置下面代理函数,在接收到消息 A 后,还会接收到针对消息 A 的阅读回执请求

/*!
 IMlib消息接收的监听器

 @discussion
 设置IMLib的消息接收监听器请参考RCIMClient的setReceiveMessageDelegate:object:方法。

 @warning 如果您使用IMlib,可以设置并实现此Delegate监听消息接收;
 如果您使用IMKit,请使用RCIM中的RCIMReceiveMessageDelegate监听消息接收,而不要使用此监听器,否则会导致IMKit中无法自动更新UI!
 */
@protocol RCIMClientReceiveMessageDelegate <NSObject>

/*!
 请求消息已读回执(收到需要阅读时发送回执的请求,收到此请求后在会话页面已经展示该 messageUId 对应的消息或者调用
 getHistoryMessages 获取消息的时候,包含此 messageUId 的消息,需要调用 sendMessageReadReceiptResponse
 接口发送消息阅读回执)

 @param messageUId       请求已读回执的消息ID
 @param conversationType conversationType
 @param targetId         targetId
 */
- (void)onMessageReceiptRequest:(RCConversationType)conversationType
                       targetId:(NSString *)targetId
                     messageUId:(NSString *)messageUId;

在代理方法中,调用下面接口发送阅读回执响应给发送方

/*!
 发送阅读回执

 @param conversationType 会话类型
 @param targetId         会话 ID
 @param messageList      已经阅读了的消息列表
 @param successBlock     发送成功的回调
 @param errorBlock       发送失败的回调[nErrorCode: 失败的错误码]

 @discussion 当用户阅读了需要阅读回执的消息,可以通过此接口发送阅读回执,消息的发送方即可直接知道那些人已经阅读。

 @remarks 高级功能
 */
- (void)sendReadReceiptResponse:(RCConversationType)conversationType
                       targetId:(NSString *)targetId
                    messageList:(NSArray<RCMessage *> *)messageList
                        success:(void (^)(void))successBlock
                          error:(void (^)(RCErrorCode nErrorCode))errorBlock;

总结

阅读回执需要区分会话类型处理,且单聊的阅读回执是针对会话的,群聊的阅读回执是针对某一条消息的:

单聊:接收方阅读某个会话的消息后,发送阅读回执 ——发送方接到阅读回执,更新 UI

群聊:发送方发送消息 A, 针对该消息,发送阅读回执请求 —— 接收方实现监听代理,接收到消息 A 的阅读回执请求 —— 接收方发送阅读回执响应 —— 发送方收到阅读回执响应,更新 UI


自定义融云会话列表 cell 选中背景

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 224 次浏览 • 2021-01-08 11:35 • 来自相关话题

项目用的融云,IMKit SDK(自带 UI),但是在使用会话列表的时候,cell 选中和长按的时候默认是灰色的。设计说需要改啊,那就研究一下如何修改吧。废话不多说,直接继承 RCConversationListViewController,然后重写以下方法1... ...查看全部

项目用的融云,IMKit SDK(自带 UI),但是在使用会话列表的时候,cell 选中和长按的时候默认是灰色的。设计说需要改啊,那就研究一下如何修改吧。废话不多说,直接继承 RCConversationListViewController,然后重写以下方法
1.以下代码是去掉选中颜色的

- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
}

2.以下代码是重写颜色的,想配啥色请随意

- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    UIView *backView = [[UIView alloc] init];
    backView.backgroundColor = [UIColor redColor];
    cell.selectedBackgroundView = backView;
}

SDK 开放出来的 .h 类对方法注释写的很详细,建议大家多看一下,这样可以快速集成,少走弯路。也是培养集成第三方库的好习惯。融云(www.rongcloud.cn)

给融云的输入框上方加个功能按钮,怎么整?

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 222 次浏览 • 2021-01-08 11:29 • 来自相关话题

给输入框上方加个功能按钮,类似常用语或者抽奖啥的,是个挺普遍的需求,可惜遍寻文档(https://docs.rongcloud.cn/v4/)无果,只能靠自己了,咱们来看看怎么做吧。首先,我们要先在聊天页面添加个属性,也就是需要功能按钮所在的 view@pro... ...查看全部

给输入框上方加个功能按钮,类似常用语或者抽奖啥的,是个挺普遍的需求,可惜遍寻文档(https://docs.rongcloud.cn/v4/)无果,只能靠自己了,咱们来看看怎么做吧。

首先,我们要先在聊天页面添加个属性,也就是需要功能按钮所在的 view

@property (nonatomic, strong) UIView *needAddView;

再就是需要重写 viewWillAppear 生命周期函数,添加这个 needAddView,设置 UI 布局,保证进入页面时,needAddView 可以正确显示

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    //初始化 needAddView,添加到 self.view 上,坐标 y = 输入框的 y 坐标 - needAddView 高度
    CGFloat needAddView_height = 50.f;
    CGFloat y = self.chatSessionInputBarControl.frame.origin.y - needAddView_height;
    self.needAddView = [[UIView alloc] initWithFrame:CGRectMake(0, y, self.conversationMessageCollectionView.frame.size.width, needAddView_height)];
    self.needAddView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:self.needAddView];
    //设置消息内容 collectionView 的高度,要减去 needAddView 的高度,避免被遮挡。
    CGRect frame = self.conversationMessageCollectionView.frame;
    frame.size.height -= needAddView_height;
    self.conversationMessageCollectionView.frame = frame;
}

最后需要根据输入框的位置变化,对 UI 布局做改变

-(void)chatInputBar:(RCChatSessionInputBarControl *)chatInputBar shouldChangeFrame:(CGRect)frame {
    //切记要调用父类方法,保证 UI 布局显示正确
    [super chatInputBar:chatInputBar shouldChangeFrame:frame];
    //needAddView 的坐标 y = 输入框的 y 坐标 - needAddView 高度。回调方法中的 frame 是输入框改变后的值。
    CGRect viewFrame = self.needAddView.frame;
    viewFrame.origin.y = frame.origin.y - viewFrame.size.height;
    self.needAddView.frame = viewFrame;
    //设置消息内容 collectionView 的高度,要减去 needAddView 的高度,避免被遮挡。
    CGRect collectionViewFrame = self.conversationMessageCollectionView.frame;
    collectionViewFrame.size.height -= self.needAddView.frame.size.height;
    self.conversationMessageCollectionView.frame = collectionViewFrame;
    //重新设置消息内容 collectionView 的 ContentOffset,正常显示消息内容。
    if (self.conversationMessageCollectionView.contentSize.height > collectionViewFrame.size.height) {
        [self.conversationMessageCollectionView setContentOffset:CGPointMake(0, self.conversationMessageCollectionView.contentSize.height - collectionViewFrame.size.height) animated:NO];
    }
}

最后再提一句,如果有类似的功能需求实现不了的,可以去融云官网(https://www.rongcloud.cn/),登录后台提工单,他们会有专人给出解决方案。

融云 Web SDK 删除历史消息

IM即时通讯木土走召 发表了文章 • 0 个评论 • 279 次浏览 • 2020-12-24 14:33 • 来自相关话题

前提: 已通过融云 SDK 实现单群聊聊天功能需求: 在现有基础上, 完成删除历史消息的功能先按照需求梳理需要完成的步骤:1、根据融云文档删除历史消息2、调用删除历史消息接口成功后. 界面同样做删除/跳转的渲染3、调用获取会话列表界面4、根... ...查看全部

前提: 已通过融云 SDK 实现单群聊聊天功能

需求: 在现有基础上, 完成删除历史消息的功能

先按照需求梳理需要完成的步骤:

1、根据融云文档删除历史消息

2、调用删除历史消息接口成功后. 界面同样做删除/跳转的渲染

3、调用获取会话列表界面

4、根据最新会话列表数据重新渲染会话列表

根据融云文档, 可知会话列表和历史消息的关系如下:

https://docs.rongcloud.cn/v4/views/im/ui/faq/glossary.html#conversation-history-msg

删除历史消息

根据融云文档, 发现融云包含两种删除消息方法, 分别为: 按消息 Id 删除按时间戳删除

按消息 Id 删除:

根据文档可知, 按消息 Id 删除其实就是传入单个 或 多个消息 messageUId, 删除传入的消息

适用场景: 1、右键删除单个消息 2、批量删除会话内消息

var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.deleteMessages([
  { messageUId: '2jJ9-KU1j-OLJG-29KL', sentTime: 1580869079801, messageDirection: 1 },
  { messageUId: '8UJ9-JU9j-WSJG-92K0', sentTime: 1580869078886, messageDirection: 1 }
]).then(function(){
  console.log('删除历史消息成功');
});

按时间戳删除:

按时间戳删除即为传入时间戳, 将会删除此时间之前的所有消息

适用场景: 1、清空某会话所有消息 2、清除某会话某时间之前所有消息

var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.clearMessages({
  timestrap: +new Date()
}).then(function(){
  console.log('清除历史消息成功');
});

综上, 本公司要求删除会话所有消息, 所以按时间戳删除更符合本公司产品逻辑

此处遇到两个问题: 1、按照时间戳删除会话的历史消息后. 刷新会话列表, 发现会话列表中依然包含刚刚清空消息的会话 2、删除时传入了当前时间, 结果却返回了 33007 (解释为未开通历史消息服务, 但开发者后台却早已开通)

于是到融云官网提出工单, 得到了这两个问题的解决方案. 以下依次说明

删除会话列表

针对问题: 按照时间戳删除会话的历史消息后. 刷新会话列表, 发现会话列表中依然包含刚刚清空消息的会话

工单解释: 清空历史消息, 并不会删除会话. 因为某些客户是需要清空消息但保留会话的. 如果需要清空后同时删除会话, 需要再调用删除会话接口

于是根据文档调用删除会话, 再次重新获取会话列表数据, 问题已经解决了

删除会话文档: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/conversation/clear/web.html

// conversation 会话实例
var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.destory().then(function(){
  console.log('删除会话成功');
});

删除历史消息时间参数

针对问题: 删除时传入了当前时间, 结果却返回了 33007 (解释为未开通历史消息服务, 但开发者后台却早已开通)

工单回复: 因传入了比融云服务器时间还要大的时间, 所以服务返回了此错误码. 如果要保证删除成功, 建议传入会话最后一条消息的 sentTime (发送时间)

这次解释比较牵强, 每个人本地时间都是不同的, 此问题应该融云服务端去解决才合理

但是为了解决项目需求, 还是采用融云提供的方法解决了此问题

var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
var time = latestMessage.sentTime; // 此处时间戳传入当前会话的最后一条消息的 发送时间
conversation.clearMessages({
  timestrap: time
}).then(function(){
  console.log('清除历史消息成功');
});


老旧系统重构技巧,轻松搞定遗留代码

科技创新王叫兽 发表了文章 • 0 个评论 • 95 次浏览 • 2020-11-30 10:03 • 来自相关话题

前几天偶然看到一位网友发的内容,说是老系统改了一行代码就崩溃了,着实令人头秃。越是成功的公司,越是有大堆的老系统和无法统计的遗留代码,尤其是基础服务相关的代码,那简直是按下葫芦浮起瓢的现实版本。创业公司倒是好一点,没有历史包袱。我们也经常重构老旧代码,不为别的... ...查看全部

摄图网_500719153_wx_商务办公绩效分析(非企业商用).jpg

前几天偶然看到一位网友发的内容,说是老系统改了一行代码就崩溃了,着实令人头秃。越是成功的公司,越是有大堆的老系统和无法统计的遗留代码,尤其是基础服务相关的代码,那简直是按下葫芦浮起瓢的现实版本。创业公司倒是好一点,没有历史包袱。我们也经常重构老旧代码,不为别的,就是怕放太久发霉。恰好最近也在做系统重构,总结下我们在做的事情和一些技巧。


代码也会发霉



会发霉的不只是食物,代码也会。我们通常称为腐化。腐化的过程每天都在发生,一个紧急需求,一个新同事加入,一个变态的问题修复,一个共建项目,等等。腐化,是永远无法避免的,就像宇宙的熵增不可逆一样。面对腐化,架构师往往会加上一个防腐层。如果你的系统还没有防腐层,赶紧考虑考虑。即便有防腐层,也招架不住岁月的摧残,就像头上那日渐稀少的秀发,这就是命啊。

所以,我们需要重构。逆熵增需要做大量的功,要对混乱的代码重新梳理,使其恢复条理清晰、架构分明的优良状态。


梳理,还是梳理



重构前花大量时间对历史逻辑做梳理,而且要细致。古人曰“工欲善其事,必先利其器”。梳理好的逻辑就是重构的指路明灯。哪些要丢弃,哪些要优化,哪些要重构,哪些要产品重新定,哪些是风险点,在梳理好后就基本明了了。梳理带来的不只是逻辑的浮现,可能还有架构的方向。通过梳理,能够明确发现业务逻辑,甚至可以定义出新的领域模型、值、事件等。想想银河纪元时代,拿着银河系实时星图作战,就知道梳理有多重要了。


回放,对比变化



重构之后,测试要全面覆盖。这时候,回放就非常重要了。阿里也开源了一个回放工具 jvm-sandbox-repeater。GitHub 地址https://github.com/alibaba/jvm-sandbox-repeater。通过回放,可以在预发环境 debug 线上问题,可以看到线上真实流量在预发环境的实际表现。通过对比,可以发现重构后哪些地方和之前不一样了,尤其是页面渲染和持久化的数据。

回放做好以后,还可以作为日常发布的快速验证。只要本次回放和上次正常回放差异不大,基本上风险就已经很小了。


架构,以终为始



既然做重构了,架构方面就要好好设计。尽量摒弃错综复杂的历史逻辑,设计新的架构方案。以提高研发效率、降低维护成本为最终目标,所有的重构设计都围绕着这个目标展开。没有什么是不能改的,如果不能,那就加两个更牛逼的程序员。如果重构后还保留一坨屎一样的遗留代码,真不知道重构的意义是什么。重构就是要以终为始,在新的架构设计中,让遗留代码重新投胎以获得新的生命。


改善,代码重构



优秀的程序员是需要不定期对已有代码做或多或少的重构的。在《重构 改善既有代码的设计》一书中,作者已经给了很多重构的具体方法。重复代码抽出、过长函数拆分、模型重新设计、封装字段、封装集合、以State/Strategy取代类型码、方法移动位置等诸多技巧,这里就不展开了。我觉得每个程序员都应该好好学习下这本书,然后深入实践下代码级重构。正如书评所说“虽不应翻着重构手册干活,但需对本书中提到的70多个重构方法成竹在胸”。


速度,速战速决



遗留代码很多已经像网贷一样了,越陷越深。投入资源做重构所获得的回报,实际上比继续维护老代码高的多。重构的过程要快,过程中日常需求尽量暂停。重构的工作量很大,但是对速度要求也很高。如果一边重构,一边线上还在做需求变更,很可能陷入困境,甚至在重构后丢失线上逻辑。梳理做好了,回放做好了,程序员就可以按照计划快速重构,多上几个人,确保快速完成。



心态,胆大心细



前面的工作都做好了,那接下来就是干了。

放心大胆地干,不要怂。这段代码看不懂怎么办,改!看回放。这段代码又臭又硬怎么办,改!看回放。

改的时候,也要心细一点,好好理解下原有的业务逻辑。在战略上藐视敌人,也要在战术上重视敌人。做好 code review,让了解的人一起看改动,或者团队成员一起把把关。


本文转自“程序之心”,原文地址https://mp.weixin.qq.com/s/AwoJOLclWhvvTp-KuzS4uA


一起爬山吗?如果张东升是个程序员

好玩创意梅川酷子 发表了文章 • 0 个评论 • 294 次浏览 • 2020-07-08 10:28 • 来自相关话题

我就问你怕不怕!张东升是一家互联网公司的程序员,一直以来都勤勤恳恳老实工作。可最近一段时间,老板接了几个项目回来,不但开启了996的工作模式,更要命的是频频更改需求,弄得大家是敢怒不敢言。时间一久,东升慢慢开始消极怠工,晚上也不怎么加班了。终于有一天,和老板在... ...查看全部

我就问你怕不怕!

1.jpg

张东升是一家互联网公司的程序员,一直以来都勤勤恳恳老实工作。
可最近一段时间,老板接了几个项目回来,不但开启了996的工作模式,更要命的是频频更改需求,弄得大家是敢怒不敢言。
时间一久,东升慢慢开始消极怠工,晚上也不怎么加班了。终于有一天,和老板在会议室吵了起来,老板决意让其忙完手头的项目就离职。
老板看大家最近一段时间都很辛苦,决定组织一次团建,在群里询问大家有什么活动建议。
这时,张东升提议:“最近大家都工作挺累,也没有什么机会锻炼,身体要紧,要不一起去爬六峰山吧”
东升的提议获得了不少人的赞同,团建活动就这么定了下来。
团建这天,爬至半山腰,东升问老板:“您看我还有机会吗?”
2.png
老板看了他一眼,没有说话,继续抽烟。
爬至山顶,大家三三两两都在拍照发朋友圈。
这时东升拉住老板到一旁说给他拍照,老板知道东升是为讨好自己,也就没有拒绝。
东升举起手机,却说老板衣服有褶皱,上前为其整理,竟趁其不备将其推下山崖···

晚上,张东升还在电脑前调试着代码,突然,一封主题为“警告”的邮件窗口从侧边弹了出来。
东升的心跳立刻加速,小心翼翼的点开了这封邮件,正文只有四个字:“请看附件”
附件是一个word文档,东升并没有立即打开,职业习惯让他打开了VMware虚拟机,在虚拟机中打开了这个文件。
原来以为白天的事无人知晓,没想到这一幕正好被对面山头正在拍摄抖音短视频的三个小孩用手机录了下来。
三个小孩看到了张东升T恤上的公司名字,并在公司网站上找到了他的邮箱,这才给他发了这封邮件。
张东升看到后,大惊失色,想找到对方,却不知道对方是什么来头。
这时他注意到附件是一个docx文件,想到office2007及其以后的版本才用这个格式,其实际上是一个压缩文件格式zip。
东升思索片刻将其重命名为一个zip文件,然后解压,想看看是否能发现些什么信息。
3.png
接着在docProps目录下找到了app.xml:
...
<AppVersion>15.0000</AppVersion>
...
版本号是15,看来对方用的是一个Office2013版本的word。
东升很快在网络上搜到了一个漏洞:CVE-2017-11882,这是一个可以远程执行代码的漏洞,字符串拷贝没有对长度进行校验导致栈溢出。
说干就干,东升打开了metasploit,通过它很快生成一段包含恶意代码的word文件,将其作为附件回复给了对方。
三个小孩此刻正聚集在朱朝阳的家里,自从他们发送了警告邮件,心里就忐忑难耐,一直盯着电脑屏幕,看看是否会有回复。
一看到邮件弹窗,就赶紧点了开来。邮件正文也只写了一句话:我想说的都在附件中。
朱朝阳没有犹豫,又立刻点了附件。电脑上的Word进程随即启动,解析附件doc文件时,触发了漏洞,执行了张东升事先编写的恶意代码。而此时,朱朝阳却一无所知。
4.png
恶意程序很快和张东升的电脑建立了网络连接,并开始收集朱朝阳电脑的信息,IP地址、MAC地址、电脑文件等等。
东升不愧是经验丰富的程序员,为了躲过电脑防火墙的拦截,他编写的恶意代码通过ICMP协议的负载字段进行数据传输。
朱朝阳正瞅着空白的word文档感到疑惑,突然电脑屏幕上出现了一个视频聊天窗口,一陌生男子的画面突然出现吓得朱朝阳惊出了一身冷汗。
定睛一看,这男子不是别人,正是推人的张东升。
张东升先开口了:“没想到竟然是个小孩。我已经知道你电脑的IP地址,也知道你家住在哪里,明天上午出来聊聊,就在你家巷子口的面馆。”,说完就切断了视频信号。
张东升的突然出现,显然吓坏了朱朝阳。一旁的严良问到:“什么是IP地址?他又是怎么知道你家的位置,连面馆都知道,这简直太可怕了。”
朱朝阳镇定了两分钟,缓过神来,说到:“一定是刚才的邮件附件有问题,我的电脑已经被他控制。IP地址是电脑接入网络分配的通信身份证号码,通过IP地址就能锁定电脑的位置,再用地图一看就能知道附近的街道布局和街景画面,知道面馆也就不足为奇了”
这一夜注定是个不眠之夜。
第二天,双方如约相见。张表示可以用钱买下手机,严良威胁张东升,要卖可以,必须30万。
东升愣了一下,“你们小小年纪,要这么多钱做什么?”
严良顶了一句:“不关你的事”
东升无奈,表示要先看到手机视频再说。
朱朝阳拿出手机,刚打开视频,手机竟然没电。张见状拿出自己手机的充电器给朱朝阳。
待手机充电,张看到了视频。张表示他一个程序员,挣得不多,要等到四月份发了年终奖才凑得齐。
三小孩却只给了他一个星期时间。
一个星期过去,见东升未曾联系,三小孩主动联系张。张却不以为意,说让他们去报警吧。
三小孩不解,正想拿着手机去报警,却发现手机竟然已经死机无法打开了。
原来张东升拿出的那个充电器是事先精心准备,充电器里面内置了一个小型芯片,数据线一旦连接到手机就植入病毒程序,等待时机进行手机数据破坏。
5.png
不过,让张东升没有想到的是,朱朝阳竟然提前备份了数据,再次发来邮件威胁。
夜晚,洗完澡的张东升看着镜子里在自己,回想这些年多少次熬夜加班,不记得何时竟已经秃头,戴上了假发。
6.png
怒从心中起,恶向胆边生。张东升决定把这三个小孩一并收拾了。
东升跟踪数日,终于找到另外两个小孩原来住在海边浅滩的破船上,一天夜里洒满汽油纵火焚烧。
随即又潜到朱朝阳的住处,竟发现虽然已是深夜,朱朝阳还在电脑旁写着代码,旁边的书桌上放满了C/C++编程、数据结构与算法、操作系统等书籍。不禁想起了当年挑灯学习编程的自己。没想到一失足成千古恨,如今自己再也回不了头了。
不知何故,张东升竟改变主意,悄然离开了。
第二天,张向朱朝阳的电子邮箱里发送了一份学习资料,什么剑指offer、分布式计算、云计算、微服务、Dubbo、高并发、数据库实战,琳琅满目,应接不暇,足足有100多G。
不久,海边纵火一事案发,警方通过在电信局部署的网络流量采集中心的木马警报日志,溯源恢复了之前的邮件来往信息,很快锁定了程序员张东升。
张东升再次来到朱朝阳家,挟持了朱,警方随后赶到。
朱问张:你杀了我的小伙伴,为什么却给我发了一堆学习资料?
张东升笑着说:“杀了你有什么意思,我要你像我一样,成为一个程序员。”
朝阳却说:“那你干嘛给我放网盘,100多G的资料,60KB/s,你知道要下多久吗?”
东升一听大怒,举起手中利器便要作势刺向朝阳,只听一声枪响,东升应声倒下。


转自:Python技术之巅(公众号:PythonPeak)

作者:轩辕之风

产品经理在我手里,一个赞捏一下

回复

好玩创意梅川酷子 回复了问题 • 2 人关注 • 1 个回复 • 209 次浏览 • 2020-08-31 18:41 • 来自相关话题

一把双刃剑 -- 融云即时通讯sdk中的自定义消息使用心得&指南 (下)

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 16 次浏览 • 3 天前 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20210224192919.png

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).

他家的官网和文档地址: 官网:https://www.rongcloud.cn/ 文档:https://docs.rongcloud.cn/v4

这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面).

心得 (下)

自定义小视频消息

接上篇对自定义消息的开发心得哈.

因为融云家自带的小视频消息是需要收费的, 需要在服务端开通小视频服务后, 同时在端上做一下配置, 才可以使用小视频消息. 我一看这还得了, 想方设法收我钱呢不是. 不过他家只是对小视频类型的消息在服务端做了限制, 而不是完全不让在消息中携带视频链接. 自定义消息是随便自定义的, 那么我自定义一个小视频消息不就好啦.

大概实现思路如下:

自定义小视频消息继承MediaMessageContent,其中mLocalPath是小视频文件本地的存放路径,mMediaUrl是小视频文件上传到文件服务器后的http/https地址。

小视频的拍摄,播放我们RongCloud SDK没有接口,开发者自己实现。

当拍摄完成,发送小视频消息时使用方法

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallback callback)或者

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallbackWithUploader callback)

这两个方法的不同是后者开发者负责小视频文件的上传到指定的服务器,前者使用我们RongCloud默认的文件服务器

以上是大致步骤,小视频开发过程中可能遇到的问题,说明如下:

1.关于缩略图的处理,我们SDK没有直接上传一张图片返回一个url地址的接口,开发者可以把缩略图上传到自己的服务器,这样缩略图跟mMediaUrl类似,小视频消息展示显示缩略图时加载一张网络图片即可。

另一种缩略图处理方式类似我们SDK发送图片消息时的缩略图处理,把缩略图做base64编码,放到自定义消息体中直接传输,这种方式涉及到消息发送时把缩略图转化为base64数据和接收到消息时还原为缩略图,在我们SDK内部使用的是MessageHandler。

关于MessageHandler,我们RongCloud的每个消息都有一个MessageHandler,此前我们文档从没有介绍过这个

MessageHandler,对用户透明的,用户的自定义消息没有指定它是因为有个默认的DefaultMessageHandler。

自定义消息时可以指定自己的MessageHandler,例如图片消息的定义如下

ImageMessage.png

MessageHandler在消息发送和接收时在IPC进程中会被自动调用,它有两个方法,encodeMessage

和decodeMessage,在消息接收后调用decodeMessage时开发者可以把base64对应的数据转化为缩略图url,这样在展示缩略图时直接使用url即可。

/**
 * 解码 {@link MessageContent} 到 {@link Message} 中。
 *
 * @param message 用于存放 MessageContent 的消息实体。
 * @param content 将要被解码的 MessageContent。
 */
public abstract void decodeMessage(Message message, T content);

/**
 * 对 {@link Message} 编码。
 *
 * @param message 将要被编码的 Message 实体。
 */
public abstract void encodeMessage(Message message);

此文档包含了两个附件分别为自定义小视频消息和对应的小视频消息MessageHandler,供开发者参考

2.开发中可能还会遇到小视频文件上传时进度更新的问题,如果开发者自定义的小视频消息不继承自MediaMessageContent而是MessageContent,需要自己在UI上维护上传进度


【Android开发】如何使用融云的消息扩展

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 14 次浏览 • 3 天前 • 来自相关话题

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html总结文... ...查看全部

微信截图_20210224192550.png

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html

总结文档中的几个重点:

  • 4.0.3之前的版本无法使用。

  • 消息扩展是 Message类的属性,也就是说自定义消息也可以使用。

  • 单条消息只能设置300个kv,(某些场景不可用,比如在群里发400个人可领的红包)。

  • 仅支持单聊、群聊。

  • key和value都是字符串类型,并且key不能是中文,否则会报错INVALID_PARAMETER。

  • 用IMKit的UI发送消息时需要拦截一下,设置消息扩展开关。

使用步骤:

  1. 消息的发送端必须给需要消息扩展的消息开开关,没有全局设置,只能单条消息设置。分以下两种情况:

  2. 如果使用的是 IMKit 提供的UI发送消息,在 ConversationFragment中的 onSendToggleClick 方法发送消息,在 sendMessage 前调用下面的方法

    /**
     * 设置消息扩展信息列表
     * 

扩展信息只支持单聊和群组,其它会话类型不能设置扩展信息。


     *
     * @param expansion 消息扩展信息列表
     */
    public void setExpansion(HashMap<String, String> expansion) {
        this.expansion = expansion;
    }

还需要设置 canIncludeExpansion 和 expansionDic 。

  • 如果是自己调用 RongIM 和 RongIMClient 接口发的消息,用 MessageContent 类的对象构造一个 Message 对象,然后设置 canIncludeExpansion 和 expansionDic 再调用RCIM中对应的 send 方法将消息发送出去即可。

  • 更新和删除扩展的操作可以参考官方文档。

  • 想要实时捕获消息扩展的更改和删除,就需要设置回调,在回调中更新UI。设置回调可参考官方文档。

融云官网:https://www.rongcloud.cn/

文档频道:https://docs.rongcloud.cn/v4


Spring Initializr中生成的mvnw是干吗的?

科技创新王叫兽 发表了文章 • 0 个评论 • 50 次浏览 • 2021-02-04 10:11 • 来自相关话题

来源于公众号-程序猿DD当我们使用Spring Initializr来创建Spring Boot工程的时候,有没有发现在工程根目录下有两个名为mvnw的文件:从命名、图标、扩展名来猜测,这两个文件的作用应该是一样的,只是cmd文件应该是用在windows下跑的... ...查看全部

来源于公众号-程序猿DD

当我们使用Spring Initializr来创建Spring Boot工程的时候,有没有发现在工程根目录下有两个名为mvnw的文件:

微信图片_20210203173520.jpg

从命名、图标、扩展名来猜测,这两个文件的作用应该是一样的,只是cmd文件应该是用在windows下跑的,而另外一个则是用于linux环境下跑的。

那么这个文件到底是用来做什么呢?下面我们一起尝试了解一下:

第一步:打开读一下

微信图片_20210203173526-1024x1531.png

因为内容较多,我这里就不放出来了。内容也非常易懂,只要你了解shell和maven,就能知道这个脚本主要做这几件事:

  1. 检测你是否有安装Maven,如果没有,就自动下载一个(这样才能完成后续的构建任务)

  2. 检查你是否有安装Java或者配置是否正确,这个无法自己完成,如果报错了,就要自己处理一下,比如JAVA_HOME没有,那就自己配置下。

  3. 检查否存在版本不兼容的情况,如果不兼容他会下载合适的版本来帮助你完成构建

更多检查的细节可以自行打开查看和学习

第二步:执行验证下

执行命令:mvnw install

微信图片_20210203173531.png等待构建完成,我们再看看:微信图片_20210203173534.png完美!轻松简单的完成了一个Spring Boot项目的构建!

拒绝白嫖!开源模式的反击:向不要脸的云服务商收费!

科技创新王叫兽 发表了文章 • 0 个评论 • 73 次浏览 • 2021-02-04 10:11 • 来自相关话题

年底将至,又到了大多数打工人开始编写年终小结的时候,但是总有那么一群人,平时碌碌无为,一等到年底,就到处打听到处收集各种成效数字,然后各种不要脸的洋洋洒洒的写在自己的年终小结里,仿佛那些完全没参与过的项目都是他一手打造的,彷佛那些别人辛苦一年才做出的成绩理应给... ...查看全部

年底将至,又到了大多数打工人开始编写年终小结的时候,但是总有那么一群人,平时碌碌无为,一等到年底,就到处打听到处收集各种成效数字,然后各种不要脸的洋洋洒洒的写在自己的年终小结里,仿佛那些完全没参与过的项目都是他一手打造的,彷佛那些别人辛苦一年才做出的成绩理应给他一样。

遇到这种人,气不气?

其实这种人不在少数,也正是有这样的人,才会有拿来主义,才会有做事毫无底线的云服务商,才导致了开源商业模式一再萎靡。

当然,老实人并不代表着一定要被欺负!就在1月15号,Elastic 公司 官方宣布:改变 Elasticsearch 和 Kibana 的开源协议,由 Apache 2.0 变更为 SSPL 与 Elastic License。

这次变更的主要针对目的,就是那些毫无底线的云服务商!

微信图片_20210203172125 (1).jpgElastic 公司 的CEO Shay Banon在他的博文中解释到:

虽然,从Apache 2.0 变更为 SSPL 与 Elastic License会改变一部分源代码,但是对于绝大多数的使用免费版本的用户及Elastic 公司的云客户以及自主管理软件客户也不会受到影响。唯一会受影响的,就是云服务商。

SSPL 允许用户以自由且不受限制的方式使用并修改代码成果,但是SSPL也有自己的要求:那就是如果将产品以作为一种服务进行交付,那么必须同时公开发布所有关于修改及 SSPL 之下管理层的源代码。

由此将限制云服务商在不对项目做出任何贡献的前提下,发布云服务商自己的 Elasticsearch 与 Kibana 服务,从而达到保护Elastic 公司的权益,亦可以让公司在开发免费产品方面持续投入的热情和资源,避免恶性循环。

微信图片_20210203172149.png

对于为什么会这么做,Shay Banon也给出了自己的解释:

长久以来,云服务商不断地肆意的将免费的开源软件集成到自己的云产品中,同时将其作为自己的云服务解决方案给予客户使用,俗称白嫖~造成了许多客户越来越多的放弃这些开源厂商的付费版本,直接断绝了开源厂商的经济来源!

早在Elastic之前,就有不少开源公司进行了变更:

Redis、MongDB、OpenCV、Google,Elastic绝不会是最后一个。

此次Elastic主要是借鉴了MongoDB的类似经验。

微信图片_20210203172152.png早在2018 年 10 月,MongoDB 就已经宣布将开源协议从 GNU AGPLv3切换到 SSPL,无论当时如何引起部分用户的不满,MongoDB 至少目前还很健康的继续经营,股价也从 2018 年的不足 100 美元 / 股涨到现在的 361 美元 / 股。可以说是开源公司学习的楷模。微信图片_20210203172155.jpg

SSPL 就是由 MongoDB 最初创建的可提供源代码的许可证,旨在体现开放源码理想的许可证,允许自由和不受限制地使用,修改和重新分发。

SSPL最简单也是最核心的一点就是前文提过的:如果将产品以作为一种服务进行交付,那么必须同时公开发布所有关于修改及 SSPL 之下管理层的源代码。

SSPL 是基于GPLv3 的 copyleft 许可证。并没有经过 OSI(Open Source Initiative,开源促进会,批准开源协议的机构) 批准,因此官方还特意声明:为了避免混淆,我们暂时不将两个产品称为开源,而使用“免费和开放” 进行描述。

有时候甚至会觉得,SSPL纯粹就是被那些习惯了白嫖的云服务商逼出来的产物,就像当每个人做事都只顾着看别人的成效数字,又有几个人能真正做好事呢?


融云 IMKit 音频录制参数

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 215 次浏览 • 2021-01-11 15:14 • 来自相关话题

场景:使用融云自带的界面进行语音消息的播放。自己进行音频录制。使用的融云的 RCHQMessage问题:语音消息 iOS 和 Android 不互通,接收到消息之后无法播放。解决方案:经过与融云开发者的确认,使用时必须保证如下录制参数:iOS AVA... ...查看全部

场景:

  1. 使用融云自带的界面进行语音消息的播放。

  2. 自己进行音频录制。

  3. 使用的融云的 RCHQMessage

问题:

  1. 语音消息 iOS 和 Android 不互通,接收到消息之后无法播放。

解决方案:

经过与融云开发者的确认,使用时必须保证如下录制参数:

iOS AVAudioRecorder 录制参数如下设置:

AVFormatIDKey : @(kAudioFormatMPEG4AAC_HE),
AVSampleRateKey : @(44100.0),
AVNumberOfChannelsKey : @1,
AVEncoderBitRateKey : @(16000)

Android MediaRecorder 录制参数如下:

setAudioSamplingRate(44100);
setAudioEncodingBitRate(16000);
setAudioChannels(1);
setAudioSource(MediaRecorder.AudioSource.MIC);
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);

其他一些内容的使用可以自己去官网文档搜索:

融云文档:https://docs.rongcloud.cn/v4/


干货分享——使用融云通讯能力库 IMLib 实现单群聊的阅读回执

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 219 次浏览 • 2021-01-11 15:14 • 来自相关话题

今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 SDK 去实现,还是有些坑在等着我们的,下面就开始分(bì)享(kēng)喽~分享之前先做一些准备工作,先找到我们需要调用的接口文档文档:https://docs.rongclou... ...查看全部

今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 SDK 去实现,还是有些坑在等着我们的,下面就开始分(bì)享(kēng)喽~

  1. 分享之前先做一些准备工作,先找到我们需要调用的接口文档

  2. 根据不同的会话类型以及消息的发送方和接收方,要分别处理

    • 单聊

      接收方 :在阅读消息后,调用 RCIMClient 类的发送阅读回执接口,参数如下:

      conversationType 单聊会话类型

      targetId 消息的会话 ID

      time 会话最后一条消息的发送时间(sentTime)

/*!
 发送某个会话中消息阅读的回执

 @param conversationType    会话类型
 @param targetId            会话 ID
 @param timestamp           该会话中已阅读的最后一条消息的发送时间戳
 @param successBlock        发送成功的回调
 @param errorBlock          发送失败的回调[nErrorCode: 失败的错误码]

 @discussion 此接口只支持单聊, 如果使用 IMLib 可以注册监听
 RCLibDispatchReadReceiptNotification 通知,使用 IMKit 直接设置RCIM.h
 中的 enabledReadReceiptConversationTypeList。

 @warning 目前仅支持单聊。

 @remarks 高级功能
 */
- (void)sendReadReceiptMessage:(RCConversationType)conversationType
                      targetId:(NSString *)targetId
                          time:(long long)timestamp
                       success:(void (^)(void))successBlock
                         error:(void (^)(RCErrorCode nErrorCode))errorBlock;

发送方:监听下面这个通知,在接收后修改消息的展示

/*!
 @const 收到已读回执的 Notification

 @discussion 收到消息已读回执之后,IMLib 会分发此通知。

 Notification 的 object 为 nil,userInfo 为 NSDictionary 对象,
 其中 key 值分别为 @"cType"、@"tId"、@"messageTime",
 对应的 value 为会话类型的 NSNumber 对象 、会话的 targetId 、已阅读的最后一条消息的 sendTime。
 如:
 NSNumber *ctype = [notification.userInfo objectForKey:@"cType"];
 NSNumber *time = [notification.userInfo objectForKey:@"messageTime"];
 NSString *targetId = [notification.userInfo objectForKey:@"tId"];
 NSString *fromUserId = [notification.userInfo objectForKey:@"fId"];

 收到这个消息之后可以更新这个会话中 messageTime 以前的消息 UI 为已读(底层数据库消息状态已经改为已读)。

 @remarks 事件监听
 */
FOUNDATION_EXPORT NSString *const RCLibDispatchReadReceiptNotification;

群聊

发送方

在发送消息 A 后,需要针对该消息发送回执请求,message 传之前发的消息 A

/*!
 请求消息阅读回执

 @param message      要求阅读回执的消息
 @param successBlock 请求成功的回调
 @param errorBlock   请求失败的回调[nErrorCode: 失败的错误码]

 @discussion 通过此接口,可以要求阅读了这条消息的用户发送阅读回执。

 @remarks 高级功能
 */
- (void)sendReadReceiptRequest:(RCMessage *)message
                       success:(void (^)(void))successBlock
                         error:(void (^)(RCErrorCode nErrorCode))errorBlock;

设置下面代理函数,在接收到发送方发来的阅读回执响应后,修改消息的展示

/*!
 IMlib消息接收的监听器

 @discussion
 设置IMLib的消息接收监听器请参考RCIMClient的setReceiveMessageDelegate:object:方法。

 @warning 如果您使用IMlib,可以设置并实现此Delegate监听消息接收;
 如果您使用IMKit,请使用RCIM中的RCIMReceiveMessageDelegate监听消息接收,而不要使用此监听器,否则会导致IMKit中无法自动更新UI!
 */
@protocol RCIMClientReceiveMessageDelegate <NSObject>

/*!
 消息已读回执响应(收到阅读回执响应,可以按照 messageUId 更新消息的阅读数)
 @param messageUId       请求已读回执的消息ID
 @param conversationType conversationType
 @param targetId         targetId
 @param userIdList 已读userId列表
 */
- (void)onMessageReceiptResponse:(RCConversationType)conversationType
                        targetId:(NSString *)targetId
                      messageUId:(NSString *)messageUId
                      readerList:(NSMutableDictionary *)userIdList;

接收方

设置下面代理函数,在接收到消息 A 后,还会接收到针对消息 A 的阅读回执请求

/*!
 IMlib消息接收的监听器

 @discussion
 设置IMLib的消息接收监听器请参考RCIMClient的setReceiveMessageDelegate:object:方法。

 @warning 如果您使用IMlib,可以设置并实现此Delegate监听消息接收;
 如果您使用IMKit,请使用RCIM中的RCIMReceiveMessageDelegate监听消息接收,而不要使用此监听器,否则会导致IMKit中无法自动更新UI!
 */
@protocol RCIMClientReceiveMessageDelegate <NSObject>

/*!
 请求消息已读回执(收到需要阅读时发送回执的请求,收到此请求后在会话页面已经展示该 messageUId 对应的消息或者调用
 getHistoryMessages 获取消息的时候,包含此 messageUId 的消息,需要调用 sendMessageReadReceiptResponse
 接口发送消息阅读回执)

 @param messageUId       请求已读回执的消息ID
 @param conversationType conversationType
 @param targetId         targetId
 */
- (void)onMessageReceiptRequest:(RCConversationType)conversationType
                       targetId:(NSString *)targetId
                     messageUId:(NSString *)messageUId;

在代理方法中,调用下面接口发送阅读回执响应给发送方

/*!
 发送阅读回执

 @param conversationType 会话类型
 @param targetId         会话 ID
 @param messageList      已经阅读了的消息列表
 @param successBlock     发送成功的回调
 @param errorBlock       发送失败的回调[nErrorCode: 失败的错误码]

 @discussion 当用户阅读了需要阅读回执的消息,可以通过此接口发送阅读回执,消息的发送方即可直接知道那些人已经阅读。

 @remarks 高级功能
 */
- (void)sendReadReceiptResponse:(RCConversationType)conversationType
                       targetId:(NSString *)targetId
                    messageList:(NSArray<RCMessage *> *)messageList
                        success:(void (^)(void))successBlock
                          error:(void (^)(RCErrorCode nErrorCode))errorBlock;

总结

阅读回执需要区分会话类型处理,且单聊的阅读回执是针对会话的,群聊的阅读回执是针对某一条消息的:

单聊:接收方阅读某个会话的消息后,发送阅读回执 ——发送方接到阅读回执,更新 UI

群聊:发送方发送消息 A, 针对该消息,发送阅读回执请求 —— 接收方实现监听代理,接收到消息 A 的阅读回执请求 —— 接收方发送阅读回执响应 —— 发送方收到阅读回执响应,更新 UI


自定义融云会话列表 cell 选中背景

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 224 次浏览 • 2021-01-08 11:35 • 来自相关话题

项目用的融云,IMKit SDK(自带 UI),但是在使用会话列表的时候,cell 选中和长按的时候默认是灰色的。设计说需要改啊,那就研究一下如何修改吧。废话不多说,直接继承 RCConversationListViewController,然后重写以下方法1... ...查看全部

项目用的融云,IMKit SDK(自带 UI),但是在使用会话列表的时候,cell 选中和长按的时候默认是灰色的。设计说需要改啊,那就研究一下如何修改吧。废话不多说,直接继承 RCConversationListViewController,然后重写以下方法
1.以下代码是去掉选中颜色的

- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
}

2.以下代码是重写颜色的,想配啥色请随意

- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    UIView *backView = [[UIView alloc] init];
    backView.backgroundColor = [UIColor redColor];
    cell.selectedBackgroundView = backView;
}

SDK 开放出来的 .h 类对方法注释写的很详细,建议大家多看一下,这样可以快速集成,少走弯路。也是培养集成第三方库的好习惯。融云(www.rongcloud.cn)

给融云的输入框上方加个功能按钮,怎么整?

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 222 次浏览 • 2021-01-08 11:29 • 来自相关话题

给输入框上方加个功能按钮,类似常用语或者抽奖啥的,是个挺普遍的需求,可惜遍寻文档(https://docs.rongcloud.cn/v4/)无果,只能靠自己了,咱们来看看怎么做吧。首先,我们要先在聊天页面添加个属性,也就是需要功能按钮所在的 view@pro... ...查看全部

给输入框上方加个功能按钮,类似常用语或者抽奖啥的,是个挺普遍的需求,可惜遍寻文档(https://docs.rongcloud.cn/v4/)无果,只能靠自己了,咱们来看看怎么做吧。

首先,我们要先在聊天页面添加个属性,也就是需要功能按钮所在的 view

@property (nonatomic, strong) UIView *needAddView;

再就是需要重写 viewWillAppear 生命周期函数,添加这个 needAddView,设置 UI 布局,保证进入页面时,needAddView 可以正确显示

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    //初始化 needAddView,添加到 self.view 上,坐标 y = 输入框的 y 坐标 - needAddView 高度
    CGFloat needAddView_height = 50.f;
    CGFloat y = self.chatSessionInputBarControl.frame.origin.y - needAddView_height;
    self.needAddView = [[UIView alloc] initWithFrame:CGRectMake(0, y, self.conversationMessageCollectionView.frame.size.width, needAddView_height)];
    self.needAddView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:self.needAddView];
    //设置消息内容 collectionView 的高度,要减去 needAddView 的高度,避免被遮挡。
    CGRect frame = self.conversationMessageCollectionView.frame;
    frame.size.height -= needAddView_height;
    self.conversationMessageCollectionView.frame = frame;
}

最后需要根据输入框的位置变化,对 UI 布局做改变

-(void)chatInputBar:(RCChatSessionInputBarControl *)chatInputBar shouldChangeFrame:(CGRect)frame {
    //切记要调用父类方法,保证 UI 布局显示正确
    [super chatInputBar:chatInputBar shouldChangeFrame:frame];
    //needAddView 的坐标 y = 输入框的 y 坐标 - needAddView 高度。回调方法中的 frame 是输入框改变后的值。
    CGRect viewFrame = self.needAddView.frame;
    viewFrame.origin.y = frame.origin.y - viewFrame.size.height;
    self.needAddView.frame = viewFrame;
    //设置消息内容 collectionView 的高度,要减去 needAddView 的高度,避免被遮挡。
    CGRect collectionViewFrame = self.conversationMessageCollectionView.frame;
    collectionViewFrame.size.height -= self.needAddView.frame.size.height;
    self.conversationMessageCollectionView.frame = collectionViewFrame;
    //重新设置消息内容 collectionView 的 ContentOffset,正常显示消息内容。
    if (self.conversationMessageCollectionView.contentSize.height > collectionViewFrame.size.height) {
        [self.conversationMessageCollectionView setContentOffset:CGPointMake(0, self.conversationMessageCollectionView.contentSize.height - collectionViewFrame.size.height) animated:NO];
    }
}

最后再提一句,如果有类似的功能需求实现不了的,可以去融云官网(https://www.rongcloud.cn/),登录后台提工单,他们会有专人给出解决方案。

融云 Web SDK 删除历史消息

IM即时通讯木土走召 发表了文章 • 0 个评论 • 279 次浏览 • 2020-12-24 14:33 • 来自相关话题

前提: 已通过融云 SDK 实现单群聊聊天功能需求: 在现有基础上, 完成删除历史消息的功能先按照需求梳理需要完成的步骤:1、根据融云文档删除历史消息2、调用删除历史消息接口成功后. 界面同样做删除/跳转的渲染3、调用获取会话列表界面4、根... ...查看全部

前提: 已通过融云 SDK 实现单群聊聊天功能

需求: 在现有基础上, 完成删除历史消息的功能

先按照需求梳理需要完成的步骤:

1、根据融云文档删除历史消息

2、调用删除历史消息接口成功后. 界面同样做删除/跳转的渲染

3、调用获取会话列表界面

4、根据最新会话列表数据重新渲染会话列表

根据融云文档, 可知会话列表和历史消息的关系如下:

https://docs.rongcloud.cn/v4/views/im/ui/faq/glossary.html#conversation-history-msg

删除历史消息

根据融云文档, 发现融云包含两种删除消息方法, 分别为: 按消息 Id 删除按时间戳删除

按消息 Id 删除:

根据文档可知, 按消息 Id 删除其实就是传入单个 或 多个消息 messageUId, 删除传入的消息

适用场景: 1、右键删除单个消息 2、批量删除会话内消息

var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.deleteMessages([
  { messageUId: '2jJ9-KU1j-OLJG-29KL', sentTime: 1580869079801, messageDirection: 1 },
  { messageUId: '8UJ9-JU9j-WSJG-92K0', sentTime: 1580869078886, messageDirection: 1 }
]).then(function(){
  console.log('删除历史消息成功');
});

按时间戳删除:

按时间戳删除即为传入时间戳, 将会删除此时间之前的所有消息

适用场景: 1、清空某会话所有消息 2、清除某会话某时间之前所有消息

var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.clearMessages({
  timestrap: +new Date()
}).then(function(){
  console.log('清除历史消息成功');
});

综上, 本公司要求删除会话所有消息, 所以按时间戳删除更符合本公司产品逻辑

此处遇到两个问题: 1、按照时间戳删除会话的历史消息后. 刷新会话列表, 发现会话列表中依然包含刚刚清空消息的会话 2、删除时传入了当前时间, 结果却返回了 33007 (解释为未开通历史消息服务, 但开发者后台却早已开通)

于是到融云官网提出工单, 得到了这两个问题的解决方案. 以下依次说明

删除会话列表

针对问题: 按照时间戳删除会话的历史消息后. 刷新会话列表, 发现会话列表中依然包含刚刚清空消息的会话

工单解释: 清空历史消息, 并不会删除会话. 因为某些客户是需要清空消息但保留会话的. 如果需要清空后同时删除会话, 需要再调用删除会话接口

于是根据文档调用删除会话, 再次重新获取会话列表数据, 问题已经解决了

删除会话文档: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/conversation/clear/web.html

// conversation 会话实例
var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
conversation.destory().then(function(){
  console.log('删除会话成功');
});

删除历史消息时间参数

针对问题: 删除时传入了当前时间, 结果却返回了 33007 (解释为未开通历史消息服务, 但开发者后台却早已开通)

工单回复: 因传入了比融云服务器时间还要大的时间, 所以服务返回了此错误码. 如果要保证删除成功, 建议传入会话最后一条消息的 sentTime (发送时间)

这次解释比较牵强, 每个人本地时间都是不同的, 此问题应该融云服务端去解决才合理

但是为了解决项目需求, 还是采用融云提供的方法解决了此问题

var conversation = im.Conversation.get({
  targetId: '接收方的 userId',
  type: RongIMLib.CONVERSATION_TYPE.PRIVATE
});
var time = latestMessage.sentTime; // 此处时间戳传入当前会话的最后一条消息的 发送时间
conversation.clearMessages({
  timestrap: time
}).then(function(){
  console.log('清除历史消息成功');
});


老旧系统重构技巧,轻松搞定遗留代码

科技创新王叫兽 发表了文章 • 0 个评论 • 95 次浏览 • 2020-11-30 10:03 • 来自相关话题

前几天偶然看到一位网友发的内容,说是老系统改了一行代码就崩溃了,着实令人头秃。越是成功的公司,越是有大堆的老系统和无法统计的遗留代码,尤其是基础服务相关的代码,那简直是按下葫芦浮起瓢的现实版本。创业公司倒是好一点,没有历史包袱。我们也经常重构老旧代码,不为别的... ...查看全部

摄图网_500719153_wx_商务办公绩效分析(非企业商用).jpg

前几天偶然看到一位网友发的内容,说是老系统改了一行代码就崩溃了,着实令人头秃。越是成功的公司,越是有大堆的老系统和无法统计的遗留代码,尤其是基础服务相关的代码,那简直是按下葫芦浮起瓢的现实版本。创业公司倒是好一点,没有历史包袱。我们也经常重构老旧代码,不为别的,就是怕放太久发霉。恰好最近也在做系统重构,总结下我们在做的事情和一些技巧。


代码也会发霉



会发霉的不只是食物,代码也会。我们通常称为腐化。腐化的过程每天都在发生,一个紧急需求,一个新同事加入,一个变态的问题修复,一个共建项目,等等。腐化,是永远无法避免的,就像宇宙的熵增不可逆一样。面对腐化,架构师往往会加上一个防腐层。如果你的系统还没有防腐层,赶紧考虑考虑。即便有防腐层,也招架不住岁月的摧残,就像头上那日渐稀少的秀发,这就是命啊。

所以,我们需要重构。逆熵增需要做大量的功,要对混乱的代码重新梳理,使其恢复条理清晰、架构分明的优良状态。


梳理,还是梳理



重构前花大量时间对历史逻辑做梳理,而且要细致。古人曰“工欲善其事,必先利其器”。梳理好的逻辑就是重构的指路明灯。哪些要丢弃,哪些要优化,哪些要重构,哪些要产品重新定,哪些是风险点,在梳理好后就基本明了了。梳理带来的不只是逻辑的浮现,可能还有架构的方向。通过梳理,能够明确发现业务逻辑,甚至可以定义出新的领域模型、值、事件等。想想银河纪元时代,拿着银河系实时星图作战,就知道梳理有多重要了。


回放,对比变化



重构之后,测试要全面覆盖。这时候,回放就非常重要了。阿里也开源了一个回放工具 jvm-sandbox-repeater。GitHub 地址https://github.com/alibaba/jvm-sandbox-repeater。通过回放,可以在预发环境 debug 线上问题,可以看到线上真实流量在预发环境的实际表现。通过对比,可以发现重构后哪些地方和之前不一样了,尤其是页面渲染和持久化的数据。

回放做好以后,还可以作为日常发布的快速验证。只要本次回放和上次正常回放差异不大,基本上风险就已经很小了。


架构,以终为始



既然做重构了,架构方面就要好好设计。尽量摒弃错综复杂的历史逻辑,设计新的架构方案。以提高研发效率、降低维护成本为最终目标,所有的重构设计都围绕着这个目标展开。没有什么是不能改的,如果不能,那就加两个更牛逼的程序员。如果重构后还保留一坨屎一样的遗留代码,真不知道重构的意义是什么。重构就是要以终为始,在新的架构设计中,让遗留代码重新投胎以获得新的生命。


改善,代码重构



优秀的程序员是需要不定期对已有代码做或多或少的重构的。在《重构 改善既有代码的设计》一书中,作者已经给了很多重构的具体方法。重复代码抽出、过长函数拆分、模型重新设计、封装字段、封装集合、以State/Strategy取代类型码、方法移动位置等诸多技巧,这里就不展开了。我觉得每个程序员都应该好好学习下这本书,然后深入实践下代码级重构。正如书评所说“虽不应翻着重构手册干活,但需对本书中提到的70多个重构方法成竹在胸”。


速度,速战速决



遗留代码很多已经像网贷一样了,越陷越深。投入资源做重构所获得的回报,实际上比继续维护老代码高的多。重构的过程要快,过程中日常需求尽量暂停。重构的工作量很大,但是对速度要求也很高。如果一边重构,一边线上还在做需求变更,很可能陷入困境,甚至在重构后丢失线上逻辑。梳理做好了,回放做好了,程序员就可以按照计划快速重构,多上几个人,确保快速完成。



心态,胆大心细



前面的工作都做好了,那接下来就是干了。

放心大胆地干,不要怂。这段代码看不懂怎么办,改!看回放。这段代码又臭又硬怎么办,改!看回放。

改的时候,也要心细一点,好好理解下原有的业务逻辑。在战略上藐视敌人,也要在战术上重视敌人。做好 code review,让了解的人一起看改动,或者团队成员一起把把关。


本文转自“程序之心”,原文地址https://mp.weixin.qq.com/s/AwoJOLclWhvvTp-KuzS4uA