feat: support workspace
This commit is contained in:
15
backend/consts.go
Normal file
15
backend/consts.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
// const (
|
||||
// WechatAppID = "wxf5bf0adeb99c2afd"
|
||||
// WechatAppSecret = "3cf8fad4aa414f2b861399f111b22bb5"
|
||||
// WechatToken = "W8Xhw5TivYBgY"
|
||||
// WechatAesKey = "F6AqCxAV4W1eCrY6llJ2zapphKK49CQN3RgtPDrjhnI"
|
||||
// )
|
||||
|
||||
const (
|
||||
WechatAppID = "wx45745a8c51091ae0"
|
||||
WechatAppSecret = "2ab33bc79d9b47efa4abef19d66e1977"
|
||||
WechatToken = "W8Xhw5TivYBgY"
|
||||
WechatAesKey = "F6AqCxAV4W1eCrY6llJ2zapphKK49CQN3RgtPDrjhnI"
|
||||
)
|
||||
44
backend/go.mod
Normal file
44
backend/go.mod
Normal file
@@ -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
|
||||
)
|
||||
98
backend/go.sum
Normal file
98
backend/go.sum
Normal file
@@ -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=
|
||||
39
backend/main.go
Normal file
39
backend/main.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.ipao.vip/rogeecn/mp-qvyun/pkg/middlewares/fiberv3"
|
||||
"git.ipao.vip/rogeecn/mp-qvyun/pkg/wechat"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
wechatClient := wechat.New(
|
||||
wechat.WithAppID(WechatAppID),
|
||||
wechat.WithAppSecret(WechatAppSecret),
|
||||
wechat.WithAESKey(WechatAesKey),
|
||||
wechat.WithToken(WechatToken),
|
||||
)
|
||||
|
||||
wechatMiddlewares := fiberv3.Init(wechatClient)
|
||||
|
||||
// create a new fiber server
|
||||
app := fiber.New()
|
||||
app.Use(LogAll)
|
||||
app.Use(wechatMiddlewares.Verify)
|
||||
app.Use(wechatMiddlewares.AuthUserInfo)
|
||||
app.Use(wechatMiddlewares.SilentAuth)
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello World")
|
||||
})
|
||||
|
||||
// listen on port 3000
|
||||
if err := app.Listen(":3000"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
17
backend/middlewares.go
Normal file
17
backend/middlewares.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/log"
|
||||
)
|
||||
|
||||
func LogAll(c fiber.Ctx) error {
|
||||
log.Info("------------------------------------------")
|
||||
log.Infof("Request Method: %s", c.Method())
|
||||
log.Infof("Request Headers: %s", &c.Request().Header)
|
||||
log.Infof("Request URL: %s", c.OriginalURL())
|
||||
log.Infof("Request Query: %+v", c.Queries())
|
||||
log.Infof("Request Body: %s", c.BodyRaw())
|
||||
log.Info("------------------------------------------")
|
||||
return c.Next()
|
||||
}
|
||||
100
backend/pkg/middlewares/fiberv3/middlewares.go
Normal file
100
backend/pkg/middlewares/fiberv3/middlewares.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package fiberv3
|
||||
|
||||
import (
|
||||
"git.ipao.vip/rogeecn/mp-qvyun/pkg/wechat"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type fiberMiddlewares struct {
|
||||
client *wechat.Client
|
||||
}
|
||||
|
||||
func Init(client *wechat.Client) *fiberMiddlewares {
|
||||
return &fiberMiddlewares{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fiberMiddlewares) Verify(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 := f.client.Verify(signature, timestamp, nonce); err != nil {
|
||||
return c.SendString(err.Error())
|
||||
}
|
||||
|
||||
return c.SendString(echostr)
|
||||
}
|
||||
|
||||
func (f *fiberMiddlewares) SilentAuth(c fiber.Ctx) error {
|
||||
// if cookie not exists key "openid", then redirect to the wechat auth page
|
||||
sid := c.Cookies("sid", "")
|
||||
if sid != "" {
|
||||
// TODO: verify sid
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// get current full url
|
||||
url := c.BaseURL()
|
||||
url = "https://qvyun.mp.jdwan.com"
|
||||
log.WithField("module", "middleware.SilentAuth").Debug("url:", url)
|
||||
|
||||
to, err := f.client.ScopeAuthorizeURL(
|
||||
wechat.ScopeAuthorizeURLWithRedirectURI(url),
|
||||
wechat.ScopeAuthorizeURLWithState("sns_basic_auth"),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get wechat auth url")
|
||||
}
|
||||
log.WithField("module", "middleware.SilentAuth").Debug("redirectTo: ", to.String())
|
||||
|
||||
return c.Redirect().To(to.String())
|
||||
}
|
||||
|
||||
func (f *fiberMiddlewares) AuthUserInfo(c fiber.Ctx) error {
|
||||
state := c.Query("state")
|
||||
code := c.Query("code")
|
||||
|
||||
if state == "" && code == "" {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
if state != "sns_basic_auth" {
|
||||
return c.Next()
|
||||
}
|
||||
log.WithField("module", "middleware.AuthUserInfo").Debug("code", code)
|
||||
|
||||
// get the openid
|
||||
token, err := f.client.AuthorizeCode2Token(code)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get openid")
|
||||
}
|
||||
// TODO: store the openid to the session
|
||||
|
||||
// set the openid to the cookie
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "sid",
|
||||
Value: token.Openid,
|
||||
HTTPOnly: true,
|
||||
})
|
||||
|
||||
return c.Redirect().To("/")
|
||||
}
|
||||
55
backend/pkg/wechat/errors.go
Normal file
55
backend/pkg/wechat/errors.go
Normal file
@@ -0,0 +1,55 @@
|
||||
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调用
|
||||
// 10003 redirect_uri域名与后台配置不一致
|
||||
// 10004 此公众号被封禁
|
||||
// 10005 此公众号并没有这些scope的权限
|
||||
// 10006 必须关注此测试号
|
||||
// 10009 操作太频繁了,请稍后重试
|
||||
// 10010 scope不能为空
|
||||
// 10011 redirect_uri不能为空
|
||||
// 10012 appid不能为空
|
||||
// 10013 state不能为空
|
||||
// 10015 公众号未授权第三方平台,请检查授权状态
|
||||
// 10016 不支持微信开放平台的Appid,请使用公众号Appid
|
||||
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调用"),
|
||||
10003: errors.New("redirect_uri域名与后台配置不一致"),
|
||||
10004: errors.New("此公众号被封禁"),
|
||||
10005: errors.New("此公众号并没有这些scope的权限"),
|
||||
10006: errors.New("必须关注此测试号"),
|
||||
10009: errors.New("操作太频繁了,请稍后重试"),
|
||||
10010: errors.New("scope不能为空"),
|
||||
10011: errors.New("redirect_uri不能为空"),
|
||||
10012: errors.New("appid不能为空"),
|
||||
10013: errors.New("state不能为空"),
|
||||
10015: errors.New("公众号未授权第三方平台,请检查授权状态"),
|
||||
10016: errors.New("不支持微信开放平台的Appid,请使用公众号Appid"),
|
||||
}
|
||||
|
||||
if err, ok := errors[errCode]; ok {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
14
backend/pkg/wechat/funcs.go
Normal file
14
backend/pkg/wechat/funcs.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package wechat
|
||||
|
||||
import "math/rand"
|
||||
|
||||
// RandomString generate random size string
|
||||
func randomString(size int) (string, error) {
|
||||
// generate size string [0-9a-zA-Z]
|
||||
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
b := make([]byte, size)
|
||||
for i := range b {
|
||||
b[i] = chars[rand.Intn(len(chars))]
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
69
backend/pkg/wechat/options.go
Normal file
69
backend/pkg/wechat/options.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
type ScopeAuthorizeURLOptions func(url.Values)
|
||||
|
||||
func ScopeAuthorizeURLWithScope(scope AuthScope) ScopeAuthorizeURLOptions {
|
||||
return func(v url.Values) {
|
||||
v.Set("scope", scope.String())
|
||||
}
|
||||
}
|
||||
|
||||
func ScopeAuthorizeURLWithRedirectURI(uri string) ScopeAuthorizeURLOptions {
|
||||
return func(v url.Values) {
|
||||
v.Set("redirect_uri", uri)
|
||||
}
|
||||
}
|
||||
|
||||
func ScopeAuthorizeURLWithState(state string) ScopeAuthorizeURLOptions {
|
||||
return func(v url.Values) {
|
||||
v.Set("state", state)
|
||||
}
|
||||
}
|
||||
|
||||
func ScopeAuthorizeURLWithForcePopup() ScopeAuthorizeURLOptions {
|
||||
return func(v url.Values) {
|
||||
v.Set("forcePopup", "true")
|
||||
}
|
||||
}
|
||||
16
backend/pkg/wechat/response.go
Normal file
16
backend/pkg/wechat/response.go
Normal file
@@ -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"` // seconds
|
||||
}
|
||||
182
backend/pkg/wechat/wechat.go
Normal file
182
backend/pkg/wechat/wechat.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"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")
|
||||
|
||||
const (
|
||||
ScopeBase = "snsapi_base"
|
||||
ScopeUserInfo = "snsapi_userinfo"
|
||||
)
|
||||
|
||||
type AuthScope string
|
||||
|
||||
func (s AuthScope) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
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) Verify(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) wrapParams(params map[string]string) map[string]string {
|
||||
if params == nil {
|
||||
params = make(map[string]string)
|
||||
}
|
||||
|
||||
params["appid"] = we.appID
|
||||
params["secret"] = we.appSecret
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
func (we *Client) GetAccessToken() (*AccessTokenResponse, error) {
|
||||
params := map[string]string{
|
||||
"grant_type": "client_credential",
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ScopeAuthorizeURL
|
||||
func (we *Client) ScopeAuthorizeURL(opts ...ScopeAuthorizeURLOptions) (*url.URL, error) {
|
||||
params := url.Values{}
|
||||
params.Add("appid", we.appID)
|
||||
params.Add("response_type", "code")
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(params)
|
||||
}
|
||||
|
||||
if params.Get("scope") == "" {
|
||||
params.Add("scope", ScopeBase)
|
||||
}
|
||||
|
||||
u, err := url.Parse("https://open.weixin.qq.com/connect/oauth2/authorize")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse url failed")
|
||||
}
|
||||
|
||||
u.Fragment = "wechat_redirect"
|
||||
u.RawQuery = url.Values(params).Encode()
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
type AuthorizeAccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
IsSnapshotuser int64 `json:"is_snapshotuser"`
|
||||
Openid string `json:"openid"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Scope string `json:"scope"`
|
||||
Unionid string `json:"unionid"`
|
||||
}
|
||||
|
||||
func (we *Client) AuthorizeCode2Token(code string) (*AuthorizeAccessToken, error) {
|
||||
params := we.wrapParams(map[string]string{
|
||||
"code": code,
|
||||
"grant_type": "authorization_code",
|
||||
})
|
||||
|
||||
var data AuthorizeAccessToken
|
||||
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/access_token")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "call /sns/oauth2/access_token failed")
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (we *Client) AuthorizeRefreshAccessToken(accessToken string) (*AuthorizeAccessToken, error) {
|
||||
params := we.wrapParams(map[string]string{
|
||||
"refresh_token": accessToken,
|
||||
"grant_type": "refresh_token",
|
||||
})
|
||||
|
||||
var data AuthorizeAccessToken
|
||||
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/refresh_token")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "call /sns/oauth2/refresh_token failed")
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
type AuthorizeUserInfo struct {
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
Headimgurl string `json:"headimgurl"`
|
||||
Nickname string `json:"nickname"`
|
||||
Openid string `json:"openid"`
|
||||
Privilege []string `json:"privilege"`
|
||||
Province string `json:"province"`
|
||||
Sex int64 `json:"sex"`
|
||||
Unionid string `json:"unionid"`
|
||||
}
|
||||
|
||||
func (we *Client) AuthorizeUserInfo(accessToken, openID string) (*AuthorizeUserInfo, error) {
|
||||
params := (map[string]string{
|
||||
"access_token": accessToken,
|
||||
"openid": openID,
|
||||
})
|
||||
|
||||
var data AuthorizeUserInfo
|
||||
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/userinfo")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "call /sns/userinfo failed")
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
86
backend/pkg/wechat/wechat_test.go
Normal file
86
backend/pkg/wechat/wechat_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
const (
|
||||
WechatAppID = "wx45745a8c51091ae0"
|
||||
WechatAppSecret = "2ab33bc79d9b47efa4abef19d66e1977"
|
||||
WechatToken = "W8Xhw5TivYBgY"
|
||||
WechatAesKey = "F6AqCxAV4W1eCrY6llJ2zapphKK49CQN3RgtPDrjhnI"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
func getClient() *Client {
|
||||
return New(
|
||||
WithAppID(WechatAppID),
|
||||
WithAppSecret(WechatAppSecret),
|
||||
WithAESKey(WechatAesKey),
|
||||
WithToken(WechatToken),
|
||||
WithClient(DefaultClient.DevMode()),
|
||||
)
|
||||
}
|
||||
|
||||
func TestWechatClient_GetAccessToken(t *testing.T) {
|
||||
Convey("Test GetAccessToken", t, func() {
|
||||
token, err := getClient().GetAccessToken()
|
||||
So(err, ShouldBeNil)
|
||||
So(token.AccessToken, ShouldNotBeEmpty)
|
||||
So(token.ExpiresIn, ShouldBeGreaterThan, 0)
|
||||
|
||||
t.Log("Access Token:", token.AccessToken)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClient_ScopeAuthorizeURL(t *testing.T) {
|
||||
Convey("Test ScopeAuthorizeURL", t, func() {
|
||||
url, err := getClient().ScopeAuthorizeURL(
|
||||
ScopeAuthorizeURLWithScope(ScopeBase),
|
||||
ScopeAuthorizeURLWithRedirectURI("https://qvyun.mp.jdwan.com/"),
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
So(url, ShouldNotBeEmpty)
|
||||
t.Log("URL:", url)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClient_AuthorizeCode2Token(t *testing.T) {
|
||||
code := "011W1sll2Xv4Ae4OjUnl2I7jvd2W1slX"
|
||||
|
||||
Convey("Test AuthorizeCode2Token", t, func() {
|
||||
token, err := getClient().AuthorizeCode2Token(code)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
t.Logf("token: %+v", token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClient_AuthorizeRefreshAccessToken(t *testing.T) {
|
||||
token := "86_m_EAHq0RKlo6RzzGAsY8gVmiCqHqIiAJufxhm8mK8imyIW6yoE4NTcIr2vaukp7dexPWId0JWP1iZWYaLpXT_MJv1N7YQW8Qt3zOZDpJY90"
|
||||
|
||||
Convey("Test AuthorizeCode2Token", t, func() {
|
||||
token, err := getClient().AuthorizeRefreshAccessToken(token)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
t.Logf("token: %+v", token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClient_AuthorizeUserInfo(t *testing.T) {
|
||||
token := "86_ZxJa8mIwbml5mDlHHbIUle_UKW8LA75nOuB0wqiome8AX5LlMWU8JwRKMZykdLEjDnKX8EJavz5GeQn3T1ot7TwpULp8imQvNIgFIjC4er8"
|
||||
openID := "oMLa5tyJ2vRHa-HI4CMEkHztq3eU"
|
||||
|
||||
Convey("Test AuthorizeUserInfo", t, func() {
|
||||
user, err := getClient().AuthorizeUserInfo(token, openID)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
t.Logf("user: %+v", user)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user