公开完整前后端的代码

This commit is contained in:
Linus Torvalds
2026-02-26 19:22:38 +08:00
commit 193de8a34f
161 changed files with 17373 additions and 0 deletions

202
back/src/handler/wechat.go Normal file
View File

@@ -0,0 +1,202 @@
// 和微信有关的handler
package handler
import (
"crypto/rand"
"errors"
"fmt"
"log/slog"
"math/big"
"net/http"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
hutil "zsxyww.com/wts/handler/handlerUtilities"
"zsxyww.com/wts/handler/logic"
"zsxyww.com/wts/model/sqlc"
)
// GET: /api/v3p/wx
// POST: /api/v3p/wx
// reveive: WeChat Specificed Request
// return 200 on success,500 on error
// type: WeCaht Specificed JSON/XML Response
// this API is used to communicate with WeChat Server, Frontend developers are unneeded to care about it.
func WXEntry(i echo.Context) error {
c := i.(*hutil.WtsCtx)
id := i.Response().Header().Get(echo.HeaderXRequestID)
slog.Info("收到HTTP请求", "id", id, "URI", i.Request().URL.Path, "from", i.RealIP(), "method", i.Request().Method, "user_agent", i.Request().UserAgent())
slog.Debug("具体的请求信息", "id", id, "headers", i.Request().Header, "query_params", i.Request().URL.Query(), "body", i.Get("body"))
wc := c.WX.GetServer(i.Request(), i.Response().Writer)
//负责回复用户从公众号聊天栏发来的消息与推送的event
wc.SetMessageHandler(logic.WXMsgHandler(c))
err := wc.Serve()
if err != nil {
i.Logger().Error("wechat server error:", err)
i.String(500, "in: "+time.Now().String()+" wechat handler error,please view logs.")
return err
}
wc.Send()
return nil
}
// 执行微信的OAuth2.0授权流程,跳转到微信授权页面
// GET: /api/v3p/wx/auth
// receive: none
// return: 500 on error
// type: string on error,no return on success, passing a cookie for OAuth2.0 verification
// special: redirect to WeChat OAuth2.0 authorization page
func WXAuth(i echo.Context) error {
c := i.(*hutil.WtsCtx)
id := i.Response().Header().Get(echo.HeaderXRequestID)
slog.Info("收到HTTP请求", "id", id, "URI", i.Request().URL.Path, "from", i.RealIP(), "method", i.Request().Method, "user_agent", i.Request().UserAgent())
slog.Debug("具体的请求信息", "id", id, "headers", i.Request().Header, "query_params", i.Request().URL.Query(), "body", i.Get("body"))
// 构造请求参数
uri := "https://" + c.Cfg.WX.CallBackURL + "/api/v3p/wx/authsuccess"
s := "snsapi_userinfo"
state := genAuthState()
// 将随机生成的state存到前端用来在回调时同步校验
cookie := new(http.Cookie)
cookie.Name = "oauth_state"
cookie.Path = "/"
cookie.Value = state
cookie.Expires = time.Now().Add(5 * time.Minute)
cookie.HttpOnly = true
cookie.Secure = true
i.SetCookie(cookie)
// 重定向到微信授权页面
to, err := c.WX.GetOauth().GetRedirectURL(uri, s, state)
if err != nil {
return c.String(500, "生成WeChat OAuth2.0授权链接失败:"+err.Error())
}
return c.Redirect(http.StatusFound, to)
}
// GET: /api/v3p/wx/authsuccess
// receive: WeChat specificed OAuth2.0 callback parameters
// return: 400/500 on error,200 on success
// type: string on error, Redirect on success(Cookie containing JWT is set)
// this is automaticly accessed after OAuth2.0 authorization success.
func WXAuthSuccess(i echo.Context) error {
c := i.(*hutil.WtsCtx)
id := i.Response().Header().Get(echo.HeaderXRequestID)
slog.Info("收到HTTP请求", "id", id, "URI", i.Request().URL.Path, "from", i.RealIP(), "method", i.Request().Method, "user_agent", i.Request().UserAgent())
slog.Debug("具体的请求信息", "id", id, "headers", i.Request().Header, "query_params", i.Request().URL.Query(), "body", i.Get("body"))
// 校验微信返回
s := i.QueryParam("state")
if s == "" {
return c.String(http.StatusBadRequest, "未获取到授权 state")
}
cookie, err := c.Cookie("oauth_state")
if err != nil {
return c.String(http.StatusBadRequest, "无法获取 state cookie请求可能已过期或非法")
}
if s != cookie.Value {
return c.String(http.StatusForbidden, "state 参数校验失败")
}
cookie.Expires = time.Now().Add(-1 * time.Hour)
i.SetCookie(cookie)
code := i.QueryParam("code")
if code == "" {
return c.String(http.StatusBadRequest, "未获取到授权 code")
}
res, err := c.WX.GetOauth().GetUserAccessToken(code)
if err != nil {
return c.String(http.StatusInternalServerError, fmt.Sprintf("换取 access_token 失败: %v", err))
}
openID := res.OpenID
userInfo, err := c.WX.GetOauth().GetUserInfo(res.AccessToken, openID, "zh_CN")
if err != nil {
return c.String(http.StatusInternalServerError, fmt.Sprintf("获取用户信息失败: %v", err))
}
// 查询数据库看看有没有OpenID对应的用户
ctx := i.Request().Context()
u := sqlc.WtsVUser{}
var reg bool
if err = c.DB.DoQuery(ctx, openID, func(q *sqlc.Queries) error {
u, err = q.GetUserByWX(ctx, openID)
if err != nil {
switch true {
case errors.Is(err, pgx.ErrNoRows):
reg = false
return nil
default:
return err
}
}
reg = true
return nil
}); err != nil {
return c.String(500, "Database Query Error,Transaction Rollbacked:"+err.Error())
}
//生成对应的JWT
var t string
if !reg {
t, err = hutil.NewWtsJWT(openID, "", sqlc.WtsAccessUnregistered, userInfo.Nickname, userInfo.HeadImgURL, "用户", 30)
if err != nil {
return c.String(500, "生成JWT(临时)失败:"+err.Error())
}
} else {
var access sqlc.WtsAccess
if u.Op && u.Access.Valid {
access = u.Access.WtsAccess
} else {
access = sqlc.WtsAccessUser
}
t, err = hutil.NewWtsJWT(u.Wx, u.Sid.String, access, userInfo.Nickname, userInfo.HeadImgURL, emptyText1(u.Name), 95)
if err != nil {
return c.String(500, "JWT生成失败"+err.Error())
}
}
// 将JWT写入Cookie后端不从cookie读取JWT前端应该立即将该cookie存储到localStorage并通过请求头传递JWT
jwt := new(http.Cookie)
jwt.Name = "jwt"
jwt.Value = t
jwt.Expires = time.Now().Add(5 * time.Minute)
jwt.HttpOnly = false
jwt.Secure = true
jwt.Path = "/"
i.SetCookie(jwt)
return c.Redirect(http.StatusFound, c.Cfg.FrontEnd.OnAuthSuccess)
}
func genAuthState() string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result := [16]byte{}
for i := 0; i < 16; i++ {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
result[i] = charset[num.Int64()]
}
return string(result[:])
}
func emptyText1(t pgtype.Text) string {
if !t.Valid {
return "你是谁?"
}
return t.String
}