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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user