Files
ai_wht_wechat/go_backend/service/auth_service.go
2025-12-19 22:36:48 +08:00

215 lines
6.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}