feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
This commit is contained in:
258
internal/auth/providers/wechat.go
Normal file
258
internal/auth/providers/wechat.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user