├── gateway ├── linkname.s ├── .gitignore ├── helper │ ├── gray │ │ ├── api │ │ │ ├── router.go │ │ │ ├── router.gen.go │ │ │ └── handlers.go │ │ ├── logic │ │ │ ├── model │ │ │ │ ├── model.proto │ │ │ │ ├── init.go │ │ │ │ └── gray_match.go │ │ │ └── logic.go │ │ ├── README.md │ │ ├── types │ │ │ ├── types.proto │ │ │ └── types.go │ │ ├── sdk │ │ │ ├── rpc_test.go │ │ │ └── rpc.go │ │ └── gray.go │ └── agent │ │ └── proto │ │ └── agent.proto ├── sdk │ ├── rpc_test.go │ └── rpc.go ├── types │ ├── api.go │ ├── api.proto │ ├── business.go │ ├── auth.go │ └── conn_hooks.go ├── README.md ├── logic │ ├── http │ │ ├── setting.go │ │ ├── log.go │ │ └── server.go │ ├── websocket │ │ ├── server.go │ │ ├── conn_tab.go │ │ └── api.go │ ├── business.go │ └── socket │ │ ├── conn_tab.go │ │ ├── api.go │ │ └── server.go ├── config.go └── gateway.go ├── examples ├── project │ ├── doc │ │ ├── README.md │ │ ├── APIDoc.md │ │ └── databases.md │ ├── log │ │ └── PID │ ├── sdk │ │ ├── rpc.go │ │ ├── rpc_test.go │ │ ├── rpc.gen_test.go │ │ └── rpc.gen.go │ ├── api │ │ ├── handler.go │ │ ├── router.go │ │ ├── push_handler.gen.go │ │ ├── router.gen.go │ │ └── pull_handler.gen.go │ ├── args │ │ ├── type.go │ │ ├── var.go │ │ ├── const.go │ │ ├── const.gen.go │ │ └── type.gen.go │ ├── __tp-micro__gen__.lock │ ├── errs │ │ └── errs.go │ ├── .gitignore │ ├── go.mod │ ├── main.go │ ├── logic │ │ ├── tmp_code.gen.go │ │ └── model │ │ │ └── init.go │ ├── README.md │ ├── config │ │ └── config.yaml │ ├── __tp-micro__tpl__.go │ └── config.go ├── custom │ ├── curl.sh │ ├── home.tpl │ ├── client.go │ ├── gateway.go │ ├── browser.go │ ├── config │ │ └── config.yaml │ └── server.go ├── simple │ ├── curl.sh │ ├── home.tpl │ ├── gateway.go │ ├── client.go │ ├── server.go │ ├── config │ │ └── config.yaml │ └── browser.go ├── binder │ ├── server.go │ └── client.go └── discovery │ ├── server.go │ └── client.go ├── micro ├── create │ ├── tpl │ │ ├── _template.bak │ │ │ ├── doc │ │ │ │ ├── README.md │ │ │ │ ├── APIDoc.md │ │ │ │ └── databases.md │ │ │ ├── sdk │ │ │ │ ├── rpc.go │ │ │ │ └── rpc_test.go │ │ │ ├── api │ │ │ │ ├── handler.go │ │ │ │ └── router.go │ │ │ ├── args │ │ │ │ ├── type.go │ │ │ │ ├── var.go │ │ │ │ └── const.go │ │ │ ├── __tp-micro__gen__.lock │ │ │ ├── errs │ │ │ │ └── errs.go │ │ │ └── .gitignore │ │ └── base_tpl.go │ ├── structtag │ │ ├── .travis.yml │ │ ├── README.md │ │ └── LICENSE │ ├── project_test.go │ ├── parse_test.go │ └── create.go ├── README.md ├── run │ └── fsnotify │ │ ├── fsnotify_open_darwin.go │ │ ├── fsnotify_open_bsd.go │ │ ├── example_test.go │ │ ├── fsnotify_symlink_test.go │ │ └── fsnotify.go └── info │ └── info.go ├── cmd ├── gateway │ ├── README.md │ └── gateway.go └── configer │ ├── README.md │ └── main.go ├── doc ├── tp-micro_flow_chart.png └── TODO.md ├── model ├── mongo │ ├── README.md │ ├── pre_db.go │ └── config.go ├── redis │ ├── config │ │ └── config.yaml │ ├── .gitignore │ ├── module.go │ └── client_test.go ├── sqlx │ ├── types │ │ ├── README.md │ │ └── types_test.go │ ├── doc.go │ ├── reflectx │ │ └── README.md │ └── iface_db_tx.go └── mysql │ ├── values.go │ ├── README.md │ ├── config.go │ ├── pre_db.go │ └── db_test.go ├── discovery ├── README.md └── common.go ├── helper ├── mod-html │ ├── a_test.tpl │ ├── b_test.tpl │ ├── render_test.go │ └── README.md └── utils.go ├── configer ├── README.md ├── common.go ├── mgr.go └── node.go ├── .gitignore ├── doc.go ├── status.go ├── common.go ├── linker.go └── go.mod /gateway/linkname.s: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/project/doc/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/project/log/PID: -------------------------------------------------------------------------------- 1 | 59664 2 | -------------------------------------------------------------------------------- /examples/project/sdk/rpc.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | -------------------------------------------------------------------------------- /gateway/.gitignore: -------------------------------------------------------------------------------- 1 | samples/simple/simple -------------------------------------------------------------------------------- /examples/project/api/handler.go: -------------------------------------------------------------------------------- 1 | package api 2 | -------------------------------------------------------------------------------- /examples/project/sdk/rpc_test.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/doc/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/sdk/rpc.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | -------------------------------------------------------------------------------- /examples/project/args/type.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | type () 4 | -------------------------------------------------------------------------------- /examples/project/args/var.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | var () 4 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/api/handler.go: -------------------------------------------------------------------------------- 1 | package api 2 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/sdk/rpc_test.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | -------------------------------------------------------------------------------- /examples/project/args/const.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | const () 4 | -------------------------------------------------------------------------------- /cmd/gateway/README.md: -------------------------------------------------------------------------------- 1 | # Command gateway 2 | 3 | A custom gateway example. 4 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/args/type.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | type () 4 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/args/var.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | var () 4 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/args/const.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | const () 4 | -------------------------------------------------------------------------------- /micro/create/structtag/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.7.x 4 | - tip 5 | -------------------------------------------------------------------------------- /cmd/configer/README.md: -------------------------------------------------------------------------------- 1 | # Command configer 2 | 3 | A configuration center server. 4 | 5 | -------------------------------------------------------------------------------- /doc/tp-micro_flow_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoenai/tp-micro/HEAD/doc/tp-micro_flow_chart.png -------------------------------------------------------------------------------- /model/mongo/README.md: -------------------------------------------------------------------------------- 1 | # mongo 2 | 3 | A mongodb ORM(Object Role Modeling) package with redis cache. 4 | -------------------------------------------------------------------------------- /discovery/README.md: -------------------------------------------------------------------------------- 1 | ## discovery 2 | 3 | A service discovery module implemented by [ETCD](https://github.com/coreos/etcd). 4 | 5 | -------------------------------------------------------------------------------- /examples/project/__tp-micro__gen__.lock: -------------------------------------------------------------------------------- 1 | NOTE: 2 | This `micro gen` command only covers files with the ".gen.go" suffix if the file exists! -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/__tp-micro__gen__.lock: -------------------------------------------------------------------------------- 1 | NOTE: 2 | This `micro gen` command only covers files with the ".gen.go" suffix if the file exists! -------------------------------------------------------------------------------- /examples/custom/curl.sh: -------------------------------------------------------------------------------- 1 | curl -v -H "Content-Type:application/json" -X POST --data '{"a":10, "b":2}' 'http://localhost:5000/math/divide?access_token=sdfghj' -------------------------------------------------------------------------------- /examples/simple/curl.sh: -------------------------------------------------------------------------------- 1 | curl -v -H "Content-Type:application/json" -X POST --data '{"a":10, "b":2}' 'http://localhost:5000/math/divide?access_token=sdfghj' -------------------------------------------------------------------------------- /examples/custom/home.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | 7 |
8 |

{{.}}

9 | 10 | -------------------------------------------------------------------------------- /examples/simple/home.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | 7 |
8 |

{{.}}

9 | 10 | -------------------------------------------------------------------------------- /model/redis/config/config.yaml: -------------------------------------------------------------------------------- 1 | test_redis: 2 | deploy_type: single 3 | for_single: 4 | addr: 127.0.0.1:6379 5 | for_cluster: 6 | addrs: [] 7 | pool_size_per_node: 0 8 | idle_timeout: 0 9 | -------------------------------------------------------------------------------- /helper/mod-html/a_test.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | html test 1 5 | 6 | 7 |
8 |

{{.}}

9 | 10 | -------------------------------------------------------------------------------- /helper/mod-html/b_test.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | html test 2 5 | 6 | 7 |
8 |

{{.}}

9 | 10 | -------------------------------------------------------------------------------- /model/sqlx/types/README.md: -------------------------------------------------------------------------------- 1 | # types 2 | 3 | The types package provides some useful types which implement the `sql.Scanner` 4 | and `driver.Valuer` interfaces, suitable for use as scan and value targets with 5 | database/sql. 6 | -------------------------------------------------------------------------------- /examples/project/api/router.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | ) 6 | 7 | // customRoute registers custom handlers to router. 8 | func customRoute(router *erpc.Router) { 9 | } 10 | -------------------------------------------------------------------------------- /gateway/helper/gray/api/router.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | ) 6 | 7 | // customRoute registers custom handlers to router. 8 | func customRoute(router *erpc.Router) { 9 | } 10 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/api/router.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | ) 6 | 7 | // customRoute registers custom handlers to router. 8 | func customRoute(router *erpc.Router) { 9 | } 10 | -------------------------------------------------------------------------------- /gateway/helper/gray/logic/model/model.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package model; 4 | 5 | message GrayMatch { 6 | string Uri = 1; 7 | string Regexp = 2; 8 | int64 CreatedAt = 3; 9 | int64 UpdatedAt = 4; 10 | } -------------------------------------------------------------------------------- /configer/README.md: -------------------------------------------------------------------------------- 1 | # configer 2 | 3 | package configer is a configuration center that uses [etcd](https://github.com/coreos/etcd) as a storage medium. 4 | 5 | Server command: [configer](https://github.com/xiaoenai/tp-micro/tree/master/cmd/configer) 6 | -------------------------------------------------------------------------------- /gateway/helper/gray/README.md: -------------------------------------------------------------------------------- 1 | # gray 2 | 3 | Use regular matching to perform grayscale access splitting with URI, session ID, real IP, or other labels. 4 | 5 | 6 | [Learn Example](https://github.com/xiaoenai/tp-micro/tree/master/gateway/examples/custom) 7 | -------------------------------------------------------------------------------- /examples/project/errs/errs.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | ) 6 | 7 | var ( 8 | // InvalidParameter status 9 | InvalidParameter = erpc.NewStatus(100001, "Invalid Parameter", "Contains invalid request parameters") 10 | ) 11 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/errs/errs.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | ) 6 | 7 | var ( 8 | // InvalidParameter status 9 | InvalidParameter = erpc.NewStatus(100001, "Invalid Parameter", "Contains invalid request parameters") 10 | ) 11 | -------------------------------------------------------------------------------- /gateway/helper/agent/proto/agent.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proto; 4 | 5 | message Agent { 6 | string session_id = 1; 7 | string inner_gw = 2; 8 | int64 online_at = 3; 9 | bool is_offline = 4; 10 | uint64 salt = 5; 11 | } 12 | 13 | message Agents { 14 | repeated Agent agents = 1; 15 | } 16 | -------------------------------------------------------------------------------- /micro/README.md: -------------------------------------------------------------------------------- 1 | # Command micro 2 | 3 | A deployment tools for [TP-Micro](https://github.com/xiaoenai/tp-micro) micro service framework. 4 | 5 | ## Feature 6 | 7 | - Quickly create a tp-micro project 8 | - Run tp-micro project with hot compilation 9 | 10 | **NOTE:** The type of handler's parameter and result must be struct! 11 | 12 | -------------------------------------------------------------------------------- /micro/run/fsnotify/fsnotify_open_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin 6 | 7 | package fsnotify 8 | 9 | import "syscall" 10 | 11 | const open_FLAGS = syscall.O_EVTONLY 12 | -------------------------------------------------------------------------------- /model/redis/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /micro/run/fsnotify/fsnotify_open_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build freebsd openbsd netbsd dragonfly 6 | 7 | package fsnotify 8 | 9 | import "syscall" 10 | 11 | const open_FLAGS = syscall.O_NONBLOCK | syscall.O_RDONLY 12 | -------------------------------------------------------------------------------- /helper/utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/henrylee2cn/erpc/v6" 7 | ) 8 | 9 | // Redirect implements request redirection of HTTP gateway. 10 | func Redirect(ctx erpc.CallCtx, code int32, targetUrl string) *erpc.Status { 11 | ctx.Output().Meta().Set("Location", targetUrl) 12 | return erpc.NewStatus(code, http.StatusText(int(code)), "") 13 | } 14 | -------------------------------------------------------------------------------- /examples/project/args/const.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package args 5 | 6 | // UserSql the statement to create 'user' mysql table 7 | const UserSql string = `` 8 | 9 | // LogSql the statement to create 'log' mysql table 10 | const LogSql string = `` 11 | 12 | // DeviceSql the statement to create 'device' mysql table 13 | const DeviceSql string = `` 14 | -------------------------------------------------------------------------------- /gateway/helper/gray/types/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package types; 3 | 4 | message IsGrayArgs { 5 | string Uri = 1; 6 | string Uid = 2; 7 | } 8 | message IsGrayResult { 9 | bool Gray = 1; 10 | } 11 | message GetArgs { 12 | string Uri = 1; 13 | } 14 | message DeleteArgs { 15 | string Uri = 1; 16 | } 17 | message SetArgs { 18 | string Uri = 1; 19 | string Regexp = 2; 20 | } -------------------------------------------------------------------------------- /examples/project/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.so 4 | _obj 5 | _test 6 | *.[568vq] 7 | [568vq].out 8 | *.cgo1.go 9 | *.cgo2.c 10 | _cgo_defun.c 11 | _cgo_gotypes.go 12 | _cgo_export.* 13 | _testmain.go 14 | *.exe 15 | *.exe~ 16 | *.test 17 | *.prof 18 | *.rar 19 | *.zip 20 | *.gz 21 | *.psd 22 | *.bmd 23 | *.cfg 24 | *.pptx 25 | *.log 26 | *nohup.out 27 | *.vscode 28 | *.sublime-project 29 | *.sublime-workspace 30 | project 31 | -------------------------------------------------------------------------------- /examples/project/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xiaoenai/tp-micro/examples/project 2 | 3 | go 1.13 4 | 5 | replace github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0 6 | 7 | require ( 8 | github.com/henrylee2cn/cfgo v0.0.0-20180417024816-e6c3cc325b21 9 | github.com/henrylee2cn/erpc/v6 v6.3.1 10 | github.com/henrylee2cn/goutil v0.0.0-20191202093501-834eaf50f6fe 11 | github.com/xiaoenai/tp-micro/v6 v6.1.2 12 | ) 13 | -------------------------------------------------------------------------------- /examples/project/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | micro "github.com/xiaoenai/tp-micro/v6" 5 | "github.com/xiaoenai/tp-micro/v6/discovery" 6 | 7 | "github.com/xiaoenai/tp-micro/examples/project/api" 8 | ) 9 | 10 | func main() { 11 | srv := micro.NewServer( 12 | cfg.Srv, 13 | discovery.ServicePlugin(cfg.Srv.InnerIpPort(), cfg.Etcd), 14 | ) 15 | api.Route("/project", srv.Router()) 16 | srv.ListenAndServe() 17 | } 18 | -------------------------------------------------------------------------------- /examples/project/api/push_handler.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package api 5 | 6 | import ( 7 | "github.com/henrylee2cn/erpc/v6" 8 | 9 | "github.com/xiaoenai/tp-micro/examples/project/args" 10 | "github.com/xiaoenai/tp-micro/examples/project/logic" 11 | ) 12 | 13 | // Stat handler 14 | func Stat(ctx erpc.PushCtx, arg *args.StatArg) *erpc.Status { 15 | return logic.Stat(ctx, arg) 16 | } 17 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.so 4 | _obj 5 | _test 6 | *.[568vq] 7 | [568vq].out 8 | *.cgo1.go 9 | *.cgo2.c 10 | _cgo_defun.c 11 | _cgo_gotypes.go 12 | _cgo_export.* 13 | _testmain.go 14 | *.exe 15 | *.exe~ 16 | *.test 17 | *.prof 18 | *.rar 19 | *.zip 20 | *.gz 21 | *.psd 22 | *.bmd 23 | *.cfg 24 | *.pptx 25 | *.log 26 | *nohup.out 27 | *.vscode 28 | *.sublime-project 29 | *.sublime-workspace 30 | {{PROJ_NAME}} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.so 4 | _obj 5 | _test 6 | *.[568vq] 7 | [568vq].out 8 | *.cgo1.go 9 | *.cgo2.c 10 | _cgo_defun.c 11 | _cgo_gotypes.go 12 | _cgo_export.* 13 | _testmain.go 14 | *.exe 15 | *.exe~ 16 | *.test 17 | *.prof 18 | *.rar 19 | *.zip 20 | *.gz 21 | *.psd 22 | *.bmd 23 | *.cfg 24 | *.pptx 25 | *.log 26 | *nohup.out 27 | *.sublime-project 28 | *.sublime-workspace 29 | *.vscode 30 | go.sum 31 | examples/*/gateway 32 | examples/*/browser 33 | examples/*/server 34 | examples/*/client 35 | examples/project/project 36 | .idea -------------------------------------------------------------------------------- /gateway/helper/gray/api/router.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package api 5 | 6 | import ( 7 | "github.com/henrylee2cn/erpc/v6" 8 | ) 9 | 10 | // Route registers handlers to router. 11 | func Route(root string, router *erpc.Router) { 12 | // root router group 13 | group := router.SubRoute(root) 14 | 15 | // custom router 16 | customRoute(group.ToRouter()) 17 | 18 | // automatically generated router 19 | 20 | // CALL APIs... 21 | group.RouteCallFunc(IsGray) 22 | group.RouteCallFunc(Get) 23 | group.RouteCallFunc(Delete) 24 | group.RouteCallFunc(Set) 25 | } 26 | -------------------------------------------------------------------------------- /model/sqlx/doc.go: -------------------------------------------------------------------------------- 1 | // Package sqlx provides general purpose extensions to database/sql. 2 | // 3 | // It is intended to seamlessly wrap database/sql and provide convenience 4 | // methods which are useful in the development of database driven applications. 5 | // None of the underlying database/sql methods are changed. Instead all extended 6 | // behavior is implemented through new methods defined on wrapper types. 7 | // 8 | // Additions include scanning into structs, named query support, rebinding 9 | // queries for different drivers, convenient shorthands for common error handling 10 | // and more. 11 | // 12 | package sqlx 13 | -------------------------------------------------------------------------------- /examples/simple/gateway.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/henrylee2cn/cfgo" 5 | "github.com/henrylee2cn/erpc/v6" 6 | "github.com/xiaoenai/tp-micro/v6/gateway" 7 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 8 | ) 9 | 10 | //go:generate go build $GOFILE 11 | 12 | func main() { 13 | cfg := gateway.NewConfig() 14 | cfg.OuterHttpServer.AllowCross = true 15 | cfgo.MustReg("gateway", cfg) 16 | // Run a gateway instance with default business logic and default socket protocol. 17 | biz := types.DefaultBusiness() 18 | err := gateway.Run(*cfg, biz, nil, nil) 19 | if err != nil { 20 | erpc.Fatalf("%v", err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/project/api/router.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package api 5 | 6 | import ( 7 | "github.com/henrylee2cn/erpc/v6" 8 | ) 9 | 10 | // Route registers handlers to router. 11 | func Route(_root string, _router *erpc.Router) { 12 | // root router group 13 | _group := _router.SubRoute(_root) 14 | 15 | // custom router 16 | customRoute(_group.ToRouter()) 17 | 18 | // automatically generated router 19 | 20 | // PULL APIs... 21 | { 22 | _group.RouteCallFunc(Home) 23 | _group.RouteCall(new(Math)) 24 | } 25 | 26 | // PUSH APIs... 27 | { 28 | _group.RoutePushFunc(Stat) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gateway/helper/gray/logic/model/init.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 5 | "github.com/xiaoenai/tp-micro/v6/model/redis" 6 | ) 7 | 8 | // dbHandler preset DB handler 9 | var dbHandler = mysql.NewPreDB() 10 | 11 | // Init initializes the model packet. 12 | func Init(dbConfig mysql.Config, redisConfig redis.Config) error { 13 | return dbHandler.Init(&dbConfig, &redisConfig) 14 | } 15 | 16 | // GetDB returns the DB handler. 17 | func GetDB() *mysql.DB { 18 | return dbHandler.DB 19 | } 20 | 21 | // GetRedis returns the redis client. 22 | func GetRedis() *redis.Client { 23 | return dbHandler.DB.Cache 24 | } 25 | -------------------------------------------------------------------------------- /examples/project/api/pull_handler.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package api 5 | 6 | import ( 7 | "github.com/henrylee2cn/erpc/v6" 8 | 9 | "github.com/xiaoenai/tp-micro/examples/project/args" 10 | "github.com/xiaoenai/tp-micro/examples/project/logic" 11 | ) 12 | 13 | // Home handler 14 | func Home(ctx erpc.CallCtx, arg *args.EmptyStruct) (*args.HomeResult, *erpc.Status) { 15 | return logic.Home(ctx, arg) 16 | } 17 | 18 | // Math controller 19 | type Math struct { 20 | erpc.CallCtx 21 | } 22 | 23 | // Divide handler 24 | func (m *Math) Divide(arg *args.DivideArg) (*args.DivideResult, *erpc.Status) { 25 | return logic.Math_Divide(m.CallCtx, arg) 26 | } 27 | -------------------------------------------------------------------------------- /gateway/sdk/rpc_test.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "testing" 5 | // micro "github.com/xiaoenai/tp-micro/v6" 6 | // "github.com/xiaoenai/tp-micro/v6/gateway/types" 7 | ) 8 | 9 | // TestSdk test SDK. 10 | func TestSdk(t *testing.T) { 11 | // etcdClient, err := etcd.EasyNew(cfg.Etcd) 12 | // if err != nil { 13 | // t.Fatalf("%v", err) 14 | // } 15 | // Init( 16 | // micro.CliConfig{ 17 | // Failover: 3, 18 | // HeartbeatSecond: 4, 19 | // }, 20 | // rawproto.NewRawProtoFunc, 21 | // etcdClient, 22 | // ) 23 | // reply, stat := SocketTotal() 24 | // if stat != nil { 25 | // t.Logf("stat: %v", stat) 26 | // } else { 27 | // t.Logf("long connections total: %d", reply.ConnTotal) 28 | // } 29 | } 30 | -------------------------------------------------------------------------------- /micro/run/fsnotify/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fsnotify_test 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/howeyc/fsnotify" 11 | ) 12 | 13 | func ExampleNewWatcher() { 14 | watcher, err := fsnotify.NewWatcher() 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | go func() { 20 | for { 21 | select { 22 | case ev := <-watcher.Event: 23 | log.Println("event:", ev) 24 | case err := <-watcher.Error: 25 | log.Println("error:", err) 26 | } 27 | } 28 | }() 29 | 30 | err = watcher.Watch("/tmp/foo") 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /model/sqlx/reflectx/README.md: -------------------------------------------------------------------------------- 1 | # reflectx 2 | 3 | The sqlx package has special reflect needs. In particular, it needs to: 4 | 5 | * be able to map a name to a field 6 | * understand embedded structs 7 | * understand mapping names to fields by a particular tag 8 | * user specified name -> field mapping functions 9 | 10 | These behaviors mimic the behaviors by the standard library marshallers and also the 11 | behavior of standard Go accessors. 12 | 13 | The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is 14 | addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct 15 | tags in the ways that are vital to most marshallers, and they are slow. 16 | 17 | This reflectx package extends reflect to achieve these goals. 18 | -------------------------------------------------------------------------------- /examples/project/doc/APIDoc.md: -------------------------------------------------------------------------------- 1 | # 用户模块 2 | 3 | ## 1.1 通过姓名获取用户接口 4 | 5 | **简要描述:** 6 | 7 | - 获取用户资料信息 8 | 9 | **请求URL:** 10 | - ` http://xxx.xxx.com/user/v1/profile/get_by_name 11 | 12 | 13 | **请求方式:** 14 | - GET 15 | - Content-Type: application/json;charset=utf-8 16 | 17 | **参数:** 18 | 19 | |参数名|必选|类型|说明| 20 | |:---- |:---|:----- |----- | 21 | | name | 是 |string | 用户姓名 | 22 | 23 | **参数示例** 24 | 25 | ``` json 26 | { 27 | "name": "micro" 28 | } 29 | ``` 30 | 31 | **返回参数说明** 32 | 33 | | 参数名 |是否必填 |描述 | 34 | | ------------ | ------------ | ------------ | 35 | | id |是|用户id| 36 | | age |是|用户年龄| 37 | | phone|是|用户手机号| 38 | 39 | **返回示例**: 40 | 41 | ``` json 42 | { 43 | "id": 123, 44 | "age": 2, 45 | "phone": "xxxx" 46 | } 47 | 48 | ``` -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package tp-micro is a simple, powerful micro service framework based on eRPC. 2 | // 3 | // Copyright 2018 HenryLee and xiaoenai. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | package micro 18 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/doc/APIDoc.md: -------------------------------------------------------------------------------- 1 | # 用户模块 2 | 3 | ## 1.1 通过姓名获取用户接口 4 | 5 | **简要描述:** 6 | 7 | - 获取用户资料信息 8 | 9 | **请求URL:** 10 | - ` http://xxx.xxx.com/user/v1/profile/get_by_name 11 | 12 | 13 | **请求方式:** 14 | - GET 15 | - Content-Type: application/json;charset=utf-8 16 | 17 | **参数:** 18 | 19 | |参数名|必选|类型|说明| 20 | |:---- |:---|:----- |----- | 21 | | name | 是 |string | 用户姓名 | 22 | 23 | **参数示例** 24 | 25 | ``` json 26 | { 27 | "name": "micro" 28 | } 29 | ``` 30 | 31 | **返回参数说明** 32 | 33 | | 参数名 |是否必填 |描述 | 34 | | ------------ | ------------ | ------------ | 35 | | id |是|用户id| 36 | | age |是|用户年龄| 37 | | phone|是|用户手机号| 38 | 39 | **返回示例**: 40 | 41 | ``` json 42 | { 43 | "id": 123, 44 | "age": 2, 45 | "phone": "xxxx" 46 | } 47 | 48 | ``` -------------------------------------------------------------------------------- /examples/binder/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | micro "github.com/xiaoenai/tp-micro/v6" 6 | ) 7 | 8 | type ( 9 | // Arg arg 10 | Arg struct { 11 | A int 12 | B int `param:""` 13 | Query 14 | XyZ string `param:""` 15 | } 16 | Query struct { 17 | X string `param:""` 18 | } 19 | ) 20 | 21 | // P handler 22 | type P struct { 23 | erpc.CallCtx 24 | } 25 | 26 | // Divide divide API 27 | func (p *P) Divide(arg *Arg) (int, *erpc.Status) { 28 | erpc.Infof("query arg x: %s, xy_z: %s", arg.Query.X, arg.XyZ) 29 | return arg.A / arg.B, nil 30 | } 31 | 32 | func main() { 33 | srv := micro.NewServer(micro.SrvConfig{ 34 | ListenAddress: ":9090", 35 | EnableHeartbeat: true, 36 | }) 37 | group := srv.SubRoute("/static") 38 | group.RouteCall(new(P)) 39 | srv.ListenAndServe() 40 | } 41 | -------------------------------------------------------------------------------- /examples/project/logic/tmp_code.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // The temporary code used to ensure successful compilation! 3 | // When the project is completed, it should be removed! 4 | 5 | package logic 6 | 7 | import ( 8 | "github.com/henrylee2cn/erpc/v6" 9 | 10 | "github.com/xiaoenai/tp-micro/examples/project/args" 11 | // "github.com/xiaoenai/tp-micro/examples/project/logic/model" 12 | // "github.com/xiaoenai/tp-micro/examples/project/errs" 13 | ) 14 | 15 | // Stat handler 16 | func Stat(ctx erpc.PushCtx, arg *args.StatArg) *erpc.Status { 17 | return nil 18 | } 19 | 20 | // Home handler 21 | func Home(ctx erpc.CallCtx, arg *args.EmptyStruct) (*args.HomeResult, *erpc.Status) { 22 | return new(args.HomeResult), nil 23 | } 24 | 25 | // Divide handler 26 | func Math_Divide(ctx erpc.CallCtx, arg *args.DivideArg) (*args.DivideResult, *erpc.Status) { 27 | return new(args.DivideResult), nil 28 | } 29 | -------------------------------------------------------------------------------- /model/redis/module.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Module defines a module for the redis key prefix. 8 | type Module struct { 9 | keyFormat string 10 | module string 11 | keyPrefix string 12 | } 13 | 14 | // NewModule creates a module for the redis key prefix. 15 | func NewModule(module string) *Module { 16 | return &Module{ 17 | keyFormat: "%s:%s", // module:key 18 | module: module, 19 | keyPrefix: fmt.Sprintf("%s:", module), 20 | } 21 | } 22 | 23 | // Key completes the internal short key to the true key in redis. 24 | func (m *Module) Key(shortKey string) string { 25 | return fmt.Sprintf(m.keyFormat, m.module, shortKey) 26 | } 27 | 28 | // Prefix returns the key prefix of current module. 29 | func (m *Module) Prefix() string { 30 | return m.keyPrefix 31 | } 32 | 33 | // String returns module description. 34 | func (m *Module) String() string { 35 | return fmt.Sprintf("module: %s", m.module) 36 | } 37 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import "github.com/henrylee2cn/erpc/v6" 4 | 5 | // NOTE: error code range [-1,999] 6 | var ( 7 | // RerrClientClosed: client is closed. 8 | RerrClientClosed = erpc.NewStatus(100, "client is closed", "") 9 | // RerrInvalidParameter: Invalid Parameter 10 | RerrInvalidParameter = erpc.NewStatus(erpc.CodeBadMessage, "Invalid Parameter", "") 11 | // RerrInternalServerError: System is busy, please try again later 12 | RerrInternalServerError = erpc.NewStatus(erpc.CodeInternalServerError, "System is busy, please try again later", "") 13 | // RerrNotFound: Not Found 14 | RerrNotFound = erpc.NewStatus(erpc.CodeNotFound, "Not Found", "") 15 | // RerrNotOnline: User is not online 16 | RerrNotOnline = erpc.NewStatus(erpc.CodeNotFound, "Not Found", "User is not online") 17 | // RerrRenderFailed: Template Rendering Failed 18 | RerrRenderFailed = erpc.NewStatus(erpc.CodeInternalServerError, "Template Rendering Failed", "") 19 | ) 20 | -------------------------------------------------------------------------------- /gateway/helper/gray/api/handlers.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | 6 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/logic" 7 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/types" 8 | ) 9 | 10 | // IsGray check whether the service should use grayscale based on the uid. 11 | func IsGray(ctx erpc.CallCtx, args *types.IsGrayArgs) (*types.IsGrayResult, *erpc.Status) { 12 | return logic.IsGray(args) 13 | } 14 | 15 | // Get get the rule of gray. 16 | func Get(ctx erpc.CallCtx, args *types.GetArgs) (*types.GrayMatch, *erpc.Status) { 17 | return logic.Get(args) 18 | } 19 | 20 | // Delete delete the rule of gray. 21 | func Delete(ctx erpc.CallCtx, args *types.DeleteArgs) (*struct{}, *erpc.Status) { 22 | return logic.Delete(args) 23 | } 24 | 25 | // Set insert or update the regular expression for matching the URI. 26 | func Set(ctx erpc.CallCtx, args *types.SetArgs) (*struct{}, *erpc.Status) { 27 | return logic.Set(args) 28 | } 29 | -------------------------------------------------------------------------------- /model/redis/client_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/go-redis/redis/v7" 8 | ) 9 | 10 | func TestClient(t *testing.T) { 11 | cfg, err := ReadConfig("test_redis") 12 | if err != nil { 13 | t.Fatal("ReadConfig(\"test_redis\")", err) 14 | } 15 | c, err := NewClient(cfg) 16 | if err != nil { 17 | t.Fatal("NewClient(\"test_redis\")", err) 18 | } 19 | 20 | m := NewModule("test") 21 | 22 | s, err := c.Set(m.Key("a_key"), "a_value", time.Second).Result() 23 | if err != nil { 24 | t.Fatal("c.Set().Result() error:", err) 25 | } 26 | t.Logf("c.Set().Result() result: %s", s) 27 | 28 | s, err = c.Get(m.Key("a_key")).Result() 29 | if err != nil { 30 | t.Fatal("c.Get().Result() error:", err) 31 | } 32 | t.Logf("c.Get().Result() result: %s", s) 33 | time.Sleep(2 * time.Second) 34 | 35 | s, err = c.Get(m.Key("a_key")).Result() 36 | if err == nil { 37 | t.Fatalf("[after 2s] c.Get().Result() result: %s", s) 38 | } 39 | t.Logf("[after 2s] c.Get().Result() is null ?: %v", err == redis.Nil) 40 | } 41 | -------------------------------------------------------------------------------- /examples/custom/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | "github.com/henrylee2cn/erpc/v6/plugin/auth" 6 | micro "github.com/xiaoenai/tp-micro/v6" 7 | ) 8 | 9 | func main() { 10 | cli := micro.NewClient( 11 | micro.CliConfig{ 12 | Failover: 3, 13 | HeartbeatSecond: 4, 14 | }, 15 | micro.NewStaticLinker(":5020"), 16 | auth.LaunchAuth(generateAuthInfo), 17 | ) 18 | // test call 19 | var reply int 20 | stat := cli.Call("/math/divide?access_token=sdfghj", Msg{A: 10, B: 2}, &reply).Status() 21 | if stat != nil { 22 | erpc.Fatalf("%v", stat) 23 | } 24 | erpc.Infof("10/2=%d", reply) 25 | 26 | // test push 27 | cli.RoutePushFunc(push) 28 | for msg := range c { 29 | // your business 30 | erpc.Infof("received: %v", msg) 31 | } 32 | } 33 | 34 | func generateAuthInfo() string { 35 | return "client-auth-info-12345" 36 | } 37 | 38 | var c = make(msgChan, 1000) 39 | 40 | type msgChan chan *Msg 41 | 42 | type Msg struct { 43 | A int 44 | B int 45 | } 46 | 47 | func push(ctx erpc.PushCtx, arg *Msg) *erpc.Status { 48 | c <- arg 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /examples/project/doc/databases.md: -------------------------------------------------------------------------------- 1 | ## MySql 2 | 3 | ## 1.1 用户信息表(user) 4 | | 参数 | 说明 | 类型 | 默认值 | 版本 | 5 | |------|------|------|------|------| 6 | | id | 自增Id| `bigint(20)` | - | V1 | 7 | | name | 用户姓名 | `varchar(20)` | 0 | V1 | 8 | | age | 用户年龄 | `int(10)` | 0 | V1 | 9 | | phone | 用户手机号 | `varchar(15)` | '' | V1 | 10 | 11 | ```sql 12 | CREATE TABLE `user` ( 13 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID', 14 | `name` varchar(20) NOT NULL DEFAULT '0' COMMENT '姓名', 15 | `age` int(10) NOT NULL DEFAULT '' COMMENT '年龄', 16 | `phone` varchar(15) NOT NULL DEFAULT '' COMMENT '手机号', 17 | `updated_at` bigint(11) NOT NULL DEFAULT '0' COMMENT '更新时间', 18 | `created_at` bigint(11) NOT NULL COMMENT '创建时间', 19 | `deleted_ts` bigint(11) NOT NULL DEFAULT '0' COMMENT '删除时间(0表示未删除)', 20 | PRIMARY KEY (`id`), 21 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表'; 22 | 23 | ``` 24 | 25 | 26 | ## Mongodb 27 | 28 | ## 1.1 用户元信息表(user_meta) 29 | 30 | | 参数 | 说明 | 类型 | 默认值 | 版本 | 31 | | ------ | -------- | -------------- | ------ | ---- | 32 | | avatar | 用户头像 | `varchar(200)` | 0 | V1 | 33 | 34 | -------------------------------------------------------------------------------- /gateway/types/api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package types 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6/codec" 19 | ) 20 | 21 | type ( 22 | // SocketTotalArgs args 23 | SocketTotalArgs = codec.PbEmpty 24 | ) 25 | 26 | type ( 27 | // SocketPushReply reply 28 | SocketPushReply = codec.PbEmpty 29 | ) 30 | 31 | type ( 32 | // WsTotalArgs args 33 | WsTotalArgs = codec.PbEmpty 34 | ) 35 | 36 | type ( 37 | // WsPushReply reply 38 | WsPushReply = codec.PbEmpty 39 | ) 40 | -------------------------------------------------------------------------------- /micro/create/tpl/_template.bak/doc/databases.md: -------------------------------------------------------------------------------- 1 | ## MySql 2 | 3 | ## 1.1 用户信息表(user) 4 | | 参数 | 说明 | 类型 | 默认值 | 版本 | 5 | |------|------|------|------|------| 6 | | id | 自增Id| `bigint(20)` | - | V1 | 7 | | name | 用户姓名 | `varchar(20)` | 0 | V1 | 8 | | age | 用户年龄 | `int(10)` | 0 | V1 | 9 | | phone | 用户手机号 | `varchar(15)` | '' | V1 | 10 | 11 | ```sql 12 | CREATE TABLE `user` ( 13 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID', 14 | `name` varchar(20) NOT NULL DEFAULT '0' COMMENT '姓名', 15 | `age` int(10) NOT NULL DEFAULT '' COMMENT '年龄', 16 | `phone` varchar(15) NOT NULL DEFAULT '' COMMENT '手机号', 17 | `updated_at` bigint(11) NOT NULL DEFAULT '0' COMMENT '更新时间', 18 | `created_at` bigint(11) NOT NULL COMMENT '创建时间', 19 | `deleted_ts` bigint(11) NOT NULL DEFAULT '0' COMMENT '删除时间(0表示未删除)', 20 | PRIMARY KEY (`id`), 21 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表'; 22 | 23 | ``` 24 | 25 | 26 | ## Mongodb 27 | 28 | ## 1.1 用户元信息表(user_meta) 29 | 30 | | 参数 | 说明 | 类型 | 默认值 | 版本 | 31 | | ------ | -------- | -------------- | ------ | ---- | 32 | | avatar | 用户头像 | `varchar(200)` | 0 | V1 | 33 | 34 | -------------------------------------------------------------------------------- /examples/project/README.md: -------------------------------------------------------------------------------- 1 | # project 2 | 3 | Command project is the tp-micro service project. 4 |
The framework reference: https://github.com/xiaoenai/tp-micro 5 | 6 | ## API Desc 7 | 8 | ### Stat 9 | 10 | Stat handler 11 | 12 | - URI: `/project/stat` 13 | - REQ-QUERY: 14 | - `ts={int64}` // timestamps 15 | - REQ-BODY: 16 | 17 | ```js 18 | {} 19 | ``` 20 | 21 | - RESULT: 22 | 23 | 24 | ### Home 25 | 26 | Home handler 27 | 28 | - URI: `/project/home` 29 | - REQ-QUERY: 30 | - REQ-BODY: 31 | - RESULT: 32 | 33 | ```js 34 | { 35 | "content": "" // {string} // text 36 | } 37 | ``` 38 | 39 | 40 | 41 | ### Math_Divide 42 | 43 | Divide handler 44 | 45 | - URI: `/project/math/divide` 46 | - REQ-QUERY: 47 | - REQ-BODY: 48 | 49 | ```js 50 | { 51 | "a": -0.000000, // {float64} // dividend 52 | "b": -0.000000 // {float64} // divisor 53 | } 54 | ``` 55 | 56 | - RESULT: 57 | 58 | ```js 59 | { 60 | "c": -0.000000 // {float64} // quotient 61 | } 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | *This is a project created by `micro gen` command.* 71 | 72 | *[About Micro Command](https://github.com/xiaoenai/tp-micro/tree/v2/cmd/micro)* 73 | -------------------------------------------------------------------------------- /cmd/configer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/henrylee2cn/cfgo" 5 | "github.com/henrylee2cn/erpc/v6" 6 | micro "github.com/xiaoenai/tp-micro/v6" 7 | "github.com/xiaoenai/tp-micro/v6/configer" 8 | "github.com/xiaoenai/tp-micro/v6/discovery" 9 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 10 | ) 11 | 12 | type config struct { 13 | Server micro.SrvConfig `yaml:"server"` 14 | Etcd etcd.EasyConfig `yaml:"etcd"` 15 | } 16 | 17 | func (e *config) Reload(bindFunc cfgo.BindFunc) error { 18 | return bindFunc() 19 | } 20 | 21 | func main() { 22 | cfg := &config{ 23 | Server: micro.SrvConfig{ 24 | ListenAddress: ":4040", 25 | }, 26 | Etcd: etcd.EasyConfig{ 27 | Endpoints: []string{"http://127.0.0.1:2379"}, 28 | }, 29 | } 30 | cfgo.MustReg("configer", cfg) 31 | 32 | etcdClient, err := etcd.EasyNew(cfg.Etcd) 33 | if err != nil { 34 | erpc.Fatalf("%v", err) 35 | } 36 | 37 | configer.InitMgr(etcdClient) 38 | 39 | srv := micro.NewServer( 40 | cfg.Server, 41 | discovery.ServicePluginFromEtcd( 42 | cfg.Server.InnerIpPort(), 43 | etcdClient, 44 | ), 45 | ) 46 | srv.RouteCall(configer.CallCtrl()) 47 | srv.ListenAndServe() 48 | } 49 | -------------------------------------------------------------------------------- /examples/discovery/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/henrylee2cn/erpc/v6" 7 | "github.com/henrylee2cn/erpc/v6/socket/example/pb" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | "github.com/xiaoenai/tp-micro/v6/discovery" 10 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 11 | ) 12 | 13 | func main() { 14 | // discovery.SetServiceNamespace("test@") 15 | erpc.SetSocketNoDelay(false) 16 | erpc.SetShutdown(time.Second*20, nil, nil) 17 | 18 | cfg := micro.SrvConfig{ 19 | DefaultBodyCodec: "protobuf", 20 | ListenAddress: ":", 21 | EnableHeartbeat: true, 22 | } 23 | srv := micro.NewServer(cfg, discovery.ServicePlugin( 24 | cfg.InnerIpPort(), 25 | etcd.EasyConfig{ 26 | Endpoints: []string{"http://127.0.0.1:2379"}, 27 | }, 28 | )) 29 | { 30 | group := srv.SubRoute("group") 31 | group.RouteCall(new(Home)) 32 | } 33 | srv.ListenAndServe() 34 | } 35 | 36 | // Home controller 37 | type Home struct { 38 | erpc.CallCtx 39 | } 40 | 41 | // Test handler 42 | func (h *Home) Test(args *pb.PbTest) (*pb.PbTest, *erpc.Status) { 43 | return &pb.PbTest{ 44 | A: args.A + args.B, 45 | B: args.A - args.B, 46 | }, nil 47 | } 48 | -------------------------------------------------------------------------------- /micro/create/project_test.go: -------------------------------------------------------------------------------- 1 | package create 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/xiaoenai/tp-micro/v6/micro/info" 8 | ) 9 | 10 | func TestGenerator(t *testing.T) { 11 | info.Init("test") 12 | proj := NewProject([]byte(__tpl__)) 13 | proj.gen() 14 | t.Logf("main.go:\n%s", proj.codeFiles["main.go"]) 15 | t.Logf("config.go:\n%s", proj.codeFiles["config.go"]) 16 | t.Logf("args/const.gen.go:\n%s", proj.codeFiles["args/const.gen.go"]) 17 | t.Logf("args/type.gen.go:\n%s", proj.codeFiles["args/type.gen.go"]) 18 | t.Logf("logic/tmp_code.gen.go:\n%s", proj.codeFiles["logic/tmp_code.gen.go"]) 19 | t.Logf("logic/model/init.go:\n%s", proj.codeFiles["logic/model/init.go"]) 20 | t.Logf("api/pull_handler.gen.go:\n%s", proj.codeFiles["api/pull_handler.gen.go"]) 21 | t.Logf("api/push_handler.gen.go:\n%s", proj.codeFiles["api/push_handler.gen.go"]) 22 | t.Logf("api/router.gen.go:\n%s", proj.codeFiles["api/router.gen.go"]) 23 | t.Logf("sdk/rpc.gen.go:\n%s", proj.codeFiles["sdk/rpc.gen.go"]) 24 | t.Logf("sdk/rpc_test.gen.go:\n%s", proj.codeFiles["sdk/rpc.gen_test.go"]) 25 | for k, v := range proj.codeFiles { 26 | if strings.HasPrefix(k, "logic/model") { 27 | t.Logf("%s:\n%s", k, v) 28 | } 29 | } 30 | t.Logf("README.md:\n%s", proj.genReadme()) 31 | } 32 | -------------------------------------------------------------------------------- /examples/discovery/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/henrylee2cn/erpc/v6" 7 | "github.com/henrylee2cn/erpc/v6/socket/example/pb" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | "github.com/xiaoenai/tp-micro/v6/discovery" 10 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 11 | ) 12 | 13 | func main() { 14 | // discovery.SetServiceNamespace("test@") 15 | erpc.SetSocketNoDelay(false) 16 | erpc.SetShutdown(time.Second*20, nil, nil) 17 | 18 | cli := micro.NewClient( 19 | micro.CliConfig{ 20 | DefaultBodyCodec: "protobuf", 21 | DefaultDialTimeout: time.Second * 5, 22 | Failover: 3, 23 | CircuitBreaker: micro.CircuitBreakerConfig{ 24 | Enable: true, 25 | ErrorPercentage: 50, 26 | }, 27 | HeartbeatSecond: 3, 28 | }, 29 | discovery.NewLinker(etcd.EasyConfig{ 30 | Endpoints: []string{"http://127.0.0.1:2379"}, 31 | }), 32 | ) 33 | defer cli.Close() 34 | var reply = new(pb.PbTest) 35 | stat := cli.Call( 36 | "/group/home/test", 37 | &pb.PbTest{A: 10, B: 2}, 38 | reply, 39 | ).Status() 40 | if stat != nil { 41 | erpc.Errorf("call error: %v", stat) 42 | } else { 43 | erpc.Infof("call reply: %v", reply) 44 | } 45 | 46 | // test heartbeat 47 | time.Sleep(10e9) 48 | cli.UseCallHeartbeat() 49 | time.Sleep(10e9) 50 | } 51 | -------------------------------------------------------------------------------- /examples/binder/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | micro "github.com/xiaoenai/tp-micro/v6" 6 | ) 7 | 8 | func main() { 9 | erpc.SetLoggerLevel("ERROR") 10 | cli := micro.NewClient( 11 | micro.CliConfig{ 12 | Failover: 3, 13 | HeartbeatSecond: 4, 14 | }, 15 | micro.NewStaticLinker(":9090"), 16 | ) 17 | defer cli.Close() 18 | 19 | type Arg struct { 20 | A int 21 | B int 22 | } 23 | 24 | var result int 25 | stat := cli.Call("/static/p/divide?x=testquery_x&xy_z=testquery_xy_z", &Arg{ 26 | A: 10, 27 | B: 2, 28 | }, &result).Status() 29 | if erpc.IsConnError(stat) { 30 | erpc.Fatalf("has conn rerror: %v", stat) 31 | } 32 | if stat != nil { 33 | erpc.Fatalf("%v", stat) 34 | } 35 | erpc.Printf("test 10/2=%d", result) 36 | 37 | stat = cli.Call("/static/p/divide?x=testquery_x&xy_z=testquery_xy_z", &Arg{ 38 | A: 10, 39 | B: 0, 40 | }, &result).Status() 41 | if erpc.IsConnError(stat) { 42 | erpc.Fatalf("has conn rerror: %v", stat) 43 | } 44 | if stat == nil { 45 | erpc.Fatalf("%v", stat) 46 | } 47 | erpc.Printf("test 10/0:%v", stat) 48 | 49 | stat = cli.Call("/static/p/divide", &Arg{ 50 | A: 10, 51 | B: 5, 52 | }, &result).Status() 53 | if stat == nil { 54 | erpc.Fatalf("%v", stat) 55 | } 56 | erpc.Printf("test 10/5:%v", stat) 57 | } 58 | -------------------------------------------------------------------------------- /gateway/README.md: -------------------------------------------------------------------------------- 1 | # Gateway 2 | 3 | Package gateway is the main program for TCP and HTTP services. 4 | 5 | ## Demo 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "github.com/henrylee2cn/cfgo" 12 | "github.com/xiaoenai/tp-micro/v6/gateway" 13 | ) 14 | 15 | func main() { 16 | cfg := gateway.NewConfig() 17 | cfgo.MustReg("gateway", cfg) 18 | // Run a gateway instance with default business logic and default socket protocol. 19 | gateway.Run(*cfg, nil, nil) 20 | } 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Authorization 26 | 27 | - HTTP short connection gateway 28 | * Optional authorization 29 | * Use query or header parameter to carry authorization token 30 | 31 | - TCP long connection gateway 32 | * Required authorization 33 | * Use the first packet of the connection to carry authorization information:
Package type `CALL`, URI `/auth/verify`, BodyType `s`, Body `access token string` 34 | 35 | ### RequestID 36 | 37 | - HTTP short connection gateway 38 | * Optional query parameter 39 | * Use query parameter `_seq` to carry request ID 40 | 41 | - TCP long connection gateway 42 | * Required packet `seq` field 43 | * The request ID is `{session ID}@{packet seq}` 44 | 45 | ### HTTP Status Code 46 | 47 | - 200 OK 48 | - 299 Business error 49 | - 500 Internal communication error 50 | - Other codes (200,600) 51 | -------------------------------------------------------------------------------- /gateway/helper/gray/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/logic/model" 5 | ) 6 | 7 | type ( 8 | // IsGrayArgs is_gray API parameters 9 | IsGrayArgs struct { 10 | Uri string `param:" " protobuf:"bytes,1,opt,name=Uri,proto3" json:"Uri,omitempty"` 11 | Uid string `param:" " protobuf:"bytes,2,opt,name=Uid,proto3" json:"Uid,omitempty"` 12 | } 13 | 14 | // IsGrayResult is_gray API result 15 | IsGrayResult struct { 16 | Gray bool `protobuf:"varint,1,opt,name=Gray,proto3" json:"Gray,omitempty"` 17 | } 18 | 19 | // GetArgs get API parameters 20 | GetArgs struct { 21 | Uri string `param:" " protobuf:"bytes,1,opt,name=Uri,proto3" json:"Uri,omitempty"` 22 | } 23 | 24 | // DeleteArgs delete API parameters 25 | DeleteArgs struct { 26 | Uri string `param:" " protobuf:"bytes,1,opt,name=Uri,proto3" json:"Uri,omitempty"` 27 | } 28 | 29 | // SetArgs set API parameters 30 | SetArgs struct { 31 | Uri string `param:" " protobuf:"bytes,1,opt,name=Uri,proto3" json:"uri"` 32 | Regexp string `protobuf:"bytes,2,opt,name=Regexp,proto3" json:"regexp"` 33 | } 34 | ) 35 | 36 | // GrayMatch 37 | type GrayMatch = model.GrayMatch 38 | -------------------------------------------------------------------------------- /configer/common.go: -------------------------------------------------------------------------------- 1 | package configer 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/henrylee2cn/cfgo" 7 | "github.com/henrylee2cn/erpc/v6" 8 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 9 | ) 10 | 11 | // Config config interface 12 | type Config interface { 13 | UnmarshalJSON([]byte) error 14 | MarshalJSON() ([]byte, error) 15 | Reload([]byte) error 16 | } 17 | 18 | const ( 19 | // KEY_PREFIX the prifix of config data key in etcd 20 | KEY_PREFIX = "MICRO-CONF" 21 | ) 22 | 23 | // NewKey creates a config data key 24 | // Note: service and version can not contant "@"! 25 | func NewKey(service string, version string) string { 26 | return KEY_PREFIX + "@" + service + "@" + version 27 | } 28 | 29 | func must(err error) { 30 | if err != nil { 31 | erpc.Fatalf("%v", err) 32 | } 33 | } 34 | 35 | var ( 36 | onceRegYaml sync.Once 37 | etcdEasyConfig *etcd.EasyConfig 38 | ) 39 | 40 | type etcdConfig etcd.EasyConfig 41 | 42 | func (e *etcdConfig) Reload(bindFunc cfgo.BindFunc) error { 43 | return bindFunc() 44 | } 45 | 46 | // NewEtcdClientFromYaml uses config/etcd.yaml to create a etcd client. 47 | func NewEtcdClientFromYaml() (*etcd.Client, error) { 48 | onceRegYaml.Do(func() { 49 | etcdEasyConfig = new(etcd.EasyConfig) 50 | cfgo. 51 | MustGet("./config/etcd.yaml"). 52 | MustReg("ETCD", (*etcdConfig)(etcdEasyConfig)) 53 | }) 54 | return etcd.EasyNew(*etcdEasyConfig) 55 | } 56 | -------------------------------------------------------------------------------- /gateway/helper/gray/sdk/rpc_test.go: -------------------------------------------------------------------------------- 1 | package sdk_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/henrylee2cn/erpc/v6" 7 | micro "github.com/xiaoenai/tp-micro/v6" 8 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 9 | 10 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray" 11 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/types" 12 | ) 13 | 14 | // TestSdk test SDK. 15 | func TestSdk(t *testing.T) { 16 | gray.Init( 17 | micro.CliConfig{ 18 | Failover: 3, 19 | HeartbeatSecond: 4, 20 | }, 21 | etcd.EasyConfig{ 22 | Endpoints: []string{"http://127.0.0.1:2379"}, 23 | }, 24 | ) 25 | 26 | { 27 | reply, stat := gray.IsGray(new(types.IsGrayArgs)) 28 | if stat != nil { 29 | erpc.Errorf("IsGray: rerr: %v", stat) 30 | } else { 31 | erpc.Infof("IsGray: reply: %#v", reply) 32 | } 33 | } 34 | { 35 | reply, stat := gray.Get(new(types.GetArgs)) 36 | if stat != nil { 37 | erpc.Errorf("Get: rerr: %v", stat) 38 | } else { 39 | erpc.Infof("Get: reply: %#v", reply) 40 | } 41 | } 42 | { 43 | reply, stat := gray.Delete(new(types.DeleteArgs)) 44 | if stat != nil { 45 | erpc.Errorf("Delete: rerr: %v", stat) 46 | } else { 47 | erpc.Infof("Delete: reply: %#v", reply) 48 | } 49 | } 50 | { 51 | reply, stat := gray.Set(new(types.SetArgs)) 52 | if stat != nil { 53 | erpc.Errorf("Set: rerr: %v", stat) 54 | } else { 55 | erpc.Infof("Set: reply: %#v", reply) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/simple/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/henrylee2cn/erpc/v6" 7 | "github.com/henrylee2cn/erpc/v6/plugin/auth" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | ) 10 | 11 | //go:generate go build $GOFILE 12 | 13 | func main() { 14 | cli := micro.NewClient( 15 | micro.CliConfig{ 16 | Failover: 3, 17 | HeartbeatSecond: 4, 18 | }, 19 | micro.NewStaticLinker(":5020"), 20 | authBearer, 21 | ) 22 | 23 | var arg = &struct { 24 | A int 25 | B int 26 | }{ 27 | A: 10, 28 | B: 2, 29 | } 30 | 31 | var reply int 32 | 33 | stat := cli.Call("/math/divide?access_token=sdfghj", arg, &reply).Status() 34 | if !stat.OK() { 35 | erpc.Fatalf("%v", stat) 36 | } 37 | erpc.Infof("10/2=%d", reply) 38 | 39 | erpc.Debugf("waiting for 10s...") 40 | time.Sleep(time.Second * 10) 41 | 42 | arg.B = 5 43 | stat = cli.Call("/math/divide?access_token=sdfghj", arg, &reply).Status() 44 | if !stat.OK() { 45 | erpc.Fatalf("%v", stat) 46 | } 47 | erpc.Infof("10/5=%d", reply) 48 | 49 | erpc.Debugf("waiting for 10s...") 50 | time.Sleep(time.Second * 10) 51 | } 52 | 53 | const clientAuthInfo = "client-auth-info-12345" 54 | 55 | var authBearer = auth.NewBearerPlugin( 56 | func(sess auth.Session, fn auth.SendOnce) (stat *erpc.Status) { 57 | var ret string 58 | stat = fn(clientAuthInfo, &ret) 59 | if !stat.OK() { 60 | return 61 | } 62 | erpc.Infof("auth info: %s, result: %s", clientAuthInfo, ret) 63 | return 64 | }, 65 | erpc.WithBodyCodec('s'), 66 | ) 67 | -------------------------------------------------------------------------------- /examples/project/sdk/rpc.gen_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package sdk_test 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | 10 | "github.com/henrylee2cn/erpc/v6" 11 | micro "github.com/xiaoenai/tp-micro/v6" 12 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 13 | 14 | "github.com/xiaoenai/tp-micro/examples/project/args" 15 | "github.com/xiaoenai/tp-micro/examples/project/sdk" 16 | ) 17 | 18 | func init() { 19 | sdk.Init( 20 | micro.CliConfig{ 21 | Failover: 3, 22 | HeartbeatSecond: 4, 23 | }, 24 | etcd.EasyConfig{ 25 | Endpoints: []string{"http://127.0.0.1:2379"}, 26 | }, 27 | ) 28 | } 29 | 30 | func toJsonBytes(i interface{}) []byte { 31 | b, _ := json.MarshalIndent(i, "", " ") 32 | return b 33 | } 34 | 35 | func ExampleStat() { 36 | status := sdk.Stat(&args.StatArg{}) 37 | if status != nil { 38 | erpc.Errorf("Stat: status: %s", toJsonBytes(status)) 39 | } 40 | fmt.Printf("") 41 | // Output: 42 | } 43 | 44 | func ExampleHome() { 45 | result, status := sdk.Home(&args.EmptyStruct{}) 46 | if status != nil { 47 | erpc.Errorf("Home: status: %s", toJsonBytes(status)) 48 | } else { 49 | erpc.Infof("Home: result: %s", toJsonBytes(result)) 50 | } 51 | fmt.Printf("") 52 | // Output: 53 | } 54 | 55 | func ExampleMath_Divide() { 56 | result, status := sdk.Math_Divide(&args.DivideArg{}) 57 | if status != nil { 58 | erpc.Errorf("Math_Divide: status: %s", toJsonBytes(status)) 59 | } else { 60 | erpc.Infof("Math_Divide: result: %s", toJsonBytes(result)) 61 | } 62 | fmt.Printf("") 63 | // Output: 64 | } 65 | -------------------------------------------------------------------------------- /examples/project/config/config.yaml: -------------------------------------------------------------------------------- 1 | project: 2 | srv: 3 | network: "" 4 | listen_address: :0 5 | tls_cert_file: "" 6 | tls_key_file: "" 7 | default_session_age: 0s 8 | default_context_age: 0s 9 | slow_comet_duration: 500ms 10 | default_body_codec: "" 11 | print_detail: true 12 | count_time: true 13 | enable_heartbeat: true 14 | cli: 15 | network: "" 16 | local_ip: "" 17 | tls_cert_file: "" 18 | tls_key_file: "" 19 | default_session_age: 0s 20 | default_context_age: 0s 21 | default_dial_timeout: 0s 22 | failover: 0 23 | slow_comet_duration: 0s 24 | default_body_codec: "" 25 | print_detail: false 26 | count_time: false 27 | heartbeat_second: 0 28 | circuit_breaker: 29 | enable: false 30 | error_percentage: 0 31 | break_duration: 0s 32 | etcd: 33 | endpoints: 34 | - http://127.0.0.1:2379 35 | dial_timeout: 0s 36 | username: "" 37 | password: "" 38 | mysql: 39 | database: "" 40 | username: "" 41 | password: "" 42 | host: "" 43 | port: 0 44 | max_idle_conns: 0 45 | max_open_conns: 0 46 | conn_max_lifetime: 0 47 | no_cache: false 48 | mongo: 49 | addrs: [] 50 | timeout: 0s 51 | database: "" 52 | username: "" 53 | passward: "" 54 | pool_limit: 0 55 | no_cache: false 56 | redis: 57 | deploy_type: single 58 | for_single: 59 | addr: 127.0.0.1:6379 60 | for_cluster: 61 | addrs: [] 62 | pool_size_per_node: 0 63 | idle_timeout: 0 64 | cache_expire: 24h0m0s 65 | log_level: TRACE 66 | -------------------------------------------------------------------------------- /doc/TODO.md: -------------------------------------------------------------------------------- 1 | #### 统一认证中心 2 | 3 | 主要是对app用户、内部用户、app等的认证服务,包括: 4 | 5 | - 用户的注册、登录验证、token鉴权 6 | - 内部信息系统用户的管理和登录鉴权 7 | - App的管理,包括app的secret生成,app信息的验证(如验证接口签名)等 8 | 9 | 之所以需要统一认证中心,就是为了能够集中对这些所有app都会用到的信息进行管理,也给所有应用提供统一的认证服务。尤其是在有很多业务需要共享用户数据的时候,构建一个统一认证中心是非常必要的。此外,通过统一认证中心构建移动app的单点登录也是水到渠成的事情。 10 | 11 | (可以将认证后的信息加密存储到etcd或redis中供多个app使用) 12 | 13 | #### 单点登录系统 14 | 15 | 只需要一次用户登录,就能够进入多个业务应用(权限可以不相同) 16 | 17 | #### 统一配置中心 18 | 19 | 将配置文件写在yaml文件中,修改的时候只需要更新文件重新部署即可,可以做到不牵扯代码层面改动的目的。统一配置中心,则是基于这种方式之上的统一对所有业务或者基础后端服务的相关配置文件进行管理的统一服务, 具有以下特性: 20 | 21 | - 能够在线动态修改配置文件并生效 22 | - 配置文件可以区分环境(开发、测试、生产等) 23 | 24 | 也可以选择etcd作为配置存储 25 | 26 | #### 统一部署中心 27 | 28 | 批量部署服务,并支持定时上下线等功能 29 | 30 | #### 统一日志服务 31 | 32 | 日志是开发过程必不可少的东西。有时候,打印日志的时机、技巧是很能体现出工程师编码水平的。毕竟,日志是线上服务能够定位、排查异常最为直接的信息。 33 | 34 | 通常的,将日志分散在各个业务中非常不方便对问题的管理和排查。统一日志服务则使用单独的日志服务器记录日志,各个业务通过统一的日志客户端将日志输出到日志服务器上。 35 | 36 | #### 故障监控 37 | 38 | 系统监控:主要指的对主机的带宽、cpu、内存、硬盘、io等硬件资源的监控。这可以使用开源的nagios、cacti等开源软件进行监控。目前,市面上也有很多第三方服务能够提供对于主机资源的监控,如监控宝等。对于分布式服务集群(如hadoop、storm、kafka、flume等集群)的监控则可以使用ganglia。此外,小米开源的OpenFalcon也很不错,涵盖了系统监控、JVM监控等,也支持自定义的监控机制。 39 | 业务监控:是在主机资源层面以上的监控,比如app的pv、uv数据异常、交易失败等。需要业务中加入相关的监控代码,比如在异常抛出的地方,加一段日志记录。 40 | 监控还有一个关键的步骤就是告警。告警的方式有很多种:邮件、im、短信等。考虑到故障的重要性不同、告警的合理性、便于定位问题等因素,有以下建议: 41 | 42 | 告警日志要记录发生故障的机器id,尤其是在集群服务中,如果没有记录机器id,那么对于后续的问题定位会很困难。 43 | 要对告警做聚合,不要每一个故障都单独进行告警,这样会对工程师造成极大的困扰。 44 | 要对告警做等级划分,不能对所有告警都做同样的优先级处理。 45 | 使用钉钉、微信做为告警软件,能够在节省短信成本的情况下,保证告警的到达率。 46 | 47 | 48 | #### 集群压测 49 | 50 | #### 限流与灰度 51 | 52 | 对每一种类型的请求都限定流量;或按照规则进行分流(灰度)。 53 | 54 | 55 | #### 项目生成工具 56 | 57 | 快速生成基于ant的项目 58 | 59 | 60 | #### 其他 61 | 62 | - 消息队列 63 | - 文件存储 -------------------------------------------------------------------------------- /examples/project/sdk/rpc.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package sdk 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/henrylee2cn/erpc/v6" 10 | micro "github.com/xiaoenai/tp-micro/v6" 11 | "github.com/xiaoenai/tp-micro/v6/discovery" 12 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 13 | 14 | "github.com/xiaoenai/tp-micro/examples/project/args" 15 | ) 16 | 17 | var _ = fmt.Sprintf 18 | var client *micro.Client 19 | 20 | // Init initializes client with configs. 21 | func Init(cliConfig micro.CliConfig, etcdConfing etcd.EasyConfig) { 22 | client = micro.NewClient( 23 | cliConfig, 24 | discovery.NewLinker(etcdConfing), 25 | ) 26 | } 27 | 28 | // InitWithClient initializes client with specified object. 29 | func InitWithClient(cli *micro.Client) { 30 | client = cli 31 | } 32 | 33 | // Stat handler 34 | func Stat(arg *args.StatArg, setting ...erpc.MessageSetting) *erpc.Status { 35 | setting = append(setting, erpc.WithAddMeta("ts", fmt.Sprintf("%v", arg.Ts))) 36 | return client.Push("/project/stat", arg, setting...) 37 | } 38 | 39 | // Home handler 40 | func Home(arg *args.EmptyStruct, setting ...erpc.MessageSetting) (*args.HomeResult, *erpc.Status) { 41 | result := new(args.HomeResult) 42 | status := client.Call("/project/home", arg, result, setting...).Status() 43 | return result, status 44 | } 45 | 46 | // Divide handler 47 | func Math_Divide(arg *args.DivideArg, setting ...erpc.MessageSetting) (*args.DivideResult, *erpc.Status) { 48 | result := new(args.DivideResult) 49 | status := client.Call("/project/math/divide", arg, result, setting...).Status() 50 | return result, status 51 | } 52 | -------------------------------------------------------------------------------- /gateway/types/api.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package types; 4 | 5 | message SocketTotalReply { 6 | int32 conn_total = 1; 7 | } 8 | 9 | message SocketPushArgs { 10 | string session_id = 1; 11 | string uri = 2; 12 | bytes body = 3; 13 | int32 body_codec = 4; 14 | } 15 | 16 | message SocketMpushArgs { 17 | repeated MpushTarget target = 1; 18 | string uri = 2; 19 | bytes body = 3; 20 | int32 body_codec = 4; 21 | } 22 | 23 | message MpushTarget { 24 | string session_id = 1; 25 | string additional_query = 2; 26 | } 27 | 28 | message SocketMpushReply { 29 | repeated string failure_session_ids = 1; 30 | } 31 | 32 | message SocketKickArgs { 33 | string session_id = 1; 34 | } 35 | 36 | message SocketKickReply { 37 | bool existed = 1; 38 | } 39 | 40 | message WsTotalReply { 41 | int32 conn_total = 1; 42 | } 43 | 44 | message WsPushArgs { 45 | string session_id = 1; 46 | string uri = 2; 47 | bytes body = 3; 48 | int32 body_codec = 4; 49 | } 50 | 51 | message WsMpushArgs { 52 | repeated MpushTarget target = 1; 53 | string uri = 2; 54 | bytes body = 3; 55 | int32 body_codec = 4; 56 | } 57 | 58 | message WsMpushReply { 59 | repeated string failure_session_ids = 1; 60 | } 61 | 62 | message WsKickArgs { 63 | string session_id = 1; 64 | } 65 | 66 | message WsKickReply { 67 | bool existed = 1; 68 | } 69 | 70 | message GwHosts { 71 | repeated string http = 1; 72 | repeated string socket = 2; 73 | repeated string ws = 3; 74 | } 75 | -------------------------------------------------------------------------------- /examples/simple/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | micro "github.com/xiaoenai/tp-micro/v6" 6 | "github.com/xiaoenai/tp-micro/v6/discovery" 7 | "github.com/xiaoenai/tp-micro/v6/helper" 8 | html "github.com/xiaoenai/tp-micro/v6/helper/mod-html" 9 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 10 | ) 11 | 12 | //go:generate go build $GOFILE 13 | 14 | func init() { 15 | // html.ParseGlob("*.tpl") 16 | 17 | html.Parse("home", ` 18 | 19 | 20 | Home 21 | 22 | 23 |
24 |

{{.}}

25 | 26 | `) 27 | } 28 | 29 | // Home HTML home page 30 | func Home(ctx erpc.CallCtx, args *struct{}) ([]byte, *erpc.Status) { 31 | return html.Render(ctx, "home", "Home Page Test!") 32 | } 33 | 34 | // Home2 HTML home page 35 | func Home2(ctx erpc.CallCtx, args *struct{}) ([]byte, *erpc.Status) { 36 | return nil, helper.Redirect(ctx, 302, "http://localhost:5000/home") 37 | } 38 | 39 | // Args args 40 | type Args struct { 41 | A int 42 | B int `param:""` 43 | } 44 | 45 | // Math handler 46 | type Math struct { 47 | erpc.CallCtx 48 | } 49 | 50 | // Divide divide API 51 | func (m *Math) Divide(args *Args) (int, *erpc.Status) { 52 | return args.A / args.B, nil 53 | } 54 | 55 | func main() { 56 | cfg := micro.SrvConfig{ 57 | ListenAddress: ":0", 58 | EnableHeartbeat: true, 59 | PrintDetail: true, 60 | } 61 | srv := micro.NewServer(cfg, discovery.ServicePlugin( 62 | cfg.InnerIpPort(), 63 | etcd.EasyConfig{ 64 | Endpoints: []string{"http://127.0.0.1:2379"}, 65 | }, 66 | )) 67 | srv.RouteCallFunc(Home) 68 | srv.RouteCallFunc(Home2) 69 | srv.RouteCall(new(Math)) 70 | srv.ListenAndServe() 71 | } 72 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 HenryLee. All Rights Reserved. 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 | package micro 16 | 17 | import ( 18 | "net" 19 | "strings" 20 | "sync" 21 | 22 | "github.com/henrylee2cn/goutil" 23 | "github.com/henrylee2cn/erpc/v6" 24 | ) 25 | 26 | // InnerIpPort returns the service's intranet address, such as '192.168.1.120:8080'. 27 | func InnerIpPort(port string) (string, error) { 28 | host, err := goutil.IntranetIP() 29 | if err != nil { 30 | return "", err 31 | } 32 | hostPort := net.JoinHostPort(host, port) 33 | return hostPort, nil 34 | } 35 | 36 | // OuterIpPort returns the service's extranet address, such as '113.116.141.121:8080'. 37 | func OuterIpPort(port string) (string, error) { 38 | host, err := goutil.ExtranetIP() 39 | if err != nil { 40 | return "", err 41 | } 42 | hostPort := net.JoinHostPort(host, port) 43 | return hostPort, nil 44 | } 45 | 46 | var initOnce sync.Once 47 | 48 | func doInit() { 49 | initOnce.Do(func() { 50 | go erpc.GraceSignal() 51 | }) 52 | } 53 | 54 | func getUriPath(serviceMethod string) string { 55 | if idx := strings.Index(serviceMethod, "?"); idx != -1 { 56 | return serviceMethod[:idx] 57 | } 58 | return serviceMethod 59 | } 60 | -------------------------------------------------------------------------------- /examples/project/__tp-micro__tpl__.go: -------------------------------------------------------------------------------- 1 | // Command project is the tp-micro service project. 2 | // The framework reference: https://github.com/xiaoenai/tp-micro 3 | package __TPL__ 4 | 5 | import "github.com/xiaoenai/tp-micro/v6/model/mongo" 6 | 7 | // __API_PULL__ register PULL router 8 | type __API_PULL__ interface { 9 | // Home handler 10 | Home(*struct{}) *HomeResult 11 | // Math controller 12 | Math 13 | } 14 | 15 | // __API_PUSH__ register PUSH router: 16 | // /stat 17 | type __API_PUSH__ interface { 18 | Stat(*StatArg) 19 | } 20 | 21 | // __MYSQL_MODEL__ create mysql model 22 | type __MYSQL_MODEL__ struct { 23 | User 24 | Log 25 | Device 26 | } 27 | 28 | // __MONGO_MODEL__ create mongodb model 29 | type __MONGO_MODEL__ struct { 30 | Meta 31 | } 32 | 33 | // Math controller 34 | type Math interface { 35 | // Divide handler 36 | Divide(*DivideArg) *DivideResult 37 | } 38 | 39 | // HomeResult home result 40 | type HomeResult struct { 41 | Content string // text 42 | } 43 | 44 | type ( 45 | // DivideArg divide api arg 46 | DivideArg struct { 47 | // dividend 48 | A float64 49 | // divisor 50 | B float64 `param:""` 51 | } 52 | // DivideResult divide api result 53 | DivideResult struct { 54 | // quotient 55 | C float64 56 | } 57 | ) 58 | 59 | // StatArg stat handler arg 60 | type StatArg struct { 61 | Ts int64 `param:""` // timestamps 62 | } 63 | 64 | // User user info 65 | type User struct { 66 | Id int64 `key:"pri"` 67 | Name string `key:"uni"` 68 | Age int32 69 | } 70 | 71 | type Log struct { 72 | Text string 73 | } 74 | 75 | type Device struct { 76 | UUID string `key:"pri"` 77 | } 78 | 79 | type Meta struct { 80 | Id mongo.ObjectId `key:"pri"` 81 | Name string `key:"uni"` 82 | Hobby []string 83 | Tags []string 84 | } 85 | -------------------------------------------------------------------------------- /model/sqlx/iface_db_tx.go: -------------------------------------------------------------------------------- 1 | package sqlx 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | ) 7 | 8 | // DbOrTx contains all the exportable methods of *DB 9 | type DbOrTx interface { 10 | BindNamed(query string, arg interface{}) (string, []interface{}, error) 11 | DriverName() string 12 | Get(dest interface{}, query string, args ...interface{}) error 13 | GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error 14 | MustExec(query string, args ...interface{}) sql.Result 15 | MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result 16 | NamedExec(query string, arg interface{}) (sql.Result, error) 17 | NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) 18 | NamedQuery(query string, arg interface{}) (*Rows, error) 19 | // NamedQueryContext(ctx context.Context, query string, arg interface{}) (*Rows, error) 20 | PrepareNamed(query string) (*NamedStmt, error) 21 | // PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) 22 | Preparex(query string) (*Stmt, error) 23 | // PreparexContext(ctx context.Context, query string) (*Stmt, error) 24 | QueryRowx(query string, args ...interface{}) *Row 25 | QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row 26 | Queryx(query string, args ...interface{}) (*Rows, error) 27 | QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) 28 | Rebind(query string) string 29 | Select(dest interface{}, query string, args ...interface{}) error 30 | SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error 31 | 32 | Exec(query string, args ...interface{}) (sql.Result, error) 33 | Prepare(query string) (*sql.Stmt, error) 34 | Query(query string, args ...interface{}) (*sql.Rows, error) 35 | QueryRow(query string, args ...interface{}) *sql.Row 36 | } 37 | -------------------------------------------------------------------------------- /micro/create/parse_test.go: -------------------------------------------------------------------------------- 1 | package create 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var tInfo = newTplInfo([]byte(` 8 | package create 9 | import ( 10 | "testing" 11 | ) 12 | import f "fmt" 13 | 14 | type __API_PULL__ interface { 15 | // Stat doc 16 | Stat(X) *struct{}// Stat comment 17 | // Math doc 18 | Math // Math comment 19 | } 20 | 21 | type Math interface { 22 | // Divide handler 23 | Divide(*DivideArg) *DivideResult 24 | Sub 25 | } 26 | 27 | type Sub interface { 28 | // Divide handler 29 | Divide(*DivideArg) *DivideResult 30 | } 31 | 32 | // the comment can not be caught! 33 | type ( 34 | // DivideArg divide api arg 35 | DivideArg struct { 36 | // dividend 37 | A float64 38 | // divisor 39 | B float64 ` + "`param:\"\"`" + ` 40 | } 41 | // DivideResult divide api result 42 | DivideResult struct { 43 | // quotient 44 | C float64 45 | } 46 | ) 47 | 48 | type __API_PUSH__ interface { 49 | Stat(*StatArg) 50 | } 51 | 52 | // StatArg stat handler arg 53 | type StatArg struct { 54 | Ts int64 // timestamps 55 | } 56 | 57 | type __MYSQL_MODEL__ struct { 58 | DivideResult 59 | StatArg 60 | } 61 | 62 | type __MONGO_MODEL__ struct { 63 | A 64 | } 65 | 66 | // A comment ... 67 | type A struct{ 68 | // X doc ... 69 | X string // X comment ... 70 | // Y doc ... 71 | Y int // Y comment ... 72 | } 73 | 74 | // X A alias 75 | type X = A 76 | 77 | type Y X 78 | 79 | type E struct{} 80 | `)) 81 | 82 | func TestParse(t *testing.T) { 83 | tInfo.Parse() 84 | t.Logf("TypeImportString: %s", tInfo.TypeImportString()) 85 | t.Logf("TypesString:\n%v", tInfo.TypesString()) 86 | t.Logf("pull apis:\n%v", tInfo.CallHandlerString(nil)) 87 | t.Logf("push apis:\n%v", tInfo.PushHandlerString(nil)) 88 | t.Logf("router:\n%v", tInfo.RouterString("_group")) 89 | for _, m := range tInfo.models.mysql { 90 | t.Logf("mysql:\n%v", m) 91 | } 92 | for _, m := range tInfo.models.mongo { 93 | t.Logf("mongo:\n%v", m) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /micro/create/structtag/README.md: -------------------------------------------------------------------------------- 1 | # structtag [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structtag) [![Build Status](https://travis-ci.org/fatih/structtag.svg?branch=master)](https://travis-ci.org/fatih/structtag) 2 | 3 | structtag provides an easy way of parsing and manipulating struct tag fields. 4 | Please vendor the library as it might change in future versions. 5 | 6 | # Install 7 | 8 | ```bash 9 | go get github.com/fatih/structtag 10 | ``` 11 | 12 | # Example 13 | 14 | ```go 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "sort" 21 | 22 | "github.com/fatih/structtag" 23 | ) 24 | 25 | func main() { 26 | type t struct { 27 | t string `json:"foo,omitempty,string" xml:"foo"` 28 | } 29 | 30 | // get field tag 31 | tag := reflect.TypeOf(t{}).Field(0).Tag 32 | 33 | // ... and start using structtag by parsing the tag 34 | tags, err := structtag.Parse(string(tag)) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | // iterate over all tags 40 | for _, t := range tags.Tags() { 41 | fmt.Printf("tag: %+v\n", t) 42 | } 43 | 44 | // get a single tag 45 | jsonTag, err := tags.Get("json") 46 | if err != nil { 47 | panic(err) 48 | } 49 | fmt.Println(jsonTag) // Output: json:"foo,omitempty,string" 50 | fmt.Println(jsonTag.Key) // Output: json 51 | fmt.Println(jsonTag.Name) // Output: foo 52 | fmt.Println(jsonTag.Options) // Output: [omitempty string] 53 | 54 | // change existing tag 55 | jsonTag.Name = "foo_bar" 56 | jsonTag.Options = nil 57 | tags.Set(jsonTag) 58 | 59 | // add new tag 60 | tags.Set(&structtag.Tag{ 61 | Key: "hcl", 62 | Name: "foo", 63 | Options: []string{"squash"}, 64 | }) 65 | 66 | // print the tags 67 | fmt.Println(tags) // Output: json:"foo_bar" xml:"foo" hcl:"foo,squash" 68 | 69 | // sort tags according to keys 70 | sort.Sort(tags) 71 | fmt.Println(tags) // Output: hcl:"foo,squash" json:"foo_bar" xml:"foo" 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /discovery/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 HenryLee. All Rights Reserved. 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 | package discovery 16 | 17 | import ( 18 | "encoding/json" 19 | "strings" 20 | "sync" 21 | 22 | "github.com/henrylee2cn/goutil" 23 | ) 24 | 25 | var ( 26 | // serviceNamespace the service prefix of ETCD key 27 | serviceNamespace = "TP-SRV@" 28 | ) 29 | 30 | // ServiceNamespace returns the service prefix of ETCD key. 31 | func ServiceNamespace() string { 32 | return serviceNamespace 33 | } 34 | 35 | // SetServiceNamespace sets the service prefix of ETCD key. 36 | // Note: It should be called the first time after importing this package. 37 | func SetServiceNamespace(prefix string) { 38 | serviceNamespace = prefix 39 | } 40 | 41 | // ServiceInfo serivce info 42 | type ServiceInfo struct { 43 | UriPaths []string `json:"uri_paths"` 44 | mu sync.RWMutex 45 | } 46 | 47 | // String returns the JSON string. 48 | func (s *ServiceInfo) String() string { 49 | s.mu.RLock() 50 | defer s.mu.RUnlock() 51 | b, _ := json.Marshal(s) 52 | return goutil.BytesToString(b) 53 | } 54 | 55 | // Append appends uri path 56 | func (s *ServiceInfo) Append(uriPath ...string) { 57 | s.mu.Lock() 58 | defer s.mu.Unlock() 59 | s.UriPaths = append(s.UriPaths, uriPath...) 60 | } 61 | 62 | func createServiceKey(addr string) string { 63 | return serviceNamespace + addr 64 | } 65 | 66 | func getHostport(serviceKey string) string { 67 | return strings.TrimPrefix(serviceKey, serviceNamespace) 68 | } 69 | -------------------------------------------------------------------------------- /configer/mgr.go: -------------------------------------------------------------------------------- 1 | package configer 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/henrylee2cn/erpc/v6" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 10 | ) 11 | 12 | var mgr = struct { 13 | etcdClient *etcd.Client 14 | }{} 15 | 16 | // InitMgr initializes a config manager. 17 | func InitMgr(etcdClient *etcd.Client) { 18 | mgr.etcdClient = etcdClient 19 | } 20 | 21 | // CallCtrl returns a new CALL controller. 22 | func CallCtrl() interface{} { 23 | return new(cfg) 24 | } 25 | 26 | type cfg struct { 27 | erpc.CallCtx 28 | } 29 | 30 | var ( 31 | statEtcdError = micro.RerrInternalServerError.Copy().SetMessage("Etcd Error") 32 | statNotFound = micro.RerrNotFound.Copy().SetReason("Config is not exist") 33 | ) 34 | 35 | func (c *cfg) List(*struct{}) ([]string, *erpc.Status) { 36 | resp, err := mgr.etcdClient.Get(context.TODO(), KEY_PREFIX, etcd.WithPrefix()) 37 | if err != nil { 38 | return nil, statEtcdError.Copy().SetReason(err.Error()) 39 | } 40 | var r = make([]string, len(resp.Kvs)) 41 | for i, kv := range resp.Kvs { 42 | r[i] = string(kv.Key) 43 | } 44 | return r, nil 45 | } 46 | 47 | func (c *cfg) Get(*struct{}) (string, *erpc.Status) { 48 | key := c.Query().Get("config-key") 49 | resp, err := mgr.etcdClient.Get(context.TODO(), key) 50 | if err != nil { 51 | return "", statEtcdError.Copy().SetReason(err.Error()) 52 | } 53 | if len(resp.Kvs) == 0 { 54 | return "", statNotFound 55 | } 56 | n := new(Node) 57 | json.Unmarshal(resp.Kvs[0].Value, n) 58 | if n.Config == "" { 59 | return "{}", nil 60 | } 61 | return n.Config, nil 62 | } 63 | 64 | // ConfigKV config key-value data. 65 | type ConfigKV struct { 66 | Key string 67 | Value string 68 | } 69 | 70 | func (c *cfg) Update(cfgKv *ConfigKV) (*struct{}, *erpc.Status) { 71 | _, err := mgr.etcdClient.Put(context.TODO(), cfgKv.Key, (&Node{ 72 | Initialized: true, 73 | Config: cfgKv.Value, 74 | }).String()) 75 | if err != nil { 76 | return nil, statEtcdError.Copy().SetReason(err.Error()) 77 | } 78 | return nil, nil 79 | } 80 | -------------------------------------------------------------------------------- /linker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 HenryLee. All Rights Reserved. 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 | package micro 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6" 19 | ) 20 | 21 | // Linker linker for client. 22 | type Linker interface { 23 | // Select selects a service address by URI path. 24 | Select(uriPath string, exclude map[string]struct{}) (addr string, stat *erpc.Status) 25 | // Len returns the number of nodes corresponding to the URI. 26 | Len(uriPath string) int 27 | // WatchOffline pushs service node offline notification. 28 | WatchOffline() <-chan string 29 | // Close closes the linker. 30 | Close() 31 | } 32 | 33 | // static linker 34 | 35 | // NewStaticLinker creates a static linker. 36 | func NewStaticLinker(srvAddr string) Linker { 37 | return &staticLinker{ 38 | srvAddr: srvAddr, 39 | ch: make(chan string), 40 | } 41 | } 42 | 43 | type staticLinker struct { 44 | srvAddr string 45 | ch chan string 46 | } 47 | 48 | // Select selects a service address by URI path. 49 | func (d *staticLinker) Select(uriPath string, exclude map[string]struct{}) (string, *erpc.Status) { 50 | return d.srvAddr, nil 51 | } 52 | 53 | // Len returns the number of nodes corresponding to the URI. 54 | func (d *staticLinker) Len(uriPath string) int { 55 | return 1 56 | } 57 | 58 | // WatchOffline pushs service node offline notification. 59 | func (d *staticLinker) WatchOffline() <-chan string { 60 | return d.ch 61 | } 62 | 63 | // Close closes the linker. 64 | func (d *staticLinker) Close() { 65 | close(d.ch) 66 | } 67 | -------------------------------------------------------------------------------- /examples/project/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/henrylee2cn/cfgo" 7 | "github.com/henrylee2cn/erpc/v6" 8 | "github.com/henrylee2cn/goutil" 9 | micro "github.com/xiaoenai/tp-micro/v6" 10 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 11 | "github.com/xiaoenai/tp-micro/v6/model/mongo" 12 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 13 | "github.com/xiaoenai/tp-micro/v6/model/redis" 14 | 15 | "github.com/xiaoenai/tp-micro/examples/project/logic/model" 16 | ) 17 | 18 | type config struct { 19 | Srv micro.SrvConfig `yaml:"srv"` 20 | Cli micro.CliConfig `yaml:"cli"` 21 | Etcd etcd.EasyConfig `yaml:"etcd"` 22 | Mysql mysql.Config `yaml:"mysql"` 23 | Mongo mongo.Config `yaml:"mongo"` 24 | Redis redis.Config `yaml:"redis"` 25 | CacheExpire time.Duration `yaml:"cache_expire"` 26 | LogLevel string `yaml:"log_level"` 27 | } 28 | 29 | func (c *config) Reload(bind cfgo.BindFunc) error { 30 | err := bind() 31 | if err != nil { 32 | return err 33 | } 34 | if c.CacheExpire == 0 { 35 | c.CacheExpire = time.Hour * 24 36 | } 37 | if len(c.LogLevel) == 0 { 38 | c.LogLevel = "TRACE" 39 | } 40 | erpc.SetLoggerLevel(c.LogLevel) 41 | var ( 42 | mysqlConfig *mysql.Config 43 | mongoConfig *mongo.Config 44 | redisConfig = &c.Redis 45 | ) 46 | if len(c.Mysql.Host) > 0 { 47 | mysqlConfig = &c.Mysql 48 | } 49 | if len(c.Mongo.Addrs) > 0 { 50 | mongoConfig = &c.Mongo 51 | } 52 | err = model.Init(mysqlConfig, mongoConfig, redisConfig, c.CacheExpire) 53 | if err != nil { 54 | erpc.Errorf("%v", err) 55 | } 56 | return nil 57 | } 58 | 59 | var cfg = &config{ 60 | Srv: micro.SrvConfig{ 61 | ListenAddress: ":0", 62 | EnableHeartbeat: true, 63 | PrintDetail: true, 64 | CountTime: true, 65 | SlowCometDuration: time.Millisecond * 500, 66 | }, 67 | Etcd: etcd.EasyConfig{ 68 | Endpoints: []string{"http://127.0.0.1:2379"}, 69 | }, 70 | Redis: *redis.NewConfig(), 71 | CacheExpire: time.Hour * 24, 72 | LogLevel: "TRACE", 73 | } 74 | 75 | func init() { 76 | goutil.WritePidFile() 77 | cfgo.MustReg("project", cfg) 78 | } 79 | -------------------------------------------------------------------------------- /gateway/helper/gray/sdk/rpc.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | micro "github.com/xiaoenai/tp-micro/v6" 6 | "github.com/xiaoenai/tp-micro/v6/discovery" 7 | types "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/types" 8 | gwLogic "github.com/xiaoenai/tp-micro/v6/gateway/logic" 9 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 10 | ) 11 | 12 | var _formalClient *micro.Client 13 | 14 | // Init initializes formal client with configs. 15 | func Init(formalClientConfig micro.CliConfig, formalEtcdConfing etcd.EasyConfig) { 16 | _formalClient = micro.NewClient( 17 | formalClientConfig, 18 | discovery.NewLinker(formalEtcdConfing), 19 | ) 20 | } 21 | 22 | // InitWithClient initializes formal client with specified object. 23 | func InitWithClient(formalClient *micro.Client) { 24 | _formalClient = formalClient 25 | } 26 | 27 | // IsGray check whether the service should use grayscale based on the uid. 28 | func IsGray(args *types.IsGrayArgs, setting ...erpc.MessageSetting) (*types.IsGrayResult, *erpc.Status) { 29 | reply := new(types.IsGrayResult) 30 | stat := _formalClient.Call("/gw/"+gwLogic.ApiVersion()+"/gray/is_gray", args, reply, setting...).Rerror() 31 | return reply, stat 32 | } 33 | 34 | // Get get the rule of gray. 35 | func Get(args *types.GetArgs, setting ...erpc.MessageSetting) (*types.GrayMatch, *erpc.Status) { 36 | reply := new(types.GrayMatch) 37 | stat := _formalClient.Call("/gw/"+gwLogic.ApiVersion()+"/gray/get", args, reply, setting...).Rerror() 38 | return reply, stat 39 | } 40 | 41 | // Delete delete the rule of gray. 42 | func Delete(args *types.DeleteArgs, setting ...erpc.MessageSetting) (*struct{}, *erpc.Status) { 43 | reply := new(struct{}) 44 | stat := _formalClient.Call("/gw/"+gwLogic.ApiVersion()+"/gray/delete", args, reply, setting...).Rerror() 45 | return reply, stat 46 | } 47 | 48 | // Set insert or update the regular expression for matching the URI. 49 | func Set(args *types.SetArgs, setting ...erpc.MessageSetting) (*struct{}, *erpc.Status) { 50 | reply := new(struct{}) 51 | stat := _formalClient.Call("/gw/"+gwLogic.ApiVersion()+"/gray/set", args, reply, setting...).Rerror() 52 | return reply, stat 53 | } 54 | -------------------------------------------------------------------------------- /examples/project/args/type.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'micro gen' command. 2 | // DO NOT EDIT! 3 | 4 | package args 5 | 6 | import ( 7 | "github.com/henrylee2cn/erpc/v6/codec" 8 | "github.com/xiaoenai/tp-micro/v6/model/mongo" 9 | ) 10 | 11 | // EmptyStruct alias of type struct {} 12 | type EmptyStruct = codec.PbEmpty 13 | 14 | // HomeResult home result 15 | type HomeResult struct { 16 | Content string `json:"content"` // text 17 | } 18 | 19 | // DivideArg divide api arg 20 | type DivideArg struct { 21 | // dividend 22 | A float64 `json:"a"` 23 | // divisor 24 | B float64 `param:"" json:"b"` 25 | } 26 | 27 | // DivideResult divide api result 28 | type DivideResult struct { 29 | // quotient 30 | C float64 `json:"c"` 31 | } 32 | 33 | // StatArg stat handler arg 34 | type StatArg struct { 35 | Ts int64 `param:"" json:"ts"` // timestamps 36 | } 37 | 38 | // User user info 39 | type User struct { 40 | Id int64 `key:"pri" json:"id"` 41 | Name string `key:"uni" json:"name"` 42 | Age int32 `json:"age"` 43 | UpdatedAt int64 `json:"updated_at"` 44 | CreatedAt int64 `json:"created_at"` 45 | DeletedTs int64 `json:"deleted_ts"` 46 | } 47 | 48 | // Log comment... 49 | type Log struct { 50 | Id int64 `json:"id" key:"pri"` 51 | Text string `json:"text"` 52 | UpdatedAt int64 `json:"updated_at"` 53 | CreatedAt int64 `json:"created_at"` 54 | DeletedTs int64 `json:"deleted_ts"` 55 | } 56 | 57 | // Device comment... 58 | type Device struct { 59 | UUID string `key:"pri" json:"uuid"` 60 | UpdatedAt int64 `json:"updated_at"` 61 | CreatedAt int64 `json:"created_at"` 62 | DeletedTs int64 `json:"deleted_ts"` 63 | } 64 | 65 | // Meta comment... 66 | type Meta struct { 67 | Id mongo.ObjectId `key:"pri" json:"_id" bson:"_id"` 68 | Name string `key:"uni" json:"name" bson:"name"` 69 | Hobby []string `json:"hobby" bson:"hobby"` 70 | Tags []string `json:"tags" bson:"tags"` 71 | UpdatedAt int64 `json:"updated_at" bson:"updated_at"` 72 | CreatedAt int64 `json:"created_at" bson:"created_at"` 73 | DeletedTs int64 `json:"deleted_ts" bson:"deleted_ts"` 74 | } 75 | -------------------------------------------------------------------------------- /cmd/gateway/gateway.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/henrylee2cn/cfgo" 7 | "github.com/henrylee2cn/erpc/v6" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | "github.com/xiaoenai/tp-micro/v6/gateway" 10 | agent "github.com/xiaoenai/tp-micro/v6/gateway/helper/agent/socket" 11 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray" 12 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 13 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 14 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 15 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 16 | "github.com/xiaoenai/tp-micro/v6/model/redis" 17 | ) 18 | 19 | type config struct { 20 | Gw gateway.Config `yaml:"gw"` 21 | GraySocketClient micro.CliConfig `yaml:"gray_socket_client"` 22 | GrayEtcd etcd.EasyConfig `yaml:"gray_etcd"` 23 | Redis redis.Config `yaml:"redis"` 24 | Mysql mysql.Config `yaml:"mysql"` 25 | } 26 | 27 | func (c *config) Reload(bind cfgo.BindFunc) error { 28 | c.Gw.OuterHttpServer.AllowCross = true 29 | err := bind() 30 | return err 31 | } 32 | 33 | func main() { 34 | logic.SetApiVersion("v1") 35 | cfg := config{ 36 | Gw: *gateway.NewConfig(), 37 | GraySocketClient: micro.CliConfig{ 38 | Failover: 3, 39 | HeartbeatSecond: 60, 40 | }, 41 | GrayEtcd: etcd.EasyConfig{ 42 | Endpoints: []string{"http://127.0.0.1:2379"}, 43 | }, 44 | Redis: *redis.NewConfig(), 45 | Mysql: *mysql.NewConfig(), 46 | } 47 | cfgo.AllowAppsShare(true) 48 | cfgo.MustReg("gateway", &cfg) 49 | 50 | biz := types.DefaultBusiness() 51 | redisClient, err := redis.NewClient(&cfg.Redis) 52 | if err != nil { 53 | erpc.Fatalf("%v", err) 54 | } 55 | agent.Init(redisClient, redisClient) 56 | biz.SocketHooks = agent.GetSocketHooks() 57 | _, err = gray.SetGray(biz, cfg.GraySocketClient, cfg.GrayEtcd, cfg.Mysql, cfg.Redis, nil) 58 | if err != nil { 59 | erpc.Fatalf("%v", err) 60 | } 61 | 62 | go func() { 63 | t := time.NewTicker(time.Second) 64 | for { 65 | <-t.C 66 | erpc.Infof("total conn: %d", gateway.TotalConn()) 67 | } 68 | }() 69 | 70 | err = gateway.Run(cfg.Gw, biz, nil) 71 | if err != nil { 72 | erpc.Fatalf("%v", err) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/custom/gateway.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/henrylee2cn/cfgo" 7 | "github.com/henrylee2cn/erpc/v6" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | "github.com/xiaoenai/tp-micro/v6/gateway" 10 | sagent "github.com/xiaoenai/tp-micro/v6/gateway/helper/agent/socket" 11 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray" 12 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 13 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 14 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 15 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 16 | "github.com/xiaoenai/tp-micro/v6/model/redis" 17 | ) 18 | 19 | type config struct { 20 | Gw gateway.Config `yaml:"gw"` 21 | GraySocketClient micro.CliConfig `yaml:"gray_socket_client"` 22 | GrayEtcd etcd.EasyConfig `yaml:"gray_etcd"` 23 | Redis redis.Config `yaml:"redis"` 24 | Mysql mysql.Config `yaml:"mysql"` 25 | } 26 | 27 | func (c *config) Reload(bind cfgo.BindFunc) error { 28 | c.Gw.OuterHttpServer.AllowCross = true 29 | err := bind() 30 | return err 31 | } 32 | 33 | func main() { 34 | logic.SetApiVersion("v1") 35 | cfg := config{ 36 | Gw: *gateway.NewConfig(), 37 | GraySocketClient: micro.CliConfig{ 38 | Failover: 3, 39 | HeartbeatSecond: 60, 40 | }, 41 | GrayEtcd: etcd.EasyConfig{ 42 | Endpoints: []string{"http://127.0.0.1:2379"}, 43 | }, 44 | Redis: *redis.NewConfig(), 45 | Mysql: *mysql.NewConfig(), 46 | } 47 | cfgo.AllowAppsShare(true) 48 | cfgo.MustReg("gateway", &cfg) 49 | 50 | biz := types.DefaultBusiness() 51 | redisClient, err := redis.NewClient(&cfg.Redis) 52 | if err != nil { 53 | erpc.Fatalf("%v", err) 54 | } 55 | sagent.Init(redisClient, redisClient) 56 | biz.SocketHooks = sagent.GetSocketHooks() 57 | _, err = gray.SetGray(biz, cfg.GraySocketClient, cfg.GrayEtcd, cfg.Mysql, cfg.Redis, nil) 58 | if err != nil { 59 | erpc.Fatalf("%v", err) 60 | } 61 | 62 | go func() { 63 | t := time.NewTicker(time.Second * 10) 64 | for { 65 | <-t.C 66 | erpc.Infof("total conn: %d", gateway.TotalConn()) 67 | } 68 | }() 69 | 70 | err = gateway.Run(cfg.Gw, biz, nil) 71 | if err != nil { 72 | erpc.Fatalf("%v", err) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gateway/logic/http/setting.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package http 16 | 17 | import ( 18 | "strings" 19 | 20 | "github.com/henrylee2cn/erpc/v6/codec" 21 | ) 22 | 23 | var ( 24 | bodyCodecMapping = map[string]byte{ 25 | "application/x-protobuf": codec.ID_PROTOBUF, 26 | "application/json": codec.ID_JSON, 27 | "application/x-www-form-urlencoded": codec.ID_FORM, 28 | "text/plain": codec.ID_PLAIN, 29 | } 30 | contentTypeMapping = map[byte]string{ 31 | codec.ID_PROTOBUF: "application/x-protobuf", 32 | codec.ID_JSON: "application/json", 33 | codec.ID_FORM: "application/x-www-form-urlencoded", 34 | codec.ID_PLAIN: "text/plain", 35 | } 36 | ) 37 | 38 | // RegBodyCodec registers a mapping of content type to body coder. 39 | func RegBodyCodec(contentType string, codecId byte) { 40 | bodyCodecMapping[contentType] = codecId 41 | contentTypeMapping[codecId] = contentType 42 | } 43 | 44 | // GetBodyCodec returns the codec id from content type. 45 | func GetBodyCodec(contentType string, defCodecId byte) byte { 46 | idx := strings.Index(contentType, ";") 47 | if idx != -1 { 48 | contentType = contentType[:idx] 49 | } 50 | codecId, ok := bodyCodecMapping[contentType] 51 | if !ok { 52 | return defCodecId 53 | } 54 | return codecId 55 | } 56 | 57 | // GetContentType returns the content type from codec id. 58 | func GetContentType(codecId byte, defContentType string) string { 59 | contentType, ok := contentTypeMapping[codecId] 60 | if !ok { 61 | return defContentType 62 | } 63 | return contentType 64 | } 65 | -------------------------------------------------------------------------------- /gateway/logic/websocket/server.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "github.com/henrylee2cn/erpc/v6" 5 | ws "github.com/henrylee2cn/erpc/v6/mixer/websocket" 6 | "github.com/henrylee2cn/erpc/v6/plugin/auth" 7 | "github.com/henrylee2cn/erpc/v6/plugin/binder" 8 | "github.com/henrylee2cn/erpc/v6/plugin/heartbeat" 9 | "github.com/henrylee2cn/erpc/v6/plugin/proxy" 10 | micro "github.com/xiaoenai/tp-micro/v6" 11 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 12 | ) 13 | 14 | var ( 15 | outerPeer erpc.Peer 16 | clientAuthInfo string 17 | ) 18 | 19 | // Serve starts websocket gateway service. 20 | func Serve(outerSrvCfg micro.SrvConfig, protoFunc erpc.ProtoFunc) { 21 | // plugins 22 | globalLeftPlugin := []erpc.Plugin{ 23 | binder.NewStructArgsBinder(nil), 24 | authChecker, 25 | webSocketConnTabPlugin, 26 | proxy.NewPlugin(logic.ProxySelector), 27 | preWritePushPlugin(), 28 | } 29 | if outerSrvCfg.EnableHeartbeat{ 30 | globalLeftPlugin = append(globalLeftPlugin,heartbeat.NewPong()) 31 | } 32 | // new ws server 33 | srv := ws.NewServer( 34 | "/", 35 | outerSrvCfg.PeerConfig(), 36 | globalLeftPlugin..., 37 | ) 38 | // ws outer peer 39 | outerPeer = srv.Peer 40 | go srv.ListenAndServe(protoFunc) 41 | 42 | select {} 43 | } 44 | 45 | // auth plugin 46 | var authChecker = auth.NewCheckerPlugin( 47 | func(sess auth.Session, fn auth.RecvOnce) (ret interface{}, stat *erpc.Status) { 48 | var authInfo string 49 | stat = fn(&authInfo) 50 | if !stat.OK() { 51 | return 52 | } 53 | erpc.Tracef("auth info: %v", authInfo) 54 | stat = webSocketConnTabPlugin.authAndLogon(authInfo, sess) 55 | if !stat.OK() { 56 | return 57 | } 58 | return "", nil 59 | }, 60 | erpc.WithBodyCodec('s'), 61 | ) 62 | 63 | // preWritePushPlugin returns PreWritePushPlugin. 64 | func preWritePushPlugin() erpc.Plugin { 65 | return &perPusher{fn: logic.WebSocketHooks().PreWritePush} 66 | } 67 | 68 | type perPusher struct { 69 | fn func(erpc.WriteCtx) *erpc.Status 70 | } 71 | 72 | func (p *perPusher) Name() string { 73 | return "PUSH-LOGIC" 74 | } 75 | 76 | var ( 77 | _ erpc.PreWritePushPlugin = (*perPusher)(nil) 78 | ) 79 | 80 | func (p *perPusher) PreWritePush(ctx erpc.WriteCtx) *erpc.Status { 81 | return p.fn(ctx) 82 | } 83 | -------------------------------------------------------------------------------- /model/mongo/pre_db.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/xiaoenai/tp-micro/v6/model/redis" 8 | "gopkg.in/mgo.v2" 9 | ) 10 | 11 | // PreDB preset *DB 12 | type PreDB struct { 13 | *DB 14 | preFuncs map[string]func() error 15 | inited bool 16 | } 17 | 18 | // NewPreDB creates a unconnected *DB 19 | func NewPreDB() *PreDB { 20 | return &PreDB{ 21 | DB: &DB{ 22 | cacheableDBs: make(map[string]*CacheableDB), 23 | }, 24 | preFuncs: make(map[string]func() error), 25 | } 26 | } 27 | 28 | func (p *PreDB) Init(dbConfig *Config, redisConfig *redis.Config) (err error) { 29 | var cache *redis.Client 30 | if !dbConfig.NoCache && redisConfig != nil { 31 | cache, err = redis.NewClient(redisConfig) 32 | if err != nil { 33 | return err 34 | } 35 | } 36 | return p.Init2(dbConfig, cache) 37 | } 38 | 39 | // Init initialize *DB. 40 | func (p *PreDB) Init2(dbConfig *Config, redisClient *redis.Client) (err error) { 41 | // connect to mongodb 42 | db, err := mgo.DialWithInfo(dbConfig.Source()) 43 | if err != nil { 44 | return err 45 | } 46 | p.DB.Session = db 47 | p.DB.dbConfig = dbConfig 48 | if !dbConfig.NoCache && redisClient != nil { 49 | p.DB.Cache = redisClient 50 | p.DB.redisConfig = redisClient.Config() 51 | } 52 | 53 | for _, preFunc := range p.preFuncs { 54 | if err = preFunc(); err != nil { 55 | return err 56 | } 57 | } 58 | 59 | p.inited = true 60 | return nil 61 | } 62 | 63 | // RegCacheableDB registers a cacheable table. 64 | func (p *PreDB) RegCacheableDB(ormStructPtr Cacheable, cacheExpiration time.Duration) (*CacheableDB, error) { 65 | if p.inited { 66 | return p.DB.RegCacheableDB(ormStructPtr, cacheExpiration) 67 | } 68 | 69 | tableName := ormStructPtr.TableName() 70 | if _, ok := p.preFuncs[ormStructPtr.TableName()]; ok { 71 | return nil, fmt.Errorf("re-register cacheable table: %s", tableName) 72 | } 73 | var cacheableDB = new(CacheableDB) 74 | 75 | var preFunc = func() error { 76 | _cacheableDB, err := p.DB.RegCacheableDB(ormStructPtr, cacheExpiration) 77 | if err == nil { 78 | *cacheableDB = *_cacheableDB 79 | p.DB.cacheableDBs[tableName] = cacheableDB 80 | } 81 | return err 82 | } 83 | p.preFuncs[tableName] = preFunc 84 | return cacheableDB, nil 85 | } 86 | -------------------------------------------------------------------------------- /examples/simple/config/config.yaml: -------------------------------------------------------------------------------- 1 | cluster_client: 2 | network: tcp 3 | local_ip: 0.0.0.0 4 | tls_cert_file: "" 5 | tls_key_file: "" 6 | default_session_age: 0s 7 | default_context_age: 0s 8 | default_dial_timeout: 0s 9 | failover: 0 10 | slow_comet_duration: 0s 11 | default_body_codec: "" 12 | print_detail: false 13 | count_time: false 14 | heartbeat_second: 0 15 | circuit_breaker: 16 | enable: false 17 | error_percentage: 50 18 | break_duration: 5s 19 | 20 | etcd: 21 | endpoints: 22 | - http://127.0.0.1:2379 23 | dial_timeout: 15s 24 | username: "" 25 | password: "" 26 | 27 | gateway: 28 | enable_http: true 29 | enable_socket: true 30 | etcd: 31 | dial_timeout: 0s 32 | endpoints: 33 | - http://127.0.0.1:2379 34 | password: "" 35 | username: "" 36 | inner_socket_client: 37 | circuit_breaker: 38 | break_duration: 0s 39 | enable: false 40 | error_percentage: 0 41 | count_time: false 42 | default_body_codec: "" 43 | default_context_age: 0s 44 | default_dial_timeout: 0s 45 | default_session_age: 0s 46 | failover: 3 47 | heartbeat_second: 60 48 | local_ip: "" 49 | network: "" 50 | print_detail: false 51 | redial_times: 0 52 | sess_max_idle_duration: 0s 53 | sess_max_quota: 0 54 | slow_comet_duration: 0s 55 | tls_cert_file: "" 56 | tls_key_file: "" 57 | inner_socket_server: 58 | count_time: true 59 | default_body_codec: "" 60 | default_context_age: 0s 61 | default_session_age: 0s 62 | enable_heartbeat: true 63 | listen_address: 0.0.0.0:5030 64 | network: "" 65 | print_detail: true 66 | slow_comet_duration: 0s 67 | tls_cert_file: "" 68 | tls_key_file: "" 69 | outer_http_server: 70 | allow_cross: true 71 | listen_address: 0.0.0.0:5000 72 | outer_host: 113.116.142.218:5000 73 | tls_cert_file: "" 74 | tls_key_file: "" 75 | outer_socket_server: 76 | count_time: true 77 | default_body_codec: "" 78 | default_context_age: 0s 79 | default_session_age: 0s 80 | enable_heartbeat: true 81 | listen_address: 0.0.0.0:5020 82 | network: "" 83 | print_detail: true 84 | slow_comet_duration: 0s 85 | tls_cert_file: "" 86 | tls_key_file: "" 87 | -------------------------------------------------------------------------------- /micro/run/fsnotify/fsnotify_symlink_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build freebsd openbsd netbsd darwin linux 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | func TestFsnotifyFakeSymlink(t *testing.T) { 17 | watcher := newWatcher(t) 18 | 19 | // Create directory to watch 20 | testDir := tempMkdir(t) 21 | defer os.RemoveAll(testDir) 22 | 23 | var errorsReceived counter 24 | // Receive errors on the error channel on a separate goroutine 25 | go func() { 26 | for errors := range watcher.Error { 27 | t.Logf("Received error: %s", errors) 28 | errorsReceived.increment() 29 | } 30 | }() 31 | 32 | // Count the CREATE events received 33 | var createEventsReceived, otherEventsReceived counter 34 | go func() { 35 | for ev := range watcher.Event { 36 | t.Logf("event received: %s", ev) 37 | if ev.IsCreate() { 38 | createEventsReceived.increment() 39 | } else { 40 | otherEventsReceived.increment() 41 | } 42 | } 43 | }() 44 | 45 | addWatch(t, watcher, testDir) 46 | 47 | if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil { 48 | t.Fatalf("Failed to create bogus symlink: %s", err) 49 | } 50 | t.Logf("Created bogus symlink") 51 | 52 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 53 | time.Sleep(500 * time.Millisecond) 54 | 55 | // Should not be error, just no events for broken links (watching nothing) 56 | if errorsReceived.value() > 0 { 57 | t.Fatal("fsnotify errors have been received.") 58 | } 59 | if otherEventsReceived.value() > 0 { 60 | t.Fatal("fsnotify other events received on the broken link") 61 | } 62 | 63 | // Except for 1 create event (for the link itself) 64 | if createEventsReceived.value() == 0 { 65 | t.Fatal("fsnotify create events were not received after 500 ms") 66 | } 67 | if createEventsReceived.value() > 1 { 68 | t.Fatal("fsnotify more create events received than expected") 69 | } 70 | 71 | // Try closing the fsnotify instance 72 | t.Log("calling Close()") 73 | watcher.Close() 74 | } 75 | -------------------------------------------------------------------------------- /gateway/logic/http/log.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "math" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/henrylee2cn/erpc/v6" 11 | "github.com/henrylee2cn/erpc/v6/plugin/proxy" 12 | "github.com/henrylee2cn/erpc/v6/utils" 13 | ) 14 | 15 | var ( 16 | printDetail bool 17 | countTime bool 18 | slowCometDuration time.Duration = math.MaxInt64 19 | ) 20 | 21 | func (r *requestHandler) runlog(startTime time.Time, label *proxy.Label, seq string, inputBody []byte, outputBody *[]byte) { 22 | var addr = r.ctx.RemoteAddr().String() 23 | if label.RealIP != "" && label.RealIP != addr { 24 | addr += "(real: " + label.RealIP + ")" 25 | } 26 | var ( 27 | costTimeStr string 28 | printFunc = erpc.Infof 29 | ) 30 | if countTime { 31 | costTime := time.Since(startTime) 32 | costTimeStr = costTime.String() 33 | if costTime >= slowCometDuration { 34 | costTimeStr += "(slow)" 35 | printFunc = erpc.Warnf 36 | } 37 | } else { 38 | costTimeStr = "-" 39 | } 40 | 41 | printFunc("CALL<- %s %s %s %q\nRECV(%s)\nSEND(%s)", addr, costTimeStr, label.ServiceMethod, seq, r.packetLogBytes(inputBody, r.ctx.Request.Header.Header(), false), r.packetLogBytes(*outputBody, r.ctx.Response.Header.Header(), r.errMsg != nil)) 42 | } 43 | 44 | func (r *requestHandler) packetLogBytes(bodyBytes, headerBytes []byte, hasErr bool) []byte { 45 | var size = len(bodyBytes) + len(headerBytes) 46 | if hasErr { 47 | size += len(r.errMsg) 48 | } 49 | var b = make([]byte, 0, 128) 50 | b = append(b, '{') 51 | b = append(b, '"', 's', 'i', 'z', 'e', '"', ':') 52 | b = append(b, strconv.FormatUint(uint64(size), 10)...) 53 | if hasErr { 54 | b = append(b, ',', '"', 'e', 'r', 'r', 'o', 'r', '"', ':') 55 | b = append(b, utils.ToJSONStr(r.errMsg, false)...) 56 | } 57 | if printDetail { 58 | b = append(b, ',', '"', 'm', 'e', 't', 'a', '"', ':') 59 | b = append(b, utils.ToJSONStr(headerBytes, false)...) 60 | if !hasErr && len(bodyBytes) > 0 { 61 | b = append(b, ',', '"', 'b', 'o', 'd', 'y', '"', ':') 62 | b = append(b, utils.ToJSONStr(bodyBytes, false)...) 63 | } 64 | } 65 | b = append(b, '}') 66 | buf := bytes.NewBuffer(nil) 67 | err := json.Indent(buf, b, "", " ") 68 | if err != nil { 69 | return b 70 | } 71 | return buf.Bytes() 72 | } 73 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xiaoenai/tp-micro/v6 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/coreos/bbolt v1.3.3 // indirect 7 | github.com/coreos/etcd v3.3.17+incompatible 8 | github.com/coreos/go-semver v0.3.0 // indirect 9 | github.com/coreos/go-systemd v0.0.0-00010101000000-000000000000 // indirect 10 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect 11 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 12 | github.com/go-redis/redis/v7 v7.0.0-beta.4 13 | github.com/go-sql-driver/mysql v1.4.1 14 | github.com/gogo/protobuf v1.3.1 15 | github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect 16 | github.com/golang/protobuf v1.3.2 17 | github.com/google/btree v1.0.0 // indirect 18 | github.com/google/uuid v1.1.1 // indirect 19 | github.com/gorilla/websocket v1.4.1 // indirect 20 | github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect 21 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 22 | github.com/grpc-ecosystem/grpc-gateway v1.12.1 // indirect 23 | github.com/henrylee2cn/cfgo v0.0.0-20180417024816-e6c3cc325b21 24 | github.com/henrylee2cn/erpc/v6 v6.3.1 25 | github.com/henrylee2cn/goutil v0.0.0-20191029125303-21920e347847 26 | github.com/howeyc/fsnotify v0.9.0 27 | github.com/jonboulle/clockwork v0.1.0 // indirect 28 | github.com/json-iterator/go v1.1.8 // indirect 29 | github.com/prometheus/client_golang v1.2.1 // indirect 30 | github.com/soheilhy/cmux v0.1.4 // indirect 31 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect 32 | github.com/urfave/cli v1.22.1 33 | github.com/valyala/fasthttp v1.6.0 34 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect 35 | go.etcd.io/bbolt v1.3.3 // indirect 36 | go.uber.org/zap v1.12.0 // indirect 37 | golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 38 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect 39 | google.golang.org/appengine v1.6.5 // indirect 40 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect 41 | google.golang.org/grpc v1.25.1 // indirect 42 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 43 | sigs.k8s.io/yaml v1.1.0 // indirect 44 | ) 45 | 46 | replace github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0 47 | -------------------------------------------------------------------------------- /micro/create/tpl/base_tpl.go: -------------------------------------------------------------------------------- 1 | package tpl 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/xiaoenai/tp-micro/v6/micro/info" 13 | ) 14 | 15 | // RestoreAsset restores an asset under the given directory 16 | func RestoreAsset(dir, name string) error { 17 | data, err := Asset(name) 18 | if err != nil { 19 | return err 20 | } 21 | info, err := AssetInfo(name) 22 | if err != nil { 23 | return err 24 | } 25 | data = bytes.Replace(data, projNameTpl, projNameBytes, -1) 26 | data = bytes.Replace(data, projPathTpl, projPathBytes, -1) 27 | if strings.HasSuffix(name, ".go") { 28 | data, _ = format.Source(data) 29 | } 30 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 31 | if err != nil { 32 | return err 33 | } 34 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 35 | if err != nil { 36 | return err 37 | } 38 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 39 | if err != nil { 40 | return err 41 | } 42 | fmt.Printf("generate %s\n", string(projPathBytes)+"/"+_filePath(dir, name)) 43 | return nil 44 | } 45 | 46 | // RestoreAssets restores an asset under the given directory recursively 47 | func RestoreAssets(dir, name string) error { 48 | children, err := AssetDir(name) 49 | // File 50 | if err != nil { 51 | if name == "__tp-micro__gen__.lock" { 52 | return nil 53 | } 54 | return RestoreAsset(dir, name) 55 | } 56 | // Dir 57 | for _, child := range children { 58 | if child == "logic" { 59 | continue 60 | } 61 | err = RestoreAssets(dir, filepath.Join(name, child)) 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func _filePath(dir, name string) string { 70 | cannonicalName := strings.Replace(name, "\\", "/", -1) 71 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 72 | } 73 | 74 | var ( 75 | projNameBytes []byte 76 | projPathBytes []byte 77 | projNameTpl = []byte("{{PROJ_NAME}}") 78 | projPathTpl = []byte("{{PROJ_PATH}}") 79 | ) 80 | 81 | // Create creates base files. 82 | func Create() { 83 | projNameBytes = []byte(info.ProjName()) 84 | projPathBytes = []byte(info.ProjPath()) 85 | RestoreAssets("./", "") 86 | } 87 | -------------------------------------------------------------------------------- /micro/info/info.go: -------------------------------------------------------------------------------- 1 | package info 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | // projInfo current project information 12 | var projInfo = struct { 13 | projName, fileName, absPath, projPath, gopath, rawCmdPath string 14 | }{} 15 | 16 | // Init initializes project information. 17 | func Init(appPath string) (err error) { 18 | if len(appPath) == 0 { 19 | return errors.New("project() path can not be empty!") 20 | } 21 | projInfo.absPath = strings.TrimSpace(appPath) 22 | projInfo.absPath, err = filepath.Abs(projInfo.absPath) 23 | if err != nil { 24 | return errors.New("get absolute project path failed: %s!" + err.Error()) 25 | } 26 | const SRC = "/src/" 27 | projInfo.absPath = strings.Replace(projInfo.absPath, `\`, `/`, -1) 28 | projInfo.absPath = strings.TrimRight(projInfo.absPath, "/") 29 | srcIdx := strings.Index(projInfo.absPath, SRC) 30 | if srcIdx == -1 { 31 | return errors.New("the project directory must be in the $GOPATH/src!") 32 | } 33 | projInfo.gopath = projInfo.absPath[:srcIdx] 34 | projInfo.projPath = projInfo.absPath[srcIdx+len(SRC):] 35 | projInfo.projName = projInfo.absPath[strings.LastIndex(projInfo.absPath, "/")+1:] 36 | projInfo.fileName = projInfo.projName 37 | if runtime.GOOS == "windows" { 38 | projInfo.fileName += ".exe" 39 | if old, ok := os.LookupEnv("GOPATH"); ok { 40 | projInfo.gopath += ";" + old 41 | } 42 | } else { 43 | if old, ok := os.LookupEnv("GOPATH"); ok { 44 | projInfo.gopath += ":" + old 45 | } 46 | } 47 | projInfo.rawCmdPath, _ = os.Getwd() 48 | return nil 49 | } 50 | 51 | // ProjName returns project name. 52 | func ProjName() string { 53 | return projInfo.projName 54 | } 55 | 56 | // FileName returns the binary execution file name. 57 | func FileName() string { 58 | return projInfo.fileName 59 | } 60 | 61 | // AbsPath returns the absolute project path. 62 | func AbsPath() string { 63 | return projInfo.absPath 64 | } 65 | 66 | // ProjPath returns the relative src project path. 67 | func ProjPath() string { 68 | return projInfo.projPath 69 | } 70 | 71 | // Gopath returns the current $GOPATH. 72 | func Gopath() string { 73 | return projInfo.gopath 74 | } 75 | 76 | // RawCmdPath returns the initial path to start. 77 | func RawCmdPath() string { 78 | return projInfo.rawCmdPath 79 | } 80 | -------------------------------------------------------------------------------- /gateway/logic/business.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package logic 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6" 19 | "github.com/henrylee2cn/erpc/v6/plugin/proxy" 20 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 21 | ) 22 | 23 | var ( 24 | globalBusiness *types.Business 25 | apiVersion = "v6" 26 | ) 27 | 28 | // SetBusiness sets business object. 29 | func SetBusiness(biz *types.Business) { 30 | if biz == nil { 31 | biz = types.DefaultBusiness() 32 | } else { 33 | biz.Init() 34 | } 35 | globalBusiness = biz 36 | } 37 | 38 | // SetApiVersion sets gateway API version. 39 | func SetApiVersion(ver string) { 40 | apiVersion = ver 41 | } 42 | 43 | // ApiVersion returns gateway API version. 44 | func ApiVersion() string { 45 | return apiVersion 46 | } 47 | 48 | // AuthFunc returns the authorization function for access behavior. 49 | func AuthFunc() types.AuthFunc { 50 | return globalBusiness.AuthFunc 51 | } 52 | 53 | // SocketHooks returns TCP socket connecting event hooks. 54 | func SocketHooks() types.SocketHooks { 55 | return globalBusiness.SocketHooks 56 | } 57 | 58 | // HttpHooks returns HTTP connecting event hooks. 59 | func HttpHooks() types.HttpHooks { 60 | return globalBusiness.HttpHooks 61 | } 62 | 63 | // WebSocketHooks returns WEB socket connecting event hooks. 64 | func WebSocketHooks() types.WebSocketHooks { 65 | return globalBusiness.WebSocketHooks 66 | } 67 | 68 | // ProxySelector returns proxy caller by label. 69 | func ProxySelector(label *proxy.Label) proxy.Forwarder { 70 | return globalBusiness.ProxySelector(label) 71 | } 72 | 73 | // InnerServerPlugins returns inner server plugins. 74 | func InnerServerPlugins() []erpc.Plugin { 75 | return globalBusiness.InnerServerPlugins 76 | } 77 | -------------------------------------------------------------------------------- /gateway/types/business.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package types 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6" 19 | "github.com/henrylee2cn/erpc/v6/plugin/proxy" 20 | "github.com/xiaoenai/tp-micro/v6/clientele" 21 | ) 22 | 23 | // Business implement your real business logic 24 | type Business struct { 25 | // AuthFunc Verifies access token 26 | AuthFunc func(accessToken string) (AccessToken, *erpc.Status) 27 | // SocketHooks TCP socket connecting event hooks 28 | SocketHooks 29 | // HttpHooks HTTP connecting event hooks 30 | HttpHooks 31 | // WebSocketHooks WEB socket connecting event hooks 32 | WebSocketHooks 33 | // ProxySelector returns proxy caller by label. 34 | ProxySelector func(*proxy.Label) proxy.Forwarder 35 | // InnerServerPlugins inner server plugins 36 | InnerServerPlugins []erpc.Plugin 37 | } 38 | 39 | // DefaultBusiness creates a new default Business object. 40 | func DefaultBusiness() *Business { 41 | biz := new(Business) 42 | biz.Init() 43 | return biz 44 | } 45 | 46 | func (biz *Business) Init() { 47 | if biz.AuthFunc == nil { 48 | biz.AuthFunc = DefaultAuthFunc() 49 | } 50 | if biz.SocketHooks == nil { 51 | biz.SocketHooks = DefaultSocketHooks() 52 | } 53 | if biz.HttpHooks == nil { 54 | biz.HttpHooks = DefaultHttpHooks() 55 | } 56 | if biz.WebSocketHooks == nil { 57 | biz.WebSocketHooks = DefaultWebSocketHooks() 58 | } 59 | if biz.ProxySelector == nil { 60 | biz.ProxySelector = DefaultProxySelector() 61 | } 62 | } 63 | 64 | // DefaultProxySelector creates a new default proxy caller selector. 65 | func DefaultProxySelector() func(*proxy.Label) proxy.Forwarder { 66 | return func(*proxy.Label) proxy.Forwarder { 67 | return clientele.GetDynamicClient() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/project/logic/model/init.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/xiaoenai/tp-micro/v6/model/mongo" 8 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 9 | "github.com/xiaoenai/tp-micro/v6/model/redis" 10 | ) 11 | 12 | // mysqlHandler preset mysql DB handler 13 | var mysqlHandler = mysql.NewPreDB() 14 | 15 | // mongoHandler preset mongo DB handler 16 | var mongoHandler = mongo.NewPreDB() 17 | 18 | var ( 19 | redisClient *redis.Client 20 | cacheExpire time.Duration 21 | ) 22 | 23 | // Init initializes the model packet. 24 | func Init(mysqlConfig *mysql.Config, mongoConfig *mongo.Config, redisConfig *redis.Config, _cacheExpire time.Duration) error { 25 | cacheExpire = _cacheExpire 26 | var err error 27 | if redisConfig != nil { 28 | redisClient, err = redis.NewClient(redisConfig) 29 | if err != nil { 30 | return err 31 | } 32 | } 33 | if mysqlConfig != nil { 34 | if err = mysqlHandler.Init2(mysqlConfig, redisClient); err != nil { 35 | return err 36 | } 37 | } 38 | if mongoConfig != nil { 39 | if err = mongoHandler.Init2(mongoConfig, redisClient); err != nil { 40 | return err 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | // GetMysqlDB returns the mysql DB handler. 47 | func GetMysqlDB() *mysql.DB { 48 | return mysqlHandler.DB 49 | } 50 | 51 | // GetMongoDB returns the mongo DB handler. 52 | func GetMongoDB() *mongo.DB { 53 | return mongoHandler.DB 54 | } 55 | 56 | // GetRedis returns the redis client. 57 | func GetRedis() *redis.Client { 58 | return redisClient 59 | } 60 | 61 | func index(s string, sub ...string) int { 62 | var i, ii = -1, -1 63 | for _, ss := range sub { 64 | ii = strings.Index(s, ss) 65 | if ii != -1 && (ii < i || i == -1) { 66 | i = ii 67 | } 68 | } 69 | return i 70 | } 71 | 72 | func insertZeroDeletedTsField(whereCond string) string { 73 | whereCond = strings.TrimSpace(whereCond) 74 | whereCond = strings.TrimRight(whereCond, ";") 75 | i := index( 76 | whereCond, 77 | "`deleted_ts`", 78 | " deleted_ts", 79 | ) 80 | if i != -1 { 81 | return whereCond 82 | } 83 | i = index( 84 | whereCond, 85 | "ORDER BY", "order by", 86 | "GROUP BY", "group by", 87 | "OFFSET", "offset", 88 | "LIMIT", "limit", 89 | ) 90 | if i == -1 { 91 | return whereCond + " AND `deleted_ts`=0" 92 | } 93 | return whereCond[:i] + " AND `deleted_ts`=0 " + whereCond[i:] 94 | } 95 | -------------------------------------------------------------------------------- /gateway/logic/socket/conn_tab.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package socket 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6" 19 | "github.com/henrylee2cn/erpc/v6/plugin/auth" 20 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 21 | ) 22 | 23 | type socketConnTab struct{} 24 | 25 | var ( 26 | socketConnTabPlugin = (*socketConnTab)(nil) 27 | ) 28 | 29 | var ( 30 | _ erpc.PostReadCallBodyPlugin = socketConnTabPlugin 31 | _ erpc.PostReadPushBodyPlugin = socketConnTabPlugin 32 | _ erpc.PostDisconnectPlugin = socketConnTabPlugin 33 | ) 34 | 35 | func (c *socketConnTab) Name() string { 36 | return "SocketConnTab" 37 | } 38 | 39 | func (c *socketConnTab) authAndLogon(authInfo string, sess auth.Session) *erpc.Status { 40 | token, stat := logic.AuthFunc()(authInfo) 41 | if stat != nil { 42 | return stat 43 | } 44 | info := token.AddedQuery() 45 | if info != nil && info.Len() > 0 { 46 | sess.Swap().Store(socketConnTabPlugin, info.String()) 47 | } 48 | stat = logic.SocketHooks().OnLogon(sess, token) 49 | if stat == nil { 50 | erpc.Tracef("[+SOCKET_CONN] addr: %s, id: %s", sess.RemoteAddr().String(), sess.(erpc.BaseSession).ID()) 51 | } 52 | return stat 53 | } 54 | 55 | func (c *socketConnTab) PostReadCallBody(ctx erpc.ReadCtx) *erpc.Status { 56 | _appendQuery, _ := ctx.Swap().Load(socketConnTabPlugin) 57 | appendQuery, _ := _appendQuery.(string) 58 | erpc.WithAddMeta("", appendQuery) 59 | return nil 60 | } 61 | 62 | func (c *socketConnTab) PostReadPushBody(ctx erpc.ReadCtx) *erpc.Status { 63 | return c.PostReadCallBody(ctx) 64 | } 65 | 66 | func (c *socketConnTab) PostDisconnect(sess erpc.BaseSession) *erpc.Status { 67 | erpc.Tracef("[-SOCKET_CONN] addr: %s, id: %s", sess.RemoteAddr().String(), sess.ID()) 68 | return logic.SocketHooks().OnLogoff(sess) 69 | } 70 | -------------------------------------------------------------------------------- /model/mongo/config.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/henrylee2cn/cfgo" 7 | "gopkg.in/mgo.v2" 8 | ) 9 | 10 | // Config mongodb setting 11 | type Config struct { 12 | Addrs []string `yaml:"addrs"` 13 | Timeout time.Duration `yaml:"timeout"` 14 | Database string `yaml:"database"` 15 | // Username and Password inform the credentials for the initial authentication 16 | // done on the database defined by the Source field. See Session.Login. 17 | Username string `yaml:"username"` 18 | Password string `yaml:"passward"` 19 | // PoolLimit defines the per-server socket pool limit. Defaults to 4096. 20 | // See Session.SetPoolLimit for details. 21 | PoolLimit int `yaml:"pool_limit"` 22 | // NoCache whether to disable cache 23 | NoCache bool `yaml:"no_cache"` 24 | 25 | init bool 26 | } 27 | 28 | var configs = make(map[string]*Config) 29 | 30 | // ReadConfig gets a mongodb db config form yaml. 31 | func ReadConfig(configSection string) (*Config, error) { 32 | conf, ok := configs[configSection] 33 | if ok { 34 | return conf, nil 35 | } 36 | conf = NewConfig() 37 | err := cfgo.Reg(configSection, conf) 38 | if err == nil { 39 | configs[configSection] = conf 40 | return conf, nil 41 | } else { 42 | return nil, err 43 | } 44 | } 45 | 46 | // NewConfig creates a default config. 47 | func NewConfig() *Config { 48 | return &Config{ 49 | Addrs: []string{"127.0.0.1:27017"}, 50 | Timeout: 10, 51 | PoolLimit: 256, 52 | Username: "root", 53 | Password: "", 54 | Database: "test", 55 | } 56 | } 57 | 58 | // Reload sync automatically config from config file. 59 | func (cfg *Config) Reload(bind cfgo.BindFunc) error { 60 | if cfg.init { 61 | return nil 62 | } 63 | err := bind() 64 | if err != nil { 65 | return err 66 | } 67 | if len(cfg.Addrs) == 0 { 68 | cfg.Addrs = []string{"127.0.0.1:27017"} 69 | } 70 | if len(cfg.Username) == 0 { 71 | cfg.Username = "root" 72 | } 73 | if len(cfg.Database) == 0 { 74 | cfg.Database = "test" 75 | } 76 | cfg.init = true 77 | return nil 78 | } 79 | 80 | // Set config 81 | func (mgoConfig *Config) Source() *mgo.DialInfo { 82 | dialInfo := &mgo.DialInfo{ 83 | Addrs: mgoConfig.Addrs, 84 | Username: mgoConfig.Username, 85 | Password: mgoConfig.Password, 86 | Database: mgoConfig.Database, 87 | Timeout: mgoConfig.Timeout, 88 | PoolLimit: mgoConfig.PoolLimit, 89 | } 90 | 91 | return dialInfo 92 | } 93 | -------------------------------------------------------------------------------- /gateway/logic/websocket/conn_tab.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 github.com/xiaoenai. All Rights Reserved. 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 | package websocket 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6" 19 | "github.com/henrylee2cn/erpc/v6/plugin/auth" 20 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 21 | ) 22 | 23 | type webSocketConnTab struct{} 24 | 25 | var ( 26 | webSocketConnTabPlugin = (*webSocketConnTab)(nil) 27 | ) 28 | 29 | var ( 30 | _ erpc.PostReadCallBodyPlugin = webSocketConnTabPlugin 31 | _ erpc.PostReadPushBodyPlugin = webSocketConnTabPlugin 32 | _ erpc.PostDisconnectPlugin = webSocketConnTabPlugin 33 | ) 34 | 35 | func (c *webSocketConnTab) Name() string { 36 | return "WebSocketConnTab" 37 | } 38 | 39 | func (c *webSocketConnTab) authAndLogon(authInfo string, sess auth.Session) *erpc.Status { 40 | token, stat := logic.AuthFunc()(authInfo) 41 | if stat != nil { 42 | return stat 43 | } 44 | info := token.AddedQuery() 45 | if info != nil && info.Len() > 0 { 46 | sess.Swap().Store(webSocketConnTabPlugin, info.String()) 47 | } 48 | stat = logic.WebSocketHooks().OnLogon(sess, token) 49 | if stat == nil { 50 | erpc.Tracef("[+SOCKET_CONN] addr: %s, id: %s", sess.RemoteAddr().String(), sess.(erpc.BaseSession).ID()) 51 | } 52 | return stat 53 | } 54 | 55 | func (c *webSocketConnTab) PostReadCallBody(ctx erpc.ReadCtx) *erpc.Status { 56 | _appendQuery, _ := ctx.Swap().Load(webSocketConnTabPlugin) 57 | appendQuery, _ := _appendQuery.(string) 58 | erpc.WithAddMeta("", appendQuery) 59 | return nil 60 | } 61 | 62 | func (c *webSocketConnTab) PostReadPushBody(ctx erpc.ReadCtx) *erpc.Status { 63 | return c.PostReadCallBody(ctx) 64 | } 65 | 66 | func (c *webSocketConnTab) PostDisconnect(sess erpc.BaseSession) *erpc.Status { 67 | erpc.Tracef("[-WEBSOCKET_CONN] addr: %s, id: %s", sess.RemoteAddr().String(), sess.ID()) 68 | return logic.WebSocketHooks().OnLogoff(sess) 69 | } 70 | -------------------------------------------------------------------------------- /gateway/types/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package types 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6" 19 | "github.com/henrylee2cn/erpc/v6/utils" 20 | ) 21 | 22 | type ( 23 | // AuthFunc Verifies access token 24 | AuthFunc func(authInfo string) (AccessToken, *erpc.Status) 25 | // AccessToken access token info 26 | AccessToken interface { 27 | // String returns the access token string. 28 | String() string 29 | // SessionId specifies the string as the session ID. 30 | SessionId() string 31 | // Uid returns the user id. 32 | Uid() string 33 | // DeviceId returns the device id. 34 | DeviceId() string 35 | // AddedQuery the user information will be appended to the URI query part. 36 | AddedQuery() *utils.Args 37 | } 38 | ) 39 | 40 | // DefaultAuthFunc returns the default authorization function for access behavior. 41 | func DefaultAuthFunc() AuthFunc { 42 | return defAuthFunc 43 | } 44 | 45 | func defAuthFunc(authInfo string) (AccessToken, *erpc.Status) { 46 | return defAccessToken(authInfo), nil 47 | } 48 | 49 | type defAccessToken string 50 | 51 | // String returns the access token string. 52 | func (d defAccessToken) String() string { 53 | return string(d) 54 | } 55 | 56 | // SessionId specifies the string as the session ID. 57 | func (d defAccessToken) SessionId() string { 58 | return string(d) 59 | } 60 | 61 | // Uid returns the user id. 62 | func (d defAccessToken) Uid() string { 63 | return string(d) 64 | } 65 | 66 | // DeviceId returns the device id. 67 | func (d defAccessToken) DeviceId() string { 68 | return string(d) 69 | } 70 | 71 | type ctx struct{ erpc.UnknownPushCtx } 72 | 73 | // AddedQuery the user information will be appended to the URI query part. 74 | func (d defAccessToken) AddedQuery() *utils.Args { 75 | args := utils.AcquireArgs() 76 | args.Set("_uid", d.Uid()) 77 | args.Set("_device_id", d.DeviceId()) 78 | return args 79 | } 80 | -------------------------------------------------------------------------------- /micro/create/create.go: -------------------------------------------------------------------------------- 1 | package create 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "strings" 7 | 8 | "github.com/henrylee2cn/erpc/v6" 9 | "github.com/henrylee2cn/goutil" 10 | "github.com/xiaoenai/tp-micro/v6/micro/create/tpl" 11 | "github.com/xiaoenai/tp-micro/v6/micro/info" 12 | ) 13 | 14 | // MicroTpl template file name 15 | const MicroTpl = "__tp-micro__tpl__.go" 16 | 17 | // MicroGenLock the file is used to markup generated project 18 | const MicroGenLock = "__tp-micro__gen__.lock" 19 | 20 | // CreateProject creates a project. 21 | func CreateProject(force, newdoc bool) { 22 | erpc.Infof("Generating project: %s", info.ProjPath()) 23 | 24 | os.MkdirAll(info.AbsPath(), os.FileMode(0755)) 25 | err := os.Chdir(info.AbsPath()) 26 | if err != nil { 27 | erpc.Fatalf("[micro] Jump working directory failed: %v", err) 28 | } 29 | 30 | force = force || !goutil.FileExists(MicroGenLock) 31 | 32 | // creates base files 33 | if force { 34 | tpl.Create() 35 | } 36 | 37 | // read temptale file 38 | b, err := ioutil.ReadFile(MicroTpl) 39 | if err != nil { 40 | b = []byte(strings.Replace(__tpl__, "__PROJ_NAME__", info.ProjName(), -1)) 41 | } 42 | 43 | // new project code 44 | proj := NewProject(b) 45 | proj.Generator(force, force || newdoc) 46 | 47 | // write template file 48 | f, err := os.OpenFile(MicroTpl, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) 49 | if err != nil { 50 | erpc.Fatalf("[micro] Create files error: %v", err) 51 | } 52 | defer f.Close() 53 | f.Write(formatSource(b)) 54 | 55 | tpl.RestoreAsset("./", MicroGenLock) 56 | 57 | erpc.Infof("Completed code generation!") 58 | } 59 | 60 | // CreateDoc creates a project doc. 61 | func CreateDoc() { 62 | erpc.Infof("Generating README.md: %s", info.ProjPath()) 63 | 64 | os.MkdirAll(info.AbsPath(), os.FileMode(0755)) 65 | err := os.Chdir(info.AbsPath()) 66 | if err != nil { 67 | erpc.Fatalf("[micro] Jump working directory failed: %v", err) 68 | } 69 | 70 | // read temptale file 71 | b, err := ioutil.ReadFile(MicroTpl) 72 | if err != nil { 73 | b = []byte(strings.Replace(__tpl__, "__PROJ_NAME__", info.ProjName(), -1)) 74 | } 75 | 76 | // new project code 77 | proj := NewProject(b) 78 | proj.gen() 79 | proj.genAndWriteReadmeFile() 80 | 81 | // write template file 82 | f, err := os.OpenFile(MicroTpl, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) 83 | if err != nil { 84 | erpc.Fatalf("[micro] Create files error: %v", err) 85 | } 86 | defer f.Close() 87 | f.Write(formatSource(b)) 88 | 89 | erpc.Infof("Completed README.md generation!") 90 | } 91 | -------------------------------------------------------------------------------- /gateway/helper/gray/logic/logic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/henrylee2cn/goutil" 7 | "github.com/henrylee2cn/erpc/v6" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | 10 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/logic/model" 11 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/types" 12 | ) 13 | 14 | var ( 15 | regexpCache = goutil.AtomicMap() 16 | notGrayResult = new(types.IsGrayResult) 17 | isGrayResult = &types.IsGrayResult{ 18 | Gray: true, 19 | } 20 | ) 21 | 22 | // IsGray check whether the service should use grayscale based on the uid. 23 | func IsGray(args *types.IsGrayArgs) (*types.IsGrayResult, *erpc.Status) { 24 | g, exist, err := model.GetGrayMatchByUri(args.Uri) 25 | if err != nil { 26 | return nil, micro.RerrInternalServerError.SetCause(err.Error()) 27 | } 28 | if !exist { 29 | return notGrayResult, nil 30 | } 31 | var re *regexp.Regexp 32 | reIface, ok := regexpCache.Load(g.Regexp) 33 | if !ok { 34 | re, err = regexp.Compile(g.Regexp) 35 | if err != nil { 36 | return nil, micro.RerrInternalServerError.SetCause(err.Error()) 37 | } 38 | regexpCache.Store(g.Regexp, re) 39 | } else { 40 | re = reIface.(*regexp.Regexp) 41 | } 42 | if re.MatchString(args.Uid) { 43 | return isGrayResult, nil 44 | } 45 | return notGrayResult, nil 46 | } 47 | 48 | // Get get the rule of gray. 49 | func Get(args *types.GetArgs) (*types.GrayMatch, *erpc.Status) { 50 | g, exist, err := model.GetGrayMatchByUri(args.Uri) 51 | if err != nil { 52 | return nil, micro.RerrInternalServerError.SetCause(err.Error()) 53 | } 54 | if !exist { 55 | return nil, micro.RerrNotFound 56 | } 57 | return g, nil 58 | } 59 | 60 | // Delete delete the rule of gray. 61 | func Delete(args *types.DeleteArgs) (*struct{}, *erpc.Status) { 62 | err := model.DeleteGrayMatchByUri(args.Uri) 63 | if err != nil { 64 | return nil, micro.RerrInternalServerError.SetCause(err.Error()) 65 | } 66 | return new(struct{}), nil 67 | } 68 | 69 | // Set insert or update the regular expression for matching the URI. 70 | func Set(args *types.SetArgs) (*struct{}, *erpc.Status) { 71 | _, ok := regexpCache.Load(args.Regexp) 72 | if !ok { 73 | re, err := regexp.Compile(args.Regexp) 74 | if err != nil { 75 | return nil, micro.RerrInvalidParameter.SetCause(err.Error()) 76 | } 77 | regexpCache.Store(args.Regexp, re) 78 | } 79 | // put 80 | err := model.UpsertGrayMatch(&model.GrayMatch{ 81 | Uri: args.Uri, 82 | Regexp: args.Regexp, 83 | }) 84 | if err != nil { 85 | return nil, micro.RerrInternalServerError.SetCause(err.Error()) 86 | } 87 | return new(struct{}), nil 88 | } 89 | -------------------------------------------------------------------------------- /gateway/helper/gray/gray.go: -------------------------------------------------------------------------------- 1 | package gray 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/henrylee2cn/erpc/v6" 7 | "github.com/henrylee2cn/erpc/v6/plugin/proxy" 8 | micro "github.com/xiaoenai/tp-micro/v6" 9 | "github.com/xiaoenai/tp-micro/v6/clientele" 10 | "github.com/xiaoenai/tp-micro/v6/discovery" 11 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/api" 12 | "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/logic" 13 | mod "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/logic/model" 14 | types "github.com/xiaoenai/tp-micro/v6/gateway/helper/gray/types" 15 | gwLogic "github.com/xiaoenai/tp-micro/v6/gateway/logic" 16 | gwTypes "github.com/xiaoenai/tp-micro/v6/gateway/types" 17 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 18 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 19 | "github.com/xiaoenai/tp-micro/v6/model/redis" 20 | ) 21 | 22 | // SetGray sets gray model to *gwTypes.Business. 23 | // Note: the result grayClient may be used externally. 24 | func SetGray( 25 | biz *gwTypes.Business, 26 | grayClientConfig micro.CliConfig, 27 | grayEtcdConfig etcd.EasyConfig, 28 | mysqlConfig mysql.Config, 29 | redisConfig redis.Config, 30 | protoFunc erpc.ProtoFunc, 31 | ) (grayClient *micro.Client, err error) { 32 | err = mod.Init(mysqlConfig, redisConfig) 33 | if err != nil { 34 | return nil, err 35 | } 36 | // if protoFunc == nil { 37 | // protoFunc = socket.NewRawProtoFunc 38 | // } 39 | grayEtcdClient, err := etcd.EasyNew(grayEtcdConfig) 40 | if err != nil { 41 | return nil, err 42 | } 43 | grayClient = micro.NewClient( 44 | grayClientConfig, 45 | discovery.NewLinkerFromEtcd(grayEtcdClient), 46 | ) 47 | grayClient.SetProtoFunc(protoFunc) 48 | 49 | biz.InnerServerPlugins = append(biz.InnerServerPlugins, new(innerServerPlugin)) 50 | biz.ProxySelector = func(label *proxy.Label) proxy.Forwarder { 51 | idx := strings.Index(label.ServiceMethod, "?") 52 | var uri string 53 | if idx != -1 { 54 | uri = label.ServiceMethod[:idx] 55 | } else { 56 | uri = label.ServiceMethod 57 | } 58 | r, stat := logic.IsGray(&types.IsGrayArgs{ 59 | Uri: uri, 60 | Uid: label.SessionID, 61 | }) 62 | if stat != nil { 63 | erpc.Errorf("%s", stat.String()) 64 | return clientele.GetDynamicClient() 65 | } 66 | if !r.Gray { 67 | return clientele.GetDynamicClient() 68 | } 69 | return grayClient 70 | } 71 | return grayClient, nil 72 | } 73 | 74 | type innerServerPlugin struct{} 75 | 76 | func (*innerServerPlugin) Name() string { 77 | return "route_gray" 78 | } 79 | 80 | var _ erpc.PostNewPeerPlugin = (*innerServerPlugin)(nil) 81 | 82 | func (*innerServerPlugin) PostNewPeer(peer erpc.EarlyPeer) error { 83 | api.Route("/gw/"+gwLogic.ApiVersion()+"/gray", peer.Router()) 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /model/mysql/values.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | ) 7 | 8 | var ( 9 | emptyObjectValue = []byte("{}") 10 | emptyArrayValue = []byte("[]") 11 | ) 12 | 13 | // Int32s db column value type. 14 | type Int32s []int32 15 | 16 | // Scan implements the sql.Scanner interface. 17 | func (i *Int32s) Scan(value interface{}) error { 18 | data, ok := value.([]byte) 19 | if !ok { 20 | return nil 21 | } 22 | return json.Unmarshal(data, i) 23 | } 24 | 25 | // Value implements the driver.Valuer interface. 26 | func (i Int32s) Value() (driver.Value, error) { 27 | if i == nil { 28 | return emptyArrayValue, nil 29 | } 30 | return json.Marshal(i) 31 | } 32 | 33 | // Int64s db column value type. 34 | type Int64s []int64 35 | 36 | // Scan implements the sql.Scanner interface. 37 | func (i *Int64s) Scan(value interface{}) error { 38 | data, ok := value.([]byte) 39 | if !ok { 40 | return nil 41 | } 42 | return json.Unmarshal(data, i) 43 | } 44 | 45 | // Value implements the driver.Valuer interface. 46 | func (i Int64s) Value() (driver.Value, error) { 47 | if i == nil { 48 | return emptyArrayValue, nil 49 | } 50 | return json.Marshal(i) 51 | } 52 | 53 | // Strings db column value type. 54 | type Strings []string 55 | 56 | // Scan implements the sql.Scanner interface. 57 | func (s *Strings) Scan(value interface{}) error { 58 | data, ok := value.([]byte) 59 | if !ok { 60 | return nil 61 | } 62 | return json.Unmarshal(data, s) 63 | } 64 | 65 | // Value implements the driver.Valuer interface. 66 | func (s Strings) Value() (driver.Value, error) { 67 | if s == nil { 68 | return emptyArrayValue, nil 69 | } 70 | return json.Marshal(s) 71 | } 72 | 73 | // Int8IFaceMap db column value type. 74 | type Int8IFaceMap map[int8]interface{} 75 | 76 | // Scan implements the sql.Scanner interface. 77 | func (i *Int8IFaceMap) Scan(value interface{}) error { 78 | data, ok := value.([]byte) 79 | if !ok { 80 | return nil 81 | } 82 | return json.Unmarshal(data, i) 83 | } 84 | 85 | // Value implements the driver.Valuer interface. 86 | func (i Int8IFaceMap) Value() (driver.Value, error) { 87 | if i == nil { 88 | return emptyObjectValue, nil 89 | } 90 | return json.Marshal(i) 91 | } 92 | 93 | // IntStringMap db column value type. 94 | type IntStringMap map[int]string 95 | 96 | // Scan implements the sql.Scanner interface. 97 | func (i *IntStringMap) Scan(value interface{}) error { 98 | data, ok := value.([]byte) 99 | if !ok { 100 | return nil 101 | } 102 | return json.Unmarshal(data, i) 103 | } 104 | 105 | // Value implements the driver.Valuer interface. 106 | func (i IntStringMap) Value() (driver.Value, error) { 107 | if i == nil { 108 | return emptyObjectValue, nil 109 | } 110 | return json.Marshal(i) 111 | } 112 | -------------------------------------------------------------------------------- /micro/run/fsnotify/fsnotify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package fsnotify implements file system notification. 6 | package fsnotify 7 | 8 | import "fmt" 9 | 10 | const ( 11 | FSN_CREATE = 1 12 | FSN_MODIFY = 2 13 | FSN_DELETE = 4 14 | FSN_RENAME = 8 15 | 16 | FSN_ALL = FSN_MODIFY | FSN_DELETE | FSN_RENAME | FSN_CREATE 17 | ) 18 | 19 | // Purge events from interal chan to external chan if passes filter 20 | func (w *Watcher) purgeEvents() { 21 | for ev := range w.internalEvent { 22 | sendEvent := false 23 | w.fsnmut.Lock() 24 | fsnFlags := w.fsnFlags[ev.Name] 25 | w.fsnmut.Unlock() 26 | 27 | if (fsnFlags&FSN_CREATE == FSN_CREATE) && ev.IsCreate() { 28 | sendEvent = true 29 | } 30 | 31 | if (fsnFlags&FSN_MODIFY == FSN_MODIFY) && ev.IsModify() { 32 | sendEvent = true 33 | } 34 | 35 | if (fsnFlags&FSN_DELETE == FSN_DELETE) && ev.IsDelete() { 36 | sendEvent = true 37 | } 38 | 39 | if (fsnFlags&FSN_RENAME == FSN_RENAME) && ev.IsRename() { 40 | sendEvent = true 41 | } 42 | 43 | if sendEvent { 44 | w.Event <- ev 45 | } 46 | 47 | // If there's no file, then no more events for user 48 | // BSD must keep watch for internal use (watches DELETEs to keep track 49 | // what files exist for create events) 50 | if ev.IsDelete() { 51 | w.fsnmut.Lock() 52 | delete(w.fsnFlags, ev.Name) 53 | w.fsnmut.Unlock() 54 | } 55 | } 56 | 57 | close(w.Event) 58 | } 59 | 60 | // Watch a given file path 61 | func (w *Watcher) Watch(path string) error { 62 | return w.WatchFlags(path, FSN_ALL) 63 | } 64 | 65 | // Watch a given file path for a particular set of notifications (FSN_MODIFY etc.) 66 | func (w *Watcher) WatchFlags(path string, flags uint32) error { 67 | w.fsnmut.Lock() 68 | w.fsnFlags[path] = flags 69 | w.fsnmut.Unlock() 70 | return w.watch(path) 71 | } 72 | 73 | // Remove a watch on a file 74 | func (w *Watcher) RemoveWatch(path string) error { 75 | w.fsnmut.Lock() 76 | delete(w.fsnFlags, path) 77 | w.fsnmut.Unlock() 78 | return w.removeWatch(path) 79 | } 80 | 81 | // String formats the event e in the form 82 | // "filename: DELETE|MODIFY|..." 83 | func (e *FileEvent) String() string { 84 | var events string = "" 85 | 86 | if e.IsCreate() { 87 | events += "|" + "CREATE" 88 | } 89 | 90 | if e.IsDelete() { 91 | events += "|" + "DELETE" 92 | } 93 | 94 | if e.IsModify() { 95 | events += "|" + "MODIFY" 96 | } 97 | 98 | if e.IsRename() { 99 | events += "|" + "RENAME" 100 | } 101 | 102 | if e.IsAttrib() { 103 | events += "|" + "ATTRIB" 104 | } 105 | 106 | if len(events) > 0 { 107 | events = events[1:] 108 | } 109 | 110 | return fmt.Sprintf("%q: %s", e.Name, events) 111 | } 112 | -------------------------------------------------------------------------------- /model/mysql/README.md: -------------------------------------------------------------------------------- 1 | # mysql 2 | 3 | A mysql ORM(Object Role Modeling) package with redis cache. 4 | 5 | ## Example 6 | 7 | ```go 8 | import ( 9 | "encoding/json" 10 | "testing" 11 | "time" 12 | 13 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 14 | "github.com/xiaoenai/tp-micro/v6/model/redis" 15 | ) 16 | 17 | type testTable struct { 18 | TestId int64 19 | TestContent string 20 | Deleted bool `db:"test_deleted"` 21 | } 22 | 23 | func (t *testTable) TableName() string { 24 | return "test_table" 25 | } 26 | 27 | func TestCacheDb(t *testing.T) { 28 | dbConf, err := mysql.ReadConfig("test_db") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | dbConf.Database = "test" 33 | redisConf, err := redis.ReadConfig("test_redis") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | db, err := mysql.Connect(dbConf, redisConf) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | _, err = db.Exec("CREATE TABLE IF NOT EXISTS `dbtest` (`test_id` INT(10) AUTO_INCREMENT, `test_content` VARCHAR(20), `test_deleted` TINYINT(2), PRIMARY KEY(`test_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测试表'") 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | c, err := db.RegCacheableDB(new(testTable), time.Second) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | obj := &testTable{ 50 | TestId: 1, 51 | TestContent: "abc", 52 | Deleted: false, 53 | } 54 | _, err = c.NamedExec("INSERT INTO dbtest (test_id,test_content,test_deleted)VALUES(:test_id,:test_content,:test_deleted) ON DUPLICATE KEY UPDATE test_id=:test_id", obj) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | // query and cache 60 | dest := &testTable{TestId: 1} 61 | err = c.CacheGet(dest) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | t.Logf("first:%#v", dest) 66 | 67 | // verify the cache 68 | cacheKey, _, err := c.CreateCacheKey(dest) 69 | t.Logf("cacheKey:%#v", cacheKey) 70 | key := cacheKey.Key 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | b, err := c.Cache.Get(key).Bytes() 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | var v1 = new(testTable) 79 | err = json.Unmarshal(b, v1) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | t.Logf("cache of before expiring: %+v", v1) 84 | 85 | time.Sleep(2 * time.Second) 86 | 87 | b, err = c.Cache.Get(key).Bytes() 88 | if err == nil { 89 | var v2 = new(testTable) 90 | err = json.Unmarshal(b, v2) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | t.Fatalf("expired but not deleted: %#v", v2) 95 | } 96 | t.Logf("expired cache error: %v", err) 97 | } 98 | ``` 99 | 100 | ```sh 101 | go test -v -run=TestCacheDb 102 | ``` -------------------------------------------------------------------------------- /model/mysql/config.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/henrylee2cn/cfgo" 8 | ) 9 | 10 | // Config db config 11 | type Config struct { 12 | Database string 13 | Username string 14 | Password string 15 | Host string 16 | Port int 17 | // the maximum number of connections in the idle connection pool. 18 | // 19 | // If MaxOpenConns is greater than 0 but less than the new MaxIdleConns 20 | // then the new MaxIdleConns will be reduced to match the MaxOpenConns limit 21 | // 22 | // If n <= 0, no idle connections are retained. 23 | MaxIdleConns int `yaml:"max_idle_conns"` 24 | // the maximum number of open connections to the database. 25 | // If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than 26 | // MaxIdleConns, then MaxIdleConns will be reduced to match the new 27 | // MaxOpenConns limit 28 | // 29 | // If n <= 0, then there is no limit on the number of open connections. 30 | // The default is 0 (unlimited). 31 | MaxOpenConns int `yaml:"max_open_conns"` 32 | // maximum amount of second a connection may be reused. 33 | // If d <= 0, connections are reused forever. 34 | ConnMaxLifetime int64 `yaml:"conn_max_lifetime"` 35 | 36 | // NoCache whether to disable cache 37 | NoCache bool `yaml:"no_cache"` 38 | 39 | init bool 40 | } 41 | 42 | var configs = make(map[string]*Config) 43 | 44 | // ReadConfig gets a mysql db config form yaml. 45 | func ReadConfig(configSection string) (*Config, error) { 46 | conf, ok := configs[configSection] 47 | if ok { 48 | return conf, nil 49 | } 50 | conf = NewConfig() 51 | err := cfgo.Reg(configSection, conf) 52 | if err == nil { 53 | configs[configSection] = conf 54 | return conf, nil 55 | } else { 56 | return nil, err 57 | } 58 | } 59 | 60 | // NewConfig creates a default config. 61 | func NewConfig() *Config { 62 | return &Config{ 63 | Host: "127.0.0.1", 64 | Port: 3306, 65 | Username: "root", 66 | Database: "test", 67 | } 68 | } 69 | 70 | // Source returns the mysql connection string. 71 | func (cfg *Config) Source() string { 72 | pwd := cfg.Password 73 | if pwd != "" { 74 | pwd = ":" + pwd 75 | } 76 | port := cfg.Port 77 | if port == 0 { 78 | port = 3306 79 | } 80 | return fmt.Sprintf("%s%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=Local&interpolateParams=true", cfg.Username, pwd, cfg.Host, port, cfg.Database) 81 | } 82 | 83 | // Reload sync automatically config from config file. 84 | func (cfg *Config) Reload(bind cfgo.BindFunc) error { 85 | if cfg.init { 86 | return nil 87 | } 88 | err := bind() 89 | if err != nil { 90 | return err 91 | } 92 | if len(cfg.Host) == 0 { 93 | cfg.Host = "127.0.0.1" 94 | } 95 | if cfg.Port <= 0 { 96 | cfg.Port = 3306 97 | } 98 | if len(cfg.Username) == 0 { 99 | cfg.Username = "root" 100 | } 101 | if len(cfg.Database) == 0 { 102 | cfg.Database = "test" 103 | } 104 | cfg.init = true 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /model/mysql/pre_db.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/henrylee2cn/goutil" 8 | "github.com/xiaoenai/tp-micro/v6/model/redis" 9 | "github.com/xiaoenai/tp-micro/v6/model/sqlx" 10 | "github.com/xiaoenai/tp-micro/v6/model/sqlx/reflectx" 11 | ) 12 | 13 | // PreDB preset *DB 14 | type PreDB struct { 15 | *DB 16 | preFuncs map[string]func() error 17 | inited bool 18 | } 19 | 20 | // NewPreDB creates a unconnected *DB 21 | func NewPreDB() *PreDB { 22 | return &PreDB{ 23 | DB: &DB{ 24 | cacheableDBs: make(map[string]*CacheableDB), 25 | }, 26 | preFuncs: make(map[string]func() error), 27 | } 28 | } 29 | 30 | // Init initialize *DB. 31 | func (p *PreDB) Init(dbConfig *Config, redisConfig *redis.Config) (err error) { 32 | var cache *redis.Client 33 | if !dbConfig.NoCache && redisConfig != nil { 34 | cache, err = redis.NewClient(redisConfig) 35 | if err != nil { 36 | return err 37 | } 38 | } 39 | return p.Init2(dbConfig, cache) 40 | } 41 | 42 | // Init2 initialize *DB. 43 | func (p *PreDB) Init2(dbConfig *Config, redisClient *redis.Client) (err error) { 44 | p.DB.DB, err = sqlx.Connect("mysql", dbConfig.Source()) 45 | if err != nil { 46 | return err 47 | } 48 | p.DB.SetMaxOpenConns(dbConfig.MaxOpenConns) 49 | p.DB.SetMaxIdleConns(dbConfig.MaxIdleConns) 50 | p.DB.SetConnMaxLifetime(time.Duration(dbConfig.ConnMaxLifetime) * time.Second) 51 | // p.DB.MapperFunc(goutil.SnakeString) 52 | p.DB.Mapper = reflectx.NewMapperFunc("json", goutil.SnakeString) 53 | p.DB.dbConfig = dbConfig 54 | if !dbConfig.NoCache && redisClient != nil { 55 | p.DB.Cache = redisClient 56 | p.DB.redisConfig = redisClient.Config() 57 | } 58 | 59 | for _, preFunc := range p.preFuncs { 60 | if err = preFunc(); err != nil { 61 | return err 62 | } 63 | } 64 | p.inited = true 65 | return nil 66 | } 67 | 68 | // RegCacheableDB registers a cacheable table. 69 | func (p *PreDB) RegCacheableDB(ormStructPtr Cacheable, cacheExpiration time.Duration, initQuery string, args ...interface{}) (*CacheableDB, error) { 70 | if p.inited { 71 | if len(initQuery) > 0 { 72 | _, err := p.DB.Exec(initQuery, args...) 73 | if err != nil { 74 | return nil, err 75 | } 76 | } 77 | return p.DB.RegCacheableDB(ormStructPtr, cacheExpiration) 78 | } 79 | 80 | tableName := ormStructPtr.TableName() 81 | if _, ok := p.preFuncs[ormStructPtr.TableName()]; ok { 82 | return nil, fmt.Errorf("re-register cacheable table: %s", tableName) 83 | } 84 | var cacheableDB = new(CacheableDB) 85 | var preFunc = func() error { 86 | if len(initQuery) > 0 { 87 | _, err := p.DB.Exec(initQuery, args...) 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | _cacheableDB, err := p.DB.RegCacheableDB(ormStructPtr, cacheExpiration) 93 | if err == nil { 94 | *cacheableDB = *_cacheableDB 95 | p.DB.cacheableDBs[tableName] = cacheableDB 96 | } 97 | return err 98 | } 99 | p.preFuncs[tableName] = preFunc 100 | return cacheableDB, nil 101 | } 102 | -------------------------------------------------------------------------------- /helper/mod-html/render_test.go: -------------------------------------------------------------------------------- 1 | package html_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | html "github.com/xiaoenai/tp-micro/v6/helper/mod-html" 8 | ) 9 | 10 | type meta struct{} 11 | 12 | func (m *meta) SetMeta(key, value string) {} 13 | 14 | func TestParseText(t *testing.T) { 15 | const ( 16 | doc1 = ` 17 | 18 | 19 | html test 1 20 | 21 | 22 |
23 |

{{.}}

24 | 25 | ` 26 | doc2 = ` 27 | 28 | 29 | html test 2 30 | 31 | 32 |
33 |

{{.}}

34 | 35 | ` 36 | ) 37 | 38 | err := html.Parse("a", doc1) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | err = html.Parse("b", doc2) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | b, stat := html.Render(new(meta), "a", "TestParse A!") 48 | t.Logf("body: %s, rerr: %v", b, stat) 49 | b, stat = html.Render(new(meta), "b", "TestParse B!") 50 | t.Logf("body: %s, rerr: %v", b, stat) 51 | } 52 | 53 | func TestParseFiles(t *testing.T) { 54 | err := html.ParseFiles("../mod-html/a_test.tpl", "../mod-html/b_test.tpl") 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | b, stat := html.Render(new(meta), "a_test.tpl", "TestParseFiles A!") 60 | t.Logf("body: %s, rerr: %v", b, stat) 61 | b, stat = html.Render(new(meta), "b_test.tpl", "TestParseFiles B!") 62 | t.Logf("body: %s, rerr: %v", b, stat) 63 | } 64 | 65 | func TestParseGlob(t *testing.T) { 66 | err := html.ParseGlob("../mod-html/*.tpl") 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | b, stat := html.Render(new(meta), "a_test.tpl", "TestParseGlob A!") 72 | t.Logf("body: %s, rerr: %v", b, stat) 73 | b, stat = html.Render(new(meta), "b_test.tpl", "TestParseGlob B!") 74 | t.Logf("body: %s, rerr: %v", b, stat) 75 | } 76 | 77 | func TestDelims(t *testing.T) { 78 | const ( 79 | doc = ` 80 | 81 | 82 | html test 1 83 | 84 | 85 |
86 |

{{{.}}}

87 | 88 | ` 89 | ) 90 | 91 | html.Delims("{{{", "}}}") 92 | err := html.Parse("doc", doc) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | b, stat := html.Render(new(meta), "doc", "TestDelims!") 97 | t.Logf("body: %s, rerr: %v", b, stat) 98 | } 99 | 100 | func TestGoTimingRefresh(t *testing.T) { 101 | err := html.ParseFiles("../mod-html/a_test.tpl") 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | err = html.ParseGlob("../mod-html/*.tpl") 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | b, stat := html.Render(new(meta), "a_test.tpl", "TestGoTimingRefresh 1!") 111 | t.Logf("test1: body: %s, rerr: %v", b, stat) 112 | 113 | html.GoTimingRefresh(time.Second * 5) 114 | time.Sleep(time.Second * 15) 115 | 116 | b, stat = html.Render(new(meta), "a_test.tpl", "TestGoTimingRefresh 2!") 117 | t.Logf("test2: body: %s, rerr: %v", b, stat) 118 | } 119 | -------------------------------------------------------------------------------- /gateway/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package gateway 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/henrylee2cn/cfgo" 21 | micro "github.com/xiaoenai/tp-micro/v6" 22 | short "github.com/xiaoenai/tp-micro/v6/gateway/logic/http" 23 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 24 | ) 25 | 26 | // Config app config 27 | type Config struct { 28 | EnableHttp bool `yaml:"enable_http"` 29 | EnableSocket bool `yaml:"enable_socket"` 30 | EnableWebSocket bool `yaml:"enable_web_socket"` 31 | OuterHttpServer short.HttpSrvConfig `yaml:"outer_http_server"` 32 | OuterSocketServer micro.SrvConfig `yaml:"outer_socket_server"` 33 | InnerSocketServer micro.SrvConfig `yaml:"inner_socket_server"` 34 | InnerSocketClient micro.CliConfig `yaml:"inner_socket_client"` 35 | WebSocketServer micro.SrvConfig `yaml:"web_socket_server"` 36 | Etcd etcd.EasyConfig `yaml:"etcd"` 37 | } 38 | 39 | // NewConfig creates a default config. 40 | func NewConfig() *Config { 41 | return &Config{ 42 | EnableHttp: true, 43 | EnableSocket: true, 44 | EnableWebSocket: true, 45 | OuterHttpServer: short.HttpSrvConfig{ 46 | ListenAddress: "0.0.0.0:5000", 47 | AllowCross: false, 48 | }, 49 | OuterSocketServer: micro.SrvConfig{ 50 | ListenAddress: "0.0.0.0:5020", 51 | EnableHeartbeat: true, 52 | PrintDetail: true, 53 | CountTime: true, 54 | SlowCometDuration: time.Millisecond * 500, 55 | }, 56 | InnerSocketServer: micro.SrvConfig{ 57 | ListenAddress: "0.0.0.0:5030", 58 | EnableHeartbeat: true, 59 | PrintDetail: true, 60 | CountTime: true, 61 | SlowCometDuration: time.Millisecond * 500, 62 | }, 63 | WebSocketServer: micro.SrvConfig{ 64 | ListenAddress: "0.0.0.0:5040", 65 | EnableHeartbeat: true, 66 | PrintDetail: true, 67 | CountTime: true, 68 | SlowCometDuration: time.Millisecond * 500, 69 | }, 70 | } 71 | } 72 | 73 | // Reload Bi-directionally synchronizes config between YAML file and memory. 74 | func (c *Config) Reload(bind cfgo.BindFunc) error { 75 | err := bind() 76 | if err == nil { 77 | c.OuterHttpServer.OuterIpPort() 78 | } 79 | c.OuterHttpServer.PrintDetail = c.OuterSocketServer.PrintDetail 80 | c.OuterHttpServer.CountTime = c.OuterSocketServer.CountTime 81 | c.OuterHttpServer.SlowCometDuration = c.OuterSocketServer.SlowCometDuration 82 | return err 83 | } 84 | 85 | // check the config 86 | func (c *Config) check() error { 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /model/sqlx/types/types_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "testing" 4 | 5 | func TestGzipText(t *testing.T) { 6 | g := GzippedText("Hello, world") 7 | v, err := g.Value() 8 | if err != nil { 9 | t.Errorf("Was not expecting an error") 10 | } 11 | err = (&g).Scan(v) 12 | if err != nil { 13 | t.Errorf("Was not expecting an error") 14 | } 15 | if string(g) != "Hello, world" { 16 | t.Errorf("Was expecting the string we sent in (Hello World), got %s", string(g)) 17 | } 18 | } 19 | 20 | func TestJSONText(t *testing.T) { 21 | j := JSONText(`{"foo": 1, "bar": 2}`) 22 | v, err := j.Value() 23 | if err != nil { 24 | t.Errorf("Was not expecting an error") 25 | } 26 | err = (&j).Scan(v) 27 | if err != nil { 28 | t.Errorf("Was not expecting an error") 29 | } 30 | m := map[string]interface{}{} 31 | j.Unmarshal(&m) 32 | 33 | if m["foo"].(float64) != 1 || m["bar"].(float64) != 2 { 34 | t.Errorf("Expected valid json but got some garbage instead? %#v", m) 35 | } 36 | 37 | j = JSONText(`{"foo": 1, invalid, false}`) 38 | v, err = j.Value() 39 | if err == nil { 40 | t.Errorf("Was expecting invalid json to fail!") 41 | } 42 | 43 | j = JSONText("") 44 | v, err = j.Value() 45 | if err != nil { 46 | t.Errorf("Was not expecting an error") 47 | } 48 | 49 | err = (&j).Scan(v) 50 | if err != nil { 51 | t.Errorf("Was not expecting an error") 52 | } 53 | 54 | j = JSONText(nil) 55 | v, err = j.Value() 56 | if err != nil { 57 | t.Errorf("Was not expecting an error") 58 | } 59 | 60 | err = (&j).Scan(v) 61 | if err != nil { 62 | t.Errorf("Was not expecting an error") 63 | } 64 | } 65 | 66 | func TestNullJSONText(t *testing.T) { 67 | j := NullJSONText{} 68 | err := j.Scan(`{"foo": 1, "bar": 2}`) 69 | if err != nil { 70 | t.Errorf("Was not expecting an error") 71 | } 72 | v, err := j.Value() 73 | if err != nil { 74 | t.Errorf("Was not expecting an error") 75 | } 76 | err = (&j).Scan(v) 77 | if err != nil { 78 | t.Errorf("Was not expecting an error") 79 | } 80 | m := map[string]interface{}{} 81 | j.Unmarshal(&m) 82 | 83 | if m["foo"].(float64) != 1 || m["bar"].(float64) != 2 { 84 | t.Errorf("Expected valid json but got some garbage instead? %#v", m) 85 | } 86 | 87 | j = NullJSONText{} 88 | err = j.Scan(nil) 89 | if err != nil { 90 | t.Errorf("Was not expecting an error") 91 | } 92 | if j.Valid != false { 93 | t.Errorf("Expected valid to be false, but got true") 94 | } 95 | } 96 | 97 | func TestBitBool(t *testing.T) { 98 | // Test true value 99 | var b BitBool = true 100 | 101 | v, err := b.Value() 102 | if err != nil { 103 | t.Errorf("Cannot return error") 104 | } 105 | err = (&b).Scan(v) 106 | if err != nil { 107 | t.Errorf("Was not expecting an error") 108 | } 109 | if !b { 110 | t.Errorf("Was expecting the bool we sent in (true), got %v", b) 111 | } 112 | 113 | // Test false value 114 | b = false 115 | 116 | v, err = b.Value() 117 | if err != nil { 118 | t.Errorf("Cannot return error") 119 | } 120 | err = (&b).Scan(v) 121 | if err != nil { 122 | t.Errorf("Was not expecting an error") 123 | } 124 | if b { 125 | t.Errorf("Was expecting the bool we sent in (false), got %v", b) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /model/mysql/db_test.go: -------------------------------------------------------------------------------- 1 | package mysql_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | 8 | "github.com/henrylee2cn/goutil" 9 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 10 | "github.com/xiaoenai/tp-micro/v6/model/redis" 11 | "github.com/xiaoenai/tp-micro/v6/model/sqlx" 12 | "github.com/xiaoenai/tp-micro/v6/model/sqlx/reflectx" 13 | ) 14 | 15 | type testTable struct { 16 | TestId int64 17 | TestContent string 18 | Deleted bool `json:"test_deleted"` 19 | } 20 | 21 | func (t *testTable) TableName() string { 22 | return "dbtest" 23 | } 24 | 25 | func TestNamed(t *testing.T) { 26 | db := new(sqlx.DB) 27 | db.Mapper = reflectx.NewMapperFunc("json", goutil.SnakeString) 28 | p := &testTable{ 29 | TestId: 123, 30 | TestContent: "ctn", 31 | Deleted: true, 32 | } 33 | whereCond, values, err := db.BindNamed("test_id=:test_id AND test_content=:test_content OR test_deleted=1", p) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | t.Logf("whereCond:%s", whereCond) 38 | t.Logf("values:%v", values) 39 | // Output: 40 | // test_id=? AND test_content=? OR test_deleted=1 41 | // values:[123 ctn] 42 | } 43 | 44 | func TestCacheDb(t *testing.T) { 45 | dbConf, err := mysql.ReadConfig("test_db") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | dbConf.Database = "test" 50 | redisConf, err := redis.ReadConfig("test_redis") 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | db, err := mysql.Connect(dbConf, redisConf) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | _, err = db.Exec("CREATE TABLE IF NOT EXISTS `dbtest` (`test_id` INT(10) AUTO_INCREMENT, `test_content` VARCHAR(20), `test_deleted` TINYINT(2), PRIMARY KEY(`test_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测试表'") 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | c, err := db.RegCacheableDB(new(testTable), time.Second) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | obj := &testTable{ 67 | TestId: 1, 68 | TestContent: "abc", 69 | Deleted: false, 70 | } 71 | _, err = c.NamedExec("INSERT INTO dbtest (test_id,test_content,test_deleted)VALUES(:test_id,:test_content,:test_deleted) ON DUPLICATE KEY UPDATE test_id=:test_id", obj) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | // query and cache 77 | dest := &testTable{TestId: 1} 78 | err = c.CacheGet(dest) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | t.Logf("first:%#v", dest) 83 | 84 | // verify the cache 85 | cacheKey, _, err := c.CreateCacheKey(dest) 86 | t.Logf("cacheKey:%#v", cacheKey) 87 | key := cacheKey.Key 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | b, err := c.Cache.Get(key).Bytes() 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | var v1 = new(testTable) 96 | err = json.Unmarshal(b, v1) 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | t.Logf("cache of before expiring: %+v", v1) 101 | 102 | time.Sleep(2 * time.Second) 103 | 104 | b, err = c.Cache.Get(key).Bytes() 105 | if err == nil { 106 | var v2 = new(testTable) 107 | err = json.Unmarshal(b, v2) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | t.Fatalf("expired but not deleted: %#v", v2) 112 | } 113 | t.Logf("expired cache error: %v", err) 114 | } 115 | -------------------------------------------------------------------------------- /gateway/logic/websocket/api.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | _ "unsafe" 7 | 8 | "github.com/henrylee2cn/erpc/v6" 9 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 10 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 11 | ) 12 | 13 | // Gw long connection controller. 14 | type Gw struct { 15 | erpc.CallCtx 16 | } 17 | 18 | // totalConn returns the long connections total. 19 | //go:linkname totalConn github.com/xiaoenai/tp-micro/gateway.TotalConn 20 | //go:nosplit 21 | func wsTotalConn() int32 { 22 | if outerPeer == nil { 23 | return 0 24 | } 25 | return int32(outerPeer.CountSession()) 26 | } 27 | 28 | // WsTotal returns the long connections total. 29 | func (g *Gw) WsTotal(*types.WsTotalArgs) (*types.WsTotalReply, *erpc.Status) { 30 | return &types.WsTotalReply{ConnTotal: wsTotalConn()}, nil 31 | } 32 | 33 | // innerPush pushes the message to the specified user. 34 | func innerPush(uid string, uri string, args interface{}, bodyCodec byte) *erpc.Status { 35 | sess, stat := logic.WebSocketHooks().GetSession(outerPeer, uid) 36 | if stat != nil { 37 | return stat 38 | } 39 | return sess.Push(uri, args, erpc.WithBodyCodec(bodyCodec)) 40 | } 41 | 42 | var wsPushReply = new(types.WsPushReply) 43 | 44 | // WsPush pushes message to the specified user. 45 | func (g *Gw) WsPush(args *types.WsPushArgs) (*types.WsPushReply, *erpc.Status) { 46 | stat := innerPush(args.SessionId, args.Uri, args.Body, byte(args.BodyCodec)) 47 | if stat != nil { 48 | return nil, stat 49 | } 50 | return wsPushReply, nil 51 | } 52 | 53 | // WsMpush multi-push messages to the specified users. 54 | func (g *Gw) WsMpush(args *types.WsMpushArgs) (*types.WsMpushReply, *erpc.Status) { 55 | var ( 56 | wg sync.WaitGroup 57 | sep = "?" 58 | failureSessionIds = make([]string, 0, len(args.Target)) 59 | lock sync.Mutex 60 | body = args.Body 61 | bodyCodec = byte(args.BodyCodec) 62 | ) 63 | if strings.Contains(args.Uri, "?") { 64 | sep = "&" 65 | } 66 | wg.Add(len(args.Target)) 67 | for _, t := range args.Target { 68 | var uri string 69 | if t.AdditionalQuery != "" { 70 | uri = args.Uri + sep + t.AdditionalQuery 71 | } else { 72 | uri = args.Uri 73 | } 74 | sessId := t.SessionId 75 | erpc.TryGo(func() { 76 | defer wg.Done() 77 | stat := innerPush(sessId, uri, body, bodyCodec) 78 | if stat != nil { 79 | lock.Lock() 80 | failureSessionIds = append(failureSessionIds, sessId) 81 | lock.Unlock() 82 | erpc.Tracef("SocketMpush: %s", stat.String()) 83 | } 84 | }) 85 | } 86 | wg.Wait() 87 | return &types.WsMpushReply{ 88 | FailureSessionIds: failureSessionIds, 89 | }, nil 90 | } 91 | 92 | // Kick kicks the uid offline. 93 | func Kick(uid string) (existed bool, err error) { 94 | sess, existed := outerPeer.GetSession(uid) 95 | if existed { 96 | err = sess.Close() 97 | } 98 | return existed, err 99 | } 100 | 101 | // SocketKick kicks the uid offline. 102 | func (g *Gw) WsKick(args *types.SocketKickArgs) (*types.SocketKickReply, *erpc.Status) { 103 | existed, _ := Kick(args.SessionId) 104 | return &types.SocketKickReply{ 105 | Existed: existed, 106 | }, nil 107 | } 108 | -------------------------------------------------------------------------------- /examples/custom/browser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "os/exec" 8 | "runtime" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | go http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | w.Write([]byte(html)) 15 | })) 16 | var cmd *exec.Cmd 17 | switch runtime.GOOS { 18 | case "windows": 19 | cmd = exec.Command("cmd", "/c", "start", "http://localhost:8080") 20 | case "darwin": 21 | cmd = exec.Command("open", "http://localhost:8080") 22 | } 23 | if cmd != nil { 24 | go func() { 25 | log.Println("[browser] Open the default browser after two seconds...") 26 | time.Sleep(time.Second * 2) 27 | cmd.Stdout = os.Stdout 28 | cmd.Stderr = os.Stderr 29 | cmd.Run() 30 | }() 31 | } 32 | select {} 33 | } 34 | 35 | const html = ` 36 | 37 | 38 | /math/divide 39 | 40 | 41 | 42 |
43 |

a:

44 |

b:

45 |
46 | 47 |
48 |

49 |
50 |

Go to Home Page

51 |

Test Redirect to Home Page

52 | 105 | 106 | ` 107 | -------------------------------------------------------------------------------- /examples/simple/browser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "os/exec" 8 | "runtime" 9 | "time" 10 | ) 11 | 12 | //go:generate go build $GOFILE 13 | 14 | func main() { 15 | go http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | w.Write([]byte(html)) 17 | })) 18 | var cmd *exec.Cmd 19 | switch runtime.GOOS { 20 | case "windows": 21 | cmd = exec.Command("cmd", "/c", "start", "http://localhost:8080") 22 | case "darwin": 23 | cmd = exec.Command("open", "http://localhost:8080") 24 | } 25 | if cmd != nil { 26 | go func() { 27 | log.Println("[browser] Open the default browser after two seconds...") 28 | time.Sleep(time.Second * 2) 29 | cmd.Stdout = os.Stdout 30 | cmd.Stderr = os.Stderr 31 | cmd.Run() 32 | }() 33 | } 34 | select {} 35 | } 36 | 37 | const html = ` 38 | 39 | 40 | /math/divide 41 | 42 | 43 | 44 |
45 |

a:

46 |

b:

47 |
48 | 49 |
50 |

51 |
52 |

Go to Home Page

53 |

Test Redirect to Home Page

54 | 107 | 108 | ` 109 | -------------------------------------------------------------------------------- /examples/custom/config/config.yaml: -------------------------------------------------------------------------------- 1 | cluster_client: 2 | network: tcp 3 | local_ip: 0.0.0.0 4 | tls_cert_file: "" 5 | tls_key_file: "" 6 | default_session_age: 0s 7 | default_context_age: 0s 8 | default_dial_timeout: 0s 9 | failover: 0 10 | slow_comet_duration: 0s 11 | default_body_codec: "" 12 | print_detail: false 13 | count_time: false 14 | heartbeat_second: 0 15 | circuit_breaker: 16 | enable: false 17 | error_percentage: 50 18 | break_duration: 5s 19 | 20 | etcd: 21 | endpoints: 22 | - http://127.0.0.1:2379 23 | dial_timeout: 15s 24 | username: "" 25 | password: "" 26 | 27 | gateway: 28 | gw: 29 | enable_http: true 30 | enable_socket: true 31 | outer_http_server: 32 | listen_address: 0.0.0.0:5000 33 | outer_host: "" 34 | tls_cert_file: "" 35 | tls_key_file: "" 36 | allow_cross: true 37 | outer_socket_server: 38 | network: "" 39 | listen_address: 0.0.0.0:5020 40 | tls_cert_file: "" 41 | tls_key_file: "" 42 | default_session_age: 0s 43 | default_context_age: 0s 44 | slow_comet_duration: 0s 45 | default_body_codec: "" 46 | print_detail: true 47 | count_time: true 48 | enable_heartbeat: true 49 | inner_socket_server: 50 | network: "" 51 | listen_address: 0.0.0.0:5030 52 | tls_cert_file: "" 53 | tls_key_file: "" 54 | default_session_age: 0s 55 | default_context_age: 0s 56 | slow_comet_duration: 0s 57 | default_body_codec: "" 58 | print_detail: true 59 | count_time: true 60 | enable_heartbeat: true 61 | gray_socket_client: 62 | network: "" 63 | local_ip: "" 64 | tls_cert_file: "" 65 | tls_key_file: "" 66 | default_session_age: 0s 67 | default_context_age: 0s 68 | default_dial_timeout: 0s 69 | failover: 3 70 | slow_comet_duration: 0s 71 | default_body_codec: "" 72 | print_detail: false 73 | count_time: false 74 | heartbeat_second: 60 75 | circuit_breaker: 76 | enable: false 77 | error_percentage: 0 78 | break_duration: 0s 79 | gray_etcd: 80 | endpoints: 81 | - http://127.0.0.1:2379 82 | dial_timeout: 0s 83 | username: "" 84 | password: "" 85 | redis: 86 | deploy_type: single 87 | for_single: 88 | addr: 127.0.0.1:6379 89 | for_cluster: 90 | addrs: [] 91 | pool_size_per_node: 0 92 | idle_timeout: 0 93 | mysql: 94 | database: test 95 | username: root 96 | password: "" 97 | host: 127.0.0.1 98 | port: 3306 99 | max_idle_conns: 0 100 | max_open_conns: 0 101 | conn_max_lifetime: 0 102 | no_cache: false 103 | 104 | server: 105 | etcd: 106 | dial_timeout: 0s 107 | endpoints: 108 | - http://127.0.0.1:2379 109 | password: "" 110 | username: "" 111 | redis: 112 | deploy_type: single 113 | for_cluster: 114 | addrs: [] 115 | for_single: 116 | addr: 127.0.0.1:6379 117 | idle_timeout: 0 118 | pool_size_per_node: 0 119 | srv: 120 | count_time: false 121 | default_body_codec: "" 122 | default_context_age: 0s 123 | default_session_age: 0s 124 | enable_heartbeat: true 125 | listen_address: :0 126 | network: "" 127 | print_detail: false 128 | slow_comet_duration: 0s 129 | tls_cert_file: "" 130 | tls_key_file: "" 131 | -------------------------------------------------------------------------------- /micro/create/structtag/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Fatih Arslan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of structtag nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | This software includes some portions from Go. Go is used under the terms of the 30 | BSD like license. 31 | 32 | Copyright (c) 2012 The Go Authors. All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions are 36 | met: 37 | 38 | * Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | * Redistributions in binary form must reproduce the above 41 | copyright notice, this list of conditions and the following disclaimer 42 | in the documentation and/or other materials provided with the 43 | distribution. 44 | * Neither the name of Google Inc. nor the names of its 45 | contributors may be used to endorse or promote products derived from 46 | this software without specific prior written permission. 47 | 48 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 49 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 50 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 51 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 52 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 53 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 54 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 55 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 56 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 57 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 58 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59 | 60 | The Go gopher was designed by Renee French. http://reneefrench.blogspot.com/ The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: https://blog.golang.org/gopher 61 | -------------------------------------------------------------------------------- /gateway/logic/http/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package http 16 | 17 | import ( 18 | "crypto/tls" 19 | "fmt" 20 | "net" 21 | "net/http" 22 | "time" 23 | 24 | "github.com/henrylee2cn/erpc/v6" 25 | "github.com/valyala/fasthttp" 26 | micro "github.com/xiaoenai/tp-micro/v6" 27 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 28 | ) 29 | 30 | // HttpSrvConfig config of HTTP server 31 | type HttpSrvConfig struct { 32 | ListenAddress string `yaml:"listen_address"` 33 | OuterHost string `yaml:"outer_host"` 34 | TlsCertFile string `yaml:"tls_cert_file"` 35 | TlsKeyFile string `yaml:"tls_key_file"` 36 | AllowCross bool `yaml:"allow_cross"` 37 | PrintDetail bool `yaml:"-"` 38 | CountTime bool `yaml:"-"` 39 | SlowCometDuration time.Duration `yaml:"-"` 40 | } 41 | 42 | // ListenPort returns the listened port, such as '8080'. 43 | func (h *HttpSrvConfig) ListenPort() string { 44 | _, port, err := net.SplitHostPort(h.ListenAddress) 45 | if err != nil { 46 | erpc.Fatalf("%v", err) 47 | } 48 | return port 49 | } 50 | 51 | // InnerIpPort returns the service's intranet address, such as '192.168.1.120:8080'. 52 | func (h *HttpSrvConfig) InnerIpPort() string { 53 | hostPort, err := micro.InnerIpPort(h.ListenPort()) 54 | if err != nil { 55 | erpc.Fatalf("%v", err) 56 | } 57 | return hostPort 58 | } 59 | 60 | // OuterIpPort returns the service's extranet address, such as '113.116.141.121:8080'. 61 | func (h *HttpSrvConfig) OuterIpPort() string { 62 | if len(h.OuterHost) == 0 { 63 | h.OuterHost, _ = micro.OuterIpPort(h.ListenPort()) 64 | } 65 | return h.OuterHost 66 | } 67 | 68 | // Serve starts HTTP gateway service. 69 | func Serve(srvCfg HttpSrvConfig) { 70 | printDetail = srvCfg.PrintDetail 71 | countTime = srvCfg.CountTime 72 | if srvCfg.SlowCometDuration > 0 { 73 | slowCometDuration = srvCfg.SlowCometDuration 74 | } 75 | gwHostsUri = "/gw/" + logic.ApiVersion() + "/hosts" 76 | var tlsConfig *tls.Config 77 | addr, err := erpc.NewFakeAddr2("tcp", srvCfg.ListenAddress) 78 | if err != nil { 79 | erpc.Fatalf("%v", err) 80 | } 81 | lis, err := erpc.NewInheritedListener(addr, tlsConfig) 82 | if err != nil { 83 | erpc.Fatalf("%v", err) 84 | } 85 | 86 | allowCross = srvCfg.AllowCross 87 | 88 | var network = "http" 89 | if tlsConfig != nil { 90 | network = "https" 91 | } 92 | erpc.Printf("register HTTP handler: %s", gwHostsUri) 93 | erpc.Printf("listen ok (network:%s, addr:%s)", network, lis.Addr()) 94 | 95 | err = (&fasthttp.Server{ 96 | Name: fmt.Sprintf("micro-gateway-%s", logic.ApiVersion()), 97 | Handler: handler, 98 | }).Serve(lis) 99 | 100 | if err != nil && err != http.ErrServerClosed { 101 | erpc.Fatalf("%v", err) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /helper/mod-html/README.md: -------------------------------------------------------------------------------- 1 | ## html 2 | 3 | HTML render for http client. 4 | 5 | ### Usage 6 | 7 | `html "github.com/xiaoenai/tp-micro/v6/helper/mod-html"` 8 | 9 | #### Test 10 | 11 | ```go 12 | package html_test 13 | 14 | import ( 15 | "testing" 16 | "time" 17 | 18 | html "github.com/xiaoenai/tp-micro/v6/helper/mod-html" 19 | ) 20 | 21 | type meta struct{} 22 | 23 | func (m *meta) SetMeta(key, value string) {} 24 | 25 | func TestParseText(t *testing.T) { 26 | const ( 27 | doc1 = ` 28 | 29 | 30 | html test 1 31 | 32 | 33 |
34 |

{{.}}

35 | 36 | ` 37 | doc2 = ` 38 | 39 | 40 | html test 2 41 | 42 | 43 |
44 |

{{.}}

45 | 46 | ` 47 | ) 48 | 49 | err := html.Parse("a", doc1) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | err = html.Parse("b", doc2) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | b, rerr := html.Render(new(meta), "a", "TestParse A!") 59 | t.Logf("body: %s, rerr: %v", b, rerr) 60 | b, rerr = html.Render(new(meta), "b", "TestParse B!") 61 | t.Logf("body: %s, rerr: %v", b, rerr) 62 | } 63 | 64 | func TestParseFiles(t *testing.T) { 65 | err := html.ParseFiles("../mod-html/a_test.tpl", "../mod-html/b_test.tpl") 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | 70 | b, rerr := html.Render(new(meta), "a_test.tpl", "TestParseFiles A!") 71 | t.Logf("body: %s, rerr: %v", b, rerr) 72 | b, rerr = html.Render(new(meta), "b_test.tpl", "TestParseFiles B!") 73 | t.Logf("body: %s, rerr: %v", b, rerr) 74 | } 75 | 76 | func TestParseGlob(t *testing.T) { 77 | err := html.ParseGlob("../mod-html/*.tpl") 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | 82 | b, rerr := html.Render(new(meta), "a_test.tpl", "TestParseGlob A!") 83 | t.Logf("body: %s, rerr: %v", b, rerr) 84 | b, rerr = html.Render(new(meta), "b_test.tpl", "TestParseGlob B!") 85 | t.Logf("body: %s, rerr: %v", b, rerr) 86 | } 87 | 88 | func TestDelims(t *testing.T) { 89 | const ( 90 | doc = ` 91 | 92 | 93 | html test 1 94 | 95 | 96 |
97 |

{{{.}}}

98 | 99 | ` 100 | ) 101 | 102 | html.Delims("{{{", "}}}") 103 | err := html.Parse("doc", doc) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | b, rerr := html.Render(new(meta), "doc", "TestDelims!") 108 | t.Logf("body: %s, rerr: %v", b, rerr) 109 | } 110 | 111 | func TestGoTimingRefresh(t *testing.T) { 112 | err := html.ParseFiles("../mod-html/a_test.tpl") 113 | if err != nil { 114 | t.Fatal(err) 115 | } 116 | err = html.ParseGlob("../mod-html/*.tpl") 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | 121 | b, rerr := html.Render(new(meta), "a_test.tpl", "TestGoTimingRefresh 1!") 122 | t.Logf("test1: body: %s, rerr: %v", b, rerr) 123 | 124 | html.GoTimingRefresh(time.Second * 5) 125 | time.Sleep(time.Second * 15) 126 | 127 | b, rerr = html.Render(new(meta), "a_test.tpl", "TestGoTimingRefresh 2!") 128 | t.Logf("test2: body: %s, rerr: %v", b, rerr) 129 | } 130 | ``` 131 | 132 | test command: 133 | 134 | ```sh 135 | go test -v -run=TestParseText 136 | go test -v -run=TestParseFiles 137 | go test -v -run=TestParseGlob 138 | go test -v -run=TestDelims 139 | go test -v -run=TestGoTimingRefresh 140 | ``` 141 | -------------------------------------------------------------------------------- /gateway/logic/socket/api.go: -------------------------------------------------------------------------------- 1 | package socket 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | _ "unsafe" 7 | 8 | "github.com/henrylee2cn/erpc/v6" 9 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 10 | "github.com/xiaoenai/tp-micro/v6/gateway/logic/hosts" 11 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 12 | ) 13 | 14 | // gw long connection controller. 15 | type gw struct { 16 | erpc.CallCtx 17 | } 18 | 19 | // Hosts returns the gateway seriver hosts. 20 | func (g *gw) Hosts(*struct{}) (*types.GwHosts, *erpc.Status) { 21 | return hosts.GwHosts(), nil 22 | } 23 | 24 | // totalConn returns the long connections total. 25 | //go:linkname totalConn github.com/xiaoenai/tp-micro/gateway.TotalConn 26 | //go:nosplit 27 | func totalConn() int32 { 28 | if outerPeer == nil { 29 | return 0 30 | } 31 | return int32(outerPeer.CountSession()) 32 | } 33 | 34 | // SocketTotal returns the long connections total. 35 | func (g *gw) SocketTotal(*types.SocketTotalArgs) (*types.SocketTotalReply, *erpc.Status) { 36 | return &types.SocketTotalReply{ConnTotal: totalConn()}, nil 37 | } 38 | 39 | // innerPush pushes the message to the specified user. 40 | func innerPush(uid string, uri string, args interface{}, bodyCodec byte) *erpc.Status { 41 | sess, stat := logic.SocketHooks().GetSession(outerPeer, uid) 42 | if stat != nil { 43 | return stat 44 | } 45 | return sess.Push(uri, args, erpc.WithBodyCodec(bodyCodec)) 46 | } 47 | 48 | var socketPushReply = new(types.SocketPushReply) 49 | 50 | // SocketPush pushes message to the specified user. 51 | func (g *gw) SocketPush(args *types.SocketPushArgs) (*types.SocketPushReply, *erpc.Status) { 52 | stat := innerPush(args.SessionId, args.Uri, args.Body, byte(args.BodyCodec)) 53 | if stat != nil { 54 | return nil, stat 55 | } 56 | return socketPushReply, nil 57 | } 58 | 59 | // SocketMpush multi-push messages to the specified users. 60 | func (g *gw) SocketMpush(args *types.SocketMpushArgs) (*types.SocketMpushReply, *erpc.Status) { 61 | var ( 62 | wg sync.WaitGroup 63 | sep = "?" 64 | failureSessionIds = make([]string, 0, len(args.Target)) 65 | lock sync.Mutex 66 | body = args.Body 67 | bodyCodec = byte(args.BodyCodec) 68 | ) 69 | if strings.Contains(args.Uri, "?") { 70 | sep = "&" 71 | } 72 | wg.Add(len(args.Target)) 73 | for _, t := range args.Target { 74 | var uri string 75 | if t.AdditionalQuery != "" { 76 | uri = args.Uri + sep + t.AdditionalQuery 77 | } else { 78 | uri = args.Uri 79 | } 80 | sessId := t.SessionId 81 | erpc.TryGo(func() { 82 | defer wg.Done() 83 | stat := innerPush(sessId, uri, body, bodyCodec) 84 | if stat != nil { 85 | lock.Lock() 86 | failureSessionIds = append(failureSessionIds, sessId) 87 | lock.Unlock() 88 | erpc.Tracef("SocketMpush: %s", stat.String()) 89 | } 90 | }) 91 | } 92 | wg.Wait() 93 | return &types.SocketMpushReply{ 94 | FailureSessionIds: failureSessionIds, 95 | }, nil 96 | } 97 | 98 | // Kick kicks the uid offline. 99 | func Kick(uid string) (existed bool, err error) { 100 | sess, existed := outerPeer.GetSession(uid) 101 | if existed { 102 | err = sess.Close() 103 | } 104 | return existed, err 105 | } 106 | 107 | // SocketKick kicks the uid offline. 108 | func (g *gw) SocketKick(args *types.SocketKickArgs) (*types.SocketKickReply, *erpc.Status) { 109 | existed, _ := Kick(args.SessionId) 110 | return &types.SocketKickReply{ 111 | Existed: existed, 112 | }, nil 113 | } 114 | -------------------------------------------------------------------------------- /gateway/gateway.go: -------------------------------------------------------------------------------- 1 | // Package gateway is the main program for TCP and HTTP services. 2 | // 3 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | package gateway 18 | 19 | import ( 20 | _ "unsafe" 21 | 22 | "github.com/henrylee2cn/erpc/v6" 23 | // "github.com/henrylee2cn/erpc/v6/proto/httproto" 24 | "github.com/henrylee2cn/erpc/v6/mixer/websocket/jsonSubProto" 25 | "github.com/henrylee2cn/erpc/v6/proto/rawproto" 26 | "github.com/xiaoenai/tp-micro/v6/clientele" 27 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 28 | "github.com/xiaoenai/tp-micro/v6/gateway/logic/hosts" 29 | short "github.com/xiaoenai/tp-micro/v6/gateway/logic/http" 30 | long "github.com/xiaoenai/tp-micro/v6/gateway/logic/socket" 31 | ws "github.com/xiaoenai/tp-micro/v6/gateway/logic/websocket" 32 | "github.com/xiaoenai/tp-micro/v6/gateway/sdk" 33 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 34 | ) 35 | 36 | // Run the gateway main program. 37 | // If protoFunc=nil, rawproto.NewRawProtoFunc is used by default. 38 | // If biz=nil, types.DefaultBusiness() is used by default. 39 | func Run(cfg Config, biz *types.Business, protoFunc, wsProtoFunc erpc.ProtoFunc) error { 40 | // config 41 | err := cfg.check() 42 | if err != nil { 43 | return err 44 | } 45 | 46 | // protocol 47 | if protoFunc == nil { 48 | protoFunc = rawproto.NewRawProtoFunc() 49 | } 50 | 51 | // client 52 | clientele.SetProtoFunc(protoFunc) 53 | 54 | // business 55 | if biz == nil { 56 | biz = types.DefaultBusiness() 57 | } 58 | logic.SetBusiness(biz) 59 | 60 | // sdk version 61 | sdk.SetApiVersion(logic.ApiVersion()) 62 | 63 | var ( 64 | httpAddr string 65 | outerSocketAddr string 66 | innerSocketAddr string 67 | webSocketAddr string 68 | ) 69 | 70 | // HTTP server 71 | if cfg.EnableHttp { 72 | httpAddr = cfg.OuterHttpServer.OuterIpPort() 73 | go short.Serve(cfg.OuterHttpServer) 74 | } 75 | 76 | // TCP socket server 77 | if cfg.EnableSocket { 78 | outerSocketAddr = cfg.OuterSocketServer.OuterIpPort() 79 | innerSocketAddr = cfg.InnerSocketServer.InnerIpPort() 80 | go long.Serve( 81 | cfg.OuterSocketServer, 82 | cfg.InnerSocketServer, 83 | protoFunc, 84 | ) 85 | } 86 | 87 | // web socket proto func 88 | if wsProtoFunc == nil { 89 | wsProtoFunc = jsonSubProto.NewJSONSubProtoFunc() 90 | } 91 | // web socket server 92 | if cfg.EnableWebSocket { 93 | webSocketAddr = cfg.WebSocketServer.OuterIpPort() 94 | go ws.Serve( 95 | cfg.WebSocketServer, 96 | wsProtoFunc, 97 | ) 98 | } 99 | 100 | hosts.Start( 101 | httpAddr, 102 | outerSocketAddr, 103 | innerSocketAddr, 104 | webSocketAddr, 105 | ) 106 | 107 | select {} 108 | } 109 | 110 | // RegBodyCodecForHTTP registers a mapping of content type to body coder (for http). 111 | func RegBodyCodecForHTTP(contentType string, codecId byte) { 112 | short.RegBodyCodec(contentType, codecId) 113 | } 114 | 115 | // TotalConn returns the current connections amount. 116 | func TotalConn() int32 117 | 118 | // HostsNamespace returns the gateway hosts prefix of ETCD key. 119 | func HostsNamespace() string { 120 | return hosts.HostsNamespace() 121 | } 122 | 123 | // SetHostsNamespace sets the gateway hosts prefix of ETCD key. 124 | func SetHostsNamespace(prefix string) { 125 | hosts.SetHostsNamespace(prefix) 126 | } 127 | -------------------------------------------------------------------------------- /gateway/types/conn_hooks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package types 16 | 17 | import ( 18 | "github.com/henrylee2cn/erpc/v6" 19 | "github.com/henrylee2cn/erpc/v6/plugin/auth" 20 | "github.com/valyala/fasthttp" 21 | micro "github.com/xiaoenai/tp-micro/v6" 22 | ) 23 | 24 | // SocketHooks TCP socket connecting event hooks 25 | type SocketHooks interface { 26 | // OnLogon is called when the client goes online. 27 | OnLogon(auth.Session, AccessToken) *erpc.Status 28 | // OnLogoff is called when the client goes offline. 29 | OnLogoff(erpc.BaseSession) *erpc.Status 30 | // GetSession returns session from peer by uid. 31 | GetSession(peer erpc.Peer, uid string) (erpc.Session, *erpc.Status) 32 | //PreWritePush is executed before writing PUSH packet. 33 | PreWritePush(erpc.WriteCtx) *erpc.Status 34 | } 35 | 36 | type ( 37 | // HttpHooks HTTP connecting event hooks 38 | HttpHooks interface { 39 | // OnRequest is called when the client requests. 40 | OnRequest(params RequestArgs, body []byte, authFunc AuthFunc) (AccessToken, []erpc.MessageSetting, *erpc.Status) 41 | } 42 | // RequestArgs http query parameters 43 | RequestArgs interface { 44 | // Query returns query arguments from request URI. 45 | QueryArgs() *fasthttp.Args 46 | // Header returns the header object of request. 47 | Header() *fasthttp.RequestHeader 48 | } 49 | ) 50 | 51 | // WebSocketHooks WEB socket connecting event hooks 52 | type WebSocketHooks interface { 53 | // OnLogon is called when the client goes online. 54 | OnLogon(auth.Session, AccessToken) *erpc.Status 55 | // OnLogoff is called when the client goes offline. 56 | OnLogoff(erpc.BaseSession) *erpc.Status 57 | // GetSession returns session from peer by uid. 58 | GetSession(peer erpc.Peer, uid string) (erpc.Session, *erpc.Status) 59 | //PreWritePush is executed before writing PUSH packet. 60 | PreWritePush(erpc.WriteCtx) *erpc.Status 61 | } 62 | 63 | // DefaultSocketHooks creates a new default SocketHooks object. 64 | func DefaultSocketHooks() SocketHooks { 65 | return new(defSocketHooks) 66 | } 67 | 68 | // DefaultWebSocketHooks creates a new default WebSocketHooks object. 69 | func DefaultWebSocketHooks() WebSocketHooks { 70 | return new(defSocketHooks) 71 | } 72 | 73 | type defSocketHooks struct{} 74 | 75 | func (d *defSocketHooks) OnLogon(sess auth.Session, accessToken AccessToken) *erpc.Status { 76 | sess.SetID(accessToken.SessionId()) 77 | return nil 78 | } 79 | 80 | func (d *defSocketHooks) OnLogoff(erpc.BaseSession) *erpc.Status { 81 | return nil 82 | } 83 | 84 | func (d *defSocketHooks) GetSession(peer erpc.Peer, uid string) (erpc.Session, *erpc.Status) { 85 | sess, ok := peer.GetSession(uid) 86 | if !ok { 87 | return nil, micro.RerrNotOnline 88 | } 89 | return sess, nil 90 | } 91 | 92 | func (d *defSocketHooks) PreWritePush(erpc.WriteCtx) *erpc.Status { 93 | return nil 94 | } 95 | 96 | // DefaultHttpHooks creates a new default HttpHooks object. 97 | func DefaultHttpHooks() HttpHooks { 98 | return new(defHttpHooks) 99 | } 100 | 101 | type defHttpHooks struct{} 102 | 103 | func (d *defHttpHooks) OnRequest(params RequestArgs, body []byte, authFunc AuthFunc) (AccessToken, []erpc.MessageSetting, *erpc.Status) { 104 | accessToken, stat := authFunc(string(params.QueryArgs().Peek("access_token"))) 105 | return accessToken, nil, stat 106 | } 107 | -------------------------------------------------------------------------------- /examples/custom/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/henrylee2cn/cfgo" 8 | "github.com/henrylee2cn/erpc/v6" 9 | "github.com/henrylee2cn/erpc/v6/codec" 10 | micro "github.com/xiaoenai/tp-micro/v6" 11 | "github.com/xiaoenai/tp-micro/v6/discovery" 12 | sagent "github.com/xiaoenai/tp-micro/v6/gateway/helper/agent/socket" 13 | gwSdk "github.com/xiaoenai/tp-micro/v6/gateway/sdk" 14 | gwTypes "github.com/xiaoenai/tp-micro/v6/gateway/types" 15 | "github.com/xiaoenai/tp-micro/v6/helper" 16 | html "github.com/xiaoenai/tp-micro/v6/helper/mod-html" 17 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 18 | "github.com/xiaoenai/tp-micro/v6/model/redis" 19 | ) 20 | 21 | func init() { 22 | // html.ParseGlob("*.tpl") 23 | 24 | html.Parse("home", ` 25 | 26 | 27 | Home 28 | 29 | 30 |
31 |

{{.}}

32 | 33 | `) 34 | } 35 | 36 | // Home HTML home page 37 | func Home(ctx erpc.CallCtx, args *struct{}) ([]byte, *erpc.Status) { 38 | return html.Render(ctx, "home", "Home Page Test!") 39 | } 40 | 41 | // Home2 HTML home page 42 | func Home2(ctx erpc.CallCtx, args *struct{}) ([]byte, *erpc.Status) { 43 | return nil, helper.Redirect(ctx, 302, "http://localhost:5000/home") 44 | } 45 | 46 | // Args args 47 | type Args struct { 48 | A int 49 | B int `param:""` 50 | } 51 | 52 | // Math handler 53 | type Math struct { 54 | erpc.CallCtx 55 | } 56 | 57 | // Divide divide API 58 | func (m *Math) Divide(args *Args) (int, *erpc.Status) { 59 | return args.A / args.B, nil 60 | } 61 | 62 | type config struct { 63 | Srv micro.SrvConfig 64 | Cli micro.CliConfig 65 | Etcd etcd.EasyConfig 66 | Redis redis.Config 67 | } 68 | 69 | func (c *config) Reload(bind cfgo.BindFunc) error { 70 | err := bind() 71 | return err 72 | } 73 | 74 | func main() { 75 | cfg := config{ 76 | Srv: micro.SrvConfig{ 77 | ListenAddress: ":0", 78 | EnableHeartbeat: true, 79 | PrintDetail: true, 80 | }, 81 | Cli: micro.CliConfig{}, 82 | Etcd: etcd.EasyConfig{ 83 | Endpoints: []string{"http://127.0.0.1:2379"}, 84 | }, 85 | Redis: *redis.NewConfig(), 86 | } 87 | cfgo.AllowAppsShare(true) 88 | cfgo.MustReg("server", &cfg) 89 | 90 | redisClient, err := redis.NewClient(&cfg.Redis) 91 | sagent.Init(redisClient, redisClient) 92 | if err != nil { 93 | erpc.Fatalf("%v", err) 94 | } 95 | 96 | erpc.Go(func() { 97 | agentNewsChan := sagent.Subscribe() 98 | for news := range agentNewsChan { 99 | erpc.Infof("agent news: sessionId:%s, event:%s", 100 | news.SessionId, news.Event, 101 | ) 102 | } 103 | }) 104 | 105 | p := discovery.ServicePlugin( 106 | cfg.Srv.InnerIpPort(), 107 | cfg.Etcd, 108 | ) 109 | srv := micro.NewServer(cfg.Srv, p) 110 | srv.RouteCallFunc(Home) 111 | srv.RouteCallFunc(Home2) 112 | srv.RouteCall(new(Math)) 113 | go srv.ListenAndServe() 114 | 115 | // test pushing, when the client progress is existed. 116 | gwSdk.Init("v1", nil) 117 | for i := 0; ; i++ { 118 | erpc.Infof("test pushing msg after 10s") 119 | time.Sleep(time.Second * 10) 120 | push([]string{"client-auth-info-12345"}, &Args{A: i, B: i}) 121 | } 122 | select {} 123 | } 124 | 125 | func push(uids []string, args *Args) { 126 | agts, stat := sagent.QueryAgent(uids) 127 | if stat != nil { 128 | erpc.Errorf("push fail: %v", stat) 129 | } 130 | for _, agt := range agts.Agents { 131 | if agt.IsOffline { 132 | continue 133 | } 134 | addr := agt.GetInnerGw() 135 | target := []*gwTypes.MpushTarget{ 136 | { 137 | SessionId: agt.GetSessionId(), 138 | AdditionalQuery: "testpush=ok", 139 | }, 140 | } 141 | targetUri := "/push" 142 | msgBytes, _ := json.Marshal(args) 143 | _, stat := gwSdk.SocketMpush( 144 | addr, 145 | &gwTypes.SocketMpushArgs{ 146 | Target: target, 147 | Uri: targetUri, 148 | Body: msgBytes, 149 | BodyCodec: codec.ID_JSON, 150 | }, 151 | erpc.WithBodyCodec(codec.ID_JSON), 152 | ) 153 | if stat != nil { 154 | erpc.Errorf("push fail: %v", stat) 155 | } else { 156 | erpc.Infof("push ok") 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /gateway/logic/socket/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package socket 16 | 17 | import ( 18 | "net" 19 | 20 | "github.com/henrylee2cn/erpc/v6" 21 | "github.com/henrylee2cn/erpc/v6/plugin/auth" 22 | "github.com/henrylee2cn/erpc/v6/plugin/proxy" 23 | micro "github.com/xiaoenai/tp-micro/v6" 24 | "github.com/xiaoenai/tp-micro/v6/clientele" 25 | "github.com/xiaoenai/tp-micro/v6/discovery" 26 | "github.com/xiaoenai/tp-micro/v6/gateway/logic" 27 | websocket "github.com/xiaoenai/tp-micro/v6/gateway/logic/websocket" 28 | ) 29 | 30 | var ( 31 | outerPeer erpc.Peer 32 | outerServer *micro.Server 33 | clientAuthInfo string 34 | ) 35 | 36 | // OuterServeConn serves connetion. 37 | func OuterServeConn(conn net.Conn) { 38 | sess, err := outerServer.ServeConn(conn) 39 | if err != nil { 40 | erpc.Errorf("Serve net.Conn error: %v", err) 41 | } 42 | <-sess.CloseNotify() 43 | } 44 | 45 | // Serve starts TCP gateway service. 46 | func Serve(outerSrvCfg, innerSrvCfg micro.SrvConfig, protoFunc erpc.ProtoFunc) { 47 | outerServer = micro.NewServer( 48 | outerSrvCfg, 49 | authChecker, 50 | socketConnTabPlugin, 51 | proxy.NewPlugin(logic.ProxySelector), 52 | preWritePushPlugin(), 53 | ) 54 | 55 | outerPeer = outerServer.Peer() 56 | 57 | innerPlugins := logic.InnerServerPlugins() 58 | discoveryService := discovery.ServicePluginFromEtcd( 59 | innerSrvCfg.InnerIpPort(), 60 | clientele.GetEtcdClient(), 61 | ) 62 | innerPlugins = append(innerPlugins, discoveryService) 63 | innerServer := micro.NewServer( 64 | innerSrvCfg, 65 | innerPlugins..., 66 | ) 67 | 68 | gwGroup := innerServer.SubRoute("/gw") 69 | { 70 | verGroup := gwGroup.SubRoute(logic.ApiVersion()) 71 | { 72 | verGroup.RouteCallFunc((*gw).Hosts) 73 | // socket api 74 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*gw).SocketTotal)) 75 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*gw).SocketPush)) 76 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*gw).SocketMpush)) 77 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*gw).SocketKick)) 78 | // ws api 79 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*websocket.Gw).WsTotal)) 80 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*websocket.Gw).WsPush)) 81 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*websocket.Gw).WsMpush)) 82 | discoveryService.ExcludeApi(verGroup.RouteCallFunc((*websocket.Gw).WsKick)) 83 | } 84 | } 85 | 86 | go outerServer.ListenAndServe(protoFunc) 87 | go innerServer.ListenAndServe(protoFunc) 88 | 89 | select {} 90 | } 91 | 92 | // auth plugin 93 | var authChecker = auth.NewCheckerPlugin( 94 | func(sess auth.Session, fn auth.RecvOnce) (ret interface{}, stat *erpc.Status) { 95 | var authInfo string 96 | stat = fn(&authInfo) 97 | if !stat.OK() { 98 | return 99 | } 100 | erpc.Tracef("auth info: %v", authInfo) 101 | stat = socketConnTabPlugin.authAndLogon(authInfo, sess) 102 | if !stat.OK() { 103 | return 104 | } 105 | return "", nil 106 | }, 107 | erpc.WithBodyCodec('s'), 108 | ) 109 | 110 | // preWritePushPlugin returns PreWritePushPlugin. 111 | func preWritePushPlugin() erpc.Plugin { 112 | return &perPusher{fn: logic.SocketHooks().PreWritePush} 113 | } 114 | 115 | type perPusher struct { 116 | fn func(erpc.WriteCtx) *erpc.Status 117 | } 118 | 119 | func (p *perPusher) Name() string { 120 | return "PUSH-LOGIC" 121 | } 122 | 123 | var ( 124 | _ erpc.PreWritePushPlugin = (*perPusher)(nil) 125 | ) 126 | 127 | func (p *perPusher) PreWritePush(ctx erpc.WriteCtx) *erpc.Status { 128 | return p.fn(ctx) 129 | } 130 | -------------------------------------------------------------------------------- /gateway/sdk/rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 github.com/xiaoenai. All Rights Reserved. 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 | package sdk 16 | 17 | import ( 18 | "path" 19 | "strings" 20 | 21 | "github.com/henrylee2cn/erpc/v6" 22 | "github.com/xiaoenai/tp-micro/v6/clientele" 23 | "github.com/xiaoenai/tp-micro/v6/gateway/types" 24 | ) 25 | 26 | // Init initializes a common inner ant client. 27 | func Init(apiVersion string, protoFunc erpc.ProtoFunc) { 28 | clientele.SetProtoFunc(protoFunc) 29 | SetApiVersion(apiVersion) 30 | } 31 | 32 | var _apiVersion string 33 | 34 | // SetApiVersion sets API version 35 | func SetApiVersion(apiVersion string) { 36 | _apiVersion = strings.TrimRight(path.Join("/", apiVersion), "/") 37 | } 38 | 39 | // GwHosts returns the gateway host list. 40 | func GwHosts(setting ...erpc.MessageSetting) (*types.GwHosts, *erpc.Status) { 41 | var reply = new(types.GwHosts) 42 | stat := clientele.DynamicCall(nil, "/gw"+_apiVersion+"/hosts", nil, reply, setting...).Status() 43 | if stat != nil { 44 | return nil, stat 45 | } 46 | return reply, nil 47 | } 48 | 49 | // SocketTotal returns the long connections total of the remote server. 50 | func SocketTotal(srvAddr string, setting ...erpc.MessageSetting) (*types.SocketTotalReply, *erpc.Status) { 51 | var reply = new(types.SocketTotalReply) 52 | stat := clientele.StaticCall(nil, srvAddr, "/gw"+_apiVersion+"/socket_total", nil, reply, setting...).Status() 53 | if stat != nil { 54 | return nil, stat 55 | } 56 | return reply, nil 57 | } 58 | 59 | // SocketPush pushes message to the specified user. 60 | func SocketPush(srvAddr string, args *types.SocketPushArgs, setting ...erpc.MessageSetting) (*types.SocketPushReply, *erpc.Status) { 61 | var reply = new(types.SocketPushReply) 62 | stat := clientele.StaticCall(nil, srvAddr, "/gw"+_apiVersion+"/socket_push", args, reply, setting...).Status() 63 | if stat != nil { 64 | return nil, stat 65 | } 66 | return reply, nil 67 | } 68 | 69 | // SocketMpush multi-push messages to the specified users. 70 | func SocketMpush(srvAddr string, args *types.SocketMpushArgs, setting ...erpc.MessageSetting) (*types.SocketMpushReply, *erpc.Status) { 71 | var reply = new(types.SocketMpushReply) 72 | stat := clientele.StaticCall(nil, srvAddr, "/gw"+_apiVersion+"/socket_mpush", args, reply, setting...).Status() 73 | if stat != nil { 74 | return nil, stat 75 | } 76 | return reply, nil 77 | } 78 | 79 | // WsTotal returns the long connections total of the remote server. 80 | func WsTotal(srvAddr string, setting ...erpc.MessageSetting) (*types.WsTotalReply, *erpc.Status) { 81 | var reply = new(types.WsTotalReply) 82 | stat := clientele.StaticCall(nil, srvAddr, "/gw"+_apiVersion+"/ws_total", nil, reply, setting...).Status() 83 | if stat != nil { 84 | return nil, stat 85 | } 86 | return reply, nil 87 | } 88 | 89 | // WsPush pushes message to the specified user. 90 | func WsPush(srvAddr string, args *types.SocketPushArgs, setting ...erpc.MessageSetting) (*types.WsPushReply, *erpc.Status) { 91 | var reply = new(types.WsPushReply) 92 | stat := clientele.StaticCall(nil, srvAddr, "/gw"+_apiVersion+"/ws_push", args, reply, setting...).Status() 93 | if stat != nil { 94 | return nil, stat 95 | } 96 | return reply, nil 97 | } 98 | 99 | // WsMpush multi-push messages to the specified users. 100 | func WsMpush(srvAddr string, args *types.WsMpushArgs, setting ...erpc.MessageSetting) (*types.WsMpushReply, *erpc.Status) { 101 | var reply = new(types.WsMpushReply) 102 | stat := clientele.StaticCall(nil, srvAddr, "/gw"+_apiVersion+"/ws_mpush", args, reply, setting...).Status() 103 | if stat != nil { 104 | return nil, stat 105 | } 106 | return reply, nil 107 | } 108 | -------------------------------------------------------------------------------- /configer/node.go: -------------------------------------------------------------------------------- 1 | package configer 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "sync" 9 | 10 | "github.com/henrylee2cn/erpc/v6" 11 | "github.com/xiaoenai/tp-micro/v6/model/etcd" 12 | ) 13 | 14 | // InitNode initializes the config node. 15 | func InitNode(etcdClient *etcd.Client) { 16 | globalNodes = &Nodes{ 17 | nodeMap: make(map[string]*Node), 18 | } 19 | 20 | var err error 21 | globalNodes.etcdSession, err = etcd.NewSession(etcdClient) 22 | if err != nil { 23 | erpc.Fatalf("Initialization of the global node failed: %s", err.Error()) 24 | } 25 | } 26 | 27 | // SyncNode registers a configuration template to etcd, 28 | // and update it when monitoring the configuration changes. 29 | func SyncNode(service, version string, cfg Config) { 30 | globalNodes.mustAdd(service, version, cfg) 31 | } 32 | 33 | // Nodes config node handlers 34 | type Nodes struct { 35 | nodeMap map[string]*Node 36 | etcdSession *etcd.Session 37 | rwMutex sync.RWMutex 38 | } 39 | 40 | var globalNodes *Nodes 41 | 42 | func (n *Nodes) mustAdd(service, version string, cfg Config) { 43 | must(n.add(service, version, cfg)) 44 | } 45 | 46 | func (n *Nodes) add(service, version string, cfg Config) (err error) { 47 | n.rwMutex.Lock() 48 | defer n.rwMutex.Unlock() 49 | 50 | key := NewKey(service, version) 51 | if _, ok := n.nodeMap[key]; ok { 52 | return fmt.Errorf("Repeat the registration configuration: %s", key) 53 | } 54 | cfgBytes, _ := cfg.MarshalJSON() 55 | node := &Node{ 56 | key: key, 57 | object: cfg, 58 | etcdMutex: etcd.NewLocker(n.etcdSession, key), 59 | Initialized: false, 60 | Config: string(cfgBytes), 61 | doInitCh: make(chan error, 1), 62 | nodes: n, 63 | } 64 | n.nodeMap[key] = node 65 | 66 | defer func() { 67 | if p := recover(); p != nil { 68 | err = fmt.Errorf("etcd concurrency lock fail: %v", p) 69 | } 70 | }() 71 | 72 | node.etcdMutex.Lock() 73 | defer node.etcdMutex.Unlock() 74 | 75 | resp, err := n.etcdSession.Client().Get(context.TODO(), key) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | if len(resp.Kvs) > 0 { 81 | err = node.bind(resp.Kvs[0].Value) 82 | if node.Initialized { 83 | go node.watch(n.etcdSession.Client()) 84 | return err 85 | } 86 | 87 | } else { 88 | _, err = n.etcdSession.Client().Put(context.TODO(), key, node.String()) 89 | if err != nil { 90 | return err 91 | } 92 | } 93 | 94 | erpc.Warnf("Wait for the configuration in the ETCD to be set: %s", key) 95 | go node.watch(n.etcdSession.Client()) 96 | return node.waitInit() 97 | } 98 | 99 | // Node config node handler 100 | type Node struct { 101 | key string 102 | object Config 103 | // Config string 104 | Config string `json:"config"` 105 | // Is it initialized? 106 | Initialized bool `json:"initialized"` 107 | doInitCh chan error 108 | etcdMutex sync.Locker 109 | nodes *Nodes 110 | } 111 | 112 | func (n *Nodes) archive() { 113 | os.Mkdir("./config", 0755) 114 | r, err := os.OpenFile("./config/archive", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 115 | if err != nil { 116 | erpc.Warnf("Archive config error: %v", err) 117 | return 118 | } 119 | b, _ := json.Marshal(n.nodeMap) 120 | r.Write(b) 121 | r.Close() 122 | } 123 | 124 | func (n *Node) bind(data []byte) error { 125 | inited := n.Initialized 126 | err := json.Unmarshal(data, n) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | n.nodes.archive() 132 | 133 | if inited { 134 | err = n.object.Reload([]byte(n.Config)) 135 | } else { 136 | err = n.object.UnmarshalJSON([]byte(n.Config)) 137 | if n.Initialized { 138 | select { 139 | case n.doInitCh <- err: 140 | default: 141 | } 142 | } 143 | } 144 | 145 | return err 146 | } 147 | 148 | func (n *Node) waitInit() error { 149 | return <-n.doInitCh 150 | } 151 | 152 | // String returns the encoding string 153 | func (n *Node) String() string { 154 | b, _ := json.Marshal(n) 155 | return string(b) 156 | } 157 | 158 | func (n *Node) watch(etcdClient *etcd.Client) { 159 | watcher := etcdClient.Watch( 160 | context.TODO(), 161 | n.key, 162 | ) 163 | for wresp := range watcher { 164 | for _, event := range wresp.Events { 165 | if event.Type != etcd.EventTypePut { 166 | continue 167 | } 168 | err := n.bind(event.Kv.Value) 169 | if err != nil { 170 | erpc.Errorf("Binding configuration from etcd failed: %s", err) 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /gateway/helper/gray/logic/model/gray_match.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | 7 | "github.com/henrylee2cn/goutil/coarsetime" 8 | "github.com/henrylee2cn/erpc/v6" 9 | "github.com/xiaoenai/tp-micro/v6/model/mysql" 10 | "github.com/xiaoenai/tp-micro/v6/model/sqlx" 11 | ) 12 | 13 | // GrayMatch 14 | type GrayMatch struct { 15 | Uri string `protobuf:"bytes,1,opt,name=Uri,proto3" json:"uri"` 16 | Regexp string `protobuf:"bytes,2,opt,name=Regexp,proto3" json:"regexp"` 17 | CreatedAt int64 `protobuf:"varint,3,opt,name=CreatedAt,proto3" json:"created_at"` 18 | UpdatedAt int64 `protobuf:"varint,4,opt,name=UpdatedAt,proto3" json:"updated_at"` 19 | } 20 | 21 | // TableName implements 'github.com/xiaoenai/tp-micro/model'.Cacheable 22 | func (*GrayMatch) TableName() string { 23 | return "gray_match" 24 | } 25 | 26 | var grayMatchDB, _ = dbHandler.RegCacheableDB( 27 | new(GrayMatch), 28 | time.Hour*24, 29 | `CREATE TABLE IF NOT EXISTS gray_match ( 30 | uri VARCHAR(190) COMMENT 'URI', 31 | `+"`regexp`"+` LONGTEXT COMMENT 'regular expression to match UID', 32 | updated_at INT(11) NOT NULL COMMENT 'updated time', 33 | created_at INT(11) NOT NULL COMMENT 'created time', 34 | PRIMARY KEY(uri) 35 | )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='gray rule'; 36 | `) 37 | 38 | // GetGrayMatchDB returns the GrayMatch DB handler. 39 | func GetGrayMatchDB() *mysql.CacheableDB { 40 | return grayMatchDB 41 | } 42 | 43 | // UpsertGrayMatch insert a GrayMatch data into database. 44 | func UpsertGrayMatch(_g *GrayMatch, tx ...*sqlx.Tx) error { 45 | _g.UpdatedAt = coarsetime.FloorTimeNow().Unix() 46 | if _g.CreatedAt == 0 { 47 | _g.CreatedAt = _g.UpdatedAt 48 | } 49 | return grayMatchDB.TransactCallback(func(tx *sqlx.Tx) error { 50 | query := "INSERT INTO `gray_match` (`uri`,`regexp`,`updated_at`,`created_at`)VALUES(:uri,:regexp,:updated_at,:created_at)\n" + 51 | "ON DUPLICATE KEY UPDATE `regexp`=VALUES(`regexp`),`updated_at`=VALUES(`updated_at`);" 52 | _, err := tx.NamedExec(query, _g) 53 | if err != nil { 54 | return err 55 | } 56 | return grayMatchDB.PutCache(_g) 57 | }, tx...) 58 | } 59 | 60 | // UpdateGrayMatchByUri update the GrayMatch data in database by id. 61 | func UpdateGrayMatchByUri(_g *GrayMatch, tx ...*sqlx.Tx) error { 62 | return grayMatchDB.TransactCallback(func(tx *sqlx.Tx) error { 63 | _g.UpdatedAt = coarsetime.FloorTimeNow().Unix() 64 | _, err := tx.NamedExec("UPDATE `gray_match` SET `uri`=:uri,`regexp`=:regexp,`created_at`=:created_at,`updated_at`=:updated_at WHERE uri=:uri LIMIT 1;", _g) 65 | if err != nil { 66 | return err 67 | } 68 | return grayMatchDB.PutCache(_g) 69 | }, tx...) 70 | } 71 | 72 | // DeleteGrayMatchByUri delete a GrayMatch data in database by id. 73 | func DeleteGrayMatchByUri(uri string, tx ...*sqlx.Tx) error { 74 | return grayMatchDB.TransactCallback(func(tx *sqlx.Tx) error { 75 | _, err := tx.Exec("DELETE FROM `gray_match` WHERE uri=?;", uri) 76 | if err != nil { 77 | return err 78 | } 79 | return grayMatchDB.PutCache(&GrayMatch{ 80 | Uri: uri, 81 | }) 82 | }, tx...) 83 | } 84 | 85 | // GetGrayMatchByUri query a GrayMatch data from database by id. 86 | // If @reply bool=false error=nil, means the data is not exist. 87 | func GetGrayMatchByUri(uri string) (*GrayMatch, bool, error) { 88 | var _g = &GrayMatch{ 89 | Uri: uri, 90 | } 91 | err := grayMatchDB.CacheGet(_g) 92 | switch err { 93 | case nil: 94 | if _g.CreatedAt == 0 { 95 | return nil, false, nil 96 | } 97 | return _g, true, nil 98 | case sql.ErrNoRows: 99 | err2 := grayMatchDB.PutCache(_g) 100 | if err2 != nil { 101 | erpc.Errorf("%s", err2.Error()) 102 | } 103 | return nil, false, nil 104 | default: 105 | return nil, false, err 106 | } 107 | } 108 | 109 | // GetGrayMatchByWhere query a GrayMatch data from database by WHERE condition. 110 | // If @reply bool=false error=nil, means the data is not exist. 111 | func GetGrayMatchByWhere(whereCond string, args ...interface{}) (*GrayMatch, bool, error) { 112 | var _g = new(GrayMatch) 113 | err := grayMatchDB.Get(_g, "SELECT `uri`,`regexp`,`created_at`,`updated_at` FROM `gray_match` WHERE "+whereCond+" LIMIT 1;", args...) 114 | switch err { 115 | case nil: 116 | return _g, true, nil 117 | case sql.ErrNoRows: 118 | return nil, false, nil 119 | default: 120 | return nil, false, err 121 | } 122 | } 123 | 124 | // SelectGrayMatchByWhere query some GrayMatch data from database by WHERE condition. 125 | func SelectGrayMatchByWhere(whereCond string, args ...interface{}) ([]*GrayMatch, error) { 126 | var objs = new([]*GrayMatch) 127 | err := grayMatchDB.Select(objs, "SELECT `uri`,`regexp`,`created_at`,`updated_at` FROM `gray_match` WHERE "+whereCond, args...) 128 | return *objs, err 129 | } 130 | 131 | // CountGrayMatchByWhere count GrayMatch data number from database by WHERE condition. 132 | func CountGrayMatchByWhere(whereCond string, args ...interface{}) (int64, error) { 133 | var count int64 134 | err := grayMatchDB.Get(&count, "SELECT count(1) FROM `gray_match` WHERE "+whereCond, args...) 135 | return count, err 136 | } 137 | --------------------------------------------------------------------------------