完善文案的状态流转

This commit is contained in:
sjk
2025-12-20 01:05:46 +08:00
parent 6802624e59
commit 15b579d64a
13 changed files with 547 additions and 181 deletions

View File

@@ -27,7 +27,7 @@ xhs:
python_service_url: "http://localhost:8000" # Python服务地址 python_service_url: "http://localhost:8000" # Python服务地址
scheduler: scheduler:
enabled: true # 是否启用定时任务 enabled: false # 是否启用定时任务
publish_cron: "* * * * * *" # 每1小时执行一次开发环境测试用 publish_cron: "* * * * * *" # 每1小时执行一次开发环境测试用
max_concurrent: 2 # 最大并发发布数 max_concurrent: 2 # 最大并发发布数
publish_timeout: 300 # 发布超时时间(秒) publish_timeout: 300 # 发布超时时间(秒)

View File

@@ -27,7 +27,7 @@ xhs:
python_service_url: "http://localhost:8000" # Python服务地址生产环境请修改为实际地址 python_service_url: "http://localhost:8000" # Python服务地址生产环境请修改为实际地址
scheduler: scheduler:
enabled: true # 是否启用定时任务 enabled: false # 是否启用定时任务
publish_cron: "0 0 */2 * * *" # 每2小时执行一次防封号策略 publish_cron: "0 0 */2 * * *" # 每2小时执行一次防封号策略
max_concurrent: 2 # 最大并发发布数 max_concurrent: 2 # 最大并发发布数
publish_timeout: 300 # 发布超时时间(秒) publish_timeout: 300 # 发布超时时间(秒)

View File

@@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type EmployeeController struct { type EmployeeController struct {
service *service.EmployeeService service *service.EmployeeService
} }
@@ -255,3 +256,41 @@ func (ctrl *EmployeeController) GetProducts(c *gin.Context) {
"list": data, "list": data,
}) })
} }
// UpdateArticleStatus 更新文案状态(通过/拒绝)
func (ctrl *EmployeeController) UpdateArticleStatus(c *gin.Context) {
employeeID := c.GetInt("employee_id")
articleID, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.Error(c, common.CodeInvalidParams, "文案ID参数错误")
return
}
var req struct {
Status string `json:"status" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
common.Error(c, common.CodeInvalidParams, "参数错误")
return
}
// 验证status值
if req.Status != "approved" && req.Status != "rejected" {
common.Error(c, common.CodeInvalidParams, "status只能为approved或rejected")
return
}
err = ctrl.service.UpdateArticleStatus(employeeID, articleID, req.Status)
if err != nil {
common.Error(c, common.CodeInternalError, err.Error())
return
}
message := "已通过"
if req.Status == "rejected" {
message = "已拒绝"
}
common.SuccessWithMessage(c, message, nil)
}

View File

@@ -54,6 +54,7 @@ func AutoMigrate() error {
&models.PublishRecord{}, &models.PublishRecord{},
&models.ArticleImage{}, &models.ArticleImage{},
&models.ArticleTag{}, &models.ArticleTag{},
&models.Author{},
&models.Log{}, &models.Log{},
// &models.XHSAccount{}, // 不再使用,小红书信息直接存储在 ai_users 表中 // &models.XHSAccount{}, // 不再使用,小红书信息直接存储在 ai_users 表中
) )

View File

@@ -6,24 +6,25 @@ import (
// Enterprise 企业表 // Enterprise 企业表
type Enterprise struct { type Enterprise struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EnterpriseID string `gorm:"type:varchar(255);not null;default:''" json:"enterprise_id" comment:"企业ID"` EnterpriseID string `gorm:"type:varchar(255);not null;default:''" json:"enterprise_id" comment:"企业ID"`
Name string `gorm:"type:varchar(200);not null;default:''" json:"name" comment:"企业名称"` Name string `gorm:"type:varchar(200);not null;default:''" json:"name" comment:"企业名称"`
ShortName string `gorm:"type:varchar(100);not null;default:''" json:"short_name" comment:"企业简称"` ShortName string `gorm:"type:varchar(100);not null;default:''" json:"short_name" comment:"企业简称"`
Icon string `gorm:"type:varchar(500);not null;default:''" json:"icon" comment:"企业图标URL"` Icon string `gorm:"type:varchar(500);not null;default:''" json:"icon" comment:"企业图标URL"`
Phone string `gorm:"type:varchar(20);not null;default:'';uniqueIndex:uk_phone" json:"phone" comment:"登录手机号"` Phone string `gorm:"type:varchar(20);not null;default:'';uniqueIndex:uk_phone" json:"phone" comment:"登录手机号"`
Password string `gorm:"type:varchar(255);not null;default:''" json:"-" comment:"登录密码(加密存储)"` Password string `gorm:"type:varchar(255);not null;default:''" json:"-" comment:"登录密码(加密存储)"`
Email string `gorm:"type:varchar(128);not null;default:''" json:"email" comment:"企业邮箱"` Email string `gorm:"type:varchar(128);not null;default:''" json:"email" comment:"企业邮箱"`
Website string `gorm:"type:varchar(255);not null;default:''" json:"website" comment:"企业网站"` Website string `gorm:"type:varchar(255);not null;default:''" json:"website" comment:"企业网站"`
Address string `gorm:"type:varchar(255);not null;default:''" json:"address" comment:"企业地址"` Address string `gorm:"type:varchar(255);not null;default:''" json:"address" comment:"企业地址"`
Status string `gorm:"type:enum('active','disabled');not null;default:'active';index:idx_status" json:"status" comment:"状态"` Status string `gorm:"type:enum('active','disabled');not null;default:'active';index:idx_status" json:"status" comment:"状态"`
UsersTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"users_total" comment:"员工总数"` UsersTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"users_total" comment:"员工总数"`
ProductsTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"products_total" comment:"产品总数"` ProductsTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"products_total" comment:"产品总数"`
ArticlesTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"articles_total" comment:"文章总数"` PublishedTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"published_total" comment:"文章发布总数"`
ReleasedMonthTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"released_month_total" comment:"本月发布数量"` ArticlesTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"articles_total" comment:"文章总数"`
LinkedToXHSNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"linked_to_xhs_num" comment:"绑定小红书"` ReleasedMonthTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"released_month_total" comment:"本月发布数量"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"` LinkedToXHSNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"linked_to_xhs_num" comment:"绑定小红书"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"` CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
} }
// User 用户账号表原Employee对应ai_users // User 用户账号表原Employee对应ai_users
@@ -37,15 +38,15 @@ type User struct {
RealName string `gorm:"type:varchar(50)" json:"real_name" comment:"真实姓名"` RealName string `gorm:"type:varchar(50)" json:"real_name" comment:"真实姓名"`
Email string `gorm:"type:varchar(100)" json:"email" comment:"邮箱"` Email string `gorm:"type:varchar(100)" json:"email" comment:"邮箱"`
Phone string `gorm:"type:varchar(20)" json:"phone" comment:"手机号"` Phone string `gorm:"type:varchar(20)" json:"phone" comment:"手机号"`
WechatOpenID *string `gorm:"type:varchar(100);uniqueIndex:uk_wechat_openid" json:"wechat_openid,omitempty" comment:"微信OpenID"` WechatOpenID *string `gorm:"column:wechat_openid;type:varchar(100);uniqueIndex:uk_wechat_openid" json:"wechat_openid,omitempty" comment:"微信OpenID"`
WechatUnionID *string `gorm:"type:varchar(100)" json:"wechat_unionid,omitempty" comment:"微信UnionID"` WechatUnionID *string `gorm:"column:wechat_unionid;type:varchar(100)" json:"wechat_unionid,omitempty" comment:"微信UnionID"`
XHSPhone string `gorm:"type:varchar(20);not null;default:''" json:"xhs_phone" comment:"小红书绑定手机号"` XHSPhone string `gorm:"type:varchar(20);not null;default:''" json:"xhs_phone" comment:"小红书绑定手机号"`
XHSAccount string `gorm:"type:varchar(255);not null;default:''" json:"xhs_account" comment:"小红书账号名称"` XHSAccount string `gorm:"type:varchar(255);not null;default:''" json:"xhs_account" comment:"小红书账号名称"`
XHSCookie string `gorm:"type:text" json:"xhs_cookie" comment:"小红书Cookie"` XHSCookie string `gorm:"type:text" json:"xhs_cookie" comment:"小红书Cookie"`
IsBoundXHS int `gorm:"type:tinyint(1);not null;default:0;index:idx_is_bound_xhs" json:"is_bound_xhs" comment:"是否绑定小红书0=未绑定1=已绑定"` IsBoundXHS int `gorm:"type:tinyint(1);not null;default:0;index:idx_is_bound_xhs" json:"is_bound_xhs" comment:"是否绑定小红书0=未绑定1=已绑定"`
BoundAt *time.Time `json:"bound_at" comment:"绑定小红书的时间"` BoundAt *time.Time `json:"bound_at" comment:"绑定小红书的时间"`
Department string `gorm:"type:varchar(50)" json:"department" comment:"部门"` Department string `gorm:"type:varchar(50)" json:"department" comment:"部门"`
Role string `gorm:"type:enum('admin','editor','reviewer','publisher','each_title_reviewer');default:'editor'" json:"role" comment:"角色"` Role string `gorm:"type:enum('admin','editor','reviewer','publisher','each_title_reviewer','enterprise');default:'editor'" json:"role" comment:"角色"`
Status string `gorm:"type:enum('active','inactive','deleted');default:'active';index:idx_status" json:"status" comment:"状态"` Status string `gorm:"type:enum('active','inactive','deleted');default:'active';index:idx_status" json:"status" comment:"状态"`
CreatedAt time.Time `json:"created_at" comment:"创建时间"` CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"` UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
@@ -59,6 +60,7 @@ type Product struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"` EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"`
Name string `gorm:"type:varchar(200);not null;default:''" json:"name" comment:"产品名称"` Name string `gorm:"type:varchar(200);not null;default:''" json:"name" comment:"产品名称"`
TypeID int `gorm:"type:int unsigned;not null;default:0" json:"type_id" comment:"type_id类型ID"`
TypeName string `gorm:"type:varchar(128);not null;default:''" json:"type_name" comment:"产品类型"` TypeName string `gorm:"type:varchar(128);not null;default:''" json:"type_name" comment:"产品类型"`
ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"产品主图URL"` ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"产品主图URL"`
ImageThumbnailURL string `gorm:"type:varchar(500);not null;default:''" json:"image_thumbnail_url" comment:"缩略图URL"` ImageThumbnailURL string `gorm:"type:varchar(500);not null;default:''" json:"image_thumbnail_url" comment:"缩略图URL"`
@@ -72,35 +74,35 @@ type Product struct {
// Article 文章表原Copy对应ai_articles // Article 文章表原Copy对应ai_articles
type Article struct { type Article struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
BatchID uint64 `gorm:"type:bigint unsigned;not null;default:0" json:"batch_id" comment:"批次ID"` BatchID uint64 `gorm:"type:bigint unsigned;not null;default:0" json:"batch_id" comment:"批次ID"`
EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"` EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"`
ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"` ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"`
TopicTypeID int `gorm:"type:int unsigned;not null;default:0" json:"topic_type_id" comment:"topic类型ID"` TopicTypeID int `gorm:"type:int unsigned;not null;default:0" json:"topic_type_id" comment:"topic类型ID"`
PromptWorkflowID int `gorm:"type:int unsigned;not null;default:0" json:"prompt_workflow_id" comment:"提示词工作流ID"` PromptWorkflowID int `gorm:"type:int unsigned;not null;default:0" json:"prompt_workflow_id" comment:"提示词工作流ID"`
Topic string `gorm:"type:varchar(255);not null;default:''" json:"topic" comment:"topic主题"` Topic string `gorm:"type:varchar(255);not null;default:''" json:"topic" comment:"topic主题"`
Title string `gorm:"type:varchar(200);not null;default:''" json:"title" comment:"标题"` Title string `gorm:"type:varchar(200);not null;default:''" json:"title" comment:"标题"`
Content string `gorm:"type:text" json:"content" comment:"文章内容"` Content string `gorm:"type:text" json:"content" comment:"文章内容"`
Department string `gorm:"type:varchar(255);not null;default:''" json:"department" comment:"部门"` Department string `gorm:"type:varchar(255);not null;default:''" json:"department" comment:"部门"`
DepartmentIDs string `gorm:"column:departmentids;type:varchar(255);not null;default:''" json:"department_ids" comment:"部门IDs"` DepartmentIDs string `gorm:"column:departmentids;type:varchar(255);not null;default:''" json:"department_ids" comment:"部门IDs"`
AuthorID *int `json:"author_id" comment:"作者ID"` AuthorID *int `json:"author_id" comment:"作者ID"`
AuthorName string `gorm:"type:varchar(100)" json:"author_name" comment:"作者名称"` AuthorName string `gorm:"type:varchar(100)" json:"author_name" comment:"作者名称"`
DepartmentID *int `json:"department_id" comment:"部门ID"` DepartmentID *int `json:"department_id" comment:"部门ID"`
DepartmentName string `gorm:"type:varchar(255)" json:"department_name" comment:"部门名称"` DepartmentName string `gorm:"type:varchar(255)" json:"department_name" comment:"部门名称"`
CreatedUserID int `gorm:"not null;default:0" json:"created_user_id" comment:"创建用户ID"` CreatedUserID int `gorm:"not null;default:0" json:"created_user_id" comment:"创建用户ID"`
ReviewUserID *int `json:"review_user_id" comment:"审核用户ID"` ReviewUserID *int `json:"review_user_id" comment:"审核用户ID"`
PublishUserID *int `json:"publish_user_id" comment:"发布用户ID"` PublishUserID *int `json:"publish_user_id" comment:"发布用户ID"`
Status string `gorm:"type:enum('topic','cover_image','generate','generate_failed','draft','pending_review','approved','rejected','published_review','published','failed');default:'draft';index:idx_status" json:"status" comment:"状态"` Status string `gorm:"type:enum('topic','cover_image','generate','generate_failed','draft','pending_review','assign_authors','approved','rejected','published_review','published','failed');default:'draft';index:idx_status" json:"status" comment:"状态"`
Channel int `gorm:"type:tinyint(1);not null;default:1" json:"channel" comment:"渠道1=baidu|2=toutiao|3=weixin"` Channel int `gorm:"type:tinyint(1);not null;default:1" json:"channel" comment:"渠道1=baidu|2=toutiao|3=weixin"`
ReviewComment string `gorm:"type:text" json:"review_comment" comment:"审核评论"` ReviewComment string `gorm:"type:text" json:"review_comment" comment:"审核评论"`
PublishTime *time.Time `json:"publish_time" comment:"发布时间"` PublishTime *time.Time `json:"publish_time" comment:"发布时间"`
BaijiahaoID string `gorm:"type:varchar(100)" json:"baijiahao_id" comment:"百家号ID"` BaijiahaoID string `gorm:"type:varchar(100)" json:"baijiahao_id" comment:"百家号ID"`
BaijiahaoStatus string `gorm:"type:varchar(50)" json:"baijiahao_status" comment:"百家号状态"` BaijiahaoStatus string `gorm:"type:varchar(50)" json:"baijiahao_status" comment:"百家号状态"`
WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"` WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"`
ImageCount int `gorm:"default:0" json:"image_count" comment:"图片数量"` ImageCount int `gorm:"default:0" json:"image_count" comment:"图片数量"`
CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"` CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"` CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"index:idx_updated_at" json:"updated_at" comment:"更新时间"` UpdatedAt time.Time `gorm:"index:idx_updated_at" json:"updated_at" comment:"更新时间"`
} }
// Copy 文案表别名,兼容旧代码 // Copy 文案表别名,兼容旧代码
@@ -108,83 +110,85 @@ type Copy = Article
// PublishRecord 发布记录表对应ai_article_published_records // PublishRecord 发布记录表对应ai_article_published_records
type PublishRecord struct { type PublishRecord struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
ArticleID *int `gorm:"index:idx_article_id" json:"article_id" comment:"文章ID"` ArticleID *int `gorm:"index:idx_article_id" json:"article_id" comment:"文章ID"`
EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"` EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"`
ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"` ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"`
Topic string `gorm:"type:varchar(255);not null;default:''" json:"topic" comment:"topic主题"` Topic string `gorm:"type:varchar(255);not null;default:''" json:"topic" comment:"topic主题"`
Title string `gorm:"type:varchar(200);not null;default:''" json:"title" comment:"标题"` Title string `gorm:"type:varchar(200);not null;default:''" json:"title" comment:"标题"`
CreatedUserID int `gorm:"not null;default:0;index:idx_created_user_id" json:"created_user_id" comment:"创建用户ID"` CreatedUserID int `gorm:"not null;default:0;index:idx_created_user_id" json:"created_user_id" comment:"创建用户ID"`
ReviewUserID *int `json:"review_user_id" comment:"审核用户ID"` ReviewUserID *int `json:"review_user_id" comment:"审核用户ID"`
PublishUserID *int `json:"publish_user_id" comment:"发布用户ID"` PublishUserID *int `json:"publish_user_id" comment:"发布用户ID"`
Status string `gorm:"type:enum('topic','cover_image','generate','generate_failed','draft','pending_review','approved','rejected','published_review','published','failed');default:'draft';index:idx_status" json:"status" comment:"状态"` Status string `gorm:"type:enum('topic','cover_image','generate','generate_failed','draft','pending_review','assign_authors','approved','rejected','published_review','published','failed');default:'draft';index:idx_status" json:"status" comment:"状态"`
Channel int `gorm:"type:tinyint(1);not null;default:1" json:"channel" comment:"渠道1=baidu|2=toutiao|3=weixin"` Channel int `gorm:"type:tinyint(1);not null;default:1" json:"channel" comment:"渠道1=baidu|2=toutiao|3=weixin"`
ReviewComment string `gorm:"type:text" json:"review_comment" comment:"审核评论"` ReviewComment string `gorm:"type:text" json:"review_comment" comment:"审核评论"`
PublishTime *time.Time `json:"publish_time" comment:"发布时间"` PublishTime *time.Time `json:"publish_time" comment:"发布时间"`
PublishLink string `gorm:"type:varchar(128);not null;default:''" json:"publish_link" comment:"发布访问链接"` PublishLink string `gorm:"type:varchar(128);not null;default:''" json:"publish_link" comment:"发布访问链接"`
WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"` WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"`
ImageCount int `gorm:"default:0" json:"image_count" comment:"图片数量"` ImageCount int `gorm:"default:0" json:"image_count" comment:"图片数量"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"` CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"index:idx_updated_at" json:"updated_at" comment:"更新时间"` UpdatedAt time.Time `gorm:"index:idx_updated_at" json:"updated_at" comment:"更新时间"`
} }
// XHSAccount 小红书账号表(保持兼容) // XHSAccount 小红书账号表(保持兼容)
type XHSAccount struct { type XHSAccount struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EmployeeID int `gorm:"not null;default:0;uniqueIndex:uk_employee_id" json:"employee_id"` EmployeeID int `gorm:"not null;default:0;uniqueIndex:uk_employee_id" json:"employee_id"`
Employee User `gorm:"foreignKey:EmployeeID" json:"employee,omitempty"` Employee User `gorm:"foreignKey:EmployeeID" json:"employee,omitempty"`
XHSUserID string `gorm:"type:varchar(100);not null;default:'';index:idx_xhs_user_id" json:"xhs_user_id"` XHSUserID string `gorm:"type:varchar(100);not null;default:'';index:idx_xhs_user_id" json:"xhs_user_id"`
XHSNickname string `gorm:"type:varchar(100);not null;default:''" json:"xhs_nickname"` XHSNickname string `gorm:"type:varchar(100);not null;default:''" json:"xhs_nickname"`
XHSPhone string `gorm:"type:varchar(20);not null;default:''" json:"xhs_phone"` XHSPhone string `gorm:"type:varchar(20);not null;default:''" json:"xhs_phone"`
XHSAvatar string `gorm:"type:varchar(500);not null;default:''" json:"xhs_avatar"` XHSAvatar string `gorm:"type:varchar(500);not null;default:''" json:"xhs_avatar"`
FansCount int `gorm:"not null;default:0" json:"fans_count"` FansCount int `gorm:"not null;default:0" json:"fans_count"`
NotesCount int `gorm:"not null;default:0" json:"notes_count"` NotesCount int `gorm:"not null;default:0" json:"notes_count"`
Cookies string `gorm:"type:text" json:"cookies"` Cookies string `gorm:"type:text" json:"cookies"`
AccessToken string `gorm:"type:varchar(500);not null;default:''" json:"access_token"` AccessToken string `gorm:"type:varchar(500);not null;default:''" json:"access_token"`
RefreshToken string `gorm:"type:varchar(500);not null;default:''" json:"refresh_token"` RefreshToken string `gorm:"type:varchar(500);not null;default:''" json:"refresh_token"`
TokenExpireAt *time.Time `json:"token_expire_at"` TokenExpireAt *time.Time `json:"token_expire_at"`
Status string `gorm:"type:enum('active','expired','banned');default:'active';index:idx_status" json:"status"` Status string `gorm:"type:enum('active','expired','banned');default:'active';index:idx_status" json:"status"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
// PromptWorkflow 提示词工作流表 // PromptWorkflow 提示词工作流表
type PromptWorkflow struct { type PromptWorkflow struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"` EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"`
PromptWorkflowName string `gorm:"type:varchar(100);not null;default:''" json:"prompt_workflow_name" comment:"提示词工作流名称"` PromptWorkflowName string `gorm:"type:varchar(100);not null;default:''" json:"prompt_workflow_name" comment:"提示词工作流名称"`
AuthToken string `gorm:"type:varchar(100);not null;default:''" json:"auth_token" comment:"认证Token"` AuthToken string `gorm:"type:varchar(100);not null;default:''" json:"auth_token" comment:"认证Token"`
WorkflowID string `gorm:"type:varchar(100);not null;default:'';index:idx_workflow_id" json:"workflow_id" comment:"工作流ID"` WorkflowID string `gorm:"type:varchar(100);not null;default:'';index:idx_workflow_id" json:"workflow_id" comment:"工作流ID"`
Content string `gorm:"type:text" json:"content" comment:"提示词内容"` Content string `gorm:"type:text" json:"content" comment:"提示词内容"`
UsageCount int `gorm:"not null;default:0" json:"usage_count" comment:"使用次数统计"` UsageCount int `gorm:"not null;default:0" json:"usage_count" comment:"使用次数统计"`
CreatedUserID int `gorm:"not null;default:0" json:"created_user_id" comment:"创建用户ID"` CreatedUserID int `gorm:"not null;default:0" json:"created_user_id" comment:"创建用户ID"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"` CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"` UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
} }
// ProductImage 产品图片库表 // ProductImage 产品图片库表
type ProductImage struct { type ProductImage struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"` EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"`
ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"` ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"`
ImageID int `gorm:"not null;default:0" json:"image_id" comment:"图片ID"` ProductName string `gorm:"type:varchar(256);not null;default:''" json:"product_name" comment:"产品名称"`
ImageName string `gorm:"type:varchar(255);not null;default:''" json:"image_name" comment:"图片名称"` ImageID int `gorm:"not null;default:0" json:"image_id" comment:"图片ID"`
ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"图片URL"` ImageName string `gorm:"type:varchar(255);not null;default:''" json:"image_name" comment:"图片名称"`
ThumbnailURL string `gorm:"type:varchar(500);not null;default:''" json:"thumbnail_url" comment:"缩略图URL"` ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"图URL"`
TypeName string `gorm:"type:varchar(50);not null;default:''" json:"type_name" comment:"图片类型"` ThumbnailURL string `gorm:"type:varchar(500);not null;default:''" json:"thumbnail_url" comment:"缩略图URL"`
Description string `gorm:"type:varchar(500);not null;default:''" json:"description" comment:"图片描述"` TypeName string `gorm:"type:varchar(50);not null;default:''" json:"type_name" comment:"图片类型"`
FileSize *int64 `json:"file_size" comment:"文件大小"` Description string `gorm:"type:varchar(500);not null;default:''" json:"description" comment:"图片描述"`
Width *int `json:"width" comment:"图片宽度"` FileSize *int64 `json:"file_size" comment:"文件大小"`
Height *int `json:"height" comment:"图片度"` Width *int `json:"width" comment:"图片度"`
UploadUserID int `gorm:"not null;default:0" json:"upload_user_id" comment:"上传用户ID"` Height *int `json:"height" comment:"图片高度"`
Status string `gorm:"type:enum('active','deleted');default:'active';index:idx_status" json:"status" comment:"状态"` UploadUserID int `gorm:"not null;default:0" json:"upload_user_id" comment:"上传用户ID"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"上传时间"` Status string `gorm:"type:enum('active','deleted');default:'active';index:idx_status" json:"status" comment:"状态"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"` CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"上传时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
} }
// ArticleImage 文章图片表 // ArticleImage 文章图片表
type ArticleImage struct { type ArticleImage struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EnterpriseID int `gorm:"not null;default:0" json:"enterprise_id" comment:"所属企业ID"`
ArticleID int `gorm:"not null;default:0;index:idx_article_id" json:"article_id" comment:"文章ID"` ArticleID int `gorm:"not null;default:0;index:idx_article_id" json:"article_id" comment:"文章ID"`
ImageID int `gorm:"not null;default:0;index:idx_image_id" json:"image_id" comment:"图片ID"` ImageID int `gorm:"not null;default:0;index:idx_image_id" json:"image_id" comment:"图片ID"`
ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"图片URL"` ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"图片URL"`
@@ -202,23 +206,24 @@ type ArticleImage struct {
// ArticleTag 文章标签表 // ArticleTag 文章标签表
type ArticleTag struct { type ArticleTag struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
ArticleID int `gorm:"not null;default:0;uniqueIndex:uk_article_tag" json:"article_id" comment:"文章ID"` EnterpriseID int `gorm:"not null;default:0" json:"enterprise_id" comment:"所属企业ID"`
CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"` ArticleID int `gorm:"not null;default:0;uniqueIndex:uk_article_tag" json:"article_id" comment:"文章ID"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"` CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
} }
// DataStatistics 数据统计表 // DataStatistics 数据统计表
type DataStatistics struct { type DataStatistics struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"` ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"` EnterpriseID int `gorm:"not null;default:0;index:idx_enterprise_id" json:"enterprise_id" comment:"所属企业ID"`
ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"` ProductID int `gorm:"not null;default:0;index:idx_product_id" json:"product_id" comment:"关联产品ID"`
CumulativeReleasesNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"cumulative_releases_num" comment:"累计发布"` CumulativeReleasesNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"cumulative_releases_num" comment:"累计发布"`
PublishedTodayNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"published_today_num" comment:"今日发布"` PublishedTodayNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"published_today_num" comment:"今日发布"`
PublishedWeekNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"published_week_num" comment:"本周发布"` PublishedWeekNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"published_week_num" comment:"本周发布"`
ParticipatingEmployees int `gorm:"type:int(10) unsigned;not null;default:0" json:"participating_employees_num" comment:"参与员工"` ParticipatingEmployees int `gorm:"type:int(10) unsigned;not null;default:0" json:"participating_employees_num" comment:"参与员工"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"` CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"` UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
} }
// Log 操作日志表对应ai_logs // Log 操作日志表对应ai_logs
@@ -238,6 +243,31 @@ type Log struct {
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"` CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
} }
// Author 作者表对应ai_authors
type Author struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EnterpriseID int `gorm:"not null;default:0" json:"enterprise_id" comment:"所属企业ID"`
CreatedUserID int `gorm:"not null;default:0" json:"created_user_id" comment:"创建用户ID"`
Phone string `gorm:"type:varchar(20)" json:"phone" comment:"手机号"`
AuthorName string `gorm:"type:varchar(100);not null;default:''" json:"author_name" comment:"作者名称"`
AppID string `gorm:"type:varchar(127);not null;default:''" json:"app_id" comment:"应用ID"`
AppToken string `gorm:"type:varchar(127);not null;default:''" json:"app_token" comment:"应用Token"`
DepartmentID int `gorm:"not null;default:0" json:"department_id" comment:"部门ID"`
DepartmentName string `gorm:"type:varchar(255);not null;default:''" json:"department_name" comment:"部门名称"`
Department string `gorm:"type:varchar(50);not null;default:''" json:"department" comment:"部门"`
Title string `gorm:"type:varchar(50)" json:"title" comment:"职称"`
Hospital string `gorm:"type:varchar(100)" json:"hospital" comment:"医院"`
Specialty string `gorm:"type:text" json:"specialty" comment:"专业"`
ToutiaoCookie string `gorm:"type:text" json:"toutiao_cookie" comment:"头条Cookie"`
ToutiaoImagesCookie string `gorm:"type:text" json:"toutiao_images_cookie" comment:"头条图片Cookie"`
Introduction string `gorm:"type:text" json:"introduction" comment:"介绍"`
AvatarURL string `gorm:"type:varchar(255)" json:"avatar_url" comment:"头像URL"`
Status string `gorm:"type:enum('active','inactive');default:'active';index:idx_status" json:"status" comment:"状态"`
Channel int `gorm:"type:tinyint(1);not null;default:1" json:"channel" comment:"渠道1=baidu|2=toutiao|3=weixin"`
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
}
// TableName 指定表名带ai_前缀 // TableName 指定表名带ai_前缀
func (Enterprise) TableName() string { func (Enterprise) TableName() string {
return "ai_enterprises" return "ai_enterprises"
@@ -286,3 +316,7 @@ func (DataStatistics) TableName() string {
func (Log) TableName() string { func (Log) TableName() string {
return "ai_logs" return "ai_logs"
} }
func (Author) TableName() string {
return "ai_authors"
}

View File

@@ -66,6 +66,9 @@ func SetupRouter(r *gin.Engine) {
// 10.9 检查小红书绑定与Cookie状态 // 10.9 检查小红书绑定与Cookie状态
employee.GET("/xhs/status", employeeCtrl.CheckXHSStatus) employee.GET("/xhs/status", employeeCtrl.CheckXHSStatus)
// 10.10 更新文案状态(通过/拒绝)
employee.POST("/article/:id/status", employeeCtrl.UpdateArticleStatus)
} }
} }
} }

View File

@@ -12,6 +12,8 @@ import (
"io" "io"
"log" "log"
"net/http" "net/http"
"gorm.io/gorm"
) )
type AuthService struct{} 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 var employee models.User
// 通过手机号查找员工
// 优先通过OpenID查找注意使用IS NOT NULL过滤空值 result := database.DB.Where("phone = ? AND status = ?", phone, "active").First(&employee)
result := database.DB.Where("wechat_openid = ? AND wechat_openid IS NOT NULL", wxResp.OpenID).First(&employee)
if result.Error != nil { 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("该账号已在其他设备登录,请使用原设备登录或联系管理员")
} }
// 通过手机号查找员工 // 如果有UnionID也需要检查一致性
result = database.DB.Where("phone = ? AND status = ?", phone, "active").First(&employee) if employee.WechatUnionID != nil && *employee.WechatUnionID != "" && wxResp.UnionID != "" {
if result.Error != nil { if *employee.WechatUnionID != wxResp.UnionID {
return "", nil, errors.New("员工不存在,请联系管理员添加") 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 employee.WechatOpenID = &wxResp.OpenID
if wxResp.UnionID != "" { if wxResp.UnionID != "" {
employee.WechatUnionID = &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) token, err := utils.GenerateToken(employee.ID)
if err != nil { if err != nil {
return "", nil, fmt.Errorf("生成token失败: %v", err) return "", nil, fmt.Errorf("生成token失败: %v", err)

View File

@@ -43,12 +43,12 @@ func (s *EmployeeService) SendXHSCode(phone string) error {
// 执行命令 // 执行命令
err := cmd.Run() err := cmd.Run()
// 打印Python脚本的日志输出stderr // 打印Python脚本的日志输出stderr
if stderr.Len() > 0 { if stderr.Len() > 0 {
log.Printf("[Python日志-发送验证码] %s", stderr.String()) log.Printf("[Python日志-发送验证码] %s", stderr.String())
} }
if err != nil { if err != nil {
return fmt.Errorf("执行Python脚本失败: %w, stderr: %s", err, stderr.String()) 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 { if err != nil {
return nil, err return nil, err
} }
// 如果已绑定小红书且有Cookie,验证Cookie是否有效 // 如果已绑定小红书且有Cookie,验证Cookie是否有效
if employee.IsBoundXHS == 1 && employee.XHSCookie != "" { if employee.IsBoundXHS == 1 && employee.XHSCookie != "" {
// 检查绑定时间刚绑定的30秒内不验证避免与绑定操作冲突 // 检查绑定时间刚绑定的30秒内不验证避免与绑定操作冲突
@@ -96,7 +96,7 @@ func (s *EmployeeService) GetProfile(employeeID int) (*models.User, error) {
log.Printf("GetProfile - 用户%d有Cookie长度: %d已跳过自动验证", employeeID, len(employee.XHSCookie)) log.Printf("GetProfile - 用户%d有Cookie长度: %d已跳过自动验证", employeeID, len(employee.XHSCookie))
// go s.VerifyCookieAndClear(employeeID) // go s.VerifyCookieAndClear(employeeID)
} }
return &employee, nil return &employee, nil
} }
@@ -164,7 +164,7 @@ func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string
} else { } else {
log.Printf("绑定小红书 - 用户%d - 警告: cookiesData为nil", employeeID) log.Printf("绑定小红书 - 用户%d - 警告: cookiesData为nil", employeeID)
} }
if cookiesJSON == "" { if cookiesJSON == "" {
log.Printf("绑定小红书 - 用户%d - 错误: 未能获取到Cookie数据", employeeID) log.Printf("绑定小红书 - 用户%d - 错误: 未能获取到Cookie数据", employeeID)
return "", errors.New("登录成功但未能获取到Cookie数据请重试") return "", errors.New("登录成功但未能获取到Cookie数据请重试")
@@ -195,7 +195,7 @@ func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string
log.Printf("绑定小红书 - 用户%d - 数据库更新失败: %v", employeeID, err) log.Printf("绑定小红书 - 用户%d - 数据库更新失败: %v", employeeID, err)
return "", fmt.Errorf("更新员工绑定状态失败: %w", err) return "", fmt.Errorf("更新员工绑定状态失败: %w", err)
} }
log.Printf("绑定小红书 - 用户%d - 数据库更新成功", employeeID) log.Printf("绑定小红书 - 用户%d - 数据库更新成功", employeeID)
// 提交事务 // 提交事务
@@ -203,7 +203,7 @@ func (s *EmployeeService) BindXHS(employeeID int, xhsPhone, code string) (string
log.Printf("绑定小红书 - 用户%d - 事务提交失败: %v", employeeID, err) log.Printf("绑定小红书 - 用户%d - 事务提交失败: %v", employeeID, err)
return "", fmt.Errorf("提交事务失败: %w", err) return "", fmt.Errorf("提交事务失败: %w", err)
} }
log.Printf("绑定小红书 - 用户%d - 绑定成功 - 账号: %s", employeeID, xhsNickname) log.Printf("绑定小红书 - 用户%d - 绑定成功 - 账号: %s", employeeID, xhsNickname)
return xhsNickname, nil return xhsNickname, nil
} }
@@ -381,7 +381,7 @@ func (s *EmployeeService) VerifyCookieAndClear(employeeID int) error {
if employee.IsBoundXHS == 0 || employee.XHSCookie == "" { if employee.IsBoundXHS == 0 || employee.XHSCookie == "" {
return nil // 没有绑定或已无Cookie,直接返回 return nil // 没有绑定或已无Cookie,直接返回
} }
// 检查绑定时间刚绑定的30秒内不验证避免与绑定操作冲突 // 检查绑定时间刚绑定的30秒内不验证避免与绑定操作冲突
if employee.BoundAt != nil { if employee.BoundAt != nil {
timeSinceBound := time.Since(*employee.BoundAt) timeSinceBound := time.Since(*employee.BoundAt)
@@ -492,20 +492,37 @@ func (s *EmployeeService) clearXHSCookie(employeeID int) error {
return nil return nil
} }
// GetAvailableCopies 获取可领取的文案列表 // GetAvailableCopies 获取可领取的文案列表根据作者ID、产品ID和状态筛选
func (s *EmployeeService) GetAvailableCopies(employeeID int, productID int) (map[string]interface{}, error) { 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 var product models.Product
if err := database.DB.First(&product, productID).Error; err != nil { if err := database.DB.First(&product, productID).Error; err != nil {
return nil, err return nil, err
} }
// 获取该产品下所有可用文案注意新数据库中status有更多状态 // 根据产品ID、作者ID和状态筛选文案
// status = 'assign_authors' 表示已分配作者的文案
var copies []models.Article 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 { query := database.DB.Where("product_id = ? AND author_id = ? AND status = ?", productID, author.ID, "assign_authors")
return nil, err
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{}{ return map[string]interface{}{
"product": map[string]interface{}{ "product": map[string]interface{}{
"id": product.ID, "id": product.ID,
@@ -513,14 +530,106 @@ func (s *EmployeeService) GetAvailableCopies(employeeID int, productID int) (map
"image": product.ImageURL, "image": product.ImageURL,
}, },
"copies": copies, "copies": copies,
"author": map[string]interface{}{
"id": author.ID,
"name": author.AuthorName,
},
}, nil }, 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 领取文案(新版本:直接返回文案信息,不再创建领取记录) // ClaimCopy 领取文案(新版本:直接返回文案信息,不再创建领取记录)
func (s *EmployeeService) ClaimCopy(employeeID int, copyID int, productID int) (map[string]interface{}, error) { func (s *EmployeeService) ClaimCopy(employeeID int, copyID int, productID int) (map[string]interface{}, error) {
// 检查文案是否存在且可用注意新数据库中status有更多状态 // 检查文案是否存在且可用注意新数据库中status有更多状态
// assign_authors: 已分配给作者,可以直接发布
// draft: 草稿状态
// approved: 已审核通过
var copy models.Article var copy models.Article
if err := database.DB.Where("id = ? AND status IN ?", copyID, []string{"draft", "approved"}).First(&copy).Error; err != nil { if err := database.DB.Where("id = ? AND status IN ?", copyID, []string{"draft", "approved", "assign_authors"}).First(&copy).Error; err != nil {
return nil, errors.New("文案不存在或不可用") 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) { func (s *EmployeeService) ClaimRandomCopy(employeeID int, productID int) (map[string]interface{}, error) {
// 查询未领取的可用文案注意新数据库中status有更多状态 // 查询未领取的可用文案注意新数据库中status有更多状态
var copy models.Article 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(&copy).Error; err != nil { if err := query.Order("RAND()").First(&copy).Error; err != nil {
return nil, errors.New("暂无可领取的文案") return nil, errors.New("暂无可领取的文案")

View File

@@ -25,8 +25,8 @@ interface EnvConfig {
const API_CONFIG: Record<EnvType, EnvConfig> = { const API_CONFIG: Record<EnvType, EnvConfig> = {
// 开发环境 - 本地开发 // 开发环境 - 本地开发
dev: { dev: {
baseURL: 'http://localhost:8080', // 本地Go服务 baseURL: 'http://192.168.17.127:8080', // 本地Go服务
pythonURL: 'http://localhost:8000', // 本地Python服务 pythonURL: 'http://192.168.17.127:8000', // 本地Python服务
timeout: 90000 timeout: 90000
}, },

View File

@@ -12,7 +12,8 @@ Page({
currentCopy: null as any, // 当前显示的文案 currentCopy: null as any, // 当前显示的文案
loading: false, loading: false,
claiming: false, // 领取中 claiming: false, // 领取中
publishing: false // 发布中 publishing: false, // 发布中
rejecting: false // 拒绝中
}, },
onLoad(options: any) { onLoad(options: any) {
@@ -150,7 +151,7 @@ Page({
// 换一个文案 // 换一个文案
changeArticle() { changeArticle() {
if (this.data.claiming || this.data.publishing) return; if (this.data.claiming || this.data.publishing || this.data.rejecting) return;
const { allCopies, currentIndex } = this.data; const { allCopies, currentIndex } = this.data;
@@ -162,19 +163,25 @@ Page({
return; return;
} }
// 切换到下一个文案 // 显示加载动画
const nextIndex = (currentIndex + 1) % allCopies.length; wx.showLoading({
title: '加载中...',
this.setData({ mask: true
currentIndex: nextIndex,
currentCopy: allCopies[nextIndex]
}); });
wx.showToast({ // 模拟加载效果,让切换更平滑
title: `${nextIndex + 1}/${allCopies.length}`, setTimeout(() => {
icon: 'none', // 切换到下一个文案
duration: 1000 const nextIndex = (currentIndex + 1) % allCopies.length;
});
this.setData({
currentIndex: nextIndex,
currentCopy: allCopies[nextIndex]
});
// 隐藏加载动画
wx.hideLoading();
}, 300);
}, },
// 一键发布(先领取,再发布) // 一键发布(先领取,再发布)
@@ -281,6 +288,84 @@ Page({
}; };
}, },
// 拒绝文案
async rejectArticle() {
if (!this.data.currentCopy) {
wx.showToast({
title: '请先选择文案',
icon: 'none'
});
return;
}
wx.showModal({
title: '确认拒绝',
content: '确定要拒绝这篇文案吗?',
confirmText: '拒绝',
confirmColor: '#ff4d4f',
success: async (res) => {
if (res.confirm) {
await this.doRejectArticle();
}
}
});
},
// 执行拒绝操作
async doRejectArticle() {
this.setData({ rejecting: true });
try {
const response = await EmployeeService.updateArticleStatus(
this.data.currentCopy.id,
'rejected'
);
if (response.code === 200) {
wx.showToast({
title: '已拒绝',
icon: 'success',
duration: 1500
});
// 从列表中移除当前文案
const { allCopies, currentIndex } = this.data;
allCopies.splice(currentIndex, 1);
// 更新显示
if (allCopies.length > 0) {
const newIndex = currentIndex >= allCopies.length ? 0 : currentIndex;
this.setData({
allCopies,
currentIndex: newIndex,
currentCopy: allCopies[newIndex],
rejecting: false
});
} else {
// 没有更多文案了
this.setData({
allCopies: [],
currentCopy: null,
rejecting: false
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
} else {
throw new Error(response.message || '拒绝失败');
}
} catch (error: any) {
console.error('拒绝失败:', error);
this.setData({ rejecting: false });
wx.showToast({
title: error.message || '操作失败',
icon: 'none'
});
}
},
// 分享到朋友圈 // 分享到朋友圈
onShareTimeline() { onShareTimeline() {
return { return {

View File

@@ -53,16 +53,24 @@
<!-- 底部操作栏 --> <!-- 底部操作栏 -->
<view class="action-bar" wx:if="{{currentCopy}}"> <view class="action-bar" wx:if="{{currentCopy}}">
<button <button
class="action-btn secondary" class="action-btn refresh"
bindtap="changeArticle" bindtap="changeArticle"
disabled="{{claiming || publishing}}" disabled="{{claiming || publishing || rejecting}}"
> >
<text class="btn-text">换一换</text> <text class="btn-text">换一换</text>
</button> </button>
<button
class="action-btn reject"
bindtap="rejectArticle"
disabled="{{claiming || publishing || rejecting}}"
loading="{{rejecting}}"
>
<text class="btn-text">{{rejecting ? '拒绝中...' : '拒绝'}}</text>
</button>
<button <button
class="action-btn primary" class="action-btn primary"
bindtap="publishArticle" bindtap="publishArticle"
disabled="{{claiming || publishing}}" disabled="{{claiming || publishing || rejecting}}"
loading="{{claiming || publishing}}" loading="{{claiming || publishing}}"
> >
<text class="btn-text">{{claiming ? '领取中...' : (publishing ? '发布中...' : '一键发布')}}</text> <text class="btn-text">{{claiming ? '领取中...' : (publishing ? '发布中...' : '一键发布')}}</text>

View File

@@ -173,7 +173,7 @@ page {
left: 0; left: 0;
right: 0; right: 0;
display: flex; display: flex;
gap: 20rpx; gap: 16rpx;
padding: 20rpx 30rpx; padding: 20rpx 30rpx;
background: white; background: white;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08); box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
@@ -182,11 +182,11 @@ page {
} }
.action-btn { .action-btn {
height: 80rpx; height: 88rpx;
border: none; border: none;
border-radius: 40rpx; border-radius: 44rpx;
font-size: 28rpx; font-size: 28rpx;
font-weight: 500; font-weight: 600;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -198,20 +198,34 @@ page {
border: none; border: none;
} }
.action-btn.secondary { /* 换一换按钮 */
flex: 0 0 180rpx; .action-btn.refresh {
flex: 0 0 220rpx;
background: #f5f5f5; background: #f5f5f5;
color: #333; color: #333;
} }
.action-btn.secondary:active { .action-btn.refresh:active {
background: #e8e8e8; background: #e8e8e8;
} }
/* 拒绝按钮 */
.action-btn.reject {
flex: 0 0 180rpx;
background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
color: white;
}
.action-btn.reject:active {
opacity: 0.9;
}
/* 一键发布按钮 */
.action-btn.primary { .action-btn.primary {
flex: 1; flex: 1;
background: #07c160; background: linear-gradient(135deg, #07c160 0%, #0ae97a 100%);
color: white; color: white;
box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.3);
} }
.action-btn.primary:active { .action-btn.primary:active {
@@ -219,7 +233,7 @@ page {
} }
.action-btn[disabled] { .action-btn[disabled] {
opacity: 0.6; opacity: 0.5;
} }
.btn-icon { .btn-icon {

View File

@@ -210,4 +210,13 @@ export class EmployeeService {
publish_time: string; publish_time: string;
}>(`/api/employee/publish-record/${recordId}`); }>(`/api/employee/publish-record/${recordId}`);
} }
/**
* 更新文案状态(通过/拒绝)
*/
static async updateArticleStatus(articleId: number, status: 'approved' | 'rejected') {
return post(`/api/employee/article/${articleId}/status`, {
status
});
}
} }