完善文案的状态流转
This commit is contained in:
@@ -12,6 +12,8 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AuthService struct{}
|
||||
@@ -141,33 +143,95 @@ func (s *AuthService) WechatLogin(code string, phone string, phoneCode string) (
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 根据OpenID查找或创建员工
|
||||
// 2. 根据手机号查找用户(手机号必填)
|
||||
if phone == "" {
|
||||
return "", nil, errors.New("请提供手机号")
|
||||
}
|
||||
|
||||
var employee models.User
|
||||
|
||||
// 优先通过OpenID查找(注意:使用IS NOT NULL过滤空值)
|
||||
result := database.DB.Where("wechat_openid = ? AND wechat_openid IS NOT NULL", wxResp.OpenID).First(&employee)
|
||||
|
||||
// 通过手机号查找员工
|
||||
result := database.DB.Where("phone = ? AND status = ?", phone, "active").First(&employee)
|
||||
if result.Error != nil {
|
||||
// OpenID不存在,需要绑定OpenID
|
||||
if phone == "" {
|
||||
return "", nil, errors.New("首次登录请提供手机号")
|
||||
// 手机号不存在,不允许登录
|
||||
return "", nil, errors.New("手机号不存在,请联系管理员添加")
|
||||
}
|
||||
|
||||
// 3. 检查微信绑定信息
|
||||
// 如果该用户已绑定微信信息,必须与当前登录的微信信息一致(单设备登录)
|
||||
if employee.WechatOpenID != nil && *employee.WechatOpenID != "" {
|
||||
// 已绑定微信,检查是否一致
|
||||
if *employee.WechatOpenID != wxResp.OpenID {
|
||||
return "", nil, errors.New("该账号已在其他设备登录,请使用原设备登录或联系管理员")
|
||||
}
|
||||
|
||||
// 通过手机号查找员工
|
||||
result = database.DB.Where("phone = ? AND status = ?", phone, "active").First(&employee)
|
||||
if result.Error != nil {
|
||||
return "", nil, errors.New("员工不存在,请联系管理员添加")
|
||||
// 如果有UnionID,也需要检查一致性
|
||||
if employee.WechatUnionID != nil && *employee.WechatUnionID != "" && wxResp.UnionID != "" {
|
||||
if *employee.WechatUnionID != wxResp.UnionID {
|
||||
return "", nil, errors.New("微信账号信息不匹配,请使用原设备登录")
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定OpenID和UnionID(使用指针)
|
||||
log.Printf("[微信登录] 用户 %s (ID:%d) 微信验证通过", employee.Phone, employee.ID)
|
||||
} else {
|
||||
// 微信信息为空,说明是新用户首次登录,保存微信信息
|
||||
log.Printf("[微信登录] 新用户首次登录,绑定微信信息: OpenID=%s, UnionID=%s", wxResp.OpenID, wxResp.UnionID)
|
||||
|
||||
employee.WechatOpenID = &wxResp.OpenID
|
||||
if wxResp.UnionID != "" {
|
||||
employee.WechatUnionID = &wxResp.UnionID
|
||||
}
|
||||
database.DB.Save(&employee)
|
||||
|
||||
// 使用事务保存微信绑定信息并创建作者记录
|
||||
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 保存微信绑定信息
|
||||
if err := tx.Save(&employee).Error; err != nil {
|
||||
return fmt.Errorf("保存微信绑定信息失败: %v", err)
|
||||
}
|
||||
|
||||
// 2. 检查是否已存在作者记录(通过手机号和企业ID)
|
||||
var existingAuthor models.Author
|
||||
result := tx.Where("phone = ? AND enterprise_id = ?", employee.Phone, employee.EnterpriseID).First(&existingAuthor)
|
||||
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
// 作者记录不存在,创建新记录
|
||||
author := models.Author{
|
||||
EnterpriseID: employee.EnterpriseID,
|
||||
CreatedUserID: employee.ID,
|
||||
Phone: employee.Phone,
|
||||
AuthorName: employee.RealName,
|
||||
Department: employee.Department,
|
||||
Status: "active",
|
||||
Channel: 3, // 3=weixin (微信小程序)
|
||||
}
|
||||
|
||||
// 如果真实姓名为空,使用用户名
|
||||
if author.AuthorName == "" {
|
||||
author.AuthorName = employee.Username
|
||||
}
|
||||
|
||||
if err := tx.Create(&author).Error; err != nil {
|
||||
return fmt.Errorf("创建作者记录失败: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("[微信登录] 创建作者记录成功: ID=%d, Name=%s", author.ID, author.AuthorName)
|
||||
} else if result.Error != nil {
|
||||
// 其他数据库错误
|
||||
return fmt.Errorf("检查作者记录失败: %v", result.Error)
|
||||
} else {
|
||||
log.Printf("[微信登录] 作者记录已存在: ID=%d", existingAuthor.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
log.Printf("[微信登录] 用户 %s (ID:%d) 微信绑定成功", employee.Phone, employee.ID)
|
||||
}
|
||||
|
||||
// 3. 生成JWT token
|
||||
// 4. 生成JWT token
|
||||
token, err := utils.GenerateToken(employee.ID)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("生成token失败: %v", err)
|
||||
|
||||
@@ -43,12 +43,12 @@ func (s *EmployeeService) SendXHSCode(phone string) error {
|
||||
|
||||
// 执行命令
|
||||
err := cmd.Run()
|
||||
|
||||
|
||||
// 打印Python脚本的日志输出(stderr)
|
||||
if stderr.Len() > 0 {
|
||||
log.Printf("[Python日志-发送验证码] %s", stderr.String())
|
||||
}
|
||||
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("执行Python脚本失败: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func (s *EmployeeService) GetProfile(employeeID int) (*models.User, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// 如果已绑定小红书且有Cookie,验证Cookie是否有效
|
||||
if employee.IsBoundXHS == 1 && employee.XHSCookie != "" {
|
||||
// 检查绑定时间,刚绑定的30秒内不验证(避免与绑定操作冲突)
|
||||
@@ -96,7 +96,7 @@ func (s *EmployeeService) GetProfile(employeeID int) (*models.User, error) {
|
||||
log.Printf("GetProfile - 用户%d有Cookie,长度: %d(已跳过自动验证)", employeeID, len(employee.XHSCookie))
|
||||
// go s.VerifyCookieAndClear(employeeID)
|
||||
}
|
||||
|
||||
|
||||
return &employee, nil
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string
|
||||
} else {
|
||||
log.Printf("绑定小红书 - 用户%d - 警告: cookiesData为nil", employeeID)
|
||||
}
|
||||
|
||||
|
||||
if cookiesJSON == "" {
|
||||
log.Printf("绑定小红书 - 用户%d - 错误: 未能获取到Cookie数据", employeeID)
|
||||
return "", errors.New("登录成功但未能获取到Cookie数据,请重试")
|
||||
@@ -195,7 +195,7 @@ func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string
|
||||
log.Printf("绑定小红书 - 用户%d - 数据库更新失败: %v", employeeID, err)
|
||||
return "", fmt.Errorf("更新员工绑定状态失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
log.Printf("绑定小红书 - 用户%d - 数据库更新成功", employeeID)
|
||||
|
||||
// 提交事务
|
||||
@@ -203,7 +203,7 @@ func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string
|
||||
log.Printf("绑定小红书 - 用户%d - 事务提交失败: %v", employeeID, err)
|
||||
return "", fmt.Errorf("提交事务失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
log.Printf("绑定小红书 - 用户%d - 绑定成功 - 账号: %s", employeeID, xhsNickname)
|
||||
return xhsNickname, nil
|
||||
}
|
||||
@@ -381,7 +381,7 @@ func (s *EmployeeService) VerifyCookieAndClear(employeeID int) error {
|
||||
if employee.IsBoundXHS == 0 || employee.XHSCookie == "" {
|
||||
return nil // 没有绑定或已无Cookie,直接返回
|
||||
}
|
||||
|
||||
|
||||
// 检查绑定时间,刚绑定的30秒内不验证(避免与绑定操作冲突)
|
||||
if employee.BoundAt != nil {
|
||||
timeSinceBound := time.Since(*employee.BoundAt)
|
||||
@@ -492,20 +492,37 @@ func (s *EmployeeService) clearXHSCookie(employeeID int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAvailableCopies 获取可领取的文案列表
|
||||
// GetAvailableCopies 获取可领取的文案列表(根据作者ID、产品ID和状态筛选)
|
||||
func (s *EmployeeService) GetAvailableCopies(employeeID int, productID int) (map[string]interface{}, error) {
|
||||
// 获取当前用户信息,查找对应的作者ID
|
||||
var employee models.User
|
||||
if err := database.DB.First(&employee, employeeID).Error; err != nil {
|
||||
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 查找对应的作者记录
|
||||
var author models.Author
|
||||
if err := database.DB.Where("phone = ? AND enterprise_id = ?", employee.Phone, employee.EnterpriseID).First(&author).Error; err != nil {
|
||||
return nil, fmt.Errorf("未找到对应的作者记录: %w", err)
|
||||
}
|
||||
|
||||
// 获取产品信息
|
||||
var product models.Product
|
||||
if err := database.DB.First(&product, productID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取该产品下所有可用文案(注意:新数据库中status有更多状态)
|
||||
// 根据产品ID、作者ID和状态筛选文案
|
||||
// status = 'assign_authors' 表示已分配作者的文案
|
||||
var copies []models.Article
|
||||
if err := database.DB.Where("product_id = ? AND status IN ?", productID, []string{"draft", "approved"}).Order("created_at DESC").Find(&copies).Error; err != nil {
|
||||
return nil, err
|
||||
query := database.DB.Where("product_id = ? AND author_id = ? AND status = ?", productID, author.ID, "assign_authors")
|
||||
|
||||
if err := query.Order("created_at DESC").Find(&copies).Error; err != nil {
|
||||
return nil, fmt.Errorf("查询文案列表失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("[获取文案列表] 用户ID=%d, 作者ID=%d, 产品ID=%d, 筛选到 %d 条文案", employeeID, author.ID, productID, len(copies))
|
||||
|
||||
return map[string]interface{}{
|
||||
"product": map[string]interface{}{
|
||||
"id": product.ID,
|
||||
@@ -513,14 +530,106 @@ func (s *EmployeeService) GetAvailableCopies(employeeID int, productID int) (map
|
||||
"image": product.ImageURL,
|
||||
},
|
||||
"copies": copies,
|
||||
"author": map[string]interface{}{
|
||||
"id": author.ID,
|
||||
"name": author.AuthorName,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateArticleStatus 更新文案状态(通过/拒绝)
|
||||
func (s *EmployeeService) UpdateArticleStatus(employeeID int, articleID int, status string) error {
|
||||
// 获取当前用户信息
|
||||
var employee models.User
|
||||
if err := database.DB.First(&employee, employeeID).Error; err != nil {
|
||||
return fmt.Errorf("获取用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 查找对应的作者记录
|
||||
var author models.Author
|
||||
if err := database.DB.Where("phone = ? AND enterprise_id = ?", employee.Phone, employee.EnterpriseID).First(&author).Error; err != nil {
|
||||
return fmt.Errorf("未找到对应的作者记录: %w", err)
|
||||
}
|
||||
|
||||
// 获取文案信息
|
||||
var article models.Article
|
||||
if err := database.DB.First(&article, articleID).Error; err != nil {
|
||||
return fmt.Errorf("获取文案信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证文案是否属于当前作者且状态为assign_authors
|
||||
if article.AuthorID == nil || *article.AuthorID != author.ID {
|
||||
return fmt.Errorf("无权操作此文案")
|
||||
}
|
||||
|
||||
if article.Status != "assign_authors" {
|
||||
return fmt.Errorf("文案当前状态为%s,无法操作", article.Status)
|
||||
}
|
||||
|
||||
// 开启事务
|
||||
tx := database.DB.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// 更新文案状态
|
||||
err := tx.Model(&article).Updates(map[string]interface{}{
|
||||
"status": status,
|
||||
"review_user_id": employeeID,
|
||||
}).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("更新状态失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建操作记录到 ai_article_published_records
|
||||
actionType := "通过"
|
||||
if status == "rejected" {
|
||||
actionType = "拒绝"
|
||||
}
|
||||
|
||||
record := models.PublishRecord{
|
||||
ArticleID: &article.ID,
|
||||
EnterpriseID: employee.EnterpriseID,
|
||||
ProductID: article.ProductID,
|
||||
Topic: article.Topic,
|
||||
Title: article.Title,
|
||||
CreatedUserID: article.CreatedUserID,
|
||||
ReviewUserID: &employeeID,
|
||||
Status: status,
|
||||
PublishTime: &now,
|
||||
WordCount: article.WordCount,
|
||||
ImageCount: article.ImageCount,
|
||||
Channel: article.Channel,
|
||||
ReviewComment: fmt.Sprintf("作者%s于%s", actionType, now.Format("2006-01-02 15:04:05")),
|
||||
}
|
||||
|
||||
if err := tx.Create(&record).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("创建操作记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return fmt.Errorf("提交事务失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("[更新文案状态] 用户ID=%d, 作者ID=%d, 文案ID=%d, 状态: assign_authors => %s, 记录ID=%d", employeeID, author.ID, articleID, status, record.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClaimCopy 领取文案(新版本:直接返回文案信息,不再创建领取记录)
|
||||
func (s *EmployeeService) ClaimCopy(employeeID int, copyID int, productID int) (map[string]interface{}, error) {
|
||||
// 检查文案是否存在且可用(注意:新数据库中status有更多状态)
|
||||
// assign_authors: 已分配给作者,可以直接发布
|
||||
// draft: 草稿状态
|
||||
// approved: 已审核通过
|
||||
var copy models.Article
|
||||
if err := database.DB.Where("id = ? AND status IN ?", copyID, []string{"draft", "approved"}).First(©).Error; err != nil {
|
||||
if err := database.DB.Where("id = ? AND status IN ?", copyID, []string{"draft", "approved", "assign_authors"}).First(©).Error; err != nil {
|
||||
return nil, errors.New("文案不存在或不可用")
|
||||
}
|
||||
|
||||
@@ -542,7 +651,7 @@ func (s *EmployeeService) ClaimCopy(employeeID int, copyID int, productID int) (
|
||||
func (s *EmployeeService) ClaimRandomCopy(employeeID int, productID int) (map[string]interface{}, error) {
|
||||
// 查询未领取的可用文案(注意:新数据库中status有更多状态)
|
||||
var copy models.Article
|
||||
query := database.DB.Where("product_id = ? AND status IN ?", productID, []string{"draft", "approved"})
|
||||
query := database.DB.Where("product_id = ? AND status IN ?", productID, []string{"draft", "approved", "assign_authors"})
|
||||
|
||||
if err := query.Order("RAND()").First(©).Error; err != nil {
|
||||
return nil, errors.New("暂无可领取的文案")
|
||||
|
||||
Reference in New Issue
Block a user