:refactor: 重构配置加载逻辑,移除不必要的Load函数,简化代码结构并优化Redis和Docker配置
This commit is contained in:
parent
c320e8951f
commit
2e009e593f
@ -13,19 +13,21 @@ database:
|
|||||||
dbname: "wechat_bot"
|
dbname: "wechat_bot"
|
||||||
charset: "utf8mb4"
|
charset: "utf8mb4"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: "10.0.0.31"
|
||||||
|
port: 6379
|
||||||
|
password: "pGhQKwj7DE7FbFL1"
|
||||||
|
db: 13
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
host: "http://10.0.0.243:2375"
|
host: "http://10.0.0.243:2375"
|
||||||
apiVersion: "1.41"
|
apiVersion: "1.41"
|
||||||
imageName: "lxh01/xybotv2:latest"
|
imageName: "lxh01/xybotv2:latest"
|
||||||
network: "bridge"
|
network: "bridge"
|
||||||
memory: 123 # 容器内存限制(MB)
|
memory: 512 # 容器内存限制(MB)
|
||||||
redis:
|
|
||||||
host: "10.0.0.31"
|
|
||||||
password: "pGhQKwj7DE7FbFL1"
|
|
||||||
db: 2
|
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
type: "password" # 支持 password 和 logto 两种
|
type: "logto" # 支持 password 和 logto 两种
|
||||||
password:
|
password:
|
||||||
secretKey: "your-secret-key-change-me" # 加密密钥
|
secretKey: "your-secret-key-change-me" # 加密密钥
|
||||||
adminToken: "admin-token-change-me" # 密码
|
adminToken: "admin-token-change-me" # 密码
|
||||||
@ -38,3 +40,11 @@ auth:
|
|||||||
logger:
|
logger:
|
||||||
level: "debug"
|
level: "debug"
|
||||||
file: "" # 空表示日志输出到控制台
|
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"
|
# type: "sqlite"
|
||||||
# dbname: "./data/wechat_demo.db"
|
# dbname: "./data/wechat_demo.db"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: "10.0.0.31"
|
||||||
|
port: 6379
|
||||||
|
password: "pGhQKwj7DE7FbFL1"
|
||||||
|
db: 13
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
host: "unix:///var/run/docker.sock"
|
host: "unix:///var/run/docker.sock"
|
||||||
apiVersion: "1.41"
|
apiVersion: "1.41"
|
||||||
imageName: "lxh01/xybotv2:latest"
|
imageName: "lxh01/xybotv2:latest"
|
||||||
network: "bridge"
|
network: "bridge"
|
||||||
memory: 123 # 容器内存限制(MB)
|
memory: 123 # 容器内存限制(MB)
|
||||||
redis:
|
|
||||||
host: "10.0.0.31"
|
|
||||||
password: "pGhQKwj7DE7FbFL1"
|
|
||||||
db: 2
|
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
type: "password" # 支持 password 和 logto 两种
|
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/go-co-op/gocron/v2 v2.16.1
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/gofiber/fiber/v2 v2.52.6
|
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/template/html/v2 v2.1.3
|
||||||
github.com/gofiber/websocket/v2 v2.2.1
|
github.com/gofiber/websocket/v2 v2.2.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@ -27,7 +28,9 @@ require (
|
|||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.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/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/distribution/reference v0.5.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // 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/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // 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/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/rs/xid v1.6.0 // 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 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
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 h1:kgwJktO/p7WbywUuAGTPH2V4VOta6dnYs1CXz6qVvZU=
|
||||||
gitee.ltd/lxh/xybot v0.0.5/go.mod h1:jYfEAQ3WPsST/PY4fEEVFjU6KtMocxn3sQi78I+vdxc=
|
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=
|
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/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 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
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 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
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 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
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=
|
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/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 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
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 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
|
||||||
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
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=
|
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
logtoClient "github.com/logto-io/go/v2/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthConfig 认证配置
|
// AuthConfig 认证配置
|
||||||
@ -90,3 +91,11 @@ func (c *AuthLogto) Validate() error {
|
|||||||
|
|
||||||
return nil
|
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
|
package config
|
||||||
|
|
||||||
import (
|
var Scd Config
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config 是应用程序的主配置结构
|
// Config 是应用程序的主配置结构
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server ServerConfig `mapstructure:"server"`
|
Server ServerConfig `mapstructure:"server"`
|
||||||
Database DatabaseConfig `mapstructure:"database"`
|
Database DatabaseConfig `mapstructure:"database"`
|
||||||
|
Redis RedisConfig `mapstructure:"redis"`
|
||||||
Docker DockerConfig `mapstructure:"docker"`
|
Docker DockerConfig `mapstructure:"docker"`
|
||||||
Auth AuthConfig `mapstructure:"auth"`
|
Auth AuthConfig `mapstructure:"auth"`
|
||||||
Logger LoggerConfig `mapstructure:"logger"`
|
Logger LoggerConfig `mapstructure:"logger"`
|
||||||
Minio MinioConfig `mapstructure:"minio"`
|
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 验证配置是否有效
|
// Validate 验证配置是否有效
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
if err := c.Server.Validate(); err != nil {
|
if err := c.Server.Validate(); err != nil {
|
||||||
@ -71,5 +33,10 @@ func (c *Config) Validate() error {
|
|||||||
if err := c.Minio.Validate(); err != nil {
|
if err := c.Minio.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.Redis.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RedisConfig 是Redis相关配置
|
|
||||||
type RedisConfig struct {
|
|
||||||
Host string `mapstructure:"host"`
|
|
||||||
Password string `mapstructure:"password"`
|
|
||||||
DB int `mapstructure:"db"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DockerConfig Docker配置
|
// DockerConfig Docker配置
|
||||||
type DockerConfig struct {
|
type DockerConfig struct {
|
||||||
Host string `mapstructure:"host"` // Docker daemon 主机地址
|
Host string `mapstructure:"host"` // Docker daemon 主机地址
|
||||||
APIVersion string `mapstructure:"apiVersion"` // Docker API版本
|
APIVersion string `mapstructure:"apiVersion"` // Docker API版本
|
||||||
ImageName string `mapstructure:"imageName"` // 微信机器人Docker镜像名称
|
ImageName string `mapstructure:"imageName"` // 微信机器人Docker镜像名称
|
||||||
Network string `mapstructure:"network"` // 容器网络
|
Network string `mapstructure:"network"` // 容器网络
|
||||||
Memory int64 `mapstructure:"memory"` // 内存限制
|
Memory int64 `mapstructure:"memory"` // 内存限制
|
||||||
Redis RedisConfig `mapstructure:"redis"` // Redis配置
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate 验证Docker配置
|
// Validate 验证Docker配置
|
||||||
@ -36,5 +28,9 @@ func (c *DockerConfig) Validate() error {
|
|||||||
c.Network = "bridge"
|
c.Network = "bridge"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Memory == 0 {
|
||||||
|
c.Memory = 512 // 默认内存限制为512M
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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. **微信机器人操作**
|
2. **状态监控**
|
||||||
- 获取登录二维码
|
|
||||||
- 微信登录状态监控
|
|
||||||
- 获取联系人列表和群成员
|
|
||||||
- 获取聊天记录
|
|
||||||
- 微信登出
|
|
||||||
|
|
||||||
3. **状态监控**
|
|
||||||
- 定期检查微信机器人容器状态
|
- 定期检查微信机器人容器状态
|
||||||
- 自动更新数据库中的状态信息
|
- 自动更新数据库中的状态信息
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
初始化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
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
"gitee.ltd/lxh/wechat-robot/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dockerClient *client.Client
|
dockerClient *client.Client
|
||||||
clientOnce sync.Once
|
|
||||||
clientErr error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitClient 初始化Docker客户端
|
// InitClient 初始化Docker客户端
|
||||||
func InitClient(cfg *config.DockerConfig) error {
|
func InitClient() {
|
||||||
clientOnce.Do(func() {
|
var err error
|
||||||
options := []client.Opt{
|
defer func() {
|
||||||
client.WithAPIVersionNegotiation(),
|
if err != nil {
|
||||||
|
log.Panicf("Docker客户端初始化失败: %v", err)
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// 如果指定了Docker主机地址
|
options := []client.Opt{
|
||||||
if cfg.Host != "" {
|
client.WithAPIVersionNegotiation(),
|
||||||
options = append(options, client.WithHost(cfg.Host))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 如果指定了API版本
|
// 如果指定了Docker主机地址
|
||||||
if cfg.APIVersion != "" {
|
if config.Scd.Docker.Host != "" {
|
||||||
options = append(options, client.WithVersion(cfg.APIVersion))
|
options = append(options, client.WithHost(config.Scd.Docker.Host))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建Docker客户端
|
// 如果指定了API版本
|
||||||
dockerClient, clientErr = client.NewClientWithOpts(options...)
|
if config.Scd.Docker.APIVersion != "" {
|
||||||
})
|
options = append(options, client.WithVersion(config.Scd.Docker.APIVersion))
|
||||||
|
}
|
||||||
|
|
||||||
return clientErr
|
// 创建Docker客户端
|
||||||
|
dockerClient, err = client.NewClientWithOpts(options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient 获取Docker客户端实例
|
// GetClient 获取Docker客户端实例
|
||||||
func GetClient() *client.Client {
|
func GetClient() *client.Client {
|
||||||
if dockerClient == nil {
|
if dockerClient == nil {
|
||||||
panic("Docker client not initialized, call InitClient first")
|
panic("Docker客户端未初始化,先调用InitClient")
|
||||||
}
|
}
|
||||||
return dockerClient
|
return dockerClient
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ type ContainerInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateContainer 创建容器
|
// 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()
|
cli := GetClient()
|
||||||
|
|
||||||
// 检测是否在容器内运行
|
// 检测是否在容器内运行
|
||||||
@ -42,7 +42,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
|||||||
currentNetworkName = getCurrentContainerNetwork(ctx, cli)
|
currentNetworkName = getCurrentContainerNetwork(ctx, cli)
|
||||||
if currentNetworkName != "" {
|
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 {
|
if port <= 0 {
|
||||||
// 查找同镜像容器的最大端口号并加1
|
// 查找同镜像容器的最大端口号并加1
|
||||||
maxPort, err := findMaxPortForImage(ctx, cli, cfg.ImageName)
|
maxPort, err := findMaxPortForImage(ctx, cli, config.Scd.Docker.ImageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果出错,使用默认端口9001
|
// 如果出错,使用默认端口9001
|
||||||
port = 9001
|
port = 9001
|
||||||
@ -79,15 +79,18 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加Redis环境变量
|
// 添加Redis环境变量
|
||||||
if cfg.Redis.Host != "" {
|
if config.Scd.Redis.Host != "" {
|
||||||
env = append(env, fmt.Sprintf("REDIS_HOST=%s", cfg.Redis.Host))
|
env = append(env, fmt.Sprintf("REDIS_HOST=%s", config.Scd.Redis.Host))
|
||||||
env = append(env, fmt.Sprintf("REDIS_PASSWORD=%s", cfg.Redis.Password))
|
env = append(env, fmt.Sprintf("REDIS_PASSWORD=%s", config.Scd.Redis.Password))
|
||||||
env = append(env, fmt.Sprintf("REDIS_DB=%d", cfg.Redis.DB))
|
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{
|
containerConfig := &container.Config{
|
||||||
Image: cfg.ImageName,
|
Image: config.Scd.Docker.ImageName,
|
||||||
Env: env,
|
Env: env,
|
||||||
ExposedPorts: exposedPorts,
|
ExposedPorts: exposedPorts,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
@ -100,19 +103,16 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
|||||||
Name: "unless-stopped",
|
Name: "unless-stopped",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if cfg.Memory == 0 {
|
hostConfig.Memory = config.Scd.Docker.Memory * 1024 * 1024 // 限制使用内存
|
||||||
cfg.Memory = 512 // 默认内存限制为512M
|
|
||||||
}
|
|
||||||
hostConfig.Memory = cfg.Memory * 1024 * 1024 // 限制使用内存
|
|
||||||
|
|
||||||
// 设置网络配置
|
// 设置网络配置
|
||||||
networkingConfig := &network.NetworkingConfig{}
|
networkingConfig := &network.NetworkingConfig{}
|
||||||
if cfg.Network != "" {
|
if config.Scd.Docker.Network != "" {
|
||||||
// 首先检查网络类型,只在用户自定义网络上分配固定IP
|
// 首先检查网络类型,只在用户自定义网络上分配固定IP
|
||||||
isUserNetwork := false
|
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 {
|
if err == nil {
|
||||||
isUserNetwork = true
|
isUserNetwork = true
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
|
|||||||
// 只在用户自定义网络上尝试分配固定IP
|
// 只在用户自定义网络上尝试分配固定IP
|
||||||
if isUserNetwork {
|
if isUserNetwork {
|
||||||
// 自动为容器分配一个递增的IP地址
|
// 自动为容器分配一个递增的IP地址
|
||||||
nextIP, err := getNextAvailableIPInNetwork(ctx, cli, cfg.Network)
|
nextIP, err := getNextAvailableIPInNetwork(ctx, cli, config.Scd.Docker.Network)
|
||||||
if err == nil && nextIP != "" {
|
if err == nil && nextIP != "" {
|
||||||
endpointSettings.IPAMConfig = &network.EndpointIPAMConfig{
|
endpointSettings.IPAMConfig = &network.EndpointIPAMConfig{
|
||||||
IPv4Address: nextIP,
|
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
|
networkingConfig.EndpointsConfig = endpointsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,7 +650,7 @@ func GetContainerIP(ctx context.Context, containerID string, cfg *config.DockerC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetContainerHost 获取容器的访问地址(格式:ip:port)
|
// 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或容器名
|
// 如果在Docker环境中运行,需要获取容器真实IP或容器名
|
||||||
if isRunningInContainer() {
|
if isRunningInContainer() {
|
||||||
cli := GetClient()
|
cli := GetClient()
|
||||||
@ -689,7 +689,7 @@ func GetContainerHost(ctx context.Context, containerID string, cfg *config.Docke
|
|||||||
return "", fmt.Errorf("无法检查容器: %w", err)
|
return "", fmt.Errorf("无法检查容器: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostIP := extractHostIP(cfg.Host)
|
hostIP := extractHostIP(config.Scd.Docker.Host)
|
||||||
|
|
||||||
// 查找9000端口的映射
|
// 查找9000端口的映射
|
||||||
for _, port := range inspect.NetworkSettings.Ports["9000/tcp"] {
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,7 +13,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CreateRobotContainer 创建微信机器人容器
|
// 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{
|
labels := map[string]string{
|
||||||
WechatBotLabelKey: WechatBotLabelValue,
|
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 {
|
if err != nil {
|
||||||
return "", "", err
|
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 {
|
if err != nil {
|
||||||
log.Warnf("警告: 无法获取容器访问地址: %v", err)
|
log.Warnf("警告: 无法获取容器访问地址: %v", err)
|
||||||
containerHost = "localhost:9000" // 使用默认值
|
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)
|
bs, _ := json.Marshal(response)
|
||||||
log.Debugf("扫码返回结果: %s", bs)
|
log.Debugf("扫码返回结果: %s", bs)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
// LoginPage 显示登录页面
|
// LoginPage 显示登录页面
|
||||||
func LoginPage(c *fiber.Ctx) error {
|
func LoginPage(c *fiber.Ctx) error {
|
||||||
// 如果已经登录,则重定向到机器人列表
|
// 如果已经登录,则重定向到机器人列表
|
||||||
if middleware.IsAuthenticated(c) {
|
if _, flag := middleware.IsAuthenticated(c); flag {
|
||||||
return c.Redirect("/admin/robots")
|
return c.Redirect("/admin/robots")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ func LoginPage(c *fiber.Ctx) error {
|
|||||||
errorMsg := c.Query("error")
|
errorMsg := c.Query("error")
|
||||||
|
|
||||||
// 加载配置
|
// 加载配置
|
||||||
if cfg, _ := config.Load(); cfg.Auth.Type == "logto" {
|
if config.Scd.Auth.Type == "logto" {
|
||||||
// 如果使用Logto认证,重定向到Logto登录页面
|
// 如果使用Logto认证,重定向到Logto登录页面
|
||||||
return c.Redirect("/auth/logto/login")
|
return c.Redirect("/auth/logto/login")
|
||||||
}
|
}
|
||||||
@ -38,31 +38,25 @@ func LoginSubmit(c *fiber.Ctx) error {
|
|||||||
// 获取用户输入的密钥
|
// 获取用户输入的密钥
|
||||||
token := c.FormValue("token")
|
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匹配
|
// 仅验证密钥是否与配置的AdminToken匹配
|
||||||
if token != cfg.Auth.Password.AdminToken {
|
if token != config.Scd.Auth.Password.AdminToken {
|
||||||
return c.Redirect("/login?error=访问密钥不正确")
|
return c.Redirect("/login?error=访问密钥不正确")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录成功,设置认证 Cookie
|
// 登录成功,设置认证 Cookie
|
||||||
cookie := new(fiber.Cookie)
|
cookie := new(fiber.Cookie)
|
||||||
cookie.Name = "auth_token"
|
cookie.Name = "auth_token"
|
||||||
cookie.Value = cfg.Auth.Password.SecretKey // 在实际应用中,这应该是一个生成的会话令牌
|
cookie.Value = config.Scd.Auth.Password.SecretKey // 在实际应用中,这应该是一个生成的会话令牌
|
||||||
cookie.Expires = time.Now().Add(time.Hour * time.Duration(cfg.Auth.Password.TokenExpiry))
|
cookie.Expires = time.Now().Add(time.Hour * time.Duration(config.Scd.Auth.Password.TokenExpiry))
|
||||||
cookie.HTTPOnly = true
|
cookie.HTTPOnly = true
|
||||||
cookie.Path = "/"
|
cookie.Path = "/"
|
||||||
c.Cookie(cookie)
|
c.Cookie(cookie)
|
||||||
|
|
||||||
// 重定向到机器人列表页面,而不是首页
|
// 重定向到机器人列表页面,而不是首页
|
||||||
return c.Redirect("/admin/robots")
|
return c.Redirect("/admin/robots")
|
||||||
} else if cfg.Auth.Type == "logto" {
|
} else if config.Scd.Auth.Type == "logto" {
|
||||||
// 对于Logto登录,我们重定向到Logto登录页面
|
// 对于Logto登录,我们重定向到Logto登录页面
|
||||||
return c.Redirect("/auth/logto/login")
|
return c.Redirect("/auth/logto/login")
|
||||||
}
|
}
|
||||||
@ -73,14 +67,8 @@ func LoginSubmit(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// Logout 处理退出登录
|
// Logout 处理退出登录
|
||||||
func Logout(c *fiber.Ctx) error {
|
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的登出流程
|
// 对于Logto登录,使用Logto的登出流程
|
||||||
return c.Redirect("/auth/logto/logout")
|
return c.Redirect("/auth/logto/logout")
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,23 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"gitee.ltd/lxh/wechat-robot/internal/logto"
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/types"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"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"
|
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||||||
|
"net/http"
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 实现Logto会话存储接口
|
|
||||||
var store = session.New(session.Config{
|
|
||||||
KeyLookup: "cookie:logto-session",
|
|
||||||
CookieSecure: false, // 开发环境可设成false
|
|
||||||
})
|
|
||||||
|
|
||||||
// LogtoLogin 重定向到Logto登录页面
|
// LogtoLogin 重定向到Logto登录页面
|
||||||
func LogtoLogin(c *fiber.Ctx) error {
|
func LogtoLogin(c *fiber.Ctx) error {
|
||||||
// 加载配置
|
|
||||||
cfg, err := config.Load()
|
|
||||||
if err != nil {
|
|
||||||
return c.Redirect("/error?error=系统错误,无法加载配置")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建回调URL
|
// 构建回调URL
|
||||||
callbackURL := c.Protocol() + "://" + c.Hostname() + "/auth/logto/callback"
|
callbackURL := c.Protocol() + "://" + c.Hostname() + "/auth/logto/callback"
|
||||||
//callbackURL := "https://wechat.0bug.dev/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)
|
signInUri, err := client.SignInWithRedirectUri(callbackURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Redirect("/error?error=Logto登录错误: " + err.Error() + "。请在Logto控制台确认回调URI为: " + callbackURL)
|
return c.Redirect("/error?error=Logto登录错误: " + err.Error() + "。请在Logto控制台确认回调URI为: " + callbackURL)
|
||||||
@ -53,23 +28,11 @@ func LogtoLogin(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// LogtoCallback 处理Logto登录回调
|
// LogtoCallback 处理Logto登录回调
|
||||||
func LogtoCallback(c *fiber.Ctx) error {
|
func LogtoCallback(c *fiber.Ctx) error {
|
||||||
// 加载配置
|
client, err := logto.GetLogtoClient(c)
|
||||||
cfg, err := config.Load()
|
|
||||||
if err != nil {
|
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{}
|
r := &http.Request{}
|
||||||
if err = fasthttpadaptor.ConvertRequest(c.Context(), r, true); err != nil {
|
if err = fasthttpadaptor.ConvertRequest(c.Context(), r, true); err != nil {
|
||||||
return c.Redirect("/error?error=转换请求错误: " + err.Error())
|
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())
|
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")
|
return c.Redirect("/admin/robots")
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogtoLogout 处理Logto退出登录
|
// LogtoLogout 处理Logto退出登录
|
||||||
func LogtoLogout(c *fiber.Ctx) error {
|
func LogtoLogout(c *fiber.Ctx) error {
|
||||||
// 加载配置
|
client, err := logto.GetLogtoClient(c)
|
||||||
cfg, err := config.Load()
|
|
||||||
if err != nil {
|
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
|
// 构建登出后重定向URL
|
||||||
postLogoutRedirectURL := c.Protocol() + "://" + c.Hostname()
|
postLogoutRedirectURL := c.Protocol() + "://" + c.Hostname()
|
||||||
|
|
||||||
@ -121,15 +63,6 @@ func LogtoLogout(c *fiber.Ctx) error {
|
|||||||
return c.Redirect("/error?error=Logto登出错误: " + err.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登出页面
|
// 重定向到Logto登出页面
|
||||||
return c.Redirect(signOutUri)
|
return c.Redirect(signOutUri)
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,14 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
"gitee.ltd/lxh/wechat-robot/internal/docker"
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/model"
|
"gitee.ltd/lxh/wechat-robot/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListRobots 列出所有机器人
|
// ListRobots 列出所有机器人
|
||||||
func ListRobots(c *fiber.Ctx) error {
|
func ListRobots(c *fiber.Ctx) error {
|
||||||
|
log.Debugf("登录用户Id: %+v", c.Get("userId"))
|
||||||
|
|
||||||
db := model.GetDB()
|
db := model.GetDB()
|
||||||
var robots []model.Robot
|
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容器
|
// 创建Docker容器
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
containerID, containerHost, err := docker.CreateRobotContainer(ctx, &cfg.Docker, robotName, port)
|
containerID, containerHost, err := docker.CreateRobotContainer(ctx, robotName, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "创建容器失败: "+err.Error())
|
return fiber.NewError(fiber.StatusInternalServerError, "创建容器失败: "+err.Error())
|
||||||
}
|
}
|
||||||
@ -105,11 +100,6 @@ func CreateRobot(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "保存机器人信息失败: "+err.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)))
|
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 {
|
if err = db.Delete(&robot).Error; err != nil {
|
||||||
// 针对API请求返回JSON
|
// 针对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
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"context"
|
||||||
|
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/config"
|
"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"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,29 +12,28 @@ import (
|
|||||||
// @description: 检查用户是否已登录
|
// @description: 检查用户是否已登录
|
||||||
// @param c
|
// @param c
|
||||||
// @return bool
|
// @return bool
|
||||||
func IsAuthenticated(c *fiber.Ctx) bool {
|
func IsAuthenticated(c *fiber.Ctx) (loginType string, flag bool) {
|
||||||
token := c.Cookies("auth_token")
|
token := c.Cookies("auth_token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return false
|
if token = c.Cookies("logto-session"); token == "" {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
// 加载配置
|
|
||||||
cfg, err := config.Load()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据认证类型验证
|
// 根据认证类型验证
|
||||||
switch cfg.Auth.Type {
|
loginType = config.Scd.Auth.Type
|
||||||
|
switch config.Scd.Auth.Type {
|
||||||
case "password":
|
case "password":
|
||||||
// 对比token (简单实现,实际应用可能需要更复杂的验证)
|
// 对比token (简单实现,实际应用可能需要更复杂的验证)
|
||||||
return token == cfg.Auth.Password.SecretKey
|
flag = token == config.Scd.Auth.Password.SecretKey
|
||||||
case "logto":
|
case "logto":
|
||||||
// 如果是Logto认证方式,检查token前缀,有前缀则认为已登录
|
// 如果是Logto认证方式,检查token前缀,有前缀则认为已登录
|
||||||
return strings.HasPrefix(token, "logto:")
|
flag = redis.Client.Exists(context.Background(), token).Val() > 0
|
||||||
default:
|
default:
|
||||||
return false
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate
|
// Authenticate
|
||||||
@ -42,9 +42,22 @@ func IsAuthenticated(c *fiber.Ctx) bool {
|
|||||||
func Authenticate() fiber.Handler {
|
func Authenticate() fiber.Handler {
|
||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
// 检查是否已登录
|
// 检查是否已登录
|
||||||
if !IsAuthenticated(c) {
|
loginType, flag := IsAuthenticated(c)
|
||||||
|
if !flag {
|
||||||
return c.Redirect("/login")
|
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()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,6 @@ import (
|
|||||||
// @return url
|
// @return url
|
||||||
// @return err
|
// @return err
|
||||||
func SaveBytes(b []byte, md5 string, suffix ...string) (url string, err error) {
|
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()
|
ctx := context.Background()
|
||||||
contentType := http.DetectContentType(b)
|
contentType := http.DetectContentType(b)
|
||||||
if strings.Contains(contentType, ";") {
|
if strings.Contains(contentType, ";") {
|
||||||
@ -48,17 +42,17 @@ func SaveBytes(b []byte, md5 string, suffix ...string) (url string, err error) {
|
|||||||
}
|
}
|
||||||
log.Debugf("开始上传文件: %v", fileName)
|
log.Debugf("开始上传文件: %v", fileName)
|
||||||
reader := bytes.NewBuffer(b)
|
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 {
|
if err != nil {
|
||||||
log.Errorf("文件上传错误: %v", err)
|
log.Errorf("文件上传错误: %v", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
log.Debugf("文件上传完毕: %v", fileName)
|
log.Debugf("文件上传完毕: %v", fileName)
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if cfg.Minio.UseSsl {
|
if config.Scd.Minio.UseSsl {
|
||||||
protocol = "https"
|
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() {
|
//go func() {
|
||||||
// wf := db.WeChatFileEntity{Url: fileUrl, HashCode: md5, FileType: contentType}
|
// wf := db.WeChatFileEntity{Url: fileUrl, HashCode: md5, FileType: contentType}
|
||||||
|
@ -13,17 +13,12 @@ var minioClient *minio.Client
|
|||||||
// Init
|
// Init
|
||||||
// @description: 初始化Minio连接
|
// @description: 初始化Minio连接
|
||||||
func Init() {
|
func Init() {
|
||||||
cfg, err := config.Load()
|
var err error
|
||||||
if err != nil {
|
|
||||||
log.Panicf("加载配置失败: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
// 初使化 minio client对象。
|
// 初使化 minio client对象。
|
||||||
minioClient, err = minio.New(cfg.Minio.Endpoint, &minio.Options{
|
minioClient, err = minio.New(config.Scd.Minio.Endpoint, &minio.Options{
|
||||||
Creds: credentials.NewStaticV4(cfg.Minio.AccessKeyID, cfg.Minio.SecretAccessKey, ""),
|
Creds: credentials.NewStaticV4(config.Scd.Minio.AccessKeyID, config.Scd.Minio.SecretAccessKey, ""),
|
||||||
Secure: cfg.Minio.UseSsl,
|
Secure: config.Scd.Minio.UseSsl,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("OSS初始化失败: %v", err.Error())
|
log.Panicf("OSS初始化失败: %v", err.Error())
|
||||||
@ -31,14 +26,14 @@ func Init() {
|
|||||||
log.Debug("OSS连接成功,开始判断桶是否存在")
|
log.Debug("OSS连接成功,开始判断桶是否存在")
|
||||||
// 判断捅是否存在,不存在就创建
|
// 判断捅是否存在,不存在就创建
|
||||||
var exists bool
|
var exists bool
|
||||||
exists, err = minioClient.BucketExists(ctx, cfg.Minio.BucketName)
|
exists, err = minioClient.BucketExists(ctx, config.Scd.Minio.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("判断桶失败: %v", err)
|
log.Panicf("判断桶失败: %v", err)
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Debug("桶不存在,开始创建")
|
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 {
|
if err != nil {
|
||||||
log.Panicf("OSS桶创建失败: %v", err.Error())
|
log.Panicf("OSS桶创建失败: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -23,16 +23,12 @@
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"github.com/Lxh/wechat-demo/internal/config"
|
"gitee.ltd/lxh/wechat-robot/internal/initialize"
|
||||||
"github.com/Lxh/wechat-demo/internal/model"
|
"github.com/Lxh/wechat-demo/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cfg, _ := config.Load()
|
initialize.Init()
|
||||||
err := model.InitDB(&cfg.Database)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用DB实例
|
// 使用DB实例
|
||||||
db := model.GetDB()
|
db := model.GetDB()
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
@ -16,69 +14,71 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
dbOnce sync.Once
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitDB 初始化数据库连接
|
// InitDB 初始化数据库连接
|
||||||
func InitDB(cfg *config.DatabaseConfig) error {
|
func InitDB() {
|
||||||
var err error
|
var err error
|
||||||
|
defer func() {
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to connect to database: %v", err)
|
log.Panicf("数据库初始化失败: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// 自动迁移数据库模型
|
gormConfig := &gorm.Config{
|
||||||
err = migrateDB()
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Failed to migrate database: %v", err)
|
log.Errorf("无法获取基础SQL DB: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于SQLite,执行一些特定的优化
|
// 启用外键约束
|
||||||
if cfg.Type == config.SQLite {
|
_, _ = sqlDB.Exec("PRAGMA foreign_keys = ON")
|
||||||
var sqlDB *sql.DB
|
// 设置连接池大小
|
||||||
sqlDB, err = db.DB()
|
sqlDB.SetMaxOpenConns(1) // SQLite建议使用单连接
|
||||||
if err != nil {
|
}
|
||||||
log.Errorf("Warning: Could not get underlying SQL DB: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启用外键约束
|
return
|
||||||
_, _ = sqlDB.Exec("PRAGMA foreign_keys = ON")
|
|
||||||
// 设置连接池大小
|
|
||||||
sqlDB.SetMaxOpenConns(1) // SQLite建议使用单连接
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDB 获取数据库连接实例
|
// GetDB 获取数据库连接实例
|
||||||
func GetDB() *gorm.DB {
|
func GetDB() *gorm.DB {
|
||||||
if db == nil {
|
if db == nil {
|
||||||
panic("Database not initialized, call InitDB first")
|
panic("数据库未初始化,先调用InitDB")
|
||||||
}
|
}
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func CloseDB() error {
|
|||||||
|
|
||||||
sqlDB, err := db.DB()
|
sqlDB, err := db.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get sql.DB instance error: %w", err)
|
return fmt.Errorf("获取sql.DB实例错误: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sqlDB.Close()
|
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服务器
|
// Server 表示HTTP服务器
|
||||||
type Server struct {
|
type Server struct {
|
||||||
app *fiber.App
|
app *fiber.App
|
||||||
config *config.Config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New 创建新的服务器实例
|
// New 创建新的服务器实例
|
||||||
func New(cfg *config.Config) *Server {
|
func New() *Server {
|
||||||
// 确保视图目录存在
|
// 确保视图目录存在
|
||||||
viewsDir := "./internal/view"
|
viewsDir := "./internal/view"
|
||||||
if _, err := os.Stat(viewsDir); os.IsNotExist(err) {
|
if _, err := os.Stat(viewsDir); os.IsNotExist(err) {
|
||||||
@ -36,8 +35,8 @@ func New(cfg *config.Config) *Server {
|
|||||||
|
|
||||||
// 初始化模板引擎
|
// 初始化模板引擎
|
||||||
engine := html.New(viewsDir, ".html")
|
engine := html.New(viewsDir, ".html")
|
||||||
engine.Reload(cfg.Server.Env == "development") // 开发环境下启用热重载
|
engine.Reload(config.Scd.Server.Env == "development") // 开发环境下启用热重载
|
||||||
engine.Debug(cfg.Server.Env == "development") // 增加Debug输出
|
engine.Debug(config.Scd.Server.Env == "development") // 增加Debug输出
|
||||||
|
|
||||||
// 添加自定义模板函数
|
// 添加自定义模板函数
|
||||||
engine.AddFunc("sub", func(a, b int) int {
|
engine.AddFunc("sub", func(a, b int) int {
|
||||||
@ -64,7 +63,7 @@ func New(cfg *config.Config) *Server {
|
|||||||
app := fiber.New(fiber.Config{
|
app := fiber.New(fiber.Config{
|
||||||
Views: engine,
|
Views: engine,
|
||||||
ViewsLayout: "layouts/main", // 默认布局
|
ViewsLayout: "layouts/main", // 默认布局
|
||||||
EnablePrintRoutes: cfg.Server.Env == "development",
|
EnablePrintRoutes: config.Scd.Server.Env == "development",
|
||||||
JSONEncoder: json.Marshal,
|
JSONEncoder: json.Marshal,
|
||||||
JSONDecoder: json.Unmarshal,
|
JSONDecoder: json.Unmarshal,
|
||||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||||
@ -107,8 +106,7 @@ func New(cfg *config.Config) *Server {
|
|||||||
app.Static("/public", "./public")
|
app.Static("/public", "./public")
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
app: app,
|
app: app,
|
||||||
config: cfg,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,8 +154,8 @@ func (s *Server) SetupRoutes() {
|
|||||||
|
|
||||||
// Start 启动HTTP服务器
|
// Start 启动HTTP服务器
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
addr := s.config.Server.Address()
|
addr := config.Scd.Server.Address()
|
||||||
fmt.Printf("Server starting on %s\n", addr)
|
fmt.Printf("服务开始启动,监听地址: %s\n", addr)
|
||||||
return s.app.Listen(addr)
|
return s.app.Listen(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
main.go
60
main.go
@ -1,69 +1,33 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"gitee.ltd/lxh/wechat-robot/internal/initialize"
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/minio"
|
"gitee.ltd/lxh/wechat-robot/internal/server"
|
||||||
"gitee.ltd/lxh/wechat-robot/internal/tasks"
|
"gitee.ltd/lxh/wechat-robot/internal/tasks"
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"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() {
|
func main() {
|
||||||
// 加载配置
|
// 初始化系统
|
||||||
cfg, err := config.Load()
|
initialize.Init()
|
||||||
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)
|
|
||||||
|
|
||||||
// 创建HTTP服务器
|
// 创建HTTP服务器
|
||||||
srv := server.New(cfg)
|
srv := server.New()
|
||||||
|
|
||||||
// 设置路由
|
// 设置路由
|
||||||
srv.SetupRoutes()
|
srv.SetupRoutes()
|
||||||
|
|
||||||
// 启动HTTP服务器
|
// 启动HTTP服务器
|
||||||
go func() {
|
go func() {
|
||||||
if err = srv.Start(); err != nil {
|
if err := srv.Start(); err != nil {
|
||||||
log.Errorf("Server error: %v", err)
|
log.Errorf("Server error: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Debug("Server started successfully")
|
log.Debug("服务器已成功启动")
|
||||||
|
|
||||||
// 启动定时任务
|
// 启动定时任务
|
||||||
tasks.Start()
|
tasks.Start()
|
||||||
@ -73,13 +37,11 @@ func main() {
|
|||||||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
|
|
||||||
log.Warn("Shutting down...")
|
log.Warn("正在关闭...")
|
||||||
cancel() // 停止容器监控
|
|
||||||
|
|
||||||
// 关闭HTTP服务器
|
// 关闭HTTP服务器
|
||||||
if err = srv.Shutdown(); err != nil {
|
if err := srv.Shutdown(); err != nil {
|
||||||
log.Fatalf("Server shutdown failed: %v", err)
|
log.Fatalf("服务器关闭失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn("Server exited properly")
|
log.Warn("服务器已正确退出")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user