first commit
This commit is contained in:
214
go_backend/service/auth_service.go
Normal file
214
go_backend/service/auth_service.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"ai_xhs/config"
|
||||
"ai_xhs/database"
|
||||
"ai_xhs/models"
|
||||
"ai_xhs/utils"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AuthService struct{}
|
||||
|
||||
func NewAuthService() *AuthService {
|
||||
return &AuthService{}
|
||||
}
|
||||
|
||||
// 微信手机号响应
|
||||
type WxPhoneResponse struct {
|
||||
PhoneInfo struct {
|
||||
PhoneNumber string `json:"phoneNumber"`
|
||||
PurePhoneNumber string `json:"purePhoneNumber"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
} `json:"phone_info"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
// 微信登录响应
|
||||
type WxLoginResponse struct {
|
||||
OpenID string `json:"openid"`
|
||||
SessionKey string `json:"session_key"`
|
||||
UnionID string `json:"unionid"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
// WechatLogin 微信小程序登录
|
||||
func (s *AuthService) WechatLogin(code string, phone string, phoneCode string) (string, *models.User, error) {
|
||||
// 1. 调用微信API验证code
|
||||
// 注意:需要在配置文件中添加小程序的AppID和AppSecret
|
||||
appID := config.AppConfig.Wechat.AppID
|
||||
appSecret := config.AppConfig.Wechat.AppSecret
|
||||
|
||||
// 调试日志:打印配置信息
|
||||
log.Printf("[微信登录] AppID: %s, AppSecret: %s (长度:%d)", appID, appSecret, len(appSecret))
|
||||
|
||||
// 如果没有配置微信AppID,使用手机号登录逻辑
|
||||
if appID == "" || appSecret == "" {
|
||||
if phone == "" {
|
||||
// 没有配置微信且没有手机号,使用默认员工ID=1
|
||||
return s.loginByEmployeeID(1)
|
||||
}
|
||||
// 使用手机号登录
|
||||
return s.PhoneLogin(phone)
|
||||
}
|
||||
|
||||
// 调用微信API
|
||||
url := fmt.Sprintf(
|
||||
"https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
|
||||
appID, appSecret, code,
|
||||
)
|
||||
|
||||
// 调试日志:打印请求URL(隐藏密钥)
|
||||
log.Printf("[微信登录] 请求URL: https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=***&js_code=%s&grant_type=authorization_code", appID, code)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("调用微信API失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 调试日志:打印微信返回的原始响应
|
||||
log.Printf("[微信登录] 微信API响应: %s", string(body))
|
||||
|
||||
var wxResp WxLoginResponse
|
||||
if err := json.Unmarshal(body, &wxResp); err != nil {
|
||||
return "", nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
if wxResp.ErrCode != 0 {
|
||||
return "", nil, fmt.Errorf("微信登录失败: %s", wxResp.ErrMsg)
|
||||
}
|
||||
|
||||
// 1.5 如果有 phoneCode,调用微信API获取手机号
|
||||
if phoneCode != "" {
|
||||
accessTokenURL := fmt.Sprintf(
|
||||
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
|
||||
appID, appSecret,
|
||||
)
|
||||
|
||||
// 获取 access_token
|
||||
accessTokenResp, err := http.Get(accessTokenURL)
|
||||
if err != nil {
|
||||
log.Printf("获取access_token失败: %v", err)
|
||||
} else {
|
||||
defer accessTokenResp.Body.Close()
|
||||
accessTokenBody, _ := io.ReadAll(accessTokenResp.Body)
|
||||
|
||||
var tokenResult struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(accessTokenBody, &tokenResult); err == nil && tokenResult.AccessToken != "" {
|
||||
// 获取手机号
|
||||
phoneURL := fmt.Sprintf(
|
||||
"https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s",
|
||||
tokenResult.AccessToken,
|
||||
)
|
||||
|
||||
phoneReqBody := map[string]string{"code": phoneCode}
|
||||
phoneReqJSON, _ := json.Marshal(phoneReqBody)
|
||||
|
||||
phoneResp, err := http.Post(phoneURL, "application/json", bytes.NewBuffer(phoneReqJSON))
|
||||
if err == nil {
|
||||
defer phoneResp.Body.Close()
|
||||
phoneBody, _ := io.ReadAll(phoneResp.Body)
|
||||
|
||||
var phoneResult WxPhoneResponse
|
||||
if err := json.Unmarshal(phoneBody, &phoneResult); err == nil && phoneResult.ErrCode == 0 {
|
||||
// 获取手机号成功,覆盖 phone 参数
|
||||
phone = phoneResult.PhoneInfo.PurePhoneNumber
|
||||
log.Printf("[微信登录] 获取手机号成功: %s", phone)
|
||||
} else {
|
||||
log.Printf("[微信登录] 获取手机号失败: %s", string(phoneBody))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 根据OpenID查找或创建员工
|
||||
var employee models.User
|
||||
|
||||
// 优先通过OpenID查找(注意:使用IS NOT NULL过滤空值)
|
||||
result := database.DB.Where("wechat_openid = ? AND wechat_openid IS NOT NULL", wxResp.OpenID).First(&employee)
|
||||
|
||||
if result.Error != nil {
|
||||
// OpenID不存在,需要绑定OpenID
|
||||
if phone == "" {
|
||||
return "", nil, errors.New("首次登录请提供手机号")
|
||||
}
|
||||
|
||||
// 通过手机号查找员工
|
||||
result = database.DB.Where("phone = ? AND status = ?", phone, "active").First(&employee)
|
||||
if result.Error != nil {
|
||||
return "", nil, errors.New("员工不存在,请联系管理员添加")
|
||||
}
|
||||
|
||||
// 绑定OpenID和UnionID(使用指针)
|
||||
employee.WechatOpenID = &wxResp.OpenID
|
||||
if wxResp.UnionID != "" {
|
||||
employee.WechatUnionID = &wxResp.UnionID
|
||||
}
|
||||
database.DB.Save(&employee)
|
||||
}
|
||||
|
||||
// 3. 生成JWT token
|
||||
token, err := utils.GenerateToken(employee.ID)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("生成token失败: %v", err)
|
||||
}
|
||||
|
||||
return token, &employee, nil
|
||||
}
|
||||
|
||||
// PhoneLogin 手机号登录(用于测试或无微信配置时)
|
||||
func (s *AuthService) PhoneLogin(phone string) (string, *models.User, error) {
|
||||
var employee models.User
|
||||
|
||||
// 查找员工
|
||||
result := database.DB.Where("phone = ? AND status = ?", phone, "active").First(&employee)
|
||||
if result.Error != nil {
|
||||
return "", nil, errors.New("员工不存在或已被禁用")
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, err := utils.GenerateToken(employee.ID)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("生成token失败: %v", err)
|
||||
}
|
||||
|
||||
return token, &employee, nil
|
||||
}
|
||||
|
||||
// loginByEmployeeID 通过员工ID登录(内部方法)
|
||||
func (s *AuthService) loginByEmployeeID(employeeID int) (string, *models.User, error) {
|
||||
var employee models.User
|
||||
|
||||
result := database.DB.Where("id = ? AND status = ?", employeeID, "active").First(&employee)
|
||||
if result.Error != nil {
|
||||
return "", nil, errors.New("员工不存在或已被禁用")
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, err := utils.GenerateToken(employee.ID)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("生成token失败: %v", err)
|
||||
}
|
||||
|
||||
return token, &employee, nil
|
||||
}
|
||||
Reference in New Issue
Block a user