package controllers import ( "encoding/json" "fmt" "io" "strings" "time" "server/models" "server/pkg/jwtutil" "server/services/wechatmp" beego "github.com/beego/beego/v2/server/web" ) // ============================================================= // 租户端「微信公众号绑定」: // 扫码关注公众号 → 公众号被动回复验证码 → 本端输入验证码完成绑定 // 绑定后(公众号认证后)可接收平台/租户侧的提醒、公告推送 // ============================================================= // BackendWechatMpController 租户端微信公众号绑定 type BackendWechatMpController struct { beego.Controller } func (c *BackendWechatMpController) claims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { return nil, fmt.Errorf("认证信息格式错误") } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, fmt.Errorf("无效的token") } if claims.UserType != "backend" && claims.UserType != "app" { return nil, fmt.Errorf("无权访问") } return claims, nil } func (c *BackendWechatMpController) 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 *BackendWechatMpController) 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() } func (c *BackendWechatMpController) readBody(out interface{}) bool { raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return false } if err := json.Unmarshal(raw, out); err != nil { c.jsonErr(400, 400, "参数错误") return false } return true } // BindStart POST /backend/wechatMp/bind/start func (c *BackendWechatMpController) BindStart() { claims, err := c.claims() if err != nil { c.jsonErr(401, 401, err.Error()) return } uid := uint64(claims.UserID) tid := uint64(claims.TenantId) if uid == 0 || tid == 0 { c.jsonErr(401, 401, "未获取到用户信息") return } scene, _, qrURL, expireAt, err := wechatmp.CreateVerifyCode(models.WechatBindTypeTenantUser, uid, tid, 0) if err != nil { c.jsonErr(400, 400, "生成二维码失败:"+err.Error()) return } c.ok(map[string]interface{}{ "scene": scene, "qrcode_url": qrURL, "expire_at": expireAt, "expires_in": int(time.Until(expireAt).Seconds()), }, "生成成功") } // BindStatus GET /backend/wechatMp/bind/status?scene=xxx func (c *BackendWechatMpController) BindStatus() { claims, err := c.claims() if err != nil { c.jsonErr(401, 401, err.Error()) return } scene := strings.TrimSpace(c.GetString("scene")) row, err := wechatmp.GetVerifyCode(scene) if err != nil { c.jsonErr(404, 404, err.Error()) return } if row.BindType != models.WechatBindTypeTenantUser || row.BindID != uint64(claims.UserID) { c.jsonErr(403, 403, "该二维码不是当前账号发起,请重新获取") return } c.ok(map[string]interface{}{ "status": row.Status, "expire_at": row.ExpireAt, }, "获取成功") } // BindConfirm POST /backend/wechatMp/bind/confirm {scene, code} func (c *BackendWechatMpController) BindConfirm() { claims, err := c.claims() if err != nil { c.jsonErr(401, 401, err.Error()) return } var p struct { Scene string `json:"scene"` Code string `json:"code"` } if !c.readBody(&p) { return } if strings.TrimSpace(p.Code) == "" { c.jsonErr(400, 400, "请输入公众号返回的验证码") return } var follower *models.WechatMpFollower if strings.TrimSpace(p.Scene) == "" { // 未展示二维码(在公众号内发送「验证码」获取):按验证码认领待认领会话 follower, err = wechatmp.ConfirmVerifyCodeByCode( models.WechatBindTypeTenantUser, uint64(claims.UserID), uint64(claims.TenantId), p.Code, ) } else { follower, err = wechatmp.ConfirmVerifyCode( models.WechatBindTypeTenantUser, uint64(claims.UserID), uint64(claims.TenantId), p.Scene, p.Code, ) } if err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(map[string]interface{}{ "openid": follower.OpenID, "nickname": follower.Nickname, "bind_time": follower.BindTime, }, "绑定成功") } // MyBinding GET /backend/wechatMp/my func (c *BackendWechatMpController) MyBinding() { claims, err := c.claims() if err != nil { c.jsonErr(401, 401, err.Error()) return } follower, err := wechatmp.GetFollowerByUser(models.WechatBindTypeTenantUser, uint64(claims.UserID)) if err != nil { c.jsonErr(500, 500, "查询失败:"+err.Error()) return } if follower == nil || follower.BindTid != uint64(claims.TenantId) { c.ok(map[string]interface{}{"bound": false}, "获取成功") return } c.ok(map[string]interface{}{ "bound": true, "openid": follower.OpenID, "nickname": follower.Nickname, "avatar": follower.Avatar, "subscribe": follower.Subscribe, "bind_time": follower.BindTime, "subscribe_time": follower.SubscribeTime, }, "获取成功") } // Unbind POST /backend/wechatMp/unbind func (c *BackendWechatMpController) Unbind() { claims, err := c.claims() if err != nil { c.jsonErr(401, 401, err.Error()) return } if err := wechatmp.UnbindByUser(models.WechatBindTypeTenantUser, uint64(claims.UserID)); err != nil { c.jsonErr(500, 500, "解绑失败:"+err.Error()) return } c.ok(nil, "已解绑") }