init
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package abosend
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/random"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/templatex"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
const BaseURL = "https://smsapi.abosend.com"
|
||||
|
||||
type Config struct {
|
||||
ApiDomain string `json:"api_domain"`
|
||||
Access string `json:"access"`
|
||||
Secret string `json:"secret"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config *Config
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
type request struct {
|
||||
OrgCode string `json:"orgCode"`
|
||||
MobileArea string `json:"mobileArea"`
|
||||
Mobile string `json:"mobiles"`
|
||||
Content string `json:"content"`
|
||||
Rand string `json:"rand"`
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
SendCode string `json:"sendCode"`
|
||||
}
|
||||
}
|
||||
|
||||
func (l *response) Unmarshal(data []byte) error {
|
||||
return json.Unmarshal(data, &l)
|
||||
}
|
||||
|
||||
func NewClient(config Config) *Client {
|
||||
client := resty.New()
|
||||
if config.ApiDomain != "" {
|
||||
client.SetBaseURL(config.ApiDomain)
|
||||
} else {
|
||||
client.SetBaseURL(BaseURL)
|
||||
}
|
||||
return &Client{
|
||||
config: &config,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SendCode(area, mobile, code string) error {
|
||||
text, err := templatex.RenderToString(c.config.Template, map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render sms template: %s", err.Error())
|
||||
}
|
||||
randNumber := random.Key(6, 0)
|
||||
sign := tool.Md5Encode(fmt.Sprintf("%s%s%s%s", c.config.Access, text, randNumber, c.config.Secret), true)
|
||||
req := request{
|
||||
OrgCode: c.config.Access,
|
||||
MobileArea: fmt.Sprintf("+%s", area),
|
||||
Mobile: fmt.Sprintf("%s%s", area, mobile),
|
||||
Content: text,
|
||||
Rand: randNumber,
|
||||
Sign: sign,
|
||||
}
|
||||
resp, err := c.client.R().SetBody(req).ForceContentType("application/json").Post("/v2/api/sendSMS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("send sms failed, status code: %d", resp.StatusCode())
|
||||
}
|
||||
var result response
|
||||
err = result.Unmarshal(resp.Body())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal response: %s", err.Error())
|
||||
}
|
||||
if result.Code != 200 {
|
||||
return fmt.Errorf("send sms failed, code: %d, msg: %s", result.Code, result.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSendCodeContent(code string) string {
|
||||
text, _ := templatex.RenderToString(c.config.Template, map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
return text
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package abosend
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
t.Skipf("Skip TestNewClient test")
|
||||
client := createClient()
|
||||
err := client.SendCode("1", "", "223322")
|
||||
if err != nil {
|
||||
t.Errorf("TestNewClient() error = %v", err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("TestNewClient success")
|
||||
}
|
||||
|
||||
func TestClient_GetSendCodeContent(t *testing.T) {
|
||||
t.Skipf("Skip TestClient_GetSendCodeContent test")
|
||||
client := createClient()
|
||||
content := client.GetSendCodeContent("223322")
|
||||
t.Logf("TestClient_GetSendCodeContent() = %v", content)
|
||||
}
|
||||
|
||||
func createClient() *Client {
|
||||
return NewClient(Config{
|
||||
ApiDomain: "https://smsapi.abosend.com",
|
||||
Access: "",
|
||||
Secret: "",
|
||||
Template: "您的验证码是:{{.code}}。请不要把验证码泄露给其他人。",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package alibabacloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/client"
|
||||
dysmsapi "github.com/alibabacloud-go/dysmsapi-20170525/v2/client"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Access string `json:"access"`
|
||||
Secret string `json:"secret"`
|
||||
SignName string `json:"sign_name"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
TemplateCode string `json:"template_code"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config *Config
|
||||
client *dysmsapi.Client
|
||||
}
|
||||
|
||||
func NewClient(config Config) *Client {
|
||||
client, err := initApiClient(config)
|
||||
if err != nil {
|
||||
logger.Error("NewClient: init Alibaba Cloud Api Client failed", logger.Field("error", err.Error()))
|
||||
}
|
||||
return &Client{
|
||||
config: &config,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SendCode(area, mobile, code string) error {
|
||||
if c.client == nil {
|
||||
return fmt.Errorf("alibaba cloud api client is nil")
|
||||
}
|
||||
jsonCode, _ := json.Marshal(map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
sendSmsRequest := &dysmsapi.SendSmsRequest{
|
||||
PhoneNumbers: tea.String(fmt.Sprintf("%s%s", area, mobile)),
|
||||
TemplateCode: tea.String(c.config.TemplateCode),
|
||||
SignName: tea.String(c.config.SignName),
|
||||
TemplateParam: tea.String(string(jsonCode)),
|
||||
}
|
||||
sendSmsResponse, err := c.client.SendSms(sendSmsRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *sendSmsResponse.Body.Code != "OK" {
|
||||
return fmt.Errorf("alibaba cloud send sms failed, code: %s, message: %s", *sendSmsResponse.Body.Code, *sendSmsResponse.Body.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initApiClient(config Config) (*dysmsapi.Client, error) {
|
||||
cfg := &openapi.Config{
|
||||
AccessKeyId: tea.String(config.Access),
|
||||
AccessKeySecret: tea.String(config.Secret),
|
||||
Endpoint: tea.String(config.Endpoint),
|
||||
}
|
||||
if config.Endpoint == "" {
|
||||
cfg.Endpoint = tea.String("dysmsapi.ap-southeast-1.aliyuncs.com")
|
||||
}
|
||||
result, err := dysmsapi.NewClient(cfg)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *Client) GetSendCodeContent(code string) string {
|
||||
return fmt.Sprintf("TemplateId: %s, SignName:%s, Code: %s", c.config.TemplateCode, c.config.SignName, code)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package sms
|
||||
|
||||
import "github.com/perfect-panel/ppanel-server/internal/types"
|
||||
|
||||
type Platform int
|
||||
|
||||
const (
|
||||
AlibabaCloud Platform = iota
|
||||
Smsbao
|
||||
Abosend
|
||||
Twilio
|
||||
unsupported
|
||||
)
|
||||
|
||||
var platformNames = map[string]Platform{
|
||||
"AlibabaCloud": AlibabaCloud,
|
||||
"smsbao": Smsbao,
|
||||
"abosend": Abosend,
|
||||
"twilio": Twilio,
|
||||
"unsupported": unsupported,
|
||||
}
|
||||
|
||||
func (p Platform) String() string {
|
||||
for k, v := range platformNames {
|
||||
if v == p {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return "unsupported"
|
||||
}
|
||||
|
||||
func parsePlatform(s string) Platform {
|
||||
if p, ok := platformNames[s]; ok {
|
||||
return p
|
||||
}
|
||||
return unsupported
|
||||
}
|
||||
|
||||
func GetSupportedPlatforms() []types.PlatformInfo {
|
||||
return []types.PlatformInfo{
|
||||
{
|
||||
Platform: AlibabaCloud.String(),
|
||||
PlatformUrl: "https://www.alibabacloud.com",
|
||||
PlatformFieldDescription: map[string]string{
|
||||
"access": "AccessKeyId",
|
||||
"secret": "AccessKeySecret",
|
||||
"template_code": "TemplateCode",
|
||||
"sign_name": "SignName",
|
||||
"endpoint": "Endpoint",
|
||||
},
|
||||
},
|
||||
{
|
||||
Platform: Smsbao.String(),
|
||||
PlatformUrl: "https://www.smsbao.com",
|
||||
PlatformFieldDescription: map[string]string{
|
||||
"access": "Username",
|
||||
"secret": "Password",
|
||||
"code_variable": "{{.code}}",
|
||||
"template": "Your verification code is: {{.code}}",
|
||||
},
|
||||
},
|
||||
{
|
||||
Platform: Abosend.String(),
|
||||
PlatformUrl: "https://www.abosend.com",
|
||||
PlatformFieldDescription: map[string]string{
|
||||
"access": "OrgCode",
|
||||
"secret": "MD5Key",
|
||||
"code_variable": "{{.code}}",
|
||||
"template": "Your verification code is: {{.code}}",
|
||||
"api_domain": "https://smsapi.abosend.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
Platform: Twilio.String(),
|
||||
PlatformUrl: "https://www.twilio.com",
|
||||
PlatformFieldDescription: map[string]string{
|
||||
"access": "AccessSID",
|
||||
"secret": "AuthToken",
|
||||
"phone_number": "Sending phone number",
|
||||
"code_variable": "{{.code}}",
|
||||
"template": "Your verification code is: {{.code}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/sms/abosend"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/sms/alibabacloud"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/sms/smsbao"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/sms/twilio"
|
||||
)
|
||||
|
||||
type Sender interface {
|
||||
SendCode(area, mobile, code string) error
|
||||
GetSendCodeContent(code string) string
|
||||
}
|
||||
|
||||
func NewSender(platform, config string) (Sender, error) {
|
||||
log.Printf("platform: %s, config: %s", platform, config)
|
||||
switch parsePlatform(platform) {
|
||||
case AlibabaCloud:
|
||||
cfg := alibabacloud.Config{}
|
||||
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("alibabacloud config unmarshal failed: %v", err.Error())
|
||||
}
|
||||
return alibabacloud.NewClient(cfg), nil
|
||||
case Abosend:
|
||||
cfg := abosend.Config{}
|
||||
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("abosend config unmarshal failed: %v", err.Error())
|
||||
}
|
||||
return abosend.NewClient(cfg), nil
|
||||
case Smsbao:
|
||||
cfg := smsbao.Config{}
|
||||
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("smsbao config unmarshal failed: %v", err.Error())
|
||||
}
|
||||
return smsbao.NewClient(cfg), nil
|
||||
case Twilio:
|
||||
cfg := twilio.Config{}
|
||||
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("twilio config unmarshal failed: %v", err.Error())
|
||||
}
|
||||
return twilio.NewClient(cfg), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported platform: %s", platform)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package smsbao
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Error int
|
||||
|
||||
const (
|
||||
Success Error = iota
|
||||
PasswordError
|
||||
AccountNotFount
|
||||
InsufficientBalance
|
||||
IPAddressRestrictions
|
||||
ContentContainsSensitiveWords
|
||||
MobileNumberIsIncorrect
|
||||
)
|
||||
|
||||
var errorDescriptions = map[Error]string{
|
||||
Success: "Success",
|
||||
PasswordError: "Password error",
|
||||
AccountNotFount: "Account not found",
|
||||
InsufficientBalance: "Insufficient balance",
|
||||
IPAddressRestrictions: "IP address restrictions",
|
||||
ContentContainsSensitiveWords: "Content contains sensitive words",
|
||||
MobileNumberIsIncorrect: "Mobile number is incorrect",
|
||||
}
|
||||
|
||||
var errorCodes = map[string]Error{
|
||||
"0": Success,
|
||||
"30": PasswordError,
|
||||
"40": AccountNotFount,
|
||||
"41": InsufficientBalance,
|
||||
"43": IPAddressRestrictions,
|
||||
"50": ContentContainsSensitiveWords,
|
||||
"51": MobileNumberIsIncorrect,
|
||||
}
|
||||
|
||||
func (e Error) String() string {
|
||||
for k, v := range errorDescriptions {
|
||||
if k == e {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return "Unknown error"
|
||||
}
|
||||
|
||||
func parseError(b []byte) error {
|
||||
if e, ok := errorCodes[string(b)]; ok {
|
||||
return e.Error()
|
||||
}
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
func (e Error) Error() error {
|
||||
if e == Success {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s", e.String())
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package smsbao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/templatex"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
)
|
||||
|
||||
const BaseURL = "https://api.smsbao.com"
|
||||
|
||||
type Config struct {
|
||||
Access string `json:"access"`
|
||||
Secret string `json:"secret"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config *Config
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewClient(config Config) *Client {
|
||||
client := resty.New()
|
||||
client.SetBaseURL(BaseURL)
|
||||
return &Client{
|
||||
config: &config,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SendCode(area, mobile, code string) error {
|
||||
apiUrl := "/sms"
|
||||
text, err := templatex.RenderToString(c.config.Template, map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render sms template: %s", err.Error())
|
||||
}
|
||||
param := map[string]string{
|
||||
"u": c.config.Access,
|
||||
"p": tool.Md5Encode(c.config.Secret, false),
|
||||
"m": mobile,
|
||||
"c": text,
|
||||
}
|
||||
if area != "86" {
|
||||
apiUrl = "/wsms"
|
||||
param["m"] = fmt.Sprintf("+%s%s", area, mobile)
|
||||
}
|
||||
resp, err := c.client.R().SetQueryParams(param).Get(apiUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = parseError(resp.Body())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSendCodeContent(code string) string {
|
||||
text, _ := templatex.RenderToString(c.config.Template, map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
return text
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package smsbao
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
t.Skipf("Skip TestNewClient test")
|
||||
client := NewClient(Config{
|
||||
Template: "【XXX】您的验证码是:{{.code}},有效期 {{.expiration}}。请不要把验证码泄露给其他人。",
|
||||
})
|
||||
err := client.SendCode("1", "", "223322")
|
||||
if err != nil {
|
||||
t.Errorf("TestNewClient() error = %v", err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("TestNewClient success")
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package twilio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/templatex"
|
||||
"github.com/twilio/twilio-go"
|
||||
twilioApi "github.com/twilio/twilio-go/rest/api/v2010"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Access string `json:"access"`
|
||||
Secret string `json:"secret"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config Config
|
||||
client *twilio.RestClient
|
||||
}
|
||||
|
||||
func NewClient(config Config) *Client {
|
||||
client := twilio.NewRestClientWithParams(twilio.ClientParams{
|
||||
Username: config.Access,
|
||||
Password: config.Secret,
|
||||
})
|
||||
return &Client{
|
||||
config: config,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SendCode(area, mobile, code string) error {
|
||||
params := &twilioApi.CreateMessageParams{}
|
||||
params.SetTo(fmt.Sprintf("+%s%s", area, mobile))
|
||||
params.SetFrom(c.config.PhoneNumber)
|
||||
text, err := templatex.RenderToString(c.config.Template, map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("twilio send code render template error", logger.Field("error", err.Error()), logger.Field("template", c.config.Template), logger.Field("code", code))
|
||||
}
|
||||
params.SetBody(text)
|
||||
resp, err := c.client.Api.CreateMessage(params)
|
||||
if err != nil {
|
||||
logger.Error("twilio send code error", logger.Field("error", err.Error()), logger.Field("params", params))
|
||||
return fmt.Errorf("twilio send code error: %s", err.Error())
|
||||
}
|
||||
if resp.ErrorCode != nil {
|
||||
logger.Error("twilio send code error", logger.Field("error_code", *resp.ErrorCode), logger.Field("error_message", *resp.ErrorMessage))
|
||||
return fmt.Errorf("twilio send code error: %s", *resp.ErrorMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSendCodeContent(code string) string {
|
||||
text, _ := templatex.RenderToString(c.config.Template, map[string]interface{}{
|
||||
"code": code,
|
||||
})
|
||||
return text
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package twilio
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestClient_SendCode(t *testing.T) {
|
||||
t.Skipf("Skip TestClient_SendCode test")
|
||||
client := NewClient(Config{
|
||||
Access: "", Secret: "", PhoneNumber: "", Template: "",
|
||||
})
|
||||
err := client.SendCode("", "", "123456")
|
||||
if err != nil {
|
||||
t.Errorf("SendCode() error = %v", err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("SendCode() success")
|
||||
}
|
||||
Reference in New Issue
Block a user