feat 实现基本功能

This commit is contained in:
timerzz 2024-04-10 17:36:56 +08:00
commit 59f1de2cd3
48 changed files with 2624 additions and 0 deletions

21
Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM golang:alpine as back
ARG GOPROXY=https://goproxy.cn,direct
WORKDIR /build
COPY . /build
RUN CGO_ENABLED=0 go build -trimpath -ldflags '-w -s' -o watcher ./main
FROM oven/bun:1 as front
COPY . /build
WORKDIR /build/wwwroot
RUN bun install && bun run build
FROM alpine:latest
ARG HTTP_PROXY=http://192.168.31.55:10809
ARG HTTPS_PROXY=http://192.168.31.55:10809
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /work
COPY --from=back /build/watcher /work/
COPY --from=front /build/wwwroot/dist /work/
ENTRYPOINT ["/work/watcher"]

151
go.mod Normal file
View File

@ -0,0 +1,151 @@
module haitao_watcher
go 1.22.2
require (
github.com/bytedance/sonic v1.9.1
github.com/glebarez/sqlite v1.11.0
github.com/go-resty/resty/v2 v2.12.0
github.com/gofiber/fiber/v3 v3.0.0-beta.2
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/metacubex/mihomo v1.18.3
github.com/pkg/errors v0.9.1
github.com/timerzz/proxypool v0.0.0-20240407102509-f13ba2d1b0ca
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.9
)
require (
github.com/3andne/restls-client-go v0.1.6 // indirect
github.com/PuerkitoBio/goquery v1.8.1 // indirect
github.com/RyuaNerin/go-krypto v1.2.4 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/antchfx/htmlquery v1.3.1 // indirect
github.com/antchfx/xmlquery v1.3.15 // indirect
github.com/antchfx/xpath v1.3.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/cloudflare/circl v1.3.6 // indirect
github.com/coreos/go-iptables v0.7.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.3.2 // indirect
github.com/gocolly/colly/v2 v2.1.0 // indirect
github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hpcloud/tail v1.0.0 // indirect
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c // indirect
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 // indirect
github.com/metacubex/sing-shadowsocks v0.2.6 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 // indirect
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo v1.10.1 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/openacid/low v0.1.21 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/robertkrimen/otto v0.2.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/sing v0.3.6 // indirect
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/utls v1.5.4 // indirect
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/samber/lo v1.39.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.2 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/temoto/robotstxt v1.1.2 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zhangyunhao116/fastrand v0.3.0 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)

504
go.sum Normal file
View File

@ -0,0 +1,504 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A=
github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go=
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0=
github.com/antchfx/htmlquery v1.3.1 h1:wm0LxjLMsZhRHfQKKZscDf2COyH4vDYA3wyH+qZ+Ylc=
github.com/antchfx/htmlquery v1.3.1/go.mod h1:PTj+f1V2zksPlwNt7uVvZPsxpKNa7mlVliCRxLX6Nx8=
github.com/antchfx/xmlquery v1.2.4/go.mod h1:KQQuESaxSlqugE2ZBcM/qn+ebIpt+d+4Xx7YcSGAIrM=
github.com/antchfx/xmlquery v1.3.15 h1:aJConNMi1sMha5G8YJoAIF5P+H+qG1L73bSItWHo8Tw=
github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA=
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xpath v1.1.8/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc=
github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c/go.mod h1:ETASDWf/FmEb6Ysrtd1QhjNedUU/ZQxBCRLh60bQ/UI=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA=
github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA=
github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoGs=
github.com/gocolly/colly/v2 v2.1.0/go.mod h1:I2MuhsLjQ+Ex+IzK3afNS8/1qP3AedHOusRPcRdC5o0=
github.com/gofiber/fiber/v3 v3.0.0-beta.2 h1:mVVgt8PTaHGup3NGl/+7U7nEoZaXJ5OComV4E+HpAao=
github.com/gofiber/fiber/v3 v3.0.0-beta.2/go.mod h1:w7sdfTY0okjZ1oVH6rSOGvuACUIt0By1iK0HKUb3uqM=
github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co=
github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
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/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/tink/go v1.6.1/go.mod h1:IGW53kTgag+st5yPhKKwJ6u2l+SSp5/v9XF7spovjlY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8 h1:V3plQrMHRWOB5zMm3yNqvBxDQVW1+/wHBSok5uPdmVs=
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg=
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.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
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/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/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/mihomo v1.18.3 h1:pIozbxnfHHYzSsF6m9uafcGIscMyHsRj/D3WuTKi2TM=
github.com/metacubex/mihomo v1.18.3/go.mod h1:9CPB84Z1tk2c3W1be7G9tm4kAqCdAkY0SRZkwB2cqaA=
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c h1:AhaPKvVqF3N/jXFmlW51Cf1+KddslKAsZqcdgGhZjr0=
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ=
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 h1:5INHs85Gp1JZsdF7fQp1pXUjfJOX2dhwZjuUQWJVSt8=
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8=
github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ=
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg=
github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A=
github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 h1:as/aO/fM8nv4W4pOr9EETP6kV/Oaujk3fUNyQSJK61c=
github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I=
github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.3.6 h1:dsEdYLKBQlrxUfw1a92x0VdPvR1/BOxQ+HIMyaoEJsQ=
github.com/sagernet/sing v0.3.6/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=
github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/timerzz/proxypool v0.0.0-20240407102509-f13ba2d1b0ca h1:EbyIUW/bB7vZtwgrno1V5nyx38mCE+4DrWurtVMs0fQ=
github.com/timerzz/proxypool v0.0.0-20240407102509-f13ba2d1b0ca/go.mod h1:KVeB0Qlvv/sdR7dbB7ys1EqK4qGKpSnSiIhPhkHukwQ=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
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.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
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/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/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.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.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.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
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=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

50
main/main.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"context"
"github.com/gofiber/fiber/v3"
"github.com/golang/glog"
"haitao_watcher/pkg/database"
"haitao_watcher/pkg/model"
"haitao_watcher/pkg/options"
"haitao_watcher/pkg/pools"
"haitao_watcher/pkg/pusher"
"haitao_watcher/pkg/watcher"
"haitao_watcher/server"
"log/slog"
"os"
"os/signal"
)
func main() {
slog.Info(">>> BEGIN INIT<<<")
ctx := context.Background()
_ctx, cancel := signal.NotifyContext(ctx, os.Kill, os.Interrupt)
defer cancel()
cfg, _ := options.LoadConfig()
db, err := database.InitDatabase(&cfg.DB)
if err != nil {
glog.Fatalf("初始化数据库失败:%v", err)
}
pool := pools.NewProxyPool(cfg.Proxy.Subscribes)
pusherCtl := pusher.NewController(_ctx, db)
ch := make(chan model.PushMsg, 30)
pusherCtl.Consume(ch)
watcherCtl := watcher.NewController(_ctx, db, pool, ch)
r := fiber.New()
r.Static("/", "/work/dist")
api := r.Group("/api/v1")
server.NewWatcherController(watcherCtl).RegistryRouter(api)
server.NewPusherSvcController(pusherCtl).RegistryRouter(api)
if err = r.Listen(":2280"); err != nil {
glog.Warningf("server over: %v", err)
}
}

17
pkg/database/init.go Normal file
View File

@ -0,0 +1,17 @@
package database
import (
"fmt"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"haitao_watcher/pkg/options"
)
func InitDatabase(opt *options.DBOption) (*gorm.DB, error) {
if opt.Timeout == 0 {
opt.Timeout = 3000
}
dialector := sqlite.Open(fmt.Sprintf("%s?_pragma=busy_timeout(%d)", opt.Path, opt.Timeout))
return gorm.Open(dialector, &gorm.Config{})
}

7
pkg/model/msg.go Normal file
View File

@ -0,0 +1,7 @@
package model
type PushMsg struct {
Title string
Content string
ToPusher []uint
}

45
pkg/model/product.go Normal file
View File

@ -0,0 +1,45 @@
package model
import (
"gorm.io/gorm"
"time"
)
type Product struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index"`
UpdateErr bool `json:"updateErr"` //更新出错了
Uid string `json:"uid" gorm:"index:,unique"`
Pid string `json:"pid" gorm:"index"` //产品编号
Name string `json:"name"`
Brand string `json:"brand"`
Website WebsiteType `json:"website"` //是什么网站
Watch bool `json:"watch"`
Orderable bool `json:"orderable"`
Link string `json:"link"`
Remark string `json:"remark,omitempty"`
AllocationResetDate time.Time `json:"allocationResetDate"` //上次补货时间
PusherIds []uint `json:"pusherIds" gorm:"serializer:json"`
}
type WebsiteType uint
func (t WebsiteType) IsZero() bool {
return t == WebsiteType(0)
}
const (
CoachOutlet WebsiteType = iota + 1
)
func (p *Product) TableName() string {
return "product_info"
}
type Producter interface {
Product() *Product
}

28
pkg/model/pusher.go Normal file
View File

@ -0,0 +1,28 @@
package model
import (
"gorm.io/gorm"
"time"
)
type PusherType int
const (
PusherType_AnPusher = iota + 1
)
type Pusher[T any] struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index"`
Type PusherType `json:"type"`
Name string `json:"name"`
Remark string `json:"remark"`
Option T `json:"option" gorm:"serializer:json"`
}
func (p *Pusher[T]) TableName() string {
return "pusher"
}

6
pkg/model/response.go Normal file
View File

@ -0,0 +1,6 @@
package model
type ListResponse[E any] struct {
Total int64 `json:"total"`
List []E `json:"list"`
}

View File

@ -0,0 +1,18 @@
package options
import (
"fmt"
"time"
)
type CoachOutletOption struct {
RawUrl string `json:"raw_url"`
Pid string `json:"pid"` //商品的id
Remark string `json:"remark"`
PusherIds []uint `json:"pusherIds"`
Interval time.Duration `json:"interval"`
}
func (o *CoachOutletOption) Uid() string {
return fmt.Sprintf("coachOutlet_%s", o.Pid)
}

View File

@ -0,0 +1,15 @@
package options
import (
"gopkg.in/yaml.v3"
"os"
"testing"
)
func TestGenConfig(t *testing.T) {
var opt = Config{
DB: DBOption{},
Proxy: ProxyOption{Subscribes: []string{"123", "345"}},
}
yaml.NewEncoder(os.Stdout).Encode(&opt)
}

6
pkg/options/db.go Normal file
View File

@ -0,0 +1,6 @@
package options
type DBOption struct {
Path string `json:"path"`
Timeout int `json:"timeout"`
}

21
pkg/options/init.go Normal file
View File

@ -0,0 +1,21 @@
package options
import (
"gopkg.in/yaml.v3"
"os"
)
type Config struct {
DB DBOption `yaml:"db"`
Proxy ProxyOption `yaml:"proxy"`
}
func LoadConfig() (*Config, error) {
var opt Config
f, err := os.Open("/data/cfg.yaml")
if err != nil {
return nil, err
}
defer f.Close()
return &opt, yaml.NewDecoder(f).Decode(&opt)
}

5
pkg/options/proxy.go Normal file
View File

@ -0,0 +1,5 @@
package options
type ProxyOption struct {
Subscribes []string `yaml:"subscribes"`
}

6
pkg/options/pusher.go Normal file
View File

@ -0,0 +1,6 @@
package options
type AnPushOption struct {
Token string `json:"token"`
Channel string `json:"channel"`
}

72
pkg/pools/proxy.go Normal file
View File

@ -0,0 +1,72 @@
package pools
import (
"context"
"fmt"
"github.com/golang/glog"
"github.com/timerzz/proxypool/pkg/getter"
"github.com/timerzz/proxypool/pkg/proxy"
"github.com/timerzz/proxypool/pkg/tool"
"log/slog"
"math/rand"
"sync"
"time"
)
type ProxyPool struct {
m sync.Mutex
proxys proxy.ProxyList
subscribes []string //订阅url
}
func NewProxyPool(subscribes []string) *ProxyPool {
var p = &ProxyPool{}
p.subscribes = subscribes
p.Update()
return p
}
// Update 更新代理池
func (p *ProxyPool) Update() {
var list = make(proxy.ProxyList, 0, len(p.proxys))
for _, url := range p.subscribes {
subscribeGetter, err := getter.NewSubscribe(tool.Options{"url": url})
if err != nil {
slog.Warn(fmt.Sprintf("创建Subscribe Getter失败%v", err))
continue
}
list = list.UniqAppendProxyList(subscribeGetter.Get())
}
glog.Infof("代理源共 %d 个: %v", len(p.subscribes), p.subscribes)
glog.Infof("获取代理共 %d 个", len(list))
p.m.Lock()
p.proxys = list
p.m.Unlock()
}
// CronUpdate 定时更新
func (p *ProxyPool) CronUpdate(ctx context.Context, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
p.Update()
}
}
}
// RandomIterator 获取随机代理的迭代器
func (p *ProxyPool) RandomIterator() func() proxy.Proxy {
return func() (proxy proxy.Proxy) {
if len(p.proxys) == 0 {
return nil
}
p.m.Lock()
defer p.m.Unlock()
curIndex := rand.Intn(len(p.proxys))
return p.proxys[curIndex]
}
}

67
pkg/pools/resty.go Normal file
View File

@ -0,0 +1,67 @@
package pools
import (
"context"
"github.com/go-resty/resty/v2"
"net"
"net/http"
"sync"
"time"
)
var rp *restyPool
func init() {
rp = NewRestyPool()
}
func GetRestyClient(conn net.Conn) (cli *resty.Client) {
return rp.Get(conn)
}
func PutRestyClient(cli *resty.Client) {
rp.Put(cli)
}
type restyPool struct {
resty sync.Pool
transport sync.Pool
}
const (
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0"
)
func NewRestyPool() *restyPool {
return &restyPool{
resty: sync.Pool{
New: func() any {
return resty.New().SetHeader("User-Agent", UserAgent)
},
},
transport: sync.Pool{
New: func() any {
return &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
}
},
},
}
}
func (r *restyPool) Get(conn net.Conn) (cli *resty.Client) {
cli = r.resty.Get().(*resty.Client)
transport := r.transport.Get().(*http.Transport)
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return conn, nil
}
return cli.SetTransport(transport)
}
func (r *restyPool) Put(cli *resty.Client) {
transport, _ := cli.Transport()
r.transport.Put(transport)
r.resty.Put(cli)
}

34
pkg/pusher/anPush.go Normal file
View File

@ -0,0 +1,34 @@
package pusher
import (
"fmt"
"github.com/go-resty/resty/v2"
"haitao_watcher/pkg/options"
"strings"
)
type AnPush struct {
opt *options.AnPushOption
client *resty.Client
}
func NewAnPush(opt *options.AnPushOption) *AnPush {
return &AnPush{
opt: opt,
client: resty.New().SetHeader("Content-Type", "application/x-www-form-urlencoded"),
}
}
type msgResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
Msgid uint64 `json:"msgid"`
}
func (a *AnPush) Push(title, content string) error {
r := a.client.R().SetBody(strings.NewReader(fmt.Sprintf("title=%s&content=%s&channel=%s", title, content, a.opt.Channel)))
var result msgResponse
r.SetResult(&result)
_, err := r.Post(fmt.Sprintf("https://api.anpush.com/push/%s", a.opt.Token))
return err
}

18
pkg/pusher/anPush_test.go Normal file
View File

@ -0,0 +1,18 @@
package pusher
import (
"haitao_watcher/pkg/options"
"testing"
)
func TestAnPush_Push(t *testing.T) {
var opt = &options.AnPushOption{
Token: "LHS13BO3FGBLGBP9MFBM53R8WV32P1",
Channel: "06683",
}
pusher := NewAnPush(opt)
err := pusher.Push("测试", "这是一个测试")
if err != nil {
t.Fatal(err)
}
}

124
pkg/pusher/controller.go Normal file
View File

@ -0,0 +1,124 @@
package pusher
import (
"context"
"fmt"
"github.com/golang/glog"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"haitao_watcher/pkg/model"
"haitao_watcher/pkg/options"
)
type Pusher interface {
Push(title, content string) error
}
type Controller struct {
m map[uint]Pusher
db *gorm.DB
ctx context.Context
}
func NewController(ctx context.Context, db *gorm.DB) *Controller {
ctl := &Controller{
m: make(map[uint]Pusher),
db: db,
ctx: ctx,
}
ctl.migrateTables()
go func() {
if err := ctl.initPushers(); err != nil {
glog.Errorf("pusher init :%v", err)
}
}()
return ctl
}
func (c *Controller) Consume(ch <-chan model.PushMsg) {
go func() {
for {
select {
case <-c.ctx.Done():
return
case msg := <-ch:
for _, pusherId := range msg.ToPusher {
if pusher, ok := c.m[pusherId]; ok {
if err := pusher.Push(msg.Title, msg.Content); err != nil {
glog.Errorf("pusher %d err: %v", pusherId, err)
}
}
}
}
}
}()
}
func (c *Controller) migrateTables() {
tables := []schema.Tabler{
&model.Pusher[options.AnPushOption]{},
}
for _, table := range tables {
if err := c.db.AutoMigrate(table); err != nil {
glog.Fatalf("failed to migrate table %s: %v", table.TableName(), err)
}
}
}
func (c *Controller) initPushers() error {
var list []model.Pusher[*options.AnPushOption]
if err := c.db.Find(&list).Error; err != nil {
return err
}
for _, p := range list {
c.m[p.ID] = NewAnPush(p.Option)
}
return nil
}
func (c *Controller) AddPusher(opt *model.Pusher[options.AnPushOption]) error {
return c.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(opt).Error; err != nil {
return err
}
fmt.Println("id", opt.ID)
return nil
})
}
type ListPusherInfoRequest struct {
Keyword string `query:"keyword,omitempty"`
Page int `query:"page"`
Size int `query:"size"`
All bool `query:"all"`
}
func (c *Controller) List(req ListPusherInfoRequest) (resp model.ListResponse[model.Pusher[*options.AnPushOption]], err error) {
tx := c.db
if req.Keyword != "" {
tx = tx.Where("name LIKE ? or remark LIKE ?", fmt.Sprintf("%%%s%%", req.Keyword), fmt.Sprintf("%%%s%%", req.Keyword))
}
if err = tx.Model(&model.Pusher[*options.AnPushOption]{}).Find(&resp.List).Error; err != nil {
return resp, fmt.Errorf("查询总数失败:%v", err)
}
resp.Total = int64(len(resp.List))
if req.All || resp.Total == 0 {
return
}
// 查询列表
if req.Page < 1 {
req.Page = 1
}
if req.Size < 1 {
req.Size = 10
}
offset := (req.Page - 1) * req.Size
if err = tx.Order("created_at desc").Limit(req.Size).Offset(offset).
Find(&resp.List).Error; err != nil {
return resp, fmt.Errorf("查询列表失败:%v", err)
}
return
}

57
pkg/utils/proxy.go Normal file
View File

@ -0,0 +1,57 @@
package utils
import (
"context"
"fmt"
"github.com/bytedance/sonic"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/constant"
"github.com/timerzz/proxypool/pkg/proxy"
"net"
"net/url"
"strconv"
)
func ConnFromProxy(ctx context.Context, p proxy.Proxy, addr constant.Metadata) (conn net.Conn, err error) {
pmap := make(map[string]interface{})
err = sonic.UnmarshalString(p.String(), &pmap)
if err != nil {
return
}
pmap["port"] = int(pmap["port"].(float64))
if p.TypeName() == "vmess" {
pmap["alterId"] = int(pmap["alterId"].(float64))
}
clashProxy, err := adapter.ParseProxy(pmap)
if err != nil {
return nil, err
}
return clashProxy.DialContext(ctx, &addr) // 建立到proxy server的connection对Proxy的类别做了自适应相当于泛型
}
// UrlToMetadata DO NOT EDIT. Copied from clash because it's an unexported function
func UrlToMetadata(rawURL string) (addr constant.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
pi, _ := strconv.Atoi(port)
addr = constant.Metadata{
Host: u.Hostname(),
DstPort: uint16(pi),
}
return
}

16
pkg/watcher/base.go Normal file
View File

@ -0,0 +1,16 @@
package watcher
import (
"haitao_watcher/pkg/model"
"haitao_watcher/pkg/options"
)
type Watcher interface {
Watch()
Cancel()
SetOnOrderableMsgChannel(ch chan<- model.PushMsg) Watcher
Option() options.CoachOutletOption
SetOption(options.CoachOutletOption)
Uid() string
Restart()
}

261
pkg/watcher/coach.go Normal file
View File

@ -0,0 +1,261 @@
package watcher
import (
"context"
"fmt"
"github.com/go-resty/resty/v2"
"github.com/pkg/errors"
"github.com/timerzz/proxypool/pkg/proxy"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"haitao_watcher/pkg/model"
"haitao_watcher/pkg/options"
pool "haitao_watcher/pkg/pools"
"haitao_watcher/pkg/utils"
"log/slog"
"math/rand/v2"
"net/url"
"time"
)
type CoachOutlet struct {
pool *pool.ProxyPool
cfg *options.CoachOutletOption
detail *model.Product
iter func() (proxy proxy.Proxy)
db *gorm.DB
fCtx context.Context
ctx context.Context
cancel context.CancelFunc
onOrderable chan<- model.PushMsg
}
func NewCoachWatcher(ctx context.Context, p *pool.ProxyPool, cfg *options.CoachOutletOption, db *gorm.DB) *CoachOutlet {
return &CoachOutlet{
fCtx: ctx,
pool: p,
cfg: cfg,
iter: p.RandomIterator(),
db: db,
}
}
func (c *CoachOutlet) Option() options.CoachOutletOption {
return *c.cfg
}
func (c *CoachOutlet) SetOption(cfg options.CoachOutletOption) {
c.cfg = &cfg
}
func (c *CoachOutlet) SetOnOrderableMsgChannel(ch chan<- model.PushMsg) Watcher {
c.onOrderable = ch
return c
}
func (c *CoachOutlet) Cancel() {
c.cancel()
}
func (c *CoachOutlet) Uid() string {
return fmt.Sprintf("coachOutlet_%s", c.cfg.Pid)
}
func (c *CoachOutlet) getDetail() {
if c.detail == nil {
subCtx, cancel := context.WithTimeout(c.ctx, time.Minute*5)
defer cancel()
if err := c.requestProductDetail(subCtx); err != nil {
slog.Error(fmt.Sprintf("获取coach %s 详情失败:%v", c.cfg.Pid, err))
}
if c.detail != nil && c.detail.Orderable {
if c.onOrderable != nil {
c.onOrderable <- model.PushMsg{
Title: "coachoutlet 商品补货",
Content: fmt.Sprintf("商品 %s 可以购买,链接: %s", c.detail.Name, c.detail.Link),
ToPusher: c.detail.PusherIds,
}
}
c.cancel()
}
}
}
func (c *CoachOutlet) Restart() {
c.Cancel()
go c.Watch()
}
func (c *CoachOutlet) Watch() {
c.ctx, c.cancel = context.WithCancel(c.fCtx)
c.getDetail()
ticker := time.NewTicker(c.cfg.Interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.getDetail()
if c.detail == nil {
continue
}
// 加一些干扰
salt := rand.IntN(70)
time.Sleep(time.Second * time.Duration(salt))
// 请求
subCtx, cancel := context.WithTimeout(c.ctx, time.Minute*5)
inventory, err := c.requestProductInventory(subCtx)
cancel()
if err != nil {
slog.Warn(fmt.Sprintf("获取coach %s 库存失败:%v", c.cfg.Pid, err))
}
c.detail.UpdateErr = err != nil
//如果获取库存没出错,那么更新一下状态
if err == nil {
c.detail.Orderable = inventory.Orderable
if !inventory.AllocationResetDate.IsZero() {
c.detail.AllocationResetDate = inventory.AllocationResetDate
}
if c.detail.Orderable {
c.detail.Watch = false
c.detail.AllocationResetDate = time.Now()
}
}
if err = c.db.Save(c.detail).Error; err != nil {
slog.Error(fmt.Sprintf("更新数据库失败:%v", err))
}
// 如果可以预定了,那么退出
if c.detail.Orderable {
if c.onOrderable != nil {
c.onOrderable <- model.PushMsg{
Title: "coachoutlet 商品补货",
Content: fmt.Sprintf("商品 %s 可以购买,链接: %s", c.detail.Name, c.detail.Link),
ToPusher: c.detail.PusherIds,
}
}
return
}
case <-c.ctx.Done():
return
}
}
}
type ProductData struct {
Id string `json:"id"`
Name string `json:"name"`
Brand string `json:"brand"`
Inventory Inventory `json:"inventory"`
Url string `json:"url"`
MasterId string `json:"masterId"`
Prices struct {
CurrentPrice float64 `json:"currentPrice"`
} `json:"prices"`
Remark string `json:"-"`
}
func (p *ProductData) Product() *model.Product {
return &model.Product{
Uid: fmt.Sprintf("coachOutlet_%s", p.Id),
Pid: p.Id,
Name: p.Name,
Brand: p.Brand,
Website: model.CoachOutlet,
Watch: true,
Orderable: p.Inventory.Orderable,
Link: p.Url,
Remark: p.Remark,
}
}
type Inventory struct {
Id string `json:"id"`
Ats int `json:"ats"`
PreOrderable bool `json:"preorderable"`
BackOrderable bool `json:"backorderable"`
Orderable bool `json:"orderable"`
AllocationResetDate time.Time `json:"allocationResetDate,omitempty"`
Perpetual bool `json:"perpetual"`
StockLevel int `json:"stockLevel"`
}
type ProductDataResponse struct {
ProductData []*ProductData `json:"productsData"`
}
// 获取
func (c *CoachOutlet) requestProductDetail(ctx context.Context) error {
urlPath := fmt.Sprintf("https://www.coachoutlet.com/api/get-products?ids=%s&includeInventory=false", url.QueryEscape(c.cfg.Pid))
var resp ProductDataResponse
err := tryRequest(ctx, urlPath, &resp, c.iter)
if err != nil {
return err
}
if len(resp.ProductData) == 0 {
return fmt.Errorf("coachoutlet %s 详情信息长度为0", c.cfg.Pid)
}
c.detail = resp.ProductData[0].Product()
c.detail.Remark = c.cfg.Remark
c.detail.Link = fmt.Sprintf("https://www.coachoutlet.com%s", c.detail.Link)
if c.detail.Orderable {
c.detail.Watch = false
if c.detail.AllocationResetDate.IsZero() {
c.detail.AllocationResetDate = time.Now()
}
}
c.detail.PusherIds = c.cfg.PusherIds
// 更新数据库
return c.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "uid"}},
UpdateAll: true,
}).Create(c.detail).Error
}
type InventoryResponse struct {
Inventory struct {
Status string `json:"status"`
InventoryListID string `json:"inventoryListID"`
InventoryInfo Inventory `json:"inventoryInfo"`
} `json:"inventory"`
}
func (c *CoachOutlet) requestProductInventory(ctx context.Context) (Inventory, error) {
urlPath := fmt.Sprintf("https://www.coachoutlet.com/api/inventory?vgId=%s&includeVariantData=false", url.QueryEscape(c.cfg.Pid))
var resp InventoryResponse
return resp.Inventory.InventoryInfo, tryRequest(ctx, urlPath, &resp, c.iter)
}
func tryRequest(ctx context.Context, urlPath string, respData any, proxyGetter func() proxy.Proxy) error {
for p := proxyGetter(); p != nil; p = proxyGetter() {
select {
case <-ctx.Done():
return nil
default:
}
_, err := callByProxy(ctx, p, urlPath, respData)
if err != nil {
slog.Debug(err.Error())
continue
}
return nil
}
return errors.New("can not get ")
}
func callByProxy(ctx context.Context, p proxy.Proxy, urlPath string, result any) (*resty.Response, error) {
addr, err := utils.UrlToMetadata(urlPath)
if err != nil {
return nil, err
}
subCtx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
conn, err := utils.ConnFromProxy(subCtx, p, addr)
if err != nil {
return nil, errors.Wrap(err, "创建conn失败")
}
defer conn.Close()
var cli = pool.GetRestyClient(conn)
defer pool.PutRestyClient(cli)
return cli.R().SetResult(result).Get(urlPath)
}

192
pkg/watcher/controller.go Normal file
View File

@ -0,0 +1,192 @@
package watcher
import (
"context"
"fmt"
"github.com/golang/glog"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"haitao_watcher/pkg/model"
"haitao_watcher/pkg/options"
pool "haitao_watcher/pkg/pools"
"sync"
"time"
)
type Controller struct {
lock sync.RWMutex
m map[string]Watcher
db *gorm.DB
pool *pool.ProxyPool
ctx context.Context
ch chan<- model.PushMsg
}
func NewController(ctx context.Context, db *gorm.DB, pool *pool.ProxyPool, ch chan<- model.PushMsg) *Controller {
ctl := &Controller{
m: make(map[string]Watcher),
db: db,
pool: pool,
ctx: ctx,
ch: ch,
}
ctl.migrateTables()
go func() {
if err := ctl.initWatchers(); err != nil {
glog.Errorf("watcher init :%v", err)
}
}()
return ctl
}
func (c *Controller) migrateTables() {
tables := []schema.Tabler{
&model.Product{},
}
for _, table := range tables {
if err := c.db.AutoMigrate(table); err != nil {
glog.Fatalf("failed to migrate table %s: %v", table.TableName(), err)
}
}
}
func (c *Controller) initWatchers() error {
var list []model.Product
if err := c.db.Find(&list, "orderable = ?", false).Error; err != nil {
return err
}
for _, p := range list {
if err := c.RunWatcher(&options.CoachOutletOption{
Pid: p.Pid,
Remark: p.Remark,
Interval: time.Minute * 5,
PusherIds: p.PusherIds,
}); err != nil {
glog.Errorf("run watcher err: %v", err)
}
}
return nil
}
func (c *Controller) RunWatcher(opt *options.CoachOutletOption) error {
uid := opt.Uid()
err := c.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "uid"}},
DoUpdates: clause.AssignmentColumns([]string{"watch", "remark", "orderable", "pusher_ids"}),
}).Create(&model.Product{Uid: uid, Watch: true, Remark: opt.Remark, Orderable: false, PusherIds: opt.PusherIds}).Error
if err != nil {
return err
}
if water, ok := c.m[uid]; ok {
water.Restart()
return nil
}
watcher := NewCoachWatcher(c.ctx, c.pool, opt, c.db).SetOnOrderableMsgChannel(c.ch)
go watcher.Watch()
c.lock.Lock()
c.m[watcher.Uid()] = watcher
c.lock.Unlock()
return nil
}
type ListWatcherInfoRequest struct {
Website model.WebsiteType `query:"website,omitempty"` //是什么网站
Watch *bool `query:"watch,omitempty"`
Orderable *bool `query:"orderable,omitempty"`
Keyword string `query:"keyword,omitempty"`
Page int `query:"page"`
Size int `query:"size"`
}
func (c *Controller) List(req ListWatcherInfoRequest) (resp model.ListResponse[model.Product], err error) {
tx := c.db
if req.Keyword != "" {
tx = tx.Where("name LIKE ? or remark LIKE ?", fmt.Sprintf("%%%s%%", req.Keyword), fmt.Sprintf("%%%s%%", req.Keyword))
}
if !req.Website.IsZero() {
tx = tx.Where("website = ?", req.Website)
}
if req.Watch != nil {
tx = tx.Where("watch = ?", req.Watch)
}
if req.Orderable != nil {
tx = tx.Where("orderable = ?", req.Orderable)
}
if err = tx.Model(&model.Product{}).Count(&resp.Total).Error; err != nil {
return resp, fmt.Errorf("查询总数失败:%v", err)
}
if resp.Total == 0 {
resp.List = make([]model.Product, 0)
return
}
// 查询列表
if req.Page < 1 {
req.Page = 1
}
if req.Size < 1 {
req.Size = 10
}
offset := (req.Page - 1) * req.Size
if err = tx.Order("created_at desc").Limit(req.Size).Offset(offset).
Find(&resp.List).Error; err != nil {
return resp, fmt.Errorf("查询列表失败:%v", err)
}
return
}
func (c *Controller) Delete(uid string) error {
c.lock.Lock()
if watcher, ok := c.m[uid]; ok {
watcher.Cancel()
delete(c.m, uid)
}
c.lock.Unlock()
return c.db.Delete(&model.Product{Uid: uid}, "uid = ?", uid).Error
}
func (c *Controller) Stop(uid string) error {
c.lock.RLock()
if watcher, ok := c.m[uid]; ok {
watcher.Cancel()
}
c.lock.RUnlock()
return c.db.Model(&model.Product{}).Where("uid = ?", uid).UpdateColumn("watch", false).Error
}
func (c *Controller) Start(uid string) error {
c.lock.RLock()
if watcher, ok := c.m[uid]; ok {
watcher.Restart()
c.lock.RUnlock()
return nil
}
c.lock.RUnlock()
var product model.Product
if err := c.db.Find(&product, "uid = ?", uid).Error; err != nil {
return err
}
return c.RunWatcher(&options.CoachOutletOption{
Pid: product.Pid,
Remark: product.Remark,
Interval: time.Minute * 5,
PusherIds: product.PusherIds,
})
}
func (c *Controller) SetPushers(uid string, pushers []uint) (err error) {
c.lock.RLock()
if watcher, ok := c.m[uid]; ok {
cfg := watcher.Option()
cfg.PusherIds = pushers
watcher.SetOption(cfg)
}
c.lock.RUnlock()
return c.db.Model(&model.Product{}).Where("uid = ?", uid).UpdateColumn("pusher_ids", pushers).Error
}

43
server/pusher_svc.go Normal file
View File

@ -0,0 +1,43 @@
package server
import (
"github.com/gofiber/fiber/v3"
"haitao_watcher/pkg/model"
"haitao_watcher/pkg/options"
"haitao_watcher/pkg/pusher"
)
type PusherSvc struct {
ctl *pusher.Controller
}
func NewPusherSvcController(ctl *pusher.Controller) *PusherSvc {
return &PusherSvc{
ctl: ctl,
}
}
func (s *PusherSvc) RegistryRouter(r fiber.Router) {
r.Get("pushers", s.ListPusherInfo)
r.Post("pushers", s.CreatePusher)
}
func (s *PusherSvc) ListPusherInfo(ctx fiber.Ctx) error {
var req pusher.ListPusherInfoRequest
if err := ctx.Bind().Query(&req); err != nil {
return err
}
resp, err := s.ctl.List(req)
if err != nil {
return err
}
return ctx.JSON(resp)
}
func (s *PusherSvc) CreatePusher(ctx fiber.Ctx) error {
var opt model.Pusher[options.AnPushOption]
if err := ctx.Bind().JSON(&opt); err != nil {
return err
}
return s.ctl.AddPusher(&opt)
}

98
server/watcher_svc.go Normal file
View File

@ -0,0 +1,98 @@
package server
import (
"github.com/gofiber/fiber/v3"
"github.com/pkg/errors"
"haitao_watcher/pkg/options"
"haitao_watcher/pkg/watcher"
"net/url"
"time"
)
type WatcherSvc struct {
ctl *watcher.Controller
}
func NewWatcherController(ctl *watcher.Controller) *WatcherSvc {
return &WatcherSvc{
ctl: ctl,
}
}
func (s *WatcherSvc) RegistryRouter(r fiber.Router) {
r.Get("watchers", s.ListWatcherInfo)
r.Post("watchers", s.CreateWatcher)
r.Delete("watchers/:uid", s.DeleteWatcher)
r.Delete("watchers/:uid/status", s.StopWatcher)
r.Post("watchers/:uid/status", s.StartWatcher)
}
func (s *WatcherSvc) ListWatcherInfo(ctx fiber.Ctx) error {
var req watcher.ListWatcherInfoRequest
if err := ctx.Bind().Query(&req); err != nil {
return err
}
resp, err := s.ctl.List(req)
if err != nil {
return err
}
return ctx.JSON(resp)
}
func (s *WatcherSvc) CreateWatcher(ctx fiber.Ctx) (err error) {
var opt options.CoachOutletOption
if err = ctx.Bind().JSON(&opt); err != nil {
return err
}
if opt.Interval == 0 {
opt.Interval = 5 * time.Minute
}
if opt.Pid, err = url.QueryUnescape(opt.Pid); err != nil {
return
}
return s.ctl.RunWatcher(&opt)
}
func (s *WatcherSvc) StopWatcher(ctx fiber.Ctx) (err error) {
uid, err := url.QueryUnescape(ctx.Params("uid"))
if err != nil {
return err
}
if uid == "" {
return errors.New("uid is empty")
}
return s.ctl.Stop(uid)
}
func (s *WatcherSvc) StartWatcher(ctx fiber.Ctx) (err error) {
uid, err := url.QueryUnescape(ctx.Params("uid"))
if err != nil {
return err
}
if uid == "" {
return errors.New("uid is empty")
}
return s.ctl.Start(uid)
}
func (s *WatcherSvc) DeleteWatcher(ctx fiber.Ctx) error {
uid, err := url.QueryUnescape(ctx.Params("uid"))
if err != nil {
return err
}
if uid == "" {
return errors.New("uid is empty")
}
return s.ctl.Delete(uid)
}
func (s *WatcherSvc) SetWatcherPushers(ctx fiber.Ctx) (err error) {
var pusherIds []uint
if err = ctx.Bind().JSON(&pusherIds); err != nil {
return
}
uid := ctx.Params("uid")
if uid == "" {
return errors.New("uid is empty")
}
return s.ctl.SetPushers(uid, pusherIds)
}

24
wwwroot/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
wwwroot/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

7
wwwroot/README.md Normal file
View File

@ -0,0 +1,7 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (previously Volar) and disable Vetur

BIN
wwwroot/bun.lockb Normal file

Binary file not shown.

13
wwwroot/index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" />-->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>可达鸭海淘</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

26
wwwroot/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "wwwroot",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"ant-design-vue": "4.x",
"mande": "^2.0.8",
"moment": "^2.30.1",
"vue": "^3.4.21",
"vue-router": "4"
},
"devDependencies": {
"@types/node": "^20.12.5",
"@vitejs/plugin-vue": "^5.0.4",
"unocss": "^0.59.0",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.2.0"
}
}

15
wwwroot/src/App.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<a-config-provider :locale="zhCN">
<layout></layout>
</a-config-provider>
</template>
<script setup>
import Layout from '@/views/layout/index.vue'
import zhCN from 'ant-design-vue/es/locale/zh_CN';
</script>
<style scoped>
</style>

11
wwwroot/src/api/pusher.js Normal file
View File

@ -0,0 +1,11 @@
import {mande} from "mande";
const pushers = mande('/api/v1/pushers')
export const ListPushers = (query) => {
return pushers.get({query:query})
}
export const AddPusher = (opt)=>{
return pushers.post(opt)
}

View File

@ -0,0 +1,22 @@
import {mande} from "mande";
const watchers = mande('/api/v1/watchers')
export const ListWatchers = (query) => {
return watchers.get({query:query})
}
export const CreateWatcher = (opt)=>{
return watchers.post(opt)
}
export const DeleteWatcher = (uid)=>{
return watchers.delete(`/${encodeURIComponent(uid)}`)
}
export const StopWatcher = (uid)=>{
return watchers.delete(`/${encodeURIComponent(uid)}/status`)
}
export const StartWatcher = (uid)=>{
return watchers.post(`/${encodeURIComponent(uid)}/status`)
}

BIN
wwwroot/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

View File

@ -0,0 +1,10 @@
export const PUSHER = {
ANPUSHER: 1,
}
export const WEBSITE_OPTIONS = [
{
label: 'anPush',
value: PUSHER.ANPUSHER
}
]

View File

@ -0,0 +1,15 @@
export const WEBSITES = {
UNKNOWN: 0,
COACHOUTLET: 1,
}
export const WEBSITE_OPTIONS = [
{
label: '未知',
value: WEBSITES.UNKNOWN
},
{
label: 'coachoutlet',
value: WEBSITES.COACHOUTLET
}
]

6
wwwroot/src/css/base.css Normal file
View File

@ -0,0 +1,6 @@
html,body,#app {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}

9
wwwroot/src/main.js Normal file
View File

@ -0,0 +1,9 @@
import { createApp } from 'vue'
import './css/base.css'
import App from './App.vue'
import 'virtual:uno.css'
import router from "@/routers/index.js";
const app = createApp(App)
app.use(router)
app.mount('#app')

View File

@ -0,0 +1,25 @@
import {createRouter, createWebHashHistory} from "vue-router";
const routes = [
{
path: '/',
redirect: '/watcher'
},
{
path: '/watcher',
name: 'watcher',
component: ()=>import('@/views/Watcher/index.vue')
},
{
path: '/pusher',
name: 'pusher',
component: ()=>import('@/views/Pusher/index.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router

View File

@ -0,0 +1,178 @@
<template>
<div class="h-full m-4 bg-white rounded-2 shadow-lg p-8 flex flex-col justify-between space-y-4">
<div class="flex justify-between">
<a-button type="primary" @click="addModal.visible=true" :disabled="loading">添加</a-button>
<div class="flex space-x-4">
<a-input placeholder="请输入关键词" v-model:value="query.keyword"></a-input>
<a-button type="primary" :disabled="loading" @click="list">搜索</a-button>
</div>
</div>
<div class="h-full border-0 border-t-1 border-solid border-gray-300 pt-4">
<a-spin :spinning="loading" :indicator="indicator">
<a-table :dataSource="data.list" :columns="columns" :pagination="false">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'type'">
<span>{{WEBSITE_OPTIONS.find(w => w.value === record.type).label}}</span>
</template>
<template v-if="column.dataIndex === 'updatedAt'">
<span>{{moment(record.updatedAt).format('YYYY-MM-DD HH:mm:ss')}}</span>
</template>
<template v-if="column.dataIndex === 'option'">
<span>{{record.option}}</span>
</template>
</template>
</a-table>
</a-spin>
</div>
<a-pagination :disabled="loading" class="text-right" v-model:current="query.page" :total="data.total" show-less-items />
</div>
<a-modal v-model:open="addModal.visible" title="添加推送通知" @ok="handleOk" >
<a-spin :spinning="addModal.loading" :indicator="indicator">
<a-form
:model="addModal.data"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
@finish="closeAddModal"
@finishFailed="closeAddModal"
>
<a-form-item label="名称" name="name" :rules="[{ required: true, message: '请填写推送名称' }]">
<a-input v-model:value="addModal.data.name" />
</a-form-item>
<a-form-item label="token" name="option.token" :rules="[{ required: false, message: '请填写token' }]">
<a-input v-model:value="addModal.data.option.token" />
</a-form-item>
<a-form-item label="channel" name="option.channel" :rules="[{ required: false, message: '请填写channel' }]">
<a-input v-model:value="addModal.data.option.channel" />
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="addModal.data.remark" />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script setup>
import {h, onMounted, reactive, ref} from "vue";
import {WEBSITE_OPTIONS} from "@/constants/website.js";
import moment from "moment/moment.js";
import {LoadingOutlined} from "@ant-design/icons-vue";
import {AddPusher, ListPushers} from "@/api/pusher.js";
import {PUSHER} from "@/constants/pusher.js";
import {message} from "ant-design-vue";
const loading = ref(false)
onMounted(()=>{
list()
})
const query = reactive({
// Website database.WebsiteType `query:"website,omitempty"` //
// Watch *bool `query:"watch,omitempty"`
// Orderable *bool `query:"orderable,omitempty"`
// Keyword string `query:"keyword,omitempty"`
keyword: '',
page: 1,
size:10
})
const data = ref({
total: 0,
list:[]
})
const list = ()=>{
loading.value = true
ListPushers(query).then(res=>{
data.value = res
}).catch(err => {
console.log(err)
}).finally(()=>{
loading.value = false
})
}
const addModal = reactive({
visible: false,
data: {
type:PUSHER.ANPUSHER,
name:'',
remark:'',
option:{
token:'',
channel: ''
}
},
loading: false
})
const closeAddModal = ()=>{
addModal.visible = false
addModal.data = {
type:PUSHER.ANPUSHER,
name:'',
remark:'',
option:{
token:'',
channel: ''
}
}
}
const handleOk = ()=>{
AddPusher(addModal.data).then(res=>{
message.success("添加成功")
}).catch(err => {
message.error("添加失败")
console.log(err)
}).finally(()=>{
list(false)
closeAddModal()
})
}
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
},
{
title: '配置',
dataIndex: 'option',
key: 'option',
},
{
title: '更新时间',
dataIndex: 'updatedAt',
key: 'updatedAt',
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
},
]
const indicator = h(LoadingOutlined, {
style: {
fontSize: '32px',
},
spin: true,
});
</script>
<style scoped>
</style>

View File

@ -0,0 +1,253 @@
<template>
<div class="h-full m-4 bg-white rounded-2 shadow-lg p-8 flex flex-col justify-between space-y-4">
<div class="flex justify-between">
<a-button type="primary" @click="addModal.visible=true" :disabled="loading">添加</a-button>
<div class="flex space-x-4">
<a-input placeholder="请输入关键词" v-model:value="query.keyword"></a-input>
<a-button type="primary" :disabled="loading" @click="list">搜索</a-button>
</div>
</div>
<div class="h-full border-0 border-t-1 border-solid border-gray-300 pt-4">
<a-spin :spinning="loading" :indicator="indicator">
<a-table :dataSource="data.list" :columns="columns" :pagination="false">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'website'">
<span>{{WEBSITE_OPTIONS.find(w => w.value === record.website).label}}</span>
</template>
<template v-else-if="column.key === 'watch'">
<a-switch :checked="record.watch" @click="watcherStatusChange(!record.watch,record.uid)"/>
</template>
<template v-else-if="column.key === 'name'">
<a v-if="record.name !== '' " :href="record.link" target="_blank">{{record.name}}</a>
<span v-else>正在抓取信息</span>
</template>
<template v-else-if="column.key === 'updatedAt'">
<span>{{moment(record.updatedAt).format('YYYY-MM-DD HH:mm:ss')}}</span>
</template>
<template v-else-if="column.key === 'pusherIds'">
<template v-if="record.pusherIds" v-for="id in record.pusherIds">
<a-tag :bordered="false" color="processing">{{pusher.list.find(p =>p.id === id)?.name}}</a-tag>
</template>
<span v-else>-</span>
</template>
<template v-else-if="column.key === 'opt'">
<a-button type="link" danger @click="onDelete(record)">删除</a-button>
</template>
</template>
</a-table>
</a-spin>
</div>
<a-pagination :disabled="loading" class="text-right" v-model:current="query.page" :total="data.total" show-less-items />
</div>
<a-modal v-model:open="addModal.visible" title="添加监听任务" @ok="handleOk" >
<a-spin :spinning="addModal.loading" :indicator="indicator">
<a-form
:model="addModal.data"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
@finish="closeAddModal"
@finishFailed="closeAddModal"
>
<a-form-item label="ID" name="pid" :rules="[{ required: true, message: '请填写商品id' }]">
<a-input v-model:value="addModal.data.pid" />
</a-form-item>
<a-form-item label="推送" name="pusherIds" :rules="[{ required: true, message: '请选择通知推送' }]">
<a-select
v-model:value="addModal.data.pusherIds"
mode="multiple"
@dropdown-visible-change="getPushers"
placeholder="请选择通知推送" :fieldNames="{label:'name',value:'id'}"
:options="pusher.list"
></a-select>
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="addModal.data.remark" />
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>
<script setup>
import moment from "moment";
import {h, onMounted, reactive, ref} from "vue";
import {ListWatchers, CreateWatcher, DeleteWatcher, StartWatcher, StopWatcher} from "@/api/watcher.js";
import { LoadingOutlined } from '@ant-design/icons-vue';
import {message, Modal} from 'ant-design-vue';
import {WEBSITE_OPTIONS} from "@/constants/website.js";
import {onBeforeRouteLeave} from "vue-router";
import {ListPushers} from "@/api/pusher.js";
let ticker = null
onMounted(()=>{
list(false)
getPushers()
ticker = setInterval(list, 2000, true)
})
onBeforeRouteLeave(()=>{
if(ticker){
clearInterval(ticker)
}
})
const query = reactive({
// Website database.WebsiteType `query:"website,omitempty"` //
// Watch *bool `query:"watch,omitempty"`
// Orderable *bool `query:"orderable,omitempty"`
// Keyword string `query:"keyword,omitempty"`
page: 1,
size:10
})
const loading = ref(false)
const data = ref({
total: 0,
list:[]
})
const list = (silent)=>{
if(!silent){
loading.value = true
}
ListWatchers(query).then(res=>{
data.value = res
}).catch(err => {
console.log(err)
}).finally(()=>{
if(!silent){
loading.value = false
}
})
}
const addModal = reactive({
visible: false,
data: {
pid:'',
remark:'',
pusherIds:[]
},
loading: false
})
const closeAddModal = ()=>{
addModal.visible = false
addModal.data = {
pid:'',
remark:'',
pusherIds: []
}
}
const handleOk = ()=>{
CreateWatcher(addModal.data).then(res=>{
message.success("添加成功")
}).catch(err => {
message.error("添加失败")
console.log(err)
}).finally(()=>{
list(false)
closeAddModal()
})
}
const pusher = reactive({
list: [],
query: {
keyword:'',
all: true
}
})
const getPushers = ()=>{
ListPushers(pusher.query).then(res=>{
pusher.list = res.list || []
}).catch(err => {
console.log(err)
})
}
const onDelete = ({name, uid})=>{
Modal.confirm({
title: '确认',
content: `确定删除 ${name} 监听?`,
centered: true,
onOk() {
DeleteWatcher(uid).then(res=>{
message.success("删除成功")
list(false)
}).catch(err=>{
message.error("删除失败")
console.log(err)
})
},
});
}
const watcherStatusChange=(changed, uid)=>{
const api = changed ? StartWatcher:StopWatcher
loading.value = true
api(uid).then(res=>{
message.success(`${changed?'开启':'关闭'}成功`)
}).catch(err=>{
message.error(`${changed?'开启':'关闭'}失败`)
}).finally(list)
}
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '品牌',
dataIndex: 'brand',
key: 'brand',
},
{
title: '网站',
dataIndex: 'website',
key: 'website',
},
{
title: '正在蹲货',
dataIndex: 'watch',
key: 'watch',
},
{
title: '上次抓取时间',
dataIndex: 'updatedAt',
key: 'updatedAt',
},
{
title: '推送',
key: 'pusherIds',
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
},
{
title: '操作',
key: 'opt',
},
]
const indicator = h(LoadingOutlined, {
style: {
fontSize: '32px',
},
spin: true,
});
</script>
<style scoped>
</style>

View File

@ -0,0 +1,42 @@
<template>
<a-menu
@click="onclick"
id="aside"
style="width: 256px"
mode="inline"
:items="items"
></a-menu>
</template>
<script setup>
import {AccountBookOutlined, BellOutlined} from "@ant-design/icons-vue";
import {h} from "vue";
import {useRouter} from "vue-router";
const items = [
{
key: 'watcher',
icon: () => h(AccountBookOutlined),
label: '蹲货',
title: '蹲货',
},
{
key: 'pusher',
icon: () => h(BellOutlined),
label: '推送',
title: '推送',
},
]
const router = useRouter()
const onclick = ({key}) => {
router.push({
name: key
})
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,14 @@
<template>
<div class="h-[100px] shadow-lg flex items-center px-12 space-x-4 z-10">
<img class="h-[60px]" src="@/assets/logo.png" alt="">
<div class="text-[24px] font-bold">
可达鸭海淘蹲货
</div>
</div>
</template>
<script setup>
</script>
<style scoped>
</style>

View File

@ -0,0 +1,14 @@
<template>
<div class="h-full w-full bg-[#f0f3f7] flex flex-col">
<router-view ></router-view>
</div>
</template>
<script setup>
</script>
<style scoped>
</style>

View File

@ -0,0 +1,17 @@
<template>
<div class="h-full w-full flex flex-col">
<Header></Header>
<div class="h-full w-full flex">
<Aside></Aside>
<Main></Main>
</div>
</div>
</template>
<script setup>
import Header from "@/views/layout/Header.vue";
import Aside from "@/views/layout/Aside.vue";
import Main from "@/views/layout/Main.vue";
</script>
<style scoped>
</style>

38
wwwroot/vite.config.js Normal file
View File

@ -0,0 +1,38 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import path from 'path'
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
UnoCSS(),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
],
server:{
open: true,
proxy: {
'/api/v1': {
target: 'http://172.25.168.160:2280/',
changeOrigin: true,
secure: false,
ws: true,
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, "./src")
}
}
})