1增加接码过期时间

This commit is contained in:
2025-09-09 20:45:37 +08:00
parent 0f084e1461
commit baa6552994
12 changed files with 533 additions and 127 deletions

View File

@ -23,9 +23,13 @@ type SmsPhone struct {
Code string `json:"code" gorm:"type:varchar(10);comment:验证码"`
Status int `json:"status" gorm:"type:tinyint;comment:状态 1-等待验证码 2-已获取"`
ExpireTime *time.Time `json:"expireTime" gorm:"type:datetime;comment:过期时间"`
StartTime *time.Time `json:"startTime" gorm:"type:datetime;comment:开始时间"`
EndTime *time.Time `json:"endTime" gorm:"type:datetime;comment:结束时间 单次接码结束时间"`
Actived int `json:"actived" gorm:"type:tinyint;comment:是否激活(长期租赁如果第一次没接收到验证码 则不会激活号码) 1-未激活 2-已激活 3-已失效"`
Price decimal.Decimal `json:"price" gorm:"type:decimal(10,2);comment:价格"`
AutoRenewal int `json:"autoRenewal" gorm:"type:tinyint;comment:是否自动续费 1-自动续费 2-手动续费"`
Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"`
PlatformName string `json:"platformName" gorm:"-"`
models.ModelTime
models.ControlBy
}

View File

@ -10,7 +10,7 @@ type SmsReceiveLog struct {
UserId int `json:"userId" gorm:"type:bigint;comment:用户id"`
Service string `json:"service" gorm:"type:varchar(30);comment:服务"`
ServiceCode string `json:"serviceCode" gorm:"type:varchar(30);comment:服务code"`
MessageId int `json:"messageId" gorm:"type:int;comment:短信id"`
MessageId string `json:"messageId" gorm:"type:int;comment:短信id"`
Phone string `json:"phone" gorm:"type:varchar(30);comment:号码"`
Code string `json:"code" gorm:"type:varchar(20);comment:验证码"`
Status int `json:"status" gorm:"type:tinyint;comment:状态 0-等待验证码 1-成功 2-失败"`

View File

@ -150,8 +150,10 @@ type WeakUpReq struct {
}
type WeakUpResp struct {
ActivationId string `json:"activationId" comment:"激活id"`
MessageId string `json:"messageId" commment:"消息id"`
ActivationId string `json:"activationId" comment:"激活id"`
MessageId string `json:"messageId" commment:"消息id"`
StartTime *time.Time `json:"startTime" comment:"接码开始时间"`
EndTime *time.Time `json:"endTime" comment:"接码过期时间"`
}
func (s *WeakUpReq) Validate() error {
@ -190,9 +192,12 @@ type SmsPhoneCleanMyPhoneReq struct {
}
type SmsPhoneGetPhoneResp struct {
Phone string `json:"phone"`
EndAt *time.Time `json:"endTime"`
Id string `json:"id"`
Phone string `json:"phone"`
EndAt *time.Time `json:"endTime"`
StartTime *time.Time `json:"-" comment:"唤醒后单次接码开始时间"`
EndTime *time.Time `json:"-" comment:"唤醒后单次接码过期时间"`
MessageId string `json:"-" comment:"消息id"`
Id string `json:"id"`
}
type DaisysmsPriceResp struct {
@ -208,6 +213,8 @@ type OpenGetNumberResp struct {
Number string `json:"number"`
ActivationId string `json:"activationId"`
ExpireTime *time.Time `json:"expireTime"`
StartTime *time.Time `json:"startTime"`
EndTime *time.Time `json:"endTime"`
}
type OpenWakeUpReq struct {

View File

@ -212,3 +212,13 @@ type TextVerifiedSmsItemResp struct {
ParsedCode string `json:"parsedCode"`
Encrypted bool `json:"encrypted"`
}
type TextVerifiedGetNumbersResp struct {
HasNext bool `json:"hasNext" comment:"是否有下一页"`
Links TextVerifiedGetNumbersLinksResp `json:"links"`
Data []VerificationDTO `json:"data"`
}
type TextVerifiedGetNumbersLinksResp struct {
Next *TextVerifiedResp `json:"next"`
}

View File

@ -115,7 +115,7 @@ func (e MemberRecharge) CustomRecharge(req *dto.MemberRechargeCustomRechargeReq,
return errors.New("server error")
}
cacheExpireDuration := time.Duration(orderExpireTime+1) * time.Minute
cacheExpireDuration := time.Duration(orderExpireTime+10) * time.Minute
key := fmt.Sprintf("pre_order:%s", amount)
err = e.Orm.Transaction(func(tx *gorm.DB) error {
if err1 := tx.Create(&data).Error; err1 != nil {

View File

@ -36,6 +36,8 @@ func (e SmsPhone) OpenGetNumber(req *dto.GetNumberReq, userId int) (dto.OpenGetN
resp.ActivationId = smsPhone.NewActivationId
resp.ExpireTime = smsPhone.ExpireTime
resp.Number = smsPhone.Phone
resp.StartTime = smsPhone.StartTime
resp.EndTime = smsPhone.EndTime
return resp, statuscode.Success
}
@ -64,7 +66,7 @@ func (e *SmsPhone) CancelNumber(req *dto.SmsPhoneCancelNumberReq, userId int) in
if code == statuscode.Success {
err := e.Orm.Transaction(func(tx *gorm.DB) error {
if err1 := e.Orm.Model(data).Updates(map[string]interface{}{"status": 3, "code": "", "actived": "3", "auto_renewal": 2}).Error; err1 != nil {
if err1 := e.Orm.Model(data).Updates(map[string]interface{}{"status": 3, "code": "", "actived": "3", "auto_renewal": 2, "deleted_at": time.Now()}).Error; err1 != nil {
e.Log.Errorf("更新短信号码失败, %s", err1)
return err1
}
@ -143,21 +145,27 @@ func (e *SmsPhone) WeakUp(req *dto.WeakUpReq, userId int, defult bool) (dto.Weak
}
if smsPhone.Status != 1 || defult {
newActivationId, messageId, code := e.WeakUpManage(&smsPhone)
newActivationId, messageId, startTime, endTime, code := e.WeakUpManage(&smsPhone)
if code == statuscode.ServerError {
return result, code
} else if code == statuscode.Success {
if err := e.Orm.Model(smsPhone).Updates(map[string]interface{}{"new_activation_id": newActivationId, "status": 1, "code": "", "message_id": messageId}).Error; err != nil {
if err := e.Orm.Model(smsPhone).
Updates(map[string]interface{}{
"new_activation_id": newActivationId, "status": 1, "code": "",
"message_id": messageId, "start_time": startTime, "end_time": endTime}).Error; err != nil {
e.Log.Errorf("更新短信号码失败, %s", err)
return result, statuscode.ServerError
}
result.ActivationId = newActivationId
result.MessageId = messageId
result.StartTime = startTime
result.EndTime = endTime
} else if code == statuscode.SmsLongNumWaitCode {
e.Log.Info("无须唤醒")
if err := e.Orm.Model(smsPhone).Updates(map[string]interface{}{"status": 1, "code": ""}).Error; err != nil {
if err := e.Orm.Model(smsPhone).
Updates(map[string]interface{}{"status": 1, "code": "", "start_time": startTime, "end_time": endTime}).
Error; err != nil {
e.Log.Errorf("更新短信号码失败, %s", err)
return result, statuscode.ServerError
}
@ -171,18 +179,18 @@ func (e *SmsPhone) WeakUp(req *dto.WeakUpReq, userId int, defult bool) (dto.Weak
// 唤醒号码
// returns newActivationId,messageId, code
func (e *SmsPhone) WeakUpManage(req *models.SmsPhone) (string, string, int) {
func (e *SmsPhone) WeakUpManage(req *models.SmsPhone) (string, string, *time.Time, *time.Time, int) {
switch req.PlatformCode {
case global.SmsPlatformDaisysms:
service := SmsDaisysms{Service: e.Service}
id, code := service.getExtraActivation(req.ActivationId)
return strconv.Itoa(id), "", code
return strconv.Itoa(id), "", nil, nil, code
case global.SmsPlatformTextVerified:
service := SmsTextVerified{Service: e.Service}
return service.getExtraActivation(req.NewActivationId)
resp, code := service.getExtraActivation(req.NewActivationId)
return resp.ReservationId, resp.Id, resp.UsageWindowStart, resp.UsageWindowEnd, code
default:
return "", "", statuscode.SmsPlatformUnavailable
return "", "", nil, nil, statuscode.SmsPlatformUnavailable
}
}
@ -201,6 +209,17 @@ func (e *SmsPhone) GetMyPage(req *dto.SmsPhoneGetPageReq, userId int, phone *[]m
return statuscode.ServerError
}
dictService := SysDictData{}
dictService.Orm = e.Orm
dictService.Log = e.Log
dictDatas, _ := dictService.GetMapByType("sms_platform")
for index := range *phone {
if dict, ok := dictDatas[(*phone)[index].PlatformCode]; ok {
(*phone)[index].PlatformName = dict.DictLabel
}
}
return statuscode.Success
}
@ -281,7 +300,7 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber
}
now := time.Now()
activationId, phone, code, expireTime := e.GetNumberManage(req.PlatformCode, req.Type, req.ServiceCode, price, req.Period)
activationId, phone, code, expireTime, startTime, endTime := e.GetNumberManage(req.PlatformCode, req.Type, req.ServiceCode, price, req.Period)
if code != statuscode.Success {
return decimal.Decimal{}, models.SmsPhone{}, code
@ -299,6 +318,8 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber
smsPhone.MessageId = activationId
smsPhone.NewActivationId = activationId
smsPhone.Actived = 1
smsPhone.StartTime = startTime
smsPhone.EndTime = endTime
smsPhone.Price = price
if req.Type == 1 {
@ -310,6 +331,10 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber
smsPhone.ExpireTime = &now
}
//平台不允许取消
if req.PlatformCode == global.SmsPlatformTextVerified {
smsPhone.Actived = 2
}
} else {
if expireTime != nil {
smsPhone.ExpireTime = expireTime
@ -355,24 +380,30 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber
}
// 租赁号码
func (e *SmsPhone) GetNumberManage(platformCode string, typ int, serviceCode string, price decimal.Decimal, period int) (string, string, int, *time.Time) {
// return activationId 激活id
// phone 号码
// code 状态码
// *expirateTime 号码过期时间
// *startTime 长效号码单次接码开始使用(textverified有用)
// *endTime 长效号码单次接码过期时间(textverified有用)
func (e *SmsPhone) GetNumberManage(platformCode string, typ int, serviceCode string, price decimal.Decimal, period int) (string, string, int, *time.Time, *time.Time, *time.Time) {
switch platformCode {
case global.SmsPlatformDaisysms:
service := SmsDaisysms{Service: e.Service}
activationId, phone, code := service.GetNumberForApi(typ, serviceCode, price, period)
return strconv.Itoa(activationId), phone, code, nil
return strconv.Itoa(activationId), phone, code, nil, nil, nil
case global.SmsPlatformTextVerified:
service := SmsTextVerified{Service: e.Service}
resp, code := service.GetNumberForApi(typ, serviceCode, price, period)
resp, code := service.GetNumberAndWakeUp(typ, serviceCode, price, period)
if code != statuscode.Success {
return "", "", code, nil
return "", "", code, nil, resp.StartTime, resp.EndTime
}
return resp.Id, resp.Phone, code, resp.EndAt
return resp.Id, resp.Phone, code, resp.EndAt, resp.StartTime, resp.EndTime
default:
return "", "", statuscode.SmsPlatformUnavailable, nil
return "", "", statuscode.SmsPlatformUnavailable, nil, nil, nil
}
}
@ -416,11 +447,17 @@ func (e *SmsPhone) SyncCodes() error {
}
for _, item := range phones {
code, codeStatus := e.GetCodeManage(item.PlatformCode, item.NewActivationId, item.Type, item.UpdatedAt.Unix())
code, codeStatus := e.GetCodeManage(item.PlatformCode, item.NewActivationId,
item.Type, item.UserId, item.Service, item.ServiceCode)
if code == "" {
expireTime := item.UpdatedAt.Add(time.Second * 150)
if item.PlatformCode == global.SmsPlatformTextVerified && expireTime.Before(time.Now()) {
var expireTime time.Time
if item.EndTime != nil {
expireTime = *item.EndTime
}
if (item.Status == 3 && item.Actived == 2) || (item.PlatformCode == global.SmsPlatformTextVerified && expireTime.Before(time.Now())) {
if err := e.Orm.Model(&item).Updates(map[string]interface{}{"status": 3, "updated_at": time.Now()}).Error; err != nil {
e.Log.Errorf("手机号一睡眠 %s", item.Phone)
}
@ -473,7 +510,7 @@ func (e *SmsPhone) SyncCodes() error {
}
// 获取验证码
func (e *SmsPhone) GetCodeManage(platformCode string, messageId string, typ int, unixTime int64) (string, int) {
func (e *SmsPhone) GetCodeManage(platformCode string, messageId string, typ int, userId int, smsService, serviceCode string) (string, int) {
switch platformCode {
case global.SmsPlatformDaisysms:
service := SmsDaisysms{Service: e.Service}
@ -481,98 +518,224 @@ func (e *SmsPhone) GetCodeManage(platformCode string, messageId string, typ int,
return service.GetCodeForApi(messageId)
case global.SmsPlatformTextVerified:
service := SmsTextVerified{Service: e.Service}
return service.GetCode(messageId, typ, unixTime)
return service.GetCode(messageId, typ, userId, smsService, serviceCode)
default:
return "", statuscode.SmsPlatformUnavailable
}
}
// 自动续期
func (e SmsServices) AutoRenewal() error {
var datas []models.SmsPhone
var data models.SmsPhone
startTime := time.Now().Add(-24 * time.Hour)
endTime := time.Now().Add(24 * time.Hour)
smsServices := SmsServices{Service: e.Service}
serviceMap := smsServices.GetMapAll()
configService := SysConfig{Service: e.Service}
mapDatas, _ := configService.GetMapByKeys([]string{"renew_number_premium_daisysms", "renew_number_premium_textverified"})
if err := e.Orm.Model(&models.SmsPhone{}).Where("auto_renewal =1 and type =1 and actived =2 and expire_time >=? and expire_time <?", startTime, endTime).Find(&datas).Error; err != nil {
// AutoRenewal 自动续期处理
// 处理即将到期的长期号码自动续费逻辑
func (e *SmsPhone) AutoRenewal() error {
// 获取24小时内到期的自动续费号码
expiredPhones, err := e.getExpiredPhones()
if err != nil {
return err
}
for _, item := range datas {
percent := decimal.NewFromInt(1)
price := decimal.Zero
// 获取服务配置映射
serviceMap, premiumMap, err := e.getRenewalConfigs()
if err != nil {
return err
}
if service, ok := serviceMap[item.PlatformCode+"_"+item.ServiceCode]; !ok {
// 处理每个到期号码
for _, phone := range expiredPhones {
if err := e.processPhoneRenewal(phone, serviceMap, premiumMap); err != nil {
e.Log.Errorf("处理号码续费失败 [PhoneID: %d]: %v", phone.Id, err)
continue
} else {
price = service.LongPrice
switch {
case item.PlatformCode == global.SmsPlatformDaisysms:
p, ok := mapDatas["renew_number_premium_daisysms"]
val, _ := decimal.NewFromString(p.ConfigValue)
if ok && val.Cmp(decimal.Zero) > 0 {
per := decimal.NewFromInt(100).Add(val)
percent = per.Div(decimal.NewFromInt(100)).Truncate(2)
}
case item.PlatformCode == global.SmsPlatformTextVerified:
p, ok := mapDatas["renew_number_premium_textverified"]
val, _ := decimal.NewFromString(p.ConfigValue)
if ok && val.Cmp(decimal.Zero) > 0 {
per := decimal.NewFromInt(100).Add(val)
percent = per.Div(decimal.NewFromInt(100)).Truncate(2)
}
}
renewLog := models.SmsRenewalLog{}
renewLog.UserId = item.UserId
renewLog.PhoneId = item.Id
renewLog.Type = item.Type
renewLog.Amount = price.Mul(percent).Truncate(2)
renewLog.BeforeTime = *item.ExpireTime
renewLog.Period = 30
if err := e.Orm.Transaction(func(tx *gorm.DB) error {
db := tx.Exec("UPDATE member_balance set balance = balance - ? where user_id = ? and balance >= ?", price, item.UserId, price)
if db.Error != nil {
return db.Error
}
if db.RowsAffected == 0 {
return errors.New("余额不足")
}
if err1 := tx.Create(&renewLog).Error; err1 != nil {
return errors.New("创建续费日志失败")
}
newExpireTime := item.ExpireTime.AddDate(0, 0, 30)
if err1 := tx.Model(data).Where("id =?", item.Id).Update("expire_time", newExpireTime).Error; err1 != nil {
return err1
}
return nil
}); err != nil {
e.Log.Errorf("自动续期失败:%s", err.Error())
}
}
}
return nil
}
// getExpiredPhones 获取即将到期的自动续费号码
func (e *SmsPhone) getExpiredPhones() ([]models.SmsPhone, error) {
var phones []models.SmsPhone
startTime := time.Now().Add(-24 * time.Hour)
endTime := time.Now().Add(24 * time.Hour)
err := e.Orm.Model(&models.SmsPhone{}).
Where("auto_renewal = 1 AND type = 1 AND actived = 2 AND expire_time >= ? AND expire_time < ?", startTime, endTime).
Find(&phones).Error
return phones, err
}
// getRenewalConfigs 获取续费相关配置
func (e *SmsPhone) getRenewalConfigs() (map[string]models.SmsServices, map[string]dto.GetSysConfigByKEYForServiceResp, error) {
smsServices := SmsServices{Service: e.Service}
serviceMap := smsServices.GetMapAll()
configService := SysConfig{Service: e.Service}
configKeys := []string{"renew_number_premium_daisysms", "renew_number_premium_textverified"}
premiumMap, err := configService.GetMapByKeys(configKeys)
return serviceMap, premiumMap, err
}
// processPhoneRenewal 处理单个号码的续费
func (e *SmsPhone) processPhoneRenewal(phone models.SmsPhone, serviceMap map[string]models.SmsServices, premiumMap map[string]dto.GetSysConfigByKEYForServiceResp) error {
// 获取服务价格
service, exists := serviceMap[phone.PlatformCode+"_"+phone.ServiceCode]
if !exists {
return errors.New("服务不存在")
}
// 计算续费价格
renewalPrice := e.calculateRenewalPrice(service.LongPrice, phone.PlatformCode, premiumMap)
if renewalPrice.IsZero() {
return errors.New("续费价格计算失败")
}
// 创建续费日志
renewLog := e.createRenewalLog(phone, renewalPrice)
// 执行续费事务
return e.executeRenewalTransaction(phone, renewalPrice, renewLog)
}
// calculateRenewalPrice 计算续费价格
func (e *SmsPhone) calculateRenewalPrice(basePrice decimal.Decimal, platformCode string, premiumMap map[string]dto.GetSysConfigByKEYForServiceResp) decimal.Decimal {
percent := decimal.NewFromInt(1)
var configKey string
switch platformCode {
case global.SmsPlatformDaisysms:
configKey = "renew_number_premium_daisysms"
case global.SmsPlatformTextVerified:
configKey = "renew_number_premium_textverified"
default:
return basePrice
}
if config, exists := premiumMap[configKey]; exists {
if val, err := decimal.NewFromString(config.ConfigValue); err == nil && val.Cmp(decimal.Zero) > 0 {
percent = decimal.NewFromInt(100).Add(val).Div(decimal.NewFromInt(100))
}
}
return basePrice.Mul(percent).Truncate(2)
}
// createRenewalLog 创建续费日志
func (e *SmsPhone) createRenewalLog(phone models.SmsPhone, amount decimal.Decimal) models.SmsRenewalLog {
return models.SmsRenewalLog{
UserId: phone.UserId,
PhoneId: phone.Id,
Type: phone.Type,
Amount: amount,
BeforeTime: *phone.ExpireTime,
Period: 30,
}
}
// executeRenewalTransaction 执行续费事务
func (e *SmsPhone) executeRenewalTransaction(phone models.SmsPhone, price decimal.Decimal, renewLog models.SmsRenewalLog) error {
err := e.Orm.Transaction(func(tx *gorm.DB) error {
// 扣除余额
result := tx.Exec("UPDATE member_balance SET balance = balance - ? WHERE user_id = ? AND balance >= ?", price, phone.UserId, price)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("余额不足")
}
// 创建续费日志
if err := tx.Create(&renewLog).Error; err != nil {
return errors.New("创建续费日志失败")
}
// 更新到期时间
newExpireTime := phone.ExpireTime.AddDate(0, 0, 30)
params := map[string]interface{}{
"expire_time": newExpireTime,
"remark": "",
}
if err := tx.Model(&models.SmsPhone{}).Where("id = ?", phone.Id).
Updates(params).Error; err != nil {
return err
}
return nil
})
if err.Error() == "余额不足" {
return e.handleInsufficientBalance(phone)
}
return err
}
// handleInsufficientBalance 处理余额不足情况
func (e *SmsPhone) handleInsufficientBalance(phone models.SmsPhone) error {
params := map[string]interface{}{
"auto_renewal": 2,
"remark": "余额不足,取消自动续费",
}
// 取消自动续费
if err := e.Orm.Model(&models.SmsPhone{}).
Where("id = ?", phone.Id).
Updates(params).Error; err != nil {
e.Log.Errorf("余额不足,取消自动续费失败: %v", err)
}
// 调用平台取消续费接口
code := e.ChangeAutoRenewManage(phone.PlatformCode, phone.ActivationId, false)
if code != statuscode.Success {
params["auto_renewal"] = 1
params["remark"] = ""
// 如果平台取消失败,恢复自动续费状态
if err := e.Orm.Model(&models.SmsPhone{}).
Where("id = ?", phone.Id).
Updates(params).Error; err != nil {
e.Log.Errorf("恢复自动续费状态失败: %v", err)
}
e.Log.Errorf("平台取消自动续费失败,状态码: %d", code)
}
return errors.New("余额不足")
}
// ChangeAutoRenew 修改自动续期
func (e *SmsPhone) ChangeAutoRenew(req *dto.SmsPhoneChangeAutoRenewReq, userId int) int {
var data models.SmsPhone
if req.AutoRenew == 1 {
smsServices := SmsServices{Service: e.Service}
balanceService := MemberBalance{Service: e.Service}
balance := balanceService.GetBalance(userId)
if err := e.Orm.Model(data).Where("id =? or activation_id =?", req.Id, req.ActivationId).First(&data).Error; err != nil {
e.Log.Errorf("修改自动续期 数据不存在 id:%d err:%s", req.Id, err.Error())
return statuscode.SmsNotExisted
}
if data.ExpireTime.Before(time.Now()) {
return statuscode.SmsRentalCantRenew
}
serviceItem, err := smsServices.GetByCode(data.PlatformCode, data.ServiceCode)
if err != nil {
e.Log.Errorf("短信服务报错:%v", err)
return statuscode.ServerError
}
configService := SysConfig{Service: e.Service}
configKeys := []string{"renew_number_premium_daisysms", "renew_number_premium_textverified"}
premiumMap, _ := configService.GetMapByKeys(configKeys)
price := e.calculateRenewalPrice(serviceItem.LongPrice, serviceItem.PlatformCode, premiumMap)
if balance.Cmp(price) < 0 {
return statuscode.BalanceNotEnough
}
}
if req.Id > 0 {
if err := e.Orm.Model(data).Where("id =? and user_id =?", req.Id, userId).First(&data).Error; err != nil {
e.Log.Errorf("修改自动续期 数据不存在 id:%d err:%s", req.Id, err.Error())

View File

@ -2,6 +2,7 @@ package service
import (
"errors"
"strconv"
"github.com/go-admin-team/go-admin-core/sdk/service"
"gorm.io/gorm"
@ -38,7 +39,7 @@ func (e SmsReceiveLog) WebHook(req *dto.SmsReceiveWebHookReq) error {
UserId: phoneLog.UserId,
Service: phoneLog.Service,
ServiceCode: phoneLog.ServiceCode,
MessageId: req.MessageID,
MessageId: strconv.Itoa(req.MessageID),
Phone: phoneLog.Phone,
Code: req.Code,
Status: 2,

View File

@ -13,7 +13,7 @@ import (
)
func initSetting() *gorm.DB {
dsn := "root:123456@tcp(127.0.0.1:3306)/proxy_server?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
dsn := "root:123456@tcp(127.0.0.1:3306)/proxy-server-prod?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
sdk.Runtime.SetDb("default", db)
config.ExtConfig.TrxGridUrl = "https://api.trongrid.io"

View File

@ -32,7 +32,7 @@ type SmsTextVerified struct {
// 获取收到的验证码
// messageId 短效或长效号码的ID
// typ 0-短效 1-长效
func (e SmsTextVerified) GetCode(messageId string, typ int, unixTime int64) (string, int) {
func (e SmsTextVerified) GetCode(messageId string, typ int, userId int, service, serviceCode string) (string, int) {
reservationType := ""
if typ == 0 {
@ -58,23 +58,84 @@ func (e SmsTextVerified) GetCode(messageId string, typ int, unixTime int64) (str
return "", statuscode.ServerError
}
messageIds := []string{}
if len(resp.Data) > 0 {
for _, v := range resp.Data {
// 2. 解析时间字符串
// time.RFC3339Nano 是一个预定义的格式常量,
// 它能够解析包含纳秒和时区信息的 ISO 8601 字符串。
messageIds = append(messageIds, v.Id)
}
}
if len(messageIds) > 0 {
receiveLogs := make([]models.SmsReceiveLog, 0)
receiveMaps := make(map[string]string, 0)
if err := e.Orm.Model(&models.SmsReceiveLog{}).
Where("message_id in ?", messageIds).
Select("message_id").
Find(&receiveLogs).Error; err != nil {
e.Log.Errorf("获取短信接收记录失败, error: %v", err)
return "", statuscode.ServerError
}
for _, v := range receiveLogs {
receiveMaps[v.MessageId] = v.MessageId
}
// 查找最新验证码并存储所有新验证码
var latestSms *dto.TextVerifiedSmsItemResp
var latestTime time.Time
var newSmsData []dto.TextVerifiedSmsItemResp
// 遍历所有验证码数据
for _, v := range resp.Data {
// 解析时间字符串
t, err := time.Parse(time.RFC3339Nano, v.CreatedAt)
if err != nil {
fmt.Println("时间解析失败:", err)
e.Log.Errorf("时间解析失败: %v", err)
continue
}
// 3. 将解析后的 time.Time 对象转换为 Unix 时间戳
// t.Unix() 返回以秒为单位的整数时间戳
unixTimestamp := t.Unix()
// 检查是否为新验证码(数据库中不存在)
if _, ok := receiveMaps[v.Id]; !ok {
newSmsData = append(newSmsData, v)
}
if unixTimestamp >= unixTime {
parsedCode = v.ParsedCode
break
// 选择最新的验证码(包括已存在的)
if latestSms == nil || t.After(latestTime) {
latestSms = &v
latestTime = t
}
}
// 批量保存所有新验证码到数据库
if len(newSmsData) > 0 {
var receiveLogs []models.SmsReceiveLog
for _, sms := range newSmsData {
receiveLog := models.SmsReceiveLog{
UserId: userId,
Service: service,
ServiceCode: serviceCode,
MessageId: sms.Id,
Phone: "1" + sms.To,
Code: sms.ParsedCode,
Status: 2,
}
receiveLogs = append(receiveLogs, receiveLog)
}
// 批量插入数据库
if err := e.Orm.Create(&receiveLogs).Error; err != nil {
e.Log.Errorf("批量创建短信接收记录失败, error: %v", err)
return "", statuscode.ServerError
}
e.Log.Infof("成功存储 %d 条新验证码记录", len(newSmsData))
// 返回最新的验证码
if latestSms != nil {
parsedCode = latestSms.ParsedCode
e.Log.Infof("返回最新验证码: %s, messageId: %s, 时间: %s", parsedCode, latestSms.Id, latestSms.CreatedAt)
}
}
}
@ -97,6 +158,7 @@ const (
renewRental = "/api/pub/v2/reservations/rental/renewable/%s/renew" //长效续期
updateRentalRenewStatus = "/api/pub/v2/reservations/rental/renewable/%s" // 更改续租状态
getSmsCode = "/api/pub/v2/sms?reservationId=%s&reservationType=%s" //获取短信验证码
getNumbers = "/api/pub/v2/reservations/rental/renewable" //获取长效号码列表 Get
)
var idPattern = regexp.MustCompile(`^[a-zA-Z0-9_]{28}$`)
@ -284,12 +346,33 @@ func (e *SmsTextVerified) SyncPrices() error {
return nil
}
// 获取号码并唤醒
func (e *SmsTextVerified) GetNumberAndWakeUp(tye int, serviceCode string, price decimal.Decimal, period int) (dto.SmsPhoneGetPhoneResp, int) {
resp, code := e.GetNumberForApi(tye, serviceCode, price, period)
if code != statuscode.Success {
return resp, code
}
wakeUpResp, code := e.getExtraActivation(resp.Id)
if code != statuscode.Success {
return resp, code
}
resp.MessageId = wakeUpResp.Id
resp.StartTime = wakeUpResp.UsageWindowStart
resp.EndTime = wakeUpResp.UsageWindowEnd
return resp, code
}
// 号码租赁
// getType 0-短效 1-长效
// service 服务code
// maxPrice 最大价格
// period 时长(月=30天)
func (e SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price decimal.Decimal, period int) (dto.SmsPhoneGetPhoneResp, int) {
func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price decimal.Decimal, period int) (dto.SmsPhoneGetPhoneResp, int) {
//这个平台的所有号码都是美国号码 默认国家代码都是 +1
var err error
var createResp dto.TextVerifiedResp
@ -477,12 +560,13 @@ func (e *SmsTextVerified) GetRentalDetail(id string) (dto.VerificationRentalDeta
}
// 唤醒号码
// returns activationId,messageId, code
func (e SmsTextVerified) getExtraActivation(id string) (string, string, int) {
// returns activationId,messageId,startTime可用开始时间,endTime可用结束时间, code,
func (e SmsTextVerified) getExtraActivation(id string) (dto.TextVerifiedWakeUpResp, int) {
client, code := e.GetTextVerifiedAuthClient()
result := dto.TextVerifiedWakeUpResp{}
if code != statuscode.Success {
return "", "", code
return result, code
}
req := dto.TextVerifiedWakeUpReq{
@ -497,31 +581,35 @@ func (e SmsTextVerified) getExtraActivation(id string) (string, string, int) {
if err != nil {
e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err)
return "", "", statuscode.ServerError
return result, statuscode.ServerError
} else if status == http.StatusCreated || status == http.StatusOK {
if resp.Method != "" && resp.Href != "" {
bytes, err := e.doRequest(&resp)
if err != nil {
e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err)
return "", "", statuscode.ServerError
return result, statuscode.ServerError
}
detailResp := dto.TextVerifiedWakeUpResp{}
if err := sonic.Unmarshal(bytes, &detailResp); err != nil {
e.Log.Errorf("唤醒号码反序列化失败 id:%s error: %v", id, err)
return "", "", statuscode.ServerError
return result, statuscode.ServerError
}
return detailResp.ReservationId, detailResp.Id, statuscode.Success
result.Id = detailResp.Id
result.ReservationId = detailResp.ReservationId
result.UsageWindowEnd = detailResp.UsageWindowEnd
result.UsageWindowStart = detailResp.UsageWindowStart
return result, statuscode.Success
} else {
e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err)
return "", "", statuscode.ServerError
return result, statuscode.ServerError
}
}
return "", "", statuscode.ServerError
return result, statuscode.ServerError
}
// 获取价格
@ -826,7 +914,121 @@ func (e *SmsTextVerified) Renew(activationId string, status bool) int {
}
}
// 重新初始化短信记录
func (e *SmsTextVerified) InitSmsLogs() error {
// phones, err := e.GetNumbers()
// if err != nil {
// e.Log.Errorf("获取长效号码列表失败 %v", err)
// return fmt.Errorf("获取长效号码列表失败 %v", err)
// }
// numbers := make([]string, 0)
// for _, v := range phones {
// numbers = append(numbers, v.Number)
// }
phoneLogs, err := e.GetUserByNumber("textverified")
if err != nil {
e.Log.Errorf("获取系统内号码记录失败 %v", err)
return fmt.Errorf("获取系统内号码记录失败 %v", err)
}
if len(phoneLogs) > 0 {
for _, v := range phoneLogs {
e.GetCode(v.ActivationId, v.Type, v.UserId, v.Service, v.ServiceCode)
}
}
return nil
}
// 根据电话号码获取系统内记录
func (e *SmsTextVerified) GetUserByNumber(platformCode string) ([]models.SmsPhone, error) {
var result []models.SmsPhone
if err := e.Orm.Model(&models.SmsPhone{}).Where("platform_code = ?", platformCode).Find(&result).Error; err != nil {
return nil, err
}
return result, nil
}
// 获取号码
func (e *SmsTextVerified) GetNumbers() ([]dto.VerificationDTO, error) {
client, code := e.GetTextVerifiedAuthClient()
result := make([]dto.VerificationDTO, 0)
if code != statuscode.Success {
e.Log.Errorf("获取长效号码列表失败 %d", code)
return result, fmt.Errorf("获取长效号码列表失败 %d", code)
}
resp := dto.TextVerifiedGetNumbersResp{}
statusCode, err := client.Get(getNumbers, map[string]string{}, resp)
if err != nil {
e.Log.Errorf("获取长效号码列表失败 %v", err)
return result, fmt.Errorf("获取长效号码列表失败 %v", err)
}
if statusCode == http.StatusOK {
// 添加第一页数据
result = append(result, resp.Data...)
// 处理分页数据
if resp.HasNext {
paginatedData, err := e.fetchPaginatedData(resp.Links.Next)
if err != nil {
return result, err
}
result = append(result, paginatedData...)
}
return result, nil
} else if statusCode == http.StatusUnauthorized {
return e.GetNumbers()
} else {
e.Log.Errorf("获取长效号码列表失败 %d", statusCode)
return result, fmt.Errorf("获取长效号码列表失败 %d", statusCode)
}
}
// 获取授权header头
// fetchPaginatedData 获取分页数据的通用方法
// 处理TextVerified API的分页响应自动获取所有页面的数据
func (e *SmsTextVerified) fetchPaginatedData(nextLink *dto.TextVerifiedResp) ([]dto.VerificationDTO, error) {
var result []dto.VerificationDTO
for nextLink != nil {
nextData, err := e.doRequest(nextLink)
if err != nil {
e.Log.Errorf("获取分页数据失败: %v", err)
return result, fmt.Errorf("获取分页数据失败: %v", err)
}
nextResp := dto.TextVerifiedGetNumbersResp{}
err = sonic.Unmarshal(nextData, &nextResp)
if err != nil {
e.Log.Errorf("解析分页数据失败: %v", err)
return result, fmt.Errorf("解析分页数据失败: %v", err)
}
result = append(result, nextResp.Data...)
// 检查是否还有下一页
if nextResp.HasNext {
nextLink = nextResp.Links.Next
} else {
nextLink = nil
}
}
return result, nil
}
func (e *SmsTextVerified) GetAuthHeader() (map[string]string, error) {
token, err := e.GetToken()
if err != nil {

View File

@ -51,3 +51,18 @@ func TestSyncTextVerifiedServices(t *testing.T) {
t.Log("SyncTextVerifiedServices succeeded")
}
}
func TestInitSmsLogs(t *testing.T) {
db := initSetting()
s := SmsTextVerified{}
s.Orm = db
s.Log = logger.NewHelper(logger.DefaultLogger)
err := s.InitSmsLogs()
if err != nil {
t.Errorf("InitSmsLogs failed: %v", err)
} else {
t.Log("InitSmsLogs succeeded")
}
}

View File

@ -37,11 +37,11 @@ func (j RenewalJob) Exec(args interface{}) error {
// 定时短信续期任务
func (j SmsRenewalJob) Exec(args interface{}) error {
smsService := service.SmsServices{}
smsService.Orm = GetDb()
smsService.Log = logger.NewHelper(logger.DefaultLogger)
smsPhoneService := service.SmsPhone{}
smsPhoneService.Orm = GetDb()
smsPhoneService.Log = logger.NewHelper(logger.DefaultLogger)
return smsService.AutoRenewal()
return smsPhoneService.AutoRenewal()
}
// 过期任务

View File

@ -41,6 +41,7 @@ var StatusCodeZh = map[int]string{
SmsInvalidType: "短信验证码_无效类型",
SmsOutOfStockOrUnavailable: "短信验证码_缺货或服务不可用",
SmsRentalRefundNotPermitted: "短信验证码_租赁退款不允许",
SmsRentalCantRenew: "短信验证码_无法续期",
}
var StatusCodeEn = map[int]string{
@ -74,6 +75,7 @@ var StatusCodeEn = map[int]string{
SmsInvalidType: "sms type invalid",
SmsOutOfStockOrUnavailable: "sms out of stock or unavailable",
SmsRentalRefundNotPermitted: "sms rental refund not permitted",
SmsRentalCantRenew: "sms rental can not renewal",
}
// GetMsg 获取状态码对应的消息
@ -161,4 +163,6 @@ const (
SmsOutOfStockOrUnavailable = 20024
// 短信-租赁退款不允许
SmsRentalRefundNotPermitted = 20025
// 短信-无法续期
SmsRentalCantRenew = 20026
)