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 }