This commit is contained in:
		
							parent
							
								
									18c2cc2ad9
								
							
						
					
					
						commit
						bbe17c431c
					
				|  | @ -15,16 +15,9 @@ | ||||||
|             "Password": "admin", |             "Password": "admin", | ||||||
|             "Workspace": "{pwd}/data/user_workspaces/admin", |             "Workspace": "{pwd}/data/user_workspaces/admin", | ||||||
|             "LatestSessionContent": { |             "LatestSessionContent": { | ||||||
|                 "FileTree": [ |                 "FileTree": [], | ||||||
|                     "1/", |                 "Files": [], | ||||||
|                     "2/" |                 "CurrentFile": "" | ||||||
|                 ], |  | ||||||
|                 "Files": [ |  | ||||||
|                     "1.go", |  | ||||||
|                     "2.go", |  | ||||||
|                     "3.go" |  | ||||||
|                 ], |  | ||||||
|                 "CurrentFile": "current file" |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								main.go
								
								
								
								
							
							
						
						
									
										3
									
								
								main.go
								
								
								
								
							|  | @ -40,6 +40,9 @@ func init() { | ||||||
| 
 | 
 | ||||||
| 	// 定时保存配置
 | 	// 定时保存配置
 | ||||||
| 	conf.FixedTimeSave() | 	conf.FixedTimeSave() | ||||||
|  | 
 | ||||||
|  | 	// 定时检查无效会话
 | ||||||
|  | 	session.FixedTimeRelease() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 登录.
 | // 登录.
 | ||||||
|  |  | ||||||
|  | @ -63,6 +63,27 @@ var WideSessions Sessions | ||||||
| // 排它锁,防止并发修改.
 | // 排它锁,防止并发修改.
 | ||||||
| var mutex sync.Mutex | 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) { | 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 { | 		if err := wsChan.Conn.ReadJSON(&input); err != nil { | ||||||
| 			glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid) | 			glog.V(3).Infof("[Session Channel] of session [%s] disconnected, releases all resources with it", sid) | ||||||
| 
 | 
 | ||||||
| 			for i, s := range WideSessions { | 			WideSessions.Remove(sid) | ||||||
| 				if s.Id == sid { |  | ||||||
| 					mutex.Lock() |  | ||||||
| 
 | 
 | ||||||
| 					// 从会话集中移除
 | 			return | ||||||
| 					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 |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		ret = map[string]interface{}{"output": "", "cmd": "session-output"} | 		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) | 	defer util.RetJSON(w, r, data) | ||||||
| 
 | 
 | ||||||
| 	args := struct { | 	args := struct { | ||||||
| 		sid string | 		Sid string | ||||||
| 		*conf.LatestSessionContent | 		*conf.LatestSessionContent | ||||||
| 	}{} | 	}{} | ||||||
| 
 | 
 | ||||||
|  | @ -154,7 +144,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	wSession := WideSessions.Get(args.sid) | 	wSession := WideSessions.Get(args.Sid) | ||||||
| 	if nil == wSession { | 	if nil == wSession { | ||||||
| 		data["succ"] = false | 		data["succ"] = false | ||||||
| 
 | 
 | ||||||
|  | @ -165,9 +155,10 @@ func SaveContent(w http.ResponseWriter, r *http.Request) { | ||||||
| 
 | 
 | ||||||
| 	for _, user := range conf.Wide.Users { | 	for _, user := range conf.Wide.Users { | ||||||
| 		if user.Name == wSession.Username { | 		if user.Name == wSession.Username { | ||||||
|  | 			// 更新配置(内存变量),conf.FixedTimeSave() 会负责定时持久化
 | ||||||
| 			user.LatestSessionContent = wSession.Content | 			user.LatestSessionContent = wSession.Content | ||||||
| 
 | 
 | ||||||
| 			// 定时任务会负责持久化
 | 			wSession.Refresh() | ||||||
| 
 | 
 | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | @ -236,9 +227,46 @@ func (sessions *Sessions) Remove(sid string) { | ||||||
| 
 | 
 | ||||||
| 	for i, s := range *sessions { | 	for i, s := range *sessions { | ||||||
| 		if s.Id == sid { | 		if s.Id == sid { | ||||||
|  | 			// 从会话集中移除
 | ||||||
| 			*sessions = append((*sessions)[:i], (*sessions)[i+1:]...) | 			*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 | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| var editors = { | var editors = { | ||||||
|     data: [], |     data: [], | ||||||
|  |     tabs: {}, | ||||||
|     init: function () { |     init: function () { | ||||||
|         editors._initAutocomplete(); |         editors._initAutocomplete(); | ||||||
|         editors.tabs = new Tabs({ |         editors.tabs = new Tabs({ | ||||||
|  |  | ||||||
|  | @ -1,37 +1,58 @@ | ||||||
| // 用于保持会话,如果该通道断开,则服务器端会销毁会话状态,回收相关资源.
 | var session = { | ||||||
| var sessionWS = new WebSocket(config.channel.session + '/session/ws?sid=' + config.wideSessionId); |     init: function () { | ||||||
| sessionWS.onopen = function () { |         this._initWS(); | ||||||
|     console.log('[session onopen] connected'); | 
 | ||||||
|  |         // 定时(30 秒)保存会话内容.
 | ||||||
|  |         setInterval(function () { | ||||||
|  |             var request = newWideRequest(), | ||||||
|  |                     filse = [], | ||||||
|  |                     fileTree = [], | ||||||
|  |                     currentFile = ""; | ||||||
|  | 
 | ||||||
|  |             editors.tabs.obj._$tabs.find("div").each(function () { | ||||||
|  |                 var $it = $(this); | ||||||
|  |                 if ($it.hasClass("current")) { | ||||||
|  |                     currentFile = $it.find("span:eq(0)").attr("title"); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 filse.push($it.find("span:eq(0)").attr("title")); | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             fileTree = tree.getOpenPaths(); | ||||||
|  | 
 | ||||||
|  |             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)); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| 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)); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // 定时(30 秒)保存会话内容.
 |  | ||||||
| setTimeout(function () { |  | ||||||
|     var request = newWideRequest(); |  | ||||||
|      |  | ||||||
|     // TODO: 会话状态保存
 |  | ||||||
|     request.currentFile = "current file"; // 当前编辑器
 |  | ||||||
|     request.fileTree = ["1/", "2/"]; // 文件树展开状态
 |  | ||||||
|     request.files = ["1.go", "2.go", "3.go"]; // 编辑器打开状态
 |  | ||||||
| 
 |  | ||||||
|     $.ajax({ |  | ||||||
|         type: 'POST', |  | ||||||
|         url: '/session/save', |  | ||||||
|         data: JSON.stringify(request), |  | ||||||
|         dataType: "json", |  | ||||||
|         success: function (data) { |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| }, 30000); |  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| var tree = { | var tree = { | ||||||
|     // 递归获取当前节点展开中的最后一个节点
 |     // 递归获取当前节点展开中的最后一个节点
 | ||||||
|     getCurrentNodeLastNode: function(node) { |     getCurrentNodeLastNode: function (node) { | ||||||
|         var returnNode = node.children[node.children.length - 1]; |         var returnNode = node.children[node.children.length - 1]; | ||||||
|         if (returnNode.open) { |         if (returnNode.open) { | ||||||
|             return tree.getCurrentNodeLastNode(returnNode); |             return tree.getCurrentNodeLastNode(returnNode); | ||||||
|  | @ -9,7 +9,7 @@ var tree = { | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     // 按照树展现获取下一个节点
 |     // 按照树展现获取下一个节点
 | ||||||
|     getNextShowNode: function(node) { |     getNextShowNode: function (node) { | ||||||
|         if (node.level !== 0) { |         if (node.level !== 0) { | ||||||
|             if (node.getParentNode().getNextNode()) { |             if (node.getParentNode().getNextNode()) { | ||||||
|                 return node.getParentNode().getNextNode(); |                 return node.getParentNode().getNextNode(); | ||||||
|  | @ -20,7 +20,7 @@ var tree = { | ||||||
|             return node.getNextNode(); |             return node.getNextNode(); | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     isBottomNode: function(node) { |     isBottomNode: function (node) { | ||||||
|         if (node.open) { |         if (node.open) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  | @ -39,7 +39,7 @@ var tree = { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     getTIdByPath: function(path) { |     getTIdByPath: function (path) { | ||||||
|         var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes()); |         var nodes = tree.fileTree.transformToArray(tree.fileTree.getNodes()); | ||||||
|         for (var i = 0, ii = nodes.length; i < ii; i++) { |         for (var i = 0, ii = nodes.length; i < ii; i++) { | ||||||
|             if (nodes[i].path === path) { |             if (nodes[i].path === path) { | ||||||
|  | @ -49,8 +49,19 @@ var tree = { | ||||||
| 
 | 
 | ||||||
|         return undefined; |         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, |     fileTree: undefined, | ||||||
|     _isParents: function(tId, parentTId) { |     _isParents: function (tId, parentTId) { | ||||||
|         var node = tree.fileTree.getNodeByTId(tId); |         var node = tree.fileTree.getNodeByTId(tId); | ||||||
|         if (!node || !node.parentTId) { |         if (!node || !node.parentTId) { | ||||||
|             return false; |             return false; | ||||||
|  | @ -62,20 +73,20 @@ var tree = { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     newFile: function() { |     newFile: function () { | ||||||
|         $("#dirRMenu").hide(); |         $("#dirRMenu").hide(); | ||||||
|         $("#dialogNewFilePrompt").dialog("open"); |         $("#dialogNewFilePrompt").dialog("open"); | ||||||
|     }, |     }, | ||||||
|     newDir: function() { |     newDir: function () { | ||||||
|         $("#dirRMenu").hide(); |         $("#dirRMenu").hide(); | ||||||
|         $("#dialogNewDirPrompt").dialog("open"); |         $("#dialogNewDirPrompt").dialog("open"); | ||||||
|     }, |     }, | ||||||
|     removeIt: function() { |     removeIt: function () { | ||||||
|         $("#dirRMenu").hide(); |         $("#dirRMenu").hide(); | ||||||
|         $("#fileRMenu").hide(); |         $("#fileRMenu").hide(); | ||||||
|         $("#dialogRemoveConfirm").dialog("open"); |         $("#dialogRemoveConfirm").dialog("open"); | ||||||
|     }, |     }, | ||||||
|     init: function() { |     init: function () { | ||||||
|         var request = newWideRequest(); |         var request = newWideRequest(); | ||||||
| 
 | 
 | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
|  | @ -83,7 +94,7 @@ var tree = { | ||||||
|             url: '/files', |             url: '/files', | ||||||
|             data: JSON.stringify(request), |             data: JSON.stringify(request), | ||||||
|             dataType: "json", |             dataType: "json", | ||||||
|             success: function(data) { |             success: function (data) { | ||||||
|                 if (data.succ) { |                 if (data.succ) { | ||||||
|                     var dirRMenu = $("#dirRMenu"); |                     var dirRMenu = $("#dirRMenu"); | ||||||
|                     var fileRMenu = $("#fileRMenu"); |                     var fileRMenu = $("#fileRMenu"); | ||||||
|  | @ -92,7 +103,7 @@ var tree = { | ||||||
|                             selectedMulti: false |                             selectedMulti: false | ||||||
|                         }, |                         }, | ||||||
|                         callback: { |                         callback: { | ||||||
|                             onRightClick: function(event, treeId, treeNode) { |                             onRightClick: function (event, treeId, treeNode) { | ||||||
|                                 if (treeNode) { |                                 if (treeNode) { | ||||||
|                                     wide.curNode = treeNode; |                                     wide.curNode = treeNode; | ||||||
|                                     if ("ico-ztree-dir " !== treeNode.iconSkin) { // 如果右击了文件
 |                                     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); |                                 tree._onClick(treeNode); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  | @ -122,7 +133,7 @@ var tree = { | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     }, |     }, | ||||||
|     _onClick: function(treeNode) { |     _onClick: function (treeNode) { | ||||||
|         if (wide.curNode) { |         if (wide.curNode) { | ||||||
|             for (var i = 0, ii = editors.data.length; i < ii; i++) { |             for (var i = 0, ii = editors.data.length; i < ii; i++) { | ||||||
|                 // 该节点文件已经打开
 |                 // 该节点文件已经打开
 | ||||||
|  | @ -147,7 +158,7 @@ var tree = { | ||||||
|                 url: '/file', |                 url: '/file', | ||||||
|                 data: JSON.stringify(request), |                 data: JSON.stringify(request), | ||||||
|                 dataType: "json", |                 dataType: "json", | ||||||
|                 success: function(data) { |                 success: function (data) { | ||||||
|                     if (!data.succ) { |                     if (!data.succ) { | ||||||
|                         alert(data.msg); |                         alert(data.msg); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -452,4 +452,5 @@ $(document).ready(function() { | ||||||
|     menu.init(); |     menu.init(); | ||||||
|     hotkeys.init(); |     hotkeys.init(); | ||||||
|     notification.init(); |     notification.init(); | ||||||
|  |     session.init(); | ||||||
| }); | }); | ||||||
		Loading…
	
		Reference in New Issue