feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers

This commit is contained in:
2026-04-02 11:19:50 +08:00
parent e59a77bc49
commit dcc1f186f8
298 changed files with 62603 additions and 0 deletions

View File

@@ -0,0 +1,258 @@
package providers
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
// WeChatProvider 微信OAuth提供者
type WeChatProvider struct {
AppID string
AppSecret string
Type string // "web" for 扫码登录, "mp" for 公众号, "mini" for 小程序
}
// WeChatAuthURLResponse 获取授权URL响应
type WeChatAuthURLResponse struct {
URL string `json:"url"`
State string `json:"state"`
Redirect string `json:"redirect,omitempty"`
}
// WeChatTokenResponse 微信Token响应
type WeChatTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
OpenID string `json:"openid"`
Scope string `json:"scope"`
UnionID string `json:"unionid,omitempty"`
}
// WeChatUserInfo 微信用户信息
type WeChatUserInfo struct {
OpenID string `json:"openid"`
Nickname string `json:"nickname"`
Sex int `json:"sex"` // 1男性, 2女性, 0未知
Province string `json:"province"`
City string `json:"city"`
Country string `json:"country"`
HeadImgURL string `json:"headimgurl"`
UnionID string `json:"unionid,omitempty"`
}
// WeChatErrorCode 微信错误码
type WeChatErrorCode struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
// NewWeChatProvider 创建微信OAuth提供者
func NewWeChatProvider(appID, appSecret, oAuthType string) *WeChatProvider {
return &WeChatProvider{
AppID: appID,
AppSecret: appSecret,
Type: oAuthType,
}
}
// GenerateState 生成随机状态码
func (w *WeChatProvider) GenerateState() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// GetAuthURL 获取微信授权URL
func (w *WeChatProvider) GetAuthURL(redirectURI, state string) (*WeChatAuthURLResponse, error) {
var authURL string
switch w.Type {
case "web":
// 微信扫码登录 (开放平台)
authURL = fmt.Sprintf(
"https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect",
w.AppID,
url.QueryEscape(redirectURI),
state,
)
case "mp":
// 微信公众号登录
authURL = fmt.Sprintf(
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect",
w.AppID,
url.QueryEscape(redirectURI),
state,
)
default:
return nil, fmt.Errorf("unsupported wechat oauth type: %s", w.Type)
}
return &WeChatAuthURLResponse{
URL: authURL,
State: state,
Redirect: redirectURI,
}, nil
}
// ExchangeCode 用授权码换取访问令牌
func (w *WeChatProvider) ExchangeCode(ctx context.Context, code string) (*WeChatTokenResponse, error) {
tokenURL := fmt.Sprintf(
"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
w.AppID,
w.AppSecret,
code,
)
req, err := http.NewRequestWithContext(ctx, "GET", tokenURL, nil)
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, err := readOAuthResponseBody(resp)
if err != nil {
return nil, fmt.Errorf("read response failed: %w", err)
}
// 检查是否返回错误
var errResp WeChatErrorCode
if err := json.Unmarshal(body, &errResp); err == nil && errResp.ErrCode != 0 {
return nil, fmt.Errorf("wechat api error: %d - %s", errResp.ErrCode, errResp.ErrMsg)
}
var tokenResp WeChatTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return nil, fmt.Errorf("parse token response failed: %w", err)
}
return &tokenResp, nil
}
// GetUserInfo 获取微信用户信息
func (w *WeChatProvider) GetUserInfo(ctx context.Context, accessToken, openID string) (*WeChatUserInfo, error) {
userInfoURL := fmt.Sprintf(
"https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN",
accessToken,
openID,
)
req, err := http.NewRequestWithContext(ctx, "GET", userInfoURL, nil)
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, err := readOAuthResponseBody(resp)
if err != nil {
return nil, fmt.Errorf("read response failed: %w", err)
}
// 检查是否返回错误
var errResp WeChatErrorCode
if err := json.Unmarshal(body, &errResp); err == nil && errResp.ErrCode != 0 {
return nil, fmt.Errorf("wechat api error: %d - %s", errResp.ErrCode, errResp.ErrMsg)
}
var userInfo WeChatUserInfo
if err := json.Unmarshal(body, &userInfo); err != nil {
return nil, fmt.Errorf("parse user info failed: %w", err)
}
return &userInfo, nil
}
// RefreshToken 刷新访问令牌
func (w *WeChatProvider) RefreshToken(ctx context.Context, refreshToken string) (*WeChatTokenResponse, error) {
refreshURL := fmt.Sprintf(
"https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s",
w.AppID,
refreshToken,
)
req, err := http.NewRequestWithContext(ctx, "GET", refreshURL, nil)
if err != nil {
return nil, fmt.Errorf("create request failed: %w", err)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, err := readOAuthResponseBody(resp)
if err != nil {
return nil, fmt.Errorf("read response failed: %w", err)
}
var errResp WeChatErrorCode
if err := json.Unmarshal(body, &errResp); err == nil && errResp.ErrCode != 0 {
return nil, fmt.Errorf("wechat api error: %d - %s", errResp.ErrCode, errResp.ErrMsg)
}
var tokenResp WeChatTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return nil, fmt.Errorf("parse token response failed: %w", err)
}
return &tokenResp, nil
}
// ValidateToken 验证访问令牌是否有效
func (w *WeChatProvider) ValidateToken(ctx context.Context, accessToken, openID string) (bool, error) {
validateURL := fmt.Sprintf(
"https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s",
accessToken,
openID,
)
req, err := http.NewRequestWithContext(ctx, "GET", validateURL, nil)
if err != nil {
return false, fmt.Errorf("create request failed: %w", err)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return false, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, err := readOAuthResponseBody(resp)
if err != nil {
return false, fmt.Errorf("read response failed: %w", err)
}
var result struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.Unmarshal(body, &result); err != nil {
return false, fmt.Errorf("parse response failed: %w", err)
}
return result.ErrCode == 0, nil
}