diff --git a/app/admin/apis/sms_abnormal_number.go b/app/admin/apis/sms_abnormal_number.go index aa85b14..a3e6b24 100644 --- a/app/admin/apis/sms_abnormal_number.go +++ b/app/admin/apis/sms_abnormal_number.go @@ -12,6 +12,8 @@ import ( "go-admin/app/admin/service" "go-admin/app/admin/service/dto" "go-admin/common/actions" + "go-admin/common/middleware" + "go-admin/common/statuscode" ) type SmsAbnormalNumber struct { @@ -212,3 +214,101 @@ func (e SmsAbnormalNumber) SyncState(c *gin.Context) { } e.OK(nil, "同步成功") } + +// 重用号码 +func (e SmsAbnormalNumber) ReUseAbnormalNumber(c *gin.Context) { + req := dto.ReUseAbnormalNumberReq{} + s := service.SmsAbnormalNumber{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + lang := "en" + userId, err := middleware.GetUserIdByApiKey(c) + + if err != nil { + e.Logger.Error(err) + e.Error(statuscode.Unauthorized, nil, statuscode.GetMsg(statuscode.Unauthorized, lang)) + return + } + + resp, code := s.ReUseAbnormalNumber(userId, &req) + + if code != statuscode.Success { + e.Error(code, nil, statuscode.GetMsg(code, lang)) + return + } + + e.OK(resp, "success") +} + +// 清空数据 +func (e SmsAbnormalNumber) Clean(c *gin.Context) { + s := service.SmsAbnormalNumber{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + lang := "en" + + if err != nil { + e.Logger.Error(err) + e.Error(statuscode.Unauthorized, nil, statuscode.GetMsg(statuscode.Unauthorized, lang)) + return + } + + err = s.Clean() + + if err != nil { + e.Error(500, err, fmt.Sprintf("清除数据失败\r\n失败信息 %s", err.Error())) + return + } + + e.OK(nil, "success") +} + +// 批量取消 +func (e SmsAbnormalNumber) BatchCancel(c *gin.Context) { + req := dto.BatchCancelAbnormalNumberReq{} + s := service.SmsAbnormalNumber{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + lang := "en" + + if err != nil { + e.Logger.Error(err) + e.Error(statuscode.Unauthorized, nil, statuscode.GetMsg(statuscode.Unauthorized, lang)) + return + } + + err = s.BatchCancel(&req) + + if err != nil { + e.Error(500, err, fmt.Sprintf("批量取消异常号码统计失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(nil, "success") +} diff --git a/app/admin/apis/sms_phone.go b/app/admin/apis/sms_phone.go index 82d65d1..1c377fa 100644 --- a/app/admin/apis/sms_phone.go +++ b/app/admin/apis/sms_phone.go @@ -2,6 +2,7 @@ package apis import ( "fmt" + "strings" "github.com/gin-gonic/gin" "github.com/go-admin-team/go-admin-core/sdk/api" @@ -747,3 +748,35 @@ func (e SmsPhone) OpenWeakUp(c *gin.Context) { e.OK(resp, "success") } + +// 手动续费 +func (e SmsPhone) ManualRenewal(c *gin.Context) { + req := dto.ManualRenewalReq{} + s := service.SmsPhone{} + + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + errorIds, err := s.ManualRenewal(req.ActivationIds) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + var data string + + if len(errorIds) > 0 { + data = strings.Join(errorIds, ",") + } + + e.OK(data, "success") +} diff --git a/app/admin/models/sms_abnormal_number.go b/app/admin/models/sms_abnormal_number.go index 3f75385..7a668fc 100644 --- a/app/admin/models/sms_abnormal_number.go +++ b/app/admin/models/sms_abnormal_number.go @@ -1,23 +1,26 @@ package models import ( - "go-admin/common/models" - + "time" ) type SmsAbnormalNumber struct { - models.Model - - Account string `json:"account" gorm:"type:varchar(255);comment:账号"` - PlatformCode string `json:"platformCode" gorm:"type:varchar(20);comment:平台code"` - Phone string `json:"phone" gorm:"type:varchar(30);comment:电话号码"` - models.ModelTime - models.ControlBy + models.Model + + Account string `json:"account" gorm:"type:varchar(255);comment:账号"` + PlatformCode string `json:"platformCode" gorm:"type:varchar(20);comment:平台code"` + ApiKey string `json:"apiKey" gorm:"type:varchar(255);comment:api key"` + Phone string `json:"phone" gorm:"type:varchar(30);comment:电话号码"` + ActivationId string `json:"activationId" gorm:"type:varchar(255);comment:激活id"` + ReceivedTime time.Time `json:"receivedTime" gorm:"type:datetime;comment:接收号码时间"` + Reused int `json:"reused" gorm:"type:tinyint(1);default:2;comment:是否重用 1-是 2-否"` + models.ModelTime + models.ControlBy } func (SmsAbnormalNumber) TableName() string { - return "sms_abnormal_number" + return "sms_abnormal_number" } func (e *SmsAbnormalNumber) Generate() models.ActiveRecord { @@ -27,4 +30,4 @@ func (e *SmsAbnormalNumber) Generate() models.ActiveRecord { func (e *SmsAbnormalNumber) GetId() interface{} { return e.Id -} \ No newline at end of file +} diff --git a/app/admin/router/sms_abnormal_number.go b/app/admin/router/sms_abnormal_number.go index c39efba..e57e568 100644 --- a/app/admin/router/sms_abnormal_number.go +++ b/app/admin/router/sms_abnormal_number.go @@ -16,14 +16,22 @@ func init() { // registerSmsAbnormalNumberRouter func registerSmsAbnormalNumberRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { api := apis.SmsAbnormalNumber{} - r := v1.Group("/sms-abnormal-number").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + r := v1.Group("/sms-abnormal-number"). + Use(authMiddleware.MiddlewareFunc()). + Use(middleware.AuthCheckRole()) { r.GET("", actions.PermissionAction(), api.GetPage) r.GET("/:id", actions.PermissionAction(), api.Get) r.POST("", api.Insert) r.PUT("/:id", actions.PermissionAction(), api.Update) r.DELETE("", api.Delete) + r.DELETE("clear", actions.PermissionAction(), api.Clean) + r.PUT("/batch-cancel", actions.PermissionAction(), api.BatchCancel) + } - r.POST("/sync-state", api.SyncState) //同步差异 + r1 := v1.Group("/sms-abnormal-number") + { + r1.POST("/sync-state", api.SyncState) //同步差异 + r1.PUT("/reuse", api.ReUseAbnormalNumber) //重用号码 } } diff --git a/app/admin/router/sms_phone.go b/app/admin/router/sms_phone.go index eccf602..e8d0c60 100644 --- a/app/admin/router/sms_phone.go +++ b/app/admin/router/sms_phone.go @@ -45,6 +45,7 @@ func registerSmsPhoneRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddl r.GET("/:id", actions.PermissionAction(), api.Get) r.POST("", api.Insert) r.PUT("/:id", actions.PermissionAction(), api.Update) + r.PUT("/manual-renewal", api.ManualRenewal) r.DELETE("", api.Delete) } diff --git a/app/admin/service/dto/sms_phone.go b/app/admin/service/dto/sms_phone.go index f87d724..2299131 100644 --- a/app/admin/service/dto/sms_phone.go +++ b/app/admin/service/dto/sms_phone.go @@ -221,3 +221,18 @@ type OpenWakeUpReq struct { PlatformCode string `json:"platformCode" comment:"短信平台"` ActivationId string `json:"activationId" comment:"平台id"` } + +type ReUseAbnormalNumberReq struct { + BeginTime int64 `json:"beginTime" comment:"开始时间"` + PlatformCode string `json:"platformCode" comment:"短信平台"` + ServiceCode string `json:"serviceCode" comment:"服务code"` +} + +// 批量取消 +type BatchCancelAbnormalNumberReq struct { + Ids []int `json:"ids" comment:"短信号码id"` +} + +type ManualRenewalReq struct { + ActivationIds []string `json:"activationIds" comment:"激活码id"` +} diff --git a/app/admin/service/dto/text_verified.go b/app/admin/service/dto/text_verified.go index 58e120c..c064898 100644 --- a/app/admin/service/dto/text_verified.go +++ b/app/admin/service/dto/text_verified.go @@ -131,6 +131,7 @@ type VerificationDTO struct { Reuse ReuseInfo `json:"reuse"` Sale TextVerifiedResp `json:"sale"` ServiceName string `json:"serviceName"` + ApiKey string `json:"apiKey"` // State 表示验证或服务的当前状态。 // 状态是以下枚举值之一,代表了从创建到完成或取消的生命周期: // * verificationPending: 正在等待验证码。 diff --git a/app/admin/service/sms_abnormal_number.go b/app/admin/service/sms_abnormal_number.go index 618386d..a04649a 100644 --- a/app/admin/service/sms_abnormal_number.go +++ b/app/admin/service/sms_abnormal_number.go @@ -5,6 +5,7 @@ import ( "time" "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/shopspring/decimal" "gorm.io/gorm" "go-admin/app/admin/models" @@ -12,12 +13,193 @@ import ( "go-admin/common/actions" cDto "go-admin/common/dto" "go-admin/common/global" + "go-admin/common/statuscode" + "go-admin/utils/utility" ) type SmsAbnormalNumber struct { service.Service } +func (e *SmsAbnormalNumber) Clean() error { + if err := e.Orm.Exec("truncate table sms_abnormal_number").Error; err != nil { + return err + } + + return nil +} + +// 批量取消 +func (e *SmsAbnormalNumber) BatchCancel(req *dto.BatchCancelAbnormalNumberReq) error { + var datas []models.SmsAbnormalNumber + + if err := e.Orm.Model(&models.SmsAbnormalNumber{}).Where("id in ?", req.Ids).Find(&datas).Error; err != nil { + return err + } + + utility.SafeGo(func() { + CancelFunc(e, datas) + }) + + return nil +} + +func CancelFunc(e *SmsAbnormalNumber, datas []models.SmsAbnormalNumber) { + phoneService := SmsPhone{Service: e.Service} + for _, item := range datas { + phoneService.ChangeAutoRenewManage(item.PlatformCode, item.ApiKey, item.ActivationId, false) + + time.Sleep(200 * time.Millisecond) + } +} + +// 重用号码 +func (e *SmsAbnormalNumber) ReUseAbnormalNumber(userId int, req *dto.ReUseAbnormalNumberReq) (dto.OpenGetNumberResp, int) { + resp := dto.OpenGetNumberResp{} + balanceService := MemberBalance{Service: e.Service} + config := dto.GetSysConfigByKEYForServiceResp{} + configReq := dto.SysConfigByKeyReq{} + configService := SysConfig{Service: e.Service} + smsServices := SmsServices{Service: e.Service} + var price decimal.Decimal + serviceItem, err := smsServices.GetByCode(req.PlatformCode, req.ServiceCode) + + if err != nil { + e.Log.Errorf("短信服务报错:%v", err) + return resp, statuscode.ServerError + } + + if serviceItem.Code == "" { + e.Log.Error("短信服务不存在") + return resp, statuscode.ServerError + } + + switch { + case req.PlatformCode == global.SmsPlatformDaisysms: + price = serviceItem.LongPrice + configReq.ConfigKey = "long_number_premium_daisysms" + case req.PlatformCode == global.SmsPlatformTextVerified: + price = serviceItem.LongPrice + configReq.ConfigKey = "long_number_premium_textverified" + } + err = configService.GetWithKey(&configReq, &config) + + if err != nil { + return resp, statuscode.ServerError + } + + if config.ConfigValue == "" { + e.Log.Errorf("短期长期租赁浮动百分比不能为空") + return resp, statuscode.ServerError + } + + percent, err := decimal.NewFromString(config.ConfigValue) + if err != nil { + e.Log.Errorf("短期或长期租赁费用格式错误") + return resp, statuscode.ServerError + } + + price = price.Mul(decimal.NewFromInt(100).Add(percent)).Div(decimal.NewFromInt(100)) + + if price.Cmp(decimal.Zero) <= 0 { + e.Log.Errorf("短期或长期租赁费用不能小于等于0") + return resp, statuscode.ServerError + } + + balance := balanceService.GetBalance(userId) + + if balance.LessThan(price) { + e.Log.Error("余额不足") + return resp, statuscode.BalanceNotEnough + } + + abnomarlNumber, err := e.GetReused(req.PlatformCode, req.BeginTime) + if err != nil { + return resp, statuscode.ServerError + } + + expireTime := abnomarlNumber.ReceivedTime.AddDate(0, 0, 30) + startTime := time.Now() + endTime := startTime + smsPhone := models.SmsPhone{} + smsPhone.PlatformCode = req.PlatformCode + smsPhone.Phone = abnomarlNumber.Phone + smsPhone.UserId = userId + smsPhone.Service = serviceItem.Name + smsPhone.ServiceCode = req.ServiceCode + smsPhone.Type = 1 + smsPhone.Period = 1 + smsPhone.ActivationId = abnomarlNumber.ActivationId + smsPhone.MessageId = abnomarlNumber.ActivationId + smsPhone.NewActivationId = abnomarlNumber.ActivationId + smsPhone.Actived = 1 + smsPhone.StartTime = &startTime + smsPhone.EndTime = &endTime + // smsPhone.ApiKey = apiInfo.ApiKey + + smsPhone.Price = price + smsPhone.ExpireTime = &expireTime + + //平台不允许取消 + if req.PlatformCode == global.SmsPlatformTextVerified { + smsPhone.Actived = 2 + } + + smsPhone.Status = 1 + + err = e.Orm.Transaction(func(tx *gorm.DB) error { + if err1 := tx.Save(&smsPhone).Error; err1 != nil { + e.Log.Errorf("获取手机号失败", err1) + return err1 + } + + if err1 := tx.Exec("UPDATE member_balance SET balance = balance -? WHERE user_id =?", price, userId).Error; err1 != nil { + e.Log.Errorf("更新余额失败", err1) + return err1 + } + + return nil + }) + + if err != nil { + return resp, statuscode.ServerError + } + + resp.ActivationId = smsPhone.NewActivationId + resp.ExpireTime = smsPhone.ExpireTime + resp.Number = smsPhone.Phone + resp.StartTime = smsPhone.StartTime + resp.EndTime = smsPhone.EndTime + + return resp, statuscode.Success +} + +// 获取重用 +func (e *SmsAbnormalNumber) GetReused(platformCode string, beginTime int64) (models.SmsAbnormalNumber, error) { + var smsAbnormalNumber models.SmsAbnormalNumber + query := e.Orm.Model(&smsAbnormalNumber). + Where("platform_code = ? AND reused = ?", platformCode, 2) + + if beginTime > 0 { + query.Where("received_time >= ?", time.Unix(beginTime, 0)) + } + + err := query.Order("received_time asc").First(&smsAbnormalNumber).Error + if err != nil { + e.Log.Errorf("SmsAbnormalNumberService GetReused error:%s \r\n", err) + return smsAbnormalNumber, err + } + + if err := e.Orm.Model(&models.SmsAbnormalNumber{}). + Where("id = ?", smsAbnormalNumber.Id). + Update("reused", 1).Error; err != nil { + e.Log.Errorf("SmsAbnormalNumberService GetReused error:%s \r\n", err) + return smsAbnormalNumber, err + } + + return smsAbnormalNumber, nil +} + // GetPage 获取SmsAbnormalNumber列表 func (e *SmsAbnormalNumber) GetPage(c *dto.SmsAbnormalNumberGetPageReq, p *actions.DataPermission, list *[]models.SmsAbnormalNumber, count *int64) error { var err error @@ -151,7 +333,6 @@ func (e *SmsAbnormalNumber) SyncState() error { return nil }) - if err != nil { e.Log.Errorf("同步后保存差异数据失败 %v", err) } @@ -187,6 +368,9 @@ func (e *SmsAbnormalNumber) findMissingNumbers(platformNumbers map[string][]dto. PlatformCode: global.SmsPlatformTextVerified, Account: account, Phone: v.Number, + ActivationId: v.ID, + ReceivedTime: v.CreatedAt, + ApiKey: v.ApiKey, }) } } diff --git a/app/admin/service/sms_phone.go b/app/admin/service/sms_phone.go index f2b08fc..3a06f4c 100644 --- a/app/admin/service/sms_phone.go +++ b/app/admin/service/sms_phone.go @@ -203,7 +203,7 @@ func (e *SmsPhone) WeakUpManage(req *models.SmsPhone, apiInfo *dto.SmsPlatformKe return strconv.Itoa(id), "", nil, nil, code case global.SmsPlatformTextVerified: service := SmsTextVerified{Service: e.Service} - resp, code := service.getExtraActivation(req.NewActivationId, apiInfo) + resp, code := service.getExtraActivation(req.NewActivationId, apiInfo, 0) return resp.ReservationId, resp.Id, resp.UsageWindowStart, resp.UsageWindowEnd, code default: return "", "", nil, nil, statuscode.SmsPlatformUnavailable @@ -594,6 +594,39 @@ func (e *SmsPhone) AutoRenewal() error { return nil } +// 手动续费 +func (e *SmsPhone) ManualRenewal(activationIds []string) ([]string, error) { + if len(activationIds) == 0 { + return nil, errors.New("activationIds 不能为空") + } + + var errorIds []string + var expiredPhones []models.SmsPhone + + // 查询所有到期号码 + if err := e.Orm.Model(&models.SmsPhone{}). + Where("activation_id IN ? and expire_time < ?", activationIds, time.Now()). + Find(&expiredPhones).Error; err != nil { + return nil, err + } + + // 获取服务配置映射 + serviceMap, premiumMap, err := e.getRenewalConfigs() + if err != nil { + return nil, err + } + + // 处理每个到期号码 + for _, phone := range expiredPhones { + if err := e.processPhoneRenewal(phone, serviceMap, premiumMap); err != nil { + e.Log.Errorf("处理号码续费失败 [PhoneID: %d]: %v", phone.Id, err) + errorIds = append(errorIds, fmt.Sprintf("处理号码续费失败 [PhoneID: %d]: %v", phone.Id, err)) + continue + } + } + return errorIds, nil +} + // getExpiredPhones 获取即将到期的自动续费号码 func (e *SmsPhone) getExpiredPhones() ([]models.SmsPhone, error) { var phones []models.SmsPhone @@ -707,8 +740,7 @@ func (e *SmsPhone) executeRenewalTransaction(phone models.SmsPhone, price decima return nil }) - if err.Error() == "余额不足" { - + if err != nil && err.Error() == "余额不足" { return e.handleInsufficientBalance(phone) } diff --git a/app/admin/service/sms_text_verified.go b/app/admin/service/sms_text_verified.go index 1c9365b..9a05d4e 100644 --- a/app/admin/service/sms_text_verified.go +++ b/app/admin/service/sms_text_verified.go @@ -372,16 +372,19 @@ func (e *SmsTextVerified) GetNumberAndWakeUp(tye int, serviceCode string, price return resp, code } - wakeUpResp, code := e.getExtraActivation(resp.Id, apiInfo) + wakeUpResp, code := e.getExtraActivation(resp.Id, apiInfo, 0) if code != statuscode.Success { - return resp, code + //唤醒失败不退出 + now := time.Now() + resp.StartTime = &now + resp.EndTime = &now + } else { + resp.MessageId = wakeUpResp.Id + resp.StartTime = wakeUpResp.UsageWindowStart + resp.EndTime = wakeUpResp.UsageWindowEnd } - resp.MessageId = wakeUpResp.Id - resp.StartTime = wakeUpResp.UsageWindowStart - resp.EndTime = wakeUpResp.UsageWindowEnd - return resp, code } @@ -579,7 +582,7 @@ func (e *SmsTextVerified) GetRentalDetail(id string, apiInfo *dto.SmsPlatformKey // 唤醒号码 // returns activationId,messageId,startTime(可用开始时间),endTime(可用结束时间), code, -func (e SmsTextVerified) getExtraActivation(id string, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.TextVerifiedWakeUpResp, int) { +func (e SmsTextVerified) getExtraActivation(id string, apiInfo *dto.SmsPlatformKeyQueueDto, retry int) (dto.TextVerifiedWakeUpResp, int) { client, code := e.GetTextVerifiedAuthClient(apiInfo) result := dto.TextVerifiedWakeUpResp{} @@ -598,8 +601,13 @@ func (e SmsTextVerified) getExtraActivation(id string, apiInfo *dto.SmsPlatformK status, err := client.Post(wakeUp, req, headers, &resp) if err != nil { - e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err) - return result, statuscode.ServerError + if strings.Contains(err.Error(), "Line is currently busy. Please try this line again later") && retry < 3 { + time.Sleep(300 * time.Millisecond) + return e.getExtraActivation(id, apiInfo, retry+1) + } else { + e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err) + return result, statuscode.ServerError + } } else if status == http.StatusCreated || status == http.StatusOK { if resp.Method != "" && resp.Href != "" { bytes, err := e.doRequest(&resp, apiInfo) @@ -1030,6 +1038,7 @@ func (e *SmsTextVerified) GetNumbers(apiInfo *dto.SmsPlatformKeyQueueDto) ([]dto // 添加第一页数据 for _, item := range resp.Data { item.Number = "1" + item.Number + item.ApiKey = apiInfo.ApiKey result = append(result, item) } @@ -1074,6 +1083,7 @@ func (e *SmsTextVerified) fetchPaginatedData(nextLink *dto.TextVerifiedResp, api for _, item := range nextResp.Data { item.Number = "1" + item.Number + item.ApiKey = apiInfo.ApiKey result = append(result, item) }