From c74a6208a15f44ef2bb5d80fddcf9fdb963a1fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Fri, 14 Nov 2025 03:53:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8docker=20v29=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- internal/data/container.go | 260 ++++++++++++++++++------- internal/data/container_image.go | 62 ++++-- internal/data/container_network.go | 96 ++++++--- internal/data/container_volume.go | 47 +++-- internal/http/middleware/entrance.go | 3 +- internal/http/middleware/middleware.go | 2 +- 7 files changed, 341 insertions(+), 131 deletions(-) diff --git a/go.mod b/go.mod index 87098527..b1a75689 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( github.com/libtnb/sessions v1.2.1 github.com/libtnb/utils v1.2.1 github.com/mholt/acmez/v3 v3.1.4 + github.com/moby/moby/api v1.52.0 github.com/moby/moby/client v0.1.0 github.com/ncruces/go-sqlite3 v0.30.1 github.com/ncruces/go-sqlite3/gormlite v0.30.1 @@ -110,7 +111,6 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/moby/api v1.52.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect diff --git a/internal/data/container.go b/internal/data/container.go index 9c5c9d41..02673e27 100644 --- a/internal/data/container.go +++ b/internal/data/container.go @@ -3,15 +3,18 @@ package data import ( "context" "fmt" + "io" "slices" + "strconv" "strings" "time" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/client" "github.com/acepanel/panel/internal/biz" "github.com/acepanel/panel/internal/http/request" - "github.com/acepanel/panel/pkg/shell" "github.com/acepanel/panel/pkg/types" ) @@ -89,125 +92,242 @@ func (r *containerRepo) ListByName(names string) ([]types.Container, error) { // Create 创建容器 func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) { - var sb strings.Builder - sb.WriteString(fmt.Sprintf("docker run -d --name %s", req.Name)) - if req.PublishAllPorts { - sb.WriteString(" -P") - } else { - for _, port := range req.Ports { - sb.WriteString(" -p ") - if port.Host.IsValid() { - sb.WriteString(fmt.Sprintf("%s:", port.Host.String())) - } - if port.HostStart == port.HostEnd || port.ContainerStart == port.ContainerEnd { - sb.WriteString(fmt.Sprintf("%d:%d/%s", port.HostStart, port.ContainerStart, port.Protocol)) - } else { - sb.WriteString(fmt.Sprintf("%d-%d:%d-%d/%s", port.HostStart, port.HostEnd, port.ContainerStart, port.ContainerEnd, port.Protocol)) - } - } + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return "", err } - if req.Network != "" { - sb.WriteString(fmt.Sprintf(" --network %s", req.Network)) - } - for _, volume := range req.Volumes { - sb.WriteString(fmt.Sprintf(" -v %s:%s:%s", volume.Host, volume.Container, volume.Mode)) - } - for _, label := range req.Labels { - sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value)) - } - for _, env := range req.Env { - sb.WriteString(fmt.Sprintf(" -e %s=%s", env.Key, env.Value)) - } - if len(req.Entrypoint) > 0 { - sb.WriteString(fmt.Sprintf(" --entrypoint '%s'", strings.Join(req.Entrypoint, " "))) - } - if len(req.Command) > 0 { - sb.WriteString(fmt.Sprintf(" '%s'", strings.Join(req.Command, " "))) - } - if req.RestartPolicy != "" { - sb.WriteString(fmt.Sprintf(" --restart %s", req.RestartPolicy)) - } - if req.AutoRemove { - sb.WriteString(" --rm") - } - if req.Privileged { - sb.WriteString(" --privileged") - } - if req.OpenStdin { - sb.WriteString(" -i") - } - if req.Tty { - sb.WriteString(" -t") - } - if req.CPUShares > 0 { - sb.WriteString(fmt.Sprintf(" --cpu-shares %d", req.CPUShares)) - } - if req.CPUs > 0 { - sb.WriteString(fmt.Sprintf(" --cpus %d", req.CPUs)) - } - if req.Memory > 0 { - sb.WriteString(fmt.Sprintf(" --memory %d", req.Memory)) + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + ctx := context.Background() + + // 构建容器配置 + config := &container.Config{ + Image: req.Image, + Tty: req.Tty, + OpenStdin: req.OpenStdin, + AttachStdin: req.OpenStdin, + AttachStdout: true, + AttachStderr: true, + Env: types.KVToSlice(req.Env), + Labels: types.KVToMap(req.Labels), + Entrypoint: req.Entrypoint, + Cmd: req.Command, } - sb.WriteString(" %s bash") - return shell.ExecfWithTTY(sb.String(), req.Image) + // 构建主机配置 + hostConfig := &container.HostConfig{ + AutoRemove: req.AutoRemove, + Privileged: req.Privileged, + PublishAllPorts: req.PublishAllPorts, + } + + // 构建网络配置 + networkConfig := &network.NetworkingConfig{} + if req.Network != "" { + switch req.Network { + case "host", "none", "bridge": + hostConfig.NetworkMode = container.NetworkMode(req.Network) + } + networkConfig.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}} + } + + // 设置端口映射 + if !req.PublishAllPorts && len(req.Ports) > 0 { + portMap := make(network.PortMap) + for _, port := range req.Ports { + if port.ContainerStart-port.ContainerEnd != port.HostStart-port.HostEnd { + return "", fmt.Errorf("容器端口和主机端口数量不匹配(容器: %d 主机: %d)", port.ContainerStart-port.ContainerEnd, port.HostStart-port.HostEnd) + } + if port.ContainerStart > port.ContainerEnd || port.HostStart > port.HostEnd || port.ContainerStart < 1 || port.HostStart < 1 { + return "", fmt.Errorf("端口范围不正确") + } + + count := uint(0) + for i := port.HostStart; i <= port.HostEnd; i++ { + bindItem := network.PortBinding{HostIP: port.Host, HostPort: strconv.Itoa(int(i))} + portMap[network.MustParsePort(fmt.Sprintf("%d/%s", port.ContainerStart+count, port.Protocol))] = []network.PortBinding{bindItem} + count++ + } + } + + exposed := make(network.PortSet) + for port := range portMap { + exposed[port] = struct{}{} + } + + config.ExposedPorts = exposed + hostConfig.PortBindings = portMap + } + // 设置卷挂载 + volumes := make(map[string]struct{}) + for _, v := range req.Volumes { + volumes[v.Container] = struct{}{} + hostConfig.Binds = append(hostConfig.Binds, fmt.Sprintf("%s:%s:%s", v.Host, v.Container, v.Mode)) + } + config.Volumes = volumes + // 设置重启策略 + hostConfig.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)} + if req.RestartPolicy == "on-failure" { + hostConfig.RestartPolicy.MaximumRetryCount = 5 + } + // 设置资源限制 + hostConfig.CPUShares = req.CPUShares + hostConfig.NanoCPUs = req.CPUs * 1e9 + hostConfig.Memory = req.Memory * 1024 * 1024 + hostConfig.MemorySwap = 0 + + // 创建容器 + resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{ + Name: req.Name, + Config: config, + HostConfig: hostConfig, + NetworkingConfig: networkConfig, + }) + if err != nil { + return "", err + } + + // 启动容器 + _, _ = apiClient.ContainerStart(ctx, resp.ID, client.ContainerStartOptions{}) + return resp.ID, nil } // Remove 移除容器 func (r *containerRepo) Remove(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker rm -f %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerRemove(context.Background(), id, client.ContainerRemoveOptions{ + Force: true, + }) return err } // Start 启动容器 func (r *containerRepo) Start(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker start %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerStart(context.Background(), id, client.ContainerStartOptions{}) return err } // Stop 停止容器 func (r *containerRepo) Stop(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker stop %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerStop(context.Background(), id, client.ContainerStopOptions{}) return err } // Restart 重启容器 func (r *containerRepo) Restart(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker restart %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerRestart(context.Background(), id, client.ContainerRestartOptions{}) return err } // Pause 暂停容器 func (r *containerRepo) Pause(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker pause %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerPause(context.Background(), id, client.ContainerPauseOptions{}) return err } // Unpause 恢复容器 func (r *containerRepo) Unpause(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker unpause %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerUnpause(context.Background(), id, client.ContainerUnpauseOptions{}) return err } // Kill 杀死容器 func (r *containerRepo) Kill(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker kill %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerKill(context.Background(), id, client.ContainerKillOptions{ + Signal: "KILL", + }) return err } // Rename 重命名容器 func (r *containerRepo) Rename(id string, newName string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker rename %s %s", id, newName) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerRename(context.Background(), id, client.ContainerRenameOptions{ + NewName: newName, + }) return err } // Logs 查看容器日志 func (r *containerRepo) Logs(id string) (string, error) { - return shell.ExecfWithTimeout(2*time.Minute, "docker logs --tail 100 %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return "", err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + reader, err := apiClient.ContainerLogs(context.Background(), id, client.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Tail: "100", + }) + if err != nil { + return "", err + } + defer func(reader io.ReadCloser) { _ = reader.Close() }(reader) + + logs, err := io.ReadAll(reader) + if err != nil { + return "", err + } + + return string(logs), nil } // Prune 清理未使用的容器 func (r *containerRepo) Prune() error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker container prune -f") + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ContainerPrune(context.Background(), client.ContainerPruneOptions{}) return err } diff --git a/internal/data/container_image.go b/internal/data/container_image.go index afdb0e5e..04a2c144 100644 --- a/internal/data/container_image.go +++ b/internal/data/container_image.go @@ -2,16 +2,17 @@ package data import ( "context" - "fmt" + "encoding/base64" + "encoding/json" "slices" "strings" "time" + "github.com/moby/moby/api/types/registry" "github.com/moby/moby/client" "github.com/acepanel/panel/internal/biz" "github.com/acepanel/panel/internal/http/request" - "github.com/acepanel/panel/pkg/shell" "github.com/acepanel/panel/pkg/tools" "github.com/acepanel/panel/pkg/types" ) @@ -59,33 +60,58 @@ func (r *containerImageRepo) List() ([]types.ContainerImage, error) { // Pull 拉取镜像 func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error { - var sb strings.Builder - - if req.Auth { - sb.WriteString(fmt.Sprintf("docker login -u %s -p %s", req.Username, req.Password)) - if _, err := shell.Exec(sb.String()); err != nil { - return fmt.Errorf("login failed: %w", err) - } - sb.Reset() - } - - sb.WriteString(fmt.Sprintf("docker pull %s", req.Name)) - - if _, err := shell.Exec(sb.String()); err != nil { + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { return err } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) - return nil + options := client.ImagePullOptions{} + if req.Auth { + authConfig := registry.AuthConfig{ + Username: req.Username, + Password: req.Password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return err + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + options.RegistryAuth = authStr + } + + out, err := apiClient.ImagePull(context.Background(), req.Name, options) + if err != nil { + return err + } + defer out.Close() + + return out.Wait(context.Background()) } // Remove 删除镜像 func (r *containerImageRepo) Remove(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker rmi %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ImageRemove(context.Background(), id, client.ImageRemoveOptions{ + Force: true, + PruneChildren: true, + }) return err } // Prune 清理未使用的镜像 func (r *containerImageRepo) Prune() error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker image prune -f") + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.ImagePrune(context.Background(), client.ImagePruneOptions{}) return err } diff --git a/internal/data/container_network.go b/internal/data/container_network.go index 8681f31d..d92deba8 100644 --- a/internal/data/container_network.go +++ b/internal/data/container_network.go @@ -3,15 +3,15 @@ package data import ( "context" "fmt" + "net/netip" "slices" "strings" - "time" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/client" "github.com/acepanel/panel/internal/biz" "github.com/acepanel/panel/internal/http/request" - "github.com/acepanel/panel/pkg/shell" "github.com/acepanel/panel/pkg/types" ) @@ -74,43 +74,93 @@ func (r *containerNetworkRepo) List() ([]types.ContainerNetwork, error) { // Create 创建网络 func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (string, error) { - var sb strings.Builder - - sb.WriteString(fmt.Sprintf("docker network create --driver %s", req.Driver)) - sb.WriteString(fmt.Sprintf(" %s", req.Name)) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return "", err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + var ipamConfigs []network.IPAMConfig if req.Ipv4.Enabled { - sb.WriteString(fmt.Sprintf(" --subnet %s", req.Ipv4.Subnet)) - sb.WriteString(fmt.Sprintf(" --gateway %s", req.Ipv4.Gateway)) - if req.Ipv4.IPRange != "" { - sb.WriteString(fmt.Sprintf(" --ip-range %s", req.Ipv4.IPRange)) + v4Subnet, err := netip.ParsePrefix(req.Ipv4.Subnet) + if err != nil { + return "", fmt.Errorf("invalid ipv4 subnet: %w", err) } + v4Gateway, err := netip.ParseAddr(req.Ipv4.Gateway) + if err != nil { + return "", fmt.Errorf("invalid ipv4 gateway: %w", err) + } + v4IPRange, err := netip.ParsePrefix(req.Ipv4.IPRange) + if err != nil { + return "", fmt.Errorf("invalid ipv4 ip range: %w", err) + } + ipamConfigs = append(ipamConfigs, network.IPAMConfig{ + Subnet: v4Subnet, + Gateway: v4Gateway, + IPRange: v4IPRange, + }) } if req.Ipv6.Enabled { - sb.WriteString(fmt.Sprintf(" --subnet %s", req.Ipv6.Subnet)) - sb.WriteString(fmt.Sprintf(" --gateway %s", req.Ipv6.Gateway)) - if req.Ipv6.IPRange != "" { - sb.WriteString(fmt.Sprintf(" --ip-range %s", req.Ipv6.IPRange)) + v6Subnet, err := netip.ParsePrefix(req.Ipv6.Subnet) + if err != nil { + return "", fmt.Errorf("invalid ipv6 subnet: %w", err) } - } - for _, label := range req.Labels { - sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value)) - } - for _, option := range req.Options { - sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value)) + v6Gateway, err := netip.ParseAddr(req.Ipv6.Gateway) + if err != nil { + return "", fmt.Errorf("invalid ipv6 gateway: %w", err) + } + v6IPRange, err := netip.ParsePrefix(req.Ipv6.IPRange) + if err != nil { + return "", fmt.Errorf("invalid ipv6 ip range: %w", err) + } + ipamConfigs = append(ipamConfigs, network.IPAMConfig{ + Subnet: v6Subnet, + Gateway: v6Gateway, + IPRange: v6IPRange, + }) } - return shell.Exec(sb.String()) + options := client.NetworkCreateOptions{ + EnableIPv4: &req.Ipv4.Enabled, + EnableIPv6: &req.Ipv6.Enabled, + Driver: req.Driver, + Options: types.KVToMap(req.Options), + Labels: types.KVToMap(req.Labels), + } + if len(ipamConfigs) > 0 { + options.IPAM = &network.IPAM{ + Config: ipamConfigs, + } + } + + resp, err := apiClient.NetworkCreate(context.Background(), req.Name, options) + if err != nil { + return "", err + } + + return resp.ID, err } // Remove 删除网络 func (r *containerNetworkRepo) Remove(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker network rm -f %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.NetworkRemove(context.Background(), id, client.NetworkRemoveOptions{}) return err } // Prune 清理未使用的网络 func (r *containerNetworkRepo) Prune() error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker network prune -f") + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.NetworkPrune(context.Background(), client.NetworkPruneOptions{}) return err } diff --git a/internal/data/container_volume.go b/internal/data/container_volume.go index 8b328fa9..6e7f6427 100644 --- a/internal/data/container_volume.go +++ b/internal/data/container_volume.go @@ -2,7 +2,6 @@ package data import ( "context" - "fmt" "slices" "strings" "time" @@ -11,7 +10,6 @@ import ( "github.com/acepanel/panel/internal/biz" "github.com/acepanel/panel/internal/http/request" - "github.com/acepanel/panel/pkg/shell" "github.com/acepanel/panel/pkg/tools" "github.com/acepanel/panel/pkg/types" ) @@ -60,32 +58,47 @@ func (r *containerVolumeRepo) List() ([]types.ContainerVolume, error) { // Create 创建存储卷 func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (string, error) { - var sb strings.Builder - sb.WriteString("docker volume create") - sb.WriteString(fmt.Sprintf(" %s", req.Name)) - - if req.Driver != "" { - sb.WriteString(fmt.Sprintf(" --driver %s", req.Driver)) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return "", err } - for _, label := range req.Labels { - sb.WriteString(fmt.Sprintf(" --label %s=%s", label.Key, label.Value)) + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + resp, err := apiClient.VolumeCreate(context.Background(), client.VolumeCreateOptions{ + Name: req.Name, + Driver: req.Driver, + DriverOpts: types.KVToMap(req.Options), + Labels: types.KVToMap(req.Labels), + }) + if err != nil { + return "", err } - for _, option := range req.Options { - sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value)) - } - - return shell.Exec(sb.String()) + return resp.Volume.Name, nil } // Remove 删除存储卷 func (r *containerVolumeRepo) Remove(id string) error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker volume rm -f %s", id) + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.VolumeRemove(context.Background(), id, client.VolumeRemoveOptions{ + Force: true, + }) return err } // Prune 清理未使用的存储卷 func (r *containerVolumeRepo) Prune() error { - _, err := shell.ExecfWithTimeout(2*time.Minute, "docker volume prune -f") + apiClient, err := getDockerClient("/var/run/docker.sock") + if err != nil { + return err + } + defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient) + + _, err = apiClient.VolumePrune(context.Background(), client.VolumePruneOptions{}) return err } diff --git a/internal/http/middleware/entrance.go b/internal/http/middleware/entrance.go index 510060b7..a6ad1c62 100644 --- a/internal/http/middleware/entrance.go +++ b/internal/http/middleware/entrance.go @@ -6,12 +6,13 @@ import ( "slices" "strings" - "github.com/acepanel/panel/pkg/punycode" "github.com/go-chi/chi/v5" "github.com/knadh/koanf/v2" "github.com/leonelquinteros/gotext" "github.com/libtnb/chix" "github.com/libtnb/sessions" + + "github.com/acepanel/panel/pkg/punycode" ) // Entrance 确保通过正确的入口访问 diff --git a/internal/http/middleware/middleware.go b/internal/http/middleware/middleware.go index e05c526b..428d93d5 100644 --- a/internal/http/middleware/middleware.go +++ b/internal/http/middleware/middleware.go @@ -5,7 +5,6 @@ import ( "net/http" "path/filepath" - "github.com/acepanel/panel/internal/app" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/httplog/v3" @@ -16,6 +15,7 @@ import ( sessionmiddleware "github.com/libtnb/sessions/middleware" "gopkg.in/natefinch/lumberjack.v2" + "github.com/acepanel/panel/internal/app" "github.com/acepanel/panel/internal/biz" )