✨ 新增正则和扩展模块
This commit is contained in:
476
server/service/app/regex_script.go
Normal file
476
server/service/app/regex_script.go
Normal file
@@ -0,0 +1,476 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user