chore: move '/internal/pkg' to '/pkg'

This commit is contained in:
Fu Diwei
2025-06-17 15:54:21 +08:00
parent 30840bbba5
commit 205275b52d
611 changed files with 693 additions and 693 deletions

26
pkg/utils/cert/common.go Normal file
View File

@@ -0,0 +1,26 @@
package cert
import (
"crypto/x509"
)
// 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。
// 注意,这不是精确比较,而只是基于证书序列号和数字签名的快速判断,但对于权威 CA 签发的证书来说不会存在误判。
//
// 入参:
// - a: 待比较的第一个 x509.Certificate 对象。
// - b: 待比较的第二个 x509.Certificate 对象。
//
// 出参:
// - 是否相同。
func EqualCertificate(a, b *x509.Certificate) bool {
if a == nil || b == nil {
return false
}
return string(a.Signature) == string(b.Signature) &&
a.SignatureAlgorithm == b.SignatureAlgorithm &&
a.SerialNumber.String() == b.SerialNumber.String() &&
a.Issuer.SerialNumber == b.Issuer.SerialNumber &&
a.Subject.SerialNumber == b.Subject.SerialNumber
}

View File

@@ -0,0 +1,56 @@
package cert
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
)
// 将 x509.Certificate 对象转换为 PEM 编码的字符串。
//
// 入参:
// - cert: x509.Certificate 对象。
//
// 出参:
// - certPEM: 证书 PEM 内容。
// - err: 错误。
func ConvertCertificateToPEM(cert *x509.Certificate) (_certPEM string, _err error) {
if cert == nil {
return "", errors.New("`cert` is nil")
}
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
return string(pem.EncodeToMemory(block)), nil
}
// 将 ecdsa.PrivateKey 对象转换为 PEM 编码的字符串。
//
// 入参:
// - privkey: ecdsa.PrivateKey 对象。
//
// 出参:
// - privkeyPEM: 私钥 PEM 内容。
// - err: 错误。
func ConvertECPrivateKeyToPEM(privkey *ecdsa.PrivateKey) (_privkeyPEM string, _err error) {
if privkey == nil {
return "", errors.New("`privkey` is nil")
}
data, _err := x509.MarshalECPrivateKey(privkey)
if _err != nil {
return "", fmt.Errorf("failed to marshal EC private key: %w", _err)
}
block := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: data,
}
return string(pem.EncodeToMemory(block)), nil
}

View File

@@ -0,0 +1,48 @@
package cert
import (
"encoding/pem"
"errors"
)
// 从 PEM 编码的证书字符串解析并提取服务器证书和中间证书。
//
// 入参:
// - certPEM: 证书 PEM 内容。
//
// 出参:
// - serverCertPEM: 服务器证书的 PEM 内容。
// - intermediaCertPEM: 中间证书的 PEM 内容。
// - err: 错误。
func ExtractCertificatesFromPEM(certPEM string) (_serverCertPEM string, _intermediaCertPEM string, _err error) {
pemBlocks := make([]*pem.Block, 0)
pemData := []byte(certPEM)
for {
block, rest := pem.Decode(pemData)
if block == nil || block.Type != "CERTIFICATE" {
break
}
pemBlocks = append(pemBlocks, block)
pemData = rest
}
serverCertPEM := ""
intermediaCertPEM := ""
if len(pemBlocks) == 0 {
return "", "", errors.New("failed to decode PEM block")
}
if len(pemBlocks) > 0 {
serverCertPEM = string(pem.EncodeToMemory(pemBlocks[0]))
}
if len(pemBlocks) > 1 {
for i := 1; i < len(pemBlocks); i++ {
intermediaCertPEM += string(pem.EncodeToMemory(pemBlocks[i]))
}
}
return serverCertPEM, intermediaCertPEM, nil
}

99
pkg/utils/cert/parser.go Normal file
View File

@@ -0,0 +1,99 @@
package cert
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/go-acme/lego/v4/certcrypto"
)
// 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。
// PEM 内容可能是包含多张证书的证书链,但只返回第一个证书(即服务器证书)。
//
// 入参:
// - certPEM: 证书 PEM 内容。
//
// 出参:
// - cert: x509.Certificate 对象。
// - err: 错误。
func ParseCertificateFromPEM(certPEM string) (_cert *x509.Certificate, _err error) {
pemData := []byte(certPEM)
block, _ := pem.Decode(pemData)
if block == nil {
return nil, errors.New("failed to decode PEM block")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}
return cert, nil
}
// 从 PEM 编码的私钥字符串解析并返回一个 crypto.PrivateKey 对象。
//
// 入参:
// - privkeyPEM: 私钥 PEM 内容。
//
// 出参:
// - privkey: crypto.PrivateKey 对象,可能是 rsa.PrivateKey、ecdsa.PrivateKey 或 ed25519.PrivateKey。
// - err: 错误。
func ParsePrivateKeyFromPEM(privkeyPEM string) (_privkey crypto.PrivateKey, _err error) {
pemData := []byte(privkeyPEM)
return certcrypto.ParsePEMPrivateKey(pemData)
}
// 从 PEM 编码的私钥字符串解析并返回一个 ecdsa.PrivateKey 对象。
//
// 入参:
// - privkeyPEM: 私钥 PEM 内容。
//
// 出参:
// - privkey: ecdsa.PrivateKey 对象。
// - err: 错误。
func ParseECPrivateKeyFromPEM(privkeyPEM string) (_privkey *ecdsa.PrivateKey, _err error) {
pemData := []byte(privkeyPEM)
block, _ := pem.Decode(pemData)
if block == nil {
return nil, errors.New("failed to decode PEM block")
}
privkey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
return privkey, nil
}
// 从 PEM 编码的私钥字符串解析并返回一个 rsa.PrivateKey 对象。
//
// 入参:
// - privkeyPEM: 私钥 PEM 内容。
//
// 出参:
// - privkey: rsa.PrivateKey 对象。
// - err: 错误。
func ParsePKCS1PrivateKeyFromPEM(privkeyPEM string) (_privkey *rsa.PrivateKey, _err error) {
pemData := []byte(privkeyPEM)
block, _ := pem.Decode(pemData)
if block == nil {
return nil, errors.New("failed to decode PEM block")
}
privkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
return privkey, nil
}

View File

@@ -0,0 +1,87 @@
package cert
import (
"bytes"
"encoding/pem"
"errors"
"time"
"github.com/pavlo-v-chernykh/keystore-go/v4"
"software.sslmate.com/src/go-pkcs12"
)
// 将 PEM 编码的证书字符串转换为 PFX 格式。
//
// 入参:
// - certPEM: 证书 PEM 内容。
// - privkeyPEM: 私钥 PEM 内容。
// - pfxPassword: PFX 导出密码。
//
// 出参:
// - data: PFX 格式的证书数据。
// - err: 错误。
func TransformCertificateFromPEMToPFX(certPEM string, privkeyPEM string, pfxPassword string) ([]byte, error) {
cert, err := ParseCertificateFromPEM(certPEM)
if err != nil {
return nil, err
}
privkey, err := ParsePrivateKeyFromPEM(privkeyPEM)
if err != nil {
return nil, err
}
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, pfxPassword)
if err != nil {
return nil, err
}
return pfxData, nil
}
// 将 PEM 编码的证书字符串转换为 JKS 格式。
//
// 入参:
// - certPEM: 证书 PEM 内容。
// - privkeyPEM: 私钥 PEM 内容。
// - jksAlias: JKS 别名。
// - jksKeypass: JKS 密钥密码。
// - jksStorepass: JKS 存储密码。
//
// 出参:
// - data: JKS 格式的证书数据。
// - err: 错误。
func TransformCertificateFromPEMToJKS(certPEM string, privkeyPEM string, jksAlias string, jksKeypass string, jksStorepass string) ([]byte, error) {
certBlock, _ := pem.Decode([]byte(certPEM))
if certBlock == nil {
return nil, errors.New("failed to decode certificate PEM")
}
privkeyBlock, _ := pem.Decode([]byte(privkeyPEM))
if privkeyBlock == nil {
return nil, errors.New("failed to decode private key PEM")
}
ks := keystore.New()
entry := keystore.PrivateKeyEntry{
CreationTime: time.Now(),
PrivateKey: privkeyBlock.Bytes,
CertificateChain: []keystore.Certificate{
{
Type: "X509",
Content: certBlock.Bytes,
},
},
}
if err := ks.SetPrivateKeyEntry(jksAlias, entry, []byte(jksKeypass)); err != nil {
return nil, err
}
var buf bytes.Buffer
if err := ks.Store(&buf, []byte(jksStorepass)); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

51
pkg/utils/file/io.go Normal file
View File

@@ -0,0 +1,51 @@
package file
import (
"fmt"
"os"
"path/filepath"
)
// 与 [Write] 类似,但写入的是字符串内容。
//
// 入参:
// - path: 文件路径。
// - content: 文件内容。
//
// 出参:
// - 错误。
func WriteString(path string, content string) error {
return Write(path, []byte(content))
}
// 将数据写入指定路径的文件。
// 如果目录不存在,将会递归创建目录。
// 如果文件不存在,将会创建该文件;如果文件已存在,将会覆盖原有内容。
//
// 入参:
// - path: 文件路径。
// - data: 文件数据字节数组。
//
// 出参:
// - 错误。
func Write(path string, data []byte) error {
dir := filepath.Dir(path)
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
return nil
}

33
pkg/utils/http/parser.go Normal file
View File

@@ -0,0 +1,33 @@
package http
import (
"bufio"
"net/http"
"net/textproto"
"strings"
)
// 从表示 HTTP 标头的字符串解析并返回一个 http.Header 对象。
//
// 入参:
// - headers: 表示 HTTP 标头的字符串。
//
// 出参:
// - header: http.Header 对象。
// - err: 错误。
func ParseHeaders(headers string) (http.Header, error) {
str := strings.TrimSpace(headers) + "\r\n\r\n"
if len(str) == 4 {
return make(http.Header), nil
}
br := bufio.NewReader(strings.NewReader(str))
tp := textproto.NewReader(br)
mimeHeader, err := tp.ReadMIMEHeader()
if err != nil {
return nil, err
}
return http.Header(mimeHeader), err
}

View File

@@ -0,0 +1,33 @@
package http
import (
"net"
"net/http"
"time"
)
// 创建并返回一个 [http.DefaultTransport] 对象副本。
//
// 出参:
// - transport: [http.DefaultTransport] 对象副本。
func NewDefaultTransport() *http.Transport {
if http.DefaultTransport != nil {
if t, ok := http.DefaultTransport.(*http.Transport); ok {
return t.Clone()
}
}
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}

256
pkg/utils/maps/get.go Normal file
View File

@@ -0,0 +1,256 @@
package maps
import (
"strconv"
)
// 以字符串形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在或者值的类型不是字符串,则返回空字符串。
func GetString(dict map[string]any, key string) string {
return GetOrDefaultString(dict, key, "")
}
// 以字符串形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
// - defaultValue: 默认值。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在、值的类型不是字符串或者值为零值,则返回默认值。
func GetOrDefaultString(dict map[string]any, key string, defaultValue string) string {
if dict == nil {
return defaultValue
}
if value, ok := dict[key]; ok {
if result, ok := value.(string); ok {
if result != "" {
return result
}
}
}
return defaultValue
}
// 以 32 位整数形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 32 位整数,则返回 0。
func GetInt32(dict map[string]any, key string) int32 {
return GetOrDefaultInt32(dict, key, 0)
}
// 以 32 位整数形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
// - defaultValue: 默认值。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在、值的类型不是 32 位整数或者值为零值,则返回默认值。
func GetOrDefaultInt32(dict map[string]any, key string, defaultValue int32) int32 {
if dict == nil {
return defaultValue
}
if value, ok := dict[key]; ok {
var result int32
switch v := value.(type) {
case int:
result = int32(v)
case int8:
result = int32(v)
case int16:
result = int32(v)
case int32:
result = v
case int64:
result = int32(v)
case uint:
result = int32(v)
case uint8:
result = int32(v)
case uint16:
result = int32(v)
case uint32:
result = int32(v)
case uint64:
result = int32(v)
case float32:
result = int32(v)
case float64:
result = int32(v)
case string:
// 兼容字符串类型的值
if t, err := strconv.ParseInt(v, 10, 32); err == nil {
result = int32(t)
}
}
if result != 0 {
return result
}
}
return defaultValue
}
// 以 64 位整数形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在或者值的类型不是 64 位整数,则返回 0。
func GetInt64(dict map[string]any, key string) int64 {
return GetOrDefaultInt64(dict, key, 0)
}
// 以 64 位整数形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
// - defaultValue: 默认值。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在、值的类型不是 64 位整数或者值为零值,则返回默认值。
func GetOrDefaultInt64(dict map[string]any, key string, defaultValue int64) int64 {
if dict == nil {
return defaultValue
}
if value, ok := dict[key]; ok {
var result int64
switch v := value.(type) {
case int:
result = int64(v)
case int8:
result = int64(v)
case int16:
result = int64(v)
case int32:
result = int64(v)
case int64:
result = v
case uint:
result = int64(v)
case uint8:
result = int64(v)
case uint16:
result = int64(v)
case uint32:
result = int64(v)
case uint64:
result = int64(v)
case float32:
result = int64(v)
case float64:
result = int64(v)
case string:
// 兼容字符串类型的值
if t, err := strconv.ParseInt(v, 10, 64); err == nil {
result = t
}
}
if result != 0 {
return result
}
}
return defaultValue
}
// 以布尔形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回 false。
func GetBool(dict map[string]any, key string) bool {
return GetOrDefaultBool(dict, key, false)
}
// 以布尔形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
// - defaultValue: 默认值。
//
// 出参:
// - 字典中键对应的值。如果指定键不存在或者值的类型不是布尔,则返回默认值。
func GetOrDefaultBool(dict map[string]any, key string, defaultValue bool) bool {
if dict == nil {
return defaultValue
}
if value, ok := dict[key]; ok {
if result, ok := value.(bool); ok {
return result
}
// 兼容字符串类型的值
if str, ok := value.(string); ok {
if result, err := strconv.ParseBool(str); err == nil {
return result
}
}
}
return defaultValue
}
// 以 `map[string]V` 形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
//
// 出参:
// - 字典中键对应的 `map[string]V` 对象。
func GetKVMap[V any](dict map[string]any, key string) map[string]V {
if dict == nil {
return make(map[string]V)
}
if val, ok := dict[key]; ok {
if result, ok := val.(map[string]V); ok {
return result
}
}
return make(map[string]V)
}
// 以 `map[string]any` 形式从字典中获取指定键的值。
//
// 入参:
// - dict: 字典。
// - key: 键。
//
// 出参:
// - 字典中键对应的 `map[string]any` 对象。
func GetKVMapAny(dict map[string]any, key string) map[string]any {
return GetKVMap[any](dict, key)
}

30
pkg/utils/maps/marshal.go Normal file
View File

@@ -0,0 +1,30 @@
package maps
import (
mapstructure "github.com/go-viper/mapstructure/v2"
)
// 将字典填充到指定类型的结构体。
// 与 [json.Unmarshal] 类似,但传入的是一个 [map[string]any] 对象而非 JSON 格式的字符串。
//
// 入参:
// - dict: 字典。
// - output: 结构体指针。
//
// 出参:
// - 错误信息。如果填充失败,则返回错误信息。
func Populate(dict map[string]any, output any) error {
config := &mapstructure.DecoderConfig{
Metadata: nil,
Result: output,
WeaklyTypedInput: true,
TagName: "json",
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(dict)
}

69
pkg/utils/slices/iter.go Normal file
View File

@@ -0,0 +1,69 @@
package slices
// 创建给定切片一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。
//
// 入参:
// - slice: 原切片。
// - filter: 为切片中的每个元素执行的函数。它应该返回一个布尔值以指使是否将元素保留在结果切片中。
//
// 出参:
// - 新切片。
func Filter[T any](slice []T, filter func(T) bool) []T {
var result []T
for _, item := range slice {
if filter(item) {
result = append(result, item)
}
}
return result
}
// 创建一个新切片,这个新切片由原切片中的每个元素都调用一次提供的函数后的返回值组成。
//
// 入参:
// - slice: 原切片。
// - mapper: 为切片中的每个元素执行的函数。它的返回值作为一个元素被添加为新切片中。
//
// 出参:
// - 新切片。
func Map[T1 any, T2 any](slice []T1, mapper func(T1) T2) []T2 {
result := make([]T2, 0, len(slice))
for _, item := range slice {
result = append(result, mapper(item))
}
return result
}
// 测试切片中是否每个元素都通过了由提供的函数实现的测试。
//
// 入参:
// - slice: 切片。
// - condition: 为切片中的每个元素执行的函数。它应该返回一个布尔值以指示元素是否通过测试。
//
// 出参:
// - 测试结果。
func Every[T any](slice []T, condition func(T) bool) bool {
for _, item := range slice {
if !condition(item) {
return false
}
}
return true
}
// 测试切片中是否至少有一个元素通过了由提供的函数实现的测试。
//
// 入参:
// - slice: 切片。
// - condition: 为切片中的每个元素执行的函数。它应该返回一个布尔值以指示元素是否通过测试。
//
// 出参:
// - 测试结果。
func Some[T any](slice []T, condition func(T) bool) bool {
for _, item := range slice {
if condition(item) {
return true
}
}
return false
}

28
pkg/utils/types/assert.go Normal file
View File

@@ -0,0 +1,28 @@
package types
import (
"reflect"
)
// 判断对象是否为 nil。
// 与直接使用 `obj == nil` 不同,该函数会正确判断接口类型对象的真实值是否为空。
//
// 入参:
// - value待判断的对象。
//
// 出参:
// - 如果对象值为 nil则返回 true否则返回 false。
func IsNil(obj any) bool {
if obj == nil {
return true
}
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
return v.IsNil()
} else if v.Kind() == reflect.Interface {
return v.Elem().IsNil()
}
return false
}

47
pkg/utils/types/cast.go Normal file
View File

@@ -0,0 +1,47 @@
package types
import (
"reflect"
)
// 将对象转换为指针。
//
// 入参:
// - 待转换的对象。
//
// 出参:
// - 返回对象的指针。
func ToPtr[T any](v T) (p *T) {
return &v
}
// 将非零值的对象转换为指针。
// 与 [ToPtr] 不同的是,如果对象的值为零值,则返回 nil。
//
// 入参:
// - 待转换的对象。
//
// 出参:
// - 返回对象的指针。
func ToPtrOrZeroNil[T any](v T) (p *T) {
if reflect.ValueOf(v).IsZero() {
return nil
}
return &v
}
// 将指针转换为对象。
//
// 入参:
// - 待转换的指针。
//
// 出参:
// - 返回指针指向的对象。如果指针为空,则返回对象的零值。
func ToVal[T any](p *T) (v T) {
if IsNil(p) {
return v
}
return *p
}