Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

21
app/admin/main/push/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/admin/main/push/cmd:all-srcs",
"//app/admin/main/push/conf:all-srcs",
"//app/admin/main/push/dao:all-srcs",
"//app/admin/main/push/http:all-srcs",
"//app/admin/main/push/model:all-srcs",
"//app/admin/main/push/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,88 @@
## push-admin
### v1.6.4
1. 修正推送测试时没有image_url的bug
### v1.6.3
1. 去掉task progress 中的 platforms 字段
### v1.6.2
1. 业务维度增加策略层白名单字段
### v1.6.1
1. 应用维度增加每日用户收到消息上限数
### v1.6.0
1. 加上传图片接口
### v1.5.1
1. 添加检测数据平台定时任务有没有出数据的接口
### v1.5.0
1. 支持图片推送字段
### v1.4.0
1. 对接数据平台用户画像
### v1.3.1
1. 用 bm binding
### v1.3.0
1. 推送服务切换到push-service
### v1.2.13
1. 修复更新任务内容不更新
### v1.2.12
1. 增加列表权限点
### v1.2.11
1. 修复json解析job出错转成字符串给到前端
### v1.2.10
1. 迁移model至push-service
### v1.2.9
1. 修复添加业务方重复添加时主键冲突错误
### v1.2.8
1. 更改jobName生成规则
### v1.2.6
1. 修复全量推送
### v1.2.5
1. 添加任务需要确认
2. 新建任务时并发
3. 检测上传文件
4. add ecode
### v1.2.4
1. 添加任务异步返回
### v1.2.3
1. business add silent time & push count limit
### v1.2.2
1. 将文件名保存到task信息里
2. 任务列表页面将拆分的任务聚合展示
### v1.2.1
1. 加权限点验证
### v1.2.0
1. 支持后台任务推送
2. mid文件保存到硬盘
### v1.1.2
1. 优化代码
2. 修复零值不更新的bug
### v1.1.1
1. 修复计数问题
### v1.1.0
1. 用orm重构代码
### v1.0.2
1. 增加tracer

View File

@@ -0,0 +1,11 @@
# Owner
liweijia
zhapuyu
renwei
caoguoliang
# Author
caoguoliang
# Reviewer
caoguoliang

View File

@@ -0,0 +1,15 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
- liweijia
- renwei
- zhapuyu
labels:
- admin
- admin/main/push
- main
options:
no_parent_owners: true
reviewers:
- caoguoliang

View File

View File

@@ -0,0 +1,45 @@
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 = ["push-admin-test.toml"],
importpath = "go-common/app/admin/main/push/cmd",
tags = ["automanaged"],
deps = [
"//app/admin/main/push/conf:go_default_library",
"//app/admin/main/push/http:go_default_library",
"//app/admin/main/push/service:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
"//library/os/signal:go_default_library",
"//library/syscall: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,56 @@
package main
import (
"flag"
"os"
"time"
"go-common/app/admin/main/push/conf"
"go-common/app/admin/main/push/http"
"go-common/app/admin/main/push/service"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/net/trace"
"go-common/library/os/signal"
"go-common/library/syscall"
)
var (
s *service.Service
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
trace.Init(conf.Conf.Tracer)
defer trace.Close()
ecode.Init(conf.Conf.Ecode)
s = service.New(conf.Conf)
http.Init(conf.Conf, s)
log.Info("push-admin start")
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.SIGSTOP, syscall.SIGINT:
time.Sleep(time.Second * 2)
log.Info("get a signal %s, stop the push-admin process", si.String())
s.Close()
s.Wait()
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,118 @@
version = "1.0.0"
user = "nobody"
dir = "./"
family = "push-admin"
[log]
dir = "/data/log/push-admin/"
[HTTPServer]
addr = "0.0.0.0:7141"
maxListen = 1000
timeout = "10m"
readTimeout = "10m"
writeTimeout = "10m"
[httpClient]
key = "b1014d7c339a5649"
secret = "75b74b612aa792b112e6504cae44c319"
dial = "10s"
timeout = "10s"
keepAlive = "60s"
[httpClient.breaker]
window ="3s"
sleep ="100ms"
bucket = 10
ratio = 0.5
request = 100
[dpClient]
key = "17d515f7fa6324a19cfc6546d17ddca7"
secret = "eee2d709e54600ce147a4f522dc3c86e"
dial = "2s"
timeout = "30s"
keepAlive = "60s"
[dpClient.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[orm]
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_push?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4&allowNativePasswords=true"
active = 5
idle = 5
idleTimeout = "4h"
[mysql]
addr = "172.16.33.205"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_push?timeout=1m&readTimeout=1m&writeTimeout=1m&parseTime=true&loc=Local&charset=utf8,utf8mb4&allowNativePasswords=true"
active = 10
idle = 5
queryTimeout = "1m"
execTimeout = "1m"
tranTimeout = "1m"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[wechat]
token = "GYQeuDWBbAsCNeGz"
secret = "ZKpmgINTkianyMbMixyxcPQjMCSHCDrk"
username = "wangjian"
[cfg]
mountDir = "/data/storage/"
diskFileExpireDay = 7
limitPerTask = 200000
taskGoroutines = 10
partitionsURL = "http://uat-api.bilibili.co/x/internal/v2/archive/typelist"
upimgURL = "http://uat-api.bilibili.co/x/internal/push-service/upimg"
[auth]
managerHost = "http://uat-manager.bilibili.co"
dashboardHost = "http://dashboard-mng.bilibili.co"
dashboardCaller = "manager-go"
[auth.DsHTTPClient]
key = "manager-go"
secret = "949bbb2dd3178252638c2407578bc7ad"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[auth.DsHTTPClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[auth.MaHTTPClient]
key = "f6433799dbd88751"
secret = "36f8ddb1806207fe07013ab6a77a3935"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[auth.MaHTTPClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[auth.session]
sessionIDLength = 32
cookieLifeTime = 1800
cookieName = "mng-go"
domain = ".bilibili.co"
[auth.session.Memcache]
name = "go-business/auth"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 10
idle = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"

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 = ["conf.go"],
importpath = "go-common/app/admin/main/push/conf",
tags = ["automanaged"],
deps = [
"//library/conf:go_default_library",
"//library/database/orm:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/permit:go_default_library",
"//library/net/trace: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,96 @@
package conf
import (
"errors"
"flag"
"go-common/library/conf"
"go-common/library/database/orm"
sqlx "go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"go-common/library/net/trace"
"github.com/BurntSushi/toml"
)
var (
// config
confPath string
client *conf.Client
// Conf .
Conf = &Config{}
)
// Config def.
type Config struct {
Ecode *ecode.Config
Auth *permit.Config
HTTPServer *bm.ServerConfig
HTTPClient *bm.ClientConfig
DPClient *bm.ClientConfig
ORM *orm.Config
MySQL *sqlx.Config
Log *log.Config
Tracer *trace.Config
Wechat *wechat
Cfg *cfg
}
type wechat struct {
Token string
Secret string
Username string
}
type cfg struct {
MountDir string
DiskFileExpireDay int64
LimitPerTask int
TaskGoroutines int
PartitionsURL string
UpimgURL string
}
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
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init int config
func Init() error {
if confPath != "" {
return local()
}
return remote()
}

View File

@@ -0,0 +1,56 @@
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/admin/main/push/conf:go_default_library",
"//app/admin/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",
"task.go",
"wechat.go",
],
importpath = "go-common/app/admin/main/push/dao",
tags = ["automanaged"],
deps = [
"//app/admin/main/push/conf:go_default_library",
"//app/admin/main/push/model:go_default_library",
"//library/database/orm: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",
"//vendor/github.com/jinzhu/gorm: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,57 @@
package dao
import (
"context"
"go-common/app/admin/main/push/conf"
"go-common/library/database/orm"
sqlx "go-common/library/database/sql"
xhttp "go-common/library/net/http/blademaster"
"github.com/jinzhu/gorm"
)
// Dao struct user of Dao.
type Dao struct {
c *conf.Config
db *sqlx.DB
DB *gorm.DB
httpClient *xhttp.Client
}
// New create a instance of Dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
db: sqlx.NewMySQL(c.MySQL),
DB: orm.NewMySQL(c.ORM),
httpClient: xhttp.NewClient(c.HTTPClient),
}
d.initORM()
return
}
func (d *Dao) initORM() {
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
if defaultTableName == "push_business" {
return defaultTableName
}
return "push_" + defaultTableName
}
d.DB.LogMode(true)
}
// Ping check connection of db , mc.
func (d *Dao) Ping(c context.Context) (err error) {
if d.DB != nil {
err = d.DB.DB().PingContext(c)
}
return
}
// Close close connection of db , mc.
func (d *Dao) Close() {
if d.DB != nil {
d.DB.Close()
}
}

View File

@@ -0,0 +1,83 @@
package dao
import (
"context"
"flag"
"path/filepath"
"testing"
"go-common/app/admin/main/push/conf"
"go-common/app/admin/main/push/model"
. "github.com/smartystreets/goconvey/convey"
)
var d *Dao
func init() {
dir, _ := filepath.Abs("../cmd/push-admin-test.toml")
flag.Set("conf", dir)
conf.Init()
d = New(conf.Conf)
}
func WithDao(f func(d *Dao)) func() {
return func() {
f(d)
}
}
func Test_Dao(t *testing.T) {
Convey("dao test", t, WithDao(func(d *Dao) {
d.Ping(context.TODO())
}))
}
func Test_AddDPCondition(t *testing.T) {
Convey("AddDPCondition", t, WithDao(func(d *Dao) {
cond := &model.DPCondition{
Task: 123,
Job: "456",
Condition: "cond",
SQL: "sql",
Status: 2,
StatusURL: "status url",
File: "file",
}
id, err := d.AddDPCondition(context.Background(), cond)
So(err, ShouldBeNil)
So(id, ShouldBeGreaterThan, 0)
}))
}
func Test_DPCondition(t *testing.T) {
Convey("DPContion", t, WithDao(func(d *Dao) {
res, err := d.DPCondition(context.Background(), "456")
So(err, ShouldBeNil)
t.Logf("res(%+v)", res)
}))
}
func Test_AddTask(t *testing.T) {
Convey("add task", t, WithDao(func(d *Dao) {
t := &model.Task{Job: "123", AppID: 2}
_, err := d.AddTask(context.Background(), t)
So(err, ShouldBeNil)
}))
}
func Test_TaskInfo(t *testing.T) {
Convey("task info", t, WithDao(func(d *Dao) {
task, err := d.TaskInfo(context.Background(), 117)
So(err, ShouldBeNil)
t.Logf("task(%+v)", task)
}))
}
func Test_Partitions(t *testing.T) {
Convey("partitions", t, WithDao(func(d *Dao) {
res, err := d.Partitions(context.Background())
So(err, ShouldBeNil)
t.Logf("partitions(%v)", res)
}))
}

View File

@@ -0,0 +1,74 @@
package dao
import (
"context"
"go-common/app/admin/main/push/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_dpConditionSQL = `select id,job,task,conditions,sql_stmt,status,status_url,file from push_dataplatform_conditions where job=?`
_addDpConditionSQL = `insert into push_dataplatform_conditions (job,task,conditions,sql_stmt,status,status_url,file) values (?,?,?,?,?,?,?) on duplicate key update task=?,conditions=?,sql_stmt=?,status=?,status_url=?,file=?`
_updateDpConditionStatusSQL = `update push_dataplatform_conditions set status=? where job=?`
)
// AddDPCondition add data platform task
func (d *Dao) AddDPCondition(ctx context.Context, cond *model.DPCondition) (id int64, err error) {
res, err := d.db.Exec(ctx, _addDpConditionSQL, cond.Job, cond.Task, cond.Condition, cond.SQL, cond.Status, cond.StatusURL, cond.File,
cond.Task, cond.Condition, cond.SQL, cond.Status, cond.StatusURL, cond.File)
if err != nil {
log.Error("d.AddDPCondition(%+v) error(%v)", cond, err)
return
}
id, err = res.LastInsertId()
return
}
// DPCondition .
func (d *Dao) DPCondition(ctx context.Context, job string) (c *model.DPCondition, err error) {
c = new(model.DPCondition)
if err = d.db.QueryRow(ctx, _dpConditionSQL, job).Scan(&c.ID, &c.Job, &c.Task, &c.Condition, &c.SQL, &c.Status, &c.StatusURL, &c.File); err != nil {
if err == sql.ErrNoRows {
c = nil
err = nil
}
return
}
return
}
// UpdateDpCondtionStatus .
func (d *Dao) UpdateDpCondtionStatus(ctx context.Context, job string, status int) (err error) {
_, err = d.db.Exec(ctx, _updateDpConditionStatusSQL, status, job)
return
}
// Partitions 获取一级分区数据
func (d *Dao) Partitions(ctx context.Context) (m map[int]string, err error) {
var res = struct {
Code int `json:"code"`
Data map[int]struct {
ID int `json:"id"`
Pid int `json:"pid"`
Name string `json:"name"`
} `json:"data"`
}{}
if err = d.httpClient.Get(ctx, d.c.Cfg.PartitionsURL, "", nil, &res); err != nil {
return
}
if !ecode.Int(res.Code).Equal(ecode.OK) {
err = ecode.Int(res.Code)
return
}
m = make(map[int]string)
for _, v := range res.Data {
if v.Pid != 0 {
continue
}
m[v.ID] = v.Name
}
return
}

View File

@@ -0,0 +1,43 @@
package dao
import (
"context"
"database/sql"
"go-common/app/admin/main/push/model"
"go-common/library/log"
)
const (
_addTaskSQL = "insert into push_tasks (job,type,app_id,business_id,title,summary,link_type,link_value,sound,vibration,push_time,expire_time,status,`group`,image_url,extra) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
_taskInfoSQL = "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 from push_tasks where id=?"
)
// TaskInfo .
func (d *Dao) TaskInfo(ctx context.Context, id int64) (t *model.Task, err error) {
t = &model.Task{}
if err = d.db.QueryRow(ctx, _taskInfoSQL, id).Scan(&t.ID, &t.Job, &t.Type, &t.AppID, &t.BusinessID, &t.Platform, &t.Title, &t.Summary, &t.LinkType, &t.LinkValue, &t.Build,
&t.Sound, &t.Vibration, &t.PassThrough, &t.MidFile, &t.Progress, &t.PushTime, &t.ExpireTime, &t.Status, &t.Group, &t.ImageURL); err != nil {
if err == sql.ErrNoRows {
t = nil
err = nil
return
}
log.Error("d.TaskInfo(%s) error(%v)", id, err)
return
}
t.PushTimeUnix = t.PushTime.Unix()
t.ExpireTimeUnix = t.ExpireTime.Unix()
return
}
// AddTask add data platform task
func (d *Dao) AddTask(ctx context.Context, t *model.Task) (id int64, err error) {
var res sql.Result
if res, err = d.db.Exec(ctx, _addTaskSQL, t.Job, t.Type, t.AppID, t.BusinessID, t.Title, t.Summary, t.LinkType, t.LinkValue, t.Sound, t.Vibration, t.PushTime, t.ExpireTime, t.Status, t.Group, t.ImageURL, t.Extra); err != nil {
log.Error("d.AddTask(%+v) error(%v)", t, err)
return
}
id, err = res.LastInsertId()
return
}

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 {
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,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"apps.go",
"auths.go",
"business.go",
"dataplatform.go",
"http.go",
"tasks.go",
"upload.go",
],
importpath = "go-common/app/admin/main/push/http",
tags = ["automanaged"],
deps = [
"//app/admin/main/push/conf:go_default_library",
"//app/admin/main/push/model:go_default_library",
"//app/admin/main/push/service:go_default_library",
"//app/service/main/push/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/permit:go_default_library",
"//library/time: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,88 @@
package http
import (
"strconv"
"time"
"go-common/app/admin/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
func appList(c *bm.Context) {
var (
err error
items []*model.App
)
if err = pushSrv.DB.Find(&items).Error; err != nil {
log.Error("appList error(%v)", err)
c.JSON(nil, err)
return
}
c.JSON(items, nil)
}
func addApp(c *bm.Context) {
app := new(model.App)
if err := c.Bind(app); err != nil {
return
}
if !pushSrv.DB.Where("name=?", app.Name).First(&model.App{}).RecordNotFound() {
log.Warn("addApp(%+v) repeat", app)
c.JSON(nil, ecode.PushRecordRepeatErr)
return
}
if err := pushSrv.DB.Create(app).Error; err != nil {
log.Error("addApp(%s) error(%v)", app.Name, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func appInfo(c *bm.Context) {
var (
req = c.Request.Form
info = &model.App{}
)
id, _ := strconv.ParseInt(req.Get("id"), 10, 64)
if id == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.First(info, id).Error; err != nil {
log.Error("appInfo(%d) error(%v)", id, err)
c.JSON(nil, err)
return
}
c.JSON(info, nil)
}
func saveApp(c *bm.Context) {
app := new(model.App)
if err := c.Bind(app); err != nil {
return
}
if err := pushSrv.DB.Model(&model.App{ID: app.ID}).Updates(map[string]interface{}{"name": app.Name, "push_limit_user": app.PushLimitUser}).Error; err != nil {
log.Error("saveApp(%d,%s) error(%v)", app.ID, app.Name, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func delApp(c *bm.Context) {
req := c.Request.Form
id, _ := strconv.ParseInt(req.Get("id"), 10, 64)
if id == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.Model(&model.App{ID: id}).Update("dtime", time.Now().Unix()).Error; err != nil {
log.Error("delApps(%d) error(%v)", id, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}

View File

@@ -0,0 +1,78 @@
package http
import (
"strconv"
"time"
"go-common/app/admin/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
func authList(c *bm.Context) {
var (
req = c.Request.Form
auths []*model.Auth
)
appID, _ := strconv.ParseInt(req.Get("app_id"), 10, 64)
if err := pushSrv.DB.Model(&model.App{ID: appID}).Related(&auths).Error; err != nil {
log.Error("authList error(%v)", err)
c.JSON(nil, err)
return
}
c.JSON(auths, nil)
}
func addAuth(c *bm.Context) {
auth := new(model.Auth)
if err := c.Bind(auth); err != nil {
return
}
if err := pushSrv.DB.Create(auth).Error; err != nil {
log.Error("addAuth(%+v) error(%v)", auth, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func authInfo(c *bm.Context) {
auth := new(model.Auth)
if err := c.Bind(auth); err != nil {
return
}
if err := pushSrv.DB.First(auth, auth.ID).Error; err != nil {
log.Error("authInfo(%d) error(%v)", auth.ID, err)
c.JSON(nil, err)
return
}
c.JSON(auth, nil)
}
func saveAuth(c *bm.Context) {
auth := new(model.Auth)
if err := c.Bind(auth); err != nil {
return
}
if err := pushSrv.DB.Model(&model.Auth{ID: auth.ID}).Update(auth).Error; err != nil {
log.Error("saveAuth(%+v) error(%v)", auth, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func delAuth(c *bm.Context) {
id, _ := strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.Model(&model.Auth{ID: id}).Update("dtime", time.Now().Unix()).Error; err != nil {
log.Error("delAuth(%d) error(%v)", id, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}

View File

@@ -0,0 +1,122 @@
package http
import (
"strconv"
"time"
"go-common/app/admin/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
func businessList(c *bm.Context) {
var (
items []*model.Business
err error
count int
apps []*model.App
appsMap = make(map[int64]*model.App)
)
pager := new(model.Pager)
if err = c.Bind(pager); err != nil {
return
}
if err = pushSrv.DB.Offset((pager.Pn - 1) * pager.Ps).Limit(pager.Ps).Find(&items).Error; err != nil {
log.Error("businessList(%d,%d) error(%v)", pager.Pn, pager.Ps, err)
c.JSON(nil, err)
return
}
if err = pushSrv.DB.Find(&apps).Error; err != nil {
log.Error("businessList(%d,%d) error(%v)", pager.Pn, pager.Ps, err)
c.JSON(nil, err)
return
}
for _, app := range apps {
appsMap[app.ID] = app
}
for _, item := range items {
if appsMap[item.AppID] != nil {
item.AppName = appsMap[item.AppID].Name
}
}
if err = pushSrv.DB.Model(&model.Business{}).Count(&count).Error; err != nil {
log.Error("businessList count error(%v)", err)
c.JSON(nil, err)
return
}
data := map[string]interface{}{
"data": items,
"pager": &model.Pager{
Pn: pager.Pn,
Ps: pager.Ps,
Total: count,
},
}
c.JSONMap(data, nil)
}
func addBusiness(c *bm.Context) {
biz := new(model.Business)
if err := c.Bind(biz); err != nil {
return
}
biz.Token = model.RandomString(32)
if !pushSrv.DB.Where("app_id=? and name=?", biz.AppID, biz.Name).First(&model.Business{}).RecordNotFound() {
log.Warn("addBusiness(%+v) repeat", biz)
c.JSON(nil, ecode.PushRecordRepeatErr)
return
}
if err := pushSrv.DB.Create(biz).Error; err != nil {
log.Error("addBusiness(%+v) error(%v)", biz, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func businessInfo(c *bm.Context) {
var (
req = c.Request.Form
info = &model.Business{}
)
id, _ := strconv.ParseInt(req.Get("id"), 10, 64)
if id == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.First(info, id).Error; err != nil {
log.Error("businessInfo(%d) error(%v)", id, err)
c.JSON(nil, err)
return
}
c.JSON(info, nil)
}
func saveBusiness(c *bm.Context) {
biz := new(model.Business)
if err := c.Bind(biz); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.Omit("token", "mtime", "ctime", "dtime").Save(biz).Error; err != nil {
log.Error("saveBusiness(%+v) error(%v)", biz, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func delBusiness(c *bm.Context) {
id, _ := strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.Model(&model.Business{ID: id}).Update("dtime", time.Now().Unix()).Error; err != nil {
log.Error("delBusiness(%d) error(%v)", id, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}

View File

@@ -0,0 +1,71 @@
package http
import (
"context"
"encoding/json"
"strconv"
"time"
"go-common/app/admin/main/push/model"
pushmdl "go-common/app/service/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
func addDPTask(ctx *bm.Context) {
task := &model.DPTask{}
if err := ctx.Bind(task); err != nil {
return
}
if err := parseDpTask(task); err != nil {
ctx.JSON(nil, ecode.RequestErr)
return
}
ctx.JSON(nil, pushSrv.AddDPTask(context.Background(), task))
}
func parseDpTask(task *model.DPTask) (err error) {
if task.ActivePeriodStr != "" {
if err = json.Unmarshal([]byte(task.ActivePeriodStr), &task.ActivePeriods); err != nil {
log.Error("parse ActivePeriod(%s) error(%v)", task.ActivePeriodStr, err)
return
}
}
if task.VipExpireStr != "" {
if err = json.Unmarshal([]byte(task.VipExpireStr), &task.VipExpires); err != nil {
log.Error("parse VipExpire(%s) error(%v)", task.VipExpireStr, err)
return
}
}
if task.AttentionStr != "" {
if err = json.Unmarshal([]byte(task.AttentionStr), &task.Attentions); err != nil {
log.Error("parse Attention(%s) error(%v)", task.AttentionStr, err)
return
}
}
task.PushTime = time.Unix(task.PushTimeUnix, 0)
task.ExpireTime = time.Unix(task.ExpireTimeUnix, 0)
task.Job = strconv.FormatInt(pushmdl.JobName(time.Now().UnixNano(), task.Summary, task.LinkValue, task.Group), 10)
task.Status = int(pushmdl.TaskStatusPending)
extra, _ := json.Marshal(pushmdl.TaskExtra{Group: task.Group})
task.Extra = string(extra)
return
}
func dpTaskInfo(ctx *bm.Context) {
task := new(model.Task)
if err := ctx.Bind(task); err != nil {
return
}
id, _ := strconv.ParseInt(task.ID, 10, 64)
if id < 1 {
ctx.JSON(nil, ecode.RequestErr)
return
}
ctx.JSON(pushSrv.DpTaskInfo(ctx, id, task.Job))
}
func checkDpData(ctx *bm.Context) {
ctx.JSON(nil, pushSrv.CheckDpData(ctx))
}

View File

@@ -0,0 +1,89 @@
package http
import (
"net/http"
"go-common/app/admin/main/push/conf"
"go-common/app/admin/main/push/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
)
var (
pushSrv *service.Service
authSrv *permit.Permit
)
// Init init http sever instance.
func Init(c *conf.Config, s *service.Service) {
pushSrv = s
authSrv = permit.New(c.Auth)
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)
g := e.Group("/x/admin/push")
{
gapp := g.Group("/apps")
{
gapp.GET("/list", authSrv.Permit("PUSH_APPS_LIST"), appList)
gapp.GET("/info", authSrv.Permit("PUSH_APPS"), appInfo)
gapp.POST("/add", authSrv.Permit("PUSH_APPS"), addApp)
gapp.POST("/save", authSrv.Permit("PUSH_APPS"), saveApp)
gapp.POST("/delete", authSrv.Permit("PUSH_APPS"), delApp)
}
gauth := g.Group("/auths", authSrv.Permit("PUSH_AUTH"))
{
gauth.GET("/list", authList)
gauth.GET("/info", authInfo)
gauth.POST("/add", addAuth)
gauth.POST("/save", saveAuth)
gauth.POST("/delete", delAuth)
}
gbiz := g.Group("/business")
{
gbiz.GET("/list", authSrv.Permit("PUSH_BUSINESS_LIST"), businessList)
gbiz.GET("/info", authSrv.Permit("PUSH_BUSINESS"), businessInfo)
gbiz.POST("/add", authSrv.Permit("PUSH_BUSINESS"), addBusiness)
gbiz.POST("/save", authSrv.Permit("PUSH_BUSINESS"), saveBusiness)
gbiz.POST("/delete", authSrv.Permit("PUSH_BUSINESS"), delBusiness)
}
gtask := g.Group("/tasks")
{
gtask.GET("/list", authSrv.Permit("PUSH_TASK"), taskList)
gtask.GET("/info", authSrv.Permit("PUSH_TASK"), taskInfo)
gtask.POST("/add", authSrv.Permit("PUSH_TASK"), addTask)
gtask.POST("/save", authSrv.Permit("PUSH_TASK"), saveTask)
gtask.POST("/delete", authSrv.Permit("PUSH_TASK"), delTask)
gtask.POST("/upload", authSrv.Permit("PUSH_TASK"), upload)
gtask.POST("/upimg", authSrv.Permit("PUSH_TASK"), upimg)
gtask.POST("/stop", authSrv.Permit("PUSH_TASK"), stopTask)
gtask.POST("/confirm", authSrv.Permit("PUSH_CONFIRM"), confirmTask)
gdp := gtask.Group("dataplatform", authSrv.Permit("PUSH_TASK"))
{
gdp.POST("/add", addDPTask)
gdp.GET("/info", dpTaskInfo)
gdp.GET("/check", checkDpData)
}
}
gtest := g.Group("/test", authSrv.Permit("PUSH_TEST"))
{
gtest.POST("/mid", testPushMid) // 按 mids 测试推送
gtest.POST("/token", testPushToken) // 按单个 token 测试推送
}
}
}
func ping(ctx *bm.Context) {
if err := pushSrv.Ping(ctx); err != nil {
log.Error("push-admin ping error(%v)", err)
ctx.AbortWithStatus(http.StatusServiceUnavailable)
}
}

View File

@@ -0,0 +1,403 @@
package http
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
"go-common/app/admin/main/push/model"
pushmdl "go-common/app/service/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
xtime "go-common/library/time"
)
func taskList(c *bm.Context) {
var (
items []*model.Task
types []int
err error
pager = new(model.Pager)
)
params := new(struct {
Type string `form:"type"`
Stime string `form:"stime"`
Etime string `form:"etime"`
})
if err = c.Bind(params); err != nil {
return
}
if err = c.Bind(pager); err != nil {
return
}
for _, v := range strings.Split(params.Type, ",") {
i, e := strconv.Atoi(v)
if e != nil {
continue
}
types = append(types, i)
}
if params.Stime == "" {
params.Stime = time.Now().Add(-1 * time.Hour).Format("2006-01-02 15:04:05")
}
if params.Etime == "" {
params.Etime = time.Now().Format("2006-01-02 15:04:05")
}
if err = pushSrv.DB.Model(&model.Task{}).Where("push_time between ? and ?", params.Stime, params.Etime).Where("type in(?)", types).Order("push_time desc").Find(&items).Error; err != nil {
log.Error("taskList(%d,%s,%s) error(%v)", params.Type, params.Stime, params.Etime, err)
c.JSON(nil, err)
return
}
res := fmtTasks(items)
total := len(res)
data := map[string]interface{}{
"pager": &model.Pager{
Pn: pager.Pn,
Ps: pager.Ps,
Total: total,
},
"data": []*model.Task{},
}
start := pager.Ps * (pager.Pn - 1)
if start >= total {
c.JSONMap(data, nil)
return
}
end := start + pager.Ps
if len(res[start:]) < pager.Ps {
end = start + len(res[start:])
}
data["data"] = res[start:end]
c.JSONMap(data, nil)
}
func fmtTasks(items []*model.Task) (res []*model.Task) {
var jobs []string
tasks := make(map[string]*pushmdl.Task)
for _, t := range items {
p := &pushmdl.Progress{}
if t.Progress != "" {
if err := json.Unmarshal([]byte(t.Progress), &p); err != nil {
log.Error("unmarshal task(%d) progress(%s) error(%v)", t.ID, t.Progress, err)
continue
}
}
extra := new(pushmdl.TaskExtra)
if t.Extra != "" {
if err := json.Unmarshal([]byte(t.Extra), &extra); err != nil {
log.Error("unmarshal task(%d) extra(%s) error(%v)", t.ID, t.Extra, err)
continue
}
}
if v, ok := tasks[t.Job]; !ok {
job, _ := strconv.ParseInt(t.Job, 10, 64)
tasks[t.Job] = &pushmdl.Task{
ID: t.ID,
Job: job,
Type: t.Type,
APPID: t.AppID,
BusinessID: t.BusinessID,
Title: t.Title,
Summary: t.Summary,
LinkType: int8(t.LinkType),
LinkValue: t.LinkValue,
Progress: p,
PushTime: xtime.Time(t.PushTime.Unix()),
ExpireTime: xtime.Time(t.ExpireTime.Unix()),
Status: int8(t.Status),
Extra: extra,
}
jobs = append(jobs, t.Job)
} else {
v.Status = calStatus(v.Status, int8(t.Status))
vp := v.Progress
vp.MidTotal += p.MidTotal
vp.MidValid += p.MidValid
vp.TokenTotal += p.TokenTotal
vp.TokenValid += p.TokenValid
vp.TokenFailed += p.TokenFailed
}
}
for _, j := range jobs {
v := tasks[j]
p, _ := json.Marshal(v.Progress)
e, _ := json.Marshal(v.Extra)
t := &model.Task{
ID: v.ID,
Job: strconv.FormatInt(v.Job, 10),
Type: v.Type,
AppID: v.APPID,
BusinessID: v.BusinessID,
Title: v.Title,
Summary: v.Summary,
LinkType: int(v.LinkType),
LinkValue: v.LinkValue,
Progress: string(p),
PushTimeUnix: int64(v.PushTime),
ExpireTimeUnix: int64(v.ExpireTime),
Status: int(v.Status),
Extra: string(e),
}
res = append(res, t)
}
return
}
func calStatus(currStatus, newStatus int8) int8 {
// 聚合任务状态
// 如果有进行中的任务,显示进行中
// 如果没有,显示其中最小的状态(负数是异常状态)
if currStatus == pushmdl.TaskStatusDoing || newStatus == pushmdl.TaskStatusDoing {
return pushmdl.TaskStatusDoing
}
if newStatus == pushmdl.TaskStatusPrepared {
return pushmdl.TaskStatusPrepared
}
if newStatus < currStatus {
return newStatus
}
return currStatus
}
func addTask(c *bm.Context) {
var (
task = &model.Task{}
filename = c.Request.Form.Get("filename")
)
if err := c.Bind(task); err != nil {
return
}
task.PushTime = time.Unix(task.PushTimeUnix, 0)
task.ExpireTime = time.Unix(task.ExpireTimeUnix, 0)
job := pushmdl.JobName(time.Now().UnixNano(), task.Summary, task.LinkValue, task.Group)
task.Job = strconv.FormatInt(job, 10)
extra, _ := json.Marshal(pushmdl.TaskExtra{
Group: task.Group,
Filename: filename,
})
task.Extra = string(extra)
task.Status = int(pushmdl.TaskStatusPending)
c.JSON(nil, pushSrv.AddTask(context.Background(), task))
}
func taskInfo(c *bm.Context) {
task := &model.Task{}
id, _ := strconv.ParseInt(c.Request.Form.Get("id"), 10, 64)
if id == 0 {
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.First(task, id).Error; err != nil {
log.Error("taskInfo(%d) error(%v)", id, err)
c.JSON(nil, err)
return
}
task.PushTimeUnix = task.PushTime.Unix()
task.ExpireTimeUnix = task.ExpireTime.Unix()
c.JSON(task, nil)
}
func saveTask(c *bm.Context) {
var (
task = new(model.Task)
filename = c.Request.Form.Get("filename")
)
if err := c.Bind(task); err != nil {
return
}
if task.Job == "" {
log.Warn("job is empty")
c.JSON(nil, ecode.RequestErr)
return
}
extra, _ := json.Marshal(pushmdl.TaskExtra{
Group: task.Group,
Filename: filename,
})
data := map[string]interface{}{
"app_id": task.AppID,
"type": task.Type,
"business_id": task.BusinessID,
"title": task.Title,
"summary": task.Summary,
"link_type": task.LinkType,
"link_value": task.LinkValue,
"build": task.Build,
"sound": task.Sound,
"vibration": task.LinkValue,
"push_time": time.Unix(task.PushTimeUnix, 0),
"expire_time": time.Unix(task.ExpireTimeUnix, 0),
"group": task.Group,
"image_url": task.ImageURL,
"extra": string(extra),
}
if err := pushSrv.DB.Model(&model.Task{}).Where("job=?", task.Job).Updates(data).Error; err != nil {
log.Error("saveTask(%+v) error(%v)", data, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func delTask(c *bm.Context) {
job := c.Request.Form.Get("job")
if job == "" {
log.Error("job is empty")
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.Model(&model.Task{}).Where("job=?", job).Update("dtime", time.Now().Unix()).Error; err != nil {
log.Error("delTask(%s) error(%v)", job, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func stopTask(c *bm.Context) {
job := c.Request.Form.Get("job")
if job == "" {
log.Error("job is empty")
c.JSON(nil, ecode.RequestErr)
return
}
if err := pushSrv.DB.Model(&model.Task{}).Where("job=?", job).Update("status", pushmdl.TaskStatusStop).Error; err != nil {
log.Error("stopTask(%s) error(%v)", job, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func confirmTask(c *bm.Context) {
job := c.Request.Form.Get("job")
if job == "" {
log.Error("job is empty")
c.JSON(nil, ecode.RequestErr)
return
}
task := &model.Task{}
if err := pushSrv.DB.Model(&model.Task{}).Where("job=?", job).First(task).Error; err != nil {
log.Error("confirmTask(%s) query task error(%v)", job, err)
c.JSON(nil, err)
return
}
status := pushmdl.TaskStatusPretreatmentPrepared
if task.Type == pushmdl.TaskTypeDataPlatformMid || task.Type == pushmdl.TaskTypeDataPlatformToken {
status = pushmdl.TaskStatusWaitDataPlatform
if err := pushSrv.UpdateDpCondtionStatus(c, job, pushmdl.DpCondStatusPrepared); err != nil {
log.Error("confirmTask(%s) update data platform conditions error(%v)", job, err)
return
}
}
if err := pushSrv.DB.Model(&model.Task{}).Where("job=?", job).Update("status", status).Error; err != nil {
log.Error("confirmTask(%s) error(%v)", job, err)
c.JSON(nil, err)
return
}
c.JSON(nil, nil)
}
func testPushMid(c *bm.Context) {
task := &model.Task{}
if err := c.Bind(task); err != nil {
return
}
builds := make(map[int]*pushmdl.Build)
if task.Build != "" {
if err := json.Unmarshal([]byte(task.Build), &builds); err != nil {
c.JSON(nil, ecode.RequestErr)
log.Warn("buildformat task(%+v) error(%v)", task, err)
return
}
}
var (
plats []string
mid = c.Request.Form.Get("mid")
)
for plat := range builds {
plats = append(plats, strconv.Itoa(plat))
}
task.Platform = strings.Join(plats, ",")
task.PushTime = time.Unix(task.PushTimeUnix, 0)
task.ExpireTime = time.Unix(task.ExpireTimeUnix, 0)
job := pushmdl.JobName(time.Now().UnixNano(), task.Summary, task.LinkValue, task.Group)
task.Job = strconv.FormatInt(job, 10)
extra, _ := json.Marshal(pushmdl.TaskExtra{Group: task.Group})
task.Extra = string(extra)
c.JSON(nil, pushSrv.TestPushMid(c, mid, task))
}
func testPushToken(c *bm.Context) {
params := c.Request.Form
appID, _ := strconv.ParseInt(params.Get("app_id"), 10, 64)
if appID < 1 {
c.JSON(nil, ecode.RequestErr)
log.Error("app_id is wrong: %s", params.Get("app_id"))
return
}
platform, _ := strconv.Atoi(params.Get("platform"))
if platform < 1 {
c.JSON(nil, ecode.RequestErr)
log.Error("platform is wrong: %s", params.Get("platform"))
return
}
alertTitle := params.Get("alert_title")
if alertTitle == "" {
alertTitle = pushmdl.DefaultMessageTitle
}
alertBody := params.Get("alert_body")
if alertBody == "" {
c.JSON(nil, ecode.RequestErr)
log.Error("alert_body is empty")
return
}
token := params.Get("token")
if token == "" {
c.JSON(nil, ecode.RequestErr)
log.Error("token is empty")
return
}
linkType, _ := strconv.Atoi(params.Get("link_type"))
if linkType < 1 {
c.JSON(nil, ecode.RequestErr)
log.Error("link_type is wrong: %s", params.Get("link_type"))
return
}
linkValue := params.Get("link_value")
expireTime, _ := strconv.ParseInt(params.Get("expire_time"), 10, 64)
if expireTime == 0 {
expireTime = time.Now().Add(7 * 24 * time.Hour).Unix()
}
sound, vibration := pushmdl.SwitchOn, pushmdl.SwitchOn
if params.Get("sound") != "" {
if sd, _ := strconv.Atoi(params.Get("sound")); sd == pushmdl.SwitchOff {
sound = pushmdl.SwitchOff
}
}
if params.Get("vibration") != "" {
if vr, _ := strconv.Atoi(params.Get("vibration")); vr == pushmdl.SwitchOff {
vibration = pushmdl.SwitchOff
}
}
passThrough, _ := strconv.Atoi(params.Get("pass_through"))
if passThrough != pushmdl.SwitchOn {
passThrough = pushmdl.SwitchOff
}
info := &pushmdl.PushInfo{
TaskID: pushmdl.TempTaskID(),
APPID: appID,
Title: alertTitle,
Summary: alertBody,
LinkType: int8(linkType),
LinkValue: linkValue,
ExpireTime: xtime.Time(expireTime),
PassThrough: passThrough,
Sound: sound,
Vibration: vibration,
}
c.JSON(nil, pushSrv.TestPushToken(c, info, platform, token))
}

View File

@@ -0,0 +1,89 @@
package http
import (
"crypto/md5"
"fmt"
"io/ioutil"
"strconv"
"strings"
"time"
"go-common/app/admin/main/push/conf"
"go-common/app/admin/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
func upload(c *bm.Context) {
var (
err error
req = c.Request
)
req.ParseMultipartForm(1024 * 1024 * 1024) // 1G
fileName := req.FormValue("filename")
if fileName == "" {
log.Error("filename is empty")
c.JSON(nil, ecode.RequestErr)
return
}
file, header, err := req.FormFile("file")
if err != nil {
log.Error("req.FormFile() error(%v)", err)
c.JSON(nil, err)
return
}
defer file.Close()
bs, err := ioutil.ReadAll(file)
if err != nil {
log.Error("ioutil.ReadAll() error(%v)", err)
c.JSON(nil, err)
return
}
typ, _ := strconv.Atoi(req.FormValue("type"))
if typ == model.UploadTypeMid {
if err = pushSrv.CheckUploadMid(c, bs); err != nil {
c.JSON(nil, err)
return
}
} else if typ == model.UploadTypeToken {
if err = pushSrv.CheckUploadToken(c, bs); err != nil {
c.JSON(nil, err)
return
}
} else {
log.Error("type(%d) invalid", typ)
c.JSON(nil, ecode.RequestErr)
return
}
dir := fmt.Sprintf("%s/%s", strings.TrimSuffix(conf.Conf.Cfg.MountDir, "/"), time.Now().Format("20060102"))
path := fmt.Sprintf("%s/%x", dir, md5.Sum([]byte(fileName)))
if err = pushSrv.Upload(c, dir, path, bs); err != nil {
log.Error("upload file file(%s) error(%v)", path, err)
c.JSON(nil, err)
return
}
c.JSON(struct {
Name string `json:"name"`
Path string `json:"path"`
}{
Name: header.Filename,
Path: path,
}, nil)
}
func upimg(ctx *bm.Context) {
f, h, err := ctx.Request.FormFile("file")
if err != nil {
log.Error("upimg error(%v)", err)
ctx.JSON(nil, err)
return
}
defer f.Close()
url, err := pushSrv.Upimg(ctx, f, h)
if err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(map[string]string{"url": url}, nil)
}

View File

@@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["model_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"dataplatform.go",
"model.go",
],
importpath = "go-common/app/admin/main/push/model",
tags = ["automanaged"],
)
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,74 @@
package model
// DPTask data platform task
type DPTask struct {
Task
DPParams
}
// DPParams data platform params
type DPParams struct {
Age int `form:"age" json:"age"`
Sex int `form:"sex" json:"sex"`
IsUp int `form:"is_up" json:"is_up"`
IsFormalMember int `form:"is_formal_member" json:"is_formal_member"`
UserActiveDay int `form:"user_active_day" json:"user_active_day"`
UserNewDay int `form:"user_new_day" json:"user_new_day"`
UserSilentDay int `form:"user_silent_day" json:"user_silent_day"`
Area []int `form:"area,split" json:"-"`
AreaStr string `json:"area"`
Level []int `form:"level,split" json:"-"`
LevelStr string `json:"level"`
Platforms []int `form:"platforms,split" json:"-"`
PlatformStr string `json:"platforms"`
Like []int `form:"like,split" json:"-"`
LikeStr string `json:"like"`
Channel []string `form:"channel,split" json:"-"`
ChannelStr string `json:"channel"`
VipExpireStr string `form:"vip_expire" json:"-"`
VipExpires []*VipExpire `json:"vip_expire"`
AttentionStr string `form:"self_attention" json:"-"`
Attentions []*SelfAttention `json:"self_attention"`
AttentionsType int `form:"self_attention_type" json:"self_attention_type"`
ActivePeriodStr string `form:"active" json:"-"`
ActivePeriods []*ActivePeriod `json:"active"`
ActivePeriod int
}
// SelfAttention 自选关注
type SelfAttention struct {
Type int `json:"type"`
Include string `json:"include"`
Exclude string `json:"exclude"`
}
// VipExpire 大会员过期时间
type VipExpire struct {
Begin string `json:"begin"`
End string `json:"end"`
}
// ActivePeriod 活跃时间段
type ActivePeriod struct {
Period int `json:"period"`
PushTime string `json:"push_time"`
ExpireTime string `json:"expire_time"`
}
// DPCondition data platform condition
type DPCondition struct {
ID int64
Task int64
Job string
Type int
Condition string
SQL string
Status int
StatusURL string
File string
}
// TableName .
func (c *DPCondition) TableName() string {
return "push_dataplatform_conditions"
}

View File

@@ -0,0 +1,121 @@
package model
import (
"math/rand"
"time"
)
const (
// UploadTypeMid 上传文件内容为 mid
UploadTypeMid = 1
// UploadTypeToken 上传文件内容为 token
UploadTypeToken = 2
)
// Page .
type Page struct {
Num int `json:"num"`
Size int `json:"size"`
Total int `json:"total"`
}
// Pager def.
type Pager struct {
Total int `json:"total"`
Pn int `json:"page" form:"pn" validate:"min=1" default:"1"`
Ps int `json:"pagesize" form:"ps" validate:"min=1" default:"20"`
}
// App .
type App struct {
ID int64 `json:"id" form:"id"`
Name string `json:"name" form:"name" validate:"required"`
PushLimitUser int `json:"push_limit_user" form:"push_limit_user"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
Dtime int64 `json:"dtime"`
Business []Business `json:"-"`
Auths []Auth `json:"-"`
}
// Auth .
type Auth struct {
ID int64 `json:"id" form:"id"`
AppID int64 `json:"app_id" form:"app_id"`
PlatformID int `json:"platform_id" form:"platform_id"`
Name string `json:"name" form:"name"`
Key string `json:"key" form:"key"`
Value string `json:"value" form:"value"`
BundleID string `json:"bundle_id" form:"bundle_id"`
Mtime time.Time `json:"mtime"`
Ctime time.Time `json:"ctime"`
Dtime int `json:"dtime"`
}
// Business .
type Business struct {
ID int64 `json:"id" form:"id"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
Dtime int `json:"dtime"`
AppID int64 `json:"app_id" form:"app_id"`
Name string `json:"name" form:"name"`
Desc string `json:"desc" gorm:"column:description" form:"desc"`
Token string `json:"token"`
Sound int `json:"sound" form:"sound"`
Vibration int `json:"vibration" form:"vibration"`
ReceiveSwitch int `json:"receive_switch" form:"receive_switch"`
PushSwitch int `json:"push_switch" form:"push_switch"`
AppName string `json:"app_name" gorm:"-"`
SilentTime string `json:"silent_time" form:"silent_time"`
PushLimitUser int `json:"push_limit_user" form:"push_limit_user"`
Whitelist int `json:"whitelist" form:"whitelist"`
}
// TableName .
func (b Business) TableName() string {
return "push_business"
}
// Task .
type Task struct {
ID string `json:"id" form:"id"`
Job string `json:"job" form:"job"`
Type int `json:"type" form:"type"`
AppID int64 `json:"app_id" form:"app_id"`
PlatformID int `json:"platform_id"`
BusinessID int64 `json:"business_id" form:"business_id"`
Platform string `json:"platform"`
Title string `json:"title" form:"title"`
Summary string `json:"summary" form:"summary"`
LinkType int `json:"link_type" form:"link_type"`
LinkValue string `json:"link_value" form:"link_value"`
Build string `json:"build" form:"build"`
Sound int `json:"sound" form:"sound"`
Vibration int `json:"vibration" form:"vibration"`
MidFile string `json:"mid_file" form:"mid_file"`
Progress string `json:"progress"`
PushTime time.Time `json:"-"`
ExpireTime time.Time `json:"-"`
PassThrough int `json:"pass_through" form:"pass_through"`
PushTimeUnix int64 `json:"push_time" form:"push_time" gorm:"-"`
ExpireTimeUnix int64 `json:"expire_time" form:"expire_time" gorm:"-"`
Status int `json:"status"`
ImageURL string `json:"image_url" form:"image_url"`
Group string `json:"group" form:"group"`
Extra string `json:"extra"`
Mtime time.Time `json:"mtime"`
Ctime time.Time `json:"ctime"`
Dtime int `json:"dtime"`
}
// RandomString gets random string by length.
func RandomString(l int) string {
bs := []byte("0123456789abcdefghijklmnopqrstuvwxyz")
r := rand.New(rand.NewSource(time.Now().UnixNano()))
var res []byte
for i := 0; i < l; i++ {
res = append(res, bs[r.Intn(len(bs))])
}
return string(res)
}

View File

@@ -0,0 +1,8 @@
package model
import "testing"
func Test_Funcs(t *testing.T) {
s := RandomString(32)
t.Logf("random string: %s", s)
}

View File

@@ -0,0 +1,58 @@
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/admin/main/push/conf:go_default_library",
"//app/admin/main/push/model: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 = [
"dataplatform.go",
"service.go",
"task.go",
"upload.go",
],
importpath = "go-common/app/admin/main/push/service",
tags = ["automanaged"],
deps = [
"//app/admin/main/push/conf:go_default_library",
"//app/admin/main/push/dao:go_default_library",
"//app/admin/main/push/model:go_default_library",
"//app/service/main/push/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/sync/errgroup:go_default_library",
"//vendor/github.com/jinzhu/gorm: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,444 @@
package service
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"go-common/app/admin/main/push/model"
pushmdl "go-common/app/service/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
const (
_countryForeign = 1
_countryChina = 2
_sexMale = 1
_sexFemale = 2
_platformAll = "4" // 全部平台,iOS、小米、华为...
_attentionTypeUnion = 1 // 多个自选关注条件用union查询 (另一种是join查询)
_timeLayout = "2006-01-02 15:04:05"
_retry = 3
// 检查数据平台当天的计算任务有没有成功
checkDpDataURL = "http://berserker.bilibili.co/api/archer/project/%d/status"
)
var (
areas = map[int]string{1: "海外", 2: "中国", 3: "青海", 4: "上海", 5: "安徽", 6: "广西", 7: "贵州", 8: "吉林", 9: "福建", 10: "黑龙江",
11: "江西", 12: "甘肃", 13: "云南", 14: "湖南", 15: "河北", 16: "山东", 17: "湖北", 18: "广东", 19: "宁夏", 20: "重庆",
21: "辽宁", 22: "内蒙古", 23: "山西", 24: "澳门", 25: "陕西", 26: "江苏", 27: "四川", 28: "浙江", 29: "海南", 30: "河南",
31: "北京", 32: "香港", 33: "台湾", 34: "天津", 35: "西藏", 36: "新疆", 37: "其他",
}
dpProjects = map[int]string{
355170: "mid维度数据",
357214: "buvid维度数据",
355628: "自选关注数据",
}
)
// DpTaskInfo .
func (s *Service) DpTaskInfo(ctx context.Context, id int64, job string) (res *model.DPTask, err error) {
var (
t *model.Task
cond *model.DPCondition
group = errgroup.Group{}
)
group.Go(func() error {
t, err = s.dao.TaskInfo(ctx, id)
return err
})
group.Go(func() error {
cond, err = s.dao.DPCondition(ctx, job)
return err
})
if err = group.Wait(); err != nil {
return
}
if t == nil {
return
}
res = new(model.DPTask)
res.Task = *t
p := new(model.DPParams)
if cond != nil {
if err = json.Unmarshal([]byte(cond.Condition), &p); err != nil {
return
}
}
if len(p.VipExpires) == 0 {
p.VipExpires = make([]*model.VipExpire, 0)
}
if len(p.Attentions) == 0 {
p.Attentions = make([]*model.SelfAttention, 0)
}
if len(p.ActivePeriods) == 0 {
p.ActivePeriods = make([]*model.ActivePeriod, 0)
}
res.DPParams = *p
return
}
// AddDPTask add data platform task
func (s *Service) AddDPTask(ctx context.Context, task *model.DPTask) (err error) {
var (
tasks []model.DPTask
condTyp = task.Type
)
if len(task.DPParams.ActivePeriods) == 0 {
tasks = append(tasks, *task)
} else {
// 多个推送需要添加多条任务
for _, p := range task.DPParams.ActivePeriods {
var (
ptime time.Time
etime time.Time
t = *task
)
if ptime, err = time.ParseInLocation(_timeLayout, p.PushTime, time.Local); err != nil {
return
}
if etime, err = time.ParseInLocation(_timeLayout, p.ExpireTime, time.Local); err != nil {
return
}
t.Job = strconv.FormatInt(pushmdl.JobName(ptime.Unix(), t.Summary, t.LinkValue, t.Group), 10)
t.PushTime = ptime
t.ExpireTime = etime
t.ActivePeriod = p.Period
tasks = append(tasks, t)
}
}
for _, t := range tasks {
go func(t model.DPTask) {
var (
id int64
e error
)
for i := 0; i < _retry; i++ {
if id, e = s.dao.AddTask(context.Background(), &t.Task); err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
if e != nil {
log.Error("s.AddDPTask(%+v) add task error(%v)", t.Task, e)
return
}
t.DPParams.LevelStr = pushmdl.JoinInts(t.DPParams.Level)
t.DPParams.AreaStr = pushmdl.JoinInts(t.DPParams.Area)
t.DPParams.PlatformStr = pushmdl.JoinInts(t.DPParams.Platforms)
t.DPParams.LikeStr = pushmdl.JoinInts(t.DPParams.Like)
t.DPParams.ChannelStr = strings.Join(t.DPParams.Channel, ",")
params, _ := json.Marshal(t.DPParams)
cond := &model.DPCondition{
Task: id,
Job: t.Job,
Type: condTyp,
Condition: string(params),
SQL: s.parseQuery(task.Type, &t.DPParams),
Status: pushmdl.DpCondStatusPending,
}
for i := 0; i < _retry; i++ {
if _, e = s.dao.AddDPCondition(context.Background(), cond); e == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
if e != nil {
log.Error("s.AddDPTask(%+v) add condition error(%v)", cond, e)
}
}(t)
}
return
}
// 把查询结构体解析成sql
func (s *Service) parseQuery(typ int, p *model.DPParams) (sql string) {
log.Info("data platform parse query start(%+v)", p)
if typ != pushmdl.TaskTypeDataPlatformToken && typ != pushmdl.TaskTypeDataPlatformMid {
log.Error("data platform parse query task type(%d) error", typ)
return
}
logDate := time.Now().Add(-24 * time.Hour).Format("20060102")
switch typ {
case pushmdl.TaskTypeDataPlatformToken:
sql = fmt.Sprintf("select platform_id,device_token from basic.dws_push_buvid where log_date='%s' ", logDate)
if p.UserActiveDay > 0 {
sql += fmt.Sprintf(" and is_visit_%d=1 ", p.UserActiveDay)
}
if p.UserNewDay > 0 {
sql += fmt.Sprintf(" and is_new_%d=1 ", p.UserNewDay)
}
if p.UserSilentDay > 0 {
sql += fmt.Sprintf(" and is_visit_%d=0 ", p.UserSilentDay)
}
case pushmdl.TaskTypeDataPlatformMid:
if len(p.Attentions) > 0 {
sql = fmt.Sprintf("select t1.platform_id,t1.device_token from (select mid,platform_id,device_token from basic.dws_push_mid where log_date='%s' ", logDate)
} else {
sql = fmt.Sprintf("select platform_id,device_token from basic.dws_push_mid where log_date='%s' ", logDate)
}
if p.Sex == _sexMale || p.Sex == _sexFemale {
sql += fmt.Sprintf(" and final_sex=%d ", p.Sex)
}
// 年龄段。0:0-17, 1:18-24, 2:25-30, 3:31+, 9:全部
if p.Age >= 0 && p.Age <= 3 {
sql += fmt.Sprintf(" and final_age_range=%d ", p.Age)
}
if len(p.Level) > 0 {
sql += fmt.Sprintf(" and level in (%s) ", pushmdl.JoinInts(p.Level))
}
// up主 0: 不是 1:是 2:全部
if p.IsUp == 0 || p.IsUp == 1 {
sql += fmt.Sprintf(" and is_up=%d ", p.IsUp)
}
// 正式会员 0:不是 1:是 2全部
if p.IsFormalMember == 0 || p.IsFormalMember == 1 {
sql += fmt.Sprintf(" and is_formal_member=%d ", p.IsFormalMember)
}
if len(p.VipExpires) > 0 {
var vips []string
for _, v := range p.VipExpires {
if v.Begin == "" && v.End == "" {
continue
}
if v.Begin == "" {
vips = append(vips, fmt.Sprintf(" vip_overdue_time <= '%s'", v.End))
continue
}
if v.End == "" {
vips = append(vips, fmt.Sprintf(" vip_overdue_time >= '%s'", v.Begin))
continue
}
vips = append(vips, fmt.Sprintf(" vip_overdue_time between '%s' and '%s' ", v.Begin, v.End))
}
if len(vips) > 0 {
sql += fmt.Sprintf(" and (%s) ", strings.Join(vips, "or"))
}
}
}
if p.PlatformStr != "" && p.PlatformStr != _platformAll {
for _, v := range p.Platforms {
if v == pushmdl.PlatformAndroid {
for _, i := range pushmdl.Platforms {
if i == pushmdl.PlatformIPhone || i == pushmdl.PlatformIPad {
continue
}
p.Platforms = append(p.Platforms, i)
}
break
}
}
sql += fmt.Sprintf(" and platform_id in (%s) ", pushmdl.JoinInts(p.Platforms))
}
if len(p.Channel) > 0 {
var brands []string
for _, v := range p.Channel {
if v == "" {
continue
}
brands = append(brands, "'"+v+"'")
}
if len(brands) > 0 {
sql += fmt.Sprintf(" and device_brand in (%s) ", strings.Join(brands, ","))
}
}
if len(p.Area) > 0 {
var countries, provinces []string
for _, v := range p.Area {
if areas[v] == "" {
continue
}
if v == _countryForeign || v == _countryChina {
countries = append(countries, "'"+areas[v]+"'")
} else {
provinces = append(provinces, "'"+areas[v]+"'")
}
}
if len(countries) > 0 {
sql += fmt.Sprintf(" and country in(%s) ", strings.Join(countries, ","))
}
if len(provinces) > 0 {
sql += fmt.Sprintf(" and province in(%s) ", strings.Join(provinces, ","))
}
}
if len(p.Like) > 0 {
var likes []string
for _, id := range p.Like {
if s.partitions[id] == "" {
continue
}
likes = append(likes, "'"+s.partitions[id]+"'")
}
if len(likes) > 0 {
sql += fmt.Sprintf(" and like_tid in(%s) ", strings.Join(likes, ","))
}
}
if p.ActivePeriod > 0 {
sql += fmt.Sprintf(" and active_hour_period=%d ", p.ActivePeriod)
}
if typ == pushmdl.TaskTypeDataPlatformMid && len(p.Attentions) > 0 {
var attentions []string
if p.AttentionsType == _attentionTypeUnion {
// 并集
sql += ") t1 join ("
for _, v := range p.Attentions {
s := fmt.Sprintf("select mid from basic.dwd_oid_mid_info where log_date='%s' and follow_type=%d ", logDate, v.Type)
if v.Include != "" {
s += fmt.Sprintf(" and name='%s' ", strings.Replace(v.Include, "'", "\\'", -1))
}
if v.Exclude != "" {
s += fmt.Sprintf("and name!='%s'", strings.Replace(v.Exclude, "'", "\\'", -1))
}
attentions = append(attentions, s)
}
sql += strings.Join(attentions, " union ") + ") t2 on t1.mid=t2.mid"
} else {
// 交集
sql += ") t1 join "
for i, v := range p.Attentions {
s := fmt.Sprintf("(select mid from basic.dwd_oid_mid_info where log_date='%s' and follow_type=%d ", logDate, v.Type)
if v.Include != "" {
s += fmt.Sprintf(" and name='%s' ", strings.Replace(v.Include, "'", "\\'", -1))
}
if v.Exclude != "" {
s += fmt.Sprintf("and name!='%s'", strings.Replace(v.Exclude, "'", "\\'", -1))
}
s += fmt.Sprintf(") t%d on t1.mid=t%d.mid ", i+5, i+5)
attentions = append(attentions, s)
}
sql += strings.Join(attentions, " join ")
}
}
log.Info("data platform parse query end(%s)", sql)
return
}
// UpdateDpCondtionStatus .
func (s *Service) UpdateDpCondtionStatus(ctx context.Context, job string, status int) (err error) {
s.dao.UpdateDpCondtionStatus(ctx, job, status)
return
}
type checkDpDataRes struct {
Code int `json:"code"`
Msg string `json:"msg"`
ProjectID int `json:"projectId"`
ProjectName string `json:"projectName"`
}
// CheckDpData check whether data platform data is ready
func (s *Service) CheckDpData(ctx context.Context) (err error) {
group := errgroup.Group{}
for id := range dpProjects {
id := id
now := time.Now()
bts := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix() * 1000 // 13位时间戳
ets := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local).Unix() * 1000
params := dpparams(s.c.DPClient.Key)
params.Set("runStartTime", strconv.FormatInt(bts, 10))
params.Set("runEndTime", strconv.FormatInt(ets, 10))
group.Go(func() (err error) {
u := fmt.Sprintf(checkDpDataURL, id)
if enc := dpsign(s.c.DPClient.Secret, params); enc != "" {
u = u + "?" + enc
}
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
log.Error("CheckDpData url(%s) error(%v)", u, err)
return
}
res := new(checkDpDataRes)
if err = s.dpClient.Do(ctx, req, res); err != nil {
log.Error("CheckDpData url(%s) error(%v)", u+"?"+params.Encode(), err)
return
}
if res.Code != http.StatusOK {
err = ecode.PushAdminDPNoDataErr
}
log.Info("check data platform url(%s) param(%s) res(%+v)", u, params.Encode(), res)
return
})
}
err = group.Wait()
return
}
// dpsign calc appkey and appsecret sign.
func dpsign(secret string, params url.Values) (query string) {
tmp := params.Encode()
signTmp := dpencode(params)
if strings.IndexByte(tmp, '+') > -1 {
tmp = strings.Replace(tmp, "+", "%20", -1)
}
var b bytes.Buffer
b.WriteString(secret)
b.WriteString(signTmp)
b.WriteString(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
}
func dpparams(appkey string) url.Values {
params := url.Values{}
params.Set("appKey", appkey)
params.Set("timestamp", time.Now().Format("2006-01-02 15:04:05"))
params.Set("version", "1.0")
params.Set("signMethod", "md5")
return params
}
var dpSignParams = []string{"appKey", "timestamp", "version"}
// encode data platform encodes the values into ``URL encoded'' form
// ("bar=baz&foo=quux") sorted by key.
func dpencode(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,107 @@
package service
import (
"context"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
"go-common/app/admin/main/push/conf"
"go-common/app/admin/main/push/dao"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"github.com/jinzhu/gorm"
)
// Service biz service def.
type Service struct {
c *conf.Config
dao *dao.Dao
DB *gorm.DB
httpClient *bm.Client
dpClient *bm.Client
partitions map[int]string
}
// New new a Service and return.
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
httpClient: bm.NewClient(c.HTTPClient),
dpClient: bm.NewClient(c.DPClient),
partitions: make(map[int]string),
}
s.DB = s.dao.DB.Scopes(func(db *gorm.DB) *gorm.DB {
return db.Where("dtime = ?", 0)
})
s.loadPartitions()
go s.loadPartitionsproc()
go s.cleanDiskFilesproc()
return s
}
func (s *Service) loadPartitions() (err error) {
m, err := s.dao.Partitions(context.Background())
if err != nil {
return
}
if len(m) > 0 {
s.partitions = m
}
return
}
func (s *Service) loadPartitionsproc() {
for {
if err := s.loadPartitions(); err != nil {
time.Sleep(time.Second)
continue
}
time.Sleep(time.Minute)
}
}
func (s *Service) cleanDiskFilesproc() {
for {
fs, err := ioutil.ReadDir(conf.Conf.Cfg.MountDir)
if err != nil {
log.Error("s.cleanFilesproc() read dir error(%v)", err)
time.Sleep(time.Minute)
continue
}
divDate := time.Now().Add(-time.Duration(24*conf.Conf.Cfg.DiskFileExpireDay) * time.Hour).Format("20060102")
div, _ := strconv.ParseInt(divDate, 10, 64)
for _, f := range fs {
if !f.IsDir() {
continue
}
d, _ := strconv.ParseInt(f.Name(), 10, 64)
if d < div {
dir := fmt.Sprintf("%s/%s", strings.TrimSuffix(conf.Conf.Cfg.MountDir, "/"), f.Name())
if err = os.RemoveAll(dir); err != nil {
log.Error("s.cleanFilesproc() remove dir(%s) error(%v)", dir, err)
time.Sleep(time.Minute)
}
}
}
time.Sleep(time.Hour)
}
}
// Ping check dao health.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Wait wait all closed.
func (s *Service) Wait() {}
// Close close all dao.
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,132 @@
package service
import (
"context"
"encoding/json"
"flag"
"fmt"
"path/filepath"
"strings"
"testing"
"time"
"go-common/app/admin/main/push/conf"
"go-common/app/admin/main/push/model"
pushmdl "go-common/app/service/main/push/model"
. "github.com/smartystreets/goconvey/convey"
)
var svr *Service
func init() {
dir, _ := filepath.Abs("../cmd/push-admin-test.toml")
flag.Set("conf", dir)
conf.Init()
svr = New(conf.Conf)
time.Sleep(time.Second)
}
func WithService(f func(s *Service)) func() {
return func() {
Reset(func() {})
f(svr)
}
}
func Test_Service(t *testing.T) {
Convey("service test", t, WithService(func(s *Service) {
s.Wait()
s.Close()
}))
}
func Test_AddTaskAll(t *testing.T) {
Convey("get last report id", t, WithService(func(s *Service) {
var maxID int64
err := s.dao.DB.Raw("SELECT MAX(id) FROM push_reports").Row().Scan(&maxID)
So(err, ShouldBeNil)
t.Logf("maxid(%d)", maxID)
}))
Convey("get tokens", t, WithService(func(s *Service) {
sqlStr := fmt.Sprintf("SELECT platform_id,device_token FROM push_reports WHERE id>%d and id<=%d and app_id=%d and dtime=0 and notify_switch=1", 1, 100, 1)
rows, err := s.dao.DB.Raw(sqlStr).Rows()
So(err, ShouldBeNil)
var tokens []string
for rows.Next() {
var (
platformID int
token string
)
err = rows.Scan(&platformID, &token)
So(err, ShouldBeNil)
tokens = append(tokens, fmt.Sprintf("%d\t%s", platformID, token))
}
t.Logf("tokens(%d)", len(tokens))
if len(tokens) > 0 {
t.Logf("token one (%s)", tokens[0])
}
}))
}
func Test_CheckUploadMid(t *testing.T) {
Convey("CheckUploadMid", t, WithService(func(s *Service) {
data := []byte("1\n2\n3")
err := s.CheckUploadMid(context.TODO(), data)
So(err, ShouldBeNil)
data = []byte("1\nabc\n3")
err = s.CheckUploadMid(context.TODO(), data)
So(err, ShouldNotBeNil)
t.Logf("check mid error(%v)", err)
}))
}
func Test_CheckUploadToken(t *testing.T) {
Convey("CheckUploadToken", t, WithService(func(s *Service) {
data := []byte("2 fdsahjfkdshaj\n3 hjkhjhjkhj")
err := s.CheckUploadToken(context.TODO(), data)
So(err, ShouldBeNil)
data = []byte("2 fdsahjfkdshaj\n3 hjkhjhjkhj\n4\n")
err = s.CheckUploadToken(context.TODO(), data)
So(err, ShouldNotBeNil)
t.Logf("check token error(%v)", err)
}))
}
func Test_parseQuery(t *testing.T) {
Convey("parse query", t, WithService(func(s *Service) {
str := `{"age":1,"sex":1,"is_up":0,"is_formal_member":1,"user_active_day":0,"user_new_day":0,"user_silentDay":0,"area":"2","level":"2,3,4","platforms":"1,2,3","like":"1,2,3","channel":"huawei,xiaomi","vip_expire":[{"begin":"2018-07-01 00:00:00","end":"2018-07-27 00:00:00"}],"self_attention":null,"self_attention_type":0,"active":null,"ActivePeriod":0}`
p := new(model.DPParams)
err := json.Unmarshal([]byte(str), p)
So(err, ShouldBeNil)
p.Area = pushmdl.SplitInts(p.AreaStr)
p.Level = pushmdl.SplitInts(p.LevelStr)
p.Platforms = pushmdl.SplitInts(p.PlatformStr)
p.Like = pushmdl.SplitInts(p.LikeStr)
p.Channel = strings.Split(p.ChannelStr, ",")
if p.VipExpireStr != "" {
err = json.Unmarshal([]byte(p.VipExpireStr), &p.VipExpires)
So(err, ShouldBeNil)
}
if p.AttentionStr != "" {
err = json.Unmarshal([]byte(p.AttentionStr), &p.Attentions)
So(err, ShouldBeNil)
}
if p.ActivePeriodStr != "" {
err = json.Unmarshal([]byte(p.ActivePeriodStr), &p.ActivePeriods)
So(err, ShouldBeNil)
}
sql := s.parseQuery(pushmdl.TaskTypeDataPlatformMid, p)
t.Logf("sql(%s)", sql)
}))
}
func TestCheckDpData(t *testing.T) {
Convey("test check data platform data", t, WithService(func(s *Service) {
err := s.CheckDpData(context.Background())
So(err, ShouldBeNil)
}))
}

View File

@@ -0,0 +1,154 @@
package service
import (
"context"
"fmt"
"net/url"
"strconv"
"strings"
"go-common/app/admin/main/push/model"
pushmdl "go-common/app/service/main/push/model"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_testPushURL = "http://api.bilibili.co/x/internal/push-service/push"
_testTokenURL = "http://api.bilibili.co/x/internal/push-service/test/token"
)
// AddTask add task.
func (s *Service) AddTask(c context.Context, task *model.Task) (err error) {
if err = s.DB.Create(task).Error; err != nil {
s.dao.SendWechat("推送后台新建任务失败原因写入DB失败")
log.Error("s.AddTask(%+v) error(%v)", task, err)
}
return
}
// TestPushMid test push by mid.
func (s *Service) TestPushMid(c context.Context, mid string, task *model.Task) (err error) {
params := url.Values{}
params.Set("app_id", fmt.Sprintf("%d", task.AppID))
params.Set("platform", task.Platform)
params.Set("alert_title", task.Title)
params.Set("alert_body", task.Summary)
params.Set("link_type", strconv.Itoa(task.LinkType))
params.Set("link_value", task.LinkValue)
params.Set("expire_time", fmt.Sprintf("%d", task.ExpireTimeUnix))
params.Set("builds", task.Build)
params.Set("sound", strconv.Itoa(task.Sound))
params.Set("vibration", strconv.Itoa(task.Vibration))
params.Set("mid", mid)
params.Set("image_url", task.ImageURL)
var res struct {
Code int `json:"code"`
Message string `json:"message"`
}
if err = s.httpClient.Post(c, _testPushURL, "", params, &res); err != nil {
log.Error("s.TestPush(%s) httpClient.Get(%s,%v) error(%v)", mid, _testPushURL, params, err)
return
}
if res.Code != ecode.OK.Code() {
log.Error("测试推送失败 mid(%s) code(%d)", mid, res.Code)
err = ecode.Int(res.Code)
}
return
}
// TestPushToken test push by token.
func (s *Service) TestPushToken(c context.Context, info *pushmdl.PushInfo, platform int, token string) (err error) {
params := url.Values{}
params.Set("app_id", fmt.Sprintf("%d", info.APPID))
params.Set("platform", strconv.Itoa(platform))
params.Set("alert_title", info.Title)
params.Set("alert_body", info.Summary)
params.Set("link_type", fmt.Sprintf("%d", info.LinkType))
params.Set("link_value", info.LinkValue)
params.Set("expire_time", fmt.Sprintf("%d", int64(info.ExpireTime)))
params.Set("sound", strconv.Itoa(info.Sound))
params.Set("vibration", strconv.Itoa(info.Vibration))
params.Set("pass_through", strconv.Itoa(info.PassThrough))
params.Set("token", token)
var res struct {
Code int `json:"code"`
Message string `json:"message"`
}
if err = s.httpClient.Post(c, _testTokenURL, "", params, &res); err != nil {
log.Error("s.TestToken(%s) httpClient.Get(%s) error(%v)", token, params.Encode(), err)
return
}
if res.Code != ecode.OK.Code() {
log.Error("测试推送失败 token(%s) code(%d)", token, res.Code)
err = ecode.Int(res.Code)
}
return
}
// 上传说明:
// 前端是批量上传,会随机按内容长度切割文件进行分批上传,有可能会切断原始行内容
// 如果想在上传的时候同步判断文件格式,产生错误时需要忽略首行和末行
// CheckUploadMid checks uploaded mid validation.
func (s *Service) CheckUploadMid(c context.Context, data []byte) (err error) {
var (
mid int64
lineNum int
lines = strings.Split(string(data), "\n")
total = len(lines)
)
for _, v := range lines {
lineNum++
v = strings.Trim(v, " \r\t")
if v == "" {
continue
}
if mid, err = strconv.ParseInt(v, 10, 64); err != nil {
log.Error("CheckUploadMid data(%s) error(%v)", v, err)
return ecode.PushUploadInvalidErr
}
if mid <= 0 {
if lineNum == 1 || lineNum == total {
continue
}
log.Error("CheckUploadMid data(%s) error(%v)", v, err)
return ecode.PushUploadInvalidErr
}
}
return
}
// CheckUploadToken checks uploaded token validation.
func (s *Service) CheckUploadToken(c context.Context, data []byte) (err error) {
var (
plat int
lineNum int
lines = strings.Split(string(data), "\n")
total = len(lines)
)
for _, v := range lines {
lineNum++
if lineNum == 1 || lineNum == total {
continue
}
v = strings.Trim(v, " \r")
if v == "" {
continue
}
res := strings.Split(v, "\t")
if len(res) != 2 {
log.Error("CheckUploadToken data(%s)", v)
return ecode.PushUploadInvalidErr
}
if res[0] == "" || res[1] == "" {
log.Error("CheckUploadToken data(%s)", v)
return ecode.PushUploadInvalidErr
}
if plat, err = strconv.Atoi(res[0]); err != nil || plat <= 0 {
log.Error("CheckUploadToken data(%s) error(%v)", v, err)
return ecode.PushUploadInvalidErr
}
}
return
}

View File

@@ -0,0 +1,109 @@
package service
import (
"bytes"
"context"
"crypto/md5"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"sort"
"strconv"
"strings"
"time"
"go-common/library/ecode"
"go-common/library/log"
)
// Upload add mids file.
func (s *Service) Upload(c context.Context, dir, path string, data []byte) (err error) {
if _, err = os.Stat(dir); err != nil {
if !os.IsNotExist(err) {
log.Error("os.IsNotExist(%s) error(%v)", dir, err)
return
}
if err = os.MkdirAll(dir, 0777); err != nil {
log.Error("os.MkdirAll(%s) error(%v)", dir, err)
return
}
}
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Error("s.Upload(%s) error(%v)", path, err)
return
}
defer f.Close()
if _, err = f.Write(data); err != nil {
log.Error("f.Write() error(%v)", err)
}
return
}
// Upimg upload image
func (s *Service) Upimg(ctx context.Context, file multipart.File, header *multipart.FileHeader) (url string, err error) {
buf := new(bytes.Buffer)
w := multipart.NewWriter(buf)
dest, err := w.CreateFormFile("file", header.Filename)
if err != nil {
log.Error("Upimg CreateFormFile error(%v)", err)
return
}
if _, err = io.Copy(dest, file); err != nil {
log.Error("Upimg Copy error(%v)", err)
return
}
w.Close()
query := map[string]string{
"ts": strconv.FormatInt(time.Now().Unix(), 10),
"appkey": s.c.HTTPClient.App.Key,
}
query["sign"] = sign(query, s.c.HTTPClient.App.Secret)
u := fmt.Sprintf("%s?ts=%s&appkey=%s&sign=%s", s.c.Cfg.UpimgURL, query["ts"], query["appkey"], query["sign"])
req, err := http.NewRequest(http.MethodPost, u, buf)
if err != nil {
log.Error("http.NewRequest(%s) error(%v)", u, err)
return
}
req.Header.Set("Content-Type", w.FormDataContentType())
res := &struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
URL string `json:"url"`
} `json:"data"`
}{}
if err = s.httpClient.Do(context.TODO(), req, &res); err != nil {
log.Error("httpClient.Do() error(%v)", err)
return
}
if !ecode.Int(res.Code).Equal(ecode.OK) {
log.Error("upimg error code(%d)", res.Code)
err = ecode.Int(res.Code)
return
}
url = strings.Replace(res.Data.URL, "http://", "https://", 1)
return
}
func sign(params map[string]string, secret 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()+secret)
return fmt.Sprintf("%x", h.Sum(nil))
}