A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 不二晨 金牌黑马   /  2018-12-18 16:54  /  1856 人查看  /  2 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

项目在github上面的地址
是beego提供的一个应用小项目,只是对本人第一次剖析go语言的一个小记录,也算自己剖析代码的思路记录,希望对他人也能有所帮助
(1)阅读readme文档,知晓项目大致内容,项目用到的各种配置文件
(2)查看main包文件WebIM.go

import (
    "github.com/astaxie/beego"
    "github.com/beego/i18n"

    "samples/WebIM/controllers"
)

const (
    APP_VER = "0.1.1.0227"
)

func main() {
    beego.Info(beego.BConfig.AppName, APP_VER)
    // Register routers.
    beego.Router("/", &controllers.AppController{})
    // Indicate AppController.Join method to handle POST requests.
    beego.Router("/join", &controllers.AppController{}, "post:Join")

    // Long polling.
    beego.Router("/lp", &controllers.LongPollingController{}, "get:Join")
    beego.Router("/lp/post", &controllers.LongPollingController{})
    beego.Router("/lp/fetch", &controllers.LongPollingController{}, "get:Fetch")

    // WebSocket.
    beego.Router("/ws", &controllers.WebSocketController{})
    beego.Router("/ws/join", &controllers.WebSocketController{}, "get:Join")

    // Register template functions.
    beego.AddFuncMap("i18n", i18n.Tr)

    beego.Run()
}

main文件包含了三个引用包,一般main文件里的函数都被封装的很好,但是先看main文件,就可以知晓程序运行顺序,再一点一点的分析每个函数的作用
(2)
beego.Router()函数:注册路由,Router 函数的两个参数函数,第一个是路径,第二个是 Controller 的指针。路由就是告诉 beego,当用户来请求的时候,该如何去调用相应的 Controller。
PS:由于我主要是想搞明白聊天室的工作原理,所以对于beego工具的运作方法暂且不细细去看,重点是controllers
(3)
app.go
先看开发文档了解功能:供用户选择技术和用户名的欢迎页面

package controllers

import (
    "strings"
    "github.com/astaxie/beego"
    "github.com/beego/i18n"
)

var langTypes []string //支持的语言,从配置文件获取

func init() {
    // Initialize language type list.
    langTypes = strings.Split(beego.AppConfig.String("lang_types"), "|")

    // Load locale files according to language types.
    for _, lang := range langTypes {
        beego.Trace("Loading language: " + lang)
        if err := i18n.SetMessage(lang, "conf/"+"locale_"+lang+".ini"); err != nil {
            beego.Error("Fail to set message file:", err)
            return
        }
    }
}

// baseController represents base router for all other app routers.
//基本控制器为所有其他的app路由提供了基本
// It implemented some methods for the same implementation;
// thus, it will be embedded into other routers.
type baseController struct {
    beego.Controller // Embed struct that has stub implementation of the interface.
    i18n.Locale      // For i18n usage when process data and render template.
}

// Prepare implemented Prepare() method for baseController.
// It's used for language option check and setting.
func (this *baseController) Prepare() {
    // Reset language option.
    this.Lang = "" // This field is from i18n.Locale.

    // 1. Get language information from 'Accept-Language'.
    al := this.Ctx.Request.Header.Get("Accept-Language")
    if len(al) > 4 {
        al = al[:5] // Only compare first 5 letters.
        if i18n.IsExist(al) {
            this.Lang = al
        }
    }

    // 2. Default language is English.
    if len(this.Lang) == 0 {
        this.Lang = "en-US"
    }

    // Set template level language option.
    this.Data["Lang"] = this.Lang
}

// AppController handles the welcome screen that allows user to pick a technology and username.
type AppController struct {
    baseController // Embed to use methods that are implemented in baseController.
}

// Get implemented Get() method for AppController.
func (this *AppController) Get() {
    this.TplName = "welcome.html"
}


//用于解决post请求
func (this *AppController) Join() {
    // Get form value.
    uname := this.GetString("uname")
    tech := this.GetString("tech")

    // Check valid.
    if len(uname) == 0 {
        this.Redirect("/", 302)
        return
    }

    switch tech {
    case "longpolling":
        this.Redirect("/lp?uname="+uname, 302)
    case "websocket":
        this.Redirect("/ws?uname="+uname, 302)
    default:
        this.Redirect("/", 302)
    }
    // Usually put return after redirect.
    return
}

chatroom.go:数据管理相关的函数

package controllers

import (
    "container/list"
    "time"

    "github.com/astaxie/beego"
    "github.com/gorilla/websocket"

    "samples/WebIM/models"
)

type Subscription struct {
    Archive []models.Event      // All the events from the archive.
    New     <-chan models.Event // New events coming in.
}

func newEvent(ep models.EventType, user, msg string) models.Event {
    return models.Event{ep, user, int(time.Now().Unix()), msg}
}

func Join(user string, ws *websocket.Conn) {
    subscribe <- Subscriber{Name: user, Conn: ws}
    //上线,记录用户名和连接
}

func Leave(user string) {
    unsubscribe <- user
    //下线
}

type Subscriber struct {
    Name string
    Conn *websocket.Conn // Only for WebSocket users; otherwise nil.
}

var (
    // Channel for new join users.
    subscribe = make(chan Subscriber, 10)
    // Channel for exit users.
    unsubscribe = make(chan string, 10)
    // Send events here to publish them.
    publish = make(chan models.Event, 10)
    // Long polling waiting list.
    waitingList = list.New()
    subscribers = list.New()
)

// This function handles all incoming chan messages.
func chatroom() {
    for {
        select {
        case sub := <-subscribe:
            if !isUserExist(subscribers, sub.Name) {
                subscribers.PushBack(sub) // Add user to the end of list.
                // Publish a JOIN event.
                publish <- newEvent(models.EVENT_JOIN, sub.Name, "")
                beego.Info("New user:", sub.Name, ";WebSocket:", sub.Conn != nil)
            } else {
                beego.Info("Old user:", sub.Name, ";WebSocket:", sub.Conn != nil)
            }
        case event := <-publish:
            // Notify waiting list.
            for ch := waitingList.Back(); ch != nil; ch = ch.Prev() {
                ch.Value.(chan bool) <- true
                waitingList.Remove(ch)
            }

            broadcastWebSocket(event)
            models.NewArchive(event)

            if event.Type == models.EVENT_MESSAGE {
                beego.Info("Message from", event.User, ";Content:", event.Content)
            }
        case unsub := <-unsubscribe:
            for sub := subscribers.Front(); sub != nil; sub = sub.Next() {
                if sub.Value.(Subscriber).Name == unsub {
                    subscribers.Remove(sub)
                    // Clone connection.
                    ws := sub.Value.(Subscriber).Conn
                    if ws != nil {
                        ws.Close()
                        beego.Error("WebSocket closed:", unsub)
                    }
                    publish <- newEvent(models.EVENT_LEAVE, unsub, "") // Publish a LEAVE event.
                    break
                }
            }
        }
    }
}

func init() {
    go chatroom()
}

func isUserExist(subscribers *list.List, user string) bool {
    for sub := subscribers.Front(); sub != nil; sub = sub.Next() {
        if sub.Value.(Subscriber).Name == user {
            return true
        }
    }
    return false
}

longpolling.go:长轮询模式的控制器和方法

package controllers

import (
    "samples/WebIM/models"
)

// LongPollingController handles long polling requests.
type LongPollingController struct {
    baseController
}

// Join method handles GET requests for LongPollingController.
func (this *LongPollingController) Join() {
    // Safe check.
    uname := this.GetString("uname")
    if len(uname) == 0 {
        this.Redirect("/", 302)
        return
    }

    // Join chat room.
    Join(uname, nil)

    this.TplName = "longpolling.html"
    this.Data["IsLongPolling"] = true
    this.Data["UserName"] = uname
}

// Post method handles receive messages requests for LongPollingController.
func (this *LongPollingController) Post() {
    this.TplName = "longpolling.html"

    uname := this.GetString("uname")
    content := this.GetString("content")
    if len(uname) == 0 || len(content) == 0 {
        return
    }

    publish <- newEvent(models.EVENT_MESSAGE, uname, content)
}

// Fetch method handles fetch archives requests for LongPollingController.
func (this *LongPollingController) Fetch() {
    lastReceived, err := this.GetInt("lastReceived")
    if err != nil {
        return
    }

    events := models.GetEvents(int(lastReceived))
    if len(events) > 0 {
        this.Data["json"] = events
        this.ServeJSON()
        return
    }

    // Wait for new message(s).
    ch := make(chan bool)
    waitingList.PushBack(ch)
    <-ch

    this.Data["json"] = models.GetEvents(int(lastReceived))
    this.ServeJSON()
}

websocket.go:数据管理相关的函数

package controllers

import (
    "encoding/json"
    "net/http"

    "github.com/astaxie/beego"
    "github.com/gorilla/websocket"

    "samples/WebIM/models"
)

// WebSocketController handles WebSocket requests.
type WebSocketController struct {
    baseController
}

// Get method handles GET requests for WebSocketController.
func (this *WebSocketController) Get() {
    // Safe check.
    uname := this.GetString("uname")
    if len(uname) == 0 {
        this.Redirect("/", 302)
        return
    }

    this.TplName = "websocket.html"
    this.Data["IsWebSocket"] = true
    this.Data["UserName"] = uname
}

// Join method handles WebSocket requests for WebSocketController.
func (this *WebSocketController) Join() {
    uname := this.GetString("uname")
    if len(uname) == 0 {
        this.Redirect("/", 302)
        return
    }

    // Upgrade from http request to WebSocket.
    ws, err := websocket.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request, nil, 1024, 1024)
    if _, ok := err.(websocket.HandshakeError); ok {
        http.Error(this.Ctx.ResponseWriter, "Not a websocket handshake", 400)
        return
    } else if err != nil {
        beego.Error("Cannot setup WebSocket connection:", err)
        return
    }

    // Join chat room.
    Join(uname, ws)
    defer Leave(uname)

    // Message receive loop.
    for {
        _, p, err := ws.ReadMessage()
        if err != nil {
            return
        }
        publish <- newEvent(models.EVENT_MESSAGE, uname, string(p))
    }
}

// broadcastWebSocket broadcasts messages to WebSocket users.
func broadcastWebSocket(event models.Event) {
    data, err := json.Marshal(event)
    if err != nil {
        beego.Error("Fail to marshal event:", err)
        return
    }

    for sub := subscribers.Front(); sub != nil; sub = sub.Next() {
        // Immediately send event to WebSocket users.
        ws := sub.Value.(Subscriber).Conn
        if ws != nil {
            if ws.WriteMessage(websocket.TextMessage, data) != nil {
                // User disconnected.
                unsubscribe <- sub.Value.(Subscriber).Name
            }
        }
    }
}

archive.go:操作数据相关的函数

package models

import (
    "container/list"
)

type EventType int

const (
    EVENT_JOIN = iota
    EVENT_LEAVE
    EVENT_MESSAGE
)

type Event struct {
    Type      EventType // JOIN, LEAVE, MESSAGE
    User      string
    Timestamp int // Unix timestamp (secs)
    Content   string
}

const archiveSize = 20

// Event archives.
var archive = list.New()

// NewArchive saves new event to archive list.
func NewArchive(event Event) {
    if archive.Len() >= archiveSize {
        archive.Remove(archive.Front())
    }
    archive.PushBack(event)
}

// GetEvents returns all events after lastReceived.
func GetEvents(lastReceived int) []Event {
    events := make([]Event, 0, archive.Len())
    for event := archive.Front(); event != nil; event = event.Next() {
        e := event.Value.(Event)
        if e.Timestamp > int(lastReceived) {
            events = append(events, e)
        }
    }
    return events
}

看完代码的感觉是好的代码真的是太好了,就完全不需要我去注释了…很简单明了,这也算是熟悉一下简单的项目了
---------------------
作者:hello_bravo_
来源:CSDN
原文:https://blog.csdn.net/hello_bravo_/article/details/74736151
版权声明:本文为博主原创文章,转载请附上博文链接!

2 个回复

倒序浏览
奈斯
回复 使用道具 举报
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马