├── .editorconfig ├── .env ├── .gitignore ├── .travis.yml ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── Makefile ├── README.md ├── api ├── 0.proto ├── Makefile ├── notification.proto ├── third_party │ ├── github.com │ │ ├── grpc-ecosystem │ │ │ └── grpc-gateway │ │ │ │ └── protoc-gen-swagger │ │ │ │ └── options │ │ │ │ ├── annotations.proto │ │ │ │ └── openapiv2.proto │ │ └── mwitkow │ │ │ └── go-proto-validators │ │ │ └── validator.proto │ └── googleapis │ │ └── google │ │ └── api │ │ ├── annotations.proto │ │ └── http.proto └── types.proto ├── build └── builder-docker │ ├── Dockerfile │ └── Makefile ├── cmd └── notification │ └── main.go ├── deploy ├── db │ ├── mysql-pass.yaml │ ├── mysql.yaml │ ├── notification-db-ctrl-job.yaml │ └── notification-db-init-job.yaml ├── etcd │ └── etcd.yaml ├── ks │ └── notification.yaml ├── readme └── redis │ └── redis.yaml ├── doc ├── images │ ├── db_design.png │ ├── notification.png │ ├── swaggerUI.png │ └── websocket.png ├── installation │ └── allinone.md └── ks_api_json │ └── api.swagger.json ├── docker-compose.yml ├── docker_push ├── go.mod ├── pkg ├── apigateway │ ├── Makefile │ ├── spec │ │ ├── Makefile │ │ ├── api.swagger.json │ │ ├── makestatic.go │ │ ├── preprocess.jq │ │ └── static.go │ └── swagger-ui │ │ ├── Makefile │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── index.html │ │ ├── makestatic.go │ │ ├── static.go │ │ ├── swagger-ui-bundle.js │ │ ├── swagger-ui-standalone-preset.js │ │ ├── swagger-ui.css │ │ ├── swagger-ui.js │ │ └── version.txt ├── client │ ├── notification │ │ └── client.go │ └── websocket │ │ ├── ws_client_test │ │ ├── ks_client │ │ │ ├── home-huojiao-ws_ks_event.html │ │ │ ├── home-huojiao-ws_ks_nf.html │ │ │ ├── home-system-ws_ks_event.html │ │ │ └── home-system-ws_ks_nf.html │ │ └── op_client │ │ │ ├── home-huojiao-ws_op_event.html │ │ │ ├── home-huojiao-ws_op_nf.html │ │ │ ├── home-system-ws_op_event.html │ │ │ └── home-system-ws_op_nf.html │ │ └── ws_manager.go ├── config │ ├── _config_product.go │ ├── _config_test.go │ ├── config.go │ ├── config_test.go │ └── env_loader.go ├── constants │ ├── common.go │ ├── validation_mail_template.go │ └── validation_mail_template_en.go ├── db │ ├── Dockerfile │ ├── common.go │ ├── db.go │ ├── db_test.go │ ├── ddl │ │ └── notification.sql │ ├── schema │ │ └── notification │ │ │ ├── V0_1__init.sql │ │ │ ├── V0_2__update.sql │ │ │ ├── V0_3__add_available_time.sql │ │ │ ├── V0_4__add_task_notify_type.sql │ │ │ ├── V0_5__email_config.sql │ │ │ ├── V0_6__address_list_alterfield.sql │ │ │ └── V0_7__add_fromemail.sql │ └── scripts │ │ └── ddl_init.sh ├── etcd │ ├── dlock.go │ ├── etcd.go │ ├── etcd_test.go │ └── mutex.go ├── gerr │ ├── codes.go │ ├── error.go │ └── message.go ├── global │ ├── global.go │ └── global_test.go ├── manager │ ├── checker.go │ ├── grpc_client.go │ └── grpc_server.go ├── models │ ├── address.go │ ├── address_info.go │ ├── address_list.go │ ├── address_list_binding.go │ ├── content.go │ ├── content_test.go │ ├── email_config.go │ ├── nf_address_list.go │ ├── notification.go │ ├── notification_test.go │ ├── query_setting.go │ ├── service_config.go │ ├── task.go │ └── user_message.go ├── pb │ ├── 0.pb.go │ ├── notification.pb.go │ ├── notification.pb.gw.go │ └── types.pb.go ├── plugins │ ├── email_notifier.go │ └── plugin.go ├── services │ └── notification │ │ ├── _api_gateway_test.go │ │ ├── am.go │ │ ├── api_gateway.go │ │ ├── controller.go │ │ ├── handler.go │ │ ├── handler_test.go │ │ ├── resource_control │ │ ├── address.go │ │ ├── address_list.go │ │ ├── email_config.go │ │ ├── email_config_test.go │ │ ├── notification.go │ │ ├── notification_test.go │ │ ├── service_config.go │ │ └── task.go │ │ ├── server.go │ │ └── validation.go ├── testhelper.go └── util │ ├── ctxutil │ ├── ctx.go │ ├── ctx_test.go │ ├── message.go │ └── request.go │ ├── emailutil │ ├── email.go │ ├── email_test.go │ └── noStartTLSPlainAuth.go │ ├── idutil │ ├── id.go │ └── id_test.go │ ├── jsonutil │ ├── interface.go │ └── json.go │ ├── pbutil │ └── pb.go │ └── stringutil │ ├── base64.go │ ├── string.go │ └── string_test.go └── test └── notification └── notification_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | # http://editorconfig.org/ 6 | 7 | root = true 8 | 9 | # Unix-style newlines with a newline ending every file 10 | [*] 11 | charset = utf-8 12 | end_of_line = lf 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*] 17 | indent_style = tab 18 | 19 | [*.{go,proto}] 20 | charset = utf-8 21 | indent_style = tab 22 | 23 | # Matches the exact files either package.json or .travis.yml 24 | [{package.json,.travis.yml}] 25 | indent_style = space 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DATA_PATH=~/data/nf/ 2 | MYSQL_ROOT_PASSWORD=password 3 | NOTIFICATION_LOG_LEVEL=info 4 | NOTIFICATION_GRPC_SHOW_ERROR_CAUSE=0 5 | NOTIFICATION_LOG_MAX_SIZE=10M 6 | NOTIFICATION_LOG_MAX_FILE=10 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | tmp/ 4 | /go.sum 5 | /go.sum 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | env: 3 | matrix: 4 | - TEST_SUITE=unit-test 5 | services: 6 | - docker 7 | language: go 8 | go: 9 | - '1.11.4' 10 | go_import_path: openpitrix.io/notification 11 | script: 12 | - make fmt-check 13 | - make check 14 | - env GO111MODULE=on make $TEST_SUITE 15 | deploy: 16 | - provider: script 17 | script: bash docker_push latest 18 | on: 19 | branch: master 20 | repo: openpitrix/notification 21 | - provider: script 22 | script: bash docker_push $TRAVIS_TAG 23 | on: 24 | tags: true 25 | repo: openpitrix/notification 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | FROM golang:1.12-alpine3.9 as builder 5 | 6 | # install tools 7 | RUN apk add --no-cache git 8 | 9 | WORKDIR /go/src/openpitrix.io/notification 10 | COPY . . 11 | 12 | ENV GO111MODULE=on 13 | ENV CGO_ENABLED=0 14 | ENV GOOS=linux 15 | 16 | RUN mkdir -p /openpitrix_bin 17 | RUN go build -v -a -installsuffix cgo -ldflags '-w' -o /openpitrix_bin/notification cmd/notification/main.go 18 | 19 | 20 | 21 | FROM alpine:3.7 22 | # modify pod (container) timezone 23 | RUN apk add -U tzdata && ls /usr/share/zoneinfo && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && apk del tzdata 24 | 25 | COPY --from=builder /openpitrix_bin/notification /usr/local/bin/ 26 | EXPOSE 9201 27 | CMD ["/usr/local/bin/notification"] 28 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 2 | RUN apk add --update ca-certificates && update-ca-certificates 3 | 4 | # modify pod (container) timezone 5 | RUN apk add -U tzdata && ls /usr/share/zoneinfo && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && apk del tzdata 6 | 7 | COPY ./* /usr/local/bin/ 8 | 9 | CMD ["sh"] 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | TRAG.Gopkg:=openpitrix.io/notification 6 | TRAG.Version:=$(TRAG.Gopkg)/pkg/version 7 | TARG.Name:=notification 8 | 9 | #GO_FMT:=goimports -l -w -e -local=openpitrix -srcdir=/go/src/$(TRAG.Gopkg) 10 | GO_FMT:=gofmt -w /go/src/$(TRAG.Gopkg) 11 | GO_MOD_TIDY:=go mod tidy 12 | GO_RACE:=go build -race 13 | GO_VET:=go vet 14 | GO_FILES:=./cmd ./pkg 15 | GO_PATH_FILES:=./cmd/... ./pkg/... 16 | DB_TEST:=NOTIFICATION_DB_UNIT_TEST=1 NOTIFICATION_MYSQL_HOST=127.0.0.1 NOTIFICATION_MYSQL_PORT=13306 17 | ETCD_TEST:=NOTIFICATION_ETCD_UNIT_TEST=1 NOTIFICATION_ETCD_ENDPOINTS=127.0.0.1:12379 18 | 19 | DOCKER_TAGS=latest 20 | BUILDER_IMAGE=openpitrix/notification-builder:v1.0.0 21 | #use local go mod lib 22 | #RUN_IN_DOCKER:=docker run -it -v $(GOPATH)/pkg/mod:/go/pkg/mod -v `pwd`:/go/src/$(TRAG.Gopkg) -v `pwd`/tmp/cache:/root/.cache/go-build -w /go/src/$(TRAG.Gopkg) -e GOBIN=/go/src/$(TRAG.Gopkg)/tmp/bin -e USER_ID=`id -u` -e GROUP_ID=`id -g` $(BUILDER_IMAGE) 23 | RUN_IN_DOCKER:=docker run -it -v `pwd`:/go/src/$(TRAG.Gopkg) -v `pwd`/tmp/cache:/root/.cache/go-build -w /go/src/$(TRAG.Gopkg) -e GOBIN=/go/src/$(TRAG.Gopkg)/tmp/bin -e USER_ID=`id -u` -e GROUP_ID=`id -g` $(BUILDER_IMAGE) 24 | 25 | define get_diff_files 26 | $(eval DIFF_FILES=$(shell git diff --name-only --diff-filter=ad | grep -e "^(cmd|pkg)/.+\.go")) 27 | endef 28 | # Get project build flags 29 | define get_build_flags 30 | $(eval SHORT_VERSION=$(shell git describe --tags --always --dirty="-dev")) 31 | $(eval SHA1_VERSION=$(shell git show --quiet --pretty=format:%H)) 32 | $(eval DATE=$(shell date +'%Y-%m-%dT%H:%M:%S')) 33 | $(eval BUILD_FLAG= -X $(TRAG.Version).ShortVersion="$(SHORT_VERSION)" \ 34 | -X $(TRAG.Version).GitSha1Version="$(SHA1_VERSION)" \ 35 | -X $(TRAG.Version).BuildDate="$(DATE)") 36 | endef 37 | 38 | CMD?=... 39 | comma:= , 40 | empty:= 41 | space:= $(empty) $(empty) 42 | CMDS=$(subst $(comma),$(space),$(CMD)) 43 | 44 | .PHONY: generate-in-local 45 | generate-in-local: ## Generate code from protobuf file in local 46 | cd ./api && make generate 47 | 48 | .PHONY: generate 49 | generate: ## Generate code from protobuf file in docker 50 | $(RUN_IN_DOCKER) make generate-in-local 51 | @echo "generate done" 52 | 53 | .PHONY: fmt-all 54 | fmt-all: ## Format all code 55 | $(RUN_IN_DOCKER) $(GO_FMT) $(GO_FILES) 56 | @echo "fmt done" 57 | 58 | .PHONY: tidy 59 | tidy: ## Tidy go.mod 60 | env GO111MODULE=on $(GO_MOD_TIDY) 61 | @echo "go mod tidy done" 62 | 63 | .PHONY: fmt-check 64 | fmt-check:fmt-all tidy ## Check whether all files be formatted 65 | $(call get_diff_files) 66 | $(if $(DIFF_FILES), \ 67 | exit 2 \ 68 | ) 69 | 70 | .PHONY: check 71 | check: ## go vet and race 72 | env GO111MODULE=on $(GO_RACE) $(GO_PATH_FILES) 73 | env GO111MODULE=on $(GO_VET) $(GO_PATH_FILES) 74 | 75 | .PHONY: build 76 | build: fmt-all ## Build notification image 77 | mkdir -p ./tmp/bin 78 | $(call get_build_flags) 79 | $(RUN_IN_DOCKER) env GO111MODULE=on time go install -tags netgo -v -ldflags '$(BUILD_FLAG)' $(foreach cmd,$(CMDS),$(TRAG.Gopkg)/cmd/$(cmd)) 80 | docker build -t $(TARG.Name) -f ./Dockerfile.dev ./tmp/bin 81 | docker image prune -f 1>/dev/null 2>&1 82 | @echo "build done" 83 | 84 | .PHONY: compose-up 85 | compose-up: 86 | docker-compose up -d 87 | @echo "compose-up done" 88 | 89 | build-image-%: ## build docker image 90 | @if [ "$*" = "latest" ];then \ 91 | docker build -t openpitrix/notification:latest .; \ 92 | docker build -t openpitrix/notification:flyway -f ./pkg/db/Dockerfile ./pkg/db/; \ 93 | elif [ "`echo "$*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+"`" != "" ];then \ 94 | docker build -t openpitrix/notification:$* .; \ 95 | docker build -t openpitrix/notification:flyway-$* -f ./pkg/db/Dockerfile ./pkg/db/; \ 96 | fi 97 | 98 | push-image-%: ## push docker image 99 | @if [ "$*" = "latest" ];then \ 100 | docker push openpitrix/notification:latest; \ 101 | docker push openpitrix/notification:flyway; \ 102 | elif [ "`echo "$*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+"`" != "" ];then \ 103 | docker push openpitrix/notification:$*; \ 104 | docker push openpitrix/notification:flyway-$*; \ 105 | fi 106 | 107 | .PHONY: test 108 | test: ## Run all tests 109 | make unit-test 110 | @echo "test done" 111 | 112 | .PHONY: unit-test 113 | unit-test: ## Run unit tests 114 | $(DB_TEST) $(ETCD_TEST) go test -a -tags="etcd db" ./... 115 | @echo "unit-test done" 116 | 117 | .PHONY: clean 118 | clean: ## Run docker-compose down 119 | docker-compose down 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notification 2 | [![License](http://img.shields.io/badge/license-apache%20v2-blue.svg)](https://github.com/KubeSphere/KubeSphere/blob/master/LICENSE) 3 | 4 | ---- 5 | 6 | ## Introduction 7 | Notification is an enterprise-grade general-purpose high-performance distribute notification system. 8 | 9 | The basic requirements for this system is below: 10 | 11 | 1.General Purpose 12 | 13 | 2.Different notification ways 14 | 15 | 3.Distribute, Asynchronous sending 16 | 17 | 4.Notification Address management 18 | 19 | It is plugin-driven and designed to support following notification ways: 20 | 21 | 1.Email 22 | 23 | 2.Websocket(WIP) 24 | 25 | 3.Wechat(todo) 26 | 27 | 4.SMS(todo) 28 | 29 | In the future it will provide more functions to support different notification ways. 30 | 31 | ## Installation: 32 | You can find the details in the [installation documents](doc/installation/allinone.md). 33 | 34 | 35 | 36 | ## Architecture Design 37 | 38 | ![Architecture](doc/images/notification.png) 39 | 40 | Notes: 41 | 42 | 1.Notification provides gRPC and RESTful api for third party call. 43 | 44 | 2.The Persistence Layer is Mysql. 45 | 46 | 3.Asynchronous sending Notification, need use MQ to temporarily store notification, using Redis or etcd queue. 47 | 48 | 49 | -------------------------------------------------------------------------------- /api/0.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | syntax = "proto3"; 6 | package notification; 7 | 8 | // set go package name to pb 9 | option go_package = "pb"; 10 | 11 | import "protoc-gen-swagger/options/annotations.proto"; 12 | 13 | // 0.pb.go define the swagger project metadata 14 | option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = { 15 | info: { 16 | title: "Notification Project"; 17 | version: "0.0.1"; 18 | contact: { 19 | name: "Notification Project"; 20 | url: "https://github.com/openpitrix/notification"; 21 | }; 22 | }; 23 | schemes: HTTP; 24 | schemes: HTTPS; 25 | consumes: "application/json"; 26 | produces: "application/json"; 27 | security_definitions: { 28 | security: { 29 | key: "BearerAuth"; 30 | value: { 31 | description: "The Authorization header must be set to Bearer followed by a space and a token. For example, 'Bearer vHUabiBEIKi8n1RdvWOjGFulGSM6zunb'."; 32 | type: TYPE_API_KEY; 33 | in: IN_HEADER; 34 | name: "Authorization"; 35 | } 36 | } 37 | } 38 | security: { 39 | security_requirement: { 40 | key: "BearerAuth"; 41 | value: {}; 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /api/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | 6 | PROTO_FILES=$(sort $(wildcard ./*.proto)) 7 | PROTOC_INC_PATH=/usr/local/include 8 | GOOGLEAPIS_PATH=third_party/googleapis 9 | GRPC_GATEWAY_PATH=third_party/github.com/grpc-ecosystem/grpc-gateway/ 10 | 11 | PROTOC_FLAGS:=-I. -I$(GOOGLEAPIS_PATH) -I$(GRPC_GATEWAY_PATH) -I$(PROTOC_INC_PATH) 12 | 13 | GOPATH:=$(shell go env GOPATH) 14 | PWD:=$(shell pwd) 15 | 16 | generate: $(PROTO_FILES) Makefile 17 | @rm -rf ../pkg/pb/* 18 | 19 | #To generate notification.pb.go file 20 | protoc $(PROTOC_FLAGS) --go_out=plugins=grpc:../pkg/pb ${PROTO_FILES} 21 | 22 | #To generate notification.pb.gw.go file 23 | protoc $(PROTOC_FLAGS) --grpc-gateway_out=logtostderr=true,allow_delete_body=true:../pkg/pb ${PROTO_FILES} 24 | 25 | #generate swagger.json file 26 | protoc $(PROTOC_FLAGS) --swagger_out=logtostderr=true,allow_delete_body=true:../pkg/apigateway/spec ${PROTO_FILES} 27 | 28 | cd ../pkg/apigateway/spec/ && cat ./*.swagger.json 0.swagger.json | jq --slurp 'reduce .[] as $$item ({}; . * $$item)' | jq -f ./preprocess.jq > ./api.swagger.json 29 | cd ../pkg/apigateway/spec/ && find . | grep .swagger.json | grep -v "api" | xargs rm 30 | 31 | make -C ../pkg/apigateway/spec 32 | make -C ../pkg/apigateway/swagger-ui 33 | 34 | goimports -l -w -e -local=openpitrix ../pkg/pb 35 | 36 | 37 | clean: 38 | rm -rf $(GOPATH)/src/openpitrix.io/notification/pkg/pb 39 | 40 | 41 | -------------------------------------------------------------------------------- /api/third_party/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options/annotations.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc.gateway.protoc_gen_swagger.options; 4 | 5 | option go_package = "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options"; 6 | 7 | import "protoc-gen-swagger/options/openapiv2.proto"; 8 | import "google/protobuf/descriptor.proto"; 9 | 10 | extend google.protobuf.FileOptions { 11 | // ID assigned by protobuf-globalcfg-extension-registry@google.com for grpc-gateway project. 12 | // 13 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 14 | // different descriptor messages. 15 | Swagger openapiv2_swagger = 1042; 16 | } 17 | extend google.protobuf.MethodOptions { 18 | // ID assigned by protobuf-globalcfg-extension-registry@google.com for grpc-gateway project. 19 | // 20 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 21 | // different descriptor messages. 22 | Operation openapiv2_operation = 1042; 23 | } 24 | extend google.protobuf.MessageOptions { 25 | // ID assigned by protobuf-globalcfg-extension-registry@google.com for grpc-gateway project. 26 | // 27 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 28 | // different descriptor messages. 29 | Schema openapiv2_schema = 1042; 30 | } 31 | extend google.protobuf.ServiceOptions { 32 | // ID assigned by protobuf-globalcfg-extension-registry@google.com for grpc-gateway project. 33 | // 34 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 35 | // different descriptor messages. 36 | Tag openapiv2_tag = 1042; 37 | } 38 | extend google.protobuf.FieldOptions { 39 | // ID assigned by protobuf-globalcfg-extension-registry@google.com for grpc-gateway project. 40 | // 41 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 42 | // different descriptor messages. 43 | JSONSchema openapiv2_field = 1042; 44 | } 45 | -------------------------------------------------------------------------------- /api/third_party/github.com/mwitkow/go-proto-validators/validator.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Michal Witkowski. All Rights Reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | // Protocol Buffers extensions for defining auto-generateable validators for messages. 5 | 6 | // TODO(mwitkow): Add example. 7 | 8 | 9 | syntax = "proto2"; 10 | package validator; 11 | 12 | import "google/protobuf/descriptor.proto"; 13 | 14 | option go_package = "validator"; 15 | 16 | // TODO(mwitkow): Email protobuf-globalcfg-extension-registry@google.com to get an extension ID. 17 | 18 | extend google.protobuf.FieldOptions { 19 | optional FieldValidator field = 65020; 20 | } 21 | 22 | message FieldValidator { 23 | // Uses a Golang RE2-syntax regex to match the field contents. 24 | optional string regex = 1; 25 | // Field value of integer strictly greater than this value. 26 | optional int64 int_gt = 2; 27 | // Field value of integer strictly smaller than this value. 28 | optional int64 int_lt = 3; 29 | // Used for nested message types, requires that the message type exists. 30 | optional bool msg_exists = 4; 31 | // Human error specifies a user-customizable error that is visible to the user. 32 | optional string human_error = 5; 33 | // Field value of double strictly greater than this value. 34 | // Note that this value can only take on a valid floating point 35 | // value. Use together with float_epsilon if you need something more specific. 36 | optional double float_gt = 6; 37 | // Field value of double strictly smaller than this value. 38 | // Note that this value can only take on a valid floating point 39 | // value. Use together with float_epsilon if you need something more specific. 40 | optional double float_lt = 7; 41 | // Field value of double describing the epsilon within which 42 | // any comparison should be considered to be true. For example, 43 | // when using float_gt = 0.35, using a float_epsilon of 0.05 44 | // would mean that any value above 0.30 is acceptable. It can be 45 | // thought of as a {float_value_condition} +- {float_epsilon}. 46 | // If unset, no correction for floating point inaccuracies in 47 | // comparisons will be attempted. 48 | optional double float_epsilon = 8; 49 | // Floating-point value compared to which the field content should be greater or equal. 50 | optional double float_gte = 9; 51 | // Floating-point value compared to which the field content should be smaller or equal. 52 | optional double float_lte = 10; 53 | // Used for string fields, requires the string to be not empty (i.e different from ""). 54 | optional bool string_not_empty = 11; 55 | // Repeated field with at least this number of elements. 56 | optional int64 repeated_count_min = 12; 57 | // Repeated field with at most this number of elements. 58 | optional int64 repeated_count_max = 13; 59 | // Field value of length greater than this value. 60 | optional int64 length_gt = 14; 61 | // Field value of length smaller than this value. 62 | optional int64 length_lt = 15; 63 | // Field value of integer strictly equal this value. 64 | optional int64 length_eq = 16; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /api/third_party/googleapis/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /api/types.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | syntax = "proto3"; 6 | package openpitrix; 7 | 8 | // set go package name to pb 9 | option go_package = "pb"; 10 | 11 | //import "google/protobuf/wrappers.proto"; 12 | //import "google/protobuf/timestamp.proto"; 13 | 14 | message ErrorDetail { 15 | string error_name = 1; 16 | string cause = 2; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /build/builder-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | 6 | FROM golang:1.12.7-alpine3.10 as builder 7 | 8 | RUN apk add --no-cache git curl openssl 9 | 10 | 11 | RUN export GO111MODULE=on; go get github.com/golang/protobuf/protoc-gen-go@v1.3.2 12 | RUN export GO111MODULE=on; go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.9.5 13 | RUN export GO111MODULE=on; go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v1.9.5 14 | RUN export GO111MODULE=on; go get golang.org/x/tools/cmd/goimports 15 | 16 | # swagger-0.13.0 17 | # RUN go get github.com/go-swagger/go-swagger/cmd/swagger 18 | RUN mkdir -p /swagger && cd /swagger \ 19 | && wget https://github.com/go-swagger/go-swagger/releases/download/0.13.0/swagger_linux_amd64 \ 20 | && chmod +x swagger_linux_amd64 && mv swagger_linux_amd64 /go/bin/swagger 21 | 22 | # the protoc can't run on alpine, 23 | # we only need the protobuf's stdarnd library in the `/protoc/include`. 24 | RUN mkdir -p /protoc && cd /protoc \ 25 | && wget https://github.com/google/protobuf/releases/download/v3.5.0/protoc-3.5.0-linux-x86_64.zip \ 26 | && unzip protoc-3.5.0-linux-x86_64.zip 27 | 28 | FROM golang:1.12.7-alpine3.10 29 | 30 | RUN apk add --no-cache git protobuf make curl openssl jq rsync upx 31 | 32 | COPY --from=builder /protoc/include /usr/local/include 33 | COPY --from=builder /go/bin /go/bin 34 | 35 | 36 | #docker build -t openpitrix/notification-builder:v1.0.0 . 37 | -------------------------------------------------------------------------------- /build/builder-docker/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | default: 6 | docker build -t openpitrix/notification-builder:v1.0.0 . 7 | @echo "ok" 8 | 9 | pull: 10 | docker pull openpitrix/notification-builder:v1.0.0 11 | @echo "ok" 12 | 13 | run: 14 | docker run --rm -it -v `pwd`:/root openpitrix/notification-builder:v1.0.0 15 | 16 | clean: 17 | @echo "ok" 18 | -------------------------------------------------------------------------------- /cmd/notification/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "openpitrix.io/notification/pkg/services/notification" 9 | ) 10 | 11 | func main() { 12 | notification.Serve() 13 | } 14 | -------------------------------------------------------------------------------- /deploy/db/mysql-pass.yaml: -------------------------------------------------------------------------------- 1 | kind: Secret 2 | apiVersion: v1 3 | metadata: 4 | name: mysql-pass 5 | namespace: kubesphere-alerting-system 6 | data: 7 | password: cGFzc3dvcmQ= 8 | type: Opaque 9 | -------------------------------------------------------------------------------- /deploy/db/mysql.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: notification-db 5 | namespace: kubesphere-alerting-system 6 | labels: 7 | app: notification 8 | tier: db 9 | version: v0.1.0 10 | spec: 11 | selector: 12 | app: notification 13 | tier: db 14 | ports: 15 | - name: tcp 16 | protocol: TCP 17 | port: 3306 18 | targetPort: 3306 19 | --- 20 | apiVersion: v1 21 | kind: PersistentVolumeClaim 22 | metadata: 23 | name: notification-db-pvc 24 | namespace: kubesphere-alerting-system 25 | labels: 26 | app: notification 27 | tier: db 28 | spec: 29 | accessModes: 30 | - ReadWriteOnce 31 | resources: 32 | requests: 33 | storage: 20Gi 34 | --- 35 | apiVersion: apps/v1beta2 36 | kind: Deployment 37 | metadata: 38 | name: notification-db-deployment 39 | namespace: kubesphere-alerting-system 40 | labels: 41 | app: notification 42 | tier: db 43 | version: v0.1.0 44 | spec: 45 | selector: 46 | matchLabels: 47 | app: notification 48 | tier: db 49 | strategy: 50 | type: Recreate 51 | template: 52 | metadata: 53 | labels: 54 | app: notification 55 | tier: db 56 | version: v0.1.0 57 | spec: 58 | containers: 59 | - name: notification-db 60 | image: mysql:8.0.11 61 | imagePullPolicy: IfNotPresent 62 | lifecycle: 63 | postStart: 64 | exec: 65 | command: ["sh", "-c", "rm -rf /var/lib/mysql/lost+found"] 66 | args: 67 | - --default-authentication-plugin=mysql_native_password 68 | - --binlog-expire-logs-seconds=604800 69 | - --max-binlog-size=1073741824 70 | - --max_allowed_packet=10485760 71 | - --max_connections=2000 72 | env: 73 | # $ kubectl create secret generic mysql-pass --from-file=password.txt 74 | # make sure password.txt does not have a trailing newline 75 | - name: MYSQL_ROOT_PASSWORD 76 | valueFrom: 77 | secretKeyRef: 78 | key: password 79 | name: mysql-pass 80 | - name: MYSQL_ROOT_HOST 81 | value: "%" 82 | ports: 83 | - containerPort: 3306 84 | name: mysql 85 | volumeMounts: 86 | - name: db-persistent-storage 87 | mountPath: /var/lib/mysql 88 | volumes: 89 | - name: db-persistent-storage 90 | persistentVolumeClaim: 91 | claimName: notification-db-pvc 92 | -------------------------------------------------------------------------------- /deploy/db/notification-db-ctrl-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: notification-db-ctrl-job 5 | namespace: kubesphere-alerting-system 6 | labels: 7 | app: notification 8 | job: notification-db-ctrl 9 | version: v0.1.0 10 | spec: 11 | backoffLimit: 6 12 | completions: 1 13 | parallelism: 1 14 | template: 15 | metadata: 16 | labels: 17 | app: notification 18 | job: notification-db-ctrl 19 | version: v0.1.0 20 | name: notification-db-ctrl 21 | spec: 22 | initContainers: 23 | - name: wait-mysql 24 | image: busybox:1.28.4 25 | imagePullPolicy: IfNotPresent 26 | command: ['sh', '-c', 'until nc -z notification-db.kubesphere-alerting-system.svc 3306; do echo "waiting for mysql"; sleep 2; done;'] 27 | containers: 28 | - command: ["flyway", "-X", "-url=jdbc:mysql://notification-db.kubesphere-alerting-system.svc/notification", "-user=root", "-validateOnMigrate=false", "-locations=filesystem:/flyway/sql/notification", "migrate"] 29 | env: 30 | - name: FLYWAY_PASSWORD 31 | valueFrom: 32 | secretKeyRef: 33 | key: password 34 | name: mysql-pass 35 | image: openpitrix/notification:flyway 36 | imagePullPolicy: Always 37 | name: notification-db-ctrl 38 | resources: {} 39 | terminationMessagePath: /dev/termination-log 40 | terminationMessagePolicy: File 41 | dnsPolicy: ClusterFirst 42 | restartPolicy: OnFailure 43 | schedulerName: default-scheduler 44 | securityContext: {} 45 | terminationGracePeriodSeconds: 30 46 | -------------------------------------------------------------------------------- /deploy/db/notification-db-init-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: notification-db-init-job 5 | namespace: kubesphere-alerting-system 6 | labels: 7 | app: notification 8 | job: notification-db-init 9 | version: v0.1.0 10 | spec: 11 | backoffLimit: 6 12 | completions: 1 13 | parallelism: 1 14 | template: 15 | metadata: 16 | labels: 17 | app: notification 18 | job: notification-db-init 19 | version: v0.1.0 20 | name: notification-db-init 21 | spec: 22 | initContainers: 23 | - name: wait-mysql 24 | image: busybox:1.28.4 25 | imagePullPolicy: IfNotPresent 26 | command: ['sh', '-c', 'until nc -z notification-db.kubesphere-alerting-system.svc 3306; do echo "waiting for mysql"; sleep 2; done;'] 27 | containers: 28 | - command: ["/flyway/sql/ddl/ddl_init.sh", "-hnotification-db.kubesphere-alerting-system.svc", "-uroot", "--connect-timeout=5"] 29 | env: 30 | - name: PASSWORD 31 | valueFrom: 32 | secretKeyRef: 33 | key: password 34 | name: mysql-pass 35 | image: openpitrix/notification:flyway 36 | imagePullPolicy: Always 37 | name: notification-db-init 38 | resources: {} 39 | terminationMessagePath: /dev/termination-log 40 | terminationMessagePolicy: File 41 | dnsPolicy: ClusterFirst 42 | restartPolicy: OnFailure 43 | schedulerName: default-scheduler 44 | securityContext: {} 45 | terminationGracePeriodSeconds: 30 46 | -------------------------------------------------------------------------------- /deploy/etcd/etcd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: notification-etcd 5 | namespace: kubesphere-alerting-system 6 | labels: 7 | app: notification 8 | tier: etcd 9 | version: v0.1.0 10 | spec: 11 | selector: 12 | app: notification 13 | tier: etcd 14 | ports: 15 | - name: tcp 16 | protocol: TCP 17 | port: 2379 18 | targetPort: 2379 19 | --- 20 | apiVersion: v1 21 | kind: PersistentVolumeClaim 22 | metadata: 23 | name: notification-etcd-pvc 24 | namespace: kubesphere-alerting-system 25 | labels: 26 | app: notification 27 | tier: etcd 28 | spec: 29 | accessModes: 30 | - ReadWriteOnce 31 | resources: 32 | requests: 33 | storage: 20Gi 34 | --- 35 | apiVersion: apps/v1beta2 36 | kind: Deployment 37 | metadata: 38 | name: notification-etcd-deployment 39 | namespace: kubesphere-alerting-system 40 | labels: 41 | app: notification 42 | tier: etcd 43 | version: v0.1.0 44 | spec: 45 | selector: 46 | matchLabels: 47 | app: notification 48 | tier: etcd 49 | strategy: 50 | type: Recreate 51 | template: 52 | metadata: 53 | labels: 54 | app: notification 55 | tier: etcd 56 | version: v0.1.0 57 | spec: 58 | containers: 59 | - name: notification-etcd 60 | image: quay.io/coreos/etcd:v3.2.18 61 | imagePullPolicy: IfNotPresent 62 | command: ["etcd", "--data-dir=/data", "--listen-client-urls=http://0.0.0.0:2379", "--advertise-client-urls=http://notification-etcd.kubesphere-alerting-system.svc:2379", "--max-snapshots=5", "--max-wals=5", "--auto-compaction-retention=168"] 63 | ports: 64 | - containerPort: 2379 65 | name: etcd 66 | volumeMounts: 67 | - name: etcd-persistent-storage 68 | mountPath: /data 69 | volumes: 70 | - name: etcd-persistent-storage 71 | persistentVolumeClaim: 72 | claimName: notification-etcd-pvc 73 | -------------------------------------------------------------------------------- /deploy/ks/notification.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: notification 6 | namespace: kubesphere-alerting-system 7 | labels: 8 | app: notification 9 | component: notification 10 | version: v0.1.0 11 | spec: 12 | selector: 13 | app: notification 14 | component: notification 15 | ports: 16 | - name: notification 17 | protocol: TCP 18 | port: 9201 19 | targetPort: 9201 20 | - name: swagger 21 | protocol: TCP 22 | port: 9200 23 | targetPort: 9200 24 | --- 25 | apiVersion: apps/v1beta2 26 | kind: Deployment 27 | metadata: 28 | name: notification-deployment 29 | namespace: kubesphere-alerting-system 30 | labels: 31 | app: notification 32 | component: notification 33 | version: v0.1.0 34 | spec: 35 | selector: 36 | matchLabels: 37 | app: notification 38 | component: notification 39 | replicas: 1 40 | template: 41 | metadata: 42 | labels: 43 | app: notification 44 | component: notification 45 | version: v0.1.0 46 | spec: 47 | initContainers: 48 | - name: wait-mysql 49 | image: busybox:1.28.4 50 | imagePullPolicy: IfNotPresent 51 | command: ['sh', '-c', 'until nc -z openpitrix-db.openpitrix-system.svc 3306; do echo "waiting for mysql"; sleep 2; done;'] 52 | - name: wait-redis 53 | image: busybox:1.28.4 54 | imagePullPolicy: IfNotPresent 55 | command: ['sh', '-c', 'until nc -z redis.kubesphere-system.svc 6379; do echo "waiting for redis"; sleep 2; done;'] 56 | - name: wait-etcd 57 | image: busybox:1.28.4 58 | imagePullPolicy: IfNotPresent 59 | command: ['sh', '-c', 'until nc -z openpitrix-etcd.openpitrix-system.svc 2379; do echo "waiting for etcd"; sleep 2; done;'] 60 | containers: 61 | - name: notification 62 | image: kubespheredev/notification:v0.2.1 63 | imagePullPolicy: Always 64 | command: 65 | - notification 66 | ports: 67 | - containerPort: 9201 68 | name: notification 69 | - containerPort: 9200 70 | name: swagger 71 | env: 72 | - name: NOTIFICATION_GRPC_SHOW_ERROR_CAUSE 73 | value: 'true' 74 | - name: NOTIFICATION_APP_HOST 75 | value: 'notification.kubesphere-alerting-system' 76 | - name: NOTIFICATION_APP_API_HOST 77 | value: 'notification.kubesphere-alerting-system' 78 | - name: NOTIFICATION_EMAIL_PROTOCOL 79 | value: 'SMTP' 80 | - name: NOTIFICATION_EMAIL_EMAIL_HOST 81 | value: 'mail.app-center.cn' 82 | - name: NOTIFICATION_EMAIL_PORT 83 | value: '25' 84 | - name: NOTIFICATION_EMAIL_DISPLAY_SENDER 85 | value: 'admin_openpitrix' 86 | - name: NOTIFICATION_EMAIL_EMAIL 87 | value: 'openpitrix@app-center.cn' 88 | - name: NOTIFICATION_EMAIL_PASSWORD 89 | value: 'openpitrix' 90 | - name: NOTIFICATION_EMAIL_SSL_ENABLE 91 | value: 'false' 92 | - name: NOTIFICATION_WEBSOCKET_SERVICE_MESSAGE_TYPES 93 | value: "none" 94 | - name: NOTIFICATION_QUEUE_TYPE 95 | value: "redis" 96 | - name: NOTIFICATION_QUEUE_ADDR 97 | value: "redis://redis.kubesphere-system.svc:6379" 98 | resources: 99 | limits: 100 | cpu: "1" 101 | memory: 1000Mi 102 | requests: 103 | cpu: 10m 104 | memory: 10Mi 105 | -------------------------------------------------------------------------------- /deploy/readme: -------------------------------------------------------------------------------- 1 | #部署Notification的顺序 2 | 1.mysql-pass.yaml 3 | 4 | 2.mysql.yaml 5 | 6 | 3.notification-db-init-job.yaml 7 | 8 | 4.notification-db-ctrl-job.yaml 9 | 10 | 5.etcd.yaml 11 | 12 | 6.redis.yaml 13 | 14 | 7.notification.yaml 15 | -------------------------------------------------------------------------------- /deploy/redis/redis.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: notification-redis 5 | namespace: kubesphere-alerting-system 6 | labels: 7 | app: notification 8 | tier: redis 9 | version: v0.1.0 10 | spec: 11 | selector: 12 | app: notification 13 | tier: redis 14 | ports: 15 | - name: tcp 16 | protocol: TCP 17 | port: 6379 18 | targetPort: 6379 19 | --- 20 | apiVersion: v1 21 | kind: PersistentVolumeClaim 22 | metadata: 23 | name: notification-redis-pvc 24 | namespace: kubesphere-alerting-system 25 | labels: 26 | app: notification 27 | tier: redis 28 | spec: 29 | accessModes: 30 | - ReadWriteOnce 31 | resources: 32 | requests: 33 | storage: 20Gi 34 | --- 35 | apiVersion: apps/v1beta2 36 | kind: Deployment 37 | metadata: 38 | name: notification-redis-deployment 39 | namespace: kubesphere-alerting-system 40 | labels: 41 | app: notification 42 | tier: redis 43 | version: v0.1.0 44 | spec: 45 | selector: 46 | matchLabels: 47 | app: notification 48 | tier: redis 49 | strategy: 50 | type: Recreate 51 | template: 52 | metadata: 53 | labels: 54 | app: notification 55 | tier: redis 56 | version: v0.1.0 57 | spec: 58 | containers: 59 | - name: notification-redis 60 | image: redis:4.0 61 | imagePullPolicy: IfNotPresent 62 | command: ["redis-server"] 63 | ports: 64 | - containerPort: 6379 65 | name: redis 66 | volumeMounts: 67 | - name: redis-persistent-storage 68 | mountPath: /data 69 | volumes: 70 | - name: redis-persistent-storage 71 | persistentVolumeClaim: 72 | claimName: notification-redis-pvc 73 | -------------------------------------------------------------------------------- /doc/images/db_design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openpitrix/notification/8b3cc8bc9b06d83d49cfda5369963bd284d1c71f/doc/images/db_design.png -------------------------------------------------------------------------------- /doc/images/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openpitrix/notification/8b3cc8bc9b06d83d49cfda5369963bd284d1c71f/doc/images/notification.png -------------------------------------------------------------------------------- /doc/images/swaggerUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openpitrix/notification/8b3cc8bc9b06d83d49cfda5369963bd284d1c71f/doc/images/swaggerUI.png -------------------------------------------------------------------------------- /doc/images/websocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openpitrix/notification/8b3cc8bc9b06d83d49cfda5369963bd284d1c71f/doc/images/websocket.png -------------------------------------------------------------------------------- /doc/installation/allinone.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "All-in-One 模式" 3 | --- 4 | 5 | All-in-One 模式部署由 [Docker-Compose](https://github.com/docker/compose) 的方式启动运行项目,Compose 能够定义和运行多个 Docker 容器的应用,允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project),通过子命令可对项目中的一组容器进行便捷地生命周期管理。 6 | 7 | ## 第一步: 准备环境 8 | 9 | ### 主机环境 10 | 11 | 需要准备一台满足最小资源要求的主机节点开始 `all-in-one` 模式的部署。 12 | 13 | | CPU | Memory | 磁盘 | 14 | |--------|---------|-------| 15 | | 1 核 | 1 G | 10 G | 16 | 17 | ### 软件环境 18 | 19 | `All-in-One` 模式需要依赖以下软件,请预先在主机中安装符合以下版本要求的软件: 20 | 21 | | 软件需求 | 最低版本 | 22 | | --- | --- | 23 | | [Docker](https://docs.docker.com/install/) | 18.03.0-ce | 24 | | [Docker-Compose](https://docs.docker.com/compose/install/) | 1.21.0 | 25 | | [Make](https://www.gnu.org/software/make/) | 3.81 | 26 | 27 | > 说明:若主机已安装 Kubernetes 环境,可能会造成 docker 容器之间网络不通。 28 | 29 | ## 第二步: 准备 Notification 源文件 30 | 31 | 可通过 git clone 命令从 GitHub 指定的 URL 下载 [Notification](https://github.com/openpitrix/notification) 的最新的源代码文件。 32 | 33 | ```bash 34 | $ git clone https://github.com/openpitrix/notification.git 35 | ``` 36 | 37 | ## 第三步: 部署 Notification 38 | 39 | 进入 解压后的 Notification 目录,编译项目。该过程需要拉取多个 Notification 相关的 docker 镜像,拉取镜像和安装速度与网络也有关系,需要等待几分钟。 40 | 41 | ```bash 42 | $ cd notification 43 | $ export GO111MODULE=on 44 | $ make generate 45 | $ make build 46 | $ make compose-up 47 | ``` 48 | 49 | ## 第四步: 验证 50 | 51 | 1. 查看所有容器的运行状况,正常情况下所有容器状态应该如下所示,确保 Notification 相关的镜像都已经成功创建: 52 | 53 | ```bash 54 | $ docker-compose ps 55 | Name Command State Ports 56 | ---------------------------------------------------------------------------------------------------------------------------- 57 | notification-db docker-entrypoint.sh --low ... Up 0.0.0.0:13306->3306/tcp 58 | notification-etcd etcd --data-dir /data --li ... Up 0.0.0.0:12379->2379/tcp, 2380/tcp 59 | notification-manager notification Up 0.0.0.0:9200->9200/tcp, 0.0.0.0:9201->9201/tcp 60 | notification-notification-db-ctrl flyway -url=jdbc:mysql://n ... Exit 1 61 | notification-redis docker-entrypoint.sh redis ... Up 0.0.0.0:6379->6379/tcp 62 | ``` 63 | 64 | 2. 您可以通过浏览器,使用运行该服务的服务器的 IP 地址和 SwaggerUI 端口号即 `:9200` 可以内部网络访问 SwaggerUI 页面,如 `http://192.168.0.4:9200/swagger-ui/`。 65 | 66 | 若需要在外网访问,在云平台需要在端口转发规则中将上述的**内网端口** 9200 转发到**源端口** 9200,然后在防火墙开放这个**源端口**,确保外网流量可以通过该端口。 67 | 68 | > 提示:例如在 QingCloud 平台配置端口转发和防火墙规则,则可以参考 [云平台配置端口转发和防火墙](https://openpitrix.io/docs/v0.4/zh-CN/appendix/qingcloud-manipulation)。 69 | 70 | ![swaggerUI](../images/swaggerUI.png) 71 | 72 | 3. 查看 API Gateway 服务 73 | 74 | Notification 部署成功后,可以在 SwaggerUI 界面下体验一下各个 API 的使用,了解各个 API 的参数设置和具体使用。 75 | 76 | Notification API 文档请参考 SwaggerUI 上详细的描述。 77 | 78 | 79 | ## 清理环境 80 | 81 | 若需要卸载 Notification 清理环境,在项目文件目录下,执行以下命令,停止并删除 Notification 所有服务,请谨慎操作。 82 | 83 | ```bash 84 | $ make clean 85 | ``` 86 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | version: '3' 6 | 7 | services: 8 | notification-db: 9 | image: "mysql:8.0.11" 10 | environment: 11 | - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} 12 | volumes: 13 | - ${DATA_PATH}/mysql:/var/lib/mysql 14 | - ./pkg/db/ddl:/docker-entrypoint-initdb.d 15 | command: --lower_case_table_names=0 --default-authentication-plugin=mysql_native_password --max_allowed_packet=10485760 --max_connections=256 --connect-timeout=5 16 | ports: 17 | - "13306:3306" # for unit-test & debug 18 | container_name: "notification-db" 19 | logging: 20 | driver: "json-file" 21 | options: 22 | max-size: ${NOTIFICATION_LOG_MAX_SIZE} 23 | max-file: ${NOTIFICATION_LOG_MAX_FILE} 24 | 25 | 26 | notification-db-ctrl: 27 | image: dhoer/flyway:5.1.4-mysql-8.0.11-alpine 28 | command: -url=jdbc:mysql://notification-db/notification -user=root -password=${MYSQL_ROOT_PASSWORD} -validateOnMigrate=false migrate 29 | volumes: 30 | - ./pkg/db/schema/notification:/flyway/sql 31 | links: 32 | - notification-db:notification-db 33 | depends_on: 34 | - notification-db 35 | container_name: "notification-db-ctrl" 36 | 37 | #notification redis 38 | notification-redis: 39 | image: "redis:5.0.5-alpine" 40 | volumes: 41 | - ${DATA_PATH}/redis:/data 42 | command: redis-server 43 | ports: 44 | - "16379:6379" 45 | depends_on: 46 | - notification-db-ctrl 47 | container_name: "notification-redis" 48 | 49 | # notification service 50 | notification-manager: 51 | #build: . 52 | image: "notification:latest" 53 | command: "notification" 54 | links: 55 | - notification-db:notification-db 56 | depends_on: 57 | - notification-db-ctrl 58 | - notification-redis 59 | container_name: "notification-manager" 60 | environment: 61 | - NOTIFICATION_LOG_LEVEL=${NOTIFICATION_LOG_LEVEL} 62 | - NOTIFICATION_GRPC_SHOW_ERROR_CAUSE=${NOTIFICATION_GRPC_SHOW_ERROR_CAUSE} 63 | - NOTIFICATION_MYSQL_DATABASE=notification 64 | - NOTIFICATION_MYSQL_LOG_MODE=false 65 | - NOTIFICATION_LOG_LEVEL=debug 66 | - NOTIFICATION_APP_MAX_WORKING_NOTIFICATIONS=5 67 | - NOTIFICATION_APP_MAX_WORKING_TASKS=5 68 | logging: 69 | driver: "json-file" 70 | options: 71 | max-size: ${NOTIFICATION_LOG_MAX_SIZE} 72 | max-file: ${NOTIFICATION_LOG_MAX_FILE} 73 | ports: 74 | - "9200:9200" 75 | - "9201:9201" 76 | 77 | -------------------------------------------------------------------------------- /docker_push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | Version=$1 4 | 5 | make build-image-${Version} 6 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin 7 | make push-image-${Version} -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module openpitrix.io/notification 2 | 3 | require ( 4 | github.com/bitly/go-simplejson v0.5.0 5 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect 6 | github.com/coreos/bbolt v1.3.2 // indirect 7 | github.com/coreos/etcd v3.3.13+incompatible 8 | github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect 9 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect 10 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 11 | github.com/fatih/camelcase v1.0.0 12 | github.com/fatih/structs v1.1.0 13 | github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 // indirect 14 | github.com/gin-gonic/gin v1.3.0 15 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect 16 | github.com/golang/protobuf v1.3.1 17 | github.com/google/gops v0.0.0-20180903072510-f341a40f99ec 18 | github.com/gorilla/websocket v1.4.0 19 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 20 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 21 | github.com/grpc-ecosystem/grpc-gateway v1.9.5 22 | github.com/jinzhu/gorm v1.9.11 23 | github.com/jonboulle/clockwork v0.1.0 // indirect 24 | github.com/json-iterator/go v1.1.5 // indirect 25 | github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect 26 | github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7 27 | github.com/mattn/go-isatty v0.0.4 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.1 // indirect 30 | github.com/onsi/ginkgo v1.8.0 // indirect 31 | github.com/onsi/gomega v1.5.0 // indirect 32 | github.com/pborman/uuid v1.2.0 33 | github.com/pkg/errors v0.8.1 34 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect 35 | github.com/sirupsen/logrus v1.3.0 // indirect 36 | github.com/soheilhy/cmux v0.1.4 // indirect 37 | github.com/sony/sonyflake v1.0.0 38 | github.com/speps/go-hashids v2.0.0+incompatible 39 | github.com/stretchr/testify v1.2.2 40 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect 41 | github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0 // indirect 42 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect 43 | go.etcd.io/bbolt v1.3.2 // indirect 44 | go.uber.org/atomic v1.3.2 // indirect 45 | go.uber.org/multierr v1.1.0 // indirect 46 | go.uber.org/zap v1.9.1 // indirect 47 | golang.org/x/net v0.0.0-20190520210107-018c4d40a106 // indirect 48 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 49 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 50 | google.golang.org/grpc v1.20.1 51 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect 52 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 53 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect 54 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df 55 | gopkg.in/mail.v2 v2.3.1 // indirect 56 | openpitrix.io/libqueue v0.4.1 57 | openpitrix.io/logger v0.1.0 58 | ) 59 | 60 | replace gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df => github.com/go-mail/mail v2.3.1+incompatible 61 | 62 | replace openpitrix.io/libqueue v0.4.1 => github.com/openpitrix/libqueue v0.4.1 63 | 64 | replace openpitrix.io/logger v0.1.0 => github.com/openpitrix/logger v0.1.0 65 | -------------------------------------------------------------------------------- /pkg/apigateway/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | default: 6 | cd spec && make 7 | cd swagger-ui && make 8 | 9 | clean: 10 | -cd spec && make clean 11 | -cd swagger-ui && make clean 12 | -------------------------------------------------------------------------------- /pkg/apigateway/spec/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | default: 6 | go run makestatic.go 7 | 8 | clean: 9 | -rm static.go 10 | -------------------------------------------------------------------------------- /pkg/apigateway/spec/makestatic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | // Command makestatic reads a set of files and writes a Go source file to "static.go" 8 | // that declares a map of string constants containing contents of the input files. 9 | // It is intended to be invoked via "go generate" (directive in "gen.go"). 10 | package main 11 | 12 | import ( 13 | "bytes" 14 | "fmt" 15 | "go/format" 16 | "io/ioutil" 17 | "os" 18 | "unicode/utf8" 19 | ) 20 | 21 | var files = []string{ 22 | "api.swagger.json", 23 | } 24 | 25 | func main() { 26 | if err := makestatic(); err != nil { 27 | fmt.Fprintln(os.Stderr, err) 28 | os.Exit(1) 29 | } 30 | } 31 | 32 | func makestatic() error { 33 | f, err := os.Create("static.go") 34 | if err != nil { 35 | return err 36 | } 37 | defer f.Close() 38 | buf := new(bytes.Buffer) 39 | fmt.Fprintf(buf, "%v\n\n%v\n\npackage static\n\n", license, warning) 40 | fmt.Fprintf(buf, "var Files = map[string]string{\n") 41 | for _, fn := range files { 42 | b, err := ioutil.ReadFile(fn) 43 | if err != nil { 44 | return err 45 | } 46 | fmt.Fprintf(buf, "\t%q: ", fn) 47 | if utf8.Valid(b) { 48 | fmt.Fprintf(buf, "`%s`", sanitize(b)) 49 | } else { 50 | fmt.Fprintf(buf, "%q", b) 51 | } 52 | fmt.Fprintln(buf, ",\n") 53 | } 54 | fmt.Fprintln(buf, "}") 55 | fmtbuf, err := format.Source(buf.Bytes()) 56 | if err != nil { 57 | return err 58 | } 59 | return ioutil.WriteFile("static.go", fmtbuf, 0666) 60 | } 61 | 62 | // sanitize prepares a valid UTF-8 string as a raw string constant. 63 | func sanitize(b []byte) []byte { 64 | // Replace ` with `+"`"+` 65 | b = bytes.Replace(b, []byte("`"), []byte("`+\"`\"+`"), -1) 66 | 67 | // Replace BOM with `+"\xEF\xBB\xBF"+` 68 | // (A BOM is valid UTF-8 but not permitted in Go source files. 69 | // I wouldn't bother handling this, but for some insane reason 70 | // jquery.js has a BOM somewhere in the middle.) 71 | return bytes.Replace(b, []byte("\xEF\xBB\xBF"), []byte("`+\"\\xEF\\xBB\\xBF\"+`"), -1) 72 | } 73 | 74 | const warning = `// Code generated by "makestatic"; DO NOT EDIT.` 75 | 76 | var license = `// Copyright 2018 The OpenPitrix Authors. All rights reserved. 77 | // Use of this source code is governed by a Apache license 78 | // that can be found in the LICENSE file. 79 | ` 80 | -------------------------------------------------------------------------------- /pkg/apigateway/spec/preprocess.jq: -------------------------------------------------------------------------------- 1 | def walk(f): 2 | . as $in 3 | | if type == "object" then 4 | reduce keys_unsorted[] as $key 5 | ( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f 6 | elif type == "array" then map( walk(f) ) | f 7 | else f 8 | end; 9 | 10 | walk( if type == "object" and .type == "array" and .in == "query" 11 | then .collectionFormat += "multi" 12 | else . 13 | end) 14 | -------------------------------------------------------------------------------- /pkg/apigateway/swagger-ui/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | default: 6 | go run makestatic.go 7 | 8 | clean: 9 | -rm static.go 10 | -------------------------------------------------------------------------------- /pkg/apigateway/swagger-ui/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openpitrix/notification/8b3cc8bc9b06d83d49cfda5369963bd284d1c71f/pkg/apigateway/swagger-ui/favicon-16x16.png -------------------------------------------------------------------------------- /pkg/apigateway/swagger-ui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openpitrix/notification/8b3cc8bc9b06d83d49cfda5369963bd284d1c71f/pkg/apigateway/swagger-ui/favicon-32x32.png -------------------------------------------------------------------------------- /pkg/apigateway/swagger-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /pkg/apigateway/swagger-ui/makestatic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | // +build ignore 6 | 7 | // Command makestatic reads a set of files and writes a Go source file to "static.go" 8 | // that declares a map of string constants containing contents of the input files. 9 | // It is intended to be invoked via "go generate" (directive in "gen.go"). 10 | package main 11 | 12 | import ( 13 | "bytes" 14 | "fmt" 15 | "go/format" 16 | "io/ioutil" 17 | "os" 18 | "unicode/utf8" 19 | ) 20 | 21 | var files = []string{ 22 | "favicon-16x16.png", 23 | "favicon-32x32.png", 24 | "index.html", 25 | "swagger-ui-bundle.js", 26 | "swagger-ui-standalone-preset.js", 27 | "swagger-ui.css", 28 | "swagger-ui.js", 29 | } 30 | 31 | func main() { 32 | if err := makestatic(); err != nil { 33 | fmt.Fprintln(os.Stderr, err) 34 | os.Exit(1) 35 | } 36 | } 37 | 38 | func makestatic() error { 39 | f, err := os.Create("static.go") 40 | if err != nil { 41 | return err 42 | } 43 | defer f.Close() 44 | buf := new(bytes.Buffer) 45 | fmt.Fprintf(buf, "%v\n\n%v\n\npackage static\n\n", license, warning) 46 | fmt.Fprintf(buf, "var Files = map[string]string{\n") 47 | for _, fn := range files { 48 | b, err := ioutil.ReadFile(fn) 49 | if err != nil { 50 | return err 51 | } 52 | fmt.Fprintf(buf, "\t%q: ", fn) 53 | if utf8.Valid(b) { 54 | fmt.Fprintf(buf, "`%s`", sanitize(b)) 55 | } else { 56 | fmt.Fprintf(buf, "%q", b) 57 | } 58 | fmt.Fprintln(buf, ",\n") 59 | } 60 | fmt.Fprintln(buf, "}") 61 | fmtbuf, err := format.Source(buf.Bytes()) 62 | if err != nil { 63 | return err 64 | } 65 | return ioutil.WriteFile("static.go", fmtbuf, 0666) 66 | } 67 | 68 | // sanitize prepares a valid UTF-8 string as a raw string constant. 69 | func sanitize(b []byte) []byte { 70 | // Replace ` with `+"`"+` 71 | b = bytes.Replace(b, []byte("`"), []byte("`+\"`\"+`"), -1) 72 | 73 | // Replace BOM with `+"\xEF\xBB\xBF"+` 74 | // (A BOM is valid UTF-8 but not permitted in Go source files. 75 | // I wouldn't bother handling this, but for some insane reason 76 | // jquery.js has a BOM somewhere in the middle.) 77 | return bytes.Replace(b, []byte("\xEF\xBB\xBF"), []byte("`+\"\\xEF\\xBB\\xBF\"+`"), -1) 78 | } 79 | 80 | const warning = `// Code generated by "makestatic"; DO NOT EDIT.` 81 | 82 | var license = `// Copyright 2018 The OpenPitrix Authors. All rights reserved. 83 | // Use of this source code is governed by a Apache license 84 | // that can be found in the LICENSE file. 85 | ` 86 | -------------------------------------------------------------------------------- /pkg/apigateway/swagger-ui/version.txt: -------------------------------------------------------------------------------- 1 | swagger-ui-3.2.2 2 | -------------------------------------------------------------------------------- /pkg/client/notification/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file 4 | 5 | package notification 6 | 7 | import ( 8 | "openpitrix.io/notification/pkg/config" 9 | "openpitrix.io/notification/pkg/manager" 10 | "openpitrix.io/notification/pkg/pb" 11 | ) 12 | 13 | type Client struct { 14 | pb.NotificationClient 15 | } 16 | 17 | func NewClient() (*Client, error) { 18 | cfg := config.GetInstance().LoadConf() 19 | conn, err := manager.NewClient(cfg.App.Host, cfg.App.Port) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &Client{ 24 | NotificationClient: pb.NewNotificationClient(conn), 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/ks_client/home-huojiao-ws_ks_event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/ks_client/home-huojiao-ws_ks_nf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/ks_client/home-system-ws_ks_event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/ks_client/home-system-ws_ks_nf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/op_client/home-huojiao-ws_op_event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/op_client/home-huojiao-ws_op_nf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/op_client/home-system-ws_op_event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/client/websocket/ws_client_test/op_client/home-system-ws_op_nf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat Example 5 | 59 | 96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 | -------------------------------------------------------------------------------- /pkg/config/_config_product.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package config 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "sync" 12 | 13 | "github.com/koding/multiconfig" 14 | "openpitrix.io/logger" 15 | 16 | "openpitrix.io/notification/pkg/constants" 17 | ) 18 | 19 | type Config struct { 20 | Log LogConfig 21 | Grpc GrpcConfig 22 | 23 | Mysql struct { 24 | Host string `default:"notification-db"` 25 | Port int `default:"3306"` 26 | User string `default:"root"` 27 | Password string `default:"password"` 28 | Database string `default:"notification"` 29 | Disable bool `default:"false"` 30 | LogMode bool `default:"false"` 31 | } 32 | 33 | Queue struct { 34 | Addr string `default:"redis://notification-redis:6379"` 35 | Type string `default:"redis"` 36 | } 37 | 38 | Email struct { 39 | Protocol string `default:"SMTP"` 40 | EmailHost string `default:"mail.app-center.cn"` 41 | Port int `default:"25"` 42 | DisplaySender string `default:"admin_openpitrix"` 43 | Email string `default:"openpitrix@app-center.cn"` 44 | Password string `default:"openpitrix"` 45 | SSLEnable bool `default:"false"` 46 | FromEmailAddr string `default:"openpitrix@app-center.cn"` 47 | } 48 | 49 | App struct { 50 | Host string `default:"notification-manager"` 51 | Port int `default:"9201"` 52 | 53 | ApiHost string `default:"notification-manager"` 54 | ApiPort int `default:"9200"` 55 | 56 | MaxWorkingNotifications int `default:"15"` 57 | MaxWorkingTasks int `default:"15"` 58 | MaxTaskRetryTimes int `default:"1"` 59 | } 60 | 61 | Websocket struct { 62 | //Service string `default:"op,ks"` 63 | Service string `default:"none"` 64 | } 65 | } 66 | 67 | var instance *Config 68 | 69 | var once sync.Once 70 | 71 | func GetInstance() *Config { 72 | once.Do(func() { 73 | instance = &Config{} 74 | instance.LoadConf() 75 | }) 76 | 77 | return instance 78 | } 79 | 80 | type LogConfig struct { 81 | //Level string `default:"error"` // debug, info, warn, error, fatal 82 | Level string `default:"info"` 83 | } 84 | 85 | type GrpcConfig struct { 86 | ShowErrorCause bool `default:"false"` // show grpc error cause to frontend 87 | } 88 | 89 | func (c *Config) PrintUsage() { 90 | fmt.Fprintf(os.Stdout, "Usage of %s:\n", os.Args[0]) 91 | flag.PrintDefaults() 92 | fmt.Fprint(os.Stdout, "\nSupported environment variables:\n") 93 | e := newLoader(constants.ServiceName) 94 | e.PrintEnvs(new(Config)) 95 | fmt.Println("") 96 | } 97 | 98 | func (c *Config) GetFlagSet() *flag.FlagSet { 99 | flag.CommandLine.Usage = c.PrintUsage 100 | return flag.CommandLine 101 | } 102 | 103 | func (c *Config) ParseFlag() { 104 | c.GetFlagSet().Parse(os.Args[1:]) 105 | } 106 | 107 | func (c *Config) LoadConf() *Config { 108 | c.ParseFlag() 109 | config := instance 110 | 111 | m := &multiconfig.DefaultLoader{} 112 | m.Loader = multiconfig.MultiLoader(newLoader(constants.ServiceName)) 113 | m.Validator = multiconfig.MultiValidator( 114 | &multiconfig.RequiredValidator{}, 115 | ) 116 | err := m.Load(config) 117 | if err != nil { 118 | panic(err) 119 | } 120 | 121 | loglevel := config.Log.Level 122 | logger.SetLevelByString(loglevel) 123 | logger.Debugf(nil, "load conf,config=[%+v]", config) 124 | return config 125 | } 126 | -------------------------------------------------------------------------------- /pkg/config/_config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package config 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "sync" 12 | 13 | "github.com/koding/multiconfig" 14 | "openpitrix.io/logger" 15 | 16 | "openpitrix.io/notification/pkg/constants" 17 | ) 18 | 19 | type Config struct { 20 | Log LogConfig 21 | Grpc GrpcConfig 22 | 23 | Mysql struct { 24 | //Host string `default:"notification-db"` 25 | //Port int `default:"3306"` 26 | Host string `default:"192.168.0.9"` 27 | Port int `default:"13306"` 28 | User string `default:"root"` 29 | Password string `default:"password"` 30 | Database string `default:"notification"` 31 | Disable bool `default:"false"` 32 | LogMode bool `default:"true"` 33 | } 34 | 35 | Queue struct { 36 | //Addr string `default:"redis://notification-redis:6379"` 37 | Type string `default:"redis"` 38 | Addr string `default:"redis://192.168.0.9:16379"` 39 | //Type string `default:"etcd"` 40 | //Addr string `default:"192.168.0.6:12379"` 41 | } 42 | 43 | Email struct { 44 | Protocol string `default:"SMTP"` 45 | EmailHost string `default:"mail.app-center.cn"` 46 | Port int `default:"25"` 47 | DisplaySender string `default:"admin_openpitrix"` 48 | Email string `default:"openpitrix@app-center.cn"` 49 | Password string `default:"openpitrix"` 50 | SSLEnable bool `default:"false"` 51 | FromEmailAddr string `default:"openpitrix@app-center.cn"` 52 | } 53 | 54 | App struct { 55 | Host string `default:"127.0.0.1"` 56 | Port int `default:"9201"` 57 | //Host string `default:"notification-manager"` 58 | //Port int `default:"9201"` 59 | 60 | ApiHost string `default:"127.0.0.1"` 61 | ApiPort int `default:"9200"` 62 | //ApiHost string `default:"notification-manager"` 63 | //ApiPort int `default:"9200"` 64 | 65 | MaxWorkingNotifications int `default:"15"` 66 | MaxWorkingTasks int `default:"15"` 67 | MaxTaskRetryTimes int `default:"1"` 68 | } 69 | 70 | Websocket struct { 71 | Service string `default:"op,ks"` 72 | } 73 | } 74 | 75 | var instance *Config 76 | 77 | var once sync.Once 78 | 79 | func GetInstance() *Config { 80 | once.Do(func() { 81 | instance = &Config{} 82 | instance.LoadConf() 83 | }) 84 | 85 | return instance 86 | } 87 | 88 | type LogConfig struct { 89 | //Level string `default:"error"` // debug, info, warn, error, fatal 90 | Level string `default:"info"` 91 | } 92 | 93 | type GrpcConfig struct { 94 | ShowErrorCause bool `default:"false"` // show grpc error cause to frontend 95 | } 96 | 97 | func (c *Config) PrintUsage() { 98 | fmt.Fprintf(os.Stdout, "Usage of %s:\n", os.Args[0]) 99 | flag.PrintDefaults() 100 | fmt.Fprint(os.Stdout, "\nSupported environment variables:\n") 101 | e := newLoader(constants.ServiceName) 102 | e.PrintEnvs(new(Config)) 103 | fmt.Println("") 104 | } 105 | 106 | func (c *Config) GetFlagSet() *flag.FlagSet { 107 | flag.CommandLine.Usage = c.PrintUsage 108 | return flag.CommandLine 109 | } 110 | 111 | func (c *Config) ParseFlag() { 112 | c.GetFlagSet().Parse(os.Args[1:]) 113 | } 114 | 115 | func (c *Config) LoadConf() *Config { 116 | c.ParseFlag() 117 | config := instance 118 | 119 | m := &multiconfig.DefaultLoader{} 120 | m.Loader = multiconfig.MultiLoader(newLoader(constants.ServiceName)) 121 | m.Validator = multiconfig.MultiValidator( 122 | &multiconfig.RequiredValidator{}, 123 | ) 124 | err := m.Load(config) 125 | if err != nil { 126 | panic(err) 127 | } 128 | 129 | loglevel := config.Log.Level 130 | logger.SetLevelByString(loglevel) 131 | logger.Debugf(nil, "load conf,config=[%+v]", config) 132 | return config 133 | } 134 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package config 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "sync" 12 | 13 | "github.com/koding/multiconfig" 14 | "openpitrix.io/logger" 15 | 16 | "openpitrix.io/notification/pkg/constants" 17 | ) 18 | 19 | type Config struct { 20 | Log LogConfig 21 | Grpc GrpcConfig 22 | 23 | Mysql struct { 24 | Host string `default:"notification-db"` 25 | Port int `default:"3306"` 26 | User string `default:"root"` 27 | Password string `default:"password"` 28 | Database string `default:"notification"` 29 | Disable bool `default:"false"` 30 | LogMode bool `default:"false"` 31 | } 32 | 33 | Queue struct { 34 | Addr string `default:"redis://notification-redis:6379"` 35 | Type string `default:"redis"` 36 | } 37 | 38 | Email struct { 39 | Protocol string `default:"SMTP"` 40 | EmailHost string `default:"mail.app-center.cn"` 41 | Port int `default:"25"` 42 | DisplaySender string `default:"admin_openpitrix"` 43 | Email string `default:"openpitrix@app-center.cn"` 44 | Password string `default:"openpitrix"` 45 | SSLEnable bool `default:"false"` 46 | FromEmailAddr string `default:"openpitrix@app-center.cn"` 47 | } 48 | 49 | App struct { 50 | Host string `default:"notification-manager"` 51 | Port int `default:"9201"` 52 | 53 | ApiHost string `default:"notification-manager"` 54 | ApiPort int `default:"9200"` 55 | 56 | MaxWorkingNotifications int `default:"15"` 57 | MaxWorkingTasks int `default:"15"` 58 | MaxTaskRetryTimes int `default:"1"` 59 | } 60 | 61 | Websocket struct { 62 | //Service string `default:"op,ks"` 63 | Service string `default:"none"` 64 | } 65 | } 66 | 67 | var instance *Config 68 | 69 | var once sync.Once 70 | 71 | func GetInstance() *Config { 72 | once.Do(func() { 73 | instance = &Config{} 74 | instance.LoadConf() 75 | }) 76 | 77 | return instance 78 | } 79 | 80 | type LogConfig struct { 81 | //Level string `default:"error"` // debug, info, warn, error, fatal 82 | Level string `default:"info"` 83 | } 84 | 85 | type GrpcConfig struct { 86 | ShowErrorCause bool `default:"false"` // show grpc error cause to frontend 87 | } 88 | 89 | func (c *Config) PrintUsage() { 90 | fmt.Fprintf(os.Stdout, "Usage of %s:\n", os.Args[0]) 91 | flag.PrintDefaults() 92 | fmt.Fprint(os.Stdout, "\nSupported environment variables:\n") 93 | e := newLoader(constants.ServiceName) 94 | e.PrintEnvs(new(Config)) 95 | fmt.Println("") 96 | } 97 | 98 | func (c *Config) GetFlagSet() *flag.FlagSet { 99 | flag.CommandLine.Usage = c.PrintUsage 100 | return flag.CommandLine 101 | } 102 | 103 | func (c *Config) ParseFlag() { 104 | c.GetFlagSet().Parse(os.Args[1:]) 105 | } 106 | 107 | func (c *Config) LoadConf() *Config { 108 | c.ParseFlag() 109 | config := instance 110 | 111 | m := &multiconfig.DefaultLoader{} 112 | m.Loader = multiconfig.MultiLoader(newLoader(constants.ServiceName)) 113 | m.Validator = multiconfig.MultiValidator( 114 | &multiconfig.RequiredValidator{}, 115 | ) 116 | err := m.Load(config) 117 | if err != nil { 118 | panic(err) 119 | } 120 | 121 | loglevel := config.Log.Level 122 | logger.SetLevelByString(loglevel) 123 | logger.Debugf(nil, "load conf,config=[%+v]", config) 124 | return config 125 | } 126 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package config 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "openpitrix.io/logger" 12 | ) 13 | 14 | func TestLoadConf(t *testing.T) { 15 | 16 | os.Setenv("NOTIFICATION_LOG_LEVEL", "debug") 17 | os.Setenv("NOTIFICATION_GRPC_SHOW_ERROR_CAUSE", "false") 18 | 19 | os.Setenv("NOTIFICATION_MYSQL_HOST", "MYSQL_HOST_test") 20 | os.Setenv("NOTIFICATION_MYSQL_PORT", "13306") 21 | 22 | os.Setenv("NOTIFICATION_APP_API_HOST", "TESTAPP_API_HOST") 23 | 24 | os.Setenv("NOTIFICATION_APP_MAX_WORKING_NOTIFICATIONS", "11") 25 | os.Setenv("NOTIFICATION_APP_MAX_WORKING_TASKS", "11") 26 | 27 | mycfg := GetInstance() 28 | mycfg.LoadConf() 29 | 30 | //loglevel := mycfg.Log.Level 31 | //logger.SetLevelByString(loglevel) 32 | 33 | logger.Debugf(nil, "Other=========================================") 34 | logger.Debugf(nil, "NOTIFICATION_LOG_LEVEL : %+v", mycfg.Log.Level) 35 | logger.Debugf(nil, "NOTIFICATION_GRPC_SHOW_ERROR_CAUSE : %+v", mycfg.Grpc.ShowErrorCause) 36 | logger.Debugf(nil, "") 37 | 38 | logger.Debugf(nil, "Mysql=========================================") 39 | logger.Debugf(nil, "NOTIFICATION_MYSQL_HOST : %+v", mycfg.Mysql.Host) 40 | logger.Debugf(nil, "NOTIFICATION_MYSQL_PORT : %+v", mycfg.Mysql.Port) 41 | logger.Debugf(nil, "NOTIFICATION_MYSQL_USER : %+v", mycfg.Mysql.User) 42 | logger.Debugf(nil, "NOTIFICATION_MYSQL_PASSWORD : %+v", mycfg.Mysql.Password) 43 | logger.Debugf(nil, "NOTIFICATION_MYSQL_DATABASE : %+v", mycfg.Mysql.Database) 44 | logger.Debugf(nil, "NOTIFICATION_MYSQL_DISABLE : %+v", mycfg.Mysql.Disable) 45 | logger.Debugf(nil, "NOTIFICATION_MYSQL_LOG_MODE : %+v", mycfg.Mysql.LogMode) 46 | logger.Debugf(nil, "") 47 | 48 | logger.Debugf(nil, "Queue=========================================") 49 | logger.Debugf(nil, "NOTIFICATION_QUEUE_TYPE : %+v", mycfg.Queue.Type) 50 | logger.Debugf(nil, "NOTIFICATION_QUEUE_ADDR : %+v", mycfg.Queue.Addr) 51 | logger.Debugf(nil, "") 52 | 53 | logger.Debugf(nil, "Email=========================================") 54 | logger.Debugf(nil, "NOTIFICATION_EMAIL_PROTOCOL : %+v", mycfg.Email.Protocol) 55 | logger.Debugf(nil, "NOTIFICATION_EMAIL_EMAIL_HOST : %+v", mycfg.Email.EmailHost) 56 | logger.Debugf(nil, "NOTIFICATION_EMAIL_PORT : %+v", mycfg.Email.Port) 57 | logger.Debugf(nil, "NOTIFICATION_EMAIL_DISPLAY_SENDER : %+v", mycfg.Email.DisplaySender) 58 | logger.Debugf(nil, "NOTIFICATION_EMAIL_EMAIL : %+v", mycfg.Email.Email) 59 | logger.Debugf(nil, "NOTIFICATION_EMAIL_PASSWORD : %+v", mycfg.Email.Password) 60 | logger.Debugf(nil, "NOTIFICATION_EMAIL_SSL_ENABLE : %+v", mycfg.Email.SSLEnable) 61 | logger.Debugf(nil, "") 62 | 63 | logger.Debugf(nil, "App=========================================") 64 | logger.Debugf(nil, "NOTIFICATION_APP_HOST : %+v", mycfg.App.Host) 65 | logger.Debugf(nil, "NOTIFICATION_APP_PORT : %+v", mycfg.App.Port) 66 | logger.Debugf(nil, "NOTIFICATION_APP_API_HOST : %+v", mycfg.App.ApiHost) 67 | logger.Debugf(nil, "NOTIFICATION_APP_API_PORT : %+v", mycfg.App.ApiPort) 68 | logger.Debugf(nil, "NOTIFICATION_APP_MAX_WORKING_NOTIFICATIONS : %+v", mycfg.App.MaxWorkingNotifications) 69 | logger.Debugf(nil, "NOTIFICATION_APP_MAX_WORKING_TASKS : %+v", mycfg.App.MaxWorkingTasks) 70 | logger.Debugf(nil, "NOTIFICATION_APP_MAX_TASK_RETRY_TIMES : %+v", mycfg.App.MaxTaskRetryTimes) 71 | logger.Debugf(nil, "") 72 | 73 | logger.Debugf(nil, "Websocket=========================================") 74 | logger.Debugf(nil, "NOTIFICATION_WEBSOCKET_SERVICE : %+v", mycfg.Websocket.Service) 75 | logger.Debugf(nil, "") 76 | 77 | mycfg.PrintUsage() 78 | 79 | } 80 | -------------------------------------------------------------------------------- /pkg/constants/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package constants 6 | 7 | const ( 8 | ServiceName = "Notification" 9 | ) 10 | 11 | const ( 12 | TagName = "json" 13 | ) 14 | 15 | const ( 16 | NotificationTaskTopicPrefix = "nf-task" 17 | NotificationTopicPrefix = "nf-job" 18 | MaxWorkingTasks = 5 19 | MaxWorkingNotifications = 5 20 | MaxTaskRetryTimes = 3 21 | ) 22 | 23 | const ( 24 | DESC = "desc" 25 | ASC = "asc" 26 | ) 27 | 28 | const ( 29 | DefaultOffset = uint32(0) 30 | DefaultLimit = uint32(20) 31 | ) 32 | 33 | const ( 34 | DefaultSelectLimit = 200 35 | ) 36 | 37 | const ( 38 | NotifyTypeEmail = "email" 39 | NotifyTypeWebsocket = "websocket" 40 | NotifyTypeSms = "sms" 41 | NotifyTypeWeChat = "wechat" 42 | ) 43 | 44 | var NotifyTypes = []string{ 45 | NotifyTypeEmail, 46 | NotifyTypeWebsocket, 47 | NotifyTypeSms, 48 | NotifyTypeWeChat, 49 | } 50 | 51 | const ( 52 | StatusPending = "pending" 53 | StatusSending = "sending" 54 | StatusSuccessful = "successful" 55 | StatusFailed = "failed" 56 | ) 57 | 58 | var NfStatuses = []string{ 59 | StatusPending, 60 | StatusSending, 61 | StatusSuccessful, 62 | StatusFailed, 63 | } 64 | 65 | var TaskStatuses = []string{ 66 | StatusPending, 67 | StatusSending, 68 | StatusSuccessful, 69 | StatusFailed, 70 | } 71 | 72 | const ( 73 | StatusActive = "active" 74 | StatusDisabled = "disabled" 75 | StatusDeleted = "deleted" 76 | ) 77 | 78 | var RecordStatuses = []string{ 79 | StatusActive, 80 | StatusDisabled, 81 | StatusDeleted, 82 | } 83 | 84 | const ( 85 | WsService = "ws_service" 86 | WsMessageType = "ws_message_type" 87 | WsMessagePrefix = "ws" 88 | ) 89 | 90 | const ( 91 | ContentFmt = "content_fmt" 92 | ContentFmtHtml = "html" 93 | ContentFmtNormal = "normal" 94 | ) 95 | 96 | const ( 97 | QueueTypeRedis = "redis" 98 | QueueTypeEtcd = "etcd" 99 | ) 100 | -------------------------------------------------------------------------------- /pkg/db/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | # Use of this source code is governed by a Apache license 3 | # that can be found in the LICENSE file. 4 | 5 | FROM dhoer/flyway:5.1.4-mysql-8.0.11-alpine 6 | 7 | RUN apk add --no-cache mysql-client 8 | 9 | COPY ./schema /flyway/sql 10 | COPY ./ddl /flyway/sql/ddl 11 | COPY ./scripts /flyway/sql/ddl 12 | -------------------------------------------------------------------------------- /pkg/db/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package db 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "sync" 11 | "time" 12 | 13 | "github.com/jinzhu/gorm" 14 | _ "github.com/jinzhu/gorm/dialects/mysql" 15 | 16 | "openpitrix.io/notification/pkg/config" 17 | ) 18 | 19 | /* 20 | * MysqlConnPool 21 | * use gorm 22 | */ 23 | type MysqlConnPool struct { 24 | } 25 | 26 | var instance *MysqlConnPool 27 | var once sync.Once 28 | 29 | var db *gorm.DB 30 | var err error 31 | 32 | func GetInstance() *MysqlConnPool { 33 | once.Do(func() { 34 | instance = &MysqlConnPool{} 35 | }) 36 | return instance 37 | } 38 | 39 | /* 40 | * @fuc init connection 41 | */ 42 | func (m *MysqlConnPool) InitDataPool() (isSucc bool) { 43 | cfg := config.GetInstance() 44 | 45 | var ( 46 | dbCfg = cfg.Mysql 47 | connectionString = fmt.Sprintf( 48 | "%v:%v@(%v:%v)/%v?charset=utf8&parseTime=True&loc=Local", 49 | dbCfg.User, 50 | dbCfg.Password, 51 | dbCfg.Host, 52 | dbCfg.Port, 53 | dbCfg.Database, 54 | ) 55 | ) 56 | 57 | db, err = gorm.Open("mysql", connectionString) 58 | if err != nil { 59 | log.Print(err) 60 | return false 61 | } 62 | 63 | err = db.DB().Ping() 64 | 65 | if err != nil { 66 | return false 67 | } 68 | 69 | db.DB().SetMaxIdleConns(100) 70 | db.DB().SetMaxOpenConns(100) 71 | db.DB().SetConnMaxLifetime(10 * time.Second) 72 | db.LogMode(cfg.Mysql.LogMode) 73 | 74 | // table name should be singular 75 | db.SingularTable(true) 76 | 77 | if err != nil { 78 | log.Fatal(err) 79 | return false 80 | } 81 | // share between goroutine, no need to close. 82 | // defer db.Close() 83 | return true 84 | } 85 | 86 | func (m *MysqlConnPool) GetMysqlDB() *gorm.DB { 87 | return db 88 | } 89 | -------------------------------------------------------------------------------- /pkg/db/db_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package db 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/jinzhu/gorm" 12 | "openpitrix.io/logger" 13 | 14 | pkg "openpitrix.io/notification/pkg" 15 | "openpitrix.io/notification/pkg/config" 16 | ) 17 | 18 | type Product struct { 19 | gorm.Model 20 | Code string 21 | Price uint 22 | } 23 | 24 | func TestGetMysqlDB(t *testing.T) { 25 | if !*pkg.LocalDevEnvEnabled { 26 | t.Skip("LocalDevEnv disabled") 27 | } 28 | 29 | logger.Debugf(nil, "step0.1:init params") 30 | config.GetInstance().LoadConf() 31 | 32 | logger.Debugf(nil, "step0.2:init db connection pool") 33 | isSucc := GetInstance().InitDataPool() 34 | if !isSucc { 35 | logger.Criticalf(nil, "init database pool failure...") 36 | os.Exit(1) 37 | } 38 | 39 | db = GetInstance().GetMysqlDB() 40 | 41 | } 42 | -------------------------------------------------------------------------------- /pkg/db/ddl/notification.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS notification DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; 2 | -------------------------------------------------------------------------------- /pkg/db/schema/notification/V0_1__init.sql: -------------------------------------------------------------------------------- 1 | /*==============================================================*/ 2 | /* Table: address */ 3 | /*==============================================================*/ 4 | create table address 5 | ( 6 | address_id varchar(50) not null, 7 | address_list_id varchar(50) not null, 8 | address varchar(200) not null, 9 | remarks varchar(200) null, 10 | verification_code varchar(50) null, 11 | create_time timestamp not null default CURRENT_TIMESTAMP, 12 | verify_time timestamp not null default CURRENT_TIMESTAMP, 13 | status_time timestamp not null default CURRENT_TIMESTAMP, 14 | notify_type varchar(50) not null comment 'web / mobile / email / sms', 15 | status varchar(50) not null, 16 | PRIMARY KEY (address_id) 17 | ); 18 | CREATE INDEX address_address_list_id_idx ON address (address_list_id); 19 | CREATE INDEX address_address_idx ON address (address); 20 | CREATE INDEX address_notify_type_idx ON address (notify_type); 21 | CREATE INDEX address_status_idx ON address (status); 22 | 23 | /*==============================================================*/ 24 | /* Table: address_list */ 25 | /*==============================================================*/ 26 | create table address_list 27 | ( 28 | address_list_id varchar(50) not null, 29 | name varchar(100) null, 30 | extra json null, 31 | status varchar(50) not null, 32 | create_time timestamp not null default CURRENT_TIMESTAMP, 33 | status_time timestamp not null default CURRENT_TIMESTAMP, 34 | PRIMARY KEY (address_list_id) 35 | ); 36 | CREATE INDEX address_list_status_idx ON address_list (status); 37 | 38 | /*==============================================================*/ 39 | /* Table: notification */ 40 | /*==============================================================*/ 41 | create table notification 42 | ( 43 | notification_id varchar(50) not null, 44 | content_type varchar(50) not null default '' comment ' network / fee / new feature etc. ', 45 | title varchar(255) not null, 46 | content text not null, 47 | short_content text not null comment 'used by sms, mobile', 48 | expired_days int not null default 0 comment 'expired days, 0 is for never', 49 | address_info json not null, 50 | owner varchar(50) not null, 51 | status varchar(50) not null comment 'pending / sending / successful / failed', 52 | create_time timestamp not null default CURRENT_TIMESTAMP, 53 | status_time timestamp not null default CURRENT_TIMESTAMP, 54 | PRIMARY KEY (notification_id) 55 | ); 56 | CREATE INDEX notification_content_type_idx ON notification (content_type); 57 | CREATE INDEX notification_owner_idx ON notification (owner); 58 | CREATE INDEX notification_status_idx ON notification (status); 59 | 60 | /*==============================================================*/ 61 | /* Table: nf_address_list */ 62 | /*==============================================================*/ 63 | create table nf_address_list 64 | ( 65 | nf_address_list_id varchar(50) not null, 66 | notification_id varchar(50) not null, 67 | address_list_id varchar(50) not null, 68 | PRIMARY KEY (nf_address_list_id) 69 | ); 70 | CREATE INDEX nf_address_list_notification_id_idx ON nf_address_list (notification_id); 71 | CREATE INDEX nf_address_list_address_list_id_idx ON nf_address_list (address_list_id); 72 | 73 | /*==============================================================*/ 74 | /* Table: task */ 75 | /*==============================================================*/ 76 | create table task 77 | ( 78 | task_id varchar(50) not null, 79 | notification_id varchar(50) not null, 80 | error_code int not null default 0, 81 | status varchar(50) not null comment 'pending / sending / successful / failed', 82 | create_time timestamp not null default CURRENT_TIMESTAMP, 83 | status_time timestamp not null default CURRENT_TIMESTAMP, 84 | directive json not null, 85 | PRIMARY KEY (task_id) 86 | ); 87 | CREATE INDEX task_notification_id_idx ON task (notification_id); 88 | CREATE INDEX task_status_idx ON task (status); 89 | CREATE INDEX task_error_code_idx ON task (error_code); 90 | -------------------------------------------------------------------------------- /pkg/db/schema/notification/V0_2__update.sql: -------------------------------------------------------------------------------- 1 | 2 | alter table address_list CHANGE name address_list_name varchar(50); 3 | alter table address_list alter column status SET DEFAULT 'active'; 4 | 5 | 6 | alter table address drop COLUMN address_list_id; 7 | 8 | create table address_list_binding 9 | ( 10 | binding_id varchar(50) not null, 11 | address_list_id varchar(50), 12 | address_id varchar(50), 13 | create_time timestamp default CURRENT_TIMESTAMP 14 | ); 15 | 16 | alter table address_list_binding 17 | add primary key (binding_id); 18 | 19 | 20 | 21 | alter table notification modify column short_content text null comment 'used by sms, mobile'; 22 | -------------------------------------------------------------------------------- /pkg/db/schema/notification/V0_3__add_available_time.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE notification 2 | ADD COLUMN available_start_time varchar(8) default ''; 3 | 4 | 5 | ALTER TABLE notification 6 | ADD COLUMN available_end_time varchar(8) default ''; 7 | -------------------------------------------------------------------------------- /pkg/db/schema/notification/V0_4__add_task_notify_type.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE task 2 | ADD COLUMN notify_type varchar(50) default 'email'; 3 | 4 | ALTER TABLE notification 5 | ADD COLUMN extra json; 6 | -------------------------------------------------------------------------------- /pkg/db/schema/notification/V0_5__email_config.sql: -------------------------------------------------------------------------------- 1 | /*==============================================================*/ 2 | /* Table: email_config */ 3 | /*==============================================================*/ 4 | create table email_config 5 | ( 6 | protocol varchar(30), 7 | email_host varchar(100), 8 | port int, 9 | display_sender varchar(100), 10 | email varchar(100), 11 | password varchar(100), 12 | ssl_enable bool, 13 | create_time timestamp not null default CURRENT_TIMESTAMP, 14 | status_time timestamp not null default CURRENT_TIMESTAMP 15 | ); 16 | 17 | 18 | INSERT INTO email_config (protocol,email_host,port,display_sender,email,password,ssl_enable) VALUES 19 | ('SMTP','mail.app-center.com.cn',25,'notification','admin@app-center.com.cn','password',0); 20 | 21 | -------------------------------------------------------------------------------- /pkg/db/schema/notification/V0_6__address_list_alterfield.sql: -------------------------------------------------------------------------------- 1 | 2 | alter table address_list modify column extra varchar(200); 3 | -------------------------------------------------------------------------------- /pkg/db/schema/notification/V0_7__add_fromemail.sql: -------------------------------------------------------------------------------- 1 | alter table email_config change email smtp_user_name varchar(100) default null; 2 | 3 | alter table email_config add from_email_addr varchar(100) default null; 4 | 5 | update email_config set from_email_addr=smtp_user_name; 6 | -------------------------------------------------------------------------------- /pkg/db/scripts/ddl_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd /flyway/sql/ddl 4 | 5 | [ -n "$PASSWORD" ] && OPT="-p$(echo "$PASSWORD" | tr -d '\n')" 6 | 7 | for F in $(ls *.sql) 8 | do 9 | echo "Start process $F" 10 | mysql "$@" "$OPT" < "$F" 11 | if [ $? -ne 0 ]; then 12 | echo "Process $F failed" 13 | return 1 14 | else 15 | echo "Process $F successful" 16 | fi 17 | done 18 | -------------------------------------------------------------------------------- /pkg/etcd/dlock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package etcd 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "openpitrix.io/logger" 12 | ) 13 | 14 | type callback func() error 15 | 16 | func (etcd *Etcd) Dlock(ctx context.Context, key string, cb callback) error { 17 | logger.Debugf(ctx, "Create dlock with key [%s]", key) 18 | mutex, err := etcd.NewMutex(key) 19 | if err != nil { 20 | logger.Criticalf(ctx, "Dlock lock error, failed to create mutex: %+v", err) 21 | return err 22 | } 23 | err = mutex.Lock(ctx) 24 | if err != nil { 25 | logger.Criticalf(ctx, "Dlock lock error, failed to lock mutex: %+v", err) 26 | return err 27 | } 28 | defer mutex.Unlock(ctx) 29 | err = cb() 30 | return err 31 | } 32 | 33 | func (etcd *Etcd) DlockWithTimeout(key string, timeout time.Duration, cb callback) error { 34 | ctxWithTimeout, cancel := context.WithTimeout(context.Background(), timeout) 35 | defer cancel() 36 | return etcd.Dlock(ctxWithTimeout, key, cb) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/etcd/etcd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package etcd 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/coreos/etcd/clientv3" 11 | "github.com/coreos/etcd/clientv3/namespace" 12 | ) 13 | 14 | type Etcd struct { 15 | *clientv3.Client 16 | } 17 | 18 | func Connect(endpoints []string, prefix string) (*Etcd, error) { 19 | cli, err := clientv3.New(clientv3.Config{ 20 | Endpoints: endpoints, 21 | DialTimeout: 5 * time.Second, 22 | }) 23 | if err != nil { 24 | return nil, err 25 | } 26 | cli.KV = namespace.NewKV(cli.KV, prefix) 27 | cli.Watcher = namespace.NewWatcher(cli.Watcher, prefix) 28 | cli.Lease = namespace.NewLease(cli.Lease, prefix) 29 | return &Etcd{cli}, err 30 | } 31 | -------------------------------------------------------------------------------- /pkg/etcd/etcd_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package etcd 6 | 7 | import ( 8 | "log" 9 | "testing" 10 | 11 | pkg "openpitrix.io/notification/pkg" 12 | ) 13 | 14 | func TestConnect(t *testing.T) { 15 | if !*pkg.LocalDevEnvEnabled { 16 | t.Skip("LocalDevEnv disabled") 17 | } 18 | endpoints := []string{"192.168.0.6:12379"} 19 | //endpoints := []string{"139.198.121.89:12379"} 20 | //endpoints := []string{"139.198.121.89:52379"} 21 | 22 | prefix := "test" 23 | e, err := Connect(endpoints, prefix) 24 | log.Println(e) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/etcd/mutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package etcd 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/coreos/etcd/clientv3/concurrency" 11 | ) 12 | 13 | type Mutex struct { 14 | *concurrency.Mutex 15 | } 16 | 17 | func (etcd *Etcd) NewMutex(key string) (*Mutex, error) { 18 | session, err := concurrency.NewSession(etcd.Client) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return &Mutex{concurrency.NewMutex(session, key)}, nil 23 | } 24 | 25 | // Lock locks the mutex with a cancelable context. If the context is canceled 26 | // while trying to acquire the lock, the mutex tries to clean its stale lock entry. 27 | func (m *Mutex) Lock(ctx context.Context) error { 28 | return m.Mutex.Lock(ctx) 29 | } 30 | 31 | func (m *Mutex) Unlock(ctx context.Context) error { 32 | return m.Mutex.Unlock(ctx) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/gerr/codes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package gerr 6 | 7 | import "google.golang.org/grpc/codes" 8 | 9 | const ( 10 | // OK is returned on success. 11 | OK = codes.OK 12 | 13 | // Canceled indicates the operation was canceled (typically by the caller). 14 | Canceled = codes.Canceled 15 | 16 | // Unknown error. An example of where this error may be returned is 17 | // if a Status value received from another address space belongs to 18 | // an error-space that is not known in this address space. Also 19 | // errors raised by APIs that do not return enough error information 20 | // may be converted to this error. 21 | Unknown = codes.Unknown 22 | 23 | // InvalidArgument indicates client specified an invalid argument. 24 | // Note that this differs from FailedPrecondition. It indicates arguments 25 | // that are problematic regardless of the state of the system 26 | // (e.g., a malformed file name). 27 | InvalidArgument = codes.InvalidArgument 28 | 29 | // DeadlineExceeded means operation expired before completion. 30 | // For operations that change the state of the system, this error may be 31 | // returned even if the operation has completed successfully. For 32 | // example, a successful response from a server could have been delayed 33 | // long enough for the deadline to expire. 34 | DeadlineExceeded = codes.DeadlineExceeded 35 | 36 | // NotFound means some requested entity (e.g., file or directory) was 37 | // not found. 38 | NotFound = codes.NotFound 39 | 40 | // AlreadyExists means an attempt to create an entity failed because one 41 | // already exists. 42 | AlreadyExists = codes.AlreadyExists 43 | 44 | // PermissionDenied indicates the caller does not have permission to 45 | // execute the specified operation. It must not be used for rejections 46 | // caused by exhausting some resource (use ResourceExhausted 47 | // instead for those errors). It must not be 48 | // used if the caller cannot be identified (use Unauthenticated 49 | // instead for those errors). 50 | PermissionDenied = codes.PermissionDenied 51 | 52 | // ResourceExhausted indicates some resource has been exhausted, perhaps 53 | // a per-user quota, or perhaps the entire file system is out of space. 54 | ResourceExhausted = codes.ResourceExhausted 55 | 56 | // FailedPrecondition indicates operation was rejected because the 57 | // system is not in a state required for the operation's execution. 58 | // For example, directory to be deleted may be non-empty, an rmdir 59 | // operation is applied to a non-directory, etc. 60 | // 61 | // A litmus test that may help a service implementor in deciding 62 | // between FailedPrecondition, Aborted, and Unavailable: 63 | // (a) Use Unavailable if the client can retry just the failing call. 64 | // (b) Use Aborted if the client should retry at a higher-level 65 | // (e.g., restarting a read-modify-write sequence). 66 | // (c) Use FailedPrecondition if the client should not retry until 67 | // the system state has been explicitly fixed. E.g., if an "rmdir" 68 | // fails because the directory is non-empty, FailedPrecondition 69 | // should be returned since the client should not retry unless 70 | // they have first fixed up the directory by deleting files from it. 71 | // (d) Use FailedPrecondition if the client performs conditional 72 | // REST Get/Update/Delete on a resource and the resource on the 73 | // server does not match the condition. E.g., conflicting 74 | // read-modify-write on the same resource. 75 | FailedPrecondition = codes.FailedPrecondition 76 | 77 | // Aborted indicates the operation was aborted, typically due to a 78 | // concurrency issue like sequencer check failures, transaction aborts, 79 | // etc. 80 | // 81 | // See litmus test above for deciding between FailedPrecondition, 82 | // Aborted, and Unavailable. 83 | Aborted = codes.Aborted 84 | 85 | // OutOfRange means operation was attempted past the valid range. 86 | // E.g., seeking or reading past end of file. 87 | // 88 | // Unlike InvalidArgument, this error indicates a problem that may 89 | // be fixed if the system state changes. For example, a 32-bit file 90 | // system will generate InvalidArgument if asked to read at an 91 | // offset that is not in the range [0,2^32-1], but it will generate 92 | // OutOfRange if asked to read from an offset past the current 93 | // file size. 94 | // 95 | // There is a fair bit of overlap between FailedPrecondition and 96 | // OutOfRange. We recommend using OutOfRange (the more specific 97 | // error) when it applies so that callers who are iterating through 98 | // a space can easily look for an OutOfRange error to detect when 99 | // they are done. 100 | OutOfRange = codes.OutOfRange 101 | 102 | // Unimplemented indicates operation is not implemented or not 103 | // supported/enabled in this service. 104 | Unimplemented = codes.Unimplemented 105 | 106 | // Internal errors. Means some invariants expected by underlying 107 | // system has been broken. If you see one of these errors, 108 | // something is very broken. 109 | Internal = codes.Internal 110 | 111 | // Unavailable indicates the service is currently unavailable. 112 | // This is a most likely a transient condition and may be corrected 113 | // by retrying with a backoff. 114 | // 115 | // See litmus test above for deciding between FailedPrecondition, 116 | // Aborted, and Unavailable. 117 | Unavailable = codes.Unavailable 118 | 119 | // DataLoss indicates unrecoverable data loss or corruption. 120 | DataLoss = codes.DataLoss 121 | 122 | // Unauthenticated indicates the request does not have valid 123 | // authentication credentials for the operation. 124 | Unauthenticated = codes.Unauthenticated 125 | ) 126 | -------------------------------------------------------------------------------- /pkg/gerr/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package gerr 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/pkg/errors" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | "openpitrix.io/logger" 15 | 16 | "openpitrix.io/notification/pkg/pb" 17 | ) 18 | 19 | const En = "en" 20 | const ZhCN = "zh_cn" 21 | const DefaultLocale = ZhCN 22 | 23 | func newStatus(ctx context.Context, code codes.Code, err error, errMsg ErrorMessage, a ...interface{}) *status.Status { 24 | locale := DefaultLocale 25 | 26 | s := status.New(code, errMsg.Message(locale, err, a...)) 27 | 28 | errorDetail := &pb.ErrorDetail{ErrorName: errMsg.Name} 29 | if err != nil { 30 | errorDetail.Cause = fmt.Sprintf("%+v", err) 31 | } 32 | logger.New().WithDepth(2).Errorf(ctx, "err: %+v, errMsg: %s", err, errMsg.Message(locale, err, a...)) 33 | 34 | sd, e := s.WithDetails(errorDetail) 35 | if e == nil { 36 | return sd 37 | } else { 38 | logger.New().WithDepth(2).Errorf(ctx, "%+v", errors.WithStack(e)) 39 | } 40 | return s 41 | } 42 | 43 | func ClearErrorCause(err error) error { 44 | if e, ok := status.FromError(err); ok { 45 | details := e.Details() 46 | if len(details) > 0 { 47 | detail := details[0] 48 | if d, ok := detail.(*pb.ErrorDetail); ok { 49 | d.Cause = "" 50 | // clear detail 51 | proto := e.Proto() 52 | proto.Details = proto.Details[:0] 53 | e = status.FromProto(proto) 54 | e, _ := e.WithDetails(d) 55 | return e.Err() 56 | } 57 | } 58 | } 59 | return err 60 | } 61 | 62 | type GRPCError interface { 63 | error 64 | GRPCStatus() *status.Status 65 | } 66 | 67 | func New(ctx context.Context, code codes.Code, errMsg ErrorMessage, a ...interface{}) GRPCError { 68 | return newStatus(ctx, code, nil, errMsg, a...).Err().(GRPCError) 69 | } 70 | 71 | func NewWithDetail(ctx context.Context, code codes.Code, err error, errMsg ErrorMessage, a ...interface{}) GRPCError { 72 | return newStatus(ctx, code, err, errMsg, a...).Err().(GRPCError) 73 | } 74 | 75 | func IsGRPCError(err error) bool { 76 | if e, ok := err.(GRPCError); ok && e != nil { 77 | return true 78 | } 79 | return false 80 | } 81 | -------------------------------------------------------------------------------- /pkg/global/global.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package global 6 | 7 | import ( 8 | "errors" 9 | "os" 10 | "sync" 11 | 12 | "github.com/google/gops/agent" 13 | "github.com/jinzhu/gorm" 14 | i "openpitrix.io/libqueue" 15 | qetcd "openpitrix.io/libqueue/etcd" 16 | q "openpitrix.io/libqueue/queue" 17 | qredis "openpitrix.io/libqueue/redis" 18 | "openpitrix.io/logger" 19 | 20 | "openpitrix.io/notification/pkg/config" 21 | "openpitrix.io/notification/pkg/constants" 22 | nfdb "openpitrix.io/notification/pkg/db" 23 | ) 24 | 25 | type GlobalCfg struct { 26 | cfg *config.Config 27 | database *gorm.DB 28 | queueClient *i.IClient 29 | pubsub *i.IPubSub 30 | } 31 | 32 | var instance *GlobalCfg 33 | var once sync.Once 34 | 35 | func GetInstance() *GlobalCfg { 36 | once.Do(func() { 37 | instance = newGlobalCfg() 38 | }) 39 | return instance 40 | } 41 | 42 | func newGlobalCfg() *GlobalCfg { 43 | cfg := config.GetInstance().LoadConf() 44 | g := &GlobalCfg{cfg: cfg} 45 | 46 | g.setLoggerLevel() 47 | g.openDatabase() 48 | g.setQueueClient() 49 | if config.GetInstance().Websocket.Service != "none" { 50 | _, err := g.setPubSub() 51 | if err != nil { 52 | logger.Errorf(nil, "Failed to set pubsub,err=%+v", err) 53 | } 54 | } 55 | 56 | if err := agent.Listen(agent.Options{ 57 | ShutdownCleanup: true, 58 | }); err != nil { 59 | logger.Criticalf(nil, "Failed to start gops agent") 60 | } 61 | return g 62 | } 63 | 64 | func (g *GlobalCfg) openDatabase() *GlobalCfg { 65 | if g.cfg.Mysql.Disable { 66 | logger.Debugf(nil, "%+s", "Database setting for Mysql.Disable is true.") 67 | return g 68 | } 69 | isSucc := nfdb.GetInstance().InitDataPool() 70 | 71 | if !isSucc { 72 | logger.Criticalf(nil, "%+s", "Init database pool failure...") 73 | os.Exit(1) 74 | } 75 | logger.Debugf(nil, "%+s", "Init database pool successfully.") 76 | 77 | db := nfdb.GetInstance().GetMysqlDB() 78 | g.database = db 79 | logger.Debugf(nil, "%+s", "Set globalcfg database value.") 80 | return g 81 | } 82 | 83 | func (g *GlobalCfg) setLoggerLevel() *GlobalCfg { 84 | AppLogMode := config.GetInstance().Log.Level 85 | logger.SetLevelByString(AppLogMode) 86 | logger.Infof(nil, "Set app log level to %+s", AppLogMode) 87 | return g 88 | } 89 | 90 | func (g *GlobalCfg) GetDB() *gorm.DB { 91 | return g.database 92 | } 93 | 94 | func (g *GlobalCfg) setQueueClient() *GlobalCfg { 95 | pubsubConnStr := g.cfg.Queue.Addr 96 | pubsubType := g.cfg.Queue.Type 97 | 98 | pubsubConfigMap := map[string]interface{}{ 99 | "connStr": pubsubConnStr, 100 | } 101 | 102 | qClient, err := q.NewIClient(pubsubType, pubsubConfigMap) 103 | if err != nil { 104 | logger.Errorf(nil, "Failed to connect %s pubsub server: %+v.", pubsubType, err) 105 | } 106 | 107 | g.queueClient = &qClient 108 | return g 109 | } 110 | 111 | func (g *GlobalCfg) GetQueueClient() *i.IClient { 112 | return g.queueClient 113 | } 114 | 115 | func (g *GlobalCfg) setPubSub() (*GlobalCfg, error) { 116 | queueType := config.GetInstance().Queue.Type 117 | var ipubsub i.IPubSub 118 | if queueType == constants.QueueTypeRedis { 119 | redisPubSub := qredis.RedisPubSub{} 120 | ipubsub = &redisPubSub 121 | } else if queueType == constants.QueueTypeEtcd { 122 | etcdPubSub := qetcd.EtcdPubSub{} 123 | ipubsub = &etcdPubSub 124 | } else { 125 | return nil, errors.New("Unsupport queue type, currently support redis and etcd.") 126 | } 127 | 128 | ipubsub.SetClient(g.queueClient) 129 | g.pubsub = &ipubsub 130 | return g, nil 131 | } 132 | 133 | func (g *GlobalCfg) GetPubSub() *i.IPubSub { 134 | return g.pubsub 135 | } 136 | -------------------------------------------------------------------------------- /pkg/global/global_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package global 6 | 7 | import ( 8 | "testing" 9 | 10 | pkg "openpitrix.io/notification/pkg" 11 | ) 12 | 13 | func TestSetGlobalCfg(t *testing.T) { 14 | if !*pkg.LocalDevEnvEnabled { 15 | t.Skip("Local Dev testing env disabled.") 16 | } 17 | GetInstance() 18 | } 19 | -------------------------------------------------------------------------------- /pkg/manager/checker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package manager 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/fatih/structs" 11 | "github.com/golang/protobuf/ptypes/wrappers" 12 | 13 | nfdb "openpitrix.io/notification/pkg/db" 14 | "openpitrix.io/notification/pkg/gerr" 15 | "openpitrix.io/notification/pkg/util/stringutil" 16 | ) 17 | 18 | type checker struct { 19 | ctx context.Context 20 | req nfdb.Request 21 | required []string 22 | stringChosen map[string][]string 23 | } 24 | 25 | func NewChecker(ctx context.Context, req nfdb.Request) *checker { 26 | return &checker{ 27 | ctx: ctx, 28 | req: req, 29 | required: []string{}, 30 | stringChosen: make(map[string][]string), 31 | } 32 | } 33 | 34 | func (c *checker) Required(params ...string) *checker { 35 | c.required = append(c.required, params...) 36 | return c 37 | } 38 | 39 | func (c *checker) checkRequired(param string, value interface{}) error { 40 | if len(c.required) > 0 && stringutil.StringIn(param, c.required) { 41 | switch v := value.(type) { 42 | case string: 43 | if v == "" { 44 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorMissingParameter, param) 45 | } 46 | case *wrappers.StringValue: 47 | if v == nil || v.GetValue() == "" { 48 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorMissingParameter, param) 49 | } 50 | case *wrappers.BytesValue: 51 | if v == nil || len(v.GetValue()) == 0 { 52 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorMissingParameter, param) 53 | } 54 | case []byte: 55 | if len(v) == 0 { 56 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorMissingParameter, param) 57 | } 58 | case []string: 59 | var values []string 60 | for _, v := range v { 61 | if v != "" { 62 | values = append(values, v) 63 | } 64 | } 65 | if len(values) == 0 { 66 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorMissingParameter, param) 67 | } 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func (c *checker) StringChosen(param string, chosen []string) *checker { 74 | if exist, ok := c.stringChosen[param]; ok { 75 | c.stringChosen[param] = append(exist, chosen...) 76 | } else { 77 | c.stringChosen[param] = chosen 78 | } 79 | return c 80 | } 81 | 82 | func (c *checker) checkStringChosen(param string, value interface{}) error { 83 | if len(c.stringChosen) > 0 { 84 | if chosen, ok := c.stringChosen[param]; ok { 85 | switch v := value.(type) { 86 | case string: 87 | if !stringutil.StringIn(v, chosen) { 88 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorUnsupportedParameterValue, param, v) 89 | } 90 | case *wrappers.StringValue: 91 | if v != nil { 92 | if !stringutil.StringIn(v.GetValue(), chosen) { 93 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorUnsupportedParameterValue, param, v.GetValue()) 94 | } 95 | } 96 | case []string: 97 | for _, s := range v { 98 | if !stringutil.StringIn(s, chosen) { 99 | return gerr.New(c.ctx, gerr.InvalidArgument, gerr.ErrorUnsupportedParameterValue, param, s) 100 | } 101 | } 102 | } 103 | } 104 | } 105 | return nil 106 | } 107 | 108 | func (c *checker) chainChecker(param string, value interface{}, checks ...func(string, interface{}) error) error { 109 | var err error 110 | for _, c := range checks { 111 | err = c(param, value) 112 | if err != nil { 113 | return err 114 | } 115 | } 116 | return nil 117 | } 118 | 119 | func (c *checker) Exec() error { 120 | for _, field := range structs.Fields(c.req) { 121 | param := nfdb.GetFieldName(field) 122 | value := field.Value() 123 | 124 | err := c.chainChecker(param, value, 125 | c.checkRequired, 126 | c.checkStringChosen, 127 | ) 128 | if err != nil { 129 | return err 130 | } 131 | } 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /pkg/manager/grpc_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package manager 6 | 7 | import ( 8 | "context" 9 | "crypto/tls" 10 | "fmt" 11 | "sync" 12 | "time" 13 | 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/credentials" 16 | "google.golang.org/grpc/keepalive" 17 | ) 18 | 19 | var ClientOptions = []grpc.DialOption{ 20 | grpc.WithInsecure(), 21 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 22 | Time: 30 * time.Second, 23 | Timeout: 10 * time.Second, 24 | PermitWithoutStream: true, 25 | }), 26 | } 27 | 28 | var clientCache sync.Map 29 | 30 | func NewClient(host string, port int) (*grpc.ClientConn, error) { 31 | endpoint := fmt.Sprintf("%s:%d", host, port) 32 | if conn, ok := clientCache.Load(endpoint); ok { 33 | return conn.(*grpc.ClientConn), nil 34 | } 35 | ctx := context.Background() 36 | conn, err := grpc.DialContext(ctx, endpoint, ClientOptions...) 37 | if err != nil { 38 | return nil, err 39 | } 40 | clientCache.Store(endpoint, conn) 41 | return conn, nil 42 | } 43 | 44 | func NewTLSClient(host string, port int, tlsConfig *tls.Config) (*grpc.ClientConn, error) { 45 | endpoint := fmt.Sprintf("%s:%d", host, port) 46 | if conn, ok := clientCache.Load(endpoint); ok { 47 | return conn.(*grpc.ClientConn), nil 48 | } 49 | creds := credentials.NewTLS(tlsConfig) 50 | tlsClientOptions := []grpc.DialOption{ 51 | grpc.WithTransportCredentials(creds), 52 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 53 | Time: 30 * time.Second, 54 | Timeout: 10 * time.Second, 55 | PermitWithoutStream: true, 56 | }), 57 | } 58 | conn, err := grpc.Dial(endpoint, tlsClientOptions...) 59 | if err != nil { 60 | return nil, err 61 | } 62 | clientCache.Store(endpoint, conn) 63 | return conn, nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/models/address.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | import ( 8 | "time" 9 | 10 | "openpitrix.io/notification/pkg/constants" 11 | "openpitrix.io/notification/pkg/pb" 12 | "openpitrix.io/notification/pkg/util/idutil" 13 | "openpitrix.io/notification/pkg/util/pbutil" 14 | ) 15 | 16 | type Address struct { 17 | AddressId string `gorm:"column:address_id"` 18 | Address string `gorm:"column:address"` 19 | Remarks string `gorm:"column:remarks"` 20 | VerificationCode string `gorm:"column:verification_code"` 21 | CreateTime time.Time `gorm:"column:create_time"` 22 | VerifyTime time.Time `gorm:"column:verify_time"` 23 | StatusTime time.Time `gorm:"column:status_time"` 24 | NotifyType string `gorm:"column:notify_type"` 25 | Status string `gorm:"column:status"` 26 | } 27 | 28 | type AddressWithListId struct { 29 | AddressId string `gorm:"column:address_id"` 30 | AddressListId string `gorm:"column:address_list_id"` 31 | Address string `gorm:"column:address"` 32 | Remarks string `gorm:"column:remarks"` 33 | VerificationCode string `gorm:"column:verification_code"` 34 | CreateTime time.Time `gorm:"column:create_time"` 35 | VerifyTime time.Time `gorm:"column:verify_time"` 36 | StatusTime time.Time `gorm:"column:status_time"` 37 | NotifyType string `gorm:"column:notify_type"` 38 | Status string `gorm:"column:status"` 39 | } 40 | 41 | const ( 42 | AddressIdPrefix = "addr-" 43 | ) 44 | 45 | //table name 46 | const ( 47 | TableAddress = "address" 48 | ) 49 | 50 | //field name 51 | //Addr is short for Address. 52 | const ( 53 | AddrColId = "address_id" 54 | AddrColAddress = "address" 55 | AddrColRemarks = "remarks" 56 | AddrColVerificationCode = "verification_code" 57 | AddrColCreateTime = "create_time" 58 | AddrColVerifyTime = "verify_time" 59 | AddrColStatusTime = "status_time" 60 | AddrColNotifyType = "notify_type" 61 | AddrColStatus = "status" 62 | ) 63 | 64 | func NewAddressId() string { 65 | return idutil.GetUuid(AddressIdPrefix) 66 | } 67 | 68 | func NewAddress(req *pb.CreateAddressRequest) *Address { 69 | address := &Address{ 70 | AddressId: NewAddressId(), 71 | Address: req.GetAddress().GetValue(), 72 | Remarks: req.GetRemarks().GetValue(), 73 | VerificationCode: req.GetVerificationCode().GetValue(), 74 | CreateTime: time.Now(), 75 | VerifyTime: time.Now(), 76 | StatusTime: time.Now(), 77 | NotifyType: req.GetNotifyType().GetValue(), 78 | Status: constants.StatusActive, 79 | } 80 | return address 81 | } 82 | 83 | func AddressWithListIdToPb(addressWithListId *AddressWithListId) *pb.Address { 84 | pbAddress := pb.Address{} 85 | pbAddress.AddressId = pbutil.ToProtoString(addressWithListId.AddressId) 86 | pbAddress.AddressListId = pbutil.ToProtoString(addressWithListId.AddressListId) 87 | pbAddress.Address = pbutil.ToProtoString(addressWithListId.Address) 88 | pbAddress.Remarks = pbutil.ToProtoString(addressWithListId.Remarks) 89 | pbAddress.VerificationCode = pbutil.ToProtoString(addressWithListId.VerificationCode) 90 | pbAddress.Status = pbutil.ToProtoString(addressWithListId.Status) 91 | pbAddress.CreateTime = pbutil.ToProtoTimestamp(addressWithListId.CreateTime) 92 | pbAddress.VerifyTime = pbutil.ToProtoTimestamp(addressWithListId.VerifyTime) 93 | pbAddress.StatusTime = pbutil.ToProtoTimestamp(addressWithListId.StatusTime) 94 | pbAddress.NotifyType = pbutil.ToProtoString(addressWithListId.NotifyType) 95 | return &pbAddress 96 | } 97 | 98 | func AddressWithListIdSet2PbSet(inAddrs []*AddressWithListId) []*pb.Address { 99 | var pbAddrs []*pb.Address 100 | for _, inAddr := range inAddrs { 101 | pbAddr := AddressWithListIdToPb(inAddr) 102 | pbAddrs = append(pbAddrs, pbAddr) 103 | } 104 | return pbAddrs 105 | } 106 | -------------------------------------------------------------------------------- /pkg/models/address_info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | import ( 8 | "openpitrix.io/logger" 9 | 10 | "openpitrix.io/notification/pkg/util/jsonutil" 11 | ) 12 | 13 | type AddressInfo map[string][]string 14 | 15 | type AddressListIds []string 16 | 17 | func DecodeAddressInfo(data string) (*AddressInfo, error) { 18 | addressInfo := new(AddressInfo) 19 | err := jsonutil.Decode([]byte(data), addressInfo) 20 | if err != nil { 21 | logger.Warnf(nil, "Try to decode as format[{\"email\": [\"xxx@abc.com\", \"xxx@xxx.com\"]}], decode [%s] into address info failed: %+v", data, err) 22 | } 23 | return addressInfo, err 24 | } 25 | 26 | func DecodeAddressListIds(data string) (*AddressListIds, error) { 27 | addressListIds := new(AddressListIds) 28 | err := jsonutil.Decode([]byte(data), addressListIds) 29 | if err != nil { 30 | logger.Warnf(nil, "Try to decode as format[\"adl-xxxxxx\"], decode [%s] into address list ids failed: %+v", data, err) 31 | } 32 | return addressListIds, err 33 | } 34 | -------------------------------------------------------------------------------- /pkg/models/address_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | import ( 8 | "time" 9 | 10 | "openpitrix.io/logger" 11 | 12 | "openpitrix.io/notification/pkg/constants" 13 | "openpitrix.io/notification/pkg/pb" 14 | "openpitrix.io/notification/pkg/util/idutil" 15 | "openpitrix.io/notification/pkg/util/jsonutil" 16 | "openpitrix.io/notification/pkg/util/pbutil" 17 | ) 18 | 19 | type AddressList struct { 20 | AddressListId string `gorm:"column:address_list_id"` 21 | AddressListName string `gorm:"column:address_list_name"` 22 | Extra string `gorm:"column:extra"` 23 | Status string `gorm:"column:status"` 24 | CreateTime time.Time `gorm:"column:create_time"` 25 | StatusTime time.Time `gorm:"column:status_time"` 26 | } 27 | 28 | const ( 29 | AddressListIdPrefix = "adl-" 30 | ) 31 | 32 | //table name 33 | const ( 34 | TableAddressList = "address_list" 35 | ) 36 | 37 | //field name 38 | //AddrLs is short for AddressList. 39 | const ( 40 | AddrLsColId = "address_list_id" 41 | AddrLsColName = "address_list_name" 42 | AddrLsColExtra = "extra" 43 | AddrLsColStatus = "status" 44 | AddrLsColCreateTime = "create_time" 45 | AddrLsColStatusTime = "status_time" 46 | ) 47 | 48 | func NewAddressListId() string { 49 | return idutil.GetUuid(AddressListIdPrefix) 50 | } 51 | 52 | func NewAddressList(req *pb.CreateAddressListRequest) *AddressList { 53 | extra := req.GetExtra().GetValue() 54 | if extra == "" { 55 | extra = "{}" 56 | } 57 | addressList := &AddressList{ 58 | AddressListId: NewAddressListId(), 59 | AddressListName: req.GetAddressListName().GetValue(), 60 | Extra: extra, 61 | Status: constants.StatusActive, 62 | CreateTime: time.Now(), 63 | StatusTime: time.Now(), 64 | } 65 | return addressList 66 | } 67 | 68 | func AddressListToPb(addressList *AddressList) *pb.AddressList { 69 | pbAddressList := pb.AddressList{} 70 | pbAddressList.AddressListId = pbutil.ToProtoString(addressList.AddressListId) 71 | pbAddressList.AddressListName = pbutil.ToProtoString(addressList.AddressListName) 72 | pbAddressList.Extra = pbutil.ToProtoString(addressList.Extra) 73 | pbAddressList.Status = pbutil.ToProtoString(addressList.Status) 74 | pbAddressList.StatusTime = pbutil.ToProtoTimestamp(addressList.StatusTime) 75 | pbAddressList.CreateTime = pbutil.ToProtoTimestamp(addressList.CreateTime) 76 | 77 | return &pbAddressList 78 | } 79 | 80 | func AddressListSet2PbSet(inAddrLists []*AddressList) []*pb.AddressList { 81 | var pbAddrsLists []*pb.AddressList 82 | for _, inAddrList := range inAddrLists { 83 | pbAddrList := AddressListToPb(inAddrList) 84 | pbAddrsLists = append(pbAddrsLists, pbAddrList) 85 | } 86 | return pbAddrsLists 87 | } 88 | 89 | func DecodeExtra(data string) (*map[string]string, error) { 90 | extra := new(map[string]string) 91 | err := jsonutil.Decode([]byte(data), extra) 92 | if err != nil { 93 | logger.Errorf(nil, "Decode [%s] into notification extra failed: %+v", data, err) 94 | } 95 | return extra, err 96 | } 97 | -------------------------------------------------------------------------------- /pkg/models/address_list_binding.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | import ( 8 | "time" 9 | 10 | "openpitrix.io/notification/pkg/util/idutil" 11 | ) 12 | 13 | type AddressListBinding struct { 14 | BindingId string `gorm:"column:binding_id"` 15 | AddressListId string `gorm:"column:address_list_id"` 16 | AddressId string `gorm:"column:address_id"` 17 | CreateTime time.Time `gorm:"column:create_time"` 18 | } 19 | 20 | const ( 21 | AddressListBindingIdPrefix = "bid-" 22 | ) 23 | 24 | //table name 25 | const ( 26 | TableAddressListBinding = "address_list_binding" 27 | ) 28 | 29 | //field name 30 | //Bind is short for AddressListBinding. 31 | const ( 32 | BindColId = "binding_id" 33 | BindColAddrListId = "address_list_id" 34 | BindColAddrId = "address_id" 35 | BindColCreateTime = "create_time" 36 | ) 37 | 38 | func NewAddressListBindingId() string { 39 | return idutil.GetUuid(AddressListBindingIdPrefix) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/models/content.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "openpitrix.io/logger" 5 | 6 | "openpitrix.io/notification/pkg/gerr" 7 | "openpitrix.io/notification/pkg/util/jsonutil" 8 | ) 9 | 10 | type Content map[string]string 11 | 12 | func DecodeContent(data string) (*Content, error) { 13 | content := new(Content) 14 | err := jsonutil.Decode([]byte(data), content) 15 | if err != nil { 16 | logger.Warnf(nil, "Try to decode as format[{\"content_type\": \"content\"}], decode [%s] into content failed: %+v", data, err) 17 | return nil, gerr.NewWithDetail(nil, gerr.InvalidArgument, err, gerr.ErrorDecodeContentFailed) 18 | } 19 | return content, err 20 | } 21 | -------------------------------------------------------------------------------- /pkg/models/content_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "testing" 5 | 6 | "openpitrix.io/logger" 7 | 8 | pkg "openpitrix.io/notification/pkg" 9 | "openpitrix.io/notification/pkg/config" 10 | "openpitrix.io/notification/pkg/constants" 11 | ) 12 | 13 | func TestDecodeContent(t *testing.T) { 14 | if !*pkg.LocalDevEnvEnabled { 15 | t.Skip("Local Dev testing env disabled.") 16 | } 17 | config.GetInstance().LoadConf() 18 | 19 | testContentStr := "{\"html\":\"register_content_html\", \"normal\":\"register_content_normal\"}" 20 | 21 | contentStruct, err := DecodeContent(testContentStr) 22 | if err != nil { 23 | logger.Errorf(nil, "error=[%+v]", err) 24 | } 25 | 26 | content2SendHtml := "" 27 | content, ok := (*contentStruct)[constants.ContentFmtHtml] 28 | if ok { 29 | content2SendHtml = content 30 | } 31 | logger.Debugf(nil, "content2SendHtml=[%s]", content2SendHtml) 32 | 33 | content2SendNormal := "" 34 | content, ok = (*contentStruct)[constants.ContentFmtNormal] 35 | if ok { 36 | content2SendNormal = content 37 | } 38 | logger.Debugf(nil, "content2SendNormal=[%s]", content2SendNormal) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /pkg/models/email_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | package models 5 | 6 | import ( 7 | "time" 8 | 9 | "openpitrix.io/notification/pkg/pb" 10 | "openpitrix.io/notification/pkg/util/pbutil" 11 | ) 12 | 13 | type EmailConfig struct { 14 | Protocol string `gorm:"column:protocol"` 15 | EmailHost string `gorm:"column:email_host"` 16 | Port uint32 `gorm:"column:port"` 17 | DisplaySender string `gorm:"column:display_sender"` 18 | Email string `gorm:"column:smtp_user_name"` 19 | Password string `gorm:"column:password"` 20 | SSLEnable bool `gorm:"column:ssl_enable"` 21 | CreateTime time.Time `gorm:"column:create_time"` 22 | StatusTime time.Time `gorm:"column:status_time"` 23 | FromEmailAddr string `gorm:"column:from_email_addr"` 24 | } 25 | 26 | //table name 27 | const ( 28 | TableEmailConfig = "email_config" 29 | ) 30 | 31 | //field name 32 | const ( 33 | EmailCfgColProtocol = "protocol" 34 | EmailCfgColEmailHost = "email_host" 35 | EmailCfgColPort = "port" 36 | EmailCfgColDisplaySender = "display_sender" 37 | EmailCfgColEmail = "smtp_user_name" 38 | EmailCfgColPassword = "password" 39 | EmailCfgColSSLEnable = "ssl_enable" 40 | EmailCfgColCreateTime = "create_time" 41 | EmailCfgColStatusTime = "status_time" 42 | EmailCfgColFromEmailAddr = "from_email_addr" 43 | ) 44 | 45 | func NewEmailConfig(req *pb.ServiceConfig) *EmailConfig { 46 | emailCfg := &EmailConfig{ 47 | Protocol: req.GetEmailServiceConfig().GetProtocol().GetValue(), 48 | EmailHost: req.GetEmailServiceConfig().GetEmailHost().GetValue(), 49 | Port: req.GetEmailServiceConfig().GetPort().GetValue(), 50 | DisplaySender: req.GetEmailServiceConfig().GetDisplaySender().GetValue(), 51 | Email: req.GetEmailServiceConfig().GetEmail().GetValue(), 52 | Password: req.GetEmailServiceConfig().GetPassword().GetValue(), 53 | SSLEnable: req.GetEmailServiceConfig().GetSslEnable().GetValue(), 54 | FromEmailAddr: req.GetEmailServiceConfig().GetFromEmailAddr().GetValue(), 55 | CreateTime: time.Now(), 56 | StatusTime: time.Now(), 57 | } 58 | return emailCfg 59 | } 60 | 61 | func EmailConfigToPb(emailConfig *EmailConfig) *pb.EmailServiceConfig { 62 | pbEmailConfig := pb.EmailServiceConfig{} 63 | pbEmailConfig.Protocol = pbutil.ToProtoString(emailConfig.Protocol) 64 | pbEmailConfig.EmailHost = pbutil.ToProtoString(emailConfig.EmailHost) 65 | pbEmailConfig.Port = pbutil.ToProtoUInt32(emailConfig.Port) 66 | pbEmailConfig.DisplaySender = pbutil.ToProtoString(emailConfig.DisplaySender) 67 | pbEmailConfig.Email = pbutil.ToProtoString(emailConfig.Email) 68 | pbEmailConfig.Password = pbutil.ToProtoString(emailConfig.Password) 69 | pbEmailConfig.SslEnable = pbutil.ToProtoBool(emailConfig.SSLEnable) 70 | pbEmailConfig.FromEmailAddr = pbutil.ToProtoString(emailConfig.FromEmailAddr) 71 | return &pbEmailConfig 72 | } 73 | -------------------------------------------------------------------------------- /pkg/models/nf_address_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | import "openpitrix.io/notification/pkg/util/idutil" 8 | 9 | type NFAddressList struct { 10 | NFAddressListId string `gorm:"column:nf_address_list_id"` 11 | NotificationId string `gorm:"column:notification_id"` 12 | AddressListId string `gorm:"column:address_list_id"` 13 | } 14 | 15 | const ( 16 | NFAddressListIdPrefix = "nfa-" 17 | ) 18 | 19 | //table name 20 | const ( 21 | TableNFAddressList = "nf_address_list" 22 | ) 23 | 24 | //field name 25 | //NFAddrLs is short for NFAddressList. 26 | const ( 27 | NFAddrLsColId = "nf_address_list_id" 28 | NFAddrLsColLsId = "address_list_id" 29 | NFAddrLsColNfId = "notification_id" 30 | ) 31 | 32 | func NewNFAddressListId() string { 33 | return idutil.GetUuid(NFAddressListIdPrefix) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/models/notification_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "testing" 5 | 6 | "openpitrix.io/logger" 7 | 8 | pkg "openpitrix.io/notification/pkg" 9 | "openpitrix.io/notification/pkg/config" 10 | "openpitrix.io/notification/pkg/constants" 11 | "openpitrix.io/notification/pkg/util/jsonutil" 12 | ) 13 | 14 | func TestDecodeNotificationExtra(t *testing.T) { 15 | if !*pkg.LocalDevEnvEnabled { 16 | t.Skip("Local Dev testing env disabled.") 17 | } 18 | config.GetInstance().LoadConf() 19 | 20 | testExtra := "{\"ws_service\": \"op\",\"ws_message_type\": \"event\"}" 21 | 22 | nfExtraMap, err := DecodeNotificationExtra(testExtra) 23 | if err != nil { 24 | logger.Errorf(nil, "error=[%+v]", err) 25 | 26 | } 27 | 28 | service := "" 29 | nfService, ok := (*nfExtraMap)[constants.WsService] 30 | if ok { 31 | service = nfService 32 | } 33 | logger.Debugf(nil, "service=[%s]", service) 34 | 35 | messageType := "" 36 | nfExtraType, ok := (*nfExtraMap)[constants.WsMessageType] 37 | if ok { 38 | messageType = nfExtraType 39 | } 40 | logger.Debugf(nil, "messageType=[%s]", messageType) 41 | 42 | } 43 | 44 | func TestCheckExtra(t *testing.T) { 45 | if !*pkg.LocalDevEnvEnabled { 46 | t.Skip("Local Dev testing env disabled.") 47 | } 48 | config.GetInstance() 49 | testExtra := "{\"ws_service1\": \"op\",\"ws_message_type\": \"event\"}" 50 | 51 | err := CheckExtra(nil, testExtra) 52 | if err != nil { 53 | logger.Errorf(nil, "error=[%+v]", err) 54 | 55 | } 56 | 57 | } 58 | 59 | func TestUseMsgStringToPb(t *testing.T) { 60 | if !*pkg.LocalDevEnvEnabled { 61 | t.Skip("Local Dev testing env disabled.") 62 | } 63 | 64 | dataStr := `{"user_id":"huojiao","service":"ks","message_type":"event","MessageDetail":{"ws_message_id":"msg-XXy43Kkkl95V","ws_user_id":"huojiao","ws_service":"ks","ws_message_type":"event","ws_message":"test_content_normal"}}` 65 | userMsg := new(UserMessage) 66 | err := jsonutil.Decode([]byte(dataStr), userMsg) 67 | if err != nil { 68 | logger.Errorf(nil, "Decode [%s] into UserMessage failed: %+v", dataStr, err) 69 | } 70 | pbUserMsg := UserMessageToPb(userMsg) 71 | 72 | logger.Infof(nil, "pbUserMsg=%+v", pbUserMsg) 73 | 74 | } 75 | -------------------------------------------------------------------------------- /pkg/models/query_setting.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | var SearchWordColumnTable = []string{ 8 | TableNotification, 9 | TableTask, 10 | TableAddress, 11 | TableAddressList, 12 | } 13 | 14 | // columns that can be search through sql '=' operator 15 | var IndexedColumns = map[string][]string{ 16 | TableNotification: { 17 | NfColId, NfColContentType, NfColOwner, NfColStatus, 18 | }, 19 | TableTask: { 20 | TaskColTaskId, TaskColNfId, TaskColErrorCode, TaskColStatus, 21 | }, 22 | TableAddress: { 23 | AddrColId, AddrColAddress, AddrColNotifyType, AddrColStatus, 24 | }, 25 | TableAddressList: { 26 | AddrLsColId, AddrLsColName, AddrLsColExtra, AddrLsColStatus, 27 | }, 28 | } 29 | 30 | // columns that can be search through sql 'like' operator 31 | var SearchColumns = map[string][]string{ 32 | TableNotification: { 33 | NfColId, NfColContentType, NfColTitle, NfColShortContent, NfColAddressInfo, NfColStatus, NfColOwner, 34 | }, 35 | TableTask: { 36 | TaskColTaskId, TaskColNfId, TaskColStatus, TaskColErrorCode, 37 | }, 38 | TableAddress: { 39 | AddrColId, AddrColAddress, AddrColNotifyType, AddrColRemarks, AddrColStatus, 40 | }, 41 | TableAddressList: { 42 | AddrLsColId, AddrLsColName, AddrLsColName, AddrLsColExtra, 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /pkg/models/service_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | const ( 8 | ServiceType = "service_type" 9 | ) 10 | 11 | const ( 12 | ServiceCfgProtocol = "protocol" 13 | ServiceCfgEmailHost = "email_host" 14 | ServiceCfgPort = "port" 15 | ServiceCfgDisplayEmail = "display_email" 16 | ServiceCfgEmail = "email" 17 | ServiceCfgPassword = "password" 18 | ) 19 | 20 | const ( 21 | TestEmailRecipient = "test_email_recipient" 22 | ) 23 | 24 | const ( 25 | ProtocolTypeSMTP = "SMTP" 26 | ProtocolTypePOP3 = "POP3" 27 | ProtocolTypeIMAP = "IMAP" 28 | ) 29 | 30 | var ProtocolTypes = []string{ 31 | ProtocolTypeSMTP, 32 | ProtocolTypePOP3, 33 | ProtocolTypeIMAP, 34 | } 35 | -------------------------------------------------------------------------------- /pkg/models/task.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | import ( 8 | "time" 9 | 10 | "openpitrix.io/logger" 11 | 12 | "openpitrix.io/notification/pkg/constants" 13 | "openpitrix.io/notification/pkg/pb" 14 | "openpitrix.io/notification/pkg/util/idutil" 15 | "openpitrix.io/notification/pkg/util/jsonutil" 16 | "openpitrix.io/notification/pkg/util/pbutil" 17 | ) 18 | 19 | type Task struct { 20 | TaskId string `gorm:"column:task_id"` 21 | NotificationId string `gorm:"column:notification_id"` 22 | ErrorCode int64 `gorm:"column:error_code"` 23 | Status string `gorm:"column:status"` 24 | CreateTime time.Time `gorm:"column:create_time"` 25 | StatusTime time.Time `gorm:"column:status_time"` 26 | Directive string `gorm:"column:directive"` 27 | NotifyType string `gorm:"column:notify_type"` 28 | } 29 | 30 | //table name 31 | const ( 32 | TableTask = "task" 33 | ) 34 | 35 | const ( 36 | TaskIdPrefix = "t-" 37 | ) 38 | 39 | //field name 40 | //Nf is short for notification. 41 | const ( 42 | TaskColNfId = "notification_id" 43 | TaskColTaskId = "task_id" 44 | TaskColStatus = "status" 45 | TaskColErrorCode = "error_code" 46 | TaskColCreateTime = "create_time" 47 | ) 48 | 49 | func NewTask(notificationId, directive string, notifyType string) *Task { 50 | task := &Task{ 51 | TaskId: NewTaskId(), 52 | NotificationId: notificationId, 53 | ErrorCode: 0, 54 | Status: constants.StatusPending, 55 | CreateTime: time.Now(), 56 | StatusTime: time.Now(), 57 | Directive: directive, 58 | NotifyType: notifyType, 59 | } 60 | return task 61 | } 62 | 63 | func NewTaskId() string { 64 | return idutil.GetUuid(TaskIdPrefix) 65 | } 66 | 67 | type TaskDirective struct { 68 | NotificationId string 69 | Address string 70 | NotifyType string 71 | ContentType string 72 | Title string 73 | Content string 74 | ShortContent string 75 | ExpiredDays uint32 76 | AvailableStartTime string 77 | AvailableEndTime string 78 | } 79 | 80 | func DecodeTaskDirective(data string) (*TaskDirective, error) { 81 | taskDirective := new(TaskDirective) 82 | err := jsonutil.Decode([]byte(data), taskDirective) 83 | if err != nil { 84 | logger.Errorf(nil, "Decode [%s] into task directive failed: %+v", data, err) 85 | } 86 | return taskDirective, err 87 | } 88 | 89 | type TaskWithNfInfo struct { 90 | NotificationId string 91 | JobID string 92 | TaskID string 93 | Title string 94 | ShortContent string 95 | Content string 96 | EmailAddr string 97 | } 98 | 99 | func TaskToPb(task *Task) *pb.Task { 100 | pbTask := pb.Task{} 101 | pbTask.NotificationId = pbutil.ToProtoString(task.NotificationId) 102 | pbTask.Status = pbutil.ToProtoString(task.Status) 103 | pbTask.TaskId = pbutil.ToProtoString(task.TaskId) 104 | pbTask.CreateTime = pbutil.ToProtoTimestamp(task.CreateTime) 105 | pbTask.StatusTime = pbutil.ToProtoTimestamp(task.StatusTime) 106 | pbTask.Directive = pbutil.ToProtoString(task.Directive) 107 | pbTask.ErrorCode = pbutil.ToProtoUInt32(uint32(task.ErrorCode)) 108 | return &pbTask 109 | } 110 | 111 | func TaskSet2PbSet(inTasks []*Task) []*pb.Task { 112 | var pbTasks []*pb.Task 113 | for _, inTask := range inTasks { 114 | pbTask := TaskToPb(inTask) 115 | pbTasks = append(pbTasks, pbTask) 116 | } 117 | return pbTasks 118 | } 119 | -------------------------------------------------------------------------------- /pkg/models/user_message.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package models 6 | 7 | import ( 8 | "github.com/gorilla/websocket" 9 | "openpitrix.io/logger" 10 | 11 | "openpitrix.io/notification/pkg/pb" 12 | "openpitrix.io/notification/pkg/util/idutil" 13 | "openpitrix.io/notification/pkg/util/jsonutil" 14 | "openpitrix.io/notification/pkg/util/pbutil" 15 | ) 16 | 17 | type UserMessage struct { 18 | UserId string `json:"user_id,omitempty"` 19 | Service string `json:"service,omitempty"` 20 | MessageType string `json:"message_type,omitempty"` 21 | MessageDetail MessageDetail 22 | } 23 | 24 | type MessageDetail struct { 25 | MessageId string `json:"ws_message_id,omitempty"` 26 | UserId string `json:"ws_user_id,omitempty"` 27 | Service string `json:"ws_service,omitempty"` 28 | MessageType string `json:"ws_message_type,omitempty"` 29 | MessageContent string `json:"ws_message,omitempty"` 30 | } 31 | 32 | type Receiver struct { 33 | Service string 34 | MessageType string 35 | UserId string 36 | Conn *websocket.Conn 37 | } 38 | 39 | const ( 40 | WsMessageIdPrefix = "msg-" 41 | ) 42 | 43 | func NewWsMessageId() string { 44 | return idutil.GetUuid(WsMessageIdPrefix) 45 | } 46 | 47 | func MessageDetailToPb(userMsgDetail *MessageDetail) *pb.MessageDetail { 48 | pbUserMsgDetail := pb.MessageDetail{} 49 | pbUserMsgDetail.MessageId = pbutil.ToProtoString(userMsgDetail.MessageId) 50 | pbUserMsgDetail.Service = pbutil.ToProtoString(userMsgDetail.Service) 51 | pbUserMsgDetail.MessageType = pbutil.ToProtoString(userMsgDetail.MessageType) 52 | pbUserMsgDetail.MessageContent = pbutil.ToProtoString(userMsgDetail.MessageContent) 53 | return &pbUserMsgDetail 54 | } 55 | 56 | func UserMessageToPb(userMsg *UserMessage) *pb.UserMessage { 57 | pbUserMsg := pb.UserMessage{} 58 | pbUserMsg.UserId = pbutil.ToProtoString(userMsg.UserId) 59 | pbUserMsg.MessageType = pbutil.ToProtoString(userMsg.MessageType) 60 | pbUserMsg.Service = pbutil.ToProtoString(userMsg.Service) 61 | pbUserMsg.MsgDetail = MessageDetailToPb(&(userMsg.MessageDetail)) 62 | return &pbUserMsg 63 | } 64 | 65 | func UseMsgStringToPb(dataStr string) (*pb.UserMessage, error) { 66 | userMsg := new(UserMessage) 67 | err := jsonutil.Decode([]byte(dataStr), userMsg) 68 | if err != nil { 69 | logger.Errorf(nil, "Decode [%s] into models.UserMessage failed: %+v", dataStr, err) 70 | } 71 | pbUserMsg := UserMessageToPb(userMsg) 72 | return pbUserMsg, err 73 | } 74 | 75 | func PbToUserMessage(pbUserMsg *pb.UserMessage) *UserMessage { 76 | userMsg := UserMessage{} 77 | userMsg.MessageType = pbUserMsg.MessageType.GetValue() 78 | userMsg.Service = pbUserMsg.Service.GetValue() 79 | userMsg.UserId = pbUserMsg.UserId.GetValue() 80 | pbMsgDetail := pbUserMsg.MsgDetail 81 | MsgDetail := PbToMessageDetail(pbMsgDetail) 82 | userMsg.MessageDetail = *MsgDetail 83 | return &userMsg 84 | } 85 | 86 | func PbToMessageDetail(pbMsgDetail *pb.MessageDetail) *MessageDetail { 87 | msgDetail := MessageDetail{} 88 | msgDetail.UserId = pbMsgDetail.UserId.GetValue() 89 | msgDetail.Service = pbMsgDetail.Service.GetValue() 90 | msgDetail.MessageType = pbMsgDetail.MessageType.GetValue() 91 | msgDetail.MessageContent = pbMsgDetail.MessageContent.GetValue() 92 | msgDetail.MessageId = pbMsgDetail.MessageId.GetValue() 93 | return &msgDetail 94 | } 95 | -------------------------------------------------------------------------------- /pkg/pb/0.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: 0.proto 3 | 4 | package pb 5 | 6 | import ( 7 | fmt "fmt" 8 | math "math" 9 | 10 | proto "github.com/golang/protobuf/proto" 11 | _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options" 12 | ) 13 | 14 | // Reference imports to suppress errors if they are not otherwise used. 15 | var _ = proto.Marshal 16 | var _ = fmt.Errorf 17 | var _ = math.Inf 18 | 19 | // This is a compile-time assertion to ensure that this generated file 20 | // is compatible with the proto package it is being compiled against. 21 | // A compilation error at this line likely means your copy of the 22 | // proto package needs to be updated. 23 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 24 | 25 | func init() { proto.RegisterFile("0.proto", fileDescriptor_b5d39afb3b422e60) } 26 | 27 | var fileDescriptor_b5d39afb3b422e60 = []byte{ 28 | // 320 bytes of a gzipped FileDescriptorProto 29 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xcb, 0x4a, 0xc3, 0x40, 30 | 0x14, 0x40, 0x4d, 0xc0, 0x07, 0x41, 0xa5, 0x04, 0x57, 0x5d, 0x5d, 0xdc, 0x14, 0x4a, 0x9b, 0xb4, 31 | 0x15, 0x44, 0xba, 0x6b, 0xc0, 0x56, 0x11, 0x1f, 0xf8, 0x40, 0xe8, 0x6e, 0x26, 0xb9, 0x4d, 0xa6, 32 | 0xa6, 0x73, 0x87, 0x99, 0x49, 0x5b, 0xfb, 0x03, 0xee, 0xdd, 0xfb, 0x07, 0x82, 0x9f, 0xe2, 0xd2, 33 | 0xdf, 0x91, 0x3e, 0x16, 0x2d, 0xba, 0x9c, 0x33, 0xf7, 0x5c, 0x0e, 0xd7, 0xdb, 0x6d, 0x04, 0x4a, 34 | 0x93, 0x25, 0x7f, 0x5f, 0x92, 0x15, 0x03, 0x11, 0x33, 0x2b, 0x48, 0x96, 0x6b, 0x0b, 0x18, 0xd7, 35 | 0x53, 0x94, 0x75, 0x33, 0x61, 0x69, 0x8a, 0x3a, 0x24, 0x35, 0xff, 0x32, 0x21, 0x93, 0x92, 0xec, 36 | 0x62, 0xcc, 0x2c, 0xdd, 0xe8, 0xc7, 0x7d, 0xef, 0x7c, 0xbb, 0x3e, 0xf3, 0x8e, 0x6e, 0xd6, 0x96, 37 | 0xc0, 0x9d, 0xa6, 0x21, 0xc6, 0xf6, 0x38, 0xfa, 0x9f, 0xfb, 0xd5, 0xcc, 0x5a, 0x65, 0xda, 0x61, 38 | 0x98, 0x0a, 0x9b, 0x15, 0x3c, 0x88, 0x69, 0x14, 0x92, 0x42, 0xa9, 0x84, 0xd5, 0x62, 0x1a, 0xae, 39 | 0x07, 0xb5, 0xb6, 0x1b, 0x41, 0x23, 0x68, 0x56, 0x5d, 0xc7, 0x6d, 0x95, 0x98, 0x52, 0xf9, 0x8a, 40 | 0x87, 0x43, 0x43, 0xb2, 0xfd, 0x87, 0xf4, 0xbf, 0x1c, 0xef, 0xd3, 0xf1, 0xbc, 0x08, 0x99, 0x46, 41 | 0xdd, 0x29, 0x6c, 0xe6, 0x7f, 0x38, 0x7b, 0xae, 0xff, 0xe6, 0x3c, 0x66, 0x08, 0xf3, 0x37, 0x69, 42 | 0x31, 0x5b, 0xd6, 0x64, 0xc8, 0x12, 0xd4, 0x30, 0x2a, 0x8c, 0x05, 0x8e, 0x60, 0xd0, 0x82, 0x25, 43 | 0x58, 0x9a, 0x30, 0xa0, 0x3c, 0xa7, 0x09, 0x26, 0xc0, 0x5f, 0x81, 0x81, 0x51, 0x2c, 0x46, 0x60, 44 | 0x32, 0x01, 0x06, 0x96, 0x5e, 0x50, 0x06, 0xd0, 0x25, 0x0d, 0x38, 0x65, 0x23, 0x95, 0x63, 0x0d, 45 | 0x2a, 0x2b, 0x6b, 0x7c, 0xf1, 0xc4, 0xb8, 0x88, 0xce, 0x2f, 0xaf, 0xc4, 0x99, 0x6c, 0xde, 0x27, 46 | 0xe3, 0xe7, 0xdb, 0x61, 0xaf, 0x5b, 0xe4, 0xbd, 0x87, 0xeb, 0xd3, 0x59, 0x21, 0x79, 0x25, 0x28, 47 | 0x1f, 0x6c, 0x46, 0xb8, 0xbc, 0xe4, 0x1d, 0x6e, 0xf4, 0x6e, 0xf5, 0x5d, 0xc5, 0xf9, 0xce, 0xe2, 48 | 0xc0, 0x27, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x62, 0xaf, 0xfe, 0xcc, 0xa7, 0x01, 0x00, 0x00, 49 | } 50 | -------------------------------------------------------------------------------- /pkg/pb/types.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: types.proto 3 | 4 | package pb 5 | 6 | import ( 7 | fmt "fmt" 8 | math "math" 9 | 10 | proto "github.com/golang/protobuf/proto" 11 | ) 12 | 13 | // Reference imports to suppress errors if they are not otherwise used. 14 | var _ = proto.Marshal 15 | var _ = fmt.Errorf 16 | var _ = math.Inf 17 | 18 | // This is a compile-time assertion to ensure that this generated file 19 | // is compatible with the proto package it is being compiled against. 20 | // A compilation error at this line likely means your copy of the 21 | // proto package needs to be updated. 22 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 23 | 24 | type ErrorDetail struct { 25 | ErrorName string `protobuf:"bytes,1,opt,name=error_name,json=errorName,proto3" json:"error_name,omitempty"` 26 | Cause string `protobuf:"bytes,2,opt,name=cause,proto3" json:"cause,omitempty"` 27 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 28 | XXX_unrecognized []byte `json:"-"` 29 | XXX_sizecache int32 `json:"-"` 30 | } 31 | 32 | func (m *ErrorDetail) Reset() { *m = ErrorDetail{} } 33 | func (m *ErrorDetail) String() string { return proto.CompactTextString(m) } 34 | func (*ErrorDetail) ProtoMessage() {} 35 | func (*ErrorDetail) Descriptor() ([]byte, []int) { 36 | return fileDescriptor_d938547f84707355, []int{0} 37 | } 38 | 39 | func (m *ErrorDetail) XXX_Unmarshal(b []byte) error { 40 | return xxx_messageInfo_ErrorDetail.Unmarshal(m, b) 41 | } 42 | func (m *ErrorDetail) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 43 | return xxx_messageInfo_ErrorDetail.Marshal(b, m, deterministic) 44 | } 45 | func (m *ErrorDetail) XXX_Merge(src proto.Message) { 46 | xxx_messageInfo_ErrorDetail.Merge(m, src) 47 | } 48 | func (m *ErrorDetail) XXX_Size() int { 49 | return xxx_messageInfo_ErrorDetail.Size(m) 50 | } 51 | func (m *ErrorDetail) XXX_DiscardUnknown() { 52 | xxx_messageInfo_ErrorDetail.DiscardUnknown(m) 53 | } 54 | 55 | var xxx_messageInfo_ErrorDetail proto.InternalMessageInfo 56 | 57 | func (m *ErrorDetail) GetErrorName() string { 58 | if m != nil { 59 | return m.ErrorName 60 | } 61 | return "" 62 | } 63 | 64 | func (m *ErrorDetail) GetCause() string { 65 | if m != nil { 66 | return m.Cause 67 | } 68 | return "" 69 | } 70 | 71 | func init() { 72 | proto.RegisterType((*ErrorDetail)(nil), "openpitrix.ErrorDetail") 73 | } 74 | 75 | func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) } 76 | 77 | var fileDescriptor_d938547f84707355 = []byte{ 78 | // 115 bytes of a gzipped FileDescriptorProto 79 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0xa9, 0x2c, 0x48, 80 | 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0xca, 0x2f, 0x48, 0xcd, 0x2b, 0xc8, 0x2c, 81 | 0x29, 0xca, 0xac, 0x50, 0x72, 0xe2, 0xe2, 0x76, 0x2d, 0x2a, 0xca, 0x2f, 0x72, 0x49, 0x2d, 0x49, 82 | 0xcc, 0xcc, 0x11, 0x92, 0xe5, 0xe2, 0x4a, 0x05, 0x71, 0xe3, 0xf3, 0x12, 0x73, 0x53, 0x25, 0x18, 83 | 0x15, 0x18, 0x35, 0x38, 0x83, 0x38, 0xc1, 0x22, 0x7e, 0x89, 0xb9, 0xa9, 0x42, 0x22, 0x5c, 0xac, 84 | 0xc9, 0x89, 0xa5, 0xc5, 0xa9, 0x12, 0x4c, 0x60, 0x19, 0x08, 0xc7, 0x89, 0x25, 0x8a, 0xa9, 0x20, 85 | 0x29, 0x89, 0x0d, 0x6c, 0xb8, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x45, 0x51, 0xf7, 0x68, 0x6b, 86 | 0x00, 0x00, 0x00, 87 | } 88 | -------------------------------------------------------------------------------- /pkg/plugins/email_notifier.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package plugins 6 | 7 | import ( 8 | "context" 9 | 10 | "openpitrix.io/logger" 11 | 12 | "openpitrix.io/notification/pkg/constants" 13 | "openpitrix.io/notification/pkg/gerr" 14 | "openpitrix.io/notification/pkg/models" 15 | "openpitrix.io/notification/pkg/util/emailutil" 16 | "openpitrix.io/notification/pkg/util/stringutil" 17 | ) 18 | 19 | type EmailNotifier struct { 20 | } 21 | 22 | func (n *EmailNotifier) Send(ctx context.Context, task *models.Task) error { 23 | directive, err := models.DecodeTaskDirective(task.Directive) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | //for email msg , use the content with html Format 29 | contentStruct, err := models.DecodeContent(directive.Content) 30 | if err != nil { 31 | logger.Errorf(ctx, "Failed to send notification, content format is not correct, %+v.", err) 32 | return err 33 | } 34 | contentFmtHtml, ok := (*contentStruct)[constants.ContentFmtHtml] 35 | fmtType := "html" 36 | if !ok { 37 | contentFmtHtml = directive.Content 38 | fmtType = "normal" 39 | } 40 | directive.Content = contentFmtHtml 41 | 42 | if directive.AvailableStartTime == "" && directive.AvailableEndTime == "" { 43 | return emailutil.SendMail(ctx, directive.Address, directive.Title, directive.Content, fmtType) 44 | } else { 45 | isOK := stringutil.CheckTimeAvailable(directive.AvailableStartTime, directive.AvailableEndTime) 46 | if isOK != true { 47 | logger.Errorf(ctx, "Failed to send notification, time is not available, %+v.", err) 48 | return gerr.New(nil, gerr.Internal, gerr.ErrorNotAvailableTimeRange, directive.AvailableStartTime, directive.AvailableEndTime) 49 | } 50 | return emailutil.SendMail(ctx, directive.Address, directive.Title, directive.Content, fmtType) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /pkg/plugins/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package plugins 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "openpitrix.io/notification/pkg/constants" 12 | "openpitrix.io/notification/pkg/models" 13 | ) 14 | 15 | type NotifyInterface interface { 16 | Send(ctx context.Context, task *models.Task) error 17 | } 18 | 19 | func GetNotifier(task *models.Task) (NotifyInterface, error) { 20 | taskDirective, err := models.DecodeTaskDirective(task.Directive) 21 | if err != nil { 22 | return nil, err 23 | } 24 | switch taskDirective.NotifyType { 25 | case constants.NotifyTypeEmail: 26 | return new(EmailNotifier), nil 27 | default: 28 | return nil, fmt.Errorf("unsupported notify type [%s]", taskDirective.NotifyType) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/services/notification/am.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package notification 6 | 7 | import ( 8 | "context" 9 | 10 | "openpitrix.io/notification/pkg/constants" 11 | "openpitrix.io/notification/pkg/manager" 12 | "openpitrix.io/notification/pkg/models" 13 | "openpitrix.io/notification/pkg/pb" 14 | ) 15 | 16 | func (s *Server) Checker(ctx context.Context, req interface{}) error { 17 | switch r := req.(type) { 18 | case *pb.ServiceConfig: 19 | return manager.NewChecker(ctx, r). 20 | Required(models.ServiceCfgProtocol, models.ServiceCfgEmailHost, models.ServiceCfgPort, models.ServiceCfgDisplayEmail). 21 | StringChosen(models.ServiceCfgProtocol, models.ProtocolTypes). 22 | Exec() 23 | case *pb.ValidateEmailServiceV2Request: 24 | return manager.NewChecker(ctx, r). 25 | Required(models.ServiceCfgProtocol, models.ServiceCfgEmailHost, models.ServiceCfgPort, models.ServiceCfgDisplayEmail). 26 | StringChosen(models.ServiceCfgProtocol, models.ProtocolTypes). 27 | Exec() 28 | case *pb.GetServiceConfigRequest: 29 | return manager.NewChecker(ctx, r). 30 | Required(models.ServiceType). 31 | StringChosen(models.ServiceType, constants.NotifyTypes). 32 | Exec() 33 | case *pb.CreateNotificationRequest: 34 | return manager.NewChecker(ctx, r). 35 | Required(models.NfColContentType, models.NfColContent, models.NfColTitle, models.NfColAddressInfo). 36 | StringChosen(models.NfColContentType, models.ContentTypes). 37 | Exec() 38 | case *pb.DescribeNotificationsRequest: 39 | return manager.NewChecker(ctx, r). 40 | StringChosen(models.NfColStatus, constants.NfStatuses). 41 | Exec() 42 | case *pb.RetryNotificationsRequest: 43 | return manager.NewChecker(ctx, r). 44 | Required(models.NfColId). 45 | Exec() 46 | case *pb.RetryTasksRequest: 47 | return manager.NewChecker(ctx, r). 48 | Required(models.TaskColTaskId). 49 | Exec() 50 | case *pb.CreateAddressRequest: 51 | return manager.NewChecker(ctx, r). 52 | Required(models.AddrColAddress, models.AddrColNotifyType). 53 | StringChosen(models.ServiceType, constants.NotifyTypes). 54 | Exec() 55 | case *pb.DescribeAddressesRequest: 56 | return manager.NewChecker(ctx, r). 57 | StringChosen(models.AddrColStatus, constants.RecordStatuses). 58 | StringChosen(models.AddrColNotifyType, constants.NotifyTypes). 59 | Exec() 60 | case *pb.ModifyAddressRequest: 61 | return manager.NewChecker(ctx, r). 62 | Required(models.AddrColId). 63 | StringChosen(models.AddrColNotifyType, constants.NotifyTypes). 64 | Exec() 65 | case *pb.DeleteAddressesRequest: 66 | return manager.NewChecker(ctx, r). 67 | Required(models.AddrColId). 68 | Exec() 69 | case *pb.DescribeTasksRequest: 70 | return manager.NewChecker(ctx, r). 71 | StringChosen(models.TaskColStatus, constants.TaskStatuses). 72 | Exec() 73 | case *pb.CreateAddressListRequest: 74 | return manager.NewChecker(ctx, r). 75 | Required(models.AddrColId). 76 | Exec() 77 | case *pb.DescribeAddressListRequest: 78 | return manager.NewChecker(ctx, r). 79 | StringChosen(models.AddrLsColStatus, constants.RecordStatuses). 80 | Exec() 81 | case *pb.ModifyAddressListRequest: 82 | return manager.NewChecker(ctx, r). 83 | Required(models.AddrLsColId). 84 | StringChosen(models.AddrLsColStatus, constants.RecordStatuses). 85 | Exec() 86 | case *pb.DeleteAddressListRequest: 87 | return manager.NewChecker(ctx, r). 88 | Required(models.AddrLsColId). 89 | Exec() 90 | } 91 | 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /pkg/services/notification/api_gateway.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | package notification 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | "io/ioutil" 12 | "net/http" 13 | "net/http/httputil" 14 | "strings" 15 | "time" 16 | 17 | "github.com/gin-gonic/gin" 18 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 19 | "github.com/pborman/uuid" 20 | "github.com/pkg/errors" 21 | "golang.org/x/tools/godoc/vfs" 22 | "golang.org/x/tools/godoc/vfs/httpfs" 23 | "golang.org/x/tools/godoc/vfs/mapfs" 24 | "google.golang.org/grpc" 25 | "openpitrix.io/logger" 26 | 27 | staticSpec "openpitrix.io/notification/pkg/apigateway/spec" 28 | staticSwaggerUI "openpitrix.io/notification/pkg/apigateway/swagger-ui" 29 | "openpitrix.io/notification/pkg/config" 30 | "openpitrix.io/notification/pkg/pb" 31 | ) 32 | 33 | type register struct { 34 | f func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) 35 | endpoint string 36 | } 37 | 38 | func ServeApiGateway() { 39 | //version.PrintVersionInfo(func(s string, i ...interface{}) { 40 | // logger.Infof(nil, s, i...) 41 | //}) 42 | 43 | cfg := config.GetInstance() 44 | logger.Infof(nil, "Notification service http://%s:%d", cfg.App.Host, cfg.App.Port) 45 | logger.Infof(nil, "Api service start http://%s:%d/swagger-ui/", cfg.App.ApiHost, cfg.App.ApiPort) 46 | 47 | s := Server{} 48 | 49 | if err := s.run(); err != nil { 50 | logger.Criticalf(nil, "Api gateway run failed: %+v", err) 51 | panic(err) 52 | } 53 | } 54 | 55 | //const ( 56 | // Authorization = "Authorization" 57 | // RequestIdKey = "X-Request-Id" 58 | //) 59 | 60 | func log() gin.HandlerFunc { 61 | l := logger.New() 62 | l.HideCallstack() 63 | return func(c *gin.Context) { 64 | requestID := uuid.New() 65 | //c.Request.Header.Set(RequestIdKey, requestID) 66 | //c.Writer.Header().Set(RequestIdKey, requestID) 67 | 68 | t := time.Now() 69 | 70 | // process request 71 | c.Next() 72 | 73 | latency := time.Since(t) 74 | clientIP := c.ClientIP() 75 | method := c.Request.Method 76 | statusCode := c.Writer.Status() 77 | path := c.Request.URL.Path 78 | 79 | logStr := fmt.Sprintf("%s | %3d | %v | %s | %s %s %s", 80 | requestID, 81 | statusCode, 82 | latency, 83 | clientIP, method, 84 | path, 85 | c.Errors.String(), 86 | ) 87 | 88 | switch { 89 | case statusCode >= 400 && statusCode <= 499: 90 | l.Warnf(nil, logStr) 91 | case statusCode >= 500: 92 | l.Errorf(nil, logStr) 93 | default: 94 | l.Infof(nil, logStr) 95 | } 96 | } 97 | } 98 | 99 | func recovery() gin.HandlerFunc { 100 | return func(c *gin.Context) { 101 | defer func() { 102 | if err := recover(); err != nil { 103 | httprequest, _ := httputil.DumpRequest(c.Request, false) 104 | logger.Criticalf(nil, "Panic recovered: %+v\n%s", err, string(httprequest)) 105 | c.JSON(500, gin.H{ 106 | "title": "Error", 107 | "err": err, 108 | }) 109 | } 110 | }() 111 | c.Next() // execute all the handlers 112 | } 113 | } 114 | 115 | func handleSwagger() http.Handler { 116 | ns := vfs.NameSpace{} 117 | ns.Bind("/", mapfs.New(staticSwaggerUI.Files), "/", vfs.BindReplace) 118 | ns.Bind("/", mapfs.New(staticSpec.Files), "/", vfs.BindBefore) 119 | return http.StripPrefix("/swagger-ui", http.FileServer(httpfs.New(ns))) 120 | } 121 | 122 | func (s *Server) run() error { 123 | gin.SetMode(gin.ReleaseMode) 124 | mainHandler := gin.WrapH(s.mainHandler()) 125 | 126 | r := gin.New() 127 | r.Use(log()) 128 | r.Use(recovery()) 129 | r.Any("/swagger-ui/*filepath", gin.WrapH(handleSwagger())) 130 | r.Any("/v1/*filepath", mainHandler) 131 | r.Any("/v2/*filepath", mainHandler) 132 | r.Any("/api/*filepath", mainHandler) 133 | 134 | cfg := config.GetInstance() 135 | return r.Run(fmt.Sprintf(":%d", cfg.App.ApiPort)) 136 | } 137 | 138 | func (s *Server) mainHandler() http.Handler { 139 | var gwmux = runtime.NewServeMux() 140 | var opts = []grpc.DialOption{grpc.WithInsecure()} 141 | var err error 142 | 143 | cfg := config.GetInstance() 144 | for _, r := range []register{{ 145 | pb.RegisterNotificationHandlerFromEndpoint, 146 | fmt.Sprintf("localhost:%d", cfg.App.Port), 147 | }} { 148 | err = r.f(context.Background(), gwmux, r.endpoint, opts) 149 | if err != nil { 150 | err = errors.WithStack(err) 151 | logger.Errorf(nil, "Dial [%s] failed: %+v", r.endpoint, err) 152 | } 153 | } 154 | 155 | mux := http.NewServeMux() 156 | mux.Handle("/", gwmux) 157 | 158 | return formWrapper(mux) 159 | } 160 | 161 | // Ref: https://github.com/grpc-ecosystem/grpc-gateway/issues/7#issuecomment-358569373 162 | func formWrapper(h http.Handler) http.Handler { 163 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 164 | if strings.HasPrefix(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") { 165 | if err := r.ParseForm(); err != nil { 166 | http.Error(w, err.Error(), http.StatusBadRequest) 167 | return 168 | } 169 | jsonMap := make(map[string]interface{}, len(r.Form)) 170 | for k, v := range r.Form { 171 | if len(v) > 0 { 172 | jsonMap[k] = v[0] 173 | } 174 | } 175 | jsonBody, err := json.Marshal(jsonMap) 176 | if err != nil { 177 | http.Error(w, err.Error(), http.StatusBadRequest) 178 | } 179 | 180 | r.Body = ioutil.NopCloser(bytes.NewReader(jsonBody)) 181 | r.ContentLength = int64(len(jsonBody)) 182 | r.Header.Set("Content-Type", "application/json") 183 | } 184 | 185 | h.ServeHTTP(w, r) 186 | }) 187 | } 188 | -------------------------------------------------------------------------------- /pkg/services/notification/resource_control/email_config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | package resource_control 5 | 6 | import ( 7 | "testing" 8 | 9 | "openpitrix.io/logger" 10 | 11 | pkg "openpitrix.io/notification/pkg" 12 | "openpitrix.io/notification/pkg/config" 13 | ) 14 | 15 | func TestResetEmailCfg(t *testing.T) { 16 | if !*pkg.LocalDevEnvEnabled { 17 | t.Skip("Local Dev testing env disabled.") 18 | } 19 | cfg := config.GetInstance() 20 | err := ResetEmailCfg(cfg) 21 | 22 | if err != nil { 23 | logger.Errorf(nil, "Failed to reset email config with data in db, %+v.", err) 24 | } 25 | logger.Debugf(nil, "Reset email config with data in db successfully.") 26 | 27 | } 28 | -------------------------------------------------------------------------------- /pkg/services/notification/resource_control/notification_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | package resource_control 5 | 6 | import ( 7 | "testing" 8 | 9 | "openpitrix.io/logger" 10 | 11 | pkg "openpitrix.io/notification/pkg" 12 | "openpitrix.io/notification/pkg/config" 13 | "openpitrix.io/notification/pkg/models" 14 | "openpitrix.io/notification/pkg/pb" 15 | "openpitrix.io/notification/pkg/util/pbutil" 16 | ) 17 | 18 | func TestDescribeNotifications4rc(t *testing.T) { 19 | if !*pkg.LocalDevEnvEnabled { 20 | t.Skip("Local Dev testing env disabled.") 21 | } 22 | 23 | config.GetInstance() 24 | 25 | var nfIds []string 26 | nfIds = append(nfIds, "nf-yM793AqkEmnj") 27 | nfIds = append(nfIds, "nf-lLZ9L8OzZwnj") 28 | 29 | var contentTypes []string 30 | contentTypes = append(contentTypes, "email") 31 | 32 | var owners []string 33 | owners = append(owners, "HuoJiao") 34 | 35 | var statuses []string 36 | statuses = append(statuses, "successful") 37 | 38 | var req = &pb.DescribeNotificationsRequest{ 39 | NotificationId: nfIds, 40 | ContentType: contentTypes, 41 | Owner: owners, 42 | Status: statuses, 43 | Limit: 20, 44 | Offset: 0, 45 | SearchWord: pbutil.ToProtoString("successful"), 46 | SortKey: pbutil.ToProtoString("status"), 47 | Reverse: pbutil.ToProtoBool(false), 48 | DisplayColumns: nil, 49 | } 50 | 51 | notifications, cnt, err := DescribeNotifications(nil, req) 52 | 53 | if err != nil { 54 | logger.Errorf(nil, "Failed to describe notifications, error, %+v.", err) 55 | } 56 | 57 | logger.Infof(nil, "Test describe notifications:,cnt = %d,notifications=[%+v]", cnt, notifications) 58 | } 59 | 60 | func TestRegisterNotification4rc(t *testing.T) { 61 | if !*pkg.LocalDevEnvEnabled { 62 | t.Skip("Local Dev testing env disabled.") 63 | } 64 | config.GetInstance() 65 | testAddrsStr := "{\"email\": [\"openpitrix@163.com\", \"openpitrix@163.com\"]}" 66 | //testAddrListIds := "[\"adl-EgoLADQkwkEr\"]" 67 | testExtra := "{\"ws_service\": \"ks\",\"ws_message_type\": \"event\"}" 68 | var req = &pb.CreateNotificationRequest{ 69 | ContentType: pbutil.ToProtoString("alert"), 70 | Title: pbutil.ToProtoString("testing alert"), 71 | Content: pbutil.ToProtoString("test content"), 72 | ShortContent: pbutil.ToProtoString("test short content"), 73 | ExpiredDays: pbutil.ToProtoUInt32(0), 74 | Owner: pbutil.ToProtoString("HuoJiao"), 75 | AddressInfo: pbutil.ToProtoString(testAddrsStr), 76 | AvailableStartTime: pbutil.ToProtoString(""), 77 | AvailableEndTime: pbutil.ToProtoString(""), 78 | Extra: pbutil.ToProtoString(testExtra), 79 | } 80 | 81 | notification := models.NewNotification(req) 82 | 83 | err := RegisterNotification(nil, notification) 84 | if err != nil { 85 | logger.Errorf(nil, "Failed to register notification, %+v.", err) 86 | } 87 | logger.Debugf(nil, "RegisterNotification [%s] in DB successfully.", notification.NotificationId) 88 | 89 | } 90 | -------------------------------------------------------------------------------- /pkg/services/notification/resource_control/service_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package resource_control 6 | 7 | import ( 8 | "os" 9 | "strconv" 10 | 11 | "openpitrix.io/logger" 12 | 13 | "openpitrix.io/notification/pkg/config" 14 | "openpitrix.io/notification/pkg/pb" 15 | "openpitrix.io/notification/pkg/util/pbutil" 16 | ) 17 | 18 | func GetEmailServiceConfig() *pb.EmailServiceConfig { 19 | cfg := config.GetInstance() 20 | 21 | protocol := cfg.Email.Protocol 22 | emailHost := cfg.Email.EmailHost 23 | port := cfg.Email.Port 24 | displaySender := cfg.Email.DisplaySender 25 | email := cfg.Email.Email 26 | password := cfg.Email.Password 27 | sslEnable := cfg.Email.SSLEnable 28 | 29 | emailCfg := &pb.EmailServiceConfig{ 30 | Protocol: pbutil.ToProtoString(protocol), 31 | EmailHost: pbutil.ToProtoString(emailHost), 32 | Port: pbutil.ToProtoUInt32(uint32(port)), 33 | DisplaySender: pbutil.ToProtoString(displaySender), 34 | Email: pbutil.ToProtoString(email), 35 | Password: pbutil.ToProtoString(password), 36 | SslEnable: pbutil.ToProtoBool(sslEnable), 37 | } 38 | 39 | return emailCfg 40 | } 41 | 42 | func SetServiceConfig(req *pb.ServiceConfig) { 43 | protocol := req.GetEmailServiceConfig().GetProtocol().GetValue() 44 | emailHost := req.GetEmailServiceConfig().GetEmailHost().GetValue() 45 | port := req.GetEmailServiceConfig().GetPort().GetValue() 46 | displaySender := req.GetEmailServiceConfig().GetDisplaySender().GetValue() 47 | email := req.GetEmailServiceConfig().GetEmail().GetValue() 48 | password := req.GetEmailServiceConfig().GetPassword().GetValue() 49 | sslEnable := req.GetEmailServiceConfig().GetSslEnable().GetValue() 50 | 51 | os.Setenv("NOTIFICATION_EMAIL_PROTOCOL", protocol) 52 | os.Setenv("NOTIFICATION_EMAIL_EMAIL_HOST", emailHost) 53 | p := strconv.Itoa(int(port)) 54 | os.Setenv("NOTIFICATION_EMAIL_PORT", p) 55 | os.Setenv("NOTIFICATION_EMAIL_DISPLAY_SENDER", displaySender) 56 | os.Setenv("NOTIFICATION_EMAIL_EMAIL", email) 57 | os.Setenv("NOTIFICATION_EMAIL_PASSWORD", password) 58 | os.Setenv("NOTIFICATION_EMAIL_SSL_ENABLE", strconv.FormatBool(sslEnable)) 59 | 60 | config.GetInstance().LoadConf() 61 | logger.Infof(nil, "Set service config successfully, %+v.", config.GetInstance().Email) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /pkg/services/notification/resource_control/task.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package resource_control 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "openpitrix.io/logger" 12 | 13 | nfdb "openpitrix.io/notification/pkg/db" 14 | "openpitrix.io/notification/pkg/global" 15 | "openpitrix.io/notification/pkg/models" 16 | "openpitrix.io/notification/pkg/pb" 17 | "openpitrix.io/notification/pkg/util/pbutil" 18 | "openpitrix.io/notification/pkg/util/stringutil" 19 | ) 20 | 21 | func RegisterTask(ctx context.Context, task *models.Task) error { 22 | db := global.GetInstance().GetDB() 23 | err := db.Create(&task).Error 24 | if err != nil { 25 | logger.Errorf(ctx, "Failed to insert task, %+v.", err) 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | func UpdateTasksStatus(ctx context.Context, taskIds []string, status string) error { 32 | db := global.GetInstance().GetDB() 33 | err := db.Table(models.TableTask).Where(models.TaskColTaskId+" in (?)", taskIds).Updates(map[string]interface{}{models.TaskColStatus: status, models.NfColStatusTime: time.Now()}).Error 34 | 35 | if err != nil { 36 | logger.Errorf(ctx, "Failed to update task status to [%s], %+v.", status, err) 37 | return err 38 | } 39 | return nil 40 | } 41 | 42 | func GetTasksByStatus(ctx context.Context, notificationId string, status []string) []*models.Task { 43 | db := global.GetInstance().GetDB() 44 | var tasks []*models.Task 45 | err := db.Where("notification_id = ? AND status in (?)", notificationId, status).Find(&tasks).Error 46 | if err != nil { 47 | logger.Errorf(ctx, "Failed to get tasks by status[%+v], %+v.", status, err) 48 | return nil 49 | } 50 | return tasks 51 | } 52 | 53 | func GetTasksByTaskIds(ctx context.Context, taskIds []string) ([]*models.Task, error) { 54 | db := global.GetInstance().GetDB() 55 | var tasks []*models.Task 56 | err := db.Where("task_id in( ? )", taskIds).Find(&tasks).Error 57 | if err != nil { 58 | logger.Errorf(ctx, "Failed to get tasks by taskIds[%+v], %+v.", taskIds, err) 59 | return nil, err 60 | } 61 | return tasks, nil 62 | } 63 | 64 | func GetTaskIdsByNfIds(ctx context.Context, nfIds []string) ([]string, error) { 65 | var tasks []*models.Task 66 | err := global.GetInstance().GetDB().Table(models.TableTask).Where(models.TaskColNfId+" in (?)", nfIds).Find(&tasks).Error 67 | if err != nil { 68 | logger.Errorf(ctx, "Failed to get task Ids by NfIds[%+v], %+v.", nfIds, err) 69 | return nil, err 70 | } 71 | 72 | var taskIds []string 73 | for _, task := range tasks { 74 | taskId := task.TaskId 75 | taskIds = append(taskIds, taskId) 76 | } 77 | 78 | return taskIds, nil 79 | 80 | } 81 | 82 | func DescribeTasks(ctx context.Context, req *pb.DescribeTasksRequest) ([]*models.Task, uint64, error) { 83 | req.NotificationId = stringutil.SimplifyStringList(req.NotificationId) 84 | req.TaskId = stringutil.SimplifyStringList(req.TaskId) 85 | req.ErrorCode = stringutil.SimplifyStringList(req.ErrorCode) 86 | req.Status = stringutil.SimplifyStringList(req.Status) 87 | offset := pbutil.GetOffsetFromRequest(req) 88 | limit := pbutil.GetLimitFromRequest(req) 89 | 90 | var tasks []*models.Task 91 | var count uint64 92 | 93 | if err := nfdb.GetChain(global.GetInstance().GetDB().Table(models.TableTask)). 94 | AddQueryOrderDir(req, models.TaskColCreateTime). 95 | BuildFilterConditions(req, models.TableTask, "and"). 96 | Offset(offset). 97 | Limit(limit). 98 | Find(&tasks).Error; err != nil { 99 | logger.Errorf(ctx, "Failed to describe tasks, %+v.", err) 100 | return nil, 0, err 101 | } 102 | 103 | if err := nfdb.GetChain(global.GetInstance().GetDB().Table(models.TableTask)). 104 | BuildFilterConditions(req, models.TableTask, "and"). 105 | Count(&count).Error; err != nil { 106 | logger.Errorf(ctx, "Failed to describe task count, %+v", err) 107 | return nil, 0, err 108 | } 109 | 110 | return tasks, count, nil 111 | 112 | } 113 | -------------------------------------------------------------------------------- /pkg/services/notification/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package notification 6 | 7 | import ( 8 | "google.golang.org/grpc" 9 | "openpitrix.io/logger" 10 | 11 | "openpitrix.io/notification/pkg/config" 12 | "openpitrix.io/notification/pkg/manager" 13 | "openpitrix.io/notification/pkg/pb" 14 | rs "openpitrix.io/notification/pkg/services/notification/resource_control" 15 | ) 16 | 17 | type Server struct { 18 | controller *Controller 19 | } 20 | 21 | func Serve() { 22 | cfg := config.GetInstance() 23 | 24 | //read email config data from db. 25 | // check the data in data is default data or not 26 | // 1.if data in DB is default data, use cfg from ENV to update data in DB. 27 | // 2.if data in DB is not default data, use the data in DB. 28 | err := rs.ResetEmailCfg(cfg) 29 | if err != nil { 30 | logger.Errorf(nil, "Failed to reset email config: %+v.", err) 31 | } 32 | 33 | controller, err := NewController() 34 | if err != nil { 35 | logger.Criticalf(nil, "Failed to start serve: %+v.", err) 36 | } 37 | s := &Server{controller: controller} 38 | 39 | /********************************************************** 40 | ** start controller ** 41 | **********************************************************/ 42 | logger.Infof(nil, "[%s]", "/**********************************************************") 43 | logger.Infof(nil, "[%s]", "** start controller **") 44 | logger.Infof(nil, "[%s]", "**********************************************************/") 45 | go s.controller.Serve() 46 | 47 | /********************************************************** 48 | ** start ServeApiGateway ** 49 | **********************************************************/ 50 | logger.Infof(nil, "[%s]", "/**********************************************************") 51 | logger.Infof(nil, "[%s]", "** start ServeApiGateway **") 52 | logger.Infof(nil, "[%s]", "**********************************************************/") 53 | go ServeApiGateway() 54 | 55 | manager.NewGrpcServer(cfg.App.Host, cfg.App.Port). 56 | ShowErrorCause(cfg.Grpc.ShowErrorCause). 57 | WithChecker(s.Checker). 58 | Serve(func(server *grpc.Server) { 59 | pb.RegisterNotificationServer(server, s) 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/testhelper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package notification 6 | 7 | import "flag" 8 | 9 | var ( 10 | LocalDevEnvEnabled = flag.Bool("LocalDevEnvEnabled", false, "disable Local Dev Env setting") 11 | //LocalDevEnvEnabled = flag.Bool("LocalDevEnvEnabled", true, "enable Local Dev Env setting") 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/util/ctxutil/ctx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package ctxutil 6 | 7 | import ( 8 | "context" 9 | 10 | "google.golang.org/grpc/metadata" 11 | ) 12 | 13 | const ( 14 | messageIdKey = "x-message-id" 15 | requestIdKey = "x-request-id" 16 | ) 17 | 18 | type getMetadataFromContext func(ctx context.Context) (md metadata.MD, ok bool) 19 | 20 | var getMetadataFromContextFunc = []getMetadataFromContext{ 21 | metadata.FromOutgoingContext, 22 | metadata.FromIncomingContext, 23 | } 24 | 25 | func GetValueFromContext(ctx context.Context, key string) []string { 26 | if ctx == nil { 27 | return []string{} 28 | } 29 | for _, f := range getMetadataFromContextFunc { 30 | md, ok := f(ctx) 31 | if !ok { 32 | continue 33 | } 34 | m, ok := md[key] 35 | if ok && len(m) > 0 { 36 | return m 37 | } 38 | } 39 | m, ok := ctx.Value(key).([]string) 40 | if ok && len(m) > 0 { 41 | return m 42 | } 43 | s, ok := ctx.Value(key).(string) 44 | if ok && len(s) > 0 { 45 | return []string{s} 46 | } 47 | return []string{} 48 | } 49 | 50 | func Copy(src, dst context.Context) context.Context { 51 | return SetMessageId(dst, GetMessageId(src)) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/util/ctxutil/ctx_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package ctxutil 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestAddMessageId(t *testing.T) { 15 | ctx := context.TODO() 16 | ctx = SetMessageId(ctx, []string{"1", "2", "3"}) 17 | 18 | messageId := GetMessageId(ctx) 19 | require.Equal(t, messageId, []string{"1", "2", "3"}) 20 | 21 | ctx = AddMessageId(ctx, "4") 22 | 23 | messageId = GetMessageId(ctx) 24 | require.Equal(t, messageId, []string{"1", "2", "3", "4"}) 25 | 26 | ctx = ClearMessageId(ctx) 27 | 28 | messageId = GetMessageId(ctx) 29 | require.Equal(t, messageId, []string{}) 30 | } 31 | 32 | func TestGetRequestId(t *testing.T) { 33 | ctx := context.TODO() 34 | requestId := "abcdef" 35 | ctx = SetRequestId(ctx, requestId) 36 | 37 | require.Equal(t, requestId, GetRequestId(ctx)) 38 | 39 | ctx = context.TODO() 40 | requestId = "12345" 41 | ctx = SetRequestId(ctx, requestId) 42 | 43 | require.Equal(t, requestId, GetRequestId(ctx)) 44 | 45 | ctx = context.TODO() 46 | requestId = "qwert" 47 | ctx = SetRequestId(ctx, requestId) 48 | 49 | require.Equal(t, requestId, GetRequestId(ctx)) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/util/ctxutil/message.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package ctxutil 6 | 7 | import ( 8 | "context" 9 | 10 | "google.golang.org/grpc/metadata" 11 | ) 12 | 13 | func GetMessageId(ctx context.Context) []string { 14 | return GetValueFromContext(ctx, messageIdKey) 15 | } 16 | 17 | func SetMessageId(ctx context.Context, messageId []string) context.Context { 18 | ctx = context.WithValue(ctx, messageIdKey, messageId) 19 | md, ok := metadata.FromOutgoingContext(ctx) 20 | if !ok { 21 | md = metadata.MD{} 22 | } 23 | md[messageIdKey] = messageId 24 | return metadata.NewOutgoingContext(ctx, md) 25 | } 26 | 27 | func AddMessageId(ctx context.Context, messageId ...string) context.Context { 28 | m := GetMessageId(ctx) 29 | m = append(m, messageId...) 30 | return SetMessageId(ctx, m) 31 | } 32 | 33 | func ClearMessageId(ctx context.Context) context.Context { 34 | return SetMessageId(ctx, []string{}) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/util/ctxutil/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package ctxutil 6 | 7 | import ( 8 | "context" 9 | 10 | "google.golang.org/grpc/metadata" 11 | ) 12 | 13 | func GetRequestId(ctx context.Context) string { 14 | rid := GetValueFromContext(ctx, requestIdKey) 15 | if len(rid) == 0 { 16 | return "" 17 | } 18 | return rid[0] 19 | } 20 | 21 | func SetRequestId(ctx context.Context, requestId string) context.Context { 22 | ctx = context.WithValue(ctx, requestIdKey, []string{requestId}) 23 | md, ok := metadata.FromOutgoingContext(ctx) 24 | if !ok { 25 | md = metadata.MD{} 26 | } 27 | md[requestIdKey] = []string{requestId} 28 | return metadata.NewOutgoingContext(ctx, md) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/util/emailutil/email_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package emailutil 6 | 7 | import ( 8 | "testing" 9 | 10 | "openpitrix.io/logger" 11 | 12 | "openpitrix.io/notification/pkg/config" 13 | ) 14 | 15 | func TestSendMail(t *testing.T) { 16 | config.GetInstance().LoadConf() 17 | emailaddr := "openpitrix@163.com" 18 | 19 | header := "email_test.go sends an email." 20 | body := "

Content:email_test.go sends an email!

" 21 | err := SendMail(nil, emailaddr, header, body, "normal") 22 | 23 | if err != nil { 24 | logger.Errorf(nil, "send email failed, [%+v]", err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/util/emailutil/noStartTLSPlainAuth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package emailutil 6 | 7 | import ( 8 | "errors" 9 | "net/smtp" 10 | ) 11 | 12 | type noStartTLSPlainAuth struct { 13 | identity string 14 | username string 15 | password string 16 | host string 17 | } 18 | 19 | func (a *noStartTLSPlainAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { 20 | //compared net/smtp Auth.go PlainAuth,remove below lines. 21 | //if !server.TLS && !isLocalhost(server.Name) { 22 | // return "", nil, errors.New("unencrypted connection") 23 | //} 24 | 25 | if server.Name != a.host { 26 | return "", nil, errors.New("wrong host name") 27 | } 28 | resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password) 29 | return "PLAIN", resp, nil 30 | } 31 | 32 | func (a *noStartTLSPlainAuth) Next(fromServer []byte, more bool) ([]byte, error) { 33 | if more { 34 | // We've already sent everything. 35 | return nil, errors.New("unexpected server challenge") 36 | } 37 | return nil, nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/util/idutil/id.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package idutil 6 | 7 | import ( 8 | "crypto/rand" 9 | "errors" 10 | "net" 11 | 12 | "github.com/sony/sonyflake" 13 | hashids "github.com/speps/go-hashids" 14 | 15 | "openpitrix.io/notification/pkg/util/stringutil" 16 | ) 17 | 18 | var sf *sonyflake.Sonyflake 19 | var upperMachineID uint16 20 | 21 | func init() { 22 | var st sonyflake.Settings 23 | sf = sonyflake.NewSonyflake(st) 24 | 25 | if sf == nil { 26 | sf = sonyflake.NewSonyflake(sonyflake.Settings{ 27 | MachineID: lower16BitIP, 28 | }) 29 | upperMachineID, _ = upper16BitIP() 30 | } 31 | } 32 | 33 | func lower16BitIP() (uint16, error) { 34 | ip, err := IPv4() 35 | if err != nil { 36 | return 0, err 37 | } 38 | 39 | return uint16(ip[2])<<8 + uint16(ip[3]), nil 40 | } 41 | 42 | func upper16BitIP() (uint16, error) { 43 | ip, err := IPv4() 44 | if err != nil { 45 | return 0, err 46 | } 47 | 48 | return uint16(ip[0])<<8 + uint16(ip[1]), nil 49 | } 50 | 51 | func IPv4() (net.IP, error) { 52 | as, err := net.InterfaceAddrs() 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | for _, a := range as { 58 | ipnet, ok := a.(*net.IPNet) 59 | if !ok || ipnet.IP.IsLoopback() { 60 | continue 61 | } 62 | 63 | ip := ipnet.IP.To4() 64 | return ip, nil 65 | 66 | } 67 | return nil, errors.New("no ip address") 68 | } 69 | 70 | func GetIntId() uint64 { 71 | id, err := sf.NextID() 72 | if err != nil { 73 | panic(err) 74 | } 75 | return id 76 | } 77 | 78 | // format likes: B6BZVN3mOPvx 79 | func GetUuid(prefix string) string { 80 | id := GetIntId() 81 | hd := hashids.NewData() 82 | h, err := hashids.NewWithData(hd) 83 | if err != nil { 84 | panic(err) 85 | } 86 | i, err := h.Encode([]int{int(id)}) 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | return prefix + stringutil.Reverse(i) 92 | } 93 | 94 | const ( 95 | Alphabet62 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" 96 | Alphabet36 = "abcdefghijklmnopqrstuvwxyz1234567890" 97 | ) 98 | 99 | // format likes: 300m50zn91nwz5 100 | func GetUuid36(prefix string) string { 101 | id := GetIntId() 102 | hd := hashids.NewData() 103 | hd.Alphabet = Alphabet36 104 | h, err := hashids.NewWithData(hd) 105 | if err != nil { 106 | panic(err) 107 | } 108 | i, err := h.Encode([]int{int(id)}) 109 | if err != nil { 110 | panic(err) 111 | } 112 | 113 | return prefix + stringutil.Reverse(i) 114 | } 115 | 116 | func randString(letters string, n int) string { 117 | output := make([]byte, n) 118 | 119 | // We will take n bytes, one byte for each character of output. 120 | randomness := make([]byte, n) 121 | 122 | // read all random 123 | _, err := rand.Read(randomness) 124 | if err != nil { 125 | panic(err) 126 | } 127 | 128 | l := len(letters) 129 | // fill output 130 | for pos := range output { 131 | // get random item 132 | random := uint8(randomness[pos]) 133 | 134 | // random % 64 135 | randomPos := random % uint8(l) 136 | 137 | // put into output 138 | output[pos] = letters[randomPos] 139 | } 140 | 141 | return string(output) 142 | } 143 | 144 | func GetSecret() string { 145 | return randString(Alphabet62, 50) 146 | } 147 | 148 | func GetRefreshToken() string { 149 | return randString(Alphabet62, 50) 150 | } 151 | -------------------------------------------------------------------------------- /pkg/util/idutil/id_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package idutil 6 | 7 | import ( 8 | "fmt" 9 | "sort" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestGetUuid(t *testing.T) { 16 | fmt.Println(GetUuid("")) 17 | } 18 | 19 | func TestGetUuid36(t *testing.T) { 20 | fmt.Println(GetUuid36("")) 21 | } 22 | 23 | func TestGetManyUuid(t *testing.T) { 24 | var strSlice []string 25 | for i := 0; i < 10000; i++ { 26 | testId := GetUuid("") 27 | strSlice = append(strSlice, testId) 28 | } 29 | sort.Strings(strSlice) 30 | } 31 | 32 | func TestRandString(t *testing.T) { 33 | str := randString(Alphabet62, 50) 34 | assert.Equal(t, 50, len(str)) 35 | t.Log(str) 36 | 37 | str = randString(Alphabet62, 255) 38 | assert.Equal(t, 255, len(str)) 39 | t.Log(str) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/util/jsonutil/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | package jsonutil 5 | 6 | type Json interface { 7 | // Interface returns the underlying data 8 | Interface() interface{} 9 | // Encode returns its marshaled data as `[]byte` 10 | Encode() ([]byte, error) 11 | // EncodePretty returns its marshaled data as `[]byte` with indentation 12 | EncodePretty() ([]byte, error) 13 | // Implements the json.Marshaler interface. 14 | MarshalJSON() ([]byte, error) 15 | // Set modifies `Json` map by `key` and `value` 16 | // Useful for changing single key/value in a `Json` object easily. 17 | Set(key string, val interface{}) 18 | // SetPath modifies `Json`, recursively checking/creating map keys for the supplied path, 19 | // and then finally writing in the value 20 | SetPath(branch []string, val interface{}) 21 | // Del modifies `Json` map by deleting `key` if it is present. 22 | Del(key string) 23 | // Get returns a pointer to a new `Json` object 24 | // for `key` in its `map` representation 25 | // 26 | // useful for chaining operations (to traverse a nested JSON): 27 | // js.Get("top_level").Get("dict").Get("value").Int() 28 | Get(key string) Json 29 | // GetPath searches for the item as specified by the branch 30 | // without the need to deep dive using Get()'s. 31 | // 32 | // js.GetPath("top_level", "dict") 33 | GetPath(branch ...string) Json 34 | // CheckGet returns a pointer to a new `Json` object and 35 | // a `bool` identifying success or failure 36 | // 37 | // useful for chained operations when success is important: 38 | // if data, ok := js.Get("top_level").CheckGet("inner"); ok { 39 | // log.Println(data) 40 | // } 41 | CheckGet(key string) (Json, bool) 42 | // Map type asserts to `map` 43 | Map() (map[string]interface{}, error) 44 | // Array type asserts to an `array` 45 | Array() ([]interface{}, error) 46 | // Bool type asserts to `bool` 47 | Bool() (bool, error) 48 | // String type asserts to `string` 49 | String() (string, error) 50 | // Bytes type asserts to `[]byte` 51 | Bytes() ([]byte, error) 52 | // StringArray type asserts to an `array` of `string` 53 | StringArray() ([]string, error) 54 | 55 | // Implements the json.Unmarshaler interface. 56 | UnmarshalJSON(p []byte) error 57 | // Float64 coerces into a float64 58 | Float64() (float64, error) 59 | // Int coerces into an int 60 | Int() (int, error) 61 | // Int64 coerces into an int64 62 | Int64() (int64, error) 63 | // Uint64 coerces into an uint64 64 | Uint64() (uint64, error) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/util/jsonutil/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package jsonutil 6 | 7 | import ( 8 | "encoding/json" 9 | 10 | simplejson "github.com/bitly/go-simplejson" 11 | "openpitrix.io/logger" 12 | ) 13 | 14 | func Encode(o interface{}) ([]byte, error) { 15 | return json.Marshal(o) 16 | } 17 | 18 | func Decode(y []byte, o interface{}) error { 19 | return json.Unmarshal(y, o) 20 | } 21 | 22 | func ToString(o interface{}) string { 23 | b, err := Encode(o) 24 | if err != nil { 25 | logger.Errorf(nil, "Failed to encode [%+v], error: %+v", o, err) 26 | return "" 27 | } 28 | return string(b) 29 | } 30 | 31 | // FIXME: need improve performance 32 | func ToJson(o interface{}) Json { 33 | var j Json 34 | j = &fakeJson{simplejson.New()} 35 | b, err := Encode(o) 36 | if err != nil { 37 | logger.Errorf(nil, "Failed to encode [%+v] to []byte, error: %+v", o, err) 38 | return j 39 | } 40 | j, err = NewJson(b) 41 | if err != nil { 42 | logger.Errorf(nil, "Failed to decode [%+v] to Json, error: %+v", o, err) 43 | } 44 | return j 45 | } 46 | 47 | type fakeJson struct { 48 | *simplejson.Json 49 | } 50 | 51 | func NewJson(y []byte) (Json, error) { 52 | j, err := simplejson.NewJson(y) 53 | return &fakeJson{j}, err 54 | } 55 | 56 | func (j *fakeJson) Get(key string) Json { 57 | return &fakeJson{j.Json.Get(key)} 58 | } 59 | 60 | func (j *fakeJson) GetPath(branch ...string) Json { 61 | return &fakeJson{j.Json.GetPath(branch...)} 62 | } 63 | 64 | func (j *fakeJson) CheckGet(key string) (Json, bool) { 65 | result, ok := j.Json.CheckGet(key) 66 | return &fakeJson{result}, ok 67 | } 68 | 69 | // 70 | //func (j *fakeJson) UnmarshalJSON(p []byte) error { 71 | // return j.Json.UnmarshalJSON(p) 72 | //} 73 | // 74 | //func (j *fakeJson) MarshalJSON() ([]byte, error) { 75 | // return j.Json.MarshalJSON() 76 | //} 77 | -------------------------------------------------------------------------------- /pkg/util/pbutil/pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package pbutil 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/golang/protobuf/ptypes" 11 | "github.com/golang/protobuf/ptypes/timestamp" 12 | "github.com/golang/protobuf/ptypes/wrappers" 13 | "openpitrix.io/logger" 14 | 15 | "openpitrix.io/notification/pkg/constants" 16 | ) 17 | 18 | type RequestHadOffset interface { 19 | GetOffset() uint32 20 | } 21 | 22 | type RequestHadLimit interface { 23 | GetLimit() uint32 24 | } 25 | 26 | func GetTime(t *timestamp.Timestamp) (tt time.Time) { 27 | if t == nil { 28 | return time.Now() 29 | } else { 30 | return FromProtoTimestamp(t) 31 | } 32 | } 33 | 34 | func FromProtoTimestamp(t *timestamp.Timestamp) (tt time.Time) { 35 | tt, err := ptypes.Timestamp(t) 36 | if err != nil { 37 | logger.Errorf(nil, "Cannot convert timestamp [T] to time.Time [%+v]: %+v", t, err) 38 | } 39 | return 40 | } 41 | 42 | func ToProtoTimestamp(t time.Time) (tt *timestamp.Timestamp) { 43 | if t.IsZero() { 44 | return nil 45 | } 46 | tt, err := ptypes.TimestampProto(t) 47 | if err != nil { 48 | logger.Errorf(nil, "Cannot convert time.Time [%+v] to ToProtoTimestamp[T]: %+v", t, err) 49 | } 50 | return 51 | } 52 | 53 | func ToProtoString(str string) *wrappers.StringValue { 54 | return &wrappers.StringValue{Value: str} 55 | } 56 | 57 | func ToProtoUInt32(uint32 uint32) *wrappers.UInt32Value { 58 | return &wrappers.UInt32Value{Value: uint32} 59 | } 60 | 61 | func ToProtoInt32(i int32) *wrappers.Int32Value { 62 | return &wrappers.Int32Value{Value: i} 63 | } 64 | 65 | func ToProtoBool(bool bool) *wrappers.BoolValue { 66 | return &wrappers.BoolValue{Value: bool} 67 | } 68 | 69 | func ToProtoBytes(bytes []byte) *wrappers.BytesValue { 70 | return &wrappers.BytesValue{Value: bytes} 71 | } 72 | 73 | func GetOffsetFromRequest(req RequestHadOffset) uint32 { 74 | n := req.GetOffset() 75 | if n == 0 { 76 | return constants.DefaultOffset 77 | } 78 | 79 | return GetOffset(n) 80 | } 81 | 82 | func GetLimitFromRequest(req RequestHadLimit) uint32 { 83 | n := req.GetLimit() 84 | if n == 0 { 85 | return constants.DefaultLimit 86 | } 87 | return GetLimit(n) 88 | } 89 | 90 | func GetLimit(n uint32) uint32 { 91 | if n < 0 { 92 | n = 0 93 | } 94 | if n > constants.DefaultSelectLimit { 95 | n = constants.DefaultSelectLimit 96 | } 97 | return n 98 | } 99 | 100 | func GetOffset(n uint32) uint32 { 101 | if n < 0 { 102 | n = 0 103 | } 104 | return n 105 | } 106 | -------------------------------------------------------------------------------- /pkg/util/stringutil/base64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package stringutil 6 | 7 | import ( 8 | "bytes" 9 | "encoding/base64" 10 | "io/ioutil" 11 | ) 12 | 13 | func DecodeBase64(i string) ([]byte, error) { 14 | return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(i))) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/util/stringutil/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package stringutil 6 | 7 | import ( 8 | "regexp" 9 | "strings" 10 | "time" 11 | "unicode/utf8" 12 | ) 13 | 14 | // Creates an slice of slice values not included in the other given slice. 15 | func Diff(base, exclude []string) (result []string) { 16 | excludeMap := make(map[string]bool) 17 | for _, s := range exclude { 18 | excludeMap[s] = true 19 | } 20 | for _, s := range base { 21 | if !excludeMap[s] { 22 | result = append(result, s) 23 | } 24 | } 25 | return result 26 | } 27 | 28 | func Unique(ss []string) (result []string) { 29 | smap := make(map[string]bool) 30 | for _, s := range ss { 31 | smap[s] = true 32 | } 33 | for s := range smap { 34 | result = append(result, s) 35 | } 36 | return result 37 | } 38 | 39 | func FindString(array []string, str string) int { 40 | for index, s := range array { 41 | if str == s { 42 | return index 43 | } 44 | } 45 | return -1 46 | } 47 | 48 | func StringIn(str string, array []string) bool { 49 | return FindString(array, str) > -1 50 | } 51 | 52 | func Reverse(s string) string { 53 | size := len(s) 54 | buf := make([]byte, size) 55 | for start := 0; start < size; { 56 | r, n := utf8.DecodeRuneInString(s[start:]) 57 | start += n 58 | utf8.EncodeRune(buf[size-start:], r) 59 | } 60 | return string(buf) 61 | } 62 | 63 | func Contains(ss []string, s string) bool { 64 | for _, v := range ss { 65 | if v == s { 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | func SimplifyStringList(s []string) []string { 73 | b := s[:0] 74 | for _, x := range s { 75 | if x := SimplifyString(x); x != "" { 76 | b = append(b, x) 77 | } 78 | } 79 | return b 80 | } 81 | 82 | // "\ta b c" => "a b c" 83 | func SimplifyString(s string) string { 84 | return reMoreSpace.ReplaceAllString(strings.TrimSpace(s), " ") 85 | } 86 | 87 | var reMoreSpace = regexp.MustCompile(`\s+`) 88 | 89 | func CheckTimeAvailable(availableStartTimeStr string, availableEndTimeStr string) bool { 90 | timeFmt := "15:04:05" 91 | currentTime := time.Now().Format(timeFmt) 92 | currentTime1, _ := time.Parse(timeFmt, currentTime) 93 | 94 | availableStartTime, _ := time.Parse(timeFmt, availableStartTimeStr) 95 | availableEndTime, _ := time.Parse(timeFmt, availableEndTimeStr) 96 | return availableStartTime.Before(currentTime1) && availableEndTime.After(currentTime1) 97 | } 98 | -------------------------------------------------------------------------------- /pkg/util/stringutil/string_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | package stringutil 6 | 7 | import "testing" 8 | 9 | func TestDiff(t *testing.T) { 10 | testCase := [][]string{ 11 | {"foo", "bar", "hello"}, 12 | {"foo", "bar", "world"}, 13 | } 14 | result := Diff(testCase[0], testCase[1]) 15 | if len(result) != 1 || result[0] != "hello" { 16 | t.Fatalf("Diff failed") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/notification/notification_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The OpenPitrix Authors. All rights reserved. 2 | // Use of this source code is governed by a Apache license 3 | // that can be found in the LICENSE file. 4 | 5 | // +build integration 6 | 7 | package notification 8 | 9 | import ( 10 | "context" 11 | "io" 12 | "log" 13 | "strconv" 14 | "testing" 15 | "time" 16 | 17 | "openpitrix.io/logger" 18 | 19 | nfclient "openpitrix.io/notification/pkg/client/notification" 20 | "openpitrix.io/notification/pkg/pb" 21 | "openpitrix.io/notification/pkg/util/pbutil" 22 | ) 23 | 24 | const Service = "notification-service" 25 | 26 | func TestNotification(t *testing.T) { 27 | client, err := nfclient.NewClient() 28 | if err != nil { 29 | t.Fatalf("failed to create nfclient.") 30 | } 31 | 32 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 33 | defer cancel() 34 | 35 | testAddrsStr := "{\"email\": [\"openpitrix@163.com\"]}" 36 | contentStr := "test content" 37 | 38 | var req = &pb.CreateNotificationRequest{ 39 | ContentType: pbutil.ToProtoString("other"), 40 | Title: pbutil.ToProtoString("handler_test.go Title_test."), 41 | Content: pbutil.ToProtoString(contentStr), 42 | ShortContent: pbutil.ToProtoString("ShortContent"), 43 | ExpiredDays: pbutil.ToProtoUInt32(0), 44 | Owner: pbutil.ToProtoString("HuoJiao"), 45 | AddressInfo: pbutil.ToProtoString(testAddrsStr), 46 | } 47 | 48 | _, err = client.CreateNotification(ctx, req) 49 | if err != nil { 50 | t.Log(err) 51 | t.Fatalf("failed to CreateNotification.") 52 | } 53 | 54 | t.Log("create notification successfully.") 55 | 56 | } 57 | 58 | func createNF(i string) { 59 | client, err := nfclient.NewClient() 60 | if err != nil { 61 | logger.Errorf(nil, "failed to create nfclient.") 62 | } 63 | 64 | ctx, cancel := context.WithTimeout(context.Background(), time.Hour) 65 | defer cancel() 66 | 67 | testAddrsStr := "{\"email\": [\"admin@app-center.com.cn\"]}" 68 | contentStr := "content for pressure Testing" 69 | var req = &pb.CreateNotificationRequest{ 70 | ContentType: pbutil.ToProtoString("other"), 71 | Title: pbutil.ToProtoString("Pressure Testing"), 72 | Content: pbutil.ToProtoString(contentStr), 73 | ShortContent: pbutil.ToProtoString("ShortContent"), 74 | ExpiredDays: pbutil.ToProtoUInt32(0), 75 | Owner: pbutil.ToProtoString("HuoJiao"), 76 | AddressInfo: pbutil.ToProtoString(testAddrsStr), 77 | } 78 | 79 | s := req.Content.GetValue() + ",第" + i + "封邮件" 80 | req.Content = pbutil.ToProtoString(s) 81 | _, err = client.CreateNotification(ctx, req) 82 | if err != nil { 83 | logger.Errorf(nil, "failed to CreateNotification,err= %+v", err) 84 | } 85 | logger.Infof(nil, "create notification successfully,i= %+v", i) 86 | 87 | } 88 | 89 | const ( 90 | Maxtasks = 1000 91 | ) 92 | 93 | func TestCreateNotificationByPressure(t *testing.T) { 94 | for i := 0; i < Maxtasks; i++ { 95 | go createNF(strconv.Itoa(i)) 96 | } 97 | 98 | for { 99 | time.Sleep(time.Second * 1) 100 | } 101 | } 102 | 103 | func TestGetStream(t *testing.T) { 104 | client, err := nfclient.NewClient() 105 | if err != nil { 106 | t.Fatalf("failed to create nfclient.") 107 | } 108 | ctx, cancel := context.WithTimeout(context.Background(), time.Hour) 109 | defer cancel() 110 | 111 | reqstreamData := &pb.StreamReqData{} 112 | res, _ := client.GetStream(ctx, reqstreamData) 113 | 114 | for { 115 | userWsMsg, err := res.Recv() 116 | logger.Infof(nil, "userWsMsg=%+v", userWsMsg) 117 | 118 | if err == io.EOF { 119 | break 120 | } 121 | if err != nil { 122 | log.Printf("failed to recv: %+v", err) 123 | } 124 | t.Log(userWsMsg) 125 | } 126 | 127 | } 128 | --------------------------------------------------------------------------------