Files
OpenFlare/openflare_server/service/tunnel.go
T
2026-06-01 08:38:31 +08:00

184 lines
4.6 KiB
Go

package service
import (
"encoding/json"
"errors"
"log/slog"
"openflare/model"
"strings"
"time"
)
type TunnelInput struct {
Name string `json:"name"`
Remark string `json:"remark"`
}
type TunnelView struct {
ID uint `json:"id"`
TunnelID string `json:"tunnel_id"`
Name string `json:"name"`
TunnelToken string `json:"tunnel_token"`
Status string `json:"status"`
ClientVersion string `json:"client_version"`
FrpVersion string `json:"frp_version"`
LastSeenAt time.Time `json:"last_seen_at"`
LastError string `json:"last_error"`
CurrentVersion string `json:"current_version"`
CurrentChecksum string `json:"current_checksum"`
ConnectedRelays []string `json:"connected_relays"`
Remark string `json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func ListTunnels() ([]*TunnelView, error) {
tunnels, err := model.ListTunnels()
if err != nil {
return nil, err
}
views := make([]*TunnelView, 0, len(tunnels))
for _, tunnel := range tunnels {
views = append(views, buildTunnelView(tunnel))
}
return views, nil
}
func GetTunnel(id uint) (*TunnelView, error) {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return nil, err
}
return buildTunnelView(tunnel), nil
}
func CreateTunnel(input TunnelInput) (*TunnelView, error) {
name := strings.TrimSpace(input.Name)
if name == "" {
return nil, errors.New("Tunnel 名称不能为空")
}
tunnelID, err := newTunnelID()
if err != nil {
return nil, err
}
tunnelToken, err := newRandomToken()
if err != nil {
return nil, err
}
tunnel := &model.Tunnel{
TunnelID: tunnelID,
Name: name,
TunnelToken: tunnelToken,
Status: "offline",
Remark: strings.TrimSpace(input.Remark),
}
if err := tunnel.Insert(); err != nil {
if model.IsUniqueConstraintError(err) {
return nil, errors.New("Tunnel 标识生成冲突,请重试")
}
return nil, err
}
slog.Info("tunnel created", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
return buildTunnelView(tunnel), nil
}
func UpdateTunnel(id uint, input TunnelInput) (*TunnelView, error) {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return nil, err
}
name := strings.TrimSpace(input.Name)
if name == "" {
return nil, errors.New("Tunnel 名称不能为空")
}
tunnel.Name = name
tunnel.Remark = strings.TrimSpace(input.Remark)
if err := tunnel.Update(); err != nil {
return nil, err
}
slog.Info("tunnel updated", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
return buildTunnelView(tunnel), nil
}
func DeleteTunnel(id uint) error {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return err
}
slog.Info("tunnel deleted", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
return tunnel.Delete()
}
func RotateTunnelToken(id uint) (*TunnelView, error) {
tunnel, err := model.GetTunnelByID(id)
if err != nil {
return nil, err
}
newToken, err := newRandomToken()
if err != nil {
return nil, err
}
tunnel.TunnelToken = newToken
if err := tunnel.Update(); err != nil {
return nil, err
}
slog.Info("tunnel token rotated", "tunnel_id", tunnel.TunnelID)
return buildTunnelView(tunnel), nil
}
func AuthenticateTunnelToken(token string) (*model.Tunnel, error) {
token = strings.TrimSpace(token)
if token == "" {
return nil, errors.New("缺少 Tunnel Token")
}
tunnel, err := model.GetTunnelByTunnelToken(token)
if err != nil {
return nil, errors.New("Tunnel Token 无效")
}
return tunnel, nil
}
func buildTunnelView(tunnel *model.Tunnel) *TunnelView {
if tunnel == nil {
return nil
}
relays := decodeTunnelConnectedRelays(tunnel.ConnectedRelays)
return &TunnelView{
ID: tunnel.ID,
TunnelID: tunnel.TunnelID,
Name: tunnel.Name,
TunnelToken: tunnel.TunnelToken,
Status: tunnel.Status,
ClientVersion: tunnel.ClientVersion,
FrpVersion: tunnel.FrpVersion,
LastSeenAt: tunnel.LastSeenAt,
LastError: tunnel.LastError,
CurrentVersion: tunnel.CurrentVersion,
CurrentChecksum: tunnel.CurrentChecksum,
ConnectedRelays: relays,
Remark: tunnel.Remark,
CreatedAt: tunnel.CreatedAt,
UpdatedAt: tunnel.UpdatedAt,
}
}
func decodeTunnelConnectedRelays(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" || raw == "[]" {
return make([]string, 0)
}
var relays []string
if err := json.Unmarshal([]byte(raw), &relays); err != nil {
return make([]string, 0)
}
return relays
}
func newTunnelID() (string, error) {
token, err := newRandomToken()
if err != nil {
return "", err
}
return "tun-" + token, nil
}