init: 1.0.0

This commit is contained in:
Chang lue Tsen
2025-04-25 12:08:29 +09:00
commit 8addcc584b
1031 changed files with 76472 additions and 0 deletions
+101
View File
@@ -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
}
+30
View File
@@ -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}}。请不要把验证码泄露给其他人。",
})
}
+76
View File
@@ -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)
}
+85
View File
@@ -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}}",
},
},
}
}
+49
View File
@@ -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)
}
}
+58
View File
@@ -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())
}
+67
View File
@@ -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
}
+16
View File
@@ -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")
}
+63
View File
@@ -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
}
+16
View File
@@ -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")
}