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

96
醉生夢死
2018.01.17 12:54* 字数 2755

前言

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

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

开发环境

根据网易云信官网 NIM Windows(PC) Demo导读 需要安装相应的环境

下载VS2013 update5版本

我在安装完此环境后依然运行不起来(由于我是c++小白,环境搭建花了点时间),然后网上各种找原因,终于找到解决方案,安装 Windows SDK 8.1 ,然后终于才把下载过来的项目跑起来。(出现问题时忘记截图了,抱歉!)


业务需要

如下图所示的消息类型

带标题图片和说明且点击打开浏览器跳转相关网页的消息

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


正文

  1. 将demo运行起来后,首先我们要修改的就是将 appkey 改为自己的。
    我这里说下VS里如何运行C++项目(可能不是标准的姿势)。
将nim_demo设置为默认启动项目

设置完成后,以后运行项目就可以直接按F5或者点击本地Windows调试器启动项目,不用每次都去点击 nim_demo右键->调试->启动新实例

启动项目直接点击或者按F5启动

如果你不嫌麻烦的话,也可以按以下方式启动


vs里选中nim_demo右键 -> 调试 -> 启动新实例
停止调试在菜单栏或者按Shift+F5
  1. 运行没有问题后,修改以下几个文件配置,将demo修改为自己所用。
    • 修改 shared/util.h 中的DEMO_GLOBAL_APP_KEYDEMO_GLOBAL_TEST_APP_KEY,填入自己的appKey
修改appKey

修改后点击 停止调试,然后在 启动新实例,启动后再使用自己的云信帐号帐号登录

登录界面
登录后的主界面

3.替换图片
替换主要与logo有关的图片
bin/themes/default/login/login.png
bin/themes/default/about/login.png
bin/themes/default/main/duoduan01.png

4.注释掉我们不需要的功能 (我这里只注释关键代码,以防哪天需要此功能时放开注释就好了)

  • 登录页 去除 注册免登录,体验匿名聊天室功能
登录页面去掉 注册 和 免登录,体验匿名聊天室功能

打开登录界面的布局文件 bin/themes/default/login/login.xml
找到name值为register_account的Button节点,添加属性 visible="false",将此按钮隐藏。
找到name值为anonymous_chatroom的Button节点,添加属性 visible="false",将此按钮隐藏。
上面两步操作完成后,点击运行新实例,发现帐号输入框无法获得焦点,解决办法是在底下的 VBox nameenter_panelmargin属性上添加name值为register_accountButton的高 15,修改前的margin值为20,0,20,0,修改后的margin值为20,15,20,0,目的是让注册按钮隐藏后,表单距顶部的高度不变。

//...
<Button name="register_account" margin="0,5,15,10" halign="right" textid="STRID_LOGIN_FORM_REGISTER" cursortype="hand" font="24" normaltextcolor="link_green" visible="false"/>
//...
<!-- margin top 需要设置为 register_account button 的高度,解决input输入框获取不到焦点问题 -->
<VBox name="enter_panel" width="240" height="auto" margin="20,15,20,0" bkimage="user_password.png">
  //...
</VBox>
//...
<Button name="anonymous_chatroom" margin="0,0,0,20" halign="center" textid="STRID_LOGIN_FORM_ANONYMOUSCHATROOM" cursortype="hand" font="24" normaltextcolor="link_green"  visible="false"/>
//...
  • 登录后的主界面 去除底部的 直播间浏览器测试
首页去掉底部的 直播间 和 浏览器测试

打开登录界面的布局文件 bin/themes/default/main/main.xml,注释掉以下节点

<!--
<HBox height="36">
    <Button name="chatroom" width="stretch" height="stretch" textid="STRID_MAINWINDOW_CHATROOM_ENTRANCE" font="7" normaltextcolor="white" cursortype="hand" normalcolor="bk_main_wnd_search" hotcolor="bk_main_wnd_title" pushedcolor="bk_main_wnd_search"/>
    <Control class="splitline_ver_level1" height="stretch"/>
    <Button name="cef_test" width="stretch" height="stretch" textid="STRID_MAINWINDOW_BROWSER_TEST" font="7" normaltextcolor="white" cursortype="hand" normalcolor="bk_main_wnd_search" hotcolor="bk_main_wnd_title" pushedcolor="bk_main_wnd_search"/>
</HBox>
 -->
  • 一对一单聊,聊天界面移除 语音视频白板提醒消息
移除这几项功能

打开 tool_kits/ui_component/ui_kit/gui/session/session_box.cpp,注释掉以下内容(大约在94行)

//...
void SessionBox::InitSessionBox()
{
   //...
   if (session_type_ == nim::kNIMSessionTypeP2P && !IsFileTransPhone())
   {
       // 将语音,视频,白板注释掉
       //btn_audio->SetVisible(true);
       //btn_video->SetVisible(true);
       //btn_rts->SetVisible(true);
   }
   //...
}
//...

打开 bin/themes/default/session/session_box.xml,找到 namebtn_tip 的 Button,添加属性visible="false",隐藏掉提醒消息。

<Button name="btn_tip" width="30" height="30" valign="center" margin="4" tooltiptextid="STRID_SESSION_NOTIFICATION_MSG"
                                forenormalimage="file='btn_tip.png' dest='5,5,25,25'" foredisabledimage="file='btn_tip_disable.png' dest='5,5,25,25' fade='80'"
                                hotimage="icon_hover.png" pushedimage="icon_pushed.png" visible="false"/>

5.新增自定义 图文链接消息的显示

  • 创建自定义的消息类型,在 tool_kits/ui_component/ui_kit/gui/session/control/bubbles 目录下创建 bubble_link.hbubble_link.cpp 文件
    1.到指定目录下右键
2.创建bubble_link.h文件
3.同样的在该目录下创建bubble_link.cpp文件

创建完成后从同级目录下的其他文件里复制代码过来,稍作修改,比如我复制的是 bubble_text.hbubble_text.cpp 文件内容到我自己创建的文件中,复制后的代码如下:

bubble_link.h 内容如下

#pragma once
#include "bubble_item.h"

namespace nim_comp
{
    /** @class MsgBubbleLink
      * @brief 会话窗体中聊天框内的图文链接消息项
      * @copyright (c) 2015, NetEase Inc. All rights reserved
      * @author Andy
      * @date 2018/1/8
      */
    class MsgBubbleLink : public MsgBubbleItem
    {
    public:
        /**
          * 初始化控件内部指针
          * @param[in] bubble_right 是否显示到右侧
          * @return void 无返回值
          */
        virtual void InitControl(bool bubble_right);

        /**
          * 初始化控件外观
          * @param[in] msg 消息信息结构体
          * @return void 无返回值
          */
        virtual void InitInfo(const nim::IMMessage &msg);

        /**
        * 响应此消息项的单击消息,打开浏览器
        *@param[in] param 被单击的按钮的相关信息
        * @return bool 返回值true: 继续传递控件消息, false: 停止传递控件消息
        */
        virtual bool OnClicked(ui::EventArgs* arg);

        /**
          * 响应此消息项的右击消息,弹出菜单
          * @param[in] param 被单击的菜单项的相关信息
          * @return bool 返回值true: 继续传递控件消息, false: 停止传递控件消息
          */
        bool OnMenu(ui::EventArgs* arg);

    private:
        /**
        * 设置图片资源的路径
        * @return void 无返回值
        */
        void InitResPath();

    protected:
        // 显示消息的box容器
        ui::ButtonBox*  msg_link_;
        // 显示图片的box容器
        ui::Box*        image_box_;
        // 标题
        ui::RichEdit*   title_;
        // 图片
        ui::Control*    image_;
        // 描述
        ui::RichEdit*   describe_;
        // 图片的目录
        std::wstring    path_;
        // 需要跳转的地址
        std::wstring    link_;
    };
}

bubble_link.cpp 内容如下

#include "bubble_link.h"

using namespace ui;

namespace nim_comp
{

    void MsgBubbleLink::InitControl(bool bubble_right)
    {
        __super::InitControl(bubble_right);
    }

    void MsgBubbleLink::InitInfo(const nim::IMMessage &msg)
    {
        __super::InitInfo(msg);
    }

    void MsgBubbleLink::InitResPath()
    {
    }
    
    bool MsgBubbleLink::OnMenu(ui::EventArgs* arg)
    {
        return false;
    }

    bool MsgBubbleLink::OnClicked(ui::EventArgs* arg)
    {
        return true;
    }
    
}

函数内的实现代码暂时没写,别急,我们继续往下走。

  • 添加图文链接消息枚举类型
    编辑文件 tool_kits/ui_component/ui_kit/module/session/session_util.h,新增 CustomMsgType_Link = 5 表示图文链接消息,如下图所示
添加标识为图文链接消息类别字段

=5 可写可不写,不写默认是5,因为从1开始刚好在第5位,强列建议加上 = 5,我其他平台的图文链接消息type是5,所以为了统一消息类型 type

  • 导入上面自己创建的头文件
    编辑文件tool_kits/ui_component/ui_kit/gui/session/session_box.h,在头部导入自己创建的 bubble_link.h
5.png

这步完成后,项目需要重新生成下,方便在其他模块下能识别获取到你刚刚创建的 bubble_link 文件,如下图。

选中nim_demo模块,右键重新生成
  • 接下来在接收消息和发送消息处理的地方添加我们自定义的 bubble_link
    编辑 tool_kits/ui_component/ui_kit/gui/session/session_box.cpp,在函数 ShowMsg 添加如下代码,大约在409行
// 头部导入相关内容
#include <tchar.h>
#include <iostream>
#include <UrlMon.h>
#pragma comment(lib,"urlmon.lib")

// 此处省略无关代码

MsgBubbleItem* SessionBox::ShowMsg(const nim::IMMessage &msg, bool first, bool show_time)
{
    // 此处忽略部分代码
    else if (msg.type_ == nim::kNIMMessageTypeCustom)
    {
        Json::Value json;
        if (StringToJson(msg.attach_, json) && json.isObject())
        {
            // 此处忽略部分代码
            // 添加此处 else if 代码
            else if (sub_type == CustomMsgType_Link)
            {
                item = new MsgBubbleLink;
                if (StringToJson(msg.attach_, json)
                    && json.isObject()
                    && json.isMember("data")
                    && json["data"]["image_url"].asString() != "")
                {
                    std::wstring image_dir = GetUserImagePath();
                    if (!nbase::FilePathIsExist(image_dir, true))
                        nbase::CreateDirectoryW(image_dir);

                    std::wstring image_path = image_dir + nbase::UTF8ToUTF16(msg.client_msg_id_);

                    // 将网络图片保存到本地
                    std::string url = json["data"]["image_url"].asString();
                    size_t len = url.length();//获取字符串长度
                    int nmlen = MultiByteToWideChar(CP_ACP, 0, url.c_str(), len + 1, NULL, 0);//如果函数运行成功,并且cchWideChar为零,
                    //返回值是接收到待转换字符串的缓冲区所需求的宽字符数大小。
                    wchar_t* buffer = new wchar_t[nmlen];
                    MultiByteToWideChar(CP_ACP, 0, url.c_str(), len + 1, buffer, nmlen);
                    
                    HRESULT hr = URLDownloadToFile(NULL, buffer, image_path.c_str(), 0, NULL);
                    if (hr == S_OK)
                    {
                        printf("下载OK");
                    }
                }
                else
                {
                    QLOG_ERR(L"There is not image_link property.");
                }
            }
        }
    }
}

编辑 tool_kits/ui_component/ui_kit/gui/session/msg_record.cpp,在函数 ShowMsg 添加如下代码,大约在120行

void MsgRecordForm::ShowMsg(const nim::IMMessage &msg, bool first, bool show_time)
{
    // ...
    MsgBubbleItem* item = NULL;
    if (msg.type_ == nim::kNIMMessageTypeText 
        || IsNetCallMsg(msg.type_, msg.attach_))
    {
        //...
    }
    else if (msg.type_ == nim::kNIMMessageTypeCustom)
    {
        Json::Value json;
        if (StringToJson(msg.attach_, json) && json.isObject())
        {
            //...
            else if (sub_type == CustomMsgType_Rts)
            {
              //...
            }
            // 添加此处 else if 代码
            else if (sub_type == CustomMsgType_Link)
            {
                item = new MsgBubbleLink;
            }
        }
    }
    //...
}
  • 添加发送图文链接测试消息,用于开发测试(正式上线前注释掉此步骤的代码)
    在发送文本消息时,如果消息是以 custom:: 开头的消息,就触发发送图文链接消息,如下所示。
    以custom::开头的文本消息就触发发送图文链接消息

编辑文件 tool_kits/ui_component/ui_kit/gui/session/session_box.cpp,修改SendText函数,添加如下代码

//...

void SessionBox::SendText( const std::string &text )
{
    nim::IMMessage msg;

    //...

    if (msg.type_ != nim::kNIMMessageTypeRobot)
    {
        // 添加测试发送图文链接消息,当用户发送的文本内容是以 custom:: 开头时,触发如下业务
        std::string prefix = "custom::";
        if (strncmp(text.c_str(), prefix.c_str(), prefix.length()) == 0)
        {
            msg.type_ = nim::kNIMMessageTypeCustom;
            // 消息内容体格式如下
            /*
            {
                type: 5,
                data: {
                    title: '消息标题',
                    link_url: '点击跳转链接',
                    image_url: '图片URL',
                    describe: '消息描述',
                }
            }
            */
            Json::Value json;
            Json::FastWriter writer;

            json["type"] = CustomMsgType_Link;
            json["data"]["title"] = Json::Value(text.substr(prefix.length()));
            json["data"]["link_url"] = Json::Value("https://www.jianshu.com/u/bd57ade96e8a");
            json["data"]["image_url"] = Json::Value("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2544269114,2104066965&fm=27&gp=0.jpg");
            json["data"]["describe"] = Json::Value(text.substr(prefix.length()));
            
            //msg.content_ = writer.write(json);
            msg.attach_ = writer.write(json);
            // 转json字符串
            json_msg = msg.ToJsonString(true);
        }
        else
        {
            //...
        }
    }
    else
    {
        //...
    }

    AddSendingMsg(msg);
    nim::Talk::SendMsg(json_msg);
}
//...

点击发送后,会发现自己发送的内容是空白,那是因为我们还没做图文链接消息的显示代码,发送之后,我们在其他客户端查看消息。

Android端登录另一个帐号查看收到的消息(此处android端已实现图文链接消息的显示)

  • 发送图文链接消息后,接下来添加图文链接消息的显示
    消息的显示都是由xml文件来控制布局的,打开文件目录 bin/themes/default/session(见下图)可以看到一些消息类型如视频消息文件消息猜拳消息图片消息等都分成 left(接收消息的显示布局文件) 和 right(发送消息的显示布局文件)结尾的 xml 文件。
消息框中各种消息类型的布局文件

同样的,我们在这个目录下创建 link_left.xmllink_right.xml 文件,文件内容如下

link_left.xml

<?xml version="1.0" encoding="UTF-8"?>
<Window>
  <!-- 可点击的消息体 -->
  <ButtonBox name="msg_link" width="auto" height="auto" menu="true" mousechild="false" cursortype="hand" bkimage="file='bubble_left.png' corner='25,15,15,15'" padding="0,0,0,10">
    <!-- 消息标题 -->
    <RichEdit name="title" width="270" height="30" margin="15,15,10,9" padding="10,6,10,6" font="7" normaltextcolor="darkcolor" readonly="true" multiline="true" vscrollbar="false" autovscroll="false" bkcolor="white"/>
    <!-- 图片,默认不显示 -->
    <Box name="image_box" width="270" height="170" margin="15,42,10,0" padding="10,0,0,0" bkcolor="white" visible="false">
      <Box width="250" height="auto" margin="0,0,0,0" padding="0,10,0,10" topbordersize="1" bottombordersize="1" bordercolor="transbkblack1" bkcolor="white">
        <Control name="image" width="250" height="140" bkimage="image_def" bkcolor="lightcolor"/>
      </Box>
    </Box>
    <!-- 描述,默认不显示 -->
    <RichEdit name="describe" height="auto" maxheight="65" margin="15,212,10,0" padding="10,8,0,10" font="11" normaltextcolor="darkcolor" readonly="true" multiline="true" vscrollbar="false" autovscroll="false" bkcolor="white" visible="false"/>
  </ButtonBox>
</Window>

link_right.xml

<?xml version="1.0" encoding="UTF-8"?>
<Window>
  <!-- 可点击的消息体 -->
  <ButtonBox name="msg_link" width="auto" height="auto" menu="true" mousechild="false" cursortype="hand" bkimage="file='bubble_right.png' corner='15,15,25,15'" padding="0,0,0,10">
    <!-- 消息标题 -->
    <RichEdit name="title" width="270" height="30" margin="10,15,15,9" padding="10,6,10,6" font="7" normaltextcolor="darkcolor" readonly="true" multiline="true" vscrollbar="false" autovscroll="false" bkcolor="white"/>
    <!-- 图片,默认不显示 -->
    <Box name="image_box" width="270" height="170" margin="10,42,15,0" padding="10,0,0,0" bkcolor="white" visible="false">
      <Box width="250" height="auto" margin="0,0,0,0" padding="0,10,0,10" topbordersize="1" bottombordersize="1" bordercolor="transbkblack1" bkcolor="white">
        <Control name="image" width="250" height="140" bkimage="image_def" bkcolor="lightcolor"/>
      </Box>
    </Box>
    <!-- 描述,默认不显示 -->
    <RichEdit name="describe" height="auto" maxheight="65" margin="10,212,15,0" padding="10,8,0,10" font="11" normaltextcolor="darkcolor" readonly="true" multiline="true" vscrollbar="false" autovscroll="false" bkcolor="white" visible="false"/>
  </ButtonBox>
</Window>

其实两个文件内容几乎一样,只是 bkimage 和 margin 值稍微有点不一样。

接着我们在前面创建好的 bubble_link.cpp 中写业务逻辑代码控制布局文件的显示隐藏和赋值

bubble_link.cpp

#include "bubble_link.h"
#include "util/user.h"
#include <windows.h>  
#include <tchar.h>  
#include <assert.h>  

using namespace ui;

namespace nim_comp
{

    void MsgBubbleLink::InitControl(bool bubble_right)
    {
        __super::InitControl(bubble_right);

        msg_link_ = new ButtonBox;
        if (bubble_right)
            GlobalManager::FillBoxWithCache(msg_link_, L"session/link_right.xml");
        else
            GlobalManager::FillBoxWithCache(msg_link_, L"session/link_left.xml");
        bubble_box_->Add(msg_link_);

        image_box_ = (Box *)msg_link_->FindSubControl(L"image_box");
        title_ = (RichEdit*)msg_link_->FindSubControl(L"title");
        image_ = this->FindSubControl(L"image");
        describe_ = (RichEdit*)msg_link_->FindSubControl(L"describe");

        // 添加鼠标右键点击事件
        msg_link_->AttachMenu(nbase::Bind(&MsgBubbleLink::OnMenu, this, std::placeholders::_1));
        msg_link_->AttachClick(nbase::Bind(&MsgBubbleLink::OnClicked, this, std::placeholders::_1));
    }

    // 字符串转宽字符
    std::wstring StringToWString(const std::string& str) {
        int num = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
        wchar_t *wide = new wchar_t[num];
        MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, wide, num);
        std::wstring w_str(wide);
        delete[] wide;
        return w_str;
    }

    void MsgBubbleLink::InitInfo(const nim::IMMessage &msg)
    {
        __super::InitInfo(msg);

        InitResPath();

        Json::Value json;
        if (StringToJson(msg.attach_, json) && json.isObject())
        {
            int sub_type = json["type"].asInt();
            if (sub_type == CustomMsgType_Link && json["data"].isObject())
            {
                link_ = nbase::UTF8ToUTF16(json["data"]["link_url"].asString());

                title_->SetText(StringToWString(json["data"]["title"].asString()));

                // 是否有图片参数
                bool hasImage = !json["data"]["image_url"].asString().empty();
                if (hasImage)
                {
                    image_box_->SetVisible(TRUE);
                    std::wstring imgeUri = path_ + nbase::UTF8ToUTF16(msg.client_msg_id_);
                    if (nbase::FilePathIsExist(imgeUri, false))
                        image_->SetBkImage(imgeUri);
                    else
                        image_->SetBkImage(L"image_def");
                }
                else
                {
                    image_box_->SetVisible(FALSE);
                }

                if (json["data"]["describe"].asString().empty())
                {
                    describe_->SetVisible(FALSE);
                }
                else
                {
                    describe_->SetVisible(TRUE);
                    describe_->SetText(StringToWString(json["data"]["describe"].asString()));

                    UiRect rect = describe_->GetMargin();
                    LONG top = hasImage ? 202 : 42;
                    describe_->SetMargin(UiRect(rect.left, top, rect.right, rect.bottom));
                }
            }
        }
        QLOG_WAR(L"user type msg undefine, attach={0}") << msg.attach_;
    }

    void MsgBubbleLink::InitResPath()
    {
        
        std::wstring wpath = GetUserImagePath();
        std::string path = nim::Talk::GetAttachmentPathFromMsg(msg_);

        if (wpath.empty() || !nbase::FilePathIsExist(wpath, false))
        {
            path_ = nbase::UTF8ToUTF16(path);
            std::wstring directory, filename;
            nbase::FilePathApartDirectory(path_, directory);
            nbase::FilePathApartFileName(path_, filename);
        }
        else
        {
            std::wstring directory, filename;
            nbase::FilePathApartDirectory(nbase::UTF8ToUTF16(path), directory);
            nbase::FilePathApartFileName(wpath, filename);
            path_ = wpath;
        }
    }
    
    bool MsgBubbleLink::OnMenu(ui::EventArgs* arg)
    {
        PopupMenu(false, true, false);
        return false;
    }

    bool MsgBubbleLink::OnClicked(ui::EventArgs* arg)
    {
        const TCHAR szOperation[] = _T("open");
        TCHAR szAddress[1024];
        _tcscpy(szAddress, link_.c_str());
        HINSTANCE hRslt = ShellExecute(NULL, szOperation,
            szAddress, NULL, NULL, SW_SHOWNORMAL);
        assert(hRslt > (HINSTANCE)HINSTANCE_ERROR);
        return true;
    }
    
}    
  • 上步操作完成后,我们就可以来测试发送图文链接消息了
    根据我们上面的规则,发送文本消息以 custom:: 开头的消息就触发图文链接消息,当然也可以注释掉部分参数,下面列出我们上面在 session_box.cpp 中写到的如下代码
Json::Value json;
Json::FastWriter writer;

json["type"] = CustomMsgType_Link;
// title 必须要有
json["data"]["title"] = Json::Value(text.substr(prefix.length()));
// link_url 跳转的链接,必须要有
json["data"]["link_url"] = Json::Value("https://www.jianshu.com/u/bd57ade96e8a");
// 图片链接地址,可选
json["data"]["image_url"] = Json::Value("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2544269114,2104066965&fm=27&gp=0.jpg");
// 商品描述,可选项
json["data"]["describe"] = Json::Value(text.substr(prefix.length()));

注释掉后,重新运行发送消息测试,经过测试,发送相关消息,最终显示如下。

消息支持只有title,只有title和image,只有title和describe等类型

点击消息可打开浏览器跳转相应的网页

  • 最后在主界面消息列表的显示修改
如图所示,我希望显示的是[图文链接],而不是自定义消息

首先打开文件目录 bin/lang 下的 zh_CN 目录 和 en_US 目录下的 gdstrings.ini 文件

此目录是多语言支持的配置文件目录

使用编辑器打开这两个 gdstrings.ini 文件,分别添加一行

en_US/gdstrings.ini

STRID_SESSION_ITEM_MSG_TYPE_LINK        =   [Link]

zh_CN/gdstrings.ini

STRID_SESSION_ITEM_MSG_TYPE_LINK        =   [图文链接]
分别在两个文件中添加一行图文链接消息的显示文字

接着回到VS编辑器打开文件 tool_kits/ui_component/ui_kit/module/session/session_util.cpp ,在
GetCustomMsg 函数中添加如下 else if 代码块

std::wstring GetCustomMsg(const std::string &sender_accid, const std::string &msg_attach)
{
    ui::MutiLanSupport* mls = ui::MutiLanSupport::GetInstance();
    std::wstring show_text = mls->GetStringViaID(L"STRID_SESSION_ITEM_MSG_TYPE_CUSTOM_MSG");
    Json::Value json;
    if (StringToJson(msg_attach, json) && json.isObject())
    {
        //...
        // 在最后添加如下判断,如果是 CustomMsgType_Link 类型消息
        else if (sub_type == CustomMsgType_Link)
        {
            show_text = mls->GetStringViaID(L"STRID_SESSION_ITEM_MSG_TYPE_LINK");
        }
    }
    return show_text;
}

完成后,我们再次运行,从消息列表可以看到。


语言为中文时显示
语言为英文时显示
  • 上面有个bug,用户在线状态。
    我另一个帐号明明是在线的,而消息列表却显示[离线]
    这个地方我不管他是在线还是离线,我都返回空字符串,不显示用户在线状态,打开 online_state_event_helper.cpp 在函数 GetOnlineState 中直接返回空字符串,大概在 140行,添加如下代码
std::wstring OnlineStateEventHelper::GetOnlineState(const nim::EventOnlineClientType& online_client_type, const EventMultiConfig& multi_config, bool is_simple)
{
    return L""; // 在线离线状态有bug,所以直接返回空字符串
        //...
}
  • 替换logo图片
    首先我们准备好一张图片,图片可以通过 https://www.ico.la/ 这个在线工具转成 128px * 128px 的 ico 文件,然后替换掉 nim_win_demo 目录下的 nim.ico 文件
替换掉logo图片

替换掉之后,你可能看到的还是显示原来的图片,不要紧,先不管它。

在VS工具里修改 VS_VERSION_INFO 信息

修改此处信息为自己的

修改完后保存,然后退出visual studio,接下来是打包发布过程

到此步基本完成了,最后是打包

打包过程我参考的是此文章,此文章有些步骤我是不需要操作的,有些步骤又没讲详细,所以下我也会列出自己打包是的步骤

  • 首先使用管理员打开 VS 开发工具,如果不是以管理员身份打开,后面的打包步骤会失败。(这只针对Windows10权限问题)
windows10用户需要使用管理员打开VS
VS中打开项目

VS自带的打包程序默认是没有安装的,如果有打包的需要,需要自己去下载一个安装程序。

文件->新建->项目

如果没有安装 InstallShield Limited Edition Project,(按照 此文章 内的1,2,3步骤安装下 。

我这是已安装的

名称可以是中文,我填写的就是我的应用名称
上步操作完成后显示这个界面,点击Application Information
填写应用相关信息
箭头所指的需要稍微修改下,其他项目根据自己需求来
我的应用不需要任何依赖,所以上面俩项都选择No

以下这个步骤,需要添加的文件和文件夹我们可以安装网易云信demo,然后打开安装目录,可以看到他依赖的所有文件和文件夹

安装完网易云信demo后打开安装目录
添加应用程序所依赖的文件和文件夹,依赖项参考上图
应用程序启动和快捷方式设置
修改安装完成后在桌面显示的应用名称
将“Releases”双击打开,然后单击树状节点“SingleImage”,在展开的内容窗口中选择“Setup.exe”选项卡并进行设置
安装时协议安装路径等配置
最后右键重新生成解决方案
最后我们打开文件目录可以查看到安装文件

尾篇

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

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

C++
Web note ad 1