'init'
This commit is contained in:
179
sc-lktx-backend/internal/pkg/wechat/wechat.go
Normal file
179
sc-lktx-backend/internal/pkg/wechat/wechat.go
Normal file
@@ -0,0 +1,179 @@
|
||||
// 微信小程序 SDK 封装,使用 silenceper/wechat/v2
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
myconfig "com.sclktx/m/v2/internal/pkg/config"
|
||||
|
||||
"github.com/silenceper/wechat/v2"
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
miniProgram "github.com/silenceper/wechat/v2/miniprogram"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/auth"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/subscribe"
|
||||
wechatConfig "github.com/silenceper/wechat/v2/miniprogram/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// MiniProgramClient 微信小程序客户端
|
||||
type MiniProgramClient struct {
|
||||
mp *miniProgram.MiniProgram
|
||||
}
|
||||
|
||||
// NewMiniProgramClient 创建小程序客户端
|
||||
func NewMiniProgramClient(cfg *myconfig.WechatConfig) *MiniProgramClient {
|
||||
// 开启 wechat SDK 日志(开发阶段使用 DebugLevel,生产环境可改为 WarnLevel)
|
||||
logrus.SetOutput(os.Stdout)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
})
|
||||
|
||||
wc := wechat.NewWechat()
|
||||
// 使用内存缓存 access_token(生产环境可换 Redis)
|
||||
memoryCache := cache.NewMemory()
|
||||
|
||||
mp := wc.GetMiniProgram(&wechatConfig.Config{
|
||||
AppID: cfg.AppID,
|
||||
AppSecret: cfg.AppSecret,
|
||||
Cache: memoryCache,
|
||||
})
|
||||
|
||||
return &MiniProgramClient{mp: mp}
|
||||
}
|
||||
|
||||
// Code2Session 用 wx.login 返回的 code 换取 openid 和 session_key
|
||||
func (c *MiniProgramClient) Code2Session(code string) (*auth.ResCode2Session, error) {
|
||||
result, err := c.mp.GetAuth().Code2Session(code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("code2session failed: %w", err)
|
||||
}
|
||||
if result.ErrCode != 0 {
|
||||
return nil, fmt.Errorf("code2session error: %d %s", result.ErrCode, result.ErrMsg)
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// DecryptPhone 解密手机号(需 session_key + encrypted_data + iv)
|
||||
func (c *MiniProgramClient) DecryptPhone(sessionKey, encryptedData, iv string) (string, error) {
|
||||
phoneInfo, err := c.mp.GetEncryptor().Decrypt(sessionKey, encryptedData, iv)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("decrypt phone failed: %w", err)
|
||||
}
|
||||
return phoneInfo.PhoneNumber, nil
|
||||
}
|
||||
|
||||
// GetAccessToken 获取小程序 access_token
|
||||
func (c *MiniProgramClient) GetAccessToken() (string, error) {
|
||||
token, err := c.mp.GetContext().GetAccessToken()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get access_token failed: %w", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// UploadImgResponse 微信上传图片接口返回结构
|
||||
type UploadImgResponse struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
URL string `json:"url"` // 微信返回的图片 URL
|
||||
}
|
||||
|
||||
// UploadImg 上传图片到微信服务器(作为永久素材)
|
||||
// imageData: 图片二进制数据
|
||||
// filename: 文件名
|
||||
func (c *MiniProgramClient) UploadImg(imageData []byte, filename string) (*UploadImgResponse, error) {
|
||||
// 获取 access_token
|
||||
accessToken, err := c.GetAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构造 multipart/form-data 请求
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
// 添加 media 字段(微信要求的字段名)
|
||||
part, err := writer.CreateFormFile("media", filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create form file failed: %w", err)
|
||||
}
|
||||
if _, err := part.Write(imageData); err != nil {
|
||||
return nil, fmt.Errorf("write image data failed: %w", err)
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, fmt.Errorf("close writer failed: %w", err)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s", accessToken)
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request failed: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("upload img request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response failed: %w", err)
|
||||
}
|
||||
|
||||
var result UploadImgResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("parse response failed: %w, body: %s", err, string(respBody))
|
||||
}
|
||||
|
||||
if result.ErrCode != 0 {
|
||||
return nil, fmt.Errorf("wechat upload img error: %d %s", result.ErrCode, result.ErrMsg)
|
||||
}
|
||||
|
||||
// 微信返回的 URL 是 http:// 协议,统一替换为 https://
|
||||
result.URL = strings.Replace(result.URL, "http://", "https://", 1)
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// SendSubscribeMessage 发送微信订阅消息
|
||||
// openid: 接收用户的 openid
|
||||
// templateID: 模板 ID
|
||||
// data: 模板数据,key 为模板字段名(如 thing1, date2),value 为字段值
|
||||
func (c *MiniProgramClient) SendSubscribeMessage(openid, templateID string, data map[string]string, page string) error {
|
||||
msg := &subscribe.Message{
|
||||
ToUser: openid,
|
||||
TemplateID: templateID,
|
||||
Page: page,
|
||||
Data: make(map[string]*subscribe.DataItem),
|
||||
}
|
||||
for k, v := range data {
|
||||
msg.Data[k] = &subscribe.DataItem{Value: v}
|
||||
}
|
||||
err := c.mp.GetSubscribe().Send(msg)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"openid": openid,
|
||||
"template_id": templateID,
|
||||
}).Errorf("微信订阅消息推送失败: %v", err)
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"openid": openid,
|
||||
"template_id": templateID,
|
||||
}).Info("微信订阅消息推送成功")
|
||||
}
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user