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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lhtzbj12/article/details/79357336

前言

笔者学完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 = "[email protected]"
)

//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}} &nbsp;&nbsp; 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) }} &nbsp;&nbsp;{{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: "[email protected]"
                    },
                    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>

猜你喜欢

转载自blog.csdn.net/lhtzbj12/article/details/79357336