go-zero之阿里發簡訊服務

charliecen 發表於 2021-10-20
Go

簡訊RPC服務

需要在阿里雲上開通簡訊服務,開通模板、簽名等必須的條件,並擁有accessKey、 accessSecret 許可權。

如果你沒有使用微服務,直接呼叫sdk程式碼就可以。

定義proto檔案

syntax = "proto3";

package sms;

message sendReq {
  string phoneNum = 1;
  int32 appId = 2;
  int32 action = 3;
}

message codeReq {
  string phoneNum = 1;
}
message Resp {
  int64 code = 1;
  string msg = 2;
  map<string, string> data = 3;
}

service Sms {
  rpc sendSms(sendReq) returns(Resp);
  rpc getCode(codeReq) returns(Resp);
}

生成檔案

❯ goctl.exe rpc proto -src .\sms.proto -dir .
Done

配置

Name: sms.rpc
ListenOn: 127.0.0.1:8081
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: sms.rpc
# redis配置
Redis:
  Host: "localhost:6379"
  Pass: "root"
# 簡訊配置
Sms:
  KaFan:
    AccessKey: $AccessKey
    AccessKeySecret: $AccessKeySecret
    SignName: $SignName
    TemplateCode: $TemplateCode
  # 快取過期時間,單位秒
  CacheExpire: 300

宣告配置型別

package config

import "github.com/tal-tech/go-zero/zrpc"

type Config struct {
    zrpc.RpcServerConf
    Sms struct {
        KaFan KaFan
        CacheExpire int
    }
}

type KaFan struct {
    AccessKey string
    AccessKeySecret string
    SignName string
    TemplateCode string
}

填充依賴

package svc

import (
    "app/rpc/sms/internal/config"
    "github.com/tal-tech/go-zero/core/stores/redis"
)

type ServiceContext struct {
    Config config.Config
    RedisClient   *redis.Redis
}

func NewServiceContext(c config.Config) *ServiceContext {
    srvCtx := &ServiceContext{
        Config: c,
    }

    //redis初始化
    if c.Redis.Host != "" {
        srvCtx.RedisClient = srvCtx.Config.Redis.NewRedis()
    }

    return srvCtx
}

填充邏輯

下面是傳送簡訊的邏輯

package logic

import (
    Sms "app/library/sms"
    "context"
    "fmt"
    "math/rand"
    "strconv"
    "time"

    "app/rpc/sms/internal/svc"
    sms "app/rpc/sms/sms"

    "github.com/tal-tech/go-zero/core/logx"
)

type SendSmsLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewSendSmsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendSmsLogic {
    return &SendSmsLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

// 傳送簡訊
func (l *SendSmsLogic) SendSms(in *sms.SendReq) (*sms.Resp, error) {
    // 檢測傳送頻率
    b := l.diffSendTime(in.PhoneNum, time.Now().Unix())
    if b == false {
        return &sms.Resp{
            Code: 201,
            Msg:  "傳送簡訊頻率過高",
            Data: nil,
        }, nil
    }
    var post = make(map[string]string)
    // 生產隨機六位數
    code := l.generateSixNum()
    // 引數
    post["accessKey"] = l.svcCtx.Config.Sms.KaFan.AccessKey
    post["accessSecret"] = l.svcCtx.Config.Sms.KaFan.AccessKeySecret
    post["phoneNum"] = in.PhoneNum
    post["code"] = code
    if in.AppId == 1 {
        post["signName"] = l.svcCtx.Config.Sms.KaFan.SignName
    }
    if in.Action == 1 {
        post["templateCode"] = l.svcCtx.Config.Sms.KaFan.TemplateCode
    }

    // 傳送簡訊
    result, err := Sms.Send(post)
    msg := "failed"
    if result != nil {
        msg = *result.Body.Message
    }

    if err != nil {
        return &sms.Resp{
            Code: 201,
            Msg:  msg,
            Data: nil,
        }, err
    }

    if *result.Body.Code != "OK" {
        return &sms.Resp{
            Code: 201,
            Msg:  msg,
            Data: nil,
        }, err
    }
    var data = make(map[string]string)
    data["code"] = code
    // 存到redis快取中,有效期5分鐘
    cacheExpire := l.svcCtx.Config.Sms.CacheExpire
    _ = l.svcCtx.RedisClient.Setex(in.PhoneNum, code, cacheExpire)

    return &sms.Resp{
        Code: 200,
        Msg: "success",
        Data: data,
    }, nil
}

// 生成6位數
func (l *SendSmsLogic) generateSixNum() string {
    rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
    return fmt.Sprintf("%06v", rnd.Int31n(1000000))
}

// 兩次傳送時間差
func (l *SendSmsLogic) diffSendTime(phoneNum string, tu int64) bool {
    key := fmt.Sprintf("%s_unix", phoneNum)
    t := strconv.FormatInt(tu, 10)
    firstTime, err := l.svcCtx.RedisClient.Get(key)
    if err != nil {
        return false
    }
    if firstTime == "" {
        err = l.svcCtx.RedisClient.Set(key, t)
        if err != nil {
            return false
        }
    } else {
        now, _ := strconv.Atoi(t)
        last, _ := strconv.Atoi(firstTime)
        if now - last > 60 {
            // 大於60秒,重置時間
            _ = l.svcCtx.RedisClient.Set(key, t)
        } else {
            return false
        }
    }
    return true
}

sdk呼叫傳送介面

package sms

import (
    "fmt"
    openapi "github.com/alibabacloud-go/darabonba-openapi/client"
    dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v2/client"
    "github.com/alibabacloud-go/tea/tea"
)

/**
 * 使用AK&SK初始化賬號Client
 * @param accessKeyId
 * @param accessKeySecret
 * @return Client
 * @throws Exception
 */
func CreateClient (accessKeyId *string, accessKeySecret *string) (_result *dysmsapi20170525.Client, _err error) {
    config := &openapi.Config{
        // 您的AccessKey ID
        AccessKeyId: accessKeyId,
        // 您的AccessKey Secret
        AccessKeySecret: accessKeySecret,
    }
    // 訪問的域名
    config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
    _result = &dysmsapi20170525.Client{}
    _result, _err = dysmsapi20170525.NewClient(config)
    return _result, _err
}

/**
 * 請求阿里雲傳送簡訊
 */
func Send(data map[string]string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
    client, _err := CreateClient(tea.String(data["accessKey"]), tea.String(data["accessSecret"]))
    if _err != nil {
        return nil, _err
    }
    codeJson := fmt.Sprintf("{\"code\": \"%s\"}", data["code"])

    sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
        SignName: tea.String(data["signName"]),
        TemplateCode: tea.String(data["templateCode"]),
        PhoneNumbers: tea.String(data["phoneNum"]),
        TemplateParam: tea.String(codeJson),
    }
    // 複製程式碼執行請自行列印 API 的返回值
    _result, _err = client.SendSms(sendSmsRequest)
    if _err != nil {
        return nil, _err
    }
    return _result, _err
}

至此RPC服務完成,啟動服務

❯ go run .\sms.go
Starting rpc server at 127.0.0.1:8081...

API 服務

寫api介面

syntax = "v1"

info(
    title: "傳送簡訊"
    desc: "傳送簡訊"
    author: "charlie"
    email: "[email protected]"
    version: "1.0"
)

type (
    SendReq {
        PhoneNum      string     `form:"phoneNum"`
        AppId        int32     `form:"appId"`
        Action      int32    `form:"action"`
    }

    CodeReq {
        PhoneNum  string `form:"phoneNum"`
    }

    SmsResp {
        Code int               `json:"code"`
        Msg  string            `json:"msg"`
        Data map[string]string `json:"data"`
    }

)

service api {
    // 傳送簡訊
    @server(
        group: sms
        handler: Code
    )
    post /api/sendSms (SendReq) returns(SmsResp)

    // 獲取簡訊
    @server(
        group: sms
        handler: GetCode
    )
    post /api/getCode (CodeReq) returns(SmsResp)
}

生成程式碼

❯ goctl.exe api go -api  api.api -dir .

配置etcd服務

Name: api
Host: 0.0.0.0
Port: 8888
SmsRpc:
  Etcd:
    Hosts:
      - localhost:2379
    Key: sms.rpc

宣告配置型別

package config

import (
    "github.com/tal-tech/go-zero/rest"
    "github.com/tal-tech/go-zero/zrpc"
)

type Config struct {
    rest.RestConf
    SmsRpc zrpc.RpcClientConf
}

填充依賴

package svc

import (
    "app/api/internal/config"
    "app/rpc/sms/smsclient"
    "github.com/tal-tech/go-zero/zrpc"
)

type ServiceContext struct {
    Config config.Config
    SmsClient smsclient.Sms

}

func NewServiceContext(c config.Config) *ServiceContext {
    return  &ServiceContext{
        Config: c,
        SmsClient: smsclient.NewSms(zrpc.MustNewClient(c.SmsRpc)),
    }
}

填充邏輯

package sms

import (
    "app/rpc/sms/smsclient"
    "context"

    "app/api/internal/svc"
    "app/api/internal/types"

    "github.com/tal-tech/go-zero/core/logx"
)

type CodeLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) CodeLogic {
    return CodeLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *CodeLogic) Code(req types.SendReq) (*types.SmsResp, error) {
    // 請求rpc服務,傳送簡訊
    result, err := l.svcCtx.SmsClient.SendSms(l.ctx, &smsclient.SendReq{
        PhoneNum: req.PhoneNum,
        AppId: req.AppId,
        Action: req.Action,
    })
    if err != nil {
        return &types.SmsResp{
            Code: 201,
            Msg: "failed",
            Data: nil,
        }, err
    }
    if result.Code != 200 {
        return &types.SmsResp{
            Code: 201,
            Msg: result.Msg,
            Data: nil,
        }, nil
    }
    return &types.SmsResp{
        Code: 200,
        Msg: "success",
        Data: result.Data,
    }, nil
}

啟動api服務

❯ go run .\api.go
Starting server at 0.0.0.0:8888...

測試傳送簡訊

curl --location --request POST 'http://localhost:8888/api/sendSms' \
--form 'phoneNum="150xxxxxxxx"' \
--form 'appId="1"' \
--form 'action="1"'

# 返回結果
{
    "code": 200,
    "msg": "success",
    "data": {
        "code": "064128"
    }
}

go-zero之阿里發簡訊服務

本作品採用《CC 協議》,轉載必須註明作者和本文連結