新增正则和扩展模块

This commit is contained in:
2026-02-11 23:44:09 +08:00
parent 2bca8e2788
commit 4e611d3a5e
47 changed files with 10058 additions and 49 deletions

View 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
}