package controllers import ( "encoding/json" "io" "strconv" "strings" "server/models" "server/pkg/identity" "server/pkg/jwtutil" "server/services/wechatmini" "server/services/wechatmp" beego "github.com/beego/beego/v2/server/web" ) // ============================================================= // 平台端「微信小程序」配置:AppID / AppSecret / 启用开关 / 自动绑定 // // 配置就绪后,uniapp 小程序端的登录页才会展示「微信手机号一键登录」入口 // (GET /auth/wechat/miniapp/config 返回 enabled)。 // AppSecret 掩码回显,提交空串或掩码表示不修改。 // ============================================================= // PlatformWechatMiniController 平台端微信小程序配置 type PlatformWechatMiniController struct { beego.Controller } func (c *PlatformWechatMiniController) claims() (*jwtutil.Claims, error) { return identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndPlatform) } func (c *PlatformWechatMiniController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *PlatformWechatMiniController) ok(data interface{}, msg string) { if msg == "" { msg = "success" } resp := map[string]interface{}{"code": 200, "msg": msg} if data != nil { resp["data"] = data } c.Data["json"] = resp _ = c.ServeJSON() } // GetConfig GET /platform/wechatMini/config func (c *PlatformWechatMiniController) GetConfig() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } data := map[string]interface{}{ "app_id": "", "app_secret": "", "enabled": 0, "auto_bind": 1, "remark": "", "configured": false, } if cfg, err := wechatmini.LoadConfig(); err == nil { data["app_id"] = cfg.AppID data["app_secret"] = wechatmp.MaskSecret(cfg.AppSecret) data["remark"] = cfg.Remark data["configured"] = cfg.AppID != "" && cfg.AppSecret != "" if cfg.Enabled { data["enabled"] = 1 } if cfg.AutoBind { data["auto_bind"] = 1 } else { data["auto_bind"] = 0 } } c.ok(data, "获取成功") } // SaveConfig POST /platform/wechatMini/config func (c *PlatformWechatMiniController) SaveConfig() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } var p struct { AppID string `json:"app_id"` AppSecret string `json:"app_secret"` Enabled int `json:"enabled"` AutoBind int `json:"auto_bind"` Remark string `json:"remark"` } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } if err := json.Unmarshal(raw, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } if err := wechatmini.SaveConfig(wechatmini.SaveInput{ AppID: p.AppID, AppSecret: p.AppSecret, Enabled: p.Enabled == 1, AutoBind: p.AutoBind == 1, Remark: p.Remark, }); err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(nil, "保存成功") } // ListApps GET /platform/wechatMini/apps 多小程序应用列表(AppSecret 不回显) func (c *PlatformWechatMiniController) ListApps() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } rows, err := wechatmini.ListApps() if err != nil { c.jsonErr(500, 500, "获取应用列表失败") return } type appDTO struct { models.WechatMiniApp Configured bool `json:"configured"` // AppSecret 已配置 } list := make([]appDTO, 0, len(rows)) for _, r := range rows { dto := appDTO{WechatMiniApp: r, Configured: r.AppSecret != ""} list = append(list, dto) } c.ok(map[string]interface{}{"list": list}, "获取成功") } // SaveApp POST /platform/wechatMini/app/save 新增/更新应用 func (c *PlatformWechatMiniController) SaveApp() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } var p struct { ID uint64 `json:"id"` Name string `json:"name"` AppID string `json:"app_id"` AppSecret string `json:"app_secret"` QrcodeURL string `json:"qrcode_url"` Enabled int `json:"enabled"` AutoBind int `json:"auto_bind"` Remark string `json:"remark"` } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } if err := json.Unmarshal(raw, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } if strings.TrimSpace(p.Name) == "" { c.jsonErr(400, 400, "应用名称不能为空") return } id, err := wechatmini.SaveApp(wechatmini.SaveAppInput{ ID: p.ID, Name: p.Name, AppID: p.AppID, AppSecret: p.AppSecret, QrcodeURL: p.QrcodeURL, Enabled: p.Enabled == 1, AutoBind: p.AutoBind == 1, Remark: p.Remark, }) if err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(map[string]interface{}{"id": id}, "保存成功") } // DeleteApp POST /platform/wechatMini/app/delete/:id 软删应用并停用 func (c *PlatformWechatMiniController) DeleteApp() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效ID") return } if err := wechatmini.DeleteApp(id); err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(nil, "已删除") } // ============================================================= // 消息推送配置(小程序后台「开发管理 → 开发设置 → 消息推送」) // ============================================================= // GetPushConfig GET /platform/wechatMini/app/push?app_id=xxx // 返回该小程序的推送参数与「回调地址」(填到微信后台 URL),AESKey 掩码回显 func (c *PlatformWechatMiniController) GetPushConfig() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } appID := strings.TrimSpace(c.GetString("app_id")) if appID == "" { c.jsonErr(400, 400, "缺少 app_id") return } cfg, err := wechatmini.LoadPushConfigByAppID(appID) if err != nil { c.jsonErr(400, 400, "小程序不存在或消息推送未配置") return } c.ok(map[string]interface{}{ "app_id": cfg.AppID, "token": cfg.Token, "aes_key": wechatmp.MaskSecret(cfg.AESKey), "encrypt_mode": cfg.EncryptMode, "data_format": cfg.DataFormat, "push_reply": cfg.ReplyText, "callback_url": wechatmini.PushCallbackURL(), "configured": cfg.Token != "", }, "获取成功") } // SavePushConfig POST /platform/wechatMini/app/push/save func (c *PlatformWechatMiniController) SavePushConfig() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } var p struct { AppID string `json:"app_id"` Token string `json:"token"` AESKey string `json:"aes_key"` EncryptMode string `json:"encrypt_mode"` DataFormat string `json:"data_format"` PushReply string `json:"push_reply"` } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } if err := json.Unmarshal(raw, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } if strings.TrimSpace(p.AppID) == "" { c.jsonErr(400, 400, "缺少 app_id") return } if err := wechatmini.SavePushConfig(wechatmini.SavePushConfigInput{ AppID: p.AppID, Token: p.Token, AESKey: p.AESKey, EncryptMode: p.EncryptMode, DataFormat: p.DataFormat, ReplyText: p.PushReply, }); err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(map[string]interface{}{"callback_url": wechatmini.PushCallbackURL()}, "保存成功") } // GeneratePushSecret POST /platform/wechatMini/app/push/generate // 生成随机 Token 与 EncodingAESKey(43 位字母数字) func (c *PlatformWechatMiniController) GeneratePushSecret() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } c.ok(map[string]interface{}{ "token": wechatmini.GeneratePushToken(), "aes_key": wechatmini.GeneratePushAESKey(), }, "生成成功") } // ============================================================= // 订阅消息模板(小程序后台「功能 → 订阅消息」选用模板后在此登记) // ============================================================= // ListTemplates GET /platform/wechatMini/templates?app_id=xxx func (c *PlatformWechatMiniController) ListTemplates() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } appID := strings.TrimSpace(c.GetString("app_id")) rows, err := wechatmini.ListTemplates(appID) if err != nil { c.jsonErr(500, 500, "获取模板列表失败") return } c.ok(map[string]interface{}{"list": rows}, "获取成功") } // SaveTemplate POST /platform/wechatMini/template/save func (c *PlatformWechatMiniController) SaveTemplate() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } var p struct { ID uint64 `json:"id"` AppID string `json:"app_id"` Code string `json:"code"` TemplateID string `json:"template_id"` Title string `json:"title"` Page string `json:"page"` Fields string `json:"fields"` Enabled int `json:"enabled"` Remark string `json:"remark"` } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } if err := json.Unmarshal(raw, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } if err := wechatmini.SaveTemplate(wechatmini.TemplateInput{ ID: p.ID, AppID: p.AppID, Code: p.Code, TemplateID: p.TemplateID, Title: p.Title, Page: p.Page, Fields: p.Fields, Enabled: p.Enabled == 1, Remark: p.Remark, }); err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(nil, "保存成功") } // DeleteTemplate POST /platform/wechatMini/template/delete/:id func (c *PlatformWechatMiniController) DeleteTemplate() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效ID") return } if err := wechatmini.DeleteTemplate(id); err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(nil, "已删除") } // TestTemplate POST /platform/wechatMini/template/test // 给指定租户用户发一条测试订阅消息,验证模板与字段映射是否正确 func (c *PlatformWechatMiniController) TestTemplate() { if _, err := c.claims(); err != nil { code := identity.Status(err) c.jsonErr(code, code, err.Error()) return } var p struct { TenantUserID uint64 `json:"tenant_user_id"` AppID string `json:"app_id"` Code string `json:"code"` Title string `json:"title"` Content string `json:"content"` } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } if err := json.Unmarshal(raw, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } if p.TenantUserID == 0 { c.jsonErr(400, 400, "请指定测试用户(租户用户ID)") return } code := strings.TrimSpace(p.Code) if code == "" { code = wechatmini.SceneNotice } title := strings.TrimSpace(p.Title) if title == "" { title = "测试提醒" } content := strings.TrimSpace(p.Content) if content == "" { content = "这是一条订阅消息测试,收到说明配置正确。" } res := wechatmini.PushSubscribeToTenantUser(p.TenantUserID, code, strings.TrimSpace(p.AppID), map[string]string{ "title": title, "content": content, }) if res.Sent > 0 { c.ok(map[string]interface{}{"sent": res.Sent}, "已发送") return } if len(res.Errors) > 0 { msg := res.Errors[0].Error() if hint := wechatmini.ExplainSubscribeError(res.Errors[0]); hint != "" { msg = hint } c.jsonErr(400, 400, msg) return } c.jsonErr(400, 400, "未发送:该用户没有授权订阅,或模板未配置/未启用") }