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,62 @@
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",
"email_template_test.go",
"email_test.go",
"redis_list_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/videoup-report/conf:go_default_library",
"//app/job/main/videoup-report/model/archive:go_default_library",
"//app/job/main/videoup-report/model/email:go_default_library",
"//library/cache/redis:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"email.go",
"email_template.go",
"redis_list.go",
],
importpath = "go-common/app/job/main/videoup-report/dao/email",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/videoup-report/conf:go_default_library",
"//app/job/main/videoup-report/model/email:go_default_library",
"//library/cache/redis:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus/report:go_default_library",
"//vendor/gopkg.in/gomail.v2: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,70 @@
package email
import (
"crypto/tls"
"go-common/app/job/main/videoup-report/conf"
"go-common/app/job/main/videoup-report/model/email"
"go-common/library/cache/redis"
gomail "gopkg.in/gomail.v2"
)
// Dao is redis dao.
type Dao struct {
c *conf.Config
redis *redis.Pool
email *gomail.Dialer
FansAddr map[int16][]string
emailAddr map[string][]string
PrivateAddr map[string][]string
//fast behavior detector
detector *email.FastDetector
//快速通道token
fastChan chan int
//邮件发送api的频率token发送邮件5s后插入
controlChan chan int64
}
// New is new redis dao.
func New(c *conf.Config) (d *Dao) {
emailAddr := make(map[string][]string)
for _, v := range c.Mail.Addr {
emailAddr[v.Type] = v.Addr
}
privateMail := make(map[string][]string)
for _, v := range c.Mail.PrivateAddr {
privateMail[v.Type] = v.Addr
}
d = &Dao{
c: c,
redis: redis.NewPool(c.Redis.Mail),
email: gomail.NewDialer(c.Mail.Host, c.Mail.Port, c.Mail.Username, c.Mail.Password),
emailAddr: emailAddr,
PrivateAddr: privateMail,
detector: email.NewFastDetector(c.Mail.SpeedThreshold, c.Mail.OverspeedThreshold),
fastChan: make(chan int, 10240),
controlChan: make(chan int64, 1),
}
d.email.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
d.fastChan <- 1
d.controlChan <- 1
return d
}
//Close close
func (d *Dao) Close() (err error) {
if d.redis != nil {
err = d.redis.Close()
}
return
}
//FastChan get fast channel
func (d *Dao) FastChan() <-chan int {
return d.fastChan
}

View File

@@ -0,0 +1,35 @@
package email
import (
"flag"
"go-common/app/job/main/videoup-report/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "videoup-report")
flag.Set("conf_token", "")
flag.Set("tree_id", "")
flag.Set("conf_version", "server-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")
} else {
flag.Set("conf", "../../cmd/videoup-report-job.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}

View File

@@ -0,0 +1,139 @@
package email
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"go-common/app/job/main/videoup-report/model/email"
"go-common/library/log"
"go-common/library/queue/databus/report"
gomail "gopkg.in/gomail.v2"
)
//SendMail send the email
func (d *Dao) SendMail(tpl *email.Template) {
var err error
headers := tpl.Headers
if len(headers[email.FROM]) == 0 || len(headers[email.TO]) == 0 || len(headers[email.SUBJECT]) == 0 {
log.Error("email lack From/To/Subject: emailTemplate(%+v)", *tpl)
return
}
if len(tpl.ContentType) == 0 {
tpl.ContentType = "text/plain"
}
log.Info("start send mail: emailTemplate(%+v)", *tpl)
msg := gomail.NewMessage()
msg.SetHeaders(headers)
msg.SetBody(tpl.ContentType, tpl.Body)
result := email.EmailResOK
if err = d.email.DialAndSend(msg); err != nil {
result = email.EmailResFail
log.Error("s.email.DialAndSend error(%v) emailTemplate(%+v)", err, tpl)
}
d.sendEmailLog(tpl, headers[email.TO], headers[email.CC], result)
//retry
if err != nil {
address := headers[email.TO]
if len(headers[email.CC]) > 0 {
address = append(address, headers[email.CC]...)
msg.SetHeader(email.CC)
}
for _, addr := range address {
msg.SetHeader(email.TO, addr)
result = email.EmailResOK
if err = d.email.DialAndSend(msg); err != nil {
result = email.EmailResFail
log.Error("s.email.DialAndSend error(%v) to(%s) emailTemplate(%+v)", err, addr, tpl)
}
d.sendEmailLog(tpl, []string{addr}, []string{}, result)
time.Sleep(time.Second * 5)
}
}
}
func (d *Dao) sendEmailLog(tpl *email.Template, to []string, cc []string, result string) {
if tpl == nil || len(tpl.Headers) <= 0 || len(tpl.Headers[email.SUBJECT]) <= 0 {
log.Error("sendEmailLog tpl nil | no headers, tpl(%+v)", tpl)
return
}
address := fmt.Sprintf("to: %s", strings.Join(to, ","))
if len(cc) > 0 {
address = fmt.Sprintf("%s\ncc: %s", address, strings.Join(cc, ","))
}
item := &report.ManagerInfo{
Uname: tpl.Username,
UID: tpl.UID,
Business: email.LogBusEmail,
Type: email.LogTypeEmailJob,
Oid: tpl.AID,
Action: tpl.Type,
Ctime: time.Now(),
Content: map[string]interface{}{
"subject": tpl.Headers[email.SUBJECT][0],
"body": tpl.Body,
"address": address,
"department": tpl.Department,
"result": result,
},
}
report.Manager(item)
log.Info("sendEmailLog template(%+v) result(%s) log.content(%+v)", tpl, result, item.Content)
}
//PushToRedis start to push email to redis according to speed
func (d *Dao) PushToRedis(c context.Context, tpl *email.Template) (isFast bool, key string, err error) {
if tpl == nil {
return
}
//探查发邮件速度快慢
isFast = d.detector.Detect(tpl.UID)
//超限名单只能被回落或下一次超限名单替代
if d.detector.IsFastUnique(tpl.UID) {
key = email.MailFastKey
d.fastChan <- 1
} else {
key = email.MailKey
}
if err = d.PushRedis(c, tpl, key); err != nil {
log.Error("PushToRedis d.PushRedis error(%v) key(%s), tpl(%+v) ", err, key, tpl)
}
return
}
//Start get email from redis and send
func (d *Dao) Start(key string) (err error) {
var (
bs []byte
tpl = &email.Template{}
)
bs, err = d.PopRedis(context.TODO(), key)
if err != nil || bs == nil {
time.Sleep(5 * time.Second)
return
}
err = json.Unmarshal(bs, tpl)
if err != nil {
log.Error("email Start json.unmarshal error(%v) template(%s)", err, string(bs))
return
}
//控制邮件发送频率
st := <-d.controlChan
d.SendMail(tpl)
time.Sleep(time.Second * 5)
d.controlChan <- st
return
}

View File

@@ -0,0 +1,130 @@
package email
import (
"fmt"
"strconv"
"go-common/app/job/main/videoup-report/model/email"
"go-common/library/log"
)
//NotifyEmailTemplate 优质UP主/时政UP主/企业UP主/十万粉丝报备邮件
func (d *Dao) NotifyEmailTemplate(params map[string]string) (tpl *email.Template) {
headers := map[string][]string{
email.FROM: {d.c.Mail.Username},
}
//to
typeIDStr := params["typeId"]
if len(d.emailAddr[typeIDStr]) == 0 {
log.Info("archive(%s) type(%s) don't config email address.", params["aid"], typeIDStr)
return
}
headers[email.TO] = d.emailAddr[typeIDStr]
//subject
headers[email.SUBJECT] = []string{fmt.Sprintf("优质/十万粉稿件处理报备[%s]--操作人: %s[%s]", params["upName"], params["username"], params["department"])}
//body
body := `
稿件标题:%s
up主%s
稿件链接http://www.bilibili.com/video/av%s
触发条件:%s
处理操作:%s
`
body = fmt.Sprintf(body, params["title"], params["upName"], params["aid"], params["condition"], params["change"])
fromVideo, err := strconv.ParseBool(params["fromVideo"])
if err != nil {
log.Error("NotifyEmailTemplate get email template: strconv.ParseBool error(%v) aid(%s) fromVideo(%s)", err, params["aid"], params["fromVideo"])
return
}
//视频追踪信息还没上线,先不写
if !fromVideo {
body += fmt.Sprintf("稿件追踪http://manager.bilibili.co/#!/archive_utils/arc-track?aid=%s", params["aid"])
}
aid, _ := strconv.ParseInt(params["aid"], 10, 64)
uid, _ := strconv.ParseInt(params["uid"], 10, 64)
tpl = &email.Template{
Headers: headers,
Body: body,
ContentType: "text/plain",
Type: email.EmailUP,
AID: aid,
UID: uid,
Username: params["username"],
Department: params["department"],
}
log.Info("NotifyEmailTemplate: email template(%+v)", tpl)
return
}
//PrivateEmailTemplate 私单报备邮件模板
func (d *Dao) PrivateEmailTemplate(params map[string]string) (tpl *email.Template) {
headers := map[string][]string{
email.FROM: {d.c.Mail.Username},
}
//to
to := d.PrivateAddr[params["typeId"]]
if len(to) == 0 {
log.Error("PrivateEmailTemplate lack email address config: typeId(%s), params(%v)", params["typeId"], params)
return
}
headers[email.TO] = to
//cc
cc := d.PrivateAddr["CC"]
if len(cc) > 0 {
headers[email.CC] = cc
}
subject := fmt.Sprintf("私单稿件报备_%s_av%s", params["upName"], params["aid"])
headers[email.SUBJECT] = []string{subject}
body := `稿件标题: %s
稿件状态: %s
禁止项状态: 排行禁止:%s ;动态禁止:%s 推荐禁止:%s
UP主 %s
粉丝量:%s
操作人: %s [%s]
备注: %s`
body = fmt.Sprintf(body, params["arcTitle"], params["arcState"], params["noRankAttr"], params["noDynamicAttr"], params["noRecommendAttr"],
params["upName"], params["upFans"], params["mngName"], params["mngDepartment"], params["note"])
aid, _ := strconv.ParseInt(params["aid"], 10, 64)
uid, _ := strconv.ParseInt(params["uid"], 10, 64)
tpl = &email.Template{
Headers: headers,
Body: body,
ContentType: "text/plain",
Type: params["emailType"],
AID: aid,
UID: uid,
Username: params["mngName"],
Department: params["mngDepartment"],
}
log.Info("PrivateEmailTemplate: email template(%+v)", tpl)
return
}
// MinitorNotifyTeamplate 审核监控报警邮件模板
func (d *Dao) MonitorNotifyTemplate(subject string, body string, toEmails []string) (tpl *email.Template) {
headers := map[string][]string{
email.FROM: {d.c.Mail.Username},
}
headers[email.TO] = toEmails
headers[email.SUBJECT] = []string{subject}
tpl = &email.Template{
Headers: headers,
Body: body,
ContentType: "text/plain",
Type: email.EmailMonitor,
AID: 0,
UID: 0,
Username: "",
Department: "",
}
log.Info("MinitorNotifyTeamplate: email template(%+v)", tpl)
return
}

View File

@@ -0,0 +1,53 @@
package email
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestEmailNotifyEmailTemplate(t *testing.T) {
var (
params = map[string]string{
"aid": "444",
"title": "随意一个标题",
"upName": "随意一个up主昵称",
"condition": "优质up主",
"change": "",
"username": "cxxx",
"department": "r&d",
"typeId": "4",
"fromVideo": "false",
"uid": "441",
}
)
convey.Convey("NotifyEmailTemplate", t, func(ctx convey.C) {
tpl := d.NotifyEmailTemplate(params)
ctx.Convey("Then tpl should not be nil.", func(ctx convey.C) {
ctx.So(tpl, convey.ShouldNotBeNil)
})
})
}
func TestEmailPrivateEmailTemplate(t *testing.T) {
var (
params = map[string]string{
"aid": "444",
"title": "随意一个标题",
"upName": "随意一个up主昵称",
"condition": "优质up主",
"change": "",
"username": "cxxx",
"department": "r&d",
"typeId": "4",
"fromVideo": "false",
"uid": "441",
}
)
convey.Convey("PrivateEmailTemplate", t, func(ctx convey.C) {
tpl := d.PrivateEmailTemplate(params)
ctx.Convey("Then tpl should not be nil.", func(ctx convey.C) {
ctx.So(tpl, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,165 @@
package email
import (
"context"
"github.com/smartystreets/goconvey/convey"
"testing"
"time"
"go-common/app/job/main/videoup-report/model/email"
"go-common/library/cache/redis"
"sync"
)
var tplTest = &email.Template{
Headers: map[string][]string{
email.TO: {"chenxi01@bilibili.com"},
email.SUBJECT: {"nothing at all"},
},
Body: "testhahaha",
ContentType: "text/plain",
}
func TestEmailSendMail(t *testing.T) {
convey.Convey("SendMail", t, func(ctx convey.C) {
tplTest.Headers[email.FROM] = []string{d.email.Username}
d.SendMail(tplTest)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
}
func TestEmailsendEmailLog(t *testing.T) {
var (
to = []string{"chenxi01@bilibili.com"}
cc = []string{""}
result = "成功"
)
convey.Convey("sendEmailLog", t, func(ctx convey.C) {
d.sendEmailLog(tplTest, to, cc, result)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
}
//分片
func batch(tk []int64, length int) (path [][]int64) {
ll := len(tk) / length
if len(tk)%length > 0 {
ll++
}
path = [][]int64{}
item := []int64{}
for i := 0; i < len(tk); i++ {
if i > 0 && i%length == 0 {
path = append(path, item)
item = []int64{}
}
item = append(item, tk[i])
}
if len(item) > 0 {
path = append(path, item)
}
return
}
func TestEmailPushToRedis(t *testing.T) {
uid := int64(123)
uids := []int64{}
speedThreshold := d.c.Mail.SpeedThreshold
overlimit := speedThreshold * d.c.Mail.OverspeedThreshold
for i := 0; i < overlimit*2; i++ {
uids = append(uids, uid)
}
tplTest.UID = uid
path := batch(uids, speedThreshold)
len1 := len(uids)
path = append(path, batch(uids, speedThreshold-1)...)
len2 := 2 * len1
path = append(path, batch(uids, speedThreshold)...)
cnt := 0
convey.Convey("连续发送邮件,间隔出现超限名额", t, func(ctx convey.C) {
tplTest.Headers[email.FROM] = []string{d.email.Username}
for index, task := range path {
now := time.Now().UnixNano()
for i := range task {
cnt++
isfast, key, err := d.PushToRedis(context.TODO(), tplTest)
//_, _, err := d.PushToRedis(context.email.TODO(), tplTest)
convey.So(err, convey.ShouldBeNil)
t.Logf("cnt=%d,index=%d, i=%d, detector=%+v", cnt, index, i, d.detector)
if cnt < overlimit+speedThreshold { //快速,探查阶段
convey.So(isfast, convey.ShouldEqual, false)
convey.So(key, convey.ShouldEqual, email.MailKey)
} else if cnt == overlimit+speedThreshold { //快速,确认为超限,提供超限名单
convey.So(isfast, convey.ShouldEqual, true)
convey.So(key, convey.ShouldEqual, email.MailFastKey)
} else if cnt < len1+speedThreshold { //快速,探查阶段,保留上一次的超限名单
convey.So(isfast, convey.ShouldEqual, false)
convey.So(key, convey.ShouldEqual, email.MailFastKey)
} else if cnt < len2+overlimit+speedThreshold { //慢速/快速探查阶段,第一次慢速时清空上一次的超限名单
convey.So(isfast, convey.ShouldEqual, false)
convey.So(key, convey.ShouldEqual, email.MailKey)
} else if cnt == len2+overlimit+speedThreshold { //快速,确认为超限,提供超限名单
convey.So(isfast, convey.ShouldEqual, true)
convey.So(key, convey.ShouldEqual, email.MailFastKey)
} else { //快速,探查阶段,保留上一次的超限名单
convey.So(isfast, convey.ShouldEqual, false)
convey.So(key, convey.ShouldEqual, email.MailFastKey)
}
}
if diff := now + 1e9 - time.Now().UnixNano(); diff > 0 {
time.Sleep(time.Duration(diff))
}
}
})
}
func TestEmailStart(t *testing.T) {
convey.Convey("email Start", t, func(ctx convey.C) {
err := d.Start(email.MailKey)
convey.So(err, convey.ShouldBeNil)
err = d.Start(email.MailKey + "_1")
convey.So(err, convey.ShouldEqual, redis.ErrNil)
})
}
func TestEmailBatchStart(t *testing.T) {
wg := sync.WaitGroup{}
convey.Convey("email Start\r\n", t, func(ctx convey.C) {
wg.Add(1)
go func() {
defer wg.Done()
for {
err := d.Start(email.MailKey)
t.Logf("start to push normal email, time=%d\r\n", time.Now().Unix())
if err == redis.ErrNil {
t.Logf("normal email stopped\r\n")
break
}
ctx.So(err, convey.ShouldBeNil)
}
}()
go func() {
defer wg.Done()
for {
err := d.Start(email.MailFastKey)
t.Logf("start to push fast email, time=%d\r\n", time.Now().Unix())
if err == redis.ErrNil {
t.Logf("fast email stopped\r\n")
break
}
ctx.So(err, convey.ShouldBeNil)
}
}()
wg.Wait()
t.Logf("end")
})
}

View File

@@ -0,0 +1,66 @@
package email
import (
"context"
"encoding/json"
"go-common/library/cache/redis"
"go-common/library/log"
)
// PushRedis rpush fail item to redis
func (d *Dao) PushRedis(c context.Context, a interface{}, key string) (err error) {
var (
conn = d.redis.Get(c)
bs []byte
)
defer conn.Close()
if bs, err = json.Marshal(a); err != nil {
log.Error("json.Marshal(%v) error(%v) key(%s)", a, err, key)
return
}
if _, err = conn.Do("RPUSH", key, bs); err != nil {
log.Error("conn.Do(RPUSH, %s, %s) error(%v)", key, bs, err)
}
return
}
// PopRedis lpop fail item from redis
func (d *Dao) PopRedis(c context.Context, key string) (bs []byte, err error) {
var conn = d.redis.Get(c)
defer conn.Close()
if bs, err = redis.Bytes(conn.Do("LPOP", key)); err != nil && err != redis.ErrNil {
log.Error("redis.Bytes(conn.Do(LPOP, %s)) error(%v)", key, err)
}
return
}
//RemoveRedis lrem an element from redis list
func (d *Dao) RemoveRedis(c context.Context, key string, member ...interface{}) (reply int, err error) {
var (
lmem = len(member)
conn redis.Conn
)
if lmem < 1 {
return
}
conn = d.redis.Get(c)
defer conn.Close()
if lmem == 1 {
reply, err = redis.Int(conn.Do("LREM", key, 0, member[0]))
} else {
lua := "local a=0;for k in pairs(ARGV) do a=a+redis.call('LREM',KEYS[1],0,ARGV[k]) end;return a;"
args := []interface{}{lua, 1, key}
args = append(args, member...)
reply, err = redis.Int(conn.Do("EVAL", args...))
}
if err != nil {
log.Error("RemoveRedis conn.Do(%s) member(%+v) error(%v)", key, member, err)
}
return
}

View File

@@ -0,0 +1,111 @@
package email
import (
"context"
"encoding/json"
"testing"
"github.com/smartystreets/goconvey/convey"
"go-common/app/job/main/videoup-report/model/archive"
"go-common/app/job/main/videoup-report/model/email"
"go-common/library/cache/redis"
)
var member = email.Retry{
Action: email.RetryActionReply,
AID: 11,
Flag: archive.ReplyOn,
FlagA: archive.ReplyDefault,
}
var key = email.RetryListKey
func TestEmailPushRedis(t *testing.T) {
var (
c = context.TODO()
)
convey.Convey("PushRedis", t, func(ctx convey.C) {
err := d.PushRedis(c, member, key)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestEmailPopRedis(t *testing.T) {
var (
c = context.TODO()
)
TestEmailPushRedis(t)
convey.Convey("PopRedis", t, func(ctx convey.C) {
bs, err := d.PopRedis(c, key)
ctx.Convey("Then err should be nil.bs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(bs, convey.ShouldNotBeNil)
})
})
}
func TestEmailRemoveRedis1(t *testing.T) {
var (
c = context.TODO()
)
TestEmailPushRedis(t)
convey.Convey("RemoveRedis a member", t, func(ctx convey.C) {
bs, _ := json.Marshal(member)
reply, err := d.RemoveRedis(c, key, string(bs))
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(reply, convey.ShouldBeGreaterThan, 0)
})
})
}
func TestEmailRemoveRedis2(t *testing.T) {
var (
c = context.TODO()
err error
reply int
)
key1 := "list"
bsList := []interface{}{"ah1", "ah3", "ah5"}
reply, err = d.RemoveRedis(c, key1, bsList...)
t.Logf("function reply(%v) err(%v)", reply, err)
//多个元素删除
var bs []byte
list := []interface{}{}
old := []int64{-1, 0, 1}
nw := []int64{0, 1}
for _, v := range old {
for _, j := range nw {
m := email.Retry{
AID: member.AID,
Action: member.Action,
Flag: j,
FlagA: v,
}
if bs, err = json.Marshal(m); err != nil {
continue
}
list = append(list, string(bs))
}
}
TestEmailPushRedis(t)
member.Flag = 1
TestEmailPushRedis(t)
convey.Convey("RemoveRedis many member", t, func(ctx convey.C) {
reply, err = d.RemoveRedis(c, key, list...)
t.Logf("member reply(%d) error(%v)", reply, err)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
bs, err := d.PopRedis(c, key)
convey.So(err, convey.ShouldEqual, redis.ErrNil)
convey.So(bs, convey.ShouldBeNil)
})
}