Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

21
app/job/main/sms/BUILD Normal file
View File

@@ -0,0 +1,21 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/job/main/sms/cmd:all-srcs",
"//app/job/main/sms/conf:all-srcs",
"//app/job/main/sms/dao:all-srcs",
"//app/job/main/sms/http:all-srcs",
"//app/job/main/sms/model:all-srcs",
"//app/job/main/sms/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,81 @@
# sms项目的Job用于发送短信
### 1.8.3
1. waitgroup
### 1.8.2
1. waitGroup.Done 按照规范移到外部
### v1.8.1
1. fix 云线路URL配置
### v1.8.0
1. 云线路和本地网络同时使用
2. 去除阿里SDK
### v1.7.2
1. 支持黑名单
### v1.7.1
1. fix log
### v1.7.0
1. 去掉db
### v1.6.0
1. 梦网使用新账号
### v1.5.0
1. use grpc
### v1.4.2
1. 修复创蓝回执时间
### v1.4.1
1. 完善兴企短信报告
### v1.4.0
1. 接入创蓝短信状态报告
### v1.3.3
1. 修复重启时 negative WaitGroup counter
### v1.3.1
1. 修复 send on closed channel panic when service down
### v1.3.0
1. 接入创蓝短信服务
2. 代码优化
### v1.2.0
1. 梦网内容转码失败报错
2. 切换到bm
### v1.1.6
1. 临时去掉阿里云
### v1.1.5
1. 批量推送时空号码不发
### v1.1.4
1. 迁移至main目录下
### v1.1.3
1. 修复批量短信走国际接口的问题
### v1.1.2
1. 将兴企和梦网sdk写成package
### v1.1.1
1. 加日志
### v1.1.0
1. 接阿里云短信服务
### v1.0.1
1. 修复XingQi发送国际短信失败的问题
2. add prom
### v1.0.0
1. 版本初始化

View File

@@ -0,0 +1,11 @@
# Owner
renwei
zhapuyu
zhoushuguang
# Author
zhoushuguang
# Reviewer
zhapuyu
guanhuaxin

16
app/job/main/sms/OWNERS Normal file
View File

@@ -0,0 +1,16 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- renwei
- zhapuyu
- zhoushuguang
labels:
- job
- job/main/sms
- main
options:
no_parent_owners: true
reviewers:
- guanhuaxin
- zhapuyu
- zhoushuguang

View File

@@ -0,0 +1,11 @@
#### sms job
##### 项目简介
> 1.消费sms产生databus消息等异步操作
##### 编译环境
> 请只用golang v1.8.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["sms-job-test.toml"],
importpath = "go-common/app/job/main/sms/cmd",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/job/main/sms/http:go_default_library",
"//app/job/main/sms/service:go_default_library",
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus/report:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,52 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/job/main/sms/conf"
"go-common/app/job/main/sms/http"
"go-common/app/job/main/sms/service"
"go-common/library/log"
"go-common/library/net/trace"
"go-common/library/queue/databus/report"
)
var (
srv *service.Service
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("sms-job start")
trace.Init(conf.Conf.Tracer)
defer trace.Close()
srv = service.New(conf.Conf)
http.Init(conf.Conf, srv)
report.InitUser(conf.Conf.UserReport)
signalHandler()
}
func signalHandler() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
si := <-ch
switch si {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("get a signal %s, stop the consume process", si.String())
srv.Close()
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,105 @@
# This is a TOML document. Boom.
version = "1.0.0"
user = "nobody"
pid = "/tmp/sms-job.pid"
dir = "./"
perf = "0.0.0.0:7780"
family = "sms-job"
[log]
dir = "/data/log/sms-job/"
[HTTPServer]
addr = "0.0.0.0:7781"
maxListen = 1000
timeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
[httpClient]
dial = "10s"
timeout = "10s"
keepAlive = "60s"
key = "4699a07e59d7149e"
secret = "test"
[httpClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[databus]
key = "9765cdac5894f2ba"
secret = "f4237d712c3ed1e7fab0137b81418b14"
group = "Sms-MainWebSvr-S"
topic = "Sms-T"
action = "sub"
buffer = 2048
name = "sms-sub"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[wechat]
token = "GYQeuDWBbAsCNeGz"
secret = "ZKpmgINTkianyMbMixyxcPQjMCSHCDrk"
username = "wangjian"
[provider]
providers = [2,4]
mengWangSmsURL = "http://43.240.124.85:8901/sms/v2/std/single_send"
mengWangSmsUser = "JJ1093"
mengWangSmsPwd = "000827"
mengWangActURL = "http://43.240.124.85:8901/sms/v2/std/single_send"
mengWangBatchURL = "http://43.240.124.85:8901/sms/v2/std/batch_send"
mengWangActUser = "JJ1095"
mengWangActPwd = "082705"
mengWangInternationURL = "http://61.145.229.28:8803/sms/v2/std/single_send"
mengWangInternationUser = "GJ1081"
mengWangInternationPwd = "082706"
chuangLanSmsURL = "http://smssh1.253.com/msg/send/json"
chuangLanSmsUser = "N5563714"
chuangLanSmsPwd = "IGpOWXdAcM7cfd"
chuangLanActURL = "http://smssh1.253.com/msg/send/json"
chuangLanActUser = "M2110245"
chuangLanActPwd = "PJMRstTDdYbb0c"
chuangLanInternationURL = "http://intapi.253.com/send/json"
chuangLanInternationUser = "I5716133"
chuangLanInternationPwd = "JxzpQY3sRc2066"
chuangLanSmsCallbackURL = "http://smssh1.253.com/msg/pull/report"
chuangLanActCallbackURL = "http://smssh1.253.com/msg/pull/report"
chuangLanInternationalCallbackURL = "http://intapi.253.com/pull/report"
mengWangSmsCallbackURL = "http://43.240.124.85:8901/sms/v2/std/get_rpt"
mengWangActCallbackURL = "http://43.240.124.85:8901/sms/v2/std/get_rpt"
mengWangInternationalCallbackURL = "http://61.145.229.28:8803/sms/v2/std/get_rpt"
[speedup]
switch = false
mengWangSmsURL = "http://message1.biliapi.co/sms/v2/std/single_send"
mengWangActURL = "http://message1.biliapi.co/sms/v2/std/single_send"
mengWangBatchURL = "http://message1.biliapi.co/sms/v2/std/batch_send"
mengWangInternationURL = "http://message2.biliapi.co/sms/v2/std/single_send"
chuangLanSmsURL = "http://smssh1.253.biliapi.com/msg/send/json"
chuangLanActURL = "http://smssh1.253.biliapi.com/msg/send/json"
chuangLanInternationURL = "http://intapi.253.biliapi.com/send/json"
chuangLanSmsCallbackURL = "http://smssh1.253.biliapi.com/msg/pull/report"
chuangLanActCallbackURL = "http://smssh1.253.biliapi.com/msg/pull/report"
chuangLanInternationalCallbackURL = "http://intapi.253.biliapi.com/pull/report"
mengWangSmsCallbackURL = "http://message1.biliapi.co/sms/v2/std/get_rpt"
mengWangActCallbackURL = "http://message1.biliapi.co/sms/v2/std/get_rpt"
mengWangInternationalCallbackURL = "http://message2.biliapi.co/sms/v2/std/get_rpt"
[sms]
passportMobileUrl = "http://uat-passport.bilibili.co/intranet/acc/getTelByMid"
callbackProc = 0
singleSendProc = 50
batchSendProc = 10
monitorProcDuration = "5m"
blacklist = ["8617621660828"]

View File

@@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/job/main/sms/conf",
tags = ["automanaged"],
deps = [
"//library/conf:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,156 @@
package conf
import (
"errors"
"flag"
"go-common/library/conf"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
)
// Conf global variable.
var (
Conf = &Config{}
client *conf.Client
confPath string
)
// Config struct of conf.
type Config struct {
Log *log.Config
Tracer *trace.Config
Databus *databus.Config
SmsGRPC *warden.ClientConfig
HTTPClient *bm.ClientConfig
HTTPServer *bm.ServerConfig
UserReport *databus.Config
Wechat *wechat
Provider *Provider
Speedup *speedup
Sms *sms
}
type sms struct {
// PassportMobileURL 从passport获取用户手机号
PassportMobileURL string
// CallbackProc 处理回执的并发数
CallbackProc int
// SingleSendGoroutines 单发短信的并发数
SingleSendProc int
// BatchSendGoroutines 批量发送短信的并发数
BatchSendProc int
// MonitorProcDuration 定期监控databus有没有消费
MonitorProcDuration xtime.Duration
// Blacklist 黑名单手机号,用于压测
Blacklist []string
}
type wechat struct {
Token string
Secret string
Username string
}
// Provider provider conf
type Provider struct {
Providers []int32
// meng wang
MengWangSmsURL string
MengWangSmsUser string
MengWangSmsPwd string
MengWangActURL string
MengWangBatchURL string
MengWangActUser string
MengWangActPwd string
MengWangInternationURL string
MengWangInternationUser string
MengWangInternationPwd string
// chaung lan
ChuangLanSmsURL string
ChuangLanSmsUser string
ChuangLanSmsPwd string
ChuangLanActURL string
ChuangLanActUser string
ChuangLanActPwd string
ChuangLanInternationURL string
ChuangLanInternationUser string
ChuangLanInternationPwd string
// chuang lan callback
ChuangLanSmsCallbackURL string
ChuangLanActCallbackURL string
ChuangLanInternationalCallbackURL string
// meng wang callback
MengWangSmsCallbackURL string
MengWangActCallbackURL string
MengWangInternationalCallbackURL string
}
// speedup network
type speedup struct {
Switch bool
// meng wang
MengWangSmsURL string
MengWangActURL string
MengWangBatchURL string
MengWangInternationURL string
// chaung lan
ChuangLanSmsURL string
ChuangLanInternationURL string
ChuangLanActURL string
// meng wang callback
MengWangSmsCallbackURL string
MengWangActCallbackURL string
MengWangInternationalCallbackURL string
// chaung lan callback
ChuangLanSmsCallbackURL string
ChuangLanActCallbackURL string
ChuangLanInternationalCallbackURL string
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init create config instance.
func Init() (err error) {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
err = load()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

View File

@@ -0,0 +1,55 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["user_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"user.go",
"wechat.go",
],
importpath = "go-common/app/job/main/sms/dao",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/job/main/sms/model:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/stat/prom:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/job/main/sms/dao/chuanglan:all-srcs",
"//app/job/main/sms/dao/mengwang:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/app/job/main/sms/dao/chuanglan",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/job/main/sms/model:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,224 @@
package chuanglan
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"go-common/app/job/main/sms/conf"
"go-common/app/job/main/sms/model"
smsmdl "go-common/app/service/main/sms/model"
"go-common/library/log"
xhttp "go-common/library/net/http/blademaster"
)
// Client .
type Client struct {
conf conf.Provider
client *xhttp.Client
}
type response struct {
Code string `json:"code"`
MsgID string `json:"msgId"`
ErrMsg string `json:"errorMsg"`
Time string `json:"time"`
}
// GetPid get pid
func (v *Client) GetPid() int32 {
return smsmdl.ProviderChuangLan
}
// NewClient new ChuangLan
func NewClient(c *conf.Config) *Client {
return &Client{
conf: *c.Provider,
client: xhttp.NewClient(c.HTTPClient),
}
}
// SendSms send sms
func (v *Client) SendSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
msg := model.SmsPrefix + r.Content
params := make(map[string]interface{})
params["account"] = v.conf.ChuangLanSmsUser
params["password"] = v.conf.ChuangLanSmsPwd
params["phone"] = r.Mobile
params["msg"] = url.QueryEscape(msg)
params["report"] = "true"
uri := v.conf.ChuangLanSmsURL
msgid, err = v.post(ctx, uri, params)
return
}
// SendActSms send act sms
func (v *Client) SendActSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
msg := model.SmsPrefix + r.Content + model.SmsSuffixChuangLan
params := make(map[string]interface{})
params["account"] = v.conf.ChuangLanActUser
params["password"] = v.conf.ChuangLanActPwd
params["phone"] = r.Mobile
params["msg"] = url.QueryEscape(msg)
params["report"] = "true"
uri := v.conf.ChuangLanActURL
msgid, err = v.post(ctx, uri, params)
return
}
// SendBatchActSms send batch act sms
func (v *Client) SendBatchActSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
msgid, err = v.SendActSms(ctx, r)
return
}
// SendInternationalSms send international sms
func (v *Client) SendInternationalSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
msg := model.SmsPrefix + r.Content
params := make(map[string]interface{})
params["account"] = v.conf.ChuangLanInternationUser
params["password"] = v.conf.ChuangLanInternationPwd
params["mobile"] = r.Country + r.Mobile
params["msg"] = msg
uri := v.conf.ChuangLanInternationURL
bytesData, err := json.Marshal(params)
if err != nil {
log.Error("ChuangLan send international Marshal error(%v)", err)
return
}
reader := bytes.NewReader(bytesData)
request, err := http.NewRequest(http.MethodPost, uri, reader)
if err != nil {
log.Error("ChuangLan send international NewRequest err(%v)", err)
return
}
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
type internation struct {
Code string `json:"code"`
Msgid string `json:"msgid"`
Error string `json:"error"`
}
res := &internation{}
if err = v.client.Do(ctx, request, res); err != nil {
log.Error("ChuangLan send international client.Do err(%v)", err)
return
}
if res.Code != "0" {
err = fmt.Errorf("ChuangLan send international sms code(%v) err(%v)", res.Code, res.Error)
return
}
msgid = res.Msgid
return
}
func (v *Client) post(ctx context.Context, uri string, params map[string]interface{}) (msgid string, err error) {
bytesData, err := json.Marshal(params)
if err != nil {
log.Error("ChuangLan Marshal error(%v)", err)
return
}
reader := bytes.NewReader(bytesData)
request, err := http.NewRequest(http.MethodPost, uri, reader)
if err != nil {
log.Error("ChuangLan NewRequest err(%v)", err)
return
}
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
res := &response{}
if err = v.client.Do(ctx, request, res); err != nil {
log.Error("ChuangLan client.Do err(%v)", err)
return
}
if res.Code != "0" {
err = fmt.Errorf("ChuangLan send sms code(%v) err(%v)", res.Code, res.ErrMsg)
return
}
msgid = res.MsgID
log.Info("url(%s) body(%v) resp(%+v)", uri, params, res)
return
}
// Callback .
type Callback struct {
MsgID string `json:"msgId"`
Mobile string `json:"mobile"`
Status string `json:"status"`
Desc string `json:"statusDesc"`
NotifyTime string `json:"notifyTime"`
ReportTime string `json:"reportTime"`
Length string `json:"length"`
}
type callbackResponse struct {
Code int `json:"ret"`
Result []*Callback `json:"result"`
}
// Callback sms callbacks.
func (v *Client) Callback(ctx context.Context, account, pwd, url string, count int) (callbacks []*Callback, err error) {
params := make(map[string]interface{})
params["account"] = account
params["password"] = pwd
params["count"] = strconv.Itoa(count)
bs, err := json.Marshal(params)
if err != nil {
log.Error("ChuangLan sms callback Marshal error(%v)", err)
return
}
request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(bs))
if err != nil {
log.Error("ChuangLan sms callback NewRequest err(%v)", err)
return
}
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
res := &callbackResponse{}
if err = v.client.Do(ctx, request, res); err != nil {
log.Error("ChuangLan sms callback client.Do err(%v)", err)
return
}
if res.Code != 0 {
err = fmt.Errorf("ChuangLan sms callback code(%d)", res.Code)
return
}
callbacks = res.Result
return
}
// CallbackInternational sms callbacks.
func (v *Client) CallbackInternational(ctx context.Context, count int) (callbacks []*Callback, err error) {
params := make(map[string]interface{})
params["account"] = v.conf.ChuangLanInternationUser
params["password"] = v.conf.ChuangLanInternationPwd
params["count"] = strconv.Itoa(count)
bs, err := json.Marshal(params)
if err != nil {
log.Error("ChuangLan international sms callback Marshal error(%v)", err)
return
}
request, err := http.NewRequest(http.MethodPost, v.conf.ChuangLanInternationalCallbackURL, bytes.NewReader(bs))
if err != nil {
log.Error("ChuangLan international sms callback NewRequest err(%v)", err)
return
}
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
type intCallbackResponse struct {
Code int `json:"code"`
Error string `json:"error"`
Result []*Callback `json:"result"`
}
res := &intCallbackResponse{}
if err = v.client.Do(ctx, request, res); err != nil {
log.Error("ChuangLan international sms callback client.Do err(%v)", err)
return
}
if res.Code != 0 {
err = fmt.Errorf("ChuangLan international sms callback code(%d)", res.Code)
return
}
callbacks = res.Result
return
}

View File

@@ -0,0 +1,71 @@
package chuanglan
import (
"context"
"flag"
"path/filepath"
"testing"
"go-common/app/job/main/sms/conf"
"go-common/app/service/main/sms/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
cl *Client
sendLog = &model.ModelSend{
Content: "卍 您的账号正在哔哩哔哩2018动画角色人气大赏活动中进行领票操作验证码为5467当日有效",
}
)
func init() {
dir, _ := filepath.Abs("../../cmd/sms-job-test.toml")
flag.Set("conf", dir)
conf.Init()
cl = NewClient(conf.Conf)
}
func TestGetPid(t *testing.T) {
Convey("test chuanglan get pid", t, func() {
pID := cl.GetPid()
So(pID, ShouldEqual, model.ProviderChuangLan)
})
}
func TestSendSms(t *testing.T) {
Convey("test chuanglan send sms", t, func() {
sendLog.Mobile = ""
msgid, err := cl.SendSms(context.TODO(), sendLog)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}
func TestSendActSms(t *testing.T) {
Convey("test ChuangLan SendActSms", t, func() {
sendLog.Mobile = ""
msgid, err := cl.SendActSms(context.TODO(), sendLog)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}
func TestSendBatchActSms(t *testing.T) {
Convey("test ChuangLan sendBatchActSms", t, func() {
sendLog.Mobile = "" // 187****3870,189****1728
msgid, err := cl.SendBatchActSms(context.TODO(), sendLog)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}
func TestSendInternationalSms(t *testing.T) {
Convey("test ChuangLan SendInternationalSms", t, func() {
sendLog.Mobile = "" // 5344295506
sendLog.Country = "1"
msgid, err := cl.SendInternationalSms(context.TODO(), sendLog)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}

View File

@@ -0,0 +1,47 @@
package dao
import (
"context"
"go-common/app/job/main/sms/conf"
bm "go-common/library/net/http/blademaster"
"go-common/library/stat/prom"
)
// Dao struct info of Dao.
type Dao struct {
c *conf.Config
httpClient *bm.Client
}
var (
errorsCount = prom.BusinessErrCount
infosCount = prom.BusinessInfoCount
)
// New new a Dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
httpClient: bm.NewClient(c.HTTPClient),
}
return
}
// PromError prometheus error count.
func PromError(name string) {
errorsCount.Incr(name)
}
// PromInfo prometheus info count.
func PromInfo(name string) {
infosCount.Incr(name)
}
// Close close connections of mc, redis, db.
func (d *Dao) Close() {}
// Ping ping health of db.
func (d *Dao) Ping(c context.Context) (err error) {
return
}

View File

@@ -0,0 +1,48 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/app/job/main/sms/dao/mengwang",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/job/main/sms/model:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,209 @@
package mengwang
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"go-common/app/job/main/sms/conf"
"go-common/app/job/main/sms/model"
smsmdl "go-common/app/service/main/sms/model"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// Client .
type Client struct {
c conf.Provider
client *bm.Client
}
type response struct {
Result int `json:"result"`
MsgID int64 `json:"msgid"`
}
// GetPid gets MengWang type ID.
func (v *Client) GetPid() int32 {
return smsmdl.ProviderMengWang
}
// NewClient new MengWang.
func NewClient(c *conf.Config) *Client {
return &Client{
c: *c.Provider,
client: bm.NewClient(c.HTTPClient),
}
}
func (v *Client) post(ctx context.Context, url string, params interface{}, res *response) (err error) {
var bs []byte
if bs, err = json.Marshal(params); err != nil {
log.Error("json.Marshal param(%v) error(%v)", params, err)
return
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer([]byte(bs)))
if err != nil {
log.Error("http.NewRequest(%s) param(%v) error(%v)", url, params, err)
return
}
req.Header.Set("Content-Type", "application/json")
if err = v.client.Do(ctx, req, &res); err != nil {
log.Error("client.Do(%s,%v) error(%v)", url, params, err)
return
}
log.Info("url(%s) body(%v) resp(%+v)", url, params, res)
return
}
// SendSms sends MengWang sms.
func (v *Client) SendSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
params := make(map[string]string)
pwd, ts := genPwd(v.c.MengWangSmsUser, v.c.MengWangSmsPwd)
params["userid"] = v.c.MengWangSmsUser
params["pwd"] = pwd
params["timestamp"] = ts
params["mobile"] = r.Mobile
params["content"] = url.QueryEscape(r.Content)
res := new(response)
if err = v.post(ctx, v.c.MengWangSmsURL, params, res); err != nil {
log.Error("mengwang SendSms param(%v) error(%v)", params, err)
return
}
if res.Result != 0 {
err = fmt.Errorf("mengwang SendSms param(%v) error(%v)", params, res.Result)
return
}
msgid = strconv.FormatInt(res.MsgID, 10)
return
}
// SendActSms sends MengWang act sms.
func (v *Client) SendActSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
params := make(map[string]string)
pwd, ts := genPwd(v.c.MengWangActUser, v.c.MengWangActPwd)
r.Content = r.Content + model.SmsSuffix
params["userid"] = v.c.MengWangActUser
params["pwd"] = pwd
params["timestamp"] = ts
params["mobile"] = r.Mobile
params["content"] = url.QueryEscape(r.Content)
res := new(response)
if err = v.post(ctx, v.c.MengWangActURL, params, res); err != nil {
log.Error("mengwang SendActSms param(%v) error(%v)", params, err)
return
}
if res.Result != 0 {
err = fmt.Errorf("mengwang SendActSms param(%v) error(%v)", params, res.Result)
return
}
msgid = strconv.FormatInt(res.MsgID, 10)
return
}
// SendBatchActSms sends multi MengWang act sms.
func (v *Client) SendBatchActSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
params := make(map[string]string)
pwd, ts := genPwd(v.c.MengWangActUser, v.c.MengWangActPwd)
params["userid"] = v.c.MengWangActUser
params["pwd"] = pwd
params["timestamp"] = ts
params["mobile"] = r.Mobile
params["content"] = url.QueryEscape(r.Content + model.SmsSuffix)
res := new(response)
if err = v.post(ctx, v.c.MengWangBatchURL, params, res); err != nil {
log.Error("mengwang SendBatchActSms param(%v) error(%v)", params, err)
return
}
if res.Result != 0 {
err = fmt.Errorf("mengwang SendBatchActSms param(%v) error(%v)", params, res.Result)
return
}
msgid = strconv.FormatInt(res.MsgID, 10)
return
}
// SendInternationalSms sends MengWang international sms.
func (v *Client) SendInternationalSms(ctx context.Context, r *smsmdl.ModelSend) (msgid string, err error) {
params := make(map[string]string)
pwd, ts := genPwd(v.c.MengWangInternationUser, v.c.MengWangInternationPwd)
params["userid"] = v.c.MengWangInternationUser
params["pwd"] = pwd
params["timestamp"] = ts
params["mobile"] = "00" + r.Country + r.Mobile
params["content"] = url.QueryEscape(r.Content)
res := new(response)
if err = v.post(ctx, v.c.MengWangInternationURL, params, res); err != nil {
log.Error("mengwang SendInternationalSms param(%v) error(%v)", params, err)
return
}
if res.Result != 0 {
err = fmt.Errorf("mengwang SendInternationalSms param(%v) error(%v)", params, res.Result)
return
}
msgid = strconv.FormatInt(res.MsgID, 10)
return
}
// Callback .
type Callback struct {
MsgID int64 `json:"msgid"`
Num int `json:"pknum"`
Total int `json:"pktotal"`
Mobile string `json:"mobile"`
SendTime string `json:"stime"`
ReportTime string `json:"rtime"`
Status string `json:"errcode"`
Desc string `json:"errdesc"`
}
type callbackResponse struct {
Result int `json:"result"`
Callbacks []*Callback `json:"rpts"`
}
// Callback .
func (v *Client) Callback(ctx context.Context, user, pwd, url string, count int) (callbacks []*Callback, err error) {
pwd, ts := genPwd(user, pwd)
params := make(map[string]string)
params["userid"] = user
params["pwd"] = pwd
params["timestamp"] = ts
params["retsize"] = strconv.Itoa(count)
bs, err := json.Marshal(params)
if err != nil {
log.Error("json.Marshal param(%v) error(%v)", params, err)
return
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer([]byte(bs)))
if err != nil {
log.Error("http.NewRequest(%s) param(%v) error(%v)", url, params, err)
return
}
req.Header.Set("Content-Type", "application/json")
res := new(callbackResponse)
if err = v.client.Do(ctx, req, &res); err != nil {
log.Error("client.Do(%s,%v) error(%v)", url, params, err)
return
}
if res.Result != 0 {
err = fmt.Errorf("mengwang callback param(%v) res(%+v)", params, res)
return
}
callbacks = res.Callbacks
return
}
func genPwd(user, pwd string) (spwd, ts string) {
ft := time.Now().Format("0102150405")
str := fmt.Sprintf("%s%s%s%s", user, "00000000", pwd, ft)
mh := md5.Sum([]byte(str))
return hex.EncodeToString(mh[:]), ft
}

View File

@@ -0,0 +1,71 @@
package mengwang
import (
"context"
"flag"
"path/filepath"
"testing"
"go-common/app/job/main/sms/conf"
"go-common/app/service/main/sms/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
mw *Client
sl = &model.ModelSend{Mobile: "", Content: "卍 测试短信验证码为5467当日有效 https://search.bilibili.com/all?keyword=你好"} // 17621660828
isl = &model.ModelSend{Country: "852", Mobile: "", Content: "卍 您的账号正在哔哩哔哩2017动画角色人气大赏活动中进行领票操作验证码为5467当日有效"} // 00852 69529378 梦网的香港测试号
)
func init() {
dir, _ := filepath.Abs("../../cmd/sms-job-test.toml")
flag.Set("conf", dir)
conf.Init()
mw = NewClient(conf.Conf)
}
func TestSendSms(t *testing.T) {
Convey("mengwang send sms", t, func() {
msgid, err := mw.SendSms(context.TODO(), sl)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}
func TestSendActSms(t *testing.T) {
Convey("mengwang send act sms", t, func() {
msgid, err := mw.SendActSms(context.TODO(), sl)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}
func TestSendBatchSms(t *testing.T) {
Convey("mengwang send batch sms", t, func() {
msl := sl
msl.Mobile = "" // 17621660828,17621660828
msgid, err := mw.SendActSms(context.TODO(), msl)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}
func TestSendInternationalSms(t *testing.T) {
Convey("mengwang send international sms", t, func() {
msgid, err := mw.SendInternationalSms(context.TODO(), isl)
So(err, ShouldBeNil)
t.Logf("msgid(%s)", msgid)
})
}
func TestCallback(t *testing.T) {
Convey("mengwang callback", t, func() {
// callbacks, err := mw.Callback(context.Background(), conf.Conf.Pconf.MengWangSmsUser, conf.Conf.Pconf.MengWangSmsPwd, conf.Conf.Pconf.MengWangSmsCallbackURL, 5)
// So(err, ShouldBeNil)
// t.Logf("callbacks(%d)", len(callbacks))
// for _, v := range callbacks {
// t.Logf("callback(%+v)", v)
// }
})
}

View File

@@ -0,0 +1,30 @@
package dao
import (
"context"
"fmt"
"net/url"
"strconv"
"go-common/app/job/main/sms/model"
"go-common/library/log"
)
// UserMobile get user mobile
func (d *Dao) UserMobile(c context.Context, mid int64) (*model.UserMobile, error) {
res := struct {
Code int `json:"code"`
Data model.UserMobile `json:"data"`
}{}
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
if err := d.httpClient.Get(c, d.c.Sms.PassportMobileURL, "", params, &res); err != nil {
log.Error("d.GetUserMobile(%d) error(%v)", mid, err)
return nil, err
}
if res.Code != 0 {
return nil, fmt.Errorf("GetUserMobile(%d) error, res(%+v)", mid, &res)
}
log.Info("GetUserMobile(%d) res(%+v)", mid, &res)
return &res.Data, nil
}

View File

@@ -0,0 +1,34 @@
package dao
import (
"context"
"flag"
"path/filepath"
"testing"
"go-common/app/job/main/sms/conf"
. "github.com/smartystreets/goconvey/convey"
)
var d *Dao
func WithDao(f func(d *Dao)) func() {
return func() {
dir, _ := filepath.Abs("../cmd/sms-job-test.toml")
flag.Set("conf", dir)
flag.Parse()
conf.Init()
d = New(conf.Conf)
f(d)
}
}
func Test_GetUserMobile(t *testing.T) {
Convey("get user mobile", t, WithDao(func(d *Dao) {
mob, err := d.UserMobile(context.TODO(), 27515615)
So(err, ShouldBeNil)
So(mob, ShouldNotBeEmpty)
t.Logf("user(%d) mobile(%v)", 27515615, mob)
}))
}

View File

@@ -0,0 +1,81 @@
package dao
import (
"bytes"
"context"
"crypto/md5"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strconv"
"time"
"go-common/library/log"
)
type wechatResp struct {
Status int `json:"status"`
Msg string `json:"msg"`
}
const (
// http://info.bilibili.co/pages/viewpage.action?pageId=5406728
_url = "http://bap.bilibili.co/api/v1/message/add"
)
// SendWechat 发送企业微信消息
func (d *Dao) SendWechat(msg string) (err error) {
log.Error("SendWechat logged error(%s)", msg)
params := map[string]string{
"content": msg,
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
"token": d.c.Wechat.Token,
"type": "wechat",
"username": d.c.Wechat.Username,
"url": "",
}
params["signature"] = d.sign(params)
b, err := json.Marshal(params)
if err != nil {
log.Error("SendWechat json.Marshal error(%v)", err)
return
}
req, err := http.NewRequest(http.MethodPost, _url, bytes.NewReader(b))
if err != nil {
log.Error("SendWechat NewRequest error(%v), params(%s)", err, string(b))
return
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := wechatResp{}
if err = d.httpClient.Do(context.TODO(), req, &res); err != nil {
log.Error("SendWechat Do error(%v), params(%s)", err, string(b))
return
}
if res.Status != 0 {
err = fmt.Errorf("status(%d) msg(%s)", res.Status, res.Msg)
log.Error("SendWechat response error(%v), params(%s)", err, string(b))
}
return
}
func (d *Dao) sign(params map[string]string) string {
var keys []string
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.Buffer{}
for _, k := range keys {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(url.QueryEscape(k) + "=")
buf.WriteString(url.QueryEscape(params[k]))
}
h := md5.New()
io.WriteString(h, buf.String()+d.c.Wechat.Secret)
return fmt.Sprintf("%x", h.Sum(nil))
}

View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["http.go"],
importpath = "go-common/app/job/main/sms/http",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/job/main/sms/service:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,39 @@
package http
import (
"net/http"
"go-common/app/job/main/sms/conf"
"go-common/app/job/main/sms/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var srv *service.Service
// Init init http service
func Init(c *conf.Config, s *service.Service) {
srv = s
engine := bm.DefaultServer(c.HTTPServer)
route(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
func route(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
}
func ping(c *bm.Context) {
if err := srv.Ping(c); err != nil {
log.Error("sms-job ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["model.go"],
importpath = "go-common/app/job/main/sms/model",
tags = ["automanaged"],
deps = ["//app/service/main/sms/model:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,58 @@
package model
import (
"container/ring"
"context"
"encoding/json"
"sync"
smsmdl "go-common/app/service/main/sms/model"
)
const (
// SmsPrefix .
SmsPrefix = "【哔哩哔哩】"
// SmsSuffix .
SmsSuffix = " 回TD退订"
// SmsSuffixChuangLan .
SmsSuffixChuangLan = " 退订回T"
)
// Provider service provider
type Provider interface {
// SendSms send sms
GetPid() int32
// SendSms send sms
SendSms(context.Context, *smsmdl.ModelSend) (string, error)
// SendActSms send act sms
SendActSms(context.Context, *smsmdl.ModelSend) (string, error)
// SendBatchActSms send batch act sms
SendBatchActSms(context.Context, *smsmdl.ModelSend) (string, error)
// SendInternationalSms send international sms
SendInternationalSms(context.Context, *smsmdl.ModelSend) (string, error)
}
// Message .
type Message struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}
// UserMobile .
type UserMobile struct {
CountryCode string `json:"code"`
Mobile string `json:"tel"`
}
// ConcurrentRing thread-safe ring
type ConcurrentRing struct {
*ring.Ring
sync.Mutex
}
// NewConcurrentRing .
func NewConcurrentRing(length int) *ConcurrentRing {
return &ConcurrentRing{Ring: ring.New(length)}
}

View File

@@ -0,0 +1,63 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/job/main/sms/model:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//library/net/trace:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"callback.go",
"log.go",
"service.go",
"sms.go",
],
importpath = "go-common/app/job/main/sms/service",
tags = ["automanaged"],
deps = [
"//app/job/main/sms/conf:go_default_library",
"//app/job/main/sms/dao:go_default_library",
"//app/job/main/sms/dao/chuanglan:go_default_library",
"//app/job/main/sms/dao/mengwang:go_default_library",
"//app/job/main/sms/model:go_default_library",
"//app/service/main/sms/api:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//library/conf/env:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,160 @@
package service
import (
"context"
"strconv"
"time"
"go-common/app/job/main/sms/dao/chuanglan"
"go-common/app/job/main/sms/dao/mengwang"
smsmdl "go-common/app/service/main/sms/model"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
const (
_callbackSize = 100
)
func (s *Service) dispatchCallback(provider int32) {
switch provider {
case smsmdl.ProviderChuangLan:
s.waiter.Add(1)
go s.callbackChuangLanproc()
case smsmdl.ProviderMengWang:
s.waiter.Add(1)
go s.callbackMengWangproc()
}
}
func (s *Service) callbackChuangLanproc() {
defer s.waiter.Done()
log.Info("callbackChuangLanproc start")
group := errgroup.Group{}
cli := chuanglan.NewClient(s.c)
for {
if s.closed {
log.Info("callbackChuangLanproc exit")
return
}
group.Go(func() error {
callbacks, err := cli.Callback(context.Background(), s.c.Provider.ChuangLanSmsUser, s.c.Provider.ChuangLanSmsPwd, s.c.Provider.ChuangLanSmsCallbackURL, _callbackSize)
if err != nil {
time.Sleep(time.Second)
return nil
}
s.sendChuangLanCallbacks(smsmdl.TypeSms, callbacks)
return nil
})
group.Go(func() error {
callbacks, err := cli.Callback(context.Background(), s.c.Provider.ChuangLanActUser, s.c.Provider.ChuangLanActPwd, s.c.Provider.ChuangLanActCallbackURL, _callbackSize)
if err != nil {
time.Sleep(time.Second)
return nil
}
s.sendChuangLanCallbacks(smsmdl.TypeActSms, callbacks)
return nil
})
group.Go(func() error {
callbacks, err := cli.CallbackInternational(context.Background(), _callbackSize)
if err != nil {
time.Sleep(time.Second)
return nil
}
s.sendChuangLanCallbacks(smsmdl.TypeSms, callbacks)
return nil
})
group.Wait()
time.Sleep(time.Second)
}
}
func (s *Service) sendChuangLanCallbacks(typ int32, cbs []*chuanglan.Callback) (err error) {
ts := time.Now().Unix()
for _, cb := range cbs {
if cb.NotifyTime != "" {
if t, e := time.ParseInLocation("060102150405", cb.NotifyTime, time.Local); e != nil {
log.Warn("sendChuangLanCallbacks(%+v) parse time error(%v)", cb, e)
} else {
ts = t.Unix()
}
}
s.sendUserActionLog(&smsmdl.ModelUserActionLog{
MsgID: cb.MsgID,
Mobile: cb.Mobile,
Status: cb.Status,
Desc: cb.Desc,
Provider: smsmdl.ProviderChuangLan,
Type: typ,
Action: smsmdl.UserActionCallback,
Ts: ts,
})
}
return
}
func (s *Service) callbackMengWangproc() {
defer s.waiter.Done()
log.Info("callbackMengWangproc start")
group := errgroup.Group{}
cli := mengwang.NewClient(s.c)
for {
if s.closed {
log.Info("callbackMengWangproc exit")
return
}
group.Go(func() error {
callbacks, err := cli.Callback(context.Background(), s.c.Provider.MengWangSmsUser, s.c.Provider.MengWangSmsPwd, s.c.Provider.MengWangSmsCallbackURL, _callbackSize)
if err != nil {
time.Sleep(time.Second)
return nil
}
s.sendMengWangCallbacks(smsmdl.TypeSms, callbacks)
return nil
})
group.Go(func() error {
callbacks, err := cli.Callback(context.Background(), s.c.Provider.MengWangActUser, s.c.Provider.MengWangActPwd, s.c.Provider.MengWangActCallbackURL, _callbackSize)
if err != nil {
time.Sleep(time.Second)
return nil
}
s.sendMengWangCallbacks(smsmdl.TypeActSms, callbacks)
return nil
})
group.Go(func() error {
callbacks, err := cli.Callback(context.Background(), s.c.Provider.MengWangInternationUser, s.c.Provider.MengWangInternationPwd, s.c.Provider.MengWangInternationalCallbackURL, _callbackSize)
if err != nil {
time.Sleep(time.Second)
return nil
}
s.sendMengWangCallbacks(smsmdl.TypeSms, callbacks)
return nil
})
group.Wait()
time.Sleep(time.Second)
}
}
func (s *Service) sendMengWangCallbacks(typ int32, cbs []*mengwang.Callback) (err error) {
ts := time.Now().Unix()
for _, cb := range cbs {
if cb.ReportTime != "" {
if t, e := time.ParseInLocation("2006-01-02 15:04:05", cb.ReportTime, time.Local); e != nil {
log.Warn("sendMengWangCallbacks(%+v) parse time error(%v)", cb, e)
} else {
ts = t.Unix()
}
}
s.sendUserActionLog(&smsmdl.ModelUserActionLog{
MsgID: strconv.FormatInt(cb.MsgID, 10),
Mobile: cb.Mobile,
Status: cb.Status,
Desc: cb.Desc,
Provider: smsmdl.ProviderMengWang,
Type: typ,
Action: smsmdl.UserActionCallback,
Ts: ts,
})
}
return
}

View File

@@ -0,0 +1,39 @@
package service
import (
"strings"
"time"
smsmdl "go-common/app/service/main/sms/model"
"go-common/library/log"
"go-common/library/queue/databus/report"
)
const (
_reportType = 111
)
func (s *Service) sendUserActionLog(l *smsmdl.ModelUserActionLog) {
if l.Mobile == "" {
log.Warn("sendUserActionLog mobile is empty, log(%+v)", l)
return
}
for _, mobile := range strings.Split(l.Mobile, ",") {
r := &report.UserInfo{
Business: _reportType,
Ctime: time.Unix(l.Ts, 0),
Index: []interface{}{mobile},
Content: map[string]interface{}{
"msgid": l.MsgID,
"content": l.Content,
"status": l.Status,
"desc": l.Desc,
"provider": l.Provider,
"type": l.Type,
"action": l.Action,
},
}
log.Info("sendUserActionLog(%+v)", r)
report.User(r)
}
}

View File

@@ -0,0 +1,150 @@
package service
import (
"context"
"sync"
"go-common/app/job/main/sms/conf"
"go-common/app/job/main/sms/dao"
"go-common/app/job/main/sms/dao/chuanglan"
"go-common/app/job/main/sms/dao/mengwang"
"go-common/app/job/main/sms/model"
smsgrpc "go-common/app/service/main/sms/api"
smsmdl "go-common/app/service/main/sms/model"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/sync/pipeline/fanout"
)
// Service struct of service.
type Service struct {
c *conf.Config
dao *dao.Dao
databus *databus.Databus
smsgrpc smsgrpc.SmsClient
waiter *sync.WaitGroup
sms chan *smsmdl.ModelSend // 验证码
actSms chan *smsmdl.ModelSend // 营销
batchSms chan *smsmdl.ModelSend // 批量
smsp *model.ConcurrentRing
intep *model.ConcurrentRing
actp *model.ConcurrentRing
batchp *model.ConcurrentRing
cache *fanout.Fanout
providers int
closed bool
smsCount int64
blacklist map[string]struct{}
}
// New create service instance and return.
func New(c *conf.Config) (s *Service) {
proLen := len(c.Provider.Providers)
if c.Speedup.Switch {
proLen *= 2 // 每个短信服务商再提供一个云线路 http client
}
s = &Service{
c: c,
dao: dao.New(c),
databus: databus.New(c.Databus),
waiter: new(sync.WaitGroup),
sms: make(chan *smsmdl.ModelSend, 10240),
actSms: make(chan *smsmdl.ModelSend, 10240),
batchSms: make(chan *smsmdl.ModelSend, 10240),
smsp: model.NewConcurrentRing(proLen),
intep: model.NewConcurrentRing(proLen),
actp: model.NewConcurrentRing(proLen),
batchp: model.NewConcurrentRing(proLen),
cache: fanout.New("async-task", fanout.Worker(1), fanout.Buffer(10240)),
providers: proLen,
}
s.initBlacklist()
var err error
if s.smsgrpc, err = smsgrpc.NewClient(c.SmsGRPC); err != nil {
panic(err)
}
s.initProviders()
s.waiter.Add(1)
go s.subproc()
go s.monitorproc()
for i := 0; i < s.c.Sms.SingleSendProc; i++ {
s.waiter.Add(1)
go s.smsproc()
s.waiter.Add(1)
go s.actsmsproc()
}
for i := 0; i < s.c.Sms.BatchSendProc; i++ {
s.waiter.Add(1)
go s.actbatchproc()
}
return
}
func (s *Service) initBlacklist() {
s.blacklist = make(map[string]struct{})
for _, v := range s.c.Sms.Blacklist {
s.blacklist[v] = struct{}{}
}
}
func (s *Service) initProviders() {
// 创建本地网络 http client
s.newProviders(s.c)
if !s.c.Speedup.Switch {
return
}
// 替换成 云加速线路 URL 配置
s.c.Provider.MengWangSmsURL = s.c.Speedup.MengWangSmsURL
s.c.Provider.MengWangActURL = s.c.Speedup.MengWangActURL
s.c.Provider.MengWangBatchURL = s.c.Speedup.MengWangBatchURL
s.c.Provider.MengWangInternationURL = s.c.Speedup.MengWangInternationURL
s.c.Provider.ChuangLanSmsURL = s.c.Speedup.ChuangLanSmsURL
s.c.Provider.ChuangLanActURL = s.c.Speedup.ChuangLanActURL
s.c.Provider.ChuangLanInternationURL = s.c.Speedup.ChuangLanInternationURL
s.c.Provider.MengWangSmsCallbackURL = s.c.Speedup.MengWangSmsCallbackURL
s.c.Provider.MengWangActCallbackURL = s.c.Speedup.MengWangActCallbackURL
s.c.Provider.MengWangInternationalCallbackURL = s.c.Speedup.MengWangInternationalCallbackURL
s.c.Provider.ChuangLanSmsCallbackURL = s.c.Speedup.ChuangLanSmsCallbackURL
s.c.Provider.ChuangLanActCallbackURL = s.c.Speedup.ChuangLanActCallbackURL
s.c.Provider.ChuangLanInternationalCallbackURL = s.c.Speedup.ChuangLanInternationalCallbackURL
// 创建云加速线路 http client
s.newProviders(s.c)
}
func (s *Service) newProviders(c *conf.Config) {
var cli model.Provider
for _, p := range c.Provider.Providers {
switch p {
case smsmdl.ProviderMengWang:
cli = mengwang.NewClient(c)
case smsmdl.ProviderChuangLan:
cli = chuanglan.NewClient(c)
default:
log.Error("invalid provider(%d)", p)
continue
}
s.smsp.Value = cli
s.smsp.Ring = s.smsp.Next()
s.intep.Value = cli
s.intep.Ring = s.intep.Next()
s.actp.Value = cli
s.actp.Ring = s.actp.Next()
s.batchp.Value = cli
s.batchp.Ring = s.batchp.Next()
for i := 0; i < s.c.Sms.CallbackProc; i++ {
s.dispatchCallback(p)
}
}
}
// Ping check service health.
func (s *Service) Ping(ctx context.Context) error {
return s.dao.Ping(ctx)
}
// Close kafka consumer close.
func (s *Service) Close() {
s.closed = true
s.databus.Close()
s.waiter.Wait()
}

View File

@@ -0,0 +1,74 @@
package service
import (
"container/ring"
"context"
"flag"
"path/filepath"
"testing"
"time"
"go-common/app/job/main/sms/conf"
"go-common/app/job/main/sms/model"
smsmdl "go-common/app/service/main/sms/model"
"go-common/library/net/trace"
. "github.com/smartystreets/goconvey/convey"
)
var srv *Service
func init() {
dir, _ := filepath.Abs("../cmd/sms-job-test.toml")
flag.Set("conf", dir)
conf.Init()
srv = New(conf.Conf)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
f(srv)
}
}
func Test_ring(t *testing.T) {
Convey("test ring", t, WithService(func(s *Service) {
r := ring.New(3)
r.Value = 0
r = r.Next()
r.Value = 1
r = r.Next()
r.Value = 2
So(r.Len(), ShouldEqual, 3)
for i := 0; i < 5; i++ {
r = r.Next()
t.Logf("%d", r.Value)
}
}))
}
func Test_Sms(t *testing.T) {
Convey("sms", t, WithService(func(s *Service) {
// http request会自动加trace header不init trace的话,header value为空为会兴企reset
trace.Init(s.c.Tracer)
defer trace.Close()
c := context.TODO()
sl := &smsmdl.ModelSend{Mobile: "", Content: "您的账号正在哔哩哔哩2017动画角色人气大赏活动中进行领票操作验证码为123456当日有效", Code: "whatever", Type: 1}
p := s.smsp.Value.(model.Provider)
_, err := p.SendSms(c, sl)
So(err, ShouldBeNil)
s.smsp.Ring = s.smsp.Next()
p = s.smsp.Value.(model.Provider)
_, err = p.SendSms(c, sl)
So(err, ShouldBeNil)
s.smsp.Ring = s.smsp.Next()
p = s.smsp.Value.(model.Provider)
_, err = p.SendSms(c, sl)
So(err, ShouldBeNil)
s.smsp.Ring = s.smsp.Next()
p = s.smsp.Value.(model.Provider)
_, err = p.SendSms(c, sl)
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,311 @@
package service
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"go-common/app/job/main/sms/dao"
"go-common/app/job/main/sms/model"
smsmdl "go-common/app/service/main/sms/model"
"go-common/library/conf/env"
"go-common/library/log"
)
const _retry = 3
var _contentRe = regexp.MustCompile(`\d`)
func (s *Service) subproc() {
defer s.waiter.Done()
for {
item, ok := <-s.databus.Messages()
if !ok {
close(s.sms)
close(s.actSms)
close(s.batchSms)
log.Info("databus: sms-job subproc consumer exit!")
return
}
s.smsCount++
msg := new(smsmdl.ModelSend)
if err := json.Unmarshal(item.Value, &msg); err != nil {
log.Error("json.Unmarshal (%v) error(%v)", string(item.Value), err)
continue
}
log.Info("subproc topic(%s) key(%s) partition(%v) offset(%d) message(%s)", item.Topic, item.Key, item.Partition, item.Offset, item.Value)
// 黑名单,用于压测
if _, ok := s.blacklist[msg.Country+msg.Mobile]; ok {
log.Info("country(%s) mobile(%s) in blacklist", msg.Country, msg.Mobile)
item.Commit()
continue
}
switch msg.Type {
case smsmdl.TypeSms:
s.sms <- msg
case smsmdl.TypeActSms:
s.actSms <- msg
case smsmdl.TypeActBatch:
s.batchSms <- msg
}
item.Commit()
}
}
func (s *Service) smsproc() {
defer s.waiter.Done()
var (
err error
msgid string
)
for {
m, ok := <-s.sms
if !ok {
log.Info("smsproc exit!")
return
}
if m.Mobile == "" {
if m.Country, m.Mobile, err = s.userMobile(m.Mid); err != nil {
continue
}
}
if m.Country == "" || m.Mobile == "" {
log.Error("invalid country or mobile, info(%+v)", m)
continue
}
content := _contentRe.ReplaceAllString(m.Content, "*")
l := &smsmdl.ModelUserActionLog{Mobile: m.Mobile, Content: content, Type: smsmdl.TypeSms, Action: smsmdl.UserActionTypeSend}
if m.Country == smsmdl.CountryChina {
for i := 0; i < s.providers; i++ {
s.smsp.Lock()
p := s.smsp.Value.(model.Provider)
s.smsp.Ring = s.smsp.Next()
s.smsp.Unlock()
l.Provider = p.GetPid()
if msgid, err = p.SendSms(context.Background(), m); err == nil {
break
}
dao.PromInfo(fmt.Sprintf("service:retry %d", l.Provider))
log.Error("retry send sms(%v) platform(%d) error(%v)", m, l.Provider, err)
}
} else {
for i := 0; i < s.providers; i++ {
s.intep.Lock()
p := s.intep.Value.(model.Provider)
s.intep.Ring = s.intep.Next()
s.intep.Unlock()
l.Provider = p.GetPid()
if msgid, err = p.SendInternationalSms(context.Background(), m); err == nil {
break
}
dao.PromInfo(fmt.Sprintf("service:retry international %d", l.Provider))
log.Error("retry send international sms(%v) platform(%d) error(%v)", m, l.Provider, err)
}
}
if err == nil {
l.Status = smsmdl.UserActionSendSuccessStatus
l.Desc = smsmdl.UserActionSendSuccessDesc
dao.PromInfo(fmt.Sprintf("service:success %d", l.Provider))
log.Info("send sms(%v) platform(%d) success", m, l.Provider)
} else {
l.Status = smsmdl.UserActionSendFailedStatus
l.Desc = smsmdl.UserActionSendFailedDesc
dao.PromError("service:sms")
log.Error("send sms(%v) error(%v)", m, err)
s.cache.Do(context.Background(), func(ctx context.Context) {
s.dao.SendWechat(fmt.Sprintf("sms-job send msg(%d) error(%v)", m.ID, err))
})
}
l.MsgID = msgid
l.Ts = time.Now().Unix()
s.sendUserActionLog(l)
}
}
func (s *Service) actsmsproc() {
defer s.waiter.Done()
var (
err error
msgid string
)
for {
m, ok := <-s.actSms
if !ok {
log.Info("actsmsproc exit!")
return
}
if m.Mobile == "" {
if m.Country, m.Mobile, err = s.userMobile(m.Mid); err != nil {
continue
}
}
if m.Country == "" || m.Mobile == "" {
log.Error("invalid country or mobile, info(%+v)", m)
continue
}
content := _contentRe.ReplaceAllString(m.Content, "*")
l := &smsmdl.ModelUserActionLog{Mobile: m.Mobile, Content: content, Type: smsmdl.TypeActSms, Action: smsmdl.UserActionTypeSend}
if m.Country == smsmdl.CountryChina {
for i := 0; i < s.providers; i++ {
s.actp.Lock()
p := s.actp.Value.(model.Provider)
s.actp.Ring = s.actp.Next()
s.actp.Unlock()
l.Provider = p.GetPid()
if msgid, err = p.SendActSms(context.Background(), m); err == nil {
break
}
dao.PromInfo(fmt.Sprintf("service:retry act china %d", l.Provider))
log.Error("retry send act sms(%v) platform(%d) error(%v)", m, l.Provider, err)
}
} else {
for i := 0; i < s.providers; i++ {
s.intep.Lock()
p := s.intep.Value.(model.Provider)
s.intep.Ring = s.intep.Next()
s.intep.Unlock()
l.Provider = p.GetPid()
if msgid, err = p.SendInternationalSms(context.Background(), m); err == nil {
break
}
dao.PromInfo(fmt.Sprintf("service:retry act international %d", l.Provider))
log.Error("retry send act international sms(%v) platform(%d)", m, l.Provider, err)
}
}
if err == nil {
l.Status = smsmdl.UserActionSendSuccessStatus
l.Desc = smsmdl.UserActionSendSuccessDesc
dao.PromInfo(fmt.Sprintf("service:act china success %d", l.Provider))
log.Info("send act sms(%v) platform(%d) success", m, l.Provider)
} else {
l.Status = smsmdl.UserActionSendFailedStatus
l.Desc = smsmdl.UserActionSendFailedDesc
dao.PromError("service:actSms")
log.Error("send act sms(%v) error(%v)", m, err)
s.cache.Do(context.Background(), func(ctx context.Context) {
s.dao.SendWechat(fmt.Sprintf("sms-job send msg(%d) error(%v)", m.ID, err))
})
}
l.MsgID = msgid
l.Ts = time.Now().Unix()
s.sendUserActionLog(l)
}
}
func (s *Service) actbatchproc() {
defer s.waiter.Done()
var (
err error
mids []string
country string
mobile string
msgid string
)
for {
m, ok := <-s.batchSms
if !ok {
log.Info("actbatchproc exit!")
return
}
if m.Mobile == "" && m.Mid != "" {
mids = strings.Split(m.Mid, ",")
var mobiles []string
for _, midStr := range mids {
if country, mobile, err = s.userMobile(midStr); err != nil {
continue
}
if country == "" || mobile == "" {
log.Error("invalid country or mobile, code(%s) mid(%s) country(%s) mobile(%s)", m.Code, midStr, country, mobile)
continue
}
if country != smsmdl.CountryChina {
continue
}
mobiles = append(mobiles, mobile)
}
m.Mobile = strings.Join(mobiles, ",")
}
if m.Mobile == "" {
continue
}
content := _contentRe.ReplaceAllString(m.Content, "*")
l := &smsmdl.ModelUserActionLog{Mobile: m.Mobile, Content: content, Type: smsmdl.TypeActSms, Action: smsmdl.UserActionTypeSend, Ts: time.Now().Unix()}
send := &smsmdl.ModelSend{Mobile: m.Mobile, Content: m.Content, Type: smsmdl.TypeActSms}
for i := 0; i < s.providers; i++ {
s.batchp.Lock()
p := s.batchp.Value.(model.Provider)
s.batchp.Ring = s.batchp.Next()
s.batchp.Unlock()
l.Provider = p.GetPid()
if msgid, err = p.SendBatchActSms(context.Background(), send); err == nil {
break
}
dao.PromInfo(fmt.Sprintf("service:retry batch %d", l.Provider))
log.Error("retry send act batch sms(%v) platform(%d)", m, l.Provider, err)
}
if err == nil {
dao.PromInfo(fmt.Sprintf("service:batch success %d", l.Provider))
log.Info("send act batch sms(%v) platform(%d) success", m, l.Provider)
l.Status = smsmdl.UserActionSendSuccessStatus
l.Desc = smsmdl.UserActionSendSuccessDesc
} else {
dao.PromError("service:actBatchSms")
log.Error("send act batch sms(%v) error(%v)", m, err)
s.cache.Do(context.Background(), func(ctx context.Context) {
s.dao.SendWechat(fmt.Sprintf("sms-job send msg(%d) error(%v)", m.ID, err))
})
l.Status = smsmdl.UserActionSendFailedStatus
l.Desc = smsmdl.UserActionSendFailedDesc
}
l.MsgID = msgid
l.Ts = time.Now().Unix()
s.sendUserActionLog(l)
}
}
func (s *Service) userMobile(midStr string) (country, mobile string, err error) {
mid, err := strconv.ParseInt(midStr, 10, 64)
if err != nil {
log.Error("userMobile parse mid(%s) error(%v)", midStr, err)
return
}
if mid <= 0 {
log.Error("userMobile invalid mid(%s)", midStr)
return
}
var um *model.UserMobile
for i := 0; i < _retry; i++ {
if um, err = s.dao.UserMobile(context.Background(), mid); err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
if err != nil {
log.Error("UserMobile mid(%d) error(%v)", mid, err)
return
}
country = um.CountryCode
mobile = um.Mobile
return
}
func (s *Service) monitorproc() {
if env.DeployEnv != env.DeployEnvProd {
return
}
var smsCount int64
for {
time.Sleep(time.Duration(s.c.Sms.MonitorProcDuration))
if s.smsCount-smsCount == 0 {
msg := fmt.Sprintf("sms-job sms did not consume within %s seconds", time.Duration(s.c.Sms.MonitorProcDuration).String())
s.dao.SendWechat(msg)
log.Warn(msg)
}
smsCount = s.smsCount
}
}