Files
ttstd-infinite-canvas/backend/internal/service/skills_seed.go

92 lines
4.0 KiB
Go

package service
import (
_ "embed"
"encoding/json"
"fmt"
"strings"
"time"
"infinite-canvas/backend/internal/model"
)
//go:embed seed/skills.json
var builtinSkillsJSON []byte
type builtinSkillDefinition struct {
SkillID string `json:"skill_id"`
SkillName string `json:"skill_name"`
Description string `json:"description"`
Instruction string `json:"instruction"`
Status int `json:"status"`
MarkdownURL string `json:"markdown_url"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
Source int `json:"source"`
Tag string `json:"tag"`
SortWeight int `json:"sort_weight"`
IsPrivate bool `json:"is_private"`
LikeCount int64 `json:"like_count"`
OwnerUID string `json:"owner_uid"`
EffectiveUser SkillEffectiveUser `json:"effective_user"`
ShowcaseMedia []SkillShowcaseMedia `json:"showcase_media"`
AddedCount int64 `json:"added_count"`
ExtraInfo string `json:"extra_info"`
}
// EnsureBuiltinSkills 校验并同步内置技能正文;用户关系独立保存,因此重复启动不会清空加入或收藏状态。
func (s *Service) EnsureBuiltinSkills() error {
var definitions []builtinSkillDefinition
if err := json.Unmarshal(builtinSkillsJSON, &definitions); err != nil {
return fmt.Errorf("解析内置技能失败: %w", err)
}
if len(definitions) == 0 {
return fmt.Errorf("内置技能不能为空")
}
seen := make(map[string]struct{}, len(definitions))
skills := make([]model.Skill, 0, len(definitions))
for _, definition := range definitions {
id := strings.TrimSpace(definition.SkillID)
ownerID := strings.TrimSpace(definition.OwnerUID)
if id == "" || len(id) > 36 || ownerID == "" || len(ownerID) > 36 {
return fmt.Errorf("内置技能 ID 或作者 ID 无效: %q", definition.SkillID)
}
if _, exists := seen[id]; exists {
return fmt.Errorf("内置技能 ID 重复: %s", id)
}
seen[id] = struct{}{}
if definition.Status != skillStatusEnabled || definition.IsPrivate {
return fmt.Errorf("内置技能必须为公开启用状态: %s", id)
}
if definition.CreateTime <= 0 || definition.UpdateTime <= 0 || definition.LikeCount < 0 || definition.AddedCount < 0 {
return fmt.Errorf("内置技能时间或计数无效: %s", id)
}
authorName := strings.TrimSpace(definition.EffectiveUser.Name)
authorAvatarURL := strings.TrimSpace(definition.EffectiveUser.AvatarURL)
if authorName == "" || (authorAvatarURL != "" && !validSkillURL(authorAvatarURL)) {
return fmt.Errorf("内置技能作者信息无效: %s", id)
}
normalized, mediaJSON, err := normalizeSkillMutationRequest(SkillMutationRequest{
SkillName: definition.SkillName, Description: definition.Description, Instruction: definition.Instruction,
Tag: definition.Tag, IsPrivate: definition.IsPrivate, MarkdownURL: definition.MarkdownURL,
ShowcaseMedia: definition.ShowcaseMedia, ExtraInfo: definition.ExtraInfo,
}, true)
if err != nil {
return fmt.Errorf("内置技能 %s 数据无效: %w", id, err)
}
skills = append(skills, model.Skill{
ID: id, OwnerID: ownerID, AuthorName: authorName, AuthorAvatarURL: authorAvatarURL,
Name: normalized.SkillName, Description: normalized.Description, Instruction: normalized.Instruction,
Status: skillStatusEnabled, Source: definition.Source, Tag: normalized.Tag, SortWeight: definition.SortWeight,
IsPrivate: false, MarkdownURL: normalized.MarkdownURL, ShowcaseMediaJSON: mediaJSON, ExtraInfo: normalized.ExtraInfo,
InitialLikeCount: definition.LikeCount, InitialAddedCount: definition.AddedCount,
CreatedAt: time.UnixMilli(definition.CreateTime), UpdatedAt: time.UnixMilli(definition.UpdateTime),
})
}
if err := s.repo.UpsertBuiltinSkills(skills); err != nil {
return fmt.Errorf("同步内置技能失败: %w", err)
}
return nil
}