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,65 @@
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",
"memcache_test.go",
"mysql_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/push/conf:go_default_library",
"//app/service/main/push/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"dataplatform.go",
"memcache.go",
"mysql.go",
"mysql_task.go",
"wechat.go",
],
importpath = "go-common/app/job/main/push/dao",
tags = ["automanaged"],
deps = [
"//app/admin/main/push/model:go_default_library",
"//app/job/main/push/conf:go_default_library",
"//app/job/main/push/model:go_default_library",
"//app/service/main/push/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/errgroup: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,76 @@
package dao
import (
"context"
"go-common/app/job/main/push/conf"
"go-common/library/cache/memcache"
xsql "go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
"go-common/library/stat/prom"
)
// Dao .
type Dao struct {
c *conf.Config
db *xsql.DB
mc *memcache.Pool
httpClient *bm.Client
dpClient *bm.Client
delCallbacksStmt *xsql.Stmt
delTasksStmt *xsql.Stmt
reportLastIDStmt *xsql.Stmt
reportsByRangeStmt *xsql.Stmt
updateTaskStatusStmt *xsql.Stmt
updateTaskStmt *xsql.Stmt
updateDpCondStmt *xsql.Stmt
}
var (
errorsCount = prom.BusinessErrCount
infosCount = prom.BusinessInfoCount
)
// New creates a dao instance.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
db: xsql.NewMySQL(c.MySQL),
mc: memcache.NewPool(c.Memcache.Config),
httpClient: bm.NewClient(c.HTTPClient),
dpClient: bm.NewClient(c.DpClient),
}
d.delCallbacksStmt = d.db.Prepared(_delCallbacksSQL)
d.delTasksStmt = d.db.Prepared(_delTasksSQL)
d.reportLastIDStmt = d.db.Prepared(_reportLastIDSQL)
d.reportsByRangeStmt = d.db.Prepared(_reportsByRangeSQL)
d.updateTaskStatusStmt = d.db.Prepared(_upadteTaskStatusSQL)
d.updateTaskStmt = d.db.Prepared(_upadteTaskSQL)
d.updateDpCondStmt = d.db.Prepared(_updateDpCondSQL)
return
}
// PromError prometheus error count.
func PromError(name string) {
errorsCount.Incr(name)
}
// PromInfo prometheus info count.
func PromInfo(name string) {
infosCount.Incr(name)
}
// Ping reports the health of the db/cache etc.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.db.Ping(c); err != nil {
return
}
err = d.pingMC(c)
return
}
// Close .
func (d *Dao) Close() {
d.db.Close()
d.mc.Close()
}

View File

@@ -0,0 +1,60 @@
package dao
import (
"context"
"flag"
"os"
"path/filepath"
"testing"
"go-common/app/job/main/push/conf"
. "github.com/smartystreets/goconvey/convey"
)
var d *Dao
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") == "uat" {
flag.Set("app_id", "main.web-svr.push-job")
flag.Set("conf_token", "4de43ccf842485eea314fd8a48f1ee84")
flag.Set("tree_id", "5220")
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")
} else {
dir, _ := filepath.Abs("../cmd/push-job-test.toml")
flag.Set("conf", dir)
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
os.Exit(m.Run())
}
func Test_Wechat(t *testing.T) {
Convey("test wechat message", t, func() {
err := d.SendWechat("test send wechat message")
So(err, ShouldBeNil)
})
}
func Test_DpDownloadFile(t *testing.T) {
Convey("data platform download file", t, func() {
_, err := d.DpDownloadFile(context.Background(), "https://raw.githubusercontent.com/Bilibili/discovery/master/README.md")
So(err, ShouldBeNil)
})
}
func Test_DpSubmitQuery(t *testing.T) {
Convey("data platform submit query", t, func() {
url, err := d.DpSubmitQuery(context.Background(), "select device_token from basic.dws_push_buvid where log_date='20180707'")
So(err, ShouldNotBeNil)
t.Logf("url(%v)", url)
})
}

View File

@@ -0,0 +1,153 @@
package dao
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
"go-common/app/job/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
)
const (
// 提交查询的接口
_dpSubmitQueryURL = "http://berserker.bilibili.co/avenger/api/74/query"
)
var (
dpSignParams = []string{"appKey", "timestamp", "version"}
)
// DpSubmitQuery .
func (d *Dao) DpSubmitQuery(ctx context.Context, query string) (statusRUL string, err error) {
params := d.params()
params.Set("query", query)
var res struct {
Code int `json:"code"`
Msg string `json:"msg"`
StatusURL string `json:"jobStatusUrl"`
}
if err = d.newRequest(ctx, _dpSubmitQueryURL, params, &res); err != nil {
log.Error("d.DpSubmitQuery newRequest url(%s) error(%v)", _dpSubmitQueryURL+"?"+params.Encode(), err)
return
}
if res.Code != http.StatusOK {
log.Error("d.DpSubmitQuery newRequest error code:%d ; url(%s) ", res.Code, _dpSubmitQueryURL+"?"+params.Encode())
err = ecode.Int(res.Code)
err = fmt.Errorf("code(%d) msg(%s)", res.Code, res.Msg)
return
}
statusRUL = res.StatusURL
return
}
// DpCheckJob .
func (d *Dao) DpCheckJob(ctx context.Context, url string) (res *model.DpCheckJobResult, err error) {
params := d.params()
if err = d.newRequest(ctx, url, params, &res); err != nil {
log.Error("d.DpCheckJob newRequest error(%v)", err)
return
}
if res.Code != http.StatusOK {
log.Error("d.DpCheckJob newRequest error code:%d ; url(%s) ", res.Code, url+"?"+params.Encode())
err = fmt.Errorf("code(%d) msg(%s)", res.Code, res.Msg)
}
return
}
// DpDownloadFile .
func (d *Dao) DpDownloadFile(ctx context.Context, url string) (content []byte, err error) {
req, _ := http.NewRequest(http.MethodGet, url, nil)
if content, err = d.dpClient.Raw(ctx, req); err != nil {
log.Error("d.dpClient.Raw(%s) error(%v)", url, err)
}
return
}
func (d *Dao) params() url.Values {
params := url.Values{}
params.Set("appKey", d.c.DpClient.Key)
params.Set("timestamp", time.Now().Format("2006-01-02 15:04:05"))
params.Set("version", "1.0")
params.Set("signMethod", "md5")
return params
}
// newRequest new http request with method, url, ip, values and headers.
func (d *Dao) newRequest(c context.Context, url string, params url.Values, res interface{}) (err error) {
enc, err := d.dpSign(params)
if err != nil {
log.Error("url:%s,params:%v", url, params)
return
}
if enc != "" {
url = url + "?" + enc
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Error("method:%s,url:%s", http.MethodGet, url)
return
}
return d.httpClient.Do(c, req, res)
}
// dpSign calc appkey and appsecret sign.
func (d *Dao) dpSign(params url.Values) (query string, err error) {
tmp := params.Encode()
signTmp := d.encode(params)
if strings.IndexByte(tmp, '+') > -1 {
tmp = strings.Replace(tmp, "+", "%20", -1)
}
var b bytes.Buffer
b.WriteString(d.c.DpClient.Secret)
b.WriteString(signTmp)
b.WriteString(d.c.DpClient.Secret)
mh := md5.Sum(b.Bytes())
var qb bytes.Buffer
qb.WriteString(tmp)
qb.WriteString("&sign=")
qb.WriteString(strings.ToUpper(hex.EncodeToString(mh[:])))
query = qb.String()
return
}
// Encode encodes the values into ``URL encoded'' form
// ("bar=baz&foo=quux") sorted by key.
func (d *Dao) encode(v url.Values) string {
if v == nil {
return ""
}
var buf bytes.Buffer
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
found := false
for _, p := range dpSignParams {
if p == k {
found = true
break
}
}
if !found {
continue
}
vs := v[k]
prefix := k
for _, v := range vs {
buf.WriteString(prefix)
buf.WriteString(v)
}
}
return buf.String()
}

View File

@@ -0,0 +1,92 @@
package dao
import (
"context"
"fmt"
"sync"
"time"
pushmdl "go-common/app/service/main/push/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
const (
_prefixReport = "r_%d"
_bulkSize = 10
)
func reportKey(mid int64) string {
return fmt.Sprintf(_prefixReport, mid)
}
// pingMc ping memcache
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
item := memcache.Item{Key: "ping", Value: []byte{1}, Expiration: int32(time.Now().Unix())}
err = conn.Set(&item)
return
}
// ReportsCacheByMids get report cache by mids.
func (d *Dao) ReportsCacheByMids(c context.Context, mids []int64) (res map[int64][]*pushmdl.Report, missed []int64, err error) {
res = make(map[int64][]*pushmdl.Report, len(mids))
if len(mids) == 0 {
return
}
allKeys := make([]string, 0, len(mids))
midmap := make(map[string]int64, len(mids))
for _, mid := range mids {
k := reportKey(mid)
allKeys = append(allKeys, k)
midmap[k] = mid
}
group := errgroup.Group{}
mutex := sync.Mutex{}
keysLen := len(allKeys)
for i := 0; i < keysLen; i += _bulkSize {
var keys []string
if (i + _bulkSize) > keysLen {
keys = allKeys[i:]
} else {
keys = allKeys[i : i+_bulkSize]
}
group.Go(func() error {
conn := d.mc.Get(context.TODO())
defer conn.Close()
replys, err := conn.GetMulti(keys)
if err != nil {
PromError("mc:获取上报")
log.Error("conn.Gets(%v) error(%v)", keys, err)
return nil
}
for k, item := range replys {
rm := make(map[int64]map[string]*pushmdl.Report)
if err = conn.Scan(item, &rm); err != nil {
PromError("mc:解析上报")
log.Error("item.Scan(%s) error(%v)", item.Value, err)
continue
}
mutex.Lock()
mid := midmap[k]
for _, v := range rm {
for _, r := range v {
res[mid] = append(res[mid], r)
}
}
delete(midmap, k)
mutex.Unlock()
}
return nil
})
}
group.Wait()
missed = make([]int64, 0, len(midmap))
for _, mid := range midmap {
missed = append(missed, mid)
}
return
}

View File

@@ -0,0 +1,22 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Ping(t *testing.T) {
Convey("ping mc ", t, func() {
err := d.Ping(context.Background())
So(err, ShouldBeNil)
})
}
func Test_ReportsCacheByMids(t *testing.T) {
Convey("ReportsCacheByMids", t, func() {
_, _, err := d.ReportsCacheByMids(context.Background(), []int64{0, 1})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,76 @@
package dao
import (
"context"
"database/sql"
"time"
pushmdl "go-common/app/service/main/push/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
)
const (
_delCallbacksSQL = `DELETE FROM push_callbacks where ctime <= ? limit ?`
_reportLastIDSQL = `SELECT MAX(id) from push_reports`
_reportsByRangeSQL = `SELECT id,app_id,platform_id,mid,buvid,device_token,build,time_zone,notify_switch,device_brand,device_model,os_version,extra FROM push_reports WHERE id>? and id<? and dtime=0`
// for 全量推送
_reportsTaskAllByRangeSQL = `SELECT platform_id,device_token,build FROM push_reports WHERE id>? and id<=? and app_id=? and dtime=0 and notify_switch=1`
)
// BeginTx begin transaction.
func (d *Dao) BeginTx(c context.Context) (*xsql.Tx, error) {
return d.db.Begin(c)
}
// DelCallbacks deletes callbacks.
func (d *Dao) DelCallbacks(c context.Context, t time.Time, limit int) (rows int64, err error) {
res, err := d.delCallbacksStmt.Exec(c, t, limit)
if err != nil {
log.Error("d.DelCallbacks(%v) error(%v)", t, err)
PromError("mysql:DelCallbacks")
return
}
rows, err = res.RowsAffected()
return
}
// ReportLastID gets the latest ID of report database record.
func (d *Dao) ReportLastID(c context.Context) (id int64, err error) {
if err = d.reportLastIDStmt.QueryRow(c).Scan(&id); err != nil {
if err == sql.ErrNoRows {
return
}
log.Error("d.ReportLastID() error(%v)", err)
PromError("mysql:ReportLastID")
}
return
}
// ReportsByRange gets reports by id range.
func (d *Dao) ReportsByRange(c context.Context, min, max int64) (rs []*pushmdl.Report, err error) {
rows, err := d.reportsByRangeStmt.Query(c, min, max)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
r := &pushmdl.Report{}
if err = rows.Scan(&r.ID, &r.APPID, &r.PlatformID, &r.Mid, &r.Buvid, &r.DeviceToken,
&r.Build, &r.TimeZone, &r.NotifySwitch, &r.DeviceBrand, &r.DeviceModel, &r.OSVersion, &r.Extra); err != nil {
log.Error("d.ReportsByRange Scan() error(%v)", err)
PromError("mysql:ReportsByRange")
return
}
rs = append(rs, r)
}
return
}
// ReportsTaskAll gets reports by range
func (d *Dao) ReportsTaskAll(c context.Context, min, max, app int64) (rows *xsql.Rows, err error) {
if rows, err = d.db.Query(c, _reportsTaskAllByRangeSQL, min, max, app); err != nil {
log.Error("ReportsTaskAll load reports start(%d) end(%d) error(%v)", min, max, err)
}
return
}

View File

@@ -0,0 +1,157 @@
package dao
import (
"context"
"database/sql"
"encoding/json"
"strconv"
"time"
pamdl "go-common/app/admin/main/push/model"
pushmdl "go-common/app/service/main/push/model"
xsql "go-common/library/database/sql"
"go-common/library/log"
)
const (
_addTaskSQL = "INSERT INTO push_tasks (job,type,app_id,business_id,platform,platform_id,title,summary,link_type,link_value,build,sound,vibration,pass_through,mid_file,push_time,expire_time,status,`group`,image_url,extra) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
_delTasksSQL = `DELETE FROM push_tasks where mtime <= ? limit ?`
_upadteTaskStatusSQL = "UPDATE push_tasks SET status=? WHERE id=?"
_taskByStatusSQL = "SELECT id,job,type,app_id,business_id,platform,title,summary,link_type,link_value,build,sound,vibration,pass_through,mid_file,progress,push_time,expire_time,status,`group`,image_url,extra FROM push_tasks WHERE status=? AND dtime=0 LIMIT 1 FOR UPDATE"
_upadteTaskSQL = "UPDATE push_tasks SET mid_file=?,status=? WHERE id=?"
// dataplatform
_txDpCondByStatusSQL = `SELECT id,job,task,conditions,sql_stmt,status,status_url,file FROM push_dataplatform_conditions WHERE status=? LIMIT 1 FOR UPDATE`
_updateDpCondSQL = `UPDATE push_dataplatform_conditions SET job=?,task=?,conditions=?,sql_stmt=?,status=?,status_url=?,file=? WHERE id=?`
_UpdateDpCondStatusSQL = `UPDATE push_dataplatform_conditions SET status=? WHERE id=?`
)
// DelTasks deletes tasks.
func (d *Dao) DelTasks(c context.Context, t time.Time, limit int) (rows int64, err error) {
res, err := d.delTasksStmt.Exec(c, t, limit)
if err != nil {
log.Error("d.DelTasks(%v) error(%v)", t, err)
PromError("mysql:DelTasks")
return
}
rows, err = res.RowsAffected()
return
}
// TxTaskByStatus gets task by status by tx.
func (d *Dao) TxTaskByStatus(tx *xsql.Tx, status int8) (t *pushmdl.Task, err error) {
var (
id int64
platform string
build string
progress string
extra string
now = time.Now()
)
t = &pushmdl.Task{Progress: &pushmdl.Progress{}, Extra: &pushmdl.TaskExtra{}}
if err = tx.QueryRow(_taskByStatusSQL, status).Scan(&id, &t.Job, &t.Type, &t.APPID, &t.BusinessID, &platform, &t.Title, &t.Summary, &t.LinkType, &t.LinkValue, &build,
&t.Sound, &t.Vibration, &t.PassThrough, &t.MidFile, &progress, &t.PushTime, &t.ExpireTime, &t.Status, &t.Group, &t.ImageURL, &extra); err != nil {
t = nil
if err == sql.ErrNoRows {
err = nil
return
}
log.Error("d.TxTaskByStatus() QueryRow(%d,%v) error(%v)", status, now, err)
PromError("mysql:按状态查询任务")
return
}
t.ID = strconv.FormatInt(id, 10)
t.Platform = pushmdl.SplitInts(platform)
t.Build = pushmdl.ParseBuild(build)
if progress != "" {
if err = json.Unmarshal([]byte(progress), t.Progress); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", progress, err)
return
}
}
if extra != "" {
if err = json.Unmarshal([]byte(extra), t.Extra); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", extra, err)
}
}
return
}
// TxUpdateTaskStatus updates task status by tx.
func (d *Dao) TxUpdateTaskStatus(tx *xsql.Tx, taskID string, status int8) (err error) {
id, _ := strconv.ParseInt(taskID, 10, 64)
if _, err = tx.Exec(_upadteTaskStatusSQL, status, id); err != nil {
log.Error("d.TxUpdateTaskStatus() Exec(%s,%d) error(%v)", taskID, status, err)
PromError("mysql:更新推送任务状态")
}
return
}
// UpdateTaskStatus update task status.
func (d *Dao) UpdateTaskStatus(c context.Context, taskID int64, status int8) (err error) {
if _, err = d.updateTaskStatusStmt.Exec(c, status, taskID); err != nil {
log.Error("d.updateTaskStatusStmt.Exec(%d,%d) error(%v)", taskID, status, err)
PromError("mysql:更新推送任务状态")
}
return
}
// UpdateTask update task.
func (d *Dao) UpdateTask(c context.Context, taskID string, file string, status int8) (err error) {
id, _ := strconv.ParseInt(taskID, 10, 64)
if _, err = d.updateTaskStmt.Exec(c, file, status, id); err != nil {
log.Error("d.updateTaskFileStmt.Exec(%d,%s,%d) error(%v)", id, file, status, err)
PromError("mysql:更新推送任务file")
}
return
}
// AddTask adds task
func (d *Dao) AddTask(ctx context.Context, t *pushmdl.Task) (err error) {
var (
platform = pushmdl.JoinInts(t.Platform)
build, _ = json.Marshal(t.Build)
extra, _ = json.Marshal(t.Extra)
)
if _, err = d.db.Exec(ctx, _addTaskSQL, t.Job, t.Type, t.APPID, t.BusinessID, platform, t.PlatformID, t.Title, t.Summary, t.LinkType, t.LinkValue,
build, t.Sound, t.Vibration, t.PassThrough, t.MidFile, t.PushTime, t.ExpireTime, t.Status, t.Group, t.ImageURL, extra); err != nil {
log.Error("d.AddTask(%+v) error(%v)", t, err)
}
return
}
// TxCondByStatus gets condition by status.
func (d *Dao) TxCondByStatus(tx *xsql.Tx, status int) (cond *pamdl.DPCondition, err error) {
cond = new(pamdl.DPCondition)
if err = tx.QueryRow(_txDpCondByStatusSQL, status).Scan(&cond.ID, &cond.Job, &cond.Task, &cond.Condition, &cond.SQL, &cond.Status, &cond.StatusURL, &cond.File); err != nil {
if err == sql.ErrNoRows {
cond = nil
err = nil
}
return
}
return
}
// UpdateDpCond update data platform query condition
func (d *Dao) UpdateDpCond(ctx context.Context, cond *pamdl.DPCondition) (err error) {
if _, err = d.updateDpCondStmt.Exec(ctx, cond.Job, cond.Task, cond.Condition, cond.SQL, cond.Status, cond.StatusURL, cond.File, cond.ID); err != nil {
log.Error("d.UpdateDpCond(%+v) error(%v)", cond, err)
}
return
}
// UpdateDpCondStatus update data platform query condition
func (d *Dao) UpdateDpCondStatus(ctx context.Context, id int64, status int) (err error) {
if _, err = d.db.Exec(ctx, _UpdateDpCondStatusSQL, status, id); err != nil {
log.Error("d.UpdateCondStatus(%d,%d) error(%v)", id, status, err)
}
return
}
// TxUpdateCondStatus update data platform query condition status
func (d *Dao) TxUpdateCondStatus(tx *xsql.Tx, id int64, status int) (err error) {
if _, err = tx.Exec(_UpdateDpCondStatusSQL, status, id); err != nil {
log.Error("d.TxUpdateCondStatus(%d,%d) error(%v)", id, status, err)
}
return
}

View File

@@ -0,0 +1,56 @@
package dao
import (
"context"
"testing"
"time"
pushmdl "go-common/app/service/main/push/model"
. "github.com/smartystreets/goconvey/convey"
)
func Test_DelCallbacks(t *testing.T) {
Convey("del callbacks", t, func() {
loc, _ := time.LoadLocation("Local")
tm := time.Date(2018, 1, 11, 18, 27, 03, 0, loc)
rows, err := d.DelCallbacks(context.TODO(), tm, 1000)
So(err, ShouldBeNil)
t.Logf("del callback rows:%d", rows)
})
}
func Test_DelTasks(t *testing.T) {
Convey("del tasks", t, func() {
loc, _ := time.LoadLocation("Local")
tm := time.Date(2018, 4, 2, 16, 00, 00, 0, loc)
rows, err := d.DelTasks(context.TODO(), tm, 1000)
So(err, ShouldBeNil)
t.Logf("del task rows:%d", rows)
})
}
func Test_ReportLastID(t *testing.T) {
Convey("get report latest id", t, func() {
id, err := d.ReportLastID(context.TODO())
So(err, ShouldBeNil)
t.Logf("latest report id(%d)", id)
})
}
func Test_TxTaskByStatus(t *testing.T) {
Convey("tx task by status", t, func() {
tx, _ := d.BeginTx(context.Background())
_, err := d.TxTaskByStatus(tx, pushmdl.TaskStatusPrepared)
So(err, ShouldBeNil)
err = tx.Commit()
So(err, ShouldBeNil)
})
}
func Test_AddTask(t *testing.T) {
Convey("tx add task", t, func() {
err := d.AddTask(context.Background(), &pushmdl.Task{})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,79 @@
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 (
_url = "http://bap.bilibili.co/api/v1/message/add"
)
// SendWechat 发送企业微信消息
func (d *Dao) SendWechat(content string) (err error) {
params := map[string]string{
"content": content,
"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 {
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))
}