完善文案的状态流转
This commit is contained in:
@@ -27,7 +27,7 @@ xhs:
|
||||
python_service_url: "http://localhost:8000" # Python服务地址
|
||||
|
||||
scheduler:
|
||||
enabled: true # 是否启用定时任务
|
||||
enabled: false # 是否启用定时任务
|
||||
publish_cron: "* * * * * *" # 每1小时执行一次(开发环境测试用)
|
||||
max_concurrent: 2 # 最大并发发布数
|
||||
publish_timeout: 300 # 发布超时时间(秒)
|
||||
|
||||
@@ -27,7 +27,7 @@ xhs:
|
||||
python_service_url: "http://localhost:8000" # Python服务地址,生产环境请修改为实际地址
|
||||
|
||||
scheduler:
|
||||
enabled: true # 是否启用定时任务
|
||||
enabled: false # 是否启用定时任务
|
||||
publish_cron: "0 0 */2 * * *" # 每2小时执行一次(防封号策略)
|
||||
max_concurrent: 2 # 最大并发发布数
|
||||
publish_timeout: 300 # 发布超时时间(秒)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type EmployeeController struct {
|
||||
service *service.EmployeeService
|
||||
}
|
||||
@@ -255,3 +256,41 @@ func (ctrl *EmployeeController) GetProducts(c *gin.Context) {
|
||||
"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)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ func AutoMigrate() error {
|
||||
&models.PublishRecord{},
|
||||
&models.ArticleImage{},
|
||||
&models.ArticleTag{},
|
||||
&models.Author{},
|
||||
&models.Log{},
|
||||
// &models.XHSAccount{}, // 不再使用,小红书信息直接存储在 ai_users 表中
|
||||
)
|
||||
|
||||
@@ -6,24 +6,25 @@ import (
|
||||
|
||||
// Enterprise 企业表
|
||||
type Enterprise struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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:"企业名称"`
|
||||
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"`
|
||||
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:"登录密码(加密存储)"`
|
||||
Email string `gorm:"type:varchar(128);not null;default:''" json:"email" 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:"企业地址"`
|
||||
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:"员工总数"`
|
||||
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:"文章总数"`
|
||||
ReleasedMonthTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"released_month_total" comment:"本月发布数量"`
|
||||
LinkedToXHSNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"linked_to_xhs_num" comment:"绑定小红书"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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:"企业名称"`
|
||||
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"`
|
||||
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:"登录密码(加密存储)"`
|
||||
Email string `gorm:"type:varchar(128);not null;default:''" json:"email" 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:"企业地址"`
|
||||
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:"员工总数"`
|
||||
ProductsTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"products_total" comment:"产品总数"`
|
||||
PublishedTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"published_total" comment:"文章发布总数"`
|
||||
ArticlesTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"articles_total" comment:"文章总数"`
|
||||
ReleasedMonthTotal int `gorm:"type:int(10) unsigned;not null;default:0" json:"released_month_total" comment:"本月发布数量"`
|
||||
LinkedToXHSNum int `gorm:"type:int(10) unsigned;not null;default:0" json:"linked_to_xhs_num" comment:"绑定小红书"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// User 用户账号表(原Employee,对应ai_users)
|
||||
@@ -37,15 +38,15 @@ type User struct {
|
||||
RealName string `gorm:"type:varchar(50)" json:"real_name" comment:"真实姓名"`
|
||||
Email string `gorm:"type:varchar(100)" json:"email" 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"`
|
||||
WechatUnionID *string `gorm:"type:varchar(100)" json:"wechat_unionid,omitempty" comment:"微信UnionID"`
|
||||
WechatOpenID *string `gorm:"column:wechat_openid;type:varchar(100);uniqueIndex:uk_wechat_openid" json:"wechat_openid,omitempty" comment:"微信OpenID"`
|
||||
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:"小红书绑定手机号"`
|
||||
XHSAccount string `gorm:"type:varchar(255);not null;default:''" json:"xhs_account" comment:"小红书账号名称"`
|
||||
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=已绑定"`
|
||||
BoundAt *time.Time `json:"bound_at" 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:"状态"`
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
@@ -59,6 +60,7 @@ type Product struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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:"产品名称"`
|
||||
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:"产品类型"`
|
||||
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"`
|
||||
@@ -72,35 +74,35 @@ type Product struct {
|
||||
|
||||
// Article 文章表(原Copy,对应ai_articles)
|
||||
type Article struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
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"`
|
||||
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主题"`
|
||||
Title string `gorm:"type:varchar(200);not null;default:''" json:"title" comment:"标题"`
|
||||
Content string `gorm:"type:text" json:"content" 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"`
|
||||
AuthorID *int `json:"author_id" comment:"作者ID"`
|
||||
AuthorName string `gorm:"type:varchar(100)" json:"author_name" comment:"作者名称"`
|
||||
DepartmentID *int `json:"department_id" comment:"部门ID"`
|
||||
DepartmentName string `gorm:"type:varchar(255)" json:"department_name" comment:"部门名称"`
|
||||
CreatedUserID int `gorm:"not null;default:0" json:"created_user_id" comment:"创建用户ID"`
|
||||
ReviewUserID *int `json:"review_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:"状态"`
|
||||
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:"审核评论"`
|
||||
PublishTime *time.Time `json:"publish_time" comment:"发布时间"`
|
||||
BaijiahaoID string `gorm:"type:varchar(100)" json:"baijiahao_id" comment:"百家号ID"`
|
||||
BaijiahaoStatus string `gorm:"type:varchar(50)" json:"baijiahao_status" comment:"百家号状态"`
|
||||
WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"`
|
||||
ImageCount int `gorm:"default:0" json:"image_count" comment:"图片数量"`
|
||||
CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"index:idx_updated_at" json:"updated_at" comment:"更新时间"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
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"`
|
||||
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主题"`
|
||||
Title string `gorm:"type:varchar(200);not null;default:''" json:"title" comment:"标题"`
|
||||
Content string `gorm:"type:text" json:"content" 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"`
|
||||
AuthorID *int `json:"author_id" comment:"作者ID"`
|
||||
AuthorName string `gorm:"type:varchar(100)" json:"author_name" comment:"作者名称"`
|
||||
DepartmentID *int `json:"department_id" comment:"部门ID"`
|
||||
DepartmentName string `gorm:"type:varchar(255)" json:"department_name" comment:"部门名称"`
|
||||
CreatedUserID int `gorm:"not null;default:0" json:"created_user_id" comment:"创建用户ID"`
|
||||
ReviewUserID *int `json:"review_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','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"`
|
||||
ReviewComment string `gorm:"type:text" json:"review_comment" comment:"审核评论"`
|
||||
PublishTime *time.Time `json:"publish_time" comment:"发布时间"`
|
||||
BaijiahaoID string `gorm:"type:varchar(100)" json:"baijiahao_id" comment:"百家号ID"`
|
||||
BaijiahaoStatus string `gorm:"type:varchar(50)" json:"baijiahao_status" comment:"百家号状态"`
|
||||
WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"`
|
||||
ImageCount int `gorm:"default:0" json:"image_count" comment:"图片数量"`
|
||||
CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"index:idx_updated_at" json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// Copy 文案表别名,兼容旧代码
|
||||
@@ -108,83 +110,85 @@ type Copy = Article
|
||||
|
||||
// PublishRecord 发布记录表(对应ai_article_published_records)
|
||||
type PublishRecord struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
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主题"`
|
||||
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"`
|
||||
ReviewUserID *int `json:"review_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:"状态"`
|
||||
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:"审核评论"`
|
||||
PublishTime *time.Time `json:"publish_time" comment:"发布时间"`
|
||||
PublishLink string `gorm:"type:varchar(128);not null;default:''" json:"publish_link" comment:"发布访问链接"`
|
||||
WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"`
|
||||
ImageCount int `gorm:"default:0" json:"image_count" 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:"更新时间"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
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主题"`
|
||||
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"`
|
||||
ReviewUserID *int `json:"review_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','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"`
|
||||
ReviewComment string `gorm:"type:text" json:"review_comment" comment:"审核评论"`
|
||||
PublishTime *time.Time `json:"publish_time" comment:"发布时间"`
|
||||
PublishLink string `gorm:"type:varchar(128);not null;default:''" json:"publish_link" comment:"发布访问链接"`
|
||||
WordCount int `gorm:"default:0" json:"word_count" comment:"字数统计"`
|
||||
ImageCount int `gorm:"default:0" json:"image_count" 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:"更新时间"`
|
||||
}
|
||||
|
||||
// XHSAccount 小红书账号表(保持兼容)
|
||||
type XHSAccount struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
EmployeeID int `gorm:"not null;default:0;uniqueIndex:uk_employee_id" json:"employee_id"`
|
||||
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"`
|
||||
XHSNickname string `gorm:"type:varchar(100);not null;default:''" json:"xhs_nickname"`
|
||||
XHSPhone string `gorm:"type:varchar(20);not null;default:''" json:"xhs_phone"`
|
||||
XHSAvatar string `gorm:"type:varchar(500);not null;default:''" json:"xhs_avatar"`
|
||||
FansCount int `gorm:"not null;default:0" json:"fans_count"`
|
||||
NotesCount int `gorm:"not null;default:0" json:"notes_count"`
|
||||
Cookies string `gorm:"type:text" json:"cookies"`
|
||||
AccessToken string `gorm:"type:varchar(500);not null;default:''" json:"access_token"`
|
||||
RefreshToken string `gorm:"type:varchar(500);not null;default:''" json:"refresh_token"`
|
||||
TokenExpireAt *time.Time `json:"token_expire_at"`
|
||||
Status string `gorm:"type:enum('active','expired','banned');default:'active';index:idx_status" json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
EmployeeID int `gorm:"not null;default:0;uniqueIndex:uk_employee_id" json:"employee_id"`
|
||||
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"`
|
||||
XHSNickname string `gorm:"type:varchar(100);not null;default:''" json:"xhs_nickname"`
|
||||
XHSPhone string `gorm:"type:varchar(20);not null;default:''" json:"xhs_phone"`
|
||||
XHSAvatar string `gorm:"type:varchar(500);not null;default:''" json:"xhs_avatar"`
|
||||
FansCount int `gorm:"not null;default:0" json:"fans_count"`
|
||||
NotesCount int `gorm:"not null;default:0" json:"notes_count"`
|
||||
Cookies string `gorm:"type:text" json:"cookies"`
|
||||
AccessToken string `gorm:"type:varchar(500);not null;default:''" json:"access_token"`
|
||||
RefreshToken string `gorm:"type:varchar(500);not null;default:''" json:"refresh_token"`
|
||||
TokenExpireAt *time.Time `json:"token_expire_at"`
|
||||
Status string `gorm:"type:enum('active','expired','banned');default:'active';index:idx_status" json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// PromptWorkflow 提示词工作流表
|
||||
type PromptWorkflow struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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:"提示词工作流名称"`
|
||||
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"`
|
||||
Content string `gorm:"type:text" json:"content" 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"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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:"提示词工作流名称"`
|
||||
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"`
|
||||
Content string `gorm:"type:text" json:"content" 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"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// ProductImage 产品图片库表
|
||||
type ProductImage struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
ImageID int `gorm:"not null;default:0" json:"image_id" comment:"图片ID"`
|
||||
ImageName string `gorm:"type:varchar(255);not null;default:''" json:"image_name" comment:"图片名称"`
|
||||
ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"图片URL"`
|
||||
ThumbnailURL string `gorm:"type:varchar(500);not null;default:''" json:"thumbnail_url" comment:"缩略图URL"`
|
||||
TypeName string `gorm:"type:varchar(50);not null;default:''" json:"type_name" comment:"图片类型"`
|
||||
Description string `gorm:"type:varchar(500);not null;default:''" json:"description" comment:"图片描述"`
|
||||
FileSize *int64 `json:"file_size" comment:"文件大小"`
|
||||
Width *int `json:"width" comment:"图片宽度"`
|
||||
Height *int `json:"height" comment:"图片高度"`
|
||||
UploadUserID int `gorm:"not null;default:0" json:"upload_user_id" comment:"上传用户ID"`
|
||||
Status string `gorm:"type:enum('active','deleted');default:'active';index:idx_status" json:"status" comment:"状态"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"上传时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
ProductName string `gorm:"type:varchar(256);not null;default:''" json:"product_name" comment:"产品名称"`
|
||||
ImageID int `gorm:"not null;default:0" json:"image_id" comment:"图片ID"`
|
||||
ImageName string `gorm:"type:varchar(255);not null;default:''" json:"image_name" comment:"图片名称"`
|
||||
ImageURL string `gorm:"type:varchar(500);not null;default:''" json:"image_url" comment:"图片URL"`
|
||||
ThumbnailURL string `gorm:"type:varchar(500);not null;default:''" json:"thumbnail_url" comment:"缩略图URL"`
|
||||
TypeName string `gorm:"type:varchar(50);not null;default:''" json:"type_name" comment:"图片类型"`
|
||||
Description string `gorm:"type:varchar(500);not null;default:''" json:"description" comment:"图片描述"`
|
||||
FileSize *int64 `json:"file_size" comment:"文件大小"`
|
||||
Width *int `json:"width" comment:"图片宽度"`
|
||||
Height *int `json:"height" comment:"图片高度"`
|
||||
UploadUserID int `gorm:"not null;default:0" json:"upload_user_id" comment:"上传用户ID"`
|
||||
Status string `gorm:"type:enum('active','deleted');default:'active';index:idx_status" json:"status" comment:"状态"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"上传时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// ArticleImage 文章图片表
|
||||
type ArticleImage struct {
|
||||
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"`
|
||||
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"`
|
||||
@@ -202,23 +206,24 @@ type ArticleImage struct {
|
||||
|
||||
// ArticleTag 文章标签表
|
||||
type ArticleTag struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
ArticleID int `gorm:"not null;default:0;uniqueIndex:uk_article_tag" json:"article_id" comment:"文章ID"`
|
||||
CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
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;uniqueIndex:uk_article_tag" json:"article_id" comment:"文章ID"`
|
||||
CozeTag string `gorm:"type:varchar(500)" json:"coze_tag" comment:"Coze生成的标签"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
}
|
||||
|
||||
// DataStatistics 数据统计表
|
||||
type DataStatistics struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
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:"今日发布"`
|
||||
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:"参与员工"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"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"`
|
||||
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:"今日发布"`
|
||||
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:"参与员工"`
|
||||
CreatedAt time.Time `gorm:"index:idx_created_at" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// Log 操作日志表(对应ai_logs)
|
||||
@@ -238,6 +243,31 @@ type Log struct {
|
||||
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_前缀)
|
||||
func (Enterprise) TableName() string {
|
||||
return "ai_enterprises"
|
||||
@@ -286,3 +316,7 @@ func (DataStatistics) TableName() string {
|
||||
func (Log) TableName() string {
|
||||
return "ai_logs"
|
||||
}
|
||||
|
||||
func (Author) TableName() string {
|
||||
return "ai_authors"
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ func SetupRouter(r *gin.Engine) {
|
||||
|
||||
// 10.9 检查小红书绑定与Cookie状态
|
||||
employee.GET("/xhs/status", employeeCtrl.CheckXHSStatus)
|
||||
|
||||
// 10.10 更新文案状态(通过/拒绝)
|
||||
employee.POST("/article/:id/status", employeeCtrl.UpdateArticleStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("暂无可领取的文案")
|
||||
|
||||
@@ -25,8 +25,8 @@ interface EnvConfig {
|
||||
const API_CONFIG: Record<EnvType, EnvConfig> = {
|
||||
// 开发环境 - 本地开发
|
||||
dev: {
|
||||
baseURL: 'http://localhost:8080', // 本地Go服务
|
||||
pythonURL: 'http://localhost:8000', // 本地Python服务
|
||||
baseURL: 'http://192.168.17.127:8080', // 本地Go服务
|
||||
pythonURL: 'http://192.168.17.127:8000', // 本地Python服务
|
||||
timeout: 90000
|
||||
},
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ Page({
|
||||
currentCopy: null as any, // 当前显示的文案
|
||||
loading: false,
|
||||
claiming: false, // 领取中
|
||||
publishing: false // 发布中
|
||||
publishing: false, // 发布中
|
||||
rejecting: false // 拒绝中
|
||||
},
|
||||
|
||||
onLoad(options: any) {
|
||||
@@ -150,7 +151,7 @@ Page({
|
||||
|
||||
// 换一个文案
|
||||
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;
|
||||
|
||||
@@ -162,19 +163,25 @@ Page({
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换到下一个文案
|
||||
const nextIndex = (currentIndex + 1) % allCopies.length;
|
||||
|
||||
this.setData({
|
||||
currentIndex: nextIndex,
|
||||
currentCopy: allCopies[nextIndex]
|
||||
// 显示加载动画
|
||||
wx.showLoading({
|
||||
title: '加载中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: `${nextIndex + 1}/${allCopies.length}`,
|
||||
icon: 'none',
|
||||
duration: 1000
|
||||
});
|
||||
// 模拟加载效果,让切换更平滑
|
||||
setTimeout(() => {
|
||||
// 切换到下一个文案
|
||||
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() {
|
||||
return {
|
||||
|
||||
@@ -53,16 +53,24 @@
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="action-bar" wx:if="{{currentCopy}}">
|
||||
<button
|
||||
class="action-btn secondary"
|
||||
class="action-btn refresh"
|
||||
bindtap="changeArticle"
|
||||
disabled="{{claiming || publishing}}"
|
||||
disabled="{{claiming || publishing || rejecting}}"
|
||||
>
|
||||
<text class="btn-text">换一换</text>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn reject"
|
||||
bindtap="rejectArticle"
|
||||
disabled="{{claiming || publishing || rejecting}}"
|
||||
loading="{{rejecting}}"
|
||||
>
|
||||
<text class="btn-text">{{rejecting ? '拒绝中...' : '拒绝'}}</text>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn primary"
|
||||
bindtap="publishArticle"
|
||||
disabled="{{claiming || publishing}}"
|
||||
disabled="{{claiming || publishing || rejecting}}"
|
||||
loading="{{claiming || publishing}}"
|
||||
>
|
||||
<text class="btn-text">{{claiming ? '领取中...' : (publishing ? '发布中...' : '一键发布')}}</text>
|
||||
|
||||
@@ -173,7 +173,7 @@ page {
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
background: white;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
@@ -182,11 +182,11 @@ page {
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
height: 80rpx;
|
||||
height: 88rpx;
|
||||
border: none;
|
||||
border-radius: 40rpx;
|
||||
border-radius: 44rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -198,20 +198,34 @@ page {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
flex: 0 0 180rpx;
|
||||
/* 换一换按钮 */
|
||||
.action-btn.refresh {
|
||||
flex: 0 0 220rpx;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.action-btn.secondary:active {
|
||||
.action-btn.refresh:active {
|
||||
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 {
|
||||
flex: 1;
|
||||
background: #07c160;
|
||||
background: linear-gradient(135deg, #07c160 0%, #0ae97a 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.primary:active {
|
||||
@@ -219,7 +233,7 @@ page {
|
||||
}
|
||||
|
||||
.action-btn[disabled] {
|
||||
opacity: 0.6;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
|
||||
@@ -210,4 +210,13 @@ export class EmployeeService {
|
||||
publish_time: string;
|
||||
}>(`/api/employee/publish-record/${recordId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文案状态(通过/拒绝)
|
||||
*/
|
||||
static async updateArticleStatus(articleId: number, status: 'approved' | 'rejected') {
|
||||
return post(`/api/employee/article/${articleId}/status`, {
|
||||
status
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user