package utils import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "time" ) // AppendCacheBuster 给 URL 追加时间戳,避免时间接口被缓存。 func AppendCacheBuster(rawURL string) string { sep := "?" if strings.Contains(rawURL, "?") { sep = "&" } return rawURL + sep + "_=" + strconv.FormatInt(time.Now().UnixNano(), 10) } // CloneStringMap 复制 map,避免异步任务读到调用方后续修改。 func CloneStringMap(in map[string]string) map[string]string { out := make(map[string]string, len(in)) for k, v := range in { out[k] = v } return out } // MergeCookie 把指定 Cookie 写入原 Cookie 字符串,已有同名 Cookie 会被覆盖。 func MergeCookie(cookie, name, value string) string { parts := strings.Split(cookie, ";") pairs := make([]string, 0, len(parts)+1) replaced := false for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } key, _, ok := strings.Cut(part, "=") if ok && strings.TrimSpace(key) == name { pairs = append(pairs, name+"="+value) replaced = true continue } pairs = append(pairs, part) } if !replaced && value != "" { pairs = append(pairs, name+"="+value) } return strings.Join(pairs, "; ") } // TrimForLog 截断接口响应,避免日志刷屏。 func TrimForLog(s string, max int) string { s = strings.TrimSpace(s) if len(s) <= max { return s } return s[:max] + "..." } // EmptyDefault 在字符串为空时返回默认值。 func EmptyDefault(value, fallback string) string { if strings.TrimSpace(value) == "" { return fallback } return value } // SafeFilename 替换文件名中的危险字符。 func SafeFilename(name string) string { replacer := strings.NewReplacer("/", "_", "\\", "_", ":", "_", "*", "_", "?", "_", "\"", "_", "<", "_", ">", "_", "|", "_") return replacer.Replace(name) } // MaskCookie 返回 Cookie 摘要,避免把完整凭据暴露给前端。 func MaskCookie(cookie string) string { pairs := strings.Split(cookie, ";") masked := make([]string, 0, 3) total := 0 for _, pair := range pairs { key, value, ok := strings.Cut(strings.TrimSpace(pair), "=") if !ok || key == "" { continue } total++ if len(masked) >= 3 { continue } masked = append(masked, key+"="+MaskValue(value)) } if total == 0 { return "" } if total > len(masked) { masked = append(masked, fmt.Sprintf("...共%d项", total)) } return strings.Join(masked, "; ") } // MaskValue 保留少量首尾字符用于识别,其余替换为星号。 func MaskValue(value string) string { value = strings.TrimSpace(value) if value == "" { return "****" } runes := []rune(value) if len(runes) <= 8 { return "****" } return string(runes[:4]) + "****" + string(runes[len(runes)-4:]) } // RoundDuration 将时间四舍五入到毫秒。 func RoundDuration(d time.Duration) time.Duration { return d.Round(time.Millisecond) } // ReadJSON 读取请求 JSON。 func ReadJSON(r *http.Request, out any) error { defer r.Body.Close() return json.NewDecoder(r.Body).Decode(out) } // WriteJSON 写出 JSON 响应。 func WriteJSON(w http.ResponseWriter, value any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") _ = json.NewEncoder(w).Encode(value) } // WriteError 写出统一错误 JSON,同时设置 HTTP 状态码。 func WriteError(w http.ResponseWriter, status int, err error) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]any{ "success": false, "error": err.Error(), }) }