From e43d78ddf09f48e6239e043a8311aac104710203 Mon Sep 17 00:00:00 2001 From: yvan <8574526@qq.com> Date: Tue, 5 Aug 2025 17:46:04 +0800 Subject: [PATCH] wechat --- .../model/request/mp_config_request.go | 42 ++ .../model/request/mp_news_request.go | 56 ++ .../service/wechat_mp_service.go | 164 ++++++ .../wechat/business/MenuEditor/index.js | 13 + .../wechat/business/MenuEditor/index.vue | 505 ++++++++++++++++++ .../wechat/business/MenuPreviewer/index.js | 13 + .../wechat/business/MenuPreviewer/index.vue | 297 ++++++++++ .../mp/auto-reply/components/ReplyTable.vue | 204 +++++++ 8 files changed, 1294 insertions(+) create mode 100644 server/plugin/wechat-integration/model/request/mp_config_request.go create mode 100644 server/plugin/wechat-integration/model/request/mp_news_request.go create mode 100644 server/plugin/wechat-integration/service/wechat_mp_service.go create mode 100644 web/src/components/wechat/business/MenuEditor/index.js create mode 100644 web/src/components/wechat/business/MenuEditor/index.vue create mode 100644 web/src/components/wechat/business/MenuPreviewer/index.js create mode 100644 web/src/components/wechat/business/MenuPreviewer/index.vue create mode 100644 web/src/view/wechat/mp/auto-reply/components/ReplyTable.vue diff --git a/server/plugin/wechat-integration/model/request/mp_config_request.go b/server/plugin/wechat-integration/model/request/mp_config_request.go new file mode 100644 index 00000000..ba24bc1c --- /dev/null +++ b/server/plugin/wechat-integration/model/request/mp_config_request.go @@ -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:"小程序描述"` +} diff --git a/server/plugin/wechat-integration/model/request/mp_news_request.go b/server/plugin/wechat-integration/model/request/mp_news_request.go new file mode 100644 index 00000000..f84e9331 --- /dev/null +++ b/server/plugin/wechat-integration/model/request/mp_news_request.go @@ -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"` +} diff --git a/server/plugin/wechat-integration/service/wechat_mp_service.go b/server/plugin/wechat-integration/service/wechat_mp_service.go new file mode 100644 index 00000000..6e3b3c24 --- /dev/null +++ b/server/plugin/wechat-integration/service/wechat_mp_service.go @@ -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("发送消息功能暂未实现") +// } diff --git a/web/src/components/wechat/business/MenuEditor/index.js b/web/src/components/wechat/business/MenuEditor/index.js new file mode 100644 index 00000000..ac514684 --- /dev/null +++ b/web/src/components/wechat/business/MenuEditor/index.js @@ -0,0 +1,13 @@ +/** + * 菜单编辑器组件 + * 参照yudao-ui-admin的MenuEditor组件设计 + */ + +import MenuEditor from './index.vue' + +// 组件安装函数 +MenuEditor.install = function(app) { + app.component(MenuEditor.name, MenuEditor) +} + +export default MenuEditor diff --git a/web/src/components/wechat/business/MenuEditor/index.vue b/web/src/components/wechat/business/MenuEditor/index.vue new file mode 100644 index 00000000..98cc53ab --- /dev/null +++ b/web/src/components/wechat/business/MenuEditor/index.vue @@ -0,0 +1,505 @@ + + + + + diff --git a/web/src/components/wechat/business/MenuPreviewer/index.js b/web/src/components/wechat/business/MenuPreviewer/index.js new file mode 100644 index 00000000..872b4ac0 --- /dev/null +++ b/web/src/components/wechat/business/MenuPreviewer/index.js @@ -0,0 +1,13 @@ +/** + * 菜单预览器组件 + * 参照yudao-ui-admin的MenuPreviewer组件设计 + */ + +import MenuPreviewer from './index.vue' + +// 组件安装函数 +MenuPreviewer.install = function(app) { + app.component(MenuPreviewer.name, MenuPreviewer) +} + +export default MenuPreviewer diff --git a/web/src/components/wechat/business/MenuPreviewer/index.vue b/web/src/components/wechat/business/MenuPreviewer/index.vue new file mode 100644 index 00000000..30209f57 --- /dev/null +++ b/web/src/components/wechat/business/MenuPreviewer/index.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/web/src/view/wechat/mp/auto-reply/components/ReplyTable.vue b/web/src/view/wechat/mp/auto-reply/components/ReplyTable.vue new file mode 100644 index 00000000..e9c48cc9 --- /dev/null +++ b/web/src/view/wechat/mp/auto-reply/components/ReplyTable.vue @@ -0,0 +1,204 @@ + + + + +