mirror of
https://github.com/unti-io/go-utils.git
synced 2026-04-22 23:47:03 +08:00
v1.9.7
This commit is contained in:
@@ -7,4 +7,35 @@ go get github.com/inis-io/go-utils
|
||||
```
|
||||
|
||||
### 使用
|
||||
> 详细的使用方法请参考 [文档](./document/README.md)
|
||||
> 详细的使用方法请参考 [文档](./document/README.md)
|
||||
|
||||
### Storage 快速使用
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/inis-io/aide/dto"
|
||||
"github.com/inis-io/aide/facade"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1) 初始化全局存储(推荐在应用启动时执行一次)
|
||||
facade.StorageInst.Init(dto.StorageConfig{
|
||||
Engine: "local",
|
||||
Local: dto.LocalStorageConfig{Domain: "http://localhost:2000"},
|
||||
})
|
||||
|
||||
// 2) 使用全局实例
|
||||
file, _ := os.Open("./avatar.png")
|
||||
defer file.Close()
|
||||
resp := facade.Storage.Dir("avatar").Ext("png").Upload(file)
|
||||
_ = resp
|
||||
|
||||
// 3) 按配置创建独立实例(适合多租户或临时切换引擎)
|
||||
custom := facade.Storage.NewStorage(dto.StorageConfig{Engine: "local"})
|
||||
_ = custom
|
||||
}
|
||||
```
|
||||
|
||||
+9
-11
@@ -87,35 +87,33 @@ type SmsBaoConfig struct {
|
||||
// SmsBody - 短信请求参数
|
||||
type SmsBody struct {
|
||||
// Target - 目标手机号或邮箱
|
||||
Target string
|
||||
Target string
|
||||
// Code - 自定义验证码
|
||||
Code string
|
||||
Code string
|
||||
// Length - 验证码长度
|
||||
Length int
|
||||
Length int
|
||||
// Template - 发送模板
|
||||
Template string
|
||||
// 主题(标题)
|
||||
Subject string
|
||||
Subject string
|
||||
// 昵称(发件人昵称)
|
||||
Nickname string
|
||||
// 用户名(收件人昵称)
|
||||
Username string
|
||||
// 过期时间(分钟)
|
||||
Expired int64
|
||||
Expired int64
|
||||
// 通信地址
|
||||
Address string
|
||||
Address string
|
||||
// 标题
|
||||
Title string
|
||||
Title string
|
||||
}
|
||||
|
||||
// SmsResp - 短信响应
|
||||
type SmsResp struct {
|
||||
// 错误信息
|
||||
Error error
|
||||
// 结果
|
||||
Result any
|
||||
Result any
|
||||
// 文本
|
||||
Text string
|
||||
Text string
|
||||
// 验证码
|
||||
VerifyCode string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package dto
|
||||
|
||||
type StorageConfig struct {
|
||||
// Engine - 存储驱动
|
||||
Engine string `json:"engine" default:"local"`
|
||||
// Local - 本地存储配置
|
||||
Local LocalStorageConfig `json:"local"`
|
||||
// OSS - 阿里OSS配置
|
||||
OSS OSS `json:"oss"`
|
||||
// COS - 腾讯COS配置
|
||||
COS COS `json:"cos"`
|
||||
// Hash - 计算配置是否发生变更
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
// LocalStorageConfig - 本地存储配置
|
||||
type LocalStorageConfig struct {
|
||||
// Domain - 本地存储域名
|
||||
Domain string `json:"domain" comment:"域名" validate:"omitempty,url" default:"http://localhost:2000"`
|
||||
}
|
||||
|
||||
// OSS - 阿里OSS配置
|
||||
type OSS struct {
|
||||
// AccessKeyId - 阿里云AccessKey ID
|
||||
AccessKeyId string `json:"access_key_id" comment:"AccessKey ID" validate:"required,alphaNum"`
|
||||
// AccessKeySecret - 阿里云AccessKey Secret
|
||||
AccessKeySecret string `json:"access_key_secret" comment:"AccessKey Secret" validate:"required,alphaNum"`
|
||||
// Endpoint - OSS 外网 Endpoint
|
||||
Endpoint string `json:"endpoint" comment:"endpoint" validate:"required,host" default:"oss-cn-guangzhou.aliyuncs.com"`
|
||||
// Bucket - OSS Bucket - 存储桶名称
|
||||
Bucket string `json:"bucket" comment:"存储桶名称" validate:"required,alphaDash"`
|
||||
// Domain - OSS 外网域名 - 用于访问 - 不填写则使用默认域名
|
||||
Domain string `json:"domain" comment:"外网域名" validate:"omitempty,url"`
|
||||
// Path - OSS 存储目录
|
||||
Path string `json:"path" comment:"存储目录" validate:"required" default:"inis"`
|
||||
}
|
||||
|
||||
// COS - 腾讯COS配置
|
||||
type COS struct {
|
||||
// AppId - 腾讯云COS AppId
|
||||
AppId string `json:"app_id" comment:"AppId" validate:"required,numeric"`
|
||||
// SecretId - 腾讯云COS SecretId
|
||||
SecretId string `json:"secret_id" comment:"SecretId" validate:"required,alphaNum"`
|
||||
// SecretKey - 腾讯云COS SecretKey
|
||||
SecretKey string `json:"secret_key" comment:"SecretKey" validate:"required,alphaNum"`
|
||||
// Bucket - COS Bucket - 存储桶名称
|
||||
Bucket string `json:"bucket" comment:"存储桶名称" validate:"required,alphaDash"`
|
||||
// Region - COS 所在地区,如这里的 ap-guangzhou(广州)
|
||||
Region string `json:"region" comment:"区域" validate:"required,alphaDash" default:"ap-guangzhou"`
|
||||
// Domain - COS 外网域名 - 用于访问 - 不填写则使用默认域名
|
||||
Domain string `json:"domain" comment:"外网域名" validate:"omitempty,url"`
|
||||
// Path - COS 存储目录
|
||||
Path string `json:"path" comment:"存储目录" default:"inis"`
|
||||
}
|
||||
+5
-10
@@ -423,9 +423,7 @@ func (this *RedisClass) Expired(second any) CacheAPI {
|
||||
// Has - 判断缓存是否存在
|
||||
func (this *RedisClass) Has(key string) (ok bool) {
|
||||
|
||||
if utils.Is.Empty(key) {
|
||||
return false
|
||||
}
|
||||
if utils.Is.Empty(key) { return false }
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -436,9 +434,7 @@ func (this *RedisClass) Has(key string) (ok bool) {
|
||||
// Get - 获取缓存
|
||||
func (this *RedisClass) Get(key string) (value any) {
|
||||
|
||||
if utils.Is.Empty(key) {
|
||||
return false
|
||||
}
|
||||
if utils.Is.Empty(key) { return false }
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -449,8 +445,8 @@ func (this *RedisClass) Get(key string) (value any) {
|
||||
|
||||
// Set - 设置缓存
|
||||
func (this *RedisClass) Set(key string, value any) (ok bool) {
|
||||
cache := this.clone()
|
||||
|
||||
cache := this.clone()
|
||||
if cache == nil || utils.Is.Empty(key) { return false }
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -468,10 +464,9 @@ func (this *RedisClass) Set(key string, value any) (ok bool) {
|
||||
|
||||
// Delete - 删除缓存
|
||||
func (this *RedisClass) Delete(key ...string) (ok bool) {
|
||||
|
||||
cache := this.clone()
|
||||
if cache == nil {
|
||||
return false
|
||||
}
|
||||
if cache == nil { return false }
|
||||
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
|
||||
+127
-193
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
||||
AliYunOpenApi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
AliYunSmsApi "github.com/alibabacloud-go/dysmsapi-20170525/v5/client"
|
||||
AliYunUtilV2 "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
@@ -36,19 +36,19 @@ func init() { SmsInst.Init() }
|
||||
|
||||
// normConfig - 统一配置默认值,避免不同项目接入时行为不一致
|
||||
func (this *SmsClass) normConfig(config dto.SmsConfig) dto.SmsConfig {
|
||||
|
||||
|
||||
config.Engine.Email = strings.ToLower(strings.TrimSpace(config.Engine.Email))
|
||||
if utils.Is.Empty(config.Engine.Email) || config.Engine.Email != "email" {
|
||||
config.Engine.Email = "email"
|
||||
}
|
||||
|
||||
|
||||
config.Engine.SMS = strings.ToLower(strings.TrimSpace(config.Engine.SMS))
|
||||
switch config.Engine.SMS {
|
||||
case "aliyun", "tencent", "smsbao":
|
||||
default:
|
||||
config.Engine.SMS = "aliyun"
|
||||
}
|
||||
|
||||
|
||||
if utils.Is.Empty(config.Email.Host) {
|
||||
config.Email.Host = "smtp.qq.com"
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (this *SmsClass) normConfig(config dto.SmsConfig) dto.SmsConfig {
|
||||
if utils.Is.Empty(config.Email.Subject) {
|
||||
config.Email.Subject = "邮件主题"
|
||||
}
|
||||
|
||||
|
||||
if utils.Is.Empty(config.AliYun.Endpoint) {
|
||||
config.AliYun.Endpoint = "dysmsapi.aliyuncs.com"
|
||||
}
|
||||
@@ -74,11 +74,11 @@ func (this *SmsClass) normConfig(config dto.SmsConfig) dto.SmsConfig {
|
||||
if utils.Is.Empty(config.Smsbao.BaseUrl) {
|
||||
config.Smsbao.BaseUrl = "https://api.smsbao.com"
|
||||
}
|
||||
|
||||
|
||||
if utils.Is.Empty(config.Hash) {
|
||||
config.Hash = utils.Hash.Sum32(utils.Json.Encode(config))
|
||||
}
|
||||
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -161,14 +161,14 @@ func (this *SmsClass) useDefaultSms() {
|
||||
func (this *SmsClass) setActiveSms(config dto.SmsConfig) {
|
||||
conf := SmsInst.normConfig(config)
|
||||
SmsInst.Config = conf
|
||||
|
||||
|
||||
GoMail = SmsInst.NewGoMail(conf)
|
||||
|
||||
|
||||
SmsAliYun = nil
|
||||
SmsTencent = nil
|
||||
SmsBao = nil
|
||||
SMS = GoMail
|
||||
|
||||
|
||||
switch SmsInst.normSmsMode(conf.Engine.SMS) {
|
||||
case "tencent":
|
||||
SmsTencent = SmsInst.NewSmsTencent(conf)
|
||||
@@ -203,15 +203,15 @@ func (this *SmsClass) setConfig(config dto.SmsConfig) *SmsClass {
|
||||
|
||||
// ReloadIfChanged - 当配置发生变化时重新加载短信服务
|
||||
func (this *SmsClass) ReloadIfChanged(config ...dto.SmsConfig) {
|
||||
|
||||
|
||||
if len(config) > 0 {
|
||||
this.setConfig(config[0])
|
||||
}
|
||||
|
||||
|
||||
if !this.HasConfig {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// hash 变化,说明配置有更新
|
||||
if this.Hash != this.Config.Hash {
|
||||
this.Init()
|
||||
@@ -220,16 +220,16 @@ func (this *SmsClass) ReloadIfChanged(config ...dto.SmsConfig) {
|
||||
|
||||
// Init 初始化 SMS
|
||||
func (this *SmsClass) Init(config ...dto.SmsConfig) {
|
||||
|
||||
|
||||
if len(config) > 0 {
|
||||
this.setConfig(config[0])
|
||||
}
|
||||
|
||||
|
||||
if !this.HasConfig {
|
||||
SmsInst.useDefaultSms()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.Config = SmsInst.normConfig(this.Config)
|
||||
this.Hash = this.Config.Hash
|
||||
SmsInst.setActiveSms(this.Config)
|
||||
@@ -262,7 +262,7 @@ type SmsAPI interface {
|
||||
// Len - 验证码长度
|
||||
Len(length int) SmsAPI
|
||||
// Send - 发送验证码
|
||||
Send(target ...any) *dto.SmsResp
|
||||
Send(target ...any) (*dto.SmsResp, error)
|
||||
// Subject - 主题(标题)
|
||||
Subject(subject string) SmsAPI
|
||||
// SetBody - 设置参数体
|
||||
@@ -280,14 +280,12 @@ type GoMailClass struct {
|
||||
// 配置
|
||||
Config dto.SmsConfig
|
||||
// 参数
|
||||
Body dto.SmsBody
|
||||
Body dto.SmsBody
|
||||
}
|
||||
|
||||
// clone - 克隆邮件实例(共享客户端,隔离上下文)
|
||||
func (this *GoMailClass) clone() *GoMailClass {
|
||||
if this == nil {
|
||||
return nil
|
||||
}
|
||||
if this == nil { return nil }
|
||||
clone := *this
|
||||
return &clone
|
||||
}
|
||||
@@ -353,47 +351,38 @@ func (this *GoMailClass) Nickname(nickname string) SmsAPI {
|
||||
}
|
||||
|
||||
// Send - 发送验证码
|
||||
func (this *GoMailClass) Send(target ...any) (response *dto.SmsResp) {
|
||||
response = &dto.SmsResp{}
|
||||
func (this *GoMailClass) Send(target ...any) (*dto.SmsResp, error) {
|
||||
|
||||
mail := this.clone()
|
||||
if mail == nil {
|
||||
response.Error = errors.New("email client is not initialized")
|
||||
return response
|
||||
}
|
||||
if mail.Client == nil {
|
||||
response.Error = errors.New("email client is not initialized")
|
||||
return response
|
||||
}
|
||||
|
||||
if mail == nil { return nil, errors.New("email client is not initialized") }
|
||||
|
||||
if mail.Client == nil { return nil, errors.New("email client is not initialized") }
|
||||
|
||||
// 这里的 target 是邮箱地址 - 优先级最高
|
||||
if len(target) > 0 {
|
||||
mail.Body.Target = cast.ToString(target[0])
|
||||
}
|
||||
|
||||
|
||||
socialType, err := utils.Identify.EmailOrPhone(mail.Body.Target)
|
||||
// 如果不是邮箱或手机号
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if socialType == "phone" {
|
||||
|
||||
sender := SmsInst.newWithConfig(mail.Config, mail.Config.Engine.SMS)
|
||||
if sender == nil {
|
||||
response.Error = errors.New("sms sender is not initialized")
|
||||
return response
|
||||
}
|
||||
if sender == nil { return nil, errors.New("sms sender is not initialized") }
|
||||
|
||||
return sender.SetBody(mail.Body).Send(mail.Body.Target)
|
||||
}
|
||||
|
||||
|
||||
// 如果自定义验证码为空,则生成一个验证码
|
||||
if utils.Is.Empty(mail.Body.Code) {
|
||||
mail.Body.Code = utils.Rand.Code(mail.Body.Length)
|
||||
}
|
||||
|
||||
subject := utils.Default(mail.Body.Subject, mail.Config.Email.Subject)
|
||||
|
||||
subject := utils.Default(mail.Body.Subject, mail.Config.Email.Subject)
|
||||
nickname := utils.Default(mail.Body.Nickname, mail.Config.Email.Nickname)
|
||||
|
||||
|
||||
item := gomail.NewMessage()
|
||||
// 设置邮件内容类型
|
||||
item.SetHeader("Content-Type", "text/html; charset=UTF-8")
|
||||
@@ -417,22 +406,21 @@ func (this *GoMailClass) Send(target ...any) (response *dto.SmsResp) {
|
||||
})
|
||||
// 设置邮件正文
|
||||
item.SetBody("text/html", temp)
|
||||
|
||||
|
||||
// 发送邮件
|
||||
// 使用明确的 “拨号 + 发送” 操作,以确保在执行AUTH和MAIL命令之前完成SMTP握手(EHLO/STARTTLS)。
|
||||
sender, err := mail.Client.Dial()
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
if err != nil { return nil, err }
|
||||
|
||||
defer func() { _ = sender.Close() }()
|
||||
|
||||
|
||||
if err := gomail.Send(sender, item); err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
return nil, err
|
||||
}
|
||||
response.VerifyCode = mail.Body.Code
|
||||
return response
|
||||
|
||||
return &dto.SmsResp{
|
||||
VerifyCode: mail.Body.Code,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetBody - 设置参数体
|
||||
@@ -473,14 +461,14 @@ func (this *SmsAliYunClass) clone() *SmsAliYunClass {
|
||||
func (this *SmsAliYunClass) Init() {
|
||||
this.Config = SmsInst.normConfig(this.Config)
|
||||
this.Body = SmsInst.mergeSmsBody(SmsInst.defaultSmsBody(), this.Body)
|
||||
|
||||
|
||||
// 创建访问凭证
|
||||
credential, err := AliYunCredential.NewCredential(nil)
|
||||
// 凭证创建失败
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 创建客户端
|
||||
client, err := AliYunSmsApi.NewClient(&AliYunOpenApi.Config{
|
||||
Credential: credential,
|
||||
@@ -495,7 +483,7 @@ func (this *SmsAliYunClass) Init() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.Client = client
|
||||
}
|
||||
|
||||
@@ -572,41 +560,34 @@ func (this *SmsAliYunClass) Nickname(nickname string) SmsAPI {
|
||||
}
|
||||
|
||||
// Send - 发送验证码
|
||||
func (this *SmsAliYunClass) Send(target ...any) (response *dto.SmsResp) {
|
||||
response = &dto.SmsResp{}
|
||||
func (this *SmsAliYunClass) Send(target ...any) (*dto.SmsResp, error) {
|
||||
|
||||
sms := this.clone()
|
||||
if sms == nil {
|
||||
response.Error = errors.New("aliyun sms client is not initialized")
|
||||
return response
|
||||
}
|
||||
if sms.Client == nil {
|
||||
response.Error = errors.New("aliyun sms client is not initialized")
|
||||
return response
|
||||
}
|
||||
|
||||
if sms == nil { return nil, errors.New("aliyun sms client is not initialized") }
|
||||
|
||||
if sms.Client == nil { return nil, errors.New("aliyun sms client is not initialized") }
|
||||
|
||||
// 这里的 target 是手机号 - 优先级最高
|
||||
if len(target) > 0 {
|
||||
sms.Body.Target = cast.ToString(target[0])
|
||||
}
|
||||
|
||||
|
||||
// 如果不是邮箱或手机号
|
||||
if attr, err := utils.Identify.EmailOrPhone(sms.Body.Target); err != nil {
|
||||
response.Error = err
|
||||
return
|
||||
return nil, err
|
||||
} else if attr == "email" {
|
||||
sender := SmsInst.newWithConfig(sms.Config, sms.Config.Engine.Email)
|
||||
if sender == nil {
|
||||
response.Error = errors.New("email sender is not initialized")
|
||||
return response
|
||||
return nil, errors.New("email sender is not initialized")
|
||||
}
|
||||
return sender.SetBody(sms.Body).Send(sms.Body.Target)
|
||||
}
|
||||
|
||||
|
||||
// 如果自定义验证码为空,则生成一个验证码
|
||||
if utils.Is.Empty(sms.Body.Code) {
|
||||
sms.Body.Code = utils.Rand.Code(sms.Body.Length)
|
||||
}
|
||||
|
||||
|
||||
params := &AliYunSmsApi.SendSmsRequest{
|
||||
PhoneNumbers: tea.String(sms.Body.Target),
|
||||
SignName: tea.String(sms.Config.AliYun.SignName),
|
||||
@@ -616,26 +597,23 @@ func (this *SmsAliYunClass) Send(target ...any) (response *dto.SmsResp) {
|
||||
"time": sms.Body.Expired,
|
||||
})),
|
||||
}
|
||||
|
||||
|
||||
resp, err := sms.Client.SendSmsWithOptions(params, &AliYunUtilV2.RuntimeOptions{})
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if resp == nil || resp.Body == nil || resp.Body.Code == nil {
|
||||
response.Error = errors.New("aliyun sms response is nil")
|
||||
return response
|
||||
return nil, errors.New("aliyun sms response is nil")
|
||||
}
|
||||
|
||||
|
||||
if strings.ToLower(*resp.Body.Code) != "ok" {
|
||||
response.Error = errors.New(cast.ToString(tea.StringValue(resp.Body.Message)))
|
||||
return response
|
||||
return nil, errors.New(cast.ToString(tea.StringValue(resp.Body.Message)))
|
||||
}
|
||||
|
||||
response.Result = cast.ToStringMap(*resp.Body)
|
||||
response.Text = utils.Json.Encode(*resp.Body)
|
||||
response.VerifyCode = sms.Body.Code
|
||||
return response
|
||||
|
||||
return &dto.SmsResp{
|
||||
Result: cast.ToStringMap(*resp.Body),
|
||||
Text: utils.Json.Encode(*resp.Body),
|
||||
VerifyCode: sms.Body.Code,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetBody - 设置参数体
|
||||
@@ -676,18 +654,18 @@ func (this *SmsTencentClass) clone() *SmsTencentClass {
|
||||
func (this *SmsTencentClass) Init() {
|
||||
this.Config = SmsInst.normConfig(this.Config)
|
||||
this.Body = SmsInst.mergeSmsBody(SmsInst.defaultSmsBody(), this.Body)
|
||||
|
||||
|
||||
credential := common.NewCredential(this.Config.Tencent.SecretId, this.Config.Tencent.SecretKey)
|
||||
clientProfile := profile.NewClientProfile()
|
||||
// sms.tencentcloudapi.com
|
||||
clientProfile.HttpProfile.Endpoint = this.Config.Tencent.Endpoint
|
||||
// ap-guangzhou
|
||||
client, err := TencentCloud.NewClient(credential, this.Config.Tencent.Region, clientProfile)
|
||||
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
this.Client = client
|
||||
}
|
||||
|
||||
@@ -742,83 +720,61 @@ func (this *SmsTencentClass) Nickname(nickname string) SmsAPI {
|
||||
}
|
||||
|
||||
// Send - 发送验证码
|
||||
func (this *SmsTencentClass) Send(target ...any) (response *dto.SmsResp) {
|
||||
response = &dto.SmsResp{}
|
||||
func (this *SmsTencentClass) Send(target ...any) (*dto.SmsResp, error) {
|
||||
|
||||
sms := this.clone()
|
||||
if sms == nil {
|
||||
response.Error = errors.New("tencent sms client is not initialized")
|
||||
return response
|
||||
}
|
||||
if sms.Client == nil {
|
||||
response.Error = errors.New("tencent sms client is not initialized")
|
||||
return response
|
||||
}
|
||||
|
||||
if sms == nil { return nil, errors.New("tencent sms client is not initialized") }
|
||||
|
||||
if sms.Client == nil { return nil, errors.New("tencent sms client is not initialized") }
|
||||
|
||||
// 这里的 target 是手机号 - 优先级最高
|
||||
if len(target) > 0 {
|
||||
sms.Body.Target = cast.ToString(target[0])
|
||||
}
|
||||
|
||||
|
||||
socialType, err := utils.Identify.EmailOrPhone(sms.Body.Target)
|
||||
// 如果不是邮箱或手机号
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if socialType == "email" {
|
||||
|
||||
sender := SmsInst.newWithConfig(sms.Config, sms.Config.Engine.Email)
|
||||
if sender == nil {
|
||||
response.Error = errors.New("email sender is not initialized")
|
||||
return response
|
||||
}
|
||||
if sender == nil { return nil, errors.New("email sender is not initialized") }
|
||||
|
||||
return sender.SetBody(sms.Body).Send(sms.Body.Target)
|
||||
}
|
||||
|
||||
|
||||
// 如果自定义验证码为空,则生成一个验证码
|
||||
if utils.Is.Empty(sms.Body.Code) {
|
||||
sms.Body.Code = utils.Rand.Code(sms.Body.Length)
|
||||
}
|
||||
|
||||
|
||||
// 实例化一个请求对象,每个接口都会对应一个request对象
|
||||
request := TencentCloud.NewSendSmsRequest()
|
||||
|
||||
|
||||
request.PhoneNumberSet = common.StringPtrs([]string{sms.Body.Target})
|
||||
request.SmsSdkAppId = common.StringPtr(sms.Config.Tencent.SmsSdkAppId)
|
||||
request.SignName = common.StringPtr(sms.Config.Tencent.SignName)
|
||||
request.TemplateId = common.StringPtr(sms.Config.Tencent.VerifyCode)
|
||||
request.TemplateParamSet = common.StringPtrs([]string{sms.Body.Code})
|
||||
|
||||
|
||||
item, err := sms.Client.SendSms(request)
|
||||
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return response
|
||||
}
|
||||
|
||||
if item.Response == nil {
|
||||
response.Error = errors.New("response is nil")
|
||||
return response
|
||||
}
|
||||
|
||||
if len(item.Response.SendStatusSet) == 0 {
|
||||
response.Error = errors.New("response send status set is nil")
|
||||
return response
|
||||
}
|
||||
if item.Response.SendStatusSet[0].Code == nil {
|
||||
response.Error = errors.New("response send status code is nil")
|
||||
return response
|
||||
}
|
||||
|
||||
if *item.Response.SendStatusSet[0].Code != "Ok" {
|
||||
response.Error = errors.New(cast.ToString(item.Response.SendStatusSet[0].Message))
|
||||
return response
|
||||
}
|
||||
|
||||
response.VerifyCode = sms.Body.Code
|
||||
response.Text = item.ToJsonString()
|
||||
response.Result = utils.Json.Decode(item.ToJsonString())
|
||||
return response
|
||||
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if item.Response == nil { return nil, errors.New("response is nil") }
|
||||
|
||||
if len(item.Response.SendStatusSet) == 0 { return nil, errors.New("response send status set is nil") }
|
||||
|
||||
if item.Response.SendStatusSet[0].Code == nil { return nil, errors.New("response send status code is nil") }
|
||||
|
||||
if *item.Response.SendStatusSet[0].Code != "Ok" { return nil, errors.New(cast.ToString(item.Response.SendStatusSet[0].Message)) }
|
||||
|
||||
return &dto.SmsResp{
|
||||
Result: utils.Json.Decode(item.ToJsonString()),
|
||||
Text: item.ToJsonString(),
|
||||
VerifyCode: sms.Body.Code,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetBody - 设置参数体
|
||||
@@ -915,52 +871,35 @@ func (this *SmsBaoClass) Nickname(nickname string) SmsAPI {
|
||||
}
|
||||
|
||||
// Send - 发送验证码
|
||||
func (this *SmsBaoClass) Send(target ...any) (response *dto.SmsResp) {
|
||||
func (this *SmsBaoClass) Send(target ...any) (*dto.SmsResp, error) {
|
||||
|
||||
response = &dto.SmsResp{}
|
||||
sms := this.clone()
|
||||
if sms == nil { return nil, errors.New("smsbao sender is not initialized") }
|
||||
|
||||
if sms == nil {
|
||||
response.Error = errors.New("smsbao sender is not initialized")
|
||||
return response
|
||||
}
|
||||
|
||||
// 这里的 target 是手机号 - 优先级最高
|
||||
if len(target) > 0 {
|
||||
sms.Body.Target = cast.ToString(target[0])
|
||||
}
|
||||
|
||||
|
||||
socialType, err := utils.Identify.EmailOrPhone(sms.Body.Target)
|
||||
// 如果不是邮箱或手机号
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if socialType == "email" {
|
||||
sender := SmsInst.newWithConfig(sms.Config, sms.Config.Engine.Email)
|
||||
if sender == nil {
|
||||
response.Error = errors.New("email sender is not initialized")
|
||||
return response
|
||||
}
|
||||
if sender == nil { return nil, errors.New("email sender is not initialized") }
|
||||
return sender.SetBody(sms.Body).Send(sms.Body.Target)
|
||||
}
|
||||
|
||||
|
||||
// 如果自定义验证码为空,则生成一个验证码
|
||||
if utils.Is.Empty(sms.Body.Code) {
|
||||
sms.Body.Code = utils.Rand.Code(sms.Body.Length)
|
||||
}
|
||||
|
||||
if utils.Is.Empty(sms.ApiKey) {
|
||||
response.Error = errors.New("API密钥不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if utils.Is.Empty(sms.Account) {
|
||||
response.Error = errors.New("账号不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if utils.Is.Empty(sms.ApiKey) { return nil, errors.New("API密钥不能为空") }
|
||||
|
||||
if utils.Is.Empty(sms.Account) { return nil, errors.New("账号不能为空") }
|
||||
|
||||
item := utils.Curl(utils.CurlRequest{
|
||||
Method: "GET",
|
||||
Url: fmt.Sprintf("%s/sms", sms.BaseUrl),
|
||||
@@ -973,20 +912,15 @@ func (this *SmsBaoClass) Send(target ...any) (response *dto.SmsResp) {
|
||||
}),
|
||||
},
|
||||
}).Send()
|
||||
|
||||
if item.Error != nil {
|
||||
response.Error = item.Error
|
||||
return
|
||||
}
|
||||
|
||||
if cast.ToInt(item.Text) != 0 {
|
||||
response.Error = errors.New("发送失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.VerifyCode = sms.Body.Code
|
||||
response.Text = item.Text
|
||||
return response
|
||||
|
||||
if item.Error != nil { return nil, item.Error }
|
||||
|
||||
if cast.ToInt(item.Text) != 0 { return nil, errors.New("发送失败") }
|
||||
|
||||
return &dto.SmsResp{
|
||||
Text: item.Text,
|
||||
VerifyCode: sms.Body.Code,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetBody - 设置参数体
|
||||
@@ -1000,4 +934,4 @@ func (this *SmsBaoClass) SetBody(body dto.SmsBody) SmsAPI {
|
||||
// NewSms - 使用传入配置创建短信实例
|
||||
func (this *SmsBaoClass) NewSms(config dto.SmsConfig) SmsAPI {
|
||||
return SmsInst.newWithConfig(config, "smsbao")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,706 @@
|
||||
package facade
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
pathpkg "path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/inis-io/aide/dto"
|
||||
"github.com/inis-io/aide/utils"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/tencentyun/cos-go-sdk-v5"
|
||||
)
|
||||
|
||||
var StorageInst = &StorageClass{}
|
||||
|
||||
type StorageClass struct {
|
||||
// 记录配置 Hash 值,用于检测配置文件是否有变化
|
||||
Hash string `json:"hash"`
|
||||
// 当前存储配置(由调用方注入)
|
||||
Config dto.StorageConfig `json:"config"`
|
||||
// 是否已经注入过配置
|
||||
HasConfig bool `json:"hasConfig"`
|
||||
// 读写锁,保护配置和Hash的并发访问
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func init() { StorageInst.Init() }
|
||||
|
||||
// normConfig 统一配置默认值,避免不同项目接入时行为不一致
|
||||
func (this *StorageClass) normConfig(config dto.StorageConfig) dto.StorageConfig {
|
||||
|
||||
config.Engine = strings.ToLower(strings.TrimSpace(config.Engine))
|
||||
switch config.Engine {
|
||||
case "oss", "cos", "local":
|
||||
default:
|
||||
config.Engine = "local"
|
||||
}
|
||||
|
||||
if utils.Is.Empty(config.Local.Domain) {
|
||||
config.Local.Domain = "http://localhost:2000"
|
||||
}
|
||||
|
||||
if utils.Is.Empty(config.OSS.Endpoint) {
|
||||
config.OSS.Endpoint = "oss-cn-guangzhou.aliyuncs.com"
|
||||
}
|
||||
if utils.Is.Empty(config.OSS.Path) {
|
||||
config.OSS.Path = "inis"
|
||||
}
|
||||
|
||||
if utils.Is.Empty(config.COS.Region) {
|
||||
config.COS.Region = "ap-guangzhou"
|
||||
}
|
||||
if utils.Is.Empty(config.COS.Path) {
|
||||
config.COS.Path = "inis"
|
||||
}
|
||||
|
||||
if utils.Is.Empty(config.Hash) {
|
||||
config.Hash = utils.Hash.Sum32(utils.Json.Encode(config))
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
}
|
||||
|
||||
// defaultConfig - 获取默认存储配置
|
||||
func (this *StorageClass) defaultConfig() dto.StorageConfig {
|
||||
return StorageInst.normConfig(dto.StorageConfig{})
|
||||
}
|
||||
|
||||
// useDefaultStorage - 使用默认配置激活存储
|
||||
func (this *StorageClass) useDefaultStorage() {
|
||||
conf := StorageInst.defaultConfig()
|
||||
|
||||
this.mu.Lock()
|
||||
this.Config = conf
|
||||
this.Hash = conf.Hash
|
||||
this.HasConfig = false
|
||||
this.mu.Unlock()
|
||||
|
||||
StorageInst.setActiveStorage(conf)
|
||||
}
|
||||
|
||||
// setActiveStorage - 按配置切换当前活动存储实现
|
||||
func (this *StorageClass) setActiveStorage(config dto.StorageConfig) {
|
||||
|
||||
conf := StorageInst.normConfig(config)
|
||||
|
||||
this.mu.Lock()
|
||||
this.Config = conf
|
||||
this.mu.Unlock()
|
||||
|
||||
Storage = StorageInst.newWithConfig(conf)
|
||||
|
||||
LocalStorage = nil
|
||||
OSS = nil
|
||||
COS = nil
|
||||
|
||||
switch impl := Storage.(type) {
|
||||
case *LocalStorageClass:
|
||||
LocalStorage = impl
|
||||
case *OssClass:
|
||||
OSS = impl
|
||||
case *CosClass:
|
||||
COS = impl
|
||||
}
|
||||
}
|
||||
|
||||
// newWithConfig - 按配置创建新的存储实现
|
||||
func (this *StorageClass) newWithConfig(config dto.StorageConfig) StorageAPI {
|
||||
conf := StorageInst.normConfig(config)
|
||||
|
||||
switch conf.Engine {
|
||||
case "oss":
|
||||
item := &OssClass{Config: conf}
|
||||
item.Init()
|
||||
if item.Client != nil {
|
||||
return item
|
||||
}
|
||||
case "cos":
|
||||
item := &CosClass{Config: conf}
|
||||
item.Init()
|
||||
if item.Client != nil {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
return &LocalStorageClass{Config: conf}
|
||||
}
|
||||
|
||||
// setConfig - 注入存储配置
|
||||
func (this *StorageClass) setConfig(config dto.StorageConfig) *StorageClass {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
|
||||
this.Config = StorageInst.normConfig(config)
|
||||
this.HasConfig = true
|
||||
return this
|
||||
}
|
||||
|
||||
// ReloadIfChanged - 当配置发生变化时重新加载存储
|
||||
func (this *StorageClass) ReloadIfChanged(config ...dto.StorageConfig) {
|
||||
|
||||
if len(config) > 0 {
|
||||
this.setConfig(config[0])
|
||||
}
|
||||
|
||||
this.mu.RLock()
|
||||
hasConfig := this.HasConfig
|
||||
hash := this.Hash
|
||||
confHash := this.Config.Hash
|
||||
this.mu.RUnlock()
|
||||
|
||||
if !hasConfig {
|
||||
return
|
||||
}
|
||||
|
||||
// hash 变化,说明配置有更新
|
||||
if hash != confHash {
|
||||
this.Init()
|
||||
}
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (this *StorageClass) Init(config ...dto.StorageConfig) {
|
||||
|
||||
if len(config) > 0 {
|
||||
this.setConfig(config[0])
|
||||
}
|
||||
|
||||
this.mu.RLock()
|
||||
hasConfig := this.HasConfig
|
||||
current := this.Config
|
||||
this.mu.RUnlock()
|
||||
|
||||
if !hasConfig {
|
||||
StorageInst.useDefaultStorage()
|
||||
return
|
||||
}
|
||||
|
||||
conf := StorageInst.normConfig(current)
|
||||
|
||||
this.mu.Lock()
|
||||
this.Config = conf
|
||||
this.Hash = conf.Hash
|
||||
this.mu.Unlock()
|
||||
|
||||
StorageInst.setActiveStorage(conf)
|
||||
|
||||
}
|
||||
|
||||
// Storage - Storage实例
|
||||
/**
|
||||
* @return StorageAPI
|
||||
* @example:
|
||||
* storage := facade.Storage.Upload(facade.Storage.Path() + suffix, bytes)
|
||||
*/
|
||||
var Storage StorageAPI
|
||||
var OSS *OssClass
|
||||
var COS *CosClass
|
||||
var LocalStorage *LocalStorageClass
|
||||
|
||||
|
||||
// StorageResp - 存储响应
|
||||
type StorageResp struct {
|
||||
Error error
|
||||
Path string
|
||||
Domain string
|
||||
Name string
|
||||
}
|
||||
|
||||
// StorageParams - 存储参数
|
||||
type StorageParams struct {
|
||||
// Dir - 存储目录
|
||||
Dir string
|
||||
// Name - 存储文件名
|
||||
Name string
|
||||
// Ext - 存储文件后缀
|
||||
Ext string
|
||||
}
|
||||
|
||||
// StorageAPI 定义了存储操作的接口。
|
||||
type StorageAPI interface {
|
||||
// Upload 上传文件
|
||||
/**
|
||||
* @param reader io.Reader - 读取器
|
||||
* @returns StorageAPI - 存储接口
|
||||
*/
|
||||
Upload(reader io.Reader) *StorageResp
|
||||
|
||||
// Dir 设置存储的目录
|
||||
/**
|
||||
* @param dir string - 目录
|
||||
* @returns StorageAPI - 存储接口
|
||||
*/
|
||||
Dir(dir string) StorageAPI
|
||||
|
||||
// Name 设置存储文件的名称
|
||||
/**
|
||||
* @param name string - 名称
|
||||
* @returns StorageAPI - 存储接口
|
||||
*/
|
||||
Name(name string) StorageAPI
|
||||
|
||||
// Ext 设置存储文件的后缀
|
||||
/**
|
||||
* @param ext string - 后缀
|
||||
* @returns StorageAPI - 存储接口
|
||||
*/
|
||||
Ext(ext string) StorageAPI
|
||||
|
||||
// NewStorage - 使用传入配置创建新的存储实例
|
||||
NewStorage(config dto.StorageConfig) StorageAPI
|
||||
}
|
||||
|
||||
// cleanDir - 标准化目录,确保目录以 / 结尾
|
||||
func (this *StorageClass) cleanDir(dir string) string {
|
||||
if !utils.Is.Empty(dir) && !strings.HasSuffix(dir, "/") {
|
||||
dir += "/"
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// cleanExt - 标准化后缀,确保以 . 开头
|
||||
func (this *StorageClass) cleanExt(ext string) string {
|
||||
if !utils.Is.Empty(ext) && !strings.HasPrefix(ext, ".") {
|
||||
ext = "." + ext
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
// fileNameFromPath - 提取路径中的文件名
|
||||
func (this *StorageClass) fileNameFromPath(path string) string {
|
||||
name := pathpkg.Base(strings.TrimSpace(path))
|
||||
if name == "." || name == "/" {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// =================================== 本地存储存储 - 开始 ===================================
|
||||
|
||||
// LocalStorageClass 本地存储
|
||||
type LocalStorageClass struct {
|
||||
// 配置
|
||||
Config dto.StorageConfig
|
||||
// 参数
|
||||
Params StorageParams
|
||||
}
|
||||
|
||||
// clone - 克隆本地存储实例(共享配置,隔离链式参数)
|
||||
func (this *LocalStorageClass) clone() *LocalStorageClass {
|
||||
if this == nil {
|
||||
return nil
|
||||
}
|
||||
clone := *this
|
||||
return &clone
|
||||
}
|
||||
|
||||
// Upload - 上传文件
|
||||
func (this *LocalStorageClass) Upload(reader io.Reader) (response *StorageResp) {
|
||||
|
||||
response = &StorageResp{}
|
||||
|
||||
path := this.Path()
|
||||
item := utils.File().Save(reader, path)
|
||||
|
||||
if item.Error != nil {
|
||||
response.Error = item.Error
|
||||
return
|
||||
}
|
||||
|
||||
// 去除前面的 public
|
||||
response.Path = strings.Replace(path, "public", "", 1)
|
||||
response.Domain = this.Config.Local.Domain
|
||||
|
||||
response.Name = StorageInst.fileNameFromPath(path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path - 本地存储位置 - 生成文件路径
|
||||
func (this *LocalStorageClass) Path() (path string) {
|
||||
|
||||
// 生成文件名 - 年月日+毫秒时间戳
|
||||
name := cast.ToString(time.Now().UnixNano() / 1e6)
|
||||
// 生成年月日目录 - 如:2023-04/10
|
||||
dir := time.Now().Format("2006-01/02/")
|
||||
|
||||
// 自定义目录
|
||||
if !utils.Is.Empty(this.Params.Dir) {
|
||||
dir = this.Params.Dir
|
||||
}
|
||||
// 自定义文件名
|
||||
if !utils.Is.Empty(this.Params.Name) {
|
||||
name = this.Params.Name
|
||||
}
|
||||
|
||||
// 得到文件路径 - 但是可能还存在重复的 /
|
||||
path = strings.Join([]string{"public", "storage", dir}, "/")
|
||||
// 替换重复的 / - 重新生成文件路径
|
||||
path = strings.Join(cast.ToStringSlice(utils.ArrayEmpty(strings.Split(path, "/"))), "/")
|
||||
// 如果不是以 / 结尾
|
||||
if !strings.HasSuffix(path, "/") { path += "/" }
|
||||
|
||||
return path + name + this.Params.Ext
|
||||
}
|
||||
|
||||
// Dir - 本地存储位置 - 生成文件目录
|
||||
func (this *LocalStorageClass) Dir(dir string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Dir = StorageInst.cleanDir(dir)
|
||||
return item
|
||||
}
|
||||
|
||||
// Name - 本地存储位置 - 生成文件名
|
||||
func (this *LocalStorageClass) Name(name string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Name = name
|
||||
return item
|
||||
}
|
||||
|
||||
// Ext - 本地存储位置 - 生成文件后缀
|
||||
func (this *LocalStorageClass) Ext(ext string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Ext = StorageInst.cleanExt(ext)
|
||||
return item
|
||||
}
|
||||
|
||||
// NewStorage - 使用传入配置创建存储实例
|
||||
func (this *LocalStorageClass) NewStorage(config dto.StorageConfig) StorageAPI {
|
||||
return StorageInst.newWithConfig(config)
|
||||
}
|
||||
|
||||
// ================================== 阿里云对象存储 - 开始 ==================================
|
||||
|
||||
// OssClass 阿里云对象存储
|
||||
type OssClass struct {
|
||||
// OSS客户端
|
||||
Client *oss.Client
|
||||
// 配置
|
||||
Config dto.StorageConfig
|
||||
// 参数
|
||||
Params StorageParams
|
||||
}
|
||||
|
||||
// clone - 克隆 OSS 存储实例(共享客户端,隔离链式参数)
|
||||
func (this *OssClass) clone() *OssClass {
|
||||
if this == nil {
|
||||
return nil
|
||||
}
|
||||
clone := *this
|
||||
return &clone
|
||||
}
|
||||
|
||||
// Init 初始化 阿里云对象存储
|
||||
func (this *OssClass) Init() {
|
||||
this.Config = StorageInst.normConfig(this.Config)
|
||||
|
||||
client, err := oss.New(this.Config.OSS.Endpoint, this.Config.OSS.AccessKeyId, this.Config.OSS.AccessKeySecret)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
this.Client = client
|
||||
}
|
||||
|
||||
// Bucket - 获取Bucket(存储桶)
|
||||
func (this *OssClass) Bucket() *oss.Bucket {
|
||||
if this.Client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
exist, err := this.Client.IsBucketExist(this.Config.OSS.Bucket)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !exist {
|
||||
// 创建存储空间。
|
||||
err = this.Client.CreateBucket(this.Config.OSS.Bucket)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
bucket, err := this.Client.Bucket(this.Config.OSS.Bucket)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bucket
|
||||
}
|
||||
|
||||
// Upload - 上传文件
|
||||
func (this *OssClass) Upload(reader io.Reader) (response *StorageResp) {
|
||||
|
||||
response = &StorageResp{}
|
||||
|
||||
path := this.Path()
|
||||
bucket := this.Bucket()
|
||||
if bucket == nil {
|
||||
response.Error = fmt.Errorf("OSS Bucket 获取失败")
|
||||
return
|
||||
}
|
||||
if err := bucket.PutObject(path, reader); err != nil {
|
||||
response.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
if utils.Is.Empty(this.Config.OSS.Domain) {
|
||||
response.Domain = "https://" + this.Config.OSS.Bucket + "." + this.Config.OSS.Endpoint
|
||||
} else {
|
||||
response.Domain = this.Config.OSS.Domain
|
||||
}
|
||||
|
||||
response.Path = "/" + path
|
||||
|
||||
response.Name = StorageInst.fileNameFromPath(path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path - OSS存储位置 - 生成文件路径
|
||||
func (this *OssClass) Path() (path string) {
|
||||
|
||||
// 生成文件名 - 年月日+毫秒时间戳
|
||||
name := cast.ToString(time.Now().UnixNano() / 1e6)
|
||||
// 存储根目录
|
||||
root := this.Config.OSS.Path
|
||||
// 生成年月日目录 - 如:2023-04/10
|
||||
dir := time.Now().Format("2006-01/02/")
|
||||
|
||||
// 自定义目录
|
||||
if !utils.Is.Empty(this.Params.Dir) {
|
||||
dir = this.Params.Dir
|
||||
}
|
||||
// 自定义文件名
|
||||
if !utils.Is.Empty(this.Params.Name) {
|
||||
name = this.Params.Name
|
||||
}
|
||||
|
||||
// 得到文件路径 - 但是可能还存在重复的 /
|
||||
path = strings.Join([]string{root, dir}, "/")
|
||||
// 替换重复的 / - 重新生成文件路径
|
||||
path = strings.Join(cast.ToStringSlice(utils.ArrayEmpty(strings.Split(path, "/"))), "/")
|
||||
// 如果不是以 / 结尾
|
||||
if !strings.HasSuffix(path, "/") { path += "/" }
|
||||
|
||||
return path + name + this.Params.Ext
|
||||
}
|
||||
|
||||
// Dir - 本地存储位置 - 生成文件目录
|
||||
func (this *OssClass) Dir(dir string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Dir = StorageInst.cleanDir(dir)
|
||||
return item
|
||||
}
|
||||
|
||||
// Name - 本地存储位置 - 生成文件名
|
||||
func (this *OssClass) Name(name string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Name = name
|
||||
return item
|
||||
}
|
||||
|
||||
// Ext - 本地存储位置 - 生成文件后缀
|
||||
func (this *OssClass) Ext(ext string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Ext = StorageInst.cleanExt(ext)
|
||||
return item
|
||||
}
|
||||
|
||||
// NewStorage - 使用传入配置创建存储实例
|
||||
func (this *OssClass) NewStorage(config dto.StorageConfig) StorageAPI {
|
||||
return StorageInst.newWithConfig(config)
|
||||
}
|
||||
|
||||
// ================================== 腾讯云对象存储 - 开始 ==================================
|
||||
|
||||
// CosClass 腾讯云对象存储
|
||||
type CosClass struct {
|
||||
// COS客户端
|
||||
Client *cos.Client
|
||||
// 配置
|
||||
Config dto.StorageConfig
|
||||
// 参数
|
||||
Params StorageParams
|
||||
}
|
||||
|
||||
// clone - 克隆 COS 存储实例(共享客户端,隔离链式参数)
|
||||
func (this *CosClass) clone() *CosClass {
|
||||
if this == nil {
|
||||
return nil
|
||||
}
|
||||
clone := *this
|
||||
return &clone
|
||||
}
|
||||
|
||||
// Init 初始化 腾讯云对象存储
|
||||
func (this *CosClass) Init() {
|
||||
this.Config = StorageInst.normConfig(this.Config)
|
||||
|
||||
cosUrl, err := url.Parse(fmt.Sprintf("https://%s-%s.cos.%s.myqcloud.com", this.Config.COS.Bucket, this.Config.COS.AppId, this.Config.COS.Region))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
this.Client = cos.NewClient(&cos.BaseURL{
|
||||
BucketURL: cosUrl,
|
||||
}, &http.Client{
|
||||
// 设置超时时间
|
||||
Timeout: 100 * time.Second,
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: this.Config.COS.SecretId,
|
||||
SecretKey: this.Config.COS.SecretKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Object - 获取Object(对象存储)
|
||||
func (this *CosClass) Object() *cos.ObjectService {
|
||||
if this.Client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查询存储桶
|
||||
exist, err := this.Client.Bucket.IsExist(context.Background())
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !exist {
|
||||
// 创建存储桶 - 默认公共读私有写
|
||||
_, err = this.Client.Bucket.Put(context.Background(), &cos.BucketPutOptions{
|
||||
XCosACL: "public-read",
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return this.Client.Object
|
||||
}
|
||||
|
||||
// Upload - 上传文件
|
||||
func (this *CosClass) Upload(reader io.Reader) (response *StorageResp) {
|
||||
|
||||
response = &StorageResp{}
|
||||
|
||||
path := this.Path()
|
||||
object := this.Object()
|
||||
if object == nil {
|
||||
response.Error = fmt.Errorf("COS Object 获取失败")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := object.Put(context.Background(), path, reader, nil)
|
||||
if err != nil {
|
||||
response.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
if utils.Is.Empty(this.Config.COS.Domain) {
|
||||
response.Domain = fmt.Sprintf("https://%s-%s.cos.%s.myqcloud.com", this.Config.COS.Bucket, this.Config.COS.AppId, this.Config.COS.Region)
|
||||
} else {
|
||||
response.Domain = this.Config.COS.Domain
|
||||
}
|
||||
|
||||
response.Path = "/" + path
|
||||
|
||||
response.Name = StorageInst.fileNameFromPath(path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path - COS存储位置 - 生成文件路径
|
||||
func (this *CosClass) Path() (path string) {
|
||||
|
||||
// 生成文件名 - 年月日+毫秒时间戳
|
||||
name := cast.ToString(time.Now().UnixNano() / 1e6)
|
||||
// 存储根目录
|
||||
root := this.Config.COS.Path
|
||||
// 生成年月日目录 - 如:2023-04/10
|
||||
dir := time.Now().Format("2006-01/02/")
|
||||
|
||||
// 自定义目录
|
||||
if !utils.Is.Empty(this.Params.Dir) {
|
||||
dir = this.Params.Dir
|
||||
}
|
||||
// 自定义文件名
|
||||
if !utils.Is.Empty(this.Params.Name) {
|
||||
name = this.Params.Name
|
||||
}
|
||||
|
||||
// 得到文件路径 - 但是可能还存在重复的 /
|
||||
path = strings.Join([]string{root, dir}, "/")
|
||||
// 替换重复的 / - 重新生成文件路径
|
||||
path = strings.Join(cast.ToStringSlice(utils.ArrayEmpty(strings.Split(path, "/"))), "/")
|
||||
// 如果不是以 / 结尾
|
||||
if !strings.HasSuffix(path, "/") { path += "/" }
|
||||
|
||||
return path + name + this.Params.Ext
|
||||
}
|
||||
|
||||
// Dir - 本地存储位置 - 生成文件目录
|
||||
func (this *CosClass) Dir(dir string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Dir = StorageInst.cleanDir(dir)
|
||||
return item
|
||||
}
|
||||
|
||||
// Name - 本地存储位置 - 生成文件名
|
||||
func (this *CosClass) Name(name string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Name = name
|
||||
return item
|
||||
}
|
||||
|
||||
// Ext - 本地存储位置 - 生成文件后缀
|
||||
func (this *CosClass) Ext(ext string) StorageAPI {
|
||||
item := this.clone()
|
||||
if item == nil {
|
||||
return this
|
||||
}
|
||||
item.Params.Ext = StorageInst.cleanExt(ext)
|
||||
return item
|
||||
}
|
||||
|
||||
// NewStorage - 使用传入配置创建存储实例
|
||||
func (this *CosClass) NewStorage(config dto.StorageConfig) StorageAPI {
|
||||
return StorageInst.newWithConfig(config)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ require (
|
||||
github.com/alibabacloud-go/dysmsapi-20170525/v5 v5.5.0
|
||||
github.com/alibabacloud-go/tea v1.4.0
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aliyun/credentials-go v1.4.12
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
@@ -20,8 +21,10 @@ require (
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.58
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.57
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.72
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/text v0.35.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -33,17 +36,21 @@ require (
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/minlz v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
@@ -58,7 +65,7 @@ require (
|
||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
@@ -46,6 +46,8 @@ github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eU
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 h1:y6pUIlhjxbZl9ObDAcmA1H3c21eaAxADHTDQmBnAIgA=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
|
||||
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||
@@ -69,6 +71,8 @@ github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
|
||||
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
@@ -90,6 +94,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -111,7 +116,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -143,6 +151,8 @@ github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZz
|
||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||
github.com/minio/minlz v1.1.0 h1:rUOGu3EP4EqJC5k3qCsIwEnZiJULKqtRyDdqbhlvMmQ=
|
||||
github.com/minio/minlz v1.1.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
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=
|
||||
@@ -150,6 +160,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
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/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
|
||||
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=
|
||||
@@ -165,6 +177,7 @@ github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfS
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -195,11 +208,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.57/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.58 h1:vLowU1ND9bMmdO525NtYejec5yZxRMSO6PkgOhrCayg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.58/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.57 h1:ZnJK+aTZYyzGN/4dmQXYWzuHsuZFrlj034uLoGaNVvQ=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.57/go.mod h1:jwLLFaeXXAnkWj37iTh0jfeXDYWf9eggaKJ1dRnc/1A=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.72 h1:k9aD8ri7Sqy2hYGYo6I2+OslDgY6IT5R0jUOHHSjW5Y=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.72/go.mod h1:STbTNaNKq03u+gscPEGOahKzLcGSYOj6Dzc5zNay7Pg=
|
||||
github.com/tencentyun/qcloud-cos-sts-sdk v0.0.0-20250515025012-e0eec8a5d123/go.mod h1:b18KQa4IxHbxeseW1GcZox53d7J0z39VNONTxvvlkXw=
|
||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
@@ -326,6 +344,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
||||
Reference in New Issue
Block a user