feat: 微信公众号技能包重命名(weixin_public_account)并增强功能

- 将 wechat_official_account 重命名为 weixin_public_account,符合中文命名规范
- 新增 DOCX 文档生成技能、聊天历史、请求 ID 中间件
- 增强工作流、热点服务、文章服务等模块功能
- 前端同步重命名组件和 API
- 新增架构文档 AR13/AR14、专员文档更新
- 补充测试用例(seed_specialists_test, db_migration_test)

Co-Authored-AI: yes
This commit is contained in:
eaiadmin
2026-09-24 21:13:19 +08:00
parent 593323a934
commit 89ae31c998
60 changed files with 7251 additions and 967 deletions
@@ -11,7 +11,7 @@ import (
"eai_agentplatform/backend/internal/model"
skillmodel "eai_agentplatform/backend/internal/skills/model"
officialaccountmodel "eai_agentplatform/backend/internal/specialists/packages/wechat_official_account/model"
weixinpublicaccountmodel "eai_agentplatform/backend/internal/specialists/packages/weixin_public_account/model"
specialistmodel "eai_agentplatform/backend/internal/specialists/model"
)
@@ -45,6 +45,9 @@ func Init(dbPath string) (*gorm.DB, error) {
if err := migrateObjectModeColumns(db); err != nil {
return nil, err
}
if err := purgeLegacyWeixinPublicAccountData(db); err != nil {
return nil, err
}
if err := db.AutoMigrate(
&model.User{},
@@ -60,6 +63,8 @@ func Init(dbPath string) (*gorm.DB, error) {
&model.SystemConfig{},
&model.KnowledgeSource{},
&model.AiCallLog{},
&model.ChatConversation{},
&model.ChatMessage{},
&model.LearningProgress{},
&model.Position{},
&model.PositionKnowledge{},
@@ -78,8 +83,8 @@ func Init(dbPath string) (*gorm.DB, error) {
&model.TaskRun{},
&model.UserXAppCenter{},
&model.XAppDefinition{},
&officialaccountmodel.OfficialAccountArticle{},
&officialaccountmodel.OfficialAccountHotspot{},
&weixinpublicaccountmodel.WeixinPublicAccountArticle{},
&weixinpublicaccountmodel.WeixinPublicAccountHotspot{},
); err != nil {
return nil, err
}
@@ -288,6 +293,74 @@ func normalizeSkillObjectKinds(db *gorm.DB) error {
`).Error
}
func purgeLegacyWeixinPublicAccountData(db *gorm.DB) error {
const legacySpecialistKey = "wechat-official-account"
changed := false
if db.Migrator().HasTable("task_artifact") {
if result := db.Exec(`DELETE FROM task_artifact WHERE specialist_key = ?`, legacySpecialistKey); result.Error != nil {
return result.Error
} else if result.RowsAffected > 0 {
changed = true
}
}
if db.Migrator().HasTable("task_run") {
if result := db.Exec(`DELETE FROM task_run WHERE specialist_key = ?`, legacySpecialistKey); result.Error != nil {
return result.Error
} else if result.RowsAffected > 0 {
changed = true
}
}
if db.Migrator().HasTable("task_record") {
if result := db.Exec(`DELETE FROM task_record WHERE specialist_key = ?`, legacySpecialistKey); result.Error != nil {
return result.Error
} else if result.RowsAffected > 0 {
changed = true
}
}
if db.Migrator().HasTable("weixin_public_account_article") {
if result := db.Exec(`DELETE FROM weixin_public_account_article WHERE specialist_key = ?`, legacySpecialistKey); result.Error != nil {
return result.Error
} else if result.RowsAffected > 0 {
changed = true
}
}
if db.Migrator().HasTable("specialist") {
if result := db.Exec(`DELETE FROM specialist WHERE key = ?`, legacySpecialistKey); result.Error != nil {
return result.Error
} else if result.RowsAffected > 0 {
changed = true
}
}
if db.Migrator().HasTable("official_account_article") {
if err := db.Migrator().DropTable("official_account_article"); err != nil {
return err
}
changed = true
}
if db.Migrator().HasTable("official_account_hotspot") {
if err := db.Migrator().DropTable("official_account_hotspot"); err != nil {
return err
}
changed = true
}
if changed {
if err := db.Exec(`VACUUM`).Error; err != nil {
return err
}
}
return nil
}
func migrateColumnData(db *gorm.DB, tableName, targetColumn, legacyColumn string) error {
if !db.Migrator().HasColumn(tableName, legacyColumn) {
return nil
@@ -9,6 +9,7 @@ import (
"eai_agentplatform/backend/internal/model"
skillmodel "eai_agentplatform/backend/internal/skills/model"
weixinpublicaccountmodel "eai_agentplatform/backend/internal/specialists/packages/weixin_public_account/model"
specialistmodel "eai_agentplatform/backend/internal/specialists/model"
)
@@ -258,6 +259,198 @@ func TestMigrateAIUsageKindColumnDropsLegacyColumn(t *testing.T) {
}
}
func TestPurgeLegacyWeixinPublicAccountDataRemovesOldRowsAndTables(t *testing.T) {
db := openMigrationTestDB(t)
if err := db.AutoMigrate(
&specialistmodel.Specialist{},
&model.TaskRecord{},
&model.TaskArtifact{},
&model.TaskRun{},
&weixinpublicaccountmodel.WeixinPublicAccountArticle{},
); err != nil {
t.Fatalf("auto migrate: %v", err)
}
if err := db.Exec(`
CREATE TABLE official_account_article (
id INTEGER PRIMARY KEY AUTOINCREMENT,
specialist_key TEXT NOT NULL
)
`).Error; err != nil {
t.Fatalf("create official_account_article: %v", err)
}
if err := db.Exec(`
CREATE TABLE official_account_hotspot (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_key TEXT NOT NULL
)
`).Error; err != nil {
t.Fatalf("create official_account_hotspot: %v", err)
}
legacySpecialist := specialistmodel.Specialist{
Key: "wechat-official-account",
Label: "公众号助手",
Tier: "generic",
SpecialistMode: "dw",
ObjectEntryRoute: "/apps/weixin-public-account",
State: "active",
MarketTag: "installed",
}
if err := db.Create(&legacySpecialist).Error; err != nil {
t.Fatalf("create legacy specialist: %v", err)
}
currentSpecialist := specialistmodel.Specialist{
Key: "weixin-public-account",
Label: "公众号创作专员",
Tier: "generic",
SpecialistMode: "dw",
ObjectEntryRoute: "/apps/weixin-public-account",
State: "active",
MarketTag: "installed",
}
if err := db.Create(&currentSpecialist).Error; err != nil {
t.Fatalf("create current specialist: %v", err)
}
legacyTask := model.TaskRecord{
SpecialistKey: "wechat-official-account",
Title: "旧公众号任务",
Priority: "P2",
Status: "待处理",
}
if err := db.Create(&legacyTask).Error; err != nil {
t.Fatalf("create legacy task: %v", err)
}
keepTask := model.TaskRecord{
SpecialistKey: "weixin-public-account",
Title: "新公众号任务",
Priority: "P2",
Status: "待处理",
}
if err := db.Create(&keepTask).Error; err != nil {
t.Fatalf("create current task: %v", err)
}
legacyArtifact := model.TaskArtifact{
TaskID: legacyTask.ID,
SpecialistKey: "wechat-official-account",
Title: "旧产物",
}
if err := db.Create(&legacyArtifact).Error; err != nil {
t.Fatalf("create legacy artifact: %v", err)
}
keepArtifact := model.TaskArtifact{
TaskID: keepTask.ID,
SpecialistKey: "weixin-public-account",
Title: "新产物",
}
if err := db.Create(&keepArtifact).Error; err != nil {
t.Fatalf("create current artifact: %v", err)
}
legacyRun := model.TaskRun{
TaskID: legacyTask.ID,
SpecialistKey: "wechat-official-account",
ActionKey: "topic",
ActionTitle: "旧运行",
}
if err := db.Create(&legacyRun).Error; err != nil {
t.Fatalf("create legacy run: %v", err)
}
keepRun := model.TaskRun{
TaskID: keepTask.ID,
SpecialistKey: "weixin-public-account",
ActionKey: "topic",
ActionTitle: "新运行",
}
if err := db.Create(&keepRun).Error; err != nil {
t.Fatalf("create current run: %v", err)
}
legacyArticle := weixinpublicaccountmodel.WeixinPublicAccountArticle{
TaskID: legacyTask.ID,
SpecialistKey: "wechat-official-account",
Keyword: "外骨骼机器人",
Audience: "合作方",
Goal: "推广",
Tone: "专业",
}
if err := db.Create(&legacyArticle).Error; err != nil {
t.Fatalf("create legacy article: %v", err)
}
keepArticle := weixinpublicaccountmodel.WeixinPublicAccountArticle{
TaskID: keepTask.ID,
SpecialistKey: "weixin-public-account",
Keyword: "外骨骼机器人",
Audience: "合作方",
Goal: "推广",
Tone: "专业",
}
if err := db.Create(&keepArticle).Error; err != nil {
t.Fatalf("create current article: %v", err)
}
if err := purgeLegacyWeixinPublicAccountData(db); err != nil {
t.Fatalf("purge legacy weixin public account data: %v", err)
}
var count int64
if err := db.Model(&specialistmodel.Specialist{}).Where("key = ?", "wechat-official-account").Count(&count).Error; err != nil {
t.Fatalf("count legacy specialist: %v", err)
}
if count != 0 {
t.Fatalf("legacy specialist should be removed, count=%d", count)
}
if err := db.Model(&specialistmodel.Specialist{}).Where("key = ?", "weixin-public-account").Count(&count).Error; err != nil {
t.Fatalf("count current specialist: %v", err)
}
if count != 1 {
t.Fatalf("current specialist should remain, count=%d", count)
}
if err := db.Model(&model.TaskRecord{}).Where("specialist_key = ?", "wechat-official-account").Count(&count).Error; err != nil {
t.Fatalf("count legacy tasks: %v", err)
}
if count != 0 {
t.Fatalf("legacy tasks should be removed, count=%d", count)
}
if err := db.Model(&model.TaskRecord{}).Where("specialist_key = ?", "weixin-public-account").Count(&count).Error; err != nil {
t.Fatalf("count current tasks: %v", err)
}
if count != 1 {
t.Fatalf("current tasks should remain, count=%d", count)
}
if err := db.Model(&model.TaskArtifact{}).Where("specialist_key = ?", "wechat-official-account").Count(&count).Error; err != nil {
t.Fatalf("count legacy artifacts: %v", err)
}
if count != 0 {
t.Fatalf("legacy artifacts should be removed, count=%d", count)
}
if err := db.Model(&model.TaskRun{}).Where("specialist_key = ?", "wechat-official-account").Count(&count).Error; err != nil {
t.Fatalf("count legacy runs: %v", err)
}
if count != 0 {
t.Fatalf("legacy runs should be removed, count=%d", count)
}
if err := db.Model(&weixinpublicaccountmodel.WeixinPublicAccountArticle{}).Where("specialist_key = ?", "wechat-official-account").Count(&count).Error; err != nil {
t.Fatalf("count legacy articles: %v", err)
}
if count != 0 {
t.Fatalf("legacy articles should be removed, count=%d", count)
}
if db.Migrator().HasTable("official_account_article") {
t.Fatalf("official_account_article should be dropped")
}
if db.Migrator().HasTable("official_account_hotspot") {
t.Fatalf("official_account_hotspot should be dropped")
}
}
func TestNormalizeSkillObjectKindsMapsAssistantToSkill(t *testing.T) {
db := openMigrationTestDB(t)
if err := db.AutoMigrate(&skillmodel.SkillDefinition{}); err != nil {