158 lines
5.1 KiB
Go
158 lines
5.1 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"yingce/backend/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const responseInterceptionSettingKey = "response_interception"
|
|
|
|
const (
|
|
maxResponseInterceptionRules = 100
|
|
maxResponseInterceptionTextRunes = 200
|
|
maxResponseInterceptionReplaceRunes = 500
|
|
)
|
|
|
|
type ResponseInterceptionRule struct {
|
|
Contains string `json:"contains"`
|
|
Replace string `json:"replace"`
|
|
}
|
|
|
|
type ResponseInterceptionSetting struct {
|
|
Enabled bool `json:"enabled"`
|
|
Rules []ResponseInterceptionRule `json:"rules"`
|
|
}
|
|
|
|
func defaultResponseInterceptionSetting() ResponseInterceptionSetting {
|
|
return ResponseInterceptionSetting{Rules: []ResponseInterceptionRule{}}
|
|
}
|
|
|
|
func (s *Service) AdminResponseInterceptionSetting(actor *model.User) (*ResponseInterceptionSetting, error) {
|
|
if err := s.RequireAdmin(actor); err != nil {
|
|
return nil, err
|
|
}
|
|
value, err := s.responseInterceptionSetting()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &value, nil
|
|
}
|
|
|
|
func (s *Service) UpdateResponseInterceptionSetting(actor *model.User, value ResponseInterceptionSetting) (*ResponseInterceptionSetting, error) {
|
|
if err := s.RequireAdmin(actor); err != nil {
|
|
return nil, err
|
|
}
|
|
value = normalizeResponseInterceptionSetting(value)
|
|
if err := validateResponseInterceptionSetting(value); err != nil {
|
|
return nil, err
|
|
}
|
|
before, err := s.responseInterceptionSetting()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
encoded, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
current, err := s.repo.SystemSetting(responseInterceptionSettingKey)
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, err
|
|
}
|
|
setting := &model.SystemSetting{Key: responseInterceptionSettingKey, ValueJSON: string(encoded), UpdatedBy: actor.ID}
|
|
if current != nil {
|
|
setting.CreatedAt = current.CreatedAt
|
|
}
|
|
if err := s.repo.SaveSystemSetting(setting); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.appendAdminAudit(actor, "response_interception.update", "system_setting", responseInterceptionSettingKey, "更新模型响应拦截", map[string]any{"before": before, "after": value}); err != nil {
|
|
return nil, err
|
|
}
|
|
return &value, nil
|
|
}
|
|
|
|
// InterceptResponseText 只投影用户可见文案,不修改上游响应或请求明细中的原始内容。
|
|
func (s *Service) InterceptResponseText(raw string) string {
|
|
raw = strings.TrimSpace(raw)
|
|
setting, readErr := s.responseInterceptionSetting()
|
|
if readErr != nil || !setting.Enabled {
|
|
return raw
|
|
}
|
|
return interceptResponseMessage(setting, raw)
|
|
}
|
|
|
|
// UserFacingErrorMessage 只投影用户可见错误,不修改上游响应或请求明细中的原始内容。
|
|
func (s *Service) UserFacingErrorMessage(err error) string {
|
|
return s.InterceptResponseText(taskFailureMessage(err))
|
|
}
|
|
|
|
func interceptResponseMessage(setting ResponseInterceptionSetting, raw string) string {
|
|
lower := strings.ToLower(raw)
|
|
for _, rule := range setting.Rules {
|
|
if strings.Contains(lower, strings.ToLower(rule.Contains)) {
|
|
return rule.Replace
|
|
}
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func (s *Service) responseInterceptionSetting() (ResponseInterceptionSetting, error) {
|
|
setting, err := s.repo.SystemSetting(responseInterceptionSettingKey)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return defaultResponseInterceptionSetting(), nil
|
|
}
|
|
if err != nil {
|
|
return ResponseInterceptionSetting{}, err
|
|
}
|
|
value := defaultResponseInterceptionSetting()
|
|
if strings.TrimSpace(setting.ValueJSON) == "" {
|
|
return value, nil
|
|
}
|
|
if err := json.Unmarshal([]byte(setting.ValueJSON), &value); err != nil {
|
|
return ResponseInterceptionSetting{}, fmt.Errorf("模型响应拦截配置格式错误: %w", err)
|
|
}
|
|
value = normalizeResponseInterceptionSetting(value)
|
|
if err := validateResponseInterceptionSetting(value); err != nil {
|
|
return ResponseInterceptionSetting{}, err
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func normalizeResponseInterceptionSetting(value ResponseInterceptionSetting) ResponseInterceptionSetting {
|
|
if value.Rules == nil {
|
|
value.Rules = []ResponseInterceptionRule{}
|
|
}
|
|
for index := range value.Rules {
|
|
value.Rules[index].Contains = strings.TrimSpace(value.Rules[index].Contains)
|
|
value.Rules[index].Replace = strings.TrimSpace(value.Rules[index].Replace)
|
|
}
|
|
return value
|
|
}
|
|
|
|
func validateResponseInterceptionSetting(value ResponseInterceptionSetting) error {
|
|
if len(value.Rules) > maxResponseInterceptionRules {
|
|
return fmt.Errorf("模型响应拦截规则不能超过 %d 条", maxResponseInterceptionRules)
|
|
}
|
|
for index, rule := range value.Rules {
|
|
if rule.Contains == "" {
|
|
return fmt.Errorf("第 %d 条拦截规则的匹配文案不能为空", index+1)
|
|
}
|
|
if len([]rune(rule.Contains)) > maxResponseInterceptionTextRunes {
|
|
return fmt.Errorf("第 %d 条拦截规则的匹配文案不能超过 %d 个字符", index+1, maxResponseInterceptionTextRunes)
|
|
}
|
|
if rule.Replace == "" {
|
|
return fmt.Errorf("第 %d 条拦截规则的替换文案不能为空", index+1)
|
|
}
|
|
if len([]rune(rule.Replace)) > maxResponseInterceptionReplaceRunes {
|
|
return fmt.Errorf("第 %d 条拦截规则的替换文案不能超过 %d 个字符", index+1, maxResponseInterceptionReplaceRunes)
|
|
}
|
|
}
|
|
return nil
|
|
}
|