From e6457b7b220816c819ad1e91afdfa0e16ee1170c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=AF=BB=E6=AC=A2?= Date: Mon, 21 Apr 2025 16:02:39 +0800 Subject: [PATCH] =?UTF-8?q?:art:=20=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E4=BB=A5=E4=BD=BF=E7=94=A8=E6=96=B0=E7=9A=84=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=BA=93=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=87=BD=E6=95=B0=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=96=B0=E7=9A=84API=EF=BC=9B=E4=BC=98=E5=8C=96=E5=86=85?= =?UTF-8?q?=E5=AD=98=E5=92=8C=E7=BD=91=E7=BB=9C=E7=BB=9F=E8=AE=A1=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 61 +++--- go.sum | 178 ++++++++------- internal/docker/container.go | 23 +- internal/docker/exec.go | 97 -------- internal/docker/monitor.go | 153 +++++++------ internal/docker/robot.go | 390 +-------------------------------- internal/docker/robot_model.go | 218 ------------------ internal/docker/stats.go | 17 +- internal/handler/api_login.go | 38 ++-- internal/handler/health.go | 6 +- internal/handler/robot.go | 54 +++-- internal/server/server.go | 4 +- internal/tasks/contract.go | 27 +-- internal/tasks/sync.go | 36 +-- internal/tasks/tasks.go | 16 +- internal/view/robot/show.html | 27 ++- 16 files changed, 330 insertions(+), 1015 deletions(-) delete mode 100644 internal/docker/exec.go delete mode 100644 internal/docker/robot_model.go diff --git a/go.mod b/go.mod index f5a66d7..31ce05c 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,14 @@ module gitee.ltd/lxh/wechat-robot go 1.24.0 require ( - github.com/docker/docker v25.0.3+incompatible + gitee.ltd/lxh/xybot v0.0.3 + github.com/docker/docker v28.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/go-co-op/gocron/v2 v2.16.1 - github.com/go-resty/resty/v2 v2.10.0 github.com/goccy/go-json v0.10.5 github.com/gofiber/fiber/v2 v2.52.6 github.com/gofiber/template/html/v2 v2.1.3 + github.com/google/uuid v1.6.0 github.com/spf13/viper v1.20.1 gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.11 @@ -19,64 +20,70 @@ require ( ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/go-resty/resty/v2 v2.16.5 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/utils v1.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.5.5 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/logto-io/go v1.0.6 // indirect + github.com/logto-io/go/v2 v2.0.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.51.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect + github.com/valyala/fasthttp v1.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/tools v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 2e5c1d2..339c009 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ +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= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 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/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -13,8 +17,8 @@ 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/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 v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= -github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= +github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -23,19 +27,24 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-co-op/gocron/v2 v2.16.1 h1:ux/5zxVRveCaCuTtNI3DiOk581KC1KpJbpJFYUEVYwo= github.com/go-co-op/gocron/v2 v2.16.1/go.mod h1:opexeOFy5BplhsKdA7bzY9zeYih8I8/WNJ4arTIFPVc= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo= -github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= @@ -50,6 +59,8 @@ github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -58,12 +69,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= -github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= @@ -74,22 +85,31 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/logto-io/go v1.0.6 h1:p+KHiXk6v0vIuq+1sYabjbgsnHQP5lV4t2ZnkLNtRkM= +github.com/logto-io/go v1.0.6/go.mod h1:owKJJjlaiQxbBGeIDFQTTtufp7ANb9odBXyqUheqWFI= +github.com/logto-io/go/v2 v2.0.0 h1:2Eo/S8nfIVNEa2VnzFODc3796vD2BwEOS4f+QboPG3U= +github.com/logto-io/go/v2 v2.0.0/go.mod h1:SqrSoACLSnGgO7yam0ecpEjc+ze1tbBaj+Q6OJVL7G8= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -98,26 +118,27 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 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= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -133,13 +154,12 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= -github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw= +github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= @@ -150,100 +170,74 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMey go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8= -google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/docker/container.go b/internal/docker/container.go index 8aaf6a3..8bcde23 100644 --- a/internal/docker/container.go +++ b/internal/docker/container.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" @@ -109,7 +108,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string, isUserNetwork := false if cfg.Network != "bridge" && cfg.Network != "host" && cfg.Network != "none" { // 检查网络是否存在 - _, err := cli.NetworkInspect(ctx, cfg.Network, types.NetworkInspectOptions{}) + _, err := cli.NetworkInspect(ctx, cfg.Network, network.InspectOptions{}) if err == nil { isUserNetwork = true } @@ -153,7 +152,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string, // getNextAvailableIPInNetwork 获取网络中下一个可用的IP地址 func getNextAvailableIPInNetwork(ctx context.Context, cli *client.Client, networkName string) (string, error) { // 获取网络信息 - networkResource, err := cli.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{}) + networkResource, err := cli.NetworkInspect(ctx, networkName, network.InspectOptions{}) if err != nil { return "", fmt.Errorf("无法检查网络: %w", err) } @@ -176,7 +175,7 @@ func getNextAvailableIPInNetwork(ctx context.Context, cli *client.Client, networ } // 获取网络中的所有容器 - containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true}) + containers, err := cli.ContainerList(ctx, container.ListOptions{All: true}) if err != nil { return "", fmt.Errorf("无法列出容器: %w", err) } @@ -300,7 +299,7 @@ func incrementIP(ip net.IP) net.IP { // getNextAvailableIP 获取网络中下一个可用的IP地址 func getNextAvailableIP(ctx context.Context, cli *client.Client, networkName string) (string, error) { // 获取网络信息 - networkResource, err := cli.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{}) + networkResource, err := cli.NetworkInspect(ctx, networkName, network.InspectOptions{}) if err != nil { return "", fmt.Errorf("无法检查网络: %w", err) } @@ -324,7 +323,7 @@ func getNextAvailableIP(ctx context.Context, cli *client.Client, networkName str // 获取现有容器的IP地址 existingIPs := make(map[string]bool) - containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true}) + containers, err := cli.ContainerList(ctx, container.ListOptions{All: true}) if err != nil { return "", fmt.Errorf("无法列出容器: %w", err) } @@ -493,9 +492,7 @@ func findMaxPortForImage(ctx context.Context, cli *client.Client, imageName stri maxPort := 9000 // 获取所有容器 - containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ - All: true, - }) + containers, err := cli.ContainerList(ctx, container.ListOptions{All: true}) if err != nil { return maxPort, err } @@ -518,7 +515,7 @@ func findMaxPortForImage(ctx context.Context, cli *client.Client, imageName stri // StartContainer 启动容器 func StartContainer(ctx context.Context, containerID string) error { cli := GetClient() - return cli.ContainerStart(ctx, containerID, types.ContainerStartOptions{}) + return cli.ContainerStart(ctx, containerID, container.StartOptions{}) } // StopContainer 停止容器 @@ -531,7 +528,7 @@ func StopContainer(ctx context.Context, containerID string, timeout *time.Durati // RemoveContainer 删除容器 func RemoveContainer(ctx context.Context, containerID string, force bool) error { cli := GetClient() - return cli.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{ + return cli.ContainerRemove(ctx, containerID, container.RemoveOptions{ Force: force, }) } @@ -548,7 +545,7 @@ func ListContainers(ctx context.Context, filterArgs map[string][]string) ([]Cont } } - containers, err := cli.ContainerList(ctx, types.ContainerListOptions{ + containers, err := cli.ContainerList(ctx, container.ListOptions{ All: true, // 包括未运行的容器 Filters: filterSet, }) @@ -575,7 +572,7 @@ func ListContainers(ctx context.Context, filterArgs map[string][]string) ([]Cont func GetContainerLogs(ctx context.Context, containerID string, tail string) (string, error) { cli := GetClient() - options := types.ContainerLogsOptions{ + options := container.LogsOptions{ ShowStdout: true, ShowStderr: true, Tail: tail, diff --git a/internal/docker/exec.go b/internal/docker/exec.go deleted file mode 100644 index da07bd8..0000000 --- a/internal/docker/exec.go +++ /dev/null @@ -1,97 +0,0 @@ -package docker - -import ( - "bufio" - "context" - "io" - - "github.com/docker/docker/api/types" -) - -// ExecConfig 命令执行配置 -type ExecConfig struct { - Cmd []string - AttachStdout bool - AttachStderr bool - Detach bool - Tty bool -} - -// DefaultExecConfig 返回默认的命令执行配置 -func DefaultExecConfig(cmd []string) ExecConfig { - return ExecConfig{ - Cmd: cmd, - AttachStdout: true, - AttachStderr: true, - Detach: false, - Tty: false, - } -} - -// ExecuteCommand 在容器中执行命令并返回执行ID -func ExecuteCommand(ctx context.Context, containerID string, config ExecConfig) (string, error) { - cli := GetClient() - - execConfig := types.ExecConfig{ - Cmd: config.Cmd, - AttachStdout: config.AttachStdout, - AttachStderr: config.AttachStderr, - Detach: config.Detach, - Tty: config.Tty, - } - - execResp, err := cli.ContainerExecCreate(ctx, containerID, execConfig) - if err != nil { - return "", err - } - - return execResp.ID, nil -} - -// StartExecCommand 启动已创建的命令执行 -func StartExecCommand(ctx context.Context, execID string) (*bufio.Reader, error) { - cli := GetClient() - - resp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{}) - if err != nil { - return nil, err - } - - return resp.Reader, nil -} - -// GetExecOutput 获取执行命令的输出 -func GetExecOutput(ctx context.Context, execID string) (string, error) { - cli := GetClient() - - // 附加到命令执行 - resp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{}) - if err != nil { - return "", err - } - defer resp.Close() - - // 读取输出 - output, err := io.ReadAll(resp.Reader) - if err != nil { - return "", err - } - - return string(output), nil -} - -// GetExecStatus 获取命令执行状态 -func GetExecStatus(ctx context.Context, execID string) (types.ContainerExecInspect, error) { - cli := GetClient() - return cli.ContainerExecInspect(ctx, execID) -} - -// ExecuteCommandWithOutput 执行命令并直接返回输出 -func ExecuteCommandWithOutput(ctx context.Context, containerID string, cmd []string) (string, error) { - execID, err := ExecuteCommand(ctx, containerID, DefaultExecConfig(cmd)) - if err != nil { - return "", err - } - - return GetExecOutput(ctx, execID) -} diff --git a/internal/docker/monitor.go b/internal/docker/monitor.go index 0b6a506..db82577 100644 --- a/internal/docker/monitor.go +++ b/internal/docker/monitor.go @@ -2,13 +2,10 @@ package docker import ( "context" - "log" "sync" "time" "gorm.io/gorm" - - "gitee.ltd/lxh/wechat-robot/internal/model" ) // ContainerMonitor 容器监控器 @@ -37,34 +34,34 @@ func NewContainerMonitor(db *gorm.DB, interval time.Duration) *ContainerMonitor // 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 - } - } - }() + //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 停止监控 @@ -93,50 +90,50 @@ func (m *ContainerMonitor) RemoveRobot(containerID string) { } // 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) - } - } - } -} +//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) +// } +// } +// } +//} diff --git a/internal/docker/robot.go b/internal/docker/robot.go index 91487fe..649f000 100644 --- a/internal/docker/robot.go +++ b/internal/docker/robot.go @@ -2,15 +2,8 @@ package docker import ( "context" - "fmt" - "log" - "strings" - "time" - - "github.com/go-resty/resty/v2" - "gitee.ltd/lxh/wechat-robot/internal/config" - "gitee.ltd/lxh/wechat-robot/internal/model" + "log" ) const ( @@ -20,13 +13,6 @@ const ( WechatBotLabelValue = "wechat-bot" ) -// 定义一个HTTP客户端生成函数,用于向容器内的API发出请求 -func newHTTPClient() *resty.Client { - return resty.New(). - SetTimeout(30*time.Second). - SetHeader("Content-Type", "application/json") -} - // CreateRobotContainer 创建微信机器人容器 func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotName string, port int) (string, string, error) { // 创建容器标签 @@ -63,377 +49,3 @@ func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotNa return containerID, containerHost, nil } - -// GetLoginQRCode 获取登录二维码(旧方法) -func GetLoginQRCode(ctx context.Context, containerID string) (string, error) { - return ExecuteCommandWithOutput(ctx, containerID, []string{"cat", "/data/qrcode.png"}) -} - -// GetQRCode 获取登录二维码 -func GetQRCode(ctx context.Context, containerHost string) (*BaseResponse[QRCodeResponse], error) { - client := newHTTPClient() - - url := fmt.Sprintf("http://%s/GetQRCode", containerHost) - - var response BaseResponse[QRCodeResponse] - resp, err := client.R(). - SetContext(ctx). - SetBody("{}"). - SetResult(&response). - Post(url) - - if err != nil { - return nil, fmt.Errorf("获取二维码请求失败: %w", err) - } - - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("获取二维码API返回错误状态码: %d", resp.StatusCode()) - } - - if !response.Success { - return nil, fmt.Errorf("获取二维码API错误: %s", response.Message) - } - - return &response, nil -} - -// CheckUuid 检查二维码状态 -func CheckUuid(ctx context.Context, uuid string, containerHost string) (*BaseResponse[CheckUuidResponse], error) { - client := newHTTPClient() - - url := fmt.Sprintf("http://%s/CheckUuid", containerHost) - reqBody := map[string]string{"Uuid": uuid} - - var response BaseResponse[CheckUuidResponse] - resp, err := client.R(). - SetContext(ctx). - SetBody(reqBody). - SetResult(&response). - Post(url) - - if err != nil { - return nil, fmt.Errorf("检查二维码状态请求失败: %w", err) - } - - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("检查二维码状态API返回错误状态码: %d", resp.StatusCode()) - } - - return &response, nil -} - -// AwakenLogin 唤醒登录 -func AwakenLogin(ctx context.Context, wxid string, containerHost string) (*BaseResponse[AwakenLoginResponse], error) { - client := newHTTPClient() - - url := fmt.Sprintf("http://%s/AwakenLogin", containerHost) - reqBody := map[string]string{"Wxid": wxid} - - var response BaseResponse[AwakenLoginResponse] - resp, err := client.R(). - SetContext(ctx). - SetBody(reqBody). - SetResult(&response). - Post(url) - - if err != nil { - return nil, fmt.Errorf("唤醒登录请求失败: %w", err) - } - - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("唤醒登录API返回错误状态码: %d", resp.StatusCode()) - } - - return &response, nil -} - -// AutoHeartbeatStart 开启自动心跳 -func AutoHeartbeatStart(ctx context.Context, wxid string, containerHost string) (*BaseResponse[AutoHeartbeatResponse], error) { - client := newHTTPClient() - - url := fmt.Sprintf("http://%s/AutoHeartbeatStart", containerHost) - reqBody := map[string]string{"Wxid": wxid} - - var response BaseResponse[AutoHeartbeatResponse] - resp, err := client.R(). - SetContext(ctx). - SetBody(reqBody). - SetResult(&response). - Post(url) - - if err != nil { - return nil, fmt.Errorf("启动自动心跳请求失败: %w", err) - } - - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("启动自动心跳API返回错误状态码: %d", resp.StatusCode()) - } - - if !response.Success { - return nil, fmt.Errorf("启动自动心跳API错误: %s", response.Message) - } - - return &response, nil -} - -// AutoHeartbeatStatus 获取自动心跳状态 -func AutoHeartbeatStatus(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatStatusResponse, error) { - client := newHTTPClient() - - url := fmt.Sprintf("http://%s/AutoHeartbeatStatus", containerHost) - reqBody := map[string]string{"Wxid": wxid} - - var response AutoHeartbeatStatusResponse - resp, err := client.R(). - SetContext(ctx). - SetBody(reqBody). - SetResult(&response). - Post(url) - - if err != nil { - return nil, fmt.Errorf("获取自动心跳状态请求失败: %w", err) - } - - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("获取自动心跳状态API返回错误状态码: %d", resp.StatusCode()) - } - - return &response, nil -} - -// AutoHeartbeatStop 停止自动心跳 -func AutoHeartbeatStop(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) { - client := newHTTPClient() - - url := fmt.Sprintf("http://%s/AutoHeartbeatStop", containerHost) - reqBody := map[string]string{"Wxid": wxid} - - var response AutoHeartbeatResponse - resp, err := client.R(). - SetContext(ctx). - SetBody(reqBody). - SetResult(&response). - Post(url) - - if err != nil { - return nil, fmt.Errorf("停止自动心跳请求失败: %w", err) - } - - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("停止自动心跳API返回错误状态码: %d", resp.StatusCode()) - } - - if !response.Success { - return nil, fmt.Errorf("停止自动心跳API错误: %s", response.Message) - } - - return &response, nil -} - -// LogOut 登出微信 -func LogOut(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) { - client := newHTTPClient() - - url := fmt.Sprintf("http://%s/LogOut", containerHost) - reqBody := map[string]string{"Wxid": wxid} - - var response AutoHeartbeatResponse - resp, err := client.R(). - SetContext(ctx). - SetBody(reqBody). - SetResult(&response). - Post(url) - - if err != nil { - return nil, fmt.Errorf("退出登录请求失败: %w", err) - } - - if resp.StatusCode() != 200 { - return nil, fmt.Errorf("退出登录API返回错误状态码: %d", resp.StatusCode()) - } - - if !response.Success { - return nil, fmt.Errorf("退出登录API错误: %s", response.Message) - } - - return &response, nil -} - -// GetWechatBotStatus 获取微信机器人状态(使用HTTP请求替代) -func GetWechatBotStatus(ctx context.Context, containerID string) (model.RobotStatus, string, error) { - // 检查容器状态 - status, err := GetContainerStatus(ctx, containerID) - if err != nil { - return model.RobotStatusError, "", fmt.Errorf("failed to get container status: %w", err) - } - - if status != "running" { - return model.RobotStatusOffline, "", nil - } - - // 获取容器IP(简化处理,默认使用localhost) - hostIP := "localhost" - - client := newHTTPClient() - url := fmt.Sprintf("http://%s:3000/api/status", hostIP) - - var response BaseResponse[any] - resp, err := client.R(). - SetContext(ctx). - SetResult(&response). - Get(url) - - if err != nil { - return model.RobotStatusError, "", fmt.Errorf("failed to get status result: %w", err) - } - - if resp.StatusCode() != 200 { - return model.RobotStatusError, "", fmt.Errorf("status API returned non-200 status code: %d", resp.StatusCode()) - } - - if !response.Success { - return model.RobotStatusError, response.Message, nil - } - - // 解析状态数据 - data, ok := response.Data.(map[string]interface{}) - if !ok { - return model.RobotStatusError, "invalid status data format", nil - } - - isLoggedIn, ok := data["isLoggedIn"].(bool) - if !ok { - return model.RobotStatusError, "invalid login status format", nil - } - - if isLoggedIn { - return model.RobotStatusOnline, "", nil - } - - return model.RobotStatusOffline, "", nil -} - -// ListWechatBots 列出所有微信机器人容器 -func ListWechatBots(ctx context.Context) ([]ContainerInfo, error) { - // 过滤条件:所有微信机器人容器 - filter := map[string][]string{ - "label": {fmt.Sprintf("%s=%s", WechatBotLabelKey, WechatBotLabelValue)}, - } - - return ListContainers(ctx, filter) -} - -// GetWechatContacts 获取微信联系人列表 -func GetWechatContacts(ctx context.Context, containerHost string) (string, error) { - client := newHTTPClient() - url := fmt.Sprintf("http://%s/api/contacts", containerHost) - - resp, err := client.R(). - SetContext(ctx). - Get(url) - - if err != nil { - return "", fmt.Errorf("获取联系人列表请求失败: %w", err) - } - - return resp.String(), nil -} - -// GetWechatGroupMembers 获取微信群成员 -func GetWechatGroupMembers(ctx context.Context, groupID string, containerHost string) (string, error) { - client := newHTTPClient() - url := fmt.Sprintf("http://%s/api/group/%s/members", containerHost, groupID) - - resp, err := client.R(). - SetContext(ctx). - Get(url) - - if err != nil { - return "", fmt.Errorf("获取群成员请求失败: %w", err) - } - - return resp.String(), nil -} - -// GetWechatMessages 获取微信消息历史 -func GetWechatMessages(ctx context.Context, containerHost, robotWxId string) (msg []Message, isOffline bool, err error) { - client := newHTTPClient() - url := fmt.Sprintf("http://%s/Sync", containerHost) - - var response BaseResponse[Sync] - _, err = client.R(). - SetContext(ctx). - SetBody(map[string]any{ - "Scene": 0, - "Synckey": "", - "Wxid": robotWxId, - }). - SetResult(&response). - Post(url) - - if err != nil { - err = fmt.Errorf("获取消息历史请求失败: %w", err) - return - } - - // 判断是否离线 - if strings.Contains(response.Message, "用户可能退出") || strings.Contains(response.Message, "数据[DecryptData]失败") { - isOffline = true - } - - msg = response.Data.AddMsgs - return -} - -// GetContactList -// @description: 获取联系人微信Id -// @param ctx -// @param containerHost -// @param robotWxId -// @return list -// @return err -func GetContactList(ctx context.Context, containerHost, robotWxId string) (list []string, err error) { - client := newHTTPClient() - url := fmt.Sprintf("http://%s/GetContractList", containerHost) - - var response BaseResponse[ContactListResponse] - _, err = client.R(). - SetContext(ctx). - SetBody(map[string]any{ - "CurrentChatroomContactSeq": 0, - "CurrentWxcontactSeq": 0, - "Wxid": robotWxId, - }). - SetResult(&response). - Post(url) - - list = response.Data.ContactUsernameList - return -} - -// GetContactDetail -// @description: 获取联系人详细信息 -// @param ctx -// @param containerHost -// @param robotWxId -// @param wxId -// @return info -// @return err -func GetContactDetail(ctx context.Context, containerHost, robotWxId string, wxId []string) (info []ContactDetailInfoItem, err error) { - client := newHTTPClient() - url := fmt.Sprintf("http://%s/GetContact", containerHost) - - var response BaseResponse[ContactDetailInfoResponse] - _, err = client.R(). - SetContext(ctx). - SetBody(map[string]any{ - "Chatroom": "", - "RequestWxids": strings.Join(wxId, ","), - "Wxid": robotWxId, - }). - SetResult(&response). - Post(url) - - info = response.Data.ContactList - return -} diff --git a/internal/docker/robot_model.go b/internal/docker/robot_model.go deleted file mode 100644 index d887530..0000000 --- a/internal/docker/robot_model.go +++ /dev/null @@ -1,218 +0,0 @@ -package docker - -import "gitee.ltd/lxh/wechat-robot/internal/types" - -// BaseResponse API响应结构 -type BaseResponse[T any] struct { - Success bool `json:"Success"` - Code int `json:"Code"` - Message string `json:"Message"` - Data T `json:"Data"` -} - -// QRCodeResponse 获取二维码响应 -type QRCodeResponse struct { - UUID string `json:"Uuid"` - ExpiredTime string `json:"ExpiredTime"` - QRCodeBase64 string `json:"QRCodeBase64"` - QRCodeURL string `json:"QRCodeURL"` -} - -// CheckUuidResponse 检查二维码状态响应 -type CheckUuidResponse struct { - Uuid string `json:"uuid"` - Status int `json:"status"` // 状态 - PushLoginUrlExpiredTime int `json:"pushLoginUrlexpiredTime"` // 推送登录url过期时间 - ExpiredTime int `json:"expiredTime"` // 过期时间(秒) - HeadImgUrl string `json:"headImgUrl"` // 头像 - NickName string `json:"nickName"` // 昵称 - AcctSectResp map[string]any `json:"acctSectResp"` // 账号信息-登录成功之后才有 -} - -// AwakenLoginResponse 唤醒登录响应 -type AwakenLoginResponse struct { - QrCodeResponse struct { - BaseResponse any `json:"BaseResponse"` - BlueToothBroadCastContent any `json:"BlueToothBroadCastContent"` - BlueToothBroadCastUuid string `json:"BlueToothBroadCastUuid"` - CheckTime int `json:"CheckTime"` - ExpiredTime int `json:"ExpiredTime"` - NotifyKey any `json:"NotifyKey"` - Uuid string `json:"Uuid"` - } `json:"QrCodeResponse"` -} - -// AutoHeartbeatResponse 心跳响应 -type AutoHeartbeatResponse struct { - Success bool `json:"success"` - Message string `json:"message"` - Data struct{} -} - -// AutoHeartbeatStatusResponse 心跳状态响应 -type AutoHeartbeatStatusResponse struct { - Success bool `json:"success"` - Message string `json:"message"` - Running bool `json:"running"` -} - -// Sync -// @description: 同步微信消息返回值 -type Sync struct { - ModUserInfos any `json:"ModUserInfos"` - ModContacts any `json:"ModContacts"` - DelContacts any `json:"DelContacts"` - ModUserImgs any `json:"ModUserImgs"` - FunctionSwitchs any `json:"FunctionSwitchs"` - UserInfoExts any `json:"UserInfoExts"` - AddMsgs []Message `json:"AddMsgs"` - ContinueFlag int `json:"ContinueFlag"` - KeyBuf struct { - ILen int `json:"iLen"` - Buffer string `json:"buffer"` - } `json:"KeyBuf"` - Status int `json:"Status"` - Continue int `json:"Continue"` - Time int `json:"Time"` - UnknownCmdId string `json:"UnknownCmdId"` - Remarks string `json:"Remarks"` -} - -// Message -// @description: 微信消息 -type Message struct { - MsgId int `json:"MsgId"` - FromUserName struct { - String string `json:"string"` - } `json:"FromUserName"` - ToWxid struct { - String string `json:"string"` - } `json:"ToWxid"` - MsgType types.MessageType `json:"MsgType"` - Content struct { - String string `json:"string"` - } `json:"Content"` - Status int `json:"Status"` - ImgStatus int `json:"ImgStatus"` - ImgBuf struct { - ILen int `json:"iLen"` - } `json:"ImgBuf"` - CreateTime int `json:"CreateTime"` - MsgSource string `json:"MsgSource"` - NewMsgId int64 `json:"NewMsgId"` - MsgSeq int `json:"MsgSeq"` - PushContent string `json:"PushContent,omitempty"` -} - -// =================================================================== - -type ContactListResponse struct { - BaseResponse struct { - Ret int `json:"ret"` - ErrMsg struct { - String string `json:"string"` - } `json:"errMsg"` - } `json:"BaseResponse"` - CurrentWxcontactSeq int `json:"CurrentWxcontactSeq"` - CurrentChatRoomContactSeq int `json:"CurrentChatRoomContactSeq"` - CountinueFlag int `json:"CountinueFlag"` - ContactUsernameList []string `json:"ContactUsernameList"` // 联系人微信Id列表 -} - -// ContactDetailInfo -// @description: 联系人详情 -type ContactDetailInfoResponse struct { - BaseResponse struct { - Ret int `json:"ret"` - ErrMsg struct { - } `json:"errMsg"` - } `json:"BaseResponse"` - ContactCount int `json:"ContactCount"` - ContactList []ContactDetailInfoItem `json:"ContactList"` - Ret []int `json:"Ret"` - Ticket []struct { - } `json:"Ticket"` -} - -// ContactDetailInfoItem -// @description: 详情 -type ContactDetailInfoItem struct { - UserName struct { - String string `json:"string"` - } `json:"UserName"` // 微信Id - NickName struct { - String string `json:"string"` - } `json:"NickName"` // 昵称 - Pyinitial struct { - String string `json:"string"` - } `json:"Pyinitial"` // 昵称拼音首字母大写 - QuanPin struct { - String string `json:"string"` - } `json:"QuanPin"` // 昵称拼音全拼小写 - Sex int `json:"Sex"` // 性别 0:未知 1:男 2:女 - ImgBuf struct { - ILen int `json:"iLen"` - } `json:"ImgBuf"` - BitMask int64 `json:"BitMask"` - BitVal int `json:"BitVal"` - ImgFlag int `json:"ImgFlag"` - Remark struct { - } `json:"Remark"` - RemarkPyinitial struct { - } `json:"RemarkPyinitial"` - RemarkQuanPin struct { - } `json:"RemarkQuanPin"` - ContactType int `json:"ContactType"` - RoomInfoCount int `json:"RoomInfoCount"` - DomainList struct { - } `json:"DomainList"` - ChatRoomNotify int `json:"ChatRoomNotify"` - AddContactScene int `json:"AddContactScene"` - Province string `json:"Province"` // 省份 - City string `json:"City"` // 城市 - Signature string `json:"Signature"` // 个性签名 - PersonalCard int `json:"PersonalCard"` - HasWeiXinHdHeadImg int `json:"HasWeiXinHdHeadImg"` - VerifyFlag int `json:"VerifyFlag"` - Level int `json:"Level"` - Source int `json:"Source"` - Alias string `json:"Alias"` // 微信号 - WeiboFlag int `json:"WeiboFlag"` - AlbumStyle int `json:"AlbumStyle"` - AlbumFlag int `json:"AlbumFlag"` - SnsUserInfo struct { - SnsFlag int `json:"SnsFlag"` - SnsBgimgId string `json:"SnsBgimgId"` // 朋友圈背景图 - SnsBgobjectId float64 `json:"SnsBgobjectId"` - SnsFlagEx int `json:"SnsFlagEx"` - } `json:"SnsUserInfo"` - Country string `json:"Country"` // 国家 - BigHeadImgUrl string `json:"BigHeadImgUrl"` // 大头像地址 - SmallHeadImgUrl string `json:"SmallHeadImgUrl"` // 小头像地址 - MyBrandList string `json:"MyBrandList"` - CustomizedInfo struct { - BrandFlag int `json:"BrandFlag"` - } `json:"CustomizedInfo"` - HeadImgMd5 string `json:"HeadImgMd5"` - EncryptUserName string `json:"EncryptUserName"` - AdditionalContactList struct { - LinkedinContactItem struct { - } `json:"LinkedinContactItem"` - } `json:"AdditionalContactList"` - ChatroomVersion int `json:"ChatroomVersion"` - ChatroomMaxCount int `json:"ChatroomMaxCount"` - ChatroomAccessType int `json:"ChatroomAccessType"` - NewChatroomData struct { - MemberCount int `json:"MemberCount"` - InfoMask int `json:"InfoMask"` - } `json:"NewChatroomData"` - DeleteFlag int `json:"DeleteFlag"` - LabelIdlist string `json:"LabelIdlist"` - PhoneNumListInfo struct { - Count int `json:"Count"` - } `json:"PhoneNumListInfo"` - ChatroomInfoVersion int `json:"ChatroomInfoVersion"` - DeleteContactScene int `json:"DeleteContactScene"` - ChatroomStatus int `json:"ChatroomStatus"` - ExtFlag int `json:"ExtFlag"` -} diff --git a/internal/docker/stats.go b/internal/docker/stats.go index b1dd691..453eacd 100644 --- a/internal/docker/stats.go +++ b/internal/docker/stats.go @@ -4,9 +4,8 @@ import ( "context" "encoding/json" "fmt" + "github.com/docker/docker/api/types/container" "time" - - "github.com/docker/docker/api/types" ) // ContainerStats 容器统计数据结构 @@ -78,7 +77,7 @@ func GetStats(ctx context.Context, containerID string) (*ContainerStats, error) defer stats.Body.Close() // 解析统计数据JSON - var statsJSON types.StatsJSON + var statsJSON container.StatsResponse if err := json.NewDecoder(stats.Body).Decode(&statsJSON); err != nil { return nil, fmt.Errorf("解析容器统计数据失败: %w", err) } @@ -111,7 +110,7 @@ func GetStats(ctx context.Context, containerID string) (*ContainerStats, error) } // 计算CPU使用百分比 -func calculateCPUPercentage(stats *types.StatsJSON) float64 { +func calculateCPUPercentage(stats *container.StatsResponse) float64 { if stats == nil { return 0.0 } @@ -133,7 +132,7 @@ func calculateCPUPercentage(stats *types.StatsJSON) float64 { } // 获取内存使用量 -func getMemoryUsage(stats *types.StatsJSON) int64 { +func getMemoryUsage(stats *container.StatsResponse) int64 { if stats == nil || stats.MemoryStats.Usage == 0 { return 0 } @@ -147,7 +146,7 @@ func getMemoryUsage(stats *types.StatsJSON) int64 { } // 获取内存限制 -func getMemoryLimit(stats *types.StatsJSON) int64 { +func getMemoryLimit(stats *container.StatsResponse) int64 { if stats == nil || stats.MemoryStats.Limit == 0 { return 0 } @@ -155,7 +154,7 @@ func getMemoryLimit(stats *types.StatsJSON) int64 { } // 计算内存使用百分比 -func calculateMemoryPercentage(stats *types.StatsJSON) float64 { +func calculateMemoryPercentage(stats *container.StatsResponse) float64 { if stats == nil { return 0.0 } @@ -177,7 +176,7 @@ type networkStats struct { } // 获取网络统计数据 -func getNetworkStats(stats *types.StatsJSON) networkStats { +func getNetworkStats(stats *container.StatsResponse) networkStats { if stats == nil || len(stats.Networks) == 0 { return networkStats{} } @@ -198,7 +197,7 @@ type ioStats struct { } // 获取IO统计数据 -func getIOStats(stats *types.StatsJSON) ioStats { +func getIOStats(stats *container.StatsResponse) ioStats { if stats == nil { return ioStats{} } diff --git a/internal/handler/api_login.go b/internal/handler/api_login.go index 36d31dc..579a0bd 100644 --- a/internal/handler/api_login.go +++ b/internal/handler/api_login.go @@ -1,11 +1,11 @@ package handler import ( - "context" "encoding/json" "errors" "gitee.ltd/lxh/wechat-robot/internal/config" "gitee.ltd/lxh/wechat-robot/internal/tasks" + "gitee.ltd/lxh/xybot" "github.com/gofiber/fiber/v2/log" "strconv" "time" @@ -13,7 +13,6 @@ import ( "github.com/gofiber/fiber/v2" "gorm.io/gorm" - "gitee.ltd/lxh/wechat-robot/internal/docker" "gitee.ltd/lxh/wechat-robot/internal/model" ) @@ -51,12 +50,15 @@ func CheckQRCodeStatus(c *fiber.Ctx) error { }) } - // 调用CheckUuid API检查二维码状态 - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - // 调用CheckUuid API检查二维码状态,传递容器访问地址 - response, err := docker.CheckUuid(ctx, uuid, robot.ContainerHost) + robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false) + if err != nil { + return c.JSON(fiber.Map{ + "success": false, + "message": "创建微信客户端失败: " + err.Error(), + }) + } + response, err := robotCli.Login.CheckUuid(uuid) if err != nil { return c.JSON(fiber.Map{ "success": false, @@ -70,20 +72,20 @@ func CheckQRCodeStatus(c *fiber.Ctx) error { } // 如果返回status=1,表示已扫码,暂存一下昵称和头像 - if response.Data.Status == 1 { - robot.Nickname = response.Data.NickName - robot.Avatar = response.Data.HeadImgUrl + if response.Status == 1 { + robot.Nickname = response.NickName + robot.Avatar = response.HeadImgUrl db.Save(&robot) } // 如果检测到已登录,更新机器人状态 - if response.Success && response.Data.AcctSectResp != nil { - response.Data.Status = 99 - robot.WechatID = response.Data.AcctSectResp["userName"].(string) + if response.AcctSectResp.Username != "" { + response.Status = 99 + robot.WechatID = response.AcctSectResp.Username // 开启自动心跳,传递容器访问地址 if robot.WechatID != "" { - _, _ = docker.AutoHeartbeatStart(ctx, robot.WechatID, robot.ContainerHost) + _ = robotCli.Login.AutoHeartbeatStart() } // 更新机器人状态 @@ -97,9 +99,9 @@ func CheckQRCodeStatus(c *fiber.Ctx) error { } return c.JSON(fiber.Map{ - "success": response.Success, - "status": response.Data.Status, - "message": response.Message, - "userInfo": response.Data.AcctSectResp, + "success": true, + "status": response.Status, + "message": "success", + "userInfo": response.AcctSectResp, }) } diff --git a/internal/handler/health.go b/internal/handler/health.go index 1947b72..f2f55fc 100644 --- a/internal/handler/health.go +++ b/internal/handler/health.go @@ -22,12 +22,12 @@ func HealthCheck(c *fiber.Ctx) error { "status": "ok", "message": "service is healthy", "timestamp": time.Now().Format(time.RFC3339), - "database": map[string]interface{}{ + "database": map[string]any{ "connected": dbErr == nil, }, - "system": map[string]interface{}{ + "system": map[string]any{ "goroutines": runtime.NumGoroutine(), - "memory": map[string]interface{}{ + "memory": map[string]any{ "alloc": m.Alloc / 1024 / 1024, "total_alloc": m.TotalAlloc / 1024 / 1024, "sys": m.Sys / 1024 / 1024, diff --git a/internal/handler/robot.go b/internal/handler/robot.go index 55a107c..4f18c99 100644 --- a/internal/handler/robot.go +++ b/internal/handler/robot.go @@ -3,6 +3,7 @@ package handler import ( "context" "errors" + "gitee.ltd/lxh/xybot" "log" "strconv" "strings" @@ -168,8 +169,13 @@ func DeleteRobot(c *fiber.Ctx) error { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() + robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false) + if err != nil { + log.Printf("创建微信客户端失败: %v", err) + } + if robot.Status == model.RobotStatusOnline { - if _, err = docker.LogOut(ctx, robot.WechatID, robot.ContainerHost); err != nil { + if err = robotCli.Login.Logout(); err != nil { log.Printf("登出机器人失败: %v", err) // 继续删除流程,不因登出失败而中断 } @@ -242,13 +248,20 @@ func RobotLogin(c *fiber.Ctx) error { // 检查是否指定使用二维码登录 forceQrcode := c.Query("qrcode") == "1" + // 创建微信客户端 + robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false) + if err != nil { + return c.Render("robot/login", fiber.Map{ + "Title": "微信登录", + "Robot": robot, + "Message": "创建微信客户端失败: " + err.Error(), + "IsError": true, + }) + } + // 如果存在WechatID且没有强制要求使用二维码,则使用唤醒登录 if robot.WechatID != "" && !forceQrcode { - // 尝试唤醒登录 - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - awakenResp, err := docker.AwakenLogin(ctx, robot.WechatID, robot.ContainerHost) + awakenResp, err := robotCli.Login.AwakenLogin() if err != nil { return c.Render("robot/login", fiber.Map{ "Title": "微信登录", @@ -262,18 +275,14 @@ func RobotLogin(c *fiber.Ctx) error { return c.Render("robot/login", fiber.Map{ "Title": "微信登录", "Robot": robot, - "UUID": awakenResp.Data.QrCodeResponse.Uuid, - "Expired": awakenResp.Data.QrCodeResponse.ExpiredTime, + "UUID": awakenResp.QrCodeResponse.Uuid, + "Expired": awakenResp.QrCodeResponse.ExpiredTime, "IsAwaken": true, }) } - // 获取登录二维码 - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - // 使用新的GetQRCode接口获取二维码,并传递容器访问地址 - qrcodeResp, err := docker.GetQRCode(ctx, robot.ContainerHost) + qrcodeResp, err := robotCli.Login.GetQRCode(robot.DeviceId, robot.DeviceName) if err != nil { return c.Render("robot/login", fiber.Map{ "Title": "微信登录", @@ -287,9 +296,9 @@ func RobotLogin(c *fiber.Ctx) error { return c.Render("robot/login", fiber.Map{ "Title": "微信登录", "Robot": robot, - "QRCode": qrcodeResp.Data.QRCodeURL, - "UUID": qrcodeResp.Data.UUID, - "Expired": qrcodeResp.Data.ExpiredTime, + "QRCode": qrcodeResp.QRCodeURL, + "UUID": qrcodeResp.Uuid, + "Expired": qrcodeResp.ExpiredTime, "IsAwaken": false, }) } @@ -311,13 +320,14 @@ func RobotLogout(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败") } - // 登出微信 - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // 使用新的LogOut API接口,传递容器访问地址 + // 创建微信客户端 + robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "创建微信客户端失败: "+err.Error()) + } + // 使用新的Logout API接口,传递容器访问地址 if robot.WechatID != "" { - if _, err = docker.LogOut(ctx, robot.WechatID, robot.ContainerHost); err != nil { + if err = robotCli.Login.Logout(); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error()) } } diff --git a/internal/server/server.go b/internal/server/server.go index 07b855c..e1cc7af 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -45,12 +45,12 @@ func New(cfg *config.Config) *Server { }) // 添加map函数,用于在模板中创建映射 - engine.AddFunc("map", func(values ...interface{}) map[string]interface{} { + engine.AddFunc("map", func(values ...any) map[string]any { if len(values)%2 != 0 { return nil } - m := make(map[string]interface{}, len(values)/2) + m := make(map[string]any, len(values)/2) for i := 0; i < len(values); i += 2 { if key, ok := values[i].(string); ok { m[key] = values[i+1] diff --git a/internal/tasks/contract.go b/internal/tasks/contract.go index 1f03c2b..3eb2337 100644 --- a/internal/tasks/contract.go +++ b/internal/tasks/contract.go @@ -1,13 +1,10 @@ package tasks import ( - "context" - "gitee.ltd/lxh/wechat-robot/internal/docker" "gitee.ltd/lxh/wechat-robot/internal/model" + "gitee.ltd/lxh/xybot" "github.com/gofiber/fiber/v2/log" - "slices" "strings" - "time" ) // syncContact @@ -16,27 +13,21 @@ import ( // @param robotWxId 机器人微信号 // @param robotId 机器人ID func syncContact(containerHost, robotWxId string, robotId uint) { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() + robotCli, err := xybot.NewClient(robotWxId, containerHost, false) + if err != nil { + log.Errorf("创建微信客户端失败: %v", err) + } // 先获取全部id - ids, err := docker.GetContactList(ctx, containerHost, robotWxId) + ids, err := robotCli.Friend.GetContractList(true) if err != nil { // 处理错误 log.Errorf("[%s]获取联系人列表失败: %v", robotWxId, err) return } - // 过滤掉特殊微信Id - var specialId = []string{"filehelper", "newsapp", "fmessage", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", - "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", - "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "officialaccounts", - "notification_messages", "wxitil", "userexperience_alarm", "notification_messages", "exmail_tool", "mphelper"} - ids = slices.DeleteFunc(ids, func(id string) bool { - return slices.Contains(specialId, id) || strings.HasPrefix(id, "gh_") || strings.TrimSpace(id) == "" - }) // 获取昵称等详细信息 - contacts, err := docker.GetContactDetail(ctx, containerHost, robotWxId, ids) + contacts, err := robotCli.Friend.GetContractDetail(ids) if err != nil { // 处理错误 log.Errorf("[%s]获取联系人详情失败: %v", robotWxId, err) @@ -71,7 +62,7 @@ func syncContact(containerHost, robotWxId string, robotId uint) { "province": contact.Province, "city": contact.City, "signature": contact.Signature, - "sns_background": contact.SnsUserInfo.SnsBgimgId, + "sns_background": contact.SnsUserInfo.SnsBgImgId, } if contact.BigHeadImgUrl == "" { pm["avatar"] = contact.SmallHeadImgUrl @@ -100,7 +91,7 @@ func syncContact(containerHost, robotWxId string, robotId uint) { c.Province = contact.Province c.City = contact.City c.Signature = contact.Signature - c.SnsBackground = contact.SnsUserInfo.SnsBgimgId + c.SnsBackground = contact.SnsUserInfo.SnsBgImgId err = db.Create(&c).Error } diff --git a/internal/tasks/sync.go b/internal/tasks/sync.go index f70224a..f3a60ee 100644 --- a/internal/tasks/sync.go +++ b/internal/tasks/sync.go @@ -1,10 +1,9 @@ package tasks import ( - "context" - "gitee.ltd/lxh/wechat-robot/internal/docker" "gitee.ltd/lxh/wechat-robot/internal/model" "gitee.ltd/lxh/wechat-robot/internal/types" + "gitee.ltd/lxh/xybot" "strings" "time" ) @@ -14,21 +13,22 @@ import ( // @param containerHost 机器人接口地址 // @param robotWxId 机器人微信Id // @param robotId 机器人数据Id -func syncMessage(containerHost, robotWxId string, robotId uint) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - messages, isOffline, err := docker.GetWechatMessages(ctx, containerHost, robotWxId) - if err != nil { - // 处理错误 - return - } +func syncMessage(client *xybot.Client, robotId uint) { + // 获取数据库连接 db := model.GetDB() - if isOffline { - // 删除定时任务 - DeleteJob(robotId) - // 修改机器人状态 - db.Model(&model.Robot{}).Where("id = ?", robotId).Update("status", "offline") + + // 获取新消息 + messages, err := client.Message.Sync() + if err != nil { + // 手动处理一下是否离线了 + isOffline := strings.Contains(err.Error(), "用户可能退出") || strings.Contains(err.Error(), "数据[DecryptData]失败") + if isOffline { + // 删除定时任务 + DeleteJob(robotId) + // 修改机器人状态 + db.Model(&model.Robot{}).Where("id = ?", robotId).Update("status", "offline") + } + return } @@ -45,7 +45,7 @@ func syncMessage(containerHost, robotWxId string, robotId uint) { m.MsgId = message.NewMsgId m.CreateTime = message.CreateTime m.CreateAt = time.Unix(int64(message.CreateTime), 0) - m.Type = message.MsgType + m.Type = types.MessageType(message.MsgType) m.Content = message.Content.String m.DisplayFullContent = message.PushContent m.FromUser = message.FromUserName.String @@ -53,7 +53,7 @@ func syncMessage(containerHost, robotWxId string, robotId uint) { // 如果是群聊消息,单独处理一下 // Sys类型的消息正文不包含微信 Id,所以不需要处理 - if strings.HasSuffix(message.FromUserName.String, "@chatroom") && message.MsgType != types.MsgTypeSys { + if strings.HasSuffix(message.FromUserName.String, "@chatroom") && m.Type != types.MsgTypeSys { // 群消息,处理一下消息和发信人 groupUser := strings.Split(m.Content, "\n")[0] groupUser = strings.ReplaceAll(groupUser, ":", "") diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index ae1abbc..bf3be85 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -2,6 +2,7 @@ package tasks import ( "gitee.ltd/lxh/wechat-robot/internal/model" + "gitee.ltd/lxh/xybot" "github.com/go-co-op/gocron/v2" "github.com/google/uuid" "log" @@ -33,10 +34,15 @@ func Start() { } // 遍历机器人,添加任务 for _, robot := range robots { + // 初始化微信客户端 + robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false) + if err != nil { + log.Panicf("初始化微信客户端失败: %v", err) + } var job gocron.Job job, err = scheduler.NewJob( gocron.CronJob("*/5 * * * * *", true), // 五秒钟同步一次 - gocron.NewTask(syncMessage, robot.ContainerHost, robot.WechatID, robot.ID), + gocron.NewTask(syncMessage, robotCli, robot.ID), ) if err != nil { log.Panicf("添加定时任务失败: %v", err) @@ -64,9 +70,15 @@ func Start() { // AddJob // @description: 添加任务 func AddJob(robot model.Robot) { + // 初始化微信客户端 + robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false) + if err != nil { + log.Panicf("初始化微信客户端失败: %v", err) + } + job, err := scheduler.NewJob( gocron.CronJob("*/5 * * * * *", true), // 五秒钟同步一次 - gocron.NewTask(syncMessage, robot.ContainerHost, robot.WechatID, robot.ID), + gocron.NewTask(syncMessage, robotCli, robot.ID), ) if err != nil { log.Printf("添加定时任务失败: %v", err) diff --git a/internal/view/robot/show.html b/internal/view/robot/show.html index 08838fb..3bd201b 100644 --- a/internal/view/robot/show.html +++ b/internal/view/robot/show.html @@ -108,28 +108,33 @@
- 0% + 0%

内存使用率

-
-
+
+
+
+ 0MB/0MB +
+
+
+ 0%
- 0%

网络接收

-

0 KB/s

+

0 KB

网络发送

-

0 KB/s

+

0 KB

@@ -210,12 +215,16 @@ const memoryUsage = data.stats.memory_usage || 0; const memoryLimit = data.stats.memory_limit || 1; const memoryPercent = ((memoryUsage / memoryLimit) * 100).toFixed(2); - document.getElementById('memory-usage').textContent = `${memoryPercent}%`; + const formattedMemoryUsage = formatBytes(memoryUsage); + const formattedMemoryTotal = formatBytes(memoryLimit); + + document.getElementById('memory-usage-display').textContent = `${formattedMemoryUsage}/${formattedMemoryTotal}`; + document.getElementById('memory-percent').textContent = `${memoryPercent}%`; document.getElementById('memory-bar').style.width = `${Math.min(memoryPercent, 100)}%`; // 更新网络信息 - const networkRx = formatBytes(data.stats.network_rx || 0) + '/s'; - const networkTx = formatBytes(data.stats.network_tx || 0) + '/s'; + const networkRx = formatBytes(data.stats.network_rx || 0); + const networkTx = formatBytes(data.stats.network_tx || 0); document.getElementById('network-rx').textContent = networkRx; document.getElementById('network-tx').textContent = networkTx; }