init
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ToInt 字符串转 int,失败返回 0
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 简化类型转换,避免每次处理错误
|
||||
func ToInt(s string) int {
|
||||
n, _ := strconv.Atoi(s)
|
||||
return n
|
||||
}
|
||||
|
||||
// ToIntDefault 字符串转 int,失败返回默认值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 提供默认值,更灵活
|
||||
func ToIntDefault(s string, def int) int {
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ToInt64 字符串转 int64,失败返回 0
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 大整数转换常用
|
||||
func ToInt64(s string) int64 {
|
||||
n, _ := strconv.ParseInt(s, 10, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
// ToInt64Default 字符串转 int64,失败返回默认值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 提供默认值,更灵活
|
||||
func ToInt64Default(s string, def int64) int64 {
|
||||
n, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ToUint64 字符串转 uint64,失败返回 0
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 无符号整数转换
|
||||
func ToUint64(s string) uint64 {
|
||||
n, _ := strconv.ParseUint(s, 10, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
// ToUint64Default 字符串转 uint64,失败返回默认值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 提供默认值
|
||||
func ToUint64Default(s string, def uint64) uint64 {
|
||||
n, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ToFloat64 字符串转 float64,失败返回 0
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 浮点数转换常用
|
||||
func ToFloat64(s string) float64 {
|
||||
n, _ := strconv.ParseFloat(s, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
// ToFloat64Default 字符串转 float64,失败返回默认值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 提供默认值
|
||||
func ToFloat64Default(s string, def float64) float64 {
|
||||
n, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ToString 整数转字符串
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 简化 strconv.Itoa 调用
|
||||
func ToString(n int) string {
|
||||
return strconv.Itoa(n)
|
||||
}
|
||||
|
||||
// ToString64 int64 转字符串
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 大整数转字符串
|
||||
func ToString64(n int64) string {
|
||||
return strconv.FormatInt(n, 10)
|
||||
}
|
||||
|
||||
// CalcPageCount 计算总页数
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 分页查询必备,避免除零错误
|
||||
func CalcPageCount(total, pageSize int64) int64 {
|
||||
if total <= 0 || pageSize <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (total + pageSize - 1) / pageSize
|
||||
}
|
||||
|
||||
// CalcOffset 计算分页偏移量
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 分页查询必备,自动处理页码边界
|
||||
func CalcOffset(page, pageSize int) int {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
return (page - 1) * pageSize
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// MD5 计算字符串的 MD5 哈希值
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 常用哈希函数,但 MD5 已不安全,仅用于非安全场景(如文件校验)
|
||||
// 注意: 不应用于密码存储
|
||||
func MD5(s string) string {
|
||||
return MD5Bytes([]byte(s))
|
||||
}
|
||||
|
||||
// MD5Bytes 计算字节数组的 MD5 哈希值
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 文件哈希常用
|
||||
func MD5Bytes(data []byte) string {
|
||||
h := md5.New()
|
||||
h.Write(data)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// SHA1 计算字符串的 SHA1 哈希值
|
||||
// 评分: ⭐⭐⭐
|
||||
// 理由: SHA1 也已不安全,仅用于兼容旧系统
|
||||
// 注意: 不应用于密码存储
|
||||
func SHA1(s string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(s))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// SHA256 计算字符串的 SHA256 哈希值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 安全哈希算法,适用于数据完整性校验
|
||||
func SHA256(s string) string {
|
||||
h := sha256.New()
|
||||
h.Write([]byte(s))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// SHA256Bytes 计算字节数组的 SHA256 哈希值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 文件哈希常用
|
||||
func SHA256Bytes(data []byte) string {
|
||||
h := sha256.New()
|
||||
h.Write(data)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// HashFile 计算文件的哈希值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 大文件哈希,支持分块读取避免内存溢出
|
||||
func HashFile(path string, newHash func() hash.Hash) (string, error) {
|
||||
data, err := ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h := newHash()
|
||||
h.Write(data)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// Base64Encode Base64 编码
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 常用编码函数
|
||||
func Base64Encode(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
// Base64Decode Base64 解码
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 常用解码函数
|
||||
func Base64Decode(s string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// Base64URLEncode URL 安全的 Base64 编码
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: URL 传输常用,替换 +/ 为 -_
|
||||
func Base64URLEncode(data []byte) string {
|
||||
return base64.URLEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
// Base64URLDecode URL 安全的 Base64 解码
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: URL 传输常用
|
||||
func Base64URLDecode(s string) ([]byte, error) {
|
||||
return base64.URLEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// Nl2br 将换行符替换为 <br> 标签
|
||||
// 评分: ⭐⭐⭐
|
||||
// 理由: 特定场景使用(富文本显示),但现代前端框架通常自行处理
|
||||
func Nl2br(s string, isXhtml bool) string {
|
||||
var br string
|
||||
if isXhtml {
|
||||
br = "<br />"
|
||||
} else {
|
||||
br = "<br>"
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
runes := []rune(s)
|
||||
length := len(runes)
|
||||
|
||||
for i, r := range runes {
|
||||
switch r {
|
||||
case '\n':
|
||||
// 检查是否是 \r\n 或 \n\r
|
||||
if i+1 < length {
|
||||
next := runes[i+1]
|
||||
if (r == '\n' && next == '\r') || (r == '\r' && next == '\n') {
|
||||
buf.WriteString(br)
|
||||
continue
|
||||
}
|
||||
}
|
||||
buf.WriteString(br)
|
||||
case '\r':
|
||||
// 单独的 \r 或 \r\n 已在上面处理
|
||||
if i+1 < length && runes[i+1] == '\n' {
|
||||
continue // \r\n 由 \n 处理
|
||||
}
|
||||
buf.WriteString(br)
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NowUnix 返回当前秒级时间戳
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 简单封装,语义清晰,避免每次写 time.Now().Unix()
|
||||
func NowUnix() int64 {
|
||||
return time.Now().Unix()
|
||||
}
|
||||
|
||||
// NowTimestamp 返回当前毫秒时间戳
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: API接口常用毫秒时间戳,封装后更易用
|
||||
func NowTimestamp() int64 {
|
||||
return time.Now().UnixMilli()
|
||||
}
|
||||
|
||||
// FromUnix 秒级时间戳转 time.Time
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 时间戳转换常用,封装后语义清晰
|
||||
func FromUnix(unix int64) time.Time {
|
||||
return time.Unix(unix, 0)
|
||||
}
|
||||
|
||||
// FromTimestamp 毫秒时间戳转 time.Time
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 毫秒时间戳转换,API交互常用
|
||||
func FromTimestamp(timestamp int64) time.Time {
|
||||
return time.UnixMilli(timestamp)
|
||||
}
|
||||
|
||||
// FormatTime 格式化时间
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 常用时间格式化封装,支持自定义布局
|
||||
func FormatTime(t time.Time, layout string) string {
|
||||
return t.Format(layout)
|
||||
}
|
||||
|
||||
// ParseTime 解析时间字符串
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 时间解析封装,统一错误处理
|
||||
func ParseTime(timeStr, layout string) (time.Time, error) {
|
||||
return time.Parse(layout, timeStr)
|
||||
}
|
||||
|
||||
// FormatDateTime 格式化为标准日期时间 "2006-01-02 15:04:05"
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 预设常用格式,避免记忆 Go 的时间布局
|
||||
func FormatDateTime(t time.Time) string {
|
||||
return t.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
// FormatDate 格式化为日期 "2006-01-02"
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 预设常用格式
|
||||
func FormatDate(t time.Time) string {
|
||||
return t.Format("2006-01-02")
|
||||
}
|
||||
|
||||
// FormatTimeOnly 格式化为时间 "15:04:05"
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 预设常用格式
|
||||
func FormatTimeOnly(t time.Time) string {
|
||||
return t.Format("15:04:05")
|
||||
}
|
||||
|
||||
// StartOfDay 返回指定时间当天的开始时间 (00:00:00)
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 统计报表、日期范围查询常用
|
||||
func StartOfDay(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// EndOfDay 返回指定时间当天的结束时间 (23:59:59.999999999)
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 统计报表、日期范围查询常用
|
||||
func EndOfDay(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, t.Location())
|
||||
}
|
||||
|
||||
// StartOfWeek 返回指定时间当周的开始时间(周一为第一天)
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 周报表统计常用
|
||||
func StartOfWeek(t time.Time) time.Time {
|
||||
weekday := int(t.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
d := time.Duration(weekday-1) * 24 * time.Hour
|
||||
return StartOfDay(t.Add(-d))
|
||||
}
|
||||
|
||||
// StartOfMonth 返回指定时间当月的开始时间
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 月度统计常用
|
||||
func StartOfMonth(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// EndOfMonth 返回指定时间当月的结束时间
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 月度统计常用
|
||||
func EndOfMonth(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month()+1, 0, 23, 59, 59, 999999999, t.Location())
|
||||
}
|
||||
|
||||
// GetDateInt 返回 yyyyMMdd 格式的日期整数
|
||||
// 评分: ⭐⭐⭐
|
||||
// 理由: 特定场景使用(如分区键),但返回 int 可能不够通用
|
||||
func GetDateInt(t time.Time) int {
|
||||
ret, _ := strconv.Atoi(t.Format("20060102"))
|
||||
return ret
|
||||
}
|
||||
|
||||
// ParseDateInt 将 yyyyMMdd 格式的整数转为时间
|
||||
// 评分: ⭐⭐⭐
|
||||
// 理由: GetDateInt 的反向操作
|
||||
func ParseDateInt(date int) time.Time {
|
||||
year := date / 10000
|
||||
month := (date % 10000) / 100
|
||||
day := date % 100
|
||||
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FileExists 检查文件是否存在
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 常用检查,语义清晰
|
||||
func FileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// DirExists 检查目录是否存在
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 区分文件和目录检查,更精确
|
||||
func DirExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
// EnsureDir 确保目录存在,不存在则创建
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 日志、上传等场景常用,避免每次判断
|
||||
func EnsureDir(path string) error {
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
|
||||
// ReadFile 读取文件内容
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 简化 os.ReadFile,增加存在性检查
|
||||
func ReadFile(path string) ([]byte, error) {
|
||||
if !FileExists(path) {
|
||||
return nil, fmt.Errorf("file not found: %s", path)
|
||||
}
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
// WriteFile 写入文件内容(覆盖)
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 简化文件写入,自动创建目录
|
||||
func WriteFile(path string, data []byte) error {
|
||||
dir := filepath.Dir(path)
|
||||
if err := EnsureDir(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// AppendFile 追加内容到文件
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 日志追加场景常用
|
||||
func AppendFile(path string, data []byte) error {
|
||||
dir := filepath.Dir(path)
|
||||
if err := EnsureDir(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// CopyFile 复制文件
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 文件操作常用,使用 io.Copy 高效复制
|
||||
func CopyFile(dst, src string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
// 确保目标目录存在
|
||||
dir := filepath.Dir(dst)
|
||||
if err := EnsureDir(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
_, err = io.Copy(dstFile, srcFile)
|
||||
return err
|
||||
}
|
||||
|
||||
// FileSize 获取文件大小
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 上传文件大小检查常用
|
||||
func FileSize(path string) (int64, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return info.Size(), nil
|
||||
}
|
||||
|
||||
// RemoveFile 删除文件(忽略不存在的错误)
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 清理临时文件时常用,避免额外判断
|
||||
func RemoveFile(path string) error {
|
||||
err := os.Remove(path)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPClient HTTP 客户端封装
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 链式调用设计优雅,Transport 复用,连接池优化
|
||||
type HTTPClient struct {
|
||||
client *http.Client
|
||||
transport *http.Transport
|
||||
timeout time.Duration
|
||||
headers map[string]string
|
||||
cookies map[string]string
|
||||
skipTLS bool
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// UploadFile 上传文件信息
|
||||
type UploadFile struct {
|
||||
FieldName string // 表单字段名
|
||||
FilePath string // 文件路径
|
||||
}
|
||||
|
||||
// HTTPClientConfig HTTP 客户端配置
|
||||
type HTTPClientConfig struct {
|
||||
Timeout time.Duration // 请求超时时间
|
||||
MaxIdleConns int // 最大空闲连接数
|
||||
IdleConnTimeout time.Duration // 空闲连接超时时间
|
||||
MaxConnsPerHost int // 每个主机最大连接数
|
||||
MaxIdleConnsPerHost int // 每个主机最大空闲连接数
|
||||
SkipTLSVerify bool // 是否跳过 TLS 验证
|
||||
}
|
||||
|
||||
// DefaultHTTPClientConfig 默认配置
|
||||
var DefaultHTTPClientConfig = HTTPClientConfig{
|
||||
Timeout: 30 * time.Second,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
MaxConnsPerHost: 10,
|
||||
MaxIdleConnsPerHost: 10,
|
||||
SkipTLSVerify: true, // 开发环境默认跳过
|
||||
}
|
||||
|
||||
// NewHTTPClient 创建 HTTP 客户端
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: Transport 在初始化时创建,连接池可复用
|
||||
func NewHTTPClient() *HTTPClient {
|
||||
cfg := DefaultHTTPClientConfig
|
||||
return NewHTTPClientWithConfig(cfg)
|
||||
}
|
||||
|
||||
// NewHTTPClientWithConfig 使用自定义配置创建 HTTP 客户端
|
||||
func NewHTTPClientWithConfig(cfg HTTPClientConfig) *HTTPClient {
|
||||
// Transport 在初始化时创建,连接池可复用
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: cfg.SkipTLSVerify,
|
||||
},
|
||||
MaxIdleConns: cfg.MaxIdleConns,
|
||||
IdleConnTimeout: cfg.IdleConnTimeout,
|
||||
MaxConnsPerHost: cfg.MaxConnsPerHost,
|
||||
MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost,
|
||||
DisableCompression: false,
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: cfg.Timeout,
|
||||
}
|
||||
|
||||
return &HTTPClient{
|
||||
client: client,
|
||||
transport: transport,
|
||||
timeout: cfg.Timeout,
|
||||
headers: make(map[string]string),
|
||||
cookies: make(map[string]string),
|
||||
skipTLS: cfg.SkipTLSVerify,
|
||||
}
|
||||
}
|
||||
|
||||
// SetTimeout 设置超时时间
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 链式调用,动态调整超时
|
||||
func (c *HTTPClient) SetTimeout(timeout time.Duration) *HTTPClient {
|
||||
c.timeout = timeout
|
||||
c.client.Timeout = timeout
|
||||
return c
|
||||
}
|
||||
|
||||
// SetHeader 设置请求头
|
||||
func (c *HTTPClient) SetHeader(key, value string) *HTTPClient {
|
||||
c.headers[key] = value
|
||||
return c
|
||||
}
|
||||
|
||||
// SetHeaders 批量设置请求头
|
||||
func (c *HTTPClient) SetHeaders(headers map[string]string) *HTTPClient {
|
||||
for k, v := range headers {
|
||||
c.headers[k] = v
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// SetCookie 设置 Cookie
|
||||
func (c *HTTPClient) SetCookie(key, value string) *HTTPClient {
|
||||
c.cookies[key] = value
|
||||
return c
|
||||
}
|
||||
|
||||
// SetSkipTLS 设置是否跳过 TLS 验证
|
||||
// 注意: 修改 TLS 配置需要重新创建 Transport
|
||||
func (c *HTTPClient) SetSkipTLS(skip bool) *HTTPClient {
|
||||
c.skipTLS = skip
|
||||
c.transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: skip,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Get 发送 GET 请求
|
||||
func (c *HTTPClient) Get(urlStr string, params map[string]string) ([]byte, error) {
|
||||
if len(params) > 0 {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q := u.Query()
|
||||
for k, v := range params {
|
||||
q.Set(k, v)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
urlStr = u.String()
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// Post 发送 POST 请求(form 表单格式)
|
||||
func (c *HTTPClient) Post(urlStr string, params map[string]string) ([]byte, error) {
|
||||
data := url.Values{}
|
||||
for k, v := range params {
|
||||
data.Set(k, v)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", urlStr, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// PostJSON 发送 POST 请求(JSON 格式)
|
||||
func (c *HTTPClient) PostJSON(urlStr string, data any) ([]byte, error) {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", urlStr, bytes.NewReader(jsonData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// Put 发送 PUT 请求(JSON 格式)
|
||||
func (c *HTTPClient) Put(urlStr string, data any) ([]byte, error) {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", urlStr, bytes.NewReader(jsonData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// Delete 发送 DELETE 请求
|
||||
func (c *HTTPClient) Delete(urlStr string) ([]byte, error) {
|
||||
req, err := http.NewRequest("DELETE", urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// Upload 上传文件
|
||||
func (c *HTTPClient) Upload(urlStr string, files []UploadFile, params map[string]string) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
writer := multipart.NewWriter(&buf)
|
||||
|
||||
for _, f := range files {
|
||||
file, err := os.Open(f.FilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
part, err := writer.CreateFormFile(f.FieldName, filepath.Base(f.FilePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = io.Copy(part, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
if err := writer.WriteField(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", urlStr, &buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// UploadFromBytes 从字节数据上传文件
|
||||
func (c *HTTPClient) UploadFromBytes(urlStr string, fieldName string, filename string, data []byte, params map[string]string) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
writer := multipart.NewWriter(&buf)
|
||||
|
||||
part, err := writer.CreateFormFile(fieldName, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = io.Copy(part, bytes.NewReader(data)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
if err := writer.WriteField(k, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", urlStr, &buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// Request 发送自定义请求
|
||||
func (c *HTTPClient) Request(method, urlStr string, body []byte) ([]byte, error) {
|
||||
req, err := http.NewRequest(method, urlStr, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
// do 执行请求(使用共享的 client 和 transport)
|
||||
func (c *HTTPClient) do(req *http.Request) ([]byte, error) {
|
||||
// 设置请求头
|
||||
for k, v := range c.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// 设置 Cookie
|
||||
for k, v := range c.cookies {
|
||||
req.AddCookie(&http.Cookie{Name: k, Value: v})
|
||||
}
|
||||
|
||||
// 发送请求(使用初始化时创建的 client)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查状态码
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("http error: %d %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// DoWithResponse 执行请求并返回完整响应
|
||||
func (c *HTTPClient) DoWithResponse(req *http.Request) (*http.Response, error) {
|
||||
for k, v := range c.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
for k, v := range c.cookies {
|
||||
req.AddCookie(&http.Cookie{Name: k, Value: v})
|
||||
}
|
||||
|
||||
return c.client.Do(req)
|
||||
}
|
||||
|
||||
// Close 关闭客户端(释放连接池资源)
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 清理资源,避免连接泄漏
|
||||
func (c *HTTPClient) Close() {
|
||||
c.transport.CloseIdleConnections()
|
||||
}
|
||||
|
||||
// JSONMarshal 内部 JSON 序列化函数
|
||||
func JSONMarshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// 全局默认 HTTP 客户端
|
||||
var defaultClient *HTTPClient
|
||||
var defaultClientOnce sync.Once
|
||||
|
||||
// DefaultHTTPClient 获取全局默认 HTTP 客户端
|
||||
func DefaultHTTPClient() *HTTPClient {
|
||||
defaultClientOnce.Do(func() {
|
||||
defaultClient = NewHTTPClient()
|
||||
})
|
||||
return defaultClient
|
||||
}
|
||||
|
||||
// HTTPGet 使用默认客户端发送 GET 请求
|
||||
func HTTPGet(url string, params map[string]string) ([]byte, error) {
|
||||
return DefaultHTTPClient().Get(url, params)
|
||||
}
|
||||
|
||||
// HTTPPost 使用默认客户端发送 POST 请求
|
||||
func HTTPPost(url string, params map[string]string) ([]byte, error) {
|
||||
return DefaultHTTPClient().Post(url, params)
|
||||
}
|
||||
|
||||
// HTTPPostJSON 使用默认客户端发送 JSON POST 请求
|
||||
func HTTPPostJSON(url string, data any) ([]byte, error) {
|
||||
return DefaultHTTPClient().PostJSON(url, data)
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
randPool = sync.Pool{
|
||||
New: func() any {
|
||||
return rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
digitBytes = "0123456789"
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index (0-63)
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
// RandString 生成指定长度的随机字符串(字母+数字)
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 使用 sync.Pool 复用 rand.Source,性能优秀;位运算优化高效
|
||||
func RandString(n int) string {
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
b := make([]byte, n)
|
||||
r := randPool.Get().(*rand.Rand)
|
||||
defer randPool.Put(r)
|
||||
|
||||
for i, cache, remain := n-1, r.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = r.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// RandDigit 生成指定长度的随机数字字符串
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 同 RandString,适用于验证码、订单号等场景
|
||||
func RandDigit(n int) string {
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
b := make([]byte, n)
|
||||
r := randPool.Get().(*rand.Rand)
|
||||
defer randPool.Put(r)
|
||||
|
||||
for i, cache, remain := n-1, r.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = r.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(digitBytes) {
|
||||
b[i] = digitBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// RandInt 返回 [min, max) 范围内的随机整数
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 实用函数,自动处理 min > max 的情况
|
||||
func RandInt(min, max int) int {
|
||||
if min == max {
|
||||
return min
|
||||
}
|
||||
if max < min {
|
||||
min, max = max, min
|
||||
}
|
||||
r := randPool.Get().(*rand.Rand)
|
||||
defer randPool.Put(r)
|
||||
return min + r.Intn(max-min)
|
||||
}
|
||||
|
||||
// RandInt64 返回 [min, max) 范围内的随机 int64
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: RandInt 的 int64 版本,适用于大范围随机数
|
||||
func RandInt64(min, max int64) int64 {
|
||||
if min == max {
|
||||
return min
|
||||
}
|
||||
if max < min {
|
||||
min, max = max, min
|
||||
}
|
||||
r := randPool.Get().(*rand.Rand)
|
||||
defer randPool.Put(r)
|
||||
return min + r.Int63n(max-min)
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// IsBlank 检查字符串是否为空或仅包含空白字符
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 常用函数,正确处理 Unicode 空白字符
|
||||
func IsBlank(s string) bool {
|
||||
if s == "" {
|
||||
return true
|
||||
}
|
||||
for _, r := range s {
|
||||
if !unicode.IsSpace(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNotBlank 检查字符串是否非空且不全是空白字符
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: IsBlank 的反向,语义更清晰
|
||||
func IsNotBlank(s string) bool {
|
||||
return !IsBlank(s)
|
||||
}
|
||||
|
||||
// IsAnyBlank 检查多个字符串中是否有任意一个为空或空白
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 参数校验场景常用,避免多次调用 IsBlank
|
||||
func IsAnyBlank(strs ...string) bool {
|
||||
for _, s := range strs {
|
||||
if IsBlank(s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAllBlank 检查所有字符串是否都为空或空白
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: IsAnyBlank 的反向,适用于批量校验
|
||||
func IsAllBlank(strs ...string) bool {
|
||||
for _, s := range strs {
|
||||
if IsNotBlank(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DefaultIfBlank 如果字符串为空或空白,返回默认值
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 配置默认值场景常用,代码简洁
|
||||
func DefaultIfBlank(s, def string) string {
|
||||
if IsBlank(s) {
|
||||
return def
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// IsEmpty 检查任意类型是否为空值
|
||||
// 评分: ⭐⭐⭐
|
||||
// 理由: 功能实用,但使用反射有性能开销;建议仅在必要时使用
|
||||
// 支持: string, []T, map[K]V, nil, 零值
|
||||
func IsEmpty(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return val == ""
|
||||
case []byte:
|
||||
return len(val) == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Trim 去除字符串首尾的空白字符(包括空格、制表符、换行符)
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 比标准库 strings.Trim 更语义化,预设常用空白字符
|
||||
func Trim(s string) string {
|
||||
return trimFunc(s, unicode.IsSpace)
|
||||
}
|
||||
|
||||
// trimFunc 内部修剪函数
|
||||
func trimFunc(s string, f func(rune) bool) string {
|
||||
s = trimLeftFunc(s, f)
|
||||
return trimRightFunc(s, f)
|
||||
}
|
||||
|
||||
func trimLeftFunc(s string, f func(rune) bool) string {
|
||||
for i, r := range s {
|
||||
if !f(r) {
|
||||
return s[i:]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func trimRightFunc(s string, f func(rune) bool) string {
|
||||
for i := len(s); i > 0; {
|
||||
r, size := utf8.DecodeLastRuneInString(s[:i])
|
||||
if !f(r) {
|
||||
return s[:i]
|
||||
}
|
||||
i -= size
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Substr 截取子字符串(按 Unicode 字符计数)
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 正确处理多字节字符(中文等),避免截断
|
||||
// 参数: start 起始位置(支持负数从末尾计算),length 截取长度
|
||||
func Substr(s string, start, length int) string {
|
||||
runes := []rune(s)
|
||||
lenRunes := len(runes)
|
||||
|
||||
if lenRunes == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 处理负数起始位置
|
||||
if start < 0 {
|
||||
start = lenRunes + start
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start >= lenRunes {
|
||||
return ""
|
||||
}
|
||||
|
||||
end := start + length
|
||||
if end > lenRunes {
|
||||
end = lenRunes
|
||||
}
|
||||
if end <= start {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(runes[start:end])
|
||||
}
|
||||
|
||||
// StrLen 获取字符串的 Unicode 字符数
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 正确计算多字节字符长度,比 len() 更准确
|
||||
func StrLen(s string) int {
|
||||
return utf8.RuneCountInString(s)
|
||||
}
|
||||
|
||||
// EqualsIgnoreCase 不区分大小写比较字符串
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 常用比较函数,使用标准库实现保证正确性
|
||||
func EqualsIgnoreCase(a, b string) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
ca := a[i]
|
||||
cb := b[i]
|
||||
if ca != cb {
|
||||
// 转小写比较
|
||||
if ca >= 'A' && ca <= 'Z' {
|
||||
ca += 32
|
||||
}
|
||||
if cb >= 'A' && cb <= 'Z' {
|
||||
cb += 32
|
||||
}
|
||||
if ca != cb {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// URLBuilder URL 构建器
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 链式调用设计优雅,便于构建复杂 URL
|
||||
type URLBuilder struct {
|
||||
u *url.URL
|
||||
query url.Values
|
||||
}
|
||||
|
||||
// ParseURL 解析 URL 字符串
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 创建 URLBuilder 的入口函数
|
||||
func ParseURL(rawURL string) (*URLBuilder, error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &URLBuilder{
|
||||
u: u,
|
||||
query: u.Query(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddQuery 添加单个查询参数
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 链式调用,支持多次添加
|
||||
func (b *URLBuilder) AddQuery(key, value string) *URLBuilder {
|
||||
b.query.Add(key, value)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddQueries 批量添加查询参数
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 批量添加更高效
|
||||
func (b *URLBuilder) AddQueries(params map[string]string) *URLBuilder {
|
||||
for k, v := range params {
|
||||
b.query.Set(k, v)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// SetQuery 设置查询参数(覆盖同名参数)
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 区别于 Add,覆盖同名参数
|
||||
func (b *URLBuilder) SetQuery(key, value string) *URLBuilder {
|
||||
b.query.Set(key, value)
|
||||
return b
|
||||
}
|
||||
|
||||
// SetPath 设置路径
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 动态修改路径
|
||||
func (b *URLBuilder) SetPath(path string) *URLBuilder {
|
||||
b.u.Path = path
|
||||
return b
|
||||
}
|
||||
|
||||
// SetScheme 设置协议(http/https)
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 协议切换
|
||||
func (b *URLBuilder) SetScheme(scheme string) *URLBuilder {
|
||||
b.u.Scheme = scheme
|
||||
return b
|
||||
}
|
||||
|
||||
// SetHost 设置主机
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 域名切换
|
||||
func (b *URLBuilder) SetHost(host string) *URLBuilder {
|
||||
b.u.Host = host
|
||||
return b
|
||||
}
|
||||
|
||||
// Build 构建最终的 URL
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 返回标准库 url.URL
|
||||
func (b *URLBuilder) Build() *url.URL {
|
||||
b.u.RawQuery = b.query.Encode()
|
||||
return b.u
|
||||
}
|
||||
|
||||
// String 返回 URL 字符串
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 最常用的输出方法
|
||||
func (b *URLBuilder) String() string {
|
||||
return b.Build().String()
|
||||
}
|
||||
|
||||
// URLEncode URL 编码
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 常用编码函数
|
||||
func URLEncode(s string) string {
|
||||
return url.QueryEscape(s)
|
||||
}
|
||||
|
||||
// URLDecode URL 解码
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 常用解码函数
|
||||
func URLDecode(s string) (string, error) {
|
||||
return url.QueryUnescape(s)
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/EthanCodeCraft/xlgo-core/utils"
|
||||
)
|
||||
|
||||
// ===== Random Tests =====
|
||||
|
||||
func TestRandString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
length int
|
||||
}{
|
||||
{"normal", 16},
|
||||
{"short", 1},
|
||||
{"long", 100},
|
||||
{"zero", 0},
|
||||
{"negative", -1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := utils.RandString(tt.length)
|
||||
if tt.length <= 0 {
|
||||
if s != "" {
|
||||
t.Errorf("RandString(%d) should return empty", tt.length)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(s) != tt.length {
|
||||
t.Errorf("RandString(%d) length = %d", tt.length, len(s))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 测试 uniqueness
|
||||
results := make(map[string]bool)
|
||||
for i := 0; i < 100; i++ {
|
||||
s := utils.RandString(16)
|
||||
if results[s] {
|
||||
t.Error("RandString generated duplicate")
|
||||
}
|
||||
results[s] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandDigit(t *testing.T) {
|
||||
s := utils.RandDigit(6)
|
||||
if len(s) != 6 {
|
||||
t.Errorf("RandDigit length = %d", len(s))
|
||||
}
|
||||
for _, c := range s {
|
||||
if c < '0' || c > '9' {
|
||||
t.Errorf("RandDigit contains non-digit: %c", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandInt(t *testing.T) {
|
||||
// 正常范围
|
||||
for i := 0; i < 100; i++ {
|
||||
r := utils.RandInt(1, 100)
|
||||
if r < 1 || r >= 100 {
|
||||
t.Errorf("RandInt(1, 100) = %d, out of range", r)
|
||||
}
|
||||
}
|
||||
|
||||
// min == max
|
||||
r := utils.RandInt(5, 5)
|
||||
if r != 5 {
|
||||
t.Errorf("RandInt(5, 5) = %d", r)
|
||||
}
|
||||
|
||||
// min > max (自动交换)
|
||||
r = utils.RandInt(100, 1)
|
||||
if r < 1 || r >= 100 {
|
||||
t.Errorf("RandInt(100, 1) = %d, should swap", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandInt64(t *testing.T) {
|
||||
r := utils.RandInt64(0, 1000000)
|
||||
if r < 0 || r >= 1000000 {
|
||||
t.Errorf("RandInt64 out of range: %d", r)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== String Tests =====
|
||||
|
||||
func TestIsBlank(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"", true},
|
||||
{" ", true},
|
||||
{"\t\n", true},
|
||||
{"abc", false},
|
||||
{" abc ", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if utils.IsBlank(tt.input) != tt.expected {
|
||||
t.Errorf("IsBlank(%q) = %v, want %v", tt.input, !tt.expected, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyBlank(t *testing.T) {
|
||||
if !utils.IsAnyBlank("", "a") {
|
||||
t.Error("IsAnyBlank should return true")
|
||||
}
|
||||
if utils.IsAnyBlank("a", "b") {
|
||||
t.Error("IsAnyBlank should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAllBlank(t *testing.T) {
|
||||
if !utils.IsAllBlank("", " ", "\t") {
|
||||
t.Error("IsAllBlank should return true")
|
||||
}
|
||||
if utils.IsAllBlank("", "a") {
|
||||
t.Error("IsAllBlank should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultIfBlank(t *testing.T) {
|
||||
if utils.DefaultIfBlank("", "def") != "def" {
|
||||
t.Error("DefaultIfBlank empty failed")
|
||||
}
|
||||
if utils.DefaultIfBlank("val", "def") != "val" {
|
||||
t.Error("DefaultIfBlank non-empty failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubstr(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
start int
|
||||
length int
|
||||
expected string
|
||||
}{
|
||||
{"hello世界", 0, 7, "hello世界"},
|
||||
{"hello世界", 0, 5, "hello"},
|
||||
{"hello世界", 5, 2, "世界"},
|
||||
{"hello世界", -2, 2, "世界"},
|
||||
{"hello世界", 100, 2, ""},
|
||||
{"", 0, 5, ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := utils.Substr(tt.input, tt.start, tt.length)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Substr(%q, %d, %d) = %q, want %q", tt.input, tt.start, tt.length, result, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrLen(t *testing.T) {
|
||||
if utils.StrLen("hello世界") != 7 {
|
||||
t.Error("StrLen Unicode count failed")
|
||||
}
|
||||
if utils.StrLen("") != 0 {
|
||||
t.Error("StrLen empty failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualsIgnoreCase(t *testing.T) {
|
||||
tests := []struct {
|
||||
a, b string
|
||||
expected bool
|
||||
}{
|
||||
{"ABC", "abc", true},
|
||||
{"Hello", "HELLO", true},
|
||||
{"abc", "abc", true},
|
||||
{"abc", "abd", false},
|
||||
{"ab", "abc", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if utils.EqualsIgnoreCase(tt.a, tt.b) != tt.expected {
|
||||
t.Errorf("EqualsIgnoreCase(%q, %q) failed", tt.a, tt.b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrim(t *testing.T) {
|
||||
if utils.Trim(" hello ") != "hello" {
|
||||
t.Error("Trim failed")
|
||||
}
|
||||
if utils.Trim("\t\nhello\n\t") != "hello" {
|
||||
t.Error("Trim whitespace failed")
|
||||
}
|
||||
}
|
||||
|
||||
// ===== DateTime Tests =====
|
||||
|
||||
func TestNowUnix(t *testing.T) {
|
||||
n := utils.NowUnix()
|
||||
if n == 0 {
|
||||
t.Error("NowUnix returned 0")
|
||||
}
|
||||
// 应接近当前时间
|
||||
expected := time.Now().Unix()
|
||||
if n < expected-1 || n > expected+1 {
|
||||
t.Errorf("NowUnix = %d, expected ~%d", n, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNowTimestamp(t *testing.T) {
|
||||
n := utils.NowTimestamp()
|
||||
if n == 0 {
|
||||
t.Error("NowTimestamp returned 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromUnix(t *testing.T) {
|
||||
now := time.Now()
|
||||
unix := now.Unix()
|
||||
result := utils.FromUnix(unix)
|
||||
if result.Unix() != unix {
|
||||
t.Errorf("FromUnix mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDateTime(t *testing.T) {
|
||||
now := time.Now()
|
||||
s := utils.FormatDateTime(now)
|
||||
if len(s) != 19 {
|
||||
t.Errorf("FormatDateTime length = %d", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatDate(t *testing.T) {
|
||||
now := time.Now()
|
||||
s := utils.FormatDate(now)
|
||||
if len(s) != 10 {
|
||||
t.Errorf("FormatDate length = %d", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartEndOfDay(t *testing.T) {
|
||||
now := time.Now()
|
||||
start := utils.StartOfDay(now)
|
||||
end := utils.EndOfDay(now)
|
||||
|
||||
if start.Hour() != 0 || start.Minute() != 0 || start.Second() != 0 {
|
||||
t.Error("StartOfDay not at 00:00:00")
|
||||
}
|
||||
if end.Hour() != 23 || end.Minute() != 59 || end.Second() != 59 {
|
||||
t.Error("EndOfDay not at 23:59:59")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartEndOfMonth(t *testing.T) {
|
||||
now := time.Now()
|
||||
start := utils.StartOfMonth(now)
|
||||
end := utils.EndOfMonth(now)
|
||||
|
||||
if start.Day() != 1 {
|
||||
t.Error("StartOfMonth day != 1")
|
||||
}
|
||||
if end.Day() < 28 || end.Day() > 31 {
|
||||
t.Errorf("EndOfMonth day = %d, unexpected", end.Day())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDateInt(t *testing.T) {
|
||||
now := time.Now()
|
||||
dateInt := utils.GetDateInt(now)
|
||||
expected := now.Year()*10000 + int(now.Month())*100 + now.Day()
|
||||
if dateInt != expected {
|
||||
t.Errorf("GetDateInt = %d, expected %d", dateInt, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDateInt(t *testing.T) {
|
||||
dateInt := 20260430
|
||||
result := utils.ParseDateInt(dateInt)
|
||||
if result.Year() != 2026 || result.Month() != 4 || result.Day() != 30 {
|
||||
t.Errorf("ParseDateInt(%d) = %v", dateInt, result)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Convert Tests =====
|
||||
|
||||
func TestToInt(t *testing.T) {
|
||||
if utils.ToInt("123") != 123 {
|
||||
t.Error("ToInt failed")
|
||||
}
|
||||
if utils.ToInt("abc") != 0 {
|
||||
t.Error("ToInt invalid should return 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToIntDefault(t *testing.T) {
|
||||
if utils.ToIntDefault("123", 999) != 123 {
|
||||
t.Error("ToIntDefault valid failed")
|
||||
}
|
||||
if utils.ToIntDefault("abc", 999) != 999 {
|
||||
t.Error("ToIntDefault invalid should return default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToInt64(t *testing.T) {
|
||||
if utils.ToInt64("1234567890123") != 1234567890123 {
|
||||
t.Error("ToInt64 failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToInt64Default(t *testing.T) {
|
||||
if utils.ToInt64Default("abc", 999) != 999 {
|
||||
t.Error("ToInt64Default failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToUint64Default(t *testing.T) {
|
||||
if utils.ToUint64Default("abc", 999) != 999 {
|
||||
t.Error("ToUint64Default failed")
|
||||
}
|
||||
if utils.ToUint64Default("123", 0) != 123 {
|
||||
t.Error("ToUint64Default valid failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToFloat64(t *testing.T) {
|
||||
if utils.ToFloat64("3.14") != 3.14 {
|
||||
t.Error("ToFloat64 failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToFloat64Default(t *testing.T) {
|
||||
if utils.ToFloat64Default("abc", 1.5) != 1.5 {
|
||||
t.Error("ToFloat64Default failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToString(t *testing.T) {
|
||||
if utils.ToString(123) != "123" {
|
||||
t.Error("ToString failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToString64(t *testing.T) {
|
||||
if utils.ToString64(1234567890123) != "1234567890123" {
|
||||
t.Error("ToString64 failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcPageCount(t *testing.T) {
|
||||
tests := []struct {
|
||||
total int64
|
||||
pageSize int64
|
||||
expected int64
|
||||
}{
|
||||
{100, 10, 10},
|
||||
{95, 10, 10},
|
||||
{0, 10, 0},
|
||||
{100, 0, 0},
|
||||
{1, 10, 1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := utils.CalcPageCount(tt.total, tt.pageSize)
|
||||
if result != tt.expected {
|
||||
t.Errorf("CalcPageCount(%d, %d) = %d, want %d", tt.total, tt.pageSize, result, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcOffset(t *testing.T) {
|
||||
tests := []struct {
|
||||
page int
|
||||
pageSize int
|
||||
expected int
|
||||
}{
|
||||
{1, 20, 0},
|
||||
{2, 20, 20},
|
||||
{3, 10, 20},
|
||||
{0, 20, 0}, // page <= 0 自动修正为 1
|
||||
{1, 0, 0}, // pageSize <= 0 自动修正为 20
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := utils.CalcOffset(tt.page, tt.pageSize)
|
||||
if result != tt.expected {
|
||||
t.Errorf("CalcOffset(%d, %d) = %d, want %d", tt.page, tt.pageSize, result, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Validator Tests =====
|
||||
|
||||
func TestIsPhone(t *testing.T) {
|
||||
valid := []string{"13812345678", "15912345678", "18812345678"}
|
||||
invalid := []string{"12345678901", "1381234567", "138123456789"}
|
||||
|
||||
for _, p := range valid {
|
||||
if !utils.IsPhone(p) {
|
||||
t.Errorf("IsPhone(%s) should be true", p)
|
||||
}
|
||||
}
|
||||
for _, p := range invalid {
|
||||
if utils.IsPhone(p) {
|
||||
t.Errorf("IsPhone(%s) should be false", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmail(t *testing.T) {
|
||||
valid := []string{"test@example.com", "user.name@domain.cn"}
|
||||
invalid := []string{"invalid", "no@", "@nodomain.com"}
|
||||
|
||||
for _, e := range valid {
|
||||
if !utils.IsEmail(e) {
|
||||
t.Errorf("IsEmail(%s) should be true", e)
|
||||
}
|
||||
}
|
||||
for _, e := range invalid {
|
||||
if utils.IsEmail(e) {
|
||||
t.Errorf("IsEmail(%s) should be false", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv4(t *testing.T) {
|
||||
valid := []string{"192.168.1.1", "0.0.0.0", "255.255.255.255"}
|
||||
invalid := []string{"256.1.1.1", "1.1.1", "1.1.1.1.1"}
|
||||
|
||||
for _, ip := range valid {
|
||||
if !utils.IsIPv4(ip) {
|
||||
t.Errorf("IsIPv4(%s) should be true", ip)
|
||||
}
|
||||
}
|
||||
for _, ip := range invalid {
|
||||
if utils.IsIPv4(ip) {
|
||||
t.Errorf("IsIPv4(%s) should be false", ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIDCard(t *testing.T) {
|
||||
// 18位身份证
|
||||
if !utils.IsIDCard("11010519900307293X") {
|
||||
t.Error("IsIDCard valid 18-digit failed")
|
||||
}
|
||||
// 无效
|
||||
if utils.IsIDCard("123456789012345") {
|
||||
t.Error("IsIDCard invalid should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsChinese(t *testing.T) {
|
||||
if !utils.IsChinese("中文") {
|
||||
t.Error("IsChinese should be true")
|
||||
}
|
||||
if utils.IsChinese("abc中文") {
|
||||
t.Error("IsChinese mixed should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNumeric(t *testing.T) {
|
||||
if !utils.IsNumeric("12345") {
|
||||
t.Error("IsNumeric should be true")
|
||||
}
|
||||
if utils.IsNumeric("123a") {
|
||||
t.Error("IsNumeric should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphanumeric(t *testing.T) {
|
||||
if !utils.IsAlphanumeric("abc123") {
|
||||
t.Error("IsAlphanumeric should be true")
|
||||
}
|
||||
if utils.IsAlphanumeric("abc-123") {
|
||||
t.Error("IsAlphanumeric with dash should be false")
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Crypto Tests =====
|
||||
|
||||
func TestMD5(t *testing.T) {
|
||||
if utils.MD5("hello") != "5d41402abc4b2a76b9719d911017c592" {
|
||||
t.Error("MD5 failed")
|
||||
}
|
||||
if utils.MD5("") != "d41d8cd98f00b204e9800998ecf8427e" {
|
||||
t.Error("MD5 empty failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSHA256(t *testing.T) {
|
||||
// SHA256 有固定长度
|
||||
hash := utils.SHA256("hello")
|
||||
if len(hash) != 64 {
|
||||
t.Errorf("SHA256 length = %d", len(hash))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64(t *testing.T) {
|
||||
original := "hello world"
|
||||
encoded := utils.Base64Encode([]byte(original))
|
||||
decoded, err := utils.Base64Decode(encoded)
|
||||
if err != nil {
|
||||
t.Errorf("Base64Decode error: %v", err)
|
||||
}
|
||||
if string(decoded) != original {
|
||||
t.Errorf("Base64 roundtrip failed: %s", decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64URL(t *testing.T) {
|
||||
original := "hello+world"
|
||||
encoded := utils.Base64URLEncode([]byte(original))
|
||||
decoded, err := utils.Base64URLDecode(encoded)
|
||||
if err != nil {
|
||||
t.Errorf("Base64URLDecode error: %v", err)
|
||||
}
|
||||
if string(decoded) != original {
|
||||
t.Errorf("Base64URL roundtrip failed")
|
||||
}
|
||||
}
|
||||
|
||||
// ===== UUID Tests =====
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
uuid := utils.UUID()
|
||||
if len(uuid) != 36 {
|
||||
t.Errorf("UUID length = %d", len(uuid))
|
||||
}
|
||||
// 格式检查: 8-4-4-4-12
|
||||
if uuid[8] != '-' || uuid[13] != '-' || uuid[18] != '-' || uuid[23] != '-' {
|
||||
t.Errorf("UUID format invalid: %s", uuid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUIDShort(t *testing.T) {
|
||||
uuid := utils.UUIDShort()
|
||||
if len(uuid) != 32 {
|
||||
t.Errorf("UUIDShort length = %d", len(uuid))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUIDValid(t *testing.T) {
|
||||
valid := "550e8400-e29b-41d4-a716-446655440000"
|
||||
invalid := "invalid-uuid"
|
||||
|
||||
if !utils.UUIDValid(valid) {
|
||||
t.Errorf("UUIDValid(%s) should be true", valid)
|
||||
}
|
||||
if utils.UUIDValid(invalid) {
|
||||
t.Errorf("UUIDValid(%s) should be false", invalid)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== URL Tests =====
|
||||
|
||||
func TestURLEncodeDecode(t *testing.T) {
|
||||
original := "hello world"
|
||||
encoded := utils.URLEncode(original)
|
||||
decoded, err := utils.URLDecode(encoded)
|
||||
if err != nil {
|
||||
t.Errorf("URLDecode error: %v", err)
|
||||
}
|
||||
if decoded != original {
|
||||
t.Errorf("URL roundtrip failed: %s -> %s -> %s", original, encoded, decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseURL(t *testing.T) {
|
||||
b, err := utils.ParseURL("https://example.com/path")
|
||||
if err != nil {
|
||||
t.Errorf("ParseURL error: %v", err)
|
||||
}
|
||||
result := b.AddQuery("key", "value").AddQuery("foo", "bar").String()
|
||||
if !contains(result, "key=value") || !contains(result, "foo=bar") {
|
||||
t.Errorf("URLBuilder result: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, sub string) bool {
|
||||
return len(s) >= len(sub) && (s == sub || len(s) > 0 && containsHelper(s, sub))
|
||||
}
|
||||
|
||||
func containsHelper(s, sub string) bool {
|
||||
for i := 0; i <= len(s)-len(sub); i++ {
|
||||
if s[i:i+len(sub)] == sub {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ===== File Tests =====
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
if utils.FileExists("/nonexistent/path") {
|
||||
t.Error("FileExists should return false for nonexistent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirExists(t *testing.T) {
|
||||
if utils.DirExists("/nonexistent/dir") {
|
||||
t.Error("DirExists should return false")
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Benchmarks =====
|
||||
|
||||
func BenchmarkRandString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.RandString(16)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRandDigit(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.RandDigit(6)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMD5(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.MD5("hello world")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSHA256(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.SHA256("hello world")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUUID(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.UUID()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStrLen(b *testing.B) {
|
||||
s := "hello世界测试字符串"
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.StrLen(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCalcPageCount(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.CalcPageCount(10000, 20)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// UUID 生成 UUID v4 字符串
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 常用唯一标识生成,使用标准库 google/uuid
|
||||
func UUID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
// UUIDShort 生成短 UUID(无横线)
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 数据库主键、订单号等场景常用
|
||||
func UUIDShort() string {
|
||||
return uuid.New().String()[:32]
|
||||
}
|
||||
|
||||
// UUIDParse 解析 UUID 字符串
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: UUID 解析验证
|
||||
func UUIDParse(s string) (uuid.UUID, error) {
|
||||
return uuid.Parse(s)
|
||||
}
|
||||
|
||||
// UUIDValid 检查 UUID 字符串是否有效
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 快速验证
|
||||
func UUIDValid(s string) bool {
|
||||
_, err := uuid.Parse(s)
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// IsPhone 检查是否为有效的中国大陆手机号
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 常用验证,但正则需要随运营商号段更新
|
||||
// 注意: 正则基于当前号段,新号段开放时需更新
|
||||
func IsPhone(phone string) bool {
|
||||
// 1开头,第二位为3-9,共11位
|
||||
pattern := `^1[3-9]\d{9}$`
|
||||
matched, _ := regexp.MatchString(pattern, phone)
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsEmail 检查是否为有效的邮箱地址
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 常用验证,简单正则覆盖大部分场景
|
||||
func IsEmail(email string) bool {
|
||||
// 简单邮箱验证:xxx@xxx.xxx
|
||||
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
|
||||
matched, _ := regexp.MatchString(pattern, email)
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsIPv4 检查是否为有效的 IPv4 地址
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 网络编程常用验证
|
||||
func IsIPv4(ip string) bool {
|
||||
pattern := `^(\d{1,3}\.){3}\d{1,3}$`
|
||||
matched, _ := regexp.MatchString(pattern, ip)
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
// 验证每个段在 0-255 范围内
|
||||
parts := splitByDot(ip)
|
||||
for _, part := range parts {
|
||||
n := ToInt(part)
|
||||
if n < 0 || n > 255 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsIDCard 检查是否为有效的中国身份证号(18位)
|
||||
// 评分: ⭐⭐⭐
|
||||
// 理由: 身份证验证需求常见,但校验位算法复杂,此为简化版
|
||||
// 注意: 仅校验格式,不校验校验位
|
||||
func IsIDCard(id string) bool {
|
||||
// 18位身份证:6位地区码 + 8位生日 + 3位顺序码 + 1位校验码
|
||||
pattern := `^\d{6}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
|
||||
matched, _ := regexp.MatchString(pattern, id)
|
||||
return matched
|
||||
}
|
||||
|
||||
// IsChinese 检查字符串是否全部为中文字符
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 中文内容校验常用
|
||||
func IsChinese(s string) bool {
|
||||
for _, r := range s {
|
||||
if r < 0x4E00 || r > 0x9FFF {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(s) > 0
|
||||
}
|
||||
|
||||
// HasChinese 检查字符串是否包含中文字符
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 混合内容检测
|
||||
func HasChinese(s string) bool {
|
||||
for _, r := range s {
|
||||
if r >= 0x4E00 && r <= 0x9FFF {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNumeric 检查字符串是否全部为数字
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 简单高效的数字检测
|
||||
func IsNumeric(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsAlpha 检查字符串是否全部为字母
|
||||
// 评分: ⭐⭐⭐⭐
|
||||
// 理由: 字母检测
|
||||
func IsAlpha(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsAlphanumeric 检查字符串是否全部为字母或数字
|
||||
// 评分: ⭐⭐⭐⭐⭐
|
||||
// 理由: 用户名、密码等常用验证
|
||||
func IsAlphanumeric(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && (r < '0' || r > '9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 内部函数
|
||||
func splitByDot(s string) []string {
|
||||
var result []string
|
||||
start := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '.' {
|
||||
result = append(result, s[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
result = append(result, s[start:])
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user