Files
yunzerwebsiteallinone/go/controllers/platform_wechat_mini.go
T
2026-10-31 19:17:37 +08:00

443 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, "未发送:该用户没有授权订阅,或模板未配置/未启用")
}