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

View File

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

View File

@@ -0,0 +1,61 @@
# sms-service
### v1.7.3
1. 165号码段支持
### v1.7.2
1. 增加191号码段支持
### v1.7.1
1. 更改grpc目录
### v1.7.0
1. 支持添加发短信任务
### v1.6.1
1. pub databus 加重试
### v1.6.0
1. 去掉db
### v1.5.2
1. 扩展短信号段
### v1.5.1
1. fix sendBatch param binding
### v1.5.0
1. use grpc
### v1.4.1
1. 用户行为日志处加日志
### v1.4.0
1. 发送记录接入用户行为日志
### v1.3.2
1. use grpc identify
### v1.3.0
1. 升级bm
### v1.2.4
1. add register
### v1.2.3
1. 迁移至main目录下
### v1.2.2
1. 修改变量名
### v1.2.1
1. update config
### v1.2.0
1. 增加 145,147(上网卡号段)
### v1.1.0
1. 增加 146,148,166,198,199 号段
### v1.0.0
1. 基础api

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
#### sms
##### 项目简介
> 1.短信平台
##### 编译环境
> 请只用golang v1.8.x以上版本编译执行。
##### 依赖包
> 1.公共包go-common
##### 特别说明
> 1.model目录可能会被其他项目引用请谨慎更改并通知各方。

View File

@@ -0,0 +1,67 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = [
"//app/service/main/sms/model:model_proto",
"@gogo_special_proto//github.com/gogo/protobuf/gogoproto",
],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/app/service/main/sms/api",
proto = ":v1_proto",
tags = ["automanaged"],
deps = [
"//app/service/main/sms/model:model_go_proto",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
embed = [":v1_go_proto"],
importpath = "go-common/app/service/main/sms/api",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/sms/model:go_default_library",
"//library/net/rpc/warden:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/main/sms/api/gorpc:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
syntax = "proto3";
package sms.service.v1;
import "app/service/main/sms/model/model.proto";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "v1";
service Sms {
// Send send sms
rpc Send(SendReq) returns(SendReply);
// SendBatch send sms batch
rpc SendBatch(SendBatchReq) returns(SendBatchReply);
}
message AddTemplateReply {}
message AddTemplateReq {
int32 stype = 1 [(gogoproto.moretags) = 'form:"type" validate:"required,min=1"'];
string tcode = 2 [(gogoproto.moretags) = 'form:"code" validate:"required"'];
string template = 3 [(gogoproto.moretags) = 'form:"content" validate:"required"'];
string submitter = 5 [(gogoproto.moretags) = 'form:"submitter"'];
}
message TemplateListReply {
repeated model.ModelTemplate list = 1;
int32 total = 2;
}
message TemplateListReq {
int32 pn = 1 [(gogoproto.moretags) = 'form:"pn" default:"1"'];
int32 ps = 2 [(gogoproto.moretags) = 'form:"ps" default:"10"'];
string st = 3 [(gogoproto.moretags) = 'form:"st"'];
string sw = 4 [(gogoproto.moretags) = 'form:"sw"'];
}
message UpdateTemplateReply {}
message UpdateTemplateReq {
int64 id = 1 [(gogoproto.customname) = "ID", (gogoproto.moretags) = 'form:"id" validate:"required,min=1"'];
int32 stype = 2 [(gogoproto.moretags) = 'form:"type" validate:"required,min=1"'];
int32 status = 3 [(gogoproto.moretags) = 'form:"status"'];
string tcode = 4 [(gogoproto.moretags) = 'form:"code" validate:"required"'];
string template = 5 [(gogoproto.moretags) = 'form:"content" validate:"required"'];
string submitter = 6 [(gogoproto.moretags) = 'form:"submitter"'];
}
message SendReply {}
message SendReq {
int64 mid = 1 [(gogoproto.moretags) = 'form:"mid"'];
string mobile = 2 [(gogoproto.moretags) = 'form:"mobile"'];
string country = 3 [(gogoproto.moretags) = 'form:"country"'];
string tcode = 4 [(gogoproto.moretags) = 'form:"tcode"'];
string tparam = 5 [(gogoproto.moretags) = 'form:"tparam"'];
}
message SendBatchReply {}
message SendBatchReq {
repeated int64 mids = 1 [(gogoproto.moretags) = 'form:"mids,split"'];
repeated string mobiles = 2 [(gogoproto.moretags) = 'form:"mobiles,split"'];
string tcode = 3 [(gogoproto.moretags) = 'form:"tcode"'];
string tparam = 4 [(gogoproto.moretags) = 'form:"tparam"'];
}
message AddTaskReq {
int32 type = 1 [(gogoproto.moretags) = 'form:"type" validate:"required,min=1"'];
int32 business_id = 2 [(gogoproto.customname) = "BusinessID", (gogoproto.moretags) = 'form:"business_id"'];
string template_code = 3 [(gogoproto.moretags) = 'form:"template_code" validate:"required"'];
string desc = 4 [(gogoproto.moretags) = 'form:"desc"'];
string file_name = 5 [(gogoproto.moretags) = 'form:"file_name" validate:"required"'];
string file_path = 6 [(gogoproto.moretags) = 'form:"file_path" validate:"required"'];
int64 send_time = 7 [(gogoproto.moretags) = 'form:"send_time" validate:"required,min=1"'];
}
message AddTaskReply {}
message UpdateTaskReq {
int64 id = 1 [(gogoproto.customname) = "ID", (gogoproto.moretags) = 'form:"id"'];
int32 type = 2 [(gogoproto.moretags) = 'form:"type" validate:"required,min=1"'];
int32 business_id = 3 [(gogoproto.customname) = "BusinessID", (gogoproto.moretags) = 'form:"business_id"'];
string template_code = 4 [(gogoproto.moretags) = 'form:"template_code" validate:"required"'];
string desc = 5 [(gogoproto.moretags) = 'form:"desc"'];
string file_name = 6 [(gogoproto.moretags) = 'form:"file_name" validate:"required"'];
string file_path = 7 [(gogoproto.moretags) = 'form:"file_path" validate:"required"'];
int64 send_time = 8 [(gogoproto.moretags) = 'form:"send_time" validate:"required,min=1"'];
}
message UpdateTaskReply {}
message DeleteTaskReq {
int64 id = 1 [(gogoproto.customname) = "ID", (gogoproto.moretags) = 'form:"id"'];
}
message DeleteTaskReply {}
message TaskInfoReq {
int64 id = 1 [(gogoproto.customname) = "ID", (gogoproto.moretags) = 'form:"id"'];
}
message TaskInfoReply {
model.ModelTask info = 1;
}
message TaskListReq {
int32 pn = 1 [(gogoproto.moretags) = 'form:"pn" default:"1"'];
int32 ps = 2 [(gogoproto.moretags) = 'form:"ps" default:"20"'];
}
message TaskListReply {
repeated model.ModelTask list = 1;
int32 total = 2;
}

View File

@@ -0,0 +1,25 @@
package v1
import (
"context"
"fmt"
"go-common/library/net/rpc/warden"
"google.golang.org/grpc"
)
// AppID .
const AppID = "sms.service"
// NewClient new grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (SmsClient, error) {
client := warden.NewClient(cfg, opts...)
cc, err := client.Dial(context.Background(), fmt.Sprintf("discovery://default/%s", AppID))
if err != nil {
return nil, err
}
return NewSmsClient(cc), nil
}
//go:generate $GOPATH/src/go-common/app/tool/warden/protoc.sh

View File

@@ -0,0 +1,41 @@
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"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/app/service/main/sms/api/gorpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/sms/model:go_default_library",
"//library/net/rpc: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,43 @@
package gorpc
import (
"context"
"go-common/app/service/main/sms/model"
"go-common/library/net/rpc"
)
const (
_appid = "sms.service"
_send = "RPC.send"
_sendBatch = "RPC.sendBatch"
)
var (
_noRes = &struct{}{}
)
// Service struct info.
type Service struct {
client *rpc.Client2
}
// New new service instance and return.
func New(c *rpc.ClientConfig) (s *Service) {
s = &Service{}
s.client = rpc.NewDiscoveryCli(_appid, c)
return
}
// Send send sms
func (s *Service) Send(c context.Context, arg *model.ArgSend) (err error) {
err = s.client.Call(c, _send, arg, _noRes)
return
}
// SendBatch batch send sms
func (s *Service) SendBatch(c context.Context, arg *model.ArgSendBatch) (err error) {
err = s.client.Call(c, _sendBatch, arg, _noRes)
return
}

View File

@@ -0,0 +1 @@
package gorpc

View File

View File

@@ -0,0 +1,44 @@
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-service-test.toml"],
importpath = "go-common/app/service/main/sms/cmd",
tags = ["automanaged"],
deps = [
"//app/service/main/sms/conf:go_default_library",
"//app/service/main/sms/server/gorpc:go_default_library",
"//app/service/main/sms/server/grpc:go_default_library",
"//app/service/main/sms/server/http:go_default_library",
"//app/service/main/sms/service:go_default_library",
"//library/log:go_default_library",
"//library/net/trace: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,53 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/service/main/sms/conf"
gorpc "go-common/app/service/main/sms/server/gorpc"
grpc "go-common/app/service/main/sms/server/grpc"
"go-common/app/service/main/sms/server/http"
"go-common/app/service/main/sms/service"
"go-common/library/log"
"go-common/library/net/trace"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Xlog)
defer log.Close()
trace.Init(conf.Conf.Tracer)
defer trace.Close()
svr := service.New(conf.Conf)
gorpcSvr := gorpc.New(conf.Conf, svr)
grpcSvr := grpc.New(conf.Conf.GRPC, svr)
http.Init(conf.Conf, svr)
log.Info("sms-service start")
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("sms-service get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT:
gorpcSvr.Close()
grpcSvr.Shutdown(context.Background())
svr.Close()
time.Sleep(time.Second * 2)
log.Info("sms-service exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}

View File

@@ -0,0 +1,89 @@
# This is a TOML document. Boom.
version = "1.0.0"
user = "nobody"
dir = "./"
family = "sms-service"
[xlog]
dir = "/data/log/sms-service/"
[httpServer]
addr = "0.0.0.0:7121"
maxListen = 1000
timeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
[httpClient]
key = "4699a07e59d7149e"
secret = "test"
dial = "100ms"
timeout = "1s"
keepAlive = "60s"
timer = 128
[httpClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[grpc]
timeout = "1s"
addr = "0.0.0.0:9000"
[mysql]
addr = "172.16.33.205:3306"
dsn = "sms:s2eEdt4kAPW4SEOk@tcp(172.16.33.205:3306)/sms?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 2
IdleTimeout ="4h"
queryTimeout = "1s"
execTimeout = "1s"
tranTimeout = "1s"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[databus]
key = "9765cdac5894f2ba"
secret="f4237d712c3ed1e7fab0137b81418b14"
group= "Sms-MainWebSvr-P"
topic= "Sms-T"
action="pub"
name = "sms-pub"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[userReport]
key = "2511663d546f1413"
secret = "cde3b480836cc76df3d635470f991caa"
group = "LogUserAction-MainSearch-P"
topic = "LogUserAction-T"
action = "pub"
buffer = 10240
name = "log-user-action/log-sub"
proto = "tcp"
addr = "172.18.33.50:6205"
active = 100
idle = 100
dialTimeout = "200ms"
readTimeout = "200ms"
writeTimeout = "200ms"
idleTimeout = "80s"
[sms]
pickUpTask = true
loadTaskInteval = "5s"
taskWorker = 10
batchSize = 100

View File

@@ -0,0 +1,40 @@
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/service/main/sms/conf",
tags = ["automanaged"],
deps = [
"//library/conf:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc: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,89 @@
package conf
import (
"errors"
"flag"
"go-common/library/conf"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc"
"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 {
App *bm.App
Xlog *log.Config
Verify *verify.Config
HTTPServer *bm.ServerConfig
HTTPClient *bm.ClientConfig
RPCServer *rpc.ServerConfig
GRPC *warden.ServerConfig
MySQL *sql.Config
Tracer *trace.Config
Databus *databus.Config
Sms *sms
}
type sms struct {
PickUpTask bool
LoadTaskInteval xtime.Duration
TaskWorker int
BatchSize int
}
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,53 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["dao_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/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 = [
"dao.go",
"databus.go",
"mysql.go",
],
importpath = "go-common/app/service/main/sms/dao",
tags = ["automanaged"],
deps = [
"//app/service/main/sms/conf:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/queue/databus: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,40 @@
package dao
import (
"context"
"go-common/app/service/main/sms/conf"
xsql "go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
)
// Dao struct info of Dao.
type Dao struct {
c *conf.Config
db *xsql.DB
client *bm.Client
databus *databus.Databus
}
// New new a Dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
db: xsql.NewMySQL(c.MySQL),
client: bm.NewClient(c.HTTPClient),
databus: databus.New(c.Databus),
}
return
}
// Ping ping health of db.
func (d *Dao) Ping(ctx context.Context) (err error) {
return d.db.Ping(ctx)
}
// Close close connections of mc, redis, db.
func (d *Dao) Close() {
d.db.Close()
d.databus.Close()
}

View File

@@ -0,0 +1,67 @@
package dao
import (
"context"
"flag"
"os"
"testing"
"go-common/app/service/main/sms/conf"
"go-common/app/service/main/sms/model"
"github.com/smartystreets/goconvey/convey"
)
var d *Dao
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") == "uat" {
flag.Set("app_id", "main.account.sms-service")
flag.Set("conf_token", "35805e3d0bd411e889af5a488f30b450")
flag.Set("tree_id", "6325")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}
func TestDaoTemplateByStatus(t *testing.T) {
convey.Convey("TemplateByStatus", t, func(ctx convey.C) {
var status = model.TemplateStatusApprovel
ctx.Convey("When everything goes positive", func(ctx convey.C) {
res, err := d.TemplateByStatus(context.Background(), status)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPubSingle(t *testing.T) {
convey.Convey("PubSingle", t, func(ctx convey.C) {
err := d.PubSingle(context.Background(), &model.ModelSend{})
ctx.Convey("Then err should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDao_PubBatch(t *testing.T) {
convey.Convey("PubBatch", t, func(ctx convey.C) {
err := d.PubBatch(context.Background(), &model.ModelSend{})
ctx.Convey("Then err should be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,43 @@
package dao
import (
"context"
"time"
"go-common/app/service/main/sms/model"
"go-common/library/log"
)
const _retry = 3
// PubSingle pub single sms to databus
func (d *Dao) PubSingle(ctx context.Context, l *model.ModelSend) (err error) {
for i := 0; i < _retry; i++ {
if err = d.databus.Send(ctx, l.Mobile, l); err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
if err != nil {
log.Error("PubSingle(%+v) error(%v)", l, err)
return
}
log.Info("PubSingle(%+v) success.", l)
return
}
// PubBatch pub batch sms to databus
func (d *Dao) PubBatch(ctx context.Context, l *model.ModelSend) (err error) {
for i := 0; i < _retry; i++ {
if err = d.databus.Send(ctx, l.Code, l); err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
if err != nil {
log.Error("PubBatch(%+v) error(%v)", l, err)
return
}
log.Info("PubBatch(%+v) success.", l)
return
}

View File

@@ -0,0 +1,74 @@
package dao
import (
"context"
"database/sql"
"time"
"go-common/app/service/main/sms/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
)
const (
_templateByStatusSQL = "SELECT id,code,stype,template,status,submitter,approver FROM sms_template_new WHERE status=?"
_taskSQL = "SELECT id,type,business_id,template_code,`desc`,file_path,file_name,send_time,status,ctime,mtime FROM sms_tasks WHERE status=? AND send_time<=? LIMIT 1 FOR UPDATE"
_upadteTaskStatusSQL = "UPDATE sms_tasks SET status=? WHERE id=?"
)
// TemplateByStatus select template by status
func (d *Dao) TemplateByStatus(ctx context.Context, status int) (res []*model.ModelTemplate, err error) {
var rows *xsql.Rows
if rows, err = d.db.Query(ctx, _templateByStatusSQL, status); err != nil {
log.Error("d.TemplateByStatus.Query(%d) error(%v)", status, err)
return
}
defer rows.Close()
for rows.Next() {
r := new(model.ModelTemplate)
if err = rows.Scan(&r.ID, &r.Code, &r.Stype, &r.Template, &r.Status, &r.Submitter, &r.Approver); err != nil {
log.Error("row.Scan() error(%v)", err)
res = nil
return
}
res = append(res, r)
}
err = rows.Err()
return
}
// BeginTx begin transaction.
func (d *Dao) BeginTx(c context.Context) (*xsql.Tx, error) {
return d.db.Begin(c)
}
// TxTask gets prepared task.
func (d *Dao) TxTask(tx *xsql.Tx) (t *model.ModelTask, err error) {
t = new(model.ModelTask)
if err = tx.QueryRow(_taskSQL, model.TaskStatusPrepared, time.Now()).Scan(&t.ID, &t.Type, &t.BusinessID,
&t.TemplateCode, &t.Desc, &t.FilePath, &t.FileName, &t.SendTime, &t.Status, &t.Ctime, &t.Mtime); err != nil {
t = nil
if err == sql.ErrNoRows {
err = nil
return
}
log.Error("d.TxTask() QueryRow() error(%v)", err)
}
return
}
// TxUpdateTaskStatus updates task status by tx.
func (d *Dao) TxUpdateTaskStatus(tx *xsql.Tx, taskID int64, status int32) (err error) {
if _, err = tx.Exec(_upadteTaskStatusSQL, status, taskID); err != nil {
log.Error("d.TxUpdateTaskStatus() Exec(%s,%d) error(%v)", taskID, status, err)
}
return
}
// UpdateTaskStatus updates task status.
func (d *Dao) UpdateTaskStatus(ctx context.Context, taskID int64, status int32) (err error) {
if _, err = d.db.Exec(ctx, _upadteTaskStatusSQL, status, taskID); err != nil {
log.Error("d.UpdateTaskStatus() Exec(%s,%d) error(%v)", taskID, status, err)
}
return
}

View File

@@ -0,0 +1,60 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"constant.go",
"rpc.go",
],
embed = [":model_go_proto"],
importpath = "go-common/app/service/main/sms/model",
tags = ["automanaged"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto: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"],
)
proto_library(
name = "model_proto",
srcs = ["model.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "model_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_proto"],
importpath = "go-common/app/service/main/sms/model",
proto = ":model_proto",
tags = ["automanaged"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)

View File

@@ -0,0 +1,85 @@
package model
//go:generate $GOPATH/src/go-common/app/tool/warden/protoc.sh
const (
// CountryChina .
CountryChina = "86" // 中国地区码
)
// 短信模板状态
const (
// TemplateStatusNew .
TemplateStatusNew = 0 // 新建
// TemplateStatusApprovel .
TemplateStatusApprovel = 1 // 已审核
// TemplateStatusCanceled .
TemplateStatusCanceled = 2 // 已取消
)
// 运营商
const (
// ProviderMengWang .
ProviderMengWang = int32(2)
// ProviderChuangLan
ProviderChuangLan = int32(4)
)
// 短信类型
const (
// TypeSms 验证码
TypeSms = int32(1)
// TypeActSms 营销短信
TypeActSms = int32(2)
// TypeActBatch 批量营销
TypeActBatch = int32(3)
)
// 短信回执用户行为日志
const (
// UserActionTypeSend 日志类型为发送
UserActionTypeSend = int32(1)
// UserActionCallback 日志类型为回执
UserActionCallback = int32(2)
// UserActionSendFailedStatus 发送失败的日志状态
UserActionSendFailedStatus = "SUBMIT FAILED"
// UserActionSendFailedDesc 发送失败的日志描述
UserActionSendFailedDesc = "短信提交失败"
// UserActionSendSuccessStatus 提交成功的日志状态
UserActionSendSuccessStatus = "SUBMIT SUCCESS"
// UserActionSendSuccessDesc 提交失败的日志描述
UserActionSendSuccessDesc = "短信提交成功"
)
// 短信状态
const (
// StatusNew .
StatusNew = int32(0)
// StatusSuccess .
StatusSuccess = int32(1)
// StatusFail .
StatusFail = int32(2)
)
// 短信任务类型
const (
// TaskTypeMobile 按手机号发送
TaskTypeMobile = int32(1)
// TaskTypeMid 按mid发送
TaskTypeMid = int32(2)
)
// 短信任务状态
const (
// TaskStatusPrepared 准备发
TaskStatusPrepared = int32(1)
// TaskStatusDoing 进行中
TaskStatusDoing = int32(2)
// TaskStatusSuccess 发送成功
TaskStatusSuccess = int32(3)
// TaskStatusFailed 发送失败
TaskStatusFailed = int32(4)
// TaskStatusStop 停止发送
TaskStatusStop = int32(5)
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
syntax = "proto3";
package sms.service.model;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "model";
message ModelTemplate {
int64 id = 1 [(gogoproto.customname) = "ID", (gogoproto.moretags) = 'json:"id"'];
string code = 2 [(gogoproto.moretags) = 'json:"code"'];
string template = 3 [(gogoproto.moretags) = 'json:"template"'];
int32 stype = 4 [(gogoproto.moretags) = 'json:"stype"'];
int32 status = 5 [(gogoproto.moretags) = 'json:"status"'];
string approver = 6 [(gogoproto.moretags) = 'json:"approver"'];
string submitter = 7 [(gogoproto.moretags) = 'json:"submitter"'];
repeated string param = 8 [(gogoproto.moretags) = 'json:"param" gorm:"-"'];
int64 ctime = 9 [(gogoproto.moretags) = 'json:"ctime" gorm:"column:ctime"', (gogoproto.casttype) = "go-common/library/time.Time"];
int64 mtime = 10 [(gogoproto.moretags) = 'json:"mtime" gorm:"column:mtime"', (gogoproto.casttype) = "go-common/library/time.Time"];
}
message ModelSend {
int64 id = 1 [(gogoproto.customname) = "ID"];
string mid = 2;
string mobile = 3;
string country = 4;
string code = 5;
string content = 6;
int32 status = 7;
int32 type = 8;
int32 pid = 9;
}
message ModelUserActionLog {
string msgid = 1 [(gogoproto.customname) = "MsgID"];
string mobile = 2;
string content = 3;
string status = 4;
string desc = 5;
int32 provider = 6;
int32 type = 7;
int32 action = 8;
int64 ts = 9;
}
message ModelTask {
int64 id = 1 [(gogoproto.customname) = "ID"];
int32 type = 2;
int32 business_id = 3 [(gogoproto.customname) = "BusinessID"];
string template_code = 4;
string template_content = 5 [(gogoproto.moretags) = 'gorm:"-"'];
string desc = 6;
string file_name = 7;
string file_path = 8;
int64 send_time = 9 [(gogoproto.casttype) = "go-common/library/time.Time"];
int32 status = 10;
int64 ctime = 11 [(gogoproto.moretags) = 'gorm:"column:ctime"', (gogoproto.casttype) = "go-common/library/time.Time"];
int64 mtime = 12 [(gogoproto.moretags) = 'gorm:"column:mtime"', (gogoproto.casttype) = "go-common/library/time.Time"];
}

View File

@@ -0,0 +1,39 @@
package model
// ArgMid is rpc mid params.
type ArgMid struct {
Mid int64
RealIP string
}
// ArgSend send sms
type ArgSend struct {
Mid int64
RealIP string
Mobile string
Country string
Tcode string
Tparam string
}
// ArgSendBatch send batch
type ArgSendBatch struct {
Mids []int64
RealIP string
Mobiles []string
Tcode string
Tparam string
}
// ArgUserActionLog add user action log
type ArgUserActionLog struct {
MsgID string // 发送短信时服务商返回的随机ID
Mobile string
Content string // 短信内容
Status string // 回执状态
Desc string // 回执状态描述
Provider int // 短信服务商ID
Type int // 短信类型,验证码/国际/营销
Action int // 操作类型,发送或回执
Ts int64 // 操作时间
}

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["rpc_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["rpc.go"],
importpath = "go-common/app/service/main/sms/server/gorpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/sms/api:go_default_library",
"//app/service/main/sms/conf:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//app/service/main/sms/service:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/context:go_default_library",
"//library/net/rpc/interceptor: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,42 @@
package gorpc
import (
pb "go-common/app/service/main/sms/api"
"go-common/app/service/main/sms/conf"
"go-common/app/service/main/sms/model"
"go-common/app/service/main/sms/service"
"go-common/library/net/rpc"
"go-common/library/net/rpc/context"
"go-common/library/net/rpc/interceptor"
)
// RPC rpc server
type RPC struct {
s *service.Service
}
// New new rpc server.
func New(c *conf.Config, s *service.Service) (svr *rpc.Server) {
r := &RPC{s: s}
svr = rpc.NewServer(c.RPCServer)
in := interceptor.NewInterceptor("")
svr.Interceptor = in
if err := svr.Register(r); err != nil {
panic(err)
}
return
}
// Send rpc send.
func (r *RPC) Send(c context.Context, a *model.ArgSend, res *struct{}) (err error) {
req := &pb.SendReq{Mid: a.Mid, Mobile: a.Mobile, Country: a.Country, Tcode: a.Tcode, Tparam: a.Tparam}
_, err = r.s.Send(c, req)
return
}
// SendBatch rpc sendbatch.
func (r *RPC) SendBatch(c context.Context, a *model.ArgSendBatch, res *struct{}) (err error) {
req := &pb.SendBatchReq{Mids: a.Mids, Mobiles: a.Mobiles, Tcode: a.Tcode, Tparam: a.Tparam}
_, err = r.s.SendBatch(c, req)
return
}

View File

@@ -0,0 +1 @@
package gorpc

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 = ["server.go"],
importpath = "go-common/app/service/main/sms/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/sms/api:go_default_library",
"//app/service/main/sms/service:go_default_library",
"//library/net/rpc/warden: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,18 @@
package grpc
import (
pb "go-common/app/service/main/sms/api"
"go-common/app/service/main/sms/service"
"go-common/library/net/rpc/warden"
)
// New Sms warden rpc server
func New(c *warden.ServerConfig, svr *service.Service) *warden.Server {
ws := warden.NewServer(c)
pb.RegisterSmsServer(ws.Server(), svr)
ws, err := ws.Start()
if err != nil {
panic(err)
}
return ws
}

View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"http.go",
"sms.go",
],
importpath = "go-common/app/service/main/sms/server/http",
tags = ["automanaged"],
deps = [
"//app/service/main/sms/api:go_default_library",
"//app/service/main/sms/conf:go_default_library",
"//app/service/main/sms/service:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify: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,49 @@
package http
import (
"net/http"
"go-common/app/service/main/sms/conf"
"go-common/app/service/main/sms/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
smsSvc *service.Service
idfSvc *verify.Verify
)
// Init init http sever instance.
func Init(c *conf.Config, s *service.Service) {
idfSvc = verify.New(c.Verify)
smsSvc = 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)
g := e.Group("/x/internal/sms", bm.CORS())
{
g.POST("/send", idfSvc.Verify, send)
g.POST("/sendBatch", idfSvc.Verify, sendBatch)
}
}
func ping(c *bm.Context) {
if err := smsSvc.Ping(c); err != nil {
log.Error("sms-service ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

View File

@@ -0,0 +1,22 @@
package http
import (
pb "go-common/app/service/main/sms/api"
bm "go-common/library/net/http/blademaster"
)
func send(ctx *bm.Context) {
req := new(pb.SendReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(smsSvc.Send(ctx, req))
}
func sendBatch(ctx *bm.Context) {
req := new(pb.SendBatchReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(smsSvc.SendBatch(ctx, req))
}

View File

@@ -0,0 +1,57 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["sms_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/sms/api:go_default_library",
"//app/service/main/sms/conf:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"service.go",
"sms.go",
"task.go",
],
importpath = "go-common/app/service/main/sms/service",
tags = ["automanaged"],
deps = [
"//app/service/main/sms/api:go_default_library",
"//app/service/main/sms/conf:go_default_library",
"//app/service/main/sms/dao:go_default_library",
"//app/service/main/sms/model:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/xstr: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,82 @@
package service
import (
"context"
"strings"
"time"
"go-common/app/service/main/sms/conf"
"go-common/app/service/main/sms/dao"
"go-common/app/service/main/sms/model"
)
// Service struct of service.
type Service struct {
dao *dao.Dao
c *conf.Config
template map[string]*model.ModelTemplate
missch chan func()
}
// New create service instance and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
missch: make(chan func(), 10240),
template: make(map[string]*model.ModelTemplate),
}
s.loadConf(context.Background())
go s.loadConfproc()
go s.loadTaskproc()
return
}
func (s *Service) loadConfproc() {
for {
time.Sleep(time.Minute)
s.loadConf(context.Background())
}
}
func (s *Service) loadConf(ctx context.Context) {
var (
err error
res []*model.ModelTemplate
)
if res, err = s.dao.TemplateByStatus(ctx, model.TemplateStatusApprovel); err != nil {
return
}
tpl := make(map[string]*model.ModelTemplate, len(res))
for _, v := range res {
v.Param = parseTemplateParam(v.Template)
tpl[v.Code] = v
}
s.template = tpl
}
func parseTemplateParam(p string) (param []string) {
mp := make(map[string]struct{})
ss := strings.SplitAfter(p, "#[")
for i, v := range ss {
if i == 0 {
continue
}
k := v[0:strings.Index(v, "]")]
mp[k] = struct{}{}
}
for k := range mp {
param = append(param, k)
}
return
}
// Ping check server ok.
func (s *Service) Ping(ctx context.Context) (err error) {
return s.dao.Ping(ctx)
}
// Close dao.
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,146 @@
package service
import (
"context"
"encoding/json"
"regexp"
"strconv"
"strings"
pb "go-common/app/service/main/sms/api"
"go-common/app/service/main/sms/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
const mobilePattern = "^((13[0-9])|(14[1,4,5,6,7,8])|(15[^4])|(16[5-7])|(17[0-8])|(18[0-9])|(19[1,8,9]))\\d{8}$"
var mobileReg, _ = regexp.Compile(mobilePattern)
// Send send sms
func (s *Service) Send(ctx context.Context, req *pb.SendReq) (res *pb.SendReply, err error) {
tpl := s.template[req.Tcode]
if tpl == nil {
err = ecode.SmsTemplateNotExist
return
}
if req.Mid != 0 && req.Mobile != "" {
err = ecode.SmsSendBothMidAndMobile
return
}
if req.Mid == 0 && req.Mobile == "" {
err = ecode.RequestErr
return
}
if req.Mobile != "" {
if req.Country == "" {
req.Country = model.CountryChina
}
if req.Country == model.CountryChina {
if match := mobileReg.MatchString(req.Mobile); !match {
err = ecode.SmsMobilePatternErr
return
}
}
}
send := &model.ModelSend{
Mid: strconv.FormatInt(req.Mid, 10),
Mobile: req.Mobile,
Type: tpl.Stype,
Country: req.Country,
Code: tpl.Code,
Content: tpl.Template,
}
m := make(map[string]interface{})
if req.Tparam != "" {
if err = json.Unmarshal([]byte(req.Tparam), &m); err != nil {
log.Error("json.Unmarshal (%v) error(%v)", req.Tparam, err)
return
}
}
for _, k := range tpl.Param {
if m[k] == "" || m[k] == nil {
err = ecode.SmsTemplateParamNotEnough
return
}
var v string
switch m[k].(type) {
case string:
v = m[k].(string)
case float64:
v = strconv.FormatFloat(m[k].(float64), 'f', -1, 64)
default:
err = ecode.SmsTemplateParamIllegal
return
}
send.Content = strings.Replace(send.Content, "#["+k+"]", v, -1)
}
err = s.dao.PubSingle(ctx, send)
return
}
// SendBatch send sms batch
func (s *Service) SendBatch(ctx context.Context, req *pb.SendBatchReq) (res *pb.SendBatchReply, err error) {
var (
tpl = s.template[req.Tcode]
mbs = make([]string, 0)
)
if tpl == nil {
err = ecode.SmsTemplateNotExist
return
}
if tpl.Stype != model.TypeActSms {
err = ecode.SmsTemplateNotAct
return
}
if len(req.Mids) == 0 && len(req.Mobiles) == 0 {
err = ecode.RequestErr
return
}
if len(req.Mids) > 0 && len(req.Mobiles) > 0 {
err = ecode.SmsSendBothMidAndMobile
return
}
if len(req.Mids)+len(req.Mobiles) > 100 {
err = ecode.SmsSendBatchOverLimit
return
}
for _, v := range req.Mobiles {
if match := mobileReg.MatchString(v); match {
mbs = append(mbs, v)
}
}
if len(req.Mobiles) > 0 && len(mbs) == 0 {
err = ecode.SmsMobilePatternErr
return
}
m := make(map[string]interface{})
if req.Tparam != "" {
if err = json.Unmarshal([]byte(req.Tparam), &m); err != nil {
log.Error("json.Unmarshal (%v) error(%v)", string(req.Tparam), err)
}
}
send := &model.ModelSend{Type: model.TypeActBatch, Code: tpl.Code, Content: tpl.Template}
for _, k := range tpl.Param {
if m[k] == "" || m[k] == nil {
err = ecode.SmsTemplateParamNotEnough
return
}
var v string
switch m[k].(type) {
case string:
v = m[k].(string)
case float64:
v = strconv.FormatFloat(m[k].(float64), 'f', -1, 64)
default:
err = ecode.SmsTemplateParamIllegal
return
}
send.Content = strings.Replace(send.Content, "#["+k+"]", v, -1)
}
send.Mid = xstr.JoinInts(req.Mids)
send.Mobile = strings.Join(mbs, ",")
err = s.dao.PubBatch(ctx, send)
return
}

View File

@@ -0,0 +1,68 @@
package service
import (
"context"
"flag"
"path/filepath"
"strings"
"testing"
pb "go-common/app/service/main/sms/api"
"go-common/app/service/main/sms/conf"
"go-common/library/log"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/sms-service-test.toml")
flag.Set("conf", dir)
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Xlog)
defer log.Close()
s = New(conf.Conf)
}
func Test_Send(t *testing.T) {
Convey("send", t, func() {
req := &pb.SendReq{
Mobile: "17621660828",
Tcode: "acc_01111",
}
_, err := s.Send(context.Background(), req)
So(err, ShouldBeNil)
})
}
func Test_Param(t *testing.T) {
Convey("solve param", t, func() {
var (
template = "test#[code].very#[code] code"
strs []string
param = make(map[string]string)
ss []string
)
strs = strings.SplitAfter(template, "#[")
for i, v := range strs {
if i == 0 {
continue
}
t.Logf("%s", v)
t.Logf("%d", strings.Index(v, "]"))
k := v[0:strings.Index(v, "]")]
param[k] = ""
}
for k := range param {
ss = append(ss, k)
}
t.Logf("%v", ss)
t.Logf(strings.Replace(template, "#[codes]", "888", -1))
})
}

View File

@@ -0,0 +1,126 @@
package service
import (
"context"
"io/ioutil"
"strings"
"time"
"go-common/app/service/main/sms/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
func (s *Service) loadTaskproc() {
for {
if !s.c.Sms.PickUpTask {
log.Warn("service do not pick up new tasks from database")
return
}
task, err := s.pickNewTask()
if err != nil {
time.Sleep(5 * time.Second)
continue
}
if task != nil {
tpl := s.template[task.TemplateCode]
if tpl == nil {
log.Error("template not exists, code(%s)", task.TemplateCode)
continue
}
task.TemplateContent = tpl.Template
if err = s.handleTask(task); err == nil {
s.dao.UpdateTaskStatus(context.Background(), task.ID, model.TaskStatusSuccess)
} else {
s.dao.UpdateTaskStatus(context.Background(), task.ID, model.TaskStatusFailed)
}
}
time.Sleep(time.Duration(s.c.Sms.LoadTaskInteval))
}
}
func (s *Service) pickNewTask() (task *model.ModelTask, err error) {
ctx := context.Background()
var tx *xsql.Tx
if tx, err = s.dao.BeginTx(ctx); err != nil {
log.Error("tx.BeginTx() error(%v)", err)
return
}
if task, err = s.dao.TxTask(tx); err != nil {
tx.Rollback()
return
}
if task == nil {
tx.Rollback()
return
}
if err = s.dao.TxUpdateTaskStatus(tx, task.ID, model.TaskStatusDoing); err != nil {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit() task(%+v) error(%v)", task, err)
return
}
return
}
func (s *Service) handleTask(task *model.ModelTask) (err error) {
bs, err := ioutil.ReadFile(task.FilePath)
if err != nil {
log.Error("ioutil.ReadFile(%s) error(v)", task.FilePath, err)
return
}
var (
counter int
group = errgroup.Group{}
data []string
)
for _, v := range strings.Split(string(bs), "\n") {
v = strings.Trim(v, " \r\t")
if v == "" {
continue
}
data = append(data, v)
}
for {
l := len(data)
if l == 0 {
break
}
n := s.c.Sms.BatchSize
if l < n {
n = l
}
part := data[:n]
data = data[n:]
group.Go(func() error {
s.sendBatch(task, part)
return nil
})
counter++
if counter > s.c.Sms.TaskWorker {
group.Wait()
counter = 0
}
}
if counter > 0 {
group.Wait()
}
return
}
func (s *Service) sendBatch(task *model.ModelTask, data []string) (err error) {
send := &model.ModelSend{Type: model.TypeActBatch, Code: task.TemplateCode, Content: task.TemplateContent}
if task.Type == model.TaskTypeMid {
send.Mid = strings.Join(data, ",")
} else if task.Type == model.TaskTypeMobile {
send.Mobile = strings.Join(data, ",")
} else {
log.Error("invalid task type, task(%+v)", task)
return
}
err = s.dao.PubBatch(context.Background(), send)
return
}