您的位置:首页 > 产品设计 > UI/UE

Golang代码搜集-基于websocket+vue.js的简易聊天室

2018-02-24 21:23 746 查看

前言

笔者学完vue.js后,总是不断地找个机会练练手,于是,在假期花了点时间使用websocket和vue.js,写了一个简单的聊天室,功能并不强大,只是实现了简单的群聊功能,但是详细地演示了websocket、chan、vue.js的应用,写在这里算是做记录了,指不定哪一天会用上。

预览

提示:邮箱是用户唯一标识





源码

main.go

//main.go
package main

import (
"encoding/json"
"fmt"
"io"
"net/http"
"sort"
"strings"
"time"

"golang.org/x/net/websocket"
)

const (
ServerID = "lhtzbj12@126.com"
)

//LoginReq 登录请求
type LoginReq struct {
Type string   `json:"type"` //请求的类别 login表示登录  msg表消息 users用户列表
Data UserInfo `json:"data"` //用户信息
}

//LoginRespData 登录返回
type LoginRespData struct {
Result int    `json:"result"`
Msg    string `json:"msg"`
}

//MesssageReq 发送消息请求
type MesssageReq struct {
Type string  `json:"type"` //请求的类别 login表示登录  msg表消息 users用户列表
Data Message `json:"data"` //用户发送的信息
}

//TransferData 数据传输结构体
type TransferData struct {
Type string      `json:"type"` //数据类别 login表示登录  msg表消息 users用户列表
Data interface{} `json:"data"` //数据
}

//Message 消息的结构体
type Message struct {
From string `json:"from"` //来自谁 email
To   string `json:"to"`   //发给谁 email 空表示表示所有人
Time string `json:"time"` //消息发出的时间
Cont string `json:"cont"` //消息内容
}

//UserInfo 用户基本信息结构体
type UserInfo struct {
NickName    string            `json:"nickname"` //昵称
Email       string            `json:"email"`    //用户邮箱
Send2Client chan TransferData `json:"-"`        //发给客户端的数据
}

//RoomInfo 房间信息
type RoomInfo struct {
RoomName    string     `json:"roomname"`    //房间名称
OnlineNum   int        `json:"onlinenum"`   //在线人数
OnlineUsers []UserInfo `json:"onlineusers"` //在线用户列表
ServerID    string     `json:"serverid"`    //服务器标识
}

var (
users         = make(map[string]UserInfo)   //用户列表
entering      = make(chan UserInfo)         //用户进入
leaving       = make(chan UserInfo)         //用户离开
transferDatas = make(chan TransferData, 10) //广播消息队列
)

func checkerr(err error) {
if err != nil {
fmt.Println(err)
}
}

//解析数据包里的type
func getType(str string) string {
key := "\"type\":"
index := strings.Index(str, key)
str = str[index+len(key)+1:]
index = strings.Index(str, "\"")
return str[0:index]
}

//任务调度 用户进入、离开、消息分发
func taskSchedule() {
//广播聊天室信息
bcroominfo := make(chan int, 1)
for {
select {
case transData := <-transferDatas:
handleTransData(transData) //有请求需要处理
case u := <-entering:
users[u.Email] = u //有新用户进入
//广播聊天室信息
bcroominfo <- 1
case u := <-leaving:
delete(users, u.Email) //有用户离开
//广播聊天室信息
bcroominfo <- 1
case <-bcroominfo:
var data = TransferData{
Type: "roominfo",
Data: RoomInfo{
RoomName:    "简易测试聊天室",
OnlineNum:   len(users),
OnlineUsers: usersMap2Slice(users),
ServerID:    ServerID,
},
}
transferDatas <- data
}
}
}
func handleTransData(data TransferData) {
//当为msg时,发给部分用户,否则发给所有人
if data.Type == "msg" {
msg := data.Data.(Message)
to := strings.TrimSpace(msg.To)
if to != "" {
//发送给发送者
if u, ok := users[msg.From]; ok {
u.Send2Client <- data
}
//如果接收者不为空,则发送给指定接收者
tos := strings.Split(to, ";")
for _, t := range tos {
//如果是自己的,则跳过
if t == msg.From {
continue
}
if u, ok := users[t]; ok {
//修改to为单个的目标
msg.To = t
data.Data = msg
u.Send2Client <- data
}
}
} else {
//发给所有人
for key := range users {
users[key].Send2Client <- data
}
}
} else {
for _, v := range users {
//发给所有人
v.Send2Client <- data
}
}
}

//Index 聊天室页面
func Index(w http.ResponseWriter, req *http.Request) {
http.ServeFile(w, req, "index.html")
}
func main() {
fmt.Println("服务启动...")
//启用任务调度gorountine
go taskSchedule()
//聊天室页面
http.HandleFunc("/", Index)
//聊天消息WebSocket
http.Handle("/chat", websocket.Handler(chatRoom))
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("监听端口失败")
}
}

// websocket请求处理
func chatRoom(ws *websocket.Conn) {
defer ws.Close()
//新建用户
user := UserInfo{
Send2Client: make(chan TransferData, 10),
}
go send2client(ws, user)
for {
var datastr string
//收到消息
err := websocket.Message.Receive(ws, &datastr)
if err != nil {
//客户端断开连接
if err == io.EOF {
//用户离开
leaving <- user
fmt.Println("client left")
break
}
}
//解析数据包里的type
dtype := getType(datastr)
switch dtype {
case "login": //用户登录
var logReq LoginReq
err = json.Unmarshal([]byte(datastr), &logReq)
if err == nil {
//设置用户信息,并将用户存入列表
user.Email = logReq.Data.Email
user.NickName = logReq.Data.NickName
//有用户进入聊天室
entering <- user
//发送欢迎语
sendMessage(user, ServerID, "欢迎进入聊天室")
///发送登录成功
sendLoginResult(user, 1, "登录成功")
}
case "msg": //收到用户发送消息
var msgReq MesssageReq
err = json.Unmarshal([]byte(datastr), &msgReq)
if err == nil {
msgReq.Data.Time = time.Now().Format("2006-01-02 15:04:05")
//将消息存入全局消息队列
var transData = TransferData{
Type: "msg",
Data: msgReq.Data,
}
transferDatas <- transData
}
}
}
}

//将用户消息chan里数据发送到客户端
func send2client(ws *websocket.Conn, user UserInfo) {
for tdata := range user.Send2Client {
b, _ := json.Marshal(tdata)
websocket.Message.Send(ws, string(b))
}
}
func sendLoginResult(user UserInfo, result int, msg string) {
//返回登录结果
var retunData = TransferData{
Type: "login",
Data: LoginRespData{
Result: result,
Msg:    msg,
},
}
user.Send2Client <- retunData
}

//给用户发送消息
func sendMessage(user UserInfo, Form, Cont string) {
//返回消息
dataout := TransferData{
Type: "msg",
Data: Message{
From: Form,
To:   user.Email,
Time: time.Now().Format("2006-01-02 15:04:05"),
Cont: Cont,
},
}
user.Send2Client <- dataout
}

//将用户列表从map转成[]
func usersMap2Slice(users map[string]UserInfo) []UserInfo {
length := len(users)
keys := make([]string, 0, length)
result := make([]UserInfo, 0, length)
for key := range users {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
result = append(result, users[key])
}
return result
}


index.html

<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>Websocket 与 vue.js测试的程序</title>
<script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
</head>

<body>
<div class="container">
<div class="row" id="chatroom">
<div class="col-md-6 col-sm-offset-3" v-show="showLoginPanel">
<div class="panel panel-info  col-sm-offset-3">
<div class="panel-heading">登录</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">昵称</label>
<div class="col-sm-10">
<input type="input" class="form-control" value="" v-model="curuser.nickname">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">邮箱</label>
<div class="col-sm-10">
<input type="input" class="form-control" value="" v-model="curuser.email">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" @click="loginFun()" class="btn btn-default">登录</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-8" v-show="showChat">
<div class="alert alert-info">
昵称: {{curuser.nickname}}    E-mail: {{curuser.email}}
</div>
<div class="panel panel-primary">
<div class="panel-heading">{{roomname}} 在线 {{onlinenum}} 人</div>
<ul class="list-group" style="height: 400px;overflow: auto;">
<li v-for="msg in messages" class="list-group-item">
<div>{{ convertfromto(msg) }}   {{msg.time}} </div>
<p>{{msg.cont}}</p>
</li>
</ul>
</div>
<div class="panel panel-info">
<div class="panel-heading">发送消息</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">发送给</label>
<div class="col-sm-10">
<input class="form-control" rows="3" v-model="newmessageto" placeholder="为空将发给所有人" readonly>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">内容</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" v-model="newmessage.cont"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" @click="sendFun()" class="btn btn-default">发送</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-info" v-show="showChat">
<div class="panel-heading">用户列表</div>
<table class="table">
<thead>
<tr>
<th></th>
<th>昵称</th>
<th>E-mail</th>
</tr>
</thead>
<tbody>
<tr v-for="user in onlineusers">
<th><input type="checkbox" value="{{user.email}}" v-on:change="siglechange(user.email)" v-bind:checked="singlechecked(user.email)"></th>
<td>{{user.nickname}}</td>
<td>{{user.email}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
let app = undefined
let ws = undefined
appinit()
wsinit()
function appinit() {
app = new Vue({
el: '#chatroom',
data: {
roomname: '简易聊天室',
serverid:'',
onlinenum: 0,
onlineusers: [],
islogin: false,
messages: [],
curuser: {
nickname: "我是谁1",
email: "lht1@126.com"
},
newmessage: {
cont: '',
to: [],
},
},
computed: {
showLoginPanel: function () {
return !this.islogin
},
showChat: function () {
return this.islogin
},
newmessageto: function () {
let to = this.newmessage.to
if (to.length == 0) {
return "所有人"
} else {
return to.join(";")
}
}
},
methods: {
sendFun: function () {
let cont = this.newmessage.cont.trim()
if (cont.length == 0) {
alert("请输入内容")
return
}
//发送消息
let msg = {
type: "msg",
data: {
from: this.curuser.email,
to: this.newmessage.to.join(";"), //多个用分号
cont: cont
}
}
console.log(msg)
ws.send(JSON.stringify(msg));
this.newmessage.cont = ""
},
loginFun: function () {
//登录
let request = {
type: "login",
data: this.curuser
}
ws.send(JSON.stringify(request));
},
singlechecked: function (id) {
return this.newmessage.to.includes(id)
},
siglechange: function (id) {
//有则删除,没有则添加
let i = this.newmessage.to.indexOf(id)
if (i > -1) {
this.newmessage.to.splice(i, 1)
} else {
this.newmessage.to.push(id)
}
console.log(this.newmessage.to)
},
convertfromto: function (msg) {
let from, to
if (msg.from === this.curuser.email) {
from = "我"
} else if (msg.from === this.serverid) {
from = "服务器"
} else {
from = this.getuserinfo(msg.from)
}

if (msg.to.length === 0) {
to = "所有人"
} else {
if (msg.to === this.curuser.email) {
to = "我"
} else {
to = this.getuserinfo(msg.to)
}
}
return `${from} to ${to}`
},
getuserinfo: function (emails) {
let es= emails.split(';')
let length = es.length,names=[],count = 0 //最多显示三个人名
for(let i=0;i<length && count <4;i++){
let u= this.onlineusers.filter((x)=>{
return x.email == es[i]
})
if(u.length>0){
count +=1
names.push(u[0].nickname)
}

}
let ret = names.join(';')
if(count > 3){
ret += '等' + length + '人'
}
return ret
}
}
})
}
function wsinit() {
ws = new WebSocket("ws://localhost:8080/chat")
try {
ws.onopen = function () {
alert("成功连接至服务器")
}
ws.onclose = function () {
if (ws) {
ws.close();
ws = null;
}
alert("连接服务器-关闭1")
}
ws.onmessage = function (ret) {
console.log(ret.data);
handleMessage(ret.data)
}
ws.onerror = function () {
if (ws) {
ws.close()
ws = null
}
alert("连接服务器-关闭2")
}
} catch (e) {
alert(e.message)
}
}
//从服务器获取消息进行处理
function handleMessage(d) {
data = JSON.parse(d)
if (data.type === "msg") {
//消息
app.messages.push(data.data)
} else if (data.type === "roominfo") {
//房间信息
app.roomname = data.data.roomname
app.onlinenum = data.data.onlinenum
app.onlineusers = data.data.onlineusers
app.serverid = data.data.serverid
} else if (data.type == "login") {
//登录结果
if (data.data.result === 1) {
app.islogin = true
alert("登录成功")
} else {
alert("登录失败")
}
}
}
</script>
</body>

</html>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息