This commit is contained in:
		
							parent
							
								
									18c2cc2ad9
								
							
						
					
					
						commit
						bbe17c431c
					
				|  | @ -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": "" | ||||
|             } | ||||
|         } | ||||
|     ] | ||||
|  |  | |||
							
								
								
									
										3
									
								
								main.go
								
								
								
								
							
							
						
						
									
										3
									
								
								main.go
								
								
								
								
							|  | @ -40,6 +40,9 @@ func init() { | |||
| 
 | ||||
| 	// 定时保存配置
 | ||||
| 	conf.FixedTimeSave() | ||||
| 
 | ||||
| 	// 定时检查无效会话
 | ||||
| 	session.FixedTimeRelease() | ||||
| } | ||||
| 
 | ||||
| // 登录.
 | ||||
|  |  | |||
|  | @ -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 | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| var editors = { | ||||
|     data: [], | ||||
|     tabs: {}, | ||||
|     init: function () { | ||||
|         editors._initAutocomplete(); | ||||
|         editors.tabs = new Tabs({ | ||||
|  |  | |||
|  | @ -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)); | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -452,4 +452,5 @@ $(document).ready(function() { | |||
|     menu.init(); | ||||
|     hotkeys.init(); | ||||
|     notification.init(); | ||||
|     session.init(); | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue