mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 22:46:38 +08:00
184 lines
4.6 KiB
Go
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
|
|
}
|