Compare commits
	
		
			5 Commits
		
	
	
		
			cbefd85f25
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 837900462a | |||
| 1a5cffa2cc | |||
| 73c6cf9844 | |||
| 996cd27fcb | |||
| 9c826f4966 | 
| @ -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") | ||||
| } | ||||
|  | ||||
| @ -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.SmsRenewalLog{} | ||||
|  | ||||
| 	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") | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| package apis | ||||
|  | ||||
| import ( | ||||
|     "fmt" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/api" | ||||
| @ -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 SmsRenewalLog struct { | ||||
| @ -29,27 +31,27 @@ type SmsRenewalLog struct { | ||||
| // @Router /api/v1/sms-renewal-log [get] | ||||
| // @Security Bearer | ||||
| func (e SmsRenewalLog) GetPage(c *gin.Context) { | ||||
|     req := dto.SmsRenewalLogGetPageReq{} | ||||
|     s := service.SmsRenewalLog{} | ||||
|     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 | ||||
|    	} | ||||
| 	req := dto.SmsRenewalLogGetPageReq{} | ||||
| 	s := service.SmsRenewalLog{} | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	p := actions.GetPermissionFromContext(c) | ||||
| 	list := make([]models.SmsRenewalLog, 0) | ||||
| 	list := make([]dto.SmsRenewalLogResp, 0) | ||||
| 	var count int64 | ||||
|  | ||||
| 	err = s.GetPage(&req, p, &list, &count) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("获取短信续期记录失败,\r\n失败信息 %s", err.Error())) | ||||
|         return | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | ||||
| @ -66,7 +68,7 @@ func (e SmsRenewalLog) GetPage(c *gin.Context) { | ||||
| func (e SmsRenewalLog) Get(c *gin.Context) { | ||||
| 	req := dto.SmsRenewalLogGetReq{} | ||||
| 	s := service.SmsRenewalLog{} | ||||
|     err := e.MakeContext(c). | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		Bind(&req). | ||||
| 		MakeService(&s.Service). | ||||
| @ -82,10 +84,10 @@ func (e SmsRenewalLog) Get(c *gin.Context) { | ||||
| 	err = s.Get(&req, p, &object) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("获取短信续期记录失败,\r\n失败信息 %s", err.Error())) | ||||
|         return | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK( object, "查询成功") | ||||
| 	e.OK(object, "查询成功") | ||||
| } | ||||
|  | ||||
| // Insert 创建短信续期记录 | ||||
| @ -99,25 +101,25 @@ func (e SmsRenewalLog) Get(c *gin.Context) { | ||||
| // @Router /api/v1/sms-renewal-log [post] | ||||
| // @Security Bearer | ||||
| func (e SmsRenewalLog) Insert(c *gin.Context) { | ||||
|     req := dto.SmsRenewalLogInsertReq{} | ||||
|     s := service.SmsRenewalLog{} | ||||
|     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 | ||||
|     } | ||||
| 	req := dto.SmsRenewalLogInsertReq{} | ||||
| 	s := service.SmsRenewalLog{} | ||||
| 	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 | ||||
| 	} | ||||
| 	// 设置创建人 | ||||
| 	req.SetCreateBy(user.GetUserId(c)) | ||||
|  | ||||
| 	err = s.Insert(&req) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("创建短信续期记录失败,\r\n失败信息 %s", err.Error())) | ||||
|         return | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(req.GetId(), "创建成功") | ||||
| @ -135,27 +137,27 @@ func (e SmsRenewalLog) Insert(c *gin.Context) { | ||||
| // @Router /api/v1/sms-renewal-log/{id} [put] | ||||
| // @Security Bearer | ||||
| func (e SmsRenewalLog) Update(c *gin.Context) { | ||||
|     req := dto.SmsRenewalLogUpdateReq{} | ||||
|     s := service.SmsRenewalLog{} | ||||
|     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 | ||||
|     } | ||||
| 	req := dto.SmsRenewalLogUpdateReq{} | ||||
| 	s := service.SmsRenewalLog{} | ||||
| 	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 | ||||
| 	} | ||||
| 	req.SetUpdateBy(user.GetUserId(c)) | ||||
| 	p := actions.GetPermissionFromContext(c) | ||||
|  | ||||
| 	err = s.Update(&req, p) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("修改短信续期记录失败,\r\n失败信息 %s", err.Error())) | ||||
|         return | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK( req.GetId(), "修改成功") | ||||
| 	e.OK(req.GetId(), "修改成功") | ||||
| } | ||||
|  | ||||
| // Delete 删除短信续期记录 | ||||
| @ -167,18 +169,18 @@ func (e SmsRenewalLog) Update(c *gin.Context) { | ||||
| // @Router /api/v1/sms-renewal-log [delete] | ||||
| // @Security Bearer | ||||
| func (e SmsRenewalLog) Delete(c *gin.Context) { | ||||
|     s := service.SmsRenewalLog{} | ||||
|     req := dto.SmsRenewalLogDeleteReq{} | ||||
|     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 | ||||
|     } | ||||
| 	s := service.SmsRenewalLog{} | ||||
| 	req := dto.SmsRenewalLogDeleteReq{} | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	// req.SetUpdateBy(user.GetUserId(c)) | ||||
| 	p := actions.GetPermissionFromContext(c) | ||||
| @ -186,7 +188,74 @@ func (e SmsRenewalLog) Delete(c *gin.Context) { | ||||
| 	err = s.Remove(&req, p) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("删除短信续期记录失败,\r\n失败信息 %s", err.Error())) | ||||
|         return | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK( req.GetId(), "删除成功") | ||||
| 	e.OK(req.GetId(), "删除成功") | ||||
| } | ||||
|  | ||||
| // 手动扣费 | ||||
| func (e SmsRenewalLog) ManualDeduct(c *gin.Context) { | ||||
| 	req := dto.ManualDeductReq{} | ||||
| 	s := service.SmsRenewalLog{} | ||||
|  | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	userId, err := middleware.GetUserIdByApiKey(c) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error("获取用户id失败", err) | ||||
| 		e.Error(statuscode.Unauthorized, nil, statuscode.GetMsg(statuscode.Unauthorized, "en")) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	result, code := s.ManualDeduct(&req, userId) | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Logger.Error(statuscode.GetMsg(code, "en")) | ||||
| 		e.Error(code, nil, statuscode.GetMsg(code, "en")) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(result, "success") | ||||
| } | ||||
|  | ||||
| // 获取详情根据tradeOrderNo | ||||
| func (e *SmsRenewalLog) GetByTradeOrderNo(c *gin.Context) { | ||||
| 	req := dto.ManualDeductDetailReq{} | ||||
| 	s := service.SmsRenewalLog{} | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	userId, err := middleware.GetUserIdByApiKey(c) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error("获取用户id失败", err) | ||||
| 		e.Error(statuscode.Unauthorized, nil, statuscode.GetMsg(statuscode.Unauthorized, "en")) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	result, code := s.GetRenewalDetailByTradeOrderNo(&req, userId) | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Logger.Error(statuscode.GetMsg(code, "en")) | ||||
| 		e.Error(code, nil, statuscode.GetMsg(code, "en")) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(result, "success") | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -30,6 +30,7 @@ type SmsPhone struct { | ||||
| 	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:备注"` | ||||
| 	BillingCycleId  string          `json:"billingCycleId" gorm:"type:varchar(50);comment:手动续费id"` | ||||
| 	PlatformName    string          `json:"platformName" gorm:"-"` | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
|  | ||||
| @ -11,12 +11,24 @@ import ( | ||||
| type SmsRenewalLog struct { | ||||
| 	models.Model | ||||
|  | ||||
| 	PhoneId    int             `json:"phoneId" gorm:"type:bigint;comment:号码id"` | ||||
| 	Type       int             `json:"type" gorm:"type:tinyint;comment:类型 0-长效 1-短效"` | ||||
| 	UserId     int             `json:"userId" gorm:"type:bigint;comment:用户id"` | ||||
| 	Amount     decimal.Decimal `json:"amount" gorm:"type:decimal(10,2);comment:扣费金额"` | ||||
| 	BeforeTime time.Time       `json:"beforeTime" gorm:"type:datetime;comment:续费前过期时间"` | ||||
| 	Period     int             `json:"period" gorm:"type:int;comment:时间段"` | ||||
| 	PhoneId        int             `json:"phoneId" gorm:"type:bigint;comment:号码id"` | ||||
| 	Phone          string          `json:"phone" gorm:"type:varchar(50);comment:号码"` | ||||
| 	PayOrderNo     string          `json:"payOrderNo" gorm:"type:varchar(50);comment:本平台支付订单号"` | ||||
| 	TradeOrderNo   string          `json:"tradeOrderNo" gorm:"type:varchar(50);comment:交易订单号"` | ||||
| 	Status         int             `json:"status" gorm:"type:tinyint;comment:状态 1-处理中(冻结) 2-成功 3-失败(解冻)"` | ||||
| 	Type           int             `json:"type" gorm:"type:tinyint;comment:类型 0-长效 1-短效"` | ||||
| 	Category       int             `json:"category" gorm:"type:tinyint;comment:类别 1-购买号码 2-续费号码"` | ||||
| 	UserId         int             `json:"userId" gorm:"type:bigint;comment:用户id"` | ||||
| 	Amount         decimal.Decimal `json:"amount" gorm:"type:decimal(10,2);comment:扣费金额"` | ||||
| 	BeforeTime     time.Time       `json:"beforeTime" gorm:"type:datetime;comment:续费前过期时间"` | ||||
| 	TargetTime     *time.Time      `json:"targetTime" gorm:"type:datetime;comment:续费目标过期时间"` | ||||
| 	Period         int             `json:"period" gorm:"type:int;comment:时间段"` | ||||
| 	Remark         string          `json:"remark" gorm:"type:varchar(255);comment:备注"` | ||||
| 	Username       string          `json:"username" gorm:"->"` | ||||
| 	PlatformCode   string          `json:"platformCode" gorm:"->"` | ||||
| 	ApiKey         string          `json:"apiKey" gorm:"->"` | ||||
| 	BillingCycleId string          `json:"billingCycleId" gorm:"->"` | ||||
| 	ActivationId   string          `json:"activationId" gorm:"->"` | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
| } | ||||
|  | ||||
| @ -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) //重用号码 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -5,8 +5,8 @@ import ( | ||||
| 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | ||||
|  | ||||
| 	"go-admin/app/admin/apis" | ||||
| 	"go-admin/common/middleware" | ||||
| 	"go-admin/common/actions" | ||||
| 	"go-admin/common/middleware" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @ -16,6 +16,14 @@ func init() { | ||||
| // registerSmsRenewalLogRouter | ||||
| func registerSmsRenewalLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { | ||||
| 	api := apis.SmsRenewalLog{} | ||||
| 	r1 := v1.Group("sms-renewal-log").Use(middleware.FrontedAuth) | ||||
| 	{ | ||||
| 		// 手动扣费续期 | ||||
| 		r1.POST("manual-deduct", api.ManualDeduct) | ||||
| 		//根据交易订单号获取续期详情 | ||||
| 		r1.GET("detail-byorderno", api.GetByTradeOrderNo) | ||||
| 	} | ||||
|  | ||||
| 	r := v1.Group("/sms-renewal-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) | ||||
| 	{ | ||||
| 		r.GET("", actions.PermissionAction(), api.GetPage) | ||||
| @ -24,4 +32,4 @@ func registerSmsRenewalLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWT | ||||
| 		r.PUT("/:id", actions.PermissionAction(), api.Update) | ||||
| 		r.DELETE("", api.Delete) | ||||
| 	} | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -14,8 +14,10 @@ type SmsPhoneGetPageReq struct { | ||||
| 	dto.Pagination `search:"-"` | ||||
| 	Service        string `form:"service"  search:"type:exact;column:service;table:sms_phone" comment:"sms 服务"` | ||||
| 	PlatformCode   string `form:"platformCode"  search:"type:exact;column:platform_code;table:sms_phone" comment:"平台code"` | ||||
| 	ServiceCode    string `form:"serviceCode"  search:"type:exact;column:service_code;table:sms_phone" comment:"服务code"` | ||||
| 	ServiceCode    string `form:"serviceCode"  search:"type:contains;column:service_code;table:sms_phone" comment:"服务code"` | ||||
| 	Phone          string `form:"phone" search:"type:contains;column:phone;table:sms_phone" comment:"号码"` | ||||
| 	Type           int    `form:"type"  search:"-" comment:"类型 0-短效 1-长效"` | ||||
| 	IsActived      int    `form:"isActived" search:"-" comment:"是否可用"` | ||||
| 	SmsPhoneOrder | ||||
| } | ||||
|  | ||||
| @ -192,12 +194,13 @@ type SmsPhoneCleanMyPhoneReq struct { | ||||
| } | ||||
|  | ||||
| type SmsPhoneGetPhoneResp struct { | ||||
| 	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"` | ||||
| 	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"` | ||||
| 	BillingCycleId string     `json:"billingCycleId" comment:"续租id"` | ||||
| } | ||||
|  | ||||
| type DaisysmsPriceResp struct { | ||||
| @ -221,3 +224,37 @@ 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"` | ||||
| 	TradeOrderNo  string   `json:"tradeOrderNo" comment:"交易订单号"` | ||||
| } | ||||
|  | ||||
| // 手动续期回调请求 | ||||
| type ManualRenewCallBackReq struct { | ||||
| 	TradeOrderNo string `json:"tradeOrderNo" comment:"交易订单号"` | ||||
| 	PayOrderNo   string `json:"payOrderNo" comment:"本平台支付订单号"` | ||||
| 	Status       int    `json:"status" comment:"状态 1-处理中 2-失败 3-回滚 4-成功"` | ||||
| 	Remark       string `json:"remark" comment:"备注"` | ||||
| } | ||||
|  | ||||
| // 获取号码响应 | ||||
| type SmsPhoneGetNumberResp struct { | ||||
| 	ActivationId   string     `json:"activationId" comment:"激活码id"` | ||||
| 	Phone          string     `json:"phone" comment:"号码"` | ||||
| 	ExpireTime     *time.Time `json:"expireTime" comment:"过期时间"` | ||||
| 	StartTime      *time.Time `json:"startTime" comment:"唤醒后单次接码开始时间"` | ||||
| 	EndTime        *time.Time `json:"endTime" comment:"唤醒后单次接码过期时间"` | ||||
| 	BillingCycleId string     `json:"billingCycleId" comment:"续租id"` | ||||
| } | ||||
|  | ||||
| @ -12,7 +12,9 @@ import ( | ||||
|  | ||||
| type SmsRenewalLogGetPageReq struct { | ||||
| 	dto.Pagination `search:"-"` | ||||
| 	Type           int64 `form:"type"  search:"type:exact;column:type;table:sms_renewal_log" comment:"类型 0-长效 1-短效"` | ||||
| 	Type           int64  `form:"type"  search:"type:exact;column:type;table:sms_renewal_log" comment:"类型 0-长效 1-短效"` | ||||
| 	Category       int    `form:"category"  search:"type:exact;column:category;table:sms_renewal_log" comment:"类别 0-普通续费 1-号码补偿"` | ||||
| 	UserName       string `form:"userName" search:"-"` | ||||
| 	SmsRenewalLogOrder | ||||
| } | ||||
|  | ||||
| @ -108,3 +110,43 @@ type SmsRenewalLogDeleteReq struct { | ||||
| func (s *SmsRenewalLogDeleteReq) GetId() interface{} { | ||||
| 	return s.Ids | ||||
| } | ||||
|  | ||||
| type SmsRenewalLogResp struct { | ||||
| 	Id           int             `json:"id" comment:"主键id"` // 主键id | ||||
| 	PhoneId      int             `json:"phoneId" comment:"号码id"` | ||||
| 	Phone        string          `json:"phone" comment:"电话号码"` | ||||
| 	TradeOrderNo string          `json:"tradeOrderNo" comment:"交易订单号"` | ||||
| 	PayOrderNo   string          `json:"payOrderNo" comment:"支付订单号"` | ||||
| 	Type         int             `json:"type" comment:"类型 0-长效 1-短效"` | ||||
| 	Category     int             `json:"category" comment:"类别 1-购买 2-续期"` | ||||
| 	UserId       int             `json:"userId" comment:"用户id"` | ||||
| 	Amount       decimal.Decimal `json:"amount" comment:"扣费金额"` | ||||
| 	BeforeTime   time.Time       `json:"beforeTime" comment:"续费前过期时间"` | ||||
| 	Period       int             `json:"period" comment:"时间(天)"` | ||||
| 	CreatedAt    time.Time       `json:"createdAt" comment:"创建时间"` | ||||
| 	Status       int             `json:"status" comment:"状态 1-预扣费 2-成功 3-失败"` | ||||
| 	// UpdatedAt  time.Time       `json:"updatedAt" comment:"更新时间"` | ||||
| 	// DeletedAt  time.Time       `json:"deletedAt" comment:"删除时间"` | ||||
| 	UserName string `json:"username" comment:"用户名"` | ||||
| } | ||||
|  | ||||
| // 手动扣费 | ||||
| type ManualDeductReq struct { | ||||
| 	ActivationId string    `json:"activationId" comment:"激活id"` | ||||
| 	TradeOrderNo string    `json:"tradeOrderNo" comment:"交易订单号"` | ||||
| 	BeginTime    time.Time `json:"beginTime" comment:"开始时间"` | ||||
| } | ||||
|  | ||||
| type ManualDeductResp struct { | ||||
| 	ActivationId string     `json:"activationId" comment:"激活id"` | ||||
| 	TradeOrderNo string     `json:"tradeOrderNo" comment:"交易订单号"` | ||||
| 	PayOrderNo   string     `json:"payOrderNo" comment:"支付订单号"` | ||||
| 	BeginTime    time.Time  `json:"beginTime" comment:"开始时间"` | ||||
| 	EndTime      *time.Time `json:"endTime" comment:"结束时间"` | ||||
| 	Status       int        `json:"status" comment:"状态 1-预扣费 2-成功 3-失败"` | ||||
| } | ||||
|  | ||||
| // 续费详情 | ||||
| type ManualDeductDetailReq struct { | ||||
| 	TradeOrderNo string `json:"tradeOrderNo" comment:"交易订单号"` | ||||
| } | ||||
|  | ||||
| @ -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: 正在等待验证码。 | ||||
| @ -159,16 +160,17 @@ type VerificationDTO struct { | ||||
|  | ||||
| // 长效详情 | ||||
| type VerificationRentalDetailResp struct { | ||||
| 	Number      string           `json:"number"` | ||||
| 	SMS         TextVerifiedResp `json:"sms"` | ||||
| 	Calls       TextVerifiedResp `json:"calls"` | ||||
| 	CreatedAt   time.Time        `json:"createdAt"` | ||||
| 	ID          string           `json:"id"` | ||||
| 	Sale        TextVerifiedResp `json:"sale"` | ||||
| 	SaleId      string           `json:"saleId"` | ||||
| 	ServiceName string           `json:"serviceName"` | ||||
| 	State       string           `json:"state"` | ||||
| 	AlwaysOn    bool             `json:"alwaysOn"` | ||||
| 	Number         string           `json:"number"` | ||||
| 	SMS            TextVerifiedResp `json:"sms"` | ||||
| 	Calls          TextVerifiedResp `json:"calls"` | ||||
| 	CreatedAt      time.Time        `json:"createdAt"` | ||||
| 	ID             string           `json:"id"` | ||||
| 	Sale           TextVerifiedResp `json:"sale"` | ||||
| 	SaleId         string           `json:"saleId"` | ||||
| 	BillingCycleId string           `json:"billingCycleId" comment:"续租id"` | ||||
| 	ServiceName    string           `json:"serviceName"` | ||||
| 	State          string           `json:"state"` | ||||
| 	AlwaysOn       bool             `json:"alwaysOn"` | ||||
| } | ||||
|  | ||||
| // 包含可执行标志 + 链接 | ||||
| @ -220,6 +222,38 @@ type TextVerifiedGetNumbersResp struct { | ||||
| 	Data    []VerificationDTO               `json:"data"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedGetRentalListResp struct { | ||||
| 	HasNext bool                            `json:"hasNext" comment:"是否有下一页"` | ||||
| 	Links   TextVerifiedGetNumbersLinksResp `json:"links"` | ||||
| 	Data    []VerificationRentalDetailResp  `json:"data"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedGetNumbersLinksResp struct { | ||||
| 	Next *TextVerifiedResp `json:"next"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedManualRenewalResp struct { | ||||
| 	CreatedAt      time.Time                         `json:"createdAt" comment:"创建时间"` | ||||
| 	Id             string                            `json:"id" comment:"id"` | ||||
| 	ExcludeRentals []TextVerifiedManualRenewalRental `json:"excludeRentals" comment:"排除的长效详情"` | ||||
| 	IncludeRentals []TextVerifiedManualRenewalRental `json:"includeRentals" comment:"包含的长效详情"` | ||||
| 	IsPaidFor      bool                              `json:"isPaidFor" comment:"是否已支付"` | ||||
| 	TotalCost      decimal.Decimal                   `json:"totalCost" comment:"总费用"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedManualRenewalRental struct { | ||||
| 	Number         string           `json:"number" comment:"号码"` | ||||
| 	Rental         TextVerifiedResp `json:"rental" comment:"长效详情"` | ||||
| 	RenewalCost    decimal.Decimal  `json:"renewalCost" comment:"续费费用"` | ||||
| 	ServiceName    string           `json:"serviceName" comment:"服务名称"` | ||||
| 	AlreadyRenewed bool             `json:"alreadyRenewed" comment:"是否已续费"` | ||||
| } | ||||
|  | ||||
| // 续费周期详情 | ||||
| type TextVerifiedGetBillingCycleResp struct { | ||||
| 	Id                   string    `json:"id" comment:"id"` | ||||
| 	RenewedThrough       time.Time `json:"renewedThrough" comment:"已续费到"` | ||||
| 	BillingCycleEndsAt   time.Time `json:"billingCycleEndsAt" comment:"计费周期结束时间"` | ||||
| 	NextAutoRenewAttempt time.Time `json:"nextAutoRenewAttempt" comment:"下次自动续费时间"` | ||||
| 	State                string    `json:"state" comment:"是否激活 active"` | ||||
| } | ||||
|  | ||||
| @ -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, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -17,6 +17,7 @@ import ( | ||||
| 	cDto "go-admin/common/dto" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"go-admin/utils/utility" | ||||
| ) | ||||
|  | ||||
| type SmsPhone struct { | ||||
| @ -24,7 +25,7 @@ type SmsPhone struct { | ||||
| } | ||||
|  | ||||
| // open-API 获取电话号码 | ||||
| func (e SmsPhone) OpenGetNumber(req *dto.GetNumberReq, userId int) (dto.OpenGetNumberResp, int) { | ||||
| func (e *SmsPhone) OpenGetNumber(req *dto.GetNumberReq, userId int) (dto.OpenGetNumberResp, int) { | ||||
| 	resp := dto.OpenGetNumberResp{} | ||||
| 	balanceService := MemberBalance{Service: e.Service} | ||||
| 	balance, smsPhone, i := e.DoGetNumber(&balanceService, req, userId) | ||||
| @ -83,6 +84,11 @@ func (e *SmsPhone) CancelNumber(req *dto.SmsPhoneCancelNumberReq, userId int) in | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("取消租赁后修改失败, %s", err) | ||||
| 			} | ||||
|  | ||||
| 			if err1 := e.Orm. | ||||
| 				Where("phone_id = ?", data.Id).Delete(&models.SmsRenewalLog{}).Error; err1 != nil { | ||||
| 				e.Log.Errorf("更新短信号码续费日志失败, %s", err1) | ||||
| 			} | ||||
| 		} else { | ||||
| 			e.Log.Errorf("取消租赁失败, %s", code) | ||||
| 			return code | ||||
| @ -203,7 +209,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 | ||||
| @ -316,7 +322,7 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| 	} | ||||
|  | ||||
| 	now := time.Now() | ||||
| 	activationId, phone, code, expireTime, startTime, endTime, apiInfo := e.GetNumberManage(req.PlatformCode, req.Type, req.ServiceCode, price, req.Period) | ||||
| 	numberResp, code, apiInfo := e.GetNumberManage(req.PlatformCode, req.Type, req.ServiceCode, price, req.Period) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, code | ||||
| @ -324,24 +330,25 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
|  | ||||
| 	smsPhone := models.SmsPhone{} | ||||
| 	smsPhone.PlatformCode = req.PlatformCode | ||||
| 	smsPhone.Phone = phone | ||||
| 	smsPhone.Phone = numberResp.Phone | ||||
| 	smsPhone.UserId = userId | ||||
| 	smsPhone.Service = serviceItem.Name | ||||
| 	smsPhone.ServiceCode = req.ServiceCode | ||||
| 	smsPhone.Type = req.Type | ||||
| 	smsPhone.Period = req.Period | ||||
| 	smsPhone.ActivationId = activationId | ||||
| 	smsPhone.MessageId = activationId | ||||
| 	smsPhone.NewActivationId = activationId | ||||
| 	smsPhone.ActivationId = numberResp.ActivationId | ||||
| 	smsPhone.MessageId = numberResp.ActivationId | ||||
| 	smsPhone.NewActivationId = numberResp.ActivationId | ||||
| 	smsPhone.Actived = 1 | ||||
| 	smsPhone.StartTime = startTime | ||||
| 	smsPhone.EndTime = endTime | ||||
| 	smsPhone.StartTime = numberResp.StartTime | ||||
| 	smsPhone.EndTime = numberResp.EndTime | ||||
| 	smsPhone.ApiKey = apiInfo.ApiKey | ||||
|  | ||||
| 	smsPhone.BillingCycleId = numberResp.BillingCycleId | ||||
| 	smsPhone.Price = price | ||||
|  | ||||
| 	if req.Type == 1 { | ||||
| 		if expireTime != nil { | ||||
| 			smsPhone.ExpireTime = expireTime | ||||
| 		if numberResp.ExpireTime != nil { | ||||
| 			smsPhone.ExpireTime = numberResp.ExpireTime | ||||
| 		} else { | ||||
| 			days := req.Period * 30 | ||||
| 			now = now.AddDate(0, 0, days) | ||||
| @ -353,8 +360,8 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| 			smsPhone.Actived = 2 | ||||
| 		} | ||||
| 	} else { | ||||
| 		if expireTime != nil { | ||||
| 			smsPhone.ExpireTime = expireTime | ||||
| 		if numberResp.ExpireTime != nil { | ||||
| 			smsPhone.ExpireTime = numberResp.ExpireTime | ||||
| 		} else { | ||||
| 			now = now.Add(time.Minute * time.Duration(serviceItem.ExpirationMinutes)) | ||||
| 			smsPhone.ExpireTime = &now | ||||
| @ -393,6 +400,23 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	log := models.SmsRenewalLog{} | ||||
| 	log.PhoneId = smsPhone.Id | ||||
| 	log.PayOrderNo = utility.GenerateTraceID() | ||||
| 	log.Phone = smsPhone.Phone | ||||
| 	log.Type = smsPhone.Type | ||||
| 	log.Category = 1 | ||||
| 	log.UserId = userId | ||||
| 	log.Amount = price | ||||
| 	log.BeforeTime = time.Now() | ||||
| 	log.Period = req.Period * 30 | ||||
| 	log.Status = 2 | ||||
| 	log.TargetTime = smsPhone.ExpireTime | ||||
|  | ||||
| 	if err := e.Orm.Save(&log).Error; err != nil { | ||||
| 		e.Log.Errorf("保存短信续费日志失败,phone_id:%d, %s", smsPhone.Id, err) | ||||
| 	} | ||||
|  | ||||
| 	return balance, smsPhone, statuscode.Success | ||||
| } | ||||
|  | ||||
| @ -404,31 +428,44 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| // *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, *dto.SmsPlatformKeyQueueDto) { | ||||
| 	price decimal.Decimal, period int) (dto.SmsPhoneGetNumberResp, int, *dto.SmsPlatformKeyQueueDto) { | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	queue, err := smsPlatformKeyRedis.GetRoundRobinKey(platformCode) | ||||
| 	result := dto.SmsPhoneGetNumberResp{} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取短信平台队列失败, %s", err) | ||||
| 		return "", "", statuscode.ServerError, nil, nil, nil, queue | ||||
| 		return result, statuscode.ServerError, queue | ||||
| 	} | ||||
|  | ||||
| 	switch platformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
| 		activationId, phone, code := service.GetNumberForApi(queue, typ, serviceCode, price, period) | ||||
| 		result.ActivationId = strconv.Itoa(activationId) | ||||
| 		result.Phone = phone | ||||
| 		result.ExpireTime = nil | ||||
| 		result.StartTime = nil | ||||
| 		result.EndTime = nil | ||||
|  | ||||
| 		return strconv.Itoa(activationId), phone, code, nil, nil, nil, queue | ||||
| 		return result, code, queue | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
| 		resp, code := service.GetNumberAndWakeUp(typ, serviceCode, price, period, queue) | ||||
| 		result.ActivationId = resp.Id | ||||
| 		result.Phone = resp.Phone | ||||
| 		result.ExpireTime = resp.EndAt | ||||
| 		result.StartTime = resp.StartTime | ||||
| 		result.EndTime = resp.EndTime | ||||
| 		result.BillingCycleId = resp.BillingCycleId | ||||
|  | ||||
| 		if code != statuscode.Success { | ||||
| 			return "", "", code, nil, resp.StartTime, resp.EndTime, queue | ||||
| 			return result, code, queue | ||||
| 		} | ||||
|  | ||||
| 		return resp.Id, resp.Phone, code, resp.EndAt, resp.StartTime, resp.EndTime, queue | ||||
| 		return result, code, queue | ||||
| 	default: | ||||
| 		return "", "", statuscode.SmsPlatformUnavailable, nil, nil, nil, queue | ||||
| 		return result, statuscode.SmsPlatformUnavailable, queue | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -568,183 +605,6 @@ func (e *SmsPhone) GetCodeManage(platformCode string, messageId string, typ int, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AutoRenewal 自动续期处理 | ||||
| // 处理即将到期的长期号码自动续费逻辑 | ||||
| func (e *SmsPhone) AutoRenewal() error { | ||||
| 	// 获取24小时内到期的自动续费号码 | ||||
| 	expiredPhones, err := e.getExpiredPhones() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// 获取服务配置映射 | ||||
| 	serviceMap, premiumMap, err := e.getRenewalConfigs() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// 处理每个到期号码 | ||||
| 	for _, phone := range expiredPhones { | ||||
| 		if err := e.processPhoneRenewal(phone, serviceMap, premiumMap); err != nil { | ||||
| 			e.Log.Errorf("处理号码续费失败 [PhoneID: %d]: %v", phone.Id, err) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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.ApiKey, 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 | ||||
| @ -772,8 +632,9 @@ func (e *SmsPhone) ChangeAutoRenew(req *dto.SmsPhoneChangeAutoRenewReq, userId i | ||||
| 		configService := SysConfig{Service: e.Service} | ||||
| 		configKeys := []string{"renew_number_premium_daisysms", "renew_number_premium_textverified"} | ||||
| 		premiumMap, _ := configService.GetMapByKeys(configKeys) | ||||
| 		renewalService := SmsRenewalLog{Service: e.Service} | ||||
|  | ||||
| 		price := e.calculateRenewalPrice(serviceItem.LongPrice, serviceItem.PlatformCode, premiumMap) | ||||
| 		price := renewalService.calculateRenewalPrice(serviceItem.LongPrice, serviceItem.PlatformCode, premiumMap) | ||||
|  | ||||
| 		if balance.Cmp(price) < 0 { | ||||
| 			return statuscode.BalanceNotEnough | ||||
| @ -864,12 +725,20 @@ func (e *SmsPhone) GetPage(c *dto.SmsPhoneGetPageReq, p *actions.DataPermission, | ||||
| 	var err error | ||||
| 	var data models.SmsPhone | ||||
|  | ||||
| 	err = e.Orm.Model(&data). | ||||
| 	query := e.Orm.Model(&data). | ||||
| 		Scopes( | ||||
| 			cDto.MakeCondition(c.GetNeedSearch()), | ||||
| 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		). | ||||
| 		) | ||||
|  | ||||
| 	switch c.IsActived { | ||||
| 	case 1: | ||||
| 		query = query.Where("expire_time > ?", time.Now()) | ||||
| 	case 2: | ||||
| 		query = query.Where("expire_time < ?", time.Now()) | ||||
| 	} | ||||
| 	err = query. | ||||
| 		Find(list).Limit(-1).Offset(-1). | ||||
| 		Count(count).Error | ||||
| 	if err != nil { | ||||
| @ -982,3 +851,71 @@ func (e *SmsPhone) DeleteExpired() error { | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 同步手动续费id | ||||
| func (e *SmsPhone) SyncBilingCycleId() error { | ||||
| 	var logs []models.SmsPhone | ||||
| 	logMap := map[string]models.SmsPhone{} | ||||
| 	if err := e.Orm.Model(&models.SmsPhone{}).Where("billing_cycle_id = '' or billing_cycle_id is null").Find(&logs).Error; err != nil { | ||||
| 		e.Log.Errorf("查询手动续费id为空的记录失败 err:%v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range logs { | ||||
| 		logMap[item.ActivationId] = item | ||||
| 	} | ||||
|  | ||||
| 	textVerifiedService := SmsTextVerified{Service: e.Service} | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetApiInfo(global.SmsPlatformTextVerified, "ZQ0swXnsaPpeGdwa3c7gT9U9I1Oh9WoDHx0amuYovvaHuqd5u6B4NBBUSUBjR") | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取平台密钥失败: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, err = e.syncLoop("", textVerifiedService, apiInfo, logMap) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *SmsPhone) syncLoop(nextUrl string, textVerifiedService SmsTextVerified, apiInfo dto.SmsPlatformKeyQueueDto, logMap map[string]models.SmsPhone) (dto.TextVerifiedGetRentalListResp, error) { | ||||
| 	if len(logMap) == 0 { | ||||
| 		return dto.TextVerifiedGetRentalListResp{}, nil | ||||
| 	} | ||||
|  | ||||
| 	rentalList, err := textVerifiedService.GetRentalList(nextUrl, &apiInfo) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取平台密钥失败: %v", err) | ||||
| 		return dto.TextVerifiedGetRentalListResp{}, err | ||||
| 	} | ||||
|  | ||||
| 	for _, log := range rentalList.Data { | ||||
| 		if log.BillingCycleId == "" { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if item, ok := logMap[log.ID]; ok { | ||||
| 			if err1 := e.Orm.Model(&models.SmsPhone{}).Where("activation_id = ?", item.ActivationId).Update("billing_cycle_id", log.BillingCycleId).Error; err1 != nil { | ||||
| 				e.Log.Errorf("更新手动续费id失败 err:%v", err1) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			delete(logMap, item.ActivationId) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if rentalList.HasNext { | ||||
| 		time.Sleep(150 * time.Millisecond) | ||||
| 		rentalList, err = e.syncLoop(rentalList.Links.Next.Href, textVerifiedService, apiInfo, logMap) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("同步续费id失败 err:%v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return rentalList, nil | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"go-admin/utils/utility" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| @ -63,3 +64,31 @@ func TestGetPrices(t *testing.T) { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSyncRenewalLogs(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	service := SmsPhone{} | ||||
| 	service.Orm = db | ||||
| 	service.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	if err := service.SyncBilingCycleId(); err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestManualRenewal(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	service := SmsRenewalLog{} | ||||
| 	service.Orm = db | ||||
| 	service.Log = logger.NewHelper(logger.DefaultLogger) | ||||
| 	activationId := "lr_01K5DP71G06SFX84S7W99D61F9" | ||||
|  | ||||
| 	if _, err := service.ManualDeduct(&dto.ManualDeductReq{ | ||||
| 		ActivationId: activationId, | ||||
| 		TradeOrderNo: utility.GenerateTraceID(), | ||||
| 	}, 1); err != statuscode.Success { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ import ( | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/config" | ||||
| 	"go-admin/utils/redishelper" | ||||
| 	"go-admin/utils/utility" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| @ -13,7 +14,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. | ||||
| @ -25,6 +26,7 @@ func initSetting() *gorm.DB { | ||||
| 	redishelper.InitDefaultRedis("127.0.0.1:6379", "", 4) | ||||
| 	redishelper.InitLockRedisConn("127.0.0.1:6379", "", "4") | ||||
|  | ||||
| 	utility.InitSnowflake() | ||||
| 	return db | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -2,14 +2,21 @@ package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
|     "github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
|  | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/actions" | ||||
| 	cDto "go-admin/common/dto" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"go-admin/utils/utility" | ||||
| ) | ||||
|  | ||||
| type SmsRenewalLog struct { | ||||
| @ -17,22 +24,45 @@ type SmsRenewalLog struct { | ||||
| } | ||||
|  | ||||
| // GetPage 获取SmsRenewalLog列表 | ||||
| func (e *SmsRenewalLog) GetPage(c *dto.SmsRenewalLogGetPageReq, p *actions.DataPermission, list *[]models.SmsRenewalLog, count *int64) error { | ||||
| func (e *SmsRenewalLog) GetPage(c *dto.SmsRenewalLogGetPageReq, p *actions.DataPermission, list *[]dto.SmsRenewalLogResp, count *int64) error { | ||||
| 	var err error | ||||
| 	var data models.SmsRenewalLog | ||||
|  | ||||
| 	err = e.Orm.Model(&data). | ||||
| 	var datas []models.SmsRenewalLog | ||||
| 	query := e.Orm.Model(&data). | ||||
| 		Joins("left join sys_user on sys_user.user_id = sms_renewal_log.user_id"). | ||||
| 		Scopes( | ||||
| 			cDto.MakeCondition(c.GetNeedSearch()), | ||||
| 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		). | ||||
| 		Find(list).Limit(-1).Offset(-1). | ||||
| 		) | ||||
|  | ||||
| 	if c.UserName != "" { | ||||
| 		query = query.Where("sys_user.username LIKE ?", "%"+c.UserName+"%") | ||||
| 	} | ||||
| 	err = query.Select("sms_renewal_log.*, sys_user.username").Find(&datas).Limit(-1).Offset(-1). | ||||
| 		Count(count).Error | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("SmsRenewalLogService GetPage error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range datas { | ||||
| 		*list = append(*list, dto.SmsRenewalLogResp{ | ||||
| 			Id:           v.Id, | ||||
| 			UserId:       v.UserId, | ||||
| 			Category:     v.Category, | ||||
| 			Phone:        v.Phone, | ||||
| 			Amount:       v.Amount, | ||||
| 			BeforeTime:   v.BeforeTime, | ||||
| 			Period:       v.Period, | ||||
| 			CreatedAt:    v.CreatedAt, | ||||
| 			UserName:     v.Username, | ||||
| 			TradeOrderNo: v.TradeOrderNo, | ||||
| 			PayOrderNo:   v.PayOrderNo, | ||||
| 			Status:       v.Status, | ||||
| 		}) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -59,9 +89,9 @@ func (e *SmsRenewalLog) Get(d *dto.SmsRenewalLogGetReq, p *actions.DataPermissio | ||||
|  | ||||
| // Insert 创建SmsRenewalLog对象 | ||||
| func (e *SmsRenewalLog) Insert(c *dto.SmsRenewalLogInsertReq) error { | ||||
|     var err error | ||||
|     var data models.SmsRenewalLog | ||||
|     c.Generate(&data) | ||||
| 	var err error | ||||
| 	var data models.SmsRenewalLog | ||||
| 	c.Generate(&data) | ||||
| 	err = e.Orm.Create(&data).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("SmsRenewalLogService Insert error:%s \r\n", err) | ||||
| @ -72,22 +102,22 @@ func (e *SmsRenewalLog) Insert(c *dto.SmsRenewalLogInsertReq) error { | ||||
|  | ||||
| // Update 修改SmsRenewalLog对象 | ||||
| func (e *SmsRenewalLog) Update(c *dto.SmsRenewalLogUpdateReq, p *actions.DataPermission) error { | ||||
|     var err error | ||||
|     var data = models.SmsRenewalLog{} | ||||
|     e.Orm.Scopes( | ||||
|             actions.Permission(data.TableName(), p), | ||||
|         ).First(&data, c.GetId()) | ||||
|     c.Generate(&data) | ||||
| 	var err error | ||||
| 	var data = models.SmsRenewalLog{} | ||||
| 	e.Orm.Scopes( | ||||
| 		actions.Permission(data.TableName(), p), | ||||
| 	).First(&data, c.GetId()) | ||||
| 	c.Generate(&data) | ||||
|  | ||||
|     db := e.Orm.Save(&data) | ||||
|     if err = db.Error; err != nil { | ||||
|         e.Log.Errorf("SmsRenewalLogService Save error:%s \r\n", err) | ||||
|         return err | ||||
|     } | ||||
|     if db.RowsAffected == 0 { | ||||
|         return errors.New("无权更新该数据") | ||||
|     } | ||||
|     return nil | ||||
| 	db := e.Orm.Save(&data) | ||||
| 	if err = db.Error; err != nil { | ||||
| 		e.Log.Errorf("SmsRenewalLogService Save error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权更新该数据") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Remove 删除SmsRenewalLog | ||||
| @ -99,11 +129,559 @@ func (e *SmsRenewalLog) Remove(d *dto.SmsRenewalLogDeleteReq, p *actions.DataPer | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		).Delete(&data, d.GetId()) | ||||
| 	if err := db.Error; err != nil { | ||||
|         e.Log.Errorf("Service RemoveSmsRenewalLog error:%s \r\n", err) | ||||
|         return err | ||||
|     } | ||||
|     if db.RowsAffected == 0 { | ||||
|         return errors.New("无权删除该数据") | ||||
|     } | ||||
| 		e.Log.Errorf("Service RemoveSmsRenewalLog error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权删除该数据") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 手动扣费 | ||||
| func (e *SmsRenewalLog) ManualDeduct(req *dto.ManualDeductReq, userId int) (dto.ManualDeductResp, int) { | ||||
| 	var entity models.SmsPhone | ||||
| 	var result dto.ManualDeductResp | ||||
| 	phoneService := SmsPhone{} | ||||
| 	phoneService.Orm = e.Orm | ||||
| 	phoneService.Log = e.Log | ||||
|  | ||||
| 	if err := e.Orm.Model(&entity). | ||||
| 		Where("activation_id =?", req.ActivationId). | ||||
| 		First(&entity).Error; err != nil { | ||||
| 		e.Log.Errorf("获取短信号码失败, %s", err) | ||||
| 		return result, statuscode.SmsNotExisted | ||||
| 	} | ||||
|  | ||||
| 	// 获取服务配置映射 | ||||
| 	serviceMap, premiumMap, err := phoneService.getRenewalConfigs() | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取短信服务配置失败, %s", err) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	var count int64 | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SmsRenewalLog{}). | ||||
| 		Where("phone_id = ? AND status = 1", entity.Id). | ||||
| 		Count(&count).Error; err != nil { | ||||
| 		e.Log.Errorf("查询号码续费记录失败 [PhoneID: %d]: %v", entity.Id, err) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if count > 0 { | ||||
| 		return result, statuscode.SmsRenewalLogExisted | ||||
| 	} | ||||
|  | ||||
| 	if renewLog, err := e.processPhoneRenewal(entity, serviceMap, premiumMap, req.TradeOrderNo, false); err != nil { | ||||
| 		e.Log.Errorf("处理号码续费失败 [PhoneID: %d 续费订单号:%s]: %v", entity.Id, renewLog.PayOrderNo, err) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} else { | ||||
| 		result.ActivationId = req.ActivationId | ||||
| 		result.TradeOrderNo = renewLog.TradeOrderNo | ||||
| 		result.PayOrderNo = renewLog.PayOrderNo | ||||
| 		result.BeginTime = req.BeginTime | ||||
| 		result.EndTime = renewLog.TargetTime | ||||
| 	} | ||||
|  | ||||
| 	return result, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 续期详情 | ||||
| func (e *SmsRenewalLog) GetRenewalDetailByTradeOrderNo(req *dto.ManualDeductDetailReq, userId int) (dto.ManualDeductResp, int) { | ||||
| 	var entity models.SmsRenewalLog | ||||
| 	result := dto.ManualDeductResp{} | ||||
|  | ||||
| 	if err := e.Orm.Model(entity). | ||||
| 		Joins("LEFT JOIN sms_phone s on s.id = sms_renewal_log.phone_id"). | ||||
| 		Where("sms_renewal_log.trade_order_no =? and sms_renewal_log.user_id =?", req.TradeOrderNo, userId). | ||||
| 		Select("sms_renewal_log.*, s.activation_id"). | ||||
| 		First(&entity).Error; err != nil { | ||||
| 		e.Log.Errorf("获取续费记录失败, %s", err) | ||||
| 		return result, statuscode.SmsNotExisted | ||||
| 	} | ||||
|  | ||||
| 	result.ActivationId = entity.ActivationId | ||||
| 	result.TradeOrderNo = entity.TradeOrderNo | ||||
| 	result.PayOrderNo = entity.PayOrderNo | ||||
| 	result.BeginTime = entity.BeforeTime | ||||
| 	result.EndTime = entity.TargetTime | ||||
|  | ||||
| 	return result, statuscode.Success | ||||
| } | ||||
|  | ||||
| // AutoRenewal 自动续期处理 | ||||
| // 处理即将到期的长期号码自动续费逻辑 | ||||
| func (e *SmsRenewalLog) AutoRenewal() error { | ||||
| 	if err := e.JudgeRenewalLogStatus(); err != nil { | ||||
| 		e.Log.Errorf("处理预扣费记录失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 获取24小时内到期的自动续费号码 | ||||
| 	phoneService := SmsPhone{} | ||||
| 	phoneService.Orm = e.Orm | ||||
| 	phoneService.Log = e.Log | ||||
| 	expiredPhones, err := e.getExpiredPhones() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// 获取服务配置映射 | ||||
| 	serviceMap, premiumMap, err := phoneService.getRenewalConfigs() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// 处理每个到期号码 | ||||
| 	for _, phone := range expiredPhones { | ||||
| 		if renewLog, err := e.processPhoneRenewal(phone, serviceMap, premiumMap, "", false); err != nil { | ||||
| 			e.Log.Errorf("处理号码续费失败 [PhoneID: %d 续费订单号:%s]: %v", phone.Id, renewLog.PayOrderNo, err) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 手动续费(处理到期号码) | ||||
| func (e *SmsRenewalLog) ManualRenewal(activationIds []string) ([]string, error) { | ||||
| 	if len(activationIds) == 0 { | ||||
| 		return nil, errors.New("activationIds 不能为空") | ||||
| 	} | ||||
|  | ||||
| 	var errorIds []string | ||||
| 	var expiredPhones []models.SmsPhone | ||||
| 	phoneService := SmsPhone{} | ||||
| 	phoneService.Orm = e.Orm | ||||
| 	phoneService.Log = e.Log | ||||
| 	// 查询所有到期号码 | ||||
| 	if err := e.Orm.Model(&models.SmsPhone{}). | ||||
| 		Where("activation_id IN ? ", activationIds). //expire_time < ? time.Now() | ||||
| 		Find(&expiredPhones).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// 获取服务配置映射 | ||||
| 	serviceMap, premiumMap, err := phoneService.getRenewalConfigs() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// 处理每个到期号码 | ||||
| 	for _, phone := range expiredPhones { | ||||
| 		if renewLog, err := e.processPhoneRenewal(phone, serviceMap, premiumMap, "", true); err != nil { | ||||
| 			e.Log.Errorf("处理号码续费失败 [PhoneID: %d 续费订单号:%s]: %v", phone.Id, renewLog.PayOrderNo, err) | ||||
| 			errorIds = append(errorIds, fmt.Sprintf("处理号码续费失败 [PhoneID: %d]: %v", phone.Id, err)) | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 	return errorIds, nil | ||||
| } | ||||
|  | ||||
| // getExpiredPhones 获取即将到期的自动续费号码 | ||||
| func (e *SmsRenewalLog) getExpiredPhones() ([]models.SmsPhone, error) { | ||||
| 	var phones []models.SmsPhone | ||||
| 	startTime := time.Now().Add(-24 * time.Hour) | ||||
| 	endTime := time.Now().Add(24 * time.Hour) | ||||
|  | ||||
| 	notExistsQuery := e.Orm.Model(&models.SmsRenewalLog{}). | ||||
| 		Select("1"). | ||||
| 		Where("sms_renewal_log.phone_id = sms_phone.id AND sms_renewal_log.status in (1,2) AND status = 1") | ||||
|  | ||||
| 	err := e.Orm.Model(&models.SmsPhone{}). | ||||
| 		Where("auto_renewal = 1 AND type = 1 AND actived = 2 AND expire_time >= ? AND expire_time < ?", startTime, endTime). | ||||
| 		Where("NOT EXISTS (?)", notExistsQuery). | ||||
| 		Find(&phones).Error | ||||
|  | ||||
| 	return phones, err | ||||
| } | ||||
|  | ||||
| // 获取预扣除的记录 | ||||
| func (e *SmsRenewalLog) GetLockRenewalLogs() ([]models.SmsRenewalLog, error) { | ||||
| 	var logs []models.SmsRenewalLog | ||||
| 	err := e.Orm.Model(&models.SmsRenewalLog{}). | ||||
| 		Joins("left join sms_phone s on s.id = sms_renewal_log.phone_id"). | ||||
| 		Where("sms_renewal_log.status = 1"). | ||||
| 		Select("sms_renewal_log.*, s.platform_code,s.api_key,s.billing_cycle_id"). | ||||
| 		Find(&logs).Error | ||||
| 	return logs, err | ||||
| } | ||||
|  | ||||
| // 判断续费是否成功 | ||||
| func (e *SmsRenewalLog) JudgeRenewalLogStatus() error { | ||||
| 	logs, err := e.GetLockRenewalLogs() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	textVerifiedService := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 	for _, item := range logs { | ||||
| 		switch item.PlatformCode { | ||||
| 		case global.SmsPlatformTextVerified: | ||||
| 			apiInfo, err := smsPlatformKeyRedis.GetApiInfo(item.PlatformCode, item.ApiKey) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("获取短信平台密钥失败 [PlatformCode: %s]: %v", item.PlatformCode, err) | ||||
| 				continue | ||||
| 			} | ||||
| 			cycleInfo, err := textVerifiedService.GetBillingCycle(item.BillingCycleId, &apiInfo) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("获取短信平台密钥失败 [PlatformCode: %s]: %v", item.PlatformCode, err) | ||||
|  | ||||
| 				if errors.Is(err, LogNotFund) { | ||||
| 					if err := e.rollbackRenewalLog(item.Id); err != nil { | ||||
| 						e.Log.Errorf("回滚续费记录失败 [RenewalLogID: %d]: %v", item.Id, err) | ||||
| 					} | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			startTime := item.BeforeTime.UTC().Truncate(24 * time.Hour) | ||||
| 			endTime := cycleInfo.RenewedThrough.Truncate(24 * time.Hour) | ||||
| 			// 续费成功 | ||||
| 			if startTime.Before(endTime) { | ||||
| 				if err := e.confirmPayment(item.PhoneId, item.Id); err != nil { | ||||
| 					e.Log.Errorf("更新续费记录状态失败 [RenewalLogID: %d]: %v", item.Id, err) | ||||
| 				} | ||||
| 			} else if item.BeforeTime.Before(time.Now()) { | ||||
| 				if err := e.rollbackRenewalLog(item.Id); err != nil { | ||||
| 					e.Log.Errorf("回滚续费记录失败 [RenewalLogID: %d]: %v", item.Id, err) | ||||
| 				} | ||||
| 			} | ||||
| 		case global.SmsPlatformDaisysms: | ||||
| 			//没有手动续费只能 在到期前直接扣费 | ||||
| 			timeDuration := time.Since(item.BeforeTime) | ||||
|  | ||||
| 			if timeDuration < 24*time.Hour { | ||||
| 				if err := e.confirmPayment(item.PhoneId, item.Id); err != nil { | ||||
| 					e.Log.Errorf("更新续费记录状态失败 [RenewalLogID: %d]: %v", item.Id, err) | ||||
| 				} | ||||
| 			} | ||||
| 		default: | ||||
| 			e.Log.Errorf("不支持的短信平台 %s 续费id:%d", item.PlatformCode, item.Id) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 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 处理单个号码的续费 | ||||
| // tradeOrderNo 交易订单号 | ||||
| func (e *SmsRenewalLog) processPhoneRenewal(phone models.SmsPhone, serviceMap map[string]models.SmsServices, | ||||
| 	premiumMap map[string]dto.GetSysConfigByKEYForServiceResp, tradeOrderNo string, admin bool) (models.SmsRenewalLog, error) { | ||||
| 	var renewLog models.SmsRenewalLog | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetApiInfo(phone.PlatformCode, phone.ApiKey) | ||||
| 	if err != nil { | ||||
| 		return renewLog, fmt.Errorf("获取短信平台密钥失败 [PlatformCode: %s]: %v", phone.PlatformCode, err) | ||||
| 	} | ||||
|  | ||||
| 	//续期id为空 重新获取 | ||||
| 	if phone.BillingCycleId == "" { | ||||
| 		switch phone.PlatformCode { | ||||
| 		case global.SmsPlatformTextVerified: | ||||
| 			//可以手动续费 | ||||
| 			textVerifiedService := SmsTextVerified{Service: e.Service} | ||||
| 			detail, code := textVerifiedService.GetRentalDetail(phone.ActivationId, &apiInfo) | ||||
|  | ||||
| 			if code != statuscode.Success { | ||||
| 				logger.Errorf("获取短信验证码续费详情失败 [ActivationID: %s]: %v", phone.ActivationId, code) | ||||
| 				return renewLog, fmt.Errorf("获取短信验证码续费详情失败 [ActivationID: %s]: %v", phone.ActivationId, code) | ||||
| 			} | ||||
|  | ||||
| 			phone.BillingCycleId = detail.BillingCycleId | ||||
| 		case global.SmsPlatformDaisysms: | ||||
| 			//只有自动续费的 用定时服务查询状态 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 获取服务价格 | ||||
| 	service, exists := serviceMap[phone.PlatformCode+"_"+phone.ServiceCode] | ||||
| 	if !exists { | ||||
| 		return renewLog, errors.New("服务不存在") | ||||
| 	} | ||||
|  | ||||
| 	// 计算续费价格 | ||||
| 	renewalPrice := e.calculateRenewalPrice(service.LongPrice, phone.PlatformCode, premiumMap) | ||||
| 	if renewalPrice.IsZero() { | ||||
| 		return renewLog, errors.New("续费价格计算失败") | ||||
| 	} | ||||
|  | ||||
| 	// 创建续费日志 | ||||
| 	renewLog = e.createRenewalLog(phone, renewalPrice, tradeOrderNo) | ||||
|  | ||||
| 	// 执行续费事务 | ||||
| 	if err := e.executeRenewalTransaction(phone, renewalPrice, &renewLog, &apiInfo, admin); err != nil { | ||||
| 		return renewLog, err | ||||
| 	} | ||||
|  | ||||
| 	return renewLog, nil | ||||
| } | ||||
|  | ||||
| // calculateRenewalPrice 计算续费价格 | ||||
| func (e *SmsRenewalLog) 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 创建续费日志 | ||||
| // tradeOrderNo 交易订单号 | ||||
| func (e *SmsRenewalLog) createRenewalLog(phone models.SmsPhone, amount decimal.Decimal, tradeOrderNo string) models.SmsRenewalLog { | ||||
| 	targetTime := phone.ExpireTime.AddDate(0, 0, 30) | ||||
|  | ||||
| 	return models.SmsRenewalLog{ | ||||
| 		UserId:       phone.UserId, | ||||
| 		TradeOrderNo: tradeOrderNo, | ||||
| 		PayOrderNo:   utility.GenerateTraceID(), | ||||
| 		Phone:        phone.Phone, | ||||
| 		Category:     2, | ||||
| 		PhoneId:      phone.Id, | ||||
| 		Type:         phone.Type, | ||||
| 		Amount:       amount, | ||||
| 		BeforeTime:   *phone.ExpireTime, | ||||
| 		TargetTime:   &targetTime, | ||||
| 		Period:       30, | ||||
| 		Status:       1, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // executeRenewalTransaction 执行续费事务 | ||||
| func (e *SmsRenewalLog) executeRenewalTransaction(phone models.SmsPhone, price decimal.Decimal, | ||||
| 	renewLog *models.SmsRenewalLog, apiInfo *dto.SmsPlatformKeyQueueDto, isAdmin bool) 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("创建续费日志失败") | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	if err != nil && err.Error() == "余额不足" && phone.ExpireTime.Before(time.Now()) { | ||||
| 		return e.handleInsufficientBalance(phone) | ||||
| 	} | ||||
|  | ||||
| 	//是否是管理员处理 | ||||
| 	if isAdmin { | ||||
| 		if err1 := e.confirmPayment(phone.Id, renewLog.Id); err1 != nil { | ||||
| 			logger.Errorf("确认扣款失败 [PhoneID: %d, RenewalLogID: %d]: %v", phone.Id, renewLog.Id, err1) | ||||
| 			return err1 | ||||
| 		} | ||||
| 	} else { | ||||
| 		switch phone.PlatformCode { | ||||
| 		case global.SmsPlatformTextVerified: | ||||
| 			//可以手动续费 | ||||
| 			textVerifiedService := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 			code := textVerifiedService.ManualRenewal(phone.BillingCycleId, apiInfo) | ||||
|  | ||||
| 			// 没有需要续费的号码 重新设置可自动续费之后再试一次 | ||||
| 			if code == statuscode.NothingToRenew { | ||||
| 				if renewCode := textVerifiedService.Renew(phone.ActivationId, true, apiInfo); renewCode == statuscode.Success { | ||||
| 					code = textVerifiedService.ManualRenewal(phone.BillingCycleId, apiInfo) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if code == statuscode.Success { | ||||
| 				if err1 := e.confirmPayment(phone.Id, renewLog.Id); err1 != nil { | ||||
| 					logger.Errorf("确认扣款失败 [PhoneID: %d, RenewalLogID: %d]: %v", phone.Id, renewLog.Id, err1) | ||||
| 					return err1 | ||||
| 				} | ||||
| 			} else { | ||||
| 				if err1 := e.rollbackRenewalLog(renewLog.Id); err1 != nil { | ||||
| 					logger.Errorf("回滚续费日志状态失败,续费日志ID: %d, 错误: %v", renewLog.Id, err1) | ||||
| 					return err1 | ||||
| 				} | ||||
|  | ||||
| 				return fmt.Errorf("手动续费失败 [BillingCycleID: %s]: %v", phone.BillingCycleId, code) | ||||
| 			} | ||||
| 		case global.SmsPlatformDaisysms: | ||||
| 			//只有自动续费的 用定时服务查询状态 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // 确认扣款成功 | ||||
| func (e *SmsRenewalLog) confirmPayment(phoneId int, renewalLogId int) error { | ||||
| 	var renewLog models.SmsRenewalLog | ||||
| 	if err := e.Orm.Where("id = ?", renewalLogId).First(&renewLog).Error; err != nil { | ||||
| 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 			// 事务ID不存在:这是致命错误,平台A必须有此记录才能Confirm | ||||
| 			e.Log.Errorf("Confirm失败: 找不到续费记录ID %s 的续费日志", renewalLogId) | ||||
| 			return errors.New("事务记录不存在,无法确认支付") | ||||
| 		} | ||||
| 		return fmt.Errorf("查询续费日志失败: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 检查幂等性 | ||||
| 	if renewLog.Status == 2 { // 状态 2: 扣费成功 | ||||
| 		// 幂等性成功:已经被 Confirm 过了,直接返回成功,不需重复操作 | ||||
| 		e.Log.Warnf("Confirm重复调用: 续费ID %s 已是成功状态", renewalLogId) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// 流程错误检查:不能对已取消或失败的事务执行 Confirm | ||||
| 	if renewLog.Status != 1 { // 状态 1: 预扣中/冻结 | ||||
| 		// 理论上不该发生。说明平台 B 的调用时序错误。 | ||||
| 		e.Log.Errorf("Confirm失败: 续费ID %s 状态为 %d,无法执行确认支付", renewalLogId, renewLog.Status) | ||||
| 		return errors.New("事务状态不正确,无法确认支付") | ||||
| 	} | ||||
|  | ||||
| 	return e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
|  | ||||
| 		// 1. 查找并更新续费日志状态 (状态 2: 扣费成功) | ||||
| 		result := tx.Model(&models.SmsRenewalLog{}). | ||||
| 			Where("id = ? AND status = ?", renewalLogId, 1). // 必须是预扣中状态 | ||||
| 			Update("status", 2) | ||||
|  | ||||
| 		if result.RowsAffected == 0 { | ||||
| 			return errors.New("续费日志状态不正确或已处理") | ||||
| 		} | ||||
|  | ||||
| 		// 2. **更新到期时间** (只有在 Confirm 阶段才能更新) | ||||
| 		var phone models.SmsPhone | ||||
| 		if err := tx.Model(&models.SmsPhone{}).Where("id = ?", phoneId).First(&phone).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		var targetTime time.Time | ||||
| 		if renewLog.TargetTime != nil { | ||||
| 			targetTime = *renewLog.TargetTime | ||||
| 		} else { | ||||
| 			targetTime = phone.ExpireTime.AddDate(0, 0, 30) | ||||
| 		} | ||||
|  | ||||
| 		if err := tx.Model(&phone).Update("expire_time", targetTime).Error; err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		return nil // TCC Confirm 成功 | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // rollbackRenewalLog 回滚续费日志状态 | ||||
| func (e *SmsRenewalLog) rollbackRenewalLog(renewLogId int) error { | ||||
| 	var renewLog models.SmsRenewalLog | ||||
| 	if err := e.Orm.Where("id = ?", renewLogId).First(&renewLog).Error; err != nil { | ||||
| 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 			e.Log.Errorf("回滚失败: 找不到续费记录ID %d", renewLogId) | ||||
| 			return errors.New("事务记录不存在,无法回滚") | ||||
| 		} | ||||
| 		return fmt.Errorf("查询续费日志失败: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 幂等性检查:如果已经处于回滚状态,直接返回成功 | ||||
| 	if renewLog.Status == 3 { | ||||
| 		e.Log.Warnf("回滚重复调用: 续费ID %d 已是回滚状态 (Status=3)", renewLogId) | ||||
| 		return nil // 幂等性成功 | ||||
| 	} | ||||
|  | ||||
| 	// 流程检查:如果已经是成功扣费状态,不能执行普通回滚 | ||||
| 	if renewLog.Status == 2 { | ||||
| 		e.Log.Errorf("回滚失败: 续费ID %d 已是成功扣费状态 (Status=2)", renewLogId) | ||||
| 		return errors.New("事务已确认支付,无法执行取消回滚") | ||||
| 	} | ||||
|  | ||||
| 	return e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		// 1. 更新续费日志状态 (状态 1: 预扣中/冻结) | ||||
| 		result := tx.Model(&models.SmsRenewalLog{}). | ||||
| 			Where("id = ? AND status = ?", renewLogId, 1). // 必须是扣费成功状态 | ||||
| 			Update("status", 3) | ||||
| 		if result.Error != nil { | ||||
| 			logger.Errorf("回滚续费日志状态失败,续费日志ID: %d, 错误: %v", renewLogId, result.Error) | ||||
| 			return result.Error | ||||
| 		} | ||||
| 		if result.RowsAffected == 0 { | ||||
| 			logger.Errorf("回滚续费日志状态失败,续费日志ID: %d", renewLogId) | ||||
| 			return errors.New("续费日志状态不正确或已处理") | ||||
| 		} | ||||
|  | ||||
| 		result = tx.Exec("UPDATE member_balance SET balance = balance + ? WHERE user_id = ? ", renewLog.Amount, renewLog.UserId) | ||||
| 		if result.Error != nil { | ||||
| 			logger.Errorf("回滚续费日志状态失败,续费日志ID: %d, 错误: %v", renewLogId, result.Error) | ||||
| 			return result.Error | ||||
| 		} | ||||
| 		if result.RowsAffected == 0 { | ||||
| 			return errors.New("余额不足") | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // handleInsufficientBalance 处理余额不足情况 | ||||
| func (e *SmsRenewalLog) 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) | ||||
| 	} | ||||
|  | ||||
| 	phoneService := SmsPhone{} | ||||
| 	phoneService.Orm = e.Orm | ||||
| 	phoneService.Log = e.Log | ||||
| 	// 调用平台取消续费接口 | ||||
| 	code := phoneService.ChangeAutoRenewManage(phone.PlatformCode, phone.ApiKey, 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("余额不足") | ||||
| } | ||||
|  | ||||
| @ -29,6 +29,32 @@ type SmsTextVerified struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| // 错误 | ||||
| var ( | ||||
| 	LogNotFund = errors.New("记录不存在") | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	loginUrl                = "/api/pub/v2/auth" | ||||
| 	getAreas                = "/api/pub/v2/area-codes" | ||||
| 	getServices             = "/api/pub/v2/services" | ||||
| 	createRental            = "/api/pub/v2/reservations/rental"                     //长效收码 | ||||
| 	createVerification      = "/api/pub/v2/verifications"                           //单次收码 | ||||
| 	cancelVerification      = "/api/pub/v2/verifications/%s/cancel"                 //取消收码 | ||||
| 	cancelRental            = "/api/pub/v2/reservations/rental/renewable/%s/refund" //取消长效号码 | ||||
| 	wakeUp                  = "/api/pub/v2/wake-requests"                           //唤醒号码 | ||||
| 	rentalPrice             = "/api/pub/v2/pricing/rentals"                         //长效收码价格 | ||||
| 	verificationPrice       = "/api/pub/v2/pricing/verifications"                   //单次收码价格 | ||||
| 	rentalDetail            = "/api/pub/v2/reservations/rental/renewable/%s"        //长效号码详情 | ||||
| 	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 | ||||
| 	manualRenewal           = "/api/pub/v2/billing-cycles/%s/renew"                 //手动续期 POST | ||||
| 	getBillingCycles        = "/api/pub/v2/billing-cycles/%s"                       //获取计费周期详情 GET | ||||
| 	rentalList              = "/api/pub/v2/reservations/rental/renewable"           //长效号码列表 | ||||
| ) | ||||
|  | ||||
| // 获取收到的验证码 | ||||
| // messageId 短效或长效号码的ID | ||||
| // typ 0-短效 1-长效 | ||||
| @ -143,24 +169,6 @@ func (e SmsTextVerified) GetCode(messageId string, typ int, userId int, service, | ||||
| 	return parsedCode, http.StatusOK | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	loginUrl                = "/api/pub/v2/auth" | ||||
| 	getAreas                = "/api/pub/v2/area-codes" | ||||
| 	getServices             = "/api/pub/v2/services" | ||||
| 	createRental            = "/api/pub/v2/reservations/rental"                     //长效收码 | ||||
| 	createVerification      = "/api/pub/v2/verifications"                           //单次收码 | ||||
| 	cancelVerification      = "/api/pub/v2/verifications/%s/cancel"                 //取消收码 | ||||
| 	cancelRental            = "/api/pub/v2/reservations/rental/renewable/%s/refund" //取消长效号码 | ||||
| 	wakeUp                  = "/api/pub/v2/wake-requests"                           //唤醒号码 | ||||
| 	rentalPrice             = "/api/pub/v2/pricing/rentals"                         //长效收码价格 | ||||
| 	verificationPrice       = "/api/pub/v2/pricing/verifications"                   //单次收码价格 | ||||
| 	rentalDetail            = "/api/pub/v2/reservations/rental/renewable/%s"        //长效号码详情 | ||||
| 	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}$`) | ||||
|  | ||||
| // 获取授权失败 | ||||
| @ -372,16 +380,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 | ||||
| } | ||||
|  | ||||
| @ -466,6 +477,7 @@ func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price dec | ||||
| 				} | ||||
|  | ||||
| 				result.Phone = "1" + detail.Number | ||||
| 				result.BillingCycleId = detail.BillingCycleId | ||||
| 				endTime := resp.UpdatedAt.AddDate(0, 0, 30) | ||||
| 				result.EndAt = &(endTime) // 30天后过期 | ||||
|  | ||||
| @ -579,7 +591,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 +610,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) | ||||
| @ -932,6 +949,41 @@ func (e *SmsTextVerified) Renew(activationId string, status bool, apiInfo *dto.S | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 手动续期 | ||||
| // 续费id | ||||
| func (e *SmsTextVerified) ManualRenewal(bilingCycleId string, apiInfo *dto.SmsPlatformKeyQueueDto) int { | ||||
| 	url := fmt.Sprintf(manualRenewal, bilingCycleId) | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取长效续期失败 %d", code) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
| 	mapData := dto.TextVerifiedManualRenewalResp{} | ||||
| 	statuCode, err := client.Post(url, nil, headers, &mapData) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("修改长效续期状态失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if statuCode >= 200 && statuCode < 300 { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("修改长效续期状态失败 %d", statuCode) | ||||
| 		// 没有需要续费的号码 重新设置可自动续费之后再试一次 | ||||
| 		if statuCode == 400 && strings.Contains(err.Error(), "Nothing to renew") { | ||||
| 			return statuscode.NothingToRenew | ||||
| 		} | ||||
|  | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 重新初始化短信记录 | ||||
| func (e *SmsTextVerified) InitSmsLogs() error { | ||||
| 	// 获取平台密钥信息 | ||||
| @ -1030,6 +1082,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 +1127,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) | ||||
| 		} | ||||
|  | ||||
| @ -1114,3 +1168,70 @@ func getIdByUrl(URLString string) string { | ||||
| func isValidID(s string) bool { | ||||
| 	return idPattern.MatchString(s) | ||||
| } | ||||
|  | ||||
| // 获取长效租赁号码列表 | ||||
| // next 分页url | ||||
| func (e *SmsTextVerified) GetRentalList(next string, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.TextVerifiedGetRentalListResp, error) { | ||||
| 	var result dto.TextVerifiedGetRentalListResp | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取长效号码列表失败 %d", code) | ||||
| 		return result, fmt.Errorf("获取长效号码列表失败 %d", code) | ||||
| 	} | ||||
|  | ||||
| 	if next == "" { | ||||
| 		next = rentalList | ||||
| 	} else { | ||||
| 		client.BaseURL = "" | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedGetRentalListResp{} | ||||
| 	statusCode, err := client.Get(next, map[string]string{}, &resp) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取长效号码列表失败 %v", err) | ||||
| 		return result, fmt.Errorf("获取长效号码列表失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if statusCode == http.StatusOK { | ||||
| 		result = resp | ||||
| 		return result, nil | ||||
| 	} else if statusCode == http.StatusUnauthorized { | ||||
| 		return e.GetRentalList(next, apiInfo) | ||||
| 	} else { | ||||
| 		e.Log.Errorf("获取长效号码列表失败 %d", statusCode) | ||||
| 		return result, fmt.Errorf("获取长效号码列表失败 %d", statusCode) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取续费周期详情 | ||||
| func (e *SmsTextVerified) GetBillingCycle(billingCycleId string, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.TextVerifiedGetBillingCycleResp, error) { | ||||
| 	var result dto.TextVerifiedGetBillingCycleResp | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取计费周期详情失败 %d", code) | ||||
| 		return result, fmt.Errorf("获取计费周期详情失败 %d", code) | ||||
| 	} | ||||
|  | ||||
| 	next := fmt.Sprintf(getBillingCycles, billingCycleId) | ||||
|  | ||||
| 	resp := dto.TextVerifiedGetBillingCycleResp{} | ||||
| 	statusCode, err := client.Get(next, map[string]string{}, &resp) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取计费周期详情失败 %v", err) | ||||
| 		return result, fmt.Errorf("获取计费周期详情失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices { | ||||
| 		result = resp | ||||
| 		return result, nil | ||||
| 	} else if statusCode == http.StatusUnauthorized { | ||||
| 		return e.GetBillingCycle(billingCycleId, apiInfo) | ||||
| 	} else if statusCode == http.StatusNotFound { | ||||
| 		return result, LogNotFund | ||||
| 	} else { | ||||
| 		e.Log.Errorf("获取计费周期详情失败 %d", statusCode) | ||||
| 		return result, fmt.Errorf("获取计费周期详情失败 %d", statusCode) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -37,7 +37,7 @@ func (j RenewalJob) Exec(args interface{}) error { | ||||
|  | ||||
| // 定时短信续期任务 | ||||
| func (j SmsRenewalJob) Exec(args interface{}) error { | ||||
| 	smsPhoneService := service.SmsPhone{} | ||||
| 	smsPhoneService := service.SmsRenewalLog{} | ||||
| 	smsPhoneService.Orm = GetDb() | ||||
| 	smsPhoneService.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
|  | ||||
| @ -23,7 +23,7 @@ func TestTrxJob(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func initSetting() { | ||||
| 	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" | ||||
|  | ||||
| @ -42,6 +42,7 @@ var StatusCodeZh = map[int]string{ | ||||
| 	SmsOutOfStockOrUnavailable:  "短信验证码_缺货或服务不可用", | ||||
| 	SmsRentalRefundNotPermitted: "短信验证码_租赁退款不允许", | ||||
| 	SmsRentalCantRenew:          "短信验证码_无法续期", | ||||
| 	SmsRenewalLogExisted:        "短信验证码_重复续期", | ||||
| } | ||||
|  | ||||
| var StatusCodeEn = map[int]string{ | ||||
| @ -76,6 +77,7 @@ var StatusCodeEn = map[int]string{ | ||||
| 	SmsOutOfStockOrUnavailable:  "sms out of stock or unavailable", | ||||
| 	SmsRentalRefundNotPermitted: "sms rental refund not permitted", | ||||
| 	SmsRentalCantRenew:          "sms rental can not renewal", | ||||
| 	SmsRenewalLogExisted:        "sms renewal log existed", | ||||
| } | ||||
|  | ||||
| // GetMsg 获取状态码对应的消息 | ||||
| @ -109,7 +111,8 @@ const ( | ||||
| 	AccountOrPasswordError = 10003 | ||||
| 	//密码不一致 | ||||
| 	PassWordNotMatch = 10004 | ||||
|  | ||||
| 	// 没有需要续费的号码 重新设置可自动续费之后再试一次 | ||||
| 	NothingToRenew = 11005 | ||||
| 	// | ||||
| 	ServerError = 500 | ||||
|  | ||||
| @ -165,4 +168,6 @@ const ( | ||||
| 	SmsRentalRefundNotPermitted = 20025 | ||||
| 	// 短信-无法续期 | ||||
| 	SmsRentalCantRenew = 20026 | ||||
| 	// 短信-重复续期 | ||||
| 	SmsRenewalLogExisted = 20028 | ||||
| ) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user