Browse Source

添加http和mqtt客户端

yjp 1 year ago
parent
commit
0f9aa4435e
5 changed files with 903 additions and 3 deletions
  1. 35 1
      go.mod
  2. 116 2
      go.sum
  3. 44 0
      http_client/http_client.go
  4. 176 0
      http_client/request.go
  5. 532 0
      mqtt_client/mqtt_client.go

+ 35 - 1
go.mod

@@ -3,8 +3,12 @@ module git.sxidc.com/go-tools/utils
 go 1.21.3
 
 require (
+	git.sxidc.com/service-supports/fserr v0.3.5
+	git.sxidc.com/service-supports/fslog v0.5.9
 	github.com/Masterminds/sprig/v3 v3.2.3
+	github.com/eclipse/paho.mqtt.golang v1.4.3
 	github.com/fatih/structs v1.1.0
+	github.com/go-resty/resty/v2 v2.11.0
 	github.com/mitchellh/mapstructure v1.5.0
 	github.com/redis/go-redis/v9 v9.4.0
 	github.com/satori/go.uuid v1.2.0
@@ -14,14 +18,44 @@ require (
 require (
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Masterminds/semver/v3 v3.2.0 // indirect
+	github.com/bytedance/sonic v1.10.2 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
+	github.com/chenzhuoyu/iasm v0.9.1 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/gin-gonic/gin v1.9.1 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.17.0 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/google/uuid v1.1.1 // indirect
+	github.com/gorilla/websocket v1.5.0 // indirect
 	github.com/huandu/xstrings v1.3.3 // indirect
 	github.com/imdario/mergo v0.3.11 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mitchellh/copystructure v1.0.0 // indirect
 	github.com/mitchellh/reflectwalk v1.0.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
+	github.com/puzpuzpuz/xsync v1.5.2 // indirect
 	github.com/shopspring/decimal v1.2.0 // indirect
 	github.com/spf13/cast v1.3.1 // indirect
-	golang.org/x/crypto v0.3.0 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.12 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/zap v1.26.0 // indirect
+	golang.org/x/arch v0.7.0 // indirect
+	golang.org/x/crypto v0.18.0 // indirect
+	golang.org/x/net v0.20.0 // indirect
+	golang.org/x/sync v0.1.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	google.golang.org/protobuf v1.32.0 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 )

+ 116 - 2
go.sum

@@ -1,3 +1,7 @@
+git.sxidc.com/service-supports/fserr v0.3.5 h1:1SDC60r3FIDd2iRq/oHRLK4OMa1gf67h9B7kierKTUE=
+git.sxidc.com/service-supports/fserr v0.3.5/go.mod h1:8U+W/ulZIGVPFojV6cE18shkGXqvaICuzaxIJpOcBqI=
+git.sxidc.com/service-supports/fslog v0.5.9 h1:q2XIK2o/fk/qmByy4x5kKLC+k7kolT5LrXHcWRSffXQ=
+git.sxidc.com/service-supports/fslog v0.5.9/go.mod h1:/m03ATmmOle75qtEgvEw8a1+Dcg6iHp08M1bGFXJTBU=
 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
@@ -8,29 +12,84 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
 github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
 github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
 github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
+github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
+github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
+github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
+github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
+github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
+github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
+github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+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/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
 github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
+github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+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/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
 github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+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/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
+github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 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/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY=
+github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg=
 github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
 github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
 github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
@@ -40,42 +99,97 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+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/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
+go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
+go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
+golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
 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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
+google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+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=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 44 - 0
http_client/http_client.go

@@ -0,0 +1,44 @@
+package http_client
+
+import (
+	"github.com/go-resty/resty/v2"
+	"time"
+)
+
+type NewRequestOption func(client *Client)
+
+func WithNewHttpRequestTimeout(timeout time.Duration) NewRequestOption {
+	return func(client *Client) {
+		client.setTimeout(timeout)
+	}
+}
+
+type Client struct {
+	client *resty.Client
+}
+
+func New() *Client {
+	return &Client{
+		client: resty.New(),
+	}
+}
+
+func Destroy(client *Client) {
+	if client == nil {
+		return
+	}
+
+	client.client = nil
+}
+
+func (client *Client) NewRequest(opts ...NewRequestOption) *Request {
+	for _, opt := range opts {
+		opt(client)
+	}
+
+	return &Request{request: client.client.R()}
+}
+
+func (client *Client) setTimeout(timeout time.Duration) {
+	client.client.SetTimeout(timeout)
+}

+ 176 - 0
http_client/request.go

@@ -0,0 +1,176 @@
+package http_client
+
+import (
+	"encoding/json"
+	"errors"
+	"github.com/go-resty/resty/v2"
+)
+
+type RequestOption func(request *Request)
+
+func WithRequestHeaders(headers map[string]string) RequestOption {
+	return func(request *Request) {
+		request.SetHeaders(headers)
+	}
+}
+
+func WithRequestQueryParams(queryParams map[string]string) RequestOption {
+	return func(request *Request) {
+		request.SetQueryParams(queryParams)
+	}
+}
+
+func WithRequestPathParams(pathParams map[string]string) RequestOption {
+	return func(request *Request) {
+		request.SetPathParams(pathParams)
+	}
+}
+
+func WithRequestFiles(files map[string]string) RequestOption {
+	return func(request *Request) {
+		request.SetFiles(files)
+	}
+}
+
+func WithRequestFormData(data map[string]string) RequestOption {
+	return func(request *Request) {
+		request.SetFormData(data)
+	}
+}
+
+type Request struct {
+	request *resty.Request
+}
+
+func (request *Request) SetHeaders(headers map[string]string) {
+	if headers == nil || len(headers) == 0 {
+		return
+	}
+
+	request.request.SetHeaders(headers)
+}
+
+func (request *Request) SetQueryParams(queryParams map[string]string) {
+	if queryParams == nil || len(queryParams) == 0 {
+		return
+	}
+
+	request.request.SetQueryParams(queryParams)
+}
+
+func (request *Request) SetPathParams(pathParams map[string]string) {
+	if pathParams == nil || len(pathParams) == 0 {
+		return
+	}
+
+	request.request.SetPathParams(pathParams)
+}
+
+func (request *Request) SetFiles(files map[string]string) {
+	if files == nil || len(files) == 0 {
+		return
+	}
+
+	request.request.SetFiles(files)
+}
+
+func (request *Request) SetFormData(data map[string]string) {
+	if data == nil || len(data) == 0 {
+		return
+	}
+
+	request.request.SetFormData(data)
+}
+
+func (request *Request) Post(url string, body any, opts ...RequestOption) (*Response, error) {
+	for _, opt := range opts {
+		opt(request)
+	}
+
+	restyRequest := request.request
+
+	resp, err := restyRequest.SetBody(body).Post(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.IsError() {
+		return nil, errors.New(resp.Status())
+	}
+
+	return &Response{response: resp}, nil
+}
+
+func (request *Request) Delete(url string, opts ...RequestOption) (*Response, error) {
+	for _, opt := range opts {
+		opt(request)
+	}
+
+	restyRequest := request.request
+
+	resp, err := restyRequest.Delete(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.IsError() {
+		return nil, errors.New(resp.Status())
+	}
+
+	return &Response{response: resp}, nil
+}
+
+func (request *Request) Put(url string, body any, opts ...RequestOption) (*Response, error) {
+	for _, opt := range opts {
+		opt(request)
+	}
+
+	restyRequest := request.request
+
+	resp, err := restyRequest.SetBody(body).Put(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.IsError() {
+		return nil, errors.New(resp.Status())
+	}
+
+	return &Response{response: resp}, nil
+}
+
+func (request *Request) Get(url string, opts ...RequestOption) (*Response, error) {
+	for _, opt := range opts {
+		opt(request)
+	}
+
+	restyRequest := request.request
+
+	resp, err := restyRequest.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.IsError() {
+		return nil, errors.New(resp.Status())
+	}
+
+	return &Response{response: resp}, nil
+}
+
+type Response struct {
+	response *resty.Response
+}
+
+func (response *Response) Body() []byte {
+	return response.response.Body()
+}
+
+func (response *Response) Json(resp any) error {
+	body := response.response.Body()
+	if body == nil || len(body) == 0 {
+		return errors.New("响应体为空")
+	}
+
+	return json.Unmarshal(body, resp)
+}

+ 532 - 0
mqtt_client/mqtt_client.go

@@ -0,0 +1,532 @@
+package mqtt_client
+
+import (
+	"encoding/json"
+	"errors"
+	"git.sxidc.com/go-tools/utils/strutils"
+	"git.sxidc.com/service-supports/fslog"
+	mqtt "github.com/eclipse/paho.mqtt.golang"
+	"log"
+	"sync"
+	"time"
+)
+
+var (
+	ErrMqttMessageIgnore = errors.New("mqtt消息忽略")
+)
+
+type MessageHandler func(client *Client, token *SubscribeToken, topic string, data []byte) error
+
+type ClientOptions struct {
+	UserName        string
+	Password        string
+	Address         string
+	ClientID        string
+	KeepAliveSec    int64
+	PingTimeoutSec  int64
+	WriteTimeoutSec int64
+}
+
+func (opts *ClientOptions) check() error {
+	if strutils.IsStringEmpty(opts.UserName) {
+		return errors.New("必须传递用户名")
+	}
+
+	if strutils.IsStringEmpty(opts.Password) {
+		return errors.New("必须传递密码")
+	}
+
+	if strutils.IsStringEmpty(opts.Address) {
+		return errors.New("必须传递地址")
+	}
+
+	if strutils.IsStringEmpty(opts.ClientID) {
+		return errors.New("必须传递客户端ID")
+	}
+
+	return nil
+}
+
+type SubscribeToken struct {
+	messageHandler     MessageHandler
+	successHandleCount int
+	handleCount        int
+}
+
+func (token *SubscribeToken) SuccessHandleCount() int {
+	return token.successHandleCount
+}
+
+func (token *SubscribeToken) HandleCount() int {
+	return token.handleCount
+}
+
+type subscribeTopic struct {
+	topic  string
+	tokens []*SubscribeToken
+}
+
+type Client struct {
+	client mqtt.Client
+	opts   *ClientOptions
+
+	topicsMutex *sync.Mutex
+	topics      []*subscribeTopic
+
+	publishAndReceiveTopicMapMutex sync.Mutex
+	publishAndReceiveTopicMap      map[string]chan any
+}
+
+func New(opts *ClientOptions) (*Client, error) {
+	if opts == nil {
+		return nil, errors.New("必须传递参数")
+	}
+
+	err := opts.check()
+	if err != nil {
+		return nil, err
+	}
+
+	if opts.WriteTimeoutSec == 0 {
+		opts.WriteTimeoutSec = 60
+	}
+
+	client := &Client{
+		opts:                      opts,
+		topicsMutex:               &sync.Mutex{},
+		topics:                    make([]*subscribeTopic, 0),
+		publishAndReceiveTopicMap: make(map[string]chan any),
+	}
+
+	mqttClient := mqtt.NewClient(mqtt.NewClientOptions().
+		SetAutoReconnect(true).
+		SetUsername(opts.UserName).
+		SetPassword(opts.Password).
+		AddBroker(opts.Address).
+		SetClientID(opts.ClientID).
+		SetKeepAlive(time.Duration(opts.KeepAliveSec)*time.Second).
+		SetPingTimeout(time.Duration(opts.PingTimeoutSec)*time.Second).
+		SetWriteTimeout(time.Duration(opts.WriteTimeoutSec)*time.Second).
+		SetOrderMatters(false).
+		SetWill(opts.ClientID+"/will", "dead", 2, true).
+		SetOnConnectHandler(func(mqttClient mqtt.Client) {
+			err := client.subscribeAll()
+			if err != nil {
+				fslog.Error("SetOnConnectHandler: " + err.Error())
+				return
+			}
+		}))
+
+	token := mqttClient.Connect()
+	if !token.WaitTimeout(time.Duration(opts.WriteTimeoutSec) * time.Second) {
+		return nil, errors.New("连接超时")
+	}
+
+	if token.Error() != nil {
+		return nil, token.Error()
+	}
+
+	client.client = mqttClient
+
+	return client, nil
+}
+
+func Destroy(client *Client) {
+	client.client.Disconnect(250)
+	client.topicsMutex.Lock()
+
+	for _, subscribedTopic := range client.topics {
+		token := client.client.Unsubscribe(subscribedTopic.topic)
+		if !token.WaitTimeout(time.Duration(client.opts.WriteTimeoutSec) * time.Second) {
+			continue
+		}
+
+		if token.Error() != nil {
+			fslog.Error(token.Error())
+			continue
+		}
+	}
+
+	client.topics = nil
+
+	client.topicsMutex.Unlock()
+
+	client = nil
+}
+
+func (client *Client) Publish(topic string, qos byte, retained bool, payload any) error {
+	if strutils.IsStringEmpty(topic) {
+		return errors.New("没有传递发布主题")
+	}
+
+	if payload == nil {
+		return errors.New("发布的payload不能为nil")
+	}
+
+	client.publish(topic, qos, retained, payload)
+
+	return nil
+}
+
+func (client *Client) Subscribe(topic string, handlerFunc MessageHandler) (*SubscribeToken, error) {
+	if strutils.IsStringEmpty(topic) {
+		return nil, errors.New("没有传递订阅主题")
+	}
+
+	if handlerFunc == nil {
+		return nil, errors.New("必须传递处理函数")
+	}
+
+	return client.subscribe(topic, handlerFunc)
+}
+
+func (client *Client) Unsubscribe(topic string, token *SubscribeToken) error {
+	if strutils.IsStringEmpty(topic) {
+		return errors.New("没有传递取消订阅主题")
+	}
+
+	return client.unsubscribe(topic, token)
+}
+
+type PublishAndReceiveReplyParams struct {
+	Topic                string
+	ReplyTopic           string
+	PublishData          []byte
+	RepublishDurationSec int64
+	TryTimes             int
+	StopBefore           bool
+}
+
+func (params *PublishAndReceiveReplyParams) Check() error {
+	if strutils.IsStringEmpty(params.Topic) {
+		return errors.New("没有传递订阅主题")
+	}
+
+	if strutils.IsStringEmpty(params.ReplyTopic) {
+		return errors.New("没有传递响应订阅主题")
+	}
+
+	if params.PublishData == nil {
+		return errors.New("发布的数据不能为nil")
+	}
+
+	return nil
+}
+
+type msgResponse struct {
+	Success bool   `json:"success"`
+	Msg     string `json:"msg"`
+}
+
+func (client *Client) PublishAndReceiveReplyMsgResponse(params *PublishAndReceiveReplyParams) error {
+	err := params.Check()
+	if err != nil {
+		return err
+	}
+
+	err = client.publishAndReceiveReply(params, func(payload []byte) error {
+		resp := new(msgResponse)
+		err := json.Unmarshal(payload, resp)
+		if err != nil {
+			return err
+		}
+
+		if !resp.Success {
+			return errors.New(resp.Msg)
+		}
+
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (client *Client) PublishAndReceiveReply(params *PublishAndReceiveReplyParams, payloadDealFunc func(payload []byte) error) error {
+	err := params.Check()
+	if err != nil {
+		return err
+	}
+
+	return client.publishAndReceiveReply(params, payloadDealFunc)
+}
+
+func (client *Client) publish(topic string, qos byte, retained bool, payload any) {
+	client.waitConnected()
+
+	token := client.client.Publish(topic, qos, retained, payload)
+	if !token.WaitTimeout(time.Duration(client.opts.WriteTimeoutSec) * time.Second) {
+		log.Println("发布超时")
+		return
+	}
+
+	if token.Error() != nil {
+		log.Println(token.Error())
+		return
+	}
+}
+
+func (client *Client) subscribe(topic string, handlerFunc MessageHandler) (*SubscribeToken, error) {
+	client.waitConnected()
+
+	return client.addSubscribedTopic(topic, handlerFunc, func(subscribedTopic *subscribeTopic) error {
+		return client.subscribeMqtt(subscribedTopic)
+	})
+}
+
+func (client *Client) unsubscribe(topic string, token *SubscribeToken) error {
+	client.waitConnected()
+
+	err := client.removeSubscribedTopic(topic, token, func(subscribedTopic *subscribeTopic) error {
+		token := client.client.Unsubscribe(subscribedTopic.topic)
+		if !token.WaitTimeout(time.Duration(client.opts.WriteTimeoutSec) * time.Second) {
+			return errors.New("取消订阅超时")
+		}
+
+		if token.Error() != nil {
+			return token.Error()
+		}
+
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (client *Client) publishAndReceiveReply(params *PublishAndReceiveReplyParams, payloadDealFunc func(payload []byte) error) error {
+	doneChan := make(chan any)
+
+	if params.StopBefore {
+		client.publishAndReceiveTopicMapMutex.Lock()
+		existDoneChan, ok := client.publishAndReceiveTopicMap[params.Topic]
+		if ok {
+			close(existDoneChan)
+			delete(client.publishAndReceiveTopicMap, params.Topic)
+		} else {
+			client.publishAndReceiveTopicMap[params.Topic] = doneChan
+		}
+		client.publishAndReceiveTopicMapMutex.Unlock()
+	}
+
+	// 订阅响应主题
+	token, err := client.subscribe(params.ReplyTopic, func(c *Client, token *SubscribeToken, topic string, data []byte) error {
+		defer func() {
+			client.publishAndReceiveTopicMapMutex.Lock()
+			existDoneChan, ok := client.publishAndReceiveTopicMap[params.Topic]
+			if ok {
+				close(existDoneChan)
+				delete(client.publishAndReceiveTopicMap, params.Topic)
+			}
+			client.publishAndReceiveTopicMapMutex.Unlock()
+		}()
+
+		if token.SuccessHandleCount() >= 1 {
+			return ErrMqttMessageIgnore
+		}
+
+		if payloadDealFunc != nil {
+			err := payloadDealFunc(data)
+			if err != nil {
+				return err
+			}
+		}
+
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+
+	go func() {
+		client.publish(params.Topic, 2, false, params.PublishData)
+
+		currentTryTime := 1
+		timer := time.NewTimer(time.Duration(params.RepublishDurationSec) * time.Second)
+		defer timer.Stop()
+
+		for {
+			select {
+			case <-doneChan:
+				err := client.unsubscribe(params.ReplyTopic, token)
+				if err != nil {
+					fslog.Error(err)
+					return
+				}
+
+				client.publishAndReceiveTopicMapMutex.Lock()
+				delete(client.publishAndReceiveTopicMap, params.Topic)
+				client.publishAndReceiveTopicMapMutex.Unlock()
+
+				return
+			case <-timer.C:
+				client.publish(params.Topic, 2, false, params.PublishData)
+
+				if params.TryTimes != 0 {
+					currentTryTime++
+				}
+
+				if params.TryTimes != 0 && currentTryTime > params.TryTimes {
+					client.publishAndReceiveTopicMapMutex.Lock()
+					existDoneChan, ok := client.publishAndReceiveTopicMap[params.Topic]
+					if ok {
+						close(existDoneChan)
+						delete(client.publishAndReceiveTopicMap, params.Topic)
+					}
+					client.publishAndReceiveTopicMapMutex.Unlock()
+					return
+				}
+
+				resetDuration := time.Duration(params.RepublishDurationSec*int64(currentTryTime)) * time.Second
+				timer.Reset(resetDuration)
+			}
+		}
+	}()
+
+	return nil
+}
+
+func (client *Client) waitConnected() {
+	for {
+		if client.client.IsConnected() {
+			break
+		}
+
+		time.Sleep(1 * time.Second)
+	}
+}
+
+func (client *Client) subscribeAll() error {
+	err := client.rangeSubscribedTopics(func(subscribedTopic *subscribeTopic) error {
+		return client.subscribeMqtt(subscribedTopic)
+	})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (client *Client) subscribeMqtt(subscribedTopic *subscribeTopic) error {
+	token := client.client.Subscribe(subscribedTopic.topic, 2, func(mqttClient mqtt.Client, message mqtt.Message) {
+		wg := sync.WaitGroup{}
+		wg.Add(len(subscribedTopic.tokens))
+		for _, token := range subscribedTopic.tokens {
+			go func(token *SubscribeToken, message mqtt.Message) {
+				defer func() {
+					token.handleCount++
+					wg.Done()
+				}()
+
+				err := token.messageHandler(client, token, subscribedTopic.topic, message.Payload())
+				if err != nil && !errors.Is(err, ErrMqttMessageIgnore) {
+					fslog.Error(err)
+					return
+				}
+
+				if err != nil && errors.Is(err, ErrMqttMessageIgnore) {
+					return
+				}
+
+				token.successHandleCount++
+			}(token, message)
+		}
+		wg.Wait()
+	})
+	if !token.WaitTimeout(time.Duration(client.opts.WriteTimeoutSec) * time.Second) {
+		return errors.New("订阅超时")
+	}
+
+	if token.Error() != nil {
+		return token.Error()
+	}
+
+	return nil
+}
+
+func (client *Client) addSubscribedTopic(topic string, handler MessageHandler, addNew func(subscribedTopic *subscribeTopic) error) (*SubscribeToken, error) {
+	client.topicsMutex.Lock()
+	defer client.topicsMutex.Unlock()
+
+	newToken := &SubscribeToken{messageHandler: handler}
+
+	for _, savedTopic := range client.topics {
+		if savedTopic.topic == topic {
+			savedTopic.tokens = append(savedTopic.tokens, newToken)
+			return newToken, nil
+		}
+	}
+
+	subscribedTopic := &subscribeTopic{
+		topic:  topic,
+		tokens: []*SubscribeToken{newToken},
+	}
+
+	err := addNew(subscribedTopic)
+	if err != nil {
+		return nil, err
+	}
+
+	client.topics = append(client.topics, subscribedTopic)
+	return newToken, nil
+}
+
+func (client *Client) removeSubscribedTopic(topic string, token *SubscribeToken, noTokens func(subscribedTopic *subscribeTopic) error) error {
+	client.topicsMutex.Lock()
+	defer client.topicsMutex.Unlock()
+
+	findSubscribeTopicIndex := -1
+	for index, subscribedTopic := range client.topics {
+		if subscribedTopic.topic == topic {
+			findSubscribeTopicIndex = index
+		}
+	}
+
+	if findSubscribeTopicIndex == -1 {
+		return nil
+	}
+
+	subscribedTopic := client.topics[findSubscribeTopicIndex]
+	if subscribedTopic.tokens != nil && len(subscribedTopic.tokens) == 1 {
+		err := noTokens(subscribedTopic)
+		if err != nil {
+			return err
+		}
+
+		client.topics = append(client.topics[:findSubscribeTopicIndex], client.topics[findSubscribeTopicIndex+1:]...)
+		return nil
+	}
+
+	findTokenIndex := -1
+	for index, savedToken := range subscribedTopic.tokens {
+		if savedToken == token {
+			findTokenIndex = index
+		}
+	}
+
+	if findTokenIndex == -1 {
+		return nil
+	}
+
+	subscribedTopic.tokens = append(subscribedTopic.tokens[:findTokenIndex], subscribedTopic.tokens[findTokenIndex+1:]...)
+	return nil
+}
+
+func (client *Client) rangeSubscribedTopics(rangeFunc func(subscribedTopic *subscribeTopic) error) error {
+	client.topicsMutex.Lock()
+	defer client.topicsMutex.Unlock()
+
+	for _, subscribedTopic := range client.topics {
+		tempTopic := subscribedTopic
+		err := rangeFunc(tempTopic)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}