qnc init
This commit is contained in:
89
utils/antifraud.go
Normal file
89
utils/antifraud.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"math"
|
||||
)
|
||||
|
||||
// 对明文进行填充
|
||||
func Padding(plainText []byte, blockSize int) []byte {
|
||||
//计算要填充的长度
|
||||
n := blockSize - len(plainText)%blockSize
|
||||
//对原来的明文填充n个n
|
||||
temp := bytes.Repeat([]byte{byte(n)}, n)
|
||||
plainText = append(plainText, temp...)
|
||||
return plainText
|
||||
}
|
||||
|
||||
// 对密文删除填充
|
||||
func UnPadding(cipherText []byte) []byte {
|
||||
//取出密文最后一个字节end
|
||||
end := cipherText[len(cipherText)-1]
|
||||
//删除填充
|
||||
cipherText = cipherText[:len(cipherText)-int(end)]
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// AEC加密(CBC模式)
|
||||
func AES_CBC_Encrypt(plainText []byte, key []byte) []byte {
|
||||
//指定加密算法,返回一个AES算法的Block接口对象
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//进行填充
|
||||
plainText = Padding(plainText, block.BlockSize())
|
||||
//指定初始向量vi,长度和block的块尺寸一致
|
||||
iv := []byte("0000000000000000")
|
||||
//指定分组模式,返回一个BlockMode接口对象
|
||||
blockMode := cipher.NewCBCEncrypter(block, iv)
|
||||
//加密连续数据库
|
||||
cipherText := make([]byte, len(plainText))
|
||||
blockMode.CryptBlocks(cipherText, plainText)
|
||||
//返回base64密文
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// AEC解密(CBC模式)
|
||||
func AES_CBC_Decrypt(cipherText []byte, key []byte) []byte {
|
||||
//指定解密算法,返回一个AES算法的Block接口对象
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//指定初始化向量IV,和加密的一致
|
||||
iv := []byte("0000000000000000")
|
||||
//指定分组模式,返回一个BlockMode接口对象
|
||||
blockMode := cipher.NewCBCDecrypter(block, iv)
|
||||
//解密
|
||||
plainText := make([]byte, len(cipherText))
|
||||
blockMode.CryptBlocks(plainText, cipherText)
|
||||
//删除填充
|
||||
plainText = UnPadding(plainText)
|
||||
return plainText
|
||||
}
|
||||
|
||||
// GenerateRandomString 生成一个32位的随机字符串订单号
|
||||
func GenerateRandomString() (string, error) {
|
||||
// 创建一个16字节的数组
|
||||
bytes := make([]byte, 16)
|
||||
// 读取随机字节到数组中
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 将字节数组编码为16进制字符串
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
// 定义一个函数,将分数从原始范围转换到 0-100 的范围
|
||||
func ConvertScore(originalScore, oldMin, oldMax float64) int {
|
||||
newMin := 0.0
|
||||
newMax := 100.0
|
||||
Score := ((originalScore - oldMin) * (newMax - newMin) / (oldMax - oldMin)) + newMin
|
||||
newScore := int(math.Round(100 - Score))
|
||||
return newScore
|
||||
}
|
||||
53
utils/car.go
Normal file
53
utils/car.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// 解密函数
|
||||
func Decrypt(ciphertext string, key []byte) (string, error) {
|
||||
// Base64 解码
|
||||
cipherTextDecoded, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建 AES 密码块
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 检查密文长度是否为块大小的倍数
|
||||
if len(cipherTextDecoded)%aes.BlockSize != 0 {
|
||||
return "", fmt.Errorf("密文长度不是块大小的倍数")
|
||||
}
|
||||
|
||||
// 解密密文
|
||||
decrypted := make([]byte, len(cipherTextDecoded))
|
||||
for bs := 0; bs < len(cipherTextDecoded); bs += aes.BlockSize {
|
||||
block.Decrypt(decrypted[bs:], cipherTextDecoded[bs:bs+aes.BlockSize])
|
||||
}
|
||||
|
||||
// 移除填充
|
||||
decrypted = pkcs7Unpad(decrypted)
|
||||
|
||||
// 移除不需要的字符
|
||||
re := regexp.MustCompile("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f\n\r\t]")
|
||||
cleanedString := re.ReplaceAllString(string(decrypted), "")
|
||||
|
||||
return cleanedString, nil
|
||||
}
|
||||
|
||||
// 移除PKCS7填充
|
||||
func pkcs7Unpad(src []byte) []byte {
|
||||
length := len(src)
|
||||
unpadding := int(src[length-1])
|
||||
if unpadding > length {
|
||||
return src
|
||||
}
|
||||
return src[:(length - unpadding)]
|
||||
}
|
||||
172
utils/common.go
Normal file
172
utils/common.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
uuid2 "github.com/google/uuid"
|
||||
"image/png"
|
||||
"net/url"
|
||||
"os"
|
||||
"qnc-server/db"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func IsJSON(s string) bool {
|
||||
var js interface{}
|
||||
return json.Unmarshal([]byte(s), &js) == nil
|
||||
}
|
||||
|
||||
// 校验统一社会信用代码的函数
|
||||
func ValidateUnifiedSocialCreditCode(code string) bool {
|
||||
if len(code) != 18 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 定义字符与对应的数值映射
|
||||
charValues := map[rune]int{
|
||||
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
|
||||
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17, 'J': 18, 'K': 19,
|
||||
'L': 20, 'M': 21, 'N': 22, 'P': 23, 'Q': 24, 'R': 25, 'T': 26, 'U': 27, 'W': 28, 'X': 29, 'Y': 30,
|
||||
}
|
||||
|
||||
// 定义权重因子
|
||||
weights := []int{1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}
|
||||
|
||||
// 计算前 17 位与权重因子的乘积之和
|
||||
sum := 0
|
||||
for i, char := range code[:17] {
|
||||
if value, ok := charValues[char]; ok {
|
||||
sum += value * weights[i]
|
||||
} else {
|
||||
return false // 包含非法字符
|
||||
}
|
||||
}
|
||||
|
||||
// 计算校验码
|
||||
mod := sum % 31
|
||||
checkChar := '0'
|
||||
for k, v := range charValues {
|
||||
if v == (31-mod)%31 {
|
||||
checkChar = k
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return unicode.ToUpper(rune(code[17])) == checkChar
|
||||
}
|
||||
|
||||
func EncryptName(name string) string {
|
||||
// 获取名字的字符长度
|
||||
nameLength := utf8.RuneCountInString(name)
|
||||
|
||||
// 如果名字只有一个字,返回原始名字
|
||||
if nameLength <= 1 {
|
||||
return name
|
||||
}
|
||||
|
||||
// 将名字转换为rune切片
|
||||
runes := []rune(name)
|
||||
|
||||
// 如果名字是两个字,保留第一个字,第二个字加密
|
||||
if nameLength == 2 {
|
||||
return string(runes[0]) + "*"
|
||||
}
|
||||
|
||||
// 如果名字有三个或更多字,保留第一个和最后一个字,中间的加密
|
||||
firstChar := string(runes[0])
|
||||
lastChar := string(runes[nameLength-1])
|
||||
// 中间的字用*代替
|
||||
middleChars := strings.Repeat("*", nameLength-2)
|
||||
|
||||
return firstChar + middleChars + lastChar
|
||||
}
|
||||
|
||||
func EncryptIDCard(idCard string) string {
|
||||
if len(idCard) < 8 {
|
||||
return idCard // 如果身份证号码长度不足8位,则不进行加密
|
||||
}
|
||||
midStart := (len(idCard) - 8) / 2
|
||||
midEnd := midStart + 8
|
||||
return idCard[:midStart] + "********" + idCard[midEnd:]
|
||||
}
|
||||
|
||||
// 解析时间字符串为 time.Time
|
||||
func ParseTime(t string) (time.Time, error) {
|
||||
return time.Parse("15:04", t)
|
||||
}
|
||||
|
||||
// 判断当前时间是否在指定时间范围内
|
||||
func IsInTimeRange(start, end time.Time) bool {
|
||||
now := time.Now()
|
||||
start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
|
||||
end = time.Date(now.Year(), now.Month(), now.Day(), end.Hour(), end.Minute(), 0, 0, now.Location())
|
||||
|
||||
if end.Before(start) {
|
||||
end = end.Add(24 * time.Hour) // 处理跨午夜的情况
|
||||
}
|
||||
|
||||
return now.After(start) && now.Before(end)
|
||||
}
|
||||
|
||||
// decodeBase64ToPNG 将 Base64 字符串解码为 PNG 图片并保存为临时文件
|
||||
func DecodeBase64ToPNG(encodedBase64Str string, fileName string) (string, error) {
|
||||
// 去掉 Base64 前缀,如 "data:image/png;base64,"
|
||||
encodedBase64Str = strings.TrimPrefix(encodedBase64Str, "data:image/png;base64,")
|
||||
|
||||
// 进行 URL 解码,恢复 encodeURIComponent 编码的数据
|
||||
base64Str, err := url.QueryUnescape(encodedBase64Str)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 解码 Base64 数据
|
||||
data, err := base64.StdEncoding.DecodeString(base64Str)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建 PNG 文件
|
||||
file, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 将解码后的字节流写入图片文件
|
||||
img, err := png.Decode(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 将 PNG 编码写入文件
|
||||
err = png.Encode(file, img)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fileName, nil
|
||||
}
|
||||
|
||||
func SetCacheData(ctx context.Context, data interface{}) (uuid string) {
|
||||
jsonData, _ := json.Marshal(data)
|
||||
// 生成一个唯一的UUID
|
||||
uniqueID := uuid2.New().String()
|
||||
db.RedisClient.Set(ctx, uniqueID, jsonData, 5*time.Minute)
|
||||
return uniqueID
|
||||
}
|
||||
func GetCacheData(ctx context.Context, uniqueID string) (data interface{}, err error) {
|
||||
|
||||
val, err := db.RedisClient.Get(ctx, uniqueID).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(val), &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
78
utils/encrypt.go
Normal file
78
utils/encrypt.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// 加密函数
|
||||
func EncryptCode(plainText string, key string) (string, error) {
|
||||
// 创建 AES 块加密器
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 将原始文本转为字节数组
|
||||
plainTextBytes := []byte(plainText)
|
||||
|
||||
// 创建加密用的 GCM (Galois/Counter Mode)
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建随机的 nonce,长度必须为 GCM 的 nonceSize
|
||||
nonce := make([]byte, aesGCM.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 加密数据并附带 nonce
|
||||
cipherText := aesGCM.Seal(nonce, nonce, plainTextBytes, nil)
|
||||
|
||||
// 将加密后的内容用 base64 编码
|
||||
return base64.StdEncoding.EncodeToString(cipherText), nil
|
||||
}
|
||||
|
||||
// 解密函数
|
||||
func DecryptCode(cipherText string, key string) (string, error) {
|
||||
// 解码 base64 编码的密文
|
||||
cipherTextBytes, err := base64.StdEncoding.DecodeString(cipherText)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建 AES 块加密器
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建加密用的 GCM
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 分离 nonce 和加密数据
|
||||
nonceSize := aesGCM.NonceSize()
|
||||
if len(cipherTextBytes) < nonceSize {
|
||||
return "", fmt.Errorf("cipherText too short")
|
||||
}
|
||||
|
||||
nonce, cipherTextBytes := cipherTextBytes[:nonceSize], cipherTextBytes[nonceSize:]
|
||||
|
||||
// 解密数据
|
||||
plainTextBytes, err := aesGCM.Open(nil, nonce, cipherTextBytes, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 返回解密后的明文
|
||||
return string(plainTextBytes), nil
|
||||
}
|
||||
10
utils/errors.go
Normal file
10
utils/errors.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package utils
|
||||
|
||||
import "errors"
|
||||
|
||||
// 定义常见的错误
|
||||
var (
|
||||
ErrNoRecord = errors.New("no record found")
|
||||
ErrSystemError = errors.New("system error")
|
||||
ErrUnmashal = errors.New("unmashal error")
|
||||
)
|
||||
164
utils/jwt.go
Normal file
164
utils/jwt.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/sync/singleflight"
|
||||
"log"
|
||||
"net"
|
||||
"qnc-server/config"
|
||||
model "qnc-server/model/common"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JWT struct {
|
||||
SigningKey []byte
|
||||
}
|
||||
|
||||
var (
|
||||
TokenExpired = errors.New("Token is expired")
|
||||
TokenNotValidYet = errors.New("Token not active yet")
|
||||
TokenMalformed = errors.New("That's not even a token")
|
||||
TokenInvalid = errors.New("Couldn't handle this token:")
|
||||
)
|
||||
|
||||
func NewJWT() *JWT {
|
||||
return &JWT{
|
||||
[]byte(config.ConfigData.JWT.SigningKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JWT) CreateClaims(baseClaims model.BaseClaims) model.CustomClaims {
|
||||
bf, _ := ParseDuration(config.ConfigData.JWT.BufferTime)
|
||||
ep, _ := ParseDuration(config.ConfigData.JWT.ExpiresTime)
|
||||
claims := model.CustomClaims{
|
||||
BaseClaims: baseClaims,
|
||||
BufferTime: int64(bf / time.Second), // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{"FM"}, // 受众
|
||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), // 签名生效时间
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(ep)), // 过期时间 7天 配置文件
|
||||
Issuer: config.ConfigData.JWT.Issuer, // 签名的发行者
|
||||
},
|
||||
}
|
||||
return claims
|
||||
}
|
||||
|
||||
// 创建一个token
|
||||
func (j *JWT) CreateToken(claims model.CustomClaims) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(j.SigningKey)
|
||||
}
|
||||
|
||||
var concurrencyControl = &singleflight.Group{}
|
||||
|
||||
// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题
|
||||
func (j *JWT) CreateTokenByOldToken(oldToken string, claims model.CustomClaims) (string, error) {
|
||||
v, err, _ := concurrencyControl.Do("JWT:"+oldToken, func() (interface{}, error) {
|
||||
return j.CreateToken(claims)
|
||||
})
|
||||
return v.(string), err
|
||||
}
|
||||
|
||||
// ParseToken 解析并验证 JWT 字符串
|
||||
func (j *JWT) ParseToken(tokenString string) (*model.CustomClaims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &model.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// 检查签名方法
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return j.SigningKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*model.CustomClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
} else {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
}
|
||||
|
||||
// 解析时间字符
|
||||
func ParseDuration(d string) (time.Duration, error) {
|
||||
d = strings.TrimSpace(d)
|
||||
dr, err := time.ParseDuration(d)
|
||||
if err == nil {
|
||||
return dr, nil
|
||||
}
|
||||
if strings.Contains(d, "d") {
|
||||
index := strings.Index(d, "d")
|
||||
|
||||
hour, _ := strconv.Atoi(d[:index])
|
||||
dr = time.Hour * 24 * time.Duration(hour)
|
||||
ndr, err := time.ParseDuration(d[index+1:])
|
||||
if err != nil {
|
||||
return dr, nil
|
||||
}
|
||||
return dr + ndr, nil
|
||||
}
|
||||
|
||||
dv, err := strconv.ParseInt(d, 10, 64)
|
||||
return time.Duration(dv), err
|
||||
}
|
||||
func SetToken(c *gin.Context, token string, maxAge int) {
|
||||
// 增加cookie x-token 向来源的web添加
|
||||
host, _, err := net.SplitHostPort(c.Request.Host)
|
||||
if err != nil {
|
||||
host = c.Request.Host
|
||||
}
|
||||
|
||||
if net.ParseIP(host) != nil {
|
||||
c.SetCookie("x-token", token, maxAge, "/", "", false, false)
|
||||
} else {
|
||||
c.SetCookie("x-token", token, maxAge, "/", host, false, false)
|
||||
}
|
||||
}
|
||||
func ClearToken(c *gin.Context) {
|
||||
// 增加cookie x-token 向来源的web添加
|
||||
host, _, err := net.SplitHostPort(c.Request.Host)
|
||||
if err != nil {
|
||||
host = c.Request.Host
|
||||
}
|
||||
|
||||
if net.ParseIP(host) != nil {
|
||||
c.SetCookie("x-token", "", -1, "/", "", false, false)
|
||||
} else {
|
||||
c.SetCookie("x-token", "", -1, "/", host, false, false)
|
||||
}
|
||||
}
|
||||
func GetToken(c *gin.Context) string {
|
||||
token, _ := c.Cookie("x-token")
|
||||
if token == "" {
|
||||
token = c.Request.Header.Get("x-token")
|
||||
}
|
||||
return token
|
||||
}
|
||||
func GetClaims(c *gin.Context) (*model.CustomClaims, error) {
|
||||
token := GetToken(c)
|
||||
j := NewJWT()
|
||||
claims, err := j.ParseToken(token)
|
||||
if err != nil {
|
||||
log.Println("从Gin的Context中获取从jwt解析信息失败, 请检查请求头是否存在x-token且claims是否为规定结构")
|
||||
}
|
||||
return claims, err
|
||||
}
|
||||
|
||||
func GetUserID(c *gin.Context) uint {
|
||||
if claims, exists := c.Get("claims"); !exists {
|
||||
if cl, err := GetClaims(c); err != nil {
|
||||
return 0
|
||||
} else {
|
||||
return cl.BaseClaims.Userid
|
||||
}
|
||||
} else {
|
||||
waitUse := claims.(*model.CustomClaims)
|
||||
return waitUse.BaseClaims.Userid
|
||||
}
|
||||
}
|
||||
22
utils/lawsuitQuery.go
Normal file
22
utils/lawsuitQuery.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateMD5() string {
|
||||
currentTime := time.Now()
|
||||
dateString := currentTime.Format("20060102")
|
||||
|
||||
inputString := "6tj4u" + dateString
|
||||
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(inputString))
|
||||
hashBytes := hasher.Sum(nil)
|
||||
|
||||
hashString := hex.EncodeToString(hashBytes)
|
||||
|
||||
return hashString
|
||||
}
|
||||
64
utils/notify.go
Normal file
64
utils/notify.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// 将整数转换为固定长度的字符串
|
||||
func intToFixedLengthString(num int, length int) string {
|
||||
return fmt.Sprintf("%0*d", length, num)
|
||||
}
|
||||
|
||||
// 加密函数
|
||||
func IDEncrypt(plainText, key string) (string, error) {
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
plainTextBytes := []byte(plainText)
|
||||
cipherText := make([]byte, aes.BlockSize+len(plainTextBytes))
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(cipherText[aes.BlockSize:], plainTextBytes)
|
||||
|
||||
// 使用URL安全的Base64编码
|
||||
encrypted := base64.URLEncoding.EncodeToString(cipherText)
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// 解密函数
|
||||
func IDDecrypt(encryptedText, key string) (string, error) {
|
||||
cipherText, err := base64.URLEncoding.DecodeString(encryptedText)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(cipherText) < aes.BlockSize {
|
||||
return "", errors.New("cipherText too short")
|
||||
}
|
||||
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
cipherText = cipherText[aes.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(cipherText, cipherText)
|
||||
|
||||
return string(cipherText), nil
|
||||
}
|
||||
75
utils/parseIdCard.go
Normal file
75
utils/parseIdCard.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"qnc-server/model/model"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func loadRegions(filename string) (map[string]model.Region, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
byteValue, _ := ioutil.ReadAll(file)
|
||||
|
||||
var regions map[string]model.Region
|
||||
json.Unmarshal(byteValue, ®ions)
|
||||
|
||||
return regions, nil
|
||||
}
|
||||
|
||||
func ParseIDCard(id string) (model.IDInfo, error) {
|
||||
|
||||
if len(id) != 18 {
|
||||
return model.IDInfo{}, fmt.Errorf("Invalid ID length")
|
||||
}
|
||||
|
||||
// Validate the ID card number
|
||||
validID, _ := regexp.MatchString(`^\d{17}[\dX]$`, id)
|
||||
if !validID {
|
||||
return model.IDInfo{}, fmt.Errorf("Invalid ID format")
|
||||
}
|
||||
|
||||
regionCode := id[:6]
|
||||
birthDate := id[6:14]
|
||||
genderCode := id[16:17]
|
||||
|
||||
regions, err := loadRegions("processed.json")
|
||||
if err != nil {
|
||||
return model.IDInfo{}, fmt.Errorf("loadRegions error: %v", err)
|
||||
}
|
||||
|
||||
region, exists := regions[regionCode]
|
||||
if !exists {
|
||||
return model.IDInfo{}, fmt.Errorf("Region code not found")
|
||||
}
|
||||
|
||||
birth, err := time.Parse("20060102", birthDate)
|
||||
if err != nil {
|
||||
return model.IDInfo{}, fmt.Errorf("Invalid birth date")
|
||||
}
|
||||
|
||||
gender, err := strconv.Atoi(genderCode)
|
||||
if err != nil {
|
||||
return model.IDInfo{}, fmt.Errorf("Invalid gender code")
|
||||
}
|
||||
|
||||
genderStr := "男"
|
||||
if gender%2 == 0 {
|
||||
genderStr = "女"
|
||||
}
|
||||
|
||||
return model.IDInfo{
|
||||
Location: region.FullRegion,
|
||||
BirthDate: birth.Format("2006年01月02日"),
|
||||
Gender: genderStr,
|
||||
}, nil
|
||||
}
|
||||
46
utils/pay.go
Normal file
46
utils/pay.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateOrderNumber() string {
|
||||
const prefix = "order"
|
||||
const length = 24
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 生成后缀的长度
|
||||
suffixLength := length - len(prefix)
|
||||
suffix := make([]byte, suffixLength)
|
||||
|
||||
// 填充随机数字
|
||||
for i := 0; i < suffixLength; i++ {
|
||||
suffix[i] = byte(rand.Intn(10)) + '0'
|
||||
}
|
||||
|
||||
return prefix + string(suffix)
|
||||
}
|
||||
func GenerateOrderRefundNumber() string {
|
||||
const prefix = "orderRefund"
|
||||
const length = 24
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// 生成后缀的长度
|
||||
suffixLength := length - len(prefix)
|
||||
suffix := make([]byte, suffixLength)
|
||||
|
||||
// 填充随机数字
|
||||
for i := 0; i < suffixLength; i++ {
|
||||
suffix[i] = byte(rand.Intn(10)) + '0'
|
||||
}
|
||||
|
||||
return prefix + string(suffix)
|
||||
}
|
||||
|
||||
// ConvertCentsToYuan 将金额从分转换为元并保留两位小数 阿里支付用
|
||||
func ConvertCentsToYuan(amountInCents int) string {
|
||||
amountInYuan := float64(amountInCents) / 100
|
||||
return fmt.Sprintf("%.2f", amountInYuan)
|
||||
}
|
||||
78
utils/veriryCode.go
Normal file
78
utils/veriryCode.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"math/rand"
|
||||
"qnc-server/config"
|
||||
"qnc-server/db"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func GenerateVerificationCode() string {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
code := fmt.Sprintf("%06d", r.Intn(1000000))
|
||||
return code
|
||||
}
|
||||
|
||||
// 验证手机号码格式的函数
|
||||
func IsValidPhoneNumber(phone string) bool {
|
||||
re := regexp.MustCompile(`^1[3-9]\d{9}$`)
|
||||
return re.MatchString(phone)
|
||||
}
|
||||
|
||||
// StoreVerificationCode 存储验证码到 Redis
|
||||
func StoreVerificationCode(phoneNumber, code string) error {
|
||||
err := db.RedisClient.Set(ctx, phoneNumber, code, config.ConfigData.VerifyCode.Expiration).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = SetThrottle(phoneNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func VerifyCode(phoneNumber, code string) (bool, error) {
|
||||
//if phoneNumber == "18276151590" {
|
||||
// return true, nil
|
||||
//}
|
||||
storedCode, err := db.RedisClient.Get(ctx, phoneNumber).Result()
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return false, fmt.Errorf("验证码不存在或已过期")
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if storedCode == code {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CanRequestCode 检查用户是否可以请求新的验证码
|
||||
func CanRequestCode(phoneNumber string) (bool, error) {
|
||||
key := "throttle:" + phoneNumber
|
||||
exists, err := db.RedisClient.Exists(ctx, key).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return exists == 1, nil
|
||||
}
|
||||
|
||||
// SetThrottle 设置用户请求的节流
|
||||
func SetThrottle(phoneNumber string) error {
|
||||
key := "throttle:" + phoneNumber
|
||||
err := db.RedisClient.Set(ctx, key, "1", config.ConfigData.VerifyCode.ThrottleTime).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
150
utils/westdex.go
Normal file
150
utils/westdex.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package utils
|
||||
|
||||
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) (string, error) {
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(encodedData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key := generateAESKey(KEY_SIZE*8, []byte(secretKey))
|
||||
plaintext, err := aesDecrypt(ciphertext, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(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:]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user