Skip to content

Commit

Permalink
feat(sms): Added SMS(with mitake provider) (#67)
Browse files Browse the repository at this point in the history
* feat(sms): Added SMS

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>

* feat(sms): fix lint

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>

* feat(sms): fix lint

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>

* test(sms): Increased unit test coverage.

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>

* test(sms): Increased unit test coverage.

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>

---------

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>
  • Loading branch information
flc1125 authored Jan 10, 2024
1 parent e896616 commit 6ee3703
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 1 deletion.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/redis/go-redis/v9 v9.4.0
github.com/robfig/cron/v3 v3.0.1
github.com/stretchr/testify v1.8.4
golang.org/x/text v0.13.0
gorm.io/gorm v1.25.5
)

Expand Down Expand Up @@ -42,8 +43,8 @@ require (
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/grpc v1.60.1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,17 @@ golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
Expand Down
104 changes: 104 additions & 0 deletions sms/mitake/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Package mitake is a sms provider for mitake.
// See: https://sms.mitake.com.tw/
package mitake

import (
"context"
"fmt"
"net/http"
"net/url"

"golang.org/x/text/encoding/traditionalchinese"

"github.com/go-kratos-ecosystem/components/v2/sms"
)

type provider struct {
api string
username string
password string

httpClient *http.Client
}

type Option func(t *provider)

func WithAPI(api string) Option {
return func(t *provider) {
t.api = api
}
}

func WithHTTPClient(httpClient *http.Client) Option {
return func(t *provider) {
t.httpClient = httpClient
}
}

func New(username, password string, opts ...Option) sms.Provider {
p := &provider{
api: "https://smsapi.mitake.com.tw",
username: username,
password: password,
httpClient: http.DefaultClient,
}

for _, opt := range opts {
opt(p)
}

return p
}

func (p *provider) Send(ctx context.Context, phone *sms.Phone, message *sms.Message) error {
if err := p.verify(phone, message); err != nil {
return err
}

// Convert to Big5
text, err := traditionalchinese.Big5.NewEncoder().String(message.Text)
if err != nil {
return err
}

// Combine params
params := url.Values{}
params.Set("username", p.username)
params.Set("password", p.password)
params.Set("type", "now")
params.Set("encoding", "big5")
params.Set("dstaddr", phone.Number)
params.Set("smbody", text)

// new request
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.api+"?"+params.Encode(), nil)
if err != nil {
return err
}

// send request
resp, err := p.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

// check response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("sms mitake: http status code: %d", resp.StatusCode)
}

return nil
}

func (p *provider) verify(phone *sms.Phone, message *sms.Message) error {
if phone.Number == "" {
return sms.ErrInvalidPhone
}

if message.Text == "" {
return sms.ErrInvalidMessage
}

return nil
}
58 changes: 58 additions & 0 deletions sms/mitake/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package mitake

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/go-kratos-ecosystem/components/v2/sms"
)

func TestProvider(t *testing.T) {
var (
username = "test"
password = "test"
number = "123456789"
text = "Hello, world"
)

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Fatal("method error")
}

if r.URL.Query().Get("username") != username {
t.Fatal("username error")
}

if r.URL.Query().Get("password") != password {
t.Fatal("password error")
}

if r.URL.Query().Get("dstaddr") != number {
t.Fatal("dstaddr error")
}

if r.URL.Query().Get("smbody") != text {
t.Fatal("smbody error")
}

w.Write([]byte("hello")) //nolint:errcheck
}))
defer srv.Close()

p := New(username, password,
WithAPI(srv.URL),
WithHTTPClient(http.DefaultClient),
)

err := p.Send(context.Background(), &sms.Phone{
Number: number,
}, &sms.Message{
Text: text,
})
if err != nil {
t.Fatal(err)
}
}
13 changes: 13 additions & 0 deletions sms/null_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package sms

import "context"

type NullProvider struct{}

func NewNullProvider() Provider {
return &NullProvider{}
}

func (p *NullProvider) Send(_ context.Context, _ *Phone, _ *Message) error {
return nil
}
7 changes: 7 additions & 0 deletions sms/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package sms

import "context"

type Provider interface {
Send(ctx context.Context, phone *Phone, message *Message) error
}
63 changes: 63 additions & 0 deletions sms/sms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package sms

import (
"context"
"errors"
"time"
)

var (
ErrInvalidPhone = errors.New("sms: invalid phone")
ErrInvalidMessage = errors.New("sms: invalid message")
)

type Phone struct {
IDDCode string
Number string
}

type Message struct {
Text string
Template string
Variables map[string]string
Schedule time.Time
}

type Sms struct {
gw Provider
}

func New(gw Provider) *Sms {
return &Sms{
gw: gw,
}
}

func (s *Sms) Send(ctx context.Context, phone *Phone, message *Message) error {
return s.gw.Send(ctx, phone, message)
}

func (s *Sms) SendText(ctx context.Context, phone *Phone, text string) error {
return s.Send(ctx, phone, &Message{
Text: text,
})
}

func (s *Sms) SendTemplate(ctx context.Context, phone *Phone, template string, variables map[string]string) error {
return s.Send(ctx, phone, &Message{
Template: template,
Variables: variables,
})
}

func (s *Sms) SendTextWithNumber(ctx context.Context, phoneNumber, text string) error {
return s.SendText(ctx, &Phone{
Number: phoneNumber,
}, text)
}

func (s *Sms) SendTemplateWithNumber(ctx context.Context, phoneNumber, template string, variables map[string]string) error { //nolint:lll
return s.SendTemplate(ctx, &Phone{
Number: phoneNumber,
}, template, variables)
}
19 changes: 19 additions & 0 deletions sms/sms_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package sms

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
)

func TestSms(t *testing.T) {
sms := New(NewNullProvider())
ctx := context.Background()

assert.NoError(t, sms.Send(ctx, nil, nil))
assert.NoError(t, sms.SendText(ctx, nil, ""))
assert.NoError(t, sms.SendTemplate(ctx, nil, "", nil))
assert.NoError(t, sms.SendTextWithNumber(ctx, "", ""))
assert.NoError(t, sms.SendTemplateWithNumber(ctx, "", "", nil))
}

0 comments on commit 6ee3703

Please sign in to comment.