package mailx import ( "encoding/json" "fmt" "strings" ) // FromBytes 从 JSON 字节构建消息(便捷入口,用于接入配置/存储)。 func FromBytes(data []byte) (*Message, error) { var m Message if err := json.Unmarshal(data, &m); err != nil { return nil, fmt.Errorf("mailx: unmarshal message: %w", err) } return &m, nil } // FromMap 从 map 构建消息,常用字段自动映射: // - from / to / cc / bcc / subject / text / html / body / replyto / reply_to // - to/cc/bcc 可为 []string、[]any 或 "a@x.com,b@y.com" 分隔串 // - attachments: []map{name,path,data} 或 []string(path) func FromMap(m map[string]any) (*Message, error) { if m == nil { return nil, fmt.Errorf("%w: map is nil", ErrInvalidConfig) } msg := &Message{} var err error str := func(keys ...string) string { for _, k := range keys { if v, ok := m[k].(string); ok { return v } } return "" } msg.From = str("from") msg.Subject = str("subject") msg.TextBody = firstNonEmptyStr(str("text"), str("textbody"), str("text_body")) msg.Body = firstNonEmptyStr(str("html"), str("body")) msg.ReplyTo = firstNonEmptyStr(str("replyto"), str("reply_to")) if msg.To, err = toList(m["to"]); err != nil { return nil, err } if msg.Cc, err = toList(m["cc"]); err != nil { return nil, err } if msg.Bcc, err = toList(m["bcc"]); err != nil { return nil, err } if atts, ok := m["attachments"]; ok { msg.Attachments, err = toAttachments(atts) if err != nil { return nil, err } } return msg, nil } // toList 将任意输入转为地址列表([]string、[]any 或逗号/分号分隔串) func toList(v any) ([]string, error) { switch t := v.(type) { case nil: return nil, nil case []string: return t, nil case string: var out []string for _, p := range strings.FieldsFunc(t, func(r rune) bool { return r == ',' || r == ';' }) { if p = strings.TrimSpace(p); p != "" { out = append(out, p) } } return out, nil case []any: out := make([]string, 0, len(t)) for _, item := range t { if s, ok := item.(string); ok { out = append(out, s) } } return out, nil default: return nil, fmt.Errorf("mailx: invalid address list type %T", v) } } // toAttachments 将 attachments 输入转为附件列表 func toAttachments(v any) ([]Attachment, error) { switch t := v.(type) { case []string: out := make([]Attachment, 0, len(t)) for _, p := range t { out = append(out, Attachment{Name: fileBaseName(p), Path: p}) } return out, nil case []map[string]any: out := make([]Attachment, 0, len(t)) for _, mm := range t { var att Attachment if name, ok := mm["name"].(string); ok { att.Name = name } if p, ok := mm["path"].(string); ok { att.Path = p if att.Name == "" { att.Name = fileBaseName(p) } } if d, ok := mm["data"]; ok { if bs, ok := d.([]byte); ok { att.Data = bs } } out = append(out, att) } return out, nil case []any: return toAttachments(mapsFromAny(t)) default: return nil, fmt.Errorf("mailx: invalid attachments type %T", v) } } // mapsFromAny 将 []any 转为 []map[string]any(忽略非 map 元素) func mapsFromAny(arr []any) []map[string]any { out := make([]map[string]any, 0, len(arr)) for _, item := range arr { if mm, ok := item.(map[string]any); ok { out = append(out, mm) } } return out } func firstNonEmptyStr(vals ...string) string { for _, v := range vals { if v != "" { return v } } return "" }