package pkg import ( "ai_scheduler/internal/entitys" "encoding/json" "errors" "fmt" "math/rand" "net/url" "reflect" "strconv" "strings" "time" ) func JsonStringIgonErr(data interface{}) string { return string(JsonByteIgonErr(data)) } func JsonByteIgonErr(data interface{}) []byte { dataByte, _ := json.Marshal(data) return dataByte } // IsChannelClosed 检查给定的 channel 是否已经关闭 // 参数 ch: 要检查的 channel,类型为 chan entitys.ResponseData // 返回值: bool 类型,true 表示 channel 已关闭,false 表示未关闭 func IsChannelClosed(ch chan entitys.ResponseData) bool { select { case _, ok := <-ch: // 尝试从 channel 中读取数据 return !ok // 如果 ok=false,说明 channel 已关闭 default: // 如果 channel 暂时无数据可读(但不一定关闭) return false // channel 未关闭(但可能有数据未读取) } } // ValidateImageURL 验证图片 URL 是否有效 func ValidateImageURL(rawURL string) error { // 1. 基础格式验证 parsed, err := url.Parse(rawURL) if err != nil { return fmt.Errorf("未知的图片格式: %v", err) } // 2. 检查协议是否为 http/https if parsed.Scheme != "http" && parsed.Scheme != "https" { return errors.New("必须是http/https结构") } // 3. 检查是否有空的主机名 if parsed.Host == "" { return errors.New("未知的url地址") } // 4. 检查路径是否为空 if strings.TrimSpace(parsed.Path) == "" { return errors.New("url为空") } return nil } // hexEncode 将 src 的二进制数据编码为十六进制字符串,写入 dst,返回写入长度 func HexEncode(src, dst []byte) int { const hextable = "0123456789abcdef" for i := 0; i < len(src); i++ { dst[i*2] = hextable[src[i]>>4] dst[i*2+1] = hextable[src[i]&0xf] } return len(src) * 2 } // Ter 三目运算 Ter(true, 1, 2) func Ter[T any](cond bool, a, b T) T { if cond { return a } return b } // StringToSlice [num,num]转slice func StringToSlice(s string) ([]int, error) { // 1. 去掉两端的方括号 trimmed := strings.Trim(s, "[]") // 2. 按逗号分割 parts := strings.Split(trimmed, ",") // 3. 转换为 []int result := make([]int, 0, len(parts)) for _, part := range parts { num, err := strconv.Atoi(strings.TrimSpace(part)) if err != nil { return nil, err } result = append(result, num) } return result, nil } // Difference 差集 func Difference[T comparable](a, b []T) []T { // 创建 b 的映射(T 必须是可比较的类型) bMap := make(map[T]struct{}, len(b)) for _, item := range b { bMap[item] = struct{}{} } var diff []T // 修正为 []T 而非 []int for _, item := range a { if _, found := bMap[item]; !found { diff = append(diff, item) } } return diff } // SliceStringToInt []string=>[]int func SliceStringToInt(strSlice []string) []int { numSlice := make([]int, len(strSlice)) for i, str := range strSlice { num, err := strconv.Atoi(str) if err != nil { return nil } numSlice[i] = num } return numSlice } // SliceIntToString []int=>[]string func SliceIntToString(slice []int) []string { strSlice := make([]string, len(slice)) // len=cap=len(slice) for i, num := range slice { strSlice[i] = strconv.Itoa(num) // 直接赋值,无 append } return strSlice } // SafeReplace 替换字符串中的 %s,并自动转义特殊字符(如 ") /** * SafeReplace 函数用于安全地替换模板字符串中的占位符 * @param template 原始模板字符串 * @param replaceTag 要被替换的占位符(如 "%s") * @param replacements 可变参数,用于替换占位符的字符串 * @return 返回替换后的字符串和可能的错误 */ func SafeReplace(template string, replaceTag string, replacements ...string) (string, error) { // 如果没有提供替换参数,直接返回原始模板 if len(replacements) == 0 { return template, nil } // 检查模板中 %s 的数量是否匹配替换参数 expectedReplacements := strings.Count(template, replaceTag) if expectedReplacements != len(replacements) { return "", fmt.Errorf("模板需要 %d 个替换参数,但提供了 %d 个", expectedReplacements, len(replacements)) } // 逐个替换 %s,并转义特殊字符 for _, rep := range replacements { // 转义特殊字符(如 ", \, \n 等) escaped := strconv.Quote(rep) // 去掉 strconv.Quote 添加的额外引号 escaped = escaped[1 : len(escaped)-1] template = strings.Replace(template, replaceTag, escaped, 1) } return template, nil } // 配置选项 type URLValuesOptions struct { ArrayFormat string // 数组格式:"brackets" -> name[], "indices" -> name[0], "repeat" -> name=value1&name=value2 TimeFormat string // 时间格式 } var defaultOptions = URLValuesOptions{ ArrayFormat: "brackets", // 默认使用括号格式 TimeFormat: time.DateTime, } // StructToURLValues 将结构体转换为 url.Values func StructToURLValues(input interface{}, options ...URLValuesOptions) (url.Values, error) { opts := defaultOptions if len(options) > 0 { opts = options[0] } values := url.Values{} if input == nil { return values, nil } v := reflect.ValueOf(input) t := reflect.TypeOf(input) // 如果是指针,获取其指向的值 if v.Kind() == reflect.Ptr { if v.IsNil() { return values, nil } v = v.Elem() t = t.Elem() } // 确保是结构体类型 if v.Kind() != reflect.Struct { return nil, fmt.Errorf("input must be a struct or pointer to struct") } // 遍历结构体字段 for i := 0; i < v.NumField(); i++ { field := t.Field(i) fieldValue := v.Field(i) // 跳过非导出字段 if !field.IsExported() { continue } // 解析 JSON 标签(也可以支持 form 标签) tag := field.Tag.Get("json") fieldName, omitempty := parseJSONTag(tag) if fieldName == "-" { continue // 忽略该字段 } if fieldName == "" { fieldName = field.Name } // 处理指针类型 if fieldValue.Kind() == reflect.Ptr { if fieldValue.IsNil() { if omitempty { continue } // 可以为 nil 指针添加空值 values.Set(fieldName, "") continue } fieldValue = fieldValue.Elem() } // 处理切片/数组 if fieldValue.Kind() == reflect.Slice || fieldValue.Kind() == reflect.Array { if fieldValue.Len() == 0 && omitempty { continue } // 将切片转换为 URL 参数 err := addSliceToValues(values, fieldName, fieldValue, opts) if err != nil { return nil, err } continue } // 检查是否需要忽略空值 if omitempty && isEmptyValue(fieldValue) { continue } // 转换单个值 str, err := valueToString(fieldValue, opts) if err != nil { return nil, err } values.Set(fieldName, str) } return values, nil } // 解析 JSON 标签 func parseJSONTag(tag string) (fieldName string, omitempty bool) { if tag == "" { return "", false } parts := strings.Split(tag, ",") fieldName = parts[0] if len(parts) > 1 { for _, part := range parts[1:] { if part == "omitempty" { omitempty = true } } } return fieldName, omitempty } // 添加切片到 values func addSliceToValues(values url.Values, fieldName string, slice reflect.Value, opts URLValuesOptions) error { length := slice.Len() if length == 0 { return nil } switch opts.ArrayFormat { case "brackets": // 格式:field[]=value1&field[]=value2 for i := 0; i < length; i++ { item := slice.Index(i) str, err := valueToString(item, opts) if err != nil { return err } values.Add(fieldName, str) } case "indices": // 格式:field[0]=value1&field[1]=value2 for i := 0; i < length; i++ { item := slice.Index(i) str, err := valueToString(item, opts) if err != nil { return err } values.Set(fmt.Sprintf("%s[%d]", fieldName, i), str) } case "repeat": // 格式:field=value1&field=value2 for i := 0; i < length; i++ { item := slice.Index(i) str, err := valueToString(item, opts) if err != nil { return err } values.Add(fieldName, str) } default: // 默认使用 brackets 格式 for i := 0; i < length; i++ { item := slice.Index(i) str, err := valueToString(item, opts) if err != nil { return err } values.Add(fieldName+"[]", str) } } return nil } // 将值转换为字符串 func valueToString(v reflect.Value, opts URLValuesOptions) (string, error) { if !v.IsValid() { return "", nil } // 处理不同类型 switch v.Kind() { case reflect.String: return v.String(), nil case reflect.Bool: return strconv.FormatBool(v.Bool()), nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(v.Int(), 10), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return strconv.FormatUint(v.Uint(), 10), nil case reflect.Float32, reflect.Float64: return strconv.FormatFloat(v.Float(), 'f', -1, 64), nil case reflect.Struct: // 特殊处理 time.Time if t, ok := v.Interface().(time.Time); ok { return t.Format(opts.TimeFormat), nil } // 其他结构体递归处理 // 这里可以扩展为递归处理嵌套结构体 default: // 默认使用 fmt 的字符串表示 return fmt.Sprintf("%v", v.Interface()), nil } return fmt.Sprintf("%v", v.Interface()), nil } // 检查值是否为空 func isEmptyValue(v reflect.Value) bool { switch v.Kind() { case reflect.String: return v.String() == "" case reflect.Bool: return false case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Slice, reflect.Array, reflect.Map: return v.Len() == 0 case reflect.Ptr, reflect.Interface: return v.IsNil() case reflect.Struct: if t, ok := v.Interface().(time.Time); ok { return t.IsZero() } return false default: return false } } // 方便函数:直接生成查询字符串 func StructToQueryString(input interface{}, options ...URLValuesOptions) (string, error) { values, err := StructToURLValues(input, options...) if err != nil { return "", err } return values.Encode(), nil } const ( letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 62个字符 ) // RandomString 生成随机字符串,包含 0-9, a-z, A-Z // length: 要生成的字符串长度 func RandomString(length int) string { // 使用 crypto/rand 替代 math/rand(更安全,适用于密码学场景) // 但如果不需要高安全性,math/rand 更快 rng := rand.New(rand.NewSource(time.Now().UnixNano())) result := make([]byte, length) for i := range result { result[i] = letterBytes[rng.Intn(len(letterBytes))] } return string(result) }