:refactor: 重构配置加载逻辑,移除不必要的Load函数,简化代码结构并优化Redis和Docker配置
This commit is contained in:
parent
c320e8951f
commit
2e009e593f
@ -13,19 +13,21 @@ database:
|
||||
dbname: "wechat_bot"
|
||||
charset: "utf8mb4"
|
||||
|
||||
redis:
|
||||
host: "10.0.0.31"
|
||||
port: 6379
|
||||
password: "pGhQKwj7DE7FbFL1"
|
||||
db: 13
|
||||
|
||||
docker:
|
||||
host: "http://10.0.0.243:2375"
|
||||
apiVersion: "1.41"
|
||||
imageName: "lxh01/xybotv2:latest"
|
||||
network: "bridge"
|
||||
memory: 123 # 容器内存限制(MB)
|
||||
redis:
|
||||
host: "10.0.0.31"
|
||||
password: "pGhQKwj7DE7FbFL1"
|
||||
db: 2
|
||||
memory: 512 # 容器内存限制(MB)
|
||||
|
||||
auth:
|
||||
type: "password" # 支持 password 和 logto 两种
|
||||
type: "logto" # 支持 password 和 logto 两种
|
||||
password:
|
||||
secretKey: "your-secret-key-change-me" # 加密密钥
|
||||
adminToken: "admin-token-change-me" # 密码
|
||||
@ -38,3 +40,11 @@ auth:
|
||||
logger:
|
||||
level: "debug"
|
||||
file: "" # 空表示日志输出到控制台
|
||||
|
||||
minio:
|
||||
endpoint: 10.0.0.5:9000
|
||||
host: 10.0.0.5:9000
|
||||
accessKey: ZsRIVPn3XZtjAeL5Mu50
|
||||
secretKey: h8uu621Z9YOvd19VWx84A2nPfIU4ND03aAo69Xlx
|
||||
bucket: wechat
|
||||
useSSL: false
|
||||
|
@ -28,16 +28,18 @@ database:
|
||||
# type: "sqlite"
|
||||
# dbname: "./data/wechat_demo.db"
|
||||
|
||||
redis:
|
||||
host: "10.0.0.31"
|
||||
port: 6379
|
||||
password: "pGhQKwj7DE7FbFL1"
|
||||
db: 13
|
||||
|
||||
docker:
|
||||
host: "unix:///var/run/docker.sock"
|
||||
apiVersion: "1.41"
|
||||
imageName: "lxh01/xybotv2:latest"
|
||||
network: "bridge"
|
||||
memory: 123 # 容器内存限制(MB)
|
||||
redis:
|
||||
host: "10.0.0.31"
|
||||
password: "pGhQKwj7DE7FbFL1"
|
||||
db: 2
|
||||
|
||||
auth:
|
||||
type: "password" # 支持 password 和 logto 两种
|
||||
|
4
go.mod
4
go.mod
@ -9,6 +9,7 @@ require (
|
||||
github.com/go-co-op/gocron/v2 v2.16.1
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/gofiber/storage/redis/v3 v3.1.4
|
||||
github.com/gofiber/template/html/v2 v2.1.3
|
||||
github.com/gofiber/websocket/v2 v2.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -27,7 +28,9 @@ require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
@ -67,6 +70,7 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
|
16
go.sum
16
go.sum
@ -1,9 +1,5 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
gitee.ltd/lxh/xybot v0.0.3 h1:/xRCnW2nMtx/hdV7TdpEcL3Sh8f9oBGHToONk0yGstA=
|
||||
gitee.ltd/lxh/xybot v0.0.3/go.mod h1:jYfEAQ3WPsST/PY4fEEVFjU6KtMocxn3sQi78I+vdxc=
|
||||
gitee.ltd/lxh/xybot v0.0.4 h1:sEs6ZOZud2oDWvW1MVpwHWJUq3AyxYA1G/SGP4En+/0=
|
||||
gitee.ltd/lxh/xybot v0.0.4/go.mod h1:jYfEAQ3WPsST/PY4fEEVFjU6KtMocxn3sQi78I+vdxc=
|
||||
gitee.ltd/lxh/xybot v0.0.5 h1:kgwJktO/p7WbywUuAGTPH2V4VOta6dnYs1CXz6qVvZU=
|
||||
gitee.ltd/lxh/xybot v0.0.5/go.mod h1:jYfEAQ3WPsST/PY4fEEVFjU6KtMocxn3sQi78I+vdxc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
@ -14,13 +10,21 @@ github.com/agiledragon/gomonkey/v2 v2.12.0 h1:ek0dYu9K1rSV+TgkW5LvNNPRWyDZVIxGMC
|
||||
github.com/agiledragon/gomonkey/v2 v2.12.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
||||
@ -61,6 +65,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/storage/redis/v3 v3.1.4 h1:lmI+exp/u17zoD7qXtCdkaGwr7OB21F4m77tZtIhMhI=
|
||||
github.com/gofiber/storage/redis/v3 v3.1.4/go.mod h1:SXHWcuzoFZlMxJ6Qnu50rvSWQOJYSxiLrOFE+8FpXAM=
|
||||
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
|
||||
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
||||
github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6dFLp8ea+o=
|
||||
@ -145,6 +151,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
|
@ -16,4 +16,4 @@
|
||||
|
||||
## 使用方法
|
||||
|
||||
应用启动时,会通过`config.Load()`函数加载配置文件和环境变量。配置文件默认位于`configs/config.yaml`,环境变量可以覆盖配置文件中的设置。
|
||||
应用启动时,会通过`initialize.Init()`函数加载配置文件和环境变量。配置文件默认位于`configs/config.yaml`,环境变量可以覆盖配置文件中的设置。
|
||||
|
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
logtoClient "github.com/logto-io/go/v2/client"
|
||||
)
|
||||
|
||||
// AuthConfig 认证配置
|
||||
@ -90,3 +91,11 @@ func (c *AuthLogto) Validate() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLogtoClient
|
||||
// @description: 获取Logto客户端
|
||||
// @receiver c
|
||||
// @return cli
|
||||
func (c *AuthLogto) GetLogtoClient() (cli *logtoClient.LogtoConfig) {
|
||||
return &logtoClient.LogtoConfig{Endpoint: c.Endpoint, AppId: c.AppId, AppSecret: c.AppSecret}
|
||||
}
|
||||
|
@ -1,56 +1,18 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
var Scd Config
|
||||
|
||||
// Config 是应用程序的主配置结构
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
Docker DockerConfig `mapstructure:"docker"`
|
||||
Auth AuthConfig `mapstructure:"auth"`
|
||||
Logger LoggerConfig `mapstructure:"logger"`
|
||||
Minio MinioConfig `mapstructure:"minio"`
|
||||
}
|
||||
|
||||
// Load 加载配置文件和环境变量
|
||||
func Load() (*Config, error) {
|
||||
viper.SetConfigName("config")
|
||||
|
||||
// 检查是否有环境变量指定使用开发配置
|
||||
if os.Getenv("APP_ENV") == "development" {
|
||||
viper.SetConfigName("config.dev")
|
||||
}
|
||||
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("./configs")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
// 环境变量覆盖
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// 读取配置文件
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
// 配置文件不存在时不返回错误
|
||||
if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := viper.Unmarshal(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// Validate 验证配置是否有效
|
||||
func (c *Config) Validate() error {
|
||||
if err := c.Server.Validate(); err != nil {
|
||||
@ -71,5 +33,10 @@ func (c *Config) Validate() error {
|
||||
if err := c.Minio.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Redis.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -4,21 +4,13 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RedisConfig 是Redis相关配置
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// DockerConfig Docker配置
|
||||
type DockerConfig struct {
|
||||
Host string `mapstructure:"host"` // Docker daemon 主机地址
|
||||
APIVersion string `mapstructure:"apiVersion"` // Docker API版本
|
||||
ImageName string `mapstructure:"imageName"` // 微信机器人Docker镜像名称
|
||||
Network string `mapstructure:"network"` // 容器网络
|
||||
Memory int64 `mapstructure:"memory"` // 内存限制
|
||||
Redis RedisConfig `mapstructure:"redis"` // Redis配置
|
||||
Host string `mapstructure:"host"` // Docker daemon 主机地址
|
||||
APIVersion string `mapstructure:"apiVersion"` // Docker API版本
|
||||
ImageName string `mapstructure:"imageName"` // 微信机器人Docker镜像名称
|
||||
Network string `mapstructure:"network"` // 容器网络
|
||||
Memory int64 `mapstructure:"memory"` // 内存限制
|
||||
}
|
||||
|
||||
// Validate 验证Docker配置
|
||||
@ -36,5 +28,9 @@ func (c *DockerConfig) Validate() error {
|
||||
c.Network = "bridge"
|
||||
}
|
||||
|
||||
if c.Memory == 0 {
|
||||
c.Memory = 512 // 默认内存限制为512M
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
22
internal/config/redis.go
Normal file
22
internal/config/redis.go
Normal file
@ -0,0 +1,22 @@
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// RedisConfig 是Redis相关配置
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// Validate 验证Redis配置
|
||||
func (r *RedisConfig) Validate() error {
|
||||
if r.Host == "" {
|
||||
return fmt.Errorf("redis主机不能为空")
|
||||
}
|
||||
if r.Port <= 0 {
|
||||
r.Port = 6379
|
||||
}
|
||||
return nil
|
||||
}
|
@ -16,71 +16,6 @@
|
||||
- 启动、停止和删除容器
|
||||
- 获取容器状态和日志
|
||||
|
||||
2. **微信机器人操作**
|
||||
- 获取登录二维码
|
||||
- 微信登录状态监控
|
||||
- 获取联系人列表和群成员
|
||||
- 获取聊天记录
|
||||
- 微信登出
|
||||
|
||||
3. **状态监控**
|
||||
2. **状态监控**
|
||||
- 定期检查微信机器人容器状态
|
||||
- 自动更新数据库中的状态信息
|
||||
|
||||
## 使用方法
|
||||
|
||||
初始化Docker客户端:
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"github.com/Lxh/wechat-demo/internal/config"
|
||||
"github.com/Lxh/wechat-demo/internal/docker"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, _ := config.Load()
|
||||
err := docker.InitClient(&cfg.Docker)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 创建微信机器人容器
|
||||
ctx := context.Background()
|
||||
containerID, err := docker.CreateRobotContainer(ctx, &cfg.Docker, "robot1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 获取容器状态
|
||||
status, errMsg, err := docker.GetWechatBotStatus(ctx, containerID)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
启动容器监控:
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"github.com/Lxh/wechat-demo/internal/model"
|
||||
"github.com/Lxh/wechat-demo/internal/docker"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db := model.GetDB()
|
||||
|
||||
// 创建监控器,每分钟检查一次
|
||||
monitor := docker.NewContainerMonitor(db, time.Minute)
|
||||
|
||||
// 启动监控
|
||||
monitor.Start(context.Background())
|
||||
|
||||
// 添加容器到监控列表
|
||||
monitor.AddRobot("container-id")
|
||||
|
||||
// 停止监控
|
||||
// monitor.Stop()
|
||||
}
|
||||
```
|
||||
|
@ -1,47 +1,47 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
)
|
||||
|
||||
var (
|
||||
dockerClient *client.Client
|
||||
clientOnce sync.Once
|
||||
clientErr error
|
||||
)
|
||||
|
||||
// InitClient 初始化Docker客户端
|
||||
func InitClient(cfg *config.DockerConfig) error {
|
||||
clientOnce.Do(func() {
|
||||
options := []client.Opt{
|
||||
client.WithAPIVersionNegotiation(),
|
||||
func InitClient() {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Panicf("Docker客户端初始化失败: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 如果指定了Docker主机地址
|
||||
if cfg.Host != "" {
|
||||
options = append(options, client.WithHost(cfg.Host))
|
||||
}
|
||||
options := []client.Opt{
|
||||
client.WithAPIVersionNegotiation(),
|
||||
}
|
||||
|
||||
// 如果指定了API版本
|
||||
if cfg.APIVersion != "" {
|
||||
options = append(options, client.WithVersion(cfg.APIVersion))
|
||||
}
|
||||
// 如果指定了Docker主机地址
|
||||
if config.Scd.Docker.Host != "" {
|
||||
options = append(options, client.WithHost(config.Scd.Docker.Host))
|
||||
}
|
||||
|
||||
// 创建Docker客户端
|
||||
dockerClient, clientErr = client.NewClientWithOpts(options...)
|
||||
})
|
||||
// 如果指定了API版本
|
||||
if config.Scd.Docker.APIVersion != "" {
|
||||
options = append(options, client.WithVersion(config.Scd.Docker.APIVersion))
|
||||
}
|
||||
|
||||
return clientErr
|
||||
// 创建Docker客户端
|
||||
dockerClient, err = client.NewClientWithOpts(options...)
|
||||
}
|
||||
|
||||
// GetClient 获取Docker客户端实例
|
||||
func GetClient() *client.Client {
|
||||
if dockerClient == nil {
|
||||
panic("Docker client not initialized, call InitClient first")
|
||||
panic("Docker客户端未初始化,先调用InitClient")
|
||||
}
|
||||
return dockerClient
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ type ContainerInfo struct {
|
||||
}
|
||||
|
||||
// CreateContainer 创建容器
|
||||
func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string, env []string, labels map[string]string, port int) (string, error) {
|
||||
func CreateContainer(ctx context.Context, name string, env []string, labels map[string]string, port int) (string, error) {
|
||||
cli := GetClient()
|
||||
|
||||
// 检测是否在容器内运行
|
||||
@ -42,7 +42,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
||||
currentNetworkName = getCurrentContainerNetwork(ctx, cli)
|
||||
if currentNetworkName != "" {
|
||||
// 使用发现的网络替代配置中的网络
|
||||
cfg.Network = currentNetworkName
|
||||
config.Scd.Docker.Network = currentNetworkName
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
||||
// 如果没有指定端口,则自动分配
|
||||
if port <= 0 {
|
||||
// 查找同镜像容器的最大端口号并加1
|
||||
maxPort, err := findMaxPortForImage(ctx, cli, cfg.ImageName)
|
||||
maxPort, err := findMaxPortForImage(ctx, cli, config.Scd.Docker.ImageName)
|
||||
if err != nil {
|
||||
// 如果出错,使用默认端口9001
|
||||
port = 9001
|
||||
@ -79,15 +79,18 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
||||
}
|
||||
|
||||
// 添加Redis环境变量
|
||||
if cfg.Redis.Host != "" {
|
||||
env = append(env, fmt.Sprintf("REDIS_HOST=%s", cfg.Redis.Host))
|
||||
env = append(env, fmt.Sprintf("REDIS_PASSWORD=%s", cfg.Redis.Password))
|
||||
env = append(env, fmt.Sprintf("REDIS_DB=%d", cfg.Redis.DB))
|
||||
if config.Scd.Redis.Host != "" {
|
||||
env = append(env, fmt.Sprintf("REDIS_HOST=%s", config.Scd.Redis.Host))
|
||||
env = append(env, fmt.Sprintf("REDIS_PASSWORD=%s", config.Scd.Redis.Password))
|
||||
env = append(env, fmt.Sprintf("REDIS_DB=%d", config.Scd.Redis.DB))
|
||||
if config.Scd.Redis.Port != 0 {
|
||||
env = append(env, fmt.Sprintf("REDIS_PORT=%d", config.Scd.Redis.Port))
|
||||
}
|
||||
}
|
||||
|
||||
// 设置容器配置
|
||||
containerConfig := &container.Config{
|
||||
Image: cfg.ImageName,
|
||||
Image: config.Scd.Docker.ImageName,
|
||||
Env: env,
|
||||
ExposedPorts: exposedPorts,
|
||||
Labels: labels,
|
||||
@ -100,19 +103,16 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
||||
Name: "unless-stopped",
|
||||
},
|
||||
}
|
||||
if cfg.Memory == 0 {
|
||||
cfg.Memory = 512 // 默认内存限制为512M
|
||||
}
|
||||
hostConfig.Memory = cfg.Memory * 1024 * 1024 // 限制使用内存
|
||||
hostConfig.Memory = config.Scd.Docker.Memory * 1024 * 1024 // 限制使用内存
|
||||
|
||||
// 设置网络配置
|
||||
networkingConfig := &network.NetworkingConfig{}
|
||||
if cfg.Network != "" {
|
||||
if config.Scd.Docker.Network != "" {
|
||||
// 首先检查网络类型,只在用户自定义网络上分配固定IP
|
||||
isUserNetwork := false
|
||||
if cfg.Network != "bridge" && cfg.Network != "host" && cfg.Network != "none" {
|
||||
if config.Scd.Docker.Network != "bridge" && config.Scd.Docker.Network != "host" && config.Scd.Docker.Network != "none" {
|
||||
// 检查网络是否存在
|
||||
_, err := cli.NetworkInspect(ctx, cfg.Network, network.InspectOptions{})
|
||||
_, err := cli.NetworkInspect(ctx, config.Scd.Docker.Network, network.InspectOptions{})
|
||||
if err == nil {
|
||||
isUserNetwork = true
|
||||
}
|
||||
@ -124,7 +124,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
||||
// 只在用户自定义网络上尝试分配固定IP
|
||||
if isUserNetwork {
|
||||
// 自动为容器分配一个递增的IP地址
|
||||
nextIP, err := getNextAvailableIPInNetwork(ctx, cli, cfg.Network)
|
||||
nextIP, err := getNextAvailableIPInNetwork(ctx, cli, config.Scd.Docker.Network)
|
||||
if err == nil && nextIP != "" {
|
||||
endpointSettings.IPAMConfig = &network.EndpointIPAMConfig{
|
||||
IPv4Address: nextIP,
|
||||
@ -132,7 +132,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
||||
}
|
||||
}
|
||||
|
||||
endpointsConfig[cfg.Network] = endpointSettings
|
||||
endpointsConfig[config.Scd.Docker.Network] = endpointSettings
|
||||
networkingConfig.EndpointsConfig = endpointsConfig
|
||||
}
|
||||
|
||||
@ -650,7 +650,7 @@ func GetContainerIP(ctx context.Context, containerID string, cfg *config.DockerC
|
||||
}
|
||||
|
||||
// GetContainerHost 获取容器的访问地址(格式:ip:port)
|
||||
func GetContainerHost(ctx context.Context, containerID string, cfg *config.DockerConfig) (string, error) {
|
||||
func GetContainerHost(ctx context.Context, containerID string) (string, error) {
|
||||
// 如果在Docker环境中运行,需要获取容器真实IP或容器名
|
||||
if isRunningInContainer() {
|
||||
cli := GetClient()
|
||||
@ -689,7 +689,7 @@ func GetContainerHost(ctx context.Context, containerID string, cfg *config.Docke
|
||||
return "", fmt.Errorf("无法检查容器: %w", err)
|
||||
}
|
||||
|
||||
hostIP := extractHostIP(cfg.Host)
|
||||
hostIP := extractHostIP(config.Scd.Docker.Host)
|
||||
|
||||
// 查找9000端口的映射
|
||||
for _, port := range inspect.NetworkSettings.Ports["9000/tcp"] {
|
||||
|
@ -1,139 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ContainerMonitor 容器监控器
|
||||
type ContainerMonitor struct {
|
||||
db *gorm.DB
|
||||
interval time.Duration
|
||||
robots map[string]struct{} // 记录正在监控的机器人容器ID
|
||||
mutex sync.RWMutex
|
||||
stopChan chan struct{}
|
||||
monitorActive bool
|
||||
}
|
||||
|
||||
// NewContainerMonitor 创建容器监控器
|
||||
func NewContainerMonitor(db *gorm.DB, interval time.Duration) *ContainerMonitor {
|
||||
if interval == 0 {
|
||||
interval = 30 * time.Second // 默认30秒检查一次
|
||||
}
|
||||
|
||||
return &ContainerMonitor{
|
||||
db: db,
|
||||
interval: interval,
|
||||
robots: make(map[string]struct{}),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动监控
|
||||
func (m *ContainerMonitor) Start(ctx context.Context) {
|
||||
//m.mutex.Lock()
|
||||
//if m.monitorActive {
|
||||
// m.mutex.Unlock()
|
||||
// return
|
||||
//}
|
||||
//m.monitorActive = true
|
||||
//m.mutex.Unlock()
|
||||
//
|
||||
//log.Println("Starting container monitor...")
|
||||
//
|
||||
//// 启动监控协程
|
||||
//go func() {
|
||||
// ticker := time.NewTicker(m.interval)
|
||||
// defer ticker.Stop()
|
||||
//
|
||||
// for {
|
||||
// select {
|
||||
// case <-ticker.C:
|
||||
// m.checkRobots(context.Background())
|
||||
// case <-m.stopChan:
|
||||
// log.Println("Container monitor stopped")
|
||||
// return
|
||||
// case <-ctx.Done():
|
||||
// log.Println("Container monitor stopped due to context cancellation")
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//}()
|
||||
}
|
||||
|
||||
// Stop 停止监控
|
||||
func (m *ContainerMonitor) Stop() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if m.monitorActive {
|
||||
close(m.stopChan)
|
||||
m.monitorActive = false
|
||||
}
|
||||
}
|
||||
|
||||
// AddRobot 添加机器人到监控列表
|
||||
func (m *ContainerMonitor) AddRobot(containerID string) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.robots[containerID] = struct{}{}
|
||||
}
|
||||
|
||||
// RemoveRobot 从监控列表中移除机器人
|
||||
func (m *ContainerMonitor) RemoveRobot(containerID string) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
delete(m.robots, containerID)
|
||||
}
|
||||
|
||||
// checkRobots 检查所有机器人状态
|
||||
//func (m *ContainerMonitor) checkRobots(ctx context.Context) {
|
||||
// m.mutex.RLock()
|
||||
// robotIDs := make([]string, 0, len(m.robots))
|
||||
// for id := range m.robots {
|
||||
// robotIDs = append(robotIDs, id)
|
||||
// }
|
||||
// m.mutex.RUnlock()
|
||||
//
|
||||
// for _, containerID := range robotIDs {
|
||||
// // 使用新的上下文,避免一个检查失败影响其他检查
|
||||
// checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
// defer cancel()
|
||||
//
|
||||
// // 获取机器人的当前状态
|
||||
// status, errMsg, err := GetWechatBotStatus(checkCtx, containerID)
|
||||
// if err != nil {
|
||||
// log.Printf("Error checking robot %s: %v", containerID, err)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // 更新数据库中的状态
|
||||
// robot := &model.Robot{}
|
||||
// result := m.db.Where("container_id = ?", containerID).First(robot)
|
||||
// if result.Error != nil {
|
||||
// log.Printf("Error finding robot %s in database: %v", containerID, result.Error)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // 只有状态变化时才更新
|
||||
// if robot.Status != status {
|
||||
// robot.Status = status
|
||||
// robot.ErrorMessage = errMsg
|
||||
//
|
||||
// // 如果状态变为在线,更新登录时间
|
||||
// if status == model.RobotStatusOnline {
|
||||
// now := time.Now()
|
||||
// robot.LastLoginAt = &now
|
||||
// }
|
||||
//
|
||||
// if err := m.db.Save(robot).Error; err != nil {
|
||||
// log.Printf("Error updating robot %s status: %v", containerID, err)
|
||||
// } else {
|
||||
// log.Printf("Robot %s status updated to %s", containerID, status)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -2,7 +2,6 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
)
|
||||
|
||||
@ -14,7 +13,7 @@ const (
|
||||
)
|
||||
|
||||
// CreateRobotContainer 创建微信机器人容器
|
||||
func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotName string, port int) (string, string, error) {
|
||||
func CreateRobotContainer(ctx context.Context, robotName string, port int) (string, string, error) {
|
||||
// 创建容器标签
|
||||
labels := map[string]string{
|
||||
WechatBotLabelKey: WechatBotLabelValue,
|
||||
@ -27,7 +26,7 @@ func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotNa
|
||||
}
|
||||
|
||||
// 创建容器
|
||||
containerID, err := CreateContainer(ctx, cfg, "wechat-bot-"+robotName, env, labels, port)
|
||||
containerID, err := CreateContainer(ctx, "wechat-bot-"+robotName, env, labels, port)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -41,7 +40,7 @@ func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotNa
|
||||
}
|
||||
|
||||
// 获取容器访问地址
|
||||
containerHost, err := GetContainerHost(ctx, containerID, cfg)
|
||||
containerHost, err := GetContainerHost(ctx, containerID)
|
||||
if err != nil {
|
||||
log.Warnf("警告: 无法获取容器访问地址: %v", err)
|
||||
containerHost = "localhost:9000" // 使用默认值
|
||||
|
@ -66,7 +66,7 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if cfg, _ := config.Load(); cfg.Server.Env == "development" {
|
||||
if config.Scd.Server.Env == "development" {
|
||||
bs, _ := json.Marshal(response)
|
||||
log.Debugf("扫码返回结果: %s", bs)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
// LoginPage 显示登录页面
|
||||
func LoginPage(c *fiber.Ctx) error {
|
||||
// 如果已经登录,则重定向到机器人列表
|
||||
if middleware.IsAuthenticated(c) {
|
||||
if _, flag := middleware.IsAuthenticated(c); flag {
|
||||
return c.Redirect("/admin/robots")
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ func LoginPage(c *fiber.Ctx) error {
|
||||
errorMsg := c.Query("error")
|
||||
|
||||
// 加载配置
|
||||
if cfg, _ := config.Load(); cfg.Auth.Type == "logto" {
|
||||
if config.Scd.Auth.Type == "logto" {
|
||||
// 如果使用Logto认证,重定向到Logto登录页面
|
||||
return c.Redirect("/auth/logto/login")
|
||||
}
|
||||
@ -38,31 +38,25 @@ func LoginSubmit(c *fiber.Ctx) error {
|
||||
// 获取用户输入的密钥
|
||||
token := c.FormValue("token")
|
||||
|
||||
// 检查凭据有效性
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return c.Redirect("/login?error=系统错误,无法加载配置")
|
||||
}
|
||||
|
||||
// 根据认证类型进行不同的验证
|
||||
if cfg.Auth.Type == "password" {
|
||||
if config.Scd.Auth.Type == "password" {
|
||||
// 仅验证密钥是否与配置的AdminToken匹配
|
||||
if token != cfg.Auth.Password.AdminToken {
|
||||
if token != config.Scd.Auth.Password.AdminToken {
|
||||
return c.Redirect("/login?error=访问密钥不正确")
|
||||
}
|
||||
|
||||
// 登录成功,设置认证 Cookie
|
||||
cookie := new(fiber.Cookie)
|
||||
cookie.Name = "auth_token"
|
||||
cookie.Value = cfg.Auth.Password.SecretKey // 在实际应用中,这应该是一个生成的会话令牌
|
||||
cookie.Expires = time.Now().Add(time.Hour * time.Duration(cfg.Auth.Password.TokenExpiry))
|
||||
cookie.Value = config.Scd.Auth.Password.SecretKey // 在实际应用中,这应该是一个生成的会话令牌
|
||||
cookie.Expires = time.Now().Add(time.Hour * time.Duration(config.Scd.Auth.Password.TokenExpiry))
|
||||
cookie.HTTPOnly = true
|
||||
cookie.Path = "/"
|
||||
c.Cookie(cookie)
|
||||
|
||||
// 重定向到机器人列表页面,而不是首页
|
||||
return c.Redirect("/admin/robots")
|
||||
} else if cfg.Auth.Type == "logto" {
|
||||
} else if config.Scd.Auth.Type == "logto" {
|
||||
// 对于Logto登录,我们重定向到Logto登录页面
|
||||
return c.Redirect("/auth/logto/login")
|
||||
}
|
||||
@ -73,14 +67,8 @@ func LoginSubmit(c *fiber.Ctx) error {
|
||||
|
||||
// Logout 处理退出登录
|
||||
func Logout(c *fiber.Ctx) error {
|
||||
// 加载配置
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return c.Redirect("/login?error=系统错误,无法加载配置")
|
||||
}
|
||||
|
||||
// 根据认证类型执行不同的登出逻辑
|
||||
if cfg.Auth.Type == "logto" {
|
||||
if config.Scd.Auth.Type == "logto" {
|
||||
// 对于Logto登录,使用Logto的登出流程
|
||||
return c.Redirect("/auth/logto/logout")
|
||||
}
|
||||
|
@ -1,48 +1,23 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gitee.ltd/lxh/wechat-robot/internal/types"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/logto"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
logtoClient "github.com/logto-io/go/v2/client"
|
||||
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||||
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 实现Logto会话存储接口
|
||||
var store = session.New(session.Config{
|
||||
KeyLookup: "cookie:logto-session",
|
||||
CookieSecure: false, // 开发环境可设成false
|
||||
})
|
||||
|
||||
// LogtoLogin 重定向到Logto登录页面
|
||||
func LogtoLogin(c *fiber.Ctx) error {
|
||||
// 加载配置
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return c.Redirect("/error?error=系统错误,无法加载配置")
|
||||
}
|
||||
|
||||
// 构建回调URL
|
||||
callbackURL := c.Protocol() + "://" + c.Hostname() + "/auth/logto/callback"
|
||||
//callbackURL := "https://wechat.0bug.dev/auth/logto/callback"
|
||||
|
||||
// 创建Logto客户端配置
|
||||
logtoConfig := &logtoClient.LogtoConfig{
|
||||
Endpoint: cfg.Auth.Logto.Endpoint,
|
||||
AppId: cfg.Auth.Logto.AppId,
|
||||
AppSecret: cfg.Auth.Logto.AppSecret,
|
||||
}
|
||||
// 获取会话
|
||||
fiberSessionStorage := &types.LogtoSessionStorage{Store: store, Ctx: c}
|
||||
|
||||
// 创建Logto客户端
|
||||
client := logtoClient.NewLogtoClient(logtoConfig, fiberSessionStorage)
|
||||
// 获取登录链接
|
||||
client, err := logto.GetLogtoClient(c)
|
||||
if err != nil {
|
||||
return c.Redirect("/error?error=Logto登录错误: " + err.Error())
|
||||
}
|
||||
signInUri, err := client.SignInWithRedirectUri(callbackURL)
|
||||
if err != nil {
|
||||
return c.Redirect("/error?error=Logto登录错误: " + err.Error() + "。请在Logto控制台确认回调URI为: " + callbackURL)
|
||||
@ -53,23 +28,11 @@ func LogtoLogin(c *fiber.Ctx) error {
|
||||
|
||||
// LogtoCallback 处理Logto登录回调
|
||||
func LogtoCallback(c *fiber.Ctx) error {
|
||||
// 加载配置
|
||||
cfg, err := config.Load()
|
||||
client, err := logto.GetLogtoClient(c)
|
||||
if err != nil {
|
||||
return c.Redirect("/error?error=系统错误,无法加载配置")
|
||||
return c.Redirect("/error?error=Logto登录错误: " + err.Error())
|
||||
}
|
||||
|
||||
// 创建Logto客户端配置
|
||||
logtoConfig := &logtoClient.LogtoConfig{
|
||||
Endpoint: cfg.Auth.Logto.Endpoint,
|
||||
AppId: cfg.Auth.Logto.AppId,
|
||||
AppSecret: cfg.Auth.Logto.AppSecret,
|
||||
}
|
||||
// 获取会话
|
||||
fiberSessionStorage := &types.LogtoSessionStorage{Store: store, Ctx: c}
|
||||
// 创建Logto客户端
|
||||
client := logtoClient.NewLogtoClient(logtoConfig, fiberSessionStorage)
|
||||
|
||||
r := &http.Request{}
|
||||
if err = fasthttpadaptor.ConvertRequest(c.Context(), r, true); err != nil {
|
||||
return c.Redirect("/error?error=转换请求错误: " + err.Error())
|
||||
@ -80,38 +43,17 @@ func LogtoCallback(c *fiber.Ctx) error {
|
||||
return c.Redirect("/error?error=Logto回调处理错误: " + err.Error())
|
||||
}
|
||||
|
||||
// 设置auth_token cookie,使用logto前缀
|
||||
cookie := new(fiber.Cookie)
|
||||
cookie.Name = "auth_token"
|
||||
cookie.Value = "logto:auth" // 设置auth_token带有logto前缀
|
||||
cookie.Expires = time.Now().Add(24 * time.Hour) // 24小时过期
|
||||
cookie.HTTPOnly = true
|
||||
cookie.Path = "/"
|
||||
c.Cookie(cookie)
|
||||
|
||||
// 重定向到机器人列表页面
|
||||
return c.Redirect("/admin/robots")
|
||||
}
|
||||
|
||||
// LogtoLogout 处理Logto退出登录
|
||||
func LogtoLogout(c *fiber.Ctx) error {
|
||||
// 加载配置
|
||||
cfg, err := config.Load()
|
||||
client, err := logto.GetLogtoClient(c)
|
||||
if err != nil {
|
||||
return c.Redirect("/error?error=系统错误,无法加载配置")
|
||||
return c.Redirect("/error?error=Logto登录错误: " + err.Error())
|
||||
}
|
||||
|
||||
// 创建Logto客户端配置
|
||||
logtoConfig := &logtoClient.LogtoConfig{
|
||||
Endpoint: cfg.Auth.Logto.Endpoint,
|
||||
AppId: cfg.Auth.Logto.AppId,
|
||||
AppSecret: cfg.Auth.Logto.AppSecret,
|
||||
}
|
||||
// 获取会话
|
||||
fiberSessionStorage := &types.LogtoSessionStorage{Store: store, Ctx: c}
|
||||
// 创建Logto客户端
|
||||
client := logtoClient.NewLogtoClient(logtoConfig, fiberSessionStorage)
|
||||
|
||||
// 构建登出后重定向URL
|
||||
postLogoutRedirectURL := c.Protocol() + "://" + c.Hostname()
|
||||
|
||||
@ -121,15 +63,6 @@ func LogtoLogout(c *fiber.Ctx) error {
|
||||
return c.Redirect("/error?error=Logto登出错误: " + err.Error())
|
||||
}
|
||||
|
||||
// 清除本地Cookie
|
||||
cookie := new(fiber.Cookie)
|
||||
cookie.Name = "auth_token"
|
||||
cookie.Value = ""
|
||||
cookie.Expires = time.Now().Add(-time.Hour) // 设置为过期
|
||||
cookie.HTTPOnly = true
|
||||
cookie.Path = "/"
|
||||
c.Cookie(cookie)
|
||||
|
||||
// 重定向到Logto登出页面
|
||||
return c.Redirect(signOutUri)
|
||||
}
|
||||
|
@ -12,13 +12,14 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
)
|
||||
|
||||
// ListRobots 列出所有机器人
|
||||
func ListRobots(c *fiber.Ctx) error {
|
||||
log.Debugf("登录用户Id: %+v", c.Get("userId"))
|
||||
|
||||
db := model.GetDB()
|
||||
var robots []model.Robot
|
||||
|
||||
@ -73,17 +74,11 @@ func CreateRobot(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "加载配置失败")
|
||||
}
|
||||
|
||||
// 创建Docker容器
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
containerID, containerHost, err := docker.CreateRobotContainer(ctx, &cfg.Docker, robotName, port)
|
||||
containerID, containerHost, err := docker.CreateRobotContainer(ctx, robotName, port)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "创建容器失败: "+err.Error())
|
||||
}
|
||||
@ -105,11 +100,6 @@ func CreateRobot(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "保存机器人信息失败: "+err.Error())
|
||||
}
|
||||
|
||||
// 添加到监控器
|
||||
// 注意:这里假设有一个全局可访问的监控器实例,实际中可能需要通过依赖注入或其他方式获取
|
||||
monitor := docker.NewContainerMonitor(db, time.Minute)
|
||||
monitor.AddRobot(containerID)
|
||||
|
||||
return c.Redirect("/admin/robots/" + strconv.Itoa(int(robot.ID)))
|
||||
}
|
||||
|
||||
@ -189,10 +179,6 @@ func DeleteRobot(c *fiber.Ctx) error {
|
||||
// 继续删除流程,不因容器删除失败而中断
|
||||
}
|
||||
|
||||
// 从监控器移除
|
||||
monitor := docker.NewContainerMonitor(db, time.Minute)
|
||||
monitor.RemoveRobot(robot.ContainerID)
|
||||
|
||||
// 删除数据库记录
|
||||
if err = db.Delete(&robot).Error; err != nil {
|
||||
// 针对API请求返回JSON
|
||||
|
67
internal/initialize/config.go
Normal file
67
internal/initialize/config.go
Normal file
@ -0,0 +1,67 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/minio"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/redis"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// initConfig
|
||||
// @description: 初始化配置文件
|
||||
func initConfig() {
|
||||
viper.SetConfigName("config")
|
||||
|
||||
// 检查是否有环境变量指定使用开发配置
|
||||
if os.Getenv("APP_ENV") == "development" {
|
||||
viper.SetConfigName("config.dev")
|
||||
}
|
||||
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("./configs")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
// 环境变量覆盖
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// 读取配置文件
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Panicf("配置文件读取失败: %v", err)
|
||||
return
|
||||
}
|
||||
// 绑定到
|
||||
if err := viper.Unmarshal(&config.Scd); err != nil {
|
||||
log.Panicf("配置文件解析失败: %v", err)
|
||||
return
|
||||
}
|
||||
log.Debugf("配置文件解析成功: %+v", config.Scd)
|
||||
if err := config.Scd.Validate(); err != nil {
|
||||
log.Panicf("配置文件验证失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 下面的代码是配置变动之后自动刷新的
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
// 绑定配置文件
|
||||
if err := viper.Unmarshal(&config.Scd); err != nil {
|
||||
log.Errorf("配置文件更新失败: %v", err)
|
||||
} else if err = config.Scd.Validate(); err != nil {
|
||||
log.Errorf("配置文件验证失败: %v", err)
|
||||
} else {
|
||||
// 初始化数据库连接等操作
|
||||
model.InitDB() // 1. 初始化数据库
|
||||
redis.InitRedisClient() // 2. 初始化Redis
|
||||
minio.Init() // 3. 初始化Minio
|
||||
docker.InitClient() // 4. 初始化Docker客户端
|
||||
}
|
||||
})
|
||||
// 监听变动
|
||||
viper.WatchConfig()
|
||||
}
|
18
internal/initialize/init.go
Normal file
18
internal/initialize/init.go
Normal file
@ -0,0 +1,18 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/minio"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/redis"
|
||||
)
|
||||
|
||||
// Init
|
||||
// @description: 系统初始化s
|
||||
func Init() {
|
||||
initConfig() // 1. 初始化配置文件
|
||||
model.InitDB() // 2. 初始化数据库
|
||||
redis.InitRedisClient() // 3. 初始化Redis
|
||||
minio.Init() // 4. 初始化Minio
|
||||
docker.InitClient() // 5. 初始化Docker客户端
|
||||
}
|
47
internal/logto/logto.go
Normal file
47
internal/logto/logto.go
Normal file
@ -0,0 +1,47 @@
|
||||
package logto
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/types"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
"github.com/gofiber/storage/redis/v3"
|
||||
logtoClient "github.com/logto-io/go/v2/client"
|
||||
)
|
||||
|
||||
var storage *redis.Storage
|
||||
|
||||
// 实现Logto会话存储接口
|
||||
var store *session.Store
|
||||
|
||||
// GetLogtoClient
|
||||
// @description: 获取logto客户端
|
||||
func GetLogtoClient(c *fiber.Ctx) (client *logtoClient.LogtoClient, err error) {
|
||||
if storage == nil {
|
||||
storage = redis.New(redis.Config{
|
||||
Host: config.Scd.Redis.Host,
|
||||
Port: config.Scd.Redis.Port,
|
||||
Password: config.Scd.Redis.Password,
|
||||
Database: config.Scd.Redis.DB,
|
||||
Reset: false, // false:启动时不清除数据库,true:清除(请谨慎使用)
|
||||
// 如果需要,可以配置其他选项,如 TLS、PoolSize 等
|
||||
})
|
||||
}
|
||||
|
||||
if store == nil {
|
||||
store = session.New(session.Config{
|
||||
Storage: storage,
|
||||
KeyLookup: "cookie:logto-session",
|
||||
CookieSecure: false, // 开发环境可设成false
|
||||
})
|
||||
}
|
||||
|
||||
// 创建Logto客户端配置
|
||||
logtoConfig := config.Scd.Auth.Logto.GetLogtoClient()
|
||||
// 获取会话
|
||||
fiberSessionStorage := &types.LogtoSessionStorage{Store: store, Ctx: c}
|
||||
|
||||
// 创建Logto客户端
|
||||
client = logtoClient.NewLogtoClient(logtoConfig, fiberSessionStorage)
|
||||
return
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/logto"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/redis"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@ -11,29 +12,28 @@ import (
|
||||
// @description: 检查用户是否已登录
|
||||
// @param c
|
||||
// @return bool
|
||||
func IsAuthenticated(c *fiber.Ctx) bool {
|
||||
func IsAuthenticated(c *fiber.Ctx) (loginType string, flag bool) {
|
||||
token := c.Cookies("auth_token")
|
||||
if token == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return false
|
||||
if token = c.Cookies("logto-session"); token == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 根据认证类型验证
|
||||
switch cfg.Auth.Type {
|
||||
loginType = config.Scd.Auth.Type
|
||||
switch config.Scd.Auth.Type {
|
||||
case "password":
|
||||
// 对比token (简单实现,实际应用可能需要更复杂的验证)
|
||||
return token == cfg.Auth.Password.SecretKey
|
||||
flag = token == config.Scd.Auth.Password.SecretKey
|
||||
case "logto":
|
||||
// 如果是Logto认证方式,检查token前缀,有前缀则认为已登录
|
||||
return strings.HasPrefix(token, "logto:")
|
||||
flag = redis.Client.Exists(context.Background(), token).Val() > 0
|
||||
default:
|
||||
return false
|
||||
// nothing
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Authenticate
|
||||
@ -42,9 +42,22 @@ func IsAuthenticated(c *fiber.Ctx) bool {
|
||||
func Authenticate() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 检查是否已登录
|
||||
if !IsAuthenticated(c) {
|
||||
loginType, flag := IsAuthenticated(c)
|
||||
if !flag {
|
||||
return c.Redirect("/login")
|
||||
}
|
||||
|
||||
// 获取Logto客户端
|
||||
if loginType == "logto" {
|
||||
client, err := logto.GetLogtoClient(c)
|
||||
if err != nil {
|
||||
return c.Redirect("/error?error=Logto登录错误: " + err.Error())
|
||||
}
|
||||
if userInfo, e := client.GetIdTokenClaims(); e == nil {
|
||||
c.Set("userId", userInfo.Sub)
|
||||
}
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
@ -23,12 +23,6 @@ import (
|
||||
// @return url
|
||||
// @return err
|
||||
func SaveBytes(b []byte, md5 string, suffix ...string) (url string, err error) {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Errorf("加载配置失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
contentType := http.DetectContentType(b)
|
||||
if strings.Contains(contentType, ";") {
|
||||
@ -48,17 +42,17 @@ func SaveBytes(b []byte, md5 string, suffix ...string) (url string, err error) {
|
||||
}
|
||||
log.Debugf("开始上传文件: %v", fileName)
|
||||
reader := bytes.NewBuffer(b)
|
||||
_, err = minioClient.PutObject(ctx, cfg.Minio.BucketName, fileName, reader, -1, minio.PutObjectOptions{ContentType: contentType})
|
||||
_, err = minioClient.PutObject(ctx, config.Scd.Minio.BucketName, fileName, reader, -1, minio.PutObjectOptions{ContentType: contentType})
|
||||
if err != nil {
|
||||
log.Errorf("文件上传错误: %v", err)
|
||||
return "", err
|
||||
}
|
||||
log.Debugf("文件上传完毕: %v", fileName)
|
||||
protocol := "http"
|
||||
if cfg.Minio.UseSsl {
|
||||
if config.Scd.Minio.UseSsl {
|
||||
protocol = "https"
|
||||
}
|
||||
url = fmt.Sprintf("%s://%s/%s/%s", protocol, cfg.Minio.Host, cfg.Minio.BucketName, fileName)
|
||||
url = fmt.Sprintf("%s://%s/%s/%s", protocol, config.Scd.Minio.Host, config.Scd.Minio.BucketName, fileName)
|
||||
// 异步数据入库
|
||||
//go func() {
|
||||
// wf := db.WeChatFileEntity{Url: fileUrl, HashCode: md5, FileType: contentType}
|
||||
|
@ -13,17 +13,12 @@ var minioClient *minio.Client
|
||||
// Init
|
||||
// @description: 初始化Minio连接
|
||||
func Init() {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Panicf("加载配置失败: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
// 初使化 minio client对象。
|
||||
minioClient, err = minio.New(cfg.Minio.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cfg.Minio.AccessKeyID, cfg.Minio.SecretAccessKey, ""),
|
||||
Secure: cfg.Minio.UseSsl,
|
||||
minioClient, err = minio.New(config.Scd.Minio.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(config.Scd.Minio.AccessKeyID, config.Scd.Minio.SecretAccessKey, ""),
|
||||
Secure: config.Scd.Minio.UseSsl,
|
||||
})
|
||||
if err != nil {
|
||||
log.Panicf("OSS初始化失败: %v", err.Error())
|
||||
@ -31,14 +26,14 @@ func Init() {
|
||||
log.Debug("OSS连接成功,开始判断桶是否存在")
|
||||
// 判断捅是否存在,不存在就创建
|
||||
var exists bool
|
||||
exists, err = minioClient.BucketExists(ctx, cfg.Minio.BucketName)
|
||||
exists, err = minioClient.BucketExists(ctx, config.Scd.Minio.BucketName)
|
||||
if err != nil {
|
||||
log.Panicf("判断桶失败: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
log.Debug("桶不存在,开始创建")
|
||||
// 创建桶
|
||||
err = minioClient.MakeBucket(ctx, cfg.Minio.BucketName, minio.MakeBucketOptions{Region: "us-east-1"})
|
||||
err = minioClient.MakeBucket(ctx, config.Scd.Minio.BucketName, minio.MakeBucketOptions{Region: "us-east-1"})
|
||||
if err != nil {
|
||||
log.Panicf("OSS桶创建失败: %v", err.Error())
|
||||
}
|
||||
|
@ -23,16 +23,12 @@
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/Lxh/wechat-demo/internal/config"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/initialize"
|
||||
"github.com/Lxh/wechat-demo/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, _ := config.Load()
|
||||
err := model.InitDB(&cfg.Database)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
initialize.Init()
|
||||
|
||||
// 使用DB实例
|
||||
db := model.GetDB()
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"sync"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
@ -16,69 +14,71 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
dbOnce sync.Once
|
||||
db *gorm.DB
|
||||
)
|
||||
|
||||
// InitDB 初始化数据库连接
|
||||
func InitDB(cfg *config.DatabaseConfig) error {
|
||||
func InitDB() {
|
||||
var err error
|
||||
|
||||
dbOnce.Do(func() {
|
||||
gormConfig := &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
}
|
||||
|
||||
dsn := cfg.DSN()
|
||||
|
||||
// 根据数据库类型选择对应的驱动
|
||||
switch cfg.Type {
|
||||
case config.PostgreSQL:
|
||||
db, err = gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
case config.MySQL:
|
||||
db, err = gorm.Open(mysql.Open(dsn), gormConfig)
|
||||
case config.SQLite:
|
||||
db, err = gorm.Open(sqlite.Open(dsn), gormConfig)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported database type: %s", cfg.Type)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to database: %v", err)
|
||||
return
|
||||
log.Panicf("数据库初始化失败: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 自动迁移数据库模型
|
||||
err = migrateDB()
|
||||
gormConfig := &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
}
|
||||
|
||||
dsn := config.Scd.Database.DSN()
|
||||
|
||||
// 根据数据库类型选择对应的驱动
|
||||
switch config.Scd.Database.Type {
|
||||
case config.PostgreSQL:
|
||||
db, err = gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
case config.MySQL:
|
||||
db, err = gorm.Open(mysql.Open(dsn), gormConfig)
|
||||
case config.SQLite:
|
||||
db, err = gorm.Open(sqlite.Open(dsn), gormConfig)
|
||||
default:
|
||||
err = fmt.Errorf("不支持的数据库类型: %s", config.Scd.Database.Type)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("无法连接到数据库: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 自动迁移数据库模型
|
||||
err = migrateDB()
|
||||
if err != nil {
|
||||
log.Fatalf("迁移数据库失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 对于SQLite,执行一些特定的优化
|
||||
if config.Scd.Database.Type == config.SQLite {
|
||||
var sqlDB *sql.DB
|
||||
sqlDB, err = db.DB()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to migrate database: %v", err)
|
||||
log.Errorf("无法获取基础SQL DB: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 对于SQLite,执行一些特定的优化
|
||||
if cfg.Type == config.SQLite {
|
||||
var sqlDB *sql.DB
|
||||
sqlDB, err = db.DB()
|
||||
if err != nil {
|
||||
log.Errorf("Warning: Could not get underlying SQL DB: %v", err)
|
||||
return
|
||||
}
|
||||
// 启用外键约束
|
||||
_, _ = sqlDB.Exec("PRAGMA foreign_keys = ON")
|
||||
// 设置连接池大小
|
||||
sqlDB.SetMaxOpenConns(1) // SQLite建议使用单连接
|
||||
}
|
||||
|
||||
// 启用外键约束
|
||||
_, _ = sqlDB.Exec("PRAGMA foreign_keys = ON")
|
||||
// 设置连接池大小
|
||||
sqlDB.SetMaxOpenConns(1) // SQLite建议使用单连接
|
||||
}
|
||||
})
|
||||
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
// GetDB 获取数据库连接实例
|
||||
func GetDB() *gorm.DB {
|
||||
if db == nil {
|
||||
panic("Database not initialized, call InitDB first")
|
||||
panic("数据库未初始化,先调用InitDB")
|
||||
}
|
||||
return db
|
||||
}
|
||||
@ -91,7 +91,7 @@ func CloseDB() error {
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get sql.DB instance error: %w", err)
|
||||
return fmt.Errorf("获取sql.DB实例错误: %w", err)
|
||||
}
|
||||
|
||||
return sqlDB.Close()
|
||||
|
36
internal/redis/redis.go
Normal file
36
internal/redis/redis.go
Normal file
@ -0,0 +1,36 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var Client *redis.Client
|
||||
|
||||
// InitRedisClient
|
||||
// @description: 初始化redis客户端
|
||||
func InitRedisClient() {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Errorf("Redis连接初始化失败: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 初始化连接
|
||||
conn := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", config.Scd.Redis.Host, config.Scd.Redis.Port),
|
||||
Password: config.Scd.Redis.Password,
|
||||
DB: config.Scd.Redis.DB,
|
||||
})
|
||||
if err = conn.Ping(context.Background()).Err(); err != nil {
|
||||
log.Errorf("Redis连接初始化失败: %s", err)
|
||||
return
|
||||
}
|
||||
log.Debug("Redis连接初始化成功")
|
||||
Client = conn
|
||||
return
|
||||
}
|
@ -22,12 +22,11 @@ import (
|
||||
|
||||
// Server 表示HTTP服务器
|
||||
type Server struct {
|
||||
app *fiber.App
|
||||
config *config.Config
|
||||
app *fiber.App
|
||||
}
|
||||
|
||||
// New 创建新的服务器实例
|
||||
func New(cfg *config.Config) *Server {
|
||||
func New() *Server {
|
||||
// 确保视图目录存在
|
||||
viewsDir := "./internal/view"
|
||||
if _, err := os.Stat(viewsDir); os.IsNotExist(err) {
|
||||
@ -36,8 +35,8 @@ func New(cfg *config.Config) *Server {
|
||||
|
||||
// 初始化模板引擎
|
||||
engine := html.New(viewsDir, ".html")
|
||||
engine.Reload(cfg.Server.Env == "development") // 开发环境下启用热重载
|
||||
engine.Debug(cfg.Server.Env == "development") // 增加Debug输出
|
||||
engine.Reload(config.Scd.Server.Env == "development") // 开发环境下启用热重载
|
||||
engine.Debug(config.Scd.Server.Env == "development") // 增加Debug输出
|
||||
|
||||
// 添加自定义模板函数
|
||||
engine.AddFunc("sub", func(a, b int) int {
|
||||
@ -64,7 +63,7 @@ func New(cfg *config.Config) *Server {
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
ViewsLayout: "layouts/main", // 默认布局
|
||||
EnablePrintRoutes: cfg.Server.Env == "development",
|
||||
EnablePrintRoutes: config.Scd.Server.Env == "development",
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
@ -107,8 +106,7 @@ func New(cfg *config.Config) *Server {
|
||||
app.Static("/public", "./public")
|
||||
|
||||
return &Server{
|
||||
app: app,
|
||||
config: cfg,
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,8 +154,8 @@ func (s *Server) SetupRoutes() {
|
||||
|
||||
// Start 启动HTTP服务器
|
||||
func (s *Server) Start() error {
|
||||
addr := s.config.Server.Address()
|
||||
fmt.Printf("Server starting on %s\n", addr)
|
||||
addr := config.Scd.Server.Address()
|
||||
fmt.Printf("服务开始启动,监听地址: %s\n", addr)
|
||||
return s.app.Listen(addr)
|
||||
}
|
||||
|
||||
|
60
main.go
60
main.go
@ -1,69 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/minio"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/initialize"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/server"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/tasks"
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||
"gitee.ltd/lxh/wechat-robot/internal/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载配置
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("无法加载配置: %v", err)
|
||||
}
|
||||
|
||||
if err = cfg.Validate(); err != nil {
|
||||
log.Fatalf("配置无效: %v", err)
|
||||
}
|
||||
|
||||
// 初始化Minio
|
||||
minio.Init()
|
||||
|
||||
// 初始化数据库
|
||||
err = model.InitDB(&cfg.Database)
|
||||
if err != nil {
|
||||
log.Fatalf("初始化数据库失败: %v", err)
|
||||
}
|
||||
defer model.CloseDB()
|
||||
|
||||
// 初始化Docker客户端
|
||||
err = docker.InitClient(&cfg.Docker)
|
||||
if err != nil {
|
||||
log.Errorf("初始化Docker客户端失败: %v", err)
|
||||
}
|
||||
defer docker.CloseClient()
|
||||
|
||||
// 启动容器监控
|
||||
db := model.GetDB()
|
||||
monitor := docker.NewContainerMonitor(db, time.Minute)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
monitor.Start(ctx)
|
||||
// 初始化系统
|
||||
initialize.Init()
|
||||
|
||||
// 创建HTTP服务器
|
||||
srv := server.New(cfg)
|
||||
srv := server.New()
|
||||
|
||||
// 设置路由
|
||||
srv.SetupRoutes()
|
||||
|
||||
// 启动HTTP服务器
|
||||
go func() {
|
||||
if err = srv.Start(); err != nil {
|
||||
if err := srv.Start(); err != nil {
|
||||
log.Errorf("Server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debug("Server started successfully")
|
||||
log.Debug("服务器已成功启动")
|
||||
|
||||
// 启动定时任务
|
||||
tasks.Start()
|
||||
@ -73,13 +37,11 @@ func main() {
|
||||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
log.Warn("Shutting down...")
|
||||
cancel() // 停止容器监控
|
||||
|
||||
log.Warn("正在关闭...")
|
||||
// 关闭HTTP服务器
|
||||
if err = srv.Shutdown(); err != nil {
|
||||
log.Fatalf("Server shutdown failed: %v", err)
|
||||
if err := srv.Shutdown(); err != nil {
|
||||
log.Fatalf("服务器关闭失败: %v", err)
|
||||
}
|
||||
|
||||
log.Warn("Server exited properly")
|
||||
log.Warn("服务器已正确退出")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user