fix: 修复关闭SSH终端标签页时会话状态未更新的问题

This commit is contained in:
2026-04-18 02:35:38 +08:00
commit 6e2e2f9387
43467 changed files with 5489040 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
package cache
import (
"time"
"github.com/patrickmn/go-cache"
)
const (
NoExpiration = -1
RememberMeExpiration = time.Hour * time.Duration(24*14)
NotRememberExpiration = time.Hour * time.Duration(2)
LoginLockExpiration = time.Minute * time.Duration(5)
)
var TokenManager *cache.Cache
var LoginFailedKeyManager *cache.Cache
var UserRolesManager *cache.Cache
func init() {
TokenManager = cache.New(5*time.Minute, 10*time.Minute)
LoginFailedKeyManager = cache.New(5*time.Minute, 10*time.Minute)
UserRolesManager = cache.New(5*time.Minute, 10*time.Minute)
}
+16
View File
@@ -0,0 +1,16 @@
package cron
import "github.com/robfig/cron/v3"
var GlobalCron *cron.Cron
type Job cron.Job
func init() {
GlobalCron = cron.New(cron.WithSeconds())
GlobalCron.Start()
}
func JobId(jobId int) cron.EntryID {
return cron.EntryID(jobId)
}
+105
View File
@@ -0,0 +1,105 @@
package gateway
import (
"errors"
"fmt"
"net"
"next-terminal/server/common/term"
"os"
"sync"
"next-terminal/server/utils"
"golang.org/x/crypto/ssh"
)
// Gateway 接入网关
type Gateway struct {
ID string // 接入网关ID
IP string
Port int
Username string
Password string
PrivateKey string
Passphrase string
Connected bool // 是否已连接
Message string // 失败原因
SshClient *ssh.Client
mutex sync.Mutex
tunnels map[string]*Tunnel
}
func (g *Gateway) OpenSshTunnel(id, ip string, port int) (exposedIP string, exposedPort int, err error) {
g.mutex.Lock()
defer g.mutex.Unlock()
if !g.Connected {
sshClient, err := term.NewSshClient(g.IP, g.Port, g.Username, g.Password, g.PrivateKey, g.Passphrase)
if err != nil {
g.Connected = false
g.Message = "接入网关不可用:" + err.Error()
return "", 0, errors.New(g.Message)
} else {
g.Connected = true
g.SshClient = sshClient
g.Message = "使用中"
}
}
localPort, err := utils.GetAvailablePort()
if err != nil {
return "", 0, err
}
hostname, err := os.Hostname()
if err != nil {
return "", 0, err
}
// debug
//hostname = "0.0.0.0"
localAddr := fmt.Sprintf("%s:%d", hostname, localPort)
listener, err := net.Listen("tcp", localAddr)
if err != nil {
return "", 0, err
}
tunnel := &Tunnel{
id: id,
localHost: hostname,
//localHost: "docker.for.mac.host.internal",
localPort: localPort,
remoteHost: ip,
remotePort: port,
listener: listener,
}
go tunnel.Open(g.SshClient)
g.tunnels[tunnel.id] = tunnel
return tunnel.localHost, tunnel.localPort, nil
}
func (g *Gateway) CloseSshTunnel(id string) {
g.mutex.Lock()
defer g.mutex.Unlock()
t := g.tunnels[id]
if t != nil {
t.Close()
delete(g.tunnels, id)
}
if len(g.tunnels) == 0 {
if g.SshClient != nil {
_ = g.SshClient.Close()
}
g.Connected = false
g.Message = "暂未使用"
}
}
func (g *Gateway) Close() {
for id := range g.tunnels {
g.CloseSshTunnel(id)
}
}
+50
View File
@@ -0,0 +1,50 @@
package gateway
import (
"sync"
"next-terminal/server/model"
)
type manager struct {
gateways sync.Map
}
func (m *manager) GetById(id string) *Gateway {
if val, ok := m.gateways.Load(id); ok {
return val.(*Gateway)
}
return nil
}
func (m *manager) Add(model *model.AccessGateway) *Gateway {
g := &Gateway{
ID: model.ID,
IP: model.IP,
Port: model.Port,
Username: model.Username,
Password: model.Password,
PrivateKey: model.PrivateKey,
Passphrase: model.Passphrase,
Connected: false,
SshClient: nil,
Message: "暂未使用",
tunnels: make(map[string]*Tunnel),
}
m.gateways.Store(g.ID, g)
return g
}
func (m *manager) Del(id string) {
g := m.GetById(id)
if g != nil {
g.Close()
}
m.gateways.Delete(id)
}
var GlobalGatewayManager *manager
func init() {
GlobalGatewayManager = &manager{}
}
+57
View File
@@ -0,0 +1,57 @@
package gateway
import (
"fmt"
"io"
"net"
"golang.org/x/crypto/ssh"
)
type Tunnel struct {
id string // 唯一标识
localHost string // 本地监听地址
localPort int // 本地端口
remoteHost string // 远程连接地址
remotePort int // 远程端口
listener net.Listener
localConnections []net.Conn
remoteConnections []net.Conn
}
func (r *Tunnel) Open(sshClient *ssh.Client) {
for {
localConn, err := r.listener.Accept()
if err != nil {
return
}
r.localConnections = append(r.localConnections, localConn)
remoteAddr := fmt.Sprintf("%s:%d", r.remoteHost, r.remotePort)
remoteConn, err := sshClient.Dial("tcp", remoteAddr)
if err != nil {
return
}
r.remoteConnections = append(r.remoteConnections, remoteConn)
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
}
func (r *Tunnel) Close() {
for i := range r.localConnections {
_ = r.localConnections[i].Close()
}
r.localConnections = nil
for i := range r.remoteConnections {
_ = r.remoteConnections[i].Close()
}
r.remoteConnections = nil
_ = r.listener.Close()
}
func copyConn(writer, reader net.Conn) {
_, _ = io.Copy(writer, reader)
}
+12
View File
@@ -0,0 +1,12 @@
package license
type License struct {
Type string `json:"type"` // 类型:免费版 free,会员版 vip, 旗舰版 ultimate, 企业版 enterprise
MachineId string `json:"machineId"` // 唯一机器码:免费版为空
Assert int64 `json:"assert"` // 资产数量
Concurrent int64 `json:"concurrent"` // 并发数量
User int64 `json:"user"` // 用户数量
Expired int64 `json:"expired"` // 过期时间
}
var CurrentLicense *License
+66
View File
@@ -0,0 +1,66 @@
package security
import (
"sort"
"sync"
)
type Security struct {
ID string
Rule string
IP string
Priority int64 // 越小优先级越高
}
type Manager struct {
securities sync.Map
values []*Security
}
func NewManager() *Manager {
return &Manager{}
}
func (m *Manager) Clear() {
m.securities.Range(func(k, _ interface{}) bool {
m.securities.Delete(k)
return true
})
}
func (m *Manager) LoadData() {
var values []*Security
m.securities.Range(func(key, value interface{}) bool {
if security, ok := value.(*Security); ok {
values = append(values, security)
}
return true
})
sort.Slice(values, func(i, j int) bool {
// 优先级数字越小代表优先级越高,因此此处用小于号
return values[i].Priority < values[j].Priority
})
m.values = values
}
func (m *Manager) Values() []*Security {
return m.values
}
func (m *Manager) Add(s *Security) {
m.securities.Store(s.ID, s)
m.LoadData()
}
func (m *Manager) Del(id string) {
m.securities.Delete(id)
m.LoadData()
}
var GlobalSecurityManager *Manager
func init() {
GlobalSecurityManager = NewManager()
}
+119
View File
@@ -0,0 +1,119 @@
package session
import (
"next-terminal/server/common/guacamole"
"next-terminal/server/common/term"
"sync"
"github.com/gorilla/websocket"
"next-terminal/server/dto"
)
type Session struct {
ID string
Protocol string
Mode string
WebSocket *websocket.Conn
GuacdTunnel *guacamole.Tunnel
NextTerminal *term.NextTerminal
Observer *Manager
mutex sync.Mutex
Uptime int64
Hostname string
}
func (s *Session) WriteMessage(msg dto.Message) error {
if s.WebSocket == nil {
return nil
}
defer s.mutex.Unlock()
s.mutex.Lock()
message := []byte(msg.ToString())
return s.WebSocket.WriteMessage(websocket.TextMessage, message)
}
func (s *Session) WriteString(str string) error {
if s.WebSocket == nil {
return nil
}
defer s.mutex.Unlock()
s.mutex.Lock()
message := []byte(str)
return s.WebSocket.WriteMessage(websocket.TextMessage, message)
}
func (s *Session) Close() {
if s.GuacdTunnel != nil {
_ = s.GuacdTunnel.Close()
}
if s.NextTerminal != nil {
s.NextTerminal.Close()
}
if s.WebSocket != nil {
_ = s.WebSocket.Close()
}
}
type Manager struct {
id string
sessions sync.Map
}
func NewManager() *Manager {
return &Manager{}
}
func NewObserver(id string) *Manager {
return &Manager{
id: id,
}
}
func (m *Manager) GetById(id string) *Session {
value, ok := m.sessions.Load(id)
if ok {
return value.(*Session)
}
return nil
}
func (m *Manager) Add(s *Session) {
m.sessions.Store(s.ID, s)
}
func (m *Manager) Del(id string) {
session := m.GetById(id)
if session != nil {
session.Close()
if session.Observer != nil {
session.Observer.Clear()
}
}
m.sessions.Delete(id)
}
func (m *Manager) Clear() {
m.sessions.Range(func(key, value interface{}) bool {
if session, ok := value.(*Session); ok {
session.Close()
}
m.sessions.Delete(key)
return true
})
}
func (m *Manager) Range(f func(key string, value *Session)) {
m.sessions.Range(func(key, value interface{}) bool {
if session, ok := value.(*Session); ok {
f(key.(string), session)
}
return true
})
}
var GlobalSessionManager *Manager
func init() {
GlobalSessionManager = NewManager()
}
+104
View File
@@ -0,0 +1,104 @@
package stat
type systemLoad struct {
LoadStat *LoadStat `json:"loadStat"`
Mem *Mem `json:"mem"`
MemStat []*entry `json:"memStat"`
Cpu *Cpu `json:"cpu"`
CpuStat []*entry `json:"cpuStat"`
Disk *Disk `json:"disk"`
DiskIOStat []*ioEntry `json:"diskIO"`
NetIOStat []*ioEntry `json:"netIO"`
}
type Mem struct {
Total uint64 `json:"total"`
Available uint64 `json:"available"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
}
type Cpu struct {
Count int `json:"count"`
PhyCount int `json:"phyCount"`
UsedPercent float64 `json:"usedPercent"`
Info []*CpuInfo `json:"info"`
}
type Disk struct {
Total uint64 `json:"total"`
Used uint64 `json:"used"`
Available uint64 `json:"available"`
UsedPercent float64 `json:"usedPercent"`
}
type CpuInfo struct {
ModelName string `json:"modelName"`
CacheSize int32 `json:"cacheSize"`
MHZ float64 `json:"mhz"`
}
type LoadStat struct {
Load1 float64 `json:"load1"`
Load5 float64 `json:"load5"`
Load15 float64 `json:"load15"`
Percent float64 `json:"percent"`
}
type entry struct {
Time string `json:"time"`
Value float64 `json:"value"`
}
func NewStat(time string, value float64) *entry {
return &entry{
Time: time,
Value: value,
}
}
func NewIOStat(time string, read, write uint64) *ioEntry {
return &ioEntry{
Time: time,
Read: read,
Write: write,
}
}
type ioEntry struct {
Time string `json:"time"`
Read uint64 `json:"read"`
Write uint64 `json:"write"`
}
var SystemLoad *systemLoad
func init() {
SystemLoad = &systemLoad{
LoadStat: &LoadStat{
Load1: 0,
Load5: 0,
Load15: 0,
Percent: 0,
},
Mem: &Mem{
Total: 0,
Available: 0,
Used: 0,
UsedPercent: 0,
},
MemStat: make([]*entry, 0),
Cpu: &Cpu{
Count: 0,
UsedPercent: 0,
},
CpuStat: make([]*entry, 0),
Disk: &Disk{
Total: 0,
Used: 0,
UsedPercent: 0,
},
DiskIOStat: make([]*ioEntry, 0),
NetIOStat: make([]*ioEntry, 0),
}
}