作者 yangfu

add sms support

1 package main 1 package main
2 2
3 -import _ "openapi/pkg/log"  
4 -  
5 import ( 3 import (
  4 + "fmt"
  5 +
6 "github.com/astaxie/beego" 6 "github.com/astaxie/beego"
  7 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/config"
7 "gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log" 8 "gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
8 - 9 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/redis"
  10 + "openapi/pkg/constant"
9 _ "openapi/pkg/infrastructure/bgorm" 11 _ "openapi/pkg/infrastructure/bgorm"
  12 + _ "openapi/pkg/log"
10 _ "openapi/pkg/port/beego" 13 _ "openapi/pkg/port/beego"
11 ) 14 )
12 15
@@ -16,5 +19,13 @@ func main() { @@ -16,5 +19,13 @@ func main() {
16 }() 19 }()
17 log.Info("server on start!") 20 log.Info("server on start!")
18 //constant.DebugConfig() 21 //constant.DebugConfig()
  22 + err := redis.Init(config.Redis{
  23 + Addr: fmt.Sprintf("%v:%v", constant.REDIS_HOST, constant.REDIS_PORT),
  24 + Password: constant.REDIS_AUTH,
  25 + MaxIdle: 50,
  26 + })
  27 + if err != nil {
  28 + log.Error(err)
  29 + }
19 beego.Run() 30 beego.Run()
20 } 31 }
  1 +package service
  2 +
  3 +import (
  4 + "fmt"
  5 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/common"
  6 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
  7 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/redis"
  8 + protocol "openapi/pkg/domain"
  9 + "openapi/pkg/infrastructure/sms"
  10 + "openapi/pkg/infrastructure/sms/yunpian"
  11 + "strings"
  12 +)
  13 +
  14 +// SmsCode 发送短信验证码
  15 +func SmsCode(header *protocol.RequestHeader, request *protocol.SmsCodeRequest) (rsp interface{}, err error) {
  16 + code := common.RandomStringWithChars(6, protocol.Nums)
  17 + var smsSvr sms.ISmsService = &yunpian.YPSmsService{}
  18 + content := fmt.Sprintf("【买买买信息科技】%v(手机验证码,请完成验证),如非本人操作,请忽略本短信", code)
  19 + // save log to db
  20 + log.Debug(fmt.Sprintf("【sms】 smscode phone:%v code:%v", request.Phone, code))
  21 + // save to redis
  22 + if err = redis.Set(smsRedisKey(request.Phone), code, sms.DefaultSmsCodeTimeOut); err != nil {
  23 + logError(err)
  24 + err = protocol.NewCustomMessage(1001, "请求超时,请检查手机号是否正确或网络连接状态")
  25 + return nil, err
  26 + }
  27 + if err = smsSvr.Send(&sms.Options{Phone: request.Phone, SendType: sms.SendSingle, Content: content}); err != nil {
  28 + logError(err)
  29 + err = protocol.NewCustomMessage(1001, "请求超时,请检查手机号是否正确或网络连接状态")
  30 + return nil, err
  31 + }
  32 + return
  33 +}
  34 +
  35 +// CheckSmsCode 检查短信验证码
  36 +func CheckSmsCode(header *protocol.RequestHeader, request *protocol.CheckSmsCodeRequest) (rsp interface{}, err error) {
  37 + var value string
  38 + if value, err = redis.Get(smsRedisKey(request.Phone)); err != nil || len(value) == 0 {
  39 + logError(err)
  40 + err = protocol.NewCustomMessage(1004, "验证码已失效")
  41 + return nil, err
  42 + }
  43 + if !strings.EqualFold(value, request.Code) {
  44 + err = protocol.NewCustomMessage(1004, "验证码错误,重新输入")
  45 + return nil, err
  46 + }
  47 + redis.Del(smsRedisKey(request.Phone))
  48 + return
  49 +}
  50 +
  51 +// SendSmsNotice 发送短信通知
  52 +func SendSmsNotice(header *protocol.RequestHeader, request *protocol.SendSmsNoticeRequest) (rsp interface{}, err error) {
  53 + var smsSvr sms.ISmsService = &yunpian.YPSmsService{}
  54 + options := sms.NewOptions(
  55 + sms.WithPhone(request.Phone),
  56 + sms.WithSendType(sms.SendSingleByTpl),
  57 + sms.WithTpl(request.TplId, request.TplValues),
  58 + )
  59 + err = smsSvr.Send(options)
  60 + return
  61 +}
  62 +
  63 +func logError(err error) {
  64 + log.Error(fmt.Sprintf("【sms】 %v", err.Error()))
  65 +}
  66 +
  67 +func smsRedisKey(phone string) string {
  68 + return strings.Join([]string{"sms", "smscode", phone}, ":")
  69 +}
@@ -8,11 +8,13 @@ import ( @@ -8,11 +8,13 @@ import (
8 var ( 8 var (
9 LogFilePath string = "F:/log/app.log" 9 LogFilePath string = "F:/log/app.log"
10 LogLevel string = "error" 10 LogLevel string = "error"
  11 + RunMode string = "dev"
11 ) 12 )
12 13
13 func init() { 14 func init() {
14 LogLevel = config.StringDefault("log_level", LogLevel) 15 LogLevel = config.StringDefault("log_level", LogLevel)
15 LogFilePath = config.StringDefault("aliyun_logs_access", LogFilePath) 16 LogFilePath = config.StringDefault("aliyun_logs_access", LogFilePath)
  17 + RunMode = config.StringDefault("RUN_MODE", RunMode)
16 } 18 }
17 19
18 func DebugConfig() { 20 func DebugConfig() {
  1 +package constant
  2 +
  3 +import "os"
  4 +
  5 +var REDIS_HOST = "127.0.0.1"
  6 +var REDIS_PORT = "6379"
  7 +var REDIS_AUTH = ""
  8 +
  9 +func init() {
  10 + if os.Getenv("REDIS_HOST") != "" {
  11 + REDIS_HOST = os.Getenv("REDIS_HOST")
  12 + REDIS_AUTH = os.Getenv("REDIS_AUTH")
  13 + }
  14 + if os.Getenv("REDIS_PORT") != "" {
  15 + REDIS_PORT = os.Getenv("REDIS_PORT")
  16 + }
  17 + if _, ok := os.LookupEnv("REDIS_AUTH"); ok {
  18 + REDIS_AUTH = os.Getenv("REDIS_AUTH")
  19 + }
  20 +}
  1 +package constant
  2 +
  3 +var (
  4 + YunPianAppKey = "0bf6fb10a11a68a95dee80901eb545b5"
  5 + YunPianSDKHost = "https://sms.yunpian.com/v2/"
  6 + SmsSendSingle = "sms/single_send.json"
  7 + SmsSendSingleByTpl = "sms/tpl_single_send.json"
  8 +)
@@ -5,4 +5,8 @@ var errmessge ErrorMap = map[int]string{ @@ -5,4 +5,8 @@ var errmessge ErrorMap = map[int]string{
5 1: "系统异常", 5 1: "系统异常",
6 2: "参数错误", 6 2: "参数错误",
7 113: "签名验证失败", 7 113: "签名验证失败",
  8 + 1001: "请求超时,请检查手机号是否正确或网络连接状态",
  9 + 1002: "请输入正确的手机号码",
  10 + 1003: "验证码错误,重新输入",
  11 + 1004: "验证码已失效",
8 } 12 }
  1 +package domain
  2 +
  3 +var Nums = "0123456789"
  4 +
  5 +/*发送短信验证码*/
  6 +type SmsCodeRequest struct {
  7 + Project string `json:"project"`
  8 + Phone string `json:"phone" valid:"required"`
  9 +}
  10 +
  11 +type CheckSmsCodeRequest struct {
  12 + Phone string `json:"phone" valid:"required"`
  13 + Code string `json:"code" valid:"required"`
  14 +}
  15 +
  16 +type SendSmsNoticeRequest struct {
  17 + // 手机号
  18 + Phone string `json:"phone" valid:"required"`
  19 + // 模板id
  20 + TplId int `json:"tplId" valid:"required"`
  21 + // 模板参数
  22 + TplValues map[string]interface{} `json:"tplValues"`
  23 +}
  1 +package sms
  2 +
  3 +const (
  4 + SendSingle SendType = 1
  5 + SendList SendType = 2
  6 + SendSingleByTpl SendType = 10
  7 +)
  8 +
  9 +const DefaultSmsCodeTimeOut int64 = 60 * 2
  10 +
  11 +type (
  12 + SendType int
  13 + ISmsService interface {
  14 + Send(option *Options) error
  15 + }
  16 + Options struct {
  17 + Phone string
  18 + Content string
  19 + SendType SendType
  20 + TplId int
  21 + TplValues map[string]interface{}
  22 + }
  23 + option func(o *Options)
  24 +)
  25 +
  26 +func NewOptions(options ...option) *Options {
  27 + o := &Options{}
  28 + for i := range options {
  29 + options[i](o)
  30 + }
  31 + return o
  32 +}
  33 +func WithPhone(phone string) option {
  34 + return func(o *Options) {
  35 + o.Phone = phone
  36 + }
  37 +}
  38 +func WithSendType(sendType SendType) option {
  39 + return func(o *Options) {
  40 + o.SendType = sendType
  41 + }
  42 +}
  43 +func WithTpl(tplId int, tplValues map[string]interface{}) option {
  44 + return func(o *Options) {
  45 + o.TplId = tplId
  46 + o.TplValues = tplValues
  47 + }
  48 +}
  1 +package yunpian
  2 +
  3 +import (
  4 + "bytes"
  5 + "fmt"
  6 + "github.com/astaxie/beego/httplib"
  7 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/common"
  8 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
  9 + "net/url"
  10 + "openapi/pkg/constant"
  11 + "openapi/pkg/infrastructure/sms"
  12 + "strconv"
  13 +)
  14 +
  15 +type (
  16 + YPSmsService struct{}
  17 + YPSmsResponse struct {
  18 + Code int `json:"code"` //0代表发送成功,其他code代表出错,详细见"返回值说明"页面
  19 + Msg string `json:"msg"` //例如""发送成功"",或者相应错误信息
  20 + Count int `json:"count"` //发送成功短信的计费条数(计费条数:70个字一条,超出70个字时按每67字一条计费)
  21 + Mobile string `json:"string"` //发送手机号
  22 + Fee float64 `json:"fee"` //扣费金额,单位:元,类型:双精度浮点型/double
  23 + Sid int64 `json:"sid"` //短信id,64位整型
  24 + }
  25 +)
  26 +
  27 +func (svr *YPSmsService) Send(option *sms.Options) error {
  28 + var err error
  29 + if option.SendType == sms.SendSingle {
  30 + _, err = sendSingle(option)
  31 + } else if option.SendType == sms.SendSingleByTpl {
  32 + _, err = sendSingleByTpl(option)
  33 + }
  34 + return err
  35 +}
  36 +
  37 +func sendSingle(option *sms.Options) (map[string]interface{}, error) {
  38 + var (
  39 + resp *YPSmsResponse
  40 + err error
  41 + url = constant.YunPianSDKHost + constant.SmsSendSingle
  42 + )
  43 + post := httplib.Post(url)
  44 + post.Param("apikey", constant.YunPianAppKey)
  45 + post.Param("mobile", option.Phone)
  46 + post.Param("text", option.Content)
  47 + if err = post.ToJSON(&resp); err != nil {
  48 + return nil, err
  49 + }
  50 + err = resolveResponse(resp, err)
  51 + return nil, err
  52 +}
  53 +
  54 +func sendSingleByTpl(option *sms.Options) (map[string]interface{}, error) {
  55 + var (
  56 + resp *YPSmsResponse
  57 + err error
  58 + url = constant.YunPianSDKHost + constant.SmsSendSingleByTpl
  59 + )
  60 + post := httplib.Post(url)
  61 + post.Param("apikey", constant.YunPianAppKey)
  62 + post.Param("mobile", option.Phone)
  63 + post.Param("tpl_id", strconv.Itoa(option.TplId))
  64 + if len(option.TplValues) > 0 {
  65 + post.Param("tpl_value", tplValue(option.TplValues))
  66 + log.Debug(tplValue(option.TplValues))
  67 + }
  68 + if err = post.ToJSON(&resp); err != nil {
  69 + return nil, err
  70 + }
  71 + err = resolveResponse(resp, err)
  72 + return nil, err
  73 +}
  74 +
  75 +func resolveResponse(resp *YPSmsResponse, err error) error {
  76 + if resp.Code != 0 {
  77 + log.Error("【sms】 send sms code:", resp.Code, " error msg:", resp.Msg)
  78 + err = common.NewErrorWithMsg(1, resp.Msg)
  79 + return err
  80 + }
  81 + return err
  82 +}
  83 +
  84 +func tplValue(tplValues map[string]interface{}) string {
  85 + var value bytes.Buffer
  86 + for k, v := range tplValues {
  87 + value.WriteString(url.PathEscape(fmt.Sprintf("#%v#=%v&", k, v)))
  88 + }
  89 + return string(value.Bytes()[:value.Len()-1])
  90 +}
@@ -167,3 +167,10 @@ func GetPageInfo(pageIndex, pageSize int) (offset, size int) { @@ -167,3 +167,10 @@ func GetPageInfo(pageIndex, pageSize int) (offset, size int) {
167 size = pageSize 167 size = pageSize
168 return 168 return
169 } 169 }
  170 +
  171 +func IsProductEnv(env string) bool {
  172 + if env == "prod" {
  173 + return true
  174 + }
  175 + return false
  176 +}
  1 +package v1
  2 +
  3 +import (
  4 + "bytes"
  5 + "encoding/json"
  6 + "github.com/astaxie/beego/validation"
  7 + "gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
  8 + sms "openapi/pkg/application/sms/service"
  9 + "openapi/pkg/constant"
  10 + protocol "openapi/pkg/domain"
  11 + "openapi/pkg/infrastructure/utils"
  12 + "openapi/pkg/port/beego/controllers"
  13 +)
  14 +
  15 +type SmsController struct {
  16 + controllers.BaseController
  17 +}
  18 +
  19 +//短信验证码 SmsCode
  20 +// @router /smsCode [post]
  21 +func (this *SmsController) SmsCode() {
  22 + var msg *protocol.ResponseMessage
  23 + defer func() {
  24 + this.Resp(msg)
  25 + }()
  26 + var request *protocol.SmsCodeRequest
  27 + decoder := json.NewDecoder(bytes.NewBuffer(this.ByteBody))
  28 + decoder.UseNumber()
  29 + if err := decoder.Decode(&request); err != nil {
  30 + log.Error(err)
  31 + msg = protocol.BadRequestParam(1)
  32 + return
  33 + }
  34 + if utils.IsProductEnv(constant.RunMode) {
  35 + valid := validation.Validation{}
  36 + if !valid.Mobile(request.Phone, "mobile").Ok {
  37 + msg = protocol.BadRequestParam(1001)
  38 + return
  39 + }
  40 + }
  41 + header := controllers.GetRequestHeader(this.Ctx)
  42 + msg = protocol.NewReturnResponse(sms.SmsCode(header, request))
  43 +}
  44 +
  45 +//检查短信验证码 CheckSmsCode
  46 +// @router /checkSmsCode [post]
  47 +func (this *SmsController) CheckSmsCode() {
  48 + var msg *protocol.ResponseMessage
  49 + defer func() {
  50 + this.Resp(msg)
  51 + }()
  52 + var request *protocol.CheckSmsCodeRequest
  53 + decoder := json.NewDecoder(bytes.NewBuffer(this.ByteBody))
  54 + decoder.UseNumber()
  55 + if err := decoder.Decode(&request); err != nil {
  56 + log.Error(err)
  57 + msg = protocol.BadRequestParam(1)
  58 + return
  59 + }
  60 + if utils.IsProductEnv(constant.RunMode) {
  61 + valid := validation.Validation{}
  62 + if !valid.Mobile(request.Phone, "mobile").Ok {
  63 + msg = protocol.BadRequestParam(1002)
  64 + return
  65 + }
  66 + }
  67 + header := controllers.GetRequestHeader(this.Ctx)
  68 + msg = protocol.NewReturnResponse(sms.CheckSmsCode(header, request))
  69 +}
  70 +
  71 +//发送短信通知 sendSmsNotice
  72 +// @router /sendSmsNotice [post]
  73 +func (this *SmsController) SendSmsNotice() {
  74 + var msg *protocol.ResponseMessage
  75 + defer func() {
  76 + this.Resp(msg)
  77 + }()
  78 + var request *protocol.SendSmsNoticeRequest
  79 + decoder := json.NewDecoder(bytes.NewBuffer(this.ByteBody))
  80 + decoder.UseNumber()
  81 + if err := decoder.Decode(&request); err != nil {
  82 + log.Error(err)
  83 + msg = protocol.BadRequestParam(1)
  84 + return
  85 + }
  86 + if utils.IsProductEnv(constant.RunMode) {
  87 + valid := validation.Validation{}
  88 + if !valid.Mobile(request.Phone, "mobile").Ok {
  89 + msg = protocol.BadRequestParam(1002)
  90 + return
  91 + }
  92 + }
  93 + header := controllers.GetRequestHeader(this.Ctx)
  94 + msg = protocol.NewReturnResponse(sms.SendSmsNotice(header, request))
  95 +}
@@ -100,4 +100,25 @@ func init() { @@ -100,4 +100,25 @@ func init() {
100 AllowHTTPMethods: []string{"post"}, 100 AllowHTTPMethods: []string{"post"},
101 MethodParams: param.Make(), 101 MethodParams: param.Make(),
102 Params: nil}) 102 Params: nil})
  103 + beego.GlobalControllerRouter["openapi/pkg/port/beego/controllers/v1:SmsController"] = append(beego.GlobalControllerRouter["openapi/pkg/port/beego/controllers/v1:SmsController"],
  104 + beego.ControllerComments{
  105 + Method: "SmsCode",
  106 + Router: `/smsCode`,
  107 + AllowHTTPMethods: []string{"post"},
  108 + MethodParams: param.Make(),
  109 + Params: nil})
  110 + beego.GlobalControllerRouter["openapi/pkg/port/beego/controllers/v1:SmsController"] = append(beego.GlobalControllerRouter["openapi/pkg/port/beego/controllers/v1:SmsController"],
  111 + beego.ControllerComments{
  112 + Method: "CheckSmsCode",
  113 + Router: `/checkSmsCode`,
  114 + AllowHTTPMethods: []string{"post"},
  115 + MethodParams: param.Make(),
  116 + Params: nil})
  117 + beego.GlobalControllerRouter["openapi/pkg/port/beego/controllers/v1:SmsController"] = append(beego.GlobalControllerRouter["openapi/pkg/port/beego/controllers/v1:SmsController"],
  118 + beego.ControllerComments{
  119 + Method: "SendSmsNotice",
  120 + Router: `/sendSmsNotice`,
  121 + AllowHTTPMethods: []string{"post"},
  122 + MethodParams: param.Make(),
  123 + Params: nil})
103 } 124 }
@@ -13,6 +13,7 @@ func init() { @@ -13,6 +13,7 @@ func init() {
13 nsV1 := beego.NewNamespace("v1", 13 nsV1 := beego.NewNamespace("v1",
14 beego.NSNamespace("vod", beego.NSBefore(controllers.AllowOption), beego.NSInclude(&v1.VodController{})), 14 beego.NSNamespace("vod", beego.NSBefore(controllers.AllowOption), beego.NSInclude(&v1.VodController{})),
15 beego.NSNamespace("push", beego.NSBefore(controllers.AllowOption), beego.NSInclude(&v1.PushController{})), 15 beego.NSNamespace("push", beego.NSBefore(controllers.AllowOption), beego.NSInclude(&v1.PushController{})),
  16 + beego.NSNamespace("sms", beego.NSBefore(controllers.AllowOption), beego.NSInclude(&v1.SmsController{})),
16 ) 17 )
17 beego.SetStaticPath("/log/NIONkenfieldon", constant.LogFilePath) 18 beego.SetStaticPath("/log/NIONkenfieldon", constant.LogFilePath)
18 beego.AddNamespace(nsV1) 19 beego.AddNamespace(nsV1)