diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..498951f --- /dev/null +++ b/.air.toml @@ -0,0 +1,52 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + silent = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitignore b/.gitignore index adf8f72..aa64d8f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ # Go workspace file go.work +tmp/ diff --git a/README.md b/README.md deleted file mode 100644 index 578ab67..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# mp-qvyun - diff --git a/consts.go b/consts.go new file mode 100644 index 0000000..f433d61 --- /dev/null +++ b/consts.go @@ -0,0 +1,8 @@ +package main + +const ( + WechatAppID = "wxf5bf0adeb99c2afd" + WechatAppSecret = "3cf8fad4aa414f2b861399f111b22bb5" + WechatToken = "W8Xhw5TivYBgY" + WechatAesKey = "F6AqCxAV4W1eCrY6llJ2zapphKK49CQN3RgtPDrjhnI" +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c2db2c2 --- /dev/null +++ b/go.mod @@ -0,0 +1,44 @@ +module git.ipao.vip/rogeecn/mp-qvyun + +go 1.23.2 + +require ( + github.com/gofiber/fiber/v3 v3.0.0-beta.3 + github.com/imroc/req/v3 v3.48.0 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.9.3 + github.com/smartystreets/goconvey v1.8.1 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/cloudflare/circl v1.4.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect + github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/onsi/ginkgo/v2 v2.20.2 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.47.0 // indirect + github.com/refraction-networking/utls v1.6.7 // indirect + github.com/smarty/assertions v1.15.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.uber.org/mock v0.4.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.25.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..946e667 --- /dev/null +++ b/go.sum @@ -0,0 +1,98 @@ +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= +github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= +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/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gofiber/fiber/v3 v3.0.0-beta.3 h1:7Q2I+HsIqnIEEDB+9oe7Gadpakh6ZLhXpTYz/L20vrg= +github.com/gofiber/fiber/v3 v3.0.0-beta.3/go.mod h1:kcMur0Dxqk91R7p4vxEpJfDWZ9u5IfvrtQc8Bvv/JmY= +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/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/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= +github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +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/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/imroc/req/v3 v3.48.0 h1:IYuMGetuwLzOOTzDCquDqs912WNwpsPK0TBXWPIvoqg= +github.com/imroc/req/v3 v3.48.0/go.mod h1:weam9gmyb00QnOtu6HXSnk44dNFkIUQb5QdMx13FeUU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +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/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= +github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +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/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y= +github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= +github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= +github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/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.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..e69eebd --- /dev/null +++ b/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "git.ipao.vip/rogeecn/mp-qvyun/pkg/wechat" + "github.com/gofiber/fiber/v3" + log "github.com/sirupsen/logrus" +) + +func main() { + wechatClient := wechat.New( + wechat.WithAppID(WechatAppID), + wechat.WithAppSecret(WechatAppSecret), + wechat.WithAESKey(WechatAesKey), + wechat.WithToken(WechatToken), + ) + + // create a new fiber server + app := fiber.New() + + app.Use(VerifyWechatServer(wechatClient)) + + app.Get("/videos", func(c fiber.Ctx) error { + log.Infof("GET: %+v", c.Queries()) + log.Infof("POST: %s", c.Body()) + return c.SendString(c.Query("echostr", "Error")) + }) + + // listen on port 3000 + if err := app.Listen(":3000"); err != nil { + log.Fatal(err) + } +} diff --git a/middlewares.go b/middlewares.go new file mode 100644 index 0000000..88e07e8 --- /dev/null +++ b/middlewares.go @@ -0,0 +1,36 @@ +package main + +import ( + "git.ipao.vip/rogeecn/mp-qvyun/pkg/wechat" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/log" +) + +func VerifyWechatServer(wechatClient *wechat.Client) fiber.Handler { + return func(c fiber.Ctx) error { + // get the query parameters + signature := c.Query("signature") + timestamp := c.Query("timestamp") + nonce := c.Query("nonce") + echostr := c.Query("echostr") + + if signature == "" || timestamp == "" || nonce == "" || echostr == "" { + return c.Next() + } + + log.Infof( + "begin verify signature, signature: %s, timestamp: %s, nonce: %s, echostr: %s", + signature, + timestamp, + nonce, + echostr, + ) + + // verify the signature + if err := wechatClient.VerifyServer(signature, timestamp, nonce); err != nil { + return c.SendString(err.Error()) + } + + return c.SendString(echostr) + } +} diff --git a/pkg/wechat/errors.go b/pkg/wechat/errors.go new file mode 100644 index 0000000..1626f78 --- /dev/null +++ b/pkg/wechat/errors.go @@ -0,0 +1,33 @@ +package wechat + +import "github.com/pkg/errors" + +// -1 系统繁忙,此时请开发者稍候再试 +// 0 请求成功 +// 40001 AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性 +// 40002 请确保grant_type字段值为client_credential +// 40164 调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。 +// 40243 AppSecret已被冻结,请登录MP解冻后再次调用。 +// 89503 此IP调用需要管理员确认,请联系管理员 +// 89501 此IP正在等待管理员确认,请联系管理员 +// 89506 24小时内该IP被管理员拒绝调用两次,24小时内不可再使用该IP调用 +// 89507 1小时内该IP被管理员拒绝调用一次,1小时内不可再使用该IP调用 +func translateError(errCode int) error { + errors := map[int]error{ + 0: nil, + -1: errors.New("系统繁忙,此时请开发者稍候再试"), + 40001: errors.New("AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性"), + 40002: errors.New("请确保grant_type字段值为client_credential"), + 40164: errors.New("调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置"), + 40243: errors.New("AppSecret已被冻结,请登录MP解冻后再次调用"), + 89503: errors.New("此IP调用需要管理员确认,请联系管理员"), + 89501: errors.New("此IP正在等待管理员确认,请联系管理员"), + 89506: errors.New("24小时内该IP被管理员拒绝调用两次,24小时内不可再使用该IP调用"), + 89507: errors.New("1小时内该IP被管理员拒绝调用一次,1小时内不可再使用该IP调用"), + } + + if err, ok := errors[errCode]; ok { + return err + } + return nil +} diff --git a/pkg/wechat/options.go b/pkg/wechat/options.go new file mode 100644 index 0000000..9acf503 --- /dev/null +++ b/pkg/wechat/options.go @@ -0,0 +1,39 @@ +package wechat + +import "github.com/imroc/req/v3" + +type Options func(*Client) + +func WithAppID(appID string) Options { + return func(we *Client) { + we.appID = appID + } +} + +// WithAppSecret sets the app secret +func WithAppSecret(appSecret string) Options { + return func(we *Client) { + we.appSecret = appSecret + } +} + +// WithToken sets the token +func WithToken(token string) Options { + return func(we *Client) { + we.token = token + } +} + +// WithAESKey sets the AES key +func WithAESKey(aesKey string) Options { + return func(we *Client) { + we.aesKey = aesKey + } +} + +// WithClient sets the http client +func WithClient(client *req.Client) Options { + return func(we *Client) { + we.client = client + } +} diff --git a/pkg/wechat/response.go b/pkg/wechat/response.go new file mode 100644 index 0000000..fd13a4e --- /dev/null +++ b/pkg/wechat/response.go @@ -0,0 +1,16 @@ +package wechat + +type Response struct { + ErrCode int `json:"errcode"` + ErrMsg int `json:"errmsg"` + ErrDescribe int `json:"-"` +} + +func (r *Response) Error() error { + return translateError(r.ErrCode) +} + +type AccessTokenResponse struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` +} diff --git a/pkg/wechat/wechat.go b/pkg/wechat/wechat.go new file mode 100644 index 0000000..d5422fc --- /dev/null +++ b/pkg/wechat/wechat.go @@ -0,0 +1,69 @@ +package wechat + +import ( + "crypto/sha1" + "encoding/hex" + "sort" + "strings" + + "github.com/imroc/req/v3" + "github.com/pkg/errors" +) + +const BaseURL = "https://api.weixin.qq.com/" + +var DefaultClient = req. + NewClient(). + SetBaseURL(BaseURL). + SetCommonHeader("Content-Type", "application/json") + +type Client struct { + client *req.Client + + appID string + appSecret string + token string + aesKey string +} + +func New(options ...Options) *Client { + we := &Client{ + client: DefaultClient, + } + + for _, opt := range options { + opt(we) + } + + return we +} + +func (we *Client) VerifyServer(signature, timestamp, nonce string) error { + params := []string{signature, timestamp, nonce, we.token} + sort.Strings(params) + str := strings.Join(params, "") + hash := sha1.Sum([]byte(str)) + hashStr := hex.EncodeToString(hash[:]) + + if hashStr == signature { + return errors.New("Signature verification failed") + } + + return nil +} + +func (we *Client) GetAccessToken() (*AccessTokenResponse, error) { + params := map[string]string{ + "grant_type": "client_credential", + "appid": we.appID, + "secret": we.appSecret, + } + + var data AccessTokenResponse + _, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/cgi-bin/token") + if err != nil { + return nil, errors.Wrap(err, "call /cgi-bin/token failed") + } + + return &data, nil +} diff --git a/pkg/wechat/wechat_test.go b/pkg/wechat/wechat_test.go new file mode 100644 index 0000000..7c461f7 --- /dev/null +++ b/pkg/wechat/wechat_test.go @@ -0,0 +1,34 @@ +package wechat + +import ( + "testing" + + log "github.com/sirupsen/logrus" + . "github.com/smartystreets/goconvey/convey" +) + +const ( + WechatAppID = "wxf5bf0adeb99c2afd" + WechatAppSecret = "3cf8fad4aa414f2b861399f111b22bb5" + WechatToken = "W8Xhw5TivYBgY" + WechatAesKey = "F6AqCxAV4W1eCrY6llJ2zapphKK49CQN3RgtPDrjhnI" +) + +func TestWechatClient_GetAccessToken(t *testing.T) { + Convey("Test GetAccessToken", t, func() { + log.SetLevel(log.DebugLevel) + + wechatClient := New( + WithAppID(WechatAppID), + WithAppSecret(WechatAppSecret), + WithAESKey(WechatAesKey), + WithToken(WechatToken), + WithClient(DefaultClient.DevMode()), + ) + + token, err := wechatClient.GetAccessToken() + So(err, ShouldBeNil) + So(token.AccessToken, ShouldNotBeEmpty) + So(token.ExpiresIn, ShouldBeGreaterThan, 0) + }) +}