package wechatmp import ( "errors" "net/url" "strings" "github.com/beego/beego/v2/core/logs" ) // ============================================================= // 公众号自定义菜单:发布「扫码登录」菜单(点击事件 → CLICK 事件 → 自动回复 // 确认登录链接,用户不用输入任何内容)。 // 注意:菜单接口(menu/create)与带参二维码同属认证后能力, // 未认证公众号会返回 48001,届时需改用「公众号内发送登录」路径。 // ============================================================= // LoginMenuKey 登录菜单的事件键值(与 isLoginMenuKey 保持一致) const LoginMenuKey = "LOGIN" type mpMenuButton struct { Type string `json:"type,omitempty"` Name string `json:"name"` Key string `json:"key,omitempty"` URL string `json:"url,omitempty"` SubButton []mpMenuButton `json:"sub_button,omitempty"` } type mpMenuGetResp struct { Menu struct { Button []mpMenuButton `json:"button"` } `json:"menu"` // 个性化菜单:命中条件时会覆盖默认菜单(公众号底部看到的可能来自这里) ConditionalMenu []map[string]interface{} `json:"conditionalmenu"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } // PublishMenuResult 发布结果(供页面展示与排查) type PublishMenuResult struct { Buttons []mpMenuButton `json:"button"` AlreadyExists bool `json:"already_exists"` ConditionalMenuCount int `json:"conditional_menu_count"` } type mpMenuCreateResp struct { ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } // PublishLoginMenu 在现有菜单基础上追加「扫码登录」菜单并发布。 // - 已存在登录菜单:不再重复发布(AlreadyExists=true); // - 一级菜单满 3 个:返回错误提示; // - 存在个性化菜单:一并返回条数(个性化菜单会覆盖默认菜单,需到公众号后台处理)。 // // 注意:微信客户端有菜单缓存,发布后通常需重新关注或等待(最长 24 小时)才可见。 func PublishLoginMenu(cfg *Config) (*PublishMenuResult, error) { token, err := GetAccessToken(cfg) if err != nil { return nil, err } // 1) 读取现有菜单;46003(菜单不存在)视为空菜单 var cur mpMenuGetResp if gerr := httpGetJSON(apiBase+"/cgi-bin/menu/get?access_token="+url.QueryEscape(token), &cur); gerr != nil { return nil, gerr } buttons := cur.Menu.Button if cur.ErrCode != 0 && cur.ErrCode != 46003 { return nil, &APIError{API: "读取自定义菜单", Code: cur.ErrCode, Msg: cur.ErrMsg} } logs.Info("[wechat-mp] 当前默认菜单 %d 项,个性化菜单 %d 条", len(buttons), len(cur.ConditionalMenu)) for i := range buttons { if strings.EqualFold(strings.TrimSpace(buttons[i].Key), LoginMenuKey) { return &PublishMenuResult{ Buttons: buttons, AlreadyExists: true, ConditionalMenuCount: len(cur.ConditionalMenu), }, nil } } if len(buttons) >= 3 { return nil, errors.New("一级菜单已达 3 个上限,请到公众号后台精简后再发布") } // 2) 追加登录菜单并发布 buttons = append(buttons, mpMenuButton{Type: "click", Name: "扫码登录", Key: LoginMenuKey}) var created mpMenuCreateResp if perr := httpPostJSON(apiBase+"/cgi-bin/menu/create?access_token="+url.QueryEscape(token), map[string]interface{}{"button": buttons}, &created); perr != nil { return nil, perr } if created.ErrCode != 0 { return nil, &APIError{API: "发布自定义菜单", Code: created.ErrCode, Msg: created.ErrMsg} } logs.Info("[wechat-mp] 菜单发布成功,当前 %d 项", len(buttons)) return &PublishMenuResult{ Buttons: buttons, ConditionalMenuCount: len(cur.ConditionalMenu), }, nil }