├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── forward-agent ├── AgentClient.go ├── AgentService.go ├── AgentServiceV1.go ├── build_win.bat ├── conf │ └── app.conf ├── go.mod ├── go.sum └── main.go ├── forward-core ├── Common │ └── KcpPipe.go ├── Constant │ ├── MagicCmd.go │ └── RunStatus.go ├── Models │ ├── ForwardConfig.go │ ├── ForwardInfo.go │ ├── FuncResult.go │ ├── LoginUser.go │ ├── PageData.go │ ├── PageParam.go │ ├── PortForward.go │ ├── PortInfo.go │ └── SysUser.go ├── NetUtils │ ├── BytesUtil.go │ ├── HttpUtil.go │ └── NetTool.go ├── Utils │ ├── CollectionUtil.go │ ├── CryptoUtil.go │ ├── DateUtil.go │ ├── JsonUtil.go │ ├── MathUtil.go │ ├── StringUtil.go │ └── SysUtil.go ├── go.mod ├── go.sum └── ikcp │ ├── ikcp.go │ ├── ikcp_h.go │ ├── ikcp_test.go │ └── ikcp_test_h.go ├── forward-server ├── Controllers │ ├── BaseCtrl │ │ ├── ApiCtrl.go │ │ ├── ConsoleCtrl.go │ │ └── WebCtrl.go │ ├── DefaultCtrl.go │ ├── ForwardCtrl.go │ ├── LoginCtrl.go │ ├── RestApiCtrl.go │ └── UCenterCtrl.go ├── Service │ ├── ConsoleServer.go │ ├── ForWardJob.go │ ├── ForwardClient.go │ ├── ForwardServer.go │ ├── InitServices.go │ ├── MagicClient.go │ ├── MagicServer.go │ ├── MagicServiceV1.go │ ├── SysDataService.go │ └── UdpForward.go ├── build_linux.bat ├── build_linux.sh ├── build_mac.bat ├── build_win.bat ├── conf │ ├── app.conf │ └── data.conf ├── data │ └── data.db ├── go.mod ├── go.sum ├── main.go ├── routers │ ├── commentsRouter_.go │ ├── commentsRouter_Controllers.go │ └── router.go ├── static │ ├── js │ │ ├── jquery.min.js │ │ ├── moment.min.js │ │ └── timer.min.js │ └── layui │ │ ├── css │ │ ├── layui.css │ │ ├── layui.mobile.css │ │ └── modules │ │ │ ├── code.css │ │ │ ├── laydate │ │ │ ├── default │ │ │ │ └── laydate.css │ │ │ ├── icon.png │ │ │ └── laydate.css │ │ │ └── layer │ │ │ └── default │ │ │ ├── icon-ext.png │ │ │ ├── icon.png │ │ │ ├── layer.css │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ │ ├── images │ │ └── face │ │ │ ├── 0.gif │ │ │ ├── 1.gif │ │ │ ├── 10.gif │ │ │ ├── 11.gif │ │ │ ├── 12.gif │ │ │ ├── 13.gif │ │ │ ├── 14.gif │ │ │ ├── 15.gif │ │ │ ├── 16.gif │ │ │ ├── 17.gif │ │ │ ├── 18.gif │ │ │ ├── 19.gif │ │ │ ├── 2.gif │ │ │ ├── 20.gif │ │ │ ├── 21.gif │ │ │ ├── 22.gif │ │ │ ├── 23.gif │ │ │ ├── 24.gif │ │ │ ├── 25.gif │ │ │ ├── 26.gif │ │ │ ├── 27.gif │ │ │ ├── 28.gif │ │ │ ├── 29.gif │ │ │ ├── 3.gif │ │ │ ├── 30.gif │ │ │ ├── 31.gif │ │ │ ├── 32.gif │ │ │ ├── 33.gif │ │ │ ├── 34.gif │ │ │ ├── 35.gif │ │ │ ├── 36.gif │ │ │ ├── 37.gif │ │ │ ├── 38.gif │ │ │ ├── 39.gif │ │ │ ├── 4.gif │ │ │ ├── 40.gif │ │ │ ├── 41.gif │ │ │ ├── 42.gif │ │ │ ├── 43.gif │ │ │ ├── 44.gif │ │ │ ├── 45.gif │ │ │ ├── 46.gif │ │ │ ├── 47.gif │ │ │ ├── 48.gif │ │ │ ├── 49.gif │ │ │ ├── 5.gif │ │ │ ├── 50.gif │ │ │ ├── 51.gif │ │ │ ├── 52.gif │ │ │ ├── 53.gif │ │ │ ├── 54.gif │ │ │ ├── 55.gif │ │ │ ├── 56.gif │ │ │ ├── 57.gif │ │ │ ├── 58.gif │ │ │ ├── 59.gif │ │ │ ├── 6.gif │ │ │ ├── 60.gif │ │ │ ├── 61.gif │ │ │ ├── 62.gif │ │ │ ├── 63.gif │ │ │ ├── 64.gif │ │ │ ├── 65.gif │ │ │ ├── 66.gif │ │ │ ├── 67.gif │ │ │ ├── 68.gif │ │ │ ├── 69.gif │ │ │ ├── 7.gif │ │ │ ├── 70.gif │ │ │ ├── 71.gif │ │ │ ├── 8.gif │ │ │ └── 9.gif │ │ ├── lay │ │ ├── dest │ │ │ └── layui.all.js │ │ └── modules │ │ │ ├── carousel.js │ │ │ ├── code.js │ │ │ ├── colorpicker.js │ │ │ ├── element.js │ │ │ ├── flow.js │ │ │ ├── form.js │ │ │ ├── jquery.js │ │ │ ├── laydate.js │ │ │ ├── layedit.js │ │ │ ├── layer.js │ │ │ ├── laypage.js │ │ │ ├── laytpl.js │ │ │ ├── mobile.js │ │ │ ├── rate.js │ │ │ ├── slider.js │ │ │ ├── table.js │ │ │ ├── transfer.js │ │ │ ├── tree.js │ │ │ ├── upload.js │ │ │ └── util.js │ │ ├── layui.all.js │ │ └── layui.js └── views │ ├── index.html │ ├── login.html │ └── ucenter │ ├── addBatchForward.html │ ├── apiDoc.html │ ├── changePwd.html │ ├── footer.html │ ├── forwardForm.html │ ├── forwardList.1.html │ ├── forwardList.html │ ├── header.html │ ├── importForward.html │ ├── index.html │ ├── layout.html │ ├── main.html │ └── netAgent.html ├── go.mod ├── go.sum └── screenshot ├── ApiDoc.png ├── List.png ├── Login.png ├── Tras_1.png ├── Tras_2.png └── edit.png /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | 4 | .idea 5 | .temp 6 | 7 | *.log 8 | *.iml 9 | *.sum 10 | 11 | /*.sum 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "forward-server", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}/forward-server", 13 | "env": {}, 14 | "args": [] 15 | }, 16 | { 17 | "name": "forward-agent", 18 | "type": "go", 19 | "request": "launch", 20 | "mode": "auto", 21 | "program": "${workspaceFolder}/forward-agent", 22 | "env": {}, 23 | "args": [] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.formatTool": "goimports", 3 | "go.alternateTools": { 4 | 5 | }, 6 | "[go]": { 7 | "editor.formatOnSave": true, 8 | "editor.codeActionsOnSave": { 9 | "source.organizeImports": true, 10 | }, 11 | // Optional: Disable snippets, as they conflict with completion ranking. 12 | "editor.snippetSuggestions": "none", 13 | }, 14 | "[go.mod]": { 15 | "editor.formatOnSave": true, 16 | "editor.codeActionsOnSave": { 17 | "source.organizeImports": true, 18 | }, 19 | }, 20 | "go.gotoSymbol.includeImports": true, 21 | "go.gotoSymbol.includeGoroot": true, 22 | "go.languageServerExperimentalFeatures": { 23 | 24 | "diagnostics": true, 25 | "documentLink": true 26 | }, 27 | "go.trace.server": "verbose", 28 | "go.overwriteGoplsMiddleware": { 29 | 30 | }, 31 | "go.useLanguageServer": true 32 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # port-forward 3 | Go语言开发的端口转发工具 for port data forward 4 | 5 | 官网地址: 6 | https://gitee.com/tavenli/port-forward 7 | 8 | ``` 9 | 开发语言:GO 10 | 控制台框架:beego 11 | 数据库:sqlite3 12 | ``` 13 | 14 | # 最近更新 15 | ``` 16 | v1.3.6 发布,重新增加 TCP端口数据分发功能 17 | v1.3.5 发布,增加批量导入、批量添加规则 18 | v1.3.3 发布,增加 一键开启所有转发 和 一键关闭所有转发 19 | v1.3.2 发布,服务稳定性已经过长时间的验证 20 | v1.3.1 发布,增加程序启动自动开启转发 21 | v1.2.9_beta 发布,完善点对点转发的稳定性,支持TCP和UDP协议转发 22 | ``` 23 | 24 | # 最新编译好的版本下载: 25 | [https://gitee.com/tavenli/port-forward/releases](https://gitee.com/tavenli/port-forward/releases) 26 | 27 | 28 | 29 | # 功能介绍 30 | 31 | > 支持Web控制台添加端口映射 32 | 33 | > 支持对每条端口映射进行开启和关闭控制 34 | 35 | > 支持 RestfulAPI 接口,方便被其它系统集成 36 | 37 | > 支持每条端口转发的同时,再分发给多个端口,满足某些测试场景 38 | 39 | > 类似企业交换机的功能,即软交换机,主要是方便企业网络维护人员或开发人员 40 | 41 | 42 | # 使用交流群 43 | 44 | > 使用问题或个性化需求可加QQ号:17020415 (申请时请备注:端口转发) 45 | 46 | # 快速安装说明 47 | 1. 下载编译好的程序包,并解压程序包 48 | 2. 执行 start.sh (Linux)或 start.bat (Win)命令 49 | 3. 打开浏览器,进入控制台,打开 http://127.0.0.1:8080/login 50 | 4. 输入用户 admin 密码 123456 进入控制台 51 | 52 | 53 | # 控制台UI 54 | ![登录](https://gitee.com/tavenli/port-forward/raw/master/screenshot/Login.png "在这里输入图片标题") 55 | 56 | 57 | ![转发列表](https://gitee.com/tavenli/port-forward/raw/master/screenshot/List.png "在这里输入图片标题") 58 | 59 | 60 | ![端口转发配置](https://gitee.com/tavenli/port-forward/raw/master/screenshot/edit.png "在这里输入图片标题") 61 | 62 | 63 | ![方便与其它平台集成接口](https://gitee.com/tavenli/port-forward/raw/master/screenshot/ApiDoc.png "在这里输入图片标题") 64 | 65 | -------------------------------------------------------------------------------- /forward-agent/AgentClient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net" 4 | 5 | type AgentClient struct { 6 | 7 | cid string 8 | encode, decode func([]byte) []byte 9 | authed bool 10 | conn net.Conn 11 | 12 | } -------------------------------------------------------------------------------- /forward-agent/AgentService.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type AgentService struct { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /forward-agent/AgentServiceV1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "forward-core/Constant" 6 | "forward-core/NetUtils" 7 | "github.com/astaxie/beego/logs" 8 | "io" 9 | "net" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | type AgentServiceV1 struct { 15 | localConnMap map[int]net.Conn 16 | MagicServerAddr string 17 | AgentOnline bool 18 | } 19 | 20 | 21 | func (_self *AgentServiceV1) ConnToMagicServer() { 22 | serviceConn, err := net.DialTimeout("tcp", _self.MagicServerAddr, 30*time.Second) 23 | 24 | if err != nil { 25 | logs.Error("try dial err", err) 26 | _self.AgentOnline = false 27 | return 28 | } 29 | 30 | callback := func(conn net.Conn, sessionId int, cmd byte, payload []byte) { 31 | //payload 收到的消息内容 32 | _self.OnTunnelRecv(conn, sessionId, cmd, payload) 33 | 34 | } 35 | logs.Debug("开始接收服务端返回指令或数据...") 36 | _self.AgentOnline = true 37 | go NetUtils.ReadConn(serviceConn, callback) 38 | } 39 | 40 | func (_self *AgentServiceV1) OnTunnelRecv(conn net.Conn, sessionId int, cmd byte, payload []byte) { 41 | logs.Debug("收到一条给 sessionId:", sessionId, " 客户端的数据,指令是:", cmd) 42 | 43 | switch cmd { 44 | case Constant.MagicCmd_AgentListenerOpen: 45 | targetAddr := string(payload) 46 | go _self.ListenForClient(targetAddr, _self.MagicServerAddr) 47 | case Constant.MagicCmd_AgentConnOpen: 48 | targetAddr := string(payload) 49 | logs.Debug("sessionId:", sessionId, " 收到 AgentConnOpen 指令是,打开本地连接:", targetAddr) 50 | //AgentConnOpen 让连接进来的客户端,在它的本地创建一个连接,并关联好sessionId 51 | localConn, err := net.DialTimeout("tcp", targetAddr, 30*time.Second) 52 | if err != nil { 53 | logs.Error("try dial err", err) 54 | return 55 | } 56 | _self.localConnMap[sessionId] = localConn 57 | //接收 localConn 返回数据,并将返回的数据,写回给 conn,带上 sessionId 58 | go _self.ReadRawConn(localConn, conn, sessionId, Constant.MagicCmd_DataToMagic) 59 | 60 | case Constant.MagicCmd_DataToAgent: 61 | logs.Debug("sessionId:", sessionId, " 收到 MsgToAgent 指令") 62 | localConn := _self.localConnMap[sessionId] 63 | localConn.Write(payload) 64 | logs.Debug("sessionId:", sessionId, " 数据已写入本地目标连接") 65 | case Constant.MagicCmd_Refused: 66 | //client := string(payload) 67 | logs.Debug("Magic服务端拒绝本次连接") 68 | } 69 | 70 | } 71 | 72 | func (_self *AgentServiceV1) ReadRawConn(from net.Conn, magic_client_Conn net.Conn, sessionId int, cmd byte) { 73 | 74 | arr := make([]byte, 5000) 75 | reader := bufio.NewReader(from) 76 | 77 | for { 78 | size, err := reader.Read(arr) 79 | if err != nil { 80 | break 81 | } 82 | 83 | err = NetUtils.WriteConn(magic_client_Conn, sessionId, cmd, arr[0:size]) 84 | 85 | if err != nil { 86 | //有异常 87 | logs.Error(err) 88 | break 89 | } 90 | } 91 | } 92 | 93 | func (_self *AgentServiceV1) ListenForClient(localListenAddr, toAddr string) { 94 | client_listener, err := net.Listen("tcp", localListenAddr) 95 | if err != nil { 96 | logs.Error("ListenForClient err:", err) 97 | return 98 | } 99 | 100 | for { 101 | logs.Debug("ListenForClient Ready to Accept ...") 102 | client_Conn, err := client_listener.Accept() 103 | if err != nil { 104 | logs.Error("Accept err:", err) 105 | break 106 | } 107 | 108 | //连接到远程服务 109 | serviceConn, err := net.DialTimeout("tcp", toAddr, 30*time.Second) 110 | 111 | if err != nil { 112 | logs.Error("try dial err", err) 113 | return 114 | } 115 | 116 | go func() { 117 | _, err = io.Copy(serviceConn, client_Conn) 118 | if err != nil { 119 | logs.Error("to magic_client 网络连接异常:", err) 120 | } 121 | }() 122 | 123 | go func() { 124 | _, err = io.Copy(client_Conn, serviceConn) 125 | if err != nil { 126 | logs.Error("to magic_client 网络连接异常2:", err) 127 | } 128 | }() 129 | } 130 | 131 | } 132 | 133 | func (_self *AgentServiceV1) ConnectToMagicLoop() { 134 | //客户端与服务端建立连接 135 | if !_self.AgentOnline { 136 | _self.ConnToMagicServer() 137 | delay := 3 138 | time.AfterFunc(time.Second*time.Duration(delay), func() { 139 | _self.ConnectToMagicLoop() 140 | }) 141 | logs.Debug("reConnect after " + strconv.Itoa(delay) + " seconds") 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /forward-agent/build_win.bat: -------------------------------------------------------------------------------- 1 | 2 | 3 | echo "Build For windows..." 4 | set GOOS=windows 5 | set GOARCH=amd64 6 | 7 | go build -o forward-agent.exe 8 | 9 | echo "--------- Build For windows Success!" 10 | 11 | 12 | pause 13 | 14 | -------------------------------------------------------------------------------- /forward-agent/conf/app.conf: -------------------------------------------------------------------------------- 1 | 2 | magic.server = "127.0.0.1:7000" 3 | 4 | -------------------------------------------------------------------------------- /forward-agent/go.mod: -------------------------------------------------------------------------------- 1 | module forward-agent 2 | 3 | go 1.14 4 | 5 | require ( 6 | forward-core v0.0.0-00010101000000-000000000000 7 | github.com/astaxie/beego v1.12.2 8 | ) 9 | 10 | replace forward-core => ../forward-core 11 | -------------------------------------------------------------------------------- /forward-agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "forward-core/Utils" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/logs" 7 | ) 8 | 9 | func main() { 10 | 11 | logs.SetLogger(logs.AdapterConsole, `{"level":7}`) 12 | logs.SetLogger(logs.AdapterFile, `{"filename":"app.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10}`) 13 | 14 | //输出文件名和行号 15 | logs.EnableFuncCallDepth(true) 16 | logs.SetLogFuncCallDepth(3) 17 | //为了让日志输出不影响性能,开启异步日志 18 | logs.Async() 19 | 20 | //远端外网服务 21 | magicServerAddr := beego.AppConfig.String("magic.server") 22 | 23 | logs.Debug("★★★★★★★★★★★★★★★★★★★★") 24 | logs.Debug(" tcp-forward 启动") 25 | logs.Debug("") 26 | logs.Debug("项目地址:https://github.com/tavenli/port-forward") 27 | logs.Debug("") 28 | logs.Debug("请求远端控制服务器:", magicServerAddr) 29 | logs.Debug("") 30 | logs.Debug("★★★★★★★★★★★★★★★★★★★★") 31 | 32 | if Utils.IsEmpty(magicServerAddr){ 33 | magicServerAddr = "forward.apiclub.top:7000" 34 | } 35 | 36 | agentService := new(AgentServiceV1) 37 | agentService.MagicServerAddr = magicServerAddr 38 | agentService.AgentOnline = false 39 | 40 | go agentService.ConnToMagicServer() 41 | 42 | go agentService.ConnectToMagicLoop() 43 | 44 | select { 45 | 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /forward-core/Constant/MagicCmd.go: -------------------------------------------------------------------------------- 1 | package Constant 2 | 3 | const ( 4 | MagicCmd_AuthFail =byte(iota) 5 | MagicCmd_ReqAuth 6 | MagicCmd_Refused 7 | MagicCmd_AgentListenerOpen 8 | MagicCmd_AgentConnOpen 9 | MagicCmd_AgentConnClose 10 | MagicCmd_DataToMagic 11 | MagicCmd_DataToAgent 12 | ) -------------------------------------------------------------------------------- /forward-core/Constant/RunStatus.go: -------------------------------------------------------------------------------- 1 | package Constant 2 | 3 | const ( 4 | RunStatus_Stoped =byte(iota) 5 | RunStatus_Running 6 | ) -------------------------------------------------------------------------------- /forward-core/Models/ForwardConfig.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | type ForwardConfig struct { 4 | RuleId int 5 | Name string 6 | Status int 7 | SrcAddr string 8 | SrcPort int 9 | Protocol string 10 | DestAddr string 11 | DestPort int 12 | Others string 13 | } 14 | -------------------------------------------------------------------------------- /forward-core/Models/ForwardInfo.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | type ForwardInfo struct { 4 | 5 | Name string 6 | Status byte 7 | SrcAddr string 8 | SrcPort int 9 | Protocol string 10 | DestAddr string 11 | DestPort int 12 | OnlineCount int 13 | Clients []string 14 | 15 | } -------------------------------------------------------------------------------- /forward-core/Models/FuncResult.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | type FuncResult struct { 4 | Code int32 5 | Msg string 6 | Data interface{} 7 | } 8 | -------------------------------------------------------------------------------- /forward-core/Models/LoginUser.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | type LoginUser struct { 4 | UserId int 5 | UserName string 6 | } 7 | -------------------------------------------------------------------------------- /forward-core/Models/PageData.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | type PageData struct { 4 | PIndex int64 5 | PSize int64 6 | TotalRows int64 7 | Pages int64 8 | Data interface{} 9 | } 10 | -------------------------------------------------------------------------------- /forward-core/Models/PageParam.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | import ( 4 | "forward-core/Utils" 5 | ) 6 | 7 | type PageParam struct { 8 | PIndex int64 9 | PSize int64 10 | // 要排序的字段名 11 | Sort string 12 | // ASC 或 DESC 13 | Direction string 14 | } 15 | 16 | func (this *PageParam) PageSize() int { 17 | 18 | return int(this.PSize) 19 | 20 | } 21 | 22 | // 分页, 排序处理 23 | func (this *PageParam) SkipRows() int { 24 | this.PIndex = Utils.If(this.PIndex < 1, 1, this.PIndex).(int64) 25 | this.PSize = Utils.If(this.PSize < 1, 5, this.PSize).(int64) 26 | 27 | skipRows := (this.PIndex - 1) * this.PSize 28 | return int(skipRows) 29 | } 30 | 31 | func (this *PageParam) SortField() string { 32 | var sortField string 33 | if !Utils.IsEmpty(this.Sort) { 34 | if !Utils.IsEmpty(this.Direction) && this.Direction == "DESC" { 35 | //降序 36 | sortField = "-" + this.Sort 37 | } else { 38 | //升序 39 | sortField = this.Sort 40 | 41 | } 42 | } 43 | 44 | return sortField 45 | } 46 | -------------------------------------------------------------------------------- /forward-core/Models/PortForward.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/astaxie/beego/orm" 7 | ) 8 | 9 | type PortForward struct { 10 | Id int `orm:"column(id);pk;auto"` 11 | Name string `orm:"column(name);size(256);null"` 12 | // 0:禁用,1:启用 13 | Status int `orm:"column(status);null"` 14 | Addr string `orm:"column(addr);size(256);null"` 15 | // 端口号 16 | Port int `orm:"column(port);null"` 17 | //协议 18 | Protocol string `orm:"column(protocol);size(32);null"` 19 | TargetAddr string `orm:"column(targetAddr);size(256);null"` 20 | // 端口号 21 | TargetPort int `orm:"column(targetPort);null"` 22 | CreateTime time.Time `orm:"column(createTime);type(datetime)"` 23 | //暂时用来存放端口分发配置,后续版本再调整 24 | Others string `orm:"column(others);size(500);null"` 25 | // 0:普通映射,1:内网穿透映射(Server->Client),2:内网穿透反向映射(Client->Server) 26 | FType int `orm:"column(fType);null"` 27 | } 28 | 29 | func (t *PortForward) TableName() string { 30 | return "t_port_forward" 31 | } 32 | 33 | func init() { 34 | orm.RegisterModel(new(PortForward)) 35 | } 36 | -------------------------------------------------------------------------------- /forward-core/Models/PortInfo.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | type PortInfo struct { 4 | 5 | Addr string 6 | Port int 7 | } -------------------------------------------------------------------------------- /forward-core/Models/SysUser.go: -------------------------------------------------------------------------------- 1 | package Models 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/astaxie/beego/orm" 7 | ) 8 | 9 | type SysUser struct { 10 | Id int `orm:"column(id);pk;auto"` 11 | UserName string `orm:"column(userName);null"` 12 | PassWord string `orm:"column(passWord);null"` 13 | // 0:禁用,1:启用 14 | Status int `orm:"column(status);null"` 15 | CreateTime time.Time `orm:"column(createTime);type(datetime)"` 16 | } 17 | 18 | func (t *SysUser) TableName() string { 19 | return "t_sys_user" 20 | } 21 | 22 | func init() { 23 | orm.RegisterModel(new(SysUser)) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /forward-core/NetUtils/BytesUtil.go: -------------------------------------------------------------------------------- 1 | package NetUtils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | 7 | "github.com/axgle/mahonia" 8 | ) 9 | 10 | func WriteBYTE(data *bytes.Buffer, val uint8) { 11 | //BYTE 长度:1 12 | buf := make([]byte, 1) 13 | buf[0] = byte(val) 14 | 15 | data.Write(buf) 16 | } 17 | 18 | func WriteWORD(data *bytes.Buffer, val uint16) { 19 | //WORD 长度:2 20 | buf := make([]byte, 2) 21 | buf[0] = byte(val) 22 | buf[1] = byte(val >> 8) 23 | 24 | data.Write(buf) 25 | } 26 | 27 | func WriteDWORD(data *bytes.Buffer, val uint32) { 28 | //DWORD 长度:4 29 | buf := make([]byte, 4) 30 | buf[0] = byte(val) 31 | buf[1] = byte(val >> 8) 32 | buf[2] = byte(val >> 16) 33 | buf[3] = byte(val >> 24) 34 | 35 | data.Write(buf) 36 | } 37 | 38 | func WriteTCHAR(data *bytes.Buffer, size int, val string) { 39 | //TCHAR 长度:由size指定 40 | buf := []byte(val) 41 | data.Write(buf) 42 | // 43 | for i := 0; i < size-len(buf); i++ { 44 | //剩余的补0 45 | data.WriteByte(0) 46 | } 47 | 48 | } 49 | 50 | func WriteUnicodeTCHAR(data *bytes.Buffer, size int, val string) { 51 | //Unicode TCHAR 长度:size*2 52 | realSize := size * 2 53 | buf := []byte(val) 54 | dataBuffer := make([]byte, realSize) 55 | k := 0 56 | for j := 0; j < len(buf); j++ { 57 | dataBuffer[k] = buf[j] 58 | dataBuffer[k+1] = byte(0) 59 | k = k + 2 60 | } 61 | 62 | data.Write(dataBuffer) 63 | // 64 | 65 | } 66 | 67 | func WriteInt(data *bytes.Buffer, val int) { 68 | //Byte 长度:4 69 | buf := make([]byte, 4) 70 | buf[0] = byte(val) 71 | buf[1] = byte(val >> 8) 72 | buf[2] = byte(val >> 16) 73 | buf[3] = byte(val >> 24) 74 | 75 | data.Write(buf) 76 | } 77 | 78 | func _ReadInt_(data *bytes.Buffer, val []byte) (int, error) { 79 | 80 | return data.Read(val) 81 | } 82 | 83 | func ReadWord(val []byte) uint16 { 84 | //binary.LittleEndian.Uint16(rData[4:6]) 85 | return binary.LittleEndian.Uint16(val) 86 | } 87 | 88 | func ReadDWord(val []byte) uint32 { 89 | return binary.LittleEndian.Uint32(val) 90 | } 91 | 92 | func ReadTCHAR(val []byte) string { 93 | return string(val) 94 | } 95 | 96 | func UTF8_To_GBK(input string) string { 97 | enc := mahonia.NewEncoder("GBK") 98 | return enc.ConvertString(input) 99 | } 100 | 101 | func GBK_To_UTF8(input string) string { 102 | dec := mahonia.NewDecoder("UTF-8") 103 | return dec.ConvertString(input) 104 | } 105 | 106 | func U2W(input string) string { 107 | //网狐荣耀版本的服务端TCHAR编码需要这样转换 108 | dec := mahonia.NewDecoder("UTF-16LE") 109 | return dec.ConvertString(input) 110 | } 111 | -------------------------------------------------------------------------------- /forward-core/NetUtils/HttpUtil.go: -------------------------------------------------------------------------------- 1 | package NetUtils 2 | 3 | import ( 4 | "errors" 5 | "forward-core/Utils" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | 11 | "bytes" 12 | 13 | "github.com/astaxie/beego" 14 | "github.com/astaxie/beego/logs" 15 | ) 16 | 17 | func GetIP(c *beego.Controller) string { 18 | //utils.GetIP(&c.Controller) 19 | //也可以直接用 c.Ctx.Input.IP() 取真实IP 20 | ip := c.Ctx.Request.Header.Get("X-Real-IP") 21 | if ip != "" { 22 | return ip 23 | } 24 | 25 | ip = c.Ctx.Request.Header.Get("Remote_addr") 26 | if ip == "" { 27 | ip = c.Ctx.Request.RemoteAddr 28 | } 29 | return ip 30 | } 31 | 32 | func HttpGet(url string) (string, error) { 33 | resp, err := http.Get(url) 34 | if err != nil { 35 | logs.Error("HttpGet error: ", err) 36 | return "", err 37 | } 38 | 39 | if resp == nil { 40 | return "", errors.New("返回对象为空") 41 | } 42 | 43 | defer resp.Body.Close() 44 | result := "" 45 | body, err := ioutil.ReadAll(resp.Body) 46 | if err == nil { 47 | result = string(body) 48 | //logs.Debug("HttpGet result: ", result) 49 | } else { 50 | logs.Error("HttpGet error: ", err) 51 | } 52 | 53 | return result, nil 54 | } 55 | 56 | func HttpPostJsonReturnByte(url string, json string) ([]byte, error) { 57 | resp, err := http.Post(url, "application/json", strings.NewReader(json)) 58 | if err != nil { 59 | logs.Error("HttpPostJson error: ", err) 60 | return nil, err 61 | } 62 | 63 | if resp == nil { 64 | return nil, errors.New("返回对象为空") 65 | } 66 | 67 | defer resp.Body.Close() 68 | 69 | body, err := ioutil.ReadAll(resp.Body) 70 | if err == nil { 71 | return body, err 72 | //logs.Debug("HttpPostJson result: ", result) 73 | } else { 74 | logs.Error("HttpPostJson error: ", err) 75 | return nil, err 76 | } 77 | 78 | } 79 | 80 | func HttpPost(url string, param map[string]string) (string, error) { 81 | 82 | var paramBuf bytes.Buffer 83 | paramBuf.WriteString("curTime=" + Utils.GetCurrentTime()) 84 | for k, v := range param { 85 | paramBuf.WriteString("&" + k + "=" + v) 86 | } 87 | 88 | resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(paramBuf.String())) 89 | if err != nil { 90 | logs.Error("HttpPost error: ", err) 91 | return "", err 92 | } 93 | 94 | if resp == nil { 95 | return "", errors.New("返回对象为空") 96 | } 97 | 98 | defer resp.Body.Close() 99 | result := "" 100 | body, err := ioutil.ReadAll(resp.Body) 101 | if err == nil { 102 | result = string(body) 103 | logs.Debug("HttpPost result: ", result) 104 | } else { 105 | logs.Error("HttpPost error: ", err) 106 | } 107 | 108 | return result, nil 109 | } 110 | 111 | func UrlEncode(input string) string { 112 | if Utils.IsEmpty(input) { 113 | return "" 114 | } 115 | return url.QueryEscape(input) 116 | } 117 | 118 | func UrlDecode(input string) string { 119 | if Utils.IsEmpty(input) { 120 | return "" 121 | } 122 | result, err := url.QueryUnescape(input) 123 | if err != nil { 124 | return input 125 | } else { 126 | return result 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /forward-core/NetUtils/NetTool.go: -------------------------------------------------------------------------------- 1 | package NetUtils 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "errors" 7 | "github.com/astaxie/beego/logs" 8 | "io" 9 | "net" 10 | "forward-core/Common" 11 | ) 12 | 13 | func NewTCP(addr string) (net.Listener, error) { 14 | tcpSocket, err := net.Listen("tcp", addr) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return tcpSocket,nil 20 | 21 | } 22 | func NewUDP(addr string) (*net.UDPConn, error) { 23 | udpAddr, err := net.ResolveUDPAddr("udp", addr) 24 | if err != nil { 25 | return nil, err 26 | } 27 | udpSocket, _err := net.ListenUDP("udp", udpAddr) 28 | if _err != nil { 29 | return nil, _err 30 | } 31 | 32 | return udpSocket,nil 33 | 34 | } 35 | 36 | func NewKCP(addr string, setting *Common.KcpSetting) (*Common.UDPListener, error) { 37 | return Common.NewKCP(addr, setting) 38 | } 39 | 40 | 41 | func DataCopy(dst io.Writer, src io.Reader) (written int64, err error) { 42 | return io.Copy(dst, src) 43 | } 44 | 45 | func MultiDataCopy(src io.Reader, dispatchConns []io.Writer) (written int64, err error) { 46 | 47 | mWriter := io.MultiWriter(dispatchConns...) 48 | return io.Copy(mWriter, src) 49 | 50 | } 51 | 52 | type ReadCallBack func(conn net.Conn, id int, cmd byte, arg []byte) 53 | 54 | func ReadConn(conn net.Conn, callback ReadCallBack) { 55 | scanner := bufio.NewScanner(conn) 56 | scanner.Split(func(data []byte, atEOF bool) (adv int, token []byte, err error) { 57 | return NetSplitV1(data, atEOF, conn, callback) 58 | }) 59 | for scanner.Scan() { 60 | } 61 | if scanner.Err() != nil { 62 | logs.Error(scanner.Err()) 63 | } 64 | } 65 | 66 | func NetSplitV1(data []byte, atEOF bool, conn net.Conn, callback ReadCallBack) (adv int, token []byte, err error) { 67 | l := len(data) 68 | if l < 6 { 69 | return 0, nil, nil 70 | } 71 | 72 | if l > 100000 { 73 | conn.Close() 74 | return 0, nil, errors.New("max data") 75 | } 76 | var id int 77 | var cmd byte 78 | id = int(int32(data[0]) | int32(data[1])<<8 | int32(data[2])<<16 | int32(data[3])<<24) 79 | cmd = data[4] 80 | isShort := data[5] 81 | var payload []byte 82 | var offset int 83 | if isShort == 1 { 84 | offset = 6 85 | } else { 86 | if l < 10 { 87 | return 0, nil, nil 88 | } 89 | ls := binary.LittleEndian.Uint32(data[6:]) 90 | tail := l - 10 91 | if tail < int(ls) { 92 | return 0, nil, nil 93 | } 94 | payload = data[10 : 10+ls] 95 | offset = 10 + int(ls) 96 | } 97 | 98 | callback(conn, id, cmd, payload) 99 | 100 | return offset, []byte{}, nil 101 | } 102 | 103 | func WriteConn(conn net.Conn, id int, cmd byte, payload []byte) error { 104 | if conn == nil { 105 | return nil 106 | } 107 | l := len(payload) 108 | var buf []byte 109 | var size int 110 | if l > 0 { 111 | size = 10 + l 112 | //4+1+1+4 id cmd isShort 113 | buf = make([]byte, size) 114 | } else { 115 | size = 6 116 | //4+1+1 id cmd isShort 117 | buf = make([]byte, size) 118 | } 119 | buf[0] = byte(id) 120 | buf[1] = byte(id >> 8) 121 | buf[2] = byte(id >> 16) 122 | buf[3] = byte(id >> 24) 123 | buf[4] = cmd 124 | if l > 0 { 125 | buf[5] = 0 126 | binary.LittleEndian.PutUint32(buf[6:], uint32(l)) 127 | copy(buf[10:], []byte(payload)) 128 | } else { 129 | buf[5] = 1 130 | } 131 | _, err := conn.Write(buf[:size]) 132 | if err != nil { 133 | logs.Error("conn.Write error:", err) 134 | } 135 | return err 136 | } 137 | -------------------------------------------------------------------------------- /forward-core/Utils/CollectionUtil.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | func IntArrayFind(slice []int, value int) int { 4 | for p, v := range slice { 5 | if v == value { 6 | return p 7 | } 8 | } 9 | return -1 10 | } 11 | 12 | func IntArrayContain(slice []int, value int) bool { 13 | for _, v := range slice { 14 | if v == value { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | func Int64ArrayFind(slice []int64, value int64) int { 22 | for p, v := range slice { 23 | if v == value { 24 | return p 25 | } 26 | } 27 | return -1 28 | } 29 | 30 | func Int64ArrayContain(slice []int64, value int64) bool { 31 | for _, v := range slice { 32 | if v == value { 33 | return true 34 | } 35 | } 36 | return false 37 | } 38 | 39 | func StrArrayContain(slice []string, value string) bool { 40 | for _, v := range slice { 41 | if v == value { 42 | return true 43 | } 44 | } 45 | return false 46 | } 47 | -------------------------------------------------------------------------------- /forward-core/Utils/CryptoUtil.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | //生成32位md5字串 11 | func GetMd5(input string) string { 12 | hash := md5.New() 13 | hash.Write([]byte(input)) 14 | return hex.EncodeToString(hash.Sum(nil)) 15 | } 16 | 17 | func GetSaltMD5(input, salt string) string { 18 | hash := md5.New() 19 | //salt = "salt123456" //盐值 20 | io.WriteString(hash, input+salt) 21 | result := fmt.Sprintf("%x", hash.Sum(nil)) 22 | return result 23 | } 24 | -------------------------------------------------------------------------------- /forward-core/Utils/DateUtil.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "time" 7 | ) 8 | 9 | // GO的诞辰 10 | const timeLayout = "2006-01-02 15:04:05" 11 | 12 | // 取当前系统时间 13 | func GetTimeNow() time.Time { 14 | return time.Now() 15 | } 16 | 17 | func GetTime(timeStr string) time.Time { 18 | toTime, _ := ToTime(timeStr) 19 | return toTime 20 | } 21 | 22 | func JavaLongTime(javaLong int64) time.Time { 23 | //1492566520958 -> 2017-04-19 09:48:40 24 | //fmt.Println(time.Unix(1492566520958/1000, 0)) 25 | //fmt.Println(time.Unix(0, 1492566520958*1000000)) 26 | return time.Unix(0, javaLong*1000000) 27 | } 28 | 29 | func ToTime(timeStr string) (time.Time, error) { 30 | loc, _ := time.LoadLocation("Local") 31 | toTime, err := time.ParseInLocation(timeLayout, timeStr, loc) 32 | //toTime, err := time.Parse(timeLayout, timeStr) 33 | return toTime, err 34 | 35 | } 36 | 37 | func ToTimeByFm(timeStr string, format string) (time.Time, error) { 38 | loc, _ := time.LoadLocation("Local") 39 | toTime, err := time.ParseInLocation(format, timeStr, loc) 40 | //toTime, err := time.Parse(timeLayout, timeStr) 41 | return toTime, err 42 | 43 | } 44 | 45 | //要想格式化为:yyyyMMddHHmmss 46 | //则 format = "20060102150405" 47 | //要想格式化为:yyyy-MM-dd HH:mm:ss 48 | //则 format = "2006-01-02 15:04:05" 49 | //要想格式化为:yyyy-MM-dd 50 | //则 format = "2006-01-02" 51 | func FormatTimeByFm(t time.Time, format string) string { 52 | 53 | return t.Format(format) 54 | } 55 | 56 | func GetCurrentTime() string { 57 | return FormatTime(time.Now()) 58 | } 59 | 60 | func GetCurrentDay() string { 61 | return FormatTimeByFm(time.Now(), "2006-01-02") 62 | } 63 | 64 | func FormatTime(t time.Time) string { 65 | // 66 | return FormatTimeByFm(t, "2006-01-02 15:04:05") 67 | } 68 | 69 | func FormatTimeToNum(t time.Time) string { 70 | // 71 | return FormatTimeByFm(t, "20060102150405") 72 | } 73 | 74 | // 在当前时间之前 75 | func IsBeforeNow(t time.Time) (result bool) { 76 | result = false 77 | if &t != nil && t.Before(time.Now()) { 78 | result = true 79 | } 80 | return 81 | } 82 | 83 | // 在当前时间之后 84 | func IsAfterNow(t time.Time) (result bool) { 85 | result = false 86 | if &t != nil && t.After(time.Now()) { 87 | result = true 88 | } 89 | return 90 | } 91 | 92 | func SubDateTime(firstTime time.Time, secondTime time.Time) (result time.Duration) { 93 | result = time.Duration(0) 94 | if &firstTime != nil && &secondTime != nil { 95 | result = secondTime.Sub(firstTime) 96 | } 97 | return 98 | } 99 | 100 | func DifferDays(firstTime time.Time, secondTime time.Time) int64 { 101 | result := SubDateTime(firstTime, secondTime).Hours() 102 | return int64(math.Abs(result) / 24) 103 | } 104 | 105 | func DifferHour(firstTime time.Time, secondTime time.Time) int64 { 106 | result := SubDateTime(firstTime, secondTime).Hours() 107 | //return int64(result) 两个时间的先后顺序不一样,可能出现负数 108 | return int64(math.Abs(result)) 109 | } 110 | 111 | func DifferMin(firstTime time.Time, secondTime time.Time) int64 { 112 | result := SubDateTime(firstTime, secondTime).Minutes() 113 | return int64(math.Abs(result)) 114 | } 115 | 116 | func DifferSec(firstTime time.Time, secondTime time.Time) int64 { 117 | result := SubDateTime(firstTime, secondTime).Seconds() 118 | return int64(math.Abs(result)) 119 | } 120 | 121 | // 24小时前的时间 122 | func Before24h() time.Time { 123 | t, _ := time.ParseDuration("-24h") 124 | return time.Now().Add(t) 125 | } 126 | 127 | func AddSecs(_time time.Time, secs int64) time.Time { 128 | t, _ := time.ParseDuration("1s") 129 | return time.Now().Add(t * time.Duration(secs)) 130 | } 131 | 132 | /* 133 | 增加10分钟:utils.AddMins(time.Now(), 10) 134 | 减少5分钟:utils.AddMins(time.Now(), -5) 135 | */ 136 | func AddMins(_time time.Time, mins int64) time.Time { 137 | t, _ := time.ParseDuration("1m") 138 | return time.Now().Add(t * time.Duration(mins)) 139 | } 140 | 141 | func AddHours(_time time.Time, hours int64) time.Time { 142 | t, _ := time.ParseDuration("1h") 143 | return time.Now().Add(t * time.Duration(hours)) 144 | } 145 | 146 | func AddDays(_time time.Time, days int) time.Time { 147 | return _time.AddDate(0, 0, days) 148 | } 149 | 150 | func AddMonths(_time time.Time, months int) time.Time { 151 | return _time.AddDate(0, months, 0) 152 | } 153 | 154 | func GetBeginTime(_time time.Time) time.Time { 155 | //2017-06-28 00:00:00 +0800 CST 156 | return GetBeginTimeByLoc(_time, time.Local) 157 | //return GetBeginTimeByLoc(_time, time.UTC) 158 | 159 | } 160 | 161 | func GetEndTime(_time time.Time) time.Time { 162 | //2017-06-28 23:59:59.999999999 +0800 CST 163 | return GetEndTimeByLoc(_time, time.Local) 164 | } 165 | 166 | func GetBeginTimeByLoc(_time time.Time, loc *time.Location) time.Time { 167 | year, month, day := _time.Date() 168 | return time.Date(year, month, day, 0, 0, 0, 0, loc) 169 | 170 | } 171 | 172 | func GetEndTimeByLoc(_time time.Time, loc *time.Location) time.Time { 173 | year, month, day := _time.Date() 174 | return time.Date(year, month, day, 23, 59, 59, 999999999, loc) 175 | } 176 | 177 | // 一行代码计算代码执行时间 178 | // defer utils.TimeCost(time.Now()) 179 | func TimeCost(start time.Time) { 180 | terminal := time.Since(start) 181 | fmt.Println("TimeCost:", terminal) 182 | } 183 | -------------------------------------------------------------------------------- /forward-core/Utils/JsonUtil.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/json-iterator/go" 9 | "reflect" 10 | 11 | "github.com/astaxie/beego/logs" 12 | "github.com/ugorji/go/codec" 13 | ) 14 | 15 | var jsonIter = jsoniter.ConfigCompatibleWithStandardLibrary 16 | 17 | func ToJson(obj interface{}) ([]byte, error) { 18 | return json.Marshal(obj) 19 | } 20 | 21 | func FromJson(data []byte, t interface{}) error { 22 | return json.Unmarshal(data, t) 23 | } 24 | 25 | func ToJsonIterator(obj interface{}) ([]byte, error) { 26 | return jsonIter.Marshal(obj) 27 | } 28 | 29 | func FromJsonIterator(data []byte, t interface{}) error { 30 | return jsonIter.Unmarshal(data, t) 31 | } 32 | 33 | func MsgpEncode(obj interface{}) []byte { 34 | var mh codec.MsgpackHandle 35 | mh.MapType = reflect.TypeOf(obj) 36 | var buf bytes.Buffer 37 | enc := codec.NewEncoder(&buf, &mh) 38 | err := enc.Encode(obj) 39 | if err == nil { 40 | return buf.Bytes() 41 | } else { 42 | logs.Error(err) 43 | return nil 44 | } 45 | 46 | } 47 | 48 | func MsgpDecode(data []byte, obj interface{}) error { 49 | var mh codec.MsgpackHandle 50 | mh.MapType = reflect.TypeOf(obj) 51 | dec := codec.NewDecoder(bytes.NewReader(data), &mh) 52 | err := dec.Decode(&obj) 53 | return err 54 | 55 | } 56 | 57 | func DeepCopy(dst, src interface{}) error { 58 | var buf bytes.Buffer 59 | if err := gob.NewEncoder(&buf).Encode(src); err != nil { 60 | return err 61 | } 62 | return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst) 63 | } 64 | 65 | func ShowObjAllProp(obj interface{}) { 66 | value_method := reflect.ValueOf(obj) 67 | obj_type := value_method.Type() 68 | 69 | fmt.Printf("输出对象的属性和方法\t%v\n", obj) 70 | 71 | fmt.Println("\tMethods...") 72 | 73 | for i := 0; i < value_method.NumMethod(); i++ { 74 | fmt.Printf("\t%d\t%s\n", i, obj_type.Method(i).Name) 75 | } 76 | 77 | value_element := reflect.ValueOf(obj).Elem() 78 | obj_element := value_element.Type() 79 | 80 | fmt.Println("\tFields...") 81 | for i := 0; i < value_element.NumField(); i++ { 82 | fmt.Printf("\t%d\t%s\n", i, obj_element.Field(i).Name) 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /forward-core/Utils/MathUtil.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | func AbsInt(num float64) int { 8 | //result := math.Abs(float64(num)) 9 | result := math.Abs(num) 10 | return int(result) 11 | } 12 | 13 | func AbsInt64(num float64) int64 { 14 | result := math.Abs(num) 15 | return int64(result) 16 | } 17 | 18 | func CeilInt(num float64) int { 19 | result := math.Ceil(num) 20 | return int(result) 21 | } 22 | 23 | func CeilInt64(num float64) int64 { 24 | //CeilInt64(5.9) = 6 25 | //CeilInt64(5.3) = 6 26 | //CeilInt64(5) = 5 27 | result := math.Ceil(num) 28 | return int64(result) 29 | } 30 | 31 | func Float64ToInt64(num float64) int64 { 32 | return int64(num) 33 | } 34 | 35 | func Float64TryToInt64(num interface{}) int64 { 36 | return int64(num.(float64)) 37 | } 38 | 39 | // 返回最大值 40 | func MaxInt(a, b int) int { 41 | if a > b { 42 | return a 43 | } 44 | return b 45 | } 46 | 47 | // 返回最小值 48 | func MinInt(a, b int) int { 49 | if a < b { 50 | return a 51 | } 52 | return b 53 | } 54 | 55 | func Pages(total, psize int64) int64 { 56 | 57 | pages := float64(total) / float64(psize) 58 | result := math.Ceil(pages) 59 | return int64(result) 60 | } 61 | -------------------------------------------------------------------------------- /forward-core/Utils/StringUtil.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func ToInt(str string) int { 12 | _num, _ := strconv.Atoi(str) 13 | return _num 14 | } 15 | 16 | func ToInt64(str string) int64 { 17 | _num, _ := strconv.ParseInt(str, 10, 64) 18 | return _num 19 | } 20 | 21 | func ToInteger(str string) (int, error) { 22 | _num, _err := strconv.Atoi(str) 23 | return _num, _err 24 | } 25 | 26 | func ToLong(str string) (int64, error) { 27 | _num, _err := strconv.ParseInt(str, 10, 64) 28 | return _num, _err 29 | } 30 | 31 | func ToFloat64(str string) (float64, error) { 32 | _num, _err := strconv.ParseFloat(str, 64) 33 | return _num, _err 34 | } 35 | 36 | func BinaryToInt(str string) (int64, error) { 37 | _num, _err := strconv.ParseInt(str, 2, 64) 38 | return _num, _err 39 | } 40 | 41 | func IntToBinary(num int64) string { 42 | bin := strconv.FormatInt(num, 2) 43 | return bin 44 | } 45 | 46 | func IsBinaryOverInt(binStr string, number int64) bool { 47 | _num, _ := strconv.ParseInt(binStr, 2, 64) 48 | return (_num & number) == number 49 | } 50 | 51 | func IsBinNumOverInt(binNum int64, number int64) bool { 52 | 53 | return (binNum & number) == number 54 | } 55 | 56 | func ToStr(_num int) string { 57 | return strconv.Itoa(_num) 58 | } 59 | 60 | func FormatInt(_num int) string { 61 | return strconv.FormatInt(int64(_num), 10) 62 | } 63 | 64 | func FormatInt64(_num int64) string { 65 | return strconv.FormatInt(_num, 10) 66 | } 67 | 68 | func FormatFloat64(_num float64) string { 69 | return strconv.FormatFloat(_num, 'f', 2, 64) 70 | } 71 | 72 | func IsEmpty(str string) bool { 73 | 74 | return Len(str) <= 0 75 | } 76 | 77 | func IsNotEmpty(str string) bool { 78 | 79 | return !IsEmpty(str) 80 | } 81 | 82 | func Replace(str string, find string, to string) string { 83 | 84 | return strings.Replace(str, find, to, 1) 85 | } 86 | 87 | func ReplaceAll(str string, find string, to string) string { 88 | 89 | return strings.Replace(str, find, to, -1) 90 | } 91 | 92 | func Split(str string, spChar string) []string { 93 | 94 | return strings.Split(str, spChar) 95 | } 96 | 97 | func Contains(str string, find string) bool { 98 | 99 | return strings.Contains(str, find) 100 | } 101 | 102 | // strings.HasPrefix("ABC_xyz", "ABC") 103 | func StartsWith(str string, find string) bool { 104 | 105 | return strings.HasPrefix(str, find) 106 | } 107 | 108 | // strings.HasSuffix("ABC_xyz", "xyz") 109 | func EndsWith(str string, find string) bool { 110 | 111 | return strings.HasSuffix(str, find) 112 | } 113 | 114 | // strings.Count("cheese", "e") = 3 115 | func Count(str string, find string) int { 116 | 117 | return strings.Count(str, find) 118 | } 119 | 120 | // 返回第一个匹配字符的位置,返回-1为未找到 121 | // strings.Index("ABC_xyz", "xyz") = 4 122 | // strings.Index("ABC_xyz", "B") = 1 123 | func Index(str string, find string) int { 124 | 125 | return strings.Index(str, find) 126 | } 127 | 128 | //strings.Join(arrays, ",") = "foo, bar, bas" 129 | func Join(strs []string, spChar string) string { 130 | 131 | return strings.Join(strs, spChar) 132 | } 133 | 134 | // 字母转为小写 135 | // strings.ToLower("Love GoLang") = "love golang" 136 | func ToLower(str string) string { 137 | 138 | return strings.ToLower(str) 139 | } 140 | 141 | // 字母转为大写 142 | // strings.ToTitle("love 中国") = "LOVE 中国" 143 | func ToUpper(str string) string { 144 | return strings.ToUpper(str) 145 | //return strings.ToTitle(str) 146 | } 147 | 148 | func Len(str string) int { 149 | 150 | return len(str) 151 | } 152 | 153 | func Print(str string) { 154 | //var show = fmt.Println 155 | //show(str) 156 | fmt.Println(str) 157 | } 158 | 159 | func FilterByRegex(expr, input, placeTo string) string { 160 | regx, _ := regexp.Compile(expr) 161 | return regx.ReplaceAllString(input, placeTo) 162 | } 163 | 164 | func FilterStyle(input string) string { 165 | //regx, _ := regexp.Compile("") 166 | regx, _ := regexp.Compile("\\") 167 | return regx.ReplaceAllString(input, "") 168 | } 169 | 170 | func FilterScript(input string) string { 171 | //regx, _ := regexp.Compile("") 172 | regx, _ := regexp.Compile("\\") 173 | return regx.ReplaceAllString(input, "") 174 | } 175 | 176 | func FilterHtml(input string) string { 177 | regx, _ := regexp.Compile("<.+?>") 178 | //regx, _ := regexp.Compile("\\<[\\S\\s]+?\\>") 179 | return regx.ReplaceAllString(input, "") 180 | } 181 | 182 | func FilterA(input string) string { 183 | 184 | regx, _ := regexp.Compile("<.?a(.|\n)*?>") 185 | return regx.ReplaceAllString(input, "") 186 | } 187 | 188 | func FilterImage(input string) string { 189 | 190 | regx, _ := regexp.Compile("") 191 | return regx.ReplaceAllString(input, "") 192 | } 193 | 194 | func FilterSpecialChar(input string) string { 195 | 196 | regx, _ := regexp.Compile("[+=|{}':;',]") 197 | return regx.ReplaceAllString(input, "") 198 | } 199 | 200 | func FilterUrlPrefix(input string) string { 201 | 202 | regx, _ := regexp.Compile("\\w+://") 203 | return regx.ReplaceAllString(input, "") 204 | } 205 | 206 | func IsNumber(input string) bool { 207 | 208 | match, _ := regexp.MatchString("^\\d+$", input) 209 | return match 210 | } 211 | 212 | func IsIP(input string) bool { 213 | 214 | match, _ := regexp.MatchString("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$", input) 215 | return match 216 | } 217 | 218 | func IsEMail(input string) bool { 219 | 220 | match, _ := regexp.MatchString("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$", input) 221 | return match 222 | } 223 | 224 | //高效拼接字符串 225 | func LinkStrs(inputs ...string) string { 226 | var buf bytes.Buffer 227 | for _, v := range inputs { 228 | buf.WriteString(v) 229 | } 230 | return buf.String() 231 | } 232 | 233 | func LinkInputs(inputs ...interface{}) string { 234 | var buf bytes.Buffer 235 | for _, v := range inputs { 236 | switch t := v.(type) { 237 | case string: 238 | buf.WriteString(t) 239 | default: 240 | buf.WriteString(fmt.Sprint(t)) 241 | 242 | } 243 | } 244 | return buf.String() 245 | } 246 | 247 | // func LinkInputs(inputs ...interface{}) string { 248 | // var buf bytes.Buffer 249 | // for _, v := range inputs { 250 | // switch t := v.(type) { 251 | // case string: 252 | // buf.WriteString(t) 253 | // //case int, int64: 254 | // case int64: 255 | // buf.WriteString(FormatInt64(t)) 256 | // case int: 257 | // buf.WriteString(FormatInt(t)) 258 | // case float64: 259 | // buf.WriteString(FormatFloat64(t)) 260 | // default: 261 | // buf.WriteString(fmt.Sprint(t)) 262 | 263 | // } 264 | 265 | // fmt.Println("v:", v) 266 | 267 | // } 268 | // return buf.String() 269 | // } 270 | -------------------------------------------------------------------------------- /forward-core/Utils/SysUtil.go: -------------------------------------------------------------------------------- 1 | package Utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "time" 8 | ) 9 | 10 | // 显示当前系统基本信息 11 | func ShowSysInf() { 12 | 13 | fmt.Println("★★★★★★★★★★★★★★★★★★★★★★★★") 14 | fmt.Println("runtime.Version --->", runtime.Version()) //GO的版本 15 | fmt.Println("runtime.NumCPU --->", runtime.NumCPU()) //CPU核数 16 | fmt.Println("runtime.GOOS --->", runtime.GOOS) //运行GO的OS操作系统 17 | fmt.Println("runtime.GOARCH --->", runtime.GOARCH) //CPU架构 18 | fmt.Println("runtime.Version --->", runtime.Version()) //当前GO语言版本 19 | fmt.Println("time --->", time.Now()) //系统当前时间 20 | fmt.Println("★★★★★★★★★★★★★★★★★★★★★★★★") 21 | 22 | //var memStats runtime.MemStats 23 | //runtime.ReadMemStats(&memStats) 24 | //fmt.Println("runtime.memStats --->", memStats) 25 | 26 | //获取全部的环境变量 27 | // data := os.Environ() 28 | // for _, val := range data { 29 | // fmt.Println(val) 30 | // } 31 | 32 | } 33 | 34 | // go不支持三元表达式,可以使用自定义的函数实现 35 | // 例如:max := utils.If(x > y, x, y).(int) 36 | func If(condition bool, trueVal, falseVal interface{}) interface{} { 37 | 38 | if condition { 39 | return trueVal 40 | } 41 | return falseVal 42 | } 43 | 44 | /* 45 | 交换int数据:a, b := utils.Swap(2, 9) 46 | 交换字符串数据:A, B := utils.Swap("Li", "Chen") 47 | */ 48 | func Swap(x, y interface{}) (interface{}, interface{}) { 49 | return y, x 50 | } 51 | 52 | // 设置环境变量 53 | func SetEnv(key, value string) error { 54 | 55 | return os.Setenv(key, value) 56 | } 57 | 58 | // 取环境变量的值 59 | func GetEnv(key string) string { 60 | 61 | return os.Getenv(key) 62 | } 63 | 64 | //取进程ID 65 | func GetPid() int { 66 | return os.Getpid() 67 | } 68 | 69 | func KillByPid(pid int) { 70 | p, _ := os.FindProcess(pid) 71 | fmt.Println("KillByPid", p) 72 | p.Kill() 73 | } 74 | 75 | func StartProcessDemo() { 76 | //例子演示 77 | attr := &os.ProcAttr{ 78 | Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, 79 | } 80 | p, _ := os.StartProcess("xxx.exe", []string{"xxx", "1.txt"}, attr) 81 | p.Release() 82 | time.Sleep(10000) 83 | p.Signal(os.Kill) 84 | os.Exit(10) 85 | } 86 | -------------------------------------------------------------------------------- /forward-core/go.mod: -------------------------------------------------------------------------------- 1 | module forward-core 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/astaxie/beego v1.12.2 7 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 8 | github.com/json-iterator/go v1.1.10 9 | github.com/klauspost/reedsolomon v1.9.9 10 | github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect 11 | github.com/ugorji/go/codec v1.1.13 12 | github.com/vzex/internal v0.0.0-20171013154326-dfb733a0d236 // indirect 13 | github.com/vzex/mathutil v0.0.0-20170925121414-53c70789c7ff // indirect 14 | github.com/vzex/zappy v0.0.0-20171014114341-f5f547bd8793 15 | ) 16 | -------------------------------------------------------------------------------- /forward-core/ikcp/ikcp_h.go: -------------------------------------------------------------------------------- 1 | package ikcp 2 | import "container/list" 3 | //===================================================================== 4 | // 5 | // KCP - A Better ARQ Protocol Implementation 6 | // skywind3000 (at) gmail.com, 2010-2011 7 | // 8 | // Features: 9 | // + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. 10 | // + Maximum RTT reduce three times vs tcp. 11 | // + Lightweight, distributed as a single source file. 12 | // 13 | //===================================================================== 14 | //--------------------------------------------------------------------- 15 | // IKCPCB 16 | //--------------------------------------------------------------------- 17 | type IKCPCB struct { 18 | conv, mtu, mss, state uint32 19 | snd_una, snd_nxt, rcv_nxt uint32 20 | ts_recent, ts_lastack, ssthresh uint32 21 | rx_rttval, rx_srtt, rx_rto, rx_minrto uint32 22 | snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe uint32 23 | current, interval, ts_flush, xmit uint32 24 | nrcv_buf, nsnd_buf uint32 25 | nrcv_que, nsnd_que uint32 26 | nodelay, updated uint32 27 | ts_probe, probe_wait uint32 28 | dead_link, incr uint32 29 | snd_queue, rcv_queue, snd_buf, rcv_buf *list.List 30 | acklist []uint32 31 | ackcount uint32 32 | ackblock uint32 33 | user interface{} 34 | buffer []byte 35 | fastresend int32 36 | nocwnd int32 37 | logmask int32 38 | writelog func (log []byte, kcp *Ikcpcb, user []byte) 39 | 40 | Output func (buf []byte, _len int32, kcp *Ikcpcb, user interface{}) (int32) 41 | } 42 | 43 | 44 | type Ikcpcb struct {IKCPCB} 45 | 46 | const IKCP_LOG_OUTPUT = 1 47 | const IKCP_LOG_INPUT = 2 48 | const IKCP_LOG_SEND = 4 49 | const IKCP_LOG_RECV = 8 50 | const IKCP_LOG_IN_DATA = 16 51 | const IKCP_LOG_IN_ACK = 32 52 | const IKCP_LOG_IN_PROBE = 64 53 | const IKCP_LOG_IN_WIN = 128 54 | const IKCP_LOG_OUT_DATA =256 55 | const IKCP_LOG_OUT_ACK = 512 56 | const IKCP_LOG_OUT_PROBE = 1024 57 | const IKCP_LOG_OUT_WINS = 2048 58 | -------------------------------------------------------------------------------- /forward-core/ikcp/ikcp_test.go: -------------------------------------------------------------------------------- 1 | package ikcp 2 | import "encoding/binary" 3 | import "bytes" 4 | import "time" 5 | import "fmt" 6 | import "testing" 7 | //===================================================================== 8 | //===================================================================== 9 | 10 | // 模拟网络 11 | var vnet *LatencySimulator 12 | 13 | // 模拟网络:模拟发送一个 udp包 14 | func udp_output(buf []byte, _len int32, kcp *Ikcpcb, user interface{}) int32 { 15 | arr := (user).([]byte) 16 | var id uint32 = uint32(arr[0]) 17 | //println("send!!!!", id, _len) 18 | if vnet.send(int(id), buf, int(_len)) != 1 { 19 | //println("wocao !!!", id, _len) 20 | } 21 | return 0 22 | } 23 | 24 | // 测试用例 25 | func test(mode int) { 26 | // 创建模拟网络:丢包率10%,Rtt 60ms~125ms 27 | vnet = &LatencySimulator{} 28 | vnet.Init(10, 60, 125, 1000) 29 | 30 | // 创建两个端点的 kcp对象,第一个参数 conv是会话编号,同一个会话需要相同 31 | // 最后一个是 user参数,用来传递标识 32 | a := []byte {0} 33 | b := []byte {1} 34 | kcp1 := Ikcp_create(0x11223344, a) 35 | kcp2 := Ikcp_create(0x11223344, b) 36 | 37 | // 设置kcp的下层输出,这里为 udp_output,模拟udp网络输出函数 38 | kcp1.Output = udp_output 39 | kcp2.Output = udp_output 40 | 41 | current := uint32(iclock()) 42 | slap := current + 20 43 | index := 0 44 | next := 0 45 | var sumrtt uint32 = 0 46 | count := 0 47 | maxrtt := 0 48 | 49 | // 配置窗口大小:平均延迟200ms,每20ms发送一个包, 50 | // 而考虑到丢包重发,设置最大收发窗口为128 51 | Ikcp_wndsize(kcp1, 128, 128) 52 | Ikcp_wndsize(kcp2, 128, 128) 53 | 54 | // 判断测试用例的模式 55 | if (mode == 0) { 56 | // 默认模式 57 | Ikcp_nodelay(kcp1, 0, 10, 0, 0) 58 | Ikcp_nodelay(kcp2, 0, 10, 0, 0) 59 | } else if (mode == 1) { 60 | // 普通模式,关闭流控等 61 | Ikcp_nodelay(kcp1, 0, 10, 0, 1) 62 | Ikcp_nodelay(kcp2, 0, 10, 0, 1) 63 | } else { 64 | // 启动快速模式 65 | // 第二个参数 nodelay-启用以后若干常规加速将启动 66 | // 第三个参数 interval为内部处理时钟,默认设置为 10ms 67 | // 第四个参数 resend为快速重传指标,设置为2 68 | // 第五个参数 为是否禁用常规流控,这里禁止 69 | Ikcp_nodelay(kcp1, 1, 10, 2, 1) 70 | Ikcp_nodelay(kcp2, 1, 10, 2, 1) 71 | } 72 | 73 | 74 | var buffer []byte = make([]byte, 2000) 75 | var hr int32 76 | 77 | ts1 := iclock() 78 | 79 | for { 80 | time.Sleep(10* time.Millisecond) 81 | current = uint32(iclock()) 82 | Ikcp_update(kcp1,uint32(iclock())) 83 | Ikcp_update(kcp2, uint32(iclock())) 84 | 85 | // 每隔 20ms,kcp1发送数据 86 | for ; current >= slap; slap += 20 { 87 | buf := new(bytes.Buffer) 88 | binary.Write(buf, binary.LittleEndian, uint32(index)) 89 | index++ 90 | binary.Write(buf, binary.LittleEndian, uint64(current)) 91 | // 发送上层协议包 92 | Ikcp_send(kcp1, buf.Bytes(), 8) 93 | //println("now", iclock()) 94 | } 95 | 96 | // 处理虚拟网络:检测是否有udp包从p1->p2 97 | for { 98 | hr = vnet.recv(1, buffer, 2000) 99 | if (hr < 0) { 100 | break 101 | } 102 | // 如果 p2收到udp,则作为下层协议输入到kcp2 103 | Ikcp_input(kcp2, buffer, int(hr)) 104 | } 105 | 106 | // 处理虚拟网络:检测是否有udp包从p2->p1 107 | for { 108 | hr = vnet.recv(0, buffer, 2000) 109 | if (hr < 0) { break } 110 | // 如果 p1收到udp,则作为下层协议输入到kcp1 111 | Ikcp_input(kcp1, buffer, int(hr)) 112 | //println("@@@@", hr, r) 113 | } 114 | 115 | // kcp2接收到任何包都返回回去 116 | for { 117 | hr = Ikcp_recv(kcp2, buffer, 10) 118 | // 没有收到包就退出 119 | if (hr < 0) { break } 120 | // 如果收到包就回射 121 | buf := bytes.NewReader(buffer) 122 | var sn uint32 123 | binary.Read(buf, binary.LittleEndian, &sn) 124 | Ikcp_send(kcp2, buffer, int(hr)) 125 | } 126 | 127 | // kcp1收到kcp2的回射数据 128 | for { 129 | hr = Ikcp_recv(kcp1, buffer, 10) 130 | buf := bytes.NewReader(buffer) 131 | // 没有收到包就退出 132 | if (hr < 0) { break } 133 | var sn uint32 134 | var ts, rtt uint32 135 | binary.Read(buf, binary.LittleEndian, &sn) 136 | binary.Read(buf, binary.LittleEndian, &ts) 137 | rtt = uint32(current) - ts 138 | 139 | if (sn != uint32(next)) { 140 | // 如果收到的包不连续 141 | //for i:=0;i<8 ;i++ { 142 | //println("---", i, buffer[i]) 143 | //} 144 | println("ERROR sn ", count, "<->", next, sn) 145 | return 146 | } 147 | 148 | next++ 149 | sumrtt += rtt 150 | count++ 151 | if (rtt > uint32(maxrtt)) { maxrtt = int(rtt) } 152 | 153 | println("[RECV] mode=", mode, " sn=",sn, " rtt=", rtt) 154 | } 155 | if (next > 100) { break } 156 | } 157 | 158 | ts1 = iclock() - ts1 159 | 160 | names := []string{ "default", "normal", "fast" } 161 | fmt.Printf("%s mode result (%dms):\n", names[mode], ts1) 162 | fmt.Printf("avgrtt=%d maxrtt=%d\n", int(sumrtt / uint32(count)), maxrtt) 163 | } 164 | 165 | func TestNetwork(t *testing.T) { 166 | test(0); // 默认模式,类似 TCP:正常模式,无快速重传,常规流控 167 | test(1); // 普通模式,关闭流控等 168 | test(2); // 快速模式,所有开关都打开,且关闭流控 169 | } 170 | 171 | /* 172 | default mode result (20917ms): 173 | avgrtt=740 maxrtt=1507 174 | 175 | normal mode result (20131ms): 176 | avgrtt=156 maxrtt=571 177 | 178 | fast mode result (20207ms): 179 | avgrtt=138 maxrtt=392 180 | */ 181 | 182 | -------------------------------------------------------------------------------- /forward-core/ikcp/ikcp_test_h.go: -------------------------------------------------------------------------------- 1 | package ikcp 2 | import "container/list" 3 | import "math/rand" 4 | import "time" 5 | 6 | func iclock() int32 { 7 | return int32((time.Now().UnixNano()/1000000) & 0xffffffff) 8 | } 9 | type DelayPacket struct { 10 | _ptr []byte 11 | _size int 12 | _ts int32 13 | } 14 | 15 | func (p *DelayPacket) Init(size int, src []byte) { 16 | p._ptr = make([]byte, size) 17 | p._size = size 18 | copy(p._ptr, src[:size]) 19 | } 20 | 21 | func (p *DelayPacket) ptr() []byte { return p._ptr } 22 | func (p *DelayPacket) size() int { return p._size } 23 | func (p *DelayPacket) ts() int32 { return p._ts } 24 | func (p *DelayPacket) setts(ts int32) { p._ts = ts} 25 | 26 | type DelayTunnel struct {*list.List} 27 | type Random *rand.Rand 28 | type LatencySimulator struct { 29 | current int32 30 | lostrate, rttmin, rttmax, nmax int 31 | p12 DelayTunnel 32 | p21 DelayTunnel 33 | r12 *rand.Rand 34 | r21 *rand.Rand 35 | } 36 | 37 | // lostrate: 往返一周丢包率的百分比,默认 10% 38 | // rttmin:rtt最小值,默认 60 39 | // rttmax:rtt最大值,默认 125 40 | //func (p *LatencySimulator)Init(int lostrate = 10, int rttmin = 60, int rttmax = 125, int nmax = 1000): 41 | func (p *LatencySimulator)Init(lostrate, rttmin ,rttmax, nmax int) { 42 | p.r12 = rand.New(rand.NewSource(9)) 43 | p.r21 = rand.New(rand.NewSource(99)) 44 | p.p12 = DelayTunnel{list.New()} 45 | p.p21 = DelayTunnel{list.New()} 46 | p.current = iclock() 47 | p.lostrate = lostrate / 2; // 上面数据是往返丢包率,单程除以2 48 | p.rttmin = rttmin / 2 49 | p.rttmax = rttmax / 2 50 | p.nmax = nmax 51 | } 52 | // 发送数据 53 | // peer - 端点0/1,从0发送,从1接收;从1发送从0接收 54 | func (p *LatencySimulator) send(peer int, data []byte, size int) int { 55 | rnd := 0 56 | if (peer == 0) { 57 | rnd = p.r12.Intn(100) 58 | } else { 59 | rnd = p.r21.Intn(100) 60 | } 61 | //println("!!!!!!!!!!!!!!!!!!!!", rnd, p.lostrate, peer) 62 | if (rnd < p.lostrate) { return 0} 63 | pkt := &DelayPacket{} 64 | pkt.Init(size, data) 65 | p.current = iclock() 66 | delay := p.rttmin 67 | if (p.rttmax > p.rttmin) { 68 | delay += rand.Int() % (p.rttmax - p.rttmin) 69 | } 70 | pkt.setts(p.current + int32(delay)) 71 | if (peer == 0) { 72 | p.p12.PushBack(pkt) 73 | } else { 74 | p.p21.PushBack(pkt) 75 | } 76 | return 1 77 | } 78 | 79 | // 接收数据 80 | func (p *LatencySimulator)recv(peer int, data []byte, maxsize int) int32 { 81 | var it *list.Element 82 | if (peer == 0) { 83 | it = p.p21.Front() 84 | if (p.p21.Len() == 0){ return -1} 85 | } else { 86 | it = p.p12.Front() 87 | if (p.p12.Len() == 0){ return -1} 88 | } 89 | pkt := it.Value.(*DelayPacket) 90 | p.current = iclock() 91 | if (p.current < pkt.ts()) { return -2 } 92 | if (maxsize < pkt.size()) { return -3 } 93 | if (peer == 0) { 94 | p.p21.Remove(it) 95 | } else { 96 | p.p12.Remove(it) 97 | } 98 | maxsize = pkt.size() 99 | copy(data, pkt.ptr()[:maxsize]) 100 | return int32(maxsize) 101 | } 102 | -------------------------------------------------------------------------------- /forward-server/Controllers/BaseCtrl/ApiCtrl.go: -------------------------------------------------------------------------------- 1 | package BaseCtrl 2 | 3 | import ( 4 | "forward-core/NetUtils" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/logs" 7 | ) 8 | 9 | type ApiCtrl struct { 10 | beego.Controller 11 | } 12 | 13 | var apiAuth = beego.AppConfig.String("api.auth") 14 | 15 | func (c *ApiCtrl) Prepare() { 16 | reqUrl := c.Ctx.Request.RequestURI 17 | userIp := NetUtils.GetIP(&c.Controller) 18 | 19 | logs.Debug("执行Prepare,当前reqUrl:", reqUrl, " userIp:", userIp) 20 | 21 | //校验鉴权参数 22 | auth := c.GetString("auth") 23 | 24 | if auth != apiAuth { 25 | //logs.Error("apiAuth验证失败:", auth) 26 | //c.Ctx.Redirect(302, "/apiAuthFail") 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /forward-server/Controllers/BaseCtrl/ConsoleCtrl.go: -------------------------------------------------------------------------------- 1 | package BaseCtrl 2 | 3 | import ( 4 | "forward-core/Models" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/logs" 7 | ) 8 | 9 | var ( 10 | ConsoleLoginUrl string = "/login" 11 | ) 12 | 13 | type ConsoleCtrl struct { 14 | beego.Controller 15 | LoginUser *Models.LoginUser 16 | } 17 | 18 | func (c *ConsoleCtrl) Prepare() { 19 | reqUrl := c.Ctx.Request.RequestURI 20 | logs.Debug("执行Prepare,当前reqUrl:", reqUrl) 21 | 22 | if ConsoleLoginUrl == reqUrl { 23 | //如果是登录地址,则不校验 24 | return 25 | } 26 | 27 | //开始访问每个action前,执行登录和权限检查 28 | userInfo := c.GetUserInfo() 29 | 30 | if userInfo == nil { 31 | //未登录 32 | c.Ctx.Redirect(302, ConsoleLoginUrl) 33 | } 34 | 35 | c.LoginUser = userInfo 36 | 37 | } 38 | 39 | //判断用户是否登录. 40 | func (c *ConsoleCtrl) isUserLoggedIn() bool { 41 | return c.LoginUser != nil && c.LoginUser.UserId > 0 42 | } 43 | 44 | func (c *ConsoleCtrl) StoreUserInfo(loginUser *Models.LoginUser) { 45 | c.SetSession("userInfo", loginUser) 46 | } 47 | 48 | func (c *ConsoleCtrl) GetUserInfo() *Models.LoginUser { 49 | 50 | userInfo := c.GetSession("userInfo") 51 | if userInfo == nil { 52 | return nil 53 | } 54 | return userInfo.(*Models.LoginUser) 55 | 56 | } 57 | 58 | func (c *ConsoleCtrl) ClearUserInfo() { 59 | 60 | c.DelSession("userInfo") 61 | c.LoginUser = nil 62 | 63 | } -------------------------------------------------------------------------------- /forward-server/Controllers/BaseCtrl/WebCtrl.go: -------------------------------------------------------------------------------- 1 | package BaseCtrl 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/astaxie/beego/logs" 6 | ) 7 | 8 | type WebCtrl struct { 9 | beego.Controller 10 | } 11 | 12 | func (c *WebCtrl) Prepare() { 13 | reqUrl := c.Ctx.Request.RequestURI 14 | logs.Debug("执行Prepare,当前reqUrl:", reqUrl) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /forward-server/Controllers/DefaultCtrl.go: -------------------------------------------------------------------------------- 1 | package Controllers 2 | 3 | import ( 4 | "forward-core/Models" 5 | "forward-server/Controllers/BaseCtrl" 6 | ) 7 | 8 | type DefaultCtrl struct { 9 | BaseCtrl.WebCtrl 10 | } 11 | 12 | 13 | // @router / [get] 14 | func (c *DefaultCtrl) Default() { 15 | 16 | c.Ctx.Redirect(302, "/login") 17 | 18 | //c.Data["currentTime"] = time.Now() 19 | //c.TplName = "index.html" 20 | } 21 | 22 | // @router /apiAuthFail [get] 23 | func (c *DefaultCtrl) ApiAuthFail() { 24 | 25 | c.Data["json"] = Models.FuncResult{Code: 1, Msg: "ApiAuth鉴权失败"} 26 | 27 | c.ServeJSON() 28 | 29 | } -------------------------------------------------------------------------------- /forward-server/Controllers/LoginCtrl.go: -------------------------------------------------------------------------------- 1 | package Controllers 2 | 3 | import ( 4 | "forward-core/Models" 5 | "forward-core/NetUtils" 6 | "forward-core/Utils" 7 | "forward-server/Controllers/BaseCtrl" 8 | "forward-server/Service" 9 | "github.com/astaxie/beego/logs" 10 | ) 11 | 12 | type LoginCtrl struct { 13 | BaseCtrl.ConsoleCtrl 14 | } 15 | 16 | // @router /logout 17 | func (c *LoginCtrl) Logout() { 18 | c.ClearUserInfo() 19 | c.Ctx.Redirect(302, "/login") 20 | 21 | } 22 | 23 | // @router /login [get] 24 | func (c *LoginCtrl) Login() { 25 | 26 | c.TplName = "login.html" 27 | 28 | } 29 | 30 | // @router /login [post] 31 | func (c *LoginCtrl) DoLogin() { 32 | userName := c.GetString("userName") 33 | passWord := c.GetString("passWord") 34 | 35 | sysUser := Service.SysDataS.GetSysUserByName(userName) 36 | if sysUser == nil { 37 | logs.Debug("用户不存在") 38 | c.Ctx.Redirect(302, "/login") 39 | return 40 | } 41 | 42 | descryptPwd := Utils.GetMd5(passWord) 43 | logs.Debug("存储的密码:", sysUser.PassWord, " 输入的密码:", descryptPwd) 44 | if sysUser.PassWord == descryptPwd { 45 | logs.Info("用户登录:", userName, " IP:", NetUtils.GetIP(&c.Controller)) 46 | loginUser := new(Models.LoginUser) 47 | loginUser.UserId = 1 48 | loginUser.UserName = userName 49 | 50 | c.SetSession("userInfo", loginUser) 51 | c.Ctx.Redirect(302, "/u/main") 52 | } else { 53 | logs.Debug("用户登录失败") 54 | c.Ctx.Redirect(302, "/login") 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /forward-server/Controllers/RestApiCtrl.go: -------------------------------------------------------------------------------- 1 | package Controllers 2 | 3 | import ( 4 | "forward-core/Models" 5 | "forward-core/Utils" 6 | "forward-server/Controllers/BaseCtrl" 7 | "forward-server/Service" 8 | "runtime" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type RestApiCtrl struct { 14 | BaseCtrl.ApiCtrl 15 | } 16 | 17 | // @router /ServerSummary [get,post] 18 | func (c *RestApiCtrl) ServerSummary() { 19 | obj := make(map[string]interface{}) 20 | obj["runtime_NumGoroutine"] = runtime.NumGoroutine() 21 | obj["runtime_GOOS"] = runtime.GOOS 22 | obj["runtime_GOARCH"] = runtime.GOARCH 23 | obj["server_Time"] = time.Now() 24 | 25 | obj["forwardList"] = Service.ForWardServ.FindAllForward() 26 | 27 | c.Data["json"] = obj 28 | 29 | c.ServeJSON() 30 | 31 | } 32 | 33 | // @router /OpenForward [get,post] 34 | func (c *RestApiCtrl) OpenForward() { 35 | 36 | fromAddr := c.GetString("fromAddr") 37 | toAddr := c.GetString("toAddr") 38 | protocol := c.GetString("protocol", "TCP") 39 | 40 | entity := Service.SysDataS.ChkPortForwardByApi(fromAddr, protocol, toAddr) 41 | if entity == nil { 42 | var err error 43 | entity, err = Service.SysDataS.SavePortForwardByApi(fromAddr, protocol, toAddr) 44 | if err != nil { 45 | c.Data["json"] = Models.FuncResult{Code: 1, Msg: "保存端口配置失败"} 46 | c.ServeJSON() 47 | return 48 | } 49 | } 50 | //测试 51 | //http://127.0.0.1:8000/api/v1/OpenForward?auth=26CCD056107481F45D1AC805A24A9E59&fromAddr=:8010&toAddr=127.0.0.1:3306 52 | resultChan := make(chan Models.FuncResult) 53 | config := Service.SysDataS.ToForwardConfig(entity) 54 | go Service.ForWardServ.OpenForward(config, resultChan) 55 | 56 | c.Data["json"] = <-resultChan 57 | 58 | c.ServeJSON() 59 | 60 | } 61 | 62 | // @router /CloseForward [get,post] 63 | func (c *RestApiCtrl) CloseForward() { 64 | 65 | fromAddr := c.GetString("fromAddr") 66 | toAddr := c.GetString("toAddr") 67 | protocol := c.GetString("protocol", "TCP") 68 | //fType, _ := c.GetInt("fType", 0) 69 | 70 | //测试 71 | //http://127.0.0.1:8000/api/v1/CloseForward?auth=26CCD056107481F45D1AC805A24A9E59&fromAddr=:8010&toAddr=127.0.0.1:3306 72 | 73 | config := new(Models.ForwardConfig) 74 | config.RuleId = 0 75 | config.Name = "" 76 | config.Protocol = protocol 77 | config.SrcAddr = strings.Split(fromAddr, ":")[0] 78 | config.SrcPort = Utils.ToInt(strings.Split(fromAddr, ":")[1]) 79 | config.DestAddr = strings.Split(toAddr, ":")[0] 80 | config.DestPort = Utils.ToInt(strings.Split(toAddr, ":")[1]) 81 | config.Status = 0 82 | Service.ForWardServ.CloseForward(config) 83 | 84 | c.Data["json"] = Models.FuncResult{Code: 0, Msg: ""} 85 | 86 | c.ServeJSON() 87 | } 88 | -------------------------------------------------------------------------------- /forward-server/Controllers/UCenterCtrl.go: -------------------------------------------------------------------------------- 1 | package Controllers 2 | 3 | import ( 4 | "forward-core/Models" 5 | "forward-core/Utils" 6 | "forward-server/Controllers/BaseCtrl" 7 | "forward-server/Service" 8 | "runtime" 9 | "time" 10 | ) 11 | 12 | type UCenterCtrl struct { 13 | BaseCtrl.ConsoleCtrl 14 | } 15 | 16 | // @router /u/main [get] 17 | func (c *UCenterCtrl) Main() { 18 | 19 | c.Layout = "ucenter/layout.html" 20 | c.TplName = "ucenter/main.html" 21 | 22 | } 23 | 24 | // @router /u/index [get] 25 | func (c *UCenterCtrl) Index() { 26 | 27 | c.Data["runtime_NumCPU"] = runtime.NumCPU() 28 | c.Data["runtime_GOOS"] = runtime.GOOS 29 | c.Data["runtime_GOARCH"] = runtime.GOARCH 30 | c.Data["runtime_NumGoroutine"] = runtime.NumGoroutine() 31 | c.Data["server_Time"] = time.Now() 32 | 33 | c.TplName = "ucenter/index.html" 34 | } 35 | 36 | // @router /u/getServerTime [post] 37 | func (c *UCenterCtrl) GetServerTime(){ 38 | 39 | c.Data["json"] = Utils.GetCurrentTime() 40 | 41 | c.ServeJSON() 42 | 43 | } 44 | 45 | // @router /u/changePwd [get] 46 | func (c *UCenterCtrl) ChangePwd() { 47 | 48 | c.TplName = "ucenter/changePwd.html" 49 | } 50 | 51 | // @router /u/doChangePwd [post] 52 | func (c *UCenterCtrl) DoChangePwd() { 53 | userInfo := c.GetUserInfo() 54 | 55 | passWord := c.GetString("passWord") 56 | passWord2 := c.GetString("passWord2") 57 | 58 | if Utils.IsEmpty(passWord) { 59 | c.Data["json"] = Models.FuncResult{Code: 1, Msg: "密码不能为空"} 60 | c.ServeJSON() 61 | return 62 | } 63 | 64 | if passWord != passWord2 { 65 | c.Data["json"] = Models.FuncResult{Code: 1, Msg: "两次输入的密码不一致"} 66 | c.ServeJSON() 67 | return 68 | } 69 | 70 | err := Service.SysDataS.ChangeUserPwd(userInfo.UserId, passWord) 71 | if err == nil { 72 | c.Data["json"] = Models.FuncResult{Code: 0, Msg: "密码修改成功"} 73 | } else { 74 | c.Data["json"] = Models.FuncResult{Code: 1, Msg: err.Error()} 75 | } 76 | 77 | c.ServeJSON() 78 | 79 | } 80 | -------------------------------------------------------------------------------- /forward-server/Service/ConsoleServer.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import ( 4 | "encoding/gob" 5 | "forward-core/Models" 6 | "github.com/astaxie/beego" 7 | "github.com/astaxie/beego/logs" 8 | ) 9 | 10 | type ConsoleServer struct { 11 | 12 | } 13 | 14 | func (_self *ConsoleServer) StartHttpServer() { 15 | 16 | //开启seesion支持,默认使用的存储引擎为:memory 17 | beego.BConfig.WebConfig.Session.SessionOn = true 18 | beego.BConfig.WebConfig.Session.SessionName = "sessionID" 19 | beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 20 | 21 | //默认static目录是可以直接访问的,其它目录需要单独指定 22 | beego.SetStaticPath("/theme", "theme") 23 | 24 | // 25 | gob.Register(&Models.LoginUser{}) 26 | 27 | logs.Debug("Http 服务启动...") 28 | 29 | //启动应用 30 | beego.Run() 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /forward-server/Service/ForWardJob.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import ( 4 | "fmt" 5 | "forward-core/Constant" 6 | "forward-core/Models" 7 | "forward-core/NetUtils" 8 | "forward-core/Utils" 9 | "io" 10 | "net" 11 | "sync" 12 | "time" 13 | 14 | "github.com/astaxie/beego/logs" 15 | ) 16 | 17 | type ForWardJob struct { 18 | Config *Models.ForwardConfig 19 | ClientMap map[string]*ForWardClient 20 | ClientMapLock sync.Mutex 21 | Status byte 22 | PortListener net.Listener 23 | UdpForwardJob *UdpForward 24 | } 25 | 26 | func (_self *ForWardJob) StartJob(result chan Models.FuncResult) { 27 | 28 | sourceAddr := fmt.Sprint(_self.Config.SrcAddr, ":", _self.Config.SrcPort) 29 | destAddr := fmt.Sprint(_self.Config.DestAddr, ":", _self.Config.DestPort) 30 | 31 | resultData := &Models.FuncResult{Code: 0, Msg: "success"} 32 | var err error 33 | if _self.IsUdpJob() { 34 | //_self.PortListener, err = NetUtils.NewKCP(sourceAddr, Common.DefaultKcpSetting()) 35 | //_self.UdpForwardJob.UdpListenerConn, err = NetUtils.NewUDP(sourceAddr) 36 | 37 | err = _self.UdpForwardJob.DoUdpForward(sourceAddr, destAddr) 38 | 39 | if err != nil { 40 | logs.Error("启动UDP监听 ", sourceAddr, " 出错:", err) 41 | resultData.Code = 1 42 | resultData.Msg = fmt.Sprint("启动UDP监听 ", sourceAddr, " 出错:", err) 43 | result <- *resultData 44 | return 45 | } 46 | 47 | _self.Status = Constant.RunStatus_Running 48 | logs.Debug("启动UDP端口转发,从 ", sourceAddr, " 到 ", destAddr) 49 | result <- *resultData 50 | 51 | } else { 52 | _self.PortListener, err = NetUtils.NewTCP(sourceAddr) 53 | 54 | if err != nil { 55 | logs.Error("启动监听 ", sourceAddr, " 出错:", err) 56 | resultData.Code = 1 57 | resultData.Msg = fmt.Sprint("启动监听 ", sourceAddr, " 出错:", err) 58 | result <- *resultData 59 | return 60 | } 61 | 62 | _self.Status = Constant.RunStatus_Running 63 | logs.Debug("启动端口转发,从 ", sourceAddr, " 到 ", destAddr) 64 | result <- *resultData 65 | 66 | _self.doTcpForward(destAddr) 67 | 68 | } 69 | 70 | } 71 | 72 | func (_self *ForWardJob) doTcpForward(destAddr string) { 73 | 74 | for { 75 | realClientConn, err := _self.PortListener.Accept() 76 | if err != nil { 77 | logs.Error("Forward Accept err:", err.Error()) 78 | logs.Error(fmt.Sprint("转发出现异常:", _self.Config.SrcAddr, ":", _self.Config.SrcPort, "->", destAddr)) 79 | _self.StopJob() 80 | break 81 | } 82 | 83 | if ForWardDebug == true { 84 | logs.Info("新用户 ", realClientConn.RemoteAddr().String(), " 数据转发规则:", fmt.Sprint(_self.Config.SrcAddr, ":", _self.Config.SrcPort), "->", destAddr) 85 | } 86 | 87 | var destConn net.Conn 88 | if _self.Config.Protocol == "UDP" { 89 | //destConn, err = Common.DialKcpTimeout(destAddr, 100) 90 | destConn, err = net.DialTimeout("UDP", destAddr, 30*time.Second) 91 | } else { 92 | destConn, err = net.DialTimeout("tcp", destAddr, 30*time.Second) 93 | } 94 | 95 | if err != nil { 96 | if ForWardDebug == true { 97 | logs.Warn("转发出现异常 Forward to Dest Addr err:", err.Error()) 98 | } 99 | 100 | //break 101 | continue 102 | 103 | } 104 | 105 | forwardClient := &ForWardClient{realClientConn, destConn, nil, _self.ClosedCallBack} 106 | 107 | if Utils.IsNotEmpty(_self.Config.Others) { 108 | var dispatchConns []io.Writer 109 | //分发方式 110 | dispatchTargets := Utils.Split(_self.Config.Others, ";") 111 | 112 | for _, dispatchTarget := range dispatchTargets { 113 | logs.Debug("分发到:", dispatchTarget) 114 | dispatchTargetConn, err := net.DialTimeout("tcp", dispatchTarget, 30*time.Second) 115 | if err == nil { 116 | dispatchConns = append(dispatchConns, dispatchTargetConn) 117 | } 118 | 119 | } 120 | 121 | forwardClient.DispatchConns = dispatchConns 122 | 123 | go forwardClient.DispatchData(dispatchConns) 124 | } else { 125 | go forwardClient.StartForward() 126 | } 127 | 128 | _self.RegistryClient(_self.GetClientId(realClientConn), forwardClient) 129 | //_self.RegistryClient(fmt.Sprint(sourceAddr, "_", "TCP", "_", id), forwardClient) 130 | 131 | } 132 | } 133 | 134 | func (_self *ForWardJob) ClosedCallBack(srcConn net.Conn, destConn net.Conn) { 135 | 136 | _self.UnRegistryClient(_self.GetClientId(srcConn)) 137 | } 138 | 139 | func (_self *ForWardJob) GetClientId(conn net.Conn) string { 140 | return conn.RemoteAddr().String() 141 | } 142 | 143 | func (_self *ForWardJob) RegistryClient(srcAddr string, forwardClient *ForWardClient) { 144 | _self.ClientMapLock.Lock() 145 | defer _self.ClientMapLock.Unlock() 146 | 147 | _self.ClientMap[srcAddr] = forwardClient 148 | 149 | } 150 | 151 | func (_self *ForWardJob) UnRegistryClient(srcAddr string) { 152 | _self.ClientMapLock.Lock() 153 | defer _self.ClientMapLock.Unlock() 154 | 155 | delete(_self.ClientMap, srcAddr) 156 | if ForWardDebug == true { 157 | logs.Debug("UnRegistryClient srcAddr: ", srcAddr) 158 | } 159 | 160 | } 161 | 162 | func (_self *ForWardJob) IsJobRunning() bool { 163 | 164 | return _self.Status == Constant.RunStatus_Running 165 | 166 | } 167 | 168 | func (_self *ForWardJob) IsUdpJob() bool { 169 | return Utils.ToUpper(_self.Config.Protocol) == "UDP" 170 | } 171 | 172 | func (_self *ForWardJob) StopJob() { 173 | 174 | if _self.IsUdpJob() { 175 | _self.stopUdpJob() 176 | } else { 177 | _self.stopTcpJob() 178 | } 179 | 180 | _self.Status = Constant.RunStatus_Stoped 181 | } 182 | 183 | func (_self *ForWardJob) stopTcpJob() { 184 | 185 | _self.PortListener.Close() 186 | 187 | for srcAddr, client := range _self.ClientMap { 188 | if ForWardDebug == true { 189 | logs.Debug("停止真实用户连接:", srcAddr) 190 | } 191 | client.StopForward() 192 | } 193 | 194 | _self.ClientMap = nil 195 | } 196 | 197 | func (_self *ForWardJob) stopUdpJob() { 198 | 199 | _self.UdpForwardJob.Close() 200 | } 201 | -------------------------------------------------------------------------------- /forward-server/Service/ForwardClient.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "github.com/astaxie/beego/logs" 8 | ) 9 | 10 | type ForWardClient struct { 11 | SrcConn net.Conn 12 | DestConn net.Conn 13 | DispatchConns []io.Writer 14 | ClosedCallBack func(srcConn net.Conn, destConn net.Conn) 15 | } 16 | 17 | func (_self *ForWardClient) StartForward() { 18 | 19 | //io.Copy(dst, src) 20 | 21 | go func() { 22 | _, err := io.Copy(_self.DestConn, _self.SrcConn) 23 | if err != nil { 24 | //logs.Error("客户端来源数据转发到目标端口异常:", err) 25 | _self.StopForward() 26 | } 27 | }() 28 | 29 | go func() { 30 | _, err := io.Copy(_self.SrcConn, _self.DestConn) 31 | if err != nil { 32 | //logs.Error("目标端口返回响应数据异常:", err) 33 | _self.StopForward() 34 | } 35 | }() 36 | } 37 | 38 | func (_self *ForWardClient) DispatchData(dispatchConns []io.Writer) { 39 | //将数据克隆给其它端口 40 | go func() { 41 | mWriter := io.MultiWriter(append(dispatchConns, _self.DestConn)...) 42 | _, err := io.Copy(mWriter, _self.SrcConn) 43 | if err != nil { 44 | logs.Error("Dispatch网络连接异常:", err) 45 | _self.StopForward() 46 | } 47 | }() 48 | 49 | go func() { 50 | _, err := io.Copy(_self.SrcConn, _self.DestConn) 51 | if err != nil { 52 | //logs.Error("目标端口返回响应数据异常:", err) 53 | _self.StopForward() 54 | } 55 | }() 56 | } 57 | 58 | func (_self *ForWardClient) StopForward() { 59 | logs.Debug("关闭一个连接:", _self.SrcConn.RemoteAddr(), " on ", _self.SrcConn.LocalAddr()) 60 | _self.SrcConn.Close() 61 | _self.DestConn.Close() 62 | _self.ClosedCallBack(_self.SrcConn, _self.DestConn) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /forward-server/Service/ForwardServer.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import ( 4 | "fmt" 5 | "forward-core/Constant" 6 | "forward-core/Models" 7 | "net" 8 | "sync" 9 | 10 | "github.com/astaxie/beego/logs" 11 | ) 12 | 13 | type ForWardServer struct { 14 | JobMap map[string]*ForWardJob 15 | JobMapLock sync.Mutex 16 | } 17 | 18 | func NewForWardServer() *ForWardServer { 19 | return &ForWardServer{ 20 | JobMap: make(map[string]*ForWardJob, 200), 21 | } 22 | } 23 | 24 | func (_self *ForWardServer) FindAllForward() []*Models.ForwardInfo { 25 | var forwardList []*Models.ForwardInfo 26 | for _, forWardJob := range _self.JobMap { 27 | 28 | forwardInfo := new(Models.ForwardInfo) 29 | 30 | forwardInfo.Name = forWardJob.Config.Name 31 | forwardInfo.Status = forWardJob.Status 32 | forwardInfo.Protocol = forWardJob.Config.Protocol 33 | forwardInfo.SrcAddr = forWardJob.Config.SrcAddr 34 | forwardInfo.SrcPort = forWardJob.Config.SrcPort 35 | forwardInfo.DestAddr = forWardJob.Config.DestAddr 36 | forwardInfo.DestPort = forWardJob.Config.DestPort 37 | 38 | if forWardJob.IsUdpJob() { 39 | for key, _ := range forWardJob.UdpForwardJob.UdpConns { 40 | 41 | forwardInfo.Clients = append(forwardInfo.Clients, key) 42 | } 43 | } else { 44 | for _, client := range forWardJob.ClientMap { 45 | forwardInfo.Clients = append(forwardInfo.Clients, client.SrcConn.RemoteAddr().String()) 46 | } 47 | } 48 | 49 | forwardInfo.OnlineCount = len(forwardInfo.Clients) 50 | 51 | forwardList = append(forwardList, forwardInfo) 52 | } 53 | 54 | return forwardList 55 | } 56 | 57 | func (_self *ForWardServer) GetForwardInfo(config *Models.ForwardConfig) *Models.ForwardInfo { 58 | 59 | forwardInfo := new(Models.ForwardInfo) 60 | forWardJob := _self.GetRegistryJob(config) 61 | if forWardJob != nil { 62 | forwardInfo.Name = forWardJob.Config.Name 63 | forwardInfo.Status = forWardJob.Status 64 | forwardInfo.Protocol = forWardJob.Config.Protocol 65 | forwardInfo.SrcAddr = forWardJob.Config.SrcAddr 66 | forwardInfo.SrcPort = forWardJob.Config.SrcPort 67 | forwardInfo.DestAddr = forWardJob.Config.DestAddr 68 | forwardInfo.DestPort = forWardJob.Config.DestPort 69 | 70 | for _, client := range forWardJob.ClientMap { 71 | forwardInfo.Clients = append(forwardInfo.Clients, client.SrcConn.RemoteAddr().String()) 72 | } 73 | 74 | forwardInfo.OnlineCount = len(forwardInfo.Clients) 75 | 76 | } 77 | 78 | return forwardInfo 79 | } 80 | 81 | func (_self *ForWardServer) GetForwardJob(config *Models.ForwardConfig) *ForWardJob { 82 | return _self.GetRegistryJob(config) 83 | } 84 | 85 | func (_self *ForWardServer) OpenForward(config *Models.ForwardConfig, result chan Models.FuncResult) { 86 | hasJob := _self.GetForwardJob(config) 87 | if hasJob != nil && hasJob.Status == Constant.RunStatus_Running { 88 | resultData := &Models.FuncResult{Code: 1, Msg: "该端口转发正在执行中"} 89 | result <- *resultData 90 | return 91 | } 92 | 93 | forWardJob := new(ForWardJob) 94 | forWardJob.ClientMap = make(map[string]*ForWardClient, 500) 95 | forWardJob.Config = config 96 | forWardJob.UdpForwardJob = NewUdpForward() 97 | 98 | go forWardJob.StartJob(result) 99 | 100 | _self.RegistryJob(config, forWardJob) 101 | 102 | } 103 | 104 | func (_self *ForWardServer) GetJobKey(config *Models.ForwardConfig) string { 105 | srcAddr := fmt.Sprint(config.SrcAddr, ":", config.SrcPort) 106 | destAddr := fmt.Sprint(config.DestAddr, ":", config.DestPort) 107 | 108 | return fmt.Sprint(srcAddr, "_", config.Protocol, "_", destAddr) 109 | } 110 | 111 | func (_self *ForWardServer) GetClientId(conn net.Conn) string { 112 | return conn.RemoteAddr().String() 113 | } 114 | 115 | func (_self *ForWardServer) RegistryJob(config *Models.ForwardConfig, forWardJob *ForWardJob) { 116 | _self.JobMapLock.Lock() 117 | defer _self.JobMapLock.Unlock() 118 | 119 | _self.JobMap[_self.GetJobKey(config)] = forWardJob 120 | 121 | } 122 | 123 | func (_self *ForWardServer) UnRegistryJob(config *Models.ForwardConfig) { 124 | _self.JobMapLock.Lock() 125 | defer _self.JobMapLock.Unlock() 126 | 127 | key := _self.GetJobKey(config) 128 | delete(_self.JobMap, key) 129 | if ForWardDebug == true { 130 | logs.Debug("UnRegistryClient key: ", key) 131 | } 132 | 133 | } 134 | 135 | func (_self *ForWardServer) GetRegistryJob(config *Models.ForwardConfig) *ForWardJob { 136 | if forWardJob, ok := _self.JobMap[_self.GetJobKey(config)]; ok { 137 | return forWardJob 138 | } 139 | 140 | return nil 141 | } 142 | 143 | func (_self *ForWardServer) CloseForward(config *Models.ForwardConfig) { 144 | 145 | forWardJob := _self.GetRegistryJob(config) 146 | if forWardJob != nil { 147 | logs.Debug("停止转发,找到执行者:", _self.GetJobKey(config)) 148 | forWardJob.StopJob() 149 | _self.UnRegistryJob(config) 150 | } 151 | 152 | } 153 | 154 | func (_self *ForWardServer) CloseAllForward() { 155 | 156 | for _, forWardJob := range _self.JobMap { 157 | forWardJob.StopJob() 158 | //delete(_self.JobMap, key) 159 | _self.UnRegistryJob(forWardJob.Config) 160 | 161 | } 162 | 163 | //_self.JobMap = nil 164 | _self.JobMap = make(map[string]*ForWardJob, 200) 165 | 166 | } 167 | -------------------------------------------------------------------------------- /forward-server/Service/InitServices.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import ( 4 | "fmt" 5 | "forward-core/Models" 6 | 7 | "github.com/astaxie/beego" 8 | "github.com/astaxie/beego/orm" 9 | _ "github.com/mattn/go-sqlite3" 10 | ) 11 | 12 | var ( 13 | OrmerS orm.Ormer 14 | //ForWardServ = new(ForWardServer) 15 | ForWardServ = NewForWardServer() 16 | MagicServ = NewMagicServiceV1() 17 | //MagicServ = new(MagicServer) 18 | ConsoleServ = new(ConsoleServer) 19 | SysDataS = new(SysDataService) 20 | 21 | ForWardDebug = true 22 | ) 23 | 24 | func init() { 25 | //开启DEBUG模式,输出SQL信息 26 | orm.Debug = true 27 | 28 | //_ "github.com/mattn/go-sqlite3" 29 | orm.RegisterDriver("sqlite3", orm.DRSqlite) 30 | orm.RegisterDataBase("default", "sqlite3", "file:data/data.db?cache=shared&loc=auto") 31 | 32 | OrmerS = orm.NewOrm() 33 | OrmerS.Using("default") 34 | 35 | onstartForward := beego.AppConfig.String("onstart.forward") 36 | if onstartForward == "true" { 37 | onStartForward() 38 | } 39 | } 40 | 41 | func onStartForward() { 42 | forwards := SysDataS.GetAllPortForwardList(1) 43 | for _, entity := range forwards { 44 | resultChan := make(chan Models.FuncResult) 45 | config := SysDataS.ToForwardConfig(entity) 46 | go ForWardServ.OpenForward(config, resultChan) 47 | 48 | fmt.Println(<-resultChan) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /forward-server/Service/MagicClient.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import "net" 4 | 5 | type MagicClient struct { 6 | 7 | cid string 8 | encode, decode func([]byte) []byte 9 | authed bool 10 | conn net.Conn 11 | 12 | } -------------------------------------------------------------------------------- /forward-server/Service/MagicServer.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import "forward-core/Models" 4 | 5 | type MagicServer struct { 6 | UseUDP bool 7 | } 8 | 9 | func (_self *MagicServer) StartMagicService(netAddr string, result chan Models.FuncResult) { 10 | 11 | } -------------------------------------------------------------------------------- /forward-server/Service/UdpForward.go: -------------------------------------------------------------------------------- 1 | package Service 2 | 3 | import ( 4 | "github.com/astaxie/beego/logs" 5 | "net" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type UdpForward struct { 11 | SrcAddr *net.UDPAddr 12 | DestAddr *net.UDPAddr 13 | LClientAddr *net.UDPAddr 14 | UdpListenerConn *net.UDPConn 15 | UdpConns map[string]UdpConn 16 | UdpConnsMutex *sync.RWMutex 17 | ChkActTime time.Duration 18 | Closed bool 19 | ConnectedEvent func(addr string) 20 | DisConnectedEvent func(addr string) 21 | } 22 | 23 | type UdpConn struct { 24 | udp *net.UDPConn 25 | lastActive time.Time 26 | } 27 | 28 | const bufferSize = 4096 29 | var chkActTime = time.Minute * 1 30 | 31 | func NewUdpForward() *UdpForward { 32 | return &UdpForward{ 33 | UdpConns:make(map[string]UdpConn), 34 | UdpConnsMutex:new(sync.RWMutex), 35 | ChkActTime:chkActTime, 36 | ConnectedEvent:func(addr string) {}, 37 | DisConnectedEvent:func(addr string){}, 38 | } 39 | } 40 | 41 | func (_self *UdpForward) DoUdpForward (srcAddr string, destAddr string) error { 42 | 43 | var err error 44 | _self.SrcAddr, err = net.ResolveUDPAddr("udp", srcAddr) 45 | if err != nil { 46 | logs.Error("ResolveUDPAddr ", srcAddr, " 出错:", err) 47 | return err 48 | } 49 | 50 | _self.DestAddr, err = net.ResolveUDPAddr("udp", destAddr) 51 | if err != nil { 52 | logs.Error("ResolveUDPAddr ", destAddr, " 出错:", err) 53 | return err 54 | } 55 | 56 | _self.LClientAddr = &net.UDPAddr{ 57 | IP: _self.SrcAddr.IP, 58 | Port: 0, 59 | Zone: _self.SrcAddr.Zone, 60 | } 61 | 62 | _self.UdpListenerConn, err = net.ListenUDP("udp", _self.SrcAddr) 63 | if err != nil { 64 | logs.Error("启动UDP监听 ", srcAddr, " 出错:", err) 65 | return err 66 | } 67 | 68 | go _self.checkAlive() 69 | go _self.runForward() 70 | 71 | return nil 72 | } 73 | 74 | 75 | func (_self *UdpForward) runForward() { 76 | for { 77 | buf := make([]byte, bufferSize) 78 | n, addr, err := _self.UdpListenerConn.ReadFromUDP(buf) 79 | if err != nil { 80 | return 81 | } 82 | go _self.forwardHandler(buf[:n], addr) 83 | } 84 | } 85 | 86 | func (_self *UdpForward) forwardHandler(data []byte, addr *net.UDPAddr) { 87 | 88 | _self.UdpConnsMutex.RLock() 89 | udpConn, found := _self.UdpConns[addr.String()] 90 | _self.UdpConnsMutex.RUnlock() 91 | 92 | if found { 93 | 94 | udpConn.udp.WriteTo(data, _self.DestAddr) 95 | }else{ 96 | 97 | conn, err := net.ListenUDP("udp", _self.LClientAddr) 98 | if err != nil { 99 | logs.Error("udp-forwader: failed to dial:", err) 100 | return 101 | } 102 | 103 | _self.UdpConnsMutex.Lock() 104 | _self.UdpConns[addr.String()] = UdpConn{ 105 | udp: conn, 106 | lastActive: time.Now(), 107 | } 108 | _self.UdpConnsMutex.Unlock() 109 | 110 | _self.ConnectedEvent(addr.String()) 111 | 112 | conn.WriteTo(data, _self.DestAddr) 113 | 114 | for { 115 | buf := make([]byte, bufferSize) 116 | n, _, err := conn.ReadFromUDP(buf) 117 | if err != nil { 118 | _self.UdpConnsMutex.Lock() 119 | conn.Close() 120 | delete(_self.UdpConns, addr.String()) 121 | _self.UdpConnsMutex.Unlock() 122 | return 123 | } 124 | 125 | go func(data []byte, conn *net.UDPConn, addr *net.UDPAddr) { 126 | _self.UdpListenerConn.WriteTo(data, addr) 127 | }(buf[:n], conn, addr) 128 | } 129 | } 130 | 131 | _self.updateActiveTime(addr) 132 | } 133 | 134 | func (_self *UdpForward) updateActiveTime(addr *net.UDPAddr) { 135 | 136 | needUpdateTime := false 137 | _self.UdpConnsMutex.RLock() 138 | if _, found := _self.UdpConns[addr.String()]; found { 139 | if _self.UdpConns[addr.String()].lastActive.Before( 140 | time.Now().Add(_self.ChkActTime / 4)) { 141 | needUpdateTime = true 142 | //logs.Debug("needUpdateTime") 143 | } 144 | } 145 | _self.UdpConnsMutex.RUnlock() 146 | 147 | if needUpdateTime { 148 | _self.UdpConnsMutex.Lock() 149 | // 150 | if _, found := _self.UdpConns[addr.String()]; found { 151 | connWrapper := _self.UdpConns[addr.String()] 152 | connWrapper.lastActive = time.Now() 153 | _self.UdpConns[addr.String()] = connWrapper 154 | } 155 | _self.UdpConnsMutex.Unlock() 156 | } 157 | } 158 | 159 | func (_self *UdpForward) checkAlive() { 160 | 161 | for !_self.Closed { 162 | time.Sleep(_self.ChkActTime) 163 | var keysToDelete []string 164 | 165 | _self.UdpConnsMutex.RLock() 166 | for k, conn := range _self.UdpConns { 167 | if conn.lastActive.Before(time.Now().Add(-_self.ChkActTime)) { 168 | keysToDelete = append(keysToDelete, k) 169 | //logs.Debug("need delete udp conn") 170 | } 171 | } 172 | _self.UdpConnsMutex.RUnlock() 173 | 174 | _self.UdpConnsMutex.Lock() 175 | for _, k := range keysToDelete { 176 | _self.UdpConns[k].udp.Close() 177 | delete(_self.UdpConns, k) 178 | } 179 | _self.UdpConnsMutex.Unlock() 180 | 181 | for _, k := range keysToDelete { 182 | _self.DisConnectedEvent(k) 183 | } 184 | } 185 | 186 | } 187 | 188 | 189 | func (_self *UdpForward) Close() { 190 | _self.UdpConnsMutex.Lock() 191 | _self.Closed = true 192 | for _, conn := range _self.UdpConns { 193 | conn.udp.Close() 194 | } 195 | _self.UdpListenerConn.Close() 196 | _self.UdpConnsMutex.Unlock() 197 | } 198 | 199 | func (_self *UdpForward) GetConnsInfo() []string { 200 | _self.UdpConnsMutex.Lock() 201 | defer _self.UdpConnsMutex.Unlock() 202 | results := make([]string, 0, len(_self.UdpConns)) 203 | for key := range _self.UdpConns { 204 | results = append(results, key) 205 | } 206 | return results 207 | } -------------------------------------------------------------------------------- /forward-server/build_linux.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/build_linux.bat -------------------------------------------------------------------------------- /forward-server/build_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export GoDevWork=/Users/tavenli/Desktop/Work/port-forward-v2 4 | 5 | echo "Build For Linux..." 6 | export GOOS=linux 7 | export GOARCH=amd64 8 | export GOPATH=${GoDevWork}:${GOPATH} 9 | go build -o forward-server 10 | 11 | echo "--------- Build For Linux Success!" 12 | 13 | 14 | -------------------------------------------------------------------------------- /forward-server/build_mac.bat: -------------------------------------------------------------------------------- 1 | 2 | 3 | echo "Build For Mac..." 4 | set GOOS=darwin 5 | set GOARCH=amd64 6 | 7 | go build -o forward-server 8 | 9 | echo "--------- Build For Mac Success!" 10 | 11 | 12 | pause 13 | 14 | -------------------------------------------------------------------------------- /forward-server/build_win.bat: -------------------------------------------------------------------------------- 1 | 2 | ::set GoDevWork="D:\CodeWork\port-forward\" 3 | 4 | echo "Clean for build..." 5 | go clean 6 | 7 | echo "Build For windows..." 8 | 9 | set GOOS=windows 10 | set GOARCH=amd64 11 | ::set GOPATH=%GoDevWork%;%GOPATH% 12 | go build -o forward-server.exe 13 | 14 | echo "--------- Build For windows Success!" 15 | 16 | 17 | pause 18 | 19 | -------------------------------------------------------------------------------- /forward-server/conf/app.conf: -------------------------------------------------------------------------------- 1 | 2 | app.name = "PortForward" 3 | app.cname = "端口转发系统" 4 | servername = "PortForward-Server" 5 | runmode ="dev" 6 | #runmode ="prod" 7 | viewspath = "views" 8 | #logfile.config = "close" 9 | 10 | [dev] 11 | httpaddr = "0.0.0.0" 12 | httpport = 8080 13 | recoverpanic = false 14 | [prod] 15 | httpaddr = "0.0.0.0" 16 | httpport = 8080 17 | recoverpanic = true 18 | graceful = true 19 | 20 | include "data.conf" 21 | -------------------------------------------------------------------------------- /forward-server/conf/data.conf: -------------------------------------------------------------------------------- 1 | magic.service = ":7000" 2 | api.auth = "26CCD056107481F45D1AC805A24A9E59" 3 | 4 | agent.auth = "722ED8F8D9900CC1AB17243DC7B51A2D" 5 | 6 | onstart.forward = "false" -------------------------------------------------------------------------------- /forward-server/data/data.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/data/data.db -------------------------------------------------------------------------------- /forward-server/go.mod: -------------------------------------------------------------------------------- 1 | module forward-server 2 | 3 | go 1.14 4 | 5 | require ( 6 | forward-core v0.0.0-00010101000000-000000000000 7 | github.com/astaxie/beego v1.12.2 8 | github.com/mattn/go-sqlite3 v2.0.3+incompatible 9 | github.com/vmihailenco/msgpack v4.0.4+incompatible 10 | google.golang.org/appengine v1.6.7 // indirect 11 | ) 12 | 13 | replace forward-core => ../forward-core 14 | -------------------------------------------------------------------------------- /forward-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "forward-server/Service" 6 | _ "forward-server/routers" 7 | 8 | "forward-core/Models" 9 | 10 | "github.com/astaxie/beego" 11 | "github.com/astaxie/beego/logs" 12 | _ "github.com/mattn/go-sqlite3" 13 | "github.com/vmihailenco/msgpack" 14 | ) 15 | 16 | func main() { 17 | 18 | logFileConfig := beego.AppConfig.String("logfile.config") 19 | 20 | //日志级别:"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug" 21 | logs.SetLogger(logs.AdapterConsole, `{"level":7}`) 22 | 23 | if len(logFileConfig) == 0 { 24 | //logFileConfig = `{"filename":"app.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10}` 25 | logFileConfig = `{"filename":"app.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10,"separate":["error"]}` 26 | 27 | } 28 | 29 | if logFileConfig != "close" { 30 | //logs.SetLogger(logs.AdapterFile, `{"filename":"app.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10}`) 31 | //logs.SetLogger(logs.AdapterFile, logFileConfig) 32 | logs.SetLogger(logs.AdapterMultiFile, logFileConfig) 33 | } 34 | 35 | //输出文件名和行号 36 | logs.EnableFuncCallDepth(true) 37 | logs.SetLogFuncCallDepth(3) 38 | //为了让日志输出不影响性能,开启异步日志 39 | logs.Async() 40 | 41 | logs.Debug("★★★★★★★★★★★★★★★★★★★★") 42 | logs.Debug(" port-forward 启动") 43 | logs.Debug("") 44 | logs.Debug("项目地址:https://github.com/tavenli/port-forward") 45 | logs.Debug("") 46 | logs.Debug("★★★★★★★★★★★★★★★★★★★★") 47 | 48 | defer logs.GetBeeLogger().Flush() 49 | 50 | //test1() 51 | 52 | //启动Web控制台和接口 53 | Service.ConsoleServ.StartHttpServer() 54 | 55 | //select {} 56 | 57 | //endRunning := make(chan bool, 1) 58 | //time.Sleep(1* time.Second) 59 | //endRunning <- true 60 | //<-endRunning 61 | } 62 | 63 | func test1() { 64 | 65 | //github.com/gogf/gf/g/os/glog 66 | //glog.Debug("This is Debug") 67 | //glog.Info("This is Info") 68 | 69 | b, err := msgpack.Marshal(&Models.PortInfo{Addr: "bar"}) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | var item Models.PortInfo 75 | err = msgpack.Unmarshal(b, &item) 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | logs.Debug(item.Addr) 81 | 82 | // 83 | config := new(Models.ForwardConfig) 84 | //config.Protocol = "TCP" 85 | config.Protocol = "UDP" 86 | config.SrcAddr = "" 87 | config.SrcPort = 8888 88 | //106.14.184.192:9999 89 | //config.DestAddr = "106.14.184.192" 90 | //config.DestPort = 9999 91 | config.DestAddr = "svn.apiclub.top" 92 | config.DestPort = 9900 93 | config.Status = 0 94 | config.Name = "测试1" 95 | config.RuleId = 1 96 | 97 | resultChan := make(chan Models.FuncResult) 98 | Service.ForWardServ.OpenForward(config, resultChan) 99 | 100 | fmt.Println(<-resultChan) 101 | 102 | } 103 | -------------------------------------------------------------------------------- /forward-server/routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "forward-server/Controllers" 5 | "github.com/astaxie/beego" 6 | ) 7 | 8 | func init() { 9 | 10 | // 11 | beego.Include(&Controllers.DefaultCtrl{}) 12 | beego.Include(&Controllers.LoginCtrl{}) 13 | beego.Include(&Controllers.UCenterCtrl{}) 14 | beego.Include(&Controllers.ForwardCtrl{}) 15 | 16 | api_ns := beego.NewNamespace("/api", 17 | beego.NSNamespace("/v1", 18 | beego.NSInclude( 19 | &Controllers.RestApiCtrl{}, 20 | ), 21 | ), 22 | ) 23 | 24 | beego.AddNamespace(api_ns) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /forward-server/static/js/timer.min.js: -------------------------------------------------------------------------------- 1 | /*! timer.jquery 0.4.14 2016-05-14*/!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],b):b(a.jQuery)}(this,function(a){function b(b){var c=b.element;a(c).data("intr",setInterval(d.bind(b),b.options.updateFrequency)),a(c).data("isTimerRunning",!0)}function c(b){clearInterval(a(b.element).data("intr")),a(b.element).data("isTimerRunning",!1)}function d(){a(this.element).data("totalSeconds",g()-a(this.element).data("startTime")),e(this),a(this.element).data("duration")&&a(this.element).data("totalSeconds")%a(this.element).data("duration")===0&&(this.options.repeat||(a(this.element).data("duration",null),this.options.duration=null),this.options.countdown&&(c(this),this.options.countdown=!1,a(this.element).data("state",r)),this.options.callback())}function e(b){var c=b.element,d=a(c).data("totalSeconds");b.options.countdown&&a(c).data("duration")>0&&(d=a(c).data("duration")-a(c).data("totalSeconds")),a(c)[q](i(d,b)),a(c).data("seconds",d)}function f(b){var c=b.element;a(c).on("focus",function(){l(b)}),a(c).on("blur",function(){var d,e=a(c)[q]();e.indexOf("sec")>0?a(c).data("totalSeconds",Number(e.replace(/\ssec/g,""))):e.indexOf("min")>0?(e=e.replace(/\smin/g,""),d=e.split(":"),a(c).data("totalSeconds",Number(60*d[0])+Number(d[1]))):e.match(/\d{1,2}:\d{2}:\d{2}/)&&(d=e.split(":"),a(c).data("totalSeconds",Number(3600*d[0])+Number(60*d[1])+Number(d[2]))),m(b)})}function g(){return Math.round((new Date).getTime()/1e3)}function h(a){var b,c=0,d=Math.floor(a/60),e=d;return a>=3600&&(c=Math.floor(a/3600)),a>=3600&&(e=Math.floor(a%3600/60)),10>e&&c>0&&(e="0"+e),b=a%60,10>b&&(e>0||c>0)&&(b="0"+b),{hours:c,minutes:e,totalMinutes:d,seconds:b,totalSeconds:a}}function i(a,b){var c="",d=h(a);if(b.options.format){var e=[{identifier:"%h",value:d.hours,pad:!1},{identifier:"%m",value:d.minutes,pad:!1},{identifier:"%s",value:d.seconds,pad:!1},{identifier:"%g",value:d.totalMinutes,pad:!1},{identifier:"%t",value:d.totalSeconds,pad:!1},{identifier:"%H",value:parseInt(d.hours),pad:!0},{identifier:"%M",value:parseInt(d.minutes),pad:!0},{identifier:"%S",value:parseInt(d.seconds),pad:!0},{identifier:"%G",value:parseInt(d.totalMinutes),pad:!0},{identifier:"%T",value:parseInt(d.totalSeconds),pad:!0}];c=b.options.format,e.forEach(function(a){c=c.replace(new RegExp(a.identifier.replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1"),"g"),a.pad&&a.value<10?"0"+a.value:a.value)})}else c=d.hours?d.hours+":"+d.minutes+":"+d.seconds:d.minutes?d.minutes+":"+d.seconds+" min":d.seconds+" sec";return c}function j(a){if(!isNaN(Number(a)))return a;var b=a.match(/\d{1,2}h/),c=a.match(/\d{1,2}m/),d=a.match(/\d{1,2}s/),e=0;return a=a.toLowerCase(),b&&(e+=3600*Number(b[0].replace("h",""))),c&&(e+=60*Number(c[0].replace("m",""))),d&&(e+=Number(d[0].replace("s",""))),e}function k(c){var d=c.element;a(d).data("isTimerRunning")||(e(c),b(c),a(d).data("state",s),c.options.startTimer.bind(c).call())}function l(b){var d=b.element;a(d).data("isTimerRunning")&&(c(b),a(d).data("state",t),b.options.pauseTimer.bind(b).call())}function m(c){var d=c.element;a(d).data("isTimerRunning")||(a(d).data("startTime",g()-a(d).data("totalSeconds")),b(c),a(d).data("state",s),c.options.resumeTimer.bind(c).call())}function n(a){o(a),k(a)}function o(b){var d=b.element;c(b),b.options.removeTimer.bind(b).call(),a(d).data("isTimerRunning",null),a(d).data("plugin_"+v,null),a(d).data("seconds",null),a(d).data("totalSeconds",null),a(d).data("state",null),a(d)[q]("")}var p={seconds:0,editable:!1,restart:!1,duration:null,callback:function(){alert("Time up!")},startTimer:function(){},pauseTimer:function(){},resumeTimer:function(){},resetTimer:function(){},removeTimer:function(){},repeat:!1,countdown:!1,format:null,updateFrequency:1e3,state:"running"},q="html",r="stopped",s="running",t="paused",u=function(b,c){var d;this.options=p=a.extend(this.options,p,c),this.element=b,a(b).data("totalSeconds",p.seconds),a(b).data("startTime",g()-a(b).data("totalSeconds")),a(b).data("seconds",a(b).data("totalSeconds")),a(b).data("state",r),d=a(b).prop("tagName").toLowerCase(),"input"!==d&&"textarea"!==d||(q="val"),this.options.duration&&(a(b).data("duration",j(this.options.duration)),this.options.duration=j(this.options.duration)),this.options.editable&&f(this)};u.prototype={start:function(){k(this)},pause:function(){l(this)},resume:function(){m(this)},reset:function(){n(this)},remove:function(){o(this)}};var v="timer";a.fn[v]=function(b){return b=b||"start",this.each(function(){a.data(this,"plugin_"+v)instanceof u||a.data(this,"plugin_"+v,new u(this,b));var c=a.data(this,"plugin_"+v);"string"==typeof b&&"function"==typeof c[b]&&c[b].call(c),"object"==typeof b&&(c.options.state===s?c.start.call(c):e(c))})}}); -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/code.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none} -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/laydate/default/laydate.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | .laydate-set-ym,.layui-laydate,.layui-laydate *,.layui-laydate-list{box-sizing:border-box}html #layuicss-laydate{display:none;position:absolute;width:1989px}.layui-laydate *{margin:0;padding:0}.layui-laydate{position:absolute;z-index:66666666;margin:5px 0;border-radius:2px;font-size:14px;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:laydate-upbit;animation-name:laydate-upbit}.layui-laydate-main{width:272px}.layui-laydate-content td,.layui-laydate-header *,.layui-laydate-list li{transition-duration:.3s;-webkit-transition-duration:.3s}@-webkit-keyframes laydate-upbit{from{-webkit-transform:translate3d(0,20px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes laydate-upbit{from{transform:translate3d(0,20px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-laydate-static{position:relative;z-index:0;display:inline-block;margin:0;-webkit-animation:none;animation:none}.laydate-ym-show .laydate-next-m,.laydate-ym-show .laydate-prev-m{display:none!important}.laydate-ym-show .laydate-next-y,.laydate-ym-show .laydate-prev-y{display:inline-block!important}.laydate-time-show .laydate-set-ym span[lay-type=month],.laydate-time-show .laydate-set-ym span[lay-type=year],.laydate-time-show .layui-laydate-header .layui-icon,.laydate-ym-show .laydate-set-ym span[lay-type=month]{display:none!important}.layui-laydate-header{position:relative;line-height:30px;padding:10px 70px 5px}.laydate-set-ym span,.layui-laydate-header i{padding:0 5px;cursor:pointer}.layui-laydate-header *{display:inline-block;vertical-align:bottom}.layui-laydate-header i{position:absolute;top:10px;color:#999;font-size:18px}.layui-laydate-header i.laydate-prev-y{left:15px}.layui-laydate-header i.laydate-prev-m{left:45px}.layui-laydate-header i.laydate-next-y{right:15px}.layui-laydate-header i.laydate-next-m{right:45px}.laydate-set-ym{width:100%;text-align:center;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.laydate-time-text{cursor:default!important}.layui-laydate-content{position:relative;padding:10px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-laydate-content table{border-collapse:collapse;border-spacing:0}.layui-laydate-content td,.layui-laydate-content th{width:36px;height:30px;padding:5px;text-align:center}.layui-laydate-content td{position:relative;cursor:pointer}.laydate-day-mark{position:absolute;left:0;top:0;width:100%;height:100%;line-height:30px;font-size:12px;overflow:hidden}.laydate-day-mark::after{position:absolute;content:'';right:2px;top:2px;width:5px;height:5px;border-radius:50%}.layui-laydate-footer{position:relative;height:46px;line-height:26px;padding:10px 20px}.layui-laydate-footer span{margin-right:15px;display:inline-block;cursor:pointer;font-size:12px}.layui-laydate-footer span:hover{color:#5FB878}.laydate-footer-btns{position:absolute;right:10px;top:10px}.laydate-footer-btns span{height:26px;line-height:26px;margin:0 0 0 -1px;padding:0 10px;border:1px solid #C9C9C9;background-color:#fff;white-space:nowrap;vertical-align:top;border-radius:2px}.layui-laydate-list>li,.layui-laydate-range .layui-laydate-main{display:inline-block;vertical-align:middle}.layui-laydate-list{position:absolute;left:0;top:0;width:100%;height:100%;padding:10px;background-color:#fff}.layui-laydate-list>li{position:relative;width:33.3%;height:36px;line-height:36px;margin:3px 0;text-align:center;cursor:pointer}.laydate-month-list>li{width:25%;margin:17px 0}.laydate-time-list>li{height:100%;margin:0;line-height:normal;cursor:default}.laydate-time-list p{position:relative;top:-4px;line-height:29px}.laydate-time-list ol{height:181px;overflow:hidden}.laydate-time-list>li:hover ol{overflow-y:auto}.laydate-time-list ol li{width:130%;padding-left:33px;line-height:30px;text-align:left;cursor:pointer}.layui-laydate-hint{position:absolute;top:115px;left:50%;width:250px;margin-left:-125px;line-height:20px;padding:15px;text-align:center;font-size:12px}.layui-laydate-range{width:546px}.layui-laydate-range .laydate-main-list-0 .laydate-next-m,.layui-laydate-range .laydate-main-list-0 .laydate-next-y,.layui-laydate-range .laydate-main-list-1 .laydate-prev-m,.layui-laydate-range .laydate-main-list-1 .laydate-prev-y{display:none}.layui-laydate-range .laydate-main-list-1 .layui-laydate-content{border-left:1px solid #e2e2e2}.layui-laydate,.layui-laydate-hint{border:1px solid #d2d2d2;box-shadow:0 2px 4px rgba(0,0,0,.12);background-color:#fff;color:#666}.layui-laydate-header{border-bottom:1px solid #e2e2e2}.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:#5FB878}.layui-laydate-content{border-top:none 0;border-bottom:none 0}.layui-laydate-content th{font-weight:400;color:#333}.layui-laydate-content td{color:#666}.layui-laydate-content td.laydate-selected{background-color:#00F7DE}.laydate-selected:hover{background-color:#00F7DE!important}.layui-laydate-content td:hover,.layui-laydate-list li:hover{background-color:#eaeaea;color:#333}.laydate-time-list li ol{margin:0;padding:0;border:1px solid #e2e2e2;border-left-width:0}.laydate-time-list li:first-child ol{border-left-width:1px}.laydate-time-list>li:hover{background:0 0}.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color:#d2d2d2}.laydate-selected.laydate-day-next,.laydate-selected.laydate-day-prev{background-color:#f8f8f8!important}.layui-laydate-footer{border-top:1px solid #e2e2e2}.layui-laydate-hint{color:#FF5722}.laydate-day-mark::after{background-color:#5FB878}.layui-laydate-content td.layui-this .laydate-day-mark::after{display:none}.layui-laydate-footer span[lay-type=date]{color:#5FB878}.layui-laydate .layui-this{background-color:#009688!important;color:#fff!important}.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{background:0 0!important;color:#d2d2d2!important;cursor:not-allowed!important;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.laydate-theme-molv{border:none}.laydate-theme-molv.layui-laydate-range{width:548px}.laydate-theme-molv .layui-laydate-main{width:274px}.laydate-theme-molv .layui-laydate-header{border:none;background-color:#009688}.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:#f6f6f6}.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color:#fff}.laydate-theme-molv .layui-laydate-content{border:1px solid #e2e2e2;border-top:none;border-bottom:none}.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content{border-left:none}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead,.laydate-theme-molv .layui-laydate-footer{border:1px solid #e2e2e2}.laydate-theme-grid .laydate-selected,.laydate-theme-grid .laydate-selected:hover{background-color:#f2f2f2!important;color:#009688!important}.laydate-theme-grid .laydate-selected.laydate-day-next,.laydate-theme-grid .laydate-selected.laydate-day-prev{color:#d2d2d2!important}.laydate-theme-grid .laydate-month-list,.laydate-theme-grid .laydate-year-list{margin:1px 0 0 1px}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li{margin:0 -1px -1px 0}.laydate-theme-grid .laydate-year-list>li{height:43px;line-height:43px}.laydate-theme-grid .laydate-month-list>li{height:71px;line-height:71px} -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/laydate/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/css/modules/laydate/icon.png -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /forward-server/static/layui/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /forward-server/static/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/font/iconfont.eot -------------------------------------------------------------------------------- /forward-server/static/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /forward-server/static/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/font/iconfont.woff -------------------------------------------------------------------------------- /forward-server/static/layui/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/font/iconfont.woff2 -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/0.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/1.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/10.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/11.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/12.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/13.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/14.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/15.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/16.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/17.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/18.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/19.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/2.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/20.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/21.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/22.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/23.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/24.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/25.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/26.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/27.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/28.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/29.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/3.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/30.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/31.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/32.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/33.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/34.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/35.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/36.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/37.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/38.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/39.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/4.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/40.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/41.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/42.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/43.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/44.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/44.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/45.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/46.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/46.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/47.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/48.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/49.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/49.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/5.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/50.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/51.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/52.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/53.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/54.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/55.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/56.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/57.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/58.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/59.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/6.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/60.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/61.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/62.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/63.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/64.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/65.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/66.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/67.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/68.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/68.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/69.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/69.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/7.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/70.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/71.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/71.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/8.gif -------------------------------------------------------------------------------- /forward-server/static/layui/images/face/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/static/layui/images/face/9.gif -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/carousel.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",l=">*[carousel-item]>*",o="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(clearInterval(e.timer),e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
    ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
"].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a/g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
  1. '+o.replace(/[\r\t\n]+/g,"
  2. ")+"
"),c.find(">.layui-code-h3")[0]||c.prepend('

'+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/element.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(t){"use strict";var a=layui.$,i=(layui.hint(),layui.device()),e="element",l="layui-this",n="layui-show",s=function(){this.config={}};s.prototype.set=function(t){var i=this;return a.extend(!0,i.config,t),i},s.prototype.on=function(t,a){return layui.onevent.call(this,e,t,a)},s.prototype.tabAdd=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.children(".layui-tab-bar"),o=l.children(".layui-tab-content"),r='
  • "+(i.title||"unnaming")+"
  • ";return s[0]?s.before(r):n.append(r),o.append('
    '+(i.content||"")+"
    "),f.hideTabMore(!0),f.tabAuto(),this},s.prototype.tabDelete=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.find('>li[lay-id="'+i+'"]');return f.tabDelete(null,s),this},s.prototype.tabChange=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.find('>li[lay-id="'+i+'"]');return f.tabClick.call(s[0],null,null,s),this},s.prototype.tab=function(t){t=t||{},b.on("click",t.headerElem,function(i){var e=a(this).index();f.tabClick.call(this,i,e,null,t)})},s.prototype.progress=function(t,i){var e="layui-progress",l=a("."+e+"[lay-filter="+t+"]"),n=l.find("."+e+"-bar"),s=n.find("."+e+"-text");return n.css("width",i),s.text(i),this};var o=".layui-nav",r="layui-nav-item",c="layui-nav-bar",u="layui-nav-tree",d="layui-nav-child",y="layui-nav-more",h="layui-anim layui-anim-upbit",f={tabClick:function(t,i,s,o){o=o||{};var r=s||a(this),i=i||r.parent().children("li").index(r),c=o.headerElem?r.parent():r.parents(".layui-tab").eq(0),u=o.bodyElem?a(o.bodyElem):c.children(".layui-tab-content").children(".layui-tab-item"),d=r.find("a"),y=c.attr("lay-filter");"javascript:;"!==d.attr("href")&&"_blank"===d.attr("target")||(r.addClass(l).siblings().removeClass(l),u.eq(i).addClass(n).siblings().removeClass(n)),layui.event.call(this,e,"tab("+y+")",{elem:c,index:i})},tabDelete:function(t,i){var n=i||a(this).parent(),s=n.index(),o=n.parents(".layui-tab").eq(0),r=o.children(".layui-tab-content").children(".layui-tab-item"),c=o.attr("lay-filter");n.hasClass(l)&&(n.next()[0]?f.tabClick.call(n.next()[0],null,s+1):n.prev()[0]&&f.tabClick.call(n.prev()[0],null,s-1)),n.remove(),r.eq(s).remove(),setTimeout(function(){f.tabAuto()},50),layui.event.call(this,e,"tabDelete("+c+")",{elem:o,index:s})},tabAuto:function(){var t="layui-tab-more",e="layui-tab-bar",l="layui-tab-close",n=this;a(".layui-tab").each(function(){var s=a(this),o=s.children(".layui-tab-title"),r=(s.children(".layui-tab-content").children(".layui-tab-item"),'lay-stope="tabmore"'),c=a('');if(n===window&&8!=i.ie&&f.hideTabMore(!0),s.attr("lay-allowClose")&&o.find("li").each(function(){var t=a(this);if(!t.find("."+l)[0]){var i=a('');i.on("click",f.tabDelete),t.append(i)}}),"string"!=typeof s.attr("lay-unauto"))if(o.prop("scrollWidth")>o.outerWidth()+1){if(o.find("."+e)[0])return;o.append(c),s.attr("overflow",""),c.on("click",function(a){o[this.title?"removeClass":"addClass"](t),this.title=this.title?"":"收缩"})}else o.find("."+e).remove(),s.removeAttr("overflow")})},hideTabMore:function(t){var i=a(".layui-tab-title");t!==!0&&"tabmore"===a(t.target).attr("lay-stope")||(i.removeClass("layui-tab-more"),i.find(".layui-tab-bar").attr("title",""))},clickThis:function(){var t=a(this),i=t.parents(o),n=i.attr("lay-filter"),s=t.parent(),c=t.siblings("."+d),y="string"==typeof s.attr("lay-unselect");"javascript:;"!==t.attr("href")&&"_blank"===t.attr("target")||y||c[0]||(i.find("."+l).removeClass(l),s.addClass(l)),i.hasClass(u)&&(c.removeClass(h),c[0]&&(s["none"===c.css("display")?"addClass":"removeClass"](r+"ed"),"all"===i.attr("lay-shrink")&&s.siblings().removeClass(r+"ed"))),layui.event.call(this,e,"nav("+n+")",t)},collapse:function(){var t=a(this),i=t.find(".layui-colla-icon"),l=t.siblings(".layui-colla-content"),s=t.parents(".layui-collapse").eq(0),o=s.attr("lay-filter"),r="none"===l.css("display");if("string"==typeof s.attr("lay-accordion")){var c=s.children(".layui-colla-item").children("."+n);c.siblings(".layui-colla-title").children(".layui-colla-icon").html(""),c.removeClass(n)}l[r?"addClass":"removeClass"](n),i.html(r?"":""),layui.event.call(this,e,"collapse("+o+")",{title:t,content:l,show:r})}};s.prototype.init=function(t,e){var l=function(){return e?'[lay-filter="'+e+'"]':""}(),s={tab:function(){f.tabAuto.call({})},nav:function(){var t=200,e={},s={},p={},b=function(l,o,r){var c=a(this),f=c.find("."+d);o.hasClass(u)?l.css({top:c.position().top,height:c.children("a").outerHeight(),opacity:1}):(f.addClass(h),l.css({left:c.position().left+parseFloat(c.css("marginLeft")),top:c.position().top+c.height()-l.height()}),e[r]=setTimeout(function(){l.css({width:c.width(),opacity:1})},i.ie&&i.ie<10?0:t),clearTimeout(p[r]),"block"===f.css("display")&&clearTimeout(s[r]),s[r]=setTimeout(function(){f.addClass(n),c.find("."+y).addClass(y+"d")},300))};a(o+l).each(function(i){var l=a(this),o=a(''),h=l.find("."+r);l.find("."+c)[0]||(l.append(o),h.on("mouseenter",function(){b.call(this,o,l,i)}).on("mouseleave",function(){l.hasClass(u)||(clearTimeout(s[i]),s[i]=setTimeout(function(){l.find("."+d).removeClass(n),l.find("."+y).removeClass(y+"d")},300))}),l.on("mouseleave",function(){clearTimeout(e[i]),p[i]=setTimeout(function(){l.hasClass(u)?o.css({height:0,top:o.position().top+o.height()/2,opacity:0}):o.css({width:0,left:o.position().left+o.width()/2,opacity:0})},t)})),h.find("a").each(function(){var t=a(this),i=(t.parent(),t.siblings("."+d));i[0]&&!t.children("."+y)[0]&&t.append(''),t.off("click",f.clickThis).on("click",f.clickThis)})})},breadcrumb:function(){var t=".layui-breadcrumb";a(t+l).each(function(){var t=a(this),i="lay-separator",e=t.attr(i)||"/",l=t.find("a");l.next("span["+i+"]")[0]||(l.each(function(t){t!==l.length-1&&a(this).after(""+e+"")}),t.css("visibility","visible"))})},progress:function(){var t="layui-progress";a("."+t+l).each(function(){var i=a(this),e=i.find(".layui-progress-bar"),l=e.attr("lay-percent");e.css("width",function(){return/^.+\/.+$/.test(l)?100*new Function("return "+l)()+"%":l}()),i.attr("lay-showPercent")&&setTimeout(function(){e.html(''+l+"")},350)})},collapse:function(){var t="layui-collapse";a("."+t+l).each(function(){var t=a(this).find(".layui-colla-item");t.each(function(){var t=a(this),i=t.find(".layui-colla-title"),e=t.find(".layui-colla-content"),l="none"===e.css("display");i.find(".layui-colla-icon").remove(),i.append(''+(l?"":"")+""),i.off("click",f.collapse).on("click",f.collapse)})})}};return s[t]?s[t]():layui.each(s,function(t,a){a()})},s.prototype.render=s.prototype.init;var p=new s,b=a(document);p.render();var v=".layui-tab-title li";b.on("click",v,f.tabClick),b.on("click",f.hideTabMore),a(window).on("resize",f.tabAuto),t(e,p)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/flow.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var l=layui.$,o=function(e){},t='';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),!i&&f.width()&&(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/laypage.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['
    ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
    "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/laytpl.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/rate.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var a=layui.jquery,i={config:{},index:layui.rate?layui.rate.index+1e4:0,set:function(e){var i=this;return i.config=a.extend({},i.config,e),i},on:function(e,a){return layui.onevent.call(this,n,e,a)}},l=function(){var e=this,a=e.config;return{setvalue:function(a){e.setvalue.call(e,a)},config:a}},n="rate",t="layui-rate",o="layui-icon-rate",s="layui-icon-rate-solid",u="layui-icon-rate-half",r="layui-icon-rate-solid layui-icon-rate-half",c="layui-icon-rate-solid layui-icon-rate",f="layui-icon-rate layui-icon-rate-half",v=function(e){var l=this;l.index=++i.index,l.config=a.extend({},l.config,i.config,e),l.render()};v.prototype.config={length:5,text:!1,readonly:!1,half:!1,value:0,theme:""},v.prototype.render=function(){var e=this,i=e.config,l=i.theme?'style="color: '+i.theme+';"':"";i.elem=a(i.elem),parseInt(i.value)!==i.value&&(i.half||(i.value=Math.ceil(i.value)-i.value<.5?Math.ceil(i.value):Math.floor(i.value)));for(var n='
      ",u=1;u<=i.length;u++){var r='
    • ";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'
    • ":n+=r}n+="
    "+(i.text?''+i.value+"星":"")+"";var c=i.elem,f=c.next("."+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next("span"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass("layui-inline"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find("i").width();l.children("li").each(function(e){var t=e+1,v=a(this);v.on("click",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next("span").text(i.value+"星"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on("mousemove",function(e){if(l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+t+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children("i").addClass(u).removeClass(s)}}),v.on("mouseleave",function(){l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+Math.floor(i.value)+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children("li:eq("+Math.floor(i.value)+")").children("i").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/slider.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var i=layui.jquery,t={config:{},index:layui.slider?layui.slider.index+1e4:0,set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,n,e,i)}},a=function(){var e=this,i=e.config;return{setValue:function(i,t){return e.slide("set",i,t||0)},config:i}},n="slider",l="layui-disabled",s="layui-slider",r="layui-slider-bar",o="layui-slider-wrap",u="layui-slider-wrap-btn",d="layui-slider-tips",v="layui-slider-input",c="layui-slider-input-txt",m="layui-slider-input-btn",p="layui-slider-hover",f=function(e){var a=this;a.index=++t.index,a.config=i.extend({},a.config,t.config,e),a.render()};f.prototype.config={type:"default",min:0,max:100,value:0,step:1,showstep:!1,tips:!0,input:!1,range:!1,height:200,disabled:!1,theme:"#009688"},f.prototype.render=function(){var e=this,t=e.config;if(t.step<1&&(t.step=1),t.maxt.min?a:t.min,t.value[1]=n>t.min?n:t.min,t.value[0]=t.value[0]>t.max?t.max:t.value[0],t.value[1]=t.value[1]>t.max?t.max:t.value[1];var r=Math.floor((t.value[0]-t.min)/(t.max-t.min)*100),v=Math.floor((t.value[1]-t.min)/(t.max-t.min)*100),m=v-r+"%";r+="%",v+="%"}else{"object"==typeof t.value&&(t.value=Math.min.apply(null,t.value)),t.valuet.max&&(t.value=t.max);var m=Math.floor((t.value-t.min)/(t.max-t.min)*100)+"%"}var p=t.disabled?"#c2c2c2":t.theme,f='
    '+(t.tips?'
    ':"")+'
    '+(t.range?'
    ':"")+"
    ",h=i(t.elem),y=h.next("."+s);if(y[0]&&y.remove(),e.elemTemp=i(f),t.range?(e.elemTemp.find("."+o).eq(0).data("value",t.value[0]),e.elemTemp.find("."+o).eq(1).data("value",t.value[1])):e.elemTemp.find("."+o).data("value",t.value),h.html(e.elemTemp),"vertical"===t.type&&e.elemTemp.height(t.height+"px"),t.showstep){for(var g=(t.max-t.min)/t.step,b="",x=1;x')}e.elemTemp.append(b)}if(t.input&&!t.range){var w=i('
    ');h.css("position","relative"),h.append(w),h.find("."+c).children("input").val(t.value),"vertical"===t.type?w.css({left:0,top:-48}):e.elemTemp.css("margin-right",w.outerWidth()+15)}t.disabled?(e.elemTemp.addClass(l),e.elemTemp.find("."+u).addClass(l)):e.slide(),e.elemTemp.find("."+u).on("mouseover",function(){var a="vertical"===t.type?t.height:e.elemTemp[0].offsetWidth,n=e.elemTemp.find("."+o),l="vertical"===t.type?a-i(this).parent()[0].offsetTop-n.height():i(this).parent()[0].offsetLeft,s=l/a*100,r=i(this).parent().data("value"),u=t.setTips?t.setTips(r):r;e.elemTemp.find("."+d).html(u),"vertical"===t.type?e.elemTemp.find("."+d).css({bottom:s+"%","margin-bottom":"20px",display:"inline-block"}):e.elemTemp.find("."+d).css({left:s+"%",display:"inline-block"})}).on("mouseout",function(){e.elemTemp.find("."+d).css("display","none")})},f.prototype.slide=function(e,t,a){var n=this,l=n.config,s=n.elemTemp,f=function(){return"vertical"===l.type?l.height:s[0].offsetWidth},h=s.find("."+o),y=s.next("."+v),g=y.children("."+c).children("input").val(),b=100/((l.max-l.min)/Math.ceil(l.step)),x=function(e,i){e=Math.ceil(e)*b>100?Math.ceil(e)*b:Math.round(e)*b,e=e>100?100:e,h.eq(i).css("vertical"===l.type?"bottom":"left",e+"%");var t=T(h[0].offsetLeft),a=l.range?T(h[1].offsetLeft):0;"vertical"===l.type?(s.find("."+d).css({bottom:e+"%","margin-bottom":"20px"}),t=T(f()-h[0].offsetTop-h.height()),a=l.range?T(f()-h[1].offsetTop-h.height()):0):s.find("."+d).css("left",e+"%"),t=t>100?100:t,a=a>100?100:a;var n=Math.min(t,a),o=Math.abs(t-a);"vertical"===l.type?s.find("."+r).css({height:o+"%",bottom:n+"%"}):s.find("."+r).css({width:o+"%",left:n+"%"});var u=l.min+Math.round((l.max-l.min)*e/100);if(g=u,y.children("."+c).children("input").val(g),h.eq(i).data("value",u),u=l.setTips?l.setTips(u):u,s.find("."+d).html(u),l.range){var v=[h.eq(0).data("value"),h.eq(1).data("value")];v[0]>v[1]&&v.reverse()}l.change&&l.change(l.range?v:u)},T=function(e){var i=e/f()*100/b,t=Math.round(i)*b;return e==f()&&(t=Math.ceil(i)*b),t},w=i(['
    f()&&(r=f());var o=r/f()*100/b;x(o,e),t.addClass(p),s.find("."+d).show(),i.preventDefault()},o=function(){t.removeClass(p),s.find("."+d).hide()};M(r,o)})}),s.on("click",function(e){var t=i("."+u);if(!t.is(event.target)&&0===t.has(event.target).length&&t.length){var a,n="vertical"===l.type?f()-e.clientY+i(this).offset().top:e.clientX-i(this).offset().left;n<0&&(n=0),n>f()&&(n=f());var s=n/f()*100/b;a=l.range?"vertical"===l.type?Math.abs(n-parseInt(i(h[0]).css("bottom")))>Math.abs(n-parseInt(i(h[1]).css("bottom")))?1:0:Math.abs(n-h[0].offsetLeft)>Math.abs(n-h[1].offsetLeft)?1:0:0,x(s,a),e.preventDefault()}}),y.hover(function(){var e=i(this);e.children("."+m).fadeIn("fast")},function(){var e=i(this);e.children("."+m).fadeOut("fast")}),y.children("."+m).children("i").each(function(e){i(this).on("click",function(){g=1==e?g-l.stepl.max?l.max:Number(g)+l.step;var i=(g-l.min)/(l.max-l.min)*100/b;x(i,0)})});var q=function(){var e=this.value;e=isNaN(e)?0:e,e=el.max?l.max:e,this.value=e;var i=(e-l.min)/(l.max-l.min)*100/b;x(i,0)};y.children("."+c).children("input").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),q.call(this))}).on("change",q)},f.prototype.events=function(){var e=this;e.config},t.render=function(e){var i=new f(e);return a.call(i)},e(n,t)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/transfer.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define(["laytpl","form"],function(e){"use strict";var a=layui.$,t=layui.laytpl,n=layui.form,i="transfer",l={config:{},index:layui[i]?layui[i].index+1e4:0,set:function(e){var t=this;return t.config=a.extend({},t.config,e),t},on:function(e,a){return layui.onevent.call(this,i,e,a)}},r=function(){var e=this,a=e.config,t=a.id||e.index;return r.that[t]=e,r.config[t]=a,{config:a,reload:function(a){e.reload.call(e,a)},getData:function(){return e.getData.call(e)}}},c="layui-hide",o="layui-btn-disabled",d="layui-none",s="layui-transfer-box",u="layui-transfer-header",h="layui-transfer-search",f="layui-transfer-active",y="layui-transfer-data",p=function(e){return e=e||{},['
    ','
    ','","
    ","{{# if(d.data.showSearch){ }}",'","{{# } }}",'
      ',"
      "].join("")},v=['
      ',p({index:0,checkAllName:"layTransferLeftCheckAll"}),'
      ','",'","
      ",p({index:1,checkAllName:"layTransferRightCheckAll"}),"
      "].join(""),x=function(e){var t=this;t.index=++l.index,t.config=a.extend({},t.config,l.config,e),t.render()};x.prototype.config={title:["列表一","列表二"],width:200,height:360,data:[],value:[],showSearch:!1,id:"",text:{none:"无数据",searchNone:"无匹配数据"}},x.prototype.reload=function(e){var t=this;layui.each(e,function(e,a){a.constructor===Array&&delete t.config[e]}),t.config=a.extend(!0,{},t.config,e),t.render()},x.prototype.render=function(){var e=this,n=e.config,i=e.elem=a(t(v).render({data:n,index:e.index})),l=n.elem=a(n.elem);l[0]&&(n.data=n.data||[],n.value=n.value||[],e.key=n.id||e.index,l.html(e.elem),e.layBox=e.elem.find("."+s),e.layHeader=e.elem.find("."+u),e.laySearch=e.elem.find("."+h),e.layData=i.find("."+y),e.layBtn=i.find("."+f+" .layui-btn"),e.layBox.css({width:n.width,height:n.height}),e.layData.css({height:function(){return n.height-e.layHeader.outerHeight()-e.laySearch.outerHeight()-2}()}),e.renderData(),e.events())},x.prototype.renderData=function(){var e=this,a=(e.config,[{checkName:"layTransferLeftCheck",views:[]},{checkName:"layTransferRightCheck",views:[]}]);e.parseData(function(e){var t=e.selected?1:0,n=["
    • ",'',"
    • "].join("");a[t].views.push(n),delete e.selected}),e.layData.eq(0).html(a[0].views.join("")),e.layData.eq(1).html(a[1].views.join("")),e.renderCheckBtn()},x.prototype.renderForm=function(e){n.render(e,"LAY-transfer-"+this.index)},x.prototype.renderCheckBtn=function(e){var t=this,n=t.config;e=e||{},t.layBox.each(function(i){var l=a(this),r=l.find("."+y),d=l.find("."+u).find('input[type="checkbox"]'),s=r.find('input[type="checkbox"]'),h=0,f=!1;if(s.each(function(){var e=a(this).data("hide");(this.checked||this.disabled||e)&&h++,this.checked&&!e&&(f=!0)}),d.prop("checked",f&&h===s.length),t.layBtn.eq(i)[f?"removeClass":"addClass"](o),!e.stopNone){var p=r.children("li:not(."+c+")").length;t.noneView(r,p?"":n.text.none)}}),t.renderForm("checkbox")},x.prototype.noneView=function(e,t){var n=a('

      '+(t||"")+"

      ");e.find("."+d)[0]&&e.find("."+d).remove(),t.replace(/\s/g,"")&&e.append(n)},x.prototype.setValue=function(){var e=this,t=e.config,n=[];return e.layBox.eq(1).find("."+y+' input[type="checkbox"]').each(function(){var e=a(this).data("hide");e||n.push(this.value)}),t.value=n,e},x.prototype.parseData=function(e){var t=this,n=t.config,i=[];return layui.each(n.data,function(t,l){l=("function"==typeof n.parseData?n.parseData(l):l)||l,i.push(l=a.extend({},l)),layui.each(n.value,function(e,a){a==l.value&&(l.selected=!0)}),e&&e(l)}),n.data=i,t},x.prototype.getData=function(e){var a=this,t=a.config,n=[];return a.setValue(),layui.each(e||t.value,function(e,a){layui.each(t.data,function(e,t){delete t.selected,a==t.value&&n.push(t)})}),n},x.prototype.events=function(){var e=this,t=e.config;e.elem.on("click",'input[lay-filter="layTransferCheckbox"]+',function(){var t=a(this).prev(),n=t[0].checked,i=t.parents("."+s).eq(0).find("."+y);t[0].disabled||("all"===t.attr("lay-type")&&i.find('input[type="checkbox"]').each(function(){this.disabled||(this.checked=n)}),e.renderCheckBtn({stopNone:!0}))}),e.layBtn.on("click",function(){var n=a(this),i=n.data("index"),l=e.layBox.eq(i),r=[];if(!n.hasClass(o)){e.layBox.eq(i).each(function(t){var n=a(this),i=n.find("."+y);i.children("li").each(function(){var t=a(this),n=t.find('input[type="checkbox"]'),i=n.data("hide");n[0].checked&&!i&&(n[0].checked=!1,l.siblings("."+s).find("."+y).append(t.clone()),t.remove(),r.push(n[0].value)),e.setValue()})}),e.renderCheckBtn();var c=l.siblings("."+s).find("."+h+" input");""===c.val()||c.trigger("keyup"),t.onchange&&t.onchange(e.getData(r),i)}}),e.laySearch.find("input").on("keyup",function(){var n=this.value,i=a(this).parents("."+h).eq(0).siblings("."+y),l=i.children("li");l.each(function(){var e=a(this),t=e.find('input[type="checkbox"]'),i=t[0].title.indexOf(n)!==-1;e[i?"removeClass":"addClass"](c),t.data("hide",!i)}),e.renderCheckBtn();var r=l.length===i.children("li."+c).length;e.noneView(i,r?t.text.searchNone:"")})},r.that={},r.config={},l.reload=function(e,a){var t=r.that[e];return t.reload(a),r.call(t)},l.getData=function(e){var a=r.that[e];return a.getData()},l.render=function(e){var a=new x(e);return r.call(a)},e(i,l)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/upload.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("layer",function(e){"use strict";var t=layui.$,i=layui.layer,n=layui.hint(),o=layui.device(),a={config:{},set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,r,e,t)}},l=function(){var e=this;return{upload:function(t){e.upload.call(e,t)},reload:function(t){e.reload.call(e,t)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",f="layui-upload-iframe",s="layui-upload-choose",p=function(e){var i=this;i.config=t.extend({},i.config,a.config,e),i.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",acceptMime:"",method:"post",data:{},drag:!0,size:0,number:0,multiple:!1},p.prototype.render=function(e){var i=this,e=i.config;e.elem=t(e.elem),e.bindAction=t(e.bindAction),i.file(),i.events()},p.prototype.file=function(){var e=this,i=e.config,n=e.elemFile=t(['"].join("")),a=i.elem.next();(a.hasClass(u)||a.hasClass(c))&&a.remove(),o.ie&&o.ie<10&&i.elem.wrap('
      '),e.isFile()?(e.elemFile=i.elem,i.field=i.elem[0].name):i.elem.after(n),o.ie&&o.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,i=e.config,n=t(''),o=t(['
      ',"
      "].join(""));t("#"+f)[0]||t("body").append(n),i.elem.next().hasClass(c)||(e.elemFile.wrap(o),i.elem.next("."+c).append(function(){var e=[];return layui.each(i.data,function(t,i){i="function"==typeof i?i():i,e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return i.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var t=this;window.FileReader&&layui.each(t.chooseFiles,function(t,i){var n=new FileReader;n.readAsDataURL(i),n.onload=function(){e&&e(t,i,this.result)}})},p.prototype.upload=function(e,i){var n,a=this,l=a.config,r=a.elemFile[0],u=function(){var i=0,n=0,o=e||a.files||a.chooseFiles||r.files,u=function(){l.multiple&&i+n===a.fileLength&&"function"==typeof l.allDone&&l.allDone({total:a.fileLength,successful:i,aborted:n})};layui.each(o,function(e,o){var r=new FormData;r.append(l.field,o),layui.each(l.data,function(e,t){t="function"==typeof t?t():t,r.append(e,t)});var c={url:l.url,type:"post",data:r,contentType:!1,processData:!1,dataType:"json",headers:l.headers||{},success:function(t){i++,d(e,t),u()},error:function(){n++,a.msg("请求上传接口出现异常"),m(e),u()}};"function"==typeof l.progress&&(c.xhr=function(){var e=t.ajaxSettings.xhr();return e.upload.addEventListener("progress",function(e){if(e.lengthComputable){var t=Math.floor(e.loaded/e.total*100);l.progress(t,l.item[0],e)}}),e}),t.ajax(c)})},c=function(){var e=t("#"+f);a.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var t,i=e.contents().find("body");try{t=i.text()}catch(n){a.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}t&&(clearInterval(p.timer),i.html(""),d(0,t))},30)},d=function(e,t){if(a.elemFile.next("."+s).remove(),r.value="","object"!=typeof t)try{t=JSON.parse(t)}catch(i){return t={},a.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(t,e||0,function(e){a.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){a.upload(e)})},h=l.exts,v=function(){var t=[];return layui.each(e||a.chooseFiles,function(e,i){t.push(i.name)}),t}(),g={preview:function(e){a.preview(e)},upload:function(e,t){var i={};i[e]=t,a.upload(i)},pushFile:function(){return a.files=a.files||{},layui.each(a.chooseFiles,function(e,t){a.files[e]=t}),a.files},resetFile:function(e,t,i){var n=new File([t],i);a.files=a.files||{},a.files[e]=n}},y=function(){if("choose"!==i&&!l.auto||(l.choose&&l.choose(g),"choose"!==i))return l.before&&l.before(g),o.ie?o.ie>9?u():c():void u()};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return a.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return a.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return a.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,t){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(t))||(n=!0)}),n)return a.msg("选择的图片中包含不支持的格式"),r.value=""}if(a.fileLength=function(){var t=0,i=e||a.files||a.chooseFiles||r.files;return layui.each(i,function(){t++}),t}(),l.number&&a.fileLength>l.number)return a.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(o.ie&&o.ie<10)){var F;if(layui.each(a.chooseFiles,function(e,t){if(t.size>1024*l.size){var i=l.size/1024;i=i>=1?i.toFixed(2)+"MB":l.size+"KB",r.value="",F=i}}),F)return a.msg("文件不能超过"+F)}y()}},p.prototype.reload=function(e){e=e||{},delete e.elem,delete e.bindAction;var i=this,e=i.config=t.extend({},i.config,a.config,e),n=e.elem.next();n.attr({name:e.name,accept:e.acceptMime,multiple:e.multiple})},p.prototype.events=function(){var e=this,i=e.config,a=function(t){e.chooseFiles={},layui.each(t,function(t,i){var n=(new Date).getTime();e.chooseFiles[n+"-"+t]=i})},l=function(t,n){var o=e.elemFile,a=t.length>1?t.length+"个文件":(t[0]||{}).name||o[0].value.match(/[^\/\\]+\..+/g)||[]||"";o.next().hasClass(s)&&o.next().remove(),e.upload(null,"choose"),e.isFile()||i.choose||o.after(''+a+"")};i.elem.off("upload.start").on("upload.start",function(){var o=t(this),a=o.attr("lay-data");if(a)try{a=new Function("return "+a)(),e.config=t.extend({},i,a)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+a)}e.config.item=o,e.elemFile[0].click()}),o.ie&&o.ie<10||i.elem.off("upload.over").on("upload.over",function(){var e=t(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=t(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,o){var r=t(this),u=o.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),a(u),i.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var t=this.files||[];a(t),i.auto?e.upload():l(t)}),i.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),i.elem.data("haveEvents")||(e.elemFile.on("change",function(){t(this).trigger("upload.change")}),i.elem.on("click",function(){e.isFile()||t(this).trigger("upload.start")}),i.drag&&i.elem.on("dragover",function(e){e.preventDefault(),t(this).trigger("upload.over")}).on("dragleave",function(e){t(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),t(this).trigger("upload.drop",e)}),i.bindAction.on("click",function(){t(this).trigger("upload.action")}),i.elem.data("haveEvents",!0))},a.render=function(e){var t=new p(e);return l.call(t)},e(r,a)}); -------------------------------------------------------------------------------- /forward-server/static/layui/lay/modules/util.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var t=layui.$,i={fixbar:function(e){var i,n,a="layui-fixbar",o="layui-fixbar-top",r=t(document),l=t("body");e=t.extend({showHeight:200},e),e.bar1=e.bar1===!0?"":e.bar1,e.bar2=e.bar2===!0?"":e.bar2,e.bgcolor=e.bgcolor?"background-color:"+e.bgcolor:"";var c=[e.bar1,e.bar2,""],u=t(['
        ',e.bar1?'
      • '+c[0]+"
      • ":"",e.bar2?'
      • '+c[1]+"
      • ":"",'
      • '+c[2]+"
      • ","
      "].join("")),g=u.find("."+o),s=function(){var t=r.scrollTop();t>=e.showHeight?i||(g.show(),i=1):i&&(g.hide(),i=0)};t("."+a)[0]||("object"==typeof e.css&&u.css(e.css),l.append(u),s(),u.find("li").on("click",function(){var i=t(this),n=i.attr("lay-type");"top"===n&&t("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,n)}),r.on("scroll",function(){clearTimeout(n),n=setTimeout(function(){s()},100)}))},countdown:function(e,t,i){var n=this,a="function"==typeof t,o=new Date(e).getTime(),r=new Date(!t||a?(new Date).getTime():t).getTime(),l=o-r,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];a&&(i=t);var u=setTimeout(function(){n.countdown(e,r+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],t,u),l<=0&&clearTimeout(u),u},timeAgo:function(e,t){var i=this,n=[[],[]],a=(new Date).getTime()-new Date(e).getTime();return a>26784e5?(a=new Date(e),n[0][0]=i.digit(a.getFullYear(),4),n[0][1]=i.digit(a.getMonth()+1),n[0][2]=i.digit(a.getDate()),t||(n[1][0]=i.digit(a.getHours()),n[1][1]=i.digit(a.getMinutes()),n[1][2]=i.digit(a.getSeconds())),n[0].join("-")+" "+n[1].join(":")):a>=864e5?(a/1e3/60/60/24|0)+"天前":a>=36e5?(a/1e3/60/60|0)+"小时前":a>=18e4?(a/1e3/60|0)+"分钟前":a<0?"未来":"刚刚"},digit:function(e,t){var i="";e=String(e),t=t||2;for(var n=e.length;n/g,">").replace(/'/g,"'").replace(/"/g,""")},event:function(e,n,a){var o=t("body");return a=a||"click",n=i.event[e]=t.extend(!0,i.event[e],n)||{},i.event.UTIL_EVENT_CALLBACK=i.event.UTIL_EVENT_CALLBACK||{},o.off(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),i.event.UTIL_EVENT_CALLBACK[e]=function(){var i=t(this),a=i.attr(e);"function"==typeof n[a]&&n[a].call(this,i)},o.on(a,"*["+e+"]",i.event.UTIL_EVENT_CALLBACK[e]),n}};!function(e,t,i){"$:nomunge";function n(){a=t[l](function(){o.each(function(){var t=e(this),i=t.width(),n=t.height(),a=e.data(this,u);(i!==a.w||n!==a.h)&&t.trigger(c,[a.w=i,a.h=n])}),n()},r[g])}var a,o=e([]),r=e.resize=e.extend(e.resize,{}),l="setTimeout",c="resize",u=c+"-special-event",g="delay",s="throttleWindow";r[g]=250,r[s]=!0,e.event.special[c]={setup:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.add(t),e.data(this,u,{w:t.width(),h:t.height()}),1===o.length&&n()},teardown:function(){if(!r[s]&&this[l])return!1;var t=e(this);o=o.not(t),t.removeData(u),o.length||clearTimeout(a)},add:function(t){function n(t,n,o){var r=e(this),l=e.data(this,u)||{};l.w=n!==i?n:r.width(),l.h=o!==i?o:r.height(),a.apply(this,arguments)}if(!r[s]&&this[l])return!1;var a;return e.isFunction(t)?(a=t,n):(a=t.handler,void(t.handler=n))}}}(t,window),e("util",i)}); -------------------------------------------------------------------------------- /forward-server/static/layui/layui.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.5.6 MIT License By https://www.layui.com */ 2 | ;!function(e){"use strict";var t=document,n={modules:{},status:{},timeout:10,event:{}},r=function(){this.v="2.5.6"},o=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,n=t.scripts,r=n.length-1,o=r;o>0;o--)if("interactive"===n[o].readyState){e=n[o].src;break}return e||n[r].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),a=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},i="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",transfer:"modules/transfer",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};r.prototype.cache=n,r.prototype.define=function(e,t){var r=this,o="function"==typeof e,a=function(){var e=function(e,t){layui[e]=t,n.status[e]=!0};return"function"==typeof t&&t(function(r,o){e(r,o),n.callback[r]=function(){t(e)}}),this};return o&&(t=e,e=[]),!layui["layui.all"]&&layui["layui.mobile"]?a.call(r):(r.use(e,a),r)},r.prototype.use=function(e,r,l){function c(e,t){var r="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||r.test((e.currentTarget||e.srcElement).readyState))&&(n.modules[d]=t,y.removeChild(h),function o(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void(n.status[d]?s():setTimeout(o,4))}())}function s(){l.push(layui[d]),e.length>1?p.use(e.slice(1),r,l):"function"==typeof r&&r.apply(layui,l)}var p=this,f=n.dir=n.dir?n.dir:o,y=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(p.each(e,function(t,n){"jquery"===n&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var d=e[0],m=0;if(l=l||[],n.host=n.host||(f.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[d]||!layui["layui.all"]&&layui["layui.mobile"]&&u[d])return s(),p;var v=(u[d]?f+"lay/":/^\{\/\}/.test(p.modules[d])?"":n.base||"")+(p.modules[d]||d)+".js";if(v=v.replace(/^\{\/\}/,""),!n.modules[d]&&layui[d]&&(n.modules[d]=v),n.modules[d])!function g(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void("string"==typeof n.modules[d]&&n.status[d]?s():setTimeout(g,4))}();else{var h=t.createElement("script");h.async=!0,h.charset="utf-8",h.src=v+function(){var e=n.version===!0?n.v||(new Date).getTime():n.version||"";return e?"?v="+e:""}(),y.appendChild(h),!h.attachEvent||h.attachEvent.toString&&h.attachEvent.toString().indexOf("[native code")<0||i?h.addEventListener("load",function(e){c(e,v)},!1):h.attachEvent("onreadystatechange",function(e){c(e,v)}),n.modules[d]=v}return p},r.prototype.getStyle=function(t,n){var r=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return r[r.getPropertyValue?"getPropertyValue":"getAttribute"](n)},r.prototype.link=function(e,r,o){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof r&&(o=r);var c=(o||e).replace(/\.|\//g,""),s=u.id="layuicss-"+c,p=0;return u.rel="stylesheet",u.href=e+(n.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(s)||l.appendChild(u),"function"!=typeof r?i:(function f(){return++p>1e3*n.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(s),"width"))?function(){r()}():setTimeout(f,100))}(),i)},n.callback={},r.prototype.factory=function(e){if(layui[e])return"function"==typeof n.callback[e]?n.callback[e]:null},r.prototype.addcss=function(e,t,r){return layui.link(n.dir+"css/"+e,t,r)},r.prototype.img=function(e,t,n){var r=new Image;return r.src=e,r.complete?t(r):(r.onload=function(){r.onload=null,"function"==typeof t&&t(r)},void(r.onerror=function(e){r.onerror=null,"function"==typeof n&&n(e)}))},r.prototype.config=function(e){e=e||{};for(var t in e)n[t]=e[t];return this},r.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),r.prototype.extend=function(e){var t=this;e=e||{};for(var n in e)t[n]||t.modules[n]?a("模块名 "+n+" 已被占用"):t.modules[n]=e[n];return t},r.prototype.router=function(e){var t=this,e=e||location.hash,n={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),n.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),n.search[t[0]]=t[1]}():n.path.push(t)}),n):n},r.prototype.url=function(e){var t=this,n={pathname:function(){var t=e?function(){var t=(e.match(/\.[^.]+?\/.+/)||[])[0]||"";return t.replace(/^[^\/]+/,"").replace(/\?.+/,"")}():location.pathname;return t.replace(/^\//,"").split("/")}(),search:function(){var n={},r=(e?function(){var t=(e.match(/\?.+/)||[])[0]||"";return t.replace(/\#.+/,"")}():location.search).replace(/^\?+/,"").split("&");return t.each(r,function(e,t){var r=t.indexOf("="),o=function(){return r<0?t.substr(0,t.length):0!==r&&t.substr(0,r)}();o&&(n[o]=r>0?t.substr(r+1):null)}),n}(),hash:t.router(function(){return e?(e.match(/#.+/)||[])[0]||"":location.hash}())};return n},r.prototype.data=function(t,n,r){if(t=t||"layui",r=r||localStorage,e.JSON&&e.JSON.parse){if(null===n)return delete r[t];n="object"==typeof n?n:{key:n};try{var o=JSON.parse(r[t])}catch(a){var o={}}return"value"in n&&(o[n.key]=n.value),n.remove&&delete o[n.key],r[t]=JSON.stringify(o),n.key?o[n.key]:o}},r.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},r.prototype.device=function(t){var n=navigator.userAgent.toLowerCase(),r=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(n.match(t)||[])[1],e||!1},o={os:function(){return/windows/.test(n)?"windows":/linux/.test(n)?"linux":/iphone|ipod|ipad|ios/.test(n)?"ios":/mac/.test(n)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((n.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:r("micromessenger")};return t&&!o[t]&&(o[t]=r(t)),o.android=/android/.test(n),o.ios="ios"===o.os,o.mobile=!(!o.android&&!o.ios),o},r.prototype.hint=function(){return{error:a}},r.prototype.each=function(e,t){var n,r=this;if("function"!=typeof t)return r;if(e=e||[],e.constructor===Object){for(n in e)if(t.call(e[n],n,e[n]))break}else for(n=0;na?1:o 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{config "String" "app.cname" "端口转发系统"}} - V1.3 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /forward-server/views/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{config "String" "app.cname" "端口转发系统"}} - V1.3 11 | 12 | 13 | 14 | 15 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
      30 | 31 |
      32 | 端口转发 系统登录 33 |
      34 |
      35 |
      36 | 37 |
      38 | 39 |
      40 |
      41 |
      42 | 43 |
      44 | 45 |
      46 |
      47 | 48 |
      49 |
      50 | 51 |
      52 |
      53 |
      54 |
      55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/addBatchForward.html: -------------------------------------------------------------------------------- 1 | 2 |
      3 |
      4 | 5 |
      6 |
      7 |
      8 | 9 | 10 |
      11 | 12 |
      13 |
      14 |
      15 |
      16 |
      17 |
      18 | 19 | 20 |
      21 |
      22 | 23 |
      24 |
      25 | 29 |
      30 |
      31 | 32 |
      33 |
      34 | 35 |
      36 |
      37 | 40 |
      41 | 42 |
      43 |
      44 | 45 |
      46 | 47 |
      48 |
      49 | 50 | 51 | 82 | 83 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/apiDoc.html: -------------------------------------------------------------------------------- 1 | 2 |
      3 | 4 |
      5 |
        6 | 本工具提供RestfulApi,方便与其它系统工具进行集成,可在其它系统中直接通过接口,实现端口转发的开启和关闭。
        7 |       
      8 |
      9 | 10 |
      11 | 当前API的AUTH鉴权密钥为:{{config "String" "api.auth" "请在data.conf中配置api.auth属性"}} 12 |
      13 | 14 |
      15 | 开启端口转发 接口 16 |
      17 |
      18 |
      19 | 20 |
      21 | 22 |
      23 | 24 |
      25 |
      26 | 27 |
      28 | 29 |
      30 |
      31 |
      32 | 33 |
      34 | 35 |
      36 |
      37 |
      38 | 39 | 40 |
      41 |
      42 | 43 |
      44 | 45 | 46 |
      47 |
      48 |
      49 | 50 |
      51 | 52 | 53 |
      54 |
      55 |
      56 | 57 |
      58 | 59 | 60 |
      61 |
      62 |
      63 | 64 |
      65 | 66 | 67 |
      68 |
      69 |
      70 |
       71 | /*
       72 | 接口返回JSON:
       73 | Code:0表示成功,1表示失败
       74 | Msg: 成功或失败的消息内容
       75 | */
       76 | {
       77 |   "Code": 0,
       78 |   "Msg": ""
       79 | }
       80 |       
      81 |
      82 | 83 |
      84 | 85 |
      86 | 关闭端口转发 接口 87 |
      88 |
      89 |
      90 | 91 |
      92 | 93 |
      94 | 95 |
      96 |
      97 | 98 |
      99 | 100 |
      101 |
      102 |
      103 | 104 |
      105 | 106 |
      107 |
      108 |
      109 | 110 | 111 |
      112 |
      113 | 114 |
      115 | 116 | 117 |
      118 |
      119 |
      120 | 121 |
      122 | 123 | 124 |
      125 |
      126 |
      127 | 128 |
      129 | 130 | 131 |
      132 |
      133 |
      134 | 135 |
      136 | 137 | 138 |
      139 |
      140 |
      141 |
      142 | /*
      143 | 接口返回JSON:
      144 | Code:0表示成功,1表示失败
      145 | Msg: 成功或失败的消息内容
      146 | */
      147 | {
      148 |   "Code": 0,
      149 |   "Msg": ""
      150 | }
      151 |       
      152 |
      153 |
      154 | 155 |
      156 | 157 | 170 | 171 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/changePwd.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
      4 | 修改当前用户的登录密码 5 |
      6 | 7 |
      8 |
      9 | 10 |
      11 | 12 |
      13 |
      14 |
      15 | 16 |
      17 | 18 |
      19 |
      20 | 21 |
      22 |
      23 | 24 |
      25 |
      26 | 27 |
      28 | 29 |
      30 | 31 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/footer.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/forward-server/views/ucenter/footer.html -------------------------------------------------------------------------------- /forward-server/views/ucenter/forwardForm.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 | 4 |
      5 |
      6 | 7 |
      8 | 9 | 10 |
      11 |
      12 |
      13 | 14 |
      15 | 16 | 17 |
      18 |
      19 |
      20 | 21 |
      22 | 23 |
      24 |
      25 |
      26 | 27 |
      28 | 31 |
      32 |
      33 |
      34 | 35 |
      36 | 40 |
      41 |
      42 |
      43 | 44 |
      45 | 46 |
      47 |
      48 |
      49 | 50 |
      51 | 52 |
      53 |
      54 |
      55 | 56 |
      57 | 58 |
      59 |
      60 |
      61 | 62 |
      63 | 64 | 65 |
      66 |
      67 |
      68 |
       69 | *多分发说明:
       70 | 如果不需要“多分发”,请留空;
       71 | 如果需要分发到多个端口,填写格式为 “IP:端口”,多个用 ; 号隔开
       72 | 如 192.1.1.100:8081;192.1.1.100:8082
       73 | 
       74 | *一键开启说明,设置为“支持”后:
       75 | 通过在配置文件中添加  onstart.forward = "true" ,可让程序每次运行后就开始转发该规则,或者在列表页面通过点击“一键开启”。
       76 | 
       77 |         
      78 |
      79 |
      80 | 81 |
      82 | 83 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/importForward.html: -------------------------------------------------------------------------------- 1 | 2 |
      3 |
      4 | 5 |
      6 | 7 |
      8 |
      9 |
      10 | 11 |
      12 | 13 |
      14 |
      15 |
      16 |
      17 |       假设分隔符为 , 号,导入数据格式为:
      18 |       名称,本地监听地址,本地监听端口,协议类型,目标地址,目标端口
      19 |       
      20 |       举例:
      21 |       测试1,127.0.0.1,8000,TCP,127.0.0.1,3306
      22 |       测试2,0.0.0.0,8001,TCP,127.0.0.1,27017
      23 |       ,,8002,UDP,192.168.1.100,1443
      24 |     
      25 |
      26 | 27 |
      28 | 29 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/index.html: -------------------------------------------------------------------------------- 1 | 2 |
      3 | 4 | 5 |
      6 | 系统信息 7 |
      8 |
      9 |
      10 | 11 |
      12 | 13 |
      14 | 15 |
      16 |
      17 | 18 |
      19 | 20 |
      21 |
      22 |
      23 | 24 |
      25 | 26 |
      27 |
      28 |
      29 | 30 |
      31 | 32 | 33 |
      34 |
      35 | 36 |
      37 |
      38 | 39 | 52 | 53 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{config "String" "app.cname" "端口转发系统"}} - V1.3.5 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 47 | 48 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | {{template "ucenter/header.html" .}} {{.LayoutContent}} 91 | 92 | 93 | {{template "ucenter/footer.html" .}} 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /forward-server/views/ucenter/main.html: -------------------------------------------------------------------------------- 1 |
      2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module port-forward 2 | 3 | go 1.14 4 | 5 | require golang.org/x/tools/gopls v0.5.1 // indirect 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= 6 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 7 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 8 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 9 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 10 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 11 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 14 | github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 15 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 16 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 17 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 20 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 21 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 22 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 23 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 24 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 25 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 26 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 27 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 28 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 29 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 30 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 31 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 32 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 33 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= 35 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 36 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 40 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 41 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 42 | golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 43 | golang.org/x/tools v0.0.0-20200930165741-f1523d29dbb9 h1:1R38tQp22dcHpTKJPjgVa16FhlDy/kHEaCM/ndi/FIc= 44 | golang.org/x/tools v0.0.0-20200930165741-f1523d29dbb9/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 45 | golang.org/x/tools v0.0.0-20201014231627-1610a49f37af h1:VIUWFyOgzG3c0t9KYop5Ybp4m56LupfOnFYX7Ipnz+I= 46 | golang.org/x/tools/gopls v0.5.1 h1:AF3Uh7HF08SZpKFfgJO6zfF3bbxyDXWqdkK4kMXiQ1o= 47 | golang.org/x/tools/gopls v0.5.1/go.mod h1:6o4z8hIK5Ef1f7mLl0oxi5+SaYAw0rfyo6YZ7TcAfRY= 48 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 49 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 50 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 51 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 52 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 55 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 57 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 58 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 59 | honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= 60 | honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 61 | mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d h1:t8TAw9WgTLghti7RYkpPmqk4JtQ3+wcP5GgZqgWeWLQ= 62 | mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws= 63 | mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A= 64 | mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8= 65 | -------------------------------------------------------------------------------- /screenshot/ApiDoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/screenshot/ApiDoc.png -------------------------------------------------------------------------------- /screenshot/List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/screenshot/List.png -------------------------------------------------------------------------------- /screenshot/Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/screenshot/Login.png -------------------------------------------------------------------------------- /screenshot/Tras_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/screenshot/Tras_1.png -------------------------------------------------------------------------------- /screenshot/Tras_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/screenshot/Tras_2.png -------------------------------------------------------------------------------- /screenshot/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tavenli/port-forward/0e9367f99f92b5b638efd5d0a3e76bcaa588dae6/screenshot/edit.png --------------------------------------------------------------------------------