commit d3e287819e237072c17856e244895aa065af02f2 Author: KynixInHK Date: Mon Jul 8 11:14:19 2024 +0800 第一次试运行 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/http-test/http-test.json b/.idea/http-test/http-test.json new file mode 100644 index 0000000..cbdb4f3 --- /dev/null +++ b/.idea/http-test/http-test.json @@ -0,0 +1,7 @@ +{ + "enableAutoRestart" : false, + "scanSuffix" : "java,rs,kt,go,py", + "scanPath" : "", + "fileSuffix" : "java,rs,kt,go,py,md,zig", + "excludePath" : ".idea,.gradle,.git,node_modules,build" +} \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..4e70543 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bc5fdaa --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/optimised-toolchain.iml b/.idea/optimised-toolchain.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/optimised-toolchain.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..774baac --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Axiomatrix Optimized Toolchain +一套大大简化开发流程的后端工具库,由Axiomatrix Org.开发并开源。 +## 已包含模块 +- Cors 中间件 +- 哈希加盐加密比对工具 +- JWT 生成和校验工具 +- 随机数字/混合字串生成工具 +- 访问频率限制中间件 +- Redis 配置和操作工具 +- RSA 非对称加解密工具 +- SMTP 邮件发送工具 + +## 作者 +Axiomatrix Org. + +## 开源授权证书 +- MIT \ No newline at end of file diff --git a/am_cors/cors.go b/am_cors/cors.go new file mode 100644 index 0000000..8d0a790 --- /dev/null +++ b/am_cors/cors.go @@ -0,0 +1,32 @@ +package am_cors + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +/* +* 跨域处理中间件 + */ +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + origin := c.Request.Header.Get("Origin") + + // 允许跨域访问响应头设定 + if origin != "" { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") + c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") + c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") + c.Header("Access-Control-Allow-Credentials", "true") + } + + // 处理预检请求 + if method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + } + + c.Next() + } +} diff --git a/am_docs/cors.md b/am_docs/cors.md new file mode 100644 index 0000000..cae1cae --- /dev/null +++ b/am_docs/cors.md @@ -0,0 +1,9 @@ +# Cors +解决跨域问题的Cors中间件,适用于gin框架。 +## 使用方法 + +```go +gin.SetMode(gin.DebugMode) +r := gin.Default() +r.Use(cors.Cors()) +``` diff --git a/am_docs/hashsalt.md b/am_docs/hashsalt.md new file mode 100644 index 0000000..6c79840 --- /dev/null +++ b/am_docs/hashsalt.md @@ -0,0 +1,12 @@ +# Hash Salt 加密件 +用于对密码等敏感信息进行加密入库。 + +## 使用方法 +```go +// 加密信息 +var originData = "123456" // 明文信息 +var encryptedMessage = am_hashsalt.HashData(originData) // 加密操作,获得加密后的字串 + +// 匹配信息 +var result = am_hashsalt.CompareData(encryptedData, originData) // 比对两者是否一致,返回bool值,true为一致,false为不一致 +``` \ No newline at end of file diff --git a/am_docs/jwt.md b/am_docs/jwt.md new file mode 100644 index 0000000..1f658d5 --- /dev/null +++ b/am_docs/jwt.md @@ -0,0 +1,29 @@ +# JWT 工具套件 +用于JWT(Json Web Token)的生成和验证。工具包附带一个全功能中间件。 + +## 使用方法 +### Token的生成 + +```go +// 配置claims +var tokenClaims = am_jwt.TokenClaims{ + Email: "example@example.com", // 用户的邮件地址 + Role: "user", // Role有四个取值级别:root、admin、user和temp,其中temp仅用于注册和重设密码 + Exp: 600, // token过期时间,以s为计数单位 + Issuer: "James", // 签发人 + SECRET: "ROMANCETILLDEATH", // 签发密钥 +} + +var token = am_jwt.GenToken(&tokenClaims) // 生成token字串 +``` + +### Token验证中间件的使用 +按照普通中间件的使用方式加到需要的controller上即可。 +```go +gin.SetMode(gin.DebugMode) +r := gin.Default() +// 参数是该controller访问所需要的权限。比该权限高的token均可访问,但temp除外,需要权限为temp的controller,user权限无法访问。 +r.POST("/test", am_jwt.JWTAuthMiddleware("user"), func(context *gin.Context) { + ... +}) +``` \ No newline at end of file diff --git a/am_docs/random.md b/am_docs/random.md new file mode 100644 index 0000000..00877f9 --- /dev/null +++ b/am_docs/random.md @@ -0,0 +1,10 @@ +# 随机生成字串 +用于随机产生指定位数的纯数字字串或数字/大写字母混合字串。 + +## 使用方法 +```go +// 产生6位随机纯数字 +var digits = am_random.CreateRandomDigits(6) +// 产生6位随机大写字母和数字混合 +var fusion = am_random.CreateRandomString(6) +``` \ No newline at end of file diff --git a/am_docs/ratelimit.md b/am_docs/ratelimit.md new file mode 100644 index 0000000..ad439a0 --- /dev/null +++ b/am_docs/ratelimit.md @@ -0,0 +1,18 @@ +# 速率限制中间件 +为了防范恶意flood攻击,需要对同一IP对同一controller的访问频率做出限制。 + +## 使用方法 +```go +// 创建速率限制配置 +// 参数1:窗口期内可访问次数 +// 参数2:窗口期时间,以s为计数单位 +// 下述配置为1秒种内可访问同一controller 5次 +var defaultLimitConfig = am_ratelimit.NewRateLimitConfig(5, 1) + +gin.SetMode(gin.DebugMode) +r := gin.Default() +// 设置中间件 +r.POST("/test", defaultLimitConfig.RateLimitMiddleware, func(context *gin.Context) { + ... +}) +``` \ No newline at end of file diff --git a/am_docs/redis.md b/am_docs/redis.md new file mode 100644 index 0000000..25a963f --- /dev/null +++ b/am_docs/redis.md @@ -0,0 +1,39 @@ +# Redis 工具包 +集成了Redis配置、set和get。 +## 使用方法 +> Attention⚠️:如果需要使用JWT套件,请务必配置Redis,因为该套件依赖Redis执行。 +### 配置Redis +```go +// 使用默认配置 127.0.0.1:6379 password="" DB=0 +am_redis.Setup(nil) + +// 使用自定义配置 +var redisConn = am_redis.RedisConn{ + Addr: "localhost", + Port: "6379", + Password: "", + DB: 0, +} + +am_redis.Setup(&redisConn) +``` + +### 存入Redis +```go +// 参数1: key +// 参数2: value +// 参数3: 过期时间,以s为计数单位。0表示永不过期 +am_redis.SetValue("key", "value", 0) +``` + +### 从Redis取出 +```go +// 参数1: key +am_redis.GetValue("key") +``` + +### 从Redis中删除 +```go +// 参数1: key +am_redis.DelValue("key") +``` \ No newline at end of file diff --git a/am_docs/rsa.md b/am_docs/rsa.md new file mode 100644 index 0000000..7c8cea5 --- /dev/null +++ b/am_docs/rsa.md @@ -0,0 +1,26 @@ +# RSA 非对称加密套件 +用于进行RSA非对称加密操作。 + +## 使用方法 +### 产生密钥对 +密钥对由公钥和私钥组成,均为PEM格式。 +```go +// 参数1: 密钥对长度 +var privateKey, publicKey, err = am_rsa.GenRSAKeypair(2048) +``` + +### 公钥加密 +```go +var originData = "123456" +// 参数1: 原始数据 +// 参数2: 公钥PEM +am_rsa.RsaEncryptBase64(originData, publicKey) +``` + +### 私钥解密 +```go +var encryptedData = "..." // encrypted data +// 参数1: 加密过后的数据 +// 参数2: 私钥PEM +am_rsa.RsaDecryptBase64(encryptedData, privateKey) +``` \ No newline at end of file diff --git a/am_docs/smtp.md b/am_docs/smtp.md new file mode 100644 index 0000000..52a158d --- /dev/null +++ b/am_docs/smtp.md @@ -0,0 +1,50 @@ +# SMTP 发信服务包 +用于通过Email的SMTP服务发送电子邮件。 + +## 使用方法 +### 配置服务连接 +```go +var emailConnection = am_smtp.EmailConnection{ + Server: "smtp.example.com", + Port: 587, + Username: "example@example.com", + Password: "123456" +} +``` +### 发送纯文字邮件 +```go +am_smtp.SendPlainEmail( + "example@example.com", // from,发信邮箱 + []string{"to@to.com"}, // to,收信邮箱,可以多个 + "subject", // subject,邮件主题 + "content", // text,邮件内容 + emailConnection // conn,上一步设定的email connection信息 +) +``` + +### 发送HTML邮件 +```go +// 设定html路径 +pwd, err := os.Getwd() +path := filepath.Join(pwd, "template", "signup", "template.html") + +// 设定填充数据(有的话) +type EmailData struct { + Code string + Time string +} + +var emailData = EmailData{ + ... +} + +// 发送 +am_smtp.SendHTMLEmail( + "example@example.com", // from,发信邮箱 + []string{"to@to.com"}, // to,收信邮箱,可以多个 + "subject", // subject,邮件主题 + path, // html,HTML模板的路径 + emailData, // 填充数据 + emailConnection, // conn,上一步设定的email connection信息 +) +``` \ No newline at end of file diff --git a/am_hashsalt/hashsalt.go b/am_hashsalt/hashsalt.go new file mode 100644 index 0000000..10b4661 --- /dev/null +++ b/am_hashsalt/hashsalt.go @@ -0,0 +1,29 @@ +package am_hashsalt + +import "golang.org/x/crypto/bcrypt" + +/* +* 哈希加盐加密 +* 参数: +* originData string:明文 + */ +func HashData(originData string) (string, error) { + // 哈希加盐加密 + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(originData), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashedPassword), nil +} + +/* +* 比对密文和明文 +* 参数: +* 1. hashedData string:哈希加盐加密过后的数据 +* 2. data string:要比对的明文数据 + */ +func CompareData(hashedData, data string) bool { + // 比对加密后数据和所需数据 + err := bcrypt.CompareHashAndPassword([]byte(hashedData), []byte(data)) + return err == nil +} diff --git a/am_jwt/jwt.go b/am_jwt/jwt.go new file mode 100644 index 0000000..924a1a1 --- /dev/null +++ b/am_jwt/jwt.go @@ -0,0 +1,325 @@ +package am_jwt + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/axiomatrix-org/optimized-toolchain/am_redis" + "github.com/gin-gonic/gin" + "gopkg.in/dgrijalva/jwt-go.v3" + "net/http" + "strconv" + "strings" + "time" +) + +// 工具返回代码 +const ( + OKCode = 2000 + InternalServerErrorCode = 5000 + ErrCodeMalformed = 4001 // token格式错误 + ErrCodeExpired = 4002 // token过期 + ErrCodeNotValidYet = 4003 // token尚未生效 + ErrCodeSignatureInvalid = 4004 // token签名无效 + ErrCodeInvalidRole = 4005 // token权限不符 + ErrCodeNoAuthHeader = 4006 // 没有对应的请求头 + ErrCodeMalformedReq = 4007 // 请求头格式不正确 +) + +// 工具错误类型 +var ( + MalformedError = errors.New("malformed jwt") + ExpiredAndDiedError = errors.New("expired jwt") + ExpiredButCanSaveError = errors.New("expired jwt, but can save it") + NotValidError = errors.New("invalid jwt") + SignatureError = errors.New("expired jwt") + InvalidRoleError = errors.New("invalid role") + UnknownError = errors.New("unknown jwt error") +) + +// 不同身份权限,数字越大,权限越大。高权限用户可以执行低权限用户的所有操作 +const ( + ROOTROLE = 4 // root权限,最大的权限,整个系统的超级管理权限 + ADMINROLE = 3 // admin权限,只能由root授予,拥有管理用户的权限,但不具备修改root的权限 + USERROLE = 2 // user权限,普通用户权限 + TEMPROLE = 1 // temp权限,临时权限,该权限仅用于注册和修改密码的临时使用 +) + +// 身份字段转换权限等级数字 +func claimToRole(claim string) int { + switch claim { + case "root": + return ROOTROLE + case "admin": + return ADMINROLE + case "user": + return USERROLE + case "temp": + return TEMPROLE + default: + return 0 + } +} + +// token claims +type TokenClaims struct { + Email string // 用户的email + Role string // 用户的身份登记 + Exp int // token过期时间,以秒计数 + Issuer string // 签发人 + SECRET string // token secret + jwt.StandardClaims // standard claims,无需用户设定 +} + +/* +* PRIVATE +* 查验redis储存情况 +* 参数: +* 1. token string:要查验的token + */ + +func checkRedis(token string) (*TokenClaims, error) { + // 从redis中查验 + claimsJson, err := am_redis.GetValue(token) + claims := &TokenClaims{} + if err != nil { + if errors.Is(err, am_redis.RedisGetNilError) { // 如果不存在于库中,则证明已经提前过期 + return nil, ExpiredAndDiedError + } else { + return nil, err + } + } + err = json.Unmarshal([]byte(claimsJson), claims) + if err != nil { + return nil, err + } + return claims, nil +} + +/* +* 生成token字串 +* 参数: +* 1. claims *TokenClaims token参数 + */ +func GenToken(claims *TokenClaims) (string, error) { + tokenClaims := claims + + // 配置standard claims + tokenClaims.StandardClaims = jwt.StandardClaims{ + ExpiresAt: time.Now().Add(time.Second * time.Duration(claims.Exp)).Unix(), // 过期时间 + IssuedAt: time.Now().Unix(), // 签名日期 + Issuer: claims.Issuer, // 签发人 + } + + // 生成token字串 + tokenGenerator := jwt.NewWithClaims(jwt.SigningMethodHS256, tokenClaims) + token, err := tokenGenerator.SignedString([]byte(claims.SECRET)) // 生成token + if err != nil { + return "", err + } + + // 储存redis + value, err := json.Marshal(claims) + if err != nil { + fmt.Println(err) + return "", err + } + + err = am_redis.SetValue(token, string(value), claims.Exp) // 键为token字串,值为原始过期时间 + if err != nil { + return "", err + } + + return token, nil +} + +/* +* 解析token +* 参数: +* 1. token string:要解析的token字串 +* 2. roleRequired int:验证通过需要的权限等级 +* 3. secret string:解码密钥 + */ +func ParseToken(token string, roleRequired int, secret string) (*TokenClaims, error) { + // 解析token + result, err := jwt.ParseWithClaims(token, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(secret), nil + }) + + // 解析出现问题 + if err != nil { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { // token格式不正确 + return nil, MalformedError + } else if ve.Errors&jwt.ValidationErrorExpired != 0 { // token过期 + claims, err := checkRedis(token) // 查验redis + if err != nil { + if errors.Is(err, ExpiredAndDiedError) { // 如果redis也过期了 + return nil, ExpiredAndDiedError // 无可救药 + } else { + return nil, err + } + } + return claims, ExpiredButCanSaveError // 否则,还能救一救(重新生成token) + } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { // token尚未生效 + return nil, NotValidError + } else if ve.Errors&jwt.ValidationErrorSignatureInvalid != 0 { // token签名错误 + return nil, SignatureError + } else { // 其他错误 + return nil, err + } + } + } + + // 解析未出现问题,即此token形式合法 + if claims, ok := result.Claims.(*TokenClaims); ok && result.Valid { + // 从redis中查验 + _, err := checkRedis(token) + if err != nil { + return nil, err + } + + // 刷新token在redis中的过期时间 + err = am_redis.SetValue(token, strconv.Itoa(claims.Exp), claims.Exp) + if err != nil { + return nil, err + } + + // 验证权限合法性 + if claimToRole(claims.Role) >= roleRequired { // 如果提供的token权限验证大于所需权限,初步判断通过 + if roleRequired == 1 && claimToRole(claims.Role) == 2 { // user权限无权新增用户 + return nil, InvalidRoleError + } + + if claimToRole(claims.Role) == 1 { // temp权限仅用于注册和重设密码临时使用,一经使用立即灭活 + _, err := Kickoff(token) + if err != nil { + return nil, err + } + } + return claims, nil // 返回claims + } else { + return nil, InvalidRoleError + } + } + return nil, UnknownError +} + +/* +* 灭活token +* 参数: +* 1. token string:需要灭活的token字串 + */ +func Kickoff(token string) (bool, error) { + err := am_redis.DelValue(token) + if err != nil { + return false, err + } + return true, nil +} + +/* +* JWT中间件 + */ +func JWTAuthMiddleware(role string, secret string) func(c *gin.Context) { + return func(c *gin.Context) { + authHeader := c.Request.Header.Get("Authorization") // 获取请求头中的Authorization字段 + // 如果没有Authorization字段,直接拦截掉,并返回4006代码 + if authHeader == "" { + c.JSON(http.StatusUnauthorized, gin.H{ + "code": ErrCodeNoAuthHeader, + "msg": "No Authorization header", + }) + c.Abort() + return + } + + // 请求头中的token必须是Bearer-Token的格式,否则拦截,返回4007代码 + parts := strings.SplitN(authHeader, " ", 2) + if !(len(parts) == 2 && parts[0] == "Bearer") { + c.JSON(http.StatusBadRequest, gin.H{ + "code": ErrCodeMalformedReq, + "msg": "Wrong Authorization header", + }) + c.Abort() + return + } + + // 开始认证 + claims, err := ParseToken(parts[1], claimToRole(role), secret) + if err != nil { + if errors.Is(err, ExpiredButCanSaveError) { // 如果还可以救一救 + tokenClaims := TokenClaims{ + Email: claims.Email, + Role: claims.Role, + Exp: claims.Exp, + Issuer: claims.Issuer, + SECRET: claims.SECRET, + } + + fmt.Println(tokenClaims) // test print + + token, err := GenToken(&tokenClaims) // 重新生成token + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": InternalServerErrorCode, + "msg": err.Error(), + }) + c.Abort() + return + } + + c.Writer.Header().Set("Token", token) // 将token放入请求头后放行 + c.Set("email", claims.Email) + c.Set("role", claims.Role) + c.Next() + return + } else if errors.Is(err, ExpiredAndDiedError) { // 救不了了 + c.JSON(http.StatusUnauthorized, gin.H{ + "code": ErrCodeExpired, + "msg": err.Error(), + }) + c.Abort() + return + } else if errors.Is(err, MalformedError) { // 格式不正确 + c.JSON(http.StatusBadRequest, gin.H{ + "code": ErrCodeMalformed, + "msg": err.Error(), + }) + c.Abort() + return + } else if errors.Is(err, InvalidRoleError) { // 权限不符 + c.JSON(http.StatusForbidden, gin.H{ + "code": ErrCodeInvalidRole, + "msg": err.Error(), + }) + c.Abort() + return + } else if errors.Is(err, NotValidError) { + c.JSON(http.StatusUnauthorized, gin.H{ + "code": ErrCodeNotValidYet, + "msg": err.Error(), + }) + c.Abort() + return + } else if errors.Is(err, SignatureError) { + c.JSON(http.StatusUnauthorized, gin.H{ + "code": ErrCodeSignatureInvalid, + "msg": err.Error(), + }) + c.Abort() + return + } else if errors.Is(err, UnknownError) { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": InternalServerErrorCode, + "msg": err.Error(), + }) + c.Abort() + return + } + } + c.Set("email", claims.Email) + c.Set("role", claims.Role) + c.Next() + } +} diff --git a/am_random/random.go b/am_random/random.go new file mode 100644 index 0000000..4b52b3d --- /dev/null +++ b/am_random/random.go @@ -0,0 +1,26 @@ +package am_random + +import ( + "math/rand" + "time" +) + +func CreateRandomDigits(length uint) string { + const digits = "0123456789" // 划定取值范围 + result := make([]byte, length) // 定义结果集 + r := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) // 种子 + for i := range result { + result[i] = digits[r.Intn(len(digits))] // 种子随机产生取值范围长度之内的下标而取值 + } + return string(result) +} + +func CreateRandomString(length uint) string { + const charas = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + result := make([]byte, length) + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := range result { + result[i] = charas[r.Intn(len(charas))] + } + return string(result) +} diff --git a/am_ratelimit/ratelimit.go b/am_ratelimit/ratelimit.go new file mode 100644 index 0000000..9179e05 --- /dev/null +++ b/am_ratelimit/ratelimit.go @@ -0,0 +1,73 @@ +package am_ratelimit + +import ( + "github.com/gin-gonic/gin" + "net/http" + "sync" + "time" +) + +// 储存用户信息,包括最后一次访问时间和访问计数 +type RequestInfo struct { + LastAccessTime time.Time + RequestCount int +} + +// 互斥锁,用于同步对共享资源(如requestInfo映射)的访问,确保在多线程环境下的数据一致性 +var mutex = &sync.Mutex{} + +// 速率限制配置项结构体 +type RateLimitConfig struct { + maxRequests int // 最大访问次数 + timeWindow time.Duration // 窗口时间 + requestInfo map[string]*RequestInfo // 用户信息 +} + +/* +* 初始化速率限制配置 +* 参数: +* 1. maxRequests int:窗口时间内最大访问数量限制 +* 2. timeWindow int:限制的窗口时间,单位为秒 + */ +func NewRateLimitConfig(maxRequests int, timeWindow int) *RateLimitConfig { + return &RateLimitConfig{ + maxRequests: maxRequests, + timeWindow: time.Duration(timeWindow) * time.Second, + requestInfo: make(map[string]*RequestInfo), + } +} + +/* +* 速率限制中间件 + */ +func (r1 *RateLimitConfig) RateLimitMiddleware(c *gin.Context) { + ip := c.ClientIP() // 获取用户IP + mutex.Lock() // 在访问共享资源前加锁,确保只有同一线程才能访问 + defer mutex.Unlock() // 在数据返回前解锁 + + info, exists := r1.requestInfo[ip] // 检查requestInfo映射中是否已经存在该IP信息 + + // 如果该IP的请求信息不存在,则初始化一个新的RequestInfo实例,并将其添加到requestInfo映射中 + if !exists { + r1.requestInfo[ip] = &RequestInfo{LastAccessTime: time.Now(), RequestCount: 1} + return + } + + // 如果最后一次请求的时间已经超出了时间窗口,重置请求计数并更新最后一次访问时间 + if time.Since(info.LastAccessTime) > r1.timeWindow { + info.RequestCount = 1 + info.LastAccessTime = time.Now() + return + } + + // 增加请求数并检查速率限制 + info.RequestCount++ + if info.RequestCount > r1.maxRequests { + c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too many requests"}) + c.Abort() + return + } + + // 更新最后一次访问时间 + info.LastAccessTime = time.Now() +} diff --git a/am_redis/redis.go b/am_redis/redis.go new file mode 100644 index 0000000..5077161 --- /dev/null +++ b/am_redis/redis.go @@ -0,0 +1,106 @@ +package am_redis + +import ( + "errors" + "github.com/go-redis/redis" + "net" + "strconv" + "time" +) + +// 工具错误类型 +var ( + RedisGetNilError = errors.New("redis get nil") // 未找到值 + TxFailedError = errors.New("transaction failed") // 握手失败 + TimeoutError = errors.New("timeout") // 连接超时 + NilPointError = errors.New("no redis connections") // 没有设定redis连接 +) + +// redis连接结构体 +type RedisConn struct { + Addr string + Port int + Password string + DB int +} + +// redis连接参数默认值 +var redisConn *RedisConn = &RedisConn{ + Addr: "127.0.0.1", + Port: 6379, + Password: "", + DB: 0, +} + +// redis连接client +var client *redis.Client + +// 初始化redis连接 +func Setup(conn *RedisConn) error { + connectionToRedis := redisConn // redis连接参数默认值 + if conn != nil { // 修改redis连接参数 + connectionToRedis = conn + } + client = redis.NewClient(&redis.Options{ // 获取redis连接 + Addr: connectionToRedis.Addr + ":" + strconv.Itoa(connectionToRedis.Port), + Password: connectionToRedis.Password, + DB: connectionToRedis.DB, + }) + _, err := client.Ping().Result() // 测试连通情况 + if err != nil { // 如果不通,抛出err + return err + } + return nil // 通,返回client连接 +} + +/* +* 向redis中添加资料 +* 参数: +* 1. key string 键 +* 2. value string 值 +* 3. exp int 过期时间,秒为单位,0永不过期 + */ +func SetValue(key string, value string, exp int) error { + if client == nil { // 如果没有redis连接,则返回空指针异常 + return NilPointError + } + client.Set(key, value, time.Duration(exp)*time.Second) + return nil +} + +/* +* 从redis中获取资料 +* 1. key string 键 + */ +func GetValue(key string) (string, error) { + if client == nil { // 如果没有redis连接,则返回空指针异常 + return "", NilPointError + } + result, err := client.Get(key).Result() + if err != nil { + if errors.Is(err, redis.Nil) { // 未找到值 + return "", RedisGetNilError + } else if errors.Is(err, redis.TxFailedErr) { // 握手失败 + return "", TxFailedError + } else if netErr, ok := err.(net.Error); ok && netErr.Timeout() { // 连接超时 + return "", TimeoutError + } else { // 其他错误 + return "", err + } + } + return result, nil +} + +/* +* 删除资料 +* 参数: +* 1. key:键 + */ +func DelValue(key string) error { + if client == nil { // 如果没有redis连接,则返回空指针异常 + return NilPointError + } + + client.Del(key) + return nil +} diff --git a/am_rsa/rsa.go b/am_rsa/rsa.go new file mode 100644 index 0000000..38d5122 --- /dev/null +++ b/am_rsa/rsa.go @@ -0,0 +1,119 @@ +package am_rsa + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" +) + +// 工具错误类型 +var ( + KeyPairAlreadyExistError = errors.New("key pair already exist") +) + +// KeyPair备份值 +var keyPair map[string]string + +/* +* 产生Keypair +* 参数: +* bits int:长度,一般为1024或2048 + */ +func GenRSAKeypair(bits int) (privateKey, publicKey string, err error) { + // 防止重复生成 + if keyPair != nil { + return "", "", KeyPairAlreadyExistError + } + // 生成keypair + key, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return "", "", err + } + + // 将私钥处理为PEM格式 + pkcs1PrivateKey := x509.MarshalPKCS1PrivateKey(key) + p := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: pkcs1PrivateKey, + } + privKeyPEM := pem.EncodeToMemory(p) + + // 将公钥处理成PEM格式 + pubKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + return "", "", err + } + p = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubKeyBytes, + } + pubKeyPEM := pem.EncodeToMemory(p) + + // 生成最后的PEM字串 + privateKey = string(privKeyPEM) + publicKey = string(pubKeyPEM) + + // 备份keypair + keyPair = map[string]string{ + "private": privateKey, + "public": publicKey, + } + return +} + +/* +* RSA公钥加密 +* 参数: +* 1. originData string:要加密的明文 +* 2. publicKey string:公钥PEM + */ +func RsaEncryptBase64(originalData, publicKey string) (string, error) { + block, _ := pem.Decode([]byte(publicKey)) // 解密公钥 + // 加载公钥 + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return "", err + } + + // 使用公钥加密数据 + encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey.(*rsa.PublicKey), []byte(originalData)) + if err != nil { + return "", err + } + + // 产生base64字串 + return base64.StdEncoding.EncodeToString(encryptedData), err +} + +/* +* RSA私钥解密 +* 参数: +* 1. encryptedData string:加密过后的密文 +* 2. privateKey string:私钥PEM + */ +func RsaDecryptBase64(encryptedData, privateKey string) (string, error) { + // 解密私钥 + encryptedDecodeBytes, err := base64.StdEncoding.DecodeString(encryptedData) + if err != nil { + return "", err + } + block, _ := pem.Decode([]byte(privateKey)) + + // 加载私钥 + priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return "", err + } + + // 使用私钥解密 + originalData, err := rsa.DecryptPKCS1v15(rand.Reader, priKey, encryptedDecodeBytes) + if err != nil { + return "", err + } + + // 返回原数据 + return string(originalData), nil +} diff --git a/am_smtp/smtp.go b/am_smtp/smtp.go new file mode 100644 index 0000000..c50c0b7 --- /dev/null +++ b/am_smtp/smtp.go @@ -0,0 +1,86 @@ +package am_smtp + +import ( + "bytes" + "gopkg.in/gomail.v2" + "html/template" +) + +// EmailConnection结构体 +type EmailConnection struct { + Server string // smtp服务器地址 + Port int // smtp服务器端口 + Username string // smtp登录名 + Password string // smtp密码,可以是授权码 +} + +/* +* 发送平信(纯文字信) +* 参数: +* 1. from string:发信邮箱地址 +* 2. to []string:寄送邮箱地址,可以多个 +* 3. subject string:邮件主题 +* 4. text string:邮件内容 +* 5. conn EmailConnection:邮件连接信息 + */ +func SendPlainMail( + from string, + to []string, + subject string, + text string, + conn EmailConnection, +) error { + message := gomail.NewMessage() + message.SetHeader("From", from) + message.SetHeader("To", to...) + message.SetHeader("Subject", subject) + message.SetBody("text/plain", text) + + d := gomail.NewDialer(conn.Server, conn.Port, conn.Username, conn.Password) + if err := d.DialAndSend(message); err != nil { + return err + } + + return nil +} + +/* +* 发送HTML信 +* 参数: +* 1. from string:发信邮箱地址 +* 2. to []string:寄送邮箱地址,可以多个 +* 3. subject string:邮件主题 +* 4. html string:邮件内容html模板所在的路径,必须能找到 +* 5. data interface{}:需要加载到html模板中的数据集合 +* 6. conn EmailConnection:邮件连接信息 + */ +func SendHTMLMail( + from string, + to []string, + subject string, + html string, + data interface{}, + conn EmailConnection, +) error { + tmpl, err := template.ParseFiles(html) + if err != nil { + return err + } + + var body bytes.Buffer + if err := tmpl.Execute(&body, data); err != nil { + return err + } + + message := gomail.NewMessage() + message.SetHeader("From", from) + message.SetHeader("To", to...) + message.SetHeader("Subject", subject) + message.SetBody("text/html", body.String()) + + d := gomail.NewDialer(conn.Server, conn.Port, conn.Username, conn.Password) + if err := d.DialAndSend(message); err != nil { + return err + } + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..19062ef --- /dev/null +++ b/go.mod @@ -0,0 +1,47 @@ +module github.com/axiomatrix-org/optimized-toolchain + +go 1.22 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/go-redis/redis v6.15.9+incompatible + golang.org/x/crypto v0.25.0 + gopkg.in/dgrijalva/jwt-go.v3 v3.2.0 + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df +) + +require ( + github.com/bytedance/sonic v1.11.9 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.33.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..09a4e1c --- /dev/null +++ b/go.sum @@ -0,0 +1,180 @@ +github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= +github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/dgrijalva/jwt-go.v3 v3.2.0 h1:N46iQqOtHry7Hxzb9PGrP68oovQmj7EhudNoKHvbOvI= +gopkg.in/dgrijalva/jwt-go.v3 v3.2.0/go.mod h1:hdNXC2Z9yC029rvsQ/on2ZNQ44Z2XToVhpXXbR+J05A= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=