Compare commits
	
		
			2 Commits
		
	
	
		
			6c95e6fb04
			...
			f73639a96c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f73639a96c | |||
| 82d5acc873 | 
| @ -13,6 +13,7 @@ import ( | ||||
| 	"go-admin/app/admin/service" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/actions" | ||||
| 	"go-admin/common/middleware" | ||||
| 	"go-admin/common/statuscode" | ||||
| ) | ||||
|  | ||||
| @ -194,7 +195,16 @@ func (e SmsPhone) Delete(c *gin.Context) { | ||||
| 	e.OK(req.GetId(), "删除成功") | ||||
| } | ||||
|  | ||||
| // 租赁号码 | ||||
| // GetNumber | ||||
| // @Summary 获取号码 | ||||
| // @Description 获取号码 | ||||
| // @Tags 短信手机号 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.GetNumberReq true "data" | ||||
| // @Success 200 {object} response.Response{data=decimal.Decimal} "获取号码" | ||||
| // @Router /api/v1/sms-phone/get-number [post] | ||||
| // @Security Bearer | ||||
| func (e SmsPhone) GetNumber(c *gin.Context) { | ||||
| 	req := dto.GetNumberReq{} | ||||
| 	s := service.SmsPhone{} | ||||
| @ -211,36 +221,38 @@ func (e SmsPhone) GetNumber(c *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := req.Validate(); err != nil { | ||||
| 		e.Logger.Errorf("租赁号码失败,%s", err.Error()) | ||||
| 	if err1 := req.Validate(); err1 != nil { | ||||
| 		e.Logger.Errorf("租赁号码失败,%s", err1.Error()) | ||||
| 		e.Error(500, err, "服务器错误请联系管理员") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var balance decimal.Decimal | ||||
| 	code := statuscode.Success | ||||
| 	lang := "zh" | ||||
| 	userId := user.GetUserId(c) | ||||
| 	smsService, err := servicesService.GetByCode(req.ServiceCode) | ||||
| 	smsService, err := servicesService.GetByCode(req.PlatformCode, req.ServiceCode) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		code = statuscode.SmsServiceUnavailable | ||||
| 	} else if smsService.Status == 2 { | ||||
| 		code = statuscode.SmsServiceUnavailable | ||||
| 	} else { | ||||
| 	} | ||||
|  | ||||
| 	var balance decimal.Decimal | ||||
| 	if code == statuscode.Success { | ||||
| 		balance, code = s.GetNumber(&req, userId) | ||||
| 	} | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		if code == statuscode.SmsServiceUnavailable { | ||||
| 			e.Error(code, nil, statuscode.GetMsg(code, "zh", smsService.Name)) | ||||
| 			e.Error(code, nil, statuscode.GetMsg(code, lang, smsService.Name)) | ||||
| 		} else { | ||||
| 			e.Error(code, nil, statuscode.GetMsg(code, "zh")) | ||||
| 			e.Error(code, nil, statuscode.GetMsg(code, lang)) | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(balance, "租赁号码成功") | ||||
| 	e.OK(balance, "获取号码成功") | ||||
| } | ||||
|  | ||||
| // 获取验证码 | ||||
| @ -327,7 +339,7 @@ func (e SmsPhone) WeakUp(c *gin.Context) { | ||||
| 	} | ||||
|  | ||||
| 	userId := user.GetUserId(c) | ||||
| 	code := s.WeakUp(&req, userId) | ||||
| 	_, code := s.WeakUp(&req, userId, false) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Error(code, nil, statuscode.GetMsg(code, "zh")) | ||||
| @ -449,3 +461,289 @@ func (e SmsPhone) CleanMyPhone(c *gin.Context) { | ||||
|  | ||||
| 	e.OK(nil, "清理成功") | ||||
| } | ||||
|  | ||||
| // OpenGetNumber | ||||
| // @Summary 开放接口-获取号码 | ||||
| // @Description 开放接口-获取号码 | ||||
| // @Tags 短信手机号 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.GetNumberReq true "data" | ||||
| // @Success 200 {object} response.Response{data=decimal.Decimal} "获取号码" | ||||
| // @Router /api/v1/open/sms-phone/get-number [post] | ||||
| func (e SmsPhone) OpenGetNumber(c *gin.Context) { | ||||
| 	req := dto.GetNumberReq{} | ||||
| 	s := service.SmsPhone{} | ||||
| 	servicesService := service.SmsServices{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		Bind(&req). | ||||
| 		MakeService(&s.Service). | ||||
| 		MakeService(&servicesService.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err1 := req.Validate(); err1 != nil { | ||||
| 		e.Logger.Errorf("租赁号码失败,%s", err1.Error()) | ||||
| 		e.Error(500, err, "服务器错误请联系管理员") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	lang := "zh" | ||||
| 	code := statuscode.Success | ||||
| 	userId, err := middleware.GetUserIdByApiKey(c) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		var errCode int | ||||
|  | ||||
| 		switch err { | ||||
| 		case middleware.ErrApiUnActived: | ||||
| 			errCode = statuscode.ApiUnActived | ||||
| 		case middleware.ErrNoAccount: | ||||
| 			errCode = statuscode.AccountNotFound | ||||
| 		default: | ||||
| 			e.Logger.Errorf("获取API用户id失败 %v", err) | ||||
| 		} | ||||
|  | ||||
| 		e.Error(errCode, nil, statuscode.GetMsg(errCode, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	smsService, err := servicesService.GetByCode(req.PlatformCode, req.ServiceCode) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		code = statuscode.SmsServiceUnavailable | ||||
| 	} else if smsService.Status == 2 { | ||||
| 		code = statuscode.SmsServiceUnavailable | ||||
| 	} | ||||
|  | ||||
| 	var resp dto.OpenGetNumberResp | ||||
| 	if code == statuscode.Success { | ||||
| 		resp, code = s.OpenGetNumber(&req, userId) | ||||
| 	} | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		if code == statuscode.SmsServiceUnavailable { | ||||
| 			e.Error(code, nil, statuscode.GetMsg(code, lang, smsService.Name)) | ||||
| 		} else { | ||||
| 			e.Error(code, nil, statuscode.GetMsg(code, lang)) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(resp, "获取号码成功") | ||||
| } | ||||
|  | ||||
| // OpenGetCodeByActivationId 开放api-获取验证码 | ||||
| // @Summary 开放接口-获取验证码 | ||||
| // @Description 开放接口-获取验证码 | ||||
| // @Tags 短信号码 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.GetCodeReq true "body" | ||||
| // @Success 200 {object} response.Response{data=dto.GetCodeResp} "成功" | ||||
| // @Router /api/v1/open/sms-phone/get-code [post] | ||||
| func (e SmsPhone) OpenGetCodeByActivationId(c *gin.Context) { | ||||
| 	req := dto.GetCodeReq{} | ||||
| 	s := service.SmsPhone{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		Bind(&req). | ||||
| 		MakeService(&s.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	lang := "zh" | ||||
| 	userId, err := middleware.GetUserIdByApiKey(c) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		var errCode int | ||||
|  | ||||
| 		switch err { | ||||
| 		case middleware.ErrApiUnActived: | ||||
| 			errCode = statuscode.ApiUnActived | ||||
| 		case middleware.ErrNoAccount: | ||||
| 			errCode = statuscode.AccountNotFound | ||||
| 		default: | ||||
| 			e.Logger.Errorf("获取API用户id失败 %v", err) | ||||
| 		} | ||||
|  | ||||
| 		e.Error(errCode, nil, statuscode.GetMsg(errCode, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	codeData, code := s.GetCodeByActivationId(&req, userId) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Error(code, nil, statuscode.GetMsg(code, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(codeData, "获取验证码成功") | ||||
| } | ||||
|  | ||||
| // OpenCancel 开放api-取消号码 | ||||
| // @Summary 开放接口-取消号码 | ||||
| // @Description 开放接口-取消号码 | ||||
| // @Tags 短信号码 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.SmsPhoneCancelNumberReq true "body" | ||||
| // @Success 200 {object} response.Response "成功" | ||||
| // @Router /api/v1/open/sms-phone/cancel [post] | ||||
| func (e SmsPhone) OpenCancel(c *gin.Context) { | ||||
| 	req := dto.SmsPhoneCancelNumberReq{} | ||||
| 	s := service.SmsPhone{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		Bind(&req). | ||||
| 		MakeService(&s.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	lang := "zh" | ||||
| 	userId, err := middleware.GetUserIdByApiKey(c) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		var errCode int | ||||
|  | ||||
| 		switch err { | ||||
| 		case middleware.ErrApiUnActived: | ||||
| 			errCode = statuscode.ApiUnActived | ||||
| 		case middleware.ErrNoAccount: | ||||
| 			errCode = statuscode.AccountNotFound | ||||
| 		default: | ||||
| 			e.Logger.Errorf("获取API用户id失败 %v", err) | ||||
| 		} | ||||
|  | ||||
| 		e.Error(errCode, nil, statuscode.GetMsg(errCode, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	code := s.CancelNumber(&req, userId) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Error(code, nil, statuscode.GetMsg(code, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(nil, "取消号码成功") | ||||
| } | ||||
|  | ||||
| // OpenAutoRenewal 开放api-修改续费状态 | ||||
| // @Summary 开放接口-修改自动续费状态 | ||||
| // @Description 开放接口-修改自动续费状态 | ||||
| // @Tags 短信号码 | ||||
| // @Accept application/json | ||||
| // @Product application/json | ||||
| // @Param data body dto.SmsPhoneChangeAutoRenewReq true "body" | ||||
| // @Success 200 {object} response.Response "成功" | ||||
| // @Router /api/v1/open/sms-phone/auto-renewal [post] | ||||
| func (e SmsPhone) OpenAutoRenewal(c *gin.Context) { | ||||
| 	req := dto.SmsPhoneChangeAutoRenewReq{} | ||||
| 	s := service.SmsPhone{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		Bind(&req). | ||||
| 		MakeService(&s.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	lang := "zh" | ||||
| 	userId, err := middleware.GetUserIdByApiKey(c) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		var errCode int | ||||
|  | ||||
| 		switch err { | ||||
| 		case middleware.ErrApiUnActived: | ||||
| 			errCode = statuscode.ApiUnActived | ||||
| 		case middleware.ErrNoAccount: | ||||
| 			errCode = statuscode.AccountNotFound | ||||
| 		default: | ||||
| 			e.Logger.Errorf("获取API用户id失败 %v", err) | ||||
| 		} | ||||
|  | ||||
| 		e.Error(errCode, nil, statuscode.GetMsg(errCode, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	code := s.ChangeAutoRenew(&req, userId) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Error(code, nil, statuscode.GetMsg(code, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(nil, "修改自动续费状态成功") | ||||
| } | ||||
|  | ||||
| // OpenApi 获取服务列表 | ||||
| func (e SmsPhone) OpenGetServices(c *gin.Context) { | ||||
| 	s := service.SmsServices{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		MakeService(&s.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := []dto.SmsServicesGetListResp{} | ||||
| 	err = s.GetList(&resp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, statuscode.GetMsg(statuscode.ServerError, "en")) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(resp, "success") | ||||
| } | ||||
|  | ||||
| // 开放接口-唤醒号码 | ||||
| func (e SmsPhone) OpenWeakUp(c *gin.Context) { | ||||
| 	req := dto.WeakUpReq{} | ||||
| 	s := service.SmsPhone{} | ||||
|  | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		Bind(&req). | ||||
| 		MakeService(&s.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	lang := "en" | ||||
| 	userId, err := middleware.GetUserIdByApiKey(c) | ||||
|  | ||||
| 	resp, code := s.WeakUp(&req, userId, false) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Error(code, nil, statuscode.GetMsg(code, lang)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(resp, "success") | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,14 @@ | ||||
| package apis | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/hmac" | ||||
| 	"crypto/sha512" | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/api" | ||||
| @ -193,7 +200,7 @@ func (e SmsReceiveLog) Delete(c *gin.Context) { | ||||
| 	e.OK(req.GetId(), "删除成功") | ||||
| } | ||||
|  | ||||
| // WebHook 接收短信发送记录回调 | ||||
| // daisysms WebHook 接收短信发送记录回调 | ||||
| func (e SmsReceiveLog) WebHook(c *gin.Context) { | ||||
| 	req := dto.SmsReceiveWebHookReq{} | ||||
| 	s := service.SmsReceiveLog{} | ||||
| @ -217,3 +224,63 @@ func (e SmsReceiveLog) WebHook(c *gin.Context) { | ||||
|  | ||||
| 	e.OK(nil, "接收短信发送记录回调成功") | ||||
| } | ||||
|  | ||||
| // textVerified WebHook 接收短信发送记录回调 | ||||
| func (e SmsReceiveLog) TextVerifiedWebHook(c *gin.Context) { | ||||
| 	// 1. 提前读取原始 body | ||||
| 	bodyBytes, err := io.ReadAll(c.Request.Body) | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error("Failed to read body:", err) | ||||
| 		e.Error(500, err, "Failed to read request body") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 2. 验签 | ||||
| 	signatureHeader := c.GetHeader("X-Webhook-Signature") | ||||
| 	if !verifyWebhookSignature(signatureHeader, bodyBytes, "whsec_UMjFrzkK4YrlOb0AyOFDZAJJ7VWChOVOhYAGKE0e5oKw") { | ||||
| 		e.Logger.Warn("Invalid webhook signature") | ||||
| 		e.Error(401, fmt.Errorf("invalid signature"), "Invalid signature") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 3. 重置 body 给 Bind 用 | ||||
| 	c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) | ||||
|  | ||||
| 	req := dto.TextVerifiedWebHookReq{} | ||||
| 	s := service.SmsTextVerified{} | ||||
|  | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	err = s.TextVerifiedWebHook(&req) | ||||
| 	if err != nil { | ||||
| 		c.JSON(500, gin.H{}) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c.Status(http.StatusOK) | ||||
| } | ||||
|  | ||||
| // 验签方法 | ||||
| func verifyWebhookSignature(header string, body []byte, secret string) bool { | ||||
| 	const prefix = "HMAC-SHA512=" | ||||
| 	if !strings.HasPrefix(header, prefix) { | ||||
| 		return false | ||||
| 	} | ||||
| 	sigBase64 := strings.TrimPrefix(header, prefix) | ||||
|  | ||||
| 	mac := hmac.New(sha512.New, []byte(secret)) | ||||
| 	mac.Write(body) | ||||
| 	expectedMAC := mac.Sum(nil) | ||||
| 	expectedBase64 := base64.StdEncoding.EncodeToString(expectedMAC) | ||||
|  | ||||
| 	return hmac.Equal([]byte(sigBase64), []byte(expectedBase64)) | ||||
| } | ||||
|  | ||||
| @ -226,6 +226,12 @@ func (e SmsServices) GetPrice(c *gin.Context) { | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if req.Valid() != nil { | ||||
| 		e.Error(500, req.Valid(), req.Valid().Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	price, err := s.GetPrice(&req) | ||||
| 	if err != nil { | ||||
| 		e.Logger.Errorf("获取价格失败,\r\n失败信息 %s", err.Error()) | ||||
| @ -234,3 +240,25 @@ func (e SmsServices) GetPrice(c *gin.Context) { | ||||
| 	} | ||||
| 	e.OK(price, "获取价格成功") | ||||
| } | ||||
|  | ||||
| // 获取翻译平台 | ||||
| func (e SmsServices) GetPlatform(c *gin.Context) { | ||||
| 	s := service.SmsServices{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		MakeService(&s.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	list := make([]dto.SmsPlatformGetListResp, 0) | ||||
| 	err = s.GetPlatform(&list) | ||||
| 	if err != nil { | ||||
| 		e.Logger.Errorf("获取翻译平台失败,\r\n失败信息 %s", err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	e.OK(list, "获取翻译平台成功") | ||||
| } | ||||
|  | ||||
| @ -492,3 +492,61 @@ func (e SysUser) GetInfo(c *gin.Context) { | ||||
| 	mp["code"] = 200 | ||||
| 	e.OK(mp, "") | ||||
| } | ||||
|  | ||||
| func (e SysUser) GetApiInfo(c *gin.Context) { | ||||
| 	s := service.MemberApi{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		MakeService(&s.Service). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	userId := user.GetUserId(c) | ||||
| 	memberData := dto.MemberApiResp{} | ||||
| 	err = s.GetByUserId(userId, &memberData) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(memberData, "") | ||||
| } | ||||
|  | ||||
| // 修改Api状态 | ||||
| func (e SysUser) ChangeApiStatus(c *gin.Context) { | ||||
| 	req := dto.MemberApiChangeStatusReq{} | ||||
| 	s := service.MemberApi{} | ||||
| 	err := e.MakeContext(c). | ||||
| 		MakeOrm(). | ||||
| 		MakeService(&s.Service). | ||||
| 		Bind(&req). | ||||
| 		Errors | ||||
| 	if err != nil { | ||||
| 		e.Logger.Error(err) | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err := req.Validate(); err != nil { | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	userId := user.GetUserId(c) | ||||
| 	err = s.ChangeStatus(req.Id, userId, req.Status) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Logger.Errorf("修改api状态失败") | ||||
|  | ||||
| 		e.Error(500, err, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	e.OK(nil, "") | ||||
| } | ||||
|  | ||||
							
								
								
									
										13
									
								
								app/admin/models/member_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/admin/models/member_api.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| package models | ||||
|  | ||||
| import "go-admin/common/models" | ||||
|  | ||||
| type MemberApi struct { | ||||
| 	models.Model | ||||
|  | ||||
| 	UserId int    `json:"userId" gorm:"type:bigint;comment:用户id"` | ||||
| 	ApiKey string `json:"api" gorm:"type:varchar(255);comment:api_key"` | ||||
| 	Status int    `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
| } | ||||
| @ -10,15 +10,16 @@ import ( | ||||
| type SmsPhone struct { | ||||
| 	models.Model | ||||
|  | ||||
| 	PlatformCode    string          `json:"platformCode" gorm:"type:varchar(20);comment:平台code"` | ||||
| 	UserId          int             `json:"userId" gorm:"type:bigint;comment:用户Id"` | ||||
| 	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-长效"` | ||||
| 	Period          int             `json:"period" gorm:"type:int;comment:时长(月)"` | ||||
| 	Phone           string          `json:"phone" gorm:"type:varchar(30);comment:号码"` | ||||
| 	ActivationId    int             `json:"activationId" gorm:"type:int;comment:激活码id"` | ||||
| 	NewActivationId int             `json:"newActivationId" gorm:"type:int;comment:新激活码id 每次获取验证码会刷新"` | ||||
| 	MessageId       int             `json:"messageId" gorm:"type:int;comment:短信模板id"` | ||||
| 	ActivationId    string          `json:"activationId" gorm:"type:varchar(50);comment:激活码id"` | ||||
| 	NewActivationId string          `json:"newActivationId" gorm:"type:varchar(50);comment:新激活码id 每次获取验证码会刷新"` | ||||
| 	MessageId       string          `json:"messageId" gorm:"type:varchar(50);comment:短信模板id"` | ||||
| 	Code            string          `json:"code" gorm:"type:varchar(10);comment:验证码"` | ||||
| 	Status          int             `json:"status" gorm:"type:tinyint;comment:状态 1-等待验证码 2-已获取"` | ||||
| 	ExpireTime      *time.Time      `json:"expireTime" gorm:"type:datetime;comment:过期时间"` | ||||
|  | ||||
| @ -2,15 +2,21 @@ package models | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/common/models" | ||||
|  | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| type SmsServices struct { | ||||
| 	models.Model | ||||
|  | ||||
| 	Name              string `json:"name" gorm:"type:varchar(255);comment:服务名称"` | ||||
| 	Code              string `json:"code" gorm:"type:varchar(50);comment:编码"` | ||||
| 	ExpirationMinutes int    `json:"expirationMinutes" gorm:"type:int;comment:过期时间(分钟)"` | ||||
| 	Status            int    `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | ||||
| 	PlatformCode      string          `json:"platformCode" gorm:"type:varchar(20);comment:平台编码"` | ||||
| 	Name              string          `json:"name" gorm:"type:varchar(255);comment:服务名称"` | ||||
| 	Code              string          `json:"code" gorm:"type:varchar(50);comment:编码"` | ||||
| 	ExpirationMinutes int             `json:"expirationMinutes" gorm:"type:int;comment:过期时间(分钟)"` | ||||
| 	Status            int             `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | ||||
| 	Price             decimal.Decimal `json:"price" gorm:"type:decimal(10,2);comment:价格"` | ||||
| 	LongPrice         decimal.Decimal `json:"longPrice" gorm:"type:decimal(10,2);comment:长号码价格"` | ||||
| 	Icon              string          `json:"icon" gorm:"type:varchar(255);comment:图标"` | ||||
| 	models.ModelTime | ||||
| 	models.ControlBy | ||||
| } | ||||
|  | ||||
| @ -29,6 +29,16 @@ func registerSmsPhoneRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddl | ||||
| 		r1.PUT("auto-renewal", api.ChangeAutoRenew)                //修改自动续费状态 | ||||
| 	} | ||||
|  | ||||
| 	r2 := v1.Group("/sms").Use(middleware.FrontedAuth) | ||||
| 	{ | ||||
| 		r2.POST("getNumber", api.OpenGetNumber)                        // 开放接口 租赁号码 | ||||
| 		r2.GET("getCodeByActivationId", api.OpenGetCodeByActivationId) //开放接口-获取验证码 | ||||
| 		r2.PUT("cancel", api.OpenCancel)                               //开放接口-取消号码 | ||||
| 		r2.PUT("auto-renewal", api.OpenAutoRenewal)                    //开放接口-修改自动续费 | ||||
| 		r2.GET("services", api.OpenGetServices)                        //开放接口-获取服务列表 | ||||
| 		r2.POST("weak-up", api.OpenWeakUp)                             //开放接口-唤醒号码 | ||||
| 	} | ||||
|  | ||||
| 	r := v1.Group("/sms-phone").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) | ||||
| 	{ | ||||
| 		r.GET("", actions.PermissionAction(), api.GetPage) | ||||
|  | ||||
| @ -27,6 +27,7 @@ func registerSmsReceiveLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWT | ||||
|  | ||||
| 	r1 := v1.Group("sms-receive-log") | ||||
| 	{ | ||||
| 		r1.POST("webhook", api.WebHook) //接收短信发送记录回调 | ||||
| 		r1.POST("webhook", api.WebHook)                           //接收短信发送记录回调 | ||||
| 		r1.POST("text-verified-webhook", api.TextVerifiedWebHook) //接收TextVerified短信发送记录回调 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -27,7 +27,8 @@ func registerSmsServicesRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMi | ||||
|  | ||||
| 	r1 := v1.Group("/sms-services").Use(authMiddleware.MiddlewareFunc()) | ||||
| 	{ | ||||
| 		r1.GET("list", api.GetList)   //获取服务列表 | ||||
| 		r1.GET("price", api.GetPrice) //获取服务价格 | ||||
| 		r1.GET("/platform", api.GetPlatform) //获取平台列表 | ||||
| 		r1.GET("/list", api.GetList)         //获取服务列表 | ||||
| 		r1.GET("/price", api.GetPrice)       //获取服务价格 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,12 @@ | ||||
| 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" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @ -35,5 +36,7 @@ func registerSysUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle | ||||
| 	v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()) | ||||
| 	{ | ||||
| 		v1auth.GET("/getinfo", api.GetInfo) | ||||
| 		v1auth.GET("/api-info", api.GetApiInfo) | ||||
| 		v1auth.PUT("/api-info", api.ChangeApiStatus) | ||||
| 	} | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -44,7 +44,7 @@ func (e *CliProxyService) GetTrafficInfo() ([]dto.CliProxyTraffics, error) { | ||||
| 		client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.CliproxyUrl, headers) | ||||
| 		api := "/json/traffic_coutry.json" | ||||
|  | ||||
| 		err := client.Get(api, nil, &resp) | ||||
| 		_, err := client.Get(api, nil, &resp) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
|  | ||||
							
								
								
									
										30
									
								
								app/admin/service/dto/member_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/admin/service/dto/member_api.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| package dto | ||||
|  | ||||
| import "errors" | ||||
|  | ||||
| type MemberApiInsertReq struct { | ||||
| 	UserId int `json:"userId" gorm:"type:bigint;comment:用户id"` | ||||
| } | ||||
|  | ||||
| type MemberApiResp struct { | ||||
| 	Id     int    `json:"id"` | ||||
| 	UserId int    `json:"userId" gorm:"type:bigint;comment:用户id"` | ||||
| 	ApiKey string `json:"api" gorm:"type:varchar(255);comment:apiKey"` | ||||
| 	Status int    `json:"status" gorm:"type:tinyint;comment:状态 1-启用 2-禁用"` | ||||
| } | ||||
|  | ||||
| type MemberApiChangeStatusReq struct { | ||||
| 	Id     int `json:"id"` | ||||
| 	Status int `json:"status" comment:"1-启用 2-禁用"` | ||||
| } | ||||
|  | ||||
| func (e *MemberApiChangeStatusReq) Validate() error { | ||||
| 	if e.Id <= 0 { | ||||
| 		return errors.New("id不能为空") | ||||
| 	} | ||||
| 	if e.Status != 1 && e.Status != 2 { | ||||
| 		return errors.New("状态不正确") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @ -5,11 +5,15 @@ import ( | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/dto" | ||||
| 	common "go-admin/common/models" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| type SmsPhoneGetPageReq struct { | ||||
| 	dto.Pagination `search:"-"` | ||||
| 	Service        string `form:"service"  search:"type:exact;column:service;table:sms_phone" comment:"sms 服务"` | ||||
| 	PlatformCode   string `form:"platformCode"  search:"type:exact;column:platform_code;table:sms_phone" comment:"平台code"` | ||||
| 	ServiceCode    string `form:"serviceCode"  search:"type:exact;column:service_code;table:sms_phone" comment:"服务code"` | ||||
| 	Type           int    `form:"type"  search:"-" comment:"类型 0-短效 1-长效"` | ||||
| 	SmsPhoneOrder | ||||
| @ -109,9 +113,10 @@ func (s *SmsPhoneDeleteReq) GetId() interface{} { | ||||
| } | ||||
|  | ||||
| type GetNumberReq struct { | ||||
| 	Type        int    `json:"type" form:"type" comment:"类型 0-短效 1-长效"` | ||||
| 	ServiceCode string `json:"serviceCode" form:"serviceCode" comment:"服务code"` | ||||
| 	Period      int    `json:"period" form:"period" comment:"时长(月)"` | ||||
| 	PlatformCode string `json:"platformCode" form:"platformCode" commment:"平台code"` | ||||
| 	Type         int    `json:"type" form:"type" comment:"类型 0-短效 1-长效"` | ||||
| 	ServiceCode  string `json:"serviceCode" form:"serviceCode" comment:"服务code"` | ||||
| 	Period       int    `json:"period" form:"period" comment:"时长(月)"` | ||||
| } | ||||
|  | ||||
| func (s *GetNumberReq) Validate() error { | ||||
| @ -131,21 +136,26 @@ func (s *GetNumberReq) Validate() error { | ||||
| } | ||||
|  | ||||
| type GetCodeReq struct { | ||||
| 	ActivationIds []int `form:"activationIds"` | ||||
| 	ActivationIds []string `form:"activationIds"` | ||||
| } | ||||
|  | ||||
| type GetCodeResp struct { | ||||
| 	ActivationId int    `json:"activationId" comment:"激活码id"` | ||||
| 	ActivationId string `json:"activationId" comment:"激活码id"` | ||||
| 	Code         string `json:"code" comment:"验证码"` | ||||
| 	Status       int    `json:"status" comment:"状态 1-等待验证码 2-已获取"` | ||||
| } | ||||
|  | ||||
| type WeakUpReq struct { | ||||
| 	ActivationId int `json:"activationId" comment:"激活码id"` | ||||
| 	ActivationId string `json:"activationId" comment:"激活码id"` | ||||
| } | ||||
|  | ||||
| type WeakUpResp struct { | ||||
| 	ActivationId string `json:"activationId" comment:"激活id"` | ||||
| 	MessageId    string `json:"messageId" commment:"消息id"` | ||||
| } | ||||
|  | ||||
| func (s *WeakUpReq) Validate() error { | ||||
| 	if s.ActivationId <= 0 { | ||||
| 	if s.ActivationId == "" { | ||||
| 		return errors.New("激活码id不能为空") | ||||
| 	} | ||||
|  | ||||
| @ -165,14 +175,42 @@ func (s *DeleteMyNumberReq) Validate() error { | ||||
| } | ||||
|  | ||||
| type SmsPhoneCancelNumberReq struct { | ||||
| 	Id int `json:"id" comment:"短信号码id"` | ||||
| 	Id           int    `json:"id" comment:"短信号码id"` | ||||
| 	ActivationId string `json:"activationId" comment:"短信id"` | ||||
| } | ||||
|  | ||||
| type SmsPhoneChangeAutoRenewReq struct { | ||||
| 	Id        int `json:"id" comment:"短信号码id"` | ||||
| 	AutoRenew int `json:"autoRenew" comment:"是否自动续费 1-不自动续费 2-自动续费"` | ||||
| 	Id           int    `json:"id" comment:"短信号码id"` | ||||
| 	ActivationId string `json:"activationId" comment:"激活码id id或activationId "` | ||||
| 	AutoRenew    int    `json:"autoRenew" comment:"是否自动续费 1-不自动续费 2-自动续费"` | ||||
| } | ||||
|  | ||||
| type SmsPhoneCleanMyPhoneReq struct { | ||||
| 	Type int `json:"type" comment:"类型 0-短效 1-长效"` | ||||
| } | ||||
|  | ||||
| type SmsPhoneGetPhoneResp struct { | ||||
| 	Phone string     `json:"phone"` | ||||
| 	EndAt *time.Time `json:"endTime"` | ||||
| 	Id    string     `json:"id"` | ||||
| } | ||||
|  | ||||
| type DaisysmsPriceResp struct { | ||||
| 	Key        string `json:"key"` | ||||
| 	Name       string `json:"name"` | ||||
| 	Count      int    `json:"count"` | ||||
| 	Cost       string `json:"cost" comment:"单价"` | ||||
| 	Repeatable bool   `json:"repeatable"` | ||||
| } | ||||
|  | ||||
| type OpenGetNumberResp struct { | ||||
| 	Blance       decimal.Decimal `json:"balance"` | ||||
| 	Number       string          `json:"number"` | ||||
| 	ActivationId string          `json:"activationId"` | ||||
| 	ExpireTime   *time.Time      `json:"expireTime"` | ||||
| } | ||||
|  | ||||
| type OpenWakeUpReq struct { | ||||
| 	PlatformCode string `json:"platformCode" comment:"短信平台"` | ||||
| 	ActivationId string `json:"activationId" comment:"平台id"` | ||||
| } | ||||
|  | ||||
| @ -1,13 +1,19 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/dto" | ||||
| 	common "go-admin/common/models" | ||||
|  | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| type SmsServicesGetPageReq struct { | ||||
| 	dto.Pagination `search:"-"` | ||||
| 	PlatformCode   string `form:"platformCode"  search:"type:exact;column:platform_code;table:sms_services" comment:"平台编码"` | ||||
| 	Name           string `form:"name"  search:"type:contains;column:name;table:sms_services" comment:"服务名称"` | ||||
| 	Status         int    `form:"status"  search:"-"` | ||||
| 	SmsServicesOrder | ||||
| } | ||||
|  | ||||
| @ -27,9 +33,14 @@ func (m *SmsServicesGetPageReq) GetNeedSearch() interface{} { | ||||
| } | ||||
|  | ||||
| type SmsServicesInsertReq struct { | ||||
| 	Id   int    `json:"-" comment:""` // | ||||
| 	Name string `json:"name" comment:"服务名称"` | ||||
| 	Code string `json:"code" comment:"编码"` | ||||
| 	Id                int             `json:"-" comment:""` // | ||||
| 	Name              string          `json:"name" comment:"服务名称"` | ||||
| 	Code              string          `json:"code" comment:"编码"` | ||||
| 	PlatformCode      string          `json:"platformCode" comment:"平台编码"` | ||||
| 	Status            int             `json:"status" comment:"状态 1-启用 2-禁"` | ||||
| 	Price             decimal.Decimal `json:"price" comment:"价格"` | ||||
| 	LongPrice         decimal.Decimal `json:"longPrice" comment:"长号码价格"` | ||||
| 	ExpirationMinutes int             `json:"expirationMinutes" comment:"过期时间(分钟)"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| @ -39,6 +50,11 @@ func (s *SmsServicesInsertReq) Generate(model *models.SmsServices) { | ||||
| 	} | ||||
| 	model.Name = s.Name | ||||
| 	model.Code = s.Code | ||||
| 	model.PlatformCode = s.PlatformCode | ||||
| 	model.Status = s.Status | ||||
| 	model.Price = s.Price | ||||
| 	model.LongPrice = s.LongPrice | ||||
| 	model.ExpirationMinutes = s.ExpirationMinutes | ||||
| 	model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 | ||||
| } | ||||
|  | ||||
| @ -47,9 +63,14 @@ func (s *SmsServicesInsertReq) GetId() interface{} { | ||||
| } | ||||
|  | ||||
| type SmsServicesUpdateReq struct { | ||||
| 	Id   int    `uri:"id" comment:""` // | ||||
| 	Name string `json:"name" comment:"服务名称"` | ||||
| 	Code string `json:"code" comment:"编码"` | ||||
| 	Id                int             `uri:"id" comment:""` // | ||||
| 	Name              string          `json:"name" comment:"服务名称"` | ||||
| 	Code              string          `json:"code" comment:"编码"` | ||||
| 	PlatformCode      string          `json:"platformCode" comment:"平台编码"` | ||||
| 	Status            int             `json:"status" comment:"状态 1-启用 2-禁"` | ||||
| 	Price             decimal.Decimal `json:"price" comment:"价格"` | ||||
| 	LongPrice         decimal.Decimal `json:"longPrice" comment:"长号码价格"` | ||||
| 	ExpirationMinutes int             `json:"expirationMinutes" comment:"过期时间(分钟)"` | ||||
| 	common.ControlBy | ||||
| } | ||||
|  | ||||
| @ -59,6 +80,11 @@ func (s *SmsServicesUpdateReq) Generate(model *models.SmsServices) { | ||||
| 	} | ||||
| 	model.Name = s.Name | ||||
| 	model.Code = s.Code | ||||
| 	model.PlatformCode = s.PlatformCode | ||||
| 	model.Status = s.Status | ||||
| 	model.Price = s.Price | ||||
| 	model.LongPrice = s.LongPrice | ||||
| 	model.ExpirationMinutes = s.ExpirationMinutes | ||||
| 	model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 | ||||
| } | ||||
|  | ||||
| @ -85,11 +111,38 @@ func (s *SmsServicesDeleteReq) GetId() interface{} { | ||||
| } | ||||
|  | ||||
| type SmsServicesGetListResp struct { | ||||
| 	Name   string `json:"name" comment:"服务名称"` | ||||
| 	Code   string `json:"code" comment:"编码"` | ||||
| 	Status int    `json:"status" comment:"状态"` | ||||
| 	PlatformCode string          `json:"platformCode" comment:"平台编码"` | ||||
| 	PlatformName string          `json:"platformName" comment:"平台名称"` | ||||
| 	Code         string          `json:"code" comment:"编码"` | ||||
| 	Name         string          `json:"name" comment:"服务名称"` | ||||
| 	Status       int             `json:"status" comment:"状态"` | ||||
| 	Price        decimal.Decimal `json:"price" comment:"价格"` | ||||
| 	LongPrice    decimal.Decimal `json:"longPrice" comment:"长号码价格"` | ||||
| } | ||||
|  | ||||
| type SmsGetPriceReq struct { | ||||
| 	Type int `json:"type" form:"type" comment:"类型"` | ||||
| 	PlatformCode string `json:"platformCode" form:"platformCode" comment:"平台code"` | ||||
| 	ServiceCode  string `json:"serviceCode" form:"serviceCode" comment:"服务code"` | ||||
| 	Type         int    `json:"type" form:"type" comment:"类型"` | ||||
| } | ||||
|  | ||||
| func (e *SmsGetPriceReq) Valid() error { | ||||
| 	if e.PlatformCode == "" { | ||||
| 		return errors.New("平台code不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if e.ServiceCode == "" { | ||||
| 		return errors.New("服务code不能为空") | ||||
| 	} | ||||
|  | ||||
| 	if e.Type < 0 || e.Type > 1 { | ||||
| 		return errors.New("类型错误") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type SmsPlatformGetListResp struct { | ||||
| 	Label string `json:"label" comment:"平台名称"` | ||||
| 	Value string `json:"value" comment:"平台code"` | ||||
| } | ||||
|  | ||||
							
								
								
									
										214
									
								
								app/admin/service/dto/text_verified.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								app/admin/service/dto/text_verified.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,214 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| // 登录相应 | ||||
| type TextVerifiedLoginResp struct { | ||||
| 	Token     string `json:"token"` | ||||
| 	ExpiresIn int    `json:"expiresIn"` | ||||
| 	ExpiresAt string `json:"expiresAt"` | ||||
| } | ||||
|  | ||||
| // 获取区号响应 | ||||
| type TextVerifiedAreaResp struct { | ||||
| 	AreaCode string `json:"areaCode"` | ||||
| 	State    string `json:"state"` | ||||
| } | ||||
|  | ||||
| // 服务列表 | ||||
| type TextVerifiedServeResp struct { | ||||
| 	ServiceName string `json:"serviceName"` | ||||
| 	Capability  string `json:"capability"` | ||||
| } | ||||
|  | ||||
| // 创建租赁 | ||||
| type TextVerifiedCreateRewalReq struct { | ||||
| 	AllowBackOrderReservations bool     `json:"allowBackOrderReservations" comment:"如果设置为 true,当请求的租赁商品缺货时,将创建租赁订单"` | ||||
| 	AlwaysOn                   bool     `json:"alwaysOn" comment:"如果设置为 true,则有库存时将分配一条不需要唤醒的线路"` | ||||
| 	AreaCodeSelectOption       []string `json:"areaCodeSelectOption" comment:"可选的区号数组"` | ||||
| 	Duration                   string   `json:"duration" comment:"枚举 oneDay┃threeDay┃sevenDay┃fourteenDay┃thirtyDay┃ninetyDay┃oneYear"` | ||||
| 	IsRenewable                bool     `json:"isRenewable" comment:"是否可再生"` | ||||
| 	NumberType                 string   `json:"numberType" comment:"枚举 mobile┃voip┃landline"` | ||||
| 	BillingCycleIdToAssignTo   string   `json:"billingCycleIdToAssignTo"` | ||||
| 	ServiceName                string   `json:"serviceName"` | ||||
| 	Capability                 string   `json:"capability" comment:"枚举 sms┃call┃smsAndCall"` | ||||
| } | ||||
|  | ||||
| // 回调请求 | ||||
| type TextVerifiedWebHookReq struct { | ||||
| 	Attempt    int                    `json:"attempt" comment:"尝试次数"` | ||||
| 	OccurredAt string                 `json:"occurredAt" comment:"发生时间"` | ||||
| 	Event      string                 `json:"event" comment:"事件类型 v2.rental.billingcycle.renewed | v2.rental.billingcycle.expired | v2.sms.received | v2.rental.backorder.fulfilled | v2.reservation.created"` | ||||
| 	Id         string                 `json:"id" comment:"唯一标识"` | ||||
| 	Data       map[string]interface{} `json:"data" comment:"数据"` | ||||
| } | ||||
|  | ||||
| // 续费成功回调请求 | ||||
| type TextVerifiedWebHookRenewedReq struct { | ||||
| 	BillingCycleId string `json:"billingCycleId" comment:"续费的计费周期ID"` | ||||
| } | ||||
|  | ||||
| // 计费周期过期回调请求 - 当您帐户的计费周期到期时触发。 | ||||
| type TextVerifiedWebHookBillingCycleExpiredReq struct { | ||||
| 	BillingCycleId string `json:"billingCycleId" comment:"过期的计费周期ID"` | ||||
| } | ||||
|  | ||||
| // 收到短信回调请求 - 当收到短信并将其分配到您的帐户时触发。 | ||||
| type TextVerifiedWebHookSmsReceivedReq struct { | ||||
| 	From          string `json:"from" comment:"发送号码"` | ||||
| 	To            string `json:"to" comment:"接收号码"` | ||||
| 	CreatedAt     string `json:"createdAt" comment:"创建时间"` | ||||
| 	SmsContent    string `json:"smsContent" comment:"短信内容"` | ||||
| 	ParsedCode    string `json:"parsedCode" comment:"解析后的验证码"` | ||||
| 	Encrypted     bool   `json:"encrypted" comment:"短信内容是否静态加密 "` | ||||
| 	ReservationId string `json:"reservationId" comment:"此短信被分配到的预订的 ID。"` | ||||
| } | ||||
|  | ||||
| // 订单预定完成时触发 | ||||
| type TextVerifiedWebHookFulfilledReq struct { | ||||
| 	BackOrderId string `json:"backOrderId" comment:"订单预定ID"` | ||||
| } | ||||
|  | ||||
| // 预定订单创建回调 | ||||
| type TextVerifiedWebHookReservationCreatedReq struct { | ||||
| 	Id   string `json:"id" comment:"订单ID"` | ||||
| 	Type string `json:"type" comment:"订单类型 verifications | rentals"` | ||||
| } | ||||
|  | ||||
| // 唤醒请求 | ||||
| type TextVerifiedWakeUpReq struct { | ||||
| 	RevervationId string `json:"reservationId"` | ||||
| } | ||||
|  | ||||
| // 唤醒响应 | ||||
| type TextVerifiedWakeUpResp struct { | ||||
| 	Id               string     `json:"id"` | ||||
| 	UsageWindowStart *time.Time `json:"usageWindowStart"` | ||||
| 	UsageWindowEnd   *time.Time `json:"usageWindowEnd"` | ||||
| 	IsScheduled      bool       `json:"isScheduled"` | ||||
| 	ReservationId    string     `json:"reservationId" comment:"预定id"` | ||||
| } | ||||
|  | ||||
| // 请求统一响应 | ||||
| type TextVerifiedResp struct { | ||||
| 	Method string `json:"method"` | ||||
| 	Href   string `json:"href"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedCreateRentalReservationDto struct { | ||||
| 	ID              string           `json:"id" comment:"号码id"` | ||||
| 	Link            TextVerifiedResp `json:"link"` | ||||
| 	ReservationType string           `json:"reservationType"` | ||||
| 	ServiceName     string           `json:"serviceName"` | ||||
| } | ||||
|  | ||||
| // 长效号码响应 | ||||
| type SaleResponse struct { | ||||
| 	CreatedAt             time.Time                                `json:"createdAt"` | ||||
| 	ID                    string                                   `json:"id" comment:"销售id"` | ||||
| 	BackOrderReservations []interface{}                            `json:"backOrderReservations"` // 由于数组为空,使用 interface{} | ||||
| 	Reservations          []TextVerifiedCreateRentalReservationDto `json:"reservations" comment:"号码信息"` | ||||
| 	State                 string                                   `json:"state"` | ||||
| 	Total                 float64                                  `json:"total"` | ||||
| 	UpdatedAt             time.Time                                `json:"updatedAt"` | ||||
| } | ||||
|  | ||||
| // 单次验证码创建后查询 | ||||
| type VerificationDTO struct { | ||||
| 	Number      string           `json:"number"` | ||||
| 	SMS         TextVerifiedResp `json:"sms"` | ||||
| 	Calls       TextVerifiedResp `json:"calls"` | ||||
| 	CreatedAt   time.Time        `json:"createdAt"` | ||||
| 	EndsAt      time.Time        `json:"endsAt"` | ||||
| 	ID          string           `json:"id"` | ||||
| 	Cancel      Action           `json:"cancel"` | ||||
| 	Reactivate  Action           `json:"reactivate"` | ||||
| 	Report      Action           `json:"report"` | ||||
| 	Reuse       ReuseInfo        `json:"reuse"` | ||||
| 	Sale        TextVerifiedResp `json:"sale"` | ||||
| 	ServiceName string           `json:"serviceName"` | ||||
| 	// State 表示验证或服务的当前状态。 | ||||
| 	// 状态是以下枚举值之一,代表了从创建到完成或取消的生命周期: | ||||
| 	// * verificationPending: 正在等待验证码。 | ||||
| 	// * verificationCompleted: 验证已成功完成。 | ||||
| 	// * verificationCanceled: 验证被取消。 | ||||
| 	// * verificationTimedOut: 验证因超时而失败。 | ||||
| 	// * verificationReported: 验证被举报。 | ||||
| 	// * verificationRefunded: 验证已退款。 | ||||
| 	// * verificationReused: 验证号码被重用。 | ||||
| 	// * verificationReactivated: 验证号码被重新激活。 | ||||
| 	// | ||||
| 	// 对于可续订服务: | ||||
| 	// * renewableActive: 服务活跃中。 | ||||
| 	// * renewableOverdue: 服务已逾期。 | ||||
| 	// * renewableExpired: 服务已过期。 | ||||
| 	// * renewableRefunded: 服务已退款。 | ||||
| 	// | ||||
| 	// 对于不可续订服务: | ||||
| 	// * nonrenewableActive: 服务活跃中。 | ||||
| 	// * nonrenewableExpired: 服务已过期。 | ||||
| 	// * 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"` | ||||
| } | ||||
|  | ||||
| // 长效详情 | ||||
| type VerificationRentalDetailResp struct { | ||||
| 	Number      string           `json:"number"` | ||||
| 	SMS         TextVerifiedResp `json:"sms"` | ||||
| 	Calls       TextVerifiedResp `json:"calls"` | ||||
| 	CreatedAt   time.Time        `json:"createdAt"` | ||||
| 	ID          string           `json:"id"` | ||||
| 	Sale        TextVerifiedResp `json:"sale"` | ||||
| 	SaleId      string           `json:"saleId"` | ||||
| 	ServiceName string           `json:"serviceName"` | ||||
| 	State       string           `json:"state"` | ||||
| 	AlwaysOn    bool             `json:"alwaysOn"` | ||||
| } | ||||
|  | ||||
| // 包含可执行标志 + 链接 | ||||
| type Action struct { | ||||
| 	CanCancel     bool             `json:"canCancel,omitempty"` | ||||
| 	CanReactivate bool             `json:"canReactivate,omitempty"` | ||||
| 	CanReport     bool             `json:"canReport,omitempty"` | ||||
| 	Link          TextVerifiedResp `json:"link"` | ||||
| } | ||||
|  | ||||
| // reuse 特殊结构 | ||||
| type ReuseInfo struct { | ||||
| 	Link          TextVerifiedResp `json:"link"` | ||||
| 	ReusableUntil *time.Time       `json:"reusableUntil"` // null -> 指针可为空 | ||||
| } | ||||
|  | ||||
| type TextVerifiedPriceReq struct { | ||||
| 	ServiceName string `json:"serviceName" comment:"服务code"` | ||||
| 	AreaCode    bool   `json:"areaCode" comment:"限制区域code"` | ||||
| 	NumberType  string `json:"numberType" comment:"mobile | voip | landline"` | ||||
| 	Capability  string `json:"capability" comment:"sms | mms | voice"` | ||||
| 	AlwaysOn    bool   `json:"alwaysOn"` | ||||
| 	IsRenewable bool   `json:"isRenewable" comment:"是否可再生"` | ||||
| 	Duration    string `json:"duration" comment:"oneDay┃threeDay┃sevenDay┃fourteenDay┃thirtyDay┃ninetyDay┃oneYear"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedPriceResp struct { | ||||
| 	Price       decimal.Decimal `json:"price"` | ||||
| 	ServiceName string          `json:"serviceName"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedSmsResp struct { | ||||
| 	Data []TextVerifiedSmsItemResp `json:"data"` | ||||
| } | ||||
|  | ||||
| type TextVerifiedSmsItemResp struct { | ||||
| 	Id         string `json:"id"` | ||||
| 	From       string `json:"from"` | ||||
| 	To         string `json:"to"` | ||||
| 	CreatedAt  string `json:"createdAt"` | ||||
| 	SmsContent string `json:"smsContent"` | ||||
| 	ParsedCode string `json:"parsedCode"` | ||||
| 	Encrypted  bool   `json:"encrypted"` | ||||
| } | ||||
							
								
								
									
										122
									
								
								app/admin/service/member_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								app/admin/service/member_api.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/rediskey" | ||||
| 	"go-admin/utils/redishelper" | ||||
| 	"go-admin/utils/utility" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/jinzhu/copier" | ||||
| ) | ||||
|  | ||||
| type MemberApi struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| func (e MemberApi) ChangeStatus(id, userId int, status int) error { | ||||
| 	var api models.MemberApi | ||||
|  | ||||
| 	if err := e.Orm.Model(&api).Where("id =? and user_id =?", id, userId).Update("status", status).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetByUserId 通过用户ID获取用户信息 | ||||
| func (e *MemberApi) GetByUserId(userId int, memberData *dto.MemberApiResp) error { | ||||
| 	var entity models.MemberApi | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.MemberApi{}).Where("user_id = ?", userId).First(&entity).Error; err != nil { | ||||
| 		e.Log.Errorf("通过用户ID获取用户信息失败, %s", err) | ||||
| 	} | ||||
|  | ||||
| 	copier.Copy(memberData, &entity) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 初始化用户API | ||||
| func (e *MemberApi) InitApis() error { | ||||
| 	var entitys []models.SysUser | ||||
| 	var memberApis []models.MemberApi | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SysUser{}). | ||||
| 		Joins("LEFT JOIN member_api ON member_api.user_id = sys_user.user_id"). | ||||
| 		Where("member_api.id is null").Find(&entitys).Error; err != nil { | ||||
| 		e.Log.Errorf("初始化 查询用户API失败, %s", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var data models.MemberApi | ||||
| 	for _, entity := range entitys { | ||||
| 		e.CreateApi(entity.UserId, &data, 0) | ||||
| 	} | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.MemberApi{}).Find(&memberApis).Error; err != nil { | ||||
| 		e.Log.Errorf("初始化 查询用户API失败, %s", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range memberApis { | ||||
| 		key := fmt.Sprintf(rediskey.MemberApiKey, item.ApiKey) | ||||
| 		val, err := sonic.MarshalString(item) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("用户api 初始化失败") | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if err := redishelper.DefaultRedis.SetString(key, val); err != nil { | ||||
| 			e.Log.Errorf("用户api 初始化失败") | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 创建api | ||||
| func (e *MemberApi) CreateApi(userId int, user *models.MemberApi, retryCount int) error { | ||||
| 	key, err := utility.GenerateBase62Key(32) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("生成API Key失败, %s", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	apiEntity := models.MemberApi{ | ||||
| 		UserId: userId, | ||||
| 		ApiKey: key, | ||||
| 		Status: 1, // 默认启用 | ||||
| 	} | ||||
|  | ||||
| 	var count int64 | ||||
| 	e.Orm.Model(user).Where("api_key =?", key).Count(&count) | ||||
|  | ||||
| 	if count > 0 && retryCount < 1000 { | ||||
| 		return e.CreateApi(userId, user, retryCount+1) | ||||
| 	} | ||||
|  | ||||
| 	if err := e.Orm.Create(&apiEntity).Error; err != nil { | ||||
| 		e.Log.Errorf("初始化用户API失败, %s", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	apiKey := fmt.Sprintf(rediskey.MemberApiKey, apiEntity.ApiKey) | ||||
| 	val, err := sonic.MarshalString(apiEntity) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("用户api 初始化失败") | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := redishelper.DefaultRedis.SetString(apiKey, val); err != nil { | ||||
| 		e.Log.Errorf("用户api 初始化失败") | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										1
									
								
								app/admin/service/open_common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/admin/service/open_common.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| package service | ||||
							
								
								
									
										355
									
								
								app/admin/service/sms_daisysms.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								app/admin/service/sms_daisysms.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,355 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"go-admin/config" | ||||
| 	"go-admin/utils/httphelper" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/shopspring/decimal" | ||||
| ) | ||||
|  | ||||
| type SmsDaisysms struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| // 同步价格 | ||||
| func (e SmsDaisysms) SyncPrices() { | ||||
| 	prices, err := e.GetPrices() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("GetPrices error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, price := range prices { | ||||
| 		if err := e.Orm.Model(&models.SmsServices{}).Where("code =? and platform_code=?", price.Key, global.SmsPlatformDaisysms).Update("price", price.Cost).Error; err != nil { | ||||
| 			e.Log.Errorf("Update price error: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetNumberForApi 获取短期或长期租赁号码 | ||||
| // getType 0-短效 1-长效 | ||||
| // service 服务code | ||||
| // maxPrice 最大价格 | ||||
| // period 时长(月) | ||||
| func (e *SmsDaisysms) GetNumberForApi(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) | ||||
|  | ||||
| 	if getType == 1 { | ||||
| 		url = fmt.Sprintf("%s&duration=%dM", url, period) | ||||
| 	} | ||||
|  | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	//ACCESS_NUMBER:999999:13476711222 | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
|  | ||||
| 		if strings.Contains(err.Error(), "NO_NUMBERS") { | ||||
| 			return acitvationId, result, statuscode.SmsOutOfStockOrUnavailable | ||||
| 		} else { | ||||
| 			return acitvationId, result, statuscode.ServerError | ||||
| 		} | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if strings.Contains(content, "ACCESS_NUMBER:") { | ||||
| 		if len(strings.Split(content, ":")) < 2 { | ||||
| 			e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 			return acitvationId, result, statuscode.ServerError | ||||
| 		} | ||||
|  | ||||
| 		acitvationId, _ = strconv.Atoi(strings.Split(content, ":")[1]) | ||||
| 		result = strings.Split(content, ":")[2] | ||||
| 	} else if strings.Contains(content, "MAX_PRICE_EXCEEDED:") { | ||||
| 		e.Log.Errorf("租赁价格超过最大价格") | ||||
| 		resultCode = statuscode.NoNumbers | ||||
| 	} else if strings.Contains(content, "NO_NUMBERS") { | ||||
| 		e.Log.Error("平台号码不足") | ||||
| 		resultCode = statuscode.NoNumbers | ||||
| 	} else if strings.Contains(content, "TOO_MANY_ACTIVE_RENTALS") { | ||||
| 		e.Log.Error("租赁数量超过限制") | ||||
| 		resultCode = statuscode.ServerError | ||||
| 	} else if strings.Contains(content, "NO_MONEY") { | ||||
| 		e.Log.Error("余额不足") | ||||
| 		resultCode = statuscode.BalanceNotEnough | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		resultCode = statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return acitvationId, result, resultCode | ||||
| } | ||||
|  | ||||
| // 设置租赁结束 | ||||
| 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) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if content == "ACCESS_ACTIVATION" { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| 	result := "" | ||||
| 	key, code := GetApiKey(e) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return result, code | ||||
| 	} | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getStatus&id=%s", key.ConfigValue, messageId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if strings.Contains(content, "STATUS_OK:") && content != "STATUS_OK:KEEP" { | ||||
| 		result = strings.Split(content, ":")[1] | ||||
| 	} else if content == "NO_ACTIVATION" { | ||||
| 		code = statuscode.SmsNoActivation | ||||
| 	} else if content == "STATUS_WAIT_CODE" || content == "STATUS_OK:KEEP" { | ||||
| 		code = statuscode.SmsWaitCode | ||||
| 	} else if content == "STATUS_CANCEL" { | ||||
| 		code = statuscode.SmsCancel | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return result, code | ||||
| } | ||||
|  | ||||
| // getExtraActivation 获取额外的激活 | ||||
| // messageId 短信id | ||||
| // return 验证码, 状态码 | ||||
| func (e *SmsDaisysms) getExtraActivation(activationId string) (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) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err1 := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err1 != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err1.Error()) | ||||
| 		return 0, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if strings.Contains(content, "ASLEEP:") { | ||||
| 		message := strings.Split(content, ":")[1] | ||||
| 		result, _ = strconv.Atoi(message) | ||||
|  | ||||
| 		if result > 0 { | ||||
| 			return result, statuscode.Success | ||||
| 		} | ||||
| 	} else if strings.Contains(content, "STATUS_OK:") { | ||||
| 		return 0, statuscode.SmsLongNumWaitCode | ||||
| 	} else if strings.Contains(content, "ACCESS_NUMBER:") { | ||||
| 		message := strings.Split(content, ":")[1] | ||||
| 		result, _ = strconv.Atoi(message) | ||||
|  | ||||
| 		if result > 0 { | ||||
| 			return result, statuscode.Success | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	e.Log.Errorf("激活长效号码失败,%s", content) | ||||
|  | ||||
| 	return 0, statuscode.ServerError | ||||
| } | ||||
|  | ||||
| // KeepLongTerm 长期租赁 | ||||
| func (e *SmsDaisysms) KeepLongTerm(activationId string) int { | ||||
| 	key, code := GetApiKey(e) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=keep&id=%s", key.ConfigValue, activationId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if content == "OK" { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 取消租赁 | ||||
| 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) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if content == "ACCESS_CANCEL" { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取价格 | ||||
| func (e *SmsDaisysms) GetPrices() ([]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) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, status, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return result, err | ||||
| 	} | ||||
|  | ||||
| 	mapData := make(map[string]map[string]dto.DaisysmsPriceResp) | ||||
| 	err = sonic.Unmarshal(bytes, &mapData) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("解析失败 %s", err.Error()) | ||||
| 		return result, err | ||||
| 	} | ||||
|  | ||||
| 	rawData, ok := mapData["187"] | ||||
|  | ||||
| 	if !ok { | ||||
| 		e.Log.Errorf("获取 187数据失败") | ||||
|  | ||||
| 		return result, errors.New("获取 187数据失败") | ||||
| 	} | ||||
|  | ||||
| 	if status != http.StatusOK { | ||||
| 		return result, fmt.Errorf("获取价格失败 %d", status) | ||||
| 	} | ||||
|  | ||||
| 	for key, v := range rawData { | ||||
| 		v.Key = key | ||||
|  | ||||
| 		result = append(result, v) | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // ChangeAutoRenew 修改自动续期 | ||||
| // activationId 短信id | ||||
| // status 状态 | ||||
| func (e *SmsDaisysms) ChangeAutoRenewForApi(activationId string, status bool) int { | ||||
| 	key, err := GetApiKey(e) | ||||
|  | ||||
| 	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) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, _, err1 := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err1 != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err1.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if content == "OK" { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("修改自动续期请求失败 id:%d, status:%t, %s", activationId, status, content) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
| @ -2,9 +2,8 @@ package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| @ -17,28 +16,55 @@ import ( | ||||
| 	cDto "go-admin/common/dto" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"go-admin/config" | ||||
| 	"go-admin/utils/httphelper" | ||||
| ) | ||||
|  | ||||
| type SmsPhone struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| // open-API 获取电话号码 | ||||
| func (e SmsPhone) OpenGetNumber(req *dto.GetNumberReq, userId int) (dto.OpenGetNumberResp, int) { | ||||
| 	resp := dto.OpenGetNumberResp{} | ||||
| 	balanceService := MemberBalance{Service: e.Service} | ||||
| 	balance, smsPhone, i := e.DoGetNumber(&balanceService, req, userId) | ||||
|  | ||||
| 	resp.Blance = balance | ||||
| 	if i != statuscode.Success { | ||||
| 		return resp, i | ||||
| 	} | ||||
|  | ||||
| 	resp.ActivationId = smsPhone.NewActivationId | ||||
| 	resp.ExpireTime = smsPhone.ExpireTime | ||||
| 	resp.Number = smsPhone.Phone | ||||
|  | ||||
| 	return resp, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 取消等待 | ||||
| func (e *SmsPhone) CancelNumber(req *dto.SmsPhoneCancelNumberReq, userId int) int { | ||||
| 	var data models.SmsPhone | ||||
| 	if err := e.Orm.Model(data).Where("id =? and user_id= ?", req.Id, userId).First(&data).Error; err != nil { | ||||
| 		e.Log.Errorf("获取短信号码失败, %s", err) | ||||
| 		return statuscode.SmsNotExisted | ||||
|  | ||||
| 	if req.Id > 0 { | ||||
| 		if err := e.Orm.Model(data).Where("id =? and user_id= ?", req.Id, userId).First(&data).Error; err != nil { | ||||
| 			e.Log.Errorf("获取短信号码失败, %s", err) | ||||
| 			return statuscode.SmsNotExisted | ||||
| 		} | ||||
| 	} else if req.ActivationId != "" { | ||||
| 		if err := e.Orm.Model(data).Where("new_activation_id =? and user_id= ?", req.ActivationId, userId).First(&data).Error; err != nil { | ||||
| 			e.Log.Errorf("获取短信号码失败, %s", err) | ||||
| 			return statuscode.SmsNotExisted | ||||
| 		} | ||||
| 	} else { | ||||
| 		e.Log.Errorf("id和activationId 都是空") | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if data.Actived == 1 { | ||||
| 		code := e.CancelRental(data.NewActivationId) | ||||
| 		code := e.CancelNumberManage(&data) | ||||
|  | ||||
| 		if code == statuscode.Success { | ||||
| 			err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 				if err1 := e.Orm.Model(data).Updates(map[string]interface{}{"status": 3, "code": "", "actived": "3"}).Error; err1 != nil { | ||||
| 				if err1 := e.Orm.Model(data).Updates(map[string]interface{}{"status": 3, "code": "", "actived": "3", "auto_renewal": 2}).Error; err1 != nil { | ||||
| 					e.Log.Errorf("更新短信号码失败, %s", err1) | ||||
| 					return err1 | ||||
| 				} | ||||
| @ -63,6 +89,23 @@ func (e *SmsPhone) CancelNumber(req *dto.SmsPhoneCancelNumberReq, userId int) in | ||||
| 	return statuscode.Success | ||||
| } | ||||
|  | ||||
| // 聚合取消 | ||||
| func (e *SmsPhone) CancelNumberManage(data *models.SmsPhone) int { | ||||
| 	switch data.PlatformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
|  | ||||
| 		return service.CancelRental(data.NewActivationId) | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 		return service.CancelRental(data.NewActivationId, data.Type) | ||||
| 	default: | ||||
| 		return statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| // 删除断信号码 | ||||
| func (e *SmsPhone) DeleteMyNumber(req *dto.DeleteMyNumberReq, userId int) int { | ||||
| 	var data models.SmsPhone | ||||
| @ -91,35 +134,56 @@ func (e *SmsPhone) DeleteMyNumber(req *dto.DeleteMyNumberReq, userId int) int { | ||||
| } | ||||
|  | ||||
| // 唤醒长效号码 | ||||
| func (e *SmsPhone) WeakUp(req *dto.WeakUpReq, 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 { | ||||
| 		e.Log.Errorf("获取短信号码失败, %s", err) | ||||
| 		return statuscode.ServerError | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if smsPhone.Status != 1 { | ||||
| 		newActivationId, code := e.getExtraActivation(smsPhone.ActivationId) | ||||
| 	if smsPhone.Status != 1 || defult { | ||||
| 		newActivationId, messageId, code := e.WeakUpManage(&smsPhone) | ||||
|  | ||||
| 		if code == statuscode.ServerError { | ||||
| 			return code | ||||
| 			return result, code | ||||
| 		} else if code == statuscode.Success { | ||||
| 			if err := e.Orm.Model(smsPhone).Updates(map[string]interface{}{"new_activation_id": newActivationId, "status": 1, "code": ""}).Error; err != nil { | ||||
| 			if err := e.Orm.Model(smsPhone).Updates(map[string]interface{}{"new_activation_id": newActivationId, "status": 1, "code": "", "message_id": messageId}).Error; err != nil { | ||||
| 				e.Log.Errorf("更新短信号码失败, %s", err) | ||||
| 				return statuscode.ServerError | ||||
| 				return result, statuscode.ServerError | ||||
| 			} | ||||
|  | ||||
| 			result.ActivationId = newActivationId | ||||
| 			result.MessageId = messageId | ||||
| 		} else if code == statuscode.SmsLongNumWaitCode { | ||||
| 			e.Log.Info("无须唤醒") | ||||
| 			if err := e.Orm.Model(smsPhone).Updates(map[string]interface{}{"status": 1, "code": ""}).Error; err != nil { | ||||
| 				e.Log.Errorf("更新短信号码失败, %s", err) | ||||
| 				return statuscode.ServerError | ||||
| 				return result, statuscode.ServerError | ||||
| 			} | ||||
|  | ||||
| 			return statuscode.Success | ||||
| 			return result, statuscode.Success | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return statuscode.Success | ||||
| 	return result, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 唤醒号码 | ||||
| // returns newActivationId,messageId, code | ||||
| func (e *SmsPhone) WeakUpManage(req *models.SmsPhone) (string, string, int) { | ||||
| 	switch req.PlatformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
| 		id, code := service.getExtraActivation(req.ActivationId) | ||||
| 		return strconv.Itoa(id), "", code | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 		return service.getExtraActivation(req.NewActivationId) | ||||
| 	default: | ||||
| 		return "", "", statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 分页查询自己的号码列表 | ||||
| @ -142,64 +206,89 @@ func (e *SmsPhone) GetMyPage(req *dto.SmsPhoneGetPageReq, userId int, phone *[]m | ||||
|  | ||||
| // 租赁号码 | ||||
| func (e *SmsPhone) GetNumber(req *dto.GetNumberReq, userId int) (decimal.Decimal, int) { | ||||
| 	balanceService := MemberBalance{Service: e.Service} | ||||
| 	balance, _, i := e.DoGetNumber(&balanceService, req, userId) | ||||
| 	if i != statuscode.Success { | ||||
| 		return balance, i | ||||
| 	} | ||||
|  | ||||
| 	balance = balanceService.GetBalance(userId) | ||||
|  | ||||
| 	return balance, statuscode.Success | ||||
| } | ||||
|  | ||||
| func (e *SmsPhone) DoGetNumber(balanceService *MemberBalance, req *dto.GetNumberReq, userId int) (decimal.Decimal, models.SmsPhone, int) { | ||||
| 	config := dto.GetSysConfigByKEYForServiceResp{} | ||||
| 	configReq := dto.SysConfigByKeyReq{} | ||||
| 	configService := SysConfig{Service: e.Service} | ||||
|  | ||||
| 	if strings.ToLower(req.ServiceCode) != "wa" { | ||||
| 		return decimal.Zero, statuscode.SmsServiceUnavailable | ||||
| 	} | ||||
|  | ||||
| 	if req.Type == 0 { | ||||
| 		configReq.ConfigKey = "number_fee_short_term" | ||||
| 	} else { | ||||
| 		configReq.ConfigKey = "number_fee_long_term" | ||||
| 	} | ||||
| 	err := configService.GetWithKey(&configReq, &config) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if config.ConfigValue == "" { | ||||
| 		e.Log.Errorf("短期或长期租赁费用不能为空") | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	price, err := decimal.NewFromString(config.ConfigValue) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("短期或长期租赁费用格式错误") | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	smsService := SmsServices{Service: e.Service} | ||||
| 	balanceService := MemberBalance{Service: e.Service} | ||||
| 	balance := balanceService.GetBalance(userId) | ||||
| 	serviceItem, err := smsService.GetByCode(req.ServiceCode) | ||||
| 	smsServices := SmsServices{Service: e.Service} | ||||
| 	var price decimal.Decimal | ||||
| 	serviceItem, err := smsServices.GetByCode(req.PlatformCode, req.ServiceCode) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("短信服务报错:%v", err) | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if serviceItem.Code == "" { | ||||
| 		e.Log.Error("短信服务不存在") | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	switch { | ||||
| 	case req.Type == 0 && req.PlatformCode == global.SmsPlatformDaisysms: | ||||
| 		price = serviceItem.Price | ||||
| 		configReq.ConfigKey = "number_premium_daisysms" | ||||
| 	case req.Type == 0 && req.PlatformCode == global.SmsPlatformTextVerified: | ||||
| 		price = serviceItem.Price | ||||
| 		configReq.ConfigKey = "number_premium_textverified" | ||||
| 	case req.Type == 1 && req.PlatformCode == global.SmsPlatformDaisysms: | ||||
| 		price = serviceItem.LongPrice | ||||
| 		configReq.ConfigKey = "long_number_premium_daisysms" | ||||
| 	case req.Type == 1 && req.PlatformCode == global.SmsPlatformTextVerified: | ||||
| 		price = serviceItem.LongPrice | ||||
| 		configReq.ConfigKey = "long_number_premium_textverified" | ||||
| 	} | ||||
| 	err = configService.GetWithKey(&configReq, &config) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if config.ConfigValue == "" { | ||||
| 		e.Log.Errorf("短期长期租赁浮动百分比不能为空") | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	percent, err := decimal.NewFromString(config.ConfigValue) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("短期或长期租赁费用格式错误") | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	price = price.Mul(decimal.NewFromInt(100).Add(percent)).Div(decimal.NewFromInt(100)) | ||||
|  | ||||
| 	if price.Cmp(decimal.Zero) <= 0 { | ||||
| 		e.Log.Errorf("短期或长期租赁费用不能小于等于0") | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	balance := balanceService.GetBalance(userId) | ||||
|  | ||||
| 	if balance.LessThan(price) { | ||||
| 		e.Log.Error("余额不足") | ||||
| 		return decimal.Zero, statuscode.BalanceNotEnough | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.BalanceNotEnough | ||||
| 	} | ||||
|  | ||||
| 	now := time.Now() | ||||
| 	activationId, phone, code := e.GetNumberForApi(req.Type, req.ServiceCode, price, req.Period) | ||||
| 	activationId, phone, code, expireTime := e.GetNumberManage(req.PlatformCode, req.Type, req.ServiceCode, price, req.Period) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return decimal.Zero, code | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, code | ||||
| 	} | ||||
|  | ||||
| 	smsPhone := models.SmsPhone{} | ||||
| 	smsPhone.PlatformCode = req.PlatformCode | ||||
| 	smsPhone.Phone = phone | ||||
| 	smsPhone.UserId = userId | ||||
| 	smsPhone.Service = serviceItem.Name | ||||
| @ -213,30 +302,45 @@ func (e *SmsPhone) GetNumber(req *dto.GetNumberReq, userId int) (decimal.Decimal | ||||
|  | ||||
| 	smsPhone.Price = price | ||||
| 	if req.Type == 1 { | ||||
| 		days := req.Period * 30 | ||||
| 		now = now.AddDate(0, 0, days) | ||||
| 		smsPhone.ExpireTime = &now | ||||
| 		if expireTime != nil { | ||||
| 			smsPhone.ExpireTime = expireTime | ||||
| 		} else { | ||||
| 			days := req.Period * 30 | ||||
| 			now = now.AddDate(0, 0, days) | ||||
| 			smsPhone.ExpireTime = &now | ||||
| 		} | ||||
|  | ||||
| 	} else { | ||||
| 		now = now.Add(time.Minute * time.Duration(serviceItem.ExpirationMinutes)) | ||||
| 		smsPhone.ExpireTime = &now | ||||
| 		smsPhone.AutoRenewal = 2 | ||||
| 		if expireTime != nil { | ||||
| 			smsPhone.ExpireTime = expireTime | ||||
| 		} else { | ||||
| 			now = now.Add(time.Minute * time.Duration(serviceItem.ExpirationMinutes)) | ||||
| 			smsPhone.ExpireTime = &now | ||||
| 			smsPhone.AutoRenewal = 2 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	smsPhone.Status = 1 | ||||
|  | ||||
| 	if err := e.Orm.Save(&smsPhone).Error; err != nil { | ||||
| 		e.Log.Errorf("获取手机号失败", err) | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if err := e.Orm.Exec("UPDATE member_balance SET balance = balance -? WHERE user_id =?", price, userId).Error; err != nil { | ||||
| 		e.Log.Errorf("更新余额失败", err) | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 		return decimal.Decimal{}, models.SmsPhone{}, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	//长效号码默认开启续费 | ||||
| 	if req.Type == 1 { | ||||
| 		code := e.ChangeAutoRenewForApi(smsPhone.NewActivationId, true) | ||||
| 		var code int | ||||
| 		if req.PlatformCode == global.SmsPlatformDaisysms { | ||||
| 			code = e.ChangeAutoRenewManage(smsPhone.PlatformCode, smsPhone.NewActivationId, true) | ||||
| 		} else { | ||||
| 			code = http.StatusOK | ||||
| 			e.WeakUp(&dto.WeakUpReq{ActivationId: smsPhone.NewActivationId}, userId, true) | ||||
| 		} | ||||
|  | ||||
| 		if code == statuscode.Success { | ||||
| 			smsPhone.AutoRenewal = 1 | ||||
| @ -247,116 +351,36 @@ func (e *SmsPhone) GetNumber(req *dto.GetNumberReq, userId int) (decimal.Decimal | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	balance = balanceService.GetBalance(userId) | ||||
|  | ||||
| 	return balance, statuscode.Success | ||||
| 	return balance, smsPhone, statuscode.Success | ||||
| } | ||||
|  | ||||
| // GetNumberForApi 获取短期或长期租赁号码 | ||||
| // getType 0-短效 1-长效 | ||||
| // service 服务code | ||||
| // maxPrice 最大价格 | ||||
| // period 时长(月) | ||||
| func (e *SmsPhone) GetNumberForApi(getType int, serviceCode string, maxPrice decimal.Decimal, period int) (int, string, int) { | ||||
| 	acitvationId := 0 | ||||
| 	result := "" | ||||
| 	resultCode := statuscode.Success | ||||
| // 租赁号码 | ||||
| func (e *SmsPhone) GetNumberManage(platformCode string, typ int, serviceCode string, price decimal.Decimal, period int) (string, string, int, *time.Time) { | ||||
| 	switch platformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
| 		activationId, phone, code := service.GetNumberForApi(typ, serviceCode, price, period) | ||||
|  | ||||
| 	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) | ||||
| 		return strconv.Itoa(activationId), phone, code, nil | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
| 		resp, code := service.GetNumberForApi(typ, serviceCode, price, period) | ||||
|  | ||||
| 	if getType == 1 { | ||||
| 		url = fmt.Sprintf("%s&duration=%dM", url, period) | ||||
| 	} | ||||
|  | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	//ACCESS_NUMBER:999999:13476711222 | ||||
| 	bytes, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return acitvationId, result, statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if strings.Contains(content, "ACCESS_NUMBER:") { | ||||
| 		if len(strings.Split(content, ":")) < 2 { | ||||
| 			e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 			return acitvationId, result, statuscode.ServerError | ||||
| 		if code != statuscode.Success { | ||||
| 			return "", "", code, nil | ||||
| 		} | ||||
|  | ||||
| 		acitvationId, _ = strconv.Atoi(strings.Split(content, ":")[1]) | ||||
| 		result = strings.Split(content, ":")[2] | ||||
| 	} else if strings.Contains(content, "MAX_PRICE_EXCEEDED:") { | ||||
| 		e.Log.Errorf("租赁价格超过最大价格") | ||||
| 		resultCode = statuscode.NoNumbers | ||||
| 	} else if strings.Contains(content, "NO_NUMBERS") { | ||||
| 		e.Log.Error("平台号码不足") | ||||
| 		resultCode = statuscode.NoNumbers | ||||
| 	} else if strings.Contains(content, "TOO_MANY_ACTIVE_RENTALS") { | ||||
| 		e.Log.Error("租赁数量超过限制") | ||||
| 		resultCode = statuscode.ServerError | ||||
| 	} else if strings.Contains(content, "NO_MONEY") { | ||||
| 		e.Log.Error("余额不足") | ||||
| 		resultCode = statuscode.BalanceNotEnough | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		resultCode = statuscode.ServerError | ||||
| 		return resp.Id, resp.Phone, code, resp.EndAt | ||||
| 	default: | ||||
| 		return "", "", statuscode.SmsPlatformUnavailable, nil | ||||
| 	} | ||||
|  | ||||
| 	return acitvationId, result, resultCode | ||||
| } | ||||
|  | ||||
| // 设置租赁结束 | ||||
| func (e *SmsPhone) 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) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if content == "ACCESS_ACTIVATION" { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func GetApiKey(e *SmsPhone) (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 | ||||
| } | ||||
|  | ||||
| func (e *SmsPhone) GetCodeByActivationId(req *dto.GetCodeReq, userId int) ([]dto.GetCodeResp, int) { | ||||
| 	var smsPhone []models.SmsPhone | ||||
| 	result := []dto.GetCodeResp{} | ||||
|  | ||||
| 	if err := e.Orm.Model(models.SmsPhone{}).Where("user_id =? and activation_id in ?", userId, req.ActivationIds).Find(&smsPhone).Error; err != nil { | ||||
| 	if err := e.Orm.Model(models.SmsPhone{}).Where("user_id =? and new_activation_id in ?", userId, req.ActivationIds).Find(&smsPhone).Error; err != nil { | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| @ -392,7 +416,18 @@ func (e *SmsPhone) SyncCodes() error { | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range phones { | ||||
| 		code, codeStatus := e.GetCodeForApi(item.NewActivationId) | ||||
| 		code, codeStatus := e.GetCodeManage(item.PlatformCode, item.NewActivationId, item.Type, item.UpdatedAt.Unix()) | ||||
|  | ||||
| 		if code == "" { | ||||
| 			expireTime := item.UpdatedAt.Add(time.Second * 150) | ||||
| 			if item.PlatformCode == global.SmsPlatformTextVerified && expireTime.Before(time.Now()) { | ||||
| 				if err := e.Orm.Model(&item).Updates(map[string]interface{}{"status": 3, "updated_at": time.Now()}).Error; err != nil { | ||||
| 					e.Log.Errorf("手机号一睡眠 %s", item.Phone) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			continue | ||||
| 		} | ||||
| 		mapData := make(map[string]interface{}) | ||||
| 		logMapData := make(map[string]interface{}) | ||||
|  | ||||
| @ -437,135 +472,18 @@ func (e *SmsPhone) SyncCodes() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetCodeForApi 获取验证码 | ||||
| // messageId 短信id | ||||
| // return 验证码, 状态码 | ||||
| func (e *SmsPhone) GetCodeForApi(messageId int) (string, int) { | ||||
| 	result := "" | ||||
| 	key, code := GetApiKey(e) | ||||
| // 获取验证码 | ||||
| func (e *SmsPhone) GetCodeManage(platformCode string, messageId string, typ int, unixTime int64) (string, int) { | ||||
| 	switch platformCode { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return result, code | ||||
| 	} | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=getStatus&id=%d", key.ConfigValue, messageId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if strings.Contains(content, "STATUS_OK:") && content != "STATUS_OK:KEEP" { | ||||
| 		result = strings.Split(content, ":")[1] | ||||
| 	} else if content == "NO_ACTIVATION" { | ||||
| 		code = statuscode.SmsNoActivation | ||||
| 	} else if content == "STATUS_WAIT_CODE" || content == "STATUS_OK:KEEP" { | ||||
| 		code = statuscode.SmsWaitCode | ||||
| 	} else if content == "STATUS_CANCEL" { | ||||
| 		code = statuscode.SmsCancel | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return result, code | ||||
| } | ||||
|  | ||||
| // getExtraActivation 获取额外的激活 | ||||
| // messageId 短信id | ||||
| // return 验证码, 状态码 | ||||
| func (e *SmsPhone) getExtraActivation(activationId int) (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=%d", key.ConfigValue, activationId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, err1 := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err1 != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err1.Error()) | ||||
| 		return 0, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if strings.Contains(content, "ASLEEP:") { | ||||
| 		message := strings.Split(content, ":")[1] | ||||
| 		result, _ = strconv.Atoi(message) | ||||
|  | ||||
| 		if result > 0 { | ||||
| 			return result, statuscode.Success | ||||
| 		} | ||||
| 	} else if strings.Contains(content, "STATUS_OK:") { | ||||
| 		return 0, statuscode.SmsLongNumWaitCode | ||||
| 	} else if strings.Contains(content, "ACCESS_NUMBER:") { | ||||
| 		message := strings.Split(content, ":")[1] | ||||
| 		result, _ = strconv.Atoi(message) | ||||
|  | ||||
| 		if result > 0 { | ||||
| 			return result, statuscode.Success | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	e.Log.Errorf("激活长效号码失败,%s", content) | ||||
|  | ||||
| 	return 0, statuscode.ServerError | ||||
| } | ||||
|  | ||||
| // KeepLongTerm 长期租赁 | ||||
| func (e *SmsPhone) KeepLongTerm(activationId int) int { | ||||
| 	key, code := GetApiKey(e) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=keep&id=%d", key.ConfigValue, activationId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if content == "OK" { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 取消租赁 | ||||
| func (e *SmsPhone) CancelRental(activationId int) 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=%d&status=8", key.ConfigValue, activationId) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, err := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| 	content := string(bytes) | ||||
|  | ||||
| 	if content == "ACCESS_CANCEL" { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", content) | ||||
| 		return statuscode.ServerError | ||||
| 		return service.GetCodeForApi(messageId) | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
| 		return service.GetCode(messageId, typ, unixTime) | ||||
| 	default: | ||||
| 		return "", statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -576,57 +494,75 @@ func (e SmsServices) AutoRenewal() error { | ||||
| 	startTime := time.Now().Add(-24 * time.Hour) | ||||
| 	endTime := time.Now().Add(24 * time.Hour) | ||||
|  | ||||
| 	smsServices := SmsServices{Service: e.Service} | ||||
| 	serviceMap := smsServices.GetMapAll() | ||||
|  | ||||
| 	configService := SysConfig{Service: e.Service} | ||||
| 	configResp := dto.GetSysConfigByKEYForServiceResp{} | ||||
| 	configService.GetWithKey(&dto.SysConfigByKeyReq{ConfigKey: "long_number_renew_deduction_standard"}, &configResp) | ||||
|  | ||||
| 	if configResp.ConfigValue == "" { | ||||
| 		e.Log.Errorf("获取续费价格失败") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	price, _ := decimal.NewFromString(configResp.ConfigValue) | ||||
| 	if price.IsZero() { | ||||
| 		e.Log.Errorf("获取续费价格失败") | ||||
| 		return nil | ||||
| 	} | ||||
| 	mapDatas, _ := configService.GetMapByKeys([]string{"renew_number_premium_daisysms", "renew_number_premium_textverified"}) | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SmsPhone{}).Where("auto_renewal =1 and type =1 and actived =2 and expire_time >=? and expire_time <?", startTime, endTime).Find(&datas).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range datas { | ||||
| 		renewLog := models.SmsRenewalLog{} | ||||
| 		renewLog.UserId = item.UserId | ||||
| 		renewLog.PhoneId = item.Id | ||||
| 		renewLog.Type = item.Type | ||||
| 		renewLog.Amount = price | ||||
| 		renewLog.BeforeTime = *item.ExpireTime | ||||
| 		renewLog.Period = 30 | ||||
| 		percent := decimal.NewFromInt(1) | ||||
| 		price := decimal.Zero | ||||
|  | ||||
| 		if err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 			db := tx.Exec("UPDATE member_balance set balance = balance - ? where user_id = ? and balance >= ?", price, item.UserId, price) | ||||
| 		if service, ok := serviceMap[item.PlatformCode+"_"+item.ServiceCode]; !ok { | ||||
| 			continue | ||||
| 		} else { | ||||
| 			price = service.LongPrice | ||||
| 			switch { | ||||
| 			case item.PlatformCode == global.SmsPlatformDaisysms: | ||||
| 				p, ok := mapDatas["renew_number_premium_daisysms"] | ||||
| 				val, _ := decimal.NewFromString(p.ConfigValue) | ||||
|  | ||||
| 			if db.Error != nil { | ||||
| 				return db.Error | ||||
| 				if ok && val.Cmp(decimal.Zero) > 0 { | ||||
| 					per := decimal.NewFromInt(100).Add(val) | ||||
| 					percent = per.Div(decimal.NewFromInt(100)).Truncate(2) | ||||
| 				} | ||||
| 			case item.PlatformCode == global.SmsPlatformTextVerified: | ||||
| 				p, ok := mapDatas["renew_number_premium_textverified"] | ||||
| 				val, _ := decimal.NewFromString(p.ConfigValue) | ||||
|  | ||||
| 				if ok && val.Cmp(decimal.Zero) > 0 { | ||||
| 					per := decimal.NewFromInt(100).Add(val) | ||||
| 					percent = per.Div(decimal.NewFromInt(100)).Truncate(2) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if db.RowsAffected == 0 { | ||||
| 				return errors.New("余额不足") | ||||
| 			} | ||||
| 			renewLog := models.SmsRenewalLog{} | ||||
| 			renewLog.UserId = item.UserId | ||||
| 			renewLog.PhoneId = item.Id | ||||
| 			renewLog.Type = item.Type | ||||
| 			renewLog.Amount = price.Mul(percent).Truncate(2) | ||||
| 			renewLog.BeforeTime = *item.ExpireTime | ||||
| 			renewLog.Period = 30 | ||||
|  | ||||
| 			if err1 := tx.Create(&renewLog).Error; err1 != nil { | ||||
| 			if err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 				db := tx.Exec("UPDATE member_balance set balance = balance - ? where user_id = ? and balance >= ?", price, item.UserId, price) | ||||
|  | ||||
| 				return errors.New("创建续费日志失败") | ||||
| 			} | ||||
| 				if db.Error != nil { | ||||
| 					return db.Error | ||||
| 				} | ||||
|  | ||||
| 			newExpireTime := item.ExpireTime.AddDate(0, 0, 30) | ||||
| 			if err1 := tx.Model(data).Where("id =?", item.Id).Update("expire_time", newExpireTime).Error; err1 != nil { | ||||
| 				return err1 | ||||
| 				if db.RowsAffected == 0 { | ||||
| 					return errors.New("余额不足") | ||||
| 				} | ||||
|  | ||||
| 				if err1 := tx.Create(&renewLog).Error; err1 != nil { | ||||
|  | ||||
| 					return errors.New("创建续费日志失败") | ||||
| 				} | ||||
|  | ||||
| 				newExpireTime := item.ExpireTime.AddDate(0, 0, 30) | ||||
| 				if err1 := tx.Model(data).Where("id =?", item.Id).Update("expire_time", newExpireTime).Error; err1 != nil { | ||||
| 					return err1 | ||||
| 				} | ||||
| 				return nil | ||||
| 			}); err != nil { | ||||
| 				e.Log.Errorf("自动续期失败:%s", err.Error()) | ||||
| 			} | ||||
| 			return nil | ||||
| 		}); err != nil { | ||||
| 			e.Log.Errorf("自动续期失败:%s", err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -637,64 +573,74 @@ func (e SmsServices) AutoRenewal() error { | ||||
| func (e *SmsPhone) ChangeAutoRenew(req *dto.SmsPhoneChangeAutoRenewReq, userId int) int { | ||||
| 	var data models.SmsPhone | ||||
|  | ||||
| 	if err := e.Orm.Model(data).Where("id =? and user_id =?", req.Id, userId).First(&data).Error; err != nil { | ||||
| 		e.Log.Errorf("修改自动续期 数据不存在 id:%d err:%s", req.Id, err.Error()) | ||||
| 		return statuscode.SmsNotExisted | ||||
| 	if req.Id > 0 { | ||||
| 		if err := e.Orm.Model(data).Where("id =? and user_id =?", req.Id, userId).First(&data).Error; err != nil { | ||||
| 			e.Log.Errorf("修改自动续期 数据不存在 id:%d err:%s", req.Id, err.Error()) | ||||
| 			return statuscode.SmsNotExisted | ||||
| 		} | ||||
| 	} else if req.ActivationId != "" { | ||||
| 		if err := e.Orm.Model(data).Where("activation_id =? and user_id =?", req.ActivationId, userId).First(&data).Error; err != nil { | ||||
| 			e.Log.Errorf("修改自动续期 数据不存在 id:%d err:%s", req.Id, err.Error()) | ||||
| 			return statuscode.SmsNotExisted | ||||
| 		} | ||||
| 	} else { | ||||
| 		return statuscode.InvalidParams | ||||
| 	} | ||||
|  | ||||
| 	// 短期租赁不支持自动续期 | ||||
| 	if data.Type == 0 { | ||||
| 		return statuscode.SmsNotAutoRenew | ||||
| 	} | ||||
|  | ||||
| 	status := false | ||||
| 	changeAutoRenewal := 2 | ||||
| 	if req.AutoRenew == 1 { | ||||
| 		status = true | ||||
| 		changeAutoRenewal = 1 | ||||
| 	} | ||||
|  | ||||
| 	data.AutoRenewal = req.AutoRenew | ||||
| 	code := e.ChangeAutoRenewForApi(data.ActivationId, status) | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if err := e.Orm.Model(&data).Update("auto_renewal", changeAutoRenewal).Error; err != nil { | ||||
| 		e.Log.Errorf("修改自动续期失败 id:%d, status:%t", data.ActivationId, status) | ||||
| 	shouldReturn, i := e.DoChangeAutoRenewal(data, req.AutoRenew) | ||||
| 	if shouldReturn { | ||||
| 		return i | ||||
| 	} | ||||
|  | ||||
| 	return statuscode.Success | ||||
| } | ||||
|  | ||||
| // ChangeAutoRenew 修改自动续期 | ||||
| // activationId 短信id | ||||
| // status 状态 | ||||
| func (e *SmsPhone) ChangeAutoRenewForApi(activationId int, status bool) int { | ||||
| 	key, err := GetApiKey(e) | ||||
|  | ||||
| 	if err != statuscode.Success { | ||||
| 		e.Log.Errorf("查询sms api请求失败 %d", activationId) | ||||
| 		return statuscode.ServerError | ||||
| // DoChangeAutoRenewal 执行修改自动续期 | ||||
| func (e *SmsPhone) DoChangeAutoRenewal(data models.SmsPhone, autoRenewal int) (bool, int) { | ||||
| 	if data.Type == 0 { | ||||
| 		return true, statuscode.SmsNotAutoRenew | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("?api_key=%s&action=setAutoRenew&id=%d&value=%t", key.ConfigValue, activationId, status) | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.DaisysmsUrl, nil) | ||||
| 	bytes, err1 := client.GetRaw(url, nil) | ||||
|  | ||||
| 	if err1 != nil { | ||||
| 		e.Log.Errorf("租赁请求失败 %s", err1.Error()) | ||||
| 		return statuscode.ServerError | ||||
| 	status := false | ||||
| 	changeAutoRenewal := 2 | ||||
| 	if autoRenewal == 1 { | ||||
| 		status = true | ||||
| 		changeAutoRenewal = 1 | ||||
| 	} | ||||
|  | ||||
| 	content := string(bytes) | ||||
| 	var code int | ||||
| 	data.AutoRenewal = autoRenewal | ||||
|  | ||||
| 	if content == "OK" { | ||||
| 		return statuscode.Success | ||||
| 	if data.Actived == 3 { | ||||
| 		code = http.StatusOK | ||||
| 	} else { | ||||
| 		e.Log.Errorf("修改自动续期请求失败 id:%d, status:%t, %s", activationId, status, content) | ||||
| 		return statuscode.ServerError | ||||
| 		code = e.ChangeAutoRenewManage(data.PlatformCode, data.ActivationId, status) | ||||
|  | ||||
| 		if code != statuscode.Success { | ||||
| 			return true, statuscode.ServerError | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := e.Orm.Model(&data).Update("auto_renewal", changeAutoRenewal).Error; err != nil { | ||||
| 		e.Log.Errorf("修改自动续期失败 id:%d, status:%t", data.ActivationId, status) | ||||
| 	} | ||||
| 	return false, 0 | ||||
| } | ||||
|  | ||||
| // ChangeAutoRenewManage 修改自动续期管理 | ||||
| func (e *SmsPhone) ChangeAutoRenewManage(platform string, activationId string, status bool) int { | ||||
| 	switch platform { | ||||
| 	case global.SmsPlatformDaisysms: | ||||
| 		service := SmsDaisysms{Service: e.Service} | ||||
|  | ||||
| 		return service.ChangeAutoRenewForApi(activationId, status) | ||||
| 	case global.SmsPlatformTextVerified: | ||||
| 		service := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 		return service.Renew(activationId, status) | ||||
| 	default: | ||||
| 		return statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										65
									
								
								app/admin/service/sms_phone_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								app/admin/service/sms_phone_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/global" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| ) | ||||
|  | ||||
| func TestVerifiedGetNumber(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	service := SmsPhone{} | ||||
| 	service.Orm = db | ||||
| 	service.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	if _, err := service.GetNumber(&dto.GetNumberReq{ | ||||
| 		PlatformCode: global.SmsPlatformTextVerified, | ||||
| 		Type:         1, | ||||
| 		ServiceCode:  "cocacola", | ||||
| 		Period:       1, | ||||
| 	}, 1); err != statuscode.Success { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestVerifiedCancel(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	service := SmsPhone{} | ||||
| 	service.Orm = db | ||||
| 	service.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	if err := service.CancelNumber(&dto.SmsPhoneCancelNumberReq{ | ||||
| 		Id: 129, | ||||
| 	}, 1); err != statuscode.Success { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestVerifiedWake(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	service := SmsPhone{} | ||||
| 	service.Orm = db | ||||
| 	service.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	service.WeakUp(&dto.WeakUpReq{ | ||||
| 		ActivationId: "lr_01K21NVPVDEXTS1C3S8HPEC612", | ||||
| 	}, 1, false) | ||||
| } | ||||
|  | ||||
| func TestGetPrices(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	service := SmsServices{} | ||||
| 	service.Orm = db | ||||
| 	service.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	if err := service.SyncPrices(); err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
| @ -48,7 +48,7 @@ func (e SmsReceiveLog) WebHook(req *dto.SmsReceiveWebHookReq) error { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		phoneService := SmsPhone{Service: e.Service} | ||||
| 		phoneService := SmsDaisysms{Service: e.Service} | ||||
| 		if code := phoneService.setStatus(req.ActivationID); code != statuscode.Success { | ||||
| 			e.Log.Errorf("接受验证码回调后修改状态失败 %d", code) | ||||
| 		} | ||||
|  | ||||
| @ -12,14 +12,19 @@ import ( | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| func initSetting() { | ||||
| func initSetting() *gorm.DB { | ||||
| 	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.TrxGridUrl = "https://api.trongrid.io" | ||||
| 	config.ExtConfig.DaisysmsUrl = "https://daisysms.com/stubs/handler_api.php" | ||||
| 	config.ExtConfig.SmsTextVerified.ApiKey = "ZQ0swXnsaPpeGdwa3c7gT9U9I1Oh9WoDHx0amuYovvaHuqd5u6B4NBBUSUBjR" | ||||
| 	config.ExtConfig.SmsTextVerified.UserName = "webspan@proton.me" | ||||
| 	config.ExtConfig.SmsTextVerified.Url = "https://www.textverified.com" | ||||
| 	redishelper.InitDefaultRedis("127.0.0.1:6379", "", 4) | ||||
| 	redishelper.InitLockRedisConn("127.0.0.1:6379", "", "4") | ||||
|  | ||||
| 	return db | ||||
| } | ||||
|  | ||||
| func TestSmsReceiveLog(t *testing.T) { | ||||
|  | ||||
| @ -11,21 +11,75 @@ import ( | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/common/actions" | ||||
| 	cDto "go-admin/common/dto" | ||||
| 	"go-admin/common/global" | ||||
| ) | ||||
|  | ||||
| type SmsServices struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| func (e SmsServices) OpenGetByCode(code string, param2 string, apiKey string) (any, error) { | ||||
| 	panic("unimplemented") | ||||
| } | ||||
|  | ||||
| // 获取平台列表 | ||||
| func (e SmsServices) GetPlatform(resp *[]dto.SmsPlatformGetListResp) error { | ||||
| 	var datas []models.SysDictData | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SysDictData{}).Where("dict_type = 'sms_platform'").Find(&datas).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range datas { | ||||
| 		var respDto dto.SmsPlatformGetListResp | ||||
| 		respDto.Label = v.DictLabel | ||||
| 		respDto.Value = v.DictValue | ||||
|  | ||||
| 		*resp = append(*resp, respDto) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetPrice 获取SmsServices价格 | ||||
| func (e SmsServices) GetPrice(req *dto.SmsGetPriceReq) (decimal.Decimal, error) { | ||||
| 	key := "number_fee_short_term" | ||||
| 	var service models.SmsServices | ||||
|  | ||||
| 	if req.Type == 1 { | ||||
| 		key = "number_fee_long_term" | ||||
| 	if err := e.Orm.Model(&service).Where("platform_code =? and code =?", req.PlatformCode, req.ServiceCode).First(&service).Error; err != nil { | ||||
| 		return decimal.Decimal{}, errors.New("获取价格失败") | ||||
| 	} | ||||
|  | ||||
| 	var price decimal.Decimal | ||||
| 	var key string | ||||
|  | ||||
| 	if req.Type == 1 { | ||||
| 		price = service.Price | ||||
|  | ||||
| 		switch req.PlatformCode { | ||||
| 		case global.SmsPlatformDaisysms: | ||||
| 			key = "long_number_premium_daisysms" | ||||
| 		case global.SmsPlatformTextVerified: | ||||
| 			key = "long_number_premium_textverified" | ||||
| 		default: | ||||
| 			return price, errors.New("获取价格失败") | ||||
| 		} | ||||
| 	} else { | ||||
| 		price = service.LongPrice | ||||
|  | ||||
| 		switch req.PlatformCode { | ||||
| 		case global.SmsPlatformDaisysms: | ||||
| 			key = "number_premium_daisysms" | ||||
| 		case global.SmsPlatformTextVerified: | ||||
| 			key = "number_premium_textverified" | ||||
| 		default: | ||||
| 			return price, errors.New("获取价格失败") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if price.IsZero() { | ||||
| 		return price, errors.New("获取价格失败") | ||||
| 	} | ||||
|  | ||||
| 	configService := SysConfig{Service: e.Service} | ||||
| 	configResp := dto.GetSysConfigByKEYForServiceResp{} | ||||
| 	err := configService.GetWithKey(&dto.SysConfigByKeyReq{ConfigKey: key}, &configResp) | ||||
| @ -34,29 +88,96 @@ func (e SmsServices) GetPrice(req *dto.SmsGetPriceReq) (decimal.Decimal, error) | ||||
| 		return price, err | ||||
| 	} | ||||
|  | ||||
| 	price, err = decimal.NewFromString(configResp.ConfigValue) | ||||
| 	percent, err := decimal.NewFromString(configResp.ConfigValue) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return price, err | ||||
| 	} | ||||
|  | ||||
| 	if percent.IsZero() { | ||||
| 		return price, nil | ||||
| 	} else { | ||||
| 		price = price.Mul((decimal.NewFromInt(100).Add(percent)).Div(decimal.NewFromInt(100))).Truncate(2) | ||||
| 	} | ||||
|  | ||||
| 	return price, nil | ||||
| } | ||||
|  | ||||
| // GetList 获取SmsServices列表 | ||||
| func (e SmsServices) GetList(resp *[]dto.SmsServicesGetListResp) error { | ||||
| 	var data []models.SmsServices | ||||
| 	err := e.Orm.Find(&data).Error | ||||
| 	err := e.Orm.Model(&models.SmsServices{}).Where("status =1").Find(&data).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("SmsServicesService GetList error:%s \r\n", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	dictService := SysDictData{Service: e.Service} | ||||
| 	dictDatas, _ := dictService.GetMapByType("sms_platform") | ||||
|  | ||||
| 	keys := []string{"number_premium_daisysms", "number_premium_textverified", "long_number_premium_daisysms", "long_number_premium_textverified"} | ||||
| 	configService := SysConfig{Service: e.Service} | ||||
| 	mapConfigs, err := configService.GetWithKeyForMap(keys) | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取配置失败,\r\n失败信息 %s", err.Error()) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range data { | ||||
| 		respItem := dto.SmsServicesGetListResp{ | ||||
| 			Name:   item.Name, | ||||
| 			Code:   item.Code, | ||||
| 			Status: item.Status, | ||||
| 			Name:         item.Name, | ||||
| 			Code:         item.Code, | ||||
| 			Status:       item.Status, | ||||
| 			PlatformCode: item.PlatformCode, | ||||
| 			Price:        item.Price, | ||||
| 			LongPrice:    item.LongPrice, | ||||
| 		} | ||||
|  | ||||
| 		if dict, ok := dictDatas[respItem.PlatformCode]; ok { | ||||
| 			respItem.PlatformName = dict.DictLabel | ||||
| 		} | ||||
|  | ||||
| 		switch item.PlatformCode { | ||||
| 		case global.SmsPlatformDaisysms: | ||||
| 			if config, ok := mapConfigs["number_premium_daisysms"]; ok { | ||||
| 				premium, err := decimal.NewFromString(config.ConfigValue) | ||||
|  | ||||
| 				if err != nil || premium.IsZero() { | ||||
| 					e.Log.Errorf("浮动百分比为0,或者是转换错误 %s", config.ConfigValue) | ||||
| 				} else { | ||||
| 					respItem.Price = respItem.Price.Mul(decimal.NewFromInt(100).Add(premium).Div(decimal.NewFromInt(100))).Truncate(2) | ||||
| 				} | ||||
| 			} | ||||
| 			if config, ok := mapConfigs["long_number_premium_daisysms"]; ok { | ||||
| 				premium, err := decimal.NewFromString(config.ConfigValue) | ||||
|  | ||||
| 				if err != nil || premium.IsZero() { | ||||
| 					e.Log.Errorf("浮动百分比为0,或者是转换错误 %s", config.ConfigValue) | ||||
| 				} else { | ||||
| 					respItem.LongPrice = respItem.LongPrice.Mul(decimal.NewFromInt(100).Add(premium).Div(decimal.NewFromInt(100))).Truncate(2) | ||||
| 				} | ||||
| 			} | ||||
| 		case global.SmsPlatformTextVerified: | ||||
| 			if config, ok := mapConfigs["number_premium_textverified"]; ok { | ||||
| 				premium, err := decimal.NewFromString(config.ConfigValue) | ||||
|  | ||||
| 				if err != nil || premium.IsZero() { | ||||
| 					e.Log.Errorf("浮动百分比为0,或者是转换错误 %s", config.ConfigValue) | ||||
| 				} else { | ||||
| 					respItem.Price = respItem.Price.Mul(decimal.NewFromInt(100).Add(premium).Div(decimal.NewFromInt(100))).Truncate(2) | ||||
| 				} | ||||
| 			} | ||||
| 			if config, ok := mapConfigs["long_number_premium_textverified"]; ok { | ||||
| 				premium, err := decimal.NewFromString(config.ConfigValue) | ||||
|  | ||||
| 				if err != nil || premium.IsZero() { | ||||
| 					e.Log.Errorf("浮动百分比为0,或者是转换错误 %s", config.ConfigValue) | ||||
| 				} else { | ||||
| 					respItem.LongPrice = respItem.LongPrice.Mul(decimal.NewFromInt(100).Add(premium).Div(decimal.NewFromInt(100))).Truncate(2) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		*resp = append(*resp, respItem) | ||||
| 	} | ||||
| 	return nil | ||||
| @ -66,13 +187,17 @@ func (e SmsServices) GetList(resp *[]dto.SmsServicesGetListResp) error { | ||||
| func (e *SmsServices) GetPage(c *dto.SmsServicesGetPageReq, p *actions.DataPermission, list *[]models.SmsServices, count *int64) error { | ||||
| 	var err error | ||||
| 	var data models.SmsServices | ||||
|  | ||||
| 	err = e.Orm.Model(&data). | ||||
| 	query := e.Orm.Model(&data). | ||||
| 		Scopes( | ||||
| 			cDto.MakeCondition(c.GetNeedSearch()), | ||||
| 			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), | ||||
| 			actions.Permission(data.TableName(), p), | ||||
| 		). | ||||
| 		) | ||||
|  | ||||
| 	if c.Status > 0 { | ||||
| 		query = query.Where("status = ?", c.Status) | ||||
| 	} | ||||
| 	err = query. | ||||
| 		Find(list).Limit(-1).Offset(-1). | ||||
| 		Count(count).Error | ||||
| 	if err != nil { | ||||
| @ -154,12 +279,48 @@ func (e *SmsServices) Remove(d *dto.SmsServicesDeleteReq, p *actions.DataPermiss | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *SmsServices) GetByCode(code string) (models.SmsServices, error) { | ||||
| func (e *SmsServices) GetByCode(platformCode, code string) (models.SmsServices, error) { | ||||
| 	var data models.SmsServices | ||||
|  | ||||
| 	if err := e.Orm.Model(data).Where("code =?", code).First(&data).Error; err != nil { | ||||
| 	if err := e.Orm.Model(data).Where("platform_code =? and  code =?", platformCode, code).First(&data).Error; err != nil { | ||||
| 		return data, err | ||||
| 	} | ||||
|  | ||||
| 	return data, nil | ||||
| } | ||||
|  | ||||
| // 同步第三方价格 | ||||
| func (e *SmsServices) SyncPrices() error { | ||||
| 	var datas []models.SmsServices | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SmsServices{}).Where("platform_code =?", global.SmsPlatformTextVerified).Find(&datas).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	daisysmsService := SmsDaisysms{Service: e.Service} | ||||
| 	textVerifiedService := SmsTextVerified{Service: e.Service} | ||||
|  | ||||
| 	//daisysms 同步价格 | ||||
| 	daisysmsService.SyncPrices() | ||||
| 	//textVerified 同步价格 | ||||
| 	textVerifiedService.SyncPrices() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 获取服务map | ||||
| // map key ={platformCode}_{serviceCode} | ||||
| func (e *SmsServices) GetMapAll() map[string]models.SmsServices { | ||||
| 	var datas []models.SmsServices | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SmsServices{}).Find(&datas).Error; err != nil { | ||||
| 		e.Log.Errorf("SmsServicesService GetMapAll error:%s \r\n", err) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	mapServices := make(map[string]models.SmsServices) | ||||
| 	for _, item := range datas { | ||||
| 		mapServices[item.PlatformCode+"_"+item.Code] = item | ||||
| 	} | ||||
|  | ||||
| 	return mapServices | ||||
| } | ||||
|  | ||||
							
								
								
									
										855
									
								
								app/admin/service/sms_text_verified.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										855
									
								
								app/admin/service/sms_text_verified.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,855 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"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" | ||||
| 	"go-admin/utils/utility" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/service" | ||||
| 	"github.com/go-redis/redis/v8" | ||||
| 	"github.com/shopspring/decimal" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| type SmsTextVerified struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| // 获取收到的验证码 | ||||
| // messageId 短效或长效号码的ID | ||||
| // typ 0-短效 1-长效 | ||||
| func (e SmsTextVerified) GetCode(messageId string, typ int, unixTime int64) (string, int) { | ||||
| 	reservationType := "" | ||||
|  | ||||
| 	if typ == 0 { | ||||
| 		reservationType = "verification" | ||||
| 	} else { | ||||
| 		reservationType = "renewable" | ||||
| 	} | ||||
| 	url := fmt.Sprintf(getSmsCode, messageId, reservationType) | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != http.StatusOK { | ||||
| 		e.Log.Errorf("获取授权请求失败,status %d", code) | ||||
| 		return "", code | ||||
| 	} | ||||
|  | ||||
| 	var parsedCode string | ||||
| 	resp := dto.TextVerifiedSmsResp{} | ||||
|  | ||||
| 	_, err := client.Get(url, nil, &resp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取验证码失败, error: %v", err) | ||||
| 		return "", statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if len(resp.Data) > 0 { | ||||
| 		for _, v := range resp.Data { | ||||
| 			// 2. 解析时间字符串 | ||||
| 			// time.RFC3339Nano 是一个预定义的格式常量, | ||||
| 			// 它能够解析包含纳秒和时区信息的 ISO 8601 字符串。 | ||||
| 			t, err := time.Parse(time.RFC3339Nano, v.CreatedAt) | ||||
| 			if err != nil { | ||||
| 				fmt.Println("时间解析失败:", err) | ||||
| 			} | ||||
|  | ||||
| 			// 3. 将解析后的 time.Time 对象转换为 Unix 时间戳 | ||||
| 			// t.Unix() 返回以秒为单位的整数时间戳 | ||||
| 			unixTimestamp := t.Unix() | ||||
|  | ||||
| 			if unixTimestamp >= unixTime { | ||||
| 				parsedCode = v.ParsedCode | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return parsedCode, http.StatusOK | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	loginUrl                = "/api/pub/v2/auth" | ||||
| 	getAreas                = "/api/pub/v2/area-codes" | ||||
| 	getServices             = "/api/pub/v2/services" | ||||
| 	createRental            = "/api/pub/v2/reservations/rental"                     //长效收码 | ||||
| 	createVerification      = "/api/pub/v2/verifications"                           //单次收码 | ||||
| 	cancelVerification      = "/api/pub/v2/verifications/%s/cancel"                 //取消收码 | ||||
| 	cancelRental            = "/api/pub/v2/reservations/rental/renewable/%s/refund" //取消长效号码 | ||||
| 	wakeUp                  = "/api/pub/v2/wake-requests"                           //唤醒号码 | ||||
| 	rentalPrice             = "/api/pub/v2/pricing/rentals"                         //长效收码价格 | ||||
| 	verificationPrice       = "/api/pub/v2/pricing/verifications"                   //单次收码价格 | ||||
| 	rentalDetail            = "/api/pub/v2/reservations/rental/renewable/%s"        //长效号码详情 | ||||
| 	renewRental             = "/api/pub/v2/reservations/rental/renewable/%s/renew"  //长效续期 | ||||
| 	updateRentalRenewStatus = "/api/pub/v2/reservations/rental/renewable/%s"        // 更改续租状态 | ||||
| 	getSmsCode              = "/api/pub/v2/sms?reservationId=%s&reservationType=%s" //获取短信验证码 | ||||
| ) | ||||
|  | ||||
| var idPattern = regexp.MustCompile(`^[a-zA-Z0-9_]{28}$`) | ||||
|  | ||||
| // 获取授权失败 | ||||
| var ErrUnAuth = errors.New("未授权,请检查配置的账号和密码") | ||||
| var ErrOutOfStockOrUnavailable = errors.New("缺货或服务不可用") | ||||
|  | ||||
| // Login 登录 | ||||
| func (e *SmsTextVerified) Login() (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, | ||||
| 	} | ||||
| 	_, err1 := client.Post(loginUrl, 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秒过期 | ||||
| 		if err := redishelper.DefaultRedis.SetStringExpire(global.TextVerifiedToken, token, time.Duration(resp.ExpiresIn)*time.Second); err != nil { | ||||
| 			e.Log.Errorf("TextVerified登录失败,缓存Token失败 error: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return token, nil | ||||
| } | ||||
|  | ||||
| // 获取token | ||||
| func (e *SmsTextVerified) GetToken() (string, error) { | ||||
| 	token, err := redishelper.DefaultRedis.GetString(global.TextVerifiedToken) | ||||
| 	if err != nil && errors.Is(err, redis.Nil) { | ||||
| 		// token不存在,重新登录获取 | ||||
| 		return e.Login() | ||||
| 	} | ||||
|  | ||||
| 	if token == "" { | ||||
| 		return e.Login() | ||||
| 	} | ||||
|  | ||||
| 	return token, nil | ||||
| } | ||||
|  | ||||
| // 获取区域 | ||||
| func (e *SmsTextVerified) GetAreas() ([]string, error) { | ||||
|  | ||||
| 	// 这里可以实现获取地区的逻辑 | ||||
| 	// 例如从数据库或配置文件中读取地区信息 | ||||
| 	areas := []string{"Area1", "Area2", "Area3"} // 示例数据 | ||||
| 	return areas, nil | ||||
| } | ||||
|  | ||||
| // 获取服务列表 | ||||
| func (e *SmsTextVerified) GetServices() ([]dto.TextVerifiedServeResp, error) { | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.SmsTextVerified.Url, nil) | ||||
| 	headers, err := e.GetAuthHeader() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	resp := []dto.TextVerifiedServeResp{} | ||||
| 	url := fmt.Sprintf("%s?numberType=mobile&reservationType=%s", getServices, "verification") | ||||
| 	_, err1 := client.Get(url, headers, &resp) | ||||
| 	if err1 != nil { | ||||
| 		e.Log.Errorf("TextVerified获取服务列表失败 error: %v", err1) | ||||
| 		return nil, err1 | ||||
| 	} | ||||
|  | ||||
| 	return resp, nil | ||||
| } | ||||
|  | ||||
| // 同步服务列表 | ||||
| func (e *SmsTextVerified) SyncServices() error { | ||||
| 	services, err := e.GetServices() | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取服务列表失败: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// 提取所有服务的 Capability 作为查询条件 | ||||
| 	capabilities := make([]string, 0, len(services)) | ||||
| 	for _, service := range services { | ||||
| 		if strings.ToLower(service.Capability) != "sms" { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if utility.ContainsString(capabilities, service.ServiceName) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		capabilities = append(capabilities, service.ServiceName) | ||||
| 	} | ||||
|  | ||||
| 	// 批量查询已存在的服务 | ||||
| 	var existingServices []models.SmsServices | ||||
| 	err = e.Orm.Model(&models.SmsServices{}). | ||||
| 		Where("platform_code = ? AND code IN ?", global.SmsPlatformTextVerified, capabilities). | ||||
| 		Find(&existingServices).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("批量查询服务失败: %v", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// 创建现有服务的 Code 集合,便于快速查找 | ||||
| 	existingServiceCodes := make(map[string]struct{}, len(existingServices)) | ||||
| 	for _, svc := range existingServices { | ||||
| 		existingServiceCodes[svc.Code] = struct{}{} | ||||
| 	} | ||||
|  | ||||
| 	// 筛选出需要新增的服务 | ||||
| 	entitys := make([]models.SmsServices, 0) | ||||
| 	for _, service := range services { | ||||
| 		if _, exists := existingServiceCodes[service.ServiceName]; !exists && strings.ToLower(service.Capability) == "sms" { | ||||
| 			entity := models.SmsServices{ | ||||
| 				Name:              service.ServiceName, | ||||
| 				Code:              service.ServiceName, | ||||
| 				PlatformCode:      global.SmsPlatformTextVerified, | ||||
| 				ExpirationMinutes: 3, | ||||
| 				Status:            2, | ||||
| 			} | ||||
| 			entitys = append(entitys, entity) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 批量创建新服务 | ||||
| 	if len(entitys) > 0 { | ||||
| 		err = e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 			if err := tx.CreateInBatches(&entitys, 500).Error; err != nil { | ||||
| 				e.Log.Errorf("同步服务列表失败: %v", err) | ||||
| 				return err | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 同步价格 | ||||
| func (e *SmsTextVerified) SyncPrices() error { | ||||
| 	var services []models.SmsServices | ||||
| 	var entity models.SmsServices | ||||
|  | ||||
| 	if err := e.Orm.Model(&entity).Where("platform_code = ? and status =1", global.SmsPlatformTextVerified).Find(&services).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range services { | ||||
| 		params := map[string]interface{}{} | ||||
|  | ||||
| 		price, code := e.GetPrice(0, v.Code) | ||||
|  | ||||
| 		if code == http.StatusOK { | ||||
| 			params["price"] = price | ||||
| 		} | ||||
|  | ||||
| 		time.Sleep(time.Microsecond * 500) // 避免请求过快被限制 | ||||
| 		longPrice, code := e.GetPrice(1, v.Code) | ||||
| 		if code == http.StatusOK { | ||||
| 			params["long_price"] = longPrice | ||||
| 		} | ||||
|  | ||||
| 		if len(params) > 0 { | ||||
| 			if err := e.Orm.Model(&entity).Where("platform_code = ? and code = ?", global.SmsPlatformTextVerified, v.Code).Updates(params).Error; err != nil { | ||||
| 				e.Log.Errorf("同步服务价格失败: %v", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 号码租赁 | ||||
| // getType 0-短效 1-长效 | ||||
| // service 服务code | ||||
| // maxPrice 最大价格 | ||||
| // period 时长(月=30天) | ||||
| func (e SmsTextVerified) GetNumberForApi(typ int, serviceCode string, price decimal.Decimal, period int) (dto.SmsPhoneGetPhoneResp, int) { | ||||
| 	//这个平台的所有号码都是美国号码 默认国家代码都是 +1 | ||||
| 	var err error | ||||
| 	var createResp dto.TextVerifiedResp | ||||
| 	var result dto.SmsPhoneGetPhoneResp | ||||
| 	switch typ { | ||||
| 	case 0: | ||||
| 		var code int | ||||
| 		createResp, code = e.CreateVerification(serviceCode) | ||||
|  | ||||
| 		if code == statuscode.Success && createResp.Href != "" { | ||||
| 			bytes, err := e.doRequest(&createResp) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("短效号码 获取号码失败:%v", e) | ||||
| 				return result, 0 | ||||
| 			} | ||||
|  | ||||
| 			resp := dto.VerificationDTO{} | ||||
|  | ||||
| 			if err := sonic.Unmarshal(bytes, &resp); err != nil { | ||||
| 				e.Log.Errorf("短效号码 获取号码失败:%v", e) | ||||
| 				return result, 0 | ||||
| 			} | ||||
|  | ||||
| 			result.Id = resp.ID | ||||
| 			result.Phone = "1" + resp.Number | ||||
| 			result.EndAt = &resp.EndsAt | ||||
|  | ||||
| 			return result, statuscode.Success | ||||
| 		} else if code == statuscode.Success && createResp.Href == "" { | ||||
| 			e.Log.Errorf("获取号码没有 返回值") | ||||
| 			return result, statuscode.ServerError | ||||
| 		} else { | ||||
| 			e.Log.Errorf("获取号码失败:%v", err) | ||||
| 			return result, statuscode.ServerError | ||||
| 		} | ||||
| 	case 1: | ||||
| 		createResp, err = e.CreateRental(serviceCode) | ||||
|  | ||||
| 		if err == nil && createResp.Href != "" { | ||||
| 			saleId := getIdByUrl(createResp.Href) | ||||
| 			bytes, err := e.doRequest(&createResp) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("通过销售id [%s] 获取号码失败:%v", saleId, err) | ||||
| 				return result, statuscode.ServerError | ||||
| 			} | ||||
|  | ||||
| 			resp := dto.SaleResponse{} | ||||
| 			if err := sonic.Unmarshal(bytes, &resp); err != nil { | ||||
| 				e.Log.Errorf("反序列化失败:%v", err) | ||||
|  | ||||
| 				return result, statuscode.ServerError | ||||
| 			} | ||||
|  | ||||
| 			if len(resp.Reservations) > 0 { | ||||
| 				for _, v := range resp.Reservations { | ||||
| 					if v.ID != "" { | ||||
| 						result.Id = v.ID | ||||
| 						continue | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if result.Id == "" { | ||||
| 					e.Log.Errorf("获取长效号码失败,没有返回值") | ||||
| 					return result, statuscode.ServerError | ||||
| 				} | ||||
|  | ||||
| 				detail, code := e.GetRentalDetail(result.Id) | ||||
|  | ||||
| 				if code != statuscode.Success { | ||||
| 					return result, code | ||||
| 				} | ||||
|  | ||||
| 				result.Phone = "1" + detail.Number | ||||
| 				endTime := resp.UpdatedAt.AddDate(0, 0, 30) | ||||
| 				result.EndAt = &(endTime) // 30天后过期 | ||||
|  | ||||
| 				return result, statuscode.Success | ||||
| 			} | ||||
|  | ||||
| 		} else if err == nil && createResp.Href == "" { | ||||
| 			e.Log.Errorf("获取长效号码失败,没有返回值") | ||||
| 			return result, statuscode.ServerError | ||||
| 		} else { | ||||
| 			if err == ErrOutOfStockOrUnavailable { | ||||
| 				return result, statuscode.SmsOutOfStockOrUnavailable | ||||
| 			} | ||||
| 			e.Log.Errorf("获取长效号码失败:%v", err) | ||||
| 			return result, statuscode.ServerError | ||||
| 		} | ||||
|  | ||||
| 	default: | ||||
| 		return result, statuscode.SmsPlatformUnavailable | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return result, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 执行查询请求 | ||||
| func (e *SmsTextVerified) doRequest(resp *dto.TextVerifiedResp) ([]byte, error) { | ||||
| 	bytes := []byte{} | ||||
| 	if resp.Href == "" { | ||||
| 		return bytes, errors.New("成功请求没有返回url") | ||||
| 	} | ||||
|  | ||||
| 	header, err := e.GetAuthHeader() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return bytes, fmt.Errorf("获取授权失败:%v", err) | ||||
| 	} | ||||
|  | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, "", header) | ||||
|  | ||||
| 	_, err = client.DoRequest(resp.Method, resp.Href, nil, nil, nil, &bytes) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return bytes, fmt.Errorf("请求失败:%v", err) | ||||
| 	} | ||||
|  | ||||
| 	return bytes, nil | ||||
| } | ||||
|  | ||||
| // 长号码租赁 | ||||
| func (e *SmsTextVerified) CreateRental(serviceCode string) (dto.TextVerifiedResp, error) { | ||||
| 	req := dto.TextVerifiedCreateRewalReq{ | ||||
| 		ServiceName: serviceCode, | ||||
| 		Capability:  "sms", | ||||
| 		NumberType:  "mobile", | ||||
| 		IsRenewable: true, | ||||
| 		AlwaysOn:    false, | ||||
| 		Duration:    "thirtyDay", | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedResp{} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取头信息失败 error: %d", code) | ||||
| 		return resp, ErrUnAuth | ||||
| 	} | ||||
|  | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
| 	status, err := client.Post(createRental, req, headers, &resp) | ||||
|  | ||||
| 	// 201 成功 | ||||
| 	if status == http.StatusCreated || resp.Href != "" { | ||||
| 		return resp, nil | ||||
| 	} else if err != nil { | ||||
| 		if strings.Contains(err.Error(), "Out of stock or unavailable") { | ||||
| 			e.Log.Errorf("缺货或服务不可用 %v", err) | ||||
| 			return resp, ErrOutOfStockOrUnavailable | ||||
| 		} | ||||
| 		e.Log.Errorf("短信平台不可用 error: %v", err) | ||||
| 		return resp, err | ||||
| 	} | ||||
|  | ||||
| 	return resp, fmt.Errorf("短信平台不可用 status: %v, error: %v", status, err) | ||||
| } | ||||
|  | ||||
| // 获取长效号码详情 | ||||
| func (e *SmsTextVerified) GetRentalDetail(id string) (dto.VerificationRentalDetailResp, int) { | ||||
| 	result := dto.VerificationRentalDetailResp{} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
| 	if code != statuscode.Success { | ||||
| 		return result, code | ||||
| 	} | ||||
|  | ||||
| 	_, err := client.Get(fmt.Sprintf(rentalDetail, id), nil, &result) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("查询长效详情失败 id:%s error: %v", id, err) | ||||
| 		return result, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return result, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 唤醒号码 | ||||
| // returns activationId,messageId, code | ||||
| func (e SmsTextVerified) getExtraActivation(id string) (string, string, int) { | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return "", "", code | ||||
| 	} | ||||
|  | ||||
| 	req := dto.TextVerifiedWakeUpReq{ | ||||
| 		RevervationId: id, | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedResp{} | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
| 	status, err := client.Post(wakeUp, req, headers, &resp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err) | ||||
| 		return "", "", statuscode.ServerError | ||||
| 	} else if status == http.StatusCreated || status == http.StatusOK { | ||||
| 		if resp.Method != "" && resp.Href != "" { | ||||
| 			bytes, err := e.doRequest(&resp) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err) | ||||
| 				return "", "", statuscode.ServerError | ||||
| 			} | ||||
|  | ||||
| 			detailResp := dto.TextVerifiedWakeUpResp{} | ||||
|  | ||||
| 			if err := sonic.Unmarshal(bytes, &detailResp); err != nil { | ||||
| 				e.Log.Errorf("唤醒号码反序列化失败 id:%s error: %v", id, err) | ||||
| 				return "", "", statuscode.ServerError | ||||
| 			} | ||||
|  | ||||
| 			return detailResp.ReservationId, detailResp.Id, statuscode.Success | ||||
| 		} else { | ||||
| 			e.Log.Errorf("唤醒号码失败 id:%s error: %v", id, err) | ||||
| 			return "", "", statuscode.ServerError | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return "", "", statuscode.ServerError | ||||
| } | ||||
|  | ||||
| // 获取价格 | ||||
| // getType 0-短效 1-长效 | ||||
| // returns decimal.Decimal(单价), int(状态code) | ||||
| func (e *SmsTextVerified) GetPrice(typ int, serviceName string) (decimal.Decimal, int) { | ||||
| 	switch typ { | ||||
| 	case 1: | ||||
| 		return e.GetRentalPrice(serviceName) | ||||
| 	case 0: | ||||
| 		return e.GetVerificationPrice(serviceName) | ||||
| 	default: | ||||
| 		return decimal.Zero, statuscode.SmsInvalidType | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取长租价格 | ||||
| func (e *SmsTextVerified) GetRentalPrice(serviceName string) (decimal.Decimal, int) { | ||||
| 	req := dto.TextVerifiedPriceReq{ | ||||
| 		ServiceName: serviceName, | ||||
| 		Capability:  "sms", | ||||
| 		NumberType:  "mobile", | ||||
| 		AreaCode:    false, | ||||
| 		Duration:    "thirtyDay", | ||||
| 	} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取授权请求失败,status %d", code) | ||||
| 		return decimal.Zero, code | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedPriceResp{} | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
|  | ||||
| 	_, err := client.Post(rentalPrice, req, headers, &resp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取价格失败, error: %v", err) | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return resp.Price, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 获取单次验证码价格 | ||||
| func (e *SmsTextVerified) GetVerificationPrice(sericeName string) (decimal.Decimal, int) { | ||||
| 	params := map[string]interface{}{ | ||||
| 		"serviceName": sericeName, | ||||
| 		"areaCode":    false, | ||||
| 		"carrier":     false, | ||||
| 		"capability":  "sms", | ||||
| 		"numberType":  "mobile", | ||||
| 	} | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取授权请求失败,status %d", code) | ||||
| 		return decimal.Zero, code | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedPriceResp{} | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
| 	_, err := client.Post(verificationPrice, params, headers, &resp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("获取价格失败, error: %v", err) | ||||
| 		return decimal.Zero, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return resp.Price, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 单次接收 | ||||
| func (e *SmsTextVerified) CreateVerification(serviceCode string) (dto.TextVerifiedResp, int) { | ||||
| 	req := dto.TextVerifiedCreateRewalReq{ | ||||
| 		ServiceName: serviceCode, | ||||
| 		Capability:  "sms", | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.TextVerifiedResp{} | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return resp, code | ||||
| 	} | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
| 	status, err := client.Post(createVerification, req, headers, &resp) | ||||
|  | ||||
| 	// 201 成功 | ||||
| 	if status == http.StatusCreated { | ||||
| 		return resp, statuscode.Success | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("TextVerified创建单次收码失败 error: %v", err) | ||||
| 		return resp, statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	return resp, statuscode.Success | ||||
| } | ||||
|  | ||||
| // 取消验证码 | ||||
| // typ 0-短效 1-长效 | ||||
| func (e SmsTextVerified) CancelRental(id string, typ int) int { | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		return code | ||||
| 	} | ||||
|  | ||||
| 	var url string | ||||
|  | ||||
| 	if typ == 0 { | ||||
| 		url = fmt.Sprintf(cancelVerification, id) | ||||
| 	} else { | ||||
| 		url = fmt.Sprintf(cancelRental, id) | ||||
| 	} | ||||
|  | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
| 	_, code, err := client.PostRaw(url, headers) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		if strings.Contains(err.Error(), "Self service refund not permitted for this rental") { | ||||
| 			return statuscode.SmsRentalRefundNotPermitted | ||||
| 		} | ||||
|  | ||||
| 		e.Log.Errorf("TextVerified取消验证码失败 error: %v", err) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	if code != http.StatusOK { | ||||
| 		e.Log.Errorf("TextVerified取消验证码失败,返回状态码 %d", code) | ||||
| 	} | ||||
|  | ||||
| 	return statuscode.Success | ||||
| } | ||||
|  | ||||
| func (e *SmsTextVerified) GetTextVerifiedAuthClient() (*httphelper.HTTPClient, int) { | ||||
| 	header, err := e.GetAuthHeader() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("取消验证码获取token失败 error: %v", err) | ||||
| 		return nil, statuscode.ServerError | ||||
| 	} | ||||
| 	client := httphelper.NewHTTPClient(10*time.Second, config.ExtConfig.SmsTextVerified.Url, header) | ||||
|  | ||||
| 	return client, statuscode.Success | ||||
| } | ||||
|  | ||||
| // TextVerifiedWebHook TextVerified短信发送记录回调 | ||||
| func (e *SmsTextVerified) TextVerifiedWebHook(req *dto.TextVerifiedWebHookReq) error { | ||||
| 	entity := &models.SmsPhone{} | ||||
|  | ||||
| 	switch req.Event { | ||||
| 	//续订回调 | ||||
| 	case "v2.rental.billingcycle.renewed": | ||||
| 		e.Log.Infof("续订回调 %v", req) | ||||
| 		data := dto.TextVerifiedWebHookRenewedReq{} | ||||
| 		val, err := sonic.MarshalString(req.Data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("数据类型错误 %v", req) | ||||
| 			return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		} | ||||
|  | ||||
| 		err = sonic.UnmarshalString(val, &data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("数据类型错误 %v", req) | ||||
| 			return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		} | ||||
| 	//过期回调 | ||||
| 	case "v2.rental.billingcycle.expired": | ||||
| 		e.Log.Infof("过期 %v", req) | ||||
| 		// data := dto.TextVerifiedWebHookBillingCycleExpiredReq{} | ||||
| 		// val, err := sonic.MarshalString(req.Data) | ||||
|  | ||||
| 		// if err != nil { | ||||
| 		// 	e.Log.Errorf("数据类型错误 %v", req) | ||||
| 		// 	return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		// } | ||||
|  | ||||
| 		// err = sonic.UnmarshalString(val, &data) | ||||
|  | ||||
| 		// if err != nil { | ||||
| 		// 	e.Log.Errorf("数据类型错误 %v", req) | ||||
| 		// 	return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		// } | ||||
|  | ||||
| 		// if err := e.Orm.Model(&entity).Where("new_activation_id =? and status ='1'", data.RevervationId).Updates(map[string]interface{}{"status": 3}).Error; err != nil { | ||||
| 		// 	return err | ||||
| 		// } | ||||
| 	//收到短信回调 | ||||
| 	case "v2.sms.received": | ||||
| 		e.Log.Infof("收到短信回调 %v", req) | ||||
| 		data := dto.TextVerifiedWebHookSmsReceivedReq{} | ||||
|  | ||||
| 		val, err := sonic.MarshalString(req.Data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("数据类型错误 %v", req) | ||||
| 			return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		} | ||||
|  | ||||
| 		err = sonic.UnmarshalString(val, &data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("数据类型错误 %v", req) | ||||
| 			return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		} | ||||
|  | ||||
| 		if err := e.Orm.Model(&entity).Where("new_activation_id =? and status =1", data.ReservationId).Updates(map[string]interface{}{"status": "2", "actived": 2, "code": data.ParsedCode}).Error; err != nil { | ||||
| 			e.Log.Errorf("数据类型错误 %v", req) | ||||
| 			return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		} | ||||
| 	//预定订单完成回调 | ||||
| 	case "v2.rental.backorder.fulfilled": | ||||
| 		e.Log.Infof("预定订单完成回调 %v", req) | ||||
| 		data := dto.TextVerifiedWebHookFulfilledReq{} | ||||
| 		val, err := sonic.MarshalString(req.Data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("数据类型错误 %v", req) | ||||
| 			return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		} | ||||
|  | ||||
| 		err = sonic.UnmarshalString(val, &data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("数据类型错误 %v", req) | ||||
| 			return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		} | ||||
|  | ||||
| 		// if err:=e.Orm.Model(&entity).Where("new_activation_id =? ",data.) | ||||
| 	// 预定订单创建 | ||||
| 	case "v2.reservation.created": | ||||
| 		// e.Log.Infof("预定订单创建 %v", req) | ||||
| 		// data := dto.TextVerifiedWebHookReservationCreatedReq{} | ||||
| 		// val, err := sonic.MarshalString(req.Data) | ||||
|  | ||||
| 		// if err != nil { | ||||
| 		// 	e.Log.Errorf("数据类型错误 %v", req) | ||||
| 		// 	return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		// } | ||||
|  | ||||
| 		// err = sonic.UnmarshalString(val, &data) | ||||
|  | ||||
| 		// if err != nil { | ||||
| 		// 	e.Log.Errorf("数据类型错误 %v", req) | ||||
| 		// 	return fmt.Errorf("数据类型错误 %s", req.Data) | ||||
| 		// } | ||||
| 	default: | ||||
| 		e.Log.Errorf("未知的事件类型 %s", req.Event) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 续期 | ||||
| // typ 0-短效 1-长效 | ||||
| func (e *SmsTextVerified) Renew(activationId string, status bool) int { | ||||
| 	url := fmt.Sprintf(updateRentalRenewStatus, activationId) | ||||
|  | ||||
| 	client, code := e.GetTextVerifiedAuthClient() | ||||
|  | ||||
| 	if code != statuscode.Success { | ||||
| 		e.Log.Errorf("获取长效续期失败 %d", code) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
|  | ||||
| 	params := map[string]interface{}{ | ||||
| 		"includeForRenewal": status, | ||||
| 	} | ||||
|  | ||||
| 	headers := map[string]string{ | ||||
| 		"Content-Type": "application/json", | ||||
| 	} | ||||
| 	mapData := map[string]interface{}{} | ||||
| 	statuCode, err := client.Post(url, params, headers, &mapData) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("修改长效续期状态失败 %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if statuCode == http.StatusOK { | ||||
| 		return statuscode.Success | ||||
| 	} else { | ||||
| 		e.Log.Errorf("修改长效续期状态失败 %d", statuCode) | ||||
| 		return statuscode.ServerError | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 获取授权header头 | ||||
| func (e *SmsTextVerified) GetAuthHeader() (map[string]string, error) { | ||||
| 	token, err := e.GetToken() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	headers := map[string]string{ | ||||
| 		"Authorization": "Bearer " + token, | ||||
| 	} | ||||
| 	return headers, nil | ||||
| } | ||||
|  | ||||
| // 获取id 根据url (https://www.textverified.com/api/pub/v2/sales/{id}}") | ||||
| func getIdByUrl(URLString string) string { | ||||
| 	parsedNewURL, _ := url.Parse(URLString) | ||||
| 	newID := path.Base(parsedNewURL.Path) | ||||
|  | ||||
| 	if isValidID(newID) { | ||||
| 		return newID | ||||
| 	} else { | ||||
| 		return "" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func isValidID(s string) bool { | ||||
| 	return idPattern.MatchString(s) | ||||
| } | ||||
							
								
								
									
										53
									
								
								app/admin/service/sms_text_verified_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/admin/service/sms_text_verified_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/logger" | ||||
| ) | ||||
|  | ||||
| func TestSmsTextVerifiedLogin(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	s := SmsTextVerified{} | ||||
| 	s.Orm = db | ||||
| 	s.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	token, err := s.Login() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Login failed: %v", err) | ||||
| 	} else { | ||||
| 		t.Logf("Login succeeded, token: %s", token) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSmsTextVerifiedGetServices(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	s := SmsTextVerified{} | ||||
| 	s.Orm = db | ||||
| 	s.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	// Now, test GetServices with the valid token | ||||
| 	servicesResp, err := s.GetServices() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("GetServices failed: %v", err) | ||||
| 	} else { | ||||
| 		t.Logf("GetServices succeeded, services: %+v", servicesResp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSyncTextVerifiedServices(t *testing.T) { | ||||
| 	db := initSetting() | ||||
|  | ||||
| 	s := SmsTextVerified{} | ||||
| 	s.Orm = db | ||||
| 	s.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	err := s.SyncServices() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("SyncTextVerifiedServices failed: %v", err) | ||||
| 	} else { | ||||
| 		t.Log("SyncTextVerifiedServices succeeded") | ||||
| 	} | ||||
| } | ||||
| @ -162,6 +162,22 @@ func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *SysConfig) GetMapByKeys(keys []string) (map[string]dto.GetSysConfigByKEYForServiceResp, error) { | ||||
| 	listData := make([]dto.GetSysConfigByKEYForServiceResp, 0) | ||||
| 	err := e.GetWithKeyForList(keys, &listData) | ||||
| 	result := make(map[string]dto.GetSysConfigByKEYForServiceResp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return result, err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range listData { | ||||
| 		result[v.ConfigKey] = v | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // GetWithKey 根据Key获取SysConfig | ||||
| func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigByKEYForServiceResp) error { | ||||
| 	var err error | ||||
| @ -175,6 +191,31 @@ func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigB | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *SysConfig) GetWithKeyForList(keys []string, list *[]dto.GetSysConfigByKEYForServiceResp) error { | ||||
| 	if err := e.Orm.Model(&models.SysConfig{}).Where("config_key in ?", keys). | ||||
| 		Find(list).Error; err != nil { | ||||
| 		e.Log.Errorf("Service GetSysConfigByKey error:%s", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (e *SysConfig) GetWithKeyForMap(keys []string) (map[string]dto.GetSysConfigByKEYForServiceResp, error) { | ||||
| 	listData := make([]dto.GetSysConfigByKEYForServiceResp, 0) | ||||
| 	err := e.GetWithKeyForList(keys, &listData) | ||||
| 	result := make(map[string]dto.GetSysConfigByKEYForServiceResp) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return result, err | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range listData { | ||||
| 		result[v.ConfigKey] = v | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func (e *SysConfig) GetWithKeyList(c *dto.SysConfigGetToSysAppReq, list *[]models.SysConfig) error { | ||||
| 	var err error | ||||
| 	err = e.Orm. | ||||
|  | ||||
| @ -15,6 +15,33 @@ type SysDictData struct { | ||||
| 	service.Service | ||||
| } | ||||
|  | ||||
| func (e *SysDictData) GetMapByType(typ string) (map[string]models.SysDictData, error) { | ||||
| 	datas, err := e.GetByType(typ) | ||||
| 	result := make(map[string]models.SysDictData) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	for _, data := range datas { | ||||
| 		result[data.DictValue] = data | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // 根据 type获取字典数据 | ||||
| func (e SysDictData) GetByType(typ string) ([]models.SysDictData, error) { | ||||
| 	var datas []models.SysDictData | ||||
|  | ||||
| 	if err := e.Orm.Model(&models.SysDictData{}).Where("dict_type = ?", typ).Find(&datas).Error; err != nil { | ||||
| 		e.Log.Errorf("根据type获取字典数据失败,\r\n失败信息 %s", err.Error()) | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return datas, nil | ||||
| } | ||||
|  | ||||
| // GetPage 获取列表 | ||||
| func (e *SysDictData) GetPage(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData, count *int64) error { | ||||
| 	var err error | ||||
|  | ||||
| @ -50,6 +50,9 @@ func (e SysUser) Register(req *dto.RegisterReq) (dto.SysUserRegisterResp, int) { | ||||
| 	} | ||||
|  | ||||
| 	data.RoleId = role.RoleId | ||||
| 	memberApiService := MemberApi{} | ||||
| 	memberApiService.Log = e.Log | ||||
| 	var memberApi models.MemberApi | ||||
|  | ||||
| 	err := e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		if err1 := tx.Create(&data).Error; err1 != nil { | ||||
| @ -67,6 +70,13 @@ func (e SysUser) Register(req *dto.RegisterReq) (dto.SysUserRegisterResp, int) { | ||||
| 		result.Token = token | ||||
| 		result.Expire = int(expire.Unix()) | ||||
|  | ||||
| 		memberApiService.Orm = tx | ||||
|  | ||||
| 		if err1 := memberApiService.CreateApi(data.UserId, &memberApi, 0); err1 != nil { | ||||
| 			e.Log.Errorf("创建用户失败 %v", err1) | ||||
| 			return err1 | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| @ -170,11 +180,27 @@ func (e *SysUser) Insert(c *dto.SysUserInsertReq) error { | ||||
| 		return err | ||||
| 	} | ||||
| 	c.Generate(&data) | ||||
| 	err = e.Orm.Create(&data).Error | ||||
| 	if err != nil { | ||||
| 		e.Log.Errorf("db error: %s", err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	memberApiService := MemberApi{} | ||||
| 	memberApiService.Log = e.Log | ||||
|  | ||||
| 	err = e.Orm.Transaction(func(tx *gorm.DB) error { | ||||
| 		err = tx.Create(&data).Error | ||||
| 		if err != nil { | ||||
| 			e.Log.Errorf("db error: %s", err) | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		memberApiService.Orm = tx | ||||
|  | ||||
| 		if err1 := memberApiService.CreateApi(data.UserId, &models.MemberApi{}, 0); err1 != nil { | ||||
| 			e.Log.Errorf("创建用户API失败, %s", err1) | ||||
| 			return err1 | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -19,6 +19,7 @@ func InitJob() { | ||||
| 		"SmsJob":               SmsJob{},               //短信定时查询验证码 | ||||
| 		"SmsRenewalJob":        SmsRenewalJob{},        //短信定时自动续期 | ||||
| 		"AutoDeleteJob":        AutoDeleteJob{},        //定时删除任务 | ||||
| 		"SmsPriceJob":          SmsPriceJob{},          // 短信价格定时同步 | ||||
| 		// ... | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -7,6 +7,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| type SmsJob struct{} | ||||
| type SmsPriceJob struct{} | ||||
|  | ||||
| // 定时查询结果 | ||||
| func (j SmsJob) Exec(args interface{}) error { | ||||
| @ -16,3 +17,17 @@ func (j SmsJob) Exec(args interface{}) error { | ||||
|  | ||||
| 	return phoneService.SyncCodes() | ||||
| } | ||||
|  | ||||
| // 定时同步价格 | ||||
| func (j SmsPriceJob) Exec(args interface{}) error { | ||||
| 	smsService := service.SmsServices{} | ||||
| 	smsService.Orm = GetDb() | ||||
| 	smsService.Log = logger.NewHelper(logger.DefaultLogger) | ||||
|  | ||||
| 	// 同步价格 | ||||
| 	if err := smsService.SyncPrices(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -25,3 +25,13 @@ func TestSmsRenew(t *testing.T) { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSmsPriceJob(t *testing.T) { | ||||
| 	initSetting() | ||||
| 	config.ExtConfig.DaisysmsUrl = "https://daisysms.com/stubs/handler_api.php" | ||||
| 	job := SmsPriceJob{} | ||||
|  | ||||
| 	if err := job.Exec(nil); err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -5,8 +5,9 @@ import ( | ||||
| 	"go-admin/app/admin/service" | ||||
| 	"go-admin/app/admin/service/dto" | ||||
| 	"go-admin/config" | ||||
| 	"go-admin/utils/redishelper" | ||||
| 	"go-admin/utils/utility" | ||||
| 	"io/ioutil" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| @ -26,6 +27,19 @@ const ( | ||||
|  | ||||
| // trx 链上支付定时查询 | ||||
| func (j TrxPaymentJob) Exec(arg interface{}) error { | ||||
| 	key := fmt.Sprintf("pre_order:%s", "*") | ||||
| 	keys, err := redishelper.DefaultRedis.ScanKeys(key) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		logger.Error("查询redis key失败", err) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if len(keys) == 0 { | ||||
| 		logger.Info("没有待处理订单") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	configService := service.SysConfig{} | ||||
| 	configService.Orm = GetDb() | ||||
| 	req := dto.SysConfigByKeyReq{} | ||||
| @ -82,19 +96,31 @@ func (j TrxPaymentJob) Exec(arg interface{}) error { | ||||
| // GetTRC20Transfers 获取指定 TRC20 代币的交易记录 | ||||
| func GetTRC20Transfers(contractAddress, accountAddress string, minTimestamp, maxTimestamp int64) ([]dto.TRC20Transfer, error) { | ||||
| 	url := fmt.Sprintf("%s/v1/accounts/%s/transactions/trc20?contract_address=%s", config.ExtConfig.TrxGridUrl, accountAddress, contractAddress) | ||||
|  | ||||
| 	if minTimestamp > 0 { | ||||
| 		url += fmt.Sprintf("&min_timestamp=%d", minTimestamp) | ||||
| 	} | ||||
| 	if maxTimestamp > 0 { | ||||
| 		url += fmt.Sprintf("&max_timestamp=%d", maxTimestamp) | ||||
| 	} | ||||
| 	resp, err := http.Get(url) | ||||
| 	// logger.Info("查询地址:", url) | ||||
| 	req, err := http.NewRequest("GET", url, nil) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create request: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// 设置请求头(包含 TronGrid API Key) | ||||
| 	req.Header.Set("Accept", "*/*") | ||||
| 	req.Header.Set("TRON-PRO-API-KEY", config.ExtConfig.TronApiKey) // 从配置读取 API Key | ||||
|  | ||||
| 	client := &http.Client{} | ||||
| 	resp, err := client.Do(req) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to send request: %v", err) | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	body, err := io.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to read response body: %v", err) | ||||
| 	} | ||||
| @ -102,6 +128,7 @@ func GetTRC20Transfers(contractAddress, accountAddress string, minTimestamp, max | ||||
| 	var result struct { | ||||
| 		Data []dto.TRC20Transfer `json:"data"` | ||||
| 	} | ||||
| 	// logger.Info("查询结果:", string(body)) | ||||
| 	if err := sonic.Unmarshal(body, &result); err != nil { | ||||
| 		return nil, fmt.Errorf("failed to unmarshal response: %v", err) | ||||
| 	} | ||||
|  | ||||
| @ -27,6 +27,10 @@ func initSetting() { | ||||
| 	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) | ||||
| 	sdk.Runtime.SetDb("default", db) | ||||
| 	config.ExtConfig.TrxGridUrl = "https://api.trongrid.io" | ||||
| 	config.ExtConfig.DaisysmsUrl = "https://daisysms.com/stubs/handler_api.php" | ||||
| 	config.ExtConfig.SmsTextVerified.ApiKey = "ZQ0swXnsaPpeGdwa3c7gT9U9I1Oh9WoDHx0amuYovvaHuqd5u6B4NBBUSUBjR" | ||||
| 	config.ExtConfig.SmsTextVerified.UserName = "webspan@proton.me" | ||||
| 	config.ExtConfig.SmsTextVerified.Url = "https://www.textverified.com" | ||||
| 	redishelper.InitDefaultRedis("127.0.0.1:6379", "", 4) | ||||
| 	redishelper.InitLockRedisConn("127.0.0.1:6379", "", "4") | ||||
| } | ||||
|  | ||||
| @ -186,9 +186,8 @@ func initRouter() { | ||||
| 	if config.SslConfig.Enable { | ||||
| 		r.Use(handler.TlsHandler()) | ||||
| 	} | ||||
| 	//r.Use(middleware.Metrics()) | ||||
| 	r.Use(common.Sentinel()). | ||||
| 		Use(common.RequestId(pkg.TrafficKey)). | ||||
| 	//r.Use(middleware.Metrics()).Use(common.Sentinel()). | ||||
| 	r.Use(common.RequestId(pkg.TrafficKey)). | ||||
| 		Use(api.SetRequestLogger) | ||||
|  | ||||
| 	common.InitMiddleware(r) | ||||
| @ -218,4 +217,10 @@ func initBusinesses() { | ||||
| 	if _, err := cliProxyService.GetTrafficInfo(); err != nil { | ||||
| 		os.Exit(-1) | ||||
| 	} | ||||
|  | ||||
| 	memberApiService := service.MemberApi{} | ||||
| 	memberApiService.Orm = cliProxyService.Orm | ||||
| 	memberApiService.Log = cliProxyService.Log | ||||
|  | ||||
| 	memberApiService.InitApis() | ||||
| } | ||||
|  | ||||
| @ -2,9 +2,16 @@ package global | ||||
|  | ||||
| //ConfigKey | ||||
| const ( | ||||
| 	TrafficProxyEffectiveDay         = "traffic_proxy_effective_day"          //流量代理有效天数 | ||||
| 	LongNumberRenewDeductionStandard = "long_number_renew_deduction_standard" //长效号码续费标准 | ||||
| 	IPRenewDeductionStandard         = "ip_renew_deduction_standard"          //长效IP续费kk扣除标准 | ||||
| 	//流量代理有效天数 | ||||
| 	TrafficProxyEffectiveDay = "traffic_proxy_effective_day" | ||||
| 	//短期号码扣除标准 | ||||
| 	ShortNumberDeductionStandard = "number_fee_short_term" | ||||
| 	//长效号码扣除标准 | ||||
| 	LongNumberDeductionStandard = "number_fee_long_term" | ||||
| 	//长效号码续费标准 | ||||
| 	LongNumberRenewDeductionStandard = "long_number_renew_deduction_standard" | ||||
| 	//长效IP续费kk扣除标准 | ||||
| 	IPRenewDeductionStandard = "ip_renew_deduction_standard" | ||||
|  | ||||
| 	ProxyExpiryTime = "proxy_expiry_time" //代理过期记录保留时长 | ||||
| 	SmsExpiryTime   = "sms_expiry_time"   //接码过期记录保留时长 | ||||
|  | ||||
							
								
								
									
										7
									
								
								common/global/sms_services.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								common/global/sms_services.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| package global | ||||
|  | ||||
| //短效平台code 字典【sms_platform】 | ||||
| const ( | ||||
| 	SmsPlatformDaisysms     = "daisysms" | ||||
| 	SmsPlatformTextVerified = "textverified" | ||||
| ) | ||||
							
								
								
									
										6
									
								
								common/global/text_verified.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/global/text_verified.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| package global | ||||
|  | ||||
| const ( | ||||
| 	// TextVerified Token | ||||
| 	TextVerifiedToken = "TextVerifiedToken" | ||||
| ) | ||||
| @ -1,13 +1,28 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/config" | ||||
| 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | ||||
| 	"go-admin/app/admin/models" | ||||
| 	"go-admin/common/middleware/handler" | ||||
| 	"go-admin/common/rediskey" | ||||
| 	"go-admin/common/statuscode" | ||||
| 	"go-admin/utils/redishelper" | ||||
|  | ||||
| 	"github.com/bytedance/sonic" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/config" | ||||
| 	"github.com/go-admin-team/go-admin-core/sdk/pkg" | ||||
| 	jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" | ||||
| ) | ||||
|  | ||||
| var ErrQueryUserId = errors.New("查询用户失败") | ||||
| var ErrNoAccount = errors.New("没有API用户") | ||||
| var ErrApiUnActived = errors.New("API未激活") | ||||
|  | ||||
| // AuthInit jwt验证new | ||||
| func AuthInit() (*jwt.GinJWTMiddleware, error) { | ||||
| 	timeout := time.Hour | ||||
| @ -33,4 +48,108 @@ func AuthInit() (*jwt.GinJWTMiddleware, error) { | ||||
| 		TimeFunc:        time.Now, | ||||
| 	}) | ||||
|  | ||||
| } | ||||
| } | ||||
|  | ||||
| // apikey授权认证 | ||||
| func FrontedAuth(c *gin.Context) { | ||||
| 	// 从请求头中获取 token 和 os | ||||
| 	apikey := c.GetHeader("x-api-key") | ||||
| 	// 如果 token 不存在,返回未登录的状态 | ||||
| 	if len(apikey) == 0 { | ||||
| 		err := ResponseWithStatus(c, statuscode.Unauthorized) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		c.Abort() // 停止后续中间件的执行 | ||||
| 		return | ||||
| 	} | ||||
| 	// 验证 token 并获取结果 | ||||
| 	key := fmt.Sprintf(rediskey.MemberApiKey, apikey) | ||||
| 	val, err := redishelper.DefaultRedis.GetString(key) | ||||
|  | ||||
| 	if err != nil || val == "" { | ||||
| 		ResponseWithStatus(c, statuscode.Unauthorized) | ||||
| 		c.Abort() // 停止后续中间件的执行 | ||||
| 	} | ||||
|  | ||||
| 	// 将解析后的 token 设置到请求头中 | ||||
| 	c.Set("apiKey", apikey) | ||||
| 	// 继续处理请求 | ||||
| 	c.Next() | ||||
| } | ||||
|  | ||||
| // ResponseWithStatus 带状态的响应 | ||||
| func ResponseWithStatus(ctx *gin.Context, code int, data ...interface{}) error { | ||||
| 	resp := statuscode.Response{ | ||||
| 		Code: code, | ||||
| 		Msg:  "un authorized", | ||||
| 	} | ||||
|  | ||||
| 	// resp.RequestID = pkg.GenerateMsgIDFromContext(ctx) | ||||
| 	if len(data) > 0 { | ||||
| 		resp.Data = data[0] | ||||
| 	} | ||||
|  | ||||
| 	switch code { | ||||
| 	case 401, 500, 405, 404: | ||||
| 		ctx.JSON(code, resp) | ||||
| 	default: | ||||
| 		ctx.JSON(200, resp) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 获取ApiKey用户id | ||||
| func GetUserIdWithApiKey(ctx *gin.Context) (int, error) { | ||||
| 	apikey, ok := ctx.Get("apiKey") | ||||
| 	if !ok { | ||||
| 		return 0, errors.New("apiKey not found") | ||||
| 	} | ||||
| 	return strconv.Atoi(apikey.(string)) | ||||
| } | ||||
|  | ||||
| // 获取用户id 根据ApiKey | ||||
| func GetUserIdByApiKey(c *gin.Context) (int, error) { | ||||
| 	apiKey, ok := c.Get("apiKey") | ||||
| 	if !ok { | ||||
| 		return 0, errors.New("apiKey not found") | ||||
| 	} | ||||
| 	db, err := pkg.GetOrm(c) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
|  | ||||
| 	var userId int | ||||
| 	val, err := redishelper.DefaultRedis.GetString(fmt.Sprintf(rediskey.MemberApiKey, apiKey)) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return userId, err | ||||
| 	} | ||||
|  | ||||
| 	if val == "" { | ||||
| 		var user models.MemberApi | ||||
|  | ||||
| 		if dbErr := db.Model(&user).Where("api_key = ?", apiKey).First(&user).Error; dbErr != nil { | ||||
| 			return userId, ErrNoAccount | ||||
| 		} | ||||
|  | ||||
| 		if user.Status != 1 { | ||||
| 			return userId, ErrApiUnActived | ||||
| 		} | ||||
|  | ||||
| 		userId = user.UserId | ||||
| 	} else { | ||||
| 		data := models.MemberApi{} | ||||
| 		err = sonic.UnmarshalString(val, &data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return userId, err | ||||
| 		} | ||||
|  | ||||
| 		userId = data.UserId | ||||
| 	} | ||||
|  | ||||
| 	return userId, nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										6
									
								
								common/rediskey/member_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								common/rediskey/member_api.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| package rediskey | ||||
|  | ||||
| const ( | ||||
| 	//用户api | ||||
| 	MemberApiKey = "member_api:%s" | ||||
| ) | ||||
| @ -2,6 +2,14 @@ package statuscode | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| type Response struct { | ||||
| 	Status    int         `json:"status"` | ||||
| 	Code      int         `json:"code"` | ||||
| 	Msg       string      `json:"msg"` | ||||
| 	Data      interface{} `json:"data"` | ||||
| 	RequestID string      `json:"RequestId"` | ||||
| } | ||||
|  | ||||
| var StatusCodeZh = map[int]string{ | ||||
| 	Success:                "成功", | ||||
| 	AccountExisted:         "账号已存在", | ||||
| @ -17,15 +25,22 @@ var StatusCodeZh = map[int]string{ | ||||
| 	MaxPriceExceeded:       "超过最大接受单价", | ||||
| 	NoNumbers:              "号码不足", | ||||
| 	RentalsNotFinished:     "需要先完成部分租赁才能继续租赁", | ||||
| 	Unauthorized:           "未授权", | ||||
| 	ApiUnActived:           "API未激活", | ||||
| 	InvalidParams:          "参数错误", | ||||
|  | ||||
| 	SmsCancel:             "短信验证码_手机号过期", | ||||
| 	SmsNoActivation:       "短信验证码_手机号不存在", | ||||
| 	SmsWaitCode:           "短信验证码_等待验证码", | ||||
| 	SmsLongNumWaitCode:    "短信验证码_长效号码已唤醒", | ||||
| 	SmsNotExisted:         "号码不存在", | ||||
| 	SmsNotExpired:         "号码未过期无法删除", | ||||
| 	SmsNotAutoRenew:       "短效号码无法自动续期", | ||||
| 	SmsServiceUnavailable: "%s服务暂不可用", | ||||
| 	SmsCancel:                   "短信验证码_手机号过期", | ||||
| 	SmsNoActivation:             "短信验证码_手机号不存在", | ||||
| 	SmsWaitCode:                 "短信验证码_等待验证码", | ||||
| 	SmsLongNumWaitCode:          "短信验证码_长效号码已唤醒", | ||||
| 	SmsNotExisted:               "号码不存在", | ||||
| 	SmsNotExpired:               "号码未过期无法删除", | ||||
| 	SmsNotAutoRenew:             "短效号码无法自动续期", | ||||
| 	SmsServiceUnavailable:       "%s服务暂不可用", | ||||
| 	SmsPlatformUnavailable:      "通道不可用", | ||||
| 	SmsInvalidType:              "短信验证码_无效类型", | ||||
| 	SmsOutOfStockOrUnavailable:  "短信验证码_缺货或服务不可用", | ||||
| 	SmsRentalRefundNotPermitted: "短信验证码_租赁退款不允许", | ||||
| } | ||||
|  | ||||
| var StatusCodeEn = map[int]string{ | ||||
| @ -43,15 +58,22 @@ var StatusCodeEn = map[int]string{ | ||||
| 	MaxPriceExceeded:       "max price exceeded", | ||||
| 	NoNumbers:              "no numbers", | ||||
| 	RentalsNotFinished:     "need to finish some rentals before renting more", | ||||
| 	Unauthorized:           "un authorized", | ||||
| 	ApiUnActived:           "API un actived.", | ||||
| 	InvalidParams:          "invalid params", | ||||
|  | ||||
| 	SmsCancel:             "sms code expired", | ||||
| 	SmsNoActivation:       "sms code not exist", | ||||
| 	SmsWaitCode:           "sms code wait for input", | ||||
| 	SmsLongNumWaitCode:    "sms code long num wake up", | ||||
| 	SmsNotExisted:         "number not exist", | ||||
| 	SmsNotExpired:         "number not expired, can not delete", | ||||
| 	SmsNotAutoRenew:       "num can not auto renew", | ||||
| 	SmsServiceUnavailable: "%s service unavailable", | ||||
| 	SmsCancel:                   "sms code expired", | ||||
| 	SmsNoActivation:             "sms code not exist", | ||||
| 	SmsWaitCode:                 "sms code wait for input", | ||||
| 	SmsLongNumWaitCode:          "sms code long num wake up", | ||||
| 	SmsNotExisted:               "number not exist", | ||||
| 	SmsNotExpired:               "number not expired, can not delete", | ||||
| 	SmsNotAutoRenew:             "num can not auto renew", | ||||
| 	SmsServiceUnavailable:       "%s service unavailable", | ||||
| 	SmsPlatformUnavailable:      "channel unavailable", | ||||
| 	SmsInvalidType:              "sms type invalid", | ||||
| 	SmsOutOfStockOrUnavailable:  "sms out of stock or unavailable", | ||||
| 	SmsRentalRefundNotPermitted: "sms rental refund not permitted", | ||||
| } | ||||
|  | ||||
| // GetMsg 获取状态码对应的消息 | ||||
| @ -108,6 +130,12 @@ const ( | ||||
| 	NoNumbers = 10012 | ||||
| 	// Need to finish some rentals before renting more | ||||
| 	RentalsNotFinished = 10013 | ||||
| 	// 未授权 | ||||
| 	Unauthorized = 10014 | ||||
| 	// Api未激活 | ||||
| 	ApiUnActived = 10015 | ||||
| 	//参数错误 | ||||
| 	InvalidParams = 10016 | ||||
|  | ||||
| 	//短信验证码_手机号过期 | ||||
| 	SmsCancel = 20014 | ||||
| @ -125,4 +153,12 @@ const ( | ||||
| 	SmsNotAutoRenew = 20020 | ||||
| 	//短信验证码_服务暂不可用 | ||||
| 	SmsServiceUnavailable = 20021 | ||||
| 	//短信-通道不可用 | ||||
| 	SmsPlatformUnavailable = 20022 | ||||
| 	//短信-未知类型 | ||||
| 	SmsInvalidType = 20023 | ||||
| 	// 短信-缺货或服务不可用 | ||||
| 	SmsOutOfStockOrUnavailable = 20024 | ||||
| 	// 短信-租赁退款不允许 | ||||
| 	SmsRentalRefundNotPermitted = 20025 | ||||
| ) | ||||
|  | ||||
| @ -10,11 +10,19 @@ var ExtConfig Extend | ||||
| // | ||||
| // 使用方法: config.ExtConfig......即可!! | ||||
| type Extend struct { | ||||
| 	AMap           AMap // 这里配置对应配置文件的结构即可 | ||||
| 	TrxGridUrl     string | ||||
| 	CliproxyUrl    string //cliproxy服务地址 | ||||
| 	CliproxyApiUrl string //cliproxy api地址 | ||||
| 	DaisysmsUrl    string //daisysms服务地址 | ||||
| 	AMap            AMap // 这里配置对应配置文件的结构即可 | ||||
| 	TrxGridUrl      string | ||||
| 	TronApiKey      string          `yaml:"tronApiKey"` | ||||
| 	CliproxyUrl     string          //cliproxy服务地址 | ||||
| 	CliproxyApiUrl  string          //cliproxy api地址 | ||||
| 	DaisysmsUrl     string          //daisysms服务地址 | ||||
| 	SmsTextVerified SmsTextVerified `yaml:"smsTextVerified"` //短信验证服务配置 | ||||
| } | ||||
|  | ||||
| type SmsTextVerified struct { | ||||
| 	Url      string `yaml:"url"` | ||||
| 	UserName string `yaml:"userName"` | ||||
| 	ApiKey   string `yaml:"apiKey"` | ||||
| } | ||||
|  | ||||
| type AMap struct { | ||||
|  | ||||
| @ -51,12 +51,19 @@ settings: | ||||
|      | ||||
|     #trx api | ||||
|     trxGridUrl: "https://api.trongrid.io" | ||||
|     tronApiKey: "223c129e-73f5-470f-9464-f9969846c134" | ||||
|     #cliproxy url | ||||
|     cliproxyUrl: "https://f.cliproxy.com" | ||||
|     cliproxyUrl: "https://f.cliproxy.com"  | ||||
|     #cliproxy api url | ||||
|     cliproxyApiUrl: "https://api.cliproxy.com" | ||||
|     #daisysms api url | ||||
|     daisysmsUrl: "https://daisysms.com/stubs/handler_api.php" | ||||
|     #sms text_verified | ||||
|     smsTextVerified: | ||||
|       url: "https://www.textverified.com" | ||||
|       userName: "webspan@proton.me" | ||||
|       apiKey: "ZQ0swXnsaPpeGdwa3c7gT9U9I1Oh9WoDHx0amuYovvaHuqd5u6B4NBBUSUBjR" | ||||
|  | ||||
|   cache: | ||||
|     redis: | ||||
|       addr: 127.0.0.1:6379 | ||||
|  | ||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| module go-admin | ||||
|  | ||||
| go 1.18 | ||||
| go 1.24 | ||||
|  | ||||
| require ( | ||||
| 	github.com/alibaba/sentinel-golang v1.0.4 | ||||
| @ -14,8 +14,10 @@ require ( | ||||
| 	github.com/go-admin-team/go-admin-core v1.4.1-0.20220809101213-21187928f7d9 | ||||
| 	github.com/go-admin-team/go-admin-core/sdk v1.4.1-0.20220809101213-21187928f7d9 | ||||
| 	github.com/go-redis/redis/v8 v8.11.5 | ||||
| 	github.com/golang-jwt/jwt v3.2.2+incompatible | ||||
| 	github.com/google/uuid v1.3.0 | ||||
| 	github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.12+incompatible | ||||
| 	github.com/jinzhu/copier v0.4.0 | ||||
| 	github.com/mssola/user_agent v0.5.2 | ||||
| 	github.com/opentracing/opentracing-go v1.1.0 | ||||
| 	github.com/pkg/errors v0.9.1 | ||||
| @ -77,7 +79,6 @@ require ( | ||||
| 	github.com/go-redis/redis/v7 v7.4.0 // indirect | ||||
| 	github.com/go-sql-driver/mysql v1.6.0 // indirect | ||||
| 	github.com/goccy/go-json v0.10.2 // indirect | ||||
| 	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect | ||||
| 	github.com/golang-jwt/jwt/v4 v4.4.2 // indirect | ||||
| 	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect | ||||
| 	github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect | ||||
| @ -97,7 +98,6 @@ require ( | ||||
| 	github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect | ||||
| 	github.com/jackc/pgtype v1.11.0 // indirect | ||||
| 	github.com/jackc/pgx/v4 v4.16.1 // indirect | ||||
| 	github.com/jinzhu/copier v0.4.0 // indirect | ||||
| 	github.com/jinzhu/inflection v1.0.0 // indirect | ||||
| 	github.com/jinzhu/now v1.1.5 // indirect | ||||
| 	github.com/josharian/intern v1.0.0 // indirect | ||||
|  | ||||
| @ -50,13 +50,13 @@ func (c *HTTPClient) applyHeaders(req *http.Request, customHeaders map[string]st | ||||
| // customHeaders: 自定义请求头,将覆盖默认请求头 | ||||
| // responseData: 用于存储响应数据的目标结构体(指针类型),如果为 nil 则表示不需要 JSON 解码 | ||||
| // rawResponse: 用于存储原始响应体字节切片(*[]byte),如果为 nil 则表示不需要原始响应 | ||||
| func (c *HTTPClient) doRequest( | ||||
| func (c *HTTPClient) DoRequest( | ||||
| 	method, path string, | ||||
| 	requestBody interface{}, | ||||
| 	customHeaders map[string]string, | ||||
| 	responseData interface{}, | ||||
| 	rawResponse *[]byte, // 新增参数:指向字节切片的指针,用于存储原始响应 | ||||
| ) error { | ||||
| ) (int, error) { | ||||
| 	// 拼接完整的 URL | ||||
| 	url := c.BaseURL + path | ||||
|  | ||||
| @ -65,7 +65,7 @@ func (c *HTTPClient) doRequest( | ||||
| 		// 将请求体编码为 JSON | ||||
| 		jsonBody, err := json.Marshal(requestBody) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("json marshal request body failed: %w", err) | ||||
| 			return -1, fmt.Errorf("json marshal request body failed: %w", err) | ||||
| 		} | ||||
| 		reqBodyReader = bytes.NewBuffer(jsonBody) | ||||
| 	} | ||||
| @ -73,7 +73,7 @@ func (c *HTTPClient) doRequest( | ||||
| 	// 创建新的 HTTP 请求 | ||||
| 	req, err := http.NewRequest(method, url, reqBodyReader) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("create http request failed: %w", err) | ||||
| 		return -1, fmt.Errorf("create http request failed: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 应用请求头 | ||||
| @ -85,14 +85,14 @@ func (c *HTTPClient) doRequest( | ||||
| 	// 发送请求 | ||||
| 	resp, err := c.Client.Do(req) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("send http request failed: %w", err) | ||||
| 		return -1, fmt.Errorf("send http request failed: %w", err) | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// 检查 HTTP 状态码 | ||||
| 	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { | ||||
| 		bodyBytes, _ := io.ReadAll(resp.Body) // 读取错误响应体 | ||||
| 		return fmt.Errorf("http request failed with status: %d, body: %s", resp.StatusCode, string(bodyBytes)) | ||||
| 		return resp.StatusCode, fmt.Errorf("http request failed with status: %d, body: %s", resp.StatusCode, string(bodyBytes)) | ||||
| 	} | ||||
|  | ||||
| 	// 解码响应(支持 gzip) | ||||
| @ -100,7 +100,7 @@ func (c *HTTPClient) doRequest( | ||||
| 	if resp.Header.Get("Content-Encoding") == "gzip" { | ||||
| 		gzipReader, err := gzip.NewReader(resp.Body) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("create gzip reader failed: %w", err) | ||||
| 			return http.StatusOK, fmt.Errorf("create gzip reader failed: %w", err) | ||||
| 		} | ||||
| 		defer gzipReader.Close() | ||||
| 		reader = gzipReader | ||||
| @ -109,7 +109,7 @@ func (c *HTTPClient) doRequest( | ||||
| 	// 首先读取整个响应体,然后决定如何处理 | ||||
| 	bodyBytes, err := io.ReadAll(reader) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("read response body failed: %w", err) | ||||
| 		return http.StatusOK, fmt.Errorf("read response body failed: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// 如果提供了原始响应目标,则填充它 | ||||
| @ -121,19 +121,19 @@ func (c *HTTPClient) doRequest( | ||||
| 	if responseData != nil { | ||||
| 		err = json.Unmarshal(bodyBytes, responseData) // 直接对字节切片使用 Unmarshal | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("json decode response body failed: %w", err) | ||||
| 			return http.StatusOK, fmt.Errorf("json decode response body failed: %w", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| 	return http.StatusOK, nil | ||||
| } | ||||
|  | ||||
| // Get 发送 GET 请求 | ||||
| // path: 请求路径 | ||||
| // customHeaders: 自定义请求头 | ||||
| // responseData: 用于存储响应数据的目标结构体(指针类型) | ||||
| func (c *HTTPClient) Get(path string, customHeaders map[string]string, responseData interface{}) error { | ||||
| 	return c.doRequest(http.MethodGet, path, nil, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| func (c *HTTPClient) Get(path string, customHeaders map[string]string, responseData interface{}) (int, error) { | ||||
| 	return c.DoRequest(http.MethodGet, path, nil, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| } | ||||
|  | ||||
| // Post 发送 POST 请求 | ||||
| @ -141,8 +141,8 @@ func (c *HTTPClient) Get(path string, customHeaders map[string]string, responseD | ||||
| // requestBody: 请求体数据 | ||||
| // customHeaders: 自定义请求头 | ||||
| // responseData: 用于存储响应数据的目标结构体(指针类型) | ||||
| func (c *HTTPClient) Post(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) error { | ||||
| 	return c.doRequest(http.MethodPost, path, requestBody, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| func (c *HTTPClient) Post(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) (int, error) { | ||||
| 	return c.DoRequest(http.MethodPost, path, requestBody, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| } | ||||
|  | ||||
| // PostWithContentType 发送 POST 请求,支持自定义 Content-Type(如 application/json 或 multipart/form-data) | ||||
| @ -232,17 +232,19 @@ func (c *HTTPClient) PostWithContentType(path string, requestBody interface{}, c | ||||
| // requestBody: 请求体数据 | ||||
| // customHeaders: 自定义请求头 | ||||
| // responseData: 用于存储响应数据的目标结构体(指针类型) | ||||
| func (c *HTTPClient) Put(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) error { | ||||
| 	return c.doRequest(http.MethodPut, path, requestBody, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| // returns 状态码 和 错误信息 | ||||
| func (c *HTTPClient) Put(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) (int, error) { | ||||
| 	return c.DoRequest(http.MethodPut, path, requestBody, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| } | ||||
|  | ||||
| // Delete 发送 DELETE 请求 | ||||
| // path: 请求路径 | ||||
| // customHeaders: 自定义请求头 | ||||
| // responseData: 用于存储响应数据的目标结构体(指针类型) | ||||
| func (c *HTTPClient) Delete(path string, customHeaders map[string]string, responseData interface{}) error { | ||||
| // returns 状态码 和 错误信息 | ||||
| func (c *HTTPClient) Delete(path string, customHeaders map[string]string, responseData interface{}) (int, error) { | ||||
| 	// DELETE 请求通常没有请求体,但某些 RESTful API 可能支持 | ||||
| 	return c.doRequest(http.MethodDelete, path, nil, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| 	return c.DoRequest(http.MethodDelete, path, nil, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| } | ||||
|  | ||||
| // Patch 发送 PATCH 请求 | ||||
| @ -250,20 +252,39 @@ func (c *HTTPClient) Delete(path string, customHeaders map[string]string, respon | ||||
| // requestBody: 请求体数据 | ||||
| // customHeaders: 自定义请求头 | ||||
| // responseData: 用于存储响应数据的目标结构体(指针类型) | ||||
| func (c *HTTPClient) Patch(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) error { | ||||
| 	return c.doRequest(http.MethodPatch, path, requestBody, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| // returns 状态码 和 错误信息 | ||||
| func (c *HTTPClient) Patch(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) (int, error) { | ||||
| 	return c.DoRequest(http.MethodPatch, path, requestBody, customHeaders, responseData, nil) // rawResponse 传递 nil | ||||
| } | ||||
|  | ||||
| // GetRaw 发送 GET 请求并返回原始响应体 | ||||
| // path: 请求路径 | ||||
| // customHeaders: 自定义请求头 | ||||
| // 返回值: 原始响应体字节切片或错误 | ||||
| func (c *HTTPClient) GetRaw(path string, customHeaders map[string]string) ([]byte, error) { | ||||
| // 状态码 | ||||
| // 错误信息 | ||||
| func (c *HTTPClient) GetRaw(path string, customHeaders map[string]string) ([]byte, int, error) { | ||||
| 	var raw []byte | ||||
| 	// responseData 传递 nil,rawResponse 传递 &raw | ||||
| 	err := c.doRequest(http.MethodGet, path, nil, customHeaders, nil, &raw) | ||||
| 	statusCode, err := c.DoRequest(http.MethodGet, path, nil, customHeaders, nil, &raw) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return nil, statusCode, err | ||||
| 	} | ||||
| 	return raw, nil | ||||
| 	return raw, statusCode, nil | ||||
| } | ||||
|  | ||||
| // PostRaw 发送 POST 请求并返回原始响应体 | ||||
| // path: 请求路径 | ||||
| // customHeaders: 自定义请求头 | ||||
| // 返回值: 原始响应体字节切片或错误 | ||||
| // 状态码 | ||||
| // 错误信息 | ||||
| func (c *HTTPClient) PostRaw(path string, customHeaders map[string]string) ([]byte, int, error) { | ||||
| 	var raw []byte | ||||
| 	// responseData 传递 nil,rawResponse 传递 &raw | ||||
| 	statusCode, err := c.DoRequest(http.MethodPost, path, nil, customHeaders, nil, &raw) | ||||
| 	if err != nil { | ||||
| 		return nil, statusCode, err | ||||
| 	} | ||||
| 	return raw, statusCode, nil | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user