├── 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 [](http://godoc.org/github.com/fatih/structtag) [](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 |
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 |
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 |
--------------------------------------------------------------------------------