- 活动切换并发安全:App/Scheduler 增加 client/cfg 读写锁与快照访问, 支持运行中热替换客户端,切换活动不再静默覆盖用户自定义 timeApiUrl - 启动失败时写入崩溃日志文件便于事后排查 - 前端类型声明从 Wails 自动生成的绑定派生,避免手写契约漂移 - 提取 parseAccountFile 消除 ExportAccounts/OrganizeAccounts 重复解析逻辑 - 删除 utils 中未使用的 ReadJSON/WriteJSON/WriteError 及空目录 internal/app - gofmt 统一格式化(activity/types/scheduler 字段对齐) - 新增 logger/seckill/utils 单元测试 - 清理 git 中遗留产物(frontend/app.js、yml.txt、其他日志/)并补全 .gitignore
117 lines
2.8 KiB
Go
117 lines
2.8 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"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)
|
|
}
|