package system import ( "context" "encoding/json" "errors" "fmt" "regexp" "strings" "time" ) var ( exportIdentifierPattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$`) exportParameterPattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) exportColumnPattern = regexp.MustCompile(`(?i)^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?(\s+AS\s+[A-Za-z_][A-Za-z0-9_]*)?$`) exportJoinOnPattern = regexp.MustCompile(`(?i)^[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*\s*=\s*[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*(\s+AND\s+[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*\s*=\s*[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*)*$`) ) type ExportCondition struct { ID uint CreatedAt, UpdatedAt time.Time TemplateID string From, Column string Operator string } type ExportJoin struct { ID uint CreatedAt, UpdatedAt time.Time TemplateID string Join, Table, On string } type ExportTemplate struct { ID uint CreatedAt time.Time UpdatedAt time.Time DBName, Name, TableName, TemplateID, TemplateInfo, SQL, ImportSQL string Limit *int Order string Conditions []ExportCondition Joins []ExportJoin } type ExportTemplateFilter struct { Name, TableName, TemplateID string StartCreatedAt, EndCreatedAt *time.Time } type ExportRepo interface { CreateExportTemplate(context.Context, *ExportTemplate) error UpdateExportTemplate(context.Context, *ExportTemplate) error DeleteExportTemplates(context.Context, []uint) error FindExportTemplate(context.Context, uint, string) (*ExportTemplate, error) ListExportTemplates(context.Context, int, int, *ExportTemplateFilter) ([]*ExportTemplate, int64, error) QueryExport(context.Context, *ExportTemplate, map[string]string) ([]map[string]any, string, error) PreviewExport(context.Context, *ExportTemplate, map[string]string) (string, error) ImportExportRows(context.Context, *ExportTemplate, []map[string]any) error } type ExportUsecase struct{ ExportRepo } func NewExportUsecase(repo ExportRepo) *ExportUsecase { return &ExportUsecase{ExportRepo: repo} } func ValidateExportTemplate(value *ExportTemplate) error { if value == nil { return errors.New("导出模板为空") } if strings.TrimSpace(value.Name) == "" || strings.TrimSpace(value.TemplateID) == "" { return errors.New("导出模板名称和模板 ID 不能为空") } if !exportIdentifierPattern.MatchString(strings.TrimSpace(value.TableName)) { return errors.New("导出模板表名不合法") } if strings.TrimSpace(value.DBName) != "" && !exportIdentifierPattern.MatchString(strings.TrimSpace(value.DBName)) { return errors.New("导出模板数据库别名不合法") } if strings.TrimSpace(value.SQL) != "" || strings.TrimSpace(value.ImportSQL) != "" { return errors.New("导出模板不允许自定义 SQL") } var columns map[string]string if err := json.Unmarshal([]byte(value.TemplateInfo), &columns); err != nil { return fmt.Errorf("导出模板列定义无效: %w", err) } if len(columns) == 0 { return errors.New("导出模板列为空") } for column := range columns { if !exportColumnPattern.MatchString(strings.TrimSpace(column)) { return fmt.Errorf("导出模板列不合法: %s", column) } } for _, join := range value.Joins { kind := strings.ToUpper(strings.Join(strings.Fields(join.Join), " ")) switch kind { case "JOIN", "INNER JOIN", "LEFT JOIN", "RIGHT JOIN": default: return fmt.Errorf("导出模板 Join 类型不合法: %s", join.Join) } if !exportIdentifierPattern.MatchString(strings.TrimSpace(join.Table)) || !exportJoinOnPattern.MatchString(strings.TrimSpace(join.On)) { return errors.New("导出模板 Join 表或条件不合法") } } for _, condition := range value.Conditions { if !exportParameterPattern.MatchString(strings.TrimSpace(condition.From)) || !exportIdentifierPattern.MatchString(strings.TrimSpace(condition.Column)) { return errors.New("导出模板筛选字段不合法") } switch strings.ToUpper(strings.Join(strings.Fields(condition.Operator), " ")) { case "=", "!=", "<>", ">", ">=", "<", "<=", "LIKE", "IN", "NOT IN", "BETWEEN": default: return fmt.Errorf("导出模板筛选运算符不合法: %s", condition.Operator) } } if value.Limit != nil && (*value.Limit < 1 || *value.Limit > 100000) { return errors.New("导出模板行数限制必须在 1 到 100000 之间") } if order := strings.TrimSpace(value.Order); order != "" { parts := strings.Fields(order) if len(parts) > 2 || !exportIdentifierPattern.MatchString(parts[0]) { return errors.New("导出模板排序不合法") } if len(parts) == 2 && !strings.EqualFold(parts[1], "asc") && !strings.EqualFold(parts[1], "desc") { return errors.New("导出模板排序方向不合法") } } return nil } func (uc *ExportUsecase) CreateExportTemplate(ctx context.Context, value *ExportTemplate) error { if err := ValidateExportTemplate(value); err != nil { return err } return uc.ExportRepo.CreateExportTemplate(ctx, value) } func (uc *ExportUsecase) UpdateExportTemplate(ctx context.Context, value *ExportTemplate) error { if err := ValidateExportTemplate(value); err != nil { return err } return uc.ExportRepo.UpdateExportTemplate(ctx, value) }