commit
This commit is contained in:
@@ -79,7 +79,8 @@ func (s *EmployeeService) SendXHSCode(phone string, employeeID int) error {
|
||||
return errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
log.Printf("[发送验证码] 调用Python HTTP服务: %s", url)
|
||||
log.Printf("[发送验证码] 调用Python HTTP服务: %s, 请求参数: phone=%s", url, phone)
|
||||
startTime := time.Now()
|
||||
|
||||
// 发送HTTP POST请求,增加超时控制(60秒)
|
||||
client := &http.Client{
|
||||
@@ -111,7 +112,7 @@ func (s *EmployeeService) SendXHSCode(phone string, employeeID int) error {
|
||||
return errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
log.Printf("[发送验证码] Python服务响应状态: %d", resp.StatusCode)
|
||||
log.Printf("[发送验证码] Python服务响应: 状态码=%d, 耗时=%.2fs", resp.StatusCode, time.Since(startTime).Seconds())
|
||||
|
||||
// 解析响应(FastAPI返回格式: {code, message, data})
|
||||
var apiResponse struct {
|
||||
@@ -276,7 +277,7 @@ func (s *EmployeeService) UpdateProfile(employeeID int, nickname, email, avatar
|
||||
}
|
||||
|
||||
// BindXHS 绑定小红书账号(异步处理,立即返回)
|
||||
func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string, error) {
|
||||
func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code, sessionID string) (string, error) {
|
||||
if code == "" {
|
||||
return "", errors.New("验证码不能为空")
|
||||
}
|
||||
@@ -297,15 +298,15 @@ func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string
|
||||
}
|
||||
|
||||
// 异步执行绑定流程
|
||||
go s.asyncBindXHS(employeeID, xhsPhone, code)
|
||||
go s.asyncBindXHS(employeeID, xhsPhone, code, sessionID)
|
||||
|
||||
// 立即返回成功,告知前端正在处理
|
||||
log.Printf("绑定小红书 - 用户%d - 异步任务已启动", employeeID)
|
||||
log.Printf("绑定小红书 - 用户%d - 异步任务已启动 (session_id=%s)", employeeID, sessionID)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// asyncBindXHS 异步执行小红书绑定流程
|
||||
func (s *EmployeeService) asyncBindXHS(employeeID int, xhsPhone, code string) {
|
||||
func (s *EmployeeService) asyncBindXHS(employeeID int, xhsPhone, code, sessionID string) {
|
||||
ctx := context.Background()
|
||||
cacheService := NewCacheService()
|
||||
|
||||
@@ -340,8 +341,8 @@ func (s *EmployeeService) asyncBindXHS(employeeID int, xhsPhone, code string) {
|
||||
}
|
||||
// err == gorm.ErrRecordNotFound 表示该手机号未被绑定,可以继续
|
||||
|
||||
// 调用Python服务进行验证码验证和登录
|
||||
loginResult, err := s.callPythonLogin(xhsPhone, code)
|
||||
// 调用Python服务进行验证码验证和登录,传递session_id
|
||||
loginResult, err := s.callPythonLogin(xhsPhone, code, sessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("小红书登录失败: %w", err)
|
||||
}
|
||||
@@ -608,7 +609,7 @@ func (s *EmployeeService) GetBindXHSStatus(employeeID int) (map[string]interface
|
||||
}
|
||||
|
||||
// callPythonLogin 调用Python HTTP服务完成小红书登录(优化:使用浏览器池)
|
||||
func (s *EmployeeService) callPythonLogin(phone, code string) (*PythonLoginResponse, error) {
|
||||
func (s *EmployeeService) callPythonLogin(phone, code, sessionID string) (*PythonLoginResponse, error) {
|
||||
// 从配置获取Python服务地址
|
||||
pythonServiceURL := config.AppConfig.XHS.PythonServiceURL
|
||||
if pythonServiceURL == "" {
|
||||
@@ -621,6 +622,7 @@ func (s *EmployeeService) callPythonLogin(phone, code string) (*PythonLoginRespo
|
||||
"phone": phone,
|
||||
"code": code,
|
||||
"country_code": "+86",
|
||||
"session_id": sessionID, // 关键:传递session_id用于复用浏览器
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(requestData)
|
||||
@@ -628,7 +630,7 @@ func (s *EmployeeService) callPythonLogin(phone, code string) (*PythonLoginRespo
|
||||
return nil, fmt.Errorf("序列化请求数据失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("[绑定小红书] 调用Python HTTP服务: %s", url)
|
||||
log.Printf("[绑定小红书] 调用Python HTTP服务: %s, session_id=%s", url, sessionID)
|
||||
|
||||
// 发送HTTP POST请求
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
|
||||
@@ -2198,3 +2200,388 @@ func (s *EmployeeService) RepublishRecord(employeeID int, recordID int) (string,
|
||||
|
||||
return publishLink, nil
|
||||
}
|
||||
|
||||
// SaveQRCodeLogin 保存扫码登录的绑定信息
|
||||
// 复用BindXHS的保存逻辑,但不需要调用Python后端,直接保存数据
|
||||
func (s *EmployeeService) SaveQRCodeLogin(employeeID int, cookiesFull []interface{}, userInfo map[string]interface{}, loginState map[string]interface{}) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 查询用户信息
|
||||
var employee models.User
|
||||
if err := database.DB.First(&employee, employeeID).Error; err != nil {
|
||||
return fmt.Errorf("获取用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 优先使用 login_state(完整登录状态),如果没有则降级使用cookies
|
||||
var loginStateJSON string
|
||||
|
||||
if len(loginState) > 0 {
|
||||
// 新版:使用完整的login_state(包含cookies + localStorage + sessionStorage)
|
||||
loginStateBytes, err := json.Marshal(loginState)
|
||||
if err == nil {
|
||||
loginStateJSON = string(loginStateBytes)
|
||||
log.Printf("扫码登录 - 用户%d - 完整LoginState长度: %d", employeeID, len(loginStateJSON))
|
||||
} else {
|
||||
log.Printf("扫码登录 - 用户%d - 序列化login_state失败: %v", employeeID, err)
|
||||
}
|
||||
} else if len(cookiesFull) > 0 {
|
||||
// 降级:使用旧版本的 cookies_full
|
||||
log.Printf("扫码登录 - 用户%d - 警告: 未找到login_state,降级使用cookies", employeeID)
|
||||
cookiesBytes, err := json.Marshal(cookiesFull)
|
||||
if err == nil {
|
||||
loginStateJSON = string(cookiesBytes)
|
||||
log.Printf("扫码登录 - 用户%d - Cookie长度: %d", employeeID, len(loginStateJSON))
|
||||
}
|
||||
}
|
||||
|
||||
if loginStateJSON == "" {
|
||||
log.Printf("扫码登录 - 用户%d - 错误: 未能获取到任何登录数据", employeeID)
|
||||
return errors.New("登录成功但未能获取到登录数据,请重试")
|
||||
}
|
||||
|
||||
// 提取小红书账号昵称
|
||||
xhsNickname := "小红书用户"
|
||||
xhsPhone := "" // 扫码登录没有手机号
|
||||
|
||||
if nickname, ok := userInfo["nickname"].(string); ok && nickname != "" {
|
||||
xhsNickname = nickname
|
||||
} else if username, ok := userInfo["username"].(string); ok && username != "" {
|
||||
xhsNickname = username
|
||||
}
|
||||
|
||||
// 尝试从 userInfo 提取 red_id 作为 phone
|
||||
if redID, ok := userInfo["red_id"].(string); ok && redID != "" {
|
||||
xhsPhone = redID
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// 开启事务
|
||||
tx := database.DB.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// 创建或更新 ai_authors 表的小红书账号记录
|
||||
log.Printf("扫码登录 - 用户%d - 开始创建或更新作者记录", employeeID)
|
||||
|
||||
author := models.Author{
|
||||
EnterpriseID: employee.EnterpriseID,
|
||||
CreatedUserID: employeeID,
|
||||
Phone: employee.Phone,
|
||||
AuthorName: xhsNickname,
|
||||
XHSCookie: loginStateJSON,
|
||||
XHSPhone: xhsPhone,
|
||||
XHSAccount: xhsNickname,
|
||||
BoundAt: &now,
|
||||
Channel: 1, // 1=小红书
|
||||
Status: "active",
|
||||
}
|
||||
|
||||
// 查询是否已存在记录
|
||||
var existingAuthor models.Author
|
||||
err := database.DB.Where("created_user_id = ? AND enterprise_id = ? AND channel = 1",
|
||||
employeeID, employee.EnterpriseID).First(&existingAuthor).Error
|
||||
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 创建新记录
|
||||
if err := tx.Create(&author).Error; err != nil {
|
||||
tx.Rollback()
|
||||
log.Printf("扫码登录 - 用户%d - 创建作者记录失败: %v", employeeID, err)
|
||||
return fmt.Errorf("创建作者记录失败: %w", err)
|
||||
}
|
||||
log.Printf("扫码登录 - 用户%d - 创建作者记录成功", employeeID)
|
||||
} else {
|
||||
// 更新现有记录
|
||||
if err := tx.Model(&models.Author{}).Where(
|
||||
"created_user_id = ? AND enterprise_id = ? AND channel = 1",
|
||||
employeeID, employee.EnterpriseID,
|
||||
).Updates(map[string]interface{}{
|
||||
"author_name": xhsNickname,
|
||||
"xhs_cookie": loginStateJSON,
|
||||
"xhs_phone": xhsPhone,
|
||||
"xhs_account": xhsNickname,
|
||||
"bound_at": &now,
|
||||
"status": "active",
|
||||
"phone": employee.Phone,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
log.Printf("扫码登录 - 用户%d - 更新作者记录失败: %v", employeeID, err)
|
||||
return fmt.Errorf("更新作者记录失败: %w", err)
|
||||
}
|
||||
log.Printf("扫码登录 - 用户%d - 更新作者记录成功", employeeID)
|
||||
}
|
||||
|
||||
// 更新 ai_users 表的绑定标识
|
||||
if err := tx.Model(&employee).Update("is_bound_xhs", 1).Error; err != nil {
|
||||
tx.Rollback()
|
||||
log.Printf("扫码登录 - 用户%d - 更新用户绑定标识失败: %v", employeeID, err)
|
||||
return fmt.Errorf("更新用户绑定标识失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("扫码登录 - 用户%d - 数据库更新成功", employeeID)
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
log.Printf("扫码登录 - 用户%d - 事务提交失败: %v", employeeID, err)
|
||||
return fmt.Errorf("提交事务失败: %w", err)
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
cacheService := NewCacheService()
|
||||
if err := cacheService.ClearUserRelatedCache(ctx, employeeID); err != nil {
|
||||
log.Printf("清除缓存失败: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("扫码登录 - 用户%d - 绑定成功 - 账号: %s", employeeID, xhsNickname)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartQRCodeLogin 启动扫码登录,转发到Python服务
|
||||
func (s *EmployeeService) StartQRCodeLogin(employeeID int) (map[string]interface{}, error) {
|
||||
log.Printf("[启动扫码登录] 用户ID: %d", employeeID)
|
||||
|
||||
// 从配置获取Python服务地址
|
||||
pythonServiceURL := config.AppConfig.XHS.PythonServiceURL
|
||||
if pythonServiceURL == "" {
|
||||
pythonServiceURL = "http://localhost:8000"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/xhs/qrcode/start", pythonServiceURL)
|
||||
log.Printf("[启动扫码登录] 调用Python服务: %s", url)
|
||||
|
||||
// 发送HTTP POST请求,启动扫码需要启动浏览器+加载页面+获取二维码,设置90秒超时
|
||||
client := &http.Client{
|
||||
Timeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
log.Printf("[启动扫码登录] 创建请求失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[启动扫码登录] 调用Python服务失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[启动扫码登录] 读取响应失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 解析响应,直接返回完整响应体
|
||||
var apiResponse map[string]interface{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
log.Printf("[启动扫码登录] 解析响应失败: %v, body: %s", err, string(body))
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 检查Python响应的code字段
|
||||
if code, ok := apiResponse["code"].(float64); ok && code != 0 {
|
||||
if msg, ok := apiResponse["message"].(string); ok {
|
||||
log.Printf("[启动扫码登录] 失败: %s", msg)
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
return nil, errors.New("启动失败")
|
||||
}
|
||||
|
||||
log.Printf("[启动扫码登录] 成功")
|
||||
// 返回完整的Python响应,保持code=0格式
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
||||
// GetQRCodeStatus 获取扫码状态,转发到Python服务
|
||||
func (s *EmployeeService) GetQRCodeStatus(employeeID int, sessionID string) (map[string]interface{}, error) {
|
||||
// 从配置获取Python服务地址
|
||||
pythonServiceURL := config.AppConfig.XHS.PythonServiceURL
|
||||
if pythonServiceURL == "" {
|
||||
pythonServiceURL = "http://localhost:8000"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/xhs/qrcode/status", pythonServiceURL)
|
||||
requestData := map[string]string{
|
||||
"session_id": sessionID,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(requestData)
|
||||
if err != nil {
|
||||
log.Printf("[扫码状态] 序列化请求数据失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 发送HTTP POST请求
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Printf("[扫码状态] 创建请求失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[扫码状态] 调用Python服务失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[扫码状态] 读取响应失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 解析响应,直接返回完整响应体
|
||||
var apiResponse map[string]interface{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
log.Printf("[扫码状态] 解析响应失败: %v, body: %s", err, string(body))
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 扫码状态接口可能返回 code=2 表示 session 失效
|
||||
// 这种情况不算错误,直接返回给前端处理
|
||||
// 直接返回完整的Python响应,让前端自己判断
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
||||
// RefreshQRCode 刷新二维码,转发到Python服务
|
||||
func (s *EmployeeService) RefreshQRCode(employeeID int, sessionID string) (map[string]interface{}, error) {
|
||||
log.Printf("[刷新二维码] 用户ID: %d, SessionID: %s", employeeID, sessionID)
|
||||
|
||||
// 从配置获取Python服务地址
|
||||
pythonServiceURL := config.AppConfig.XHS.PythonServiceURL
|
||||
if pythonServiceURL == "" {
|
||||
pythonServiceURL = "http://localhost:8000"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/xhs/qrcode/refresh", pythonServiceURL)
|
||||
requestData := map[string]string{
|
||||
"session_id": sessionID,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(requestData)
|
||||
if err != nil {
|
||||
log.Printf("[刷新二维码] 序列化请求数据失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 发送HTTP POST请求,刷新二维码需要重新加载页面,设置60秒超时
|
||||
client := &http.Client{
|
||||
Timeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Printf("[刷新二维码] 创建请求失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[刷新二维码] 调用Python服务失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[刷新二维码] 读取响应失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 解析响应,直接返回完整响应体
|
||||
var apiResponse map[string]interface{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
log.Printf("[刷新二维码] 解析响应失败: %v, body: %s", err, string(body))
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 刷新接口可能返回 code=3 表示需要重启
|
||||
// 这种情况不算错误,直接返回给前端处理
|
||||
// 直接返回完整的Python响应,让前端自己判断
|
||||
log.Printf("[刷新二维码] 成功")
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
||||
// CancelQRCodeLogin 取消扫码登录,转发到Python服务
|
||||
func (s *EmployeeService) CancelQRCodeLogin(sessionID string) (map[string]interface{}, error) {
|
||||
log.Printf("[取消扫码] SessionID: %s", sessionID)
|
||||
|
||||
// 从配置获取Python服务地址
|
||||
pythonServiceURL := config.AppConfig.XHS.PythonServiceURL
|
||||
if pythonServiceURL == "" {
|
||||
pythonServiceURL = "http://localhost:8000"
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/api/xhs/qrcode/cancel", pythonServiceURL)
|
||||
requestData := map[string]string{
|
||||
"session_id": sessionID,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(requestData)
|
||||
if err != nil {
|
||||
log.Printf("[取消扫码] 序列化请求数据失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
|
||||
// 发送HTTP POST请求
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second, // 短超时,取消操作应该很快
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Printf("[取消扫码] 创建请求失败: %v", err)
|
||||
return nil, errors.New("网络错误,请稍后重试")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[取消扫码] 调用Python服务失败: %v", err)
|
||||
// 取消失败也返回成功,不影响用户体验
|
||||
return map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "已取消扫码登录",
|
||||
}, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[取消扫码] 读取响应失败: %v", err)
|
||||
return map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "已取消扫码登录",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 解析响应,直接返回完整响应体
|
||||
var apiResponse map[string]interface{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
log.Printf("[取消扫码] 解析响应失败: %v, body: %s", err, string(body))
|
||||
return map[string]interface{}{
|
||||
"code": 0,
|
||||
"message": "已取消扫码登录",
|
||||
}, nil
|
||||
}
|
||||
|
||||
log.Printf("[取消扫码] 成功")
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"ai_xhs/config"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type XHSService struct{}
|
||||
@@ -38,128 +40,93 @@ type LoginResponse struct {
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// SendVerificationCode 调用Python脚本发送验证码
|
||||
// SendVerificationCode 调用Python HTTP API发送验证码
|
||||
func (s *XHSService) SendVerificationCode(phone, countryCode string) (*SendCodeResponse, error) {
|
||||
// 如果没有传国家码,默认使用+86
|
||||
if countryCode == "" {
|
||||
countryCode = "+86"
|
||||
}
|
||||
|
||||
// 获取Python脚本路径和venv中的Python解释器
|
||||
backendDir := filepath.Join("..", "backend")
|
||||
pythonScript := filepath.Join(backendDir, "xhs_cli.py")
|
||||
// 获取Python服务地址
|
||||
pythonURL := config.GetPythonServiceURL()
|
||||
apiURL := fmt.Sprintf("%s/api/xhs/send-code", pythonURL)
|
||||
|
||||
// 使用venv中的Python解释器 (跨平台)
|
||||
pythonCmd := getPythonPath(backendDir)
|
||||
|
||||
// 执行Python脚本
|
||||
cmd := exec.Command(pythonCmd, pythonScript, "send_code", phone, countryCode)
|
||||
|
||||
// 设置工作目录为Python脚本所在目录
|
||||
cmd.Dir = backendDir
|
||||
|
||||
// 捕获输出
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
// 执行命令
|
||||
err := cmd.Run()
|
||||
|
||||
// 打印Python脚本的日志输出(stderr)
|
||||
if stderr.Len() > 0 {
|
||||
log.Printf("[Python日志-发送验证码] %s", stderr.String())
|
||||
// 构造请求体
|
||||
reqData := map[string]interface{}{
|
||||
"phone": phone,
|
||||
"country_code": countryCode,
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(reqData)
|
||||
|
||||
// 发送HTTP POST请求
|
||||
client := &http.Client{
|
||||
Timeout: 60 * time.Second, // 60秒超时
|
||||
}
|
||||
resp, err := client.Post(apiURL, "application/json", bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("执行Python脚本失败: %w, stderr: %s", err, stderr.String())
|
||||
return nil, fmt.Errorf("调用Python服务失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取UTF-8编码的输出
|
||||
outputStr := stdout.String()
|
||||
|
||||
// 解析JSON输出
|
||||
log.Printf("[Python API-发送验证码] 响应: %s", string(body))
|
||||
|
||||
// 解析JSON响应
|
||||
var result SendCodeResponse
|
||||
if err := json.Unmarshal([]byte(outputStr), &result); err != nil {
|
||||
return nil, fmt.Errorf("解析Python输出失败: %w, output: %s", err, outputStr)
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %w, body: %s", err, string(body))
|
||||
}
|
||||
|
||||
// 检查Python脚本返回的success字段
|
||||
if !result.Data["success"].(bool) {
|
||||
return &SendCodeResponse{
|
||||
Code: 1,
|
||||
Message: result.Data["error"].(string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &SendCodeResponse{
|
||||
Code: 0,
|
||||
Message: "验证码已发送",
|
||||
Data: result.Data,
|
||||
}, nil
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// VerifyLogin 调用Python脚本验证登录
|
||||
// VerifyLogin 调用Python HTTP API验证登录
|
||||
func (s *XHSService) VerifyLogin(phone, code, countryCode string) (*LoginResponse, error) {
|
||||
// 如果没有传国家码,默认使用+86
|
||||
if countryCode == "" {
|
||||
countryCode = "+86"
|
||||
}
|
||||
|
||||
// 获取Python脚本路径和venv中的Python解释器
|
||||
backendDir := filepath.Join("..", "backend")
|
||||
pythonScript := filepath.Join(backendDir, "xhs_cli.py")
|
||||
// 获取Python服务地址
|
||||
pythonURL := config.GetPythonServiceURL()
|
||||
apiURL := fmt.Sprintf("%s/api/xhs/login", pythonURL)
|
||||
|
||||
// 使用venv中的Python解释器 (跨平台)
|
||||
pythonCmd := getPythonPath(backendDir)
|
||||
|
||||
// 执行Python脚本
|
||||
cmd := exec.Command(pythonCmd, pythonScript, "login", phone, code, countryCode)
|
||||
|
||||
// 设置工作目录
|
||||
cmd.Dir = backendDir
|
||||
|
||||
// 捕获输出
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
// 执行命令
|
||||
err := cmd.Run()
|
||||
|
||||
// 打印Python脚本的日志输出(stderr)
|
||||
if stderr.Len() > 0 {
|
||||
log.Printf("[Python日志-登录] %s", stderr.String())
|
||||
// 构造请求体
|
||||
reqData := map[string]interface{}{
|
||||
"phone": phone,
|
||||
"code": code,
|
||||
"country_code": countryCode,
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(reqData)
|
||||
|
||||
// 发送HTTP POST请求
|
||||
client := &http.Client{
|
||||
Timeout: 120 * time.Second, // 120秒超时(登录可能较慢)
|
||||
}
|
||||
resp, err := client.Post(apiURL, "application/json", bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("执行Python脚本失败: %w, stderr: %s", err, stderr.String())
|
||||
return nil, fmt.Errorf("调用Python服务失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取UTF-8编码的输出
|
||||
outputStr := stdout.String()
|
||||
|
||||
// 解析JSON输出
|
||||
log.Printf("[Python API-验证登录] 响应: %s", string(body))
|
||||
|
||||
// 解析JSON响应
|
||||
var result LoginResponse
|
||||
if err := json.Unmarshal([]byte(outputStr), &result); err != nil {
|
||||
return nil, fmt.Errorf("解析Python输出失败: %w, output: %s", err, outputStr)
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %w, body: %s", err, string(body))
|
||||
}
|
||||
|
||||
// 检查Python脚本返回的success字段
|
||||
if !result.Data["success"].(bool) {
|
||||
errorMsg := "登录失败"
|
||||
if errStr, ok := result.Data["error"].(string); ok {
|
||||
errorMsg = errStr
|
||||
}
|
||||
return &LoginResponse{
|
||||
Code: 1,
|
||||
Message: errorMsg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &LoginResponse{
|
||||
Code: 0,
|
||||
Message: "登录成功",
|
||||
Data: result.Data,
|
||||
}, nil
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user