Files
sundynix-pets/pets-be/internal/handler/wx_seccheck.go
T
Blizzard 67b97e4b38 feat(be+admin): 后台看板扩展 + 社区内容安全审核
看板(Recharts):
- 概览页加 DAU/WAU/MAU、今日/本月新增 KPI,新增趋势/活跃趋势/月活/留存曲线
- GET /admin/analytics 内存计算,活跃口径=当天有记录/发帖/评论,排除 bot

内容安全(微信官方 UGC):
- 文本 msg_sec_check 同步判、图片 media_check_async 异步查
- 帖子/评论加 pending/rejected 审核态,feed 只放 published
- 图片结果回调 /api/wx/sec-callback,JSON/XML + 明文模式,签名校验
- 检测不了/未发布时一律转待审核,走后台手动审核
- 后台帖子/评论审核页:修好看不到图(补 attachPostImages)、
  加状态筛选 + 通过/打回;新增评论审核状态接口

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 19:01:48 +08:00

77 lines
2.4 KiB
Go

package handler
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"encoding/xml"
"io"
"log"
"net/http"
"sort"
"strings"
"github.com/gin-gonic/gin"
)
// secCallbackMsg 兼容消息推送的两种数据格式:JSON 和 XML(字段名一致,标签两套都标)
type secCallbackMsg struct {
Event string `json:"Event" xml:"Event"`
MsgType string `json:"MsgType" xml:"MsgType"`
TraceID string `json:"trace_id" xml:"trace_id"`
Result struct {
Suggest string `json:"suggest" xml:"suggest"`
} `json:"result" xml:"result"`
}
// 图片异步安全检测的结果回调(微信「消息推送」服务器配置指向这里)。
// GET 校验服务器:把 echostr 原样返回
// POST 接收 wxa_media_check 结果:用 trace_id 找回帖子/评论并放出或打回
//
// 明文模式(消息加解密方式选「明文」)下 body 就是 JSON,直接解析;
// 若配了加密模式还需要 AES 解密,这里暂不处理(上线时消息推送选明文即可)。
// verifyWxSignature 校验 signature = sha1(sort(token, timestamp, nonce))
func (h *Handler) verifyWxSignature(c *gin.Context) bool {
token := h.cfg.WeChat.MsgToken
if token == "" {
return true // 没配 token(本地调试):不校验
}
sig := c.Query("signature")
parts := []string{token, c.Query("timestamp"), c.Query("nonce")}
sort.Strings(parts)
sum := sha1.Sum([]byte(strings.Join(parts, "")))
return hex.EncodeToString(sum[:]) == sig
}
// WxSecCallback GET+POST /api/wx/sec-callback
func (h *Handler) WxSecCallback(c *gin.Context) {
if !h.verifyWxSignature(c) {
c.String(http.StatusForbidden, "invalid signature")
return
}
if c.Request.Method == http.MethodGet {
c.String(http.StatusOK, c.Query("echostr")) // 服务器配置校验
return
}
raw, _ := io.ReadAll(c.Request.Body)
// 数据格式 JSON / XML 都兼容:以 '<' 开头当 XML,否则当 JSON
var msg secCallbackMsg
var err error
if trimmed := strings.TrimSpace(string(raw)); strings.HasPrefix(trimmed, "<") {
err = xml.Unmarshal(raw, &msg)
} else {
err = json.Unmarshal(raw, &msg)
}
if err != nil {
log.Printf("[warn] sec-callback 解析失败: %v, body=%s", err, string(raw))
c.String(http.StatusOK, "success") // 回 success 让微信别重推
return
}
if msg.Event == "wxa_media_check" && msg.TraceID != "" {
h.svc.ResolveMediaCheck(msg.TraceID, msg.Result.Suggest)
}
c.String(http.StatusOK, "success")
}