400 lines
10 KiB
Go
400 lines
10 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"crypto/sha1"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"github.com/redis/go-redis/v9"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"net/url"
|
||
"qnc-server/config"
|
||
"qnc-server/db"
|
||
"qnc-server/model/model"
|
||
"qnc-server/model/response"
|
||
"sort"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
type UserService struct {
|
||
}
|
||
|
||
// 全能查小程序Code获取openid Unionid
|
||
func (UserService *UserService) RequestWx(code string) (wxResponse response.WxLoginResp, err error) {
|
||
|
||
// 向微信发出登录请求
|
||
baseURL := config.ConfigData.System.WxLoginUrl
|
||
// 创建查询参数
|
||
params := url.Values{}
|
||
params.Add("appid", config.ConfigData.System.WxAppId)
|
||
params.Add("secret", config.ConfigData.System.WxSecret)
|
||
params.Add("js_code", code)
|
||
params.Add("grant_type", "authorization_code")
|
||
|
||
// 构建完整的请求 URL
|
||
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
|
||
|
||
// 发送 GET 请求
|
||
resp, err := http.Get(requestURL)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 读取响应体
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return
|
||
}
|
||
// 将响应体解析为结构体
|
||
err = json.Unmarshal(body, &wxResponse)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
if wxResponse.ErrCode != 0 {
|
||
err = errors.New(wxResponse.ErrMsg)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
func (UserService *UserService) RequestWxTyData(code string) (wxResponse response.WxLoginResp, err error) {
|
||
|
||
// 向微信发出登录请求
|
||
baseURL := config.ConfigData.System.WxTyDataLoginUrl
|
||
// 创建查询参数
|
||
params := url.Values{}
|
||
params.Add("appid", config.ConfigData.System.WxTyDataAppId)
|
||
params.Add("secret", config.ConfigData.System.WxTyDataSecret)
|
||
params.Add("js_code", code)
|
||
params.Add("grant_type", "authorization_code")
|
||
|
||
// 构建完整的请求 URL
|
||
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
|
||
|
||
// 发送 GET 请求
|
||
resp, err := http.Get(requestURL)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 读取响应体
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return
|
||
}
|
||
// 将响应体解析为结构体
|
||
err = json.Unmarshal(body, &wxResponse)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
if wxResponse.ErrCode != 0 {
|
||
err = errors.New(wxResponse.ErrMsg)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// Login 全能查 公众号 获取access_token
|
||
func (UserService *UserService) RequestWxH5(code string) (wxH5Response response.WxH5LoginResp, err error) {
|
||
|
||
// 向微信发出登录请求
|
||
baseURL := config.ConfigData.System.WxH5LoginUrl
|
||
// 创建查询参数
|
||
params := url.Values{}
|
||
params.Add("appid", config.ConfigData.System.WxH5AppId)
|
||
params.Add("secret", config.ConfigData.System.WxH5Secret)
|
||
params.Add("code", code)
|
||
params.Add("grant_type", "authorization_code")
|
||
|
||
// 构建完整的请求 URL
|
||
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
|
||
|
||
// 发送 GET 请求
|
||
resp, err := http.Get(requestURL)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 读取响应体
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = json.Unmarshal(body, &wxH5Response)
|
||
if err != nil {
|
||
return
|
||
}
|
||
log.Printf("H5-以Code获取access_token响应:%v", wxH5Response)
|
||
if wxH5Response.Errcode != 0 {
|
||
err = errors.New(wxH5Response.Errmsg)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// 刷新token
|
||
// access_token 2小时过期
|
||
// refresh_token 30天过期
|
||
//func (UserService *UserService) RefreshToken(openid string) (err error) {
|
||
//
|
||
//}
|
||
|
||
// snsapi_userinfo授权拉取用户信息
|
||
func (UserService *UserService) GetSnsUserInfo(accessToken string, openid string) (userinfoResp response.WeChatUserInfoResp, err error) {
|
||
//https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
|
||
// 向微信发出登录请求
|
||
baseURL := config.ConfigData.System.WxH5UserinfoUrl
|
||
// 创建查询参数
|
||
params := url.Values{}
|
||
params.Add("access_token", accessToken)
|
||
params.Add("openid", openid)
|
||
params.Add("lang", "zh_CN")
|
||
// 构建完整的请求 URL
|
||
requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode())
|
||
log.Printf("请求url: %s", requestURL)
|
||
// 发送 GET 请求
|
||
resp, err := http.Get(requestURL)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 读取响应体
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = json.Unmarshal(body, &userinfoResp)
|
||
if err != nil {
|
||
return
|
||
}
|
||
log.Printf("H5-snsapi_userinfo授权拉取用户信息响应:%v", userinfoResp)
|
||
if userinfoResp.Errcode != 0 {
|
||
err = errors.New(userinfoResp.Errmsg)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// 刷新token
|
||
// access_token 2小时过期
|
||
// refresh_token 30天过期
|
||
//func (UserService *UserService) RefreshToken(openid string) (err error) {
|
||
//
|
||
//}
|
||
|
||
// 注册用户,创建
|
||
func (UserService *UserService) Register(user model.User, authType model.AuthType, authIdentifier model.AuthIdentifier) (auth model.Authentication, err error) {
|
||
err = db.DB.Create(&user).Error
|
||
if err != nil {
|
||
return
|
||
}
|
||
auth = model.Authentication{
|
||
UserID: user.ID,
|
||
AuthType: authType,
|
||
OpenID: authIdentifier.OpenID,
|
||
UnionID: authIdentifier.UnionID,
|
||
Phone: authIdentifier.Phone,
|
||
}
|
||
err = db.DB.Create(&auth).Error
|
||
if err != nil {
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// MatchingAuthentications配对是否有相交的校验
|
||
func (UserService *UserService) MatchingAuthentications(authIdentifier model.AuthIdentifier) (authentications []model.Authentication, err error) {
|
||
query := db.DB
|
||
if authIdentifier.OpenID != "" {
|
||
query = query.Or("open_id = ?", authIdentifier.OpenID)
|
||
}
|
||
if authIdentifier.UnionID != "" {
|
||
query = query.Or("union_id = ?", authIdentifier.UnionID)
|
||
}
|
||
if authIdentifier.Phone != "" {
|
||
query = query.Or("phone = ?", authIdentifier.Phone)
|
||
}
|
||
err = query.Find(&authentications).Error
|
||
return
|
||
}
|
||
|
||
// CreateAuthentications 创建校验
|
||
func (UserService *UserService) CreateAuthentications(userid uint, authIdentifier model.AuthIdentifier, AuthType model.AuthType) (authentications model.Authentication, err error) {
|
||
authentications = model.Authentication{
|
||
UserID: userid,
|
||
AuthType: AuthType,
|
||
OpenID: authIdentifier.OpenID,
|
||
UnionID: authIdentifier.UnionID,
|
||
Phone: authIdentifier.Phone,
|
||
}
|
||
err = db.DB.Create(&authentications).Error
|
||
if err != nil {
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// GetUser 根据openid获取全能查用户记录
|
||
func (UserService *UserService) GetUser(id uint) (user *model.User, err error) {
|
||
err = db.DB.Where("id = ?", id).First(&user).Error
|
||
return
|
||
}
|
||
|
||
// GetUser 根据h5_openid获取全能查用户记录
|
||
func (UserService *UserService) GetUserByH5(h5Openid string) (user *model.User, err error) {
|
||
err = db.DB.Where("h5_openid = ?", h5Openid).First(&user).Error
|
||
return
|
||
}
|
||
|
||
// GetUser 根据unionid获取全能查用户记录
|
||
func (UserService *UserService) GetUserByUnionid(unionid string) (user *model.User, err error) {
|
||
err = db.DB.Where("unionid = ?", unionid).First(&user).Error
|
||
return
|
||
}
|
||
|
||
// GetUser 根据userid获取全能查用户记录
|
||
func (UserService *UserService) GetUserByUserid(userid uint) (user *model.User, err error) {
|
||
err = db.DB.Where("userid = ?", userid).First(&user).Error
|
||
return
|
||
}
|
||
|
||
// CreateUser 创建全能查用户
|
||
func (UserService *UserService) CreateUser(user *model.User) (err error) {
|
||
err = db.DB.Create(&user).Error
|
||
return err
|
||
}
|
||
|
||
// update 按unionid更新用户信息
|
||
func (UserService *UserService) UpdateUser(user *model.User) (err error) {
|
||
err = db.DB.Save(user).Error
|
||
return err
|
||
}
|
||
|
||
// h5 SDK 获取config部分
|
||
var (
|
||
mutex sync.Mutex
|
||
)
|
||
var ctx = context.Background()
|
||
|
||
// GetJsapiTicket 获取JSAPI Ticket
|
||
func (UserService *UserService) GetJsapiTicket() (string, error) {
|
||
|
||
jsapiTicket, err := db.RedisClient.Get(ctx, "JsapiTicket").Result()
|
||
if err != nil && !errors.Is(err, redis.Nil) {
|
||
return "", err
|
||
}
|
||
if jsapiTicket != "" {
|
||
return jsapiTicket, nil
|
||
}
|
||
mutex.Lock()
|
||
defer mutex.Unlock()
|
||
|
||
accessToken, err := UserService.getAccessToken()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
ticket, err := UserService.fetchJsapiTicket(accessToken)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
log.Printf("获取到新的JsapiTicket:%s", ticket)
|
||
jsapiTicket = ticket
|
||
|
||
err = db.RedisClient.Set(ctx, "JsapiTicket", jsapiTicket, 7200*time.Second).Err()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return jsapiTicket, nil
|
||
}
|
||
|
||
func (UserService *UserService) getAccessToken() (string, error) {
|
||
reqUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", config.ConfigData.System.WxH5AppId, config.ConfigData.System.WxH5Secret)
|
||
resp, err := http.Get(reqUrl)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
var result struct {
|
||
AccessToken string `json:"access_token"`
|
||
ExpiresIn int `json:"expires_in"`
|
||
Errcode int `json:"errcode"`
|
||
Errmsg string `json:"errmsg"`
|
||
}
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if result.Errcode != 0 {
|
||
log.Printf("获取AccessToken错误:%s", result.Errmsg)
|
||
return "", errors.New(result.Errmsg)
|
||
}
|
||
return result.AccessToken, nil
|
||
}
|
||
|
||
func (UserService *UserService) fetchJsapiTicket(accessToken string) (string, error) {
|
||
reqUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", accessToken)
|
||
resp, err := http.Get(reqUrl)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
var result struct {
|
||
Ticket string `json:"ticket"`
|
||
ExpiresIn int `json:"expires_in"`
|
||
Errcode int `json:"errcode"`
|
||
Errmsg string `json:"errmsg"`
|
||
}
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if result.Errcode != 0 {
|
||
log.Printf("获取JsapiTicket错误:%s", result.Errmsg)
|
||
return "", errors.New(result.Errmsg)
|
||
}
|
||
return result.Ticket, nil
|
||
}
|
||
|
||
// CreateSignature 创建签名
|
||
func (UserService *UserService) CreateSignature(ticket, nonceStr, timestamp, url string) string {
|
||
// 将参数按 key=value 格式拼接
|
||
params := map[string]string{
|
||
"jsapi_ticket": ticket,
|
||
"noncestr": nonceStr,
|
||
"timestamp": timestamp,
|
||
"url": url,
|
||
}
|
||
// 按照 key 的字典序排序
|
||
keys := make([]string, 0, len(params))
|
||
for key := range params {
|
||
keys = append(keys, key)
|
||
}
|
||
sort.Strings(keys)
|
||
|
||
// 拼接成一个字符串
|
||
str := ""
|
||
for _, key := range keys {
|
||
if str != "" {
|
||
str += "&"
|
||
}
|
||
str += fmt.Sprintf("%s=%s", key, params[key])
|
||
}
|
||
// 对字符串进行 SHA1 加密
|
||
h := sha1.New()
|
||
h.Write([]byte(str))
|
||
return hex.EncodeToString(h.Sum(nil))
|
||
}
|