This commit is contained in:
2025-08-05 17:46:04 +08:00
parent e03259608e
commit e43d78ddf0
8 changed files with 1294 additions and 0 deletions
@@ -0,0 +1,42 @@
package request
// SaveMpConfigRequest 保存微信配置请求
type SaveMpConfigRequest struct {
ConfigType string `json:"configType" binding:"required" example:"mp"`
AppID string `json:"appId" binding:"required" example:"wx1234567890"`
AppSecret string `json:"appSecret" binding:"required" example:"your_app_secret"`
Token *string `json:"token" example:"your_token"`
EncodingAESKey *string `json:"encodingAESKey" example:"your_encoding_aes_key"`
// 公众号特有配置
ServerURL *string `json:"serverUrl" example:"https://your-server.com"`
WebhookURL *string `json:"webhookUrl" example:"https://your-server.com/webhook"`
// 小程序特有配置
MiniAppName *string `json:"miniAppName" example:"我的小程序"`
MiniAppDesc *string `json:"miniAppDesc" example:"小程序描述"`
// 状态信息
Status string `json:"status" example:"active"`
// 扩展配置
ExtraConfig *string `json:"extraConfig" example:"{}"`
Remark *string `json:"remark" example:"配置备注"`
}
// TestMpConfigRequest 测试微信配置请求
type TestMpConfigRequest struct {
ConfigType string `json:"configType" binding:"required" example:"mp"`
AppID string `json:"appId" binding:"required" example:"wx1234567890"`
AppSecret string `json:"appSecret" binding:"required" example:"your_app_secret"`
Token *string `json:"token" example:"your_token"`
EncodingAESKey *string `json:"encodingAESKey" example:"your_encoding_aes_key"`
// 公众号特有配置
ServerURL *string `json:"serverUrl" example:"https://your-server.com"`
WebhookURL *string `json:"webhookUrl" example:"https://your-server.com/webhook"`
// 小程序特有配置
MiniAppName *string `json:"miniAppName" example:"我的小程序"`
MiniAppDesc *string `json:"miniAppDesc" example:"小程序描述"`
}
@@ -0,0 +1,56 @@
package request
// CreateMpNewsRequest 创建图文记录请求
type CreateMpNewsRequest struct {
MediaID string `json:"mediaId" example:"media_id_123"`
Title string `json:"title" binding:"required" example:"图文标题"`
Author *string `json:"author" example:"作者名称"`
Digest *string `json:"digest" example:"图文摘要"`
Content *string `json:"content" example:"图文内容"`
ContentURL *string `json:"contentUrl" example:"https://mp.weixin.qq.com/s/xxx"`
SourceURL *string `json:"sourceUrl" example:"https://example.com"`
ThumbMediaID *string `json:"thumbMediaId" example:"thumb_media_id"`
ThumbURL *string `json:"thumbUrl" example:"https://example.com/thumb.jpg"`
ShowCover bool `json:"showCover" example:"true"`
NeedOpenComment bool `json:"needOpenComment" example:"false"`
OnlyFansCanComment bool `json:"onlyFansCanComment" example:"false"`
// 发布信息
PublishStatus string `json:"publishStatus" example:"draft"`
// 微信相关
WechatURL *string `json:"wechatUrl" example:"https://mp.weixin.qq.com/s/xxx"`
WechatMsgID *string `json:"wechatMsgId" example:"msg_id_123"`
}
// UpdateMpNewsRequest 更新图文记录请求
type UpdateMpNewsRequest struct {
ID uint `json:"id" binding:"required"`
MediaID string `json:"mediaId"`
Title string `json:"title"`
Author *string `json:"author"`
Digest *string `json:"digest"`
Content *string `json:"content"`
ContentURL *string `json:"contentUrl"`
SourceURL *string `json:"sourceUrl"`
ThumbMediaID *string `json:"thumbMediaId"`
ThumbURL *string `json:"thumbUrl"`
ShowCover bool `json:"showCover"`
NeedOpenComment bool `json:"needOpenComment"`
OnlyFansCanComment bool `json:"onlyFansCanComment"`
// 发布信息
PublishStatus string `json:"publishStatus"`
// 微信相关
WechatURL *string `json:"wechatUrl"`
WechatMsgID *string `json:"wechatMsgId"`
}
// MpNewsPageRequest 图文记录分页请求
type MpNewsPageRequest struct {
PageInfo
Title *string `json:"title" form:"title"`
Author *string `json:"author" form:"author"`
PublishStatus *string `json:"publishStatus" form:"publishStatus"`
}
@@ -0,0 +1,164 @@
package service
import (
"errors"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model"
"github.com/silenceper/wechat/v2"
"github.com/silenceper/wechat/v2/cache"
"github.com/silenceper/wechat/v2/officialaccount"
"github.com/silenceper/wechat/v2/officialaccount/basic"
"github.com/silenceper/wechat/v2/officialaccount/config"
)
type WechatMpService struct{}
// GetWechatOfficialAccount 获取微信公众号实例
func (w *WechatMpService) GetWechatOfficialAccount(mpConfig *model.MpConfig) (*officialaccount.OfficialAccount, error) {
if mpConfig == nil {
return nil, errors.New("微信公众号配置不能为空")
}
if mpConfig.AppID == "" || mpConfig.AppSecret == "" {
return nil, errors.New("微信公众号配置不完整")
}
wc := wechat.NewWechat()
memory := cache.NewMemory()
cfg := &config.Config{
AppID: mpConfig.AppID,
AppSecret: mpConfig.AppSecret,
Cache: memory,
}
// 如果有Token和EncodingAESKey,设置服务器配置
if mpConfig.Token != nil && *mpConfig.Token != "" {
cfg.Token = *mpConfig.Token
}
if mpConfig.EncodingAESKey != nil && *mpConfig.EncodingAESKey != "" {
cfg.EncodingAESKey = *mpConfig.EncodingAESKey
}
return wc.GetOfficialAccount(cfg), nil
}
// GenerateQrCode 生成公众号二维码
func (w *WechatMpService) GenerateQrCode(mpConfig *model.MpConfig) (string, error) {
oa, err := w.GetWechatOfficialAccount(mpConfig)
if err != nil {
return "", err
}
// 获取基础服务
basicService := oa.GetBasic()
// 创建永久二维码请求
qrRequest := basic.NewLimitQrRequest("mp_config_qr")
// 获取二维码ticket
ticket, err := basicService.GetQRTicket(qrRequest)
if err != nil {
global.GVA_LOG.Error("获取二维码ticket失败: " + err.Error())
return "", fmt.Errorf("获取二维码ticket失败: %v", err)
}
// 生成二维码图片URL
qrCodeURL := basic.ShowQRCode(ticket)
global.GVA_LOG.Info("生成公众号二维码成功: " + qrCodeURL)
return qrCodeURL, nil
}
// ClearQuota 清空API配额
func (w *WechatMpService) ClearQuota(mpConfig *model.MpConfig) error {
oa, err := w.GetWechatOfficialAccount(mpConfig)
if err != nil {
return err
}
// 调用清空API配额接口
basic := oa.GetBasic()
err = basic.ClearQuota()
if err != nil {
global.GVA_LOG.Error("清空API配额失败: " + err.Error())
return fmt.Errorf("清空API配额失败: %v", err)
}
global.GVA_LOG.Info("清空API配额成功")
return nil
}
// ValidateConfig 验证公众号配置
func (w *WechatMpService) ValidateConfig(mpConfig *model.MpConfig) error {
oa, err := w.GetWechatOfficialAccount(mpConfig)
if err != nil {
return err
}
// 通过获取access_token来验证配置是否正确
basic := oa.GetBasic()
token, err := basic.GetAccessToken()
if err != nil {
global.GVA_LOG.Error("验证公众号配置失败: " + err.Error())
return fmt.Errorf("验证配置失败: %v", err)
}
if token == "" {
return errors.New("获取access_token失败,请检查AppID和AppSecret")
}
global.GVA_LOG.Info("验证公众号配置成功")
return nil
}
// GetServerIPs 获取微信服务器IP地址
func (w *WechatMpService) GetServerIPs(mpConfig *model.MpConfig) ([]string, error) {
oa, err := w.GetWechatOfficialAccount(mpConfig)
if err != nil {
return nil, err
}
basic := oa.GetBasic()
ips, err := basic.GetCallbackIP()
if err != nil {
global.GVA_LOG.Error("获取微信服务器IP失败: " + err.Error())
return nil, fmt.Errorf("获取微信服务器IP失败: %v", err)
}
return ips, nil
}
// GetUserInfo 获取用户基本信息
func (w *WechatMpService) GetUserInfo(mpConfig *model.MpConfig, openID string) (map[string]interface{}, error) {
oa, err := w.GetWechatOfficialAccount(mpConfig)
if err != nil {
return nil, err
}
user := oa.GetUser()
userInfo, err := user.GetUserInfo(openID)
if err != nil {
global.GVA_LOG.Error("获取用户信息失败: " + err.Error())
return nil, fmt.Errorf("获取用户信息失败: %v", err)
}
return map[string]interface{}{
"openid": userInfo.OpenID,
"nickname": userInfo.Nickname,
"sex": userInfo.Sex,
"province": userInfo.Province,
"city": userInfo.City,
"country": userInfo.Country,
"headimgurl": userInfo.Headimgurl,
"unionid": userInfo.UnionID,
}, nil
}
// SendTextMessage 发送文本消息 (暂时注释,需要正确的API调用方式)
// func (w *WechatMpService) SendTextMessage(mpConfig *model.MpConfig, openID, content string) error {
// // TODO: 实现发送文本消息功能
// return errors.New("发送消息功能暂未实现")
// }