Jekyll2021-03-09T20:11:31+08:00https://www.comsince.cn/feed.xmlcomsince坚持分享,分享快乐comsince2020技术总结与飞享IM项目规划发展2020-12-31T00:00:00+08:002020-12-31T00:00:00+08:00https://www.comsince.cn/2020/12/31/year-summary<h2 id="一年大事记载">一年大事记载</h2>
<h3 id="概述">概述</h3>
<p>飞享IM起源于2019年的推送项目,当时的项目还只是定位于消息推送,因为我从事推送行业已经有几年时间了,为了能够实现技术自主可控,一致希望能自主开发推送系统,加上<a href="https://tio.org">tio</a>项目这几年在国内比较火,同时也为了能够快速开发,所以选择了此网络框架.项目在2019年的时候一度是以开源的方式进行的.那是还不是一个IM项目.</p>
<h3 id="疫情蔓延">疫情蔓延</h3>
<p>2019年末的时候,项目进行了演变开始在向IM项目上推进,到2019年为了支持web聊天功能,我开始研究前端,并在年底之前开源的现在[vue-chat]也就是web版本飞享项目,一直延续到现在.
2020年不平凡的一年,开年疫情蔓延,从1月到3月一致没有管项目了.长达几个月的时间,我以为项目就失败了.那个时候服务端项目还是开源的,项目在短短时间内,收到很多的人关注,我看到大家对IM的研究热情一致没有减退.所以那是也建立qq群组.每天都有一些加我好友,询问一些IM的技术问题.</p>
<h3 id="项目发展">项目发展</h3>
<p>项目这个时候飞速发展,我一直都在埋头苦干,不断增加功能,期间也有人不断提出新的需求.但是项目的可持续性一直没有找到一个合适的方法,知道群里面的一位<code class="language-plaintext highlighter-rouge">唐C</code>好友找了我,那个时候他主动找我,给予我一些服务器上的支持,后来也有些项目上合作.虽然最后项目合作失败了,没有能够帮助到他,但是他是第一个伸出援手,支持开源项目的.在此我也向这位朋友表示感谢.在4,5月疫情如此复杂的情况下给予我帮助.也正式因为他的激励,我更想找到一种可持续的项目发展方式.希望以开源的方式提供技术支持,从而带动项目发展.</p>
<p>这个时候为了以后版权的纠纷,项目在5月份闭源,开始走一些开源项目惯用的方式.我以前也很反感这种方式.但是为了生活,为了能够让项目持续下去,也只能这么做了.希望通过开源的方式跟大家分享技术,并且希望通过这种方式促成与一些企业的合作.所以我一直在个人博客分享IM的技术,即使没有机会合作,也能够分享我在开发IM的过程中一些经验与教训.</p>
<h3 id="开源协议">开源协议</h3>
<p>项目变更了开源协议,使用商业性使用限制协议.这个时候发生了奇怪的事情,群里一位朋友拿着以前的开源代码,在没有经过允许的情况下自主修改擅自开源.并且在项目中并没有声明使用项目的出处.这位群友也是IM爱好者,中间我们进行过多次交流.没想到是最伤我的人.我生气的不是他我的一些设计思想代码开源,主要这个人实在不遵循开源协议,引用项目设计出处拒不说明.因为我一直在使用其他人的开源项目,凡是我用到的项目我都会在项目的Readme中说明出处.在这一年中可能我做的不好的就是我没有能给于开源项目中作出捐赠,或者说非常少.但是以后我也就不断支持其他开源项目,希望我在获利的时候给予别人于支持和鼓励.因为我深知开源不易.尤其是在国内开源环境不是太好的情况下.
这件事对我直接的后果可能是找我寻求技术帮助,我显得有些谨慎了,以为我不知道来自是何目的.所以各位在如果有商业合作,直接跟我说,我也会毫不避讳的分享我自己的体会.</p>
<h3 id="合作共赢">合作共赢</h3>
<p>在不断的与人交流中,我也积累了大量的需求,那些IM并没有好友概念.群中的一位朋友,让我完善整个IM的流程,所以就有了<code class="language-plaintext highlighter-rouge">加好友</code>,<code class="language-plaintext highlighter-rouge">创建群组</code>,<code class="language-plaintext highlighter-rouge">单聊</code>等完整的IM流程.过了一段时间也有人需要<code class="language-plaintext highlighter-rouge">一对一音视频</code>,我也在不断研究过程,逐步实现了这个功能.可以说,是社区中不断的问题反馈才有了IM继续发展的动力.在此也对这些针对飞享提出建议与问题的用户表示感谢.
当然最重要的是感谢一位愿意为技术付费的用户,是你让项目开启了一种全新的道路.让技术变现成为可能,让我有了动力不断优化系统,梳理系统架构</p>
<h3 id="商业客户">商业客户</h3>
<p>项目发展到能够支持商业化应用也少不了一些个人企业用户的支持,彼此之间的合作,让IM功能不断完善.此时有一个群友给我列了下面的需求,虽然最后没有达成合作,但是他建议也为<code class="language-plaintext highlighter-rouge">飞享IM</code>的功能规划奠定了基础.在此对这位群友表示感谢.</p>
<h3 id="im需求">IM需求</h3>
<table>
<thead>
<tr>
<th>项目需求目标</th>
<th>飞享功能测试(web)</th>
<th>飞享功能测试(安卓)</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>提供的登录接口,登录到即时通信服务,同时平台会为每个用户生成一个会话token,作为通信凭证。</td>
<td>支持</td>
<td>支持</td>
<td>通过手机号+手机验证码登录</td>
</tr>
<tr>
<td>以服务方式提供:首先注册成为大数据综合应用平台的用户,注册后与手机绑定,并且与统一登录服务验证。</td>
<td>不支持</td>
<td>不支持</td>
<td> </td>
</tr>
<tr>
<td>文本消息</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>表情消息</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>图片消息</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>发送附件,附件可以是图片、普通格式文件、音乐文件、视频文件</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>地理位置发送</td>
<td>不支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>语音发送</td>
<td>不支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>视频聊天</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>语音聊天</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>语音播放</td>
<td>不支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>语音聊天(多人)</td>
<td>不支持</td>
<td>不支持</td>
<td> </td>
</tr>
<tr>
<td>视频聊天(多人)</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>发送文本消息</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>图片消息</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>表情消息</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>发送附件,附件可以是图片、普通格式文件、音乐文件、视频文件</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>地理位置发送</td>
<td>不支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>用户可以邀请自己的好友进入讨论组进行群聊</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>创建讨论组的用户支持删除修改操作</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>被邀请用户可以退出讨论组</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>支持群聊的所有聊天功能</td>
<td>部分支持</td>
<td>部分支持</td>
<td>讨论组就是群聊,群聊未区分群主和管理员</td>
</tr>
<tr>
<td>即时通讯消息的发送,当消息发送到对端用户后,提供已发送消息回执机制,确保即时通讯消息可靠发送到对方。</td>
<td>不支持</td>
<td>不支持</td>
<td> </td>
</tr>
<tr>
<td>添加好友</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>修改好友基础信息</td>
<td>部分支持</td>
<td>部分支持</td>
<td>支持修改好友备注信息</td>
</tr>
<tr>
<td>删除好友</td>
<td>不支持</td>
<td>不支持</td>
<td> </td>
</tr>
<tr>
<td>好友黑名单</td>
<td>不支持</td>
<td>不支持</td>
<td> </td>
</tr>
<tr>
<td>好友申请同意、拒绝以及忽略</td>
<td>部分支持</td>
<td>部分支持</td>
<td>有添加按钮,没有拒绝和忽略按钮</td>
</tr>
<tr>
<td>用户新建群组</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
<tr>
<td>用户修改群组</td>
<td>部分支持</td>
<td>部分支持</td>
<td>只支持修改群名</td>
</tr>
<tr>
<td>用户解散群组</td>
<td>支持</td>
<td>支持</td>
<td>创建者可以解散群组,非创建者不可以</td>
</tr>
<tr>
<td>用户搜索群组</td>
<td>部分支持</td>
<td>部分支持</td>
<td>只能搜索已加入的群组</td>
</tr>
<tr>
<td>申请入群</td>
<td>不支持</td>
<td>不支持</td>
<td> </td>
</tr>
<tr>
<td>退出群组</td>
<td>支持</td>
<td>支持</td>
<td> </td>
</tr>
</tbody>
</table>
<p><strong>NOTE:</strong> 以上功能pc版本的客户端支持最全,具体功能参见:https://fsharechat.cn/docs/introduction/#%E5%8A%9F%E8%83%BD%E5%88%97%E8%A1%A8</p>
<h3 id="主要事件">主要事件</h3>
<ul>
<li><code class="language-plaintext highlighter-rouge">2020-05</code> 服务端专业版发布,支持minio存储</li>
<li><code class="language-plaintext highlighter-rouge">2020-08</code> 支持群组视频通话</li>
<li><code class="language-plaintext highlighter-rouge">2020-09</code> 基于<code class="language-plaintext highlighter-rouge">Electron</code>的pc版本客户端发布</li>
<li><code class="language-plaintext highlighter-rouge">2020-10</code> 支持已读回执功能,<a href="https://fsharechat.cn">飞享</a>官方网站正式上线</li>
<li><code class="language-plaintext highlighter-rouge">2020-11</code> 服务端支持集群部署,方便横向扩展</li>
<li><code class="language-plaintext highlighter-rouge">2020-12</code> 管理后台预览版上线测试</li>
</ul>
<h2 id="2021-项目规划">2021 项目规划</h2>
<h3 id="管理平台项目">管理平台项目</h3>
<ul>
<li>主页</li>
<li>IM业务管理
<ul>
<li>用户管理
<ul>
<li>封禁用户 即是禁止登录</li>
<li>查看用户详细信息 用户视角查看会话</li>
</ul>
</li>
<li>当前在线用户</li>
<li>用户登录日志</li>
<li>消息管理
<ul>
<li>消息列表
<ul>
<li>消息撤回功能</li>
</ul>
</li>
</ul>
</li>
<li>群组管理
<ul>
<li>查看群组会话</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="im开放平台">IM开放平台</h3>
<p>主要提供开放的接入IM系统,以便复用三方应用系统的登录认证逻辑,IM只负责核心业务</p>
<h3 id="客户端项目">客户端项目</h3>
<ul>
<li>web项目专业版本升级</li>
</ul>
<h2 id="建议">建议</h2>
<p>如果你有其他任何建议可以到QQ群反馈</p>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://tableconvert.com/">Excel 转MarkDown</a></li>
</ul>comsince一年大事记载飞享官网上线2020-11-23T00:00:00+08:002020-11-23T00:00:00+08:00https://www.comsince.cn/2020/11/23/fsharechat-cn<font color="#bd4147">飞享官网上线</font>
<p><br /> 详情请点击<a href="https://fsharechat.cn/">https://fsharechat.cn/</a><br />
<img src="https://media.comsince.cn/minio-bucket-file-name/
fshare-chat-cn.png" alt="img" /></p>comsince飞享官网上线 详情请点击https://fsharechat.cn/送达与已读回执的设计方案说明2020-10-26T00:00:00+08:002020-10-26T00:00:00+08:00https://www.comsince.cn/2020/10/26/delivery-read-report<p>本文主要说明送达与阅读回执的实现方案,并给出具体的设计,仅供参考</p>
<h2 id="设计要点">设计要点</h2>
<ul>
<li>送达回执只上报单聊,群聊消息不上报送达回执</li>
<li>送达只在单聊界面中显示,群聊不关心送达.那么根据<code class="language-plaintext highlighter-rouge">uid</code>和<code class="language-plaintext highlighter-rouge">target</code>就能确定当前用户的单聊界面最后一次消息的送达时间</li>
<li>
<p>单聊已读回执,同送达回执原理一样</p>
</li>
<li>群聊没有送达回执,只统计已读回执.
<ul>
<li>上报时机.当用户进行会话切换</li>
<li>如果用户读取了改群组的最后一条消息,代表这个用户读取所有其他用户发送的消息.即是任意发送消息给该群组的用户可以通过<code class="language-plaintext highlighter-rouge">uid</code>和<code class="language-plaintext highlighter-rouge">target</code>来确定该uid用户是否读取了消息</li>
<li>对于群组消息的已读回执上报,要上报当前群组会话中,发送消息的用户ID</li>
</ul>
</li>
</ul>
<h2 id="送达回执">送达回执</h2>
<ul>
<li>送达<br />
代表接收到对方发送的消息,这里指拉取到最新的用户消息的时间点</li>
</ul>
<p>每个用户上报自己最后一次接收消息的时间</p>
<blockquote>
<p>以下为送达回执sql</p>
</blockquote>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="nv">`t_delivery_report`</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`t_delivery_report`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="nv">`_uid`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_target`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_rid`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_dt`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">UNIQUE</span> <span class="k">INDEX</span> <span class="nv">`delivery_index`</span> <span class="p">(</span><span class="nv">`_uid`</span> <span class="k">DESC</span><span class="p">,</span> <span class="nv">`_target`</span> <span class="k">DESC</span> <span class="p">)</span>
<span class="p">)</span>
<span class="n">ENGINE</span> <span class="o">=</span> <span class="n">InnoDB</span>
<span class="k">DEFAULT</span> <span class="nb">CHARACTER</span> <span class="k">SET</span> <span class="o">=</span> <span class="n">utf8mb4</span>
<span class="k">COLLATE</span> <span class="o">=</span> <span class="n">utf8mb4_unicode_ci</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li>获取送达回执</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="nv">`t_user_delivery_report`</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`t_user_delivery_report`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="nv">`_rid`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_uid`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_seq`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_dt`</span> <span class="nb">DATETIME</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="n">NOW</span><span class="p">(),</span>
<span class="k">INDEX</span> <span class="nv">`user_delivery_index`</span> <span class="p">(</span><span class="nv">`_uid`</span> <span class="k">DESC</span><span class="p">,</span> <span class="nv">`_seq`</span> <span class="k">DESC</span><span class="p">),</span>
<span class="k">UNIQUE</span> <span class="k">INDEX</span> <span class="nv">`user_delivery_index2`</span> <span class="p">(</span><span class="nv">`_uid`</span> <span class="k">DESC</span><span class="p">,</span> <span class="nv">`_rid`</span> <span class="k">DESC</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">ENGINE</span> <span class="o">=</span> <span class="n">InnoDB</span>
<span class="k">DEFAULT</span> <span class="nb">CHARACTER</span> <span class="k">SET</span> <span class="o">=</span> <span class="n">utf8mb4</span>
<span class="k">COLLATE</span> <span class="o">=</span> <span class="n">utf8mb4_unicode_ci</span><span class="p">;</span>
</code></pre></div></div>
<p>送达回执持久化存储,采用即时推送机制,用户发送送达回执后,推送给发送者当前送达回执seq,发送者根据seq获取最新的送达回执列表.
原理同发送消息的推拉模式</p>
<h2 id="已读回执">已读回执</h2>
<ul>
<li>已读<br />
已读跟已送达有点区别,送达代表用户已经拉取到对方发来的消息,在收到消息后,上报此时收到新消息的最新时间,这个时间点.就是该用户的接收消息时间点.
由于消息在每个会话中,每个会话都有阅读时间点.用户没有点击这个会话代表用户没有阅读该会话内的消息.如果只上报一条已读消息,无法区分用户到底阅读的是那个会话里面的消息</li>
</ul>
<blockquote>
<p>已读回执存储</p>
</blockquote>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="nv">`t_read_report`</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`t_read_report`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="nv">`_uid`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_rid`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_type`</span> <span class="nb">tinyint</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_line`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_target`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_dt`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="mi">0</span><span class="p">,</span>
<span class="k">INDEX</span> <span class="nv">`read_report_index`</span> <span class="p">(</span><span class="nv">`_uid`</span><span class="p">,</span> <span class="nv">`_type`</span><span class="p">,</span> <span class="nv">`_line`</span><span class="p">,</span> <span class="nv">`_target`</span><span class="p">),</span>
<span class="k">UNIQUE</span> <span class="k">INDEX</span> <span class="nv">`read_report_index2`</span> <span class="p">(</span><span class="nv">`_uid`</span> <span class="k">DESC</span><span class="p">,</span> <span class="nv">`_target`</span> <span class="k">DESC</span> <span class="p">)</span>
<span class="p">)</span>
<span class="n">ENGINE</span> <span class="o">=</span> <span class="n">InnoDB</span>
<span class="k">DEFAULT</span> <span class="nb">CHARACTER</span> <span class="k">SET</span> <span class="o">=</span> <span class="n">utf8mb4</span>
<span class="k">COLLATE</span> <span class="o">=</span> <span class="n">utf8mb4_unicode_ci</span><span class="p">;</span>
</code></pre></div></div>
<ul>
<li>获取已读回执</li>
</ul>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">DROP</span> <span class="k">TABLE</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="nv">`t_user_read_report`</span><span class="p">;</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="nv">`t_user_read_report`</span> <span class="p">(</span>
<span class="nv">`id`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
<span class="nv">`_rid`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_uid`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_seq`</span> <span class="nb">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="nv">`_dt`</span> <span class="nb">DATETIME</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="n">NOW</span><span class="p">(),</span>
<span class="k">INDEX</span> <span class="nv">`user_read_report_index`</span> <span class="p">(</span><span class="nv">`_uid`</span> <span class="k">DESC</span><span class="p">,</span> <span class="nv">`_seq`</span> <span class="k">DESC</span><span class="p">),</span>
<span class="k">UNIQUE</span> <span class="k">INDEX</span> <span class="nv">`user_read_report_index2`</span> <span class="p">(</span><span class="nv">`_uid`</span> <span class="k">DESC</span><span class="p">,</span> <span class="nv">`_rid`</span> <span class="k">DESC</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">ENGINE</span> <span class="o">=</span> <span class="n">InnoDB</span>
<span class="k">DEFAULT</span> <span class="nb">CHARACTER</span> <span class="k">SET</span> <span class="o">=</span> <span class="n">utf8mb4</span>
<span class="k">COLLATE</span> <span class="o">=</span> <span class="n">utf8mb4_unicode_ci</span><span class="p">;</span>
</code></pre></div></div>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://docs.wildfirechat.cn/blogs/%E5%B7%B2%E9%80%81%E8%BE%BE%E5%92%8C%E5%B7%B2%E8%AF%BB%E5%9B%9E%E6%89%A7%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.html">已送达和已读回执功能说明</a></li>
</ul>comsince本文主要说明送达与阅读回执的实现方案,并给出具体的设计,仅供参考飞享即时聊天系统在windows部署指南2020-10-14T00:00:00+08:002020-10-14T00:00:00+08:00https://www.comsince.cn/2020/10/14/fshare-on-windows<p>主要说明在windows部署步骤</p>
<h1 id="基础环境">基础环境</h1>
<p>基础环境同centos.主要包括<code class="language-plaintext highlighter-rouge">zookeeper</code>,<code class="language-plaintext highlighter-rouge">mysql5.7</code>,<code class="language-plaintext highlighter-rouge">minio</code>,<code class="language-plaintext highlighter-rouge">JDK1.8</code></p>
<p><strong>NOTE:</strong> 在运行之前请确保<code class="language-plaintext highlighter-rouge">zookeeper</code>,<code class="language-plaintext highlighter-rouge">mysql</code>已经部署成功. <code class="language-plaintext highlighter-rouge">minio</code>是对象存储服务器,在发送图片,文件类型类型消息时需要使用.在运行之前请检查是否安装<code class="language-plaintext highlighter-rouge">Jdk</code></p>
<h1 id="安装包">安装包</h1>
<p>下载如下安装包,解压文件到任意目录即可</p>
<ul>
<li><a href="https://media.comsince.cn/minio-bucket-file-name/fshare-chat-windows-pro.tar.gz">飞享windows安装包</a></li>
</ul>
<h1 id="配置">配置</h1>
<p>解压成功后,会看到<code class="language-plaintext highlighter-rouge">boot</code>如下目录</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>boot
├── download
│ └── chat-debug.apk
├── push-connector
│ ├── config
│ │ ├── application.properties
│ │ ├── chat.comsince.cn.jks
│ │ ├── chat.comsince.cn.trustkeystore.jks
│ │ └── logback.xml
│ ├── jvm.ini
│ ├── lib
│ │ └── spring-boot-dubbo-push-connector-1.2.3.jar
│ ├── logs
│ │ ├── push-connector-error.log
│ │ ├── push-connector-error.log.20201013
│ │ ├── push-connector.log
│ │ └── push-connector.log.20201013
│ ├── push-connector
│ └── push-connector.bat
└── push-group
├── config
│ └── application.properties
├── jvm.ini
├── lib
│ └── spring-boot-web-push-group-1.2.3.jar
├── logs
│ ├── push-group-error.log
│ ├── push-group-error.log.20201013
│ ├── push-group.log
│ └── push-group.log.20201013
├── push-group
└── push-group.bat
</code></pre></div></div>
<h2 id="push-connector配置">push-connector配置</h2>
<blockquote>
<p>修改<code class="language-plaintext highlighter-rouge">push-connector\config\application.properties</code></p>
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># wss ssl 配置,这里配置jks需要指定其绝对路径地址.测试演示可以先不起动ssl.这里做配置为空即可</span>
<span class="s">push.ssl.keystore=</span>
<span class="s">push.ssl.truststore=</span>
<span class="s">push.ssl.password=</span>
<span class="c1">## Dubbo Registry</span>
<span class="s">dubbo.registry.address=zookeeper://{修改这里为你的zookeeper地址}:2181</span>
<span class="c1">## kafka broker </span>
<span class="c1">#push.kafka.broker=kafka:9092</span>
<span class="c1">## 多人音视频媒体服务,默认使用公网服务,可先暂时不用修改</span>
<span class="s">kurento.clientUrl=ws://media.comsince.cn:8888/kurento</span>
<span class="c1">## minio对象存储,如果暂时不需要支持文件,图片,视频类消息发送,可以暂时不用配置</span>
<span class="s">minio.url=https://media.comsince.cn</span>
<span class="c1">## minio access_key</span>
<span class="s">minio.access_key=</span>
<span class="c1">## minio secret_key</span>
<span class="s">minio.secret_key=</span>
</code></pre></div></div>
<h2 id="push-group配置">push-group配置</h2>
<blockquote>
<p>修改<code class="language-plaintext highlighter-rouge">push-group\config\application.properties</code></p>
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">## Dubbo 注册中心</span>
<span class="s">dubbo.registry.address=zookeeper://{修改这里为你的zookeeper地址}:2181</span>
<span class="c1">## 绑定dubbo 本机host地址,防止dubbo无法绑定服务地址,导致不同机器无法访问服务,push-group与push-connector部署在不同机器时最好设置</span>
<span class="c1">#dubbo.protocol.host=172.16.47.60</span>
<span class="c1">#云短信厂商,0: 代表暂时关闭短信通道 1:代表阿里云短信 2: 代表腾讯云短信</span>
<span class="s">sms.cp=0</span>
<span class="c1"># 应用id</span>
<span class="s">sms.appid=LTAI4Ff1jtqrSr3rkHMKEnfs</span>
<span class="c1"># 应用key</span>
<span class="s">sms.appkey=gG33mvmMAxGYol7Vd1AEG6InRK9VCD</span>
<span class="c1"># 模板id</span>
<span class="s">sms.templateId=SMS_180355435</span>
<span class="c1"># 短信签名由于编码问题,请到相应的代码里面设置</span>
<span class="c1"># 短信超级验证码,正式上线请修改,你可以使用这个超级验证码登录任意帐号</span>
<span class="s">sms.superCode=66666</span>
<span class="c1"># 是否使用内置数据库 1: 表示使用 0: 使用mySql</span>
<span class="s">im.embed_db=0</span>
<span class="c1"># jdbc url</span>
<span class="s">im.jdbc_url=jdbc:mysql://{你的mysql服务地址}:3306/fsharechat?useSSL=false&serverTimezone=GMT&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8</span>
<span class="c1"># mysql数据库访问用户名</span>
<span class="s">im.user=root</span>
<span class="c1">#mysql数据库访问密码</span>
<span class="s">im.password=123456</span>
</code></pre></div></div>
<h1 id="启动服务">启动服务</h1>
<p><strong>NOTE:</strong> 启动服务请到各自服务目录下启动bat脚本</p>
<ul>
<li>
<p>启动<code class="language-plaintext highlighter-rouge">push-group</code>服务,到<code class="language-plaintext highlighter-rouge">data\push-group</code>目录下执行<code class="language-plaintext highlighter-rouge">push-group.bat</code></p>
</li>
<li>
<p>启动<code class="language-plaintext highlighter-rouge">push-connector</code>服务,到<code class="language-plaintext highlighter-rouge">data\push-connector</code>目录下执行<code class="language-plaintext highlighter-rouge">push-connector.bat</code></p>
</li>
</ul>comsince主要说明在windows部署步骤即时聊天系统在Ubuntu上单机部署实践2020-08-31T00:00:00+08:002020-08-31T00:00:00+08:00https://www.comsince.cn/2020/08/31/ubuntu-install-fshare<p>此文档主要说明在ubuntu单击部署飞享的基本步骤与注意事项,重点说明可能出错的地方与解决方案</p>
<h1 id="nginx-安装">Nginx 安装</h1>
<h2 id="1-apt安装">1. apt安装</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>nginx
</code></pre></div></div>
<h2 id="2-文件位置">2. 文件位置</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/sbin/nginx :主程序
/etc/nginx :配置文件
/usr/share/nginx :存放静态文件
/var/log/nginx :存放日志
</code></pre></div></div>
<h2 id="3-启动nginx">3. 启动nginx</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service nginx start <span class="c"># 启动nginx</span>
service nginx reload <span class="c"># 重新加载nginx配置文件</span>
</code></pre></div></div>
<h2 id="4-nginx命令">4. nginx命令</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx <span class="nt">-s</span> reopen <span class="c"># 重启nginx</span>
nginx <span class="nt">-s</span> stop <span class="c"># 停止nginx</span>
nginx <span class="nt">-v</span> <span class="c"># 查看版本号</span>
</code></pre></div></div>
<h1 id="minio安装">Minio安装</h1>
<h2 id="本地化安装">本地化安装</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
chmod +x minio
## 前台启动
./minio server data/
## 后台启动
nohup ./minio server miniodata/ >/data/minio.log 2>&1 &
## 修改参数启动 目前不启用https,所有的都经过nginx转发
MINIO_ACCESS_KEY=test MINIO_SECRET_KEY=test123456 nohup ./minio server miniodata/ > /opt/minio/minio.log 2>&1 &
## 启动https 注意accesskey 和secretkey 保持不变
MINIO_ACCESS_KEY=test MINIO_SECRET_KEY=test nohup ./minio server --address ":443" /data/miniodata/ > /data/minio.log 2>&1 &
</code></pre></div></div>
<h1 id="im-服务安装">IM 服务安装</h1>
<p><strong>NOTE:</strong> 参见<a href="https://github.com/fsharechat/chat-server-release">服务安装说明</a></p>
<h2 id="base脚本启动问题">base脚本启动问题</h2>
<p><strong>NOTE:</strong> 注意修改声明<code class="language-plaintext highlighter-rouge">#!/bin/sh</code></p>
<h2 id="jks-配置">JKS 配置</h2>
<h2 id="数据库问题">数据库问题</h2>
<h3 id="数据库链接serverzone问题">数据库链接serverzone问题</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">set global time_zone='+8:00';</span>
</code></pre></div></div>
<h3 id="数据库版本问题">数据库版本问题</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">SQL</span> <span class="nc">State</span> <span class="o">:</span> <span class="mi">42000</span>
<span class="nc">Error</span> <span class="nc">Code</span> <span class="o">:</span> <span class="mi">1067</span>
<span class="nc">Message</span> <span class="o">:</span> <span class="nc">Invalid</span> <span class="k">default</span> <span class="n">value</span> <span class="k">for</span> <span class="err">'</span><span class="n">_dt</span><span class="err">'</span>
<span class="nc">Location</span> <span class="o">:</span> <span class="n">migrate</span><span class="o">/</span><span class="n">mysql</span><span class="o">/</span><span class="n">V2__create_table</span><span class="o">.</span><span class="na">sql</span> <span class="o">(/</span><span class="n">data</span><span class="o">/</span><span class="n">boot</span><span class="o">/</span><span class="n">push</span><span class="o">-</span><span class="n">group</span><span class="o">/</span><span class="nl">file:</span><span class="o">/</span><span class="n">data</span><span class="o">/</span><span class="n">boot</span><span class="o">/</span><span class="n">push</span><span class="o">-</span><span class="n">group</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">spring</span><span class="o">-</span><span class="n">boot</span><span class="o">-</span><span class="n">web</span><span class="o">-</span><span class="n">push</span><span class="o">-</span><span class="n">group</span><span class="o">-</span><span class="mf">1.2</span><span class="o">.</span><span class="mi">1</span><span class="o">.</span><span class="na">jar</span><span class="o">!/</span><span class="no">BOOT</span><span class="o">-</span><span class="no">INF</span><span class="o">/</span><span class="n">classes</span><span class="o">!/</span><span class="n">migrate</span><span class="o">/</span><span class="n">mysql</span><span class="o">/</span><span class="n">V2__create_table</span><span class="o">.</span><span class="na">sql</span><span class="o">)</span>
<span class="nc">Line</span> <span class="o">:</span> <span class="mi">21</span>
</code></pre></div></div>
<p><strong>NOTE:</strong> 需要5.6.5之后版本才支持</p>
<h1 id="nginx-服务配置">Nginx 服务配置</h1>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">user</span> <span class="err">www-data;</span>
<span class="err">worker_processes</span> <span class="err">auto;</span>
<span class="err">pid</span> <span class="err">/run/nginx.pid;</span>
<span class="err">include</span> <span class="err">/etc/nginx/modules-enabled/*.conf;</span>
<span class="err">events</span> <span class="err">{</span>
<span class="err">worker_connections</span> <span class="err">768;</span>
<span class="c"># multi_accept on;
</span><span class="err">}</span>
<span class="err">http</span> <span class="err">{</span>
<span class="err">upstream</span> <span class="err">blog</span> <span class="err">{</span>
<span class="err">server</span> <span class="py">129.227.138.58</span><span class="p">:</span><span class="s">4000;</span>
<span class="err">}</span>
<span class="err">server</span> <span class="err">{</span>
<span class="err">listen</span> <span class="err">80;</span>
<span class="err">server_name</span> <span class="err">comsince.cn</span> <span class="err">www.comsince.cn;</span>
<span class="c">#rewrite ^ https://$host$request_uri? permanent;
</span> <span class="c">#rewrite ^(.*)$ https://$host$1 permanent;
</span> <span class="err">if</span> <span class="err">($scheme</span> <span class="c">!= "https") {
</span> <span class="err">return</span> <span class="err">301</span> <span class="py">https</span><span class="p">:</span><span class="s">//$host$request_uri;</span>
<span class="err">}</span>
<span class="err">if</span> <span class="err">($</span><span class="py">host</span> <span class="p">=</span> <span class="s">www.comsince.cn) {</span>
<span class="err">return</span> <span class="err">301</span> <span class="py">https</span><span class="p">:</span><span class="s">//$host$request_uri;</span>
<span class="err">}</span>
<span class="err">if</span> <span class="err">($</span><span class="py">host</span> <span class="p">=</span> <span class="s">comsince.cn) {</span>
<span class="err">return</span> <span class="err">301</span> <span class="py">https</span><span class="p">:</span><span class="s">//$host$request_uri;</span>
<span class="err">}</span>
<span class="err">if</span> <span class="err">($</span><span class="py">host</span> <span class="p">=</span> <span class="s">media.comsince.cn) {</span>
<span class="err">return</span> <span class="err">301</span> <span class="py">https</span><span class="p">:</span><span class="s">//$host$request_uri;</span>
<span class="err">}</span>
<span class="c">#return 404;
</span> <span class="err">root</span> <span class="err">html;</span>
<span class="err">index</span> <span class="err">index.html</span> <span class="err">index.htm</span> <span class="err">index.php;</span>
<span class="err">}</span>
<span class="err">server</span> <span class="err">{</span>
<span class="err">listen</span> <span class="err">443</span> <span class="err">ssl;</span>
<span class="err">server_name</span> <span class="err">localhost;</span>
<span class="err">ssl_certificate</span> <span class="err">/etc/letsencrypt/live/comsince.cn/fullchain.pem;</span>
<span class="err">ssl_certificate_key</span> <span class="err">/etc/letsencrypt/live/comsince.cn/privkey.pem;</span>
<span class="err">ssl_session_cache</span> <span class="py">shared</span><span class="p">:</span><span class="s">SSL:1m;</span>
<span class="err">ssl_session_timeout</span> <span class="err">5m;</span>
<span class="err">location</span> <span class="err">/web</span> <span class="err">{</span>
<span class="err">root</span> <span class="err">/data/boot/;</span>
<span class="err">index</span> <span class="err">index.html</span> <span class="err">index.htm;</span>
<span class="err">}</span>
<span class="err">location</span> <span class="err">/mobile</span> <span class="err">{</span>
<span class="err">root</span> <span class="err">/data/boot/;</span>
<span class="err">index</span> <span class="err">index.html</span> <span class="err">index.htm;</span>
<span class="err">}</span>
<span class="err">location</span> <span class="err">/minio</span> <span class="err">{</span>
<span class="err">proxy_pass</span> <span class="py">http</span><span class="p">:</span><span class="s">//localhost:9000;</span>
<span class="err">}</span>
<span class="err">location</span> <span class="err">/login</span> <span class="err">{</span>
<span class="err">proxy_pass</span> <span class="py">http</span><span class="p">:</span><span class="s">//localhost:8081;</span>
<span class="err">}</span>
<span class="err">location</span> <span class="err">/send_code</span> <span class="err">{</span>
<span class="err">proxy_pass</span> <span class="py">http</span><span class="p">:</span><span class="s">//localhost:8081;</span>
<span class="err">}</span>
<span class="err">location</span> <span class="err">~*</span> <span class="err">/minio-bucket*</span> <span class="err">{</span>
<span class="err">proxy_set_header</span> <span class="err">X-Real-IP</span> <span class="err">$remote_addr;</span>
<span class="err">proxy_set_header</span> <span class="err">X-Forwarded-For</span> <span class="err">$proxy_add_x_forwarded_for;</span>
<span class="err">proxy_set_header</span> <span class="err">X-Forwarded-Proto</span> <span class="err">$scheme;</span>
<span class="err">proxy_set_header</span> <span class="err">Host</span> <span class="err">$http_host;</span>
<span class="err">client_max_body_size</span> <span class="err">500m;</span>
<span class="err">proxy_connect_timeout</span> <span class="err">300;</span>
<span class="c"># Default is HTTP/1, keepalive is only enabled in HTTP/1.1
</span> <span class="err">proxy_http_version</span> <span class="err">1.1;</span>
<span class="err">proxy_set_header</span> <span class="err">Connection</span> <span class="err">"";</span>
<span class="err">chunked_transfer_encoding</span> <span class="err">off;</span>
<span class="err">proxy_pass</span> <span class="py">http</span><span class="p">:</span><span class="s">//localhost:9000;</span>
<span class="err">}</span>
<span class="err">}</span>
<span class="c">##
</span> <span class="c"># Basic Settings
</span> <span class="c">##
</span>
<span class="err">sendfile</span> <span class="err">on;</span>
<span class="err">tcp_nopush</span> <span class="err">on;</span>
<span class="err">tcp_nodelay</span> <span class="err">on;</span>
<span class="err">keepalive_timeout</span> <span class="err">65;</span>
<span class="err">types_hash_max_size</span> <span class="err">2048;</span>
<span class="c"># server_tokens off;
</span>
<span class="c"># server_names_hash_bucket_size 64;
</span> <span class="c"># server_name_in_redirect off;
</span>
<span class="err">include</span> <span class="err">/etc/nginx/mime.types;</span>
<span class="err">default_type</span> <span class="err">application/octet-stream;</span>
<span class="c">##
</span> <span class="c"># SSL Settings
</span> <span class="c">##
</span>
<span class="err">ssl_protocols</span> <span class="err">TLSv1</span> <span class="err">TLSv1.1</span> <span class="err">TLSv1.2;</span> <span class="c"># Dropping SSLv3, ref: POODLE
</span> <span class="err">ssl_prefer_server_ciphers</span> <span class="err">on;</span>
<span class="c">##
</span> <span class="c"># Logging Settings
</span> <span class="c">##
</span>
<span class="err">access_log</span> <span class="err">/var/log/nginx/access.log;</span>
<span class="err">error_log</span> <span class="err">/var/log/nginx/error.log;</span>
<span class="c">##
</span> <span class="c"># Gzip Settings
</span> <span class="c">##
</span>
<span class="err">gzip</span> <span class="err">on;</span>
<span class="c"># gzip_vary on;
</span> <span class="c"># gzip_proxied any;
</span> <span class="c"># gzip_comp_level 6;
</span> <span class="c"># gzip_buffers 16 8k;
</span> <span class="c"># gzip_http_version 1.1;
</span> <span class="c"># gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
</span>
<span class="c">##
</span> <span class="c"># Virtual Host Configs
</span> <span class="c">##
</span>
<span class="err">include</span> <span class="err">/etc/nginx/conf.d/*.conf;</span>
<span class="err">include</span> <span class="err">/etc/nginx/sites-enabled/*;</span>
<span class="err">}</span>
<span class="c">#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}
</span>
</code></pre></div></div>comsince此文档主要说明在ubuntu单击部署飞享的基本步骤与注意事项,重点说明可能出错的地方与解决方案飞享项目说明2020-07-13T00:00:00+08:002020-07-13T00:00:00+08:00https://www.comsince.cn/2020/07/13/fs-readme<h1 id="项目概述">项目概述</h1>
<p>为了便于项目的管理与发展,将项目相关的仓库全部移动到这里<a href="https://github.com/fsharechat">Github飞享开发组</a>,<a href="https://gitee.com/comsince">gitee</a>的个人账户下面的项目,原则上全部移动到这里维护</p>
<h1 id="项目列表">项目列表</h1>
<h2 id="服务端项目">服务端项目</h2>
<ul>
<li><a href="https://github.com/fsharechat/chat-server-release">chat-server-release</a> 飞享服务端发布项目,便于快速本地部署</li>
<li><a href="https://github.com/fsharechat/chat-server">chat-server</a> IM服务端项目</li>
<li><a href="https://github.com/fsharechat/chat-proto">chat-proto</a> 基于protobuf的相关proto定义文件</li>
</ul>
<h2 id="android客户端">Android客户端</h2>
<ul>
<li><a href="https://github.com/fsharechat/android-chat">android-chat</a> Android客户端项目,仅仅支持一对一音视频</li>
<li><a href="https://github.com/fsharechat/android-chat-pro">android-chat-pro</a> Android专业版,支持群组音视频</li>
</ul>
<h2 id="vue-web客户端">Vue Web客户端</h2>
<ul>
<li><a href="https://github.com/fsharechat/vue-chat">vue-chat</a> 基于vue的web端项目</li>
</ul>
<h2 id="vue-移动端">vue 移动端</h2>
<ul>
<li><a href="https://github.com/fsharechat/vue-mobile-chat">vue-mobile-chat</a> 基于vue的移动端项目</li>
</ul>
<h2 id="electron-客户端">electron 客户端</h2>
<ul>
<li><a href="https://github.com/fsharechat/electron-chat">electron-chat</a> 基于electron的pc端项目</li>
</ul>
<p><strong>NOTE:</strong> 专业版提供付费技术支持,并且提供源码授权,请联系QQ <code class="language-plaintext highlighter-rouge">1282212195</code></p>
<h1 id="项目截图">项目截图</h1>
<h2 id="android-客户端">Android 客户端</h2>
<table>
<thead>
<tr>
<th style="text-align: center">主界面</th>
<th style="text-align: center">群组音视频聊天</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><img src="https://media.comsince.cn/minio-bucket-image-name/android-main.png" alt="图片替换文本" width="300" height="533" align="center" /></td>
<td style="text-align: center"><img src="https://media.comsince.cn/minio-bucket-image-name/android-group-call.png" alt="图片替换文本" width="300" height="533" align="center" /></td>
</tr>
</tbody>
</table>
<h2 id="web-客户端">web 客户端</h2>
<p><img src="https://media.comsince.cn/minio-bucket-image-name/vue-chat-group-info.png" alt="image" /><br />
<img src="https://media.comsince.cn/minio-bucket-image-name/vue-chat-main.png" alt="image" /><br />
<img src="https://media.comsince.cn/minio-bucket-image-name/vue-chat-create-group.png" alt="image" /></p>
<h1 id="项目演示说明">项目演示说明</h1>
<ul>
<li><a href="https://www.comsince.cn/web">vue-chat-web版本</a></li>
<li><a href="https://www.comsince.cn/mobile">vue-mobile-chat移动版本</a></li>
<li>Android扫码下载</li>
</ul>
<p><img src="https://media.comsince.cn/minio-bucket-image-name/1-373z3zNN-1594953226715-fshare-chat-apk-qrcode.png" alt="image" /></p>
<p><strong>NOTE:</strong> 由于现在没有开通短信功能,可以使用下演示帐号登录</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>帐号:13800000000, 13800000001, 13800000002
验证码:556677
</code></pre></div></div>
<h2 id="项目迁移问题">项目迁移问题</h2>
<p><strong>NOTE:</strong> 针对项目改变地址,可以使用以下方法快速切换代码拉取地址,以下以<code class="language-plaintext highlighter-rouge">chat-server-release</code>具体说明,其他项目基本类似</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote <span class="nb">rm </span>origin
git remote add origin git@github.com:fsharechat/chat-server-release.git
<span class="c">## 同步以下主干分支提交</span>
git branch <span class="nt">--set-upstream-to</span><span class="o">=</span>origin/master
</code></pre></div></div>
<h1 id="文档列表">文档列表</h1>
<h2 id="技术说明">技术说明</h2>
<ul>
<li><a href="https://www.comsince.cn/2020/05/18/universe-push-tech-doc/">飞享-即时聊天系统技术文档</a>
<h2 id="部署">部署</h2>
</li>
<li><a href="https://www.comsince.cn/2020/04/13/universe-push-start-on-centos/">即时聊天系统在Centos上单机部署实践</a></li>
<li><a href="https://www.comsince.cn/2020/05/07/universe-push-start-on-windows/">即时聊天系统在Windows上单机测试部署实践</a>
<h2 id="音视频方案">音视频方案</h2>
</li>
<li><a href="https://www.comsince.cn/2020/03/04/web-rtc/">实时音视频开发的工程化实践</a></li>
<li><a href="https://www.comsince.cn/2020/06/01/muti-conference-webrtc/">多人音视频会话方案预研</a></li>
</ul>
<h1 id="功能列表">功能列表</h1>
<table>
<tr>
<th>主功能</th>
<th>功能说明</th>
<th>web</th>
<th>h5</th>
<th>android</th>
</tr>
<tr>
<td>登录</td>
<td>支持腾讯云,阿里云验证码登录</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td rowspan="2">用户信息</td>
<td>修改用户头像</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>修改用户昵称</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td rowspan="4">好友列表</td>
<td>发送好友请求</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>处理好友请求</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>修改好友备注名</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>好友列表查看</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td rowspan="5">单聊</td>
<td>文本/视频/图片</td>
<td>√</td>
<td>√</td>
<td>√</td>
</tr>
<tr>
<td>语音</td>
<td>×</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>消息删除</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>消息撤回</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>消息转发</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td rowspan="7">群聊(含基本单聊功能)</td>
<td>群聊创建</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>修改群名称</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>群聊退出</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>群聊解散</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>群成员列表</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>成员邀请</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>成员删除</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td rowspan="2">实时音视频</td>
<td>一对一音视频</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
<tr>
<td>群组音视频</td>
<td>√</td>
<td>×</td>
<td>√</td>
</tr>
</table>
<h1 id="商业说明">商业说明</h1>
<h2 id="开源协议">开源协议</h2>
<p>本项目使用非商业性署名协议<a href="LICENSE">Creative Commons Attribution Non Commercial 3.0 Unported</a></p>
<h2 id="一次性赞助">一次性赞助</h2>
<p>但是随着项目的增长,也需要相应的资金支持,你可以通过以下方式来赞助此项目</p>
<table>
<thead>
<tr>
<th style="text-align: center">支付宝</th>
<th style="text-align: center">微信</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><img src="https://media.comsince.cn/minio-bucket-image-name/zfb-purse.jpg" alt="图片替换文本" width="300" height="300" align="center" /></td>
<td style="text-align: center"><img src="https://media.comsince.cn/minio-bucket-image-name/wx-purse.png" alt="图片替换文本" width="300" height="300" align="center" /></td>
</tr>
</tbody>
</table>
<h2 id="技术支持">技术支持</h2>
<p>如果公司采用本项目或者需要有商业需求,需要二次开发,提供技术支持,联系QQ:<code class="language-plaintext highlighter-rouge">1282212195</code></p>
<h2 id="qq群交流">QQ群交流</h2>
<table>
<thead>
<tr>
<th style="text-align: center">QQ群</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><img src="https://media.comsince.cn/minio-bucket-image-name/qq-group.jpg" alt="图片替换文本" width="300" height="411" align="center" /></td>
</tr>
</tbody>
</table>comsince项目概述 为了便于项目的管理与发展,将项目相关的仓库全部移动到这里Github飞享开发组,gitee的个人账户下面的项目,原则上全部移动到这里维护多人音视频会话方案预研2020-06-01T00:00:00+08:002020-06-01T00:00:00+08:00https://www.comsince.cn/2020/06/01/muti-conference-webrtc<p>本文主要说明多人音视频的工程技术方案选型,此文侧重于工程化实践,不讨论相关音视频编解码算法,旨在寻找一种适合中小型企业部署的多音视频方案以降低企业进行多人会议的成本.方案的选型主要还是适合现有的<code class="language-plaintext highlighter-rouge">飞享</code>聊天系统的相关技术栈,以便于能够对现有系统进行更快的集成.合理的方案最终满足企业对技术方案的可控,以及需要考虑框架周边的完善程度</p>
<h1 id="webrtc多方方案概述">WebRTC多方方案概述</h1>
<p>当媒体服务器充当媒体中继时,它通常被称为SFU(Selective Forwarding Unit选择性转发单位),这意味着其主要目的是在客户端之间转发媒体流。还有一个MCU(Multipoint Conferencing Unit多点会议单元)的概念,MCU服务器不仅可以转发,而且可以对媒体流进行混合和编码压缩(比如把各个客户端的数据打包转发,和SFU相比,这样将大幅度降低转发数据的带宽需求,但对CPU有更高的要求)。</p>
<p><img src="/images/im/webrtc-commuication-model.png" alt="image" /></p>
<h2 id="mesh架构">Mesh架构</h2>
<p>每个端都与其它端互连。以上图最左侧为例,5个浏览器,二二建立p2p连接,每个浏览器与其它4个建立连接,总共需要10个连接。如果每条连接占用1m带宽,则每个端上行需要4m,下行带宽也要4m,总共带宽消耗20m。而且除了带宽问题,每个浏览器上还要有音视频“编码/解码”,cpu使用率也是问题,一般这种架构只能支持4-6人左右,不过优点也很明显,没有中心节点,实现很简单。</p>
<p><strong>优点:</strong></p>
<ul>
<li>逻辑简单,容易实现</li>
<li>服务端比较 “轻量”,TURN 服务器比较简单,一定比例的 P2P 成功率可极大减轻服务端的压力</li>
</ul>
<p><strong>缺点:</strong></p>
<ul>
<li>每新增一个客户端,所有的客户端都需要新增一路数据上行,客户端上行带宽占用太大。因此,通话人数越多,效果越差</li>
<li>无法在服务端对视频进行额外处理,如:录制存储回放、实时转码、智能分析、多路合流、转推直播等等</li>
</ul>
<h2 id="mcu-multipoint-control-unit">MCU (MultiPoint Control Unit)</h2>
<p>这是一种传统的中心化架构(上图中间部分),每个浏览器仅与中心的MCU服务器连接,MCU服务器负责所有的视频编码、转码、解码、混合等复杂逻辑,每个浏览器只要1个连接,整个应用仅消耗5个连接,带宽占用(包括上行、下行)共10m,浏览器端的压力要小很多,可以支持更多的人同时音视频通讯,比较适合多人视频会议。但是MCU服务器的压力较大,需要较高的配置。</p>
<p>以前在电信行业做视频会议一般都使用MCU(Multipoint Control Unit),也就是多方推流在MCU上进行合流,在拉流的时候只有一路合流,这样的好处是无论几方推流,拉流只有一路,下行带宽比较小。但是问题也比较多,只要推流方一多,MCU的压力就比较大,而且分布式的部署也比较难,成本又很高。</p>
<h2 id="sfuselective-forwarding-unit">SFU(Selective Forwarding Unit)</h2>
<p>上图右侧部分,咋一看,跟MCU好象没什么区别,但是思路不同,仍然有中心节点服务器,但是中心节点只负责转发,不做太重的处理,所以服务器的压力会低很多,配置也不象MCU要求那么高。但是每个端需要建立一个连接用于上传自己的视频,同时还要有N-1个连接用于下载其它参与方的视频信息。所以总连接数为5*5,消耗的带宽也是最大的,如果每个连接1M带宽,总共需要25M带宽,它的典型场景是1对N的视频互动。</p>
<p>SFU 服务器最核心的特点是把自己 “伪装” 成了一个 WebRTC 的 Peer 客户端,WebRTC 的其他客户端其实并不知道自己通过 P2P 连接过去的是一台真实的客户端还是一台服务器,我们通常把这种连接称之为 P2S,即:Peer to Server。除了 “伪装” 成一个 WebRTC 的 Peer 客户端外,SFU 服务器还有一个最重要的能力就是具备 one-to-many 的能力,即可以将一个 Client 端的数据转发到其他多个 Client 端。</p>
<p>这种网络拓扑结构中,无论多少人同时进行视频通话,每个 WebRTC 的客户端只需要连接一个 SFU 服务器,上行一路数据即可,极大减少了多人视频通话场景下 Mesh 模型给客户端带来的上行带宽压力。</p>
<p>SFU 服务器跟 TURN 服务器最大的不同是,TURN 服务器仅仅是为 WebRTC 客户端提供的一种辅助的数据转发通道,在 P2P 不通的时候进行透明的数据转发。而 SFU 是 “懂业务” 的, 它跟 WebRTC 客户端是平等的关系,甚至 “接管了” WebRTC 客户端的数据转发的申请和控制。</p>
<p>现在互联网行业比较流行的是SFU(Selective Forwarding Unit),简单说就是只负责转发流,不负责合流,压力很小。这样的模式可以依托CDN进行分布式的部署,不过拉流的方数限于客户端的带宽和处理能力。</p>
<h2 id="为啥推荐选择-sfu-">为啥推荐选择 SFU ?</h2>
<p>纯 mesh 方案无法适应多人视频通话,也无法实现服务端的各种视频处理需求,最先排除在商业应用之外。</p>
<p>SFU 相比于 MCU,服务器的压力更小(纯转发,无转码合流),灵活性更好(可选择性开关任意一路数据的上下行等),受到更广泛的欢迎和应用,常见的开源 SFU 服务器有:<code class="language-plaintext highlighter-rouge">Licode</code>,<code class="language-plaintext highlighter-rouge">Kurento</code>,<code class="language-plaintext highlighter-rouge">Janus</code>,<code class="language-plaintext highlighter-rouge">Jitsi</code>,<code class="language-plaintext highlighter-rouge">Mediasoup</code>等。</p>
<p>当然,也可以组合使用 SFU + MCU 的混合方案,以灵活应对不同场景的应用需要。</p>
<h1 id="开源方案">开源方案</h1>
<h2 id="流媒体选型要考虑的主要因素">流媒体选型要考虑的主要因素</h2>
<ul>
<li>你是否深刻理解其代码?</li>
<li>代码版本是否足够新?</li>
<li>有谁在使用它?</li>
<li>它的文档是否齐全?</li>
<li>它可以debug吗?</li>
<li>它可以伸缩吗?</li>
<li>它使用哪种语言?</li>
<li>对于媒体服务器而言,这种语言的性能是否足够?</li>
<li>团队是否足够了解这门语言?</li>
<li>是否适应你现有的Signaling范式?</li>
<li>你在看的Media Server是否容易与你决定使用的STUN/TURN服务器集成</li>
<li>许可证是否适合你?</li>
<li>谁在提供支持?
很多成功的、被良好维护的开源项目背后都有一个商业模式,尤其是中小型的项目,这意味着有一个团队以此为谋生手段。
具备可选的付费支持意味着:
<ul>
<li>有人愿意全职来改善这东西,而不是作为爱好来维护。</li>
<li>如果你需要紧急帮助,只要花钱就能得到。</li>
</ul>
</li>
</ul>
<h2 id="我们最后为啥选择了kurento">我们最后为啥选择了Kurento?</h2>
<ul>
<li>开源</li>
<li>支持SFU和MCU</li>
<li>支持音视频流的转码,记录,混合,广播和路由</li>
<li>内置模块我们将来可以直接用</li>
<li>API公开其所有功能,与语言无关,可以使用任何语言</li>
<li>可拔插框架,容易扩展</li>
<li>文档丰富,demo多</li>
<li>社区活跃度高</li>
</ul>
<p>选择这个开源Media Server 主要是因为其完善的文档,基于Java的客户端API,方便集成现有的<a href="https://github.com/comsince/universe_push">unverse_push</a>信令服务器</p>
<h1 id="本地安装kms">本地安装KMS</h1>
<h2 id="安装gpg">安装GPG</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update <span class="o">&&</span> <span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">--no-install-recommends</span> <span class="nt">--yes</span> gnupg
</code></pre></div></div>
<h2 id="确定ubuntu版本">确定Ubuntu版本</h2>
<p>Run only one of these lines:</p>
<h2 id="运行如下一行命令即可">运行如下一行命令即可</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DISTRO</span><span class="o">=</span><span class="s2">"xenial"</span> <span class="c"># KMS for Ubuntu 16.04 (Xenial)</span>
<span class="nv">DISTRO</span><span class="o">=</span><span class="s2">"bionic"</span> <span class="c"># KMS for Ubuntu 18.04 (Bionic)</span>
</code></pre></div></div>
<h2 id="添加kurento-仓库地址">添加Kurento 仓库地址</h2>
<p>Run these two commands in the same terminal you used in the previous step:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-key adv <span class="nt">--keyserver</span> keyserver.ubuntu.com <span class="nt">--recv-keys</span> 5AFA7A83
<span class="nb">sudo tee</span> <span class="s2">"/etc/apt/sources.list.d/kurento.list"</span> <span class="o">></span>/dev/null <span class="o"><<</span><span class="no">EOF</span><span class="sh">
# Kurento Media Server - Release packages
deb [arch=amd64] http://ubuntu.openvidu.io/6.13.0 </span><span class="nv">$DISTRO</span><span class="sh"> kms6
</span><span class="no">EOF
</span></code></pre></div></div>
<h2 id="安装kms">安装KMS</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update <span class="o">&&</span> <span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">--yes</span> kurento-media-server
</code></pre></div></div>
<p>以上命令会安装最新版本的KMS</p>
<h2 id="启动与停止">启动与停止</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>service kurento-media-server start
<span class="nb">sudo </span>service kurento-media-server stop
</code></pre></div></div>
<h2 id="检验是否安装成功">检验是否安装成功</h2>
<h3 id="检查进程是否存在">检查进程是否存在</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ps <span class="nt">-fC</span> kurento-media-server
UID PID PPID C STIME TTY TIME CMD
kurento 17799 1 0 17:45 ? 00:00:00 /usr/bin/kurento-media-server
</code></pre></div></div>
<h3 id="检查websocket-rpc端口是否存在">检查websocket RPC端口是否存在</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$sudo</span> netstat <span class="nt">-tupln</span> | <span class="nb">grep</span> <span class="nt">-e</span> kurento <span class="nt">-e</span> 8888
tcp6 0 0 :::8888 :::<span class="k">*</span> LISTEN 17799/kurento-media
</code></pre></div></div>
<h3 id="测试websocket-rpc链接">测试websocket rpc链接</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$curl</span> <span class="nt">-i</span> <span class="nt">-N</span> <span class="nt">-H</span> <span class="s2">"Connection: Upgrade"</span> <span class="nt">-H</span> <span class="s2">"Upgrade: websocket"</span> <span class="nt">-H</span> <span class="s2">"Host: 127.0.0.1:8888"</span> <span class="nt">-H</span> <span class="s2">"Origin: 127.0.0.1"</span> http://127.0.0.1:8888/kurento
</code></pre></div></div>
<h2 id="返回结果">返回结果</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 500 Internal Server Error
Server: WebSocket++/0.7.0
</code></pre></div></div>
<p><strong>NOTE:</strong> 此测试命令在sudo 命令下执行</p>
<h1 id="loopback-本地测试">loopback 本地测试</h1>
<p>这里使用回环测试,说明<code class="language-plaintext highlighter-rouge">kurento</code>的基本使用,主要结合信令服务器说明</p>
<p><strong>NOTE:</strong> 下图是摘自官方文档的一个本地回环测试的架构图</p>
<p><img src="/images/im/kurento-java-tutorial-1-helloworld-pipeline.png" alt="image" /></p>
<p>有两个websocket链接这里说明一下:</p>
<ul>
<li><strong>一个是客户端与信息服务器通过websocket链接,用于信令交互</strong></li>
<li><strong>一个是kurento java客户端与KMS之间的链接</strong></li>
</ul>
<h2 id="信令交互图">信令交互图</h2>
<p><img src="/images/im/kurento-java-tutorial-1-helloworld-signaling.png" alt="image" /></p>
<h1 id="一对一聊天">一对一聊天</h1>
<p>这里使用SFU服务器进行中间转发.说明SFU进行消息转发的基本使用方法</p>
<h2 id="sfu与直连会话的对比">SFU与直连会话的对比</h2>
<h3 id="p2p模式">p2p模式</h3>
<p>这种聊天方式,信令服务器只是中转信令,中间对信令的内容不需要感知,信令服务器只需要转发信令到指定接收者即可.</p>
<h3 id="基于sfu的转发模式">基于SFU的转发模式</h3>
<p>本质是各个客户端与KMS建立链接.双方的sdp <code class="language-plaintext highlighter-rouge">offer/answer</code> 是各个客户端与KMS之间的协商,KMS伪装成<code class="language-plaintext highlighter-rouge">webRTC</code>客户端接收各个客户端的链接.然后内部进行消息的转发.</p>
<h3 id="信令设计">信令设计</h3>
<p><strong>NOTE:</strong> 如下图全新设计的信令交互图</p>
<p><img src="/images/im/one2oneSingnal.png" alt="image" /></p>
<p>以下事项需要注意:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">CallStartmessage</code>和<code class="language-plaintext highlighter-rouge">AnswerCallMessage</code>,<code class="language-plaintext highlighter-rouge">Byemessage</code>需要转发到目标客户端</li>
<li><code class="language-plaintext highlighter-rouge">SignalMessage</code>只在客户端与信令服务器端转发,不需要转发到对端</li>
</ul>
<h1 id="群组聊天">群组聊天</h1>
<h2 id="信令设计-1">信令设计</h2>
<h3 id="基于sfu的原始信令设计">基于SFU的原始信令设计</h3>
<ul>
<li>新的参与者发送信令<code class="language-plaintext highlighter-rouge">joinRoom</code>,加入到群组聊天中</li>
<li>信令服务器处理<code class="language-plaintext highlighter-rouge">joinRoom</code>消息,向群组中的用户广播有新的参与者加入.并且给新参数返回群组现有的成员名单</li>
<li>新的参与者会接收到现有的群组成员名单
<ul>
<li>建立推流通道,用于专门发送本地视频到KMS</li>
<li>根据现有参与者,每一个参与者都建立一条拉流通道,用于分别接收群组中其他参与者的视频流</li>
</ul>
</li>
</ul>
<h3 id="基于im的群组视频聊天的信令设计">基于IM的群组视频聊天的信令设计</h3>
<ul>
<li>群组某个成员发起群组视频聊天,发起<code class="language-plaintext highlighter-rouge">CallStartMessage</code>,并等待加入者</li>
<li>被通知者收到接收到的群组音视频通知,决定是否接收群组视频邀请</li>
<li>如果被邀请者同意接收音视频邀请,则回应<code class="language-plaintext highlighter-rouge">callAnswerMessage</code></li>
<li>信令服务器接收到用户接收请求邀请,分别给发起者发送<code class="language-plaintext highlighter-rouge">SignalMessage</code>包含新加入的用户,接收者发送包含正在音视频通话的列表的<code class="language-plaintext highlighter-rouge">SignalMessage</code></li>
</ul>
<h2 id="交互设计">交互设计</h2>
<ul>
<li>群组中任何一个用户,点击语音或者视频聊天,之后选择要参与的用户,目前群聊用户初步限定为9人</li>
<li>一旦发起者建立群组会话,中间其他群组成员,不允许在进入,除非发起者邀请进入</li>
</ul>
<h1 id="技术实现">技术实现</h1>
<p>目前已经实现了基于vue web的群组音视频和基于android的群组音视频,详情请参考<a href="https://github.com/fsharechat/Readme">飞享IM项目说明</a></p>
<h1 id="参考资料">参考资料</h1>
<ul>
<li><a href="https://juejin.im/post/5eca3f15e51d45789129173e#heading-21">互动直播之WebRTC服务开源技术选型</a></li>
<li><a href="https://juejin.im/post/5cb008c26fb9a068547345eb#heading-5">WebRTC现状以及多人视频通话分析</a></li>
<li><a href="https://doc-kurento.readthedocs.io/en/6.13.0/user/installation.html">kurento 官方文档</a></li>
<li><a href="https://www.jianshu.com/p/ac307371def4">架构设计:基于Webrtc、Kurento的一种低延迟架构实现</a></li>
</ul>comsince本文主要说明多人音视频的工程技术方案选型,此文侧重于工程化实践,不讨论相关音视频编解码算法,旨在寻找一种适合中小型企业部署的多音视频方案以降低企业进行多人会议的成本.方案的选型主要还是适合现有的飞享聊天系统的相关技术栈,以便于能够对现有系统进行更快的集成.合理的方案最终满足企业对技术方案的可控,以及需要考虑框架周边的完善程度飞享-即时聊天系统技术文档2020-05-18T00:00:00+08:002020-05-18T00:00:00+08:00https://www.comsince.cn/2020/05/18/universe-push-tech-doc<p>本文档主要说明飞享即时聊天系统的技术相关文档,用于支持其他多端开发.说明系统的整体架构与后续技术<code class="language-plaintext highlighter-rouge">发展规划</code>,<code class="language-plaintext highlighter-rouge">技术愿景</code>,<code class="language-plaintext highlighter-rouge">未来商业化支持</code></p>
<p><strong><center><font size="5" color="#bd4147">**本文未经授权禁止转载**</font></center></strong></p>
<h1 id="概述">概述</h1>
<p>飞享是一个即时聊天系统整体解决方案,更像一个开箱即用的即时通讯产品化解决方案.在设计之初,尽量遵照平台原生开发的要求进行,因为我们始终觉得原生的体验是达到一个优秀即时通讯的基本要求.在对客户端,服务端设计的过程中,尽量采用业界通用的方案进行.不用过于依赖某项技术,因为我们任何只有合适的技术用在合适的系统上才能发挥它固有的价值.</p>
<p>初衷开始这个项目只是对即时通讯的喜爱,慢慢不断的发展成为一个即时通讯类产品,在功能的不断迭代中,需要我们停下脚本思考一些问题.更希望这个一个技术解决方案,而不是基于某种语言或者框架的解决方案.也是服务端通信框架是基于<code class="language-plaintext highlighter-rouge">t-io</code>(基于AIO的网络编程框架,提供便捷的API,方便管理,快速使用),或者是基于<code class="language-plaintext highlighter-rouge">Netty</code>(基于NIO的异步网络编程框架).也是服务端编程语言是基于<code class="language-plaintext highlighter-rouge">Java</code>或者是基于<code class="language-plaintext highlighter-rouge">Go</code>.技术本身是为了解决实际问题,不应该是限制具体某个领域的发展.在编写客户端应用时,可以支持<code class="language-plaintext highlighter-rouge">Android</code>,也可以支持<code class="language-plaintext highlighter-rouge">iOS</code>,可以支持<code class="language-plaintext highlighter-rouge">Web</code>.支持我们遵循我们设计的交互协议规范,这些都可以迎刃而解,不管你是采用Android或者iOS原生开发,还是跨平台开发,使用Js框架Vue,React.</p>
<h1 id="系统架构">系统架构</h1>
<p>系统架构在以后更多的是解决用户不断增多进而导致的,硬件支持,软件支持.更多的用户带来的挑战包括不断增长的数据,需要不断优化的用户体验.功能的迭代带来系统复杂度不断增大,给软件架构带来更多的挑战.所以基于我们现在的简单分布式架构,解决小部分用户使用尚可,后续需要考虑更多的用户,更优的用户体验,因此需要不断的优化软件架构</p>
<p><strong>NOTE:</strong> 如下为简要的系统部署图</p>
<p><img src="/images/im/push-universe-deploy.png" alt="image" /></p>
<h1 id="系统流程图">系统流程图</h1>
<p><img src="/images/im/push-universe-flow.png" alt="image" /></p>
<p>重点关注核心要点:<code class="language-plaintext highlighter-rouge">登录实现</code>,<code class="language-plaintext highlighter-rouge">消息不丢失设计</code></p>
<h2 id="登录设计">登录设计</h2>
<p>系统登录是进行用户管理的关键,现行设计采用<code class="language-plaintext highlighter-rouge">手机号</code>+<code class="language-plaintext highlighter-rouge">验证码</code>的方式进行,登录的目地是生成会话,以支持同一帐号不同设备登录的功能</p>
<h3 id="用户token生成">用户token生成</h3>
<p><strong>NOTE:</strong> 每一个用户Id登录不同设备,采用不同的cid,即是采用uid(用户id)+cid(设备唯一id)生成session,token中间分隔符为<code class="language-plaintext highlighter-rouge">|</code></p>
<h4 id="token规则">token规则</h4>
<table>
<tbody>
<tr>
<td>usertoken</td>
<td>session secret</td>
<td>db secret</td>
</tr>
</tbody>
</table>
<h4 id="usertoken规则">usertoken规则</h4>
<table>
<tbody>
<tr>
<td>testim</td>
<td>时间戳</td>
<td>用户名</td>
</tr>
</tbody>
</table>
<ul>
<li>核心代码</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getToken</span><span class="o">(</span><span class="nc">String</span> <span class="n">username</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">signKey</span> <span class="o">=</span> <span class="no">KEY</span> <span class="o">+</span> <span class="s">"|"</span> <span class="o">+</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">())</span> <span class="o">+</span> <span class="s">"|"</span> <span class="o">+</span> <span class="n">username</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="no">DES</span><span class="o">.</span><span class="na">encryptDES</span><span class="o">(</span><span class="n">signKey</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// TODO Auto-generated catch block</span>
<span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
<li>返回的token是经过AES加密然后Base64编码</li>
</ul>
<h3 id="客户端接入方式">客户端接入方式</h3>
<p><strong>NOTE:</strong> 用户登录成功后,会成功获取到上面生成的token,例如web端在获取到这个<code class="language-plaintext highlighter-rouge">token</code>保存在浏览器的<code class="language-plaintext highlighter-rouge">localstorage</code>里面.这个toekn将作为后续发送验证请求的关键.登录成功后要发送<code class="language-plaintext highlighter-rouge">connect</code>信令进行链接验证,关于信令的传输将会在下面介绍</p>
<p><img src="/images/im/login.png" alt="image" /></p>
<h3 id="解密分离token">解密分离token</h3>
<p>上面已经知道token是三部分加密组成,因此这里需要解密token,得到如下三部分内容</p>
<ul>
<li>usertoken</li>
<li>session secret</li>
<li>db secret</li>
</ul>
<h3 id="生成密码">生成密码</h3>
<p><strong>NOTE:</strong> 以下是发送<code class="language-plaintext highlighter-rouge">connect</code>信令携带的消息体</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nl">userName</span><span class="p">:</span> <span class="nx">LocalStore</span><span class="p">.</span><span class="nx">getUserId</span><span class="p">(),</span> <span class="c1">//用户id</span>
<span class="nx">password</span><span class="p">:</span> <span class="nx">pwdAesBase64</span><span class="p">,</span> <span class="c1">//生成的用户密码</span>
<span class="nx">clientIdentifier</span><span class="p">:</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="nx">KEY_VUE_DEVICE_ID</span><span class="p">)</span> <span class="c1">//当前用户登录设备唯一id</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="生成规则">生成规则</h3>
<p>password 为 usertoken使用session secret经过AES加密得出的.</p>
<h3 id="服务端认证">服务端认证</h3>
<p>客户端在传入上述的<code class="language-plaintext highlighter-rouge">connect</code>消息后,需要经过消息认证才算接入成功,每次用户会话都有一个session,利用session secret
对<code class="language-plaintext highlighter-rouge">password</code>字段进行AES解密,如果能够解密成功,则表示登录验证成功</p>
<h1 id="系统接口文档">系统接口文档</h1>
<p>系统的接入方式多种多样,带来的挑战是需要支持多种接入方式.对于Android,ios需要基于TCP实现用户长链接,保证消息即时可达.因此需要设计私有协议,
对于web端,可以采用websocket协议,只需要在消息体中设计我们的消息格式即可.</p>
<h2 id="私有二进制协议">私有二进制协议</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+---------------+---------------+---------------+---------------+
| magic(8) | version(8) | signal(8) | |
+---------------+---------------+---------------+---------------+
| length(32) |
+---------------+---------------+---------------+---------------+
| subsignal(8)| messageId(16) | paload data :
+---------------+---------------+---------------+---------------+
: payload data(length) :
+---------------+---------------+---------------+---------------+
</code></pre></div></div>
<p><strong>NOTE:</strong> 参数说明如下</p>
<ul>
<li><strong>magic</strong> 魔数,用于标记消息的起始位置</li>
<li><strong>version</strong> 协议版本号</li>
<li><strong>signal</strong> 主消息信令</li>
<li><strong>length</strong> 消息体长度</li>
<li><strong>subsignal</strong> 消息子信令</li>
<li><strong>messageId</strong> 消息ID,用于标记当前消息,可用作消息确认</li>
<li><strong>payload data</strong> 消息体,长度由<code class="language-plaintext highlighter-rouge">length</code>定义</li>
</ul>
<h3 id="信令定义说明">信令定义说明</h3>
<p><strong>NOTE:</strong> 如下是主信令与子信令对应关系</p>
<table>
<tr>
<th>主信令</th>
<th>子信令</th>
<th>信令说明</th>
</tr>
<tr>
<td>PING</td>
<td>无</td>
<td>心跳主信令</td>
</tr>
<tr>
<td>PUSH</td>
<td>无</td>
<td>推送消息主信令</td>
</tr>
<tr>
<td rowspan="8">CONNECT</td>
<td>CONNECTION_ACCEPTED</td>
<td>接受链接</td>
</tr>
<tr>
<td>CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION</td>
<td>不可接受的协议</td>
</tr>
<tr>
<td>CONNECTION_REFUSED_IDENTIFIER_REJECTED</td>
<td>用户拒绝</td>
</tr>
<tr>
<td>CONNECTION_REFUSED_SERVER_UNAVAILABLE</td>
<td>服务不可用</td>
</tr>
<tr>
<td>CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD</td>
<td>用户名或密码错误</td>
</tr>
<tr>
<td>CONNECTION_REFUSED_NOT_AUTHORIZED</td>
<td>没有授权</td>
</tr>
<tr>
<td>CONNECTION_REFUSED_UNEXPECT_NODE</td>
<td>节点拒绝链接</td>
</tr>
<tr>
<td>CONNECTION_REFUSED_SESSION_NOT_EXIST</td>
<td>会话不存在</td>
</tr>
<tr>
<td>DISCONNECT</td>
<td>无</td>
<td>心跳主信令</td>
</tr>
<tr>
<td>CONNECT_ACK</td>
<td>无</td>
<td>心跳主信令</td>
</tr>
<tr>
<td rowspan="23">PUBLISH</td>
<td>US</td>
<td>用户搜索</td>
</tr>
<tr>
<td>FAR</td>
<td>朋友添加请求</td>
</tr>
<tr>
<td>UPUI</td>
<td>用户信息</td>
</tr>
<tr>
<td>FRN</td>
<td>朋友添加通知</td>
</tr>
<tr>
<td>FRP</td>
<td>拉取朋友请求</td>
</tr>
<tr>
<td>FHR</td>
<td>处理朋友申请</td>
</tr>
<tr>
<td>FP</td>
<td>获取朋友列表</td>
</tr>
<tr>
<td>MN</td>
<td>消息通知</td>
</tr>
<tr>
<td>MS</td>
<td>发送消息</td>
</tr>
<tr>
<td>MP</td>
<td>获取消息</td>
</tr>
<tr>
<td>FN</td>
<td>朋友添加通知</td>
</tr>
<tr>
<td>GC</td>
<td>创建群组</td>
</tr>
<tr>
<td>GPGI</td>
<td>获取群组信息</td>
</tr>
<tr>
<td>GPGM</td>
<td>获取群组成员</td>
</tr>
<tr>
<td>GAM</td>
<td>添加群组成员</td>
</tr>
<tr>
<td>GKM</td>
<td>移除群组成员</td>
</tr>
<tr>
<td>GQ</td>
<td>退出群组</td>
</tr>
<tr>
<td>GMI</td>
<td>修改群组信息</td>
</tr>
<tr>
<td>MMI</td>
<td>修改个人信息</td>
</tr>
<tr>
<td>GQNUT</td>
<td>获取上传文件token</td>
</tr>
<tr>
<td>MR</td>
<td>消息撤回</td>
</tr>
<tr>
<td>RMN</td>
<td>远程消息通知</td>
</tr>
<tr>
<td>LRM</td>
<td>拉取历史消息</td>
</tr>
<tr>
<td>PUB_ACK</td>
<td>确认主信令</td>
<td>子信令同上面Publish的子信令,这里不再一一列出</td>
</tr>
</table>
<h3 id="信令交互路程">信令交互路程</h3>
<p>信令交互流程主要是指如何使用以上的信令进行业务处理,信令的设计都采用的发送确认机制,例如客户端发送<code class="language-plaintext highlighter-rouge">connect</code>信令后,正常情况下都会受到服务器返回的ack确认指令,这里就是<code class="language-plaintext highlighter-rouge">connect_ack</code>.
下面主要针对<code class="language-plaintext highlighter-rouge">PUBLISH</code>信令说明,因为这个主信令下面有比较多的子信令,每个信令的具体含义已经在上面做了相应的说明。下面主要来说明具体业务是如何交互的。下面以发送消息说明</p>
<ul>
<li>客户端构造信令消息体,此时<strong>signal</strong>为<strong>PUSHLISH</strong>,子信令为<strong>MS</strong>,代表用户需要发送消息</li>
<li>服务端接收到消息后解析指令,根据子信令处理相应的业务逻辑。处理成功后,服务端构造<strong>PUSH__ACK</strong>确认消息,此时主信令就是<code class="language-plaintext highlighter-rouge">PUSH_ACK</code>,子信令依旧保持不变为<code class="language-plaintext highlighter-rouge">MS</code></li>
<li>客户端收到确认指令后,根据返回的子消息信令进行后续的业务处理</li>
<li>由于信令支持messageId,用户可以根据messageId,确认是否是之前的消息,可以用其实现异步消息转为同步处理</li>
</ul>
<h2 id="基于websocket的消息体信令设计">基于websocket的消息体信令设计</h2>
<p>由于web端采用的是websocket协议,所有我们只需要定义传输消息体格式既可,如下为websocket消息通讯的Json格式定义</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"signal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"connect"</span><span class="p">,</span><span class="w"> </span><span class="err">//主信令</span><span class="w">
</span><span class="nl">"sub_signal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"conect_ack"</span><span class="p">,</span><span class="w"> </span><span class="err">//子信令</span><span class="w">
</span><span class="nl">"message_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="err">//消息id</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w"> </span><span class="err">//消息体</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><strong>NOTE:</strong> 这里的Json格式定义有点类似二进制通讯协议,只不过由于websocket协议本身定义了消息头,消息长度,因为不需要我们自己处理,因此我们只需要关注以上字段既可</p>
<h2 id="基于消息信令的交互方式">基于消息信令的交互方式</h2>
<p>业务交互主要是基于<code class="language-plaintext highlighter-rouge">PUBLISH</code>与<code class="language-plaintext highlighter-rouge">PUB_ACK</code>模式进行的,使用子信令进行业务类型区分,例如发送请求用户信息,websocket协议Json格式为</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"signal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PUBLISH"</span><span class="p">,</span><span class="w"> </span><span class="err">//主信令</span><span class="w">
</span><span class="nl">"sub_signal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UPUI"</span><span class="p">,</span><span class="w"> </span><span class="err">//子信令</span><span class="w">
</span><span class="nl">"message_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="err">//消息id</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"userids"</span><span class="p">]</span><span class="w"> </span><span class="err">//用户id列表</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>了解完业务消息设计定义,你可以参考<code class="language-plaintext highlighter-rouge">unverse_push</code>项目的中<code class="language-plaintext highlighter-rouge">push-connector</code>中<code class="language-plaintext highlighter-rouge">com.comsince.github.websocket.model</code>包中定义的数据格式进行分析,了解不同业务信令下的数据格式定义</p>
<h2 id="发送消息体结构">发送消息体结构</h2>
<p>这里把发送消息体的定义单独列出来说明,是因为<code class="language-plaintext highlighter-rouge">MS</code>子信令的消息体结构设计跟上面的基本业务数据结构有很大的不同,有必要单独拿出来说明.这个这个消息体定义了发送聊天消息的各种类型,其也是以后作为扩展消息所必须要知道的数据结构</p>
<p><strong>NOTE:</strong> 以下是发送一个文本消息完整消息体</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"signal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PUBLISH"</span><span class="p">,</span><span class="w">
</span><span class="nl">"subSignal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MS"</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageId"</span><span class="p">:</span><span class="w"> </span><span class="mi">81</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4A4A4Aaa"</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"searchableContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1234"</span><span class="p">,</span><span class="w">
</span><span class="nl">"pushContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"binaryContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"localContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"mediaType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteMediaUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"localMediaPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"mentionedType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"mentionedTargets"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"messageId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1589944408832</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageUid"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1589944408832</span><span class="p">,</span><span class="w">
</span><span class="nl">"to"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"conversationType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d9dRdRoo"</span><span class="p">,</span><span class="w">
</span><span class="nl">"line"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>主信令与子信令含义不再描述,这里重点说明<code class="language-plaintext highlighter-rouge">content</code>消息定义,</p>
<p><strong>下图为消息定义参数对照表</strong></p>
<table>
<tr>
<th>字段名称</th>
<th>子字段</th>
<th>字段描述</th>
</tr>
<tr>
<td>from</td>
<td>无</td>
<td>来源</td>
</tr>
<tr>
<td>messageId</td>
<td>无</td>
<td>消息id,发送时随机生成</td>
</tr>
<tr>
<td>direction</td>
<td>无</td>
<td>消息方向,接收还是发送</td>
</tr>
<tr>
<td>status</td>
<td>无</td>
<td>推送转台</td>
</tr>
<tr>
<td>messageUid</td>
<td>无</td>
<td>消息唯一Id,由服务端返回</td>
</tr>
<tr>
<td>timestamp</td>
<td>无</td>
<td>消息时间戳</td>
</tr>
<tr>
<td>to</td>
<td>无</td>
<td>目标</td>
</tr>
<tr>
<td>conversationType</td>
<td>无</td>
<td>会话类型,单聊/群组</td>
</tr>
<tr>
<td>target</td>
<td>无</td>
<td>目标接收者Id</td>
</tr>
<tr>
<td>line</td>
<td>无</td>
<td>线路</td>
</tr>
<tr>
<td rowspan="11">content</td>
<td>type</td>
<td>消息内容类型</td>
</tr>
<tr>
<td>searchableContent</td>
<td>可供搜索的文本内容</td>
</tr>
<tr>
<td>pushContent</td>
<td>推送内容</td>
</tr>
<tr>
<td>content</td>
<td>内容</td>
</tr>
<tr>
<td>binaryContent</td>
<td>二进制消息内容,经过编码</td>
</tr>
<tr>
<td>localContent</td>
<td>消息本地内容</td>
</tr>
<tr>
<td>mediaType</td>
<td>媒体类型</td>
</tr>
<tr>
<td>remoteMediaUrl</td>
<td>媒体远程url,例如图片,视频,文件url</td>
</tr>
<tr>
<td>localMediaPath</td>
<td>本地媒体文件路径</td>
</tr>
<tr>
<td>mentionedType</td>
<td>提及类型</td>
</tr>
<tr>
<td>mentionedTargets</td>
<td>提及的对象ID</td>
</tr>
</table>
<p><strong>NOTE:</strong> 消息最终发送时,都会转化为Json格式,下面给出几个消息类型的示例</p>
<h3 id="纯文本消息">纯文本消息</h3>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"signal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PUBLISH"</span><span class="p">,</span><span class="w">
</span><span class="nl">"subSignal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MS"</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageId"</span><span class="p">:</span><span class="w"> </span><span class="mi">82</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4A4A4Aaa"</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"mentionedType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"mentionedTargets"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"searchableContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"纯文本消息"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"messageId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1589955328518</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageUid"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1589955328518</span><span class="p">,</span><span class="w">
</span><span class="nl">"to"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"conversationType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d9dRdRoo"</span><span class="p">,</span><span class="w">
</span><span class="nl">"line"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="图片类型消息">图片类型消息</h3>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"signal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PUBLISH"</span><span class="p">,</span><span class="w">
</span><span class="nl">"subSignal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MS"</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageId"</span><span class="p">:</span><span class="w"> </span><span class="mi">84</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4A4A4Aaa"</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"mentionedType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"mentionedTargets"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
</span><span class="nl">"searchableContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[图片]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"binaryContent"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"mediaType"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteMediaUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://image.comsince.cn/1-4A4A4Aaa-1589955429816-push-universe.png"</span><span class="p">,</span><span class="w">
</span><span class="nl">"localMediaPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"messageId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1589955430361</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageUid"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1589955430361</span><span class="p">,</span><span class="w">
</span><span class="nl">"to"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"conversationType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d9dRdRoo"</span><span class="p">,</span><span class="w">
</span><span class="nl">"line"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><strong>NOTE:</strong> 与文本消息的区别在图片类消息携带的是图片的<code class="language-plaintext highlighter-rouge">remoteMediaUrl</code>,<code class="language-plaintext highlighter-rouge">type</code>类型为3,注意对于一些内容为空的字段进行省略</p>
<h3 id="通知类消息">通知类消息</h3>
<p>通知类消息,例如<code class="language-plaintext highlighter-rouge">加群通知</code>,<code class="language-plaintext highlighter-rouge">退群通知</code>,<code class="language-plaintext highlighter-rouge">撤回消息</code></p>
<ul>
<li>移除群组成员的通知类消息</li>
</ul>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"binaryContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"eyJnIjoiYm9iQ2JDUFAiLCJvIjoiVllWTFZMMjIiLCJtcyI6WyJkemRKZEpfXyJdfQ=="</span><span class="p">,</span><span class="w">
</span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"mediaType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"mentionedType"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"pushContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"remoteMediaUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"searchableContent"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="mi">106</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"conversationType"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"direction"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="s2">"VYVLVL22"</span><span class="p">,</span><span class="w">
</span><span class="nl">"line"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageId"</span><span class="p">:</span><span class="w"> </span><span class="mi">157747907456403130</span><span class="p">,</span><span class="w">
</span><span class="nl">"messageUid"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bobCbCPP"</span><span class="p">,</span><span class="w">
</span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1589956063904</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="消息类型扩展说明">消息类型扩展说明</h3>
<p>为了以后能够支持更多的消息类型,支持更多的展示方式,本质还是定义出更多type类型的消息,例如可以添加相应的<code class="language-plaintext highlighter-rouge">商品链接消息</code>,展示<code class="language-plaintext highlighter-rouge">用户文章类分享</code>,这些都可以通过以上的数据结构扩展</p>
<h1 id="音视频通讯">音视频通讯</h1>
<p><strong>NOTE:</strong> 目前只支持一对一音视频通话
音视频通话详见: <a href="/2020/03/04/web-rtc">实时音视频开发的工程化实践</a></p>
<p><strong>NOTE:</strong> 进行音视频前,请先检查浏览器的支持情况,可以打开此链接<a href="https://www.qcloudtrtc.com/webrtc-samples/abilitytest/index.html">检测</a></p>
<h1 id="系统规划">系统规划</h1>
<h2 id="系统架构设计">系统架构设计</h2>
<p>系统会随着主流软件设计逐步进行架构设计,主要对系统的主要性能瓶颈进行一定的重新设计,例如接入用户数如何水平扩展,如果保证用户消息的大规模存储.
并尽量保证最具竞争力的设计,实现在即时通讯领域应用最新的技术架构,实现业务的稳步提升</p>
<h2 id="多端开发系统体验">多端开发系统体验</h2>
<p>在用户体验上,尽量使用原生开发,当然在这些基础上,可以使用小程序开发,方便大家快速体验系统功能.系统体验上,尽量追求,普通大众最能接收的设计.</p>
<h2 id="可扩展性">可扩展性</h2>
<p>相信很多用户可能对系统在实际应用中的效果并不满意,因此考虑系统的可扩展性,二次开发的便利性,需要提升</p>
<h2 id="接入方便">接入方便</h2>
<p>服务端接入包括两种,基于TCP的二进制协议,web端基于websocket,为了简化接入人员的接入工作量,可以提供包括<code class="language-plaintext highlighter-rouge">Android</code>,<code class="language-plaintext highlighter-rouge">IOS</code>,<code class="language-plaintext highlighter-rouge">Web</code>的端的SDK,提供基础用于socket编程的基础sdk,包含信令消息发送与接收的sdk</p>
<p><strong><center><font size="5" color="#bd4147">**本文未经授权禁止转载**</font></center></strong></p>comsince本文档主要说明飞享即时聊天系统的技术相关文档,用于支持其他多端开发.说明系统的整体架构与后续技术发展规划,技术愿景,未来商业化支持即时聊天系统在Windows上单机测试部署实践2020-05-07T00:00:00+08:002020-05-07T00:00:00+08:00https://www.comsince.cn/2020/05/07/universe-push-start-on-windows<h1 id="windows开发调试说明">Windows开发调试说明</h1>
<h2 id="环境准备">环境准备</h2>
<ul>
<li><strong>服务端</strong>:IDEA,JDK,Zookeeper</li>
<li><strong>WEB端</strong>:Node.js,npm</li>
<li><strong>zookeeper</strong><br />
dubbo使用了zookeeper作为注册中心,因此需要安装zookeeper,windows安装自行百度。</li>
<li>调试服务,只需要启动<code class="language-plaintext highlighter-rouge">spring-boot-dubbo-push-connector</code>和<code class="language-plaintext highlighter-rouge">spring-boot-web-push-group</code>服务</li>
<li>
<p>本地测试请修改这些配置</p>
<blockquote>
<p>spring-boot-dubbo-push-connector中application.properties</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # wss ssl 配置,本地测试可以删除
#push.ssl.keystore=classpath:github.comsince.cn.jks
#push.ssl.truststore=classpath:github.comsince.cn.trustkeystore.jks
#push.ssl.password=123456
</code></pre></div> </div>
<blockquote>
<p>spring-boot-web-push-group中application.properties</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ## https 证书,本地测试请注销这些配置,如果使用nignx转发,请到nginx配置相关证书
#server.ssl.key-store: classpath:github.comsince.cn.pfx
#server.ssl.key-store-password: effjgv2y
#server.ssl.keyStoreType: PKCS12
</code></pre></div> </div>
<blockquote>
<p>spring-boot-web-push-group中c3p0-config.xml</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 自行修改对应数据库信息和host表
<!--MySQL数据库地址 注意的这里的mysql,由于阿里云限制,这里mysql要设置为localhost地址127.0.0.1-->
<property name="jdbcUrl">jdbc:mysql://mysql:3306/wfchat?useSSL=false&amp;serverTimezone=GMT%2B8&amp;allowPublicKeyRetrieval=true&amp;useUnicode=true&amp;characterEncoding=utf8</property>
<!--MySQL数据库用户名-->
<property name="user">root</property>
<!--MySQL数据库密码-->
<property name="password">root</property>
</code></pre></div> </div>
</li>
<li>
<p><strong>前端配置</strong></p>
<blockquote>
<p>vue-chat\src\constant\index.js</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> //export const WS_PROTOCOL = 'wss';
export const WS_PROTOCOL = 'ws';
//export const WS_IP = 'github.comsince.cn';
export const WS_IP = 'localhost';
//export const HTTP_HOST = "https://"+WS_IP + ":8443/"
export const HTTP_HOST = "http://"+WS_IP + ":8081/"
</code></pre></div> </div>
</li>
</ul>
<h2 id="启动顺序">启动顺序</h2>
<ul>
<li>spring-boot-web-push-group</li>
<li>spring-boot-dubbo-push-connector</li>
</ul>
<h2 id="常见问题">常见问题</h2>
<ul>
<li>Mysql连接报乱码错误,百度查询解决方案,通常设置时区可以解决。</li>
<li>PublishMessageHandler-Exception Windows的文件路径问题,修改ClassUtil.java中的getClassNameByFile传入的filePath格式和路径。</li>
</ul>comsinceWindows开发调试说明 环境准备 服务端:IDEA,JDK,Zookeeper WEB端:Node.js,npm zookeeper dubbo使用了zookeeper作为注册中心,因此需要安装zookeeper,windows安装自行百度。 调试服务,只需要启动spring-boot-dubbo-push-connector和spring-boot-web-push-group服务 本地测试请修改这些配置即时聊天系统在Centos上单机部署实践2020-04-13T00:00:00+08:002020-04-13T00:00:00+08:00https://www.comsince.cn/2020/04/13/universe-push-start-on-centos<p>本文主要说明<code class="language-plaintext highlighter-rouge">飞享</code>在<code class="language-plaintext highlighter-rouge">centos</code>单机上的部署流程,如果大家购买相关mysql服务,可以选择部署相关服务</p>
<h1 id="mysql安装">MySQL安装</h1>
<ul>
<li><a href="/wiki/2019-07-01-mysql-00-install/">【数据库】- MySQL基本安装</a></li>
</ul>
<p><strong>NOTE:</strong> 不同版本的Centos安装可能存在差异,安装过程中注意错误提示</p>
<h2 id="密码策略">密码策略</h2>
<p>当然如果不需要密码策略,可以禁用:
在/etc/my.cnf文件添加</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>validate_password <span class="o">=</span> off
重启生效
systemctl restart mysqld
</code></pre></div></div>
<h1 id="对象存储">对象存储</h1>
<p>私有化对象存储可以采用<a href="http://docs.minio.org.cn/docs/">minio</a></p>
<h2 id="本地化安装">本地化安装</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://dl.minio.org.cn/server/minio/release/linux-amd64/minio
<span class="nb">chmod</span> +x minio
<span class="c">## 前台启动</span>
./minio server data/
<span class="c">## 后台启动</span>
<span class="nb">nohup</span> ./minio server miniodata/ <span class="o">></span>/data/minio.log 2>&1 &
<span class="c">## 修改参数启动</span>
<span class="nv">MINIO_ACCESS_KEY</span><span class="o">=</span><span class="nb">test </span><span class="nv">MINIO_SECRET_KEY</span><span class="o">=</span><span class="nb">test nohup</span> ./minio server miniodata/ <span class="o">></span> /opt/minio/minio.log 2>&1 &
<span class="c">## 启动https 注意accesskey 和secretkey 保持不变</span>
<span class="nv">MINIO_ACCESS_KEY</span><span class="o">=</span><span class="nb">test </span><span class="nv">MINIO_SECRET_KEY</span><span class="o">=</span><span class="nb">test nohup</span> ./minio server <span class="nt">--address</span> <span class="s2">":443"</span> /data/miniodata/ <span class="o">></span> /data/minio.log 2>&1 &
</code></pre></div></div>
<p><strong>NOTE:</strong> 更多高级配置,参见<a href="http://docs.minio.org.cn/docs/master/minio-server-configuration-guide">MinIO Server config.json (v18) 指南</a></p>
<h2 id="聊天作为对象存储服务">聊天作为对象存储服务</h2>
<p>MinIO 默认的策略是分享地址的有效时间最多是7天,要突破这种限制,可以在 bucket 中进行策略设置。点击对应的 bucket ,edit policy 添加策略 <em>.</em>
<strong>NOTE:</strong> 另外上传的文件必须带文件后缀,不然无法下载</p>
<ul>
<li>
<p><a href="http://docs.minio.org.cn/docs/master/setup-nginx-proxy-with-minio">为MinIO Server设置Nginx代理</a></p>
</li>
<li>客户端可以使用相应的SDK进行上传,到指定的bucket</li>
<li>上传成功后需要将设置对应的外网访问地址</li>
</ul>
<h2 id="管理后台操作">管理后台操作</h2>
<ul>
<li>创建不同的bucket用于存储不同的文件类型,如下</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_GENERAL_NAME</span> <span class="o">=</span> <span class="s">"minio-bucket-general-name"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_GENERAL_DOMAIN</span> <span class="o">=</span> <span class="no">MINIO_UPLOAD_ENDPOINT</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="no">MINIO_BUCKET_GENERAL_NAME</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_IMAGE_NAME</span> <span class="o">=</span> <span class="s">"minio-bucket-image-name"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_IMAGE_DOMAIN</span> <span class="o">=</span> <span class="no">MINIO_UPLOAD_ENDPOINT</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="no">MINIO_BUCKET_IMAGE_NAME</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_VOICE_NAME</span> <span class="o">=</span> <span class="s">"minio-bucket-voice-name"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_VOICE_DOMAIN</span> <span class="o">=</span> <span class="no">MINIO_UPLOAD_ENDPOINT</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="no">MINIO_BUCKET_VOICE_NAME</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_VIDEO_NAME</span> <span class="o">=</span> <span class="s">"minio-bucket-video-name"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_VIDEO_DOMAIN</span> <span class="o">=</span> <span class="no">MINIO_UPLOAD_ENDPOINT</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="no">MINIO_BUCKET_VIDEO_NAME</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_FILE_NAME</span> <span class="o">=</span> <span class="s">"minio-bucket-file-name"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_FILE_DOMAIN</span> <span class="o">=</span> <span class="no">MINIO_UPLOAD_ENDPOINT</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="no">MINIO_BUCKET_FILE_NAME</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_PORTRAIT_NAME</span> <span class="o">=</span> <span class="s">"minio-bucket-portrait-name"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_PORTRAIT_DOMAIN</span> <span class="o">=</span> <span class="no">MINIO_UPLOAD_ENDPOINT</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="no">MINIO_BUCKET_PORTRAIT_NAME</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_FAVORITE_NAME</span> <span class="o">=</span> <span class="s">"minio-bucket-favorite-name"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="no">MINIO_BUCKET_FAVORITE_DOMAIN</span> <span class="o">=</span> <span class="no">MINIO_UPLOAD_ENDPOINT</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="no">MINIO_BUCKET_FAVORITE_NAME</span><span class="o">;</span>
</code></pre></div></div>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="http://docs.minio.org.cn/docs/">MinIO Quickstart Guide</a></li>
</ul>
<h1 id="系统软件安装">系统软件安装</h1>
<ul>
<li>安装nc</li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum <span class="nb">install </span>nc
</code></pre></div></div>
<p><strong>NOTE:</strong> 我们提供一个安装目录,如下,里面包含<code class="language-plaintext highlighter-rouge">jdk</code>,<code class="language-plaintext highlighter-rouge">zookeeper</code>,以及应该安装包,一级目录如下:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── boot
├── jdk
└── zookeeper-3.4.6
</code></pre></div></div>
<h1 id="java环境配置">Java环境配置</h1>
<ul>
<li>编辑<code class="language-plaintext highlighter-rouge">~/.bash_profile</code></li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">JAVA_HOME</span><span class="o">=</span>/data/jdk
<span class="nb">export </span><span class="nv">JRE_HOME</span><span class="o">=</span><span class="nv">$JAVA_HOME</span>/jre
<span class="nb">export </span><span class="nv">CLASSPATH</span><span class="o">=</span>.:<span class="nv">$JAVA_HOME</span>/lib:<span class="nv">$JRE_HOME</span>/lib:<span class="nv">$CLASSPATH</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$JAVA_HOME</span>/bin:<span class="nv">$JRE_HOME</span>/bin:<span class="nv">$PATH</span>
</code></pre></div></div>
<ul>
<li>执行以下命令生效配置</li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> ~/.bash_profile
</code></pre></div></div>
<ul>
<li>检查是否安装成功</li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>root@VM_0_2_centos data]# java <span class="nt">-version</span>
java version <span class="s2">"1.8.0_131"</span>
Java<span class="o">(</span>TM<span class="o">)</span> SE Runtime Environment <span class="o">(</span>build 1.8.0_131-b11<span class="o">)</span>
Java HotSpot<span class="o">(</span>TM<span class="o">)</span> 64-Bit Server VM <span class="o">(</span>build 25.131-b11, mixed mode<span class="o">)</span>
</code></pre></div></div>
<h1 id="zookeeper安装与启动">Zookeeper安装与启动</h1>
<ul>
<li>zookeeper已经打包在整个安装配置文件中,只需要启动zookeeper就行</li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>root@VM_0_2_centos data]# ./zookeeper-3.4.6/bin/zkServer.sh start
JMX enabled by default
Using config: /data/zookeeper-3.4.6/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
执行jps,查看zookeeper进程
<span class="o">[</span>root@VM_0_2_centos data]# jps
25462 Jps
25211 QuorumPeerMain
</code></pre></div></div>
<h1 id="项目部署结构说明">项目部署结构说明</h1>
<p><strong>NOTE:</strong> 以下为<code class="language-plaintext highlighter-rouge">boot</code>目录下的文件结构,主要说明两个服务的目录结构,以及如何启动服务</p>
<h2 id="项目结构目录概览">项目结构目录概览</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── download <span class="c">#android Apk</span>
│ ├── chat-debug-0.7.2.apk
│ ├── chat-debug.0.7.3.apk
│ ├── chat-debug.0.7.4.apk
│ ├── chat-debug.0.7.5.apk
│ └── chat-debug.apk
├── push-connector <span class="c"># 信令消息服务器目录,支持TCP,WSS链接</span>
│ ├── jvm.ini <span class="c">#jvm参数配置</span>
│ ├── lib
│ │ └── spring-boot-dubbo-push-connector-1.0.0-SNAPSHOT.jar
│ ├── logs <span class="c"># 日志</span>
│ └── push-connector <span class="c"># 启动脚本</span>
└── push-group <span class="c"># 业务相关逻辑服务,包括http登录接口</span>
├── jvm.ini <span class="c">#jvm参数配置</span>
├── lib
│ └── spring-boot-web-push-group-1.0.0-SNAPSHOT.jar
├── logs <span class="c"># 日志</span>
└── push-group <span class="c"># 启动脚本</span>
</code></pre></div></div>
<h2 id="项目配置">项目配置</h2>
<p><strong>NOTE:</strong> 以下证书配置都是基于我申请的域名得到的证书,如果本地部署可能导入证书错误,无法访问,实际编译的时候去掉相关配置</p>
<h3 id="push-group证书配置">push-group证书配置</h3>
<p>暂时去掉,在application.properties</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">## https 证书,本地测试请注销这些配置</span>
<span class="c1">#server.ssl.key-store: classpath:2436378_github.comsince.cn.pfx</span>
<span class="c1">#server.ssl.key-store-password: effjgv2y</span>
<span class="c1">#server.ssl.keyStoreType: PKCS12</span>
</code></pre></div></div>
<h3 id="push-connector">push-connector</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># wss ssl 配置,本地测试可以删除</span>
<span class="c1">#push.ssl.keystore=classpath:github.comsince.cn.jks</span>
<span class="c1">#push.ssl.truststore=classpath:trustkeystore.jks</span>
<span class="c1">#push.ssl.password=123456</span>
</code></pre></div></div>
<p><strong>NOTE:</strong></p>
<ul>
<li><a href="https://blog.sprov.xyz/2019/05/06/crt-or-pem-to-jks/">免费证书生成工具转换为jks</a></li>
<li><a href="https://keymanager.org/">KeyManager 多平台免费下载</a></li>
<li><a href="https://biteeniu.github.io/ssl/convert_pem_to_jks/">如何将PEM证书转换成JKS证书</a></li>
</ul>
<h3 id="certbot证书配置">certbot证书配置</h3>
<h4 id="unbutu-安装">unbutu 安装</h4>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install </span>software-properties-common
<span class="nb">sudo </span>add-apt-repository universe
<span class="nb">sudo </span>add-apt-repository ppa:certbot/certbot
<span class="nb">sudo </span>apt-get update
</code></pre></div></div>
<h4 id="生成证书">生成证书</h4>
<p>生成证书可以使用nginx 自动绑定生成的方式,这里采用standalone方式,这个是后需要将你要生成证书的域名设置DNS解析</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certbot certonly <span class="nt">--standalone</span> <span class="nt">-d</span> media.comsince.cn <span class="nt">--staple-ocsp</span> <span class="nt">-m</span> ljlong_2008@126.com <span class="nt">--agree-tos</span>
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge <span class="k">for </span>media.comsince.cn
Waiting <span class="k">for </span>verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/media.comsince.cn/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/media.comsince.cn/privkey.pem
Your cert will expire on 2020-09-11. To obtain a new or tweaked
version of this certificate <span class="k">in </span>the future, simply run certbot
again. To non-interactively renew <span class="k">*</span>all<span class="k">*</span> of your certificates, run
<span class="s2">"certbot renew"</span>
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let<span class="s1">'s Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
</span></code></pre></div></div>
<h4 id="证书续期问题">证书续期问题</h4>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$certbot</span> renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/chat.comsince.cn.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OCSP check failed <span class="k">for</span> /etc/letsencrypt/archive/chat.comsince.cn/cert1.pem <span class="o">(</span>are we offline?<span class="o">)</span>
Cert is due <span class="k">for </span>renewal, auto-renewing...
Plugins selected: Authenticator nginx, Installer nginx
Starting new HTTPS connection <span class="o">(</span>1<span class="o">)</span>: acme-v02.api.letsencrypt.org
Renewing an existing certificate
Performing the following challenges:
http-01 challenge <span class="k">for </span>chat.comsince.cn
Waiting <span class="k">for </span>verification...
Cleaning up challenges
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed with reload of nginx server<span class="p">;</span> fullchain is
/etc/letsencrypt/live/chat.comsince.cn/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/chat.comsince.cn/fullchain.pem <span class="o">(</span>success<span class="o">)</span>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
</code></pre></div></div>
<ul>
<li>服务端证书更新
当刷新了证书,服务端Jks也需要更新,使用KeyManager,使用格式转换工具,导入fullchain.pem,private.key即可</li>
</ul>
<p><img src="/images/im/keymanager-pem-setting.png" alt="image" /></p>
<ul>
<li>keytool 显示jks详细信息</li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool <span class="nt">-list</span> <span class="nt">-keystore</span> comsince.cn.jks
输入密钥库口令:
密钥库类型: JKS
密钥库提供方: SUN
您的密钥库包含 1 个条目
1, 2020-7-31, PrivateKeyEntry,
证书指纹 <span class="o">(</span>SHA1<span class="o">)</span>: 02:72:5F:EB:86:D7:42:2B:58:5B:D9:F3:05:F3:E5:17:45:15:D6:A5
</code></pre></div></div>
<p><strong>NOTE:</strong> 可以看到别名alias 为1</p>
<ul>
<li>生成truststore.jks</li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool <span class="nt">-import</span> <span class="nt">-alias</span> certificatekey <span class="nt">-file</span> <span class="o">{</span>公钥证书<span class="o">}</span> <span class="nt">-keystore</span> comsince.cn.trustkeystore.jks
</code></pre></div></div>
<h4 id="申请泛域名证书">申请泛域名证书</h4>
<ul>
<li><a href="https://yq.aliyun.com/articles/713724">CentOS 7 上使用Certbot申请通配符证书</a></li>
<li><a href="https://www.timelate.com/archives/use-certbot-to-apply-and-install-letsencrypt-pan-domain-certificate.html">Ubuntu 18.04 使用 Certbot 申请并安装 Let’s Encrypt 泛域名证书</a></li>
</ul>
<h4 id="参考资料-1">参考资料</h4>
<ul>
<li><a href="https://certbot.eff.org/lets-encrypt/ubuntubionic-other">certbot安装</a></li>
<li><a href="http://docs.minio.org.cn/docs/master/generate-let-s-encypt-certificate-using-concert-for-minio">使用Certbot生成Let’s Encrypt证书</a></li>
</ul>
<h3 id="mysql链接配置">mysql链接配置</h3>
<p><strong>NOTE:</strong> 在<code class="language-plaintext highlighter-rouge">push-group</code>的resource目录的<code class="language-plaintext highlighter-rouge">c3p0-config.xml</code>中配置</p>
<h3 id="zookeepermysql-host配置">zookeeper,mysql host配置</h3>
<p>由于代码中使用了zookeeper,mysql相关的host,你可以在你启动的机器中配置相关host.修改<code class="language-plaintext highlighter-rouge">/etc/hosts</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>your centos ip zookeeper
127.0.0.1 mysql
</code></pre></div></div>
<h2 id="项目编译">项目编译</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>进行universe-push工程目录下,执行如下命令打包
mvn clean package <span class="nt">-Dmaven</span>.test.skip<span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>
<ul>
<li>成功如下提示</li>
</ul>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>INFO]
<span class="o">[</span>INFO] comsince ........................................... SUCCESS <span class="o">[</span> 0.736 s]
<span class="o">[</span>INFO] tio-core ........................................... SUCCESS <span class="o">[</span> 9.127 s]
<span class="o">[</span>INFO] push-stub .......................................... SUCCESS <span class="o">[</span> 2.071 s]
<span class="o">[</span>INFO] push-common ........................................ SUCCESS <span class="o">[</span> 1.060 s]
<span class="o">[</span>INFO] push-sdk ........................................... SUCCESS <span class="o">[</span> 0.001 s]
<span class="o">[</span>INFO] push-aio-sdk ....................................... SUCCESS <span class="o">[</span> 0.231 s]
<span class="o">[</span>INFO] push-nio-sdk ....................................... SUCCESS <span class="o">[</span> 2.049 s]
<span class="o">[</span>INFO] sofa-bolt-sdk ...................................... SUCCESS <span class="o">[</span> 0.707 s]
<span class="o">[</span>INFO] spring-boot-dubbo-push-connector ................... SUCCESS <span class="o">[</span> 4.394 s]
<span class="o">[</span>INFO] spring-boot-dubbo-push-subscribe ................... SUCCESS <span class="o">[</span> 0.248 s]
<span class="o">[</span>INFO] spring-boot-web-push-api ........................... SUCCESS <span class="o">[</span> 1.149 s]
<span class="o">[</span>INFO] spring-boot-web-push-group ......................... SUCCESS <span class="o">[</span> 9.126 s]
<span class="o">[</span>INFO] <span class="nt">------------------------------------------------------------------------</span>
<span class="o">[</span>INFO] BUILD SUCCESS
<span class="o">[</span>INFO] <span class="nt">------------------------------------------------------------------------</span>
<span class="o">[</span>INFO] Total <span class="nb">time</span>: 32.301 s
<span class="o">[</span>INFO] Finished at: 2020-04-13T16:33:58+08:00
<span class="o">[</span>INFO] Final Memory: 85M/814M
<span class="o">[</span>INFO] <span class="nt">------------------------------------------------------------------------</span>
</code></pre></div></div>
<h2 id="更新项目jar包">更新项目jar包</h2>
<p><strong>NOTE:</strong> 以<code class="language-plaintext highlighter-rouge">push-connector</code>为例上传远程服务上,以更新服务</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp spring-boot-dubbo-push-connector/target/spring-boot-dubbo-push-connector.jar root@aliyun:/data/boot/push-connector/lib
</code></pre></div></div>
<h2 id="项目启动">项目启动</h2>
<h2 id="启动push-group">启动push-group</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./push-connector start
</code></pre></div></div>
<h2 id="启动push-connector">启动push-connector</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./push-group start
</code></pre></div></div>
<h2 id="云服务网络配置">云服务网络配置</h2>
<p><strong>NOTE:</strong> 如果使用了腾讯云与阿里云,请开启相关的入端口<code class="language-plaintext highlighter-rouge">8081</code>,<code class="language-plaintext highlighter-rouge">8443</code>,<code class="language-plaintext highlighter-rouge">6789</code>,<code class="language-plaintext highlighter-rouge">9326</code></p>
<h1 id="安装包下载">安装包下载</h1>
<p><strong>NOTE:</strong> 由于安装包大,所以请在<a href="https://pan.baidu.com/s/1zbcUPdN_r87Gzeqrwyl6vA">百度云盘</a>下载,提取码<code class="language-plaintext highlighter-rouge">6xft</code></p>
<h1 id="演示登录">演示登录</h1>
<p>输入任意手机号,输入超级验证码<code class="language-plaintext highlighter-rouge">66666</code>登录即可</p>comsince本文主要说明飞享在centos单机上的部署流程,如果大家购买相关mysql服务,可以选择部署相关服务