first commit

This commit is contained in:
2025-01-10 00:09:25 +08:00
commit f54ead0f90
215 changed files with 16058 additions and 0 deletions

105
pkg/lzkit/crypto/crypto.go Normal file
View File

@@ -0,0 +1,105 @@
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"io"
)
// PKCS7填充
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// 去除PKCS7填充
func PKCS7UnPadding(origData []byte) ([]byte, error) {
length := len(origData)
if length == 0 {
return nil, errors.New("input data error")
}
unpadding := int(origData[length-1])
if unpadding > length {
return nil, errors.New("unpadding size is invalid")
}
// 检查填充字节是否一致
for i := 0; i < unpadding; i++ {
if origData[length-1-i] != byte(unpadding) {
return nil, errors.New("invalid padding")
}
}
return origData[:(length - unpadding)], nil
}
// AES CBC模式加密Base64传入传出
func AesEncrypt(plainText, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
blockSize := block.BlockSize()
plainText = PKCS7Padding(plainText, blockSize)
cipherText := make([]byte, blockSize+len(plainText))
iv := cipherText[:blockSize] // 使用前blockSize字节作为IV
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
return "", err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(cipherText[blockSize:], plainText)
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// AES CBC模式解密Base64传入传出
func AesDecrypt(cipherTextBase64 string, key []byte) ([]byte, error) {
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
if len(cipherText) < blockSize {
return nil, errors.New("ciphertext too short")
}
iv := cipherText[:blockSize]
cipherText = cipherText[blockSize:]
if len(cipherText)%blockSize != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(cipherText, cipherText)
plainText, err := PKCS7UnPadding(cipherText)
if err != nil {
return nil, err
}
return plainText, nil
}
// Md5Encrypt 用于对传入的message进行MD5加密
func Md5Encrypt(message string) string {
hash := md5.New()
hash.Write([]byte(message)) // 将字符串转换为字节切片并写入
return hex.EncodeToString(hash.Sum(nil)) // 将哈希值转换为16进制字符串并返回
}

View File

@@ -0,0 +1,63 @@
package crypto
import (
"crypto/rand"
"encoding/hex"
"io"
mathrand "math/rand"
"strconv"
"time"
)
// 生成AES-128密钥的函数符合市面规范
func GenerateSecretKey() (string, error) {
key := make([]byte, 16) // 16字节密钥
_, err := io.ReadFull(rand.Reader, key)
if err != nil {
return "", err
}
return hex.EncodeToString(key), nil
}
func GenerateSecretId() (string, error) {
// 创建一个字节数组,用于存储随机数据
bytes := make([]byte, 8) // 因为每个字节表示两个16进制字符
// 读取随机字节到数组中
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
// 将字节数组转换为16进制字符串
return hex.EncodeToString(bytes), nil
}
// GenerateTransactionID 生成16位数的交易单号
func GenerateTransactionID() string {
length := 16
// 获取当前时间戳
timestamp := time.Now().UnixNano()
// 转换为字符串
timeStr := strconv.FormatInt(timestamp, 10)
// 生成随机数
mathrand.Seed(time.Now().UnixNano())
randomPart := strconv.Itoa(mathrand.Intn(1000000))
// 组合时间戳和随机数
combined := timeStr + randomPart
// 如果长度超出指定值,则截断;如果不够,则填充随机字符
if len(combined) >= length {
return combined[:length]
}
// 如果长度不够填充0
for len(combined) < length {
combined += strconv.Itoa(mathrand.Intn(10)) // 填充随机数
}
return combined
}

View File

@@ -0,0 +1,150 @@
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
)
const (
KEY_SIZE = 16 // AES-128, 16 bytes
)
// Encrypt encrypts the given data using AES encryption in ECB mode with PKCS5 padding
func WestDexEncrypt(data, secretKey string) (string, error) {
key := generateAESKey(KEY_SIZE*8, []byte(secretKey))
ciphertext, err := aesEncrypt([]byte(data), key)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Decrypt decrypts the given base64-encoded string using AES encryption in ECB mode with PKCS5 padding
func WestDexDecrypt(encodedData, secretKey string) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encodedData)
if err != nil {
return nil, err
}
key := generateAESKey(KEY_SIZE*8, []byte(secretKey))
plaintext, err := aesDecrypt(ciphertext, key)
if err != nil {
return nil, err
}
return plaintext, nil
}
// generateAESKey generates a key for AES encryption using a SHA-1 based PRNG
func generateAESKey(length int, password []byte) []byte {
h := sha1.New()
h.Write(password)
state := h.Sum(nil)
keyBytes := make([]byte, 0, length/8)
for len(keyBytes) < length/8 {
h := sha1.New()
h.Write(state)
state = h.Sum(nil)
keyBytes = append(keyBytes, state...)
}
return keyBytes[:length/8]
}
// aesEncrypt encrypts plaintext using AES in ECB mode with PKCS5 padding
func aesEncrypt(plaintext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
paddedPlaintext := pkcs5Padding(plaintext, block.BlockSize())
ciphertext := make([]byte, len(paddedPlaintext))
mode := newECBEncrypter(block)
mode.CryptBlocks(ciphertext, paddedPlaintext)
return ciphertext, nil
}
// aesDecrypt decrypts ciphertext using AES in ECB mode with PKCS5 padding
func aesDecrypt(ciphertext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
plaintext := make([]byte, len(ciphertext))
mode := newECBDecrypter(block)
mode.CryptBlocks(plaintext, ciphertext)
return pkcs5Unpadding(plaintext), nil
}
// pkcs5Padding pads the input to a multiple of the block size using PKCS5 padding
func pkcs5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// pkcs5Unpadding removes PKCS5 padding from the input
func pkcs5Unpadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}
// ECB mode encryption/decryption
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
type ecbDecrypter ecb
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}

View File

@@ -0,0 +1,65 @@
package delay
import (
"errors"
"fmt"
"time"
)
// ProgressiveDelay 用于管理渐进式延迟策略
type ProgressiveDelay struct {
initialDelay time.Duration // 初始延迟时间
growthFactor float64 // 延迟增长因子 (例如 1.5)
maxDelay time.Duration // 最大延迟时间
maxRetryDuration time.Duration // 最大重试时间
currentDelay time.Duration // 当前延迟时间
startTime time.Time // 重试开始时间
}
// New 创建一个新的渐进式延迟对象
func New(initialDelay, maxDelay, maxRetryDuration time.Duration, growthFactor float64) (*ProgressiveDelay, error) {
// 参数校验
if initialDelay <= 0 {
return nil, errors.New("initialDelay must be greater than zero")
}
if maxDelay <= 0 {
return nil, errors.New("maxDelay must be greater than zero")
}
if maxRetryDuration <= 0 {
return nil, errors.New("maxRetryDuration must be greater than zero")
}
if growthFactor <= 1.0 {
return nil, errors.New("growthFactor must be greater than 1")
}
// 初始化并返回
return &ProgressiveDelay{
initialDelay: initialDelay,
maxDelay: maxDelay,
maxRetryDuration: maxRetryDuration,
growthFactor: growthFactor,
currentDelay: initialDelay,
startTime: time.Now(),
}, nil
}
// NextDelay 计算并返回下次的延迟时间
func (pd *ProgressiveDelay) NextDelay() (time.Duration, error) {
// 检查最大重试时间是否已过
if time.Since(pd.startTime) > pd.maxRetryDuration {
return 0, fmt.Errorf("最大重试时间超过限制: %v", pd.maxRetryDuration)
}
// 返回当前延迟时间
delay := pd.currentDelay
// 计算下一个延迟时间并更新 currentDelay
pd.currentDelay = time.Duration(float64(pd.currentDelay) * pd.growthFactor)
// 如果下次延迟超过最大延迟时间,限制在最大值
if pd.currentDelay > pd.maxDelay {
pd.currentDelay = pd.maxDelay
}
return delay, nil
}

View File

@@ -0,0 +1,38 @@
package lzUtils
import (
"database/sql"
"time"
)
// StringToNullString 将 string 转换为 sql.NullString
func StringToNullString(s string) sql.NullString {
return sql.NullString{
String: s,
Valid: s != "",
}
}
// NullStringToString 将 sql.NullString 转换为 string
func NullStringToString(ns sql.NullString) string {
if ns.Valid {
return ns.String
}
return ""
}
// TimeToNullTime 将 time.Time 转换为 sql.NullTime
func TimeToNullTime(t time.Time) sql.NullTime {
return sql.NullTime{
Time: t,
Valid: !t.IsZero(), // 仅当 t 不是零值时才设置为有效
}
}
// NullTimeToTime 将 sql.NullTime 转换为 time.Time
func NullTimeToTime(nt sql.NullTime) time.Time {
if nt.Valid {
return nt.Time
}
return time.Time{} // 返回零值时间
}

View File

@@ -0,0 +1,15 @@
package lzUtils
import "fmt"
// ToWechatAmount 将金额从元转换为微信支付 SDK 需要的分int64 类型)
func ToWechatAmount(amount float64) int64 {
// 将金额从元转换为分,并四舍五入
return int64(amount*100 + 0.5)
}
// ToAlipayAmount 将金额从元转换为支付宝支付 SDK 需要的字符串格式,保留两位小数
func ToAlipayAmount(amount float64) string {
// 格式化为字符串,保留两位小数
return fmt.Sprintf("%.2f", amount)
}

View File

@@ -0,0 +1,38 @@
package validator
// 定义自定义错误消息
var customMessages = map[string]string{
"Name.min": "姓名不能少于1个字",
"Name.required": "姓名是必填项",
"Name.name": "姓名只能包含中文",
"NameMan.min": "男方姓名不能少于1个字",
"NameMan.required": "男方姓名是必填项",
"NameMan.name": "男方姓名只能包含中文",
"NameWoman.min": "女方姓名不能少于1个字",
"NameWoman.required": "女方姓名是必填项",
"NameWoman.name": "女方姓名只能包含中文",
"Mobile.required": "手机号是必填项",
"Mobile.min": "电话号码必须为有效的中国电话号码",
"Mobile.max": "电话号码必须为有效的中国电话号码",
"Mobile.mobile": "电话号码必须为有效的中国电话号码",
"IDCard.required": "身份证号是必填项",
"IDCard.idCard": "无效的身份证号码",
"IDCardMan.required": "男方身份证号是必填项",
"IDCardMan.idCard": "无效的男方身份证号码",
"IDCardWoman.required": "女方身份证号是必填项",
"IDCardWoman.idCard": "无效的女方身份证号码",
"Password.min": "密码不能少于8位数",
"Password.max": "密码不能超过32位数",
"Password.password": "密码强度太弱",
//"EntCode.required":"请输入统一社会信用代码",
//"EntCode.USCI": "请输入正确的统一社会信用代码",
}
// 获取自定义错误消息
func GetErrorMessage(field, tag string) string {
key := field + "." + tag
if msg, exists := customMessages[key]; exists {
return msg
}
return "请输入正确格式的参数"
}

View File

@@ -0,0 +1,174 @@
package validator
import (
"errors"
"fmt"
"github.com/go-playground/validator/v10"
"regexp"
"strings"
)
var validate *validator.Validate
// 初始化自定义校验器
func init() {
validate = validator.New()
if err := validate.RegisterValidation("name", validName); err != nil {
panic(fmt.Sprintf("注册 name 验证器时发生错误: %v", err))
}
// 注册自定义验证器 validmobile
if err := validate.RegisterValidation("mobile", validmobile); err != nil {
panic(fmt.Sprintf("注册 mobile 验证器时发生错误: %v", err))
}
// 注册自定义验证器 validDate
if err := validate.RegisterValidation("date", validDate); err != nil {
panic(fmt.Sprintf("注册 date 验证器时发生错误: %v", err))
}
// 注册自定义验证器 validIDCard
if err := validate.RegisterValidation("idCard", validIDCard); err != nil {
panic(fmt.Sprintf("注册 idCard 验证器时发生错误: %v", err))
}
if err := validate.RegisterValidation("bankCard", validBankCard); err != nil {
panic(fmt.Sprintf("注册 bankCard 验证器时发生错误: %v", err))
}
if err := validate.RegisterValidation("USCI", validUSCI); err != nil {
panic(fmt.Sprintf("注册 USCI 社会统一信用代码 验证器时发生错误: %v", err))
}
if err := validate.RegisterValidation("mobileType", validMobileType); err != nil {
panic(fmt.Sprintf("注册 mobileType 验证器时发生错误: %v", err))
}
if err := validate.RegisterValidation("password", validatePassword); err != nil {
panic(fmt.Sprintf("注册 password 验证器时发生错误: %v", err))
}
if err := validate.RegisterValidation("payMethod", validatePayMethod); err != nil {
panic(fmt.Sprintf("注册 payMethod 验证器时发生错误: %v", err))
}
}
// 弱口令列表
var weakPasswords = []string{
"12345678", "password", "123456789", "qwerty", "123456", "letmein",
"1234567", "welcome", "abc123", "password1", "1234", "111111", "admin",
}
// Validate 校验参数逻辑
func Validate(req interface{}) error {
if err := validate.Struct(req); err != nil {
// 检查 err 是否是 ValidationErrors 类型
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, validationErr := range validationErrors {
field := validationErr.StructField()
tag := validationErr.Tag()
return errors.New(GetErrorMessage(field, tag))
}
} else {
// 其他错误处理
return fmt.Errorf("验证时出现未知错误: %v", err)
}
}
return nil
}
// 自定义的名称验证
func validName(fl validator.FieldLevel) bool {
name := fl.Field().String()
validNamePattern := `^[\p{Han}]+$`
matched, _ := regexp.MatchString(validNamePattern, name)
return matched
}
// 自定义的手机号验证
func validmobile(fl validator.FieldLevel) bool {
phone := fl.Field().String()
validmobilePattern := `^1[3-9]\d{9}$`
matched, _ := regexp.MatchString(validmobilePattern, phone)
return matched
}
// 自定义正则表达式校验 yyyyMMdd 格式
func validDate(fl validator.FieldLevel) bool {
date := fl.Field().String()
validDatePattern := `^\d{4}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])$`
matched, _ := regexp.MatchString(validDatePattern, date)
return matched
}
// 自定义身份证校验
func validIDCard(fl validator.FieldLevel) bool {
id := fl.Field().String()
validIDPattern := `^\d{17}(\d|X|x)$` // 匹配18位身份证号码
matched, _ := regexp.MatchString(validIDPattern, id)
return matched
}
func validBankCard(fl validator.FieldLevel) bool {
bankCard := fl.Field().String()
// 银行卡号一般是13到19位的数字
validBankCardPattern := `^\d{13,19}$`
matched, _ := regexp.MatchString(validBankCardPattern, bankCard)
return matched
}
func validUSCI(fl validator.FieldLevel) bool {
usci := fl.Field().String()
// 社会信用代码为18位数字和大写字母的组合最后一位为校验码
validUSCIPattern := `^[1-9A-Z]{2}[0-9]{6}[0-9A-Z]{9}[0-9A-Z]$`
matched, _ := regexp.MatchString(validUSCIPattern, usci)
return matched
}
// 自定义的手机号类型验证(可以为空)
func validMobileType(fl validator.FieldLevel) bool {
mobileType := fl.Field().String()
if mobileType == "" {
return true // 如果为空,认为是有效的
}
// 校验是否是 CTCC, CMCC, CUCC 之一
validTypes := map[string]bool{
"CTCC": true, // 中国电信
"CMCC": true, // 中国移动
"CUCC": true, // 中国联通
}
return validTypes[mobileType]
}
// 自定义密码强度校验函数
func validatePassword(fl validator.FieldLevel) bool {
password := fl.Field().String()
// 检查密码是否在弱口令列表中
for _, weakPwd := range weakPasswords {
if strings.ToLower(password) == weakPwd {
return false
}
}
return true
}
// 支付方式
func validatePayMethod(fl validator.FieldLevel) bool {
payMethod := fl.Field().String()
if payMethod == "" {
return true // 如果为空,认为是有效的
}
validTypes := map[string]bool{
"alipay": true, // 中国电信
"wechatpay": true, // 中国移动
}
return validTypes[payMethod]
}