加入微信sdk

This commit is contained in:
xinyu 2026-03-20 12:30:58 +08:00
parent 7fc841a3a5
commit 5c6095a8c4
5 changed files with 251 additions and 4 deletions

View File

@ -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
View File

@ -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
)

52
handler/wechat-handler.go Normal file
View File

@ -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)
}

View File

@ -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
}

123
service/wechat-service.go Normal file
View File

@ -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
}