package app import ( "errors" "fmt" "regexp" "strings" "time" "git.echol.cn/loser/st/server/global" "git.echol.cn/loser/st/server/model/app" "git.echol.cn/loser/st/server/model/app/request" "git.echol.cn/loser/st/server/model/app/response" "github.com/lib/pq" "go.uber.org/zap" "gorm.io/datatypes" "gorm.io/gorm" ) type RegexScriptService struct{} // CreateRegexScript 创建正则脚本 func (rs *RegexScriptService) CreateRegexScript(userID uint, req *request.CreateRegexScriptRequest) (*app.AIRegexScript, error) { // 验证正则表达式 if _, err := regexp.Compile(req.FindRegex); err != nil { return nil, errors.New("无效的正则表达式: " + err.Error()) } // 序列化 ScriptData var scriptDataJSON datatypes.JSON if req.ScriptData != nil { data, err := datatypes.NewJSONType(req.ScriptData).MarshalJSON() if err != nil { return nil, errors.New("序列化脚本数据失败: " + err.Error()) } scriptDataJSON = data } linkedChars := pq.StringArray{} if req.LinkedChars != nil { linkedChars = req.LinkedChars } script := &app.AIRegexScript{ UserID: userID, ScriptName: req.ScriptName, Description: req.Description, FindRegex: req.FindRegex, ReplaceString: req.ReplaceString, Enabled: req.Enabled, IsGlobal: req.IsGlobal, TrimStrings: req.TrimStrings, OnlyFormat: req.OnlyFormat, RunOnEdit: req.RunOnEdit, SubstituteRegex: req.SubstituteRegex, MinDepth: req.MinDepth, MaxDepth: req.MaxDepth, Placement: req.Placement, AffectMinDepth: req.AffectMinDepth, AffectMaxDepth: req.AffectMaxDepth, LinkedChars: linkedChars, ScriptData: scriptDataJSON, } if err := global.GVA_DB.Create(script).Error; err != nil { return nil, err } return script, nil } // UpdateRegexScript 更新正则脚本 func (rs *RegexScriptService) UpdateRegexScript(userID, scriptID uint, req *request.UpdateRegexScriptRequest) error { // 查询脚本 var script app.AIRegexScript if err := global.GVA_DB.Where("id = ? AND user_id = ?", scriptID, userID).First(&script).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("脚本不存在") } return err } // 验证正则表达式 if req.FindRegex != "" { if _, err := regexp.Compile(req.FindRegex); err != nil { return errors.New("无效的正则表达式: " + err.Error()) } script.FindRegex = req.FindRegex } // 更新字段 if req.ScriptName != "" { script.ScriptName = req.ScriptName } if req.Description != "" { script.Description = req.Description } if req.ReplaceString != "" { script.ReplaceString = req.ReplaceString } if req.Enabled != nil { script.Enabled = *req.Enabled } if req.IsGlobal != nil { script.IsGlobal = *req.IsGlobal } if req.TrimStrings != nil { script.TrimStrings = *req.TrimStrings } if req.OnlyFormat != nil { script.OnlyFormat = *req.OnlyFormat } if req.RunOnEdit != nil { script.RunOnEdit = *req.RunOnEdit } if req.SubstituteRegex != nil { script.SubstituteRegex = *req.SubstituteRegex } if req.MinDepth != nil { script.MinDepth = req.MinDepth } if req.MaxDepth != nil { script.MaxDepth = req.MaxDepth } if req.Placement != "" { script.Placement = req.Placement } if req.AffectMinDepth != nil { script.AffectMinDepth = req.AffectMinDepth } if req.AffectMaxDepth != nil { script.AffectMaxDepth = req.AffectMaxDepth } if req.LinkedChars != nil { script.LinkedChars = req.LinkedChars } if req.ScriptData != nil { data, err := datatypes.NewJSONType(req.ScriptData).MarshalJSON() if err != nil { return errors.New("序列化脚本数据失败: " + err.Error()) } script.ScriptData = data } return global.GVA_DB.Save(&script).Error } // DeleteRegexScript 删除正则脚本 func (rs *RegexScriptService) DeleteRegexScript(userID, scriptID uint) error { result := global.GVA_DB.Where("id = ? AND user_id = ?", scriptID, userID).Delete(&app.AIRegexScript{}) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return errors.New("脚本不存在") } return nil } // GetRegexScript 获取正则脚本详情 func (rs *RegexScriptService) GetRegexScript(userID, scriptID uint) (*app.AIRegexScript, error) { var script app.AIRegexScript if err := global.GVA_DB.Where("id = ? AND user_id = ?", scriptID, userID).First(&script).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("脚本不存在") } return nil, err } return &script, nil } // GetRegexScriptList 获取正则脚本列表 func (rs *RegexScriptService) GetRegexScriptList(userID uint, req *request.RegexScriptListRequest) ([]app.AIRegexScript, int64, error) { db := global.GVA_DB.Where("user_id = ?", userID) // 条件筛选 if req.ScriptName != "" { db = db.Where("script_name ILIKE ?", "%"+req.ScriptName+"%") } if req.IsGlobal != nil { db = db.Where("is_global = ?", *req.IsGlobal) } if req.Enabled != nil { db = db.Where("enabled = ?", *req.Enabled) } if req.CharacterID != nil { db = db.Where("? = ANY(linked_chars)", fmt.Sprintf("%d", *req.CharacterID)) } // 查询总数 var total int64 if err := db.Model(&app.AIRegexScript{}).Count(&total).Error; err != nil { return nil, 0, err } // 分页查询 var scripts []app.AIRegexScript offset := (req.Page - 1) * req.PageSize if err := db.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&scripts).Error; err != nil { return nil, 0, err } return scripts, total, nil } // LinkCharactersToRegex 关联角色到正则脚本 func (rs *RegexScriptService) LinkCharactersToRegex(userID, scriptID uint, characterIDs []uint) error { // 查询脚本 var script app.AIRegexScript if err := global.GVA_DB.Where("id = ? AND user_id = ?", scriptID, userID).First(&script).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("脚本不存在") } return err } // 转换为字符串数组 linkedChars := make([]string, len(characterIDs)) for i, id := range characterIDs { linkedChars[i] = fmt.Sprintf("%d", id) } // 更新 LinkedChars script.LinkedChars = linkedChars return global.GVA_DB.Save(&script).Error } // GetCharacterRegexScripts 获取角色关联的正则脚本列表 func (rs *RegexScriptService) GetCharacterRegexScripts(userID, characterID uint) ([]app.AIRegexScript, error) { var scripts []app.AIRegexScript err := global.GVA_DB. Where("user_id = ? AND (is_global = true OR ? = ANY(linked_chars))", userID, fmt.Sprintf("%d", characterID)). Where("enabled = true"). Order("created_at ASC"). Find(&scripts).Error return scripts, err } // DuplicateRegexScript 复制正则脚本 func (rs *RegexScriptService) DuplicateRegexScript(userID, scriptID uint) (*app.AIRegexScript, error) { // 获取原脚本 original, err := rs.GetRegexScript(userID, scriptID) if err != nil { return nil, err } // 创建副本 newScript := &app.AIRegexScript{ UserID: userID, ScriptName: original.ScriptName + " (副本)", Description: original.Description, FindRegex: original.FindRegex, ReplaceString: original.ReplaceString, Enabled: original.Enabled, IsGlobal: false, // 副本默认非全局 TrimStrings: original.TrimStrings, OnlyFormat: original.OnlyFormat, RunOnEdit: original.RunOnEdit, SubstituteRegex: original.SubstituteRegex, MinDepth: original.MinDepth, MaxDepth: original.MaxDepth, Placement: original.Placement, AffectMinDepth: original.AffectMinDepth, AffectMaxDepth: original.AffectMaxDepth, LinkedChars: original.LinkedChars, ScriptData: original.ScriptData, } if err := global.GVA_DB.Create(newScript).Error; err != nil { return nil, err } return newScript, nil } // TestRegexScript 测试正则脚本 func (rs *RegexScriptService) TestRegexScript(req *request.TestRegexScriptRequest) (*response.TestRegexScriptResponse, error) { // 编译正则表达式 re, err := regexp.Compile(req.FindRegex) if err != nil { return &response.TestRegexScriptResponse{ Success: false, Input: req.TestInput, Output: req.TestInput, Error: "无效的正则表达式: " + err.Error(), }, nil } // 应用替换 output := req.TestInput if req.TrimStrings { output = strings.TrimSpace(output) } // 查找所有匹配 matches := re.FindAllString(output, -1) // 执行替换 if req.SubstituteRegex { // 使用正则替换 output = re.ReplaceAllString(output, req.ReplaceString) } else { // 简单字符串替换 output = re.ReplaceAllLiteralString(output, req.ReplaceString) } return &response.TestRegexScriptResponse{ Success: true, Input: req.TestInput, Output: output, MatchedCount: len(matches), Matches: matches, }, nil } // ApplyRegexScripts 应用正则脚本 func (rs *RegexScriptService) ApplyRegexScripts(userID uint, req *request.ApplyRegexScriptsRequest) (*response.ApplyRegexScriptsResponse, error) { var scripts []app.AIRegexScript // 收集要应用的脚本 db := global.GVA_DB.Where("user_id = ? AND enabled = true", userID) if len(req.RegexIDs) > 0 { // 应用指定的脚本 db = db.Where("id IN ?", req.RegexIDs) } else { // 根据条件自动选择脚本 conditions := []string{} if req.UseGlobal { conditions = append(conditions, "is_global = true") } if req.CharacterID != nil { conditions = append(conditions, fmt.Sprintf("'%d' = ANY(linked_chars)", *req.CharacterID)) } if len(conditions) > 0 { db = db.Where(strings.Join(conditions, " OR ")) } // 筛选位置 if req.Placement != "" { db = db.Where("(placement = '' OR placement = ?)", req.Placement) } } if err := db.Order("created_at ASC").Find(&scripts).Error; err != nil { return nil, err } // 应用脚本 processedText := req.Text appliedScripts := []uint{} for _, script := range scripts { // 检查深度限制 if req.MinDepth != nil && script.MinDepth != nil && *req.MinDepth < *script.MinDepth { continue } if req.MaxDepth != nil && script.MaxDepth != nil && *req.MaxDepth > *script.MaxDepth { continue } // 编译正则表达式 re, err := regexp.Compile(script.FindRegex) if err != nil { global.GVA_LOG.Warn("正则表达式编译失败", zap.Uint("scriptID", script.ID), zap.String("regex", script.FindRegex), zap.Error(err)) continue } // 应用替换 beforeText := processedText if script.TrimStrings { processedText = strings.TrimSpace(processedText) } if script.SubstituteRegex { processedText = re.ReplaceAllString(processedText, script.ReplaceString) } else { processedText = re.ReplaceAllLiteralString(processedText, script.ReplaceString) } // 记录成功应用的脚本 if beforeText != processedText { appliedScripts = append(appliedScripts, script.ID) // 更新使用统计 now := time.Now().Unix() global.GVA_DB.Model(&script).Updates(map[string]interface{}{ "usage_count": gorm.Expr("usage_count + 1"), "last_used_at": now, }) } } return &response.ApplyRegexScriptsResponse{ OriginalText: req.Text, ProcessedText: processedText, AppliedCount: len(appliedScripts), AppliedScripts: appliedScripts, }, nil } // ImportRegexScripts 导入正则脚本 func (rs *RegexScriptService) ImportRegexScripts(userID uint, scripts []app.AIRegexScript, overwriteMode string) (int, error) { imported := 0 for _, script := range scripts { script.UserID = userID script.ID = 0 // 重置 ID // 检查是否存在同名脚本 var existing app.AIRegexScript err := global.GVA_DB.Where("user_id = ? AND script_name = ?", userID, script.ScriptName).First(&existing).Error if err == nil { // 脚本已存在 switch overwriteMode { case "skip": continue case "overwrite": script.ID = existing.ID if err := global.GVA_DB.Save(&script).Error; err != nil { global.GVA_LOG.Warn("覆盖脚本失败", zap.Error(err)) continue } case "merge": script.ScriptName = script.ScriptName + " (导入)" if err := global.GVA_DB.Create(&script).Error; err != nil { global.GVA_LOG.Warn("合并导入脚本失败", zap.Error(err)) continue } default: continue } } else { // 新脚本 if err := global.GVA_DB.Create(&script).Error; err != nil { global.GVA_LOG.Warn("创建脚本失败", zap.Error(err)) continue } } imported++ } return imported, nil } // ExportRegexScripts 导出正则脚本 func (rs *RegexScriptService) ExportRegexScripts(userID uint, scriptIDs []uint) (*response.RegexScriptExportData, error) { var scripts []app.AIRegexScript db := global.GVA_DB.Where("user_id = ?", userID) if len(scriptIDs) > 0 { db = db.Where("id IN ?", scriptIDs) } if err := db.Find(&scripts).Error; err != nil { return nil, err } // 转换为响应格式 responses := make([]response.RegexScriptResponse, len(scripts)) for i, script := range scripts { responses[i] = response.ToRegexScriptResponse(&script) } return &response.RegexScriptExportData{ Version: "1.0", Scripts: responses, ExportedAt: time.Now().Unix(), }, nil }