使用socketio实现多房间通信聊天室

websocket的实现有很多种,像ws和socket.io,这里使用的是socket.io来实现多房间的效果。 这里的使用没有使用socket.io官方提供的namespace和room,而是完全通过一个namespace实现的。数据传输使用JSON格式,封装了消息规范 消息体规范const actionType = { join:'JOIN',//加入 leave:'LEAVE',//离开 talk:'TALK',//消息 action:'ACTION',//用户操作 push:'PUSH'//系统推送}//消息体class MSG { constructor(type,body){ this.type = type; this.body= body; }}安装使用npm install socket.io-rooms --savedemo演示把项目从github上clone下来后,执行npm start,然后打开example/index.html即可品尝到演示效果 使用方式服务端Serverconst {User,Rooms} = require('socket.io-rooms')const server = require('http').createServer();const io = require('socket.io')(server);//大厅io.on('connection', client => { let user = new User(); client.emit('user',user); client.on('join', data => { /\* 加入某个房间 \*/ Rooms.join(data,user,io,client) }); client.on('message',msg=>{ if(user.roomId){ // io.to(user.roomId).emit('message',msg) if(msg.type == 'update'){ user.update(msg.body); } msg.user = user.uid; Rooms.send(user.roomId,msg) }else{ io.emit('message',msg) } console.log(msg) }) client.on('disconnect', () => { /\* … \*/ console.log("连接断开") Rooms.leave(user) });});server.listen(80);这里传输统一使用`JSON`格式,消息`title`也以`message`为主,这里端口写的80,你可以使用其他端口,如果你是Express,也可以共用80端口。 ...

October 15, 2019 · 1 min · jiezi

抓住语音社交风口1天快速搭建语音聊天室

语音聊天室孵化一起KTV、众人大合唱、语音开黑、狼人杀、剧本杀、多人配音、观影、语音电台、相亲联谊社交等,一般都是在语音聊天室中进行,那么语音聊天室产品如此火热的原因有哪些呢? 一对一社交适用于朋友、家人之间,而更多的社交场景需要多人参与,聊天室的多人属性,正好满足此需求,用户按照自己的兴趣去交友聊天,也让社交更加多样化、娱乐化。对于有视频社交压力的用户来讲,实时展示自己的画面会是一个高压场景,压力也会明显大于文字、图片、语音等。另外,文字、图片社交不能携带太多用户的情绪和态度。语音社交不仅可以解决视频社交压力用户的痛点,也可以通过感知对方音量、音色等,感受到一个“真实”的对话者,让用户在轻松氛围中交友聊天。语音聊天室原型越来越多的产品提供语音聊天室,在语音聊天室的基础上创建多种玩法,那么一个标准的语音聊天室原型是怎样的呢? 语音聊天室应该有如下角色:创建者、管理员、普通成员。房间创建者拥有聊天室所有权限;管理员应该具有拉黑、禁言、踢人、设置普通成员权限、设置房间属性:如设置背景音乐、背景图、房间密码等;普通成员可以加入房间,申请连麦,参与语音聊天互动等。语音聊天室的技术关键点语音聊天室需要为用户提供长时间、高频次语音连麦互动功能,在网络抖动时保证语音通话流畅、延迟小、卡顿低、音质好。因此开发语音聊天室,一些技术关键点需要关注,如: 频繁麦位切换:抢麦、跳麦、麦位排序、抱麦、上麦、下麦等是典型场景,复杂的麦位逻辑需要详细设计,否则影响会产品体验;高并发:应用高峰一般会出现在午休时刻、晚上或者周末,音视频通话发起、接听数会瞬间爆发增长,如果音视频架构不能支持海量并发,那么通话发起或者接听不成功、丢包、卡顿、延时等现象出现几率极高;卡顿:通常是由丢包引起,涉及到音视频编解码性能质量,以及端到端的传输链路设计都会对卡顿造成影响;时延:通话延时大于150ms时,就会影响通话质量通话连续性影响,当最大时延大于400ms,基本听不到对方讲话;音质:噪声、回声、听不清、无声等音质问题,特别影响用户体验,提高音质,适配机型、适配音频编解码、调优性能等工作需要重视。网易云信语音聊天室方案根据语音聊天室用户的需求,以及需要考虑的核心要点,网易云信提出两种语音聊天室方案,让客户迅速搭建语音聊天室。在介绍方案之前先介绍几个概念:房间:用户进行多人实时音视频通话的地方房主:音视频房间的创建者或者管理员连麦者:在多人通话时参与语音互动,发言的人,可以发送语音,接收其他连麦者、房主语音观众:只可以收听的人,没有发言的权限,其中连麦者和观众身份可以随时切换。 方案A,实时音视频方案方案A的连麦互动基于多人实时音视频通话架构实现,选择纯音频模式,多人连麦进行实时音频通话,观众只可以听连麦者、房主的声音,不可以发言。要强调的是方案A的优点是观众听到声音延时小,但是支持观众人数有上限。 方案B,互动直播方案方案B是基于音视频直播与实时互动开发架构实现,互动直播由连麦互动和直播两部分组成,其中连麦互动基于音频通话实现,房主、连麦者互动合并直播,普通观众拉流观看语音聊天室的直播。要强调的是方案B支持观众人数无上限,但是拉流延时相对方案A有点大。介绍完语音聊天室的架构之后,接下来重点介绍一下核心模块实现,如多人语音互动、麦位管理以及成员权限管理等。• 语音互动语言聊天室多人语音互动是基于自研的音视频通话开发框架实现,流程如图所示 多人语音互动流程• 麦位管理麦位管理是聊天室常用功能之一,麦位管理分类主要有:上麦 、下麦、跳麦、抱麦、抢麦、禁麦、解禁等,网易云信麦位管理方案基于自研的聊天室队列实现。 • 权限管理语音聊天室不同成员具有不同权限,按照不同角色进行分类: 语音聊天室权限网易云信的语音聊天室权限管理方案基于自研的IM以及聊天室自定义消息、系统消息实现。 网易语音聊天室方案特点丰富灵活的API:实现场景自由切换与角色灵活设置,连麦者、观众观战轻松掌控、群聊/私聊切换;音质清晰:音频独家48kHz超宽屏音质,支持全频带编解码,PLC丢包补偿算法,自适应音频模式提供复杂音频环境解决方案,满足音质要求的痛点;抗抖动、丢包:智能网络探测,智能Qos保障,音视频码率自适应,多种核心算法保障弱网环境音频体验,可抗800ms网络抖动,30%丢包;低延时:端到端平均延时低于200ms,实时连麦互动无压力;易集成、扩展:集成稳定IM、音视频服务,满足即时通信聊天场景,高度灵活可扩展,不仅支持多人语音连麦,还支持多人视频连麦;麦位管理方便:IM、聊天室自定义消息、聊天室队列接口,便捷实现频繁麦位管理需求,优化麦位管理逻辑;高可用:服务器使用高可用的架构部署,对于服务器宕机、网络切断,使用了相应的恢复和切换策略。 网易云信已经为诸多专注语音社交的客户提供优质音视频服务,语音聊天室方案的功能与服务也会越来越全面,满足更广泛场景需求,让用户快速搭建,抓住语音社交的风口。 想要阅读更多行业洞察和技术干货,请关注网易云信博客。

June 28, 2019 · 1 min · jiezi

剧本杀继狼人杀之后的下一个风口

“剧本杀”简介2018年上半年,随着几款连麦推理社交游戏的上架,“剧本杀”一词开始迅速走红,有望成为继狼人杀之后的下一个风口级游戏。“剧本杀”最初源自线下游戏“谋杀之谜”,是一款 LARP (实时角色扮演)游戏。不同游戏的剧本内容各不相同,但是玩法基本大同小异:游戏开始阶段,每一名玩家选择扮演剧本中的一个角色,其中有一名玩家会在其他玩家不知情的情况下扮演凶手,其他玩家需要在故事情节以及所搜寻到的证据的分析推理下,共同找出真凶。行业发展过程剧本杀最初起源于一款风靡欧美的线下派对游戏,派对中的玩家们需要在故事情节的推动下共同找出凶手。随后,该游戏被引入国内,成为了各个线下桌游店里的热门游戏之一。2016年,视频行业快速发展,芒果TV推出了中国首档明星推理综艺秀《明星大侦探》,主打“烧脑剧情”和“悬疑推理”的综艺模式。在该节目第三季播出结束后,芒果播放量突破32亿次,也因此收获了大批“剧本杀”粉丝。随后,创业团队纷纷抓住这次机遇,将线下“剧本杀”迁移至线上。目前,线上比较火的“剧本杀”游戏有《戏精大侦探》、《我是谜》、《百变大侦探》等,同时《我是谜》和《戏精大侦探》都获得了数百万元的天使轮投资。 “剧本杀”搜索指数 从“剧本杀”的百度搜索指数来看,7月整体流量增长近一倍,也预示着“剧本杀”大有复制当年“狼人杀”的趋势,有望成为下一个游戏风口。游戏环节分析不同的“剧本杀”游戏有不同细度的阶段划分方式,但是主体框架会包括以下五个环节: “剧本杀”游戏环节 选择角色玩家进入游戏房间后,默认进入语音群聊,同时需要选择想要扮演的剧本中的角色。在语音群聊的过程中,可对自己进行禁麦和解禁的操作,房间一般还支持文字聊天。此阶段花费时间较短,待所有玩家角色选择完毕,即可进入下一阶段。 人物剧本本阶段,,所有玩家阅读个人人物剧本,剧本内容包含人物过往经历、重要线索、时间点等情节。剧本阅读完毕,玩家们在语音群聊房间内依次自我介绍,并讲述过往经历及重要线索。 搜集线索所有玩家共同阅读线索信息,同时就具体线索展开讨论。此阶段包含对多个角色的线索搜集及玩家讨论的过程,直至所有的线索搜集并讨论完成,方可结束。此阶段为整个游戏花费时间最长的阶段,所以语音聊天也是玩家体验的核心关键点。 圆桌讨论搜证阶段结束后,玩家们再次共同讨论并寻找故事真凶。此阶段不提供任何剧本及线索,所以各玩家首先需要回忆之前各个阶段的逻辑推理再作讨论。 最终投票就圆桌阶段讨论的结果,玩家们共同投票,选择凶手。投票阶段一般会计时,玩家需要在计时结束前完成投票。投票结束后,公布结局真相,并给出答案解析,游戏结束。目前主流的“剧本杀”游戏,玩家的主体精力基本放在“搜集线索”环节,需要进行多次搜证并对线索加以讨论。在房间人数上大多采用多人局,最多支持8人,其中《我是谜》app还提供了1-2人局,在极大程度解决了用户匹配的问题。游戏基本采用纯音频形式进行游戏,未来或许会出现类似“狼人杀”的视频面杀形式。用户分析对于“剧本杀”游戏的玩家群体,从技术竞技性、社交追求度和游戏排他性可以大致分为以下几类:竞技型玩家、社交型玩家、语音型玩家。 “剧本杀”游戏用户群体细分 对于竞技性玩家群体,他们喜爱游戏本身,对游戏剧本的内容,游戏结果以及整体流程体验会更加看重,极度追求技术,渴望和高手切磋。同时,此类玩家大多有线下“剧本杀”桌游的经历,对游戏的各个环节和打法比较了解。社交型玩家通常会青睐社交属性较重的游戏产品,也包括在线狼人杀等其他连麦社交游戏。这类玩家会更看重社交方面的体验,热爱在游戏过程中结交朋友,而对于游戏本身的关注所占比例较少。语音型玩家对于游戏本身、剧本内容,或游戏技术上没有过高的要求,但是对于包含语音连麦功能的游戏都极度热爱,包括各类吃鸡手游、在线狼人杀等等,对连麦时语音的质量也会有更加严格的把控。游戏关键要素在各大应用商店“剧本杀”的评论反馈中,可以看到很多玩家的好评,例如“玩法很新颖”、“有一种自己演电影的感觉”、“很锻炼逻辑思维能力”等,但仍有一些玩家发声表明了现在“剧本杀”存在的一些问题,例如“希望剧本的筛选能更用心”、“有些人麦的回音很大”、“无法闭麦”等。总结下来,主要是以下四个问题:剧本内容”剧本杀”是以剧本内容为主要打法的社交产品,剧本内容的质量、可玩性也成为了能否吸引玩家的重要因素。有不少玩家表示,玩“剧本杀”游戏的过程在极大程度上锻炼了自己的逻辑思维能力,在角色扮演过程中也会比较深入地代入角色情感,游戏体验很好。然而现在游戏的剧本来源主要依靠粉丝网友的投稿,有部分玩家表示希望剧本的筛选能更加严格,剧本的内容可玩性也有待提高。目前,各大平台开始设定奖励机制吸引优质写手,签约长期合作的剧本作者,这也是未来提高剧本质量的理想途径。实时互动对于“剧本杀”这类强互动性的实时语音连麦游戏来说,除了阅读剧本,搜集线索的过程,90%以上的游戏体验核心都在语音聊天上。而由于各种各样的原因,例如网络不稳定、运营商网络出现故障等等,可能导致语音通话过程不顺畅,聊天时延过长等问题,导致玩家不能实时语音交流互动,这也将是一个较为致命的产品体验痛点。音质效果玩家在游戏过程中,大部分时间都通过语音连麦聊天的方式和其他玩家交流,这种方式对于语音通话的质量也产生了更高的标准。在应用商店的用户评论处可以看到这样的评价:“玩了几局,发现每局都有人麦的回音很大,而有回音的这个人开麦以后完全听不到在说什么,特别乱,经常大家什么都没弄明白就投票了。”由此可见,与声音音质有关的杂音、丢音、回声、噪声等问题都会严重影响玩家的游戏体验。稳定流畅移动端在线游戏体验流畅的前提是拥有稳定的网络条件,由于在线“剧本杀”正处于快速增长阶段,用户数量的激增会导致网络拥塞、丢包等各种问题。所以在应用商店里,经常能够看到诸如“希望能早点修复莫名其妙离线等bug”、“一直显示请求超时,偶尔才能刷新出新东西”、“游戏过程中会断开连接”、“一直加载失败,链接超时”等反馈,种种声音表明,网络异常问题导致的游戏过程断续,导致玩家无法体验最基本的游戏流程。如何解决网络问题,为玩家提供稳定流畅的体验,也急需提上日程。网易云信方案及优势主流的“剧本杀”游戏,从用户场景上划分为剧本玩家实时游戏房间和观众观战房间。剧本玩家实时游戏是整个剧本杀游戏最为核心的场景,涉及到多个玩家语音连麦群聊、两个玩家一对一单聊、聊天室内文字交流互动等,这些场景对实时性的要求很高,同时对于语音聊天是否稳定流畅、是否有杂音噪音等都有很严格的标准。而在观众观看场景下,由于“剧本杀”游戏本身属性原因,观众在游戏房间内没有语音连麦及发表文字的权限,所以这一场景的体验关键点,主要在于直播画面是否同步、语音是否流畅清晰。针对以上两个场景的不同特性,以及在技术实现上的不同要求,网易云信推出了完整的一套“剧本杀”游戏方案,整体框架如下图所示: “剧本杀”音视频游戏房间技术框架 网易云信在实现方案上主要突显三大能力:实时音视频、IM即时通讯和互动直播。其中,实时音视频能力为游戏房间内的剧本玩家提供了真实的连麦互动体验,IM聊天室提供文字聊天互动能力,互动直播技术保证观众观看游戏过程的流畅体验。在游戏过程的实时互动性上,网易云信提供的技术方案保证端到端平均延时低于200ms,确保语音连麦互动的实时性,为“剧本杀”游戏的各个玩家提供酣畅淋漓的即时互动体验。在语音连麦聊天的音质上,采用48kHz音频采集,全频带音质,打造业界最高标准;同时,针对玩家语音连麦时可能出现的噪声、回声等情况,采用语音智能降噪,自动增益,回声消除等算法,打造无损音质,为玩家还原最真实、清晰的音质。对于网络不稳定、信号较差的问题,方案提供了智能网络探测,智能Qos保障,音视频码率自适应等多种核心算法保障,同时可抗800ms网络抖动,定制化的FEC/ Jitter Buffer/ QoS策略,确保70%丢包仍可正常通话,保障游戏进程的稳定流畅性。针对“剧本杀”游戏包含的不同场景模式,网易云信提供的技术实现方案完美解决了从开发到实现的诸多难题,让开发者能够迅速开发出在线“剧本杀”游戏。同时,在保证游戏稳定运行的前提下,能够为游戏玩家们提供极致的游戏体验。随着音视频技术的不断演进,从“在线狼人杀”、“在线抓娃娃”,到现在的“在线剧本杀”,泛娱乐行业内一个个风口级产品如雨后春笋般出现。这种从线下游戏迁移至线上app的模式,是目前泛娱乐行业出现频率较高的新兴玩法,也是网易云信所十分看好的。针对不同场景化游戏,网易云信都分别为其量身定制了不同的技术实现方案,未来,我们也将持续关注行业新动态,希望能为开发者解决更多的难题。想要阅读更多行业洞察和技术干货,请关注网易云信博客。

June 28, 2019 · 1 min · jiezi

如何在零JS代码情况下实现一个实时聊天功能❓

引言前段时间在 github 上看到了一个很“trick”的项目:用纯 CSS(即不使用 JavaScript)实现一个聊天应用 —— css-only-chat。即下图所示效果。 在我们的印象里,实现一个简单的聊天应用(消息发送与多页面同步)并不困难 —— 这是在我们有 JavaScript 的帮助下。而如果让你只能使用 CSS,不能有前端的 JavaScript 代码,那你能够实现么? 原版是用 Ruby 写的后端。可能大家对 Ruby 不太了解,所以我按照原作者思路,用 NodeJS 实现了一版 css-only-chat-node,对大家来说可能会更易读些。1. 我们要解决什么问题首先强调一下,服务端的代码肯定还是需要写的,而且这部分显然不能是 CSS。所以这里的“纯 CSS”主要指在浏览器端只使用 CSS。 回忆一下,如果使用 JavaScript 来实现上图中展示的聊天功能,有哪些问题需要处理呢? 首先,需要添加按钮的click事件监听,包括字符按钮的点击与发送按钮的点击;其次,点击相应按钮后,要将信息通过 Ajax 的方式发送到后端服务;再者,要实现实时的消息展示,一般会建立一个 WebSocket 连接;最后,对于后端同步来的消息,我们会在浏览器端操作 DOM API 来改变 DOM 内容,展示消息记录。涉及到 JavaScript 的操作主要就是上面四个了。但是,现在我们只能使用 CSS,那对于上面这几个操作,可以用什么方式实现呢? 2. Trick Time2.1. 解决“点击监听”的问题使用 JavaScript 的话一行代码可以搞定: document.getElementById('btn').addEventListener('click', function () { // ……});使用 CSS 的话,其实有个伪类可以帮我们,即:active。它可以选择激活的元素,而当我们点击某个元素时,它就会处于激活状态。 所以,对于上面动图中的26个字母(再加上 send 按钮),可以分配不同的classname,然后设置伪类选择器,这样就可以在点击该字母对应的按钮时触发命中某个 CSS 规则。例如可以对字符“a”设置如下规则用于“捕获”点击: .btn_a:active { /* …… */ }2.2. 发送请求如果有 JavaScript 的帮助,发送请求只需要用个 XHR 即可,很方便。而对于 CSS,如果要想发一个请求的话有什么办法么? ...

May 21, 2019 · 2 min · jiezi

reactreactrouterreduxNodejssocketio写一个聊天webapp

一、项目预览之前看一个写聊天器的教程,自己也跟着教程做了一遍,由于懒得去找图片和一些图标我就用教程中的素材来做,主要是用了react+react-router+redux+Node.js+socket.io的技术栈,接下来就是项目的预览 1.首先在/login下能看到有登录和注册按钮 2.点击注册按钮,路由跳到/register,注册一个账号,用户和密码都为LHH,选择“牛人”,点击注册,之后路由会跳到/geniusinfo,即牛人完善信息页,选择一个头像并完善信息后点击保存按钮 3.可以看到已经进入有三个tab选项的内容页面了,点击“我”,路由跳转到/me即可看到个人中心内容,但此时boss和消息的tab页仍没有内容,可以按照之前步骤注册一个Boss账号,只需在注册的时候选择Boss选项 4.现在在LHH和LCE账号分别能看到的列表 5.点击进入聊天室,输入内容 二、接下来对项目的主要内容进行解释1.项目的除掉node_modules后的目录├─build│ └─static│ ├─css│ └─js├─config│ └─jest├─public├─scripts├─server└─src ├─component │ ├─authroute │ ├─avatar-selector │ ├─boss │ ├─chat │ ├─dashboard │ ├─genius │ ├─img │ ├─logo │ ├─msg │ ├─navlink │ │ └─img │ ├─user │ └─usercard ├─container │ ├─bossinfo │ ├─geniusinfo │ ├─login │ └─register └─redux其中build文件夹的内容为npm run build打包后的内容,在项目中如果启用后端接口也可访问 2.入口页面import React from 'react';import ReactDOM from 'react-dom';import { createStore, applyMiddleware, compose } from 'redux';import thunk from 'redux-thunk';import { Provider } from 'react-redux';// eslint-disable-next-lineimport { BrowserRouter } from 'react-router-dom';import App from './app'import reducers from './reducer'import './config'import './index.css'const store = createStore(reducers, compose( applyMiddleware(thunk), window.devToolsExtension?window.devToolsExtension():f=>f))// boss genius me msg 4个页面ReactDOM.render( (<Provider store={store}> <BrowserRouter> <App></App> </BrowserRouter> </Provider> ), document.getElementById('root') )使用react-redux的Provider,可实现全局的状态存储,子组件可通过props获得存储在全局的状态 ...

May 2, 2019 · 4 min · jiezi

用Java构建一个简单的WebSocket聊天项目之新增HTTP接口调度

本文首发公众号与个人博客:Java猫说 & 猫叔的博客 | MySelf,转载请申明出处。前言大家可以看看上一篇:用Java构建一个简单的WebSocket聊天室在上一篇文章中我们已经实现了:自我对话、好友交流、群聊、离线消息等的功能。而本篇,我们的框架升级了,并且开通了几个新的HTTP接口功能,同时也把原先框架的一些异常做了处理。我们将使用更少的代码完成功能更加完善的聊天项目!采用框架我们整个Demo基本不需要大家花费太多时间,就可以实现以下的功能。用户token登录校验自我聊天点对点聊天群聊获取在线用户数与用户标签列表发送系统通知首先,我们需要介绍一下我们今天打算采用的框架,InChat : 一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架,采用这个框架,我们基本上只需要两三个类就可以实现我们今天需要的功能了。先看看效果需要了解SSM & SpringBoot 吗?InChat ,本身不依赖于任何的底层框架,所以大家只要会基本的Java语言就可以实现一套自己的WebSocket聊天室。框架使用手册(新版V1.1.2刚刚发布)关于详细的手册说明,大家可以看看官网的介绍:V1.1.2版本使用说明V1.1.2版本视频教学<dependency> <groupId>com.github.UncleCatMySelf</groupId> <artifactId>InChat</artifactId> <version>1.1.2</version></dependency>开始Demo搭建构建一个空的Maven项目我们不需要依赖其他的Maven包,只要本文提及的框架即可。<dependency> <groupId>com.github.UncleCatMySelf</groupId> <artifactId>InChat</artifactId> <version>1.1.2</version></dependency>InChat启动参数可以自配置你只需要继承InChat的默认配置类InitNetty即可,如下public class MyInit extends InitNetty { /** 自定义启动监听端口 */ @Override public int getWebport() { return 8090; }}获取聊天消息数据此接口与原先一样,仅修改了方法名public class DataBaseServiceImpl implements InChatToDataBaseService { @Override public Boolean writeMessage(InChatMessage message) { System.out.println(message.toString()); return true; }}登录校验与群聊消息此接口没有做过多的修改public class VerifyServiceImpl implements InChatVerifyService { @Override public boolean verifyToken(String token) { return true; } @Override public JSONArray getArrayByGroupId(String groupId) { JSONArray jsonArray = JSONArray.parseArray("["1111","2222","3333"]"); return jsonArray; }}服务端发送通知消息枚举类此接口具有Demo模板,用户需要继承InChat框架的FromServerService接口,同时该接口注释也有实例demo,我们需要实现一个自定义的枚举,你可以这样写:public enum FromServerServiceImpl implements FromServerService { //你可以自定义自己的系统消息,请以Integer-String的形式 TYPE1(1,"【系统通知】您的账号存在异常,请注意安全保密信息。"), TYPE2(2,"【系统通知】恭喜您连续登录超过5天,奖励5积分。"); private Integer code; private String message; FromServerServiceImpl(Integer code, String message){ this.code = code; this.message = message; } public Integer getCode() { return code; } //实现接口的方法,遍历本枚举的code,获取对应的消息,作为系统消息发送 public String findByCode(Object code) { Integer codes = (Integer)code; for (FromServerServiceImpl item: FromServerServiceImpl.values()) { if (item.code == codes){ return item.message; } } return null; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }}启动项目1.1.2版本的启动项目变得异常的简单,你只需要配置启动的配置工厂即可。public class application { public static void main(String[] args) { //配置你的自定义配置 ConfigFactory.initNetty = new MyInit(); //配置校验类 ConfigFactory.inChatVerifyService = new VerifyServiceImpl(); //配置消息接收处理类 ConfigFactory.inChatToDataBaseService = new DataBaseServiceImpl(); //配置服务端系统消息枚举,这里的值无所谓 TYPE1或者TYPE2或者TYPEN均可以 ConfigFactory.fromServerService = FromServerServiceImpl.TYPE1; //启动InChat InitServer.open(); }}项目效果启动成功DEBUG - -Dio.netty.threadLocalDirectBufferSize: 0DEBUG - -Dio.netty.maxThreadLocalCharBufferSize: 16384 INFO - 服务端启动成功【192.168.56.1:8090】当聊天连接未注册情况下,客户端自动断开后,服务会自动包对应的异常 INFO - [Handler:channelInactive]/192.168.56.1:8090关闭成功ERROR - [捕获异常:NotFindLoginChannlException]-[Handler:channelInactive] 关闭未正常注册链接!原先的自我发送,点对点发送,群聊均与原来一样原先的接口说明可以看上一版本: v1.1.0-alpha版本使用说明新功能添加 HTTP新增HTTP接口三个,在你启动Inchat的时候,默认启动,对于你的其他web API并无任何影响,它是一个IM的辅助作用。本版本不支持用户自定义相关的InChat HTTP接口获取在线用户数地址:[ip:端口]/get_size GET返回值{ “code”: 200, “data”: { “online”: 1,//当前在线数 “time”: “Jan 3, 2019 10:06:45 PM”//查询时间 }}获取在线用户标识地址:[ip:端口]/get_list GET返回值{ “code”: 200, “data”: { //返回在线用户列表 “tokens”: [ “1111” ] }}根据用户标签,发送系统指定消息地址:[ip:端口]/send_from_server POST参数:token(你可以从get_list中得到在线用户标签)、value(你在系统中添加枚举的code值,这里不接受字符串)返回值{ “code”: 400, “data”: { “message”: “通知发送成功” }}(有个小BUG,返回值code应该是200)关于前端InChat : 一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架,大家可以直接来这个项目下获取前端页面,或者直接访问这个地址:https://github.com/UncleCatMy…对于这个前端页面,我们需要更改一下IP地址。运行调试项目接下来直接启动后端项目,当我们看到以下的信息,则项目启动成功。 INFO - 服务端启动成功【192.168.1.121:8090】这里的IP需要更换以下读者启动后的IP地址。接着直接用浏览器打开chat.html的页面即可,关于js的方法,大家可以看看InChatV1.1.0版本使用说明。运行效果已经提前展示啦!公众号:Java猫说现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。 ...

January 4, 2019 · 2 min · jiezi

用Java构建一个简单的WebSocket聊天室

前言首先对于一个简单的聊天室,大家应该都有一定的概念了,这里我们省略用户模块的讲解,而是单纯的先说说聊天室的几个功能:自我对话、好友交流、群聊、离线消息等。今天我们要做的demo就能帮我们做到这一点啦!!!采用框架我们整个Demo基本不需要大家花费太多时间,就可以实现以上的几个功能。首先,我们需要介绍一下我们今天打算采用的框架,InChat : 一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架,采用这个框架,我们基本上只需要两三个类就可以实现我们今天需要的功能了。需要了解SSM & SpringBoot 吗?InChat ,本身不依赖于任何的底层框架,所以大家只要会基本的Java语言就可以实现一套自己的WebSocket聊天室。框架使用手册关于详细的手册说明,大家可以看看官网的介绍:InChatV1.1.0版本使用说明开始Demo搭建构建一个空的Maven项目我们不需要依赖其他的Maven包,只要本文提及的框架即可。com.github.UncleCatMySelfInChat1.1.0-alpha对接两个接口与实现一个是框架提供给我们用户进行数据保存与读取的,通过这个接口的实现,我们可以异步拿到每个聊天的通信数据。这里的InChatMessage是一个框架自定义的通信对象。public class ToDataBaseServiceImpl implements InChatToDataBaseService{ @Override public Boolean writeMapToDB(InChatMessage message) { System.out.println(message.toString()); return true; }}还有一个接口是对登录的校验(这里我们审理用户登录与校验模块,所以直接返回true即可),还有一个是返回群聊的数组信息。public class verifyServiceImpl implements InChatVerifyService { @Override public boolean verifyToken(String token) { //登录校验 return true; } @Override public JSONArray getArrayByGroupId(String groupId) { //根据群聊id获取对应的群聊人员ID JSONArray jsonArray = JSONArray.parseArray("["1111","2222","3333"]"); return jsonArray; }}我们可以再详细的说下,获取群聊信息,是通过一个groupId来获取对应的用户Id数组,我们可以自己做一个数据查询。核心的框架启动代码直接上代码,然后我们再讲解一下。public class DemoApplication { public static void main(String[] args) { //配置InChat配置工厂 ConfigFactory.inChatToDataBaseService = new ToDataBaseServiceImpl(); ConfigFactory.inChatVerifyService = new verifyServiceImpl(); //默认启动InChat InitServer initServer = new InitServer(new InitNetty()); initServer.open(); //获取用户值 WebSocketChannelService webSocketChannelService = new WebSocketChannelService(); //启动新线程 new Thread(new Runnable() { @Override public void run() { //设定默认服务器发送值 Map map = new HashMap<>(); map.put(“server”,“服务器”); //获取控制台用户想发送的用户Token Scanner scanner = new Scanner(System.in); String token = scanner.nextLine(); //获取用户连接 Channel channel = (Channel) webSocketChannelService.getChannel(token); //调用接口发送 webSocketChannelService.sendFromServer(channel,map); } }).start(); }}好了,以上已经基本完成了我们的聊天室Demo了,是不是很简单!?首先,我们将实现的两个类,配置到框架的配置工厂中,然后启动框架即可,相关的类,都是框架提供的。下面的线程是一个框架的接口,以服务器第一人称发送给针对用户通知信息,输入“1111”,Demo演示的用户token值。关于前端InChat : 一个轻量级、高效率的支持多端(应用与硬件Iot)的异步网络应用通讯框架,大家可以直接来这个项目下获取前端页面,或者直接访问这个地址:https://github.com/UncleCatMy…对于这个前端页面,我们需要更改一下IP地址。运行调试项目接下来直接启动后端项目,当我们看到以下的信息,则项目启动成功。 INFO - 服务端启动成功【192.168.1.121:8090】这里的IP需要更换以下读者启动后的IP地址。接着直接用浏览器打开chat.html的页面即可,关于js的方法,大家可以看看InChatV1.1.0版本使用说明。运行效果如下: INFO - 服务端启动成功【192.168.1.121:8090】DEBUG - -Dio.netty.buffer.bytebuf.checkAccessible: trueDEBUG - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@68ad4247 INFO - [DefaultWebSocketHandler.channelActive]/192.168.1.121:17330链接成功DEBUG - -Dio.netty.recycler.maxCapacityPerThread: 4096DEBUG - -Dio.netty.recycler.maxSharedCapacityFactor: 2DEBUG - -Dio.netty.recycler.linkCapacity: 16DEBUG - -Dio.netty.recycler.ratio: 8DEBUG - [id: 0xabb0dbad, L:/192.168.1.121:8090 - R:/192.168.1.121:17330] WebSocket version V13 server handshakeDEBUG - WebSocket version 13 server handshake key: JYErdeATDgbPmgK0mZ+IlQ==, response: YK9ZiJehNP+IwtlkpoVkPt94yWY=DEBUG - Decoding WebSocket Frame opCode=1DEBUG - Decoding WebSocket Frame length=31 INFO - [DefaultWebSocketHandler.textdoMessage.LOGIN]DEBUG - Encoding WebSocket Frame opCode=1 length=33DEBUG - Decoding WebSocket Frame opCode=1DEBUG - Decoding WebSocket Frame length=43 INFO - [DefaultWebSocketHandler.textdoMessage.SENDME]1111DEBUG - Encoding WebSocket Frame opCode=1 length=28 INFO - 【异步写入数据】InChatMessage{time=Mon Dec 24 10:03:00 CST 2018, type=‘sendMe’, value=’’, token=‘1111’, groudId=‘null’, online=‘null’, onlineGroup=null, one=‘null’}DEBUG - Decoding WebSocket Frame opCode=1DEBUG - Decoding WebSocket Frame length=56 INFO - [DefaultWebSocketHandler.textdoMessage.SENDTO]1111DEBUG - Encoding WebSocket Frame opCode=1 length=41 INFO - 【异步写入数据】InChatMessage{time=Mon Dec 24 10:03:01 CST 2018, type=‘sendTo’, value=’’, token=‘1111’, groudId=‘null’, online=‘2222’, onlineGroup=null, one=‘2222’}DEBUG - Decoding WebSocket Frame opCode=1DEBUG - Decoding WebSocket Frame length=60 INFO - [DefaultWebSocketHandler.textdoMessage.SENDGROUP]1111DEBUG - Encoding WebSocket Frame opCode=1 length=59 INFO - 【异步写入数据】InChatMessage{time=Mon Dec 24 10:03:02 CST 2018, type=‘sendGroup’, value=’’, token=‘1111’, groudId=‘2’, online=‘null’, onlineGroup=[2222, 3333], one=‘null’}1111DEBUG - Encoding WebSocket Frame opCode=1 length=22 ...

December 24, 2018 · 2 min · jiezi

h5语音聊天audio实战|仿微信语音效果|h5即时聊天系统

最近一段时间不是那么忙,就抽空整理了下之前的项目,因为之前有开发过H5聊天项目,只是觉得好些功能都没有特别的完善,所以就把之前项目重新开发了下,如是就有了这个html5版实时聊天语音项目weChatIM系统。依旧使用的是h5+css3+jquery+wcPop+swiper+weScroll等技术架构开发,新增了上拉刷新加载数据,右键长按菜单弹窗、仿微信语音效果(按住说话,上滑取消发送)及地图定位功能。// >>> 【按住说话核心模块】——————————————// …按住说话var _voiceObj = $(".J__wdtVoice"), eY1 = 0, eY2 = 0, eY3 = 0, isDrag = true;var voiceIdx;var difftime = 0;function initVoice(){ _voiceObj.on(“touchstart”, function(e){ difftime = new Date(); if(!isDrag) return; isDrag = false; eY1 = e.originalEvent.targetTouches[0].pageY; _voiceObj.text(“松开 结束”); // 弹窗提示 voiceIdx = wcPop({ id: ‘wdtVoice’, skin: ’toast’, content: ‘<div style=“margin-top:-10px;"><i class=“iconfont icon-yuyin” style=“font-size:65px;"></i><div style=“line-height:32px;">手指上滑,取消发送</div></div>’, style: ‘border-radius:6px;height: 160px; width:160px;’, time: 10, opacity: 0, }); _voiceObj.on(“touchmove”, function (e) { e.preventDefault(); eY3 = e.originalEvent.targetTouches[0].pageY; if(eY1 - eY3 < 150){ _voiceObj.text(“松开 结束”); }else{ _voiceObj.text(“松开手指,取消发送”); // 弹窗提示 $("#wdtVoice .popui__panel-cnt”).html(’<div style=“margin-top:-10px;"><i class=“iconfont icon-quxiao” style=“font-size:65px;"></i><div style=“background:#c53838; border-radius:3px; line-height:32px;">松开手指,取消发送</div></div>’); } }); }); _voiceObj.on(“touchend”, function (e) { e.preventDefault(); eY2 = e.originalEvent.changedTouches[0].pageY; _voiceObj.text(“按住 说话”); // 录音时间太短提示 if(new Date() - difftime < 1000){ // 弹窗提示 $("#wdtVoice .popui__panel-cnt”).html(’<div style=“margin-top:-10px;"><i class=“iconfont icon-gantan” style=“font-size:65px;"></i><div style=“line-height:32px;">录音时间太短!</div></div>’); } else{ if (eY1 - eY2 < 150) { // 发送成功 submitData(); console.log(“测试数据”); } else { // 取消发送 console.log(“cancel”); } } // 关闭弹窗 setTimeout(function(){ wcPop.close(voiceIdx); }, 500); isDrag = true; });}// >>> 【摇一摇加好友核心模块】——————————————// 摇一摇加好友弹窗$("#J__popScreen_shake”).on(“click”, function () { var shakePopIdx = wcPop({ id: ‘wcim_shake_fullscreen’, skin: ‘fullscreen’, title: ‘摇一摇’, content: $("#J__popupTmpl-shakeFriends”).html(), position: ‘right’, xclose: true, style: ‘background: #303030;’, show: function(){ // 摇一摇功能 var _shake = new Shake({threshold: 15}); _shake.start(); window.addEventListener(“shake”, function(){ window.navigator.vibrate && navigator.vibrate(500); // console.log(“触发摇一摇!”); $(".J__shakeInfoBox”).html(””); $(".J__shakeLoading”).fadeIn(300); // 消息模板 var shakeTpl = [ ‘<div class=“shake-info flexbox flex-alignc”>\ <img class=“uimg” src=“img/uimg/u__chat-img08.jpg” />\ <div class=“flex1”>\ <h2 class=“name”>大幂幂<i class=“iconfont icon-nv c-f37e7d”></i></h2>\ <label class=“lbl clamp1”>开森每一刻,每天都要美美哒!</label>\ </div>\ </div>’ ].join(”"); setTimeout(function(){ $(".J__shakeLoading").fadeOut(300); $(".J__shakeInfoBox").html(shakeTpl); }, 1500); }, false); } });});// 切换摇一摇项目$(“body”).on(“click”, “.J__swtShakeItem a”, function(){ $(this).addClass(“active”).siblings().removeClass(“active”);});// 摇一摇设置$(“body”).on(“click”, “.J__shakeSetting”, function(){ wcPop({ skin: ‘actionsheetMini’, anim: ‘footer’, btns: [ { text: ‘<div class=“flexbox flex-alignc”><span class=“flex1”>是否开启震动</span> <span class=“rpr-30”><input class=“cp__checkboxPX-switch” type=“checkbox” checked /></span></div>’ }, { text: ‘摇到的历史’ }, ] });});欢迎大家一起交流、学习 Q:282310962 wx:xy190310 ...

December 23, 2018 · 2 min · jiezi

h5仿钉钉实战|仿钉钉聊天|仿钉钉模板界面

html5仿钉钉聊天weDingTalk微钉|h5钉聊系统|仿钉钉界面基于web端h5+css3+Zepto+jquery+swiper+wcPop等技术开发的仿钉钉移动办公系统,仿钉钉聊天界面模板,实现了类似钉钉聊天界面,发送消息、表情,预览图片、视频,发送红包,右键长按菜单等功能。][3]// 发送信息function isEmpty() { var html = $editor.html(); html = html.replace(/<br[\s/]{0,2}>/ig, “\r\n”); html = html.replace(/<[^img].*?>/ig, “”); html = html.replace(/&nbsp;/ig, “”); return html.replace(/\r\n|\n|\r/, “”).replace(/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, “”) == “”;}$(".J__wchatSubmit").on(“click”, function () { // 判断内容是否为空 if (isEmpty()) return; var _html = $editor.html(); var reg = /(http://|https://)((\w|=|?|.|/|&|-)+)/g; _html = _html.replace(reg, “<a href=’$1$2’ target=’_blank’>$1$2</a>”); var msgTpl = [ ‘<li class=“me”>\ <div class=“content”>\ <p class=“author”>风铃子</p>\ <div class=“msg”>’+ _html +’</div>\ </div>\ <a class=“avatar” href=“微钉-好友主页(详细资料).html”><img src=“img/uimg/u__chat-img07.jpg” /></a>\ </li>’ ].join(""); $chatMsgList.append(msgTpl); // 清空聊天框并获取焦点(处理输入法和表情 - 聚焦) if (!$(".wdt__choose-panel").is(":hidden")) { $editor.html(""); } else { $editor.html("").focus(); } wchat_ToBottom();});// >>> 【工具栏选择功能模块】——————————————// …选择图片$("#J__choosePicture").on(“change”, function () { $(".wdt__choose-panel").hide(); var file = this.files[0]; var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function (e) { var _img = this.result; var _tpl = [ ‘<li class=“me”>\ <div class=“content”>\ <p class=“author”>风铃子</p>\ <div class=“msg picture”><img class=“img__pic” src="’+ _img +’" /></div>\ </div>\ <a class=“avatar” href=“微钉-好友主页(详细资料).html”><img src=“img/uimg/u__chat-img07.jpg” /></a>\ </li>’ ].join(""); $chatMsgList.append(_tpl); setTimeout(function(){wchat_ToBottom();}, 17); }});// …选择文件$("#J__chooseFile").on(“change”, function () { $(".wdt__choose-panel").hide(); var file = this.files[0], fileSuffix = /.[^*]+/.exec(file.name).toString(), fileExt = fileSuffix.substr(fileSuffix.lastIndexOf(’.’) + 1, fileSuffix.length).toLowerCase(); console.log(fileSuffix); console.log(fileExt); var fileTypeArr = [‘jpg’, ‘jpeg’, ‘png’, ‘gif’, ’txt’, ‘rar’, ‘zip’, ‘pdf’, ‘docx’, ‘xls’]; if ($.inArray(fileExt, fileTypeArr) < 0) { wcPop({content: ‘附件只支持jpg、jpeg、png、gif、txt、rar、zip、pdf、docx、xls格式的文件’, time: 2}); return; } var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function (e) { var _file = this.result; console.log(_file); var _tpl = [ ‘<li class= “me”>\ <div class=“content”>\ <p class=“author”>风铃子</p>\ <div class=“msg attachment”>\ <div class=“card flexbox flex-alignc”>\ <span class=“ico-bg wdt__bg01”><i class=“iconfont icon-fujian”></i></span>\ <div class=“file-info flex1” title="’+ file.name +’">\ <p class=“name”>’+ file.name +’</p><p class=“size”>’+ formateSize(file.size) +’</p>\ </div>\ <a class=“btn-down” href="’+ _file +’" target="_blank" download="’+ file.name +’"><i class=“iconfont icon-down”></i></a>\ </div>\ </div>\ </div>\ <a class=“avatar” href=“微钉-好友主页(详细资料).html”><img src=“img/uimg/u__chat-img07.jpg” /></a>\ </li>’ ].join(""); $chatMsgList.append(_tpl); setTimeout(function () {wchat_ToBottom();}, 17); } /** 格式化文件大小显示 value : file文件的大小值 */ formateSize = function (value) { if (null == value || value == ‘’) { return “0 Bytes”; } var unitArr = new Array(“B”, “KB”, “MB”, “GB”, “TB”, “PB”, “EB”, “ZB”, “YB”); var index = 0; var srcsize = parseFloat(value); index = Math.floor(Math.log(srcsize) / Math.log(1024)); var size = srcsize / Math.pow(1024, index); size = size.toFixed(2); //保留的小数位数 return size + unitArr[index]; }}); ...

December 19, 2018 · 2 min · jiezi