1、平台多ApiKey支持
2、平台号码跟系统号码对比自动续费
This commit is contained in:
		
							
								
								
									
										214
									
								
								app/admin/apis/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								app/admin/apis/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,214 @@ | ||||
| package apis | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/api" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" | ||||
| 	_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" | ||||
|  | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/app/admin/service" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/actions" | ||||
| ) | ||||
|  | ||||
| type SmsAbnormalNumber struct { | ||||
| 	api.Api | ||||
| } | ||||
|  | ||||
| // GetPage 获取异常号码统计列表 | ||||
| // @Summary 获取异常号码统计列表 | ||||
| // @Description 获取异常号码统计列表 | ||||
| // @Tags 异常号码统计 | ||||
| // @Param platformCode query string false "平台code" | ||||
| // @Param phone query string false "电话号码" | ||||
| // @Param pageSize query int false "页条数" | ||||
| // @Param pageIndex query int false "页码" | ||||
| // @Success 200 {object} response.Response{data=response.Page{list=[]models.SmsAbnormalNumber}} "{"code": 200, "data": [...]}" | ||||
| // @Router /api/v1/sms-abnormal-number [get] | ||||
| // @Security Bearer | ||||
| func (e SmsAbnormalNumber) GetPage(c *gin.Context) { | ||||
| 	req := dto.SmsAbnormalNumberGetPageReq{} | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	p := actions.GetPermissionFromContext(c) | ||||
| 	list := make([]models.SmsAbnormalNumber, 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 | ||||
| 	} | ||||
|  | ||||
| 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | ||||
| } | ||||
|  | ||||
| // Get 获取异常号码统计 | ||||
| // @Summary 获取异常号码统计 | ||||
| // @Description 获取异常号码统计 | ||||
| // @Tags 异常号码统计 | ||||
| // @Param id path int false "id" | ||||
| // @Success 200 {object} response.Response{data=models.SmsAbnormalNumber} "{"code": 200, "data": [...]}" | ||||
| // @Router /api/v1/sms-abnormal-number/{id} [get] | ||||
| // @Security Bearer | ||||
| func (e SmsAbnormalNumber) Get(c *gin.Context) { | ||||
| 	req := dto.SmsAbnormalNumberGetReq{} | ||||
| 	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 | ||||
| 	} | ||||
| 	var object models.SmsAbnormalNumber | ||||
|  | ||||
| 	p := actions.GetPermissionFromContext(c) | ||||
| 	err = s.Get(&req, p, &object) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("获取异常号码统计失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(object, "查询成功") | ||||
| } | ||||
|  | ||||
| // Insert 创建异常号码统计 | ||||
| // @Summary 创建异常号码统计 | ||||
| // @Description 创建异常号码统计 | ||||
| // @Tags 异常号码统计 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.SmsAbnormalNumberInsertReq true "data" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}" | ||||
| // @Router /api/v1/sms-abnormal-number [post] | ||||
| // @Security Bearer | ||||
| func (e SmsAbnormalNumber) Insert(c *gin.Context) { | ||||
| 	req := dto.SmsAbnormalNumberInsertReq{} | ||||
| 	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 | ||||
| 	} | ||||
| 	// 设置创建人 | ||||
| 	req.SetCreateBy(user.GetUserId(c)) | ||||
|  | ||||
| 	err = s.Insert(&req) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("创建异常号码统计失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(req.GetId(), "创建成功") | ||||
| } | ||||
|  | ||||
| // Update 修改异常号码统计 | ||||
| // @Summary 修改异常号码统计 | ||||
| // @Description 修改异常号码统计 | ||||
| // @Tags 异常号码统计 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param id path int true "id" | ||||
| // @Param data body dto.SmsAbnormalNumberUpdateReq true "body" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}" | ||||
| // @Router /api/v1/sms-abnormal-number/{id} [put] | ||||
| // @Security Bearer | ||||
| func (e SmsAbnormalNumber) Update(c *gin.Context) { | ||||
| 	req := dto.SmsAbnormalNumberUpdateReq{} | ||||
| 	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 | ||||
| 	} | ||||
| 	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 | ||||
| 	} | ||||
| 	e.OK(req.GetId(), "修改成功") | ||||
| } | ||||
|  | ||||
| // Delete 删除异常号码统计 | ||||
| // @Summary 删除异常号码统计 | ||||
| // @Description 删除异常号码统计 | ||||
| // @Tags 异常号码统计 | ||||
| // @Param data body dto.SmsAbnormalNumberDeleteReq true "body" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}" | ||||
| // @Router /api/v1/sms-abnormal-number [delete] | ||||
| // @Security Bearer | ||||
| func (e SmsAbnormalNumber) Delete(c *gin.Context) { | ||||
| 	s := service.SmsAbnormalNumber{} | ||||
| 	req := dto.SmsAbnormalNumberDeleteReq{} | ||||
| 	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.Remove(&req, p) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("删除异常号码统计失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK(req.GetId(), "删除成功") | ||||
| } | ||||
|  | ||||
| // 同步平台和系统的差异 | ||||
| func (e SmsAbnormalNumber) SyncState(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 | ||||
| 	} | ||||
|  | ||||
| 	err = s.SyncState() | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("同步异常号码统计失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK(nil, "同步成功") | ||||
| } | ||||
							
								
								
									
										192
									
								
								app/admin/apis/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								app/admin/apis/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,192 @@ | ||||
| package apis | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/api" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" | ||||
| 	_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" | ||||
|  | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/app/admin/service" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/actions" | ||||
| ) | ||||
|  | ||||
| type SmsPlatformKey struct { | ||||
| 	api.Api | ||||
| } | ||||
|  | ||||
| // GetPage 获取平台密钥管理列表 | ||||
| // @Summary 获取平台密钥管理列表 | ||||
| // @Description 获取平台密钥管理列表 | ||||
| // @Tags 平台密钥管理 | ||||
| // @Param platformCode query string false "平台code" | ||||
| // @Param pageSize query int false "页条数" | ||||
| // @Param pageIndex query int false "页码" | ||||
| // @Success 200 {object} response.Response{data=response.Page{list=[]models.SmsPlatformKey}} "{"code": 200, "data": [...]}" | ||||
| // @Router /api/v1/sms-platform-key [get] | ||||
| // @Security Bearer | ||||
| func (e SmsPlatformKey) GetPage(c *gin.Context) { | ||||
| 	req := dto.SmsPlatformKeyGetPageReq{} | ||||
| 	s := service.SmsPlatformKey{} | ||||
| 	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.SmsPlatformKey, 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 | ||||
| 	} | ||||
|  | ||||
| 	e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") | ||||
| } | ||||
|  | ||||
| // Get 获取平台密钥管理 | ||||
| // @Summary 获取平台密钥管理 | ||||
| // @Description 获取平台密钥管理 | ||||
| // @Tags 平台密钥管理 | ||||
| // @Param id path int false "id" | ||||
| // @Success 200 {object} response.Response{data=models.SmsPlatformKey} "{"code": 200, "data": [...]}" | ||||
| // @Router /api/v1/sms-platform-key/{id} [get] | ||||
| // @Security Bearer | ||||
| func (e SmsPlatformKey) Get(c *gin.Context) { | ||||
| 	req := dto.SmsPlatformKeyGetReq{} | ||||
| 	s := service.SmsPlatformKey{} | ||||
| 	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 | ||||
| 	} | ||||
| 	var object models.SmsPlatformKey | ||||
|  | ||||
| 	p := actions.GetPermissionFromContext(c) | ||||
| 	err = s.Get(&req, p, &object) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("获取平台密钥管理失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(object, "查询成功") | ||||
| } | ||||
|  | ||||
| // Insert 创建平台密钥管理 | ||||
| // @Summary 创建平台密钥管理 | ||||
| // @Description 创建平台密钥管理 | ||||
| // @Tags 平台密钥管理 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.SmsPlatformKeyInsertReq true "data" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "添加成功"}" | ||||
| // @Router /api/v1/sms-platform-key [post] | ||||
| // @Security Bearer | ||||
| func (e SmsPlatformKey) Insert(c *gin.Context) { | ||||
| 	req := dto.SmsPlatformKeyInsertReq{} | ||||
| 	s := service.SmsPlatformKey{} | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	e.OK(req.GetId(), "创建成功") | ||||
| } | ||||
|  | ||||
| // Update 修改平台密钥管理 | ||||
| // @Summary 修改平台密钥管理 | ||||
| // @Description 修改平台密钥管理 | ||||
| // @Tags 平台密钥管理 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param id path int true "id" | ||||
| // @Param data body dto.SmsPlatformKeyUpdateReq true "body" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "修改成功"}" | ||||
| // @Router /api/v1/sms-platform-key/{id} [put] | ||||
| // @Security Bearer | ||||
| func (e SmsPlatformKey) Update(c *gin.Context) { | ||||
| 	req := dto.SmsPlatformKeyUpdateReq{} | ||||
| 	s := service.SmsPlatformKey{} | ||||
| 	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 | ||||
| 	} | ||||
| 	e.OK(req.GetId(), "修改成功") | ||||
| } | ||||
|  | ||||
| // Delete 删除平台密钥管理 | ||||
| // @Summary 删除平台密钥管理 | ||||
| // @Description 删除平台密钥管理 | ||||
| // @Tags 平台密钥管理 | ||||
| // @Param data body dto.SmsPlatformKeyDeleteReq true "body" | ||||
| // @Success 200 {object} response.Response	"{"code": 200, "message": "删除成功"}" | ||||
| // @Router /api/v1/sms-platform-key [delete] | ||||
| // @Security Bearer | ||||
| func (e SmsPlatformKey) Delete(c *gin.Context) { | ||||
| 	s := service.SmsPlatformKey{} | ||||
| 	req := dto.SmsPlatformKeyDeleteReq{} | ||||
| 	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.Remove(&req, p) | ||||
| 	if err != nil { | ||||
| 		e.Error(500, err, fmt.Sprintf("删除平台密钥管理失败,\r\n失败信息 %s", err.Error())) | ||||
| 		return | ||||
| 	} | ||||
| 	e.OK(req.GetId(), "删除成功") | ||||
| } | ||||
							
								
								
									
										30
									
								
								app/admin/models/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/admin/models/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| package models | ||||
|  | ||||
| import ( | ||||
|  | ||||
| 	"go-admin/common/models" | ||||
|  | ||||
| ) | ||||
|  | ||||
| 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 | ||||
| } | ||||
|  | ||||
| func (SmsAbnormalNumber) TableName() string { | ||||
|     return "sms_abnormal_number" | ||||
| } | ||||
|  | ||||
| func (e *SmsAbnormalNumber) Generate() models.ActiveRecord { | ||||
| 	o := *e | ||||
| 	return &o | ||||
| } | ||||
|  | ||||
| func (e *SmsAbnormalNumber) GetId() interface{} { | ||||
| 	return e.Id | ||||
| } | ||||
| @ -12,6 +12,7 @@ type SmsPhone struct { | ||||
|  | ||||
| 	PlatformCode    string          `json:"platformCode" gorm:"type:varchar(20);comment:平台code"` | ||||
| 	UserId          int             `json:"userId" gorm:"type:bigint;comment:用户Id"` | ||||
| 	ApiKey          string          `json:"apiKey" gorm:"type:varchar(255);comment:apiKey"` | ||||
| 	Service         string          `json:"service" gorm:"type:varchar(50);comment:sms 服务"` | ||||
| 	ServiceCode     string          `json:"serviceCode" gorm:"type:varchar(30);comment:服务code"` | ||||
| 	Type            int             `json:"type" gorm:"type:tinyint;comment:类型 0-短效 1-长效"` | ||||
|  | ||||
							
								
								
									
										31
									
								
								app/admin/models/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/admin/models/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| package models | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/models" | ||||
| ) | ||||
|  | ||||
| type SmsPlatformKey struct { | ||||
| 	models.Model | ||||
|  | ||||
| 	PlatformCode string `json:"platformCode" gorm:"type:varchar(20);comment:平台code"` | ||||
| 	Account      string `json:"account" gorm:"type:varchar(50);comment:账号"` | ||||
| 	ApiKey       string `json:"apiKey" gorm:"type:varchar(500);comment:平台key"` | ||||
| 	ApiSecret    string `json:"apiSecret" gorm:"type:varchar(255);comment:平台私钥"` | ||||
| 	Status       int64  `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | ||||
| 	Remark       string `json:"remark" gorm:"type:varchar(255);comment:备注"` | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
| } | ||||
|  | ||||
| func (SmsPlatformKey) TableName() string { | ||||
| 	return "sms_platform_key" | ||||
| } | ||||
|  | ||||
| func (e *SmsPlatformKey) Generate() models.ActiveRecord { | ||||
| 	o := *e | ||||
| 	return &o | ||||
| } | ||||
|  | ||||
| func (e *SmsPlatformKey) GetId() interface{} { | ||||
| 	return e.Id | ||||
| } | ||||
							
								
								
									
										29
									
								
								app/admin/router/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/admin/router/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| package router | ||||
|  | ||||
| import ( | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | ||||
|  | ||||
| 	"go-admin/app/admin/apis" | ||||
| 	"go-admin/common/actions" | ||||
| 	"go-admin/common/middleware" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	routerCheckRole = append(routerCheckRole, registerSmsAbnormalNumberRouter) | ||||
| } | ||||
|  | ||||
| // 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.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.POST("/sync-state", api.SyncState) //同步差异 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								app/admin/router/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/admin/router/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| package router | ||||
|  | ||||
| import ( | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	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" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	routerCheckRole = append(routerCheckRole, registerSmsPlatformKeyRouter) | ||||
| } | ||||
|  | ||||
| // registerSmsPlatformKeyRouter | ||||
| func registerSmsPlatformKeyRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { | ||||
| 	api := apis.SmsPlatformKey{} | ||||
| 	r := v1.Group("/sms-platform-key").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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										93
									
								
								app/admin/service/dto/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								app/admin/service/dto/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
|  | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/dto" | ||||
| 	common "go-admin/common/models" | ||||
| ) | ||||
|  | ||||
| type SmsAbnormalNumberGetPageReq struct { | ||||
| 	dto.Pagination     `search:"-"` | ||||
|     PlatformCode string `form:"platformCode"  search:"type:exact;column:platform_code;table:sms_abnormal_number" comment:"平台code"` | ||||
|     Phone string `form:"phone"  search:"type:contains;column:phone;table:sms_abnormal_number" comment:"电话号码"` | ||||
|     SmsAbnormalNumberOrder | ||||
| } | ||||
|  | ||||
| type SmsAbnormalNumberOrder struct { | ||||
|     Id string `form:"idOrder"  search:"type:order;column:id;table:sms_abnormal_number"` | ||||
|     Account string `form:"accountOrder"  search:"type:order;column:account;table:sms_abnormal_number"` | ||||
|     PlatformCode string `form:"platformCodeOrder"  search:"type:order;column:platform_code;table:sms_abnormal_number"` | ||||
|     Phone string `form:"phoneOrder"  search:"type:order;column:phone;table:sms_abnormal_number"` | ||||
|     CreatedAt string `form:"createdAtOrder"  search:"type:order;column:created_at;table:sms_abnormal_number"` | ||||
|     UpdatedAt string `form:"updatedAtOrder"  search:"type:order;column:updated_at;table:sms_abnormal_number"` | ||||
|     DeletedAt string `form:"deletedAtOrder"  search:"type:order;column:deleted_at;table:sms_abnormal_number"` | ||||
|     CreateBy string `form:"createByOrder"  search:"type:order;column:create_by;table:sms_abnormal_number"` | ||||
|     UpdateBy string `form:"updateByOrder"  search:"type:order;column:update_by;table:sms_abnormal_number"` | ||||
|      | ||||
| } | ||||
|  | ||||
| func (m *SmsAbnormalNumberGetPageReq) GetNeedSearch() interface{} { | ||||
| 	return *m | ||||
| } | ||||
|  | ||||
| type SmsAbnormalNumberInsertReq struct { | ||||
|     Id int `json:"-" comment:"主键id"` // 主键id | ||||
|     Account string `json:"account" comment:"账号"` | ||||
|     PlatformCode string `json:"platformCode" comment:"平台code"` | ||||
|     Phone string `json:"phone" comment:"电话号码"` | ||||
|     common.ControlBy | ||||
| } | ||||
|  | ||||
| func (s *SmsAbnormalNumberInsertReq) Generate(model *models.SmsAbnormalNumber)  { | ||||
|     if s.Id == 0 { | ||||
|         model.Model = common.Model{ Id: s.Id } | ||||
|     } | ||||
|     model.Account = s.Account | ||||
|     model.PlatformCode = s.PlatformCode | ||||
|     model.Phone = s.Phone | ||||
|     model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||
| } | ||||
|  | ||||
| func (s *SmsAbnormalNumberInsertReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| type SmsAbnormalNumberUpdateReq struct { | ||||
|     Id int `uri:"id" comment:"主键id"` // 主键id | ||||
|     Account string `json:"account" comment:"账号"` | ||||
|     PlatformCode string `json:"platformCode" comment:"平台code"` | ||||
|     Phone string `json:"phone" comment:"电话号码"` | ||||
|     common.ControlBy | ||||
| } | ||||
|  | ||||
| func (s *SmsAbnormalNumberUpdateReq) Generate(model *models.SmsAbnormalNumber)  { | ||||
|     if s.Id == 0 { | ||||
|         model.Model = common.Model{ Id: s.Id } | ||||
|     } | ||||
|     model.Account = s.Account | ||||
|     model.PlatformCode = s.PlatformCode | ||||
|     model.Phone = s.Phone | ||||
|     model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||
| } | ||||
|  | ||||
| func (s *SmsAbnormalNumberUpdateReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| // SmsAbnormalNumberGetReq 功能获取请求参数 | ||||
| type SmsAbnormalNumberGetReq struct { | ||||
|      Id int `uri:"id"` | ||||
| } | ||||
| func (s *SmsAbnormalNumberGetReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| // SmsAbnormalNumberDeleteReq 功能删除请求参数 | ||||
| type SmsAbnormalNumberDeleteReq struct { | ||||
| 	Ids []int `json:"ids"` | ||||
| } | ||||
|  | ||||
| func (s *SmsAbnormalNumberDeleteReq) GetId() interface{} { | ||||
| 	return s.Ids | ||||
| } | ||||
							
								
								
									
										117
									
								
								app/admin/service/dto/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								app/admin/service/dto/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/dto" | ||||
| 	common "go-admin/common/models" | ||||
| ) | ||||
|  | ||||
| type SmsPlatformKeyGetPageReq struct { | ||||
| 	dto.Pagination `search:"-"` | ||||
| 	PlatformCode   string `form:"platformCode"  search:"type:exact;column:platform_code;table:sms_platform_key" comment:"平台code"` | ||||
| 	SmsPlatformKeyOrder | ||||
| } | ||||
|  | ||||
| type SmsPlatformKeyOrder struct { | ||||
| 	Id           string `form:"idOrder"  search:"type:order;column:id;table:sms_platform_key"` | ||||
| 	PlatformCode string `form:"platformCodeOrder"  search:"type:order;column:platform_code;table:sms_platform_key"` | ||||
| 	ApiKey       string `form:"apiKeyOrder"  search:"type:order;column:api_key;table:sms_platform_key"` | ||||
| 	ApiSecret    string `form:"apiSecretOrder"  search:"type:order;column:api_secret;table:sms_platform_key"` | ||||
| 	Status       string `form:"statusOrder"  search:"type:order;column:status;table:sms_platform_key"` | ||||
| 	Remark       string `form:"remarkOrder"  search:"type:order;column:remark;table:sms_platform_key"` | ||||
| 	CreatedAt    string `form:"createdAtOrder"  search:"type:order;column:created_at;table:sms_platform_key"` | ||||
| 	UpdatedAt    string `form:"updatedAtOrder"  search:"type:order;column:updated_at;table:sms_platform_key"` | ||||
| 	DeletedAt    string `form:"deletedAtOrder"  search:"type:order;column:deleted_at;table:sms_platform_key"` | ||||
| 	CreateBy     string `form:"createByOrder"  search:"type:order;column:create_by;table:sms_platform_key"` | ||||
| 	UpdateBy     string `form:"updateByOrder"  search:"type:order;column:update_by;table:sms_platform_key"` | ||||
| } | ||||
|  | ||||
| func (m *SmsPlatformKeyGetPageReq) GetNeedSearch() interface{} { | ||||
| 	return *m | ||||
| } | ||||
|  | ||||
| type SmsPlatformKeyInsertReq struct { | ||||
| 	Id           int    `json:"-" comment:"主键id"` // 主键id | ||||
| 	PlatformCode string `json:"platformCode" comment:"平台code"` | ||||
| 	Account      string `json:"account" comment:"account"` | ||||
| 	ApiKey       string `json:"apiKey" comment:"平台key"` | ||||
| 	ApiSecret    string `json:"apiSecret" comment:"平台私钥"` | ||||
| 	Status       int64  `json:"status" comment:"状态 1-启用 2-禁用"` | ||||
| 	Remark       string `json:"remark" comment:"备注"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| func (s *SmsPlatformKeyInsertReq) Generate(model *models.SmsPlatformKey) { | ||||
| 	if s.Id == 0 { | ||||
| 		model.Model = common.Model{Id: s.Id} | ||||
| 	} | ||||
| 	model.PlatformCode = s.PlatformCode | ||||
| 	model.Account = s.Account | ||||
| 	model.ApiKey = s.ApiKey | ||||
| 	model.ApiSecret = s.ApiSecret | ||||
| 	model.Status = s.Status | ||||
| 	model.Remark = s.Remark | ||||
| 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||
| } | ||||
|  | ||||
| func (s *SmsPlatformKeyInsertReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| type SmsPlatformKeyUpdateReq struct { | ||||
| 	Id           int    `uri:"id" comment:"主键id"` // 主键id | ||||
| 	PlatformCode string `json:"platformCode" comment:"平台code"` | ||||
| 	Account      string `json:"account" comment:"平台账号"` | ||||
| 	ApiKey       string `json:"apiKey" comment:"平台key"` | ||||
| 	ApiSecret    string `json:"apiSecret" comment:"平台私钥"` | ||||
| 	Status       int64  `json:"status" comment:"状态 1-启用 2-禁用"` | ||||
| 	Remark       string `json:"remark" comment:"备注"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| func (s *SmsPlatformKeyUpdateReq) Generate(model *models.SmsPlatformKey) { | ||||
| 	if s.Id == 0 { | ||||
| 		model.Model = common.Model{Id: s.Id} | ||||
| 	} | ||||
| 	model.PlatformCode = s.PlatformCode | ||||
| 	model.Account = s.Account | ||||
| 	model.ApiKey = s.ApiKey | ||||
| 	model.ApiSecret = s.ApiSecret | ||||
| 	model.Status = s.Status | ||||
| 	model.Remark = s.Remark | ||||
| 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||
| } | ||||
|  | ||||
| func (s *SmsPlatformKeyUpdateReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| // SmsPlatformKeyGetReq 功能获取请求参数 | ||||
| type SmsPlatformKeyGetReq struct { | ||||
| 	Id int `uri:"id"` | ||||
| } | ||||
|  | ||||
| func (s *SmsPlatformKeyGetReq) GetId() interface{} { | ||||
| 	return s.Id | ||||
| } | ||||
|  | ||||
| // SmsPlatformKeyDeleteReq 功能删除请求参数 | ||||
| type SmsPlatformKeyDeleteReq struct { | ||||
| 	Ids []int `json:"ids"` | ||||
| } | ||||
|  | ||||
| func (s *SmsPlatformKeyDeleteReq) GetId() interface{} { | ||||
| 	return s.Ids | ||||
| } | ||||
|  | ||||
| type SmsPlatformKeyQueueDto struct { | ||||
| 	PlatformCode string `json:"platformCode" comment:"平台code"` | ||||
| 	Account      string `json:"account" comment:"平台账号"` | ||||
| 	ApiKey       string `json:"apiKey" comment:"平台key"` | ||||
| 	ApiSecret    string `json:"apiSecret" comment:"平台私钥"` | ||||
| } | ||||
|  | ||||
| type SmsPlatformKeyGroupDto struct { | ||||
| 	PlatformCode string `json:"platformCode"` | ||||
| 	Count        int    `json:"count"` | ||||
| } | ||||
| @ -154,6 +154,7 @@ type VerificationDTO struct { | ||||
| 	// * nonrenewableRefunded: 服务已退款。 | ||||
| 	State                    string  `json:"state" comment:"verificationPending┃verificationCompleted┃verificationCanceled┃verificationTimedOut┃verificationReported┃verificationRefunded┃verificationReused┃verificationReactivated┃renewableActive┃renewableOverdue┃renewableExpired┃renewableRefunded┃nonrenewableActive┃nonrenewableExpired┃nonrenewableRefunded"` | ||||
| 	TotalCost                float64 `json:"totalCost"` | ||||
| 	IsIncludedForNextRenewal bool    `json:"isIncludedForNextRenewal" comment:"是否自动续期"` | ||||
| } | ||||
|  | ||||
| // 长效详情 | ||||
|  | ||||
							
								
								
									
										250
									
								
								app/admin/service/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								app/admin/service/sms_abnormal_number.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,250 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| type SmsAbnormalNumber struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| // GetPage 获取SmsAbnormalNumber列表 | ||||
| func (e *SmsAbnormalNumber) GetPage(c *dto.SmsAbnormalNumberGetPageReq, p *actions.DataPermission, list *[]models.SmsAbnormalNumber, count *int64) error { | ||||
| 	var err error | ||||
| 	var data models.SmsAbnormalNumber | ||||
|  | ||||
| 	err = e.Orm.Model(&data). | ||||
| 		Scopes( | ||||
| 			cDto.MakeCondition(c.GetNeedSearch()), | ||||
| 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		). | ||||
| 		Find(list).Limit(-1).Offset(-1). | ||||
| 		Count(count).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("SmsAbnormalNumberService GetPage error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Get 获取SmsAbnormalNumber对象 | ||||
| func (e *SmsAbnormalNumber) Get(d *dto.SmsAbnormalNumberGetReq, p *actions.DataPermission, model *models.SmsAbnormalNumber) error { | ||||
| 	var data models.SmsAbnormalNumber | ||||
|  | ||||
| 	err := e.Orm.Model(&data). | ||||
| 		Scopes( | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		). | ||||
| 		First(model, d.GetId()).Error | ||||
| 	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 		err = errors.New("查看对象不存在或无权查看") | ||||
| 		e.Log.Errorf("Service GetSmsAbnormalNumber error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("db error:%s", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Insert 创建SmsAbnormalNumber对象 | ||||
| func (e *SmsAbnormalNumber) Insert(c *dto.SmsAbnormalNumberInsertReq) error { | ||||
| 	var err error | ||||
| 	var data models.SmsAbnormalNumber | ||||
| 	c.Generate(&data) | ||||
| 	err = e.Orm.Create(&data).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("SmsAbnormalNumberService Insert error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update 修改SmsAbnormalNumber对象 | ||||
| func (e *SmsAbnormalNumber) Update(c *dto.SmsAbnormalNumberUpdateReq, p *actions.DataPermission) error { | ||||
| 	var err error | ||||
| 	var data = models.SmsAbnormalNumber{} | ||||
| 	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("SmsAbnormalNumberService Save error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权更新该数据") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Remove 删除SmsAbnormalNumber | ||||
| func (e *SmsAbnormalNumber) Remove(d *dto.SmsAbnormalNumberDeleteReq, p *actions.DataPermission) error { | ||||
| 	var data models.SmsAbnormalNumber | ||||
|  | ||||
| 	db := e.Orm.Model(&data). | ||||
| 		Scopes( | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		).Delete(&data, d.GetId()) | ||||
| 	if err := db.Error; err != nil { | ||||
| 		e.Log.Errorf("Service RemoveSmsAbnormalNumber error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权删除该数据") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 获取平台上不再系统内的自动续期号码 | ||||
| func (e *SmsAbnormalNumber) SyncState() error { | ||||
| 	textVerified := SmsTextVerified{Service: e.Service} | ||||
| 	// daiSysms := SmsDaisysms{Service: e.Service} 没有列表api | ||||
|  | ||||
| 	textVerifiedNumbers, err := textVerified.GetAllPlatformNumbers() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取长效号码列表失败 %v", err) | ||||
| 	} else { | ||||
| 		// 获取系统内textverified平台的自动续期号码 | ||||
| 		// 参数: 平台代码, 号码类型(1=长效), 自动续费(1=开启), 激活状态(2=已激活) | ||||
| 		systemNumbers, err := e.GetPhoneByPlatformCode(global.SmsPlatformTextVerified, 1, 1, 2) | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("获取系统内textverified自动续期号码失败: %v", err) | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		e.Log.Infof("系统内textverified自动续期号码数量: %d", len(systemNumbers)) | ||||
|  | ||||
| 		// 比较平台号码和系统号码,找出差异 | ||||
| 		missingNumbers := e.findMissingNumbers(textVerifiedNumbers, systemNumbers) | ||||
|  | ||||
| 		if len(missingNumbers) > 0 { | ||||
| 			e.Log.Warnf("发现%d个平台开启自动续费但系统内缺失的号码", len(missingNumbers)) | ||||
|  | ||||
| 			err = e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 				if err1 := tx.Model(&models.SmsAbnormalNumber{}). | ||||
| 					Unscoped(). | ||||
| 					Where(" platform_code =?", global.SmsPlatformTextVerified). | ||||
| 					Delete(&models.SmsAbnormalNumber{}). | ||||
| 					Error; err1 != nil { | ||||
| 					return err1 | ||||
| 				} | ||||
|  | ||||
| 				if err1 := tx.CreateInBatches(missingNumbers, 100).Error; err1 != nil { | ||||
| 					return err1 | ||||
| 				} | ||||
|  | ||||
| 				return nil | ||||
| 			}) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("同步后保存差异数据失败 %v", err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			e.Log.Infof("所有平台自动续期号码在系统内都存在") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // findMissingNumbers 比较平台号码和系统号码,找出平台上有但系统内没有的号码 | ||||
| // 参数: | ||||
| //   - platformNumbers: 平台上的号码列表 | ||||
| //   - systemNumbers: 系统内的号码列表 | ||||
| // | ||||
| // 返回: | ||||
| //   - []dto.VerificationDTO: 平台上有但系统内缺失的号码列表 | ||||
| func (e *SmsAbnormalNumber) findMissingNumbers(platformNumbers map[string][]dto.VerificationDTO, systemNumbers []models.SmsPhone) []models.SmsAbnormalNumber { | ||||
| 	// 创建系统号码的映射表,用于快速查找 | ||||
| 	systemNumberMap := make(map[string]bool) | ||||
| 	for _, sysPhone := range systemNumbers { | ||||
| 		// 使用号码作为key | ||||
| 		systemNumberMap[sysPhone.Phone] = true | ||||
| 	} | ||||
|  | ||||
| 	// 找出平台上有但系统内没有的号码 | ||||
| 	missingNumbers := make([]models.SmsAbnormalNumber, 0) | ||||
| 	for account, platformPhone := range platformNumbers { | ||||
| 		for _, v := range platformPhone { | ||||
| 			if v.State == "renewableActive" && v.IsIncludedForNextRenewal && !systemNumberMap[v.Number] { | ||||
| 				missingNumbers = append(missingNumbers, models.SmsAbnormalNumber{ | ||||
| 					PlatformCode: global.SmsPlatformTextVerified, | ||||
| 					Account:      account, | ||||
| 					Phone:        v.Number, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return missingNumbers | ||||
| } | ||||
|  | ||||
| // GetPhoneByPlatformCode 循环获取对应平台自动续期的号码 | ||||
| // 为避免一次性查询过多数据,使用分页查询,每次最多查询100条记录 | ||||
| // @param platformCode 平台代码 (如: textverified, daisysms) | ||||
| // @return []models.SmsPhone 号码列表 | ||||
| // @return error 错误信息 | ||||
| func (e *SmsAbnormalNumber) GetPhoneByPlatformCode(platformCode string, numberType, autoRenewal, status int) ([]models.SmsPhone, error) { | ||||
| 	var phones []models.SmsPhone | ||||
| 	var allPhones []models.SmsPhone | ||||
|  | ||||
| 	// 分页参数 | ||||
| 	pageSize := 1000 // 每页最多1000条记录 | ||||
| 	offset := 0 | ||||
|  | ||||
| 	// 循环分页查询,避免一次性查询过多数据 | ||||
| 	for { | ||||
| 		// 查询条件: | ||||
| 		// 1. platform_code = platformCode (指定平台) | ||||
| 		// 2. type = numberType (号码类型) | ||||
| 		// 3. auto_renewal = autoRenewal (自动续费状态) | ||||
| 		// 4. actived = status (激活状态) | ||||
| 		err := e.Orm.Model(&models.SmsPhone{}). | ||||
| 			Where("platform_code = ? AND type = ? AND auto_renewal = ? AND actived = ? AND expire_time > ?", | ||||
| 				platformCode, numberType, autoRenewal, status, time.Now()). | ||||
| 			Order("id ASC"). | ||||
| 			Limit(pageSize). | ||||
| 			Offset(offset). | ||||
| 			Find(&phones).Error | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("查询平台[%s]自动续期号码失败: %v", platformCode, err) | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		// 如果没有查询到数据,说明已经查询完毕 | ||||
| 		if len(phones) == 0 { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// 将本次查询结果添加到总结果中 | ||||
| 		allPhones = append(allPhones, phones...) | ||||
|  | ||||
| 		// 如果本次查询结果少于pageSize,说明已经是最后一页 | ||||
| 		if len(phones) < pageSize { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// 更新偏移量,准备查询下一页 | ||||
| 		offset += pageSize | ||||
| 	} | ||||
|  | ||||
| 	e.Log.Infof("平台[%s]查询到%d个自动续期号码", platformCode, len(allPhones)) | ||||
| 	return allPhones, nil | ||||
| } | ||||
							
								
								
									
										20
									
								
								app/admin/service/sms_abnormal_number_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/admin/service/sms_abnormal_number_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| ) | ||||
|  | ||||
| // 同步差异号码 | ||||
| func TestSyncSat(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	smsAbnormalNumber := SmsAbnormalNumber{} | ||||
| 	smsAbnormalNumber.Orm = db | ||||
| 	smsAbnormalNumber.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	if err := smsAbnormalNumber.SyncState(); err != nil { | ||||
| 		t.Errorf("同步差异号码失败 %v", err) | ||||
| 	} | ||||
| } | ||||
| @ -25,7 +25,9 @@ type SmsDaisysms struct { | ||||
|  | ||||
| // 同步价格 | ||||
| func (e SmsDaisysms) SyncPrices() { | ||||
| 	prices, err := e.GetPrices() | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetRoundRobinKey(global.SmsPlatformDaisysms) | ||||
| 	prices, err := e.GetPrices(apiInfo) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("GetPrices error: %v", err) | ||||
| @ -44,16 +46,12 @@ func (e SmsDaisysms) SyncPrices() { | ||||
| // service 服务code | ||||
| // maxPrice 最大价格 | ||||
| // period 时长(月) | ||||
| func (e *SmsDaisysms) GetNumberForApi(getType int, serviceCode string, maxPrice decimal.Decimal, period int) (int, string, int) { | ||||
| func (e *SmsDaisysms) GetNumberForApi(apiInfo *dto.SmsPlatformKeyQueueDto, getType int, serviceCode string, maxPrice decimal.Decimal, period int) (int, string, int) { | ||||
| 	acitvationId := 0 | ||||
| 	result := "" | ||||
| 	resultCode := statuscode.Success | ||||
|  | ||||
| 	configResp, code := GetApiKey(e) | ||||
| 	if code != statuscode.Success { | ||||
| 		return acitvationId, result, code | ||||
| 	} | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getNumber&service=%s", configResp.ConfigValue, serviceCode) | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getNumber&service=%s", apiInfo.ApiKey, serviceCode) | ||||
|  | ||||
| 	if getType == 1 { | ||||
| 		url = fmt.Sprintf("%s&duration=%dM", url, period) | ||||
| @ -103,13 +101,8 @@ func (e *SmsDaisysms) GetNumberForApi(getType int, serviceCode string, maxPrice | ||||
| } | ||||
|  | ||||
| // 设置租赁结束 | ||||
| func (e *SmsDaisysms) setStatus(id int) int { | ||||
| 	configResp, code := GetApiKey(e) | ||||
| 	if code != statuscode.Success { | ||||
| 		return code | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=setStatus&id=%d&status=6", configResp.ConfigValue, id) | ||||
| func (e *SmsDaisysms) setStatus(id int, apiInfo *dto.SmsPlatformKeyQueueDto) int { | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=setStatus&id=%d&status=6", apiInfo.ApiKey, id) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| @ -127,34 +120,14 @@ func (e *SmsDaisysms) setStatus(id int) int { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func GetApiKey(e *SmsDaisysms) (dto.GetSysConfigByKEYForServiceResp, int) { | ||||
| 	configService := SysConfig{Service: e.Service} | ||||
| 	configResp := dto.GetSysConfigByKEYForServiceResp{} | ||||
| 	err := configService.GetWithKey(&dto.SysConfigByKeyReq{ConfigKey: "sms_key"}, &configResp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取短信api失败, %s", err) | ||||
| 		return dto.GetSysConfigByKEYForServiceResp{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if configResp.ConfigValue == "" { | ||||
| 		e.Log.Error("短信api不能为空") | ||||
| 		return dto.GetSysConfigByKEYForServiceResp{}, statuscode.ServerError | ||||
| 	} | ||||
| 	return configResp, statuscode.Success | ||||
| } | ||||
|  | ||||
| // GetCodeForApi 获取验证码 | ||||
| // messageId 短信id | ||||
| // return 验证码, 状态码 | ||||
| func (e *SmsDaisysms) GetCodeForApi(messageId string) (string, int) { | ||||
| func (e *SmsDaisysms) GetCodeForApi(messageId string, apiInfo *dto.SmsPlatformKeyQueueDto) (string, int) { | ||||
| 	result := "" | ||||
| 	key, code := GetApiKey(e) | ||||
| 	code := statuscode.Success | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return result, code | ||||
| 	} | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getStatus&id=%s", key.ConfigValue, messageId) | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getStatus&id=%s", apiInfo.ApiKey, messageId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| @ -183,13 +156,10 @@ func (e *SmsDaisysms) GetCodeForApi(messageId string) (string, int) { | ||||
| // getExtraActivation 获取额外的激活 | ||||
| // messageId 短信id | ||||
| // return 验证码, 状态码 | ||||
| func (e *SmsDaisysms) getExtraActivation(activationId string) (int, int) { | ||||
| func (e *SmsDaisysms) getExtraActivation(activationId string, apiInfo *dto.SmsPlatformKeyQueueDto) (int, int) { | ||||
| 	result := 0 | ||||
| 	key, err := GetApiKey(e) | ||||
| 	if err != statuscode.Success { | ||||
| 		return 0, statuscode.ServerError | ||||
| 	} | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getExtraActivation&activationId=%s", key.ConfigValue, activationId) | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getExtraActivation&activationId=%s", apiInfo.ApiKey, activationId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err1 := client.GetRaw(url, nil) | ||||
|  | ||||
| @ -224,14 +194,9 @@ func (e *SmsDaisysms) getExtraActivation(activationId string) (int, int) { | ||||
| } | ||||
|  | ||||
| // KeepLongTerm 长期租赁 | ||||
| func (e *SmsDaisysms) KeepLongTerm(activationId string) int { | ||||
| 	key, code := GetApiKey(e) | ||||
| func (e *SmsDaisysms) KeepLongTerm(activationId string, apiInfo *dto.SmsPlatformKeyQueueDto) int { | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=keep&id=%s", key.ConfigValue, activationId) | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=keep&id=%s", apiInfo.ApiKey, activationId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| @ -250,15 +215,8 @@ func (e *SmsDaisysms) KeepLongTerm(activationId string) int { | ||||
| } | ||||
|  | ||||
| // 取消租赁 | ||||
| func (e *SmsDaisysms) CancelRental(activationId string) int { | ||||
| 	key, code := GetApiKey(e) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("租赁api请求失败 %s") | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=setStatus&id=%s&status=8", key.ConfigValue, activationId) | ||||
| func (e *SmsDaisysms) CancelRental(activationId string, apiInfo *dto.SmsPlatformKeyQueueDto) int { | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=setStatus&id=%s&status=8", apiInfo.ApiKey, activationId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| @ -277,16 +235,10 @@ func (e *SmsDaisysms) CancelRental(activationId string) int { | ||||
| } | ||||
|  | ||||
| // 获取价格 | ||||
| func (e *SmsDaisysms) GetPrices() ([]dto.DaisysmsPriceResp, error) { | ||||
| func (e *SmsDaisysms) GetPrices(apiInfo *dto.SmsPlatformKeyQueueDto) ([]dto.DaisysmsPriceResp, error) { | ||||
| 	result := make([]dto.DaisysmsPriceResp, 0) | ||||
| 	key, code := GetApiKey(e) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("租赁api请求失败 %s") | ||||
| 		return result, errors.New("获取租赁ApiKey失败") | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getPrices", key.ConfigValue) | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getPrices", apiInfo.ApiKey) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, status, err := client.GetRaw(url, nil) | ||||
|  | ||||
| @ -327,15 +279,9 @@ func (e *SmsDaisysms) GetPrices() ([]dto.DaisysmsPriceResp, error) { | ||||
| // ChangeAutoRenew 修改自动续期 | ||||
| // activationId 短信id | ||||
| // status 状态 | ||||
| func (e *SmsDaisysms) ChangeAutoRenewForApi(activationId string, status bool) int { | ||||
| 	key, err := GetApiKey(e) | ||||
| func (e *SmsDaisysms) ChangeAutoRenewForApi(activationId string, status bool, apiInfo *dto.SmsPlatformKeyQueueDto) int { | ||||
|  | ||||
| 	if err != statuscode.Success { | ||||
| 		e.Log.Errorf("查询sms api请求失败 %d", activationId) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=setAutoRenew&id=%s&value=%t", key.ConfigValue, activationId, status) | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=setAutoRenew&id=%s&value=%t", apiInfo.ApiKey, activationId, status) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err1 := client.GetRaw(url, nil) | ||||
|  | ||||
|  | ||||
| @ -2,6 +2,7 @@ package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
| @ -93,15 +94,22 @@ func (e *SmsPhone) CancelNumber(req *dto.SmsPhoneCancelNumberReq, userId int) in | ||||
|  | ||||
| // 聚合取消 | ||||
| func (e *SmsPhone) CancelNumberManage(data *models.SmsPhone) int { | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetApiInfo(data.PlatformCode, data.ApiKey) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取平台密钥失败, %s", err) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	switch data.PlatformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
|  | ||||
| 		return service.CancelRental(data.NewActivationId) | ||||
| 		return service.CancelRental(data.NewActivationId, &apiInfo) | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 		return service.CancelRental(data.NewActivationId, data.Type) | ||||
| 		return service.CancelRental(data.NewActivationId, data.Type, &apiInfo) | ||||
| 	default: | ||||
| 		return statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
| @ -139,13 +147,21 @@ func (e *SmsPhone) DeleteMyNumber(req *dto.DeleteMyNumberReq, userId int) int { | ||||
| func (e *SmsPhone) WeakUp(req *dto.WeakUpReq, userId int, defult bool) (dto.WeakUpResp, int) { | ||||
| 	smsPhone := models.SmsPhone{} | ||||
| 	result := dto.WeakUpResp{} | ||||
| 	if err := e.Orm.Model(smsPhone).Where("activation_id =? and user_id= ?", req.ActivationId, userId).First(&smsPhone).Error; err != nil { | ||||
| 	if err := e.Orm.Model(smsPhone). | ||||
| 		Where("activation_id =? and user_id= ?", req.ActivationId, userId). | ||||
| 		First(&smsPhone).Error; err != nil { | ||||
| 		e.Log.Errorf("获取短信号码失败, %s", err) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if smsPhone.Status != 1 || defult { | ||||
| 		newActivationId, messageId, startTime, endTime, code := e.WeakUpManage(&smsPhone) | ||||
| 		smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 		apiInfo, err := smsPlatformKeyRedis.GetApiInfo(smsPhone.PlatformCode, smsPhone.ApiKey) | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("获取平台密钥失败, %s", err) | ||||
| 			return result, statuscode.ServerError | ||||
| 		} | ||||
| 		newActivationId, messageId, startTime, endTime, code := e.WeakUpManage(&smsPhone, &apiInfo) | ||||
|  | ||||
| 		if code == statuscode.ServerError { | ||||
| 			return result, code | ||||
| @ -179,15 +195,15 @@ func (e *SmsPhone) WeakUp(req *dto.WeakUpReq, userId int, defult bool) (dto.Weak | ||||
|  | ||||
| // 唤醒号码 | ||||
| // returns newActivationId,messageId, code | ||||
| func (e *SmsPhone) WeakUpManage(req *models.SmsPhone) (string, string, *time.Time, *time.Time, int) { | ||||
| func (e *SmsPhone) WeakUpManage(req *models.SmsPhone, apiInfo *dto.SmsPlatformKeyQueueDto) (string, string, *time.Time, *time.Time, int) { | ||||
| 	switch req.PlatformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
| 		id, code := service.getExtraActivation(req.ActivationId) | ||||
| 		id, code := service.getExtraActivation(req.ActivationId, apiInfo) | ||||
| 		return strconv.Itoa(id), "", nil, nil, code | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
| 		resp, code := service.getExtraActivation(req.NewActivationId) | ||||
| 		resp, code := service.getExtraActivation(req.NewActivationId, apiInfo) | ||||
| 		return resp.ReservationId, resp.Id, resp.UsageWindowStart, resp.UsageWindowEnd, code | ||||
| 	default: | ||||
| 		return "", "", nil, nil, statuscode.SmsPlatformUnavailable | ||||
| @ -300,7 +316,7 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| 	} | ||||
|  | ||||
| 	now := time.Now() | ||||
| 	activationId, phone, code, expireTime, startTime, endTime := e.GetNumberManage(req.PlatformCode, req.Type, req.ServiceCode, price, req.Period) | ||||
| 	activationId, phone, code, expireTime, startTime, endTime, apiInfo := e.GetNumberManage(req.PlatformCode, req.Type, req.ServiceCode, price, req.Period) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, code | ||||
| @ -320,6 +336,7 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| 	smsPhone.Actived = 1 | ||||
| 	smsPhone.StartTime = startTime | ||||
| 	smsPhone.EndTime = endTime | ||||
| 	smsPhone.ApiKey = apiInfo.ApiKey | ||||
|  | ||||
| 	smsPhone.Price = price | ||||
| 	if req.Type == 1 { | ||||
| @ -361,7 +378,7 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| 	if req.Type == 1 { | ||||
| 		var code int | ||||
| 		if req.PlatformCode == global.SmsPlatformDaisysms { | ||||
| 			code = e.ChangeAutoRenewManage(smsPhone.PlatformCode, smsPhone.NewActivationId, true) | ||||
| 			code = e.ChangeAutoRenewManage(smsPhone.PlatformCode, smsPhone.ApiKey, smsPhone.NewActivationId, true) | ||||
| 		} else { | ||||
| 			code = http.StatusOK | ||||
| 			e.WeakUp(&dto.WeakUpReq{ActivationId: smsPhone.NewActivationId}, userId, true) | ||||
| @ -386,24 +403,32 @@ func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumber | ||||
| // *expirateTime 号码过期时间 | ||||
| // *startTime 长效号码单次接码开始使用(textverified有用) | ||||
| // *endTime 长效号码单次接码过期时间(textverified有用) | ||||
| func (e *SmsPhone) GetNumberManage(platformCode string, typ int, serviceCode string, price decimal.Decimal, period int) (string, string, int, *time.Time, *time.Time, *time.Time) { | ||||
| 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) { | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	queue, err := smsPlatformKeyRedis.GetRoundRobinKey(platformCode) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取短信平台队列失败, %s", err) | ||||
| 		return "", "", statuscode.ServerError, nil, nil, nil, queue | ||||
| 	} | ||||
|  | ||||
| 	switch platformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
| 		activationId, phone, code := service.GetNumberForApi(typ, serviceCode, price, period) | ||||
| 		activationId, phone, code := service.GetNumberForApi(queue, typ, serviceCode, price, period) | ||||
|  | ||||
| 		return strconv.Itoa(activationId), phone, code, nil, nil, nil | ||||
| 		return strconv.Itoa(activationId), phone, code, nil, nil, nil, queue | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
| 		resp, code := service.GetNumberAndWakeUp(typ, serviceCode, price, period) | ||||
| 		resp, code := service.GetNumberAndWakeUp(typ, serviceCode, price, period, queue) | ||||
|  | ||||
| 		if code != statuscode.Success { | ||||
| 			return "", "", code, nil, resp.StartTime, resp.EndTime | ||||
| 			return "", "", code, nil, resp.StartTime, resp.EndTime, queue | ||||
| 		} | ||||
|  | ||||
| 		return resp.Id, resp.Phone, code, resp.EndAt, resp.StartTime, resp.EndTime | ||||
| 		return resp.Id, resp.Phone, code, resp.EndAt, resp.StartTime, resp.EndTime, queue | ||||
| 	default: | ||||
| 		return "", "", statuscode.SmsPlatformUnavailable, nil, nil, nil | ||||
| 		return "", "", statuscode.SmsPlatformUnavailable, nil, nil, nil, queue | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -445,10 +470,29 @@ func (e *SmsPhone) SyncCodes() error { | ||||
| 	if err := e.Orm.Model(models.SmsPhone{}).Where("status =1").Find(&phones).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	mapData := make(map[string]*dto.SmsPlatformKeyQueueDto) | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
|  | ||||
| 	for _, item := range phones { | ||||
| 		key := fmt.Sprintf("%s:%s", item.PlatformCode, item.ApiKey) | ||||
|  | ||||
| 		if _, ok := mapData[key]; !ok { | ||||
| 			apiInfo, _ := smsPlatformKeyRedis.GetApiInfo(item.PlatformCode, item.ApiKey) | ||||
|  | ||||
| 			if apiInfo.ApiKey != "" { | ||||
| 				mapData[key] = &apiInfo | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range phones { | ||||
| 		apiInfo, ok := mapData[fmt.Sprintf("%s:%s", item.PlatformCode, item.ApiKey)] | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		code, codeStatus := e.GetCodeManage(item.PlatformCode, item.NewActivationId, | ||||
| 			item.Type, item.UserId, item.Service, item.ServiceCode) | ||||
| 			item.Type, item.UserId, item.Service, item.ServiceCode, apiInfo) | ||||
|  | ||||
| 		if code == "" { | ||||
| 			var expireTime time.Time | ||||
| @ -510,15 +554,15 @@ func (e *SmsPhone) SyncCodes() error { | ||||
| } | ||||
|  | ||||
| // 获取验证码 | ||||
| func (e *SmsPhone) GetCodeManage(platformCode string, messageId string, typ int, userId int, smsService, serviceCode string) (string, int) { | ||||
| func (e *SmsPhone) GetCodeManage(platformCode string, messageId string, typ int, userId int, smsService, serviceCode string, apiInfo *dto.SmsPlatformKeyQueueDto) (string, int) { | ||||
| 	switch platformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
|  | ||||
| 		return service.GetCodeForApi(messageId) | ||||
| 		return service.GetCodeForApi(messageId, apiInfo) | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
| 		return service.GetCode(messageId, typ, userId, smsService, serviceCode) | ||||
| 		return service.GetCode(messageId, typ, userId, smsService, serviceCode, apiInfo) | ||||
| 	default: | ||||
| 		return "", statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
| @ -685,7 +729,7 @@ func (e *SmsPhone) handleInsufficientBalance(phone models.SmsPhone) error { | ||||
| 	} | ||||
|  | ||||
| 	// 调用平台取消续费接口 | ||||
| 	code := e.ChangeAutoRenewManage(phone.PlatformCode, phone.ActivationId, false) | ||||
| 	code := e.ChangeAutoRenewManage(phone.PlatformCode, phone.ApiKey, phone.ActivationId, false) | ||||
| 	if code != statuscode.Success { | ||||
| 		params["auto_renewal"] = 1 | ||||
| 		params["remark"] = "" | ||||
| @ -778,7 +822,7 @@ func (e *SmsPhone) DoChangeAutoRenewal(data models.SmsPhone, autoRenewal int) (b | ||||
| 	if data.Actived == 3 { | ||||
| 		code = http.StatusOK | ||||
| 	} else { | ||||
| 		code = e.ChangeAutoRenewManage(data.PlatformCode, data.ActivationId, status) | ||||
| 		code = e.ChangeAutoRenewManage(data.PlatformCode, data.ApiKey, data.ActivationId, status) | ||||
|  | ||||
| 		if code != statuscode.Success { | ||||
| 			return true, statuscode.ServerError | ||||
| @ -792,16 +836,24 @@ func (e *SmsPhone) DoChangeAutoRenewal(data models.SmsPhone, autoRenewal int) (b | ||||
| } | ||||
|  | ||||
| // ChangeAutoRenewManage 修改自动续期管理 | ||||
| func (e *SmsPhone) ChangeAutoRenewManage(platform string, activationId string, status bool) int { | ||||
| func (e *SmsPhone) ChangeAutoRenewManage(platform, apiKey string, activationId string, status bool) int { | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetApiInfo(platform, apiKey) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取平台密钥失败: %v", err) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	switch platform { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
|  | ||||
| 		return service.ChangeAutoRenewForApi(activationId, status) | ||||
| 		return service.ChangeAutoRenewForApi(activationId, status, &apiInfo) | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 		return service.Renew(activationId, status) | ||||
| 		return service.Renew(activationId, status, &apiInfo) | ||||
| 	default: | ||||
| 		return statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										1
									
								
								app/admin/service/sms_phone_extend.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/admin/service/sms_phone_extend.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| package service | ||||
							
								
								
									
										314
									
								
								app/admin/service/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								app/admin/service/sms_platform_key.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,314 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"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/utils/utility" | ||||
| ) | ||||
|  | ||||
| type SmsPlatformKey struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| // GetPage 获取SmsPlatformKey列表 | ||||
| func (e *SmsPlatformKey) GetPage(c *dto.SmsPlatformKeyGetPageReq, p *actions.DataPermission, list *[]models.SmsPlatformKey, count *int64) error { | ||||
| 	var err error | ||||
| 	var data models.SmsPlatformKey | ||||
|  | ||||
| 	err = e.Orm.Model(&data). | ||||
| 		Scopes( | ||||
| 			cDto.MakeCondition(c.GetNeedSearch()), | ||||
| 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		). | ||||
| 		Find(list).Limit(-1).Offset(-1). | ||||
| 		Count(count).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("SmsPlatformKeyService GetPage error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for index := range *list { | ||||
| 		(*list)[index].ApiKey = utility.DesensitizeGeneric((*list)[index].ApiKey, 3, 3, '*') | ||||
| 		(*list)[index].ApiSecret = utility.DesensitizeGeneric((*list)[index].ApiSecret, 3, 3, '*') | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetRandomKey 随机获取一个密钥 | ||||
| func (e *SmsPlatformKey) GetRandomKey(platformCode string) (*dto.SmsPlatformKeyQueueDto, error) { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.GetRandomKey(platformCode) | ||||
| } | ||||
|  | ||||
| // GetRoundRobinKey 轮询获取密钥 | ||||
| func (e *SmsPlatformKey) GetRoundRobinKey(platformCode string) (*dto.SmsPlatformKeyQueueDto, error) { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.GetRoundRobinKey(platformCode) | ||||
| } | ||||
|  | ||||
| // GetQueueLength 获取队列长度 | ||||
| func (e *SmsPlatformKey) GetQueueLength(platformCode string) (int64, error) { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.GetQueueLength(platformCode) | ||||
| } | ||||
|  | ||||
| // ClearPlatformQueue 清空指定平台的队列 | ||||
| func (e *SmsPlatformKey) ClearPlatformQueue(platformCode string) error { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.ClearPlatformQueue(platformCode) | ||||
| } | ||||
|  | ||||
| // Get 获取SmsPlatformKey对象 | ||||
| func (e *SmsPlatformKey) Get(d *dto.SmsPlatformKeyGetReq, p *actions.DataPermission, model *models.SmsPlatformKey) error { | ||||
| 	var data models.SmsPlatformKey | ||||
|  | ||||
| 	err := e.Orm.Model(&data). | ||||
| 		Scopes( | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		). | ||||
| 		First(model, d.GetId()).Error | ||||
| 	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 		err = errors.New("查看对象不存在或无权查看") | ||||
| 		e.Log.Errorf("Service GetSmsPlatformKey error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("db error:%s", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Insert 创建SmsPlatformKey对象 | ||||
| func (e *SmsPlatformKey) Insert(c *dto.SmsPlatformKeyInsertReq) error { | ||||
| 	var err error | ||||
| 	var data models.SmsPlatformKey | ||||
| 	var count int64 | ||||
| 	c.Generate(&data) | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SmsPlatformKey{}). | ||||
| 		Where("platform_code = ? and api_key =? and account=?", data.PlatformCode, data.ApiKey, data.Account). | ||||
| 		Count(&count).Error; err != nil { | ||||
| 		e.Log.Errorf("SmsPlatformKeyService Insert error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if count > 0 { | ||||
| 		return errors.New("ApiKey或账号 已存在") | ||||
| 	} | ||||
|  | ||||
| 	err = e.Orm.Create(&data).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("SmsPlatformKeyService Insert error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := e.AddQueque(dto.SmsPlatformKeyQueueDto{ | ||||
| 		PlatformCode: data.PlatformCode, | ||||
| 		Account:      data.Account, | ||||
| 		ApiKey:       data.ApiKey, | ||||
| 		ApiSecret:    data.ApiSecret, | ||||
| 	}); err != nil { | ||||
| 		e.Log.Errorf("添加队列失败,%v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update 修改SmsPlatformKey对象 | ||||
| func (e *SmsPlatformKey) Update(c *dto.SmsPlatformKeyUpdateReq, p *actions.DataPermission) error { | ||||
| 	var err error | ||||
| 	var data = models.SmsPlatformKey{} | ||||
| 	var count int64 | ||||
| 	e.Orm.Scopes( | ||||
| 		actions.Permission(data.TableName(), p), | ||||
| 	).First(&data, c.GetId()) | ||||
| 	oldKey := data.ApiKey | ||||
| 	oldSecret := data.ApiSecret | ||||
| 	oldStatus := data.Status | ||||
| 	oldPlatformCode := data.PlatformCode | ||||
|  | ||||
| 	if err1 := e.Orm.Model(&models.SmsPlatformKey{}). | ||||
| 		Where("platform_code = ? and api_key =? and account=? and id != ?", | ||||
| 			data.PlatformCode, data.ApiKey, data.Account, data.Id). | ||||
| 		Count(&count).Error; err1 != nil { | ||||
| 		e.Log.Errorf("SmsPlatformKeyService Insert error:%s \r\n", err1) | ||||
| 		return err1 | ||||
| 	} | ||||
| 	if count > 0 { | ||||
| 		return errors.New("ApiKey或账号 已存在") | ||||
| 	} | ||||
|  | ||||
| 	c.Generate(&data) | ||||
|  | ||||
| 	// 如果要将启用状态改为禁用状态,需要检查该平台是否还有其他启用的密钥 | ||||
| 	if data.Status == 2 { | ||||
| 		var remainingCount int64 | ||||
| 		err1 := e.Orm.Model(&models.SmsPlatformKey{}). | ||||
| 			Where("platform_code = ? AND status = ? AND id != ?", oldPlatformCode, 1, c.GetId()). | ||||
| 			Count(&remainingCount).Error | ||||
| 		if err1 != nil { | ||||
| 			e.Log.Errorf("检查平台剩余启用密钥失败: %v", err1) | ||||
| 			return errors.New("检查平台剩余启用密钥失败") | ||||
| 		} | ||||
| 		if remainingCount == 0 { | ||||
| 			return fmt.Errorf("平台 %s 至少需要保留一个启用状态的密钥,无法禁用", oldPlatformCode) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	db := e.Orm.Save(&data) | ||||
| 	if err = db.Error; err != nil { | ||||
| 		e.Log.Errorf("SmsPlatformKeyService Save error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	if db.RowsAffected == 0 { | ||||
| 		return errors.New("无权更新该数据") | ||||
| 	} | ||||
|  | ||||
| 	if oldStatus == 1 && data.Status == 2 { | ||||
| 		if err := e.RemoveQueque(dto.SmsPlatformKeyQueueDto{ | ||||
| 			PlatformCode: data.PlatformCode, | ||||
| 			Account:      data.Account, | ||||
| 			ApiKey:       data.ApiKey, | ||||
| 			ApiSecret:    data.ApiSecret, | ||||
| 		}, false); err != nil { | ||||
| 			e.Log.Errorf("删除失败,%v", err) | ||||
| 			return err | ||||
| 		} | ||||
| 	} else if oldStatus == 2 && data.Status == 1 { | ||||
| 		if err := e.AddQueque(dto.SmsPlatformKeyQueueDto{ | ||||
| 			PlatformCode: data.PlatformCode, | ||||
| 			Account:      data.Account, | ||||
| 			ApiKey:       data.ApiKey, | ||||
| 			ApiSecret:    data.ApiSecret, | ||||
| 		}); err != nil { | ||||
| 			e.Log.Errorf("添加队列失败,%v", err) | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		oldQueueData := dto.SmsPlatformKeyQueueDto{ | ||||
| 			PlatformCode: data.PlatformCode, | ||||
| 			Account:      data.Account, | ||||
| 			ApiKey:       oldKey, | ||||
| 			ApiSecret:    oldSecret, | ||||
| 		} | ||||
| 		queueData := dto.SmsPlatformKeyQueueDto{ | ||||
| 			PlatformCode: data.PlatformCode, | ||||
| 			Account:      data.Account, | ||||
| 			ApiKey:       data.ApiKey, | ||||
| 			ApiSecret:    data.ApiSecret, | ||||
| 		} | ||||
| 		if err := e.Replace(oldQueueData, queueData); err != nil { | ||||
| 			e.Log.Errorf("替换队列失败,%v", err) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Remove 删除SmsPlatformKey | ||||
| func (e *SmsPlatformKey) Remove(d *dto.SmsPlatformKeyDeleteReq, p *actions.DataPermission) error { | ||||
| 	var data models.SmsPlatformKey | ||||
| 	var datas []models.SmsPlatformKey | ||||
|  | ||||
| 	if err1 := e.Orm.Where("id in (?)", d.GetId()).Find(&datas).Error; err1 != nil { | ||||
| 		e.Log.Errorf("Service RemoveSmsPlatformKey error:%s \r\n", err1) | ||||
| 		return err1 | ||||
| 	} | ||||
|  | ||||
| 	var platformCodes []string | ||||
|  | ||||
| 	for _, item := range datas { | ||||
| 		if utility.ContainsString(platformCodes, item.PlatformCode) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		platformCodes = append(platformCodes, item.PlatformCode) | ||||
| 	} | ||||
|  | ||||
| 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		db := tx.Model(&data). | ||||
| 			Scopes( | ||||
| 				actions.Permission(data.TableName(), p), | ||||
| 			).Delete(&data, d.GetId()) | ||||
|  | ||||
| 		var count []string | ||||
|  | ||||
| 		if err1 := tx.Model(&data).Where("status =1"). | ||||
| 			Group("platform_code"). | ||||
| 			Select("platform_code"). | ||||
| 			Scan(&count). | ||||
| 			Error; err1 != nil { | ||||
| 			e.Log.Errorf("Service RemoveSmsPlatformKey error:%s \r\n", err1) | ||||
| 			return err1 | ||||
| 		} | ||||
|  | ||||
| 		for _, item := range platformCodes { | ||||
| 			if !utility.ContainsString(count, item) { | ||||
| 				return errors.New("删除失败,通道最少需要保留一个可用ApiKey") | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if err := db.Error; err != nil { | ||||
| 			e.Log.Errorf("Service RemoveSmsPlatformKey error:%s \r\n", err) | ||||
| 			return err | ||||
| 		} | ||||
| 		if db.RowsAffected == 0 { | ||||
| 			return errors.New("无权删除该数据") | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range datas { | ||||
| 		queueDta := dto.SmsPlatformKeyQueueDto{ | ||||
| 			PlatformCode: item.PlatformCode, | ||||
| 			Account:      data.Account, | ||||
| 			ApiKey:       item.ApiKey, | ||||
| 			ApiSecret:    item.ApiSecret, | ||||
| 		} | ||||
|  | ||||
| 		if err := e.RemoveQueque(queueDta, true); err != nil { | ||||
| 			e.Log.Errorf("移出队列失败,%v", err) | ||||
| 			return errors.New("删除队列失败") | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // InitQueque 初始化Redis缓存队列 | ||||
| func (e *SmsPlatformKey) InitQueque() error { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.InitRedisQueque() | ||||
| } | ||||
|  | ||||
| // Replace 替换Redis缓存中的密钥 | ||||
| func (e *SmsPlatformKey) Replace(oldEntity dto.SmsPlatformKeyQueueDto, entity dto.SmsPlatformKeyQueueDto) error { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.ReplaceRedisKey(oldEntity, entity) | ||||
| } | ||||
|  | ||||
| // RemoveQueque 从Redis缓存中移出队列 | ||||
| func (e *SmsPlatformKey) RemoveQueque(entity dto.SmsPlatformKeyQueueDto, shouldDel bool) error { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.RemoveRedisQueque(entity, shouldDel) | ||||
| } | ||||
|  | ||||
| // AddQueque 添加到Redis缓存队列 | ||||
| func (e *SmsPlatformKey) AddQueque(entity dto.SmsPlatformKeyQueueDto) error { | ||||
| 	redisService := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	return redisService.AddRedisQueque(entity) | ||||
| } | ||||
							
								
								
									
										327
									
								
								app/admin/service/sms_platform_key_redis.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								app/admin/service/sms_platform_key_redis.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,327 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
|  | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/utils/redishelper" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // SmsPlatformKeyRedis Redis版本的SMS平台密钥管理服务 | ||||
| type SmsPlatformKeyRedis struct { | ||||
| 	SmsPlatformKey | ||||
| 	redisHelper *redishelper.RedisHelper | ||||
| } | ||||
|  | ||||
| // NewSmsPlatformKeyRedis 创建Redis版本的SMS平台密钥服务 | ||||
| func NewSmsPlatformKeyRedis(orm *gorm.DB, log logger.Logger) *SmsPlatformKeyRedis { | ||||
| 	return &SmsPlatformKeyRedis{ | ||||
| 		SmsPlatformKey: SmsPlatformKey{ | ||||
| 			Service: service.Service{ | ||||
| 				Orm: orm, | ||||
| 				Log: logger.NewHelper(log), | ||||
| 			}, | ||||
| 		}, | ||||
| 		redisHelper: redishelper.DefaultRedis, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // getRedisKey 获取Redis键名 | ||||
| func (e *SmsPlatformKeyRedis) getRedisKey(platformCode string) string { | ||||
| 	return fmt.Sprintf("sms:platform:keys:%s", platformCode) | ||||
| } | ||||
|  | ||||
| // getRedisIndexKey 获取Redis索引键名(用于快速查找) | ||||
| func (e *SmsPlatformKeyRedis) getRedisIndexKey(platformCode, apiKey string) string { | ||||
| 	return fmt.Sprintf("sms:platform:index:%s:%s", platformCode, apiKey) | ||||
| } | ||||
|  | ||||
| func (e *SmsPlatformKeyRedis) GetApiInfo(platformCode, apiKey string) (dto.SmsPlatformKeyQueueDto, error) { | ||||
| 	indexKey := e.getRedisIndexKey(platformCode, apiKey) | ||||
| 	data, err := e.redisHelper.GetString(indexKey) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取Redis索引失败 [%s:%s]: %v", platformCode, apiKey, err) | ||||
| 		return dto.SmsPlatformKeyQueueDto{}, err | ||||
| 	} | ||||
|  | ||||
| 	var apiInfo dto.SmsPlatformKeyQueueDto | ||||
| 	err = sonic.Unmarshal([]byte(data), &apiInfo) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("解析Redis数据失败 [%s:%s]: %v", platformCode, apiKey, err) | ||||
| 		return dto.SmsPlatformKeyQueueDto{}, err | ||||
| 	} | ||||
|  | ||||
| 	return apiInfo, nil | ||||
| } | ||||
|  | ||||
| // InitRedisQueque 初始化Redis缓存队列 | ||||
| // 从数据库加载所有SMS平台密钥并存储到Redis中 | ||||
| func (e *SmsPlatformKeyRedis) InitRedisQueque() error { | ||||
| 	// 查询所有启用的SMS平台密钥 | ||||
| 	var list []models.SmsPlatformKey | ||||
| 	err := e.Orm.Where("status = ?", 1).Find(&list).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("查询SMS平台密钥失败: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if len(list) == 0 { | ||||
| 		e.Log.Warn("未找到启用的SMS平台密钥") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// 按平台分组 | ||||
| 	platformKeyMap := make(map[string][]dto.SmsPlatformKeyQueueDto) | ||||
| 	for _, item := range list { | ||||
| 		data := dto.SmsPlatformKeyQueueDto{ | ||||
| 			PlatformCode: item.PlatformCode, | ||||
| 			Account:      item.Account, | ||||
| 			ApiKey:       item.ApiKey, | ||||
| 			ApiSecret:    item.ApiSecret, | ||||
| 		} | ||||
| 		platformKeyMap[item.PlatformCode] = append(platformKeyMap[item.PlatformCode], data) | ||||
| 	} | ||||
|  | ||||
| 	// 将每个平台的密钥存储到Redis | ||||
| 	for platformCode, keys := range platformKeyMap { | ||||
| 		redisKey := e.getRedisKey(platformCode) | ||||
|  | ||||
| 		// 清空现有数据 | ||||
| 		err := e.redisHelper.DeleteString(redisKey) | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("清空Redis队列失败 [%s]: %v", platformCode, err) | ||||
| 		} | ||||
|  | ||||
| 		// 存储密钥列表 | ||||
| 		for _, key := range keys { | ||||
| 			keyJson, _ := json.Marshal(key) | ||||
| 			err := e.redisHelper.RPushList(redisKey, string(keyJson)) | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("添加密钥到Redis队列失败 [%s]: %v", platformCode, err) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			// 创建索引,便于快速查找和删除 | ||||
| 			indexKey := e.getRedisIndexKey(platformCode, key.ApiKey) | ||||
| 			err = e.redisHelper.SetString(indexKey, string(keyJson)) | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("创建密钥索引失败 [%s:%s]: %v", platformCode, key.ApiKey, err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// 设置过期时间(24小时) | ||||
| 		err = e.redisHelper.SetKeyExpiration(redisKey, 24*time.Hour) | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("设置Redis队列过期时间失败 [%s]: %v", platformCode, err) | ||||
| 		} | ||||
|  | ||||
| 		e.Log.Infof("平台 [%s] 初始化 %d 个密钥到Redis缓存", platformCode, len(keys)) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // AddRedisQueque 添加密钥到Redis缓存 | ||||
| func (e *SmsPlatformKeyRedis) AddRedisQueque(entity dto.SmsPlatformKeyQueueDto) error { | ||||
| 	redisKey := e.getRedisKey(entity.PlatformCode) | ||||
| 	indexKey := e.getRedisIndexKey(entity.PlatformCode, entity.ApiKey) | ||||
|  | ||||
| 	// 检查是否已存在 | ||||
| 	// exists, err := e.redisHelper.GetString(indexKey) | ||||
| 	// if err == nil && exists != "" { | ||||
| 	// 	return errors.New("密钥已存在") | ||||
| 	// } | ||||
|  | ||||
| 	// 序列化密钥数据 | ||||
| 	keyJson, err := json.Marshal(entity) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("序列化密钥数据失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 添加到队列 | ||||
| 	err = e.redisHelper.RPushList(redisKey, string(keyJson)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("添加密钥到Redis队列失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 创建索引 | ||||
| 	err = e.redisHelper.SetString(indexKey, string(keyJson)) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("创建密钥索引失败: %v", err) | ||||
| 		// 索引创建失败不影响主要功能 | ||||
| 	} | ||||
|  | ||||
| 	// 设置过期时间 | ||||
| 	err = e.redisHelper.SetKeyExpiration(redisKey, 24*time.Hour) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("设置Redis队列过期时间失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	e.Log.Infof("成功添加密钥到Redis缓存 [%s:%s]", entity.PlatformCode, entity.ApiKey) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RemoveRedisQueque 从Redis缓存中删除密钥 | ||||
| // shouldDel 是否删除索引 | ||||
| func (e *SmsPlatformKeyRedis) RemoveRedisQueque(entity dto.SmsPlatformKeyQueueDto, shouldDel bool) error { | ||||
| 	redisKey := e.getRedisKey(entity.PlatformCode) | ||||
| 	indexKey := e.getRedisIndexKey(entity.PlatformCode, entity.ApiKey) | ||||
|  | ||||
| 	// 获取要删除的密钥数据 | ||||
| 	keyData, err := e.redisHelper.GetString(indexKey) | ||||
| 	if err != nil || keyData == "" { | ||||
| 		return errors.New("密钥不存在") | ||||
| 	} | ||||
|  | ||||
| 	// 从队列中删除 | ||||
| 	_, err = e.redisHelper.LRem(redisKey, keyData) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("从Redis队列删除密钥失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 删除索引 | ||||
| 	if shouldDel { | ||||
| 		err = e.redisHelper.DeleteString(indexKey) | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("删除密钥索引失败: %v", err) | ||||
| 			// 索引删除失败不影响主要功能 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// e.Log.Infof("成功从Redis缓存删除密钥 [%s:%s]", entity.PlatformCode, entity.ApiKey) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetRandomKey 随机获取一个密钥 | ||||
| func (e *SmsPlatformKeyRedis) GetRandomKey(platformCode string) (*dto.SmsPlatformKeyQueueDto, error) { | ||||
| 	redisKey := e.getRedisKey(platformCode) | ||||
|  | ||||
| 	// 获取所有密钥 | ||||
| 	keys, err := e.redisHelper.GetAllList(redisKey) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("获取Redis队列失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if len(keys) == 0 { | ||||
| 		return nil, errors.New("队列为空") | ||||
| 	} | ||||
|  | ||||
| 	// 随机选择一个 | ||||
| 	rand.Seed(time.Now().UnixNano()) | ||||
| 	randomIndex := rand.Intn(len(keys)) | ||||
| 	keyJson := keys[randomIndex] | ||||
|  | ||||
| 	var keyDto dto.SmsPlatformKeyQueueDto | ||||
| 	err = json.Unmarshal([]byte(keyJson), &keyDto) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("反序列化密钥数据失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return &keyDto, nil | ||||
| } | ||||
|  | ||||
| // GetRoundRobinKey 轮询获取密钥 | ||||
| func (e *SmsPlatformKeyRedis) GetRoundRobinKey(platformCode string) (*dto.SmsPlatformKeyQueueDto, error) { | ||||
| 	redisKey := e.getRedisKey(platformCode) | ||||
|  | ||||
| 	// 从队列头部取出一个密钥 | ||||
| 	keyJson, err := e.redisHelper.LPopList(redisKey) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("从Redis队列获取密钥失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if keyJson == "" { | ||||
| 		return nil, errors.New("队列为空") | ||||
| 	} | ||||
|  | ||||
| 	// 将密钥重新放到队列尾部(实现轮询) | ||||
| 	err = e.redisHelper.RPushList(redisKey, keyJson) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("轮询密钥回放失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	var keyDto dto.SmsPlatformKeyQueueDto | ||||
| 	err = json.Unmarshal([]byte(keyJson), &keyDto) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("反序列化密钥数据失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return &keyDto, nil | ||||
| } | ||||
|  | ||||
| // GetQueueLength 获取队列长度 | ||||
| func (e *SmsPlatformKeyRedis) GetQueueLength(platformCode string) (int64, error) { | ||||
| 	redisKey := e.getRedisKey(platformCode) | ||||
| 	keys, err := e.redisHelper.GetAllList(redisKey) | ||||
| 	if err != nil { | ||||
| 		return 0, fmt.Errorf("获取Redis队列长度失败: %v", err) | ||||
| 	} | ||||
| 	return int64(len(keys)), nil | ||||
| } | ||||
|  | ||||
| // ReplaceRedisKey 替换Redis缓存中的密钥 | ||||
| func (e *SmsPlatformKeyRedis) ReplaceRedisKey(oldEntity, newEntity dto.SmsPlatformKeyQueueDto) error { | ||||
| 	// 先删除旧密钥 | ||||
| 	err := e.RemoveRedisQueque(oldEntity, true) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("删除旧密钥失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 再添加新密钥 | ||||
| 	err = e.AddRedisQueque(newEntity) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("添加新密钥失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// e.Log.Infof("成功替换Redis缓存密钥 [%s] %s -> %s", newEntity.PlatformCode, oldEntity.ApiKey, newEntity.ApiKey) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ClearPlatformQueue 清空指定平台的队列 | ||||
| func (e *SmsPlatformKeyRedis) ClearPlatformQueue(platformCode string) error { | ||||
| 	redisKey := e.getRedisKey(platformCode) | ||||
| 	err := e.redisHelper.DeleteString(redisKey) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("清空平台队列失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 清空相关索引 | ||||
| 	pattern := fmt.Sprintf("sms:platform:index:%s:*", platformCode) | ||||
| 	err = e.redisHelper.DeleteKeysByPrefix(pattern) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("清空平台索引失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	e.Log.Infof("成功清空平台 [%s] 的Redis队列", platformCode) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 获取平台所有密钥 | ||||
| func (e *SmsPlatformKeyRedis) GetPlatformKeys(platformCode string) ([]dto.SmsPlatformKeyQueueDto, error) { | ||||
| 	redisKey := e.getRedisKey(platformCode) | ||||
| 	keys, err := e.redisHelper.GetAllList(redisKey) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("获取Redis队列失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	var keyDtos []dto.SmsPlatformKeyQueueDto | ||||
| 	for _, keyJson := range keys { | ||||
| 		var keyDto dto.SmsPlatformKeyQueueDto | ||||
| 		err = json.Unmarshal([]byte(keyJson), &keyDto) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("反序列化密钥数据失败: %v", err) | ||||
| 		} | ||||
| 		keyDtos = append(keyDtos, keyDto) | ||||
| 	} | ||||
| 	return keyDtos, nil | ||||
| } | ||||
							
								
								
									
										23
									
								
								app/admin/service/sms_platform_key_redis_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/admin/service/sms_platform_key_redis_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go-admin/common/global" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| ) | ||||
|  | ||||
| func TestNextKey(t *testing.T) { | ||||
| 	orm := initSetting() | ||||
|  | ||||
| 	redisService := NewSmsPlatformKeyRedis(orm, logger.DefaultLogger) | ||||
|  | ||||
| 	val, err := redisService.GetRoundRobinKey(global.SmsPlatformTextVerified) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		t.Errorf("GetRoundRobinKey error:%s \r\n", err) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("val:%s \r\n", val) | ||||
| } | ||||
| @ -49,8 +49,16 @@ func (e SmsReceiveLog) WebHook(req *dto.SmsReceiveWebHookReq) error { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 		apiInfo, err := smsPlatformKeyRedis.GetApiInfo(phoneLog.PlatformCode, phoneLog.ApiKey) | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("获取平台密钥失败, %s", err) | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		phoneService := SmsDaisysms{Service: e.Service} | ||||
| 		if code := phoneService.setStatus(req.ActivationID); code != statuscode.Success { | ||||
|  | ||||
| 		if code := phoneService.setStatus(req.ActivationID, &apiInfo); code != statuscode.Success { | ||||
| 			e.Log.Errorf("接受验证码回调后修改状态失败 %d", code) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -13,9 +13,10 @@ import ( | ||||
| ) | ||||
|  | ||||
| func initSetting() *gorm.DB { | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/proxy-server-prod?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	dsn := "root:123456@tcp(127.0.0.1:3306)/proxy_server?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
| 	// config.ExtConfig. | ||||
| 	config.ExtConfig.TrxGridUrl = "https://api.trongrid.io" | ||||
| 	config.ExtConfig.DaisysmsUrl = "https://daisysms.com/stubs/handler_api.php" | ||||
| 	config.ExtConfig.SmsTextVerified.ApiKey = "ZQ0swXnsaPpeGdwa3c7gT9U9I1Oh9WoDHx0amuYovvaHuqd5u6B4NBBUSUBjR" | ||||
|  | ||||
| @ -32,7 +32,7 @@ type SmsTextVerified struct { | ||||
| // 获取收到的验证码 | ||||
| // messageId 短效或长效号码的ID | ||||
| // typ 0-短效 1-长效 | ||||
| func (e SmsTextVerified) GetCode(messageId string, typ int, userId int, service, serviceCode string) (string, int) { | ||||
| func (e SmsTextVerified) GetCode(messageId string, typ int, userId int, service, serviceCode string, apiInfo *dto.SmsPlatformKeyQueueDto) (string, int) { | ||||
| 	reservationType := "" | ||||
|  | ||||
| 	if typ == 0 { | ||||
| @ -41,7 +41,7 @@ func (e SmsTextVerified) GetCode(messageId string, typ int, userId int, service, | ||||
| 		reservationType = "renewable" | ||||
| 	} | ||||
| 	url := fmt.Sprintf(getSmsCode, messageId, reservationType) | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != http.StatusOK { | ||||
| 		e.Log.Errorf("获取授权请求失败,status %d", code) | ||||
| @ -168,13 +168,13 @@ var ErrUnAuth = errors.New("未授权,请检查配置的账号和密码") | ||||
| var ErrOutOfStockOrUnavailable = errors.New("缺货或服务不可用") | ||||
|  | ||||
| // Login 登录 | ||||
| func (e *SmsTextVerified) Login() (string, error) { | ||||
| func (e *SmsTextVerified) Login(apiInfo *dto.SmsPlatformKeyQueueDto) (string, error) { | ||||
| 	resp := dto.TextVerifiedLoginResp{} | ||||
| 	var token string | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.SmsTextVerified.Url, nil) | ||||
| 	headers := map[string]string{ | ||||
| 		"X-API-USERNAME": config.ExtConfig.SmsTextVerified.UserName, | ||||
| 		"X-API-KEY":      config.ExtConfig.SmsTextVerified.ApiKey, | ||||
| 		"X-API-USERNAME": apiInfo.Account, | ||||
| 		"X-API-KEY":      apiInfo.ApiKey, | ||||
| 	} | ||||
| 	_, err1 := client.Post(loginUrl, nil, headers, &resp) | ||||
|  | ||||
| @ -192,7 +192,8 @@ func (e *SmsTextVerified) Login() (string, error) { | ||||
|  | ||||
| 	if resp.ExpiresIn >= 10 { | ||||
| 		resp.ExpiresIn = resp.ExpiresIn - 10 // 提前10秒过期 | ||||
| 		if err := redishelper.DefaultRedis.SetStringExpire(global.TextVerifiedToken, token, time.Duration(resp.ExpiresIn)*time.Second); err != nil { | ||||
| 		key := fmt.Sprintf(global.TextVerifiedToken, apiInfo.ApiKey) | ||||
| 		if err := redishelper.DefaultRedis.SetStringExpire(key, token, time.Duration(resp.ExpiresIn)*time.Second); err != nil { | ||||
| 			e.Log.Errorf("TextVerified登录失败,缓存Token失败 error: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| @ -201,15 +202,16 @@ func (e *SmsTextVerified) Login() (string, error) { | ||||
| } | ||||
|  | ||||
| // 获取token | ||||
| func (e *SmsTextVerified) GetToken() (string, error) { | ||||
| 	token, err := redishelper.DefaultRedis.GetString(global.TextVerifiedToken) | ||||
| func (e *SmsTextVerified) GetToken(apiInfo *dto.SmsPlatformKeyQueueDto) (string, error) { | ||||
| 	key := fmt.Sprintf(global.TextVerifiedToken, apiInfo.ApiKey) | ||||
| 	token, err := redishelper.DefaultRedis.GetString(key) | ||||
| 	if err != nil && errors.Is(err, redis.Nil) { | ||||
| 		// token不存在,重新登录获取 | ||||
| 		return e.Login() | ||||
| 		return e.Login(apiInfo) | ||||
| 	} | ||||
|  | ||||
| 	if token == "" { | ||||
| 		return e.Login() | ||||
| 		return e.Login(apiInfo) | ||||
| 	} | ||||
|  | ||||
| 	return token, nil | ||||
| @ -225,9 +227,9 @@ func (e *SmsTextVerified) GetAreas() ([]string, error) { | ||||
| } | ||||
|  | ||||
| // 获取服务列表 | ||||
| func (e *SmsTextVerified) GetServices() ([]dto.TextVerifiedServeResp, error) { | ||||
| func (e *SmsTextVerified) GetServices(apiInfo *dto.SmsPlatformKeyQueueDto) ([]dto.TextVerifiedServeResp, error) { | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.SmsTextVerified.Url, nil) | ||||
| 	headers, err := e.GetAuthHeader() | ||||
| 	headers, err := e.GetAuthHeader(apiInfo) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -244,7 +246,15 @@ func (e *SmsTextVerified) GetServices() ([]dto.TextVerifiedServeResp, error) { | ||||
|  | ||||
| // 同步服务列表 | ||||
| func (e *SmsTextVerified) SyncServices() error { | ||||
| 	services, err := e.GetServices() | ||||
| 	// 获取API信息 | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetRoundRobinKey(global.SmsPlatformTextVerified) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取API信息失败: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	services, err := e.GetServices(apiInfo) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取服务列表失败: %v", err) | ||||
| 		return err | ||||
| @ -321,17 +331,25 @@ func (e *SmsTextVerified) SyncPrices() error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetRoundRobinKey(global.SmsPlatformTextVerified) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取API信息失败: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range services { | ||||
| 		params := map[string]interface{}{} | ||||
|  | ||||
| 		price, code := e.GetPrice(0, v.Code) | ||||
| 		price, code := e.GetPrice(0, v.Code, apiInfo) | ||||
|  | ||||
| 		if code == http.StatusOK { | ||||
| 			params["price"] = price | ||||
| 		} | ||||
|  | ||||
| 		time.Sleep(time.Microsecond * 500) // 避免请求过快被限制 | ||||
| 		longPrice, code := e.GetPrice(1, v.Code) | ||||
| 		longPrice, code := e.GetPrice(1, v.Code, apiInfo) | ||||
| 		if code == http.StatusOK { | ||||
| 			params["long_price"] = longPrice | ||||
| 		} | ||||
| @ -347,14 +365,14 @@ func (e *SmsTextVerified) SyncPrices() error { | ||||
| } | ||||
|  | ||||
| // 获取号码并唤醒 | ||||
| func (e *SmsTextVerified) GetNumberAndWakeUp(tye int, serviceCode string, price decimal.Decimal, period int) (dto.SmsPhoneGetPhoneResp, int) { | ||||
| 	resp, code := e.GetNumberForApi(tye, serviceCode, price, period) | ||||
| func (e *SmsTextVerified) GetNumberAndWakeUp(tye int, serviceCode string, price decimal.Decimal, period int, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.SmsPhoneGetPhoneResp, int) { | ||||
| 	resp, code := e.GetNumberForApi(tye, serviceCode, price, period, apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return resp, code | ||||
| 	} | ||||
|  | ||||
| 	wakeUpResp, code := e.getExtraActivation(resp.Id) | ||||
| 	wakeUpResp, code := e.getExtraActivation(resp.Id, apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return resp, code | ||||
| @ -372,7 +390,7 @@ func (e *SmsTextVerified) GetNumberAndWakeUp(tye int, serviceCode string, price | ||||
| // service 服务code | ||||
| // maxPrice 最大价格 | ||||
| // period 时长(月=30天) | ||||
| func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price decimal.Decimal, period int) (dto.SmsPhoneGetPhoneResp, int) { | ||||
| func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price decimal.Decimal, period int, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.SmsPhoneGetPhoneResp, int) { | ||||
| 	//这个平台的所有号码都是美国号码 默认国家代码都是 +1 | ||||
| 	var err error | ||||
| 	var createResp dto.TextVerifiedResp | ||||
| @ -380,10 +398,10 @@ func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price dec | ||||
| 	switch typ { | ||||
| 	case 0: | ||||
| 		var code int | ||||
| 		createResp, code = e.CreateVerification(serviceCode) | ||||
| 		createResp, code = e.CreateVerification(serviceCode, apiInfo) | ||||
|  | ||||
| 		if code == statuscode.Success && createResp.Href != "" { | ||||
| 			bytes, err := e.doRequest(&createResp) | ||||
| 			bytes, err := e.doRequest(&createResp, apiInfo) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("短效号码 获取号码失败:%v", e) | ||||
| @ -410,11 +428,11 @@ func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price dec | ||||
| 			return result, statuscode.ServerError | ||||
| 		} | ||||
| 	case 1: | ||||
| 		createResp, err = e.CreateRental(serviceCode) | ||||
| 		createResp, err = e.CreateRental(serviceCode, apiInfo) | ||||
|  | ||||
| 		if err == nil && createResp.Href != "" { | ||||
| 			saleId := getIdByUrl(createResp.Href) | ||||
| 			bytes, err := e.doRequest(&createResp) | ||||
| 			bytes, err := e.doRequest(&createResp, apiInfo) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("通过销售id [%s] 获取号码失败:%v", saleId, err) | ||||
| @ -441,7 +459,7 @@ func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price dec | ||||
| 					return result, statuscode.ServerError | ||||
| 				} | ||||
|  | ||||
| 				detail, code := e.GetRentalDetail(result.Id) | ||||
| 				detail, code := e.GetRentalDetail(result.Id, apiInfo) | ||||
|  | ||||
| 				if code != statuscode.Success { | ||||
| 					return result, code | ||||
| @ -477,13 +495,13 @@ func (e *SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price dec | ||||
| } | ||||
|  | ||||
| // 执行查询请求 | ||||
| func (e *SmsTextVerified) doRequest(resp *dto.TextVerifiedResp) ([]byte, error) { | ||||
| func (e *SmsTextVerified) doRequest(resp *dto.TextVerifiedResp, apiInfo *dto.SmsPlatformKeyQueueDto) ([]byte, error) { | ||||
| 	bytes := []byte{} | ||||
| 	if resp.Href == "" { | ||||
| 		return bytes, errors.New("成功请求没有返回url") | ||||
| 	} | ||||
|  | ||||
| 	header, err := e.GetAuthHeader() | ||||
| 	header, err := e.GetAuthHeader(apiInfo) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return bytes, fmt.Errorf("获取授权失败:%v", err) | ||||
| @ -501,7 +519,7 @@ func (e *SmsTextVerified) doRequest(resp *dto.TextVerifiedResp) ([]byte, error) | ||||
| } | ||||
|  | ||||
| // 长号码租赁 | ||||
| func (e *SmsTextVerified) CreateRental(serviceCode string) (dto.TextVerifiedResp, error) { | ||||
| func (e *SmsTextVerified) CreateRental(serviceCode string, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.TextVerifiedResp, error) { | ||||
| 	req := dto.TextVerifiedCreateRewalReq{ | ||||
| 		ServiceName: serviceCode, | ||||
| 		Capability:  "sms", | ||||
| @ -513,7 +531,7 @@ func (e *SmsTextVerified) CreateRental(serviceCode string) (dto.TextVerifiedResp | ||||
|  | ||||
| 	resp := dto.TextVerifiedResp{} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取头信息失败 error: %d", code) | ||||
| @ -541,10 +559,10 @@ func (e *SmsTextVerified) CreateRental(serviceCode string) (dto.TextVerifiedResp | ||||
| } | ||||
|  | ||||
| // 获取长效号码详情 | ||||
| func (e *SmsTextVerified) GetRentalDetail(id string) (dto.VerificationRentalDetailResp, int) { | ||||
| func (e *SmsTextVerified) GetRentalDetail(id string, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.VerificationRentalDetailResp, int) { | ||||
| 	result := dto.VerificationRentalDetailResp{} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
| 	if code != statuscode.Success { | ||||
| 		return result, code | ||||
| 	} | ||||
| @ -561,8 +579,8 @@ func (e *SmsTextVerified) GetRentalDetail(id string) (dto.VerificationRentalDeta | ||||
|  | ||||
| // 唤醒号码 | ||||
| // returns activationId,messageId,startTime(可用开始时间),endTime(可用结束时间), code, | ||||
| func (e SmsTextVerified) getExtraActivation(id string) (dto.TextVerifiedWakeUpResp, int) { | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| func (e SmsTextVerified) getExtraActivation(id string, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.TextVerifiedWakeUpResp, int) { | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
| 	result := dto.TextVerifiedWakeUpResp{} | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| @ -584,7 +602,7 @@ func (e SmsTextVerified) getExtraActivation(id string) (dto.TextVerifiedWakeUpRe | ||||
| 		return result, statuscode.ServerError | ||||
| 	} else if status == http.StatusCreated || status == http.StatusOK { | ||||
| 		if resp.Method != "" && resp.Href != "" { | ||||
| 			bytes, err := e.doRequest(&resp) | ||||
| 			bytes, err := e.doRequest(&resp, apiInfo) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err) | ||||
| @ -615,19 +633,19 @@ func (e SmsTextVerified) getExtraActivation(id string) (dto.TextVerifiedWakeUpRe | ||||
| // 获取价格 | ||||
| // getType 0-短效 1-长效 | ||||
| // returns decimal.Decimal(单价), int(状态code) | ||||
| func (e *SmsTextVerified) GetPrice(typ int, serviceName string) (decimal.Decimal, int) { | ||||
| func (e *SmsTextVerified) GetPrice(typ int, serviceName string, apiInfo *dto.SmsPlatformKeyQueueDto) (decimal.Decimal, int) { | ||||
| 	switch typ { | ||||
| 	case 1: | ||||
| 		return e.GetRentalPrice(serviceName) | ||||
| 		return e.GetRentalPrice(serviceName, apiInfo) | ||||
| 	case 0: | ||||
| 		return e.GetVerificationPrice(serviceName) | ||||
| 		return e.GetVerificationPrice(serviceName, apiInfo) | ||||
| 	default: | ||||
| 		return decimal.Zero, statuscode.SmsInvalidType | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取长租价格 | ||||
| func (e *SmsTextVerified) GetRentalPrice(serviceName string) (decimal.Decimal, int) { | ||||
| func (e *SmsTextVerified) GetRentalPrice(serviceName string, apiInfo *dto.SmsPlatformKeyQueueDto) (decimal.Decimal, int) { | ||||
| 	req := dto.TextVerifiedPriceReq{ | ||||
| 		ServiceName: serviceName, | ||||
| 		Capability:  "sms", | ||||
| @ -636,7 +654,7 @@ func (e *SmsTextVerified) GetRentalPrice(serviceName string) (decimal.Decimal, i | ||||
| 		Duration:    "thirtyDay", | ||||
| 	} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取授权请求失败,status %d", code) | ||||
| @ -659,7 +677,7 @@ func (e *SmsTextVerified) GetRentalPrice(serviceName string) (decimal.Decimal, i | ||||
| } | ||||
|  | ||||
| // 获取单次验证码价格 | ||||
| func (e *SmsTextVerified) GetVerificationPrice(sericeName string) (decimal.Decimal, int) { | ||||
| func (e *SmsTextVerified) GetVerificationPrice(sericeName string, apiInfo *dto.SmsPlatformKeyQueueDto) (decimal.Decimal, int) { | ||||
| 	params := map[string]interface{}{ | ||||
| 		"serviceName": sericeName, | ||||
| 		"areaCode":    false, | ||||
| @ -668,7 +686,7 @@ func (e *SmsTextVerified) GetVerificationPrice(sericeName string) (decimal.Decim | ||||
| 		"numberType":  "mobile", | ||||
| 	} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取授权请求失败,status %d", code) | ||||
| @ -690,14 +708,14 @@ func (e *SmsTextVerified) GetVerificationPrice(sericeName string) (decimal.Decim | ||||
| } | ||||
|  | ||||
| // 单次接收 | ||||
| func (e *SmsTextVerified) CreateVerification(serviceCode string) (dto.TextVerifiedResp, int) { | ||||
| func (e *SmsTextVerified) CreateVerification(serviceCode string, apiInfo *dto.SmsPlatformKeyQueueDto) (dto.TextVerifiedResp, int) { | ||||
| 	req := dto.TextVerifiedCreateRewalReq{ | ||||
| 		ServiceName: serviceCode, | ||||
| 		Capability:  "sms", | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedResp{} | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return resp, code | ||||
| @ -722,8 +740,8 @@ func (e *SmsTextVerified) CreateVerification(serviceCode string) (dto.TextVerifi | ||||
|  | ||||
| // 取消验证码 | ||||
| // typ 0-短效 1-长效 | ||||
| func (e SmsTextVerified) CancelRental(id string, typ int) int { | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| func (e SmsTextVerified) CancelRental(id string, typ int, apiInfo *dto.SmsPlatformKeyQueueDto) int { | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return code | ||||
| @ -758,8 +776,8 @@ func (e SmsTextVerified) CancelRental(id string, typ int) int { | ||||
| 	return statuscode.Success | ||||
| } | ||||
|  | ||||
| func (e *SmsTextVerified) GetTextVerifiedAuthClient() (*httphelper.HTTPClient, int) { | ||||
| 	header, err := e.GetAuthHeader() | ||||
| func (e *SmsTextVerified) GetTextVerifiedAuthClient(apiInfo *dto.SmsPlatformKeyQueueDto) (*httphelper.HTTPClient, int) { | ||||
| 	header, err := e.GetAuthHeader(apiInfo) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("取消验证码获取token失败 error: %v", err) | ||||
| @ -882,10 +900,10 @@ func (e *SmsTextVerified) TextVerifiedWebHook(req *dto.TextVerifiedWebHookReq) e | ||||
|  | ||||
| // 续期 | ||||
| // typ 0-短效 1-长效 | ||||
| func (e *SmsTextVerified) Renew(activationId string, status bool) int { | ||||
| func (e *SmsTextVerified) Renew(activationId string, status bool, apiInfo *dto.SmsPlatformKeyQueueDto) int { | ||||
| 	url := fmt.Sprintf(updateRentalRenewStatus, activationId) | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取长效续期失败 %d", code) | ||||
| @ -916,18 +934,13 @@ func (e *SmsTextVerified) Renew(activationId string, status bool) int { | ||||
|  | ||||
| // 重新初始化短信记录 | ||||
| func (e *SmsTextVerified) InitSmsLogs() error { | ||||
| 	// phones, err := e.GetNumbers() | ||||
|  | ||||
| 	// if err != nil { | ||||
| 	// 	e.Log.Errorf("获取长效号码列表失败 %v", err) | ||||
| 	// 	return fmt.Errorf("获取长效号码列表失败 %v", err) | ||||
| 	// } | ||||
|  | ||||
| 	// numbers := make([]string, 0) | ||||
|  | ||||
| 	// for _, v := range phones { | ||||
| 	// 	numbers = append(numbers, v.Number) | ||||
| 	// } | ||||
| 	// 获取平台密钥信息 | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetApiInfo("textverified", "") | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取平台密钥失败: %v", err) | ||||
| 		return fmt.Errorf("获取平台密钥失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	phoneLogs, err := e.GetUserByNumber("textverified") | ||||
|  | ||||
| @ -938,7 +951,7 @@ func (e *SmsTextVerified) InitSmsLogs() error { | ||||
|  | ||||
| 	if len(phoneLogs) > 0 { | ||||
| 		for _, v := range phoneLogs { | ||||
| 			e.GetCode(v.ActivationId, v.Type, v.UserId, v.Service, v.ServiceCode) | ||||
| 			e.GetCode(v.ActivationId, v.Type, v.UserId, v.Service, v.ServiceCode, &apiInfo) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -956,9 +969,48 @@ func (e *SmsTextVerified) GetUserByNumber(platformCode string) ([]models.SmsPhon | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // 获取平台下 所有账号的获取记录 | ||||
| // map[string][]dto.VerificationDTO key:account | ||||
| func (e *SmsTextVerified) GetAllPlatformNumbers() (map[string][]dto.VerificationDTO, error) { | ||||
| 	// 获取平台密钥信息 | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(e.Orm, e.Log) | ||||
| 	platformKeys, err := smsPlatformKeyRedis.GetPlatformKeys(global.SmsPlatformTextVerified) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取平台密钥失败: %v", err) | ||||
| 		return nil, fmt.Errorf("获取平台密钥失败: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	allNumbers := make(map[string][]dto.VerificationDTO) | ||||
|  | ||||
| 	for _, v := range platformKeys { | ||||
| 		numbersw, err1 := e.GetAllNumbers(&v) | ||||
|  | ||||
| 		if err1 != nil { | ||||
| 			e.Log.Errorf("获取平台密钥失败: %v", err1) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		allNumbers[v.Account] = numbersw | ||||
| 	} | ||||
|  | ||||
| 	return allNumbers, nil | ||||
| } | ||||
|  | ||||
| func (e *SmsTextVerified) GetAllNumbers(apiInfo *dto.SmsPlatformKeyQueueDto) ([]dto.VerificationDTO, error) { | ||||
| 	phones, err := e.GetNumbers(apiInfo) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取长效号码列表失败 %v", err) | ||||
| 		return phones, fmt.Errorf("获取长效号码列表失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return phones, nil | ||||
| } | ||||
|  | ||||
| // 获取号码 | ||||
| func (e *SmsTextVerified) GetNumbers() ([]dto.VerificationDTO, error) { | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| func (e *SmsTextVerified) GetNumbers(apiInfo *dto.SmsPlatformKeyQueueDto) ([]dto.VerificationDTO, error) { | ||||
| 	client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
| 	result := make([]dto.VerificationDTO, 0) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| @ -967,7 +1019,7 @@ func (e *SmsTextVerified) GetNumbers() ([]dto.VerificationDTO, error) { | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedGetNumbersResp{} | ||||
| 	statusCode, err := client.Get(getNumbers, map[string]string{}, resp) | ||||
| 	statusCode, err := client.Get(getNumbers, map[string]string{}, &resp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取长效号码列表失败 %v", err) | ||||
| @ -976,11 +1028,15 @@ func (e *SmsTextVerified) GetNumbers() ([]dto.VerificationDTO, error) { | ||||
|  | ||||
| 	if statusCode == http.StatusOK { | ||||
| 		// 添加第一页数据 | ||||
| 		result = append(result, resp.Data...) | ||||
| 		for _, item := range resp.Data { | ||||
| 			item.Number = "1" + item.Number | ||||
|  | ||||
| 			result = append(result, item) | ||||
| 		} | ||||
|  | ||||
| 		// 处理分页数据 | ||||
| 		if resp.HasNext { | ||||
| 			paginatedData, err := e.fetchPaginatedData(resp.Links.Next) | ||||
| 			paginatedData, err := e.fetchPaginatedData(resp.Links.Next, apiInfo) | ||||
| 			if err != nil { | ||||
| 				return result, err | ||||
| 			} | ||||
| @ -989,7 +1045,7 @@ func (e *SmsTextVerified) GetNumbers() ([]dto.VerificationDTO, error) { | ||||
|  | ||||
| 		return result, nil | ||||
| 	} else if statusCode == http.StatusUnauthorized { | ||||
| 		return e.GetNumbers() | ||||
| 		return e.GetNumbers(apiInfo) | ||||
| 	} else { | ||||
| 		e.Log.Errorf("获取长效号码列表失败 %d", statusCode) | ||||
| 		return result, fmt.Errorf("获取长效号码列表失败 %d", statusCode) | ||||
| @ -999,11 +1055,11 @@ func (e *SmsTextVerified) GetNumbers() ([]dto.VerificationDTO, error) { | ||||
| // 获取授权header头 | ||||
| // fetchPaginatedData 获取分页数据的通用方法 | ||||
| // 处理TextVerified API的分页响应,自动获取所有页面的数据 | ||||
| func (e *SmsTextVerified) fetchPaginatedData(nextLink *dto.TextVerifiedResp) ([]dto.VerificationDTO, error) { | ||||
| func (e *SmsTextVerified) fetchPaginatedData(nextLink *dto.TextVerifiedResp, apiInfo *dto.SmsPlatformKeyQueueDto) ([]dto.VerificationDTO, error) { | ||||
| 	var result []dto.VerificationDTO | ||||
|  | ||||
| 	for nextLink != nil { | ||||
| 		nextData, err := e.doRequest(nextLink) | ||||
| 		nextData, err := e.doRequest(nextLink, apiInfo) | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("获取分页数据失败: %v", err) | ||||
| 			return result, fmt.Errorf("获取分页数据失败: %v", err) | ||||
| @ -1016,7 +1072,10 @@ func (e *SmsTextVerified) fetchPaginatedData(nextLink *dto.TextVerifiedResp) ([] | ||||
| 			return result, fmt.Errorf("解析分页数据失败: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		result = append(result, nextResp.Data...) | ||||
| 		for _, item := range nextResp.Data { | ||||
| 			item.Number = "1" + item.Number | ||||
| 			result = append(result, item) | ||||
| 		} | ||||
|  | ||||
| 		// 检查是否还有下一页 | ||||
| 		if nextResp.HasNext { | ||||
| @ -1029,8 +1088,8 @@ func (e *SmsTextVerified) fetchPaginatedData(nextLink *dto.TextVerifiedResp) ([] | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func (e *SmsTextVerified) GetAuthHeader() (map[string]string, error) { | ||||
| 	token, err := e.GetToken() | ||||
| func (e *SmsTextVerified) GetAuthHeader(apiInfo *dto.SmsPlatformKeyQueueDto) (map[string]string, error) { | ||||
| 	token, err := e.GetToken(apiInfo) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										224
									
								
								app/admin/service/sms_text_verified_enhanced.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								app/admin/service/sms_text_verified_enhanced.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,224 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"go-admin/config" | ||||
| 	"go-admin/utils/httphelper" | ||||
| 	"go-admin/utils/redishelper" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/go-redis/redis/v8" | ||||
| ) | ||||
|  | ||||
| // SmsTextVerifiedEnhanced 增强版TextVerified服务,支持token自动刷新 | ||||
| type SmsTextVerifiedEnhanced struct { | ||||
| 	service.Service | ||||
| 	maxRetries int // 最大重试次数 | ||||
| } | ||||
|  | ||||
| // NewSmsTextVerifiedEnhanced 创建增强版TextVerified服务实例 | ||||
| func NewSmsTextVerifiedEnhanced() *SmsTextVerifiedEnhanced { | ||||
| 	return &SmsTextVerifiedEnhanced{ | ||||
| 		maxRetries: 2, // 默认最大重试2次 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AuthenticatedRequest 带自动token刷新的HTTP请求包装器 | ||||
| // 当遇到401未授权错误时,会自动刷新token并重试请求 | ||||
| func (e *SmsTextVerifiedEnhanced) AuthenticatedRequest( | ||||
| 	apiInfo *dto.SmsPlatformKeyQueueDto, | ||||
| 	requestFunc func(client *httphelper.HTTPClient) (int, error), | ||||
| ) (int, error) { | ||||
| 	var lastErr error | ||||
| 	 | ||||
| 	// 尝试执行请求,包含重试逻辑 | ||||
| 	for attempt := 0; attempt <= e.maxRetries; attempt++ { | ||||
| 		// 获取认证客户端 | ||||
| 		client, code := e.GetTextVerifiedAuthClient(apiInfo) | ||||
| 		if code != statuscode.Success { | ||||
| 			e.Log.Errorf("获取授权客户端失败,状态码: %d", code) | ||||
| 			return code, fmt.Errorf("获取授权客户端失败,状态码: %d", code) | ||||
| 		} | ||||
|  | ||||
| 		// 执行请求 | ||||
| 		status, err := requestFunc(client) | ||||
| 		 | ||||
| 		// 如果请求成功,直接返回 | ||||
| 		if err == nil { | ||||
| 			return status, nil | ||||
| 		} | ||||
|  | ||||
| 		// 检查是否为401未授权错误 | ||||
| 		if e.isUnauthorizedError(status, err) { | ||||
| 			e.Log.Warnf("检测到token过期或未授权错误 (尝试 %d/%d): %v", attempt+1, e.maxRetries+1, err) | ||||
| 			 | ||||
| 			// 如果不是最后一次尝试,清除token缓存并重试 | ||||
| 			if attempt < e.maxRetries { | ||||
| 				if clearErr := e.clearTokenCache(apiInfo); clearErr != nil { | ||||
| 					e.Log.Errorf("清除token缓存失败: %v", clearErr) | ||||
| 				} | ||||
| 				e.Log.Infof("正在重试请求... (尝试 %d/%d)", attempt+2, e.maxRetries+1) | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// 如果不是401错误或已达到最大重试次数,记录错误并退出循环 | ||||
| 		lastErr = err | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	// 返回最后一次的错误 | ||||
| 	return statuscode.ServerError, fmt.Errorf("请求失败,已重试%d次: %v", e.maxRetries, lastErr) | ||||
| } | ||||
|  | ||||
| // isUnauthorizedError 检查错误是否为401未授权错误 | ||||
| func (e *SmsTextVerifiedEnhanced) isUnauthorizedError(status int, err error) bool { | ||||
| 	if status == http.StatusUnauthorized { | ||||
| 		return true | ||||
| 	} | ||||
| 	 | ||||
| 	if err != nil { | ||||
| 		errorMsg := strings.ToLower(err.Error()) | ||||
| 		// 检查常见的未授权错误消息 | ||||
| 		return strings.Contains(errorMsg, "unauthorized") || | ||||
| 			strings.Contains(errorMsg, "401") || | ||||
| 			strings.Contains(errorMsg, "invalid token") || | ||||
| 			strings.Contains(errorMsg, "token expired") || | ||||
| 			strings.Contains(errorMsg, "authentication failed") | ||||
| 	} | ||||
| 	 | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // clearTokenCache 清除Redis中的token缓存 | ||||
| func (e *SmsTextVerifiedEnhanced) clearTokenCache(apiInfo *dto.SmsPlatformKeyQueueDto) error { | ||||
| 	key := fmt.Sprintf(global.TextVerifiedToken, apiInfo.ApiKey) | ||||
| 	err := redishelper.DefaultRedis.DeleteString(key) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("删除token缓存失败: %v", err) | ||||
| 	} | ||||
| 	e.Log.Infof("已清除token缓存: %s", key) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetTextVerifiedAuthClient 获取认证的HTTP客户端 | ||||
| // 这个方法与原版相同,但可以在这里添加额外的逻辑 | ||||
| func (e *SmsTextVerifiedEnhanced) GetTextVerifiedAuthClient(apiInfo *dto.SmsPlatformKeyQueueDto) (*httphelper.HTTPClient, int) { | ||||
| 	header, err := e.GetAuthHeader(apiInfo) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取授权头失败: %v", err) | ||||
| 		return nil, statuscode.ServerError | ||||
| 	} | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.SmsTextVerified.Url, header) | ||||
| 	return client, statuscode.Success | ||||
| } | ||||
|  | ||||
| // GetAuthHeader 获取认证头 | ||||
| func (e *SmsTextVerifiedEnhanced) GetAuthHeader(apiInfo *dto.SmsPlatformKeyQueueDto) (map[string]string, error) { | ||||
| 	token, err := e.GetToken(apiInfo) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	headers := map[string]string{ | ||||
| 		"Authorization": "Bearer " + token, | ||||
| 	} | ||||
| 	return headers, nil | ||||
| } | ||||
|  | ||||
| // GetToken 获取token(与原版相同的逻辑) | ||||
| func (e *SmsTextVerifiedEnhanced) GetToken(apiInfo *dto.SmsPlatformKeyQueueDto) (string, error) { | ||||
| 	key := fmt.Sprintf(global.TextVerifiedToken, apiInfo.ApiKey) | ||||
| 	token, err := redishelper.DefaultRedis.GetString(key) | ||||
| 	if err != nil && errors.Is(err, redis.Nil) { | ||||
| 		// token不存在,重新登录获取 | ||||
| 		return e.Login(apiInfo) | ||||
| 	} | ||||
|  | ||||
| 	if token == "" { | ||||
| 		return e.Login(apiInfo) | ||||
| 	} | ||||
|  | ||||
| 	return token, nil | ||||
| } | ||||
|  | ||||
| // Login 登录获取token(与原版相同的逻辑) | ||||
| func (e *SmsTextVerifiedEnhanced) Login(apiInfo *dto.SmsPlatformKeyQueueDto) (string, error) { | ||||
| 	resp := dto.TextVerifiedLoginResp{} | ||||
| 	var token string | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.SmsTextVerified.Url, nil) | ||||
| 	headers := map[string]string{ | ||||
| 		"X-API-USERNAME": apiInfo.ApiSecret, | ||||
| 		"X-API-KEY":      apiInfo.ApiKey, | ||||
| 	} | ||||
| 	_, err1 := client.Post("/api/pub/v2/auth", nil, headers, &resp) | ||||
|  | ||||
| 	if err1 != nil { | ||||
| 		e.Log.Errorf("TextVerified登录失败 error: %v", err1) | ||||
| 		return "", err1 | ||||
| 	} | ||||
|  | ||||
| 	if resp.Token == "" { | ||||
| 		e.Log.Errorf("TextVerified登录失败,返回的Token为空") | ||||
| 		return "", errors.New("TextVerified登录失败,返回的Token为空") | ||||
| 	} | ||||
|  | ||||
| 	token = resp.Token | ||||
|  | ||||
| 	if resp.ExpiresIn >= 10 { | ||||
| 		resp.ExpiresIn = resp.ExpiresIn - 10 // 提前10秒过期 | ||||
| 		key := fmt.Sprintf(global.TextVerifiedToken, apiInfo.ApiKey) | ||||
| 		if err := redishelper.DefaultRedis.SetStringExpire(key, token, time.Duration(resp.ExpiresIn)*time.Second); err != nil { | ||||
| 			e.Log.Errorf("TextVerified登录失败,缓存Token失败 error: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return token, nil | ||||
| } | ||||
|  | ||||
| // 使用示例:增强版的GetCode方法 | ||||
| func (e *SmsTextVerifiedEnhanced) GetCodeEnhanced(messageId string, typ int, userId int, service, serviceCode string, apiInfo *dto.SmsPlatformKeyQueueDto) (string, int) { | ||||
| 	reservationType := "" | ||||
| 	if typ == 0 { | ||||
| 		reservationType = "verification" | ||||
| 	} else { | ||||
| 		reservationType = "renewable" | ||||
| 	} | ||||
| 	url := fmt.Sprintf("/api/pub/v2/sms?reservationId=%s&reservationType=%s", messageId, reservationType) | ||||
| 	 | ||||
| 	var parsedCode string | ||||
| 	var finalStatus int | ||||
| 	 | ||||
| 	// 使用增强的请求方法 | ||||
| 	status, err := e.AuthenticatedRequest(apiInfo, func(client *httphelper.HTTPClient) (int, error) { | ||||
| 		resp := dto.TextVerifiedSmsResp{} | ||||
| 		status, err := client.Get(url, nil, &resp) | ||||
| 		 | ||||
| 		if err != nil { | ||||
| 			return status, err | ||||
| 		} | ||||
| 		 | ||||
| 		// 处理响应数据... | ||||
| 		if len(resp.Data) > 0 { | ||||
| 			// 这里可以添加原有的验证码处理逻辑 | ||||
| 			parsedCode = resp.Data[0].ParsedCode | ||||
| 		} | ||||
| 		 | ||||
| 		return http.StatusOK, nil | ||||
| 	}) | ||||
| 	 | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取验证码失败: %v", err) | ||||
| 		finalStatus = statuscode.ServerError | ||||
| 	} else { | ||||
| 		finalStatus = status | ||||
| 	} | ||||
| 	 | ||||
| 	return parsedCode, finalStatus | ||||
| } | ||||
| @ -1,6 +1,7 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/global" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| @ -13,7 +14,15 @@ func TestSmsTextVerifiedLogin(t *testing.T) { | ||||
| 	s.Orm = db | ||||
| 	s.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	token, err := s.Login() | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(s.Orm, s.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetRoundRobinKey(global.SmsPlatformTextVerified) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		s.Log.Errorf("获取API信息失败: %v", err) | ||||
| 		t.Error("获取API信息失败", err) | ||||
| 	} | ||||
|  | ||||
| 	token, err := s.Login(apiInfo) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Login failed: %v", err) | ||||
| 	} else { | ||||
| @ -27,9 +36,16 @@ func TestSmsTextVerifiedGetServices(t *testing.T) { | ||||
| 	s := SmsTextVerified{} | ||||
| 	s.Orm = db | ||||
| 	s.Log = logger.NewHelper(logger.DefaultLogger) | ||||
| 	smsPlatformKeyRedis := NewSmsPlatformKeyRedis(s.Orm, s.Log) | ||||
| 	apiInfo, err := smsPlatformKeyRedis.GetRoundRobinKey(global.SmsPlatformTextVerified) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		s.Log.Errorf("获取API信息失败: %v", err) | ||||
| 		t.Error("获取API信息失败", err) | ||||
| 	} | ||||
|  | ||||
| 	// Now, test GetServices with the valid token | ||||
| 	servicesResp, err := s.GetServices() | ||||
| 	servicesResp, err := s.GetServices(apiInfo) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("GetServices failed: %v", err) | ||||
| 	} else { | ||||
|  | ||||
| @ -223,4 +223,12 @@ func initBusinesses() { | ||||
| 	memberApiService.Log = cliProxyService.Log | ||||
|  | ||||
| 	memberApiService.InitApis() | ||||
|  | ||||
| 	// 创建SMS平台密钥服务实例并初始化Redis缓存 | ||||
| 	smsPlatformKey := service.SmsPlatformKey{} | ||||
| 	smsPlatformKey.Orm = cliProxyService.Orm | ||||
| 	smsPlatformKey.Log = cliProxyService.Log | ||||
|  | ||||
| 	// 初始化Redis缓存队列 | ||||
| 	smsPlatformKey.InitQueque() | ||||
| } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								cmd/proxy-server
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cmd/proxy-server
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,6 +1,6 @@ | ||||
| package global | ||||
|  | ||||
| const ( | ||||
| 	// TextVerified Token | ||||
| 	TextVerifiedToken = "TextVerifiedToken" | ||||
| 	// TextVerified Token {apiKey} | ||||
| 	TextVerifiedToken = "TextVerifiedToken:%s" | ||||
| ) | ||||
|  | ||||
							
								
								
									
										6
									
								
								common/rediskey/sms-platform-key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/rediskey/sms-platform-key.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| package rediskey | ||||
|  | ||||
| const ( | ||||
| 	//平台key | ||||
| 	SmsPlatformKey = "sms_platform_key:%s" | ||||
| ) | ||||
							
								
								
									
										221
									
								
								utils/utility/crypto_helper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								utils/utility/crypto_helper.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,221 @@ | ||||
| package utility | ||||
|  | ||||
| import ( | ||||
| 	"crypto/aes" | ||||
| 	"crypto/cipher" | ||||
| 	"crypto/rand" | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| // 加密key | ||||
| var CryptoKey = "ProxyServer@#(123321)!Keycrypto" | ||||
|  | ||||
| // CryptoHelper 加密帮助类 | ||||
| type CryptoHelper struct { | ||||
| 	key []byte | ||||
| } | ||||
|  | ||||
| // NewCryptoHelper 创建新的加密帮助实例 | ||||
| // key: 32字节的加密密钥,如果长度不足会自动填充,超出会截断 | ||||
| func NewCryptoHelper(key string) *CryptoHelper { | ||||
| 	// 确保密钥长度为32字节(AES-256) | ||||
| 	keyBytes := make([]byte, 32) | ||||
| 	copy(keyBytes, []byte(key)) | ||||
| 	return &CryptoHelper{ | ||||
| 		key: keyBytes, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Encrypt 加密字符串 | ||||
| // plaintext: 要加密的明文 | ||||
| // 返回: base64编码的密文和错误信息 | ||||
| func (c *CryptoHelper) Encrypt(plaintext string) (string, error) { | ||||
| 	if plaintext == "" { | ||||
| 		return "", errors.New("plaintext cannot be empty") | ||||
| 	} | ||||
|  | ||||
| 	// 创建AES cipher | ||||
| 	block, err := aes.NewCipher(c.key) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to create cipher: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 使用GCM模式 | ||||
| 	gcm, err := cipher.NewGCM(block) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to create GCM: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 生成随机nonce | ||||
| 	nonce := make([]byte, gcm.NonceSize()) | ||||
| 	if _, err := io.ReadFull(rand.Reader, nonce); err != nil { | ||||
| 		return "", fmt.Errorf("failed to generate nonce: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 加密数据 | ||||
| 	ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) | ||||
|  | ||||
| 	// 返回base64编码的结果 | ||||
| 	return base64.StdEncoding.EncodeToString(ciphertext), nil | ||||
| } | ||||
|  | ||||
| // Decrypt 解密字符串 | ||||
| // ciphertext: base64编码的密文 | ||||
| // 返回: 解密后的明文和错误信息 | ||||
| func (c *CryptoHelper) Decrypt(ciphertext string) (string, error) { | ||||
| 	if ciphertext == "" { | ||||
| 		return "", errors.New("ciphertext cannot be empty") | ||||
| 	} | ||||
|  | ||||
| 	// base64解码 | ||||
| 	data, err := base64.StdEncoding.DecodeString(ciphertext) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to decode base64: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 创建AES cipher | ||||
| 	block, err := aes.NewCipher(c.key) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to create cipher: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 使用GCM模式 | ||||
| 	gcm, err := cipher.NewGCM(block) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to create GCM: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 检查数据长度 | ||||
| 	nonceSize := gcm.NonceSize() | ||||
| 	if len(data) < nonceSize { | ||||
| 		return "", errors.New("ciphertext too short") | ||||
| 	} | ||||
|  | ||||
| 	// 提取nonce和密文 | ||||
| 	nonce, cipherData := data[:nonceSize], data[nonceSize:] | ||||
|  | ||||
| 	// 解密数据 | ||||
| 	plaintext, err := gcm.Open(nil, nonce, cipherData, nil) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to decrypt: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return string(plaintext), nil | ||||
| } | ||||
|  | ||||
| // EncryptBytes 加密字节数组 | ||||
| // data: 要加密的字节数组 | ||||
| // 返回: 加密后的字节数组和错误信息 | ||||
| func (c *CryptoHelper) EncryptBytes(data []byte) ([]byte, error) { | ||||
| 	if len(data) == 0 { | ||||
| 		return nil, errors.New("data cannot be empty") | ||||
| 	} | ||||
|  | ||||
| 	// 创建AES cipher | ||||
| 	block, err := aes.NewCipher(c.key) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create cipher: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 使用GCM模式 | ||||
| 	gcm, err := cipher.NewGCM(block) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create GCM: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 生成随机nonce | ||||
| 	nonce := make([]byte, gcm.NonceSize()) | ||||
| 	if _, err := io.ReadFull(rand.Reader, nonce); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to generate nonce: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 加密数据 | ||||
| 	ciphertext := gcm.Seal(nonce, nonce, data, nil) | ||||
|  | ||||
| 	return ciphertext, nil | ||||
| } | ||||
|  | ||||
| // DecryptBytes 解密字节数组 | ||||
| // ciphertext: 加密后的字节数组 | ||||
| // 返回: 解密后的字节数组和错误信息 | ||||
| func (c *CryptoHelper) DecryptBytes(ciphertext []byte) ([]byte, error) { | ||||
| 	if len(ciphertext) == 0 { | ||||
| 		return nil, errors.New("ciphertext cannot be empty") | ||||
| 	} | ||||
|  | ||||
| 	// 创建AES cipher | ||||
| 	block, err := aes.NewCipher(c.key) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create cipher: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 使用GCM模式 | ||||
| 	gcm, err := cipher.NewGCM(block) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create GCM: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 检查数据长度 | ||||
| 	nonceSize := gcm.NonceSize() | ||||
| 	if len(ciphertext) < nonceSize { | ||||
| 		return nil, errors.New("ciphertext too short") | ||||
| 	} | ||||
|  | ||||
| 	// 提取nonce和密文 | ||||
| 	nonce, cipherData := ciphertext[:nonceSize], ciphertext[nonceSize:] | ||||
|  | ||||
| 	// 解密数据 | ||||
| 	plaintext, err := gcm.Open(nil, nonce, cipherData, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to decrypt: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return plaintext, nil | ||||
| } | ||||
|  | ||||
| // GenerateKey 生成随机密钥 | ||||
| // 返回: 32字节的随机密钥字符串 | ||||
| func GenerateKey() (string, error) { | ||||
| 	key := make([]byte, 32) | ||||
| 	if _, err := io.ReadFull(rand.Reader, key); err != nil { | ||||
| 		return "", fmt.Errorf("failed to generate key: %w", err) | ||||
| 	} | ||||
| 	return base64.StdEncoding.EncodeToString(key), nil | ||||
| } | ||||
|  | ||||
| // QuickEncrypt 快速加密函数(使用默认密钥) | ||||
| // plaintext: 要加密的明文 | ||||
| // key: 加密密钥 | ||||
| // 返回: base64编码的密文和错误信息 | ||||
| func QuickEncrypt(plaintext, key string) (string, error) { | ||||
| 	crypto := NewCryptoHelper(key) | ||||
| 	return crypto.Encrypt(plaintext) | ||||
| } | ||||
|  | ||||
| // QuickDecrypt2 快速解密函数(使用默认密钥) | ||||
| // ciphertext: base64编码的密文 | ||||
| // 返回: 解密后的明文和错误信息 | ||||
| func QuickDecrypt2(ciphertext string) (string, error) { | ||||
| 	crypto := NewCryptoHelper(CryptoKey) | ||||
| 	return crypto.Decrypt(ciphertext) | ||||
| } | ||||
|  | ||||
| // QuickEncrypt2 快速加密函数(使用默认密钥) | ||||
| // plaintext: 要加密的明文 | ||||
| // 返回: base64编码的密文和错误信息 | ||||
| func QuickEncrypt2(plaintext string) (string, error) { | ||||
| 	crypto := NewCryptoHelper(CryptoKey) | ||||
| 	return crypto.Encrypt(plaintext) | ||||
| } | ||||
|  | ||||
| // QuickDecrypt 快速解密函数(使用默认密钥) | ||||
| // ciphertext: base64编码的密文 | ||||
| // key: 解密密钥 | ||||
| // 返回: 解密后的明文和错误信息 | ||||
| func QuickDecrypt(ciphertext, key string) (string, error) { | ||||
| 	crypto := NewCryptoHelper(key) | ||||
| 	return crypto.Decrypt(ciphertext) | ||||
| } | ||||
							
								
								
									
										371
									
								
								utils/utility/generic_queue.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								utils/utility/generic_queue.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,371 @@ | ||||
| package utility | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // GenericQueue 通用循环队列 | ||||
| // 支持存储任意类型的数据,实现负载均衡和容错 | ||||
| type GenericQueue[T any] struct { | ||||
| 	data     []T               // 存储数据的数组 | ||||
| 	current  int               // 当前指针位置 | ||||
| 	size     int               // 队列中的元素数量 | ||||
| 	capacity int               // 队列容量 | ||||
| 	mutex    sync.RWMutex      // 读写锁,保证线程安全 | ||||
| 	lastUsed map[int]time.Time // 记录每个位置的最后使用时间 | ||||
| 	cooldown time.Duration     // 冷却时间,避免频繁使用同一个元素 | ||||
| 	comparer func(T, T) bool   // 比较函数,用于检查重复元素 | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	// QuequeMap 全局队列映射表,用于管理多个命名队列 | ||||
| 	// 使用interface{}类型以支持不同泛型类型的队列 | ||||
| 	QuequeMap = make(map[string]interface{}) | ||||
| ) | ||||
|  | ||||
| // NewGenericQueue 创建新的通用循环队列 | ||||
| // capacity: 队列容量 | ||||
| // comparer: 比较函数,用于检查重复元素(可选) | ||||
| // cooldown: 元素使用冷却时间(可选,默认0表示无冷却) | ||||
| func NewGenericQueue[T any](capacity int, comparer func(T, T) bool, cooldown ...time.Duration) *GenericQueue[T] { | ||||
| 	cd := time.Duration(0) | ||||
| 	if len(cooldown) > 0 { | ||||
| 		cd = cooldown[0] | ||||
| 	} | ||||
|  | ||||
| 	return &GenericQueue[T]{ | ||||
| 		data:     make([]T, capacity), | ||||
| 		capacity: capacity, | ||||
| 		lastUsed: make(map[int]time.Time), | ||||
| 		cooldown: cd, | ||||
| 		comparer: comparer, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Add 添加元素到队列 | ||||
| // item: 要添加的元素 | ||||
| // 返回: 是否添加成功 | ||||
| func (q *GenericQueue[T]) Add(item T) bool { | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
|  | ||||
| 	if q.size >= q.capacity { | ||||
| 		return false // 队列已满 | ||||
| 	} | ||||
|  | ||||
| 	// 如果提供了比较函数,检查是否已存在相同的元素 | ||||
| 	if q.comparer != nil { | ||||
| 		for i := 0; i < q.size; i++ { | ||||
| 			if q.comparer(q.data[i], item) { | ||||
| 				return false // 已存在 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	q.data[q.size] = item | ||||
| 	q.size++ | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Remove 从队列中移除指定的元素 | ||||
| // item: 要移除的元素 | ||||
| // 返回: 是否移除成功 | ||||
| func (q *GenericQueue[T]) Remove(item T) bool { | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
|  | ||||
| 	if q.comparer == nil { | ||||
| 		return false // 没有比较函数无法移除 | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < q.size; i++ { | ||||
| 		if q.comparer(q.data[i], item) { | ||||
| 			// 将后面的元素前移 | ||||
| 			for j := i; j < q.size-1; j++ { | ||||
| 				q.data[j] = q.data[j+1] | ||||
| 			} | ||||
| 			q.size-- | ||||
|  | ||||
| 			// 调整current指针 | ||||
| 			if q.current >= q.size && q.size > 0 { | ||||
| 				q.current = 0 | ||||
| 			} | ||||
|  | ||||
| 			// 清理lastUsed记录 | ||||
| 			delete(q.lastUsed, i) | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // GetNext 获取下一个可用的元素(轮询方式) | ||||
| // 返回: 元素和是否获取成功 | ||||
| func (q *GenericQueue[T]) GetNext() (T, bool) { | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
|  | ||||
| 	var zero T | ||||
| 	if q.size == 0 { | ||||
| 		return zero, false // 队列为空 | ||||
| 	} | ||||
|  | ||||
| 	// 如果没有设置冷却时间,直接返回下一个元素 | ||||
| 	if q.cooldown == 0 { | ||||
| 		item := q.data[q.current] | ||||
| 		q.lastUsed[q.current] = time.Now() | ||||
| 		q.current = (q.current + 1) % q.size | ||||
| 		return item, true | ||||
| 	} | ||||
|  | ||||
| 	// 寻找可用的元素(考虑冷却时间) | ||||
| 	startPos := q.current | ||||
| 	for { | ||||
| 		lastUsed, exists := q.lastUsed[q.current] | ||||
|  | ||||
| 		// 如果元素从未使用过,或者已过冷却时间 | ||||
| 		if !exists || time.Since(lastUsed) >= q.cooldown { | ||||
| 			item := q.data[q.current] | ||||
| 			q.lastUsed[q.current] = time.Now() | ||||
| 			q.current = (q.current + 1) % q.size | ||||
| 			return item, true | ||||
| 		} | ||||
|  | ||||
| 		q.current = (q.current + 1) % q.size | ||||
|  | ||||
| 		// 如果遍历了一圈都没找到可用的元素 | ||||
| 		if q.current == startPos { | ||||
| 			// 返回当前元素,忽略冷却时间 | ||||
| 			item := q.data[q.current] | ||||
| 			q.lastUsed[q.current] = time.Now() | ||||
| 			q.current = (q.current + 1) % q.size | ||||
| 			return item, true | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetRandom 随机获取一个元素 | ||||
| // 返回: 元素和是否获取成功 | ||||
| func (q *GenericQueue[T]) GetRandom() (T, bool) { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
|  | ||||
| 	var zero T | ||||
| 	if q.size == 0 { | ||||
| 		return zero, false | ||||
| 	} | ||||
|  | ||||
| 	// 使用当前时间作为随机种子 | ||||
| 	index := int(time.Now().UnixNano()) % q.size | ||||
| 	item := q.data[index] | ||||
| 	q.lastUsed[index] = time.Now() | ||||
| 	return item, true | ||||
| } | ||||
|  | ||||
| // GetAll 获取所有元素的副本 | ||||
| // 返回: 元素切片 | ||||
| func (q *GenericQueue[T]) GetAll() []T { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
|  | ||||
| 	items := make([]T, q.size) | ||||
| 	copy(items, q.data[:q.size]) | ||||
| 	return items | ||||
| } | ||||
|  | ||||
| // Size 获取队列中的元素数量 | ||||
| // 返回: 元素数量 | ||||
| func (q *GenericQueue[T]) Size() int { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
| 	return q.size | ||||
| } | ||||
|  | ||||
| // IsEmpty 检查队列是否为空 | ||||
| // 返回: 是否为空 | ||||
| func (q *GenericQueue[T]) IsEmpty() bool { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
| 	return q.size == 0 | ||||
| } | ||||
|  | ||||
| // IsFull 检查队列是否已满 | ||||
| // 返回: 是否已满 | ||||
| func (q *GenericQueue[T]) IsFull() bool { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
| 	return q.size >= q.capacity | ||||
| } | ||||
|  | ||||
| // Clear 清空队列 | ||||
| func (q *GenericQueue[T]) Clear() { | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
|  | ||||
| 	q.size = 0 | ||||
| 	q.current = 0 | ||||
| 	q.lastUsed = make(map[int]time.Time) | ||||
| } | ||||
|  | ||||
| // SetCooldown 设置元素使用冷却时间 | ||||
| // cooldown: 冷却时间 | ||||
| func (q *GenericQueue[T]) SetCooldown(cooldown time.Duration) { | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
| 	q.cooldown = cooldown | ||||
| } | ||||
|  | ||||
| // GetUsageInfo 获取元素使用信息 | ||||
| // 返回: 位置使用时间映射 | ||||
| func (q *GenericQueue[T]) GetUsageInfo() map[int]time.Time { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
|  | ||||
| 	usage := make(map[int]time.Time) | ||||
| 	for k, v := range q.lastUsed { | ||||
| 		usage[k] = v | ||||
| 	} | ||||
| 	return usage | ||||
| } | ||||
|  | ||||
| // BatchAdd 批量添加元素 | ||||
| // items: 要添加的元素切片 | ||||
| // 返回: 成功添加的数量 | ||||
| func (q *GenericQueue[T]) BatchAdd(items []T) int { | ||||
| 	count := 0 | ||||
| 	for _, item := range items { | ||||
| 		if q.Add(item) { | ||||
| 			count++ | ||||
| 		} | ||||
| 	} | ||||
| 	return count | ||||
| } | ||||
|  | ||||
| // Replace 替换所有元素 | ||||
| // items: 新的元素切片 | ||||
| // 返回: 是否替换成功 | ||||
| func (q *GenericQueue[T]) Replace(items []T) bool { | ||||
| 	if len(items) > q.capacity { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
|  | ||||
| 	q.size = 0 | ||||
| 	q.current = 0 | ||||
| 	q.lastUsed = make(map[int]time.Time) | ||||
|  | ||||
| 	for _, item := range items { | ||||
| 		q.data[q.size] = item | ||||
| 		q.size++ | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // ReplaceItem 替换指定的单个元素 | ||||
| // oldItem: 要被替换的元素 | ||||
| // newItem: 新的元素 | ||||
| // 返回: 是否替换成功 | ||||
| func (q *GenericQueue[T]) ReplaceItem(oldItem, newItem T) bool { | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
|  | ||||
| 	if q.comparer == nil { | ||||
| 		return false // 没有比较函数无法查找元素 | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < q.size; i++ { | ||||
| 		if q.comparer(q.data[i], oldItem) { | ||||
| 			q.data[i] = newItem | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false // 未找到要替换的元素 | ||||
| } | ||||
|  | ||||
| // Enqueue 入队操作(队列尾部添加元素) | ||||
| // item: 要添加的元素 | ||||
| // 返回: 是否添加成功 | ||||
| func (q *GenericQueue[T]) Enqueue(item T) bool { | ||||
| 	return q.Add(item) | ||||
| } | ||||
|  | ||||
| // Dequeue 出队操作(从队列头部移除并返回元素) | ||||
| // 返回: 元素和是否成功 | ||||
| func (q *GenericQueue[T]) Dequeue() (T, bool) { | ||||
| 	q.mutex.Lock() | ||||
| 	defer q.mutex.Unlock() | ||||
|  | ||||
| 	var zero T | ||||
| 	if q.size == 0 { | ||||
| 		return zero, false // 队列为空 | ||||
| 	} | ||||
|  | ||||
| 	// 获取队列头部元素 | ||||
| 	item := q.data[0] | ||||
|  | ||||
| 	// 将后面的元素前移 | ||||
| 	for i := 0; i < q.size-1; i++ { | ||||
| 		q.data[i] = q.data[i+1] | ||||
| 	} | ||||
| 	q.size-- | ||||
|  | ||||
| 	// 调整current指针 | ||||
| 	if q.current > 0 { | ||||
| 		q.current-- | ||||
| 	} | ||||
| 	if q.current >= q.size && q.size > 0 { | ||||
| 		q.current = 0 | ||||
| 	} | ||||
|  | ||||
| 	// 重新映射lastUsed(因为索引发生了变化) | ||||
| 	newLastUsed := make(map[int]time.Time) | ||||
| 	for index, lastTime := range q.lastUsed { | ||||
| 		if index > 0 { | ||||
| 			newLastUsed[index-1] = lastTime | ||||
| 		} | ||||
| 	} | ||||
| 	q.lastUsed = newLastUsed | ||||
|  | ||||
| 	return item, true | ||||
| } | ||||
|  | ||||
| // Peek 查看队列头部元素(不移除) | ||||
| // 返回: 元素和是否成功 | ||||
| func (q *GenericQueue[T]) Peek() (T, bool) { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
|  | ||||
| 	var zero T | ||||
| 	if q.size == 0 { | ||||
| 		return zero, false // 队列为空 | ||||
| 	} | ||||
|  | ||||
| 	return q.data[0], true | ||||
| } | ||||
|  | ||||
| // PeekLast 查看队列尾部元素(不移除) | ||||
| // 返回: 元素和是否成功 | ||||
| func (q *GenericQueue[T]) PeekLast() (T, bool) { | ||||
| 	q.mutex.RLock() | ||||
| 	defer q.mutex.RUnlock() | ||||
|  | ||||
| 	var zero T | ||||
| 	if q.size == 0 { | ||||
| 		return zero, false // 队列为空 | ||||
| 	} | ||||
|  | ||||
| 	return q.data[q.size-1], true | ||||
| } | ||||
|  | ||||
| // ApiKeyInfo API密钥信息结构体 | ||||
| type ApiKeyInfo struct { | ||||
| 	Key      string            `json:"key"`      // API密钥 | ||||
| 	Name     string            `json:"name"`     // 密钥名称 | ||||
| 	Weight   int               `json:"weight"`   // 权重 | ||||
| 	Enabled  bool              `json:"enabled"`  // 是否启用 | ||||
| 	Metadata map[string]string `json:"metadata"` // 元数据 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user