加入微信sdk
This commit is contained in:
parent
7fc841a3a5
commit
5c6095a8c4
|
|
@ -10,6 +10,7 @@ func routerSetup(router *gin.Engine) {
|
|||
router.Use(gin.Recovery())
|
||||
api := router.Group("/api")
|
||||
api.POST("/login", handler.AuthLogin)
|
||||
api.GET(`/wechat/qr/:id`, handler.WechatQrGet)
|
||||
|
||||
protected := router.Group("/api")
|
||||
protected.Use(auth(), authorize())
|
||||
|
|
|
|||
16
go.mod
16
go.mod
|
|
@ -9,7 +9,7 @@ require (
|
|||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/crypto v0.49.0
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
gorm.io/plugin/dbresolver v1.6.2
|
||||
|
|
@ -17,15 +17,20 @@ require (
|
|||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // 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.30.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
|
|
@ -43,12 +48,17 @@ require (
|
|||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/silenceper/wechat/v2 v2.1.12 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"myschools.me/heritage/heritage-api/service"
|
||||
)
|
||||
|
||||
func WechatQrGet(c *gin.Context) {
|
||||
reqid := c.Param("id")
|
||||
if reqid == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "reqid is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := service.WechatQrGet(&reqid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func WechatAuth(c *gin.Context) {
|
||||
reqid := c.Param("id")
|
||||
if reqid == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "reqid is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
code := c.Query("code")
|
||||
if code == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "code is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
resp, err := service.WechatAuth(&reqid, &code)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func newID() string {
|
||||
i := uuid.Must(uuid.NewV7()).String()
|
||||
i := uuid.Must(uuid.NewUUID()).String()
|
||||
return strings.ReplaceAll(i, "-", "")
|
||||
}
|
||||
|
||||
|
|
@ -33,3 +37,60 @@ func newToken() string {
|
|||
i := uuid.Must(uuid.NewUUID()).String()
|
||||
return strings.ReplaceAll(i, "-", "")
|
||||
}
|
||||
|
||||
// httpDO 通用的HTTP请求函数
|
||||
func httpDO(url, method string, headers map[string]string, body []byte) ([]byte, error) {
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
reqBody = bytes.NewBuffer(body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, reqBody)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"func": "httpDO",
|
||||
"url": url,
|
||||
"method": method,
|
||||
}).Errorf("http.NewRequest: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"func": "httpDO",
|
||||
"url": url,
|
||||
"method": method,
|
||||
}).Errorf("client.Do failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"func": "httpDO",
|
||||
"url": url,
|
||||
"method": method,
|
||||
}).Errorf("io.ReadAll failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"func": "httpDO",
|
||||
"url": url,
|
||||
"method": method,
|
||||
"statusCode": resp.StatusCode,
|
||||
"response": string(respBody),
|
||||
}).Errorf("HTTP request failed with status code: %d", resp.StatusCode)
|
||||
return respBody, err
|
||||
}
|
||||
|
||||
return respBody, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/silenceper/wechat/v2"
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/officialaccount"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ticketRequest struct {
|
||||
ExpireSeconds int `json:"expire_seconds"`
|
||||
ActionName string `json:"action_name"`
|
||||
ActionInfo struct {
|
||||
Scene struct {
|
||||
SceneStr string `json:"scene_str"`
|
||||
} `json:"scene"`
|
||||
} `json:"action_info"`
|
||||
}
|
||||
|
||||
type ticketResponse struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
Ticket string `json:"ticket"`
|
||||
ExpireSeconds int `json:"expire_seconds"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type WechatGetQrCodeResponse struct {
|
||||
Ticket string `json:"ticket"`
|
||||
ExpireSeconds string `json:"expire_seconds"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
var (
|
||||
mp *officialaccount.OfficialAccount
|
||||
)
|
||||
|
||||
func init() {
|
||||
wx := wechat.NewWechat()
|
||||
wx.SetCache(cache.NewRedis(nil, &cache.RedisOpts{
|
||||
Host: os.Getenv("REDIS_HOST"),
|
||||
Password: os.Getenv("REDIS_PWD"),
|
||||
Database: func() int {
|
||||
db := os.Getenv("REDIS_DB")
|
||||
n, err := strconv.Atoi(db)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"func": "init",
|
||||
}).Warnf("strconv.Atoi failed: %v", err)
|
||||
n = 0
|
||||
}
|
||||
return n
|
||||
}(),
|
||||
MaxIdle: 3,
|
||||
MaxActive: 300,
|
||||
IdleTimeout: 600,
|
||||
}))
|
||||
|
||||
mp = wx.GetOfficialAccount(&config.Config{
|
||||
AppID: os.Getenv("APPID"),
|
||||
AppSecret: os.Getenv("APPSECRET"),
|
||||
Token: os.Getenv("TOKEN"),
|
||||
EncodingAESKey: os.Getenv("EncodingAESKey"),
|
||||
Cache: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// 通过ID获取临时二维码
|
||||
func WechatQrGet(reqid *string) (any, error) {
|
||||
token, err := mp.GetAccessToken()
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"func": "WechatQrGet",
|
||||
}).Errorf("mp.GetAccessToken: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accurl := "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + token
|
||||
data := ticketRequest{
|
||||
ExpireSeconds: 180,
|
||||
ActionName: "QR_STR_SCENE",
|
||||
ActionInfo: struct {
|
||||
Scene struct {
|
||||
SceneStr string `json:"scene_str"`
|
||||
} `json:"scene"`
|
||||
}{
|
||||
Scene: struct {
|
||||
SceneStr string `json:"scene_str"`
|
||||
}{
|
||||
SceneStr: *reqid,
|
||||
},
|
||||
},
|
||||
}
|
||||
marshal, err := json.Marshal(&data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := httpDO(accurl, "post", nil, marshal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result *ticketResponse
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := &WechatGetQrCodeResponse{
|
||||
Ticket: result.Ticket,
|
||||
ExpireSeconds: fmt.Sprintf("%d", result.ExpireSeconds),
|
||||
Url: result.Url,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func WechatAuth(reqid, code *string) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
Loading…
Reference in New Issue