first commit

This commit is contained in:
2022-03-17 15:59:24 +08:00
commit 2b0debb847
592 changed files with 73946 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
版本号`major.minor.patch`具体规则如下:
- major主版本号如有重大版本重构则该字段递增通常各主版本间接口不兼容。
- minor次版本号各次版本号间接口保持兼容如有接口新增或优化则该字段递增。
- patch补丁号如有功能改善或缺陷修复则该字段递增。
## version 0.0.2
init offline-push
## example x.x.x @yy.mm.dd
**Feature**
**Bug Fixes**
**Improvement**
**Breaking Change**

View File

@@ -0,0 +1,37 @@
# golang1.9 or latest
# 1. make help
# 2. make dep
# 3. make build
# ...
VERSION := $(shell echo $(shell cat version.go | grep "Version =" | cut -d '=' -f2))
APP_NAME := offline-push
BUILD_DIR := build
APP := ${BUILD_DIR}/${APP_NAME}_v${VERSION}
PKG_NAME := ${APP_NAME}_v${VERSION}
PKG := ${PKG_NAME}.tar.gz
LDFLAGS := -ldflags "-w -s -X gitlab.33.cn/chat/dtalk/service/$(APP_NAME).GitCommit=`git rev-parse --short=8 HEAD`"
LDGRPC := -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn"
.PHONY: clean build pkg
clean: ## Remove previous build
@rm -rf ${BUILD_DIR}
@go clean
build: #checkgofmt ## Build the binary file
GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(LDGRPC) $(LDFLAGS) -o $(APP) cmd/main.go
pkg: build ## Package
mkdir -p ${PKG_NAME}/bin
mkdir -p ${PKG_NAME}/etc
cp ${APP} ${PKG_NAME}/bin/
cp etc/* ${PKG_NAME}/etc/
tar zvcf ${PKG} ${PKG_NAME}
rm -rf ${PKG_NAME}
REMOTE_BIN_PATH := /opt/dtalk/srv/app/bin
upload: build
rsync -r $(APP) 107:$(REMOTE_BIN_PATH)
ssh 107 "cd $(REMOTE_BIN_PATH) && chmod 777 $(PKG_NAME) && ln -sf $(PKG_NAME) $(APP_NAME) && supervisorctl restart $(APP_NAME)"

View File

@@ -0,0 +1,268 @@
// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.1
// source: api.proto
package offlinepush
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Device int32
const (
Device_Android Device = 0
Device_IOS Device = 1
)
// Enum value maps for Device.
var (
Device_name = map[int32]string{
0: "Android",
1: "IOS",
}
Device_value = map[string]int32{
"Android": 0,
"IOS": 1,
}
)
func (x Device) Enum() *Device {
p := new(Device)
*p = x
return p
}
func (x Device) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Device) Descriptor() protoreflect.EnumDescriptor {
return file_api_proto_enumTypes[0].Descriptor()
}
func (Device) Type() protoreflect.EnumType {
return &file_api_proto_enumTypes[0]
}
func (x Device) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Device.Descriptor instead.
func (Device) EnumDescriptor() ([]byte, []int) {
return file_api_proto_rawDescGZIP(), []int{0}
}
// record --> mq
type OffPushMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AppId string `protobuf:"bytes,1,opt,name=appId,proto3" json:"appId,omitempty"`
Device Device `protobuf:"varint,2,opt,name=device,proto3,enum=dtalk.offline_push.Device" json:"device,omitempty"`
Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"`
Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"`
Token string `protobuf:"bytes,5,opt,name=token,proto3" json:"token,omitempty"`
ChannelType int32 `protobuf:"varint,6,opt,name=channelType,proto3" json:"channelType,omitempty"`
Target string `protobuf:"bytes,7,opt,name=target,proto3" json:"target,omitempty"`
Timeout int64 `protobuf:"varint,8,opt,name=timeout,proto3" json:"timeout,omitempty"`
}
func (x *OffPushMsg) Reset() {
*x = OffPushMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_api_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OffPushMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OffPushMsg) ProtoMessage() {}
func (x *OffPushMsg) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OffPushMsg.ProtoReflect.Descriptor instead.
func (*OffPushMsg) Descriptor() ([]byte, []int) {
return file_api_proto_rawDescGZIP(), []int{0}
}
func (x *OffPushMsg) GetAppId() string {
if x != nil {
return x.AppId
}
return ""
}
func (x *OffPushMsg) GetDevice() Device {
if x != nil {
return x.Device
}
return Device_Android
}
func (x *OffPushMsg) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *OffPushMsg) GetContent() string {
if x != nil {
return x.Content
}
return ""
}
func (x *OffPushMsg) GetToken() string {
if x != nil {
return x.Token
}
return ""
}
func (x *OffPushMsg) GetChannelType() int32 {
if x != nil {
return x.ChannelType
}
return 0
}
func (x *OffPushMsg) GetTarget() string {
if x != nil {
return x.Target
}
return ""
}
func (x *OffPushMsg) GetTimeout() int64 {
if x != nil {
return x.Timeout
}
return 0
}
var File_api_proto protoreflect.FileDescriptor
var file_api_proto_rawDesc = []byte{
0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x64, 0x74, 0x61,
0x6c, 0x6b, 0x2e, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x75, 0x73, 0x68, 0x22,
0xf0, 0x01, 0x0a, 0x0a, 0x4f, 0x66, 0x66, 0x50, 0x75, 0x73, 0x68, 0x4d, 0x73, 0x67, 0x12, 0x14,
0x0a, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61,
0x70, 0x70, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2e, 0x6f, 0x66, 0x66,
0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65,
0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x18,
0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65,
0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20,
0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20,
0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65,
0x6f, 0x75, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f,
0x75, 0x74, 0x2a, 0x1e, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x0b, 0x0a, 0x07,
0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x49, 0x4f, 0x53,
0x10, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x2e, 0x33, 0x33, 0x2e,
0x63, 0x6e, 0x2f, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x64, 0x74, 0x61, 0x6c, 0x6b, 0x2f, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x70, 0x75, 0x73,
0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_api_proto_rawDescOnce sync.Once
file_api_proto_rawDescData = file_api_proto_rawDesc
)
func file_api_proto_rawDescGZIP() []byte {
file_api_proto_rawDescOnce.Do(func() {
file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData)
})
return file_api_proto_rawDescData
}
var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_api_proto_goTypes = []interface{}{
(Device)(0), // 0: dtalk.offline_push.Device
(*OffPushMsg)(nil), // 1: dtalk.offline_push.OffPushMsg
}
var file_api_proto_depIdxs = []int32{
0, // 0: dtalk.offline_push.OffPushMsg.device:type_name -> dtalk.offline_push.Device
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_api_proto_init() }
func file_api_proto_init() {
if File_api_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OffPushMsg); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_proto_rawDesc,
NumEnums: 1,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_api_proto_goTypes,
DependencyIndexes: file_api_proto_depIdxs,
EnumInfos: file_api_proto_enumTypes,
MessageInfos: file_api_proto_msgTypes,
}.Build()
File_api_proto = out.File
file_api_proto_rawDesc = nil
file_api_proto_goTypes = nil
file_api_proto_depIdxs = nil
}

View File

@@ -0,0 +1,22 @@
// protoc -I=. -I=$GOPATH/src --go_out=plugins=grpc:. *.proto
syntax = "proto3";
package dtalk.offline_push;
option go_package = "gitlab.33.cn/chat/dtalk/service/offlinepush";
enum Device {
Android = 0;
IOS = 1;
}
// record --> mq
message OffPushMsg {
string appId = 1;
Device device = 2;
string title = 3;
string content = 4;
string token = 5;
int32 channelType = 6;
string target = 7;
int64 timeout = 8;
}

View File

@@ -0,0 +1,6 @@
#!/bin/sh
protoc -I. -I$GOPATH/src \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
*.proto

View File

@@ -0,0 +1,87 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/inconshreveable/log15"
"gitlab.33.cn/chat/dtalk/service/offline-push/config"
_ "gitlab.33.cn/chat/dtalk/service/offline-push/pusher/android"
_ "gitlab.33.cn/chat/dtalk/service/offline-push/pusher/ios"
"gitlab.33.cn/chat/dtalk/service/offline-push/service"
)
const srvName = "offline-push"
var log = log15.New("cmd", srvName)
var (
// projectVersion 项目版本
projectVersion = "0.0.2"
// goVersion go版本
goVersion = ""
// gitCommit git提交commit id
gitCommit = ""
// buildTime 编译时间
buildTime = ""
isShowVersion = flag.Bool("v", false, "show project version")
)
// showVersion 显示项目版本信息
func showVersion(isShow bool) {
if isShow {
fmt.Printf("Project: %s\n", srvName)
fmt.Printf(" Version: %s\n", projectVersion)
fmt.Printf(" Go Version: %s\n", goVersion)
fmt.Printf(" Git Commit: %s\n", gitCommit)
fmt.Printf(" Build Time: %s\n", buildTime)
os.Exit(0)
}
}
// @Title 聊天单模块集成测试
// @Version 0.1
// @Description
// @SecurityDefinitions.ApiKey ApiKeyAuth
// @In header
// @Name Authorization
// @BasePath /
func main() {
flag.Parse()
showVersion(*isShowVersion)
if err := config.Init(); err != nil {
panic(err)
}
log.Info("config info:",
"AppId", config.Conf.AppId,
"Pushers Android", config.Conf.Pushers["Android"],
"Pushers iOS", config.Conf.Pushers["iOS"],
"MQSub", config.Conf.MQSub)
// service init
svc := service.New(config.Conf)
svc.ListenMQ()
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("service get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
time.Sleep(time.Second * 2)
log.Info(srvName + " server exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}

View File

@@ -0,0 +1,53 @@
package config
import (
"flag"
"github.com/BurntSushi/toml"
)
var (
confPath string
// Conf config
Conf *Config
)
func init() {
flag.StringVar(&confPath, "conf", "offline-push.toml", "default config path.")
}
// Init init config.
func Init() (err error) {
Conf = Default()
_, err = toml.DecodeFile(confPath, &Conf)
return
}
// Default new a config with specified defualt value.
func Default() *Config {
return &Config{
MQSub: &MQSubClient{
Brokers: nil,
Number: 0,
},
}
}
type Config struct {
//push config
AppId string
Pushers map[string]*Pusher
MQSub *MQSubClient
}
type Pusher struct {
Env string
AppKey string
AppMasterSecret string
MiActivity string
}
type MQSubClient struct {
Brokers []string
Number uint32
}

View File

@@ -0,0 +1,22 @@
AppId= "dtalk"
[server]
addr="0.0.0.0:18100"
[MQSub]
Brokers=["127.0.0.1:9092"]
Number=16
# 友盟离线推送物料
[pushers]
[pushers.Android]
Env = "debug"
AppKey = ""
AppMasterSecret = ""
MiActivity = ""
[pushers.iOS]
Env = "debug"
AppKey = ""
AppMasterSecret = ""
MiActivity = ""

View File

@@ -0,0 +1,9 @@
package model
import "errors"
var (
ErrAppId = errors.New("appId not compared")
ErrConsumeRedo = errors.New("process msg failed")
ErrCustomNotSupport = errors.New("pusher device type not support")
)

View File

@@ -0,0 +1,18 @@
package android
import "gitlab.33.cn/chat/dtalk/service/offline-push/pusher"
const Name = "Android"
func init() {
pusher.Register(Name, New)
}
func New(cfg pusher.Config) pusher.IPusher {
return &androidPusher{
AppKey: cfg.AppKey,
AppMasterSecret: cfg.AppMasterSecret,
MiActivity: cfg.MiActivity,
environment: cfg.Environment,
}
}

View File

@@ -0,0 +1,74 @@
package android
import (
"errors"
"gitlab.33.cn/chat/dtalk/pkg/util"
"strconv"
push "github.com/oofpgDLD/u-push"
android_push "github.com/oofpgDLD/u-push/android"
"gitlab.33.cn/chat/dtalk/service/offline-push/pusher"
)
type androidPusher struct {
AppKey string
AppMasterSecret string
MiActivity string
environment string
}
func (t *androidPusher) SinglePush(deviceToken, title, text string, extra *pusher.Extra) error {
var client push.PushClient
unicast := android_push.NewAndroidUnicast(t.AppKey, t.AppMasterSecret)
//fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text)
unicast.SetDeviceToken(deviceToken)
unicast.SetTitle(title)
unicast.SetText(text)
unicast.GoCustomAfterOpen("")
unicast.SetDisplayType(push.NOTIFICATION)
unicast.SetMipush(true, t.MiActivity)
unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05"))
switch t.environment {
case "debug":
// 测试模式
unicast.SetTestMode()
case "release":
// 线上模式
unicast.SetReleaseMode()
default:
return errors.New("unknown environment")
}
// Set customized fields
unicast.SetExtraField("address", extra.Address)
unicast.SetExtraField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10))
return client.Send(unicast)
}
func (t *androidPusher) SingleCustomPush(address, title, text string, extra *pusher.Extra) error {
var client push.PushClient
unicast := android_push.NewAndroidCustomizedcast(t.AppKey, t.AppMasterSecret)
//fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text)
unicast.SetAlias(address, "ADDRESS")
unicast.SetTitle(title)
unicast.SetText(text)
unicast.GoCustomAfterOpen("")
unicast.SetDisplayType(push.NOTIFICATION)
unicast.SetMipush(true, t.MiActivity)
unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05"))
switch t.environment {
case "debug":
// 测试模式
unicast.SetTestMode()
case "release":
// 线上模式
unicast.SetReleaseMode()
default:
return errors.New("unknown environment")
}
// Set customized fields
unicast.SetExtraField("address", extra.Address)
unicast.SetExtraField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10))
return client.Send(unicast)
}

View File

@@ -0,0 +1,60 @@
package android
import (
"gitlab.33.cn/chat/dtalk/service/offline-push/pusher"
"testing"
)
func Test_androidPusher_SinglePush(t1 *testing.T) {
type fields struct {
AppKey string
AppMasterSecret string
MiActivity string
environment string
}
type args struct {
deviceToken string
title string
text string
extra *pusher.Extra
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "test android offline push",
fields: fields{
AppKey: "606ebf176a23f17dcf15b2cd",
AppMasterSecret: "uengh9mzrvm5zdclyt5ean05ckqc2lxl",
MiActivity: "",
environment: "debug",
},
args: args{
deviceToken: "Apjwo_0X0-y0sGcWPxzGrY1dl2qvv_uE7LAeCoivoHjf",
title: "测试title",
text: "测试text",
extra: &pusher.Extra{
Address: "1FdnxKR4r952x2HQA2BTTpFH6tgHYYNs3M",
ChannelType: 0,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
t := &androidPusher{
AppKey: tt.fields.AppKey,
AppMasterSecret: tt.fields.AppMasterSecret,
MiActivity: tt.fields.MiActivity,
environment: tt.fields.environment,
}
if err := t.SinglePush(tt.args.deviceToken, tt.args.title, tt.args.text, tt.args.extra); (err != nil) != tt.wantErr {
t1.Errorf("SinglePush() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -0,0 +1,18 @@
package ios
import "gitlab.33.cn/chat/dtalk/service/offline-push/pusher"
const Name = "iOS"
func init() {
pusher.Register(Name, New)
}
func New(cfg pusher.Config) pusher.IPusher {
return &iOSPusher{
AppKey: cfg.AppKey,
AppMasterSecret: cfg.AppMasterSecret,
MiActivity: cfg.MiActivity,
environment: cfg.Environment,
}
}

View File

@@ -0,0 +1,82 @@
package ios
import (
"errors"
"gitlab.33.cn/chat/dtalk/pkg/util"
"strconv"
push "github.com/oofpgDLD/u-push"
ios_push "github.com/oofpgDLD/u-push/ios"
"gitlab.33.cn/chat/dtalk/service/offline-push/pusher"
)
type iOSPusher struct {
AppKey string
AppMasterSecret string
MiActivity string
environment string
}
func (t *iOSPusher) SinglePush(deviceToken, title, text string, extra *pusher.Extra) error {
var client push.PushClient
unicast := ios_push.NewIOSUnicast(t.AppKey, t.AppMasterSecret)
//fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text)
unicast.SetDeviceToken(deviceToken)
//unicast.SetAlert("IOS 单播测试")
unicast.SetAlertJson(push.IOSAlert{
Title: title,
Body: text,
})
//unicast.SetBadge(0)
unicast.SetSound("default")
unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05"))
switch t.environment {
case "debug":
// 测试模式
unicast.SetTestMode()
case "release":
// 线上模式
unicast.SetReleaseMode()
default:
return errors.New("unknown environment")
}
// Set customized fields
unicast.SetCustomizedField("address", extra.Address)
unicast.SetCustomizedField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10))
return client.Send(unicast)
}
func (t *iOSPusher) SingleCustomPush(address, title, text string, extra *pusher.Extra) error {
var client push.PushClient
unicast := ios_push.NewIOSCustomizedcast(t.AppKey, t.AppMasterSecret)
//fmt.Println(t.AppKey, t.AppMasterSecret, t.DeviceToken, title, text)
unicast.SetAlias(address, "ADDRESS")
//unicast.SetAlert("IOS 单播测试")
unicast.SetAlertJson(push.IOSAlert{
Title: title,
Body: text,
})
//unicast.SetBadge(0)
unicast.SetSound("default")
unicast.SetExpireTime(util.UnixToTime(extra.TimeOutTime).In(util.Shanghai()).Format("2006-01-02 15:04:05"))
switch t.environment {
case "debug":
// 测试模式
unicast.SetTestMode()
case "release":
// 线上模式
unicast.SetReleaseMode()
default:
return errors.New("unknown environment")
}
// Set customized fields
unicast.SetCustomizedField("address", extra.Address)
unicast.SetCustomizedField("channelType", strconv.FormatInt(int64(extra.ChannelType), 10))
return client.Send(unicast)
}

View File

@@ -0,0 +1,44 @@
package pusher
import (
"encoding/json"
"errors"
)
var execPusher = make(map[string]CreateFunc)
type CreateFunc func(cfg Config) IPusher
func Register(name string, exec CreateFunc) {
execPusher[name] = exec
}
func Load(name string) (CreateFunc, error) {
exec, ok := execPusher[name]
if !ok {
return nil, errors.New("pusher not find")
}
return exec, nil
}
type Config struct {
AppKey string
AppMasterSecret string
MiActivity string
Environment string
}
type IPusher interface {
SinglePush(deviceToken, title, text string, extra *Extra) error
SingleCustomPush(address, title, text string, extra *Extra) error
}
type Extra struct {
Address string `json:"address"`
ChannelType int32 `json:"channelType"`
TimeOutTime int64 `json:"-"`
}
func (e *Extra) ToBytes() ([]byte, error) {
return json.Marshal(e)
}

View File

@@ -0,0 +1,49 @@
package kafka
import (
cluster "github.com/bsm/sarama-cluster"
"github.com/golang/protobuf/proto"
"github.com/inconshreveable/log15"
offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api"
"gitlab.33.cn/chat/dtalk/service/offline-push/model"
)
var log = log15.New("model", "offputhtest")
type Process interface {
Deal(m *offlinepush.OffPushMsg) error
}
type Consumer struct {
*cluster.Consumer
}
func (c *Consumer) Listen(p Process) {
for {
select {
case err := <-c.Errors():
log.Error("consumer error", "err", err)
case n := <-c.Notifications():
log.Info("consumer rebalanced", "number", n)
case msg, ok := <-c.Messages():
if !ok {
log.Debug("consume not ok", "topic", msg.Topic, "partition", msg.Partition, "offset", msg.Offset, "key", msg.Key)
return
}
bizMsg := new(offlinepush.OffPushMsg)
if err := proto.Unmarshal(msg.Value, bizMsg); err != nil {
log.Error("proto.Unmarshal error", "err", err, "msg", msg)
continue
}
log.Debug("consume process", "topic", msg.Topic, "partition", msg.Partition, "offset", msg.Offset, "key", msg.Key, "bizMsg", bizMsg)
err := p.Deal(bizMsg)
if err != nil {
log.Debug("p.Deal error", "err", err, "bizMsg", bizMsg)
if err == model.ErrConsumeRedo {
//TODO redo consume message
}
}
c.MarkOffset(msg, "")
}
}
}

View File

@@ -0,0 +1,34 @@
package kafka
import (
"fmt"
cluster "github.com/bsm/sarama-cluster"
"gitlab.33.cn/chat/dtalk/service/offline-push/config"
"strconv"
)
func NewKafkaConsumers(appId string, cfg *config.MQSubClient, groupIdx int) map[string]*Consumer {
store := make(map[string]*Consumer)
num := int(cfg.Number)
for i := 0; i < num; i++ {
store[strconv.Itoa(i)] = &Consumer{Consumer: newKafkaSub(appId, groupIdx, cfg.Brokers)}
}
return store
}
func newKafkaSub(appId string, groupIdx int, brokers []string) *cluster.Consumer {
c := cluster.NewConfig()
c.Consumer.Return.Errors = true
c.Group.Return.Notifications = true
topic := fmt.Sprintf("offpush-%s-topic", appId)
group := fmt.Sprintf("offpush-%s-group", appId)
if groupIdx > 0 {
group = fmt.Sprintf("%s-%d", group, groupIdx)
}
consumer, err := cluster.NewConsumer(brokers, group, []string{topic}, c)
if err != nil {
panic(err)
}
return consumer
}

View File

@@ -0,0 +1,95 @@
package service
import (
"fmt"
"time"
"github.com/inconshreveable/log15"
offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api"
"gitlab.33.cn/chat/dtalk/service/offline-push/config"
"gitlab.33.cn/chat/dtalk/service/offline-push/model"
"gitlab.33.cn/chat/dtalk/service/offline-push/pusher"
"gitlab.33.cn/chat/dtalk/service/offline-push/pusher/android"
"gitlab.33.cn/chat/dtalk/service/offline-push/pusher/ios"
"gitlab.33.cn/chat/dtalk/service/offline-push/service/kafka"
)
type Service struct {
log log15.Logger
cfg *config.Config
consumers map[string]*kafka.Consumer
pushers map[offlinepush.Device]pusher.IPusher
}
func New(c *config.Config) *Service {
s := &Service{
log: log15.New("module", "offline-push/service"),
cfg: c,
consumers: kafka.NewKafkaConsumers(c.AppId, c.MQSub, 0),
pushers: make(map[offlinepush.Device]pusher.IPusher),
}
s.loadPushers()
return s
}
func (s Service) Config() *config.Config {
return s.cfg
}
func (s *Service) ListenMQ() {
for i, c := range s.consumers {
s.log.Debug(fmt.Sprintf("accept %v", i))
go c.Listen(s)
}
}
func (s *Service) loadPushers() {
androidCreator, err := pusher.Load(android.Name)
if err != nil {
panic(err)
}
iOSCreator, err := pusher.Load(ios.Name)
if err != nil {
panic(err)
}
s.pushers[offlinepush.Device_Android] = androidCreator(pusher.Config{
AppKey: s.cfg.Pushers[android.Name].AppKey,
AppMasterSecret: s.cfg.Pushers[android.Name].AppMasterSecret,
MiActivity: s.cfg.Pushers[android.Name].MiActivity,
Environment: s.cfg.Pushers[android.Name].Env,
})
s.pushers[offlinepush.Device_IOS] = iOSCreator(pusher.Config{
AppKey: s.cfg.Pushers[ios.Name].AppKey,
AppMasterSecret: s.cfg.Pushers[ios.Name].AppMasterSecret,
MiActivity: s.cfg.Pushers[ios.Name].MiActivity,
Environment: s.cfg.Pushers[ios.Name].Env,
})
}
func (s *Service) Deal(m *offlinepush.OffPushMsg) error {
if m.AppId != s.cfg.AppId {
return model.ErrAppId
}
p, ok := s.pushers[m.Device]
if !ok {
s.log.Error("pusher exec not find", "deviceType", m.Device.String(), "pushers", s.pushers)
return model.ErrCustomNotSupport
}
if tm := time.Now().Unix(); tm > m.Timeout {
s.log.Info("message offline push timeout",
"appId", m.GetAppId(),
"deviceType", m.GetDevice().String(),
"deviceToken", m.GetToken(),
"channelType", m.GetChannelType(),
"target", m.GetTarget(),
"timeout time", m.GetTimeout(),
"now time", tm,
)
return nil
}
return p.SinglePush(m.Token, m.Title, m.Content, &pusher.Extra{
Address: m.Target,
ChannelType: m.ChannelType,
TimeOutTime: m.Timeout,
})
}

View File

@@ -0,0 +1,21 @@
# golang1.9 or latest
# 1. make help
# 2. make dep
# 3. make build
# ...
APP_NAME := client
BUILD_DIR := build
APP := ${BUILD_DIR}/${APP_NAME}
LDFLAGS := -ldflags "-w -s -X gitlab.33.cn/chat/dtalk/version.GitCommit=`git rev-parse --short=8 HEAD`"
LDGRPC := -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn"
.PHONY: clean build
clean: ## Remove previous build
@rm -rf ${BUILD_DIR}
@go clean
build: #checkgofmt ## Build the binary file
GOOS=linux GOARCH=amd64 GO111MODULE=on GOPROXY=https://goproxy.cn,direct GOSUMDB="sum.golang.google.cn" go build -v $(LDGRPC) $(LDFLAGS) -o $(APP) cmd/main.go

View File

@@ -0,0 +1,38 @@
package mock
import (
"context"
"fmt"
"gitlab.33.cn/chat/dtalk/service/record/kafka/publisher"
"gopkg.in/Shopify/sarama.v1"
kafka "gopkg.in/Shopify/sarama.v1"
)
type Client struct {
brokers []string
appId string
offPushPub kafka.SyncProducer
}
func NewClient(appId string, brokers []string) *Client {
c := &Client{
appId: appId,
brokers: brokers,
offPushPub: publisher.NewKafkaPub(brokers),
}
return c
}
func (c *Client) PublishOfflineMsg(ctx context.Context, fromId string, b []byte) error {
if c.offPushPub == nil {
return fmt.Errorf("kafka publish client not init")
}
appTopic := fmt.Sprintf("offpush-%s-topic", c.appId)
m := &sarama.ProducerMessage{
Key: sarama.StringEncoder(fromId),
Topic: appTopic,
Value: sarama.ByteEncoder(b),
}
_, _, err := c.offPushPub.SendMessage(m)
return err
}

View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
"gitlab.33.cn/chat/dtalk/service/offline-push/tools/mock/cmd/push"
)
var rootCmd = &cobra.Command{
Use: "tools",
Short: "offline push mock tools",
Example: " tools auth -d <data>\n",
}
func init() {
rootCmd.AddCommand(push.SinglePushCmd)
}
// Execute executes the root command and its subcommands.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func main() {
Execute()
}

View File

@@ -0,0 +1,50 @@
package push
import (
"context"
"fmt"
"github.com/spf13/cobra"
offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api"
"gitlab.33.cn/chat/dtalk/service/offline-push/tools/mock"
config "gitlab.33.cn/chat/dtalk/service/offline-push/tools/mock/etc"
)
var SinglePushCmd = &cobra.Command{
Use: "single",
Short: "single push",
Long: "single push",
Example: "single -n 1 --hm=true -d true",
Run: singlePush,
}
func init() {
SinglePushCmd.Flags().StringVarP(&config.ConfPath, "conf", "c", "config.toml", "default config path.")
}
func singlePush(cmd *cobra.Command, args []string) {
err := config.Init()
if err != nil {
fmt.Printf("config init error:%v\n", err)
return
}
fmt.Printf("Client Config: %v\n Msg Config: %v\n", config.Conf.Client, config.Conf.Msg)
c := mock.NewClient(config.Conf.Client.AppId, config.Conf.Client.Brokers)
m := mock.Msg{
AppId: config.Conf.Msg.AppId,
DeviceType: offlinepush.Device(config.Conf.Msg.DeviceType),
Nickname: config.Conf.Msg.Nickname,
TargetId: config.Conf.Msg.TargetId,
DeviceToken: config.Conf.Msg.DeviceToken,
}
data, err := m.Data()
if err != nil {
fmt.Printf("msg data error:%v\n", err)
return
}
err = c.PublishOfflineMsg(context.Background(), config.Conf.Client.FromId, data)
if err != nil {
fmt.Printf("push msg error:%v\n", err)
return
}
fmt.Println("success")
}

View File

@@ -0,0 +1,53 @@
package config
import (
"github.com/BurntSushi/toml"
)
var (
ConfPath string
Conf *Config
)
// Init init config.
func Init() (err error) {
Conf = Default()
_, err = toml.DecodeFile(ConfPath, &Conf)
return
}
func Default() *Config {
return &Config{
Client: Client{
AppId: "",
FromId: "",
Brokers: nil,
},
Msg: Msg{
AppId: "",
DeviceType: 0,
Nickname: "",
TargetId: "",
DeviceToken: "",
},
}
}
type Config struct {
Client Client
Msg Msg
}
type Client struct {
AppId string
FromId string
Brokers []string
}
type Msg struct {
AppId string
DeviceType int32
Nickname string
TargetId string
DeviceToken string
}

View File

@@ -0,0 +1,11 @@
[client]
AppId = ""
FromId="test"
Brokers=[""]
[msg]
AppId = ""
DeviceType = 0
Nickname = ""
TargetId = ""
DeviceToken = ""

View File

@@ -0,0 +1,31 @@
package mock
import (
"github.com/golang/protobuf/proto"
offlinepush "gitlab.33.cn/chat/dtalk/service/offline-push/api"
xproto "gitlab.33.cn/chat/imparse/proto"
"time"
)
type Msg struct {
AppId string
DeviceType offlinepush.Device
Nickname string
TargetId string
DeviceToken string
}
func (m *Msg) Data() ([]byte, error) {
//需要推送
pushMsg := &offApi.OffPushMsg{
AppId: m.AppId,
Device: m.DeviceType,
Title: m.Nickname,
Content: "[你收到一条消息]",
Token: m.DeviceToken,
ChannelType: int32(xproto.Channel_ToUser),
Target: m.TargetId,
Timeout: time.Now().Add(time.Minute * 7).Unix(),
}
return proto.Marshal(pushMsg)
}

View File

@@ -0,0 +1,16 @@
package version
//var (
// // The full version string
// Version = "0.0.2"
//
// // GitCommit is set with --ldflags "-X main.gitCommit=$(git rev-parse --short=8 HEAD)"
// GitCommit string
//)
//
//func GetVersion() string {
// if GitCommit != "" {
// return Version + "-" + GitCommit
// }
// return Version
//}