92 lines
4.0 KiB
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
|
|
}
|