refactor: reorganize codebase into modular packages

feat(global): add global package for shared variables and types
refactor(handlers): move handlers to dedicated package and update imports
refactor(session): extract session management to separate package
refactor(config): move config handling to dedicated package
refactor(router): update route handlers to use new package structure
refactor(main): simplify main.go by moving logic to packages
This commit is contained in:
2026-04-22 12:57:04 +08:00
parent df8df78bab
commit ddf91299e7
11 changed files with 667 additions and 663 deletions

433
config/config.go Normal file
View File

@@ -0,0 +1,433 @@
package config
import (
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"strings"
"github.com/BurntSushi/toml"
)
type Config struct {
ListenAddr string `json:"listenAddr"`
ListenPort string `json:"listenPort"`
FrpcPath string `json:"frpcPath"`
InstancePath string `json:"instancePath"`
Debug bool `json:"debug"`
Watchdog struct {
Port int `json:"port"`
} `json:"watchdog"`
}
type InstanceInfo struct {
Name string `json:"name"`
ServerAddr string `json:"serverAddr"`
ServerPort string `json:"serverPort"`
AuthMethod string `json:"auth_method"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type FrpcProxyInfo struct {
Name string `json:"name"`
Type string `json:"type"`
LocalIP string `json:"local_ip"`
LocalPort int `json:"local_port"`
RemotePort int `json:"remote_port"`
}
type CreateInstanceRequest struct {
InstanceInfo InstanceInfo `json:"instanceInfo"`
BootAtStart bool `json:"bootAtStart"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type CreateProxyRequest struct {
InstanceID string `json:"instanceID"`
ProxyInfo FrpcProxyInfo `json:"proxyInfo"`
}
type FrpcConfig struct {
Global map[string]interface{} `toml:"-"`
Proxies []map[string]interface{} `toml:"proxies"`
}
var globalConfig *Config
func LoadConfig(configPath string, getInitSystem func() string) (*Config, error) {
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
if config.ListenAddr == "" {
config.ListenAddr = "0.0.0.0"
}
if config.ListenPort == "" {
config.ListenPort = "8080"
}
if config.FrpcPath == "" {
if getInitSystem() == "windows" {
config.FrpcPath = "frp_client/frpc.exe"
} else {
config.FrpcPath = "/usr/bin/frpc"
}
}
if config.InstancePath == "" {
config.InstancePath = "./configs"
}
if config.Watchdog.Port == 0 {
config.Watchdog.Port = 12380
}
if err := os.MkdirAll(config.InstancePath, 0755); err != nil {
return nil, fmt.Errorf("failed to create config directory: %w", err)
}
globalConfig = &config
return &config, nil
}
func GetConfig() (*Config, error) {
if globalConfig == nil {
return nil, errors.New("config not loaded")
}
return globalConfig, nil
}
func SaveConfig(configPath string, config *Config) error {
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(configPath, data, 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
globalConfig = config
return nil
}
func DecodeFrpcConfig(configContent string) (FrpcConfig, error) {
var config FrpcConfig
var rawMap map[string]interface{}
meta, err := toml.Decode(configContent, &rawMap)
if err != nil {
return config, fmt.Errorf("failed to parse config: %w", err)
}
config.Global = make(map[string]interface{})
config.Proxies = make([]map[string]interface{}, 0)
getNestedValue := func(m map[string]interface{}, key string) (interface{}, bool) {
parts := strings.Split(key, ".")
current := m
for i, part := range parts {
if i == len(parts)-1 {
val, ok := current[part]
return val, ok
}
next, ok := current[part].(map[string]interface{})
if !ok {
return nil, false
}
current = next
}
return nil, false
}
for _, key := range meta.Keys() {
keyStr := key.String()
if keyStr == "proxies" {
if proxies, ok := rawMap["proxies"].([]map[string]interface{}); ok {
config.Proxies = proxies
} else if proxies, ok := rawMap["proxies"].([]interface{}); ok {
for _, p := range proxies {
if pm, ok := p.(map[string]interface{}); ok {
config.Proxies = append(config.Proxies, pm)
}
}
}
} else if !strings.HasPrefix(keyStr, "proxies.") {
if val, ok := getNestedValue(rawMap, keyStr); ok {
config.Global[keyStr] = val
}
}
}
return config, nil
}
func EncodeFrpcConfig(config FrpcConfig) (string, error) {
var buf strings.Builder
if len(config.Global) > 0 {
for key, value := range config.Global {
switch v := value.(type) {
case string:
buf.WriteString(fmt.Sprintf("%s = %q\n", key, v))
case int:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case int64:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case float64:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
case bool:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
default:
data, err := json.Marshal(v)
if err != nil {
return "", fmt.Errorf("failed to marshal value for key %s: %w", key, err)
}
buf.WriteString(fmt.Sprintf("%s = %s\n", key, string(data)))
}
}
buf.WriteString("\n")
}
if len(config.Proxies) > 0 {
for _, proxy := range config.Proxies {
buf.WriteString("[[proxies]]\n")
for key, value := range proxy {
switch v := value.(type) {
case string:
buf.WriteString(fmt.Sprintf("%s = %q\n", key, v))
case int:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case int64:
buf.WriteString(fmt.Sprintf("%s = %d\n", key, v))
case float64:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
case bool:
buf.WriteString(fmt.Sprintf("%s = %v\n", key, v))
default:
data, err := json.Marshal(v)
if err != nil {
return "", fmt.Errorf("failed to marshal value for key %s: %w", key, err)
}
buf.WriteString(fmt.Sprintf("%s = %s\n", key, string(data)))
}
}
buf.WriteString("\n")
}
}
return strings.TrimSuffix(buf.String(), "\n"), nil
}
func HandleConfigFileCreate(configPath string, info InstanceInfo, setKeyText func(configPath, key, section, value string) error) error {
config := FrpcConfig{
Global: make(map[string]interface{}),
}
result, err := EncodeFrpcConfig(config)
if err != nil {
return fmt.Errorf("failed to encode empty config: %w", err)
}
if err := os.WriteFile(configPath, []byte(result), 0644); err != nil {
return fmt.Errorf("failed to create config file: %w", err)
}
configData := make(map[string]interface{})
configData["serverAddr"] = info.ServerAddr
configData["serverPort"] = info.ServerPort
configData["auth.method"] = info.AuthMethod
if authToken, ok := info.Additional["auth_token"]; ok {
configData["auth.token"] = authToken
}
for key, value := range info.Additional {
if key == "auth_token" || key == "auth_method" {
continue
}
configData[key] = value
}
for key, value := range configData {
var configValue string
if key == "serverPort" {
switch v := value.(type) {
case float64:
configValue = strconv.Itoa(int(v))
case int:
configValue = strconv.Itoa(v)
case string:
var intVal int
if _, err := fmt.Sscanf(v, "%d", &intVal); err == nil {
configValue = strconv.Itoa(intVal)
} else {
configValue = v
}
default:
configValue = fmt.Sprintf("%v", value)
}
} else {
configValue = fmt.Sprintf("%v", value)
}
if err := setKeyText(configPath, key, "", configValue); err != nil {
return fmt.Errorf("failed to set key %s: %w", key, err)
}
}
return nil
}
func AddFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) {
config, err := DecodeFrpcConfig(configContent)
if err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
proxy := map[string]interface{}{
"name": info.Name,
"type": info.Type,
"localIP": info.LocalIP,
"localPort": info.LocalPort,
"remotePort": info.RemotePort,
}
config.Proxies = append(config.Proxies, proxy)
result, err := EncodeFrpcConfig(config)
if err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return result, nil
}
func RemoveFrpcProxy(configContent string, proxyName string) (string, error) {
config, err := DecodeFrpcConfig(configContent)
if err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
var found bool
var newProxies []map[string]interface{}
for _, proxy := range config.Proxies {
if name, ok := proxy["name"].(string); ok && name == proxyName {
found = true
continue
}
newProxies = append(newProxies, proxy)
}
if !found {
return "", fmt.Errorf("proxy %s not found", proxyName)
}
config.Proxies = newProxies
result, err := EncodeFrpcConfig(config)
if err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return result, nil
}
func ModifyFrpcProxy(configContent string, info FrpcProxyInfo) (string, error) {
config, err := DecodeFrpcConfig(configContent)
if err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
var found bool
for i, proxy := range config.Proxies {
if name, ok := proxy["name"].(string); ok && name == info.Name {
config.Proxies[i] = map[string]interface{}{
"name": info.Name,
"type": info.Type,
"localIP": info.LocalIP,
"localPort": info.LocalPort,
"remotePort": info.RemotePort,
}
found = true
break
}
}
if !found {
return "", fmt.Errorf("proxy %s not found", info.Name)
}
result, err := EncodeFrpcConfig(config)
if err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return result, nil
}
func GetKeyText(configPath, key, section string, readFile func(string) ([]byte, error)) (value string, err error) {
configContent, err := readFile(configPath)
if err != nil {
return "", fmt.Errorf("failed to read config file: %w", err)
}
config, err := DecodeFrpcConfig(string(configContent))
if err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
if config.Global == nil {
return "", fmt.Errorf("config file has no global fields")
}
if v, ok := config.Global[key]; ok {
value = fmt.Sprintf("%v", v)
}
return value, nil
}
func SetKeyText(configPath, key, section string, value string, readFile func(string) ([]byte, error), writeFile func(string, []byte, os.FileMode) error) error {
configContent, err := readFile(configPath)
if err != nil {
return fmt.Errorf("failed to read config file: %w", err)
}
config, err := DecodeFrpcConfig(string(configContent))
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
if config.Global == nil {
config.Global = make(map[string]interface{})
}
if intVal, err := strconv.Atoi(value); err == nil {
config.Global[key] = intVal
} else {
config.Global[key] = value
}
result, err := EncodeFrpcConfig(config)
if err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
if err := writeFile(configPath, []byte(result), 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}