1、平台多ApiKey支持

2、平台号码跟系统号码对比自动续费
This commit is contained in:
2025-09-11 20:01:00 +08:00
parent baa6552994
commit cbefd85f25
29 changed files with 2762 additions and 180 deletions

View 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, "同步成功")
}

View 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(), "删除成功")
}

View 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
}

View File

@ -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-长效"`

View 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
}

View 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) //同步差异
}
}

View 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)
}
}

View 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
}

View 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"`
}

View File

@ -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:"是否自动续期"`
}
// 长效详情

View 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
}

View 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)
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -0,0 +1 @@
package service

View 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)
}

View 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
}

View 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)
}

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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
}

View 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
}

View File

@ -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 {

View File

@ -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()
}

Binary file not shown.

View File

@ -1,6 +1,6 @@
package global
const (
// TextVerified Token
TextVerifiedToken = "TextVerifiedToken"
// TextVerified Token {apiKey}
TextVerifiedToken = "TextVerifiedToken:%s"
)

View File

@ -0,0 +1,6 @@
package rediskey
const (
//平台key
SmsPlatformKey = "sms_platform_key:%s"
)

View 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)
}

View 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"` // 元数据
}