diff --git a/conf/wide.json b/conf/wide.json index 67329aa..1b0f0d3 100644 --- a/conf/wide.json +++ b/conf/wide.json @@ -15,16 +15,9 @@ "Password": "admin", "Workspace": "{pwd}/data/user_workspaces/admin", "LatestSessionContent": { - "FileTree": [ - "1/", - "2/" - ], - "Files": [ - "1.go", - "2.go", - "3.go" - ], - "CurrentFile": "current file" + "FileTree": [], + "Files": [], + "CurrentFile": "" } } ] diff --git a/main.go b/main.go index 83c2d1a..8217283 100644 --- a/main.go +++ b/main.go @@ -40,6 +40,9 @@ func init() { // 定时保存配置 conf.FixedTimeSave() + + // 定时检查无效会话 + session.FixedTimeRelease() } // 登录. diff --git a/session/sessions.go b/session/sessions.go index 16e8107..0a12d65 100644 --- a/session/sessions.go +++ b/session/sessions.go @@ -63,6 +63,27 @@ var WideSessions Sessions // 排它锁,防止并发修改. var mutex sync.Mutex +// 在一些特殊情况(例如浏览器不间断刷新时会话通道建立并发)下 Wide 会话集内会出现无效会话,该函数定时(1 小时)检查并移除这些无效会话. +// 无效会话:在检查时间内 30 分钟都没有使用过的会话,WideSession.Updated 字段. +func FixedTimeRelease() { + go func() { + for { + hour, _ := time.ParseDuration("-30m") + threshold := time.Now().Add(hour) + + for _, s := range WideSessions { + if s.Updated.Before(threshold) { + glog.V(3).Infof("Removes a invalid session [%s]", s.Id) + + WideSessions.Remove(s.Id) + } + } + + time.Sleep(time.Hour) + } + }() +} + // 建立会话通道. // 通道断开时销毁会话状态,回收相关资源. func WSHandler(w http.ResponseWriter, r *http.Request) { @@ -90,40 +111,9 @@ func WSHandler(w http.ResponseWriter, r *http.Request) { if err := wsChan.Conn.ReadJSON(&input); err != nil { glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid) - for i, s := range WideSessions { - if s.Id == sid { - mutex.Lock() + WideSessions.Remove(sid) - // 从会话集中移除 - WideSessions = append(WideSessions[:i], WideSessions[i+1:]...) - - // 关闭用户事件队列 - event.UserEventQueues.Close(sid) - - // 杀进程 - for _, p := range s.Processes { - if err := p.Kill(); nil != err { - glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid) - } else { - glog.V(3).Infof("Killed a process [%d] of session [%s]", p.Pid, sid) - } - } - - // 回收所有通道 - OutputWS[sid].Close() - delete(OutputWS, sid) - - NotificationWS[sid].Close() - delete(NotificationWS, sid) - - sessionWS[sid].Close() - delete(sessionWS, sid) - - mutex.Unlock() - - return - } - } + return } ret = map[string]interface{}{"output": "", "cmd": "session-output"} @@ -143,7 +133,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) { defer util.RetJSON(w, r, data) args := struct { - sid string + Sid string *conf.LatestSessionContent }{} @@ -154,7 +144,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) { return } - wSession := WideSessions.Get(args.sid) + wSession := WideSessions.Get(args.Sid) if nil == wSession { data["succ"] = false @@ -165,9 +155,10 @@ func SaveContent(w http.ResponseWriter, r *http.Request) { for _, user := range conf.Wide.Users { if user.Name == wSession.Username { + // 更新配置(内存变量),conf.FixedTimeSave() 会负责定时持久化 user.LatestSessionContent = wSession.Content - // 定时任务会负责持久化 + wSession.Refresh() return } @@ -236,9 +227,46 @@ func (sessions *Sessions) Remove(sid string) { for i, s := range *sessions { if s.Id == sid { + // 从会话集中移除 *sessions = append((*sessions)[:i], (*sessions)[i+1:]...) - glog.V(3).Infof("Removed a session [%s], has [%d] wide sessions currently", sid, len(*sessions)) + // 关闭用户事件队列 + event.UserEventQueues.Close(sid) + + // 杀进程 + for _, p := range s.Processes { + if err := p.Kill(); nil != err { + glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid) + } else { + glog.V(3).Infof("Killed a process [%d] of session [%s]", p.Pid, sid) + } + } + + // 回收所有通道 + if ws, ok := OutputWS[sid]; ok { + ws.Close() + delete(OutputWS, sid) + } + + if ws, ok := NotificationWS[sid]; ok { + ws.Close() + delete(NotificationWS, sid) + } + + if ws, ok := sessionWS[sid]; ok { + ws.Close() + delete(sessionWS, sid) + } + + glog.V(3).Infof("Removed a session [%s]", s.Id) + + cnt := 0 // 统计当前 HTTP 会话关联的 Wide 会话数量 + for _, s := range *sessions { + if s.HTTPSession.ID == s.HTTPSession.ID { + cnt++ + } + } + glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt) return } diff --git a/static/js/editor.js b/static/js/editor.js index 3bf9bc1..5abe475 100644 --- a/static/js/editor.js +++ b/static/js/editor.js @@ -1,5 +1,6 @@ var editors = { data: [], + tabs: {}, init: function () { editors._initAutocomplete(); editors.tabs = new Tabs({ diff --git a/static/js/session.js b/static/js/session.js index b66bedc..e0892b2 100644 --- a/static/js/session.js +++ b/static/js/session.js @@ -1,37 +1,58 @@ -// 用于保持会话,如果该通道断开,则服务器端会销毁会话状态,回收相关资源. -var sessionWS = new WebSocket(config.channel.session + '/session/ws?sid=' + config.wideSessionId); -sessionWS.onopen = function () { - console.log('[session onopen] connected'); -}; +var session = { + init: function () { + this._initWS(); -sessionWS.onmessage = function (e) { - console.log('[session onmessage]' + e.data); - var data = JSON.parse(e.data); + // 定时(30 秒)保存会话内容. + setInterval(function () { + var request = newWideRequest(), + filse = [], + fileTree = [], + currentFile = ""; -}; -sessionWS.onclose = function (e) { - console.log('[session onclose] disconnected (' + e.code + ')'); - delete sessionWS; -}; -sessionWS.onerror = function (e) { - console.log('[session onerror] ' + JSON.parse(e)); -}; + editors.tabs.obj._$tabs.find("div").each(function () { + var $it = $(this); + if ($it.hasClass("current")) { + currentFile = $it.find("span:eq(0)").attr("title"); + } -// 定时(30 秒)保存会话内容. -setTimeout(function () { - var request = newWideRequest(); - - // TODO: 会话状态保存 - request.currentFile = "current file"; // 当前编辑器 - request.fileTree = ["1/", "2/"]; // 文件树展开状态 - request.files = ["1.go", "2.go", "3.go"]; // 编辑器打开状态 + filse.push($it.find("span:eq(0)").attr("title")); + }); + + fileTree = tree.getOpenPaths(); - $.ajax({ - type: 'POST', - url: '/session/save', - data: JSON.stringify(request), - dataType: "json", - success: function (data) { - } - }); -}, 30000); + request.currentFile = currentFile; // 当前编辑器 + request.fileTree = fileTree; // 文件树展开状态 + request.files = filse; // 编辑器打开状态 + + $.ajax({ + type: 'POST', + url: '/session/save', + data: JSON.stringify(request), + dataType: "json", + success: function (data) { + } + }); + }, 5000); + }, + _initWS: function () { + // 用于保持会话,如果该通道断开,则服务器端会销毁会话状态,回收相关资源. + var sessionWS = new WebSocket(config.channel.session + '/session/ws?sid=' + config.wideSessionId); + + sessionWS.onopen = function () { + console.log('[session onopen] connected'); + }; + + sessionWS.onmessage = function (e) { + console.log('[session onmessage]' + e.data); + var data = JSON.parse(e.data); + + }; + sessionWS.onclose = function (e) { + console.log('[session onclose] disconnected (' + e.code + ')'); + delete sessionWS; + }; + sessionWS.onerror = function (e) { + console.log('[session onerror] ' + JSON.parse(e)); + }; + } +}; \ No newline at end of file diff --git a/static/js/tree.js b/static/js/tree.js index 9bd0564..66f237a 100644 --- a/static/js/tree.js +++ b/static/js/tree.js @@ -1,6 +1,6 @@ var tree = { // 递归获取当前节点展开中的最后一个节点 - getCurrentNodeLastNode: function(node) { + getCurrentNodeLastNode: function (node) { var returnNode = node.children[node.children.length - 1]; if (returnNode.open) { return tree.getCurrentNodeLastNode(returnNode); @@ -9,7 +9,7 @@ var tree = { } }, // 按照树展现获取下一个节点 - getNextShowNode: function(node) { + getNextShowNode: function (node) { if (node.level !== 0) { if (node.getParentNode().getNextNode()) { return node.getParentNode().getNextNode(); @@ -20,7 +20,7 @@ var tree = { return node.getNextNode(); } }, - isBottomNode: function(node) { + isBottomNode: function (node) { if (node.open) { return false; } @@ -39,7 +39,7 @@ var tree = { } } }, - getTIdByPath: function(path) { + getTIdByPath: function (path) { var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes()); for (var i = 0, ii = nodes.length; i < ii; i++) { if (nodes[i].path === path) { @@ -49,8 +49,19 @@ var tree = { return undefined; }, + getOpenPaths: function () { + var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes()), + paths = []; + for (var i = 0, ii = nodes.length; i < ii; i++) { + if (nodes[i].open) { + paths.push(nodes[i].path); + } + } + + return paths; + }, fileTree: undefined, - _isParents: function(tId, parentTId) { + _isParents: function (tId, parentTId) { var node = tree.fileTree.getNodeByTId(tId); if (!node || !node.parentTId) { return false; @@ -62,20 +73,20 @@ var tree = { } } }, - newFile: function() { + newFile: function () { $("#dirRMenu").hide(); $("#dialogNewFilePrompt").dialog("open"); }, - newDir: function() { + newDir: function () { $("#dirRMenu").hide(); $("#dialogNewDirPrompt").dialog("open"); }, - removeIt: function() { + removeIt: function () { $("#dirRMenu").hide(); $("#fileRMenu").hide(); $("#dialogRemoveConfirm").dialog("open"); }, - init: function() { + init: function () { var request = newWideRequest(); $.ajax({ @@ -83,7 +94,7 @@ var tree = { url: '/files', data: JSON.stringify(request), dataType: "json", - success: function(data) { + success: function (data) { if (data.succ) { var dirRMenu = $("#dirRMenu"); var fileRMenu = $("#fileRMenu"); @@ -92,7 +103,7 @@ var tree = { selectedMulti: false }, callback: { - onRightClick: function(event, treeId, treeNode) { + onRightClick: function (event, treeId, treeNode) { if (treeNode) { wide.curNode = treeNode; if ("ico-ztree-dir " !== treeNode.iconSkin) { // 如果右击了文件 @@ -112,7 +123,7 @@ var tree = { } } }, - onClick: function(event, treeId, treeNode, clickFlag) { + onClick: function (event, treeId, treeNode, clickFlag) { tree._onClick(treeNode); } } @@ -122,7 +133,7 @@ var tree = { } }); }, - _onClick: function(treeNode) { + _onClick: function (treeNode) { if (wide.curNode) { for (var i = 0, ii = editors.data.length; i < ii; i++) { // 该节点文件已经打开 @@ -147,7 +158,7 @@ var tree = { url: '/file', data: JSON.stringify(request), dataType: "json", - success: function(data) { + success: function (data) { if (!data.succ) { alert(data.msg); diff --git a/static/js/wide.js b/static/js/wide.js index 9939d5f..d97a7d1 100644 --- a/static/js/wide.js +++ b/static/js/wide.js @@ -452,4 +452,5 @@ $(document).ready(function() { menu.init(); hotkeys.init(); notification.init(); + session.init(); }); \ No newline at end of file