github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/modules/jsock/jsock.go (about)

     1  package jsock
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net/url"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/insionng/yougam/helper"
    12  	"github.com/insionng/yougam/libraries/igm/sockjs-go.v2/sockjs"
    13  	"github.com/insionng/yougam/models"
    14  	"github.com/insionng/yougam/modules/setting"
    15  	"github.com/insionng/yougam/routers"
    16  
    17  	"github.com/insionng/makross"
    18  )
    19  
    20  //Client 客户端
    21  type Client struct {
    22  	session  sockjs.Session
    23  	clientIP string
    24  	userid   int64
    25  }
    26  
    27  //Box 盒子
    28  type Box struct {
    29  	sync.RWMutex
    30  	key string
    31  	box []*Client
    32  }
    33  
    34  //Boxes 盒子集合
    35  type Boxes struct {
    36  	sync.RWMutex
    37  	boxes []*Box
    38  }
    39  
    40  var boxes = newBoxes()
    41  
    42  func (b *Box) appendClient(client *Client) {
    43  	b.Lock()
    44  	defer b.Unlock()
    45  	b.box = append(b.box, client)
    46  	/*
    47  		for _, c := range b.box {
    48  			if c != client {
    49  				log.Println(client.clientIP, "进入聊天~")
    50  			}
    51  		}
    52  	*/
    53  
    54  }
    55  
    56  func (b *Box) removeClient(client *Client) {
    57  	b.Lock()
    58  	defer b.Unlock()
    59  
    60  	for index, c := range b.box {
    61  		if c == client {
    62  			b.box = append(b.box[:index], b.box[(index+1):]...)
    63  			if err := c.session.Send(`<div class="alert alert-warning alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button><h4 class="alert-heading">警告</h4>` + `你的账号在别处连接,当前通信已经失效,如非本人操作请立即修改账号密码..` + `</div>`); err != nil {
    64  				return
    65  			}
    66  		} else {
    67  			if err := client.session.Send(`<div class="alert alert-warning alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button><h4 class="alert-heading">警告</h4>` + `你的账号已经重新连接,原连接已经失效,系统禁止同一账号在多客户端同时连接..` + `</div>`); err != nil {
    68  				return
    69  			}
    70  		}
    71  	}
    72  }
    73  
    74  func (b *Box) removeUser(client *Client) {
    75  	b.Lock()
    76  	defer b.Unlock()
    77  
    78  	for index, c := range b.box {
    79  		if c.userid == client.userid {
    80  			b.box = append(b.box[:index], b.box[(index+1):]...)
    81  			if err := c.session.Send(`<div class="alert alert-warning alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button><h4 class="alert-heading">警告</h4>` + `你的账号在别处连接,当前通信已经失效,如非本人操作请立即修改账号密码..` + `</div>`); err != nil {
    82  				return
    83  			}
    84  		} else {
    85  			if err := client.session.Send(`<div class="alert alert-warning alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button><h4 class="alert-heading">警告</h4>` + `你的账号已经重新连接,原连接已经失效,系统禁止同一账号在多客户端同时连接..` + `</div>`); err != nil {
    86  				return
    87  			}
    88  		}
    89  	}
    90  }
    91  
    92  func tpl(align, username, avatar, datetime, message string) string {
    93  
    94  	var bg = "b-light"
    95  	if align == "right" {
    96  		bg = "bg-light"
    97  	}
    98  
    99  	if len(avatar) == 0 {
   100  		avatar = "/identicon/" + username + "/48/default.png"
   101  	}
   102  
   103  	var tpl = `
   104                  <article class="chat-item ` + align + `">
   105                      <a href="/user/` + username + `/" class="pull-` + align + ` thumb-sm avatar">
   106                      	<img src="` + avatar + `" alt="` + username + `"/>
   107                      	<div class="text-ellipsis text-center">` + username + `</div>
   108                      </a>
   109                      <section class="chat-body">
   110                          <div class="panel ` + bg + ` text-sm m-b-none">
   111                              <div class="panel-body">
   112                                  <span class="arrow ` + align + `"></span>
   113                                  <div class="m-b-none">` + message + `</div>
   114                                  <div class="clear"></div>
   115                              </div>
   116                          </div>
   117                          <small class="text-muted"> <i class="fa fa-ok text-success"></i>
   118                              ` + datetime + `
   119                          </small>
   120                      </section>
   121                  </article>
   122  `
   123  	return tpl
   124  }
   125  
   126  func (b *Box) broadcastMessage(currentUserid int64, username, avatar, datetime, message string) {
   127  	b.Lock()
   128  	defer b.Unlock()
   129  
   130  	var align string
   131  	for _, client := range b.box {
   132  
   133  		if client.userid == currentUserid {
   134  			align = "right"
   135  		} else {
   136  			align = "left"
   137  		}
   138  
   139  		if err := client.session.Send(tpl(align, username, avatar, datetime, message)); err != nil {
   140  			return
   141  		}
   142  	}
   143  }
   144  
   145  func (b *Box) broadcastMessageTo(isMyself bool, currentUserid int64, username, avatar, datetime, message string) {
   146  	b.Lock()
   147  	defer b.Unlock()
   148  
   149  	var align string
   150  	for _, client := range b.box {
   151  
   152  		if client.userid == currentUserid {
   153  			align = "right"
   154  		} else {
   155  			align = "left"
   156  		}
   157  
   158  		if isMyself {
   159  			if client.userid == currentUserid {
   160  				if err := client.session.Send(tpl(align, username, avatar, datetime, message)); err != nil {
   161  					return
   162  				}
   163  			}
   164  		} else {
   165  			if client.userid != currentUserid {
   166  				if err := client.session.Send(tpl(align, username, avatar, datetime, message)); err != nil {
   167  					return
   168  				}
   169  			}
   170  		}
   171  
   172  	}
   173  }
   174  
   175  func (b *Boxes) getBox(key string) *Box {
   176  	b.Lock()
   177  	defer b.Unlock()
   178  
   179  	for _, Box := range b.boxes {
   180  		if Box.key == key {
   181  			return Box
   182  		}
   183  	}
   184  
   185  	box := &Box{sync.RWMutex{}, key, make([]*Client, 0)}
   186  	b.boxes = append(b.boxes, box)
   187  	return box
   188  }
   189  
   190  func newBoxes() *Boxes {
   191  	return &Boxes{sync.RWMutex{}, make([]*Box, 0)}
   192  }
   193  
   194  func sockHandler(session sockjs.Session) {
   195  	l, err := url.Parse(session.Request().RequestURI)
   196  	if err != nil {
   197  		session.Close(1008, "Bad Request")
   198  		return
   199  	}
   200  
   201  	sender := l.Query()["sender"][0]
   202  	receiver := l.Query()["receiver"][0]
   203  	token := l.Query()["token"][0]
   204  
   205  	if (sender == receiver) || (len(sender) == 0) || (len(receiver) == 0) || (len(token) != 48) {
   206  		session.Close(1008, "Bad Request")
   207  		return
   208  	}
   209  
   210  	if len(receiver) > 0 {
   211  		recipient, err := models.GetUserByUsername(receiver)
   212  		if (err != nil) || (recipient == nil) {
   213  			log.Println(err)
   214  			session.Close(1008, "Bad Request")
   215  			return
   216  		}
   217  
   218  		if key := sender + ":" + helper.AesKey + ":" + receiver; !helper.ValidateHash(token, key) {
   219  			session.Close(1008, "Bad Request")
   220  			return
   221  		}
   222  
   223  		var me *models.User
   224  		item := setting.Cache.Get(token)
   225  		if item == nil {
   226  			session.Close(1008, "Bad Request")
   227  			return
   228  		}
   229  
   230  		if user, okay := item.Value().(*models.User); okay {
   231  
   232  			usr, e := models.GetUserByUsername(sender)
   233  			if (e != nil) || (usr == nil) {
   234  				session.Close(1011, "Unauthorized")
   235  				return
   236  			}
   237  
   238  			if (usr.Password != user.Password) || (usr.Username != user.Username) {
   239  				session.Close(1008, "Bad Request")
   240  				return
   241  			}
   242  
   243  			if !models.IsFriend(usr.Id, recipient.Id) {
   244  				session.Close(1008, "Bad Request")
   245  				return
   246  			}
   247  
   248  			setting.Cache.Delete(token) //用完即弃
   249  			me = usr
   250  
   251  		} else {
   252  			session.Close(1008, "Bad Request")
   253  			return
   254  		}
   255  
   256  		rAddr := session.Request().RemoteAddr
   257  		sockCli := &Client{session, rAddr, me.Id}
   258  		orderKey := helper.OrderKey(me.Id, recipient.Id)
   259  		box := boxes.getBox(orderKey)
   260  
   261  		if len(box.box) < 2 {
   262  			box.appendClient(sockCli)
   263  		} else {
   264  			uid := fmt.Sprintf("%v", me.Id)
   265  			//如果是原来的用户则更新连接地址,即禁止用户同时登录2个以上客户端
   266  			if s := strings.Split(box.key, ":"); (s[0] == uid) || (s[1] == uid) {
   267  				box.removeUser(sockCli)
   268  				box.appendClient(sockCli)
   269  			} else {
   270  				session.Close(1008, "Bad Request")
   271  				return
   272  			}
   273  
   274  		}
   275  
   276  		//读取并发送离线消息
   277  		messages, e := models.GetMessagesViaReceiverWithSender(0, 0, me.Username, receiver, "asc")
   278  		if (e == nil) && (messages != nil) {
   279  			for _, v := range *messages {
   280  				models.DelMessage(v.Id) //阅后即焚
   281  				box.broadcastMessageTo(false, v.Uid, v.Sender, v.Avatar, helper.TimeSince(v.Created), v.Content)
   282  			}
   283  		}
   284  
   285  		for {
   286  
   287  			//若果双方好友关系已经解除
   288  			if !models.IsFriend(me.Id, recipient.Id) {
   289  				session.Close(1008, "Bad Request")
   290  				return
   291  			}
   292  
   293  			message, err := session.Recv()
   294  			if err != nil {
   295  				box.removeClient(sockCli)
   296  				session.Close(1000, "Closed Request")
   297  				return
   298  			}
   299  
   300  			policy := helper.ObjPolicy()
   301  			body := policy.Sanitize(message)
   302  			now := time.Now()
   303  			box.broadcastMessage(me.Id, me.Username, me.AvatarMedium, now.Format("2006-01-02 03:04"), body)
   304  			go func() {
   305  				//若果对方没有连线
   306  				if box := boxes.getBox(orderKey); len(box.box) == 1 {
   307  					m := new(models.Message)
   308  					m.Key = orderKey
   309  					m.Uid = me.Id
   310  					m.Sender = me.Username
   311  					m.Avatar = me.AvatarMedium
   312  					m.Receiver = receiver
   313  					m.Content = body
   314  					m.Created = now.Unix()
   315  					models.PostMessage(m)
   316  				}
   317  
   318  				//always save the history message
   319  				m := new(models.HistoryMessage)
   320  				m.Key = orderKey
   321  				m.Uid = me.Id
   322  				m.Sender = me.Username
   323  				m.Avatar = me.AvatarMedium
   324  				m.Receiver = receiver
   325  				m.Content = body
   326  				m.Created = now.Unix()
   327  				models.PostHistoryMessage(m)
   328  			}()
   329  
   330  		}
   331  	} else {
   332  		session.Close(1008, "Bad Request")
   333  		return
   334  	}
   335  
   336  }
   337  
   338  //JSockHandler 对象
   339  var JSockHandler = sockjs.NewHandler("/sock", sockjs.Options{
   340  	Websocket:       true,
   341  	JSessionID:      nil,
   342  	SockJSURL:       fmt.Sprintf("http://%s/%s", helper.Domain, "/libs/sockjs-client-1.1.0/sockjs.min.js"),
   343  	HeartbeatDelay:  25 * time.Second,
   344  	DisconnectDelay: 5 * time.Second,
   345  	ResponseLimit:   128 * 1024,
   346  }, sockHandler)
   347  
   348  //JSock 路由
   349  func JSock(m *makross.Makross) {
   350  	m.Get("/contact/", routers.GetContactHandler)
   351  	m.Get("/contact/search/", routers.GetContactHandler)
   352  	m.Get("/connect/<name:([\\x{4e00}-\\x{9fa5}A-Z0-9a-z_-]+)>/", routers.GetConnectHandler)
   353  }