├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── app ├── interface │ ├── README │ └── web │ │ ├── Makefile │ │ ├── api │ │ └── client.go │ │ ├── cmd │ │ ├── config.yaml │ │ ├── main.go │ │ └── plugin.go │ │ ├── config │ │ └── config.go │ │ ├── controllers │ │ ├── jobs.go │ │ ├── scheduler_logs.go │ │ ├── schedulers.go │ │ ├── users.go │ │ ├── worker_groups.go │ │ └── workers.go │ │ ├── models │ │ └── user.go │ │ ├── router │ │ └── init.go │ │ ├── server │ │ └── init.go │ │ └── service │ │ ├── init.go │ │ └── users │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ └── register.go └── service │ ├── README │ └── web │ ├── README.md │ ├── cmd │ ├── config.yaml │ ├── logs │ │ ├── debug.log.2020-04-19 │ │ └── info.log.2020-04-19 │ ├── main.go │ └── plugin.go │ ├── config │ └── config.go │ ├── model │ ├── init.go │ └── tb_user.go │ ├── proto │ └── v1 │ │ ├── api.micro.go │ │ ├── api.pb.go │ │ └── api.proto │ ├── run.sh │ ├── server │ └── init.go │ └── service │ └── user.go ├── common ├── auth │ └── auth.go ├── configs │ └── config.go ├── errno │ ├── code.go │ ├── common_code.go │ ├── error.go │ └── srv_code.go ├── http │ ├── handle │ │ └── result.go │ └── middleware │ │ ├── auth.go │ │ └── trace.go ├── logger │ └── logger.go ├── proto │ └── init.go ├── token │ └── token.go ├── tracer │ ├── init.go │ └── server.go ├── uuid │ └── uuid.go └── verify │ ├── verify.go │ └── verify_test.go ├── cores ├── balancer │ ├── balancer.go │ ├── consistent │ │ └── consistent.go │ ├── defind.go │ ├── errors.go │ ├── hash │ │ ├── hash.go │ │ └── hash_test.go │ ├── options.go │ ├── radom │ │ ├── radom.go │ │ └── radom_test.go │ ├── round │ │ ├── round.go │ │ └── round_test.go │ └── roundRobin │ │ └── roundRobin.go └── discovery │ ├── README.md │ ├── cache │ ├── cache.go │ ├── cache_test.go │ └── options.go │ ├── defind.go │ ├── discovery.go │ ├── etcdv3 │ ├── client_test.go │ ├── etcd.go │ ├── server_test.go │ └── watcher.go │ ├── options.go │ ├── utils.go │ └── watcher.go ├── deployments └── docker-compose.yml ├── docs ├── object.md ├── 权限管理.md └── 组件.md ├── go.mod ├── go.sum └── scripts ├── README ├── build.sh └── dockerfile.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | deployments/bin -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | - go 3 | 4 | sudo: true 5 | 6 | go: 7 | - "1.13.x" 8 | 9 | env: 10 | - GO111MODULE=on 11 | 12 | before_install: 13 | - chmod +x ./scripts/build.sh 14 | - chmod +x ./scripts/dockerfile.sh 15 | 16 | script: 17 | - ./scripts/build.sh all 18 | - ./scripts/dockerfile.sh all 19 | - go test -v ./... -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright {yyyy} {name of copyright owner} 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cheetah 2 | 3 | [![Build Status](https://travis-ci.org/liangjfblue/cheetah.svg?branch=master)](https://travis-ci.org/github/liangjfblue/cheetah) 4 | 5 | ## 💬介绍 6 | 😎😎😎cheetah(猎豹)是一个微服务,分布式架构的任务调度中心 7 | 8 | ## ☁架构 9 | 10 | 11 | ## 🌟组件 12 | 13 | 14 | ## ⚙特性 15 | - 微服务架构。随心所欲的水平扩展,高可靠,高可用 16 | - 模块组件解耦,方便扩展。网关-web-调度器master-worker分层 17 | - 用户权限管理,接口权限管理 18 | - 支持定时任务,延时任务(redis实现) 19 | - 实时查看任务执行进度(websocket实现) 20 | - 支持多种类型的任务调度 21 | - 接口级别 22 | - 脚本启动级别 23 | - 代码级别(支持shell,golang,python) 24 | - 支持多种任务调度方式 25 | - 单任务调度 26 | - 多任务并行调度 27 | - 因果关系任务调度(任务A->任务B->任务C->任务D,中间有任务运行失败,整个任务调度失败) 28 | - 支持插件化调度算法 29 | - 内部提供**随机**、**轮训**、**Worker权重**、**Worker最少负载**调度算法 30 | - 重写提供的接口,可自定义调度算法 31 | - 支持多平台报警 32 | - 邮件 33 | - 企业微信 34 | - web callback url 35 | 36 | ## 👏技术栈 37 | - go-micro(微服务框架) 38 | - gin(http服务) 39 | - docker(容器化) 40 | - etcd(服务发现注册中心) 41 | - gorm(数据库orm) 42 | - mysql 43 | - redis 44 | - OpenTracing(分布式链路追踪) 45 | - casbin(权限管理) 46 | - Traefik(反向代理) 47 | 48 | 49 | ## 🧪使用 50 | ### 1、编译 51 | `./scripts/build.sh all` 52 | 53 | 54 | ### 2、生成Dockerfile 55 | `./scripts/dockerfile.sh all` 56 | 57 | ### 3、运行 58 | 创建`deployments/db/mysql_data` 目录 59 | 60 | #### 3.0 打包 61 | 进入deployments目录: `sudo docker-compose build` 62 | 63 | #### 3.1、运行 64 | 进入deployments目录: `sudo docker-compose up` 65 | 66 | #### 3.2、停止 67 | 进入deployments目录: `sudo docker-compose down` 68 | 69 | #### 3.3、水平扩展master,worker 70 | 进入deployments目录: `sudo docker-compose --scala srv_xxx=3` 71 | 72 | 73 | ## 🗨️TODO 74 | - k8s部署 75 | 76 | 77 | ## 赞助 78 | 79 | -------------------------------------------------------------------------------- /app/interface/README: -------------------------------------------------------------------------------- 1 | 端口分配 2 | 3 | - web api gateway: 8099 -------------------------------------------------------------------------------- /app/interface/web/Makefile: -------------------------------------------------------------------------------- 1 | MODULE_NAME:=post 2 | 3 | .PHONY: build 4 | build: 5 | 6 | go build -o cmd/${MODULE_NAME}-web cmd/main.go cmd/plugin.go 7 | 8 | .PHONY: test 9 | test: 10 | go test -v ./... -cover 11 | 12 | .PHONY: docker 13 | docker: 14 | docker build . -t ${MODULE_NAME}-web:latest 15 | -------------------------------------------------------------------------------- /app/interface/web/api/client.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/liangjfblue/cheetah/common/proto" 5 | 6 | userv1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 7 | 8 | "github.com/micro/go-micro/client" 9 | ) 10 | 11 | func NewUserSrvClient(cli client.Client) userv1.UserService { 12 | return userv1.NewUserService(proto.UserSrvName, cli) 13 | } 14 | -------------------------------------------------------------------------------- /app/interface/web/cmd/config.yaml: -------------------------------------------------------------------------------- 1 | http: 2 | port: 7030 3 | 4 | log: 5 | name: web-web 6 | logDir: ./logs 7 | level: 1 8 | openAccessLog: true 9 | 10 | etcd: 11 | addrs: http://172.16.7.16:9002,http://172.16.7.16:9004,http://172.16.7.16:9006 12 | timeout: 5 -------------------------------------------------------------------------------- /app/interface/web/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/liangjfblue/cheetah/app/interface/web/server" 5 | ) 6 | 7 | const ( 8 | srvName = "cheetah.web.web" 9 | srvVersion = "v1.0.0" 10 | ) 11 | 12 | func main() { 13 | srv := server.NewServer(srvName, srvVersion) 14 | srv.Init() 15 | 16 | srv.Run() 17 | } 18 | -------------------------------------------------------------------------------- /app/interface/web/cmd/plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //import ( 4 | // // registry 5 | // // k8s 6 | // //_ "github.com/micro/go-plugins/registry/kubernetes" 7 | // // etcd v3 8 | // //_ "github.com/micro/go-plugins/registry/etcd" 9 | // // transport 10 | // // tcp 11 | // //_ "github.com/micro/go-plugins/transport/tcp" 12 | // // nats 13 | // //_ "github.com/micro/go-plugins/transport/nats" 14 | // // broker 15 | // // kafka 16 | // //_ "github.com/micro/go-plugins/broker/kafka" 17 | //) 18 | -------------------------------------------------------------------------------- /app/interface/web/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | type Config struct { 10 | HttpConf *HttpConfig 11 | LogConf *LogConfig 12 | EtcdConf *EtcdConfig 13 | } 14 | 15 | type HttpConfig struct { 16 | Port int 17 | } 18 | 19 | type LogConfig struct { 20 | Name string 21 | LogDir string 22 | Level int32 23 | OpenAccessLog bool 24 | } 25 | 26 | type EtcdConfig struct { 27 | Addrs []string 28 | Timeout int 29 | } 30 | 31 | var _configInstance *Config 32 | 33 | func ConfigInstance() *Config { 34 | return _configInstance 35 | } 36 | 37 | func Init() { 38 | if err := initConfig(); err != nil { 39 | panic(err) 40 | } 41 | 42 | _configInstance = &Config{ 43 | HttpConf: &HttpConfig{ 44 | Port: viper.GetInt("http.port"), 45 | }, 46 | LogConf: &LogConfig{ 47 | Name: viper.GetString("log.name"), 48 | LogDir: viper.GetString("log.logDir"), 49 | Level: viper.GetInt32("log.level"), 50 | OpenAccessLog: viper.GetBool("log.openAccessLog"), 51 | }, 52 | EtcdConf: &EtcdConfig{ 53 | Addrs: viper.GetStringSlice("etcd.addrs"), 54 | Timeout: viper.GetInt("etcd.timeout"), 55 | }, 56 | } 57 | } 58 | 59 | func initConfig() error { 60 | viper.AddConfigPath(".") 61 | viper.SetConfigName("config") 62 | 63 | viper.SetConfigType("yaml") 64 | viper.AutomaticEnv() 65 | viper.SetEnvPrefix("web-web") 66 | replacer := strings.NewReplacer(".", "_") 67 | viper.SetEnvKeyReplacer(replacer) 68 | if err := viper.ReadInConfig(); err != nil { 69 | return err 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /app/interface/web/controllers/jobs.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /app/interface/web/controllers/scheduler_logs.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /app/interface/web/controllers/schedulers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /app/interface/web/controllers/users.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/liangjfblue/cheetah/common/verify" 9 | 10 | "github.com/liangjfblue/cheetah/common/configs" 11 | 12 | "github.com/liangjfblue/cheetah/app/interface/web/service/users" 13 | 14 | "github.com/liangjfblue/cheetah/common/logger" 15 | 16 | "github.com/gin-gonic/gin" 17 | "github.com/liangjfblue/cheetah/app/interface/web/models" 18 | "github.com/liangjfblue/cheetah/common/errno" 19 | "github.com/liangjfblue/cheetah/common/http/handle" 20 | "github.com/liangjfblue/cheetah/common/tracer" 21 | ) 22 | 23 | func UserLogin(c *gin.Context) { 24 | var ( 25 | err error 26 | result handle.Result 27 | req models.LoginRequest 28 | ) 29 | 30 | //tracer 31 | cc, exist := c.Get(configs.TraceContext) 32 | if !exist { 33 | logger.Error("no TraceContext") 34 | result.Failure(c, errno.ErrTraceNoContext) 35 | return 36 | } 37 | ctx := cc.(context.Context) 38 | ctx, span, err := tracer.TraceIntoContext(ctx, "WebUserLogin") 39 | if err != nil { 40 | logger.Error("web web err: %s", err.Error()) 41 | result.Failure(c, errno.ErrTraceIntoContext) 42 | return 43 | } 44 | defer span.Finish() 45 | 46 | if err = c.BindJSON(&req); err != nil { 47 | logger.Error("web web Login err: %s", err.Error()) 48 | result.Failure(c, errno.ErrBind) 49 | return 50 | } 51 | 52 | if err = verify.Validate(req); err != nil { 53 | logger.Error("web web Login err: %s", err.Error()) 54 | result.Failure(c, errno.ErrParams) 55 | return 56 | } 57 | 58 | resp, err := users.Login(ctx, &req) 59 | if err != nil { 60 | logger.Error("web web Login err: %s", err.Error()) 61 | result.Failure(c, err) 62 | return 63 | } 64 | 65 | result.Success(c, resp) 66 | } 67 | 68 | func UserRegister(c *gin.Context) { 69 | var ( 70 | err error 71 | result handle.Result 72 | req models.RegisterRequest 73 | ) 74 | 75 | //tracer 76 | cc, exist := c.Get(configs.TraceContext) 77 | if !exist { 78 | logger.Error("no TraceContext") 79 | result.Failure(c, errno.ErrTraceNoContext) 80 | return 81 | } 82 | ctx := cc.(context.Context) 83 | ctx, span, err := tracer.TraceIntoContext(ctx, "WebUserRegister") 84 | if err != nil { 85 | logger.Error("web web err: %s", err.Error()) 86 | result.Failure(c, errno.ErrTraceIntoContext) 87 | return 88 | } 89 | defer span.Finish() 90 | 91 | if err = c.BindJSON(&req); err != nil { 92 | result.Failure(c, errno.ErrBind) 93 | return 94 | } 95 | 96 | resp, err := users.Register(ctx, &req) 97 | if err != nil { 98 | logger.Error("web web Register err: %s", err.Error()) 99 | result.Failure(c, err) 100 | return 101 | } 102 | 103 | result.Success(c, resp) 104 | } 105 | 106 | func UserGet(c *gin.Context) { 107 | fmt.Println("UserGet") 108 | var ( 109 | err error 110 | result handle.Result 111 | req models.GetRequest 112 | ) 113 | 114 | //tracer 115 | cc, exist := c.Get(configs.TraceContext) 116 | if !exist { 117 | logger.Error("no TraceContext") 118 | result.Failure(c, errno.ErrTraceNoContext) 119 | return 120 | } 121 | ctx := cc.(context.Context) 122 | ctx, span, err := tracer.TraceIntoContext(ctx, "WebUserGet") 123 | if err != nil { 124 | logger.Error("web web err: %s", err.Error()) 125 | result.Failure(c, errno.ErrTraceIntoContext) 126 | return 127 | } 128 | defer span.Finish() 129 | 130 | uid, ok := c.Get("uid") 131 | if !ok { 132 | logger.Error("web web err: token no uid") 133 | result.Failure(c, errno.ErrNoTokenUid) 134 | return 135 | } 136 | req.Uid = uid.(string) 137 | 138 | resp, err := users.Get(ctx, &req) 139 | if err != nil { 140 | logger.Error("web web err: %s", err.Error()) 141 | result.Failure(c, err) 142 | return 143 | } 144 | 145 | result.Success(c, resp) 146 | } 147 | 148 | func UserList(c *gin.Context) { 149 | var ( 150 | err error 151 | result handle.Result 152 | req models.ListRequest 153 | ) 154 | 155 | //tracer 156 | cc, exist := c.Get(configs.TraceContext) 157 | if !exist { 158 | logger.Error("no TraceContext") 159 | result.Failure(c, errno.ErrTraceNoContext) 160 | return 161 | } 162 | ctx := cc.(context.Context) 163 | ctx, span, err := tracer.TraceIntoContext(ctx, "WebUserList") 164 | if err != nil { 165 | logger.Error("web web err: %s", err.Error()) 166 | result.Failure(c, errno.ErrTraceIntoContext) 167 | return 168 | } 169 | defer span.Finish() 170 | 171 | page, _ := strconv.Atoi(c.Query("page")) 172 | pageSize, _ := strconv.Atoi(c.Query("pageSize")) 173 | username := c.Query("username") 174 | 175 | req.Page = int32(page) 176 | req.PageSize = int32(pageSize) 177 | req.Username = username 178 | 179 | resp, err := users.List(ctx, &req) 180 | if err != nil { 181 | logger.Error("web web err: %s", err.Error()) 182 | result.Failure(c, err) 183 | return 184 | } 185 | 186 | result.Success(c, resp) 187 | } 188 | -------------------------------------------------------------------------------- /app/interface/web/controllers/worker_groups.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /app/interface/web/controllers/workers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /app/interface/web/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type RegisterRequest struct { 4 | Username string `json:"username"` 5 | Password string `json:"password"` 6 | Age int32 `json:"age"` 7 | Addr string `json:"addr"` 8 | } 9 | 10 | type RegisterRespond struct { 11 | Code int32 `json:"code"` 12 | Uid string `json:"uid"` 13 | } 14 | 15 | type LoginRequest struct { 16 | Username string `json:"username" validate:"required"` 17 | Password string `json:"password" validate:"required"` 18 | } 19 | 20 | type LoginRespond struct { 21 | Code int32 `json:"code"` 22 | Token string `json:"token"` 23 | } 24 | 25 | type GetRequest struct { 26 | Uid string `json:"uid"` 27 | } 28 | 29 | type GetRespond struct { 30 | Code int32 `json:"code"` 31 | Username string `json:"username"` 32 | Age int32 `json:"age"` 33 | Addr string `json:"addr"` 34 | } 35 | 36 | type AuthRequest struct { 37 | Token string `json:"token" validate:"required"` 38 | } 39 | 40 | type AuthResponse struct { 41 | Code int32 `json:"code"` 42 | UID string `json:"uid"` 43 | } 44 | 45 | type ListRequest struct { 46 | Page int32 `json:"page"` 47 | PageSize int32 `json:"pageSize"` 48 | Username string `json:"username"` 49 | } 50 | 51 | type User struct { 52 | Username string `json:"username"` 53 | Age int32 `json:"age"` 54 | Addr string `json:"addr"` 55 | } 56 | type ListRespond struct { 57 | Code int32 `json:"code"` 58 | Page int32 `json:"page"` 59 | PageSize int32 `json:"pageSize"` 60 | Count int32 `json:"count"` 61 | Users []User `json:"users"` 62 | } 63 | -------------------------------------------------------------------------------- /app/interface/web/router/init.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/liangjfblue/cheetah/app/interface/web/controllers" 7 | 8 | "github.com/liangjfblue/cheetah/common/http/middleware" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/liangjfblue/cheetah/app/interface/web/service" 12 | ) 13 | 14 | type Router struct { 15 | G *gin.Engine 16 | } 17 | 18 | func NewRouter() *Router { 19 | return &Router{ 20 | G: gin.Default(), 21 | } 22 | } 23 | 24 | func (r *Router) Init() { 25 | r.G.Use(gin.Recovery()) 26 | r.G.NoRoute(func(c *gin.Context) { 27 | c.String(http.StatusNotFound, "The incorrect API route") 28 | }) 29 | 30 | r.initRouter() 31 | } 32 | 33 | func (r *Router) initRouter() { 34 | gWorkers := r.G.Group("/v1/workers") 35 | gWorkers.Use(middleware.OpenTracingMid(), service.AuthMid.AuthMid()) 36 | { 37 | 38 | } 39 | 40 | gWorkerGroups := r.G.Group("/v1/worker_groups") 41 | gWorkerGroups.Use(middleware.OpenTracingMid(), service.AuthMid.AuthMid()) 42 | { 43 | 44 | } 45 | 46 | gJobs := r.G.Group("/v1/jobs") 47 | gJobs.Use(middleware.OpenTracingMid(), service.AuthMid.AuthMid()) 48 | { 49 | 50 | } 51 | 52 | gSchedulers := r.G.Group("/v1/schedulers") 53 | gSchedulers.Use(middleware.OpenTracingMid(), service.AuthMid.AuthMid()) 54 | { 55 | 56 | } 57 | 58 | gUsers := r.G.Group("/v1/users") 59 | gUsers.Use(middleware.OpenTracingMid()) 60 | { 61 | gUsers.POST("/register", controllers.UserRegister) 62 | gUsers.POST("/login", controllers.UserLogin) 63 | 64 | gUsers.Use(service.AuthMid.AuthMid()) 65 | { 66 | gUsers.GET("/get", controllers.UserGet) 67 | gUsers.GET("/list", controllers.UserList) 68 | } 69 | } 70 | 71 | gSchedulerLogs := r.G.Group("/v1/scheduler_logs") 72 | gSchedulerLogs.Use(middleware.OpenTracingMid(), service.AuthMid.AuthMid()) 73 | { 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/interface/web/server/init.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | ratelimit2 "github.com/juju/ratelimit" 9 | 10 | "github.com/liangjfblue/cheetah/app/interface/web/service" 11 | 12 | "github.com/liangjfblue/cheetah/app/interface/web/config" 13 | 14 | "github.com/liangjfblue/cheetah/common/configs" 15 | 16 | "github.com/liangjfblue/gglog" 17 | "github.com/micro/go-micro" 18 | "github.com/micro/go-micro/registry" 19 | 20 | "github.com/liangjfblue/cheetah/common/tracer" 21 | 22 | "github.com/liangjfblue/cheetah/common/logger" 23 | 24 | "github.com/liangjfblue/cheetah/app/interface/web/router" 25 | 26 | "github.com/micro/go-plugins/registry/etcdv3" 27 | "github.com/micro/go-plugins/wrapper/breaker/hystrix" 28 | "github.com/micro/go-plugins/wrapper/ratelimiter/ratelimit" 29 | ) 30 | 31 | type Server struct { 32 | serviceName string 33 | serviceVersion string 34 | 35 | Service micro.Service 36 | Router *router.Router 37 | 38 | Tracer *tracer.Tracer 39 | } 40 | 41 | func NewServer(serviceName, serviceVersion string) *Server { 42 | s := new(Server) 43 | 44 | s.serviceName = serviceName 45 | s.serviceVersion = serviceVersion 46 | 47 | s.Router = router.NewRouter() 48 | s.Tracer = tracer.New(configs.TraceAddr, s.serviceName) 49 | 50 | return s 51 | } 52 | 53 | func (s *Server) Init() { 54 | config.Init() 55 | 56 | logger.Init( 57 | gglog.Name(config.ConfigInstance().LogConf.Name), 58 | gglog.Level(config.ConfigInstance().LogConf.Level), 59 | gglog.LogDir(config.ConfigInstance().LogConf.LogDir), 60 | gglog.OpenAccessLog(config.ConfigInstance().LogConf.OpenAccessLog), 61 | ) 62 | 63 | s.Tracer.Init() 64 | 65 | reg := etcdv3.NewRegistry(func(op *registry.Options) { 66 | op.Addrs = config.ConfigInstance().EtcdConf.Addrs 67 | op.Timeout = time.Duration(config.ConfigInstance().EtcdConf.Timeout) * time.Second 68 | }) 69 | 70 | //令牌限流 初始化令牌桶的容量为10, 每1秒往桶放1个令牌 71 | //即限流 10 request/s 72 | bRate := ratelimit2.NewBucketWithRate(1, 10) 73 | s.Service = micro.NewService( 74 | micro.Name(s.serviceName), 75 | micro.Version(s.serviceVersion), 76 | micro.Registry(reg), 77 | micro.WrapClient(ratelimit.NewClientWrapper(bRate, false)), //加入限流功能, false为不等待(超限即返回请求失败) 78 | micro.WrapClient(hystrix.NewClientWrapper()), // 加入熔断功能, 处理rpc调用失败的情况 79 | ) 80 | 81 | s.Service.Init() 82 | 83 | //init rpc services client 84 | cli := s.Service.Client() 85 | service.InitSrvRpc(cli) 86 | 87 | s.Router.Init() 88 | } 89 | 90 | func (s *Server) Run() { 91 | defer func() { 92 | logger.Info("web close, clean and close something") 93 | s.Tracer.Close() 94 | }() 95 | 96 | logger.Debug("web server run") 97 | logger.Error(http.ListenAndServe(fmt.Sprintf(":%d", config.ConfigInstance().HttpConf.Port), s.Router.G).Error()) 98 | } 99 | -------------------------------------------------------------------------------- /app/interface/web/service/init.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/liangjfblue/cheetah/app/interface/web/api" 5 | userv1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 6 | "github.com/liangjfblue/cheetah/common/http/middleware" 7 | "github.com/micro/go-micro/client" 8 | ) 9 | 10 | var ( 11 | AuthMid *middleware.Auth 12 | UserSrvClient userv1.UserService 13 | ) 14 | 15 | func InitSrvRpc(cli client.Client) { 16 | AuthMid = middleware.New(cli) 17 | UserSrvClient = api.NewUserSrvClient(cli) 18 | } 19 | -------------------------------------------------------------------------------- /app/interface/web/service/users/get.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/jinzhu/copier" 8 | v1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 9 | 10 | "github.com/liangjfblue/cheetah/app/interface/web/models" 11 | "github.com/liangjfblue/cheetah/app/interface/web/service" 12 | "github.com/liangjfblue/cheetah/common/errno" 13 | "github.com/liangjfblue/cheetah/common/logger" 14 | ) 15 | 16 | func Get(ctx context.Context, req *models.GetRequest) (*models.GetRespond, error) { 17 | result, err := service.UserSrvClient.Get(ctx, &v1.GetRequest{ 18 | Uid: req.Uid, 19 | }) 20 | if err != nil { 21 | logger.Error("web web Get err: %s", err.Error()) 22 | if strings.Contains(err.Error(), "too many request") { 23 | err = errno.ErrTooManyReqyest 24 | } else { 25 | err = errno.ErrUserInfo 26 | } 27 | return nil, err 28 | } 29 | 30 | resp := &models.GetRespond{} 31 | if err := copier.Copy(resp, result); err != nil { 32 | logger.Error("web web Get err: %s", err.Error()) 33 | return nil, errno.ErrCopy 34 | } 35 | 36 | return resp, nil 37 | } 38 | -------------------------------------------------------------------------------- /app/interface/web/service/users/list.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/liangjfblue/cheetah/app/interface/web/models" 8 | "github.com/liangjfblue/cheetah/app/interface/web/service" 9 | v1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 10 | "github.com/liangjfblue/cheetah/common/errno" 11 | "github.com/liangjfblue/cheetah/common/logger" 12 | ) 13 | 14 | func List(ctx context.Context, req *models.ListRequest) (*models.ListRespond, error) { 15 | result, err := service.UserSrvClient.List(ctx, &v1.ListRequest{ 16 | Page: req.Page, 17 | PageSize: req.PageSize, 18 | Username: req.Username, 19 | }) 20 | if err != nil { 21 | logger.Error("web web List err: %s", err.Error()) 22 | if strings.Contains(err.Error(), "too many request") { 23 | err = errno.ErrTooManyReqyest 24 | } else { 25 | err = errno.ErrUserList 26 | } 27 | return nil, err 28 | } 29 | 30 | resp := new(models.ListRespond) 31 | resp.Users = make([]models.User, 0) 32 | 33 | resp.Code = result.Code 34 | resp.Count = result.Count 35 | for _, one := range result.All { 36 | resp.Users = append(resp.Users, models.User{ 37 | Username: one.Username, 38 | Age: one.Age, 39 | Addr: one.Addr, 40 | }) 41 | } 42 | 43 | return resp, nil 44 | } 45 | -------------------------------------------------------------------------------- /app/interface/web/service/users/login.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/jinzhu/copier" 9 | 10 | "github.com/liangjfblue/cheetah/common/errno" 11 | "github.com/liangjfblue/cheetah/common/logger" 12 | 13 | "github.com/liangjfblue/cheetah/app/interface/web/models" 14 | "github.com/liangjfblue/cheetah/app/interface/web/service" 15 | v1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 16 | ) 17 | 18 | func Login(ctx context.Context, req *models.LoginRequest) (*models.LoginRespond, error) { 19 | result, err := service.UserSrvClient.Login(ctx, &v1.LoginRequest{ 20 | Username: req.Username, 21 | Password: req.Password, 22 | }) 23 | if err != nil { 24 | logger.Error("web web Login err: %s", err.Error()) 25 | if strings.Contains(err.Error(), "too many request") { 26 | err = errno.ErrTooManyReqyest 27 | } else { 28 | err = errno.ErrUserLogin 29 | } 30 | return nil, err 31 | } 32 | 33 | fmt.Println(result) 34 | resp := &models.LoginRespond{} 35 | if err := copier.Copy(&resp, result); err != nil { 36 | logger.Error("web web Info err: %s", err.Error()) 37 | return nil, errno.ErrCopy 38 | } 39 | 40 | return resp, nil 41 | } 42 | -------------------------------------------------------------------------------- /app/interface/web/service/users/register.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/jinzhu/copier" 9 | 10 | "github.com/liangjfblue/cheetah/app/interface/web/service" 11 | 12 | userV1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 13 | 14 | "github.com/liangjfblue/cheetah/common/errno" 15 | "github.com/liangjfblue/cheetah/common/logger" 16 | 17 | "github.com/liangjfblue/cheetah/app/interface/web/models" 18 | ) 19 | 20 | func Register(ctx context.Context, req *models.RegisterRequest) (*models.RegisterRespond, error) { 21 | fmt.Println(req) 22 | result, err := service.UserSrvClient.Register(ctx, &userV1.RegisterRequest{ 23 | Username: req.Username, 24 | Password: req.Password, 25 | Age: req.Age, 26 | Addr: req.Addr, 27 | }) 28 | if err != nil { 29 | logger.Error("web web Register err: %s", err.Error()) 30 | if strings.Contains(err.Error(), "too many request") { 31 | err = errno.ErrTooManyReqyest 32 | } else { 33 | err = errno.ErrUserRegister 34 | } 35 | return nil, err 36 | } 37 | 38 | fmt.Println(result) 39 | 40 | resp := &models.RegisterRespond{} 41 | if err := copier.Copy(&resp, result); err != nil { 42 | logger.Error("web web Info err: %s", err.Error()) 43 | return nil, errno.ErrCopy 44 | } 45 | 46 | return resp, nil 47 | } 48 | -------------------------------------------------------------------------------- /app/service/README: -------------------------------------------------------------------------------- 1 | 各微服务 2 | 3 | auth服务 4 | - 用户鉴权 5 | 6 | master服务 7 | - 调度 8 | - worker资源监控 9 | 10 | worker服务 11 | - 任务执行单元 12 | - master分配worker来执行任务 13 | 14 | -------------------------------------------------------------------------------- /app/service/web/README.md: -------------------------------------------------------------------------------- 1 | # user Service 2 | 3 | 注册用户初始+10铜币 -------------------------------------------------------------------------------- /app/service/web/cmd/config.yaml: -------------------------------------------------------------------------------- 1 | runmode: debug 2 | name: web-srv 3 | 4 | mysql: 5 | addr: 172.16.7.16:3306 6 | db: db_cheetah 7 | user: root 8 | password: 123456 9 | maxIdleConns: 50 10 | maxOpenConns: 100 11 | 12 | log: 13 | name: srv-web 14 | logDir: ./logs 15 | level: 1 16 | openAccessLog: true 17 | 18 | etcd: 19 | addrs: http://172.16.7.16:9002,http://172.16.7.16:9004,http://172.16.7.16:9006 20 | timeout: 5 -------------------------------------------------------------------------------- /app/service/web/cmd/logs/debug.log.2020-04-19: -------------------------------------------------------------------------------- 1 | [2020-04-19 16:50:45][init.go:98][DEBUG]: service user server run 2 | -------------------------------------------------------------------------------- /app/service/web/cmd/logs/info.log.2020-04-19: -------------------------------------------------------------------------------- 1 | [2020-04-19 16:50:45][init.go:30][INFO]: init open tracering:&{serviceName:micro.srv.user hostIPv4:3232235632 sampler:0xc000364c00 reporter:0xc00008b680 metrics:{TracesStartedSampled:{} TracesStartedNotSampled:{} TracesStartedDelayedSampling:{} TracesJoinedSampled:{} TracesJoinedNotSampled:{} SpansStartedSampled:{} SpansStartedNotSampled:{} SpansStartedDelayedSampling:{} SpansFinishedSampled:{} SpansFinishedNotSampled:{} SpansFinishedDelayedSampling:{} DecodingErrors:{} ReporterSuccess:{} ReporterFailure:{} ReporterDropped:{} ReporterQueueLength:{} SamplerRetrieved:{} SamplerQueryFailure:{} SamplerUpdated:{} SamplerUpdateFailure:{} BaggageUpdateSuccess:{} BaggageUpdateFailure:{} BaggageTruncate:{} BaggageRestrictionsUpdateSuccess:{} BaggageRestrictionsUpdateFailure:{} ThrottledDebugSpans:{} ThrottlerUpdateSuccess:{} ThrottlerUpdateFailure:{}} logger:0x250eaa0 timeNow:0xa27120 randomNumber:0x111adf0 options:{gen128Bit:false zipkinSharedRPCSpan:false highTraceIDGenerator: maxTagValueLength:256 noDebugFlagOnForcedSampling:false maxLogsPerSpan:0} spanAllocator:{} injectors:map[0:0xc0002480f0 1:0xc00000c5a0 2:0xc00000c780 0:0xc00051f328 zipkin-span-format:0xc00051f330] extractors:map[0:0xc0002480f0 1:0xc00000c5a0 2:0xc00000c780 0:0xc00051f328 zipkin-span-format:0xc00051f330] observer:{observers:[]} tags:[{key:jaeger.version value:Go-2.22.1} {key:hostname value:blue} {key:ip value:192.168.0.112}] process:{Service:micro.srv.user UUID:4b6b486007c2c11a Tags:[{key:jaeger.version value:Go-2.22.1} {key:hostname value:blue} {key:ip value:192.168.0.112}]} baggageRestrictionManager: baggageSetter:0xc000225660 debugThrottler:{}} 2 | -------------------------------------------------------------------------------- /app/service/web/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/liangjfblue/cheetah/app/service/web/server" 5 | "github.com/liangjfblue/cheetah/common/proto" 6 | ) 7 | 8 | const ( 9 | srvName = proto.UserSrvName 10 | srvVersion = proto.UserSrvVersion 11 | ) 12 | 13 | func main() { 14 | srv := server.NewServer(srvName, srvVersion) 15 | srv.Init() 16 | 17 | srv.Run() 18 | } 19 | -------------------------------------------------------------------------------- /app/service/web/cmd/plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // registry 4 | // k8s 5 | //_ "github.com/micro/go-plugins/registry/kubernetes" 6 | // etcd v3 7 | //_ "github.com/micro/go-plugins/registry/etcdv3" 8 | // transport 9 | // tcp 10 | //_ "github.com/micro/go-plugins/transport/tcp" 11 | // nats 12 | //_ "github.com/micro/go-plugins/transport/nats" 13 | // broker 14 | // kafka 15 | //_ "github.com/micro/go-plugins/broker/kafka" 16 | -------------------------------------------------------------------------------- /app/service/web/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/viper" 7 | ) 8 | 9 | type Config struct { 10 | MysqlConf *MysqlConfig 11 | LogConf *LogConfig 12 | EtcdConf *EtcdConfig 13 | } 14 | 15 | type LogConfig struct { 16 | Name string 17 | LogDir string 18 | Level int32 19 | OpenAccessLog bool 20 | } 21 | 22 | type MysqlConfig struct { 23 | Addr string 24 | Db string 25 | User string 26 | Password string 27 | MaxIdleConns int 28 | MaxOpenConns int 29 | } 30 | 31 | type EtcdConfig struct { 32 | Addrs []string 33 | Timeout int 34 | } 35 | 36 | var _configInstance *Config 37 | 38 | func ConfigInstance() *Config { 39 | return _configInstance 40 | } 41 | 42 | func Init() { 43 | if err := initConfig(); err != nil { 44 | panic(err) 45 | } 46 | 47 | _configInstance = &Config{ 48 | MysqlConf: &MysqlConfig{ 49 | Addr: viper.GetString("mysql.addr"), 50 | Db: viper.GetString("mysql.db"), 51 | User: viper.GetString("mysql.user"), 52 | Password: viper.GetString("mysql.password"), 53 | MaxIdleConns: viper.GetInt("mysql.maxIdleConns"), 54 | MaxOpenConns: viper.GetInt("mysql.maxOpenConns"), 55 | }, 56 | LogConf: &LogConfig{ 57 | Name: viper.GetString("log.name"), 58 | LogDir: viper.GetString("log.logDir"), 59 | Level: viper.GetInt32("log.level"), 60 | OpenAccessLog: viper.GetBool("log.openAccessLog"), 61 | }, 62 | EtcdConf: &EtcdConfig{ 63 | Addrs: viper.GetStringSlice("etcd.addrs"), 64 | Timeout: viper.GetInt("etcd.timeout"), 65 | }, 66 | } 67 | } 68 | 69 | func initConfig() error { 70 | viper.AddConfigPath(".") 71 | viper.SetConfigName("config") 72 | 73 | viper.SetConfigType("yaml") 74 | viper.AutomaticEnv() 75 | viper.SetEnvPrefix("web-srv") 76 | replacer := strings.NewReplacer(".", "_") 77 | viper.SetEnvKeyReplacer(replacer) 78 | if err := viper.ReadInConfig(); err != nil { 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /app/service/web/model/init.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/liangjfblue/cheetah/app/service/web/config" 8 | 9 | "github.com/jinzhu/gorm" 10 | _ "github.com/jinzhu/gorm/dialects/mysql" 11 | ) 12 | 13 | var ( 14 | DB *gorm.DB 15 | ) 16 | 17 | func Init() { 18 | var ( 19 | err error 20 | addr string 21 | ) 22 | 23 | addr = os.Getenv("CONFIGOR_MYSQL_ADDR") 24 | if addr == "" { 25 | addr = config.ConfigInstance().MysqlConf.Addr 26 | } 27 | str := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", 28 | config.ConfigInstance().MysqlConf.User, config.ConfigInstance().MysqlConf.Password, addr, config.ConfigInstance().MysqlConf.Db) 29 | 30 | DB, err = gorm.Open("mysql", str) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | DB.LogMode(true) 36 | DB.SingularTable(true) 37 | DB.DB().SetMaxIdleConns(config.ConfigInstance().MysqlConf.MaxIdleConns) 38 | DB.DB().SetMaxOpenConns(config.ConfigInstance().MysqlConf.MaxOpenConns) 39 | 40 | DB.AutoMigrate(&TBUser{}) 41 | 42 | return 43 | } 44 | 45 | func CheckPageSize(page, pageSize int32) (int32, int32) { 46 | if page < 1 { 47 | page = 1 48 | } 49 | if pageSize < 15 { 50 | pageSize = 15 51 | } 52 | return page, pageSize 53 | } 54 | -------------------------------------------------------------------------------- /app/service/web/model/tb_user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/liangjfblue/cheetah/common/auth" 7 | 8 | "gopkg.in/go-playground/validator.v9" 9 | ) 10 | 11 | type TBUser struct { 12 | ID uint 13 | CreatedAt time.Time 14 | UpdatedAt time.Time 15 | DeletedAt *time.Time `sql:"index"` 16 | Uid string `gorm:"column:uid;type:varchar(100);unique_index" description:"uuid"` 17 | Username string `gorm:"column:username;type:varchar(100);unique_index" description:"账号"` 18 | Password string `gorm:"column:password;type:varchar(80);null" description:"密码"` 19 | Age int32 `gorm:"column:age;not null" description:"年龄"` 20 | Address string `gorm:"column:address;type:varchar(250);null" description:"地址"` 21 | IsAvailable int8 `gorm:"column:is_available;null" description:"是否可用 1-可用 0-不可用" ` 22 | LastLogin time.Time `gorm:"column:last_login;type(datetime);null" description:"最后登录时间"` 23 | LoginIp string `gorm:"column:login_ip;type:varchar(20);null" description:"登录IP"` 24 | } 25 | 26 | func (t *TBUser) TableName() string { 27 | return "tb_user" 28 | } 29 | 30 | func (t *TBUser) Create() error { 31 | return DB.Create(t).Error 32 | } 33 | 34 | func GetUser(u *TBUser) (*TBUser, error) { 35 | var user TBUser 36 | err := DB.Where(u).First(&user).Error 37 | return &user, err 38 | } 39 | 40 | func ListUsers(username string, page, pageSize int32) (uint64, []*TBUser, error) { 41 | var ( 42 | err error 43 | users = make([]*TBUser, 0) 44 | count uint64 45 | ) 46 | 47 | if username == "" { 48 | err = DB.Model(&TBUser{}).Count(&count).Error 49 | err = DB.Offset((page - 1) * pageSize).Limit(pageSize).Order("id desc").Find(&users).Error 50 | 51 | } else { 52 | err = DB.Model(&TBUser{}).Where("username LIKE ?", "%"+username+"%").Count(&count).Error 53 | err = DB.Where("username LIKE ?", "%"+username+"%"). 54 | Offset((page - 1) * pageSize).Limit(pageSize).Order("id desc").Find(&users).Error 55 | } 56 | 57 | return count, users, err 58 | } 59 | 60 | func DeleteUser(id uint) error { 61 | user := TBUser{ 62 | ID: id, 63 | } 64 | return DB.Delete(&user).Error 65 | } 66 | 67 | func (t *TBUser) Update() error { 68 | return DB.Save(t).Error 69 | } 70 | 71 | func (t *TBUser) Encrypt() (err error) { 72 | t.Password, err = auth.Encrypt(t.Password) 73 | return 74 | } 75 | 76 | func (t *TBUser) Validate() error { 77 | validate := validator.New() 78 | return validate.Struct(t) 79 | } 80 | -------------------------------------------------------------------------------- /app/service/web/proto/v1/api.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: api.proto 3 | 4 | //protoc --proto_path=. --micro_out=. --go_out=. ./api.proto 5 | 6 | package micro_srv_cheetah_web 7 | 8 | import ( 9 | fmt "fmt" 10 | proto "github.com/golang/protobuf/proto" 11 | math "math" 12 | ) 13 | 14 | import ( 15 | context "context" 16 | client "github.com/micro/go-micro/client" 17 | server "github.com/micro/go-micro/server" 18 | ) 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = fmt.Errorf 23 | var _ = math.Inf 24 | 25 | // This is a compile-time assertion to ensure that this generated file 26 | // is compatible with the proto package it is being compiled against. 27 | // A compilation error at this line likely means your copy of the 28 | // proto package needs to be updated. 29 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 30 | 31 | // Reference imports to suppress errors if they are not otherwise used. 32 | var _ context.Context 33 | var _ client.Option 34 | var _ server.Option 35 | 36 | // Client API for User service 37 | 38 | type UserService interface { 39 | Register(ctx context.Context, in *RegisterRequest, opts ...client.CallOption) (*RegisterRespond, error) 40 | Login(ctx context.Context, in *LoginRequest, opts ...client.CallOption) (*LoginRespond, error) 41 | Get(ctx context.Context, in *GetRequest, opts ...client.CallOption) (*GetRespond, error) 42 | List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListRespond, error) 43 | Auth(ctx context.Context, in *AuthRequest, opts ...client.CallOption) (*AuthRespond, error) 44 | } 45 | 46 | type userService struct { 47 | c client.Client 48 | name string 49 | } 50 | 51 | func NewUserService(name string, c client.Client) UserService { 52 | if c == nil { 53 | c = client.NewClient() 54 | } 55 | if len(name) == 0 { 56 | name = "micro.srv.cheetah.web" 57 | } 58 | return &userService{ 59 | c: c, 60 | name: name, 61 | } 62 | } 63 | 64 | func (c *userService) Register(ctx context.Context, in *RegisterRequest, opts ...client.CallOption) (*RegisterRespond, error) { 65 | req := c.c.NewRequest(c.name, "User.Register", in) 66 | out := new(RegisterRespond) 67 | err := c.c.Call(ctx, req, out, opts...) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return out, nil 72 | } 73 | 74 | func (c *userService) Login(ctx context.Context, in *LoginRequest, opts ...client.CallOption) (*LoginRespond, error) { 75 | req := c.c.NewRequest(c.name, "User.Login", in) 76 | out := new(LoginRespond) 77 | err := c.c.Call(ctx, req, out, opts...) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return out, nil 82 | } 83 | 84 | func (c *userService) Get(ctx context.Context, in *GetRequest, opts ...client.CallOption) (*GetRespond, error) { 85 | req := c.c.NewRequest(c.name, "User.Get", in) 86 | out := new(GetRespond) 87 | err := c.c.Call(ctx, req, out, opts...) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return out, nil 92 | } 93 | 94 | func (c *userService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListRespond, error) { 95 | req := c.c.NewRequest(c.name, "User.List", in) 96 | out := new(ListRespond) 97 | err := c.c.Call(ctx, req, out, opts...) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return out, nil 102 | } 103 | 104 | func (c *userService) Auth(ctx context.Context, in *AuthRequest, opts ...client.CallOption) (*AuthRespond, error) { 105 | req := c.c.NewRequest(c.name, "User.Auth", in) 106 | out := new(AuthRespond) 107 | err := c.c.Call(ctx, req, out, opts...) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return out, nil 112 | } 113 | 114 | // Server API for User service 115 | 116 | type UserHandler interface { 117 | Register(context.Context, *RegisterRequest, *RegisterRespond) error 118 | Login(context.Context, *LoginRequest, *LoginRespond) error 119 | Get(context.Context, *GetRequest, *GetRespond) error 120 | List(context.Context, *ListRequest, *ListRespond) error 121 | Auth(context.Context, *AuthRequest, *AuthRespond) error 122 | } 123 | 124 | func RegisterUserHandler(s server.Server, hdlr UserHandler, opts ...server.HandlerOption) error { 125 | type user interface { 126 | Register(ctx context.Context, in *RegisterRequest, out *RegisterRespond) error 127 | Login(ctx context.Context, in *LoginRequest, out *LoginRespond) error 128 | Get(ctx context.Context, in *GetRequest, out *GetRespond) error 129 | List(ctx context.Context, in *ListRequest, out *ListRespond) error 130 | Auth(ctx context.Context, in *AuthRequest, out *AuthRespond) error 131 | } 132 | type User struct { 133 | user 134 | } 135 | h := &userHandler{hdlr} 136 | return s.Handle(s.NewHandler(&User{h}, opts...)) 137 | } 138 | 139 | type userHandler struct { 140 | UserHandler 141 | } 142 | 143 | func (h *userHandler) Register(ctx context.Context, in *RegisterRequest, out *RegisterRespond) error { 144 | return h.UserHandler.Register(ctx, in, out) 145 | } 146 | 147 | func (h *userHandler) Login(ctx context.Context, in *LoginRequest, out *LoginRespond) error { 148 | return h.UserHandler.Login(ctx, in, out) 149 | } 150 | 151 | func (h *userHandler) Get(ctx context.Context, in *GetRequest, out *GetRespond) error { 152 | return h.UserHandler.Get(ctx, in, out) 153 | } 154 | 155 | func (h *userHandler) List(ctx context.Context, in *ListRequest, out *ListRespond) error { 156 | return h.UserHandler.List(ctx, in, out) 157 | } 158 | 159 | func (h *userHandler) Auth(ctx context.Context, in *AuthRequest, out *AuthRespond) error { 160 | return h.UserHandler.Auth(ctx, in, out) 161 | } 162 | -------------------------------------------------------------------------------- /app/service/web/proto/v1/api.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: api.proto 3 | 4 | //protoc --proto_path=. --micro_out=. --go_out=. ./api.proto 5 | 6 | package micro_srv_cheetah_web 7 | 8 | import ( 9 | fmt "fmt" 10 | proto "github.com/golang/protobuf/proto" 11 | math "math" 12 | ) 13 | 14 | // Reference imports to suppress errors if they are not otherwise used. 15 | var _ = proto.Marshal 16 | var _ = fmt.Errorf 17 | var _ = math.Inf 18 | 19 | // This is a compile-time assertion to ensure that this generated file 20 | // is compatible with the proto package it is being compiled against. 21 | // A compilation error at this line likely means your copy of the 22 | // proto package needs to be updated. 23 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 24 | 25 | type RegisterRequest struct { 26 | Username string `protobuf:"bytes,1,opt,name=Username,proto3" json:"Username,omitempty"` 27 | Password string `protobuf:"bytes,2,opt,name=Password,proto3" json:"Password,omitempty"` 28 | Age int32 `protobuf:"varint,3,opt,name=Age,proto3" json:"Age,omitempty"` 29 | Addr string `protobuf:"bytes,4,opt,name=Addr,proto3" json:"Addr,omitempty"` 30 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 31 | XXX_unrecognized []byte `json:"-"` 32 | XXX_sizecache int32 `json:"-"` 33 | } 34 | 35 | func (m *RegisterRequest) Reset() { *m = RegisterRequest{} } 36 | func (m *RegisterRequest) String() string { return proto.CompactTextString(m) } 37 | func (*RegisterRequest) ProtoMessage() {} 38 | func (*RegisterRequest) Descriptor() ([]byte, []int) { 39 | return fileDescriptor_00212fb1f9d3bf1c, []int{0} 40 | } 41 | 42 | func (m *RegisterRequest) XXX_Unmarshal(b []byte) error { 43 | return xxx_messageInfo_RegisterRequest.Unmarshal(m, b) 44 | } 45 | func (m *RegisterRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 46 | return xxx_messageInfo_RegisterRequest.Marshal(b, m, deterministic) 47 | } 48 | func (m *RegisterRequest) XXX_Merge(src proto.Message) { 49 | xxx_messageInfo_RegisterRequest.Merge(m, src) 50 | } 51 | func (m *RegisterRequest) XXX_Size() int { 52 | return xxx_messageInfo_RegisterRequest.Size(m) 53 | } 54 | func (m *RegisterRequest) XXX_DiscardUnknown() { 55 | xxx_messageInfo_RegisterRequest.DiscardUnknown(m) 56 | } 57 | 58 | var xxx_messageInfo_RegisterRequest proto.InternalMessageInfo 59 | 60 | func (m *RegisterRequest) GetUsername() string { 61 | if m != nil { 62 | return m.Username 63 | } 64 | return "" 65 | } 66 | 67 | func (m *RegisterRequest) GetPassword() string { 68 | if m != nil { 69 | return m.Password 70 | } 71 | return "" 72 | } 73 | 74 | func (m *RegisterRequest) GetAge() int32 { 75 | if m != nil { 76 | return m.Age 77 | } 78 | return 0 79 | } 80 | 81 | func (m *RegisterRequest) GetAddr() string { 82 | if m != nil { 83 | return m.Addr 84 | } 85 | return "" 86 | } 87 | 88 | type RegisterRespond struct { 89 | Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` 90 | Uid string `protobuf:"bytes,2,opt,name=Uid,proto3" json:"Uid,omitempty"` 91 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 92 | XXX_unrecognized []byte `json:"-"` 93 | XXX_sizecache int32 `json:"-"` 94 | } 95 | 96 | func (m *RegisterRespond) Reset() { *m = RegisterRespond{} } 97 | func (m *RegisterRespond) String() string { return proto.CompactTextString(m) } 98 | func (*RegisterRespond) ProtoMessage() {} 99 | func (*RegisterRespond) Descriptor() ([]byte, []int) { 100 | return fileDescriptor_00212fb1f9d3bf1c, []int{1} 101 | } 102 | 103 | func (m *RegisterRespond) XXX_Unmarshal(b []byte) error { 104 | return xxx_messageInfo_RegisterRespond.Unmarshal(m, b) 105 | } 106 | func (m *RegisterRespond) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 107 | return xxx_messageInfo_RegisterRespond.Marshal(b, m, deterministic) 108 | } 109 | func (m *RegisterRespond) XXX_Merge(src proto.Message) { 110 | xxx_messageInfo_RegisterRespond.Merge(m, src) 111 | } 112 | func (m *RegisterRespond) XXX_Size() int { 113 | return xxx_messageInfo_RegisterRespond.Size(m) 114 | } 115 | func (m *RegisterRespond) XXX_DiscardUnknown() { 116 | xxx_messageInfo_RegisterRespond.DiscardUnknown(m) 117 | } 118 | 119 | var xxx_messageInfo_RegisterRespond proto.InternalMessageInfo 120 | 121 | func (m *RegisterRespond) GetCode() int32 { 122 | if m != nil { 123 | return m.Code 124 | } 125 | return 0 126 | } 127 | 128 | func (m *RegisterRespond) GetUid() string { 129 | if m != nil { 130 | return m.Uid 131 | } 132 | return "" 133 | } 134 | 135 | type LoginRequest struct { 136 | Username string `protobuf:"bytes,1,opt,name=Username,proto3" json:"Username,omitempty"` 137 | Password string `protobuf:"bytes,2,opt,name=Password,proto3" json:"Password,omitempty"` 138 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 139 | XXX_unrecognized []byte `json:"-"` 140 | XXX_sizecache int32 `json:"-"` 141 | } 142 | 143 | func (m *LoginRequest) Reset() { *m = LoginRequest{} } 144 | func (m *LoginRequest) String() string { return proto.CompactTextString(m) } 145 | func (*LoginRequest) ProtoMessage() {} 146 | func (*LoginRequest) Descriptor() ([]byte, []int) { 147 | return fileDescriptor_00212fb1f9d3bf1c, []int{2} 148 | } 149 | 150 | func (m *LoginRequest) XXX_Unmarshal(b []byte) error { 151 | return xxx_messageInfo_LoginRequest.Unmarshal(m, b) 152 | } 153 | func (m *LoginRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 154 | return xxx_messageInfo_LoginRequest.Marshal(b, m, deterministic) 155 | } 156 | func (m *LoginRequest) XXX_Merge(src proto.Message) { 157 | xxx_messageInfo_LoginRequest.Merge(m, src) 158 | } 159 | func (m *LoginRequest) XXX_Size() int { 160 | return xxx_messageInfo_LoginRequest.Size(m) 161 | } 162 | func (m *LoginRequest) XXX_DiscardUnknown() { 163 | xxx_messageInfo_LoginRequest.DiscardUnknown(m) 164 | } 165 | 166 | var xxx_messageInfo_LoginRequest proto.InternalMessageInfo 167 | 168 | func (m *LoginRequest) GetUsername() string { 169 | if m != nil { 170 | return m.Username 171 | } 172 | return "" 173 | } 174 | 175 | func (m *LoginRequest) GetPassword() string { 176 | if m != nil { 177 | return m.Password 178 | } 179 | return "" 180 | } 181 | 182 | type LoginRespond struct { 183 | Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` 184 | Token string `protobuf:"bytes,2,opt,name=Token,proto3" json:"Token,omitempty"` 185 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 186 | XXX_unrecognized []byte `json:"-"` 187 | XXX_sizecache int32 `json:"-"` 188 | } 189 | 190 | func (m *LoginRespond) Reset() { *m = LoginRespond{} } 191 | func (m *LoginRespond) String() string { return proto.CompactTextString(m) } 192 | func (*LoginRespond) ProtoMessage() {} 193 | func (*LoginRespond) Descriptor() ([]byte, []int) { 194 | return fileDescriptor_00212fb1f9d3bf1c, []int{3} 195 | } 196 | 197 | func (m *LoginRespond) XXX_Unmarshal(b []byte) error { 198 | return xxx_messageInfo_LoginRespond.Unmarshal(m, b) 199 | } 200 | func (m *LoginRespond) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 201 | return xxx_messageInfo_LoginRespond.Marshal(b, m, deterministic) 202 | } 203 | func (m *LoginRespond) XXX_Merge(src proto.Message) { 204 | xxx_messageInfo_LoginRespond.Merge(m, src) 205 | } 206 | func (m *LoginRespond) XXX_Size() int { 207 | return xxx_messageInfo_LoginRespond.Size(m) 208 | } 209 | func (m *LoginRespond) XXX_DiscardUnknown() { 210 | xxx_messageInfo_LoginRespond.DiscardUnknown(m) 211 | } 212 | 213 | var xxx_messageInfo_LoginRespond proto.InternalMessageInfo 214 | 215 | func (m *LoginRespond) GetCode() int32 { 216 | if m != nil { 217 | return m.Code 218 | } 219 | return 0 220 | } 221 | 222 | func (m *LoginRespond) GetToken() string { 223 | if m != nil { 224 | return m.Token 225 | } 226 | return "" 227 | } 228 | 229 | type GetRequest struct { 230 | Uid string `protobuf:"bytes,1,opt,name=Uid,proto3" json:"Uid,omitempty"` 231 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 232 | XXX_unrecognized []byte `json:"-"` 233 | XXX_sizecache int32 `json:"-"` 234 | } 235 | 236 | func (m *GetRequest) Reset() { *m = GetRequest{} } 237 | func (m *GetRequest) String() string { return proto.CompactTextString(m) } 238 | func (*GetRequest) ProtoMessage() {} 239 | func (*GetRequest) Descriptor() ([]byte, []int) { 240 | return fileDescriptor_00212fb1f9d3bf1c, []int{4} 241 | } 242 | 243 | func (m *GetRequest) XXX_Unmarshal(b []byte) error { 244 | return xxx_messageInfo_GetRequest.Unmarshal(m, b) 245 | } 246 | func (m *GetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 247 | return xxx_messageInfo_GetRequest.Marshal(b, m, deterministic) 248 | } 249 | func (m *GetRequest) XXX_Merge(src proto.Message) { 250 | xxx_messageInfo_GetRequest.Merge(m, src) 251 | } 252 | func (m *GetRequest) XXX_Size() int { 253 | return xxx_messageInfo_GetRequest.Size(m) 254 | } 255 | func (m *GetRequest) XXX_DiscardUnknown() { 256 | xxx_messageInfo_GetRequest.DiscardUnknown(m) 257 | } 258 | 259 | var xxx_messageInfo_GetRequest proto.InternalMessageInfo 260 | 261 | func (m *GetRequest) GetUid() string { 262 | if m != nil { 263 | return m.Uid 264 | } 265 | return "" 266 | } 267 | 268 | type GetRespond struct { 269 | Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` 270 | Username string `protobuf:"bytes,2,opt,name=Username,proto3" json:"Username,omitempty"` 271 | Age int32 `protobuf:"varint,3,opt,name=Age,proto3" json:"Age,omitempty"` 272 | Addr string `protobuf:"bytes,4,opt,name=Addr,proto3" json:"Addr,omitempty"` 273 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 274 | XXX_unrecognized []byte `json:"-"` 275 | XXX_sizecache int32 `json:"-"` 276 | } 277 | 278 | func (m *GetRespond) Reset() { *m = GetRespond{} } 279 | func (m *GetRespond) String() string { return proto.CompactTextString(m) } 280 | func (*GetRespond) ProtoMessage() {} 281 | func (*GetRespond) Descriptor() ([]byte, []int) { 282 | return fileDescriptor_00212fb1f9d3bf1c, []int{5} 283 | } 284 | 285 | func (m *GetRespond) XXX_Unmarshal(b []byte) error { 286 | return xxx_messageInfo_GetRespond.Unmarshal(m, b) 287 | } 288 | func (m *GetRespond) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 289 | return xxx_messageInfo_GetRespond.Marshal(b, m, deterministic) 290 | } 291 | func (m *GetRespond) XXX_Merge(src proto.Message) { 292 | xxx_messageInfo_GetRespond.Merge(m, src) 293 | } 294 | func (m *GetRespond) XXX_Size() int { 295 | return xxx_messageInfo_GetRespond.Size(m) 296 | } 297 | func (m *GetRespond) XXX_DiscardUnknown() { 298 | xxx_messageInfo_GetRespond.DiscardUnknown(m) 299 | } 300 | 301 | var xxx_messageInfo_GetRespond proto.InternalMessageInfo 302 | 303 | func (m *GetRespond) GetCode() int32 { 304 | if m != nil { 305 | return m.Code 306 | } 307 | return 0 308 | } 309 | 310 | func (m *GetRespond) GetUsername() string { 311 | if m != nil { 312 | return m.Username 313 | } 314 | return "" 315 | } 316 | 317 | func (m *GetRespond) GetAge() int32 { 318 | if m != nil { 319 | return m.Age 320 | } 321 | return 0 322 | } 323 | 324 | func (m *GetRespond) GetAddr() string { 325 | if m != nil { 326 | return m.Addr 327 | } 328 | return "" 329 | } 330 | 331 | type ListRequest struct { 332 | Page int32 `protobuf:"varint,1,opt,name=Page,proto3" json:"Page,omitempty"` 333 | PageSize int32 `protobuf:"varint,2,opt,name=PageSize,proto3" json:"PageSize,omitempty"` 334 | Username string `protobuf:"bytes,3,opt,name=Username,proto3" json:"Username,omitempty"` 335 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 336 | XXX_unrecognized []byte `json:"-"` 337 | XXX_sizecache int32 `json:"-"` 338 | } 339 | 340 | func (m *ListRequest) Reset() { *m = ListRequest{} } 341 | func (m *ListRequest) String() string { return proto.CompactTextString(m) } 342 | func (*ListRequest) ProtoMessage() {} 343 | func (*ListRequest) Descriptor() ([]byte, []int) { 344 | return fileDescriptor_00212fb1f9d3bf1c, []int{6} 345 | } 346 | 347 | func (m *ListRequest) XXX_Unmarshal(b []byte) error { 348 | return xxx_messageInfo_ListRequest.Unmarshal(m, b) 349 | } 350 | func (m *ListRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 351 | return xxx_messageInfo_ListRequest.Marshal(b, m, deterministic) 352 | } 353 | func (m *ListRequest) XXX_Merge(src proto.Message) { 354 | xxx_messageInfo_ListRequest.Merge(m, src) 355 | } 356 | func (m *ListRequest) XXX_Size() int { 357 | return xxx_messageInfo_ListRequest.Size(m) 358 | } 359 | func (m *ListRequest) XXX_DiscardUnknown() { 360 | xxx_messageInfo_ListRequest.DiscardUnknown(m) 361 | } 362 | 363 | var xxx_messageInfo_ListRequest proto.InternalMessageInfo 364 | 365 | func (m *ListRequest) GetPage() int32 { 366 | if m != nil { 367 | return m.Page 368 | } 369 | return 0 370 | } 371 | 372 | func (m *ListRequest) GetPageSize() int32 { 373 | if m != nil { 374 | return m.PageSize 375 | } 376 | return 0 377 | } 378 | 379 | func (m *ListRequest) GetUsername() string { 380 | if m != nil { 381 | return m.Username 382 | } 383 | return "" 384 | } 385 | 386 | type One struct { 387 | Username string `protobuf:"bytes,1,opt,name=Username,proto3" json:"Username,omitempty"` 388 | Age int32 `protobuf:"varint,2,opt,name=Age,proto3" json:"Age,omitempty"` 389 | Addr string `protobuf:"bytes,3,opt,name=Addr,proto3" json:"Addr,omitempty"` 390 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 391 | XXX_unrecognized []byte `json:"-"` 392 | XXX_sizecache int32 `json:"-"` 393 | } 394 | 395 | func (m *One) Reset() { *m = One{} } 396 | func (m *One) String() string { return proto.CompactTextString(m) } 397 | func (*One) ProtoMessage() {} 398 | func (*One) Descriptor() ([]byte, []int) { 399 | return fileDescriptor_00212fb1f9d3bf1c, []int{7} 400 | } 401 | 402 | func (m *One) XXX_Unmarshal(b []byte) error { 403 | return xxx_messageInfo_One.Unmarshal(m, b) 404 | } 405 | func (m *One) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 406 | return xxx_messageInfo_One.Marshal(b, m, deterministic) 407 | } 408 | func (m *One) XXX_Merge(src proto.Message) { 409 | xxx_messageInfo_One.Merge(m, src) 410 | } 411 | func (m *One) XXX_Size() int { 412 | return xxx_messageInfo_One.Size(m) 413 | } 414 | func (m *One) XXX_DiscardUnknown() { 415 | xxx_messageInfo_One.DiscardUnknown(m) 416 | } 417 | 418 | var xxx_messageInfo_One proto.InternalMessageInfo 419 | 420 | func (m *One) GetUsername() string { 421 | if m != nil { 422 | return m.Username 423 | } 424 | return "" 425 | } 426 | 427 | func (m *One) GetAge() int32 { 428 | if m != nil { 429 | return m.Age 430 | } 431 | return 0 432 | } 433 | 434 | func (m *One) GetAddr() string { 435 | if m != nil { 436 | return m.Addr 437 | } 438 | return "" 439 | } 440 | 441 | type ListRespond struct { 442 | Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` 443 | Count int32 `protobuf:"varint,2,opt,name=Count,proto3" json:"Count,omitempty"` 444 | All map[int32]*One `protobuf:"bytes,3,rep,name=All,proto3" json:"All,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 445 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 446 | XXX_unrecognized []byte `json:"-"` 447 | XXX_sizecache int32 `json:"-"` 448 | } 449 | 450 | func (m *ListRespond) Reset() { *m = ListRespond{} } 451 | func (m *ListRespond) String() string { return proto.CompactTextString(m) } 452 | func (*ListRespond) ProtoMessage() {} 453 | func (*ListRespond) Descriptor() ([]byte, []int) { 454 | return fileDescriptor_00212fb1f9d3bf1c, []int{8} 455 | } 456 | 457 | func (m *ListRespond) XXX_Unmarshal(b []byte) error { 458 | return xxx_messageInfo_ListRespond.Unmarshal(m, b) 459 | } 460 | func (m *ListRespond) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 461 | return xxx_messageInfo_ListRespond.Marshal(b, m, deterministic) 462 | } 463 | func (m *ListRespond) XXX_Merge(src proto.Message) { 464 | xxx_messageInfo_ListRespond.Merge(m, src) 465 | } 466 | func (m *ListRespond) XXX_Size() int { 467 | return xxx_messageInfo_ListRespond.Size(m) 468 | } 469 | func (m *ListRespond) XXX_DiscardUnknown() { 470 | xxx_messageInfo_ListRespond.DiscardUnknown(m) 471 | } 472 | 473 | var xxx_messageInfo_ListRespond proto.InternalMessageInfo 474 | 475 | func (m *ListRespond) GetCode() int32 { 476 | if m != nil { 477 | return m.Code 478 | } 479 | return 0 480 | } 481 | 482 | func (m *ListRespond) GetCount() int32 { 483 | if m != nil { 484 | return m.Count 485 | } 486 | return 0 487 | } 488 | 489 | func (m *ListRespond) GetAll() map[int32]*One { 490 | if m != nil { 491 | return m.All 492 | } 493 | return nil 494 | } 495 | 496 | type AuthRequest struct { 497 | Token string `protobuf:"bytes,1,opt,name=Token,proto3" json:"Token,omitempty"` 498 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 499 | XXX_unrecognized []byte `json:"-"` 500 | XXX_sizecache int32 `json:"-"` 501 | } 502 | 503 | func (m *AuthRequest) Reset() { *m = AuthRequest{} } 504 | func (m *AuthRequest) String() string { return proto.CompactTextString(m) } 505 | func (*AuthRequest) ProtoMessage() {} 506 | func (*AuthRequest) Descriptor() ([]byte, []int) { 507 | return fileDescriptor_00212fb1f9d3bf1c, []int{9} 508 | } 509 | 510 | func (m *AuthRequest) XXX_Unmarshal(b []byte) error { 511 | return xxx_messageInfo_AuthRequest.Unmarshal(m, b) 512 | } 513 | func (m *AuthRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 514 | return xxx_messageInfo_AuthRequest.Marshal(b, m, deterministic) 515 | } 516 | func (m *AuthRequest) XXX_Merge(src proto.Message) { 517 | xxx_messageInfo_AuthRequest.Merge(m, src) 518 | } 519 | func (m *AuthRequest) XXX_Size() int { 520 | return xxx_messageInfo_AuthRequest.Size(m) 521 | } 522 | func (m *AuthRequest) XXX_DiscardUnknown() { 523 | xxx_messageInfo_AuthRequest.DiscardUnknown(m) 524 | } 525 | 526 | var xxx_messageInfo_AuthRequest proto.InternalMessageInfo 527 | 528 | func (m *AuthRequest) GetToken() string { 529 | if m != nil { 530 | return m.Token 531 | } 532 | return "" 533 | } 534 | 535 | type AuthRespond struct { 536 | Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"` 537 | Uid string `protobuf:"bytes,2,opt,name=Uid,proto3" json:"Uid,omitempty"` 538 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 539 | XXX_unrecognized []byte `json:"-"` 540 | XXX_sizecache int32 `json:"-"` 541 | } 542 | 543 | func (m *AuthRespond) Reset() { *m = AuthRespond{} } 544 | func (m *AuthRespond) String() string { return proto.CompactTextString(m) } 545 | func (*AuthRespond) ProtoMessage() {} 546 | func (*AuthRespond) Descriptor() ([]byte, []int) { 547 | return fileDescriptor_00212fb1f9d3bf1c, []int{10} 548 | } 549 | 550 | func (m *AuthRespond) XXX_Unmarshal(b []byte) error { 551 | return xxx_messageInfo_AuthRespond.Unmarshal(m, b) 552 | } 553 | func (m *AuthRespond) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 554 | return xxx_messageInfo_AuthRespond.Marshal(b, m, deterministic) 555 | } 556 | func (m *AuthRespond) XXX_Merge(src proto.Message) { 557 | xxx_messageInfo_AuthRespond.Merge(m, src) 558 | } 559 | func (m *AuthRespond) XXX_Size() int { 560 | return xxx_messageInfo_AuthRespond.Size(m) 561 | } 562 | func (m *AuthRespond) XXX_DiscardUnknown() { 563 | xxx_messageInfo_AuthRespond.DiscardUnknown(m) 564 | } 565 | 566 | var xxx_messageInfo_AuthRespond proto.InternalMessageInfo 567 | 568 | func (m *AuthRespond) GetCode() int32 { 569 | if m != nil { 570 | return m.Code 571 | } 572 | return 0 573 | } 574 | 575 | func (m *AuthRespond) GetUid() string { 576 | if m != nil { 577 | return m.Uid 578 | } 579 | return "" 580 | } 581 | 582 | func init() { 583 | proto.RegisterType((*RegisterRequest)(nil), "micro.srv.cheetah.web.RegisterRequest") 584 | proto.RegisterType((*RegisterRespond)(nil), "micro.srv.cheetah.web.RegisterRespond") 585 | proto.RegisterType((*LoginRequest)(nil), "micro.srv.cheetah.web.LoginRequest") 586 | proto.RegisterType((*LoginRespond)(nil), "micro.srv.cheetah.web.LoginRespond") 587 | proto.RegisterType((*GetRequest)(nil), "micro.srv.cheetah.web.GetRequest") 588 | proto.RegisterType((*GetRespond)(nil), "micro.srv.cheetah.web.GetRespond") 589 | proto.RegisterType((*ListRequest)(nil), "micro.srv.cheetah.web.ListRequest") 590 | proto.RegisterType((*One)(nil), "micro.srv.cheetah.web.One") 591 | proto.RegisterType((*ListRespond)(nil), "micro.srv.cheetah.web.ListRespond") 592 | proto.RegisterMapType((map[int32]*One)(nil), "micro.srv.cheetah.web.ListRespond.AllEntry") 593 | proto.RegisterType((*AuthRequest)(nil), "micro.srv.cheetah.web.AuthRequest") 594 | proto.RegisterType((*AuthRespond)(nil), "micro.srv.cheetah.web.AuthRespond") 595 | } 596 | 597 | func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) } 598 | 599 | var fileDescriptor_00212fb1f9d3bf1c = []byte{ 600 | // 475 bytes of a gzipped FileDescriptorProto 601 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x94, 0x51, 0x6b, 0xd4, 0x40, 602 | 0x10, 0xc7, 0xcd, 0x6d, 0x22, 0xd7, 0x39, 0x41, 0x59, 0x14, 0x42, 0x1e, 0xe4, 0xdc, 0x82, 0x1c, 603 | 0x08, 0x41, 0xae, 0x0f, 0x16, 0xc1, 0x87, 0x50, 0xf4, 0x5e, 0x2a, 0x3d, 0xb6, 0xf6, 0x41, 0xf0, 604 | 0x25, 0xd7, 0x0c, 0xb9, 0xd0, 0x34, 0x7b, 0xee, 0xee, 0xb5, 0xd4, 0xcf, 0xe4, 0x27, 0xf1, 0x53, 605 | 0xc9, 0x66, 0xb3, 0x69, 0x5a, 0x4d, 0x4e, 0xe9, 0xdb, 0xcc, 0xed, 0x7f, 0xff, 0xf3, 0xbb, 0xd9, 606 | 0x99, 0xc0, 0x5e, 0xba, 0x29, 0xe2, 0x8d, 0x14, 0x5a, 0xd0, 0x17, 0x97, 0xc5, 0xb9, 0x14, 0xb1, 607 | 0x92, 0x57, 0xf1, 0xf9, 0x1a, 0x51, 0xa7, 0xeb, 0xf8, 0x1a, 0x57, 0x4c, 0xc0, 0x53, 0x8e, 0x79, 608 | 0xa1, 0x34, 0x4a, 0x8e, 0xdf, 0xb7, 0xa8, 0x34, 0x8d, 0x60, 0x7c, 0xa6, 0x50, 0x56, 0xe9, 0x25, 609 | 0x86, 0xde, 0xd4, 0x9b, 0xed, 0xf1, 0x36, 0x37, 0x67, 0xcb, 0x54, 0xa9, 0x6b, 0x21, 0xb3, 0x70, 610 | 0x64, 0xcf, 0x5c, 0x4e, 0x9f, 0x01, 0x49, 0x72, 0x0c, 0xc9, 0xd4, 0x9b, 0x05, 0xdc, 0x84, 0x94, 611 | 0x82, 0x9f, 0x64, 0x99, 0x0c, 0xfd, 0x5a, 0x59, 0xc7, 0xec, 0x5d, 0xb7, 0xa0, 0xda, 0x88, 0x2a, 612 | 0x33, 0xb2, 0x23, 0x91, 0xd9, 0x62, 0x01, 0xaf, 0x63, 0x63, 0x76, 0x56, 0xb8, 0x1a, 0x26, 0x64, 613 | 0x9f, 0xe0, 0xc9, 0xb1, 0xc8, 0x8b, 0xea, 0x81, 0x98, 0xec, 0xb0, 0xf5, 0xe9, 0xaf, 0xfe, 0x1c, 614 | 0x82, 0x2f, 0xe2, 0x02, 0xab, 0xe6, 0xb2, 0x4d, 0xd8, 0x4b, 0x80, 0x05, 0x6a, 0x57, 0xbf, 0x21, 615 | 0xf4, 0x6e, 0x09, 0x57, 0xcd, 0x79, 0xbf, 0x6f, 0x97, 0x79, 0x74, 0x8f, 0xf9, 0xdf, 0xda, 0xf7, 616 | 0x15, 0x26, 0xc7, 0x85, 0x6a, 0x21, 0x28, 0xf8, 0xcb, 0x34, 0x6f, 0x8b, 0x98, 0xd8, 0xfe, 0xf9, 617 | 0x1c, 0x4f, 0x8b, 0x1f, 0xb6, 0x48, 0xc0, 0xdb, 0xfc, 0x0e, 0x00, 0xb9, 0x0b, 0xc0, 0x16, 0x40, 618 | 0x4e, 0x2a, 0x1c, 0xec, 0x6b, 0xc3, 0x38, 0xfa, 0x93, 0x91, 0x74, 0x18, 0x7f, 0x79, 0x0e, 0x72, 619 | 0xb0, 0xc3, 0x47, 0x62, 0x5b, 0xe9, 0xc6, 0xcb, 0x26, 0xf4, 0x03, 0x90, 0xa4, 0x2c, 0x43, 0x32, 620 | 0x25, 0xb3, 0xc9, 0xfc, 0x4d, 0xfc, 0xd7, 0x91, 0x8d, 0x3b, 0xd6, 0x71, 0x52, 0x96, 0x1f, 0x2b, 621 | 0x2d, 0x6f, 0xb8, 0xb9, 0x17, 0x71, 0x18, 0xbb, 0x1f, 0x0c, 0xea, 0x05, 0xde, 0x34, 0x35, 0x4d, 622 | 0x48, 0xdf, 0x42, 0x70, 0x95, 0x96, 0x5b, 0x8b, 0x3f, 0x99, 0x47, 0x3d, 0xf6, 0x27, 0x15, 0x72, 623 | 0x2b, 0x7c, 0x3f, 0x3a, 0xf4, 0xd8, 0x3e, 0x4c, 0x92, 0xad, 0x5e, 0xbb, 0x86, 0xb7, 0x93, 0xe1, 624 | 0x75, 0x27, 0xe3, 0xc0, 0x89, 0xfe, 0x63, 0xa0, 0xe7, 0x3f, 0x09, 0xf8, 0xa6, 0xb3, 0xf4, 0x1b, 625 | 0x8c, 0xdd, 0x4a, 0xd0, 0xd7, 0x3d, 0x54, 0xf7, 0x96, 0x34, 0xda, 0xad, 0xab, 0x51, 0xd8, 0x23, 626 | 0x7a, 0x0a, 0x41, 0x3d, 0xef, 0x74, 0xbf, 0xaf, 0x9f, 0x9d, 0xad, 0x8a, 0x76, 0x88, 0x9c, 0xe9, 627 | 0x67, 0x20, 0x0b, 0xd4, 0xf4, 0x55, 0x8f, 0xfa, 0x76, 0x4d, 0xa2, 0x41, 0x89, 0xb3, 0x5b, 0x82, 628 | 0x6f, 0x5e, 0x95, 0xb2, 0xc1, 0x27, 0xb7, 0x86, 0x6c, 0xf7, 0x58, 0x58, 0x47, 0xf3, 0x22, 0xbd, 629 | 0x8e, 0x9d, 0x37, 0x8d, 0x86, 0x35, 0x8d, 0xe3, 0xea, 0x71, 0xfd, 0x1d, 0x3d, 0xf8, 0x1d, 0x00, 630 | 0x00, 0xff, 0xff, 0x8a, 0x80, 0x1c, 0x72, 0x54, 0x05, 0x00, 0x00, 631 | } 632 | -------------------------------------------------------------------------------- /app/service/web/proto/v1/api.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | //protoc --proto_path=. --micro_out=. --go_out=. ./api.proto 4 | package micro.srv.cheetah.web; 5 | 6 | message RegisterRequest { 7 | string Username = 1; 8 | string Password = 2; 9 | int32 Age = 3; 10 | string Addr = 4; 11 | } 12 | 13 | message RegisterRespond { 14 | int32 Code = 1; 15 | string Uid = 2; 16 | } 17 | 18 | message LoginRequest { 19 | string Username = 1; 20 | string Password = 2; 21 | } 22 | 23 | message LoginRespond { 24 | int32 Code = 1; 25 | string Token = 2; 26 | } 27 | 28 | message GetRequest { 29 | string Uid = 1; 30 | } 31 | 32 | message GetRespond { 33 | int32 Code = 1; 34 | string Username = 2; 35 | int32 Age = 3; 36 | string Addr = 4; 37 | } 38 | 39 | message ListRequest { 40 | int32 Page = 1; 41 | int32 PageSize = 2; 42 | string Username = 3; 43 | } 44 | 45 | message One { 46 | string Username = 1; 47 | int32 Age = 2; 48 | string Addr = 3; 49 | } 50 | message ListRespond { 51 | int32 Code = 1; 52 | int32 Count = 2; 53 | map All = 3; 54 | } 55 | 56 | message AuthRequest { 57 | string Token = 1; 58 | } 59 | 60 | message AuthRespond { 61 | int32 Code = 1; 62 | string Uid = 2; 63 | } 64 | 65 | service User { 66 | rpc Register(RegisterRequest) returns (RegisterRespond) {} 67 | rpc Login(LoginRequest) returns (LoginRespond) {} 68 | rpc Get(GetRequest) returns (GetRespond) {} 69 | rpc List(ListRequest) returns (ListRespond) {} 70 | rpc Auth(AuthRequest) returns (AuthRespond) {} 71 | } 72 | -------------------------------------------------------------------------------- /app/service/web/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd cmd && \ 4 | go run main.go --registry=etcd --registry_address=172.16.7.16:9002,172.16.7.16:9004,172.16.7.16:9006 5 | -------------------------------------------------------------------------------- /app/service/web/server/init.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/micro/go-micro/registry" 7 | "github.com/micro/go-plugins/registry/etcdv3" 8 | 9 | "github.com/liangjfblue/cheetah/app/service/web/config" 10 | 11 | "github.com/liangjfblue/cheetah/common/configs" 12 | 13 | "github.com/liangjfblue/gglog" 14 | 15 | ot "github.com/micro/go-plugins/wrapper/trace/opentracing" 16 | "github.com/opentracing/opentracing-go" 17 | 18 | authv1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 19 | 20 | "github.com/liangjfblue/cheetah/app/service/web/model" 21 | 22 | "github.com/liangjfblue/cheetah/common/tracer" 23 | 24 | authSrv "github.com/liangjfblue/cheetah/app/service/web/service" 25 | "github.com/micro/go-micro" 26 | "github.com/micro/go-micro/server" 27 | 28 | "github.com/liangjfblue/cheetah/common/logger" 29 | ) 30 | 31 | type Server struct { 32 | serviceName string 33 | serviceVersion string 34 | 35 | service micro.Service 36 | Tracer *tracer.Tracer 37 | } 38 | 39 | func NewServer(serviceName, serviceVersion string) *Server { 40 | s := new(Server) 41 | 42 | s.serviceName = serviceName 43 | s.serviceVersion = serviceVersion 44 | 45 | s.Tracer = tracer.New(configs.TraceAddr, s.serviceName) 46 | 47 | return s 48 | } 49 | 50 | func (s *Server) Init() { 51 | config.Init() 52 | 53 | logger.Init( 54 | gglog.Name(config.ConfigInstance().LogConf.Name), 55 | gglog.Level(config.ConfigInstance().LogConf.Level), 56 | gglog.LogDir(config.ConfigInstance().LogConf.LogDir), 57 | gglog.OpenAccessLog(config.ConfigInstance().LogConf.OpenAccessLog), 58 | ) 59 | 60 | model.Init() 61 | 62 | s.Tracer.Init() 63 | 64 | reg := etcdv3.NewRegistry(func(op *registry.Options) { 65 | op.Addrs = config.ConfigInstance().EtcdConf.Addrs 66 | op.Timeout = time.Duration(config.ConfigInstance().EtcdConf.Timeout) * time.Second 67 | }) 68 | 69 | s.service = micro.NewService( 70 | micro.Name(s.serviceName), 71 | micro.Version(s.serviceVersion), 72 | micro.RegisterTTL(time.Second*30), 73 | micro.RegisterInterval(time.Second*15), 74 | micro.WrapClient(ot.NewClientWrapper(opentracing.GlobalTracer())), 75 | micro.WrapHandler(ot.NewHandlerWrapper(opentracing.GlobalTracer())), 76 | micro.Registry(reg), 77 | ) 78 | 79 | s.service.Init() 80 | 81 | s.initRegisterHandler() 82 | } 83 | 84 | func (s *Server) initRegisterHandler() { 85 | srv := &authSrv.UserService{} 86 | if err := authv1.RegisterUserHandler(s.service.Server(), srv, server.InternalHandler(true)); err != nil { 87 | logger.Error("service web err: %s", err.Error()) 88 | return 89 | } 90 | } 91 | 92 | func (s *Server) Run() { 93 | defer func() { 94 | logger.Info("srv web close, clean and close something") 95 | s.Tracer.Close() 96 | }() 97 | 98 | logger.Debug("service web server run") 99 | if err := s.service.Run(); err != nil { 100 | logger.Error("service web err: %s", err.Error()) 101 | return 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/service/web/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/liangjfblue/cheetah/common/logger" 9 | 10 | v1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 11 | 12 | "github.com/liangjfblue/cheetah/common/auth" 13 | "github.com/liangjfblue/cheetah/common/token" 14 | "google.golang.org/grpc/codes" 15 | "google.golang.org/grpc/status" 16 | 17 | "github.com/liangjfblue/cheetah/common/uuid" 18 | 19 | "github.com/jinzhu/gorm" 20 | "github.com/liangjfblue/cheetah/app/service/web/model" 21 | "github.com/pkg/errors" 22 | 23 | "github.com/liangjfblue/cheetah/common/errno" 24 | ) 25 | 26 | type UserService struct { 27 | } 28 | 29 | func (s *UserService) Register(ctx context.Context, in *v1.RegisterRequest, out *v1.RegisterRespond) error { 30 | fmt.Println("UserService Register") 31 | if ctx.Err() == context.Canceled { 32 | return ctx.Err() 33 | } 34 | 35 | if _, err := model.GetUser(&model.TBUser{Username: in.Username}); err != nil && !gorm.IsRecordNotFoundError(err) { 36 | logger.Error("service web: %s", err.Error()) 37 | return errors.Wrap(err, " service web") 38 | } 39 | 40 | user := model.TBUser{ 41 | Uid: uuid.UUID(), 42 | Username: in.Username, 43 | Password: in.Password, 44 | Age: in.Age, 45 | Address: in.Addr, 46 | IsAvailable: 1, 47 | LastLogin: time.Now(), 48 | } 49 | 50 | if err := user.Validate(); err != nil { 51 | logger.Error("service web: %s", err.Error()) 52 | return errors.Wrap(err, " service web") 53 | } 54 | 55 | if err := user.Encrypt(); err != nil { 56 | logger.Error("service web: %s", err.Error()) 57 | return errors.Wrap(err, " service web") 58 | } 59 | 60 | if err := user.Create(); err != nil { 61 | logger.Error("service web: %s", err.Error()) 62 | return errors.Wrap(err, " service web") 63 | } 64 | 65 | out.Code = errno.Success.Code 66 | out.Uid = user.Uid 67 | 68 | return nil 69 | } 70 | 71 | func (s *UserService) Login(ctx context.Context, in *v1.LoginRequest, out *v1.LoginRespond) error { 72 | if ctx.Err() == context.Canceled { 73 | return errors.Wrap(status.New(codes.Canceled, "Client cancelled, abandoning").Err(), "service web") 74 | } 75 | 76 | var ( 77 | err error 78 | user *model.TBUser 79 | tokenStr string 80 | ) 81 | 82 | user, err = model.GetUser(&model.TBUser{Username: in.Username}) 83 | if err != nil { 84 | logger.Error("service web: %s", err.Error()) 85 | return errors.Wrap(err, "service web") 86 | } 87 | 88 | if err = auth.Compare(user.Password, in.Password); err != nil { 89 | logger.Error("service web: %s", err.Error()) 90 | return errors.Wrap(err, "service web") 91 | } 92 | 93 | if user.IsAvailable != 1 { 94 | logger.Error("web unavailable") 95 | return errors.Wrap(errors.New("web unavailable"), "service web") 96 | } 97 | 98 | user.LastLogin = time.Now() 99 | if err = user.Update(); err != nil { 100 | logger.Error("service web: %s", err.Error()) 101 | return errors.Wrap(err, "service web") 102 | } 103 | 104 | tokenStr, err = token.SignToken(token.Context{Uid: user.Uid}) 105 | if err != nil { 106 | logger.Error("service web: %s", err.Error()) 107 | return errors.Wrap(err, "service web") 108 | } 109 | 110 | out.Code = errno.Success.Code 111 | out.Token = tokenStr 112 | return nil 113 | } 114 | 115 | func (s *UserService) Get(ctx context.Context, in *v1.GetRequest, out *v1.GetRespond) error { 116 | fmt.Println("UserService Get") 117 | var ( 118 | err error 119 | user *model.TBUser 120 | ) 121 | 122 | if ctx.Err() == context.Canceled { 123 | return errors.Wrap(status.New(codes.Canceled, "Client cancelled, abandoning").Err(), "service web") 124 | } 125 | 126 | user, err = model.GetUser(&model.TBUser{Uid: in.Uid}) 127 | if err != nil { 128 | logger.Error("service web: %s", err.Error()) 129 | return errors.Wrap(err, "service web") 130 | } 131 | 132 | out.Code = errno.Success.Code 133 | out.Username = user.Username 134 | out.Age = user.Age 135 | out.Addr = user.Address 136 | 137 | return nil 138 | } 139 | 140 | func (s *UserService) List(ctx context.Context, in *v1.ListRequest, out *v1.ListRespond) error { 141 | fmt.Println("UserService List") 142 | if ctx.Err() == context.Canceled { 143 | return errors.Wrap(status.New(codes.Canceled, "Client cancelled, abandoning").Err(), "service web") 144 | } 145 | 146 | in.Page, in.PageSize = model.CheckPageSize(in.Page, in.PageSize) 147 | 148 | count, users, err := model.ListUsers(in.Username, in.Page, in.PageSize) 149 | if err != nil { 150 | logger.Error("service web: %s", err.Error()) 151 | return errors.Wrap(err, "service web") 152 | } 153 | 154 | out = &v1.ListRespond{ 155 | Code: errno.Success.Code, 156 | Count: int32(count), 157 | } 158 | 159 | out.All = make(map[int32]*v1.One, 0) 160 | for k, user := range users { 161 | out.All[int32(k)] = &v1.One{ 162 | Username: user.Username, 163 | Age: user.Age, 164 | Addr: user.Address, 165 | } 166 | } 167 | 168 | return nil 169 | } 170 | 171 | func (s *UserService) Auth(ctx context.Context, in *v1.AuthRequest, out *v1.AuthRespond) error { 172 | fmt.Println("UserService Auth") 173 | var ( 174 | err error 175 | t *token.Context 176 | user *model.TBUser 177 | ) 178 | 179 | if ctx.Err() == context.Canceled { 180 | logger.Error("service web: %s", ctx.Err().Error()) 181 | return errors.Wrap(status.New(codes.Canceled, "Client cancelled, abandoning.").Err(), "service web") 182 | } 183 | 184 | if t, err = token.ParseRequest(in.Token); err != nil { 185 | logger.Error("service web: %s", err.Error()) 186 | return errors.Wrap(err, "service web") 187 | } 188 | 189 | if t.Uid == "" { 190 | logger.Error("service web: uid empty") 191 | return errors.Wrap(errors.New("token uid is empty"), "service web") 192 | } 193 | 194 | user, err = model.GetUser(&model.TBUser{Uid: t.Uid}) 195 | if err != nil { 196 | logger.Error("service web: %s", err.Error()) 197 | return errors.Wrap(err, "service web") 198 | } 199 | 200 | out.Uid = user.Uid 201 | 202 | return nil 203 | } 204 | -------------------------------------------------------------------------------- /common/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "golang.org/x/crypto/bcrypt" 4 | 5 | func Encrypt(source string) (string, error) { 6 | hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) 7 | return string(hashedBytes), err 8 | } 9 | 10 | func Compare(hashedPassword, password string) error { 11 | return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) 12 | } 13 | -------------------------------------------------------------------------------- /common/configs/config.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | var ( 4 | TraceContext = "trace_ctx" 5 | TraceAddr = "127.0.0.1:6831" 6 | TraceParam = "req_param" 7 | 8 | TokenKey = "jhf987y01h1j1h89" 9 | TokenTime = 3600 10 | ) 11 | -------------------------------------------------------------------------------- /common/errno/code.go: -------------------------------------------------------------------------------- 1 | package errno 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var ( 8 | _codes = map[int32]struct{}{} 9 | ) 10 | 11 | func New(e int32) int32 { 12 | if e <= 0 { 13 | panic("business ecode must greater than zero") 14 | } 15 | return add(e) 16 | } 17 | 18 | func add(e int32) int32 { 19 | if _, ok := _codes[e]; ok { 20 | panic(fmt.Sprintf("ecode: %d already exist", e)) 21 | } 22 | _codes[e] = struct{}{} 23 | return e 24 | } 25 | 26 | type Errno struct { 27 | Code int32 `json:"Code"` 28 | Msg string `json:"Msg"` 29 | Data interface{} `json:"Data,omitempty"` 30 | } 31 | 32 | func (e Errno) Error() string { 33 | return fmt.Sprintf("code:%d msg:%s", e.Code, e.Msg) 34 | } 35 | -------------------------------------------------------------------------------- /common/errno/common_code.go: -------------------------------------------------------------------------------- 1 | package errno 2 | 3 | // [1, 10000) 4 | var ( 5 | Success = &Errno{Code: New(1), Msg: "ok"} 6 | 7 | ErrBind = &Errno{Code: New(10), Msg: "bind json error"} 8 | ErrParams = &Errno{Code: New(11), Msg: "params empty error"} 9 | ErrTraceNoContext = &Errno{Code: New(12), Msg: "tracer no context error"} 10 | ErrTraceIntoContext = &Errno{Code: New(13), Msg: "tracer into context error"} 11 | ErrCopy = &Errno{Code: New(14), Msg: "copy data error"} 12 | ErrNoTokenUid = &Errno{Code: New(15), Msg: "no token uid error"} 13 | ErrTooManyReqyest = &Errno{Code: New(16), Msg: "too many request error"} 14 | ) 15 | -------------------------------------------------------------------------------- /common/errno/error.go: -------------------------------------------------------------------------------- 1 | package errno 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrVerifyEmail = errors.New("verify email error") 7 | ErrVerifyPhone = errors.New("verify phone error") 8 | ) 9 | -------------------------------------------------------------------------------- /common/errno/srv_code.go: -------------------------------------------------------------------------------- 1 | package errno 2 | 3 | // [10000, 1000000) 4 | var ( 5 | ErrUserRegister = &Errno{Code: New(100000), Msg: "web user register error"} 6 | ErrUserInfo = &Errno{Code: New(100001), Msg: "web user get error"} 7 | ErrUserLogin = &Errno{Code: New(100002), Msg: "web user login error"} 8 | ErrUserAuthMid = &Errno{Code: New(100003), Msg: "web user auth mid error"} 9 | ErrUserList = &Errno{Code: New(100004), Msg: "web user list error"} 10 | ) 11 | -------------------------------------------------------------------------------- /common/http/handle/result.go: -------------------------------------------------------------------------------- 1 | package handle 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/liangjfblue/cheetah/common/errno" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | type Result struct { 12 | Code int `json:"code"` 13 | Msg string `json:"msg"` 14 | Data interface{} `json:"data,omitempty"` 15 | } 16 | 17 | func (r *Result) Success(c *gin.Context, data interface{}) { 18 | c.JSON(http.StatusOK, Result{ 19 | Code: 1, 20 | Data: data, 21 | Msg: "ok", 22 | }) 23 | } 24 | 25 | func (r *Result) Failure(c *gin.Context, err error) { 26 | if e, ok := err.(*errno.Errno); ok { 27 | c.JSON(http.StatusOK, Result{ 28 | Code: 0, 29 | Data: map[string]interface{}{ 30 | "code": e.Code, 31 | "msg": e.Msg, 32 | }, 33 | Msg: "error", 34 | }) 35 | } else { 36 | c.JSON(http.StatusOK, Result{ 37 | Code: 0, 38 | Data: map[string]interface{}{ 39 | "code": -1, 40 | "msg": "system error", 41 | }, 42 | Msg: "error", 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /common/http/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/liangjfblue/cheetah/common/proto" 8 | 9 | "github.com/liangjfblue/cheetah/common/errno" 10 | 11 | "github.com/liangjfblue/cheetah/common/configs" 12 | 13 | "github.com/gin-gonic/gin" 14 | userv1 "github.com/liangjfblue/cheetah/app/service/web/proto/v1" 15 | "github.com/liangjfblue/cheetah/common/http/handle" 16 | "github.com/liangjfblue/cheetah/common/logger" 17 | "github.com/liangjfblue/cheetah/common/tracer" 18 | "github.com/micro/go-micro/client" 19 | ) 20 | 21 | type Auth struct { 22 | userSrvClient userv1.UserService 23 | } 24 | 25 | func New(cli client.Client) *Auth { 26 | a := new(Auth) 27 | 28 | a.userSrvClient = userv1.NewUserService(proto.UserSrvName, cli) 29 | 30 | return a 31 | } 32 | 33 | func (m *Auth) AuthMid() gin.HandlerFunc { 34 | return func(c *gin.Context) { 35 | var ( 36 | err error 37 | result handle.Result 38 | ) 39 | 40 | //tracer 41 | cc, ok := c.Get(configs.TraceContext) 42 | if !ok { 43 | logger.Error("no TraceContext") 44 | result.Failure(c, errno.ErrTraceNoContext) 45 | c.Abort() 46 | return 47 | } 48 | 49 | ctx := cc.(context.Context) 50 | ctx, span, err := tracer.TraceIntoContext(ctx, "VerifyToken") 51 | if err != nil { 52 | logger.Error(err.Error()) 53 | result.Failure(c, errno.ErrTraceIntoContext) 54 | c.Abort() 55 | return 56 | } 57 | defer span.Finish() 58 | 59 | //jwt 60 | token := c.Request.Header.Get("Authorization") 61 | 62 | req := userv1.AuthRequest{ 63 | Token: token, 64 | } 65 | 66 | resp, err := m.userSrvClient.Auth(c, &req) 67 | if err != nil { 68 | logger.Error(err.Error()) 69 | if strings.Contains(err.Error(), "too many request") { 70 | err = errno.ErrTooManyReqyest 71 | } else { 72 | err = errno.ErrUserAuthMid 73 | } 74 | result.Failure(c, err) 75 | c.Abort() 76 | return 77 | } 78 | 79 | c.Set("uid", resp.Uid) 80 | 81 | c.Next() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /common/http/middleware/trace.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/liangjfblue/cheetah/common/configs" 7 | 8 | "github.com/gin-gonic/gin" 9 | 10 | "github.com/liangjfblue/cheetah/common/tracer" 11 | ) 12 | 13 | func OpenTracingMid() gin.HandlerFunc { 14 | return func(c *gin.Context) { 15 | ctx, span, err := tracer.TraceFromHeader(context.Background(), "api:"+c.Request.URL.Path, c.Request.Header) 16 | if err == nil { 17 | defer span.Finish() 18 | c.Set(configs.TraceContext, ctx) 19 | } else { 20 | c.Set(configs.TraceContext, context.Background()) 21 | } 22 | 23 | c.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common/logger/logger.go: -------------------------------------------------------------------------------- 1 | /* 2 | @Time : 2020/4/19 14:28 3 | @Author : liangjiefan 4 | */ 5 | package logger 6 | 7 | import ( 8 | "github.com/liangjfblue/gglog" 9 | ) 10 | 11 | type Logger struct { 12 | } 13 | 14 | var ( 15 | _gglog gglog.GGLog 16 | ) 17 | 18 | func Init(option ...gglog.Option) { 19 | _gglog = gglog.NewGGLog(option...) 20 | _gglog.Init() 21 | } 22 | 23 | func Debug(format string, args ...interface{}) { 24 | _gglog.Debug(format, args...) 25 | } 26 | func Info(format string, args ...interface{}) { 27 | _gglog.Info(format, args...) 28 | } 29 | func Warn(format string, args ...interface{}) { 30 | _gglog.Warn(format, args...) 31 | } 32 | func Error(format string, args ...interface{}) { 33 | _gglog.Error(format, args...) 34 | } 35 | func Access(format string, args ...interface{}) { 36 | _gglog.Access(format, args...) 37 | } 38 | func InterfaceAvgDuration(format string, args ...interface{}) { 39 | _gglog.InterfaceAvgDuration(format, args...) 40 | } 41 | -------------------------------------------------------------------------------- /common/proto/init.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | const ( 4 | UserSrvName = "micro.srv.cheetah.web" 5 | UserSrvVersion = "v1.0.0" 6 | ) 7 | -------------------------------------------------------------------------------- /common/token/token.go: -------------------------------------------------------------------------------- 1 | /* 2 | @Time : 2020/4/19 14:28 3 | @Author : liangjiefan 4 | */ 5 | package token 6 | 7 | import ( 8 | "errors" 9 | "time" 10 | 11 | "github.com/liangjfblue/cheetah/common/configs" 12 | 13 | "github.com/dgrijalva/jwt-go" 14 | ) 15 | 16 | type Context struct { 17 | Uid string `json:"uid"` 18 | } 19 | 20 | type Token struct { 21 | JwtKey string 22 | JwtTime int 23 | } 24 | 25 | var ( 26 | _token = Token{ 27 | JwtKey: configs.TokenKey, 28 | JwtTime: configs.TokenTime, 29 | } 30 | ) 31 | 32 | func secretFunc(secret string) jwt.Keyfunc { 33 | return func(token *jwt.Token) (interface{}, error) { 34 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 35 | return nil, jwt.ErrSignatureInvalid 36 | } 37 | 38 | return []byte(secret), nil 39 | } 40 | } 41 | 42 | func Parse(tokenString string, secret string) (*Context, error) { 43 | ctx := &Context{} 44 | 45 | token, err := jwt.Parse(tokenString, secretFunc(secret)) 46 | if err != nil { 47 | return ctx, err 48 | } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 49 | ctx.Uid = claims["uid"].(string) 50 | return ctx, nil 51 | } else { 52 | return ctx, err 53 | } 54 | } 55 | 56 | func ParseRequest(token string) (*Context, error) { 57 | if len(token) == 0 { 58 | return &Context{}, errors.New("`Authorization` header token is 0") 59 | } 60 | 61 | return Parse(token, _token.JwtKey) 62 | } 63 | 64 | func SignToken(c Context) (tokenString string, err error) { 65 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 66 | "uid": c.Uid, 67 | "nbf": time.Now().Unix(), 68 | "iat": time.Now().Unix(), 69 | "exp": time.Now().Add(time.Second * time.Duration(_token.JwtTime)).Unix(), 70 | }) 71 | 72 | tokenString, err = token.SignedString([]byte(_token.JwtKey)) 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /common/tracer/init.go: -------------------------------------------------------------------------------- 1 | package tracer 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/liangjfblue/cheetah/common/logger" 7 | "github.com/opentracing/opentracing-go" 8 | "github.com/uber/jaeger-client-go" 9 | jaegercfg "github.com/uber/jaeger-client-go/config" 10 | jaegerlog "github.com/uber/jaeger-client-go/log" 11 | "github.com/uber/jaeger-lib/metrics" 12 | ) 13 | 14 | type Tracer struct { 15 | c io.Closer 16 | 17 | traceAddress string 18 | serviceName string 19 | } 20 | 21 | func New(traceAddress, serviceName string) *Tracer { 22 | return &Tracer{ 23 | traceAddress: traceAddress, 24 | serviceName: serviceName, 25 | } 26 | } 27 | 28 | func (t *Tracer) Init() { 29 | t.c = t.traceingInit(t.traceAddress, t.serviceName) 30 | logger.Info("init open tracering:%+v", opentracing.GlobalTracer()) 31 | } 32 | 33 | func (t *Tracer) Close() { 34 | if t.c != nil { 35 | _ = t.c.Close() 36 | } 37 | } 38 | 39 | func (t *Tracer) traceingInit(address, servicename string) io.Closer { 40 | cfg := jaegercfg.Configuration{ 41 | Sampler: &jaegercfg.SamplerConfig{ 42 | Type: jaeger.SamplerTypeConst, 43 | Param: 1, 44 | }, 45 | Reporter: &jaegercfg.ReporterConfig{ 46 | LogSpans: true, 47 | }, 48 | } 49 | 50 | jLogger := jaegerlog.StdLogger 51 | jMetricsFactory := metrics.NullFactory 52 | 53 | //metricsFactory := metrics.NewLocalFactory(0) 54 | _metrics := jaeger.NewMetrics(jMetricsFactory, nil) 55 | 56 | sender, err := jaeger.NewUDPTransport(address, 0) 57 | if err != nil { 58 | logger.Info("could not initialize jaeger sender: " + err.Error()) 59 | return nil 60 | } 61 | 62 | repoter := jaeger.NewRemoteReporter(sender, jaeger.ReporterOptions.Metrics(_metrics)) 63 | 64 | closer, err := cfg.InitGlobalTracer( 65 | servicename, 66 | jaegercfg.Logger(jLogger), 67 | jaegercfg.Metrics(jMetricsFactory), 68 | jaegercfg.Reporter(repoter), 69 | ) 70 | 71 | if err != nil { 72 | logger.Info("could not initialize jaeger tracer: " + err.Error()) 73 | return nil 74 | } 75 | return closer 76 | } 77 | -------------------------------------------------------------------------------- /common/tracer/server.go: -------------------------------------------------------------------------------- 1 | package tracer 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/micro/go-micro/metadata" 8 | "github.com/opentracing/opentracing-go" 9 | ) 10 | 11 | //记录tag 12 | func tag(ctx context.Context, sp opentracing.Span) (context.Context, opentracing.Span) { 13 | ctx = opentracing.ContextWithSpan(ctx, sp) 14 | 15 | //s := strings.Split(fmt.Sprintf("%v", sp), ":") 16 | //if len(s) >= 3 { 17 | // sp.SetTag("trace_id", s[0]) 18 | // sp.SetTag("span_id", s[1]) 19 | // sp.SetTag("parent_id", s[2]) 20 | //} 21 | return ctx, sp 22 | } 23 | 24 | func traceIntoContextByGlobalTracer(ctx context.Context, tracer opentracing.Tracer, name string) (context.Context, opentracing.Span, error) { 25 | md, ok := metadata.FromContext(ctx) 26 | if !ok { 27 | md = make(map[string]string) 28 | } 29 | var sp opentracing.Span 30 | wireContext, err := tracer.Extract(opentracing.TextMap, opentracing.TextMapCarrier(md)) 31 | if err != nil { 32 | sp = tracer.StartSpan(name) 33 | } else { 34 | sp = tracer.StartSpan(name, opentracing.ChildOf(wireContext)) 35 | } 36 | if err := sp.Tracer().Inject(sp.Context(), opentracing.TextMap, opentracing.TextMapCarrier(md)); err != nil { 37 | return nil, nil, err 38 | } 39 | ctx, sp = tag(ctx, sp) 40 | ctx = metadata.NewContext(ctx, md) 41 | return ctx, sp, nil 42 | } 43 | 44 | func traceFromHeaderByGlobalTracer(ctx context.Context, tracer opentracing.Tracer, name string, header http.Header) (context.Context, opentracing.Span, error) { 45 | var sp opentracing.Span 46 | wireContext, err := tracer.Extract(opentracing.TextMap, opentracing.HTTPHeadersCarrier(header)) 47 | if err != nil { 48 | sp = tracer.StartSpan(name) 49 | } else { 50 | sp = tracer.StartSpan(name, opentracing.ChildOf(wireContext)) 51 | } 52 | md, ok := metadata.FromContext(ctx) 53 | if !ok { 54 | md = make(map[string]string) 55 | } 56 | if err := sp.Tracer().Inject(sp.Context(), opentracing.TextMap, opentracing.TextMapCarrier(md)); err != nil { 57 | return nil, nil, err 58 | } 59 | ctx, sp = tag(ctx, sp) 60 | ctx = metadata.NewContext(ctx, md) 61 | return ctx, sp, nil 62 | } 63 | 64 | func traceToHeaderByGlobalTracer(ctx context.Context, tracer opentracing.Tracer, name string, header http.Header) (context.Context, opentracing.Span, error) { 65 | md, ok := metadata.FromContext(ctx) 66 | if !ok { 67 | md = make(map[string]string) 68 | } 69 | var sp opentracing.Span 70 | wireContext, err := tracer.Extract(opentracing.TextMap, opentracing.TextMapCarrier(md)) 71 | if err != nil { 72 | sp = tracer.StartSpan(name) 73 | } else { 74 | sp = tracer.StartSpan(name, opentracing.ChildOf(wireContext)) 75 | } 76 | if err := sp.Tracer().Inject(sp.Context(), opentracing.TextMap, opentracing.HTTPHeadersCarrier(header)); err != nil { 77 | return nil, nil, err 78 | } 79 | ctx, sp = tag(ctx, sp) 80 | return ctx, sp, nil 81 | } 82 | 83 | //TraceIntoContext opentracing从context获取,写入context,适用RPC 84 | func TraceIntoContext(ctx context.Context, name string) (context.Context, opentracing.Span, error) { 85 | return traceIntoContextByGlobalTracer(ctx, opentracing.GlobalTracer(), name) 86 | } 87 | 88 | //TraceFromHeader opentracing从header获取,写入context,适用获取http 89 | func TraceFromHeader(ctx context.Context, name string, header http.Header) (context.Context, opentracing.Span, error) { 90 | return traceFromHeaderByGlobalTracer(ctx, opentracing.GlobalTracer(), name, header) 91 | } 92 | 93 | //TraceToHeader opentracing从context获取,写入http,适用将调用http 94 | func TraceToHeader(ctx context.Context, name string, header http.Header) (context.Context, opentracing.Span, error) { 95 | return traceToHeaderByGlobalTracer(ctx, opentracing.GlobalTracer(), name, header) 96 | } 97 | -------------------------------------------------------------------------------- /common/uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "strings" 5 | 6 | guuid "github.com/satori/go.uuid" 7 | ) 8 | 9 | func UUID() string { 10 | return strings.Replace(guuid.NewV4().String(), "-", "", -1) 11 | } 12 | -------------------------------------------------------------------------------- /common/verify/verify.go: -------------------------------------------------------------------------------- 1 | /* 2 | @Time : 2020/4/24 17:57 3 | @Author : liangjiefan 4 | */ 5 | package verify 6 | 7 | import ( 8 | "reflect" 9 | "strings" 10 | 11 | "gopkg.in/go-playground/validator.v9" 12 | ) 13 | 14 | func Validate(v interface{}) error { 15 | validate := validator.New() 16 | validate.RegisterTagNameFunc(func(fld reflect.StructField) string { 17 | name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] 18 | if name == "-" { 19 | return "" 20 | } 21 | return fld.Tag.Get("msg") + " " + "[" + name + "]" 22 | }) 23 | return validate.Struct(v) 24 | } 25 | 26 | func TranslateErr2MsgTag(err error) string { 27 | for _, err := range err.(validator.ValidationErrors) { 28 | return err.Field() 29 | } 30 | return err.Error() 31 | } 32 | -------------------------------------------------------------------------------- /common/verify/verify_test.go: -------------------------------------------------------------------------------- 1 | package verify 2 | 3 | import "testing" 4 | 5 | func TestValidate(t *testing.T) { 6 | type AddWorkOrder struct { 7 | Order string `json:"order" validate:"required" msg:"缺少工单号参数"` 8 | CustomerNumber int `json:"customer" validate:"required,gt=10" msg:"客服编号必须大于10"` 9 | } 10 | 11 | req := AddWorkOrder{ 12 | Order: "123", 13 | CustomerNumber: 15, 14 | } 15 | 16 | if err := Validate(req); err != nil { 17 | t.Fatal(TranslateErr2MsgTag(err)) 18 | } 19 | } 20 | 21 | func TestValidateGt(t *testing.T) { 22 | type AddWorkOrder struct { 23 | Order string `json:"order" validate:"required" msg:"缺少工单号参数"` 24 | CustomerNumber int `json:"customer" validate:"required,gt=10" msg:"客服编号必须大于10"` 25 | } 26 | 27 | req := AddWorkOrder{ 28 | Order: "123", 29 | CustomerNumber: 15, 30 | } 31 | 32 | if err := Validate(req); err != nil { 33 | t.Fatal(TranslateErr2MsgTag(err)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cores/balancer/balancer.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | type IBalancer interface { 4 | Init(...Option) 5 | Options() Options 6 | DoBalance([]*Instance, ...Option) (*Instance, error) 7 | String() string 8 | } 9 | 10 | type Option func(o *Options) 11 | -------------------------------------------------------------------------------- /cores/balancer/consistent/consistent.go: -------------------------------------------------------------------------------- 1 | package consistent 2 | -------------------------------------------------------------------------------- /cores/balancer/defind.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | type Instance struct { 4 | Ip string 5 | Port int 6 | } 7 | -------------------------------------------------------------------------------- /cores/balancer/errors.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNotFoundInstance = errors.New("not found instance") 7 | ErrEmptyInstance = errors.New("empty instance") 8 | ) 9 | -------------------------------------------------------------------------------- /cores/balancer/hash/hash.go: -------------------------------------------------------------------------------- 1 | package radom 2 | 3 | import ( 4 | "hash/crc32" 5 | 6 | "github.com/liangjfblue/cheetah/cores/balancer" 7 | ) 8 | 9 | type hashBalancer struct { 10 | opts balancer.Options 11 | } 12 | 13 | func (b *hashBalancer) Init(opts ...balancer.Option) { 14 | for _, o := range opts { 15 | o(&b.opts) 16 | } 17 | } 18 | 19 | func (b *hashBalancer) Options() balancer.Options { 20 | return b.opts 21 | } 22 | 23 | func (b *hashBalancer) DoBalance(ins []*balancer.Instance, opts ...balancer.Option) (*balancer.Instance, error) { 24 | max := uint32(len(ins)) 25 | if max <= 0 { 26 | return nil, balancer.ErrEmptyInstance 27 | } 28 | 29 | for _, o := range opts { 30 | o(&b.opts) 31 | } 32 | 33 | idxSum := crc32.ChecksumIEEE([]byte(b.opts.Key)) 34 | idx := idxSum % max 35 | 36 | return ins[idx], nil 37 | } 38 | func (b *hashBalancer) String() string { 39 | return "hashBalancer" 40 | } 41 | 42 | func New(opts ...balancer.Option) balancer.IBalancer { 43 | b := new(hashBalancer) 44 | b.opts = balancer.DefaultOptions 45 | 46 | for _, o := range opts { 47 | o(&b.opts) 48 | } 49 | 50 | return b 51 | } 52 | -------------------------------------------------------------------------------- /cores/balancer/hash/hash_test.go: -------------------------------------------------------------------------------- 1 | package radom 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/liangjfblue/cheetah/cores/balancer" 8 | ) 9 | 10 | func TestHashBalancer(t *testing.T) { 11 | ins := []*balancer.Instance{ 12 | { 13 | Ip: "127.0.0.1", 14 | Port: 1, 15 | }, 16 | { 17 | Ip: "127.0.0.2", 18 | Port: 2, 19 | }, 20 | { 21 | Ip: "127.0.0.3", 22 | Port: 3, 23 | }, 24 | { 25 | Ip: "127.0.0.4", 26 | Port: 4, 27 | }, 28 | } 29 | 30 | b := New() 31 | for i := 0; i < 10; i++ { 32 | t.Log(b.DoBalance(ins, balancer.WithKey(fmt.Sprint(i)))) 33 | } 34 | t.Log("------------------") 35 | for i := 0; i < 10; i++ { 36 | t.Log(b.DoBalance(ins, balancer.WithKey("test"))) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cores/balancer/options.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | type Options struct { 4 | Index uint32 5 | Key string 6 | } 7 | 8 | var ( 9 | DefaultOptions = Options{ 10 | Index: 0, 11 | Key: "dead", 12 | } 13 | ) 14 | 15 | func WithIndex(index uint32) Option { 16 | return func(o *Options) { 17 | o.Index = index 18 | } 19 | } 20 | 21 | func WithKey(key string) Option { 22 | return func(o *Options) { 23 | o.Key = key 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cores/balancer/radom/radom.go: -------------------------------------------------------------------------------- 1 | package radom 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/liangjfblue/cheetah/cores/balancer" 7 | ) 8 | 9 | type randomBalancer struct { 10 | opts balancer.Options 11 | } 12 | 13 | func (b *randomBalancer) Init(opts ...balancer.Option) { 14 | for _, o := range opts { 15 | o(&b.opts) 16 | } 17 | } 18 | 19 | func (b *randomBalancer) Options() balancer.Options { 20 | return b.opts 21 | } 22 | 23 | func (b *randomBalancer) DoBalance(ins []*balancer.Instance, opts ...balancer.Option) (*balancer.Instance, error) { 24 | if len(ins) <= 0 { 25 | return nil, balancer.ErrEmptyInstance 26 | } 27 | 28 | idx := rand.Intn(len(ins)) 29 | return ins[idx], nil 30 | } 31 | func (b *randomBalancer) String() string { 32 | return "randomBalancer" 33 | } 34 | 35 | func New(opts ...balancer.Option) balancer.IBalancer { 36 | b := new(randomBalancer) 37 | b.opts = balancer.DefaultOptions 38 | 39 | for _, o := range opts { 40 | o(&b.opts) 41 | } 42 | 43 | return b 44 | } 45 | -------------------------------------------------------------------------------- /cores/balancer/radom/radom_test.go: -------------------------------------------------------------------------------- 1 | package radom 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/liangjfblue/cheetah/cores/balancer" 7 | ) 8 | 9 | func TestRoundBalancer(t *testing.T) { 10 | ins := []*balancer.Instance{ 11 | { 12 | Ip: "127.0.0.1", 13 | Port: 1, 14 | }, 15 | { 16 | Ip: "127.0.0.2", 17 | Port: 2, 18 | }, 19 | { 20 | Ip: "127.0.0.3", 21 | Port: 3, 22 | }, 23 | { 24 | Ip: "127.0.0.4", 25 | Port: 4, 26 | }, 27 | } 28 | 29 | b := New() 30 | for i := 0; i < 18; i++ { 31 | t.Log(b.DoBalance(ins)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cores/balancer/round/round.go: -------------------------------------------------------------------------------- 1 | package round 2 | 3 | import ( 4 | "github.com/liangjfblue/cheetah/cores/balancer" 5 | ) 6 | 7 | type roundBalancer struct { 8 | opts balancer.Options 9 | } 10 | 11 | func (b *roundBalancer) Init(opts ...balancer.Option) { 12 | for _, o := range opts { 13 | o(&b.opts) 14 | } 15 | } 16 | 17 | func (b *roundBalancer) Options() balancer.Options { 18 | return b.opts 19 | } 20 | 21 | func (b *roundBalancer) DoBalance(ins []*balancer.Instance, opts ...balancer.Option) (*balancer.Instance, error) { 22 | max := uint32(len(ins)) 23 | if max <= 0 { 24 | return nil, balancer.ErrEmptyInstance 25 | } 26 | 27 | defer func() { 28 | b.opts.Index++ 29 | }() 30 | 31 | for _, o := range opts { 32 | o(&b.opts) 33 | } 34 | 35 | if b.opts.Index >= max { 36 | b.opts.Index = 0 37 | } 38 | 39 | return ins[b.opts.Index], nil 40 | } 41 | 42 | func (b *roundBalancer) String() string { 43 | return "round" 44 | } 45 | 46 | func New(opts ...balancer.Option) balancer.IBalancer { 47 | b := new(roundBalancer) 48 | b.opts = balancer.DefaultOptions 49 | 50 | for _, o := range opts { 51 | o(&b.opts) 52 | } 53 | 54 | return b 55 | } 56 | -------------------------------------------------------------------------------- /cores/balancer/round/round_test.go: -------------------------------------------------------------------------------- 1 | package round 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/liangjfblue/cheetah/cores/balancer" 7 | ) 8 | 9 | func TestRoundBalancer(t *testing.T) { 10 | ins := []*balancer.Instance{ 11 | { 12 | Ip: "127.0.0.1", 13 | Port: 1, 14 | }, 15 | { 16 | Ip: "127.0.0.2", 17 | Port: 2, 18 | }, 19 | { 20 | Ip: "127.0.0.3", 21 | Port: 3, 22 | }, 23 | { 24 | Ip: "127.0.0.4", 25 | Port: 4, 26 | }, 27 | } 28 | 29 | b := New(balancer.WithIndex(0)) 30 | for i := 0; i < 18; i++ { 31 | t.Log(b.DoBalance(ins)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cores/balancer/roundRobin/roundRobin.go: -------------------------------------------------------------------------------- 1 | package roundRobin 2 | -------------------------------------------------------------------------------- /cores/discovery/README.md: -------------------------------------------------------------------------------- 1 | # 服务发现 2 | 3 | 参考go-micro实现的服务发现组件, 主要是可插拔的设计模式, 通过把服务发现, watch服务, 缓存服务cache分为不同模块, 通过接口和依赖注入的凡是, 4 | 大大提高了组件间的耦合, 从而实现可插拔的目的. 5 | 6 | ## 插件化实现 7 | - 函数选项模式 8 | - 接口 9 | - 依赖注入 10 | 11 | 服务发现主要是服务注册, 服务监听, 服务缓存. 其中比较重要的是数据一致性, 失败策略, 缓存提高效率等. 12 | 13 | ## 亮点 14 | - 1.支持服务多版本并存, 一个服务多节点, 支持自定义元数据(metaData),方便携带定制化信息 15 | 16 | - 2.通过cache来缓存服务信息, 提高了查询速度. 并且通过ttl来控制服务的缓存时间, 从而避免"死服务"的存在, 从而造成服务请求的异常. 17 | 18 | - 3.因为cache的存在, 可以单独实现一个http, 提供Restful接口来查询服务发现的相关信息, 比如服务列表, 服务的节点列表, 服务信息, 节点信息等. 19 | 20 | ## 设计服务发现的相关笔记: 21 | 22 | ### 健康检查分为两个方法 23 | 24 | - 客户端心跳 25 | - 服务端TCP主动探测 26 | 27 | 28 | ### 如何获取IP 29 | - 通过手动配置,然后我们解析对应的文件就可以获取 30 | - 遍历网卡,第一个不为本地环回地址的 IP 地址,很多开源框架都是如此来获取的,比如netty,dubbo 31 | 32 | ### 如何获取port 33 | 通过配置文件 34 | 35 | ### 如何生成服务的元数据 36 | - 服务Id(Id): 使用时间戳 + 机房 + 当前工作机器 + 序列号来生成 37 | - 服务名(ServiceName):通过配置文件里面手动配置来获取 38 | - 版本(version):服务的版本,通过配置文件里面配置来获取,有了版本就可以做类似灰度发布等功能。 39 | - 注册时间(registerTime):服务注册的时间戳 40 | 41 | ### kv设计 42 | key: path/srvName 43 | value: nodeInfo 44 | 45 | nodeInfo 46 | 47 | Version //版本号 48 | Path //注册路径 49 | Env //环境 50 | SrvName //服务名 51 | Addr //服务地址 52 | Hostname //主机名(必须唯一) 53 | Status //状态,1表示接收流量,2表示不接收 54 | Color //灰度或集群标识 55 | ... 56 | 57 | -------------------------------------------------------------------------------- /cores/discovery/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "math" 7 | "math/rand" 8 | "sync" 9 | "time" 10 | 11 | "github.com/liangjfblue/cheetah/cores/discovery" 12 | ) 13 | 14 | type Cache interface { 15 | discovery.IDiscovery 16 | Stop() 17 | } 18 | 19 | type Options struct { 20 | TTL time.Duration 21 | } 22 | 23 | type Option func(o *Options) 24 | 25 | type cache struct { 26 | discovery.IDiscovery 27 | opts Options 28 | 29 | sync.RWMutex 30 | cache map[string][]*discovery.Service 31 | ttls map[string]time.Time 32 | watched map[string]bool 33 | 34 | stop chan bool 35 | running bool 36 | status error 37 | } 38 | 39 | var ( 40 | DefaultTTL = time.Minute 41 | ) 42 | 43 | //backoff 重试时间间隔, 随机 44 | func backoff(attempts int) time.Duration { 45 | if attempts <= 0 { 46 | //若是首次,无间隔执行 47 | return 0 48 | } 49 | 50 | return time.Duration(math.Pow(10, float64(attempts))) * time.Millisecond 51 | } 52 | 53 | func (c *cache) getStatus() error { 54 | c.RLock() 55 | defer c.RUnlock() 56 | return c.status 57 | } 58 | 59 | func (c *cache) setStatus(err error) { 60 | c.Lock() 61 | defer c.Unlock() 62 | c.status = err 63 | } 64 | 65 | func (c *cache) isValid(services []*discovery.Service, ttl time.Time) bool { 66 | if len(services) == 0 { 67 | return false 68 | } 69 | 70 | if ttl.IsZero() { 71 | return false 72 | } 73 | 74 | //已过期? 75 | if time.Since(ttl) > 0 { 76 | return false 77 | } 78 | 79 | return true 80 | } 81 | 82 | func (c *cache) quit() bool { 83 | select { 84 | case <-c.stop: 85 | return true 86 | default: 87 | return false 88 | } 89 | } 90 | 91 | func (c *cache) del(service string) { 92 | if err := c.status; err != nil { 93 | return 94 | } 95 | 96 | delete(c.cache, service) 97 | delete(c.ttls, service) 98 | } 99 | 100 | //get 在调用GetService时真正触发拉一次服务和开始监听,然后染回服务信息 101 | func (c *cache) get(service string) ([]*discovery.Service, error) { 102 | c.RLock() 103 | 104 | services := c.cache[service] 105 | ttl := c.ttls[service] 106 | 107 | cp := discovery.Copy(services) 108 | 109 | //服务存在并且在缓存有效期,直接返回 110 | if c.isValid(cp, ttl) { 111 | c.RUnlock() 112 | return services, nil 113 | } 114 | 115 | get := func(service string, cached []*discovery.Service) ([]*discovery.Service, error) { 116 | services, err := c.IDiscovery.GetService(service) 117 | if err != nil { 118 | if len(cached) > 0 { 119 | c.setStatus(err) 120 | return cached, nil 121 | } 122 | 123 | return nil, err 124 | } 125 | 126 | //清楚状态标记 127 | if err := c.getStatus(); err != nil { 128 | c.setStatus(nil) 129 | } 130 | 131 | //更新缓存 132 | c.Lock() 133 | defer c.RLock() 134 | c.set(service, services) 135 | 136 | return services, nil 137 | } 138 | 139 | _, ok := c.watched[service] 140 | 141 | c.RUnlock() 142 | 143 | if !ok { 144 | c.Lock() 145 | c.watched[service] = true 146 | if !c.running { 147 | go c.run() 148 | } 149 | c.Unlock() 150 | } 151 | 152 | return get(service, cp) 153 | } 154 | 155 | func (c *cache) set(service string, services []*discovery.Service) { 156 | c.cache[service] = services 157 | //服务缓存过期, 因watch事件,会定时更新,因此正常情况会定时刷新缓存 158 | c.ttls[service] = time.Now().Add(c.opts.TTL) 159 | } 160 | 161 | func (c *cache) update(resp *discovery.Result) { 162 | //判空 163 | if resp == nil || resp.Service == nil { 164 | return 165 | } 166 | 167 | //上锁 168 | c.Lock() 169 | defer c.Unlock() 170 | 171 | //是否是监听服务 172 | if _, ok := c.watched[resp.Service.SrvName]; !ok { 173 | return 174 | } 175 | 176 | //当前服务名的所有版本 177 | services, ok := c.cache[resp.Service.SrvName] 178 | if !ok { 179 | return 180 | } 181 | 182 | //缓存的服务已经没有节点 183 | if resp.Action == discovery.Delete && len(resp.Service.Nodes) <= 0 { 184 | c.del(resp.Service.SrvName) 185 | return 186 | } 187 | 188 | var ( 189 | index int 190 | //对应版本的服务 191 | service *discovery.Service 192 | ) 193 | for i, srv := range services { 194 | if srv.Version == resp.Service.Version { 195 | index = i 196 | service = srv 197 | } 198 | } 199 | 200 | //判断resp类型 201 | switch resp.Action { 202 | case discovery.Update, discovery.Create: 203 | //event的服务不在缓存中,直接新增到服务版本列表 204 | if service == nil { 205 | c.set(resp.Service.SrvName, append(services, resp.Service)) 206 | return 207 | } 208 | 209 | //event的服务在缓存中,证明对应服务版本有节点node新增 210 | //把旧的node添加到event的nodes中 211 | seen := false 212 | for _, oldNode := range service.Nodes { 213 | for _, newNode := range resp.Service.Nodes { 214 | if oldNode.Id == newNode.Id { 215 | seen = true 216 | break 217 | } 218 | } 219 | if !seen { 220 | resp.Service.Nodes = append(resp.Service.Nodes, oldNode) 221 | } 222 | seen = false 223 | } 224 | 225 | services[index] = resp.Service 226 | c.set(resp.Service.SrvName, services) 227 | case discovery.Delete: 228 | //两种情况: 229 | //1.删除一个服务列表的其中某个版本; 230 | //2.删除服务列表的其中一个版本的某些节点 231 | 232 | //没有当前服务名直接返回 233 | if service == nil { 234 | return 235 | } 236 | 237 | var ( 238 | seen = false 239 | nodes []*discovery.Node 240 | ) 241 | //删除的是服务版本中的节点列表的一个节点 242 | for _, oldNode := range service.Nodes { 243 | for _, newNode := range resp.Service.Nodes { 244 | if oldNode.Id == newNode.Id { 245 | seen = true 246 | break 247 | } 248 | } 249 | if !seen { 250 | nodes = append(nodes, oldNode) 251 | } 252 | } 253 | 254 | //若是删除版本服务列表的节点列表中的一个节点,直接更新查询到的版本服务的节点列表 255 | if len(nodes) > 0 { 256 | service.Nodes = nodes 257 | services[index] = service 258 | c.set(resp.Service.SrvName, services) 259 | return 260 | } 261 | 262 | //若服务列表只有一个服务,那么当前删除事件后就无可用服务,清缓存 263 | if len(services) == 1 { 264 | c.del(service.SrvName) 265 | return 266 | } 267 | 268 | //删除对应版本的服务 269 | var srvs []*discovery.Service 270 | for _, s := range services { 271 | if s.Version != service.Version { 272 | srvs = append(srvs, s) 273 | } 274 | } 275 | 276 | c.set(service.SrvName, srvs) 277 | } 278 | } 279 | 280 | func (c *cache) run() { 281 | c.Lock() 282 | c.running = true 283 | c.Unlock() 284 | 285 | defer func() { 286 | c.Lock() 287 | c.running = false 288 | c.watched = make(map[string]bool) 289 | c.Unlock() 290 | }() 291 | 292 | var a, b int 293 | for { 294 | if c.quit() { 295 | return 296 | } 297 | 298 | j := rand.Int63n(100) 299 | time.Sleep(time.Duration(j) * time.Millisecond) 300 | 301 | //new watch 302 | dw, err := c.IDiscovery.Watch() 303 | if err != nil { 304 | if c.quit() { 305 | return 306 | } 307 | 308 | d := backoff(a) 309 | c.setStatus(err) 310 | 311 | if a > 3 { 312 | a = 0 313 | log.Fatal("cache: ", err, " backing off ", d) 314 | } 315 | a++ 316 | time.Sleep(d) 317 | continue 318 | } 319 | 320 | a = 0 321 | 322 | //watch 323 | if err := c.watch(dw); err != nil { 324 | if c.quit() { 325 | return 326 | } 327 | 328 | d := backoff(b) 329 | c.setStatus(err) 330 | 331 | if b > 3 { 332 | b = 0 333 | log.Fatal("rcache: ", err, " backing off ", d) 334 | } 335 | b++ 336 | time.Sleep(d) 337 | continue 338 | } 339 | 340 | b = 0 341 | } 342 | } 343 | 344 | func (c *cache) watch(w discovery.Watcher) error { 345 | stop := make(chan struct{}, 1) 346 | 347 | go func() { 348 | defer w.Stop() 349 | 350 | select { 351 | case <-c.stop: 352 | return 353 | case <-stop: 354 | return 355 | } 356 | }() 357 | 358 | for { 359 | resp, err := w.Next() 360 | if err != nil { 361 | close(stop) 362 | return err 363 | } 364 | 365 | if err := c.getStatus(); err != nil { 366 | c.setStatus(nil) 367 | } 368 | 369 | c.update(resp) 370 | } 371 | } 372 | 373 | func (c *cache) GetService(service string) ([]*discovery.Service, error) { 374 | services, err := c.get(service) 375 | if err != nil { 376 | return nil, err 377 | } 378 | 379 | if len(services) == 0 { 380 | return nil, errors.New("service empty") 381 | } 382 | 383 | return services, nil 384 | } 385 | 386 | func (c *cache) Stop() { 387 | c.Lock() 388 | defer c.Unlock() 389 | 390 | select { 391 | case <-c.stop: 392 | return 393 | default: 394 | close(c.stop) 395 | } 396 | } 397 | 398 | func (c *cache) String() string { 399 | return "cache" 400 | } 401 | 402 | func New(r discovery.IDiscovery, opts ...Option) Cache { 403 | rand.Seed(time.Now().UnixNano()) 404 | options := Options{ 405 | TTL: DefaultTTL, 406 | } 407 | 408 | for _, o := range opts { 409 | o(&options) 410 | } 411 | 412 | return &cache{ 413 | IDiscovery: r, 414 | opts: options, 415 | watched: make(map[string]bool), 416 | cache: make(map[string][]*discovery.Service), 417 | ttls: make(map[string]time.Time), 418 | stop: make(chan bool), 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /cores/discovery/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCache_GetService(t *testing.T) { 8 | //d := etcdv3.NewDiscovery( 9 | // discovery.Addrs([]string{"172.16.7.16:9002", "172.16.7.16:9004", "172.16.7.16:9006"}...), 10 | // discovery.Timeout(time.Second*5), 11 | //) 12 | // 13 | ////30秒拉取一次服务列表 14 | //c := New(d, WithTTL(30*time.Second)) 15 | // 16 | //for i := 0; i < 10; i++ { 17 | // start := time.Now().UnixNano() 18 | // services, err := c.GetService("user") 19 | // if err != nil { 20 | // //TODO 从本地配置获取服务的备用地址 21 | // t.Log(err) 22 | // } 23 | // for _, service := range services { 24 | // t.Log(service.SrvName) 25 | // for _, node := range service.Nodes { 26 | // t.Log(node) 27 | // } 28 | // } 29 | // t.Log(fmt.Sprintf("cost ms:%d", (time.Now().UnixNano()-start)/1e6)) 30 | // time.Sleep(time.Second * 5) 31 | //} 32 | } 33 | -------------------------------------------------------------------------------- /cores/discovery/cache/options.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "time" 4 | 5 | func WithTTL(ttl time.Duration) Option { 6 | return func(o *Options) { 7 | o.TTL = ttl 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cores/discovery/defind.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | // NodeInfo node info 4 | type Service struct { 5 | SrvName string `json:"srvName"` //服务名 6 | Version string `json:"version"` //版本号 7 | Metadata map[string]string `json:"metadata"` //服务元数据 8 | Endpoints []*Endpoint `json:"endpoints"` // 9 | Nodes []*Node `json:"nodes"` 10 | } 11 | 12 | type Node struct { 13 | Id string `json:"id"` 14 | Address string `json:"address"` 15 | Metadata map[string]string `json:"metadata"` 16 | } 17 | 18 | type Endpoint struct { 19 | Name string `json:"name"` 20 | Request *Value `json:"request"` 21 | Response *Value `json:"response"` 22 | Metadata map[string]string `json:"metadata"` 23 | } 24 | 25 | type Value struct { 26 | Name string `json:"name"` 27 | Type string `json:"type"` 28 | Values []*Value `json:"values"` 29 | } 30 | -------------------------------------------------------------------------------- /cores/discovery/discovery.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | type IDiscovery interface { 4 | Init(...Option) error 5 | Options() Options 6 | Register(*Service, ...RegisterOption) error 7 | Deregister(*Service) error 8 | GetService(string) ([]*Service, error) 9 | ListServices() ([]*Service, error) 10 | Watch(...WatchOption) (Watcher, error) 11 | String() string 12 | } 13 | 14 | type Option func(*Options) 15 | type RegisterOption func(*RegisterOptions) 16 | type WatchOption func(*WatchOptions) 17 | -------------------------------------------------------------------------------- /cores/discovery/etcdv3/client_test.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestClient(t *testing.T) { 8 | //r := NewDiscovery( 9 | // discovery.Addrs([]string{"http://172.16.7.16:9002", "http://172.16.7.16:9004", "http://172.16.7.16:9006"}...), 10 | // discovery.Timeout(time.Second*5), 11 | //) 12 | // 13 | //service := &discovery.Service{ 14 | // SrvName: "user", 15 | // Version: "1.0.0", 16 | // Nodes: []*discovery.Node{ 17 | // { 18 | // Id: uuid.New().String(), 19 | // Address: "172.16.7.16:8899", 20 | // }, 21 | // }, 22 | //} 23 | // 24 | //if err := r.Register(service, discovery.RegisterTTL(time.Second*3)); err != nil { 25 | // log.Fatal(err) 26 | //} 27 | //defer r.Deregister(service) 28 | // 29 | //t.Log("had register service") 30 | //select {} 31 | } 32 | -------------------------------------------------------------------------------- /cores/discovery/etcdv3/etcd.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "sort" 7 | "sync" 8 | "time" 9 | 10 | "github.com/liangjfblue/cheetah/cores/discovery" 11 | 12 | "github.com/pkg/errors" 13 | 14 | "github.com/coreos/etcd/clientv3" 15 | hash "github.com/mitchellh/hashstructure" 16 | ) 17 | 18 | type etcdDiscovery struct { 19 | client *clientv3.Client 20 | options discovery.Options 21 | sync.RWMutex 22 | srvNodes map[string][]discovery.Service 23 | registers map[string]uint64 24 | leases map[string]clientv3.LeaseID 25 | } 26 | 27 | func NewDiscovery(option ...discovery.Option) *etcdDiscovery { 28 | e := &etcdDiscovery{ 29 | options: discovery.Options{}, 30 | srvNodes: make(map[string][]discovery.Service, 0), 31 | registers: make(map[string]uint64, 0), 32 | leases: make(map[string]clientv3.LeaseID, 0), 33 | } 34 | 35 | if err := e.configure(option...); err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | return e 40 | } 41 | 42 | func (e *etcdDiscovery) configure(opts ...discovery.Option) error { 43 | for _, o := range opts { 44 | o(&e.options) 45 | } 46 | 47 | if e.options.Timeout == 0 { 48 | e.options.Timeout = time.Second * 5 49 | } 50 | 51 | config := clientv3.Config{ 52 | Endpoints: []string{"127.0.0.1:2379"}, 53 | } 54 | if e.options.Addrs != nil { 55 | config.Endpoints = e.options.Addrs 56 | } 57 | 58 | cli, err := clientv3.New(config) 59 | if err != nil { 60 | return err 61 | } 62 | e.client = cli 63 | 64 | return nil 65 | } 66 | 67 | func (e *etcdDiscovery) Init(opts ...discovery.Option) error { 68 | return e.configure(opts...) 69 | } 70 | 71 | func (e *etcdDiscovery) Options() discovery.Options { 72 | return e.options 73 | } 74 | 75 | func (e *etcdDiscovery) Register(service *discovery.Service, opts ...discovery.RegisterOption) error { 76 | if len(service.Nodes) == 0 { 77 | return errors.New("discovery nodes is empty") 78 | } 79 | 80 | var err error 81 | for _, node := range service.Nodes { 82 | go func(err error, node *discovery.Node) { 83 | err = e.keepAlive(service, node, opts...) 84 | }(err, node) 85 | } 86 | return err 87 | } 88 | 89 | func (e *etcdDiscovery) keepAlive(service *discovery.Service, node *discovery.Node, opts ...discovery.RegisterOption) error { 90 | var ( 91 | err error 92 | ro discovery.RegisterOptions 93 | lgr *clientv3.LeaseGrantResponse 94 | ) 95 | 96 | for _, o := range opts { 97 | o(&ro) 98 | } 99 | if ro.TTL.Seconds() <= 0 { 100 | ro.TTL = time.Second * 5 101 | } 102 | 103 | ctx, cancel := context.WithTimeout(context.TODO(), e.options.Timeout) 104 | defer cancel() 105 | 106 | if ro.TTL.Seconds() > 0 { 107 | lgr, err = e.client.Grant(ctx, int64(ro.TTL.Seconds())) 108 | if err != nil { 109 | return err 110 | } 111 | } 112 | 113 | h, err := hash.Hash(node, nil) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | e.Lock() 119 | e.registers[service.SrvName+node.Id] = h 120 | e.leases[service.SrvName+node.Id] = lgr.ID 121 | e.Unlock() 122 | 123 | if _, err := e.client.Put(ctx, 124 | discovery.NodePath(service.SrvName, node.Id), 125 | discovery.Encode(service), 126 | clientv3.WithLease(lgr.ID)); err != nil { 127 | return err 128 | } 129 | ticker := time.NewTicker(time.Duration(int(ro.TTL.Seconds())/2) * time.Second) 130 | for { 131 | select { 132 | case <-ticker.C: 133 | if _, err := e.client.KeepAlive(context.TODO(), lgr.ID); err != nil { 134 | return err 135 | } 136 | } 137 | } 138 | } 139 | 140 | func (e *etcdDiscovery) Deregister(service *discovery.Service) error { 141 | ctx, cancel := context.WithTimeout(context.TODO(), e.options.Timeout) 142 | defer cancel() 143 | 144 | if len(service.Nodes) == 0 { 145 | return errors.New("Require at least one node") 146 | } 147 | 148 | for _, node := range service.Nodes { 149 | e.Lock() 150 | leaseId, ok := e.leases[service.SrvName+node.Id] 151 | delete(e.leases, service.SrvName+node.Id) 152 | delete(e.registers, service.SrvName+node.Id) 153 | e.Unlock() 154 | 155 | if ok { 156 | if _, err := e.client.Revoke(ctx, leaseId); err != nil { 157 | return err 158 | } 159 | } 160 | } 161 | 162 | return nil 163 | } 164 | 165 | func (e *etcdDiscovery) GetService(srvName string) ([]*discovery.Service, error) { 166 | ctx, cancel := context.WithTimeout(context.TODO(), e.options.Timeout) 167 | defer cancel() 168 | 169 | resp, err := e.client.Get(ctx, discovery.ServicePath(srvName)+"/", clientv3.WithPrefix(), clientv3.WithSerializable()) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | if len(resp.Kvs) <= 0 { 175 | return nil, errors.New("service not found node") 176 | } 177 | 178 | srvMap := make(map[string]*discovery.Service) 179 | 180 | for _, kv := range resp.Kvs { 181 | if srvNode := discovery.Decode(kv.Value); srvNode != nil { 182 | s, ok := srvMap[srvNode.Version] 183 | if !ok { 184 | s = &discovery.Service{ 185 | SrvName: srvNode.SrvName, 186 | Version: srvNode.Version, 187 | Metadata: srvNode.Metadata, 188 | Endpoints: srvNode.Endpoints, 189 | Nodes: srvNode.Nodes, 190 | } 191 | srvMap[srvNode.Version] = s 192 | } 193 | 194 | for _, node := range srvNode.Nodes { 195 | s.Nodes = append(s.Nodes, node) 196 | } 197 | } 198 | } 199 | 200 | srvList := make([]*discovery.Service, 0, len(srvMap)) 201 | for _, srv := range srvMap { 202 | srvList = append(srvList, srv) 203 | } 204 | 205 | return srvList, nil 206 | } 207 | 208 | func (e *etcdDiscovery) ListServices() ([]*discovery.Service, error) { 209 | ctx, cancel := context.WithTimeout(context.TODO(), e.options.Timeout) 210 | defer cancel() 211 | 212 | resp, err := e.client.Get(ctx, discovery.ServicePrefixPath()+"/", clientv3.WithPrefix(), clientv3.WithSerializable()) 213 | if err != nil { 214 | return nil, err 215 | } 216 | 217 | if len(resp.Kvs) <= 0 { 218 | return nil, errors.New("service not found node") 219 | } 220 | 221 | versions := make(map[string]*discovery.Service) 222 | 223 | for _, kv := range resp.Kvs { 224 | if srvNode := discovery.Decode(kv.Value); srvNode != nil { 225 | //去重 226 | v, ok := versions[srvNode.SrvName+srvNode.Version] 227 | if !ok { 228 | versions[srvNode.SrvName+srvNode.Version] = srvNode 229 | continue 230 | } 231 | 232 | v.Nodes = append(v.Nodes, srvNode.Nodes...) 233 | } 234 | } 235 | 236 | srvList := make([]*discovery.Service, 0, len(versions)) 237 | for _, srv := range versions { 238 | srvList = append(srvList, srv) 239 | } 240 | 241 | sort.Slice(srvList, func(i, j int) bool { return srvList[i].SrvName < srvList[j].SrvName }) 242 | 243 | return srvList, nil 244 | } 245 | 246 | func (e *etcdDiscovery) Watch(opts ...discovery.WatchOption) (discovery.Watcher, error) { 247 | return newEtcdWatcher(e, e.options.Timeout, opts...) 248 | } 249 | 250 | func (e *etcdDiscovery) String() string { 251 | return "etcd discovery" 252 | } 253 | -------------------------------------------------------------------------------- /cores/discovery/etcdv3/server_test.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestServer(t *testing.T) { 8 | //r := NewDiscovery( 9 | // discovery.Addrs([]string{"172.16.7.16:9002", "172.16.7.16:9004", "172.16.7.16:9006"}...), 10 | // discovery.Timeout(time.Second*5), 11 | //) 12 | // 13 | //w, err := r.Watch(discovery.WatchService("user")) 14 | //if err != nil { 15 | // t.Fatal(err) 16 | //} 17 | // 18 | //ch := make(chan os.Signal, 1) 19 | //signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL) 20 | // 21 | //t.Log("now to watch event") 22 | //defer w.Stop() 23 | //for { 24 | // select { 25 | // case <-ch: 26 | // t.Log("get a signal, return") 27 | // return 28 | // default: 29 | // resp, err := w.Next() 30 | // if err != nil { 31 | // t.Fatal(err) 32 | // return 33 | // } 34 | // 35 | // //TODO you can update cache 36 | // t.Log(resp.Action) 37 | // t.Log(resp.Service) 38 | // for _, node := range resp.Service.Nodes { 39 | // t.Log(*node) 40 | // } 41 | // } 42 | //} 43 | } 44 | -------------------------------------------------------------------------------- /cores/discovery/etcdv3/watcher.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/coreos/etcd/clientv3" 10 | 11 | "github.com/liangjfblue/cheetah/cores/discovery" 12 | ) 13 | 14 | type etcdWatcher struct { 15 | w clientv3.WatchChan 16 | client *clientv3.Client 17 | timeout time.Duration 18 | stop chan struct{} 19 | } 20 | 21 | func newEtcdWatcher(d *etcdDiscovery, timeout time.Duration, opts ...discovery.WatchOption) (discovery.Watcher, error) { 22 | var wp discovery.WatchOptions 23 | for _, o := range opts { 24 | o(&wp) 25 | } 26 | 27 | ctx, cancel := context.WithCancel(context.TODO()) 28 | stop := make(chan bool, 1) 29 | 30 | go func() { 31 | <-stop 32 | cancel() 33 | }() 34 | 35 | watchPath := discovery.ServicePrefixPath() 36 | if len(wp.Service) > 0 { 37 | watchPath = discovery.ServicePath(wp.Service) + "/" 38 | } 39 | 40 | return &etcdWatcher{ 41 | w: d.client.Watch(ctx, watchPath, clientv3.WithPrefix(), clientv3.WithPrevKV()), 42 | client: d.client, 43 | timeout: timeout, 44 | stop: make(chan struct{}, 1), 45 | }, nil 46 | } 47 | 48 | func (e *etcdWatcher) Next() (*discovery.Result, error) { 49 | for v := range e.w { 50 | if v.Err() != nil { 51 | return nil, v.Err() 52 | } 53 | for _, event := range v.Events { 54 | var action discovery.EventType 55 | service := discovery.Decode(event.Kv.Value) 56 | 57 | switch event.Type { 58 | case clientv3.EventTypePut: 59 | if event.IsCreate() { 60 | action = discovery.Create 61 | } else if event.IsModify() { 62 | action = discovery.Update 63 | } 64 | case clientv3.EventTypeDelete: 65 | action = discovery.Delete 66 | service = discovery.Decode(event.PrevKv.Value) 67 | } 68 | 69 | if service == nil { 70 | continue 71 | } 72 | 73 | return &discovery.Result{ 74 | Action: action, 75 | Service: service, 76 | }, nil 77 | } 78 | } 79 | return nil, errors.New("could not get next") 80 | } 81 | 82 | func (e *etcdWatcher) Stop() { 83 | select { 84 | case <-e.stop: 85 | return 86 | default: 87 | close(e.stop) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /cores/discovery/options.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | //注册中心 9 | type Options struct { 10 | Addrs []string 11 | Timeout time.Duration 12 | Context context.Context 13 | } 14 | 15 | //注册参数 16 | type RegisterOptions struct { 17 | TTL time.Duration 18 | Context context.Context 19 | } 20 | 21 | //监听参数 22 | type WatchOptions struct { 23 | Service string 24 | Context context.Context 25 | } 26 | 27 | func Addrs(addrs ...string) Option { 28 | return func(o *Options) { 29 | o.Addrs = addrs 30 | } 31 | } 32 | 33 | func Timeout(t time.Duration) Option { 34 | return func(o *Options) { 35 | o.Timeout = t 36 | } 37 | } 38 | 39 | func RegisterTTL(t time.Duration) RegisterOption { 40 | return func(o *RegisterOptions) { 41 | o.TTL = t 42 | } 43 | } 44 | 45 | func WatchService(name string) WatchOption { 46 | return func(o *WatchOptions) { 47 | o.Service = name 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cores/discovery/utils.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "encoding/json" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | prefix = "/discovery/services/" 11 | ) 12 | 13 | func Encode(s *Service) string { 14 | b, _ := json.Marshal(s) 15 | return string(b) 16 | } 17 | 18 | func Decode(ds []byte) *Service { 19 | var s *Service 20 | json.Unmarshal(ds, &s) 21 | return s 22 | } 23 | 24 | //nodePath 以防node中有/误导 25 | func NodePath(s, id string) string { 26 | service := strings.Replace(s, "/", "-", -1) 27 | node := strings.Replace(id, "/", "-", -1) 28 | return path.Join(prefix, service, node) 29 | } 30 | 31 | //servicePath 以防srvName中有/误导 32 | func ServicePath(s string) string { 33 | return path.Join(prefix, strings.Replace(s, "/", "-", -1)) 34 | } 35 | 36 | //ServicePrefixPath 所有服务根目录 37 | func ServicePrefixPath() string { 38 | return prefix 39 | } 40 | 41 | //Copy 拷贝服务 42 | func Copy(current []*Service) []*Service { 43 | services := make([]*Service, len(current)) 44 | for i, service := range current { 45 | services[i] = CopyService(service) 46 | } 47 | return services 48 | } 49 | 50 | //CopyService 深拷贝服务 51 | func CopyService(service *Service) *Service { 52 | s := new(Service) 53 | *s = *service 54 | 55 | nodes := make([]*Node, len(service.Nodes)) 56 | for j, node := range service.Nodes { 57 | n := new(Node) 58 | *n = *node 59 | nodes[j] = n 60 | } 61 | s.Nodes = nodes 62 | 63 | eps := make([]*Endpoint, len(service.Endpoints)) 64 | for j, ep := range service.Endpoints { 65 | e := new(Endpoint) 66 | *e = *ep 67 | eps[j] = e 68 | } 69 | s.Endpoints = eps 70 | return s 71 | } 72 | -------------------------------------------------------------------------------- /cores/discovery/watcher.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import "time" 4 | 5 | type Watcher interface { 6 | Next() (*Result, error) 7 | Stop() 8 | } 9 | 10 | type Result struct { 11 | Action EventType 12 | Service *Service 13 | } 14 | 15 | type EventType int 16 | 17 | const ( 18 | Create EventType = iota 19 | Delete 20 | Update 21 | ) 22 | 23 | func (t EventType) String() string { 24 | switch t { 25 | case Create: 26 | return "create" 27 | case Delete: 28 | return "delete" 29 | case Update: 30 | return "update" 31 | default: 32 | return "unknown" 33 | } 34 | } 35 | 36 | type Event struct { 37 | Id string 38 | Type EventType 39 | Timestamp time.Time 40 | Service *Service 41 | } 42 | -------------------------------------------------------------------------------- /deployments/docker-compose.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangjfblue/cheetah/ab065fd3ec64c22378c63b32727f658e4a69912a/deployments/docker-compose.yml -------------------------------------------------------------------------------- /docs/object.md: -------------------------------------------------------------------------------- 1 | model设计 2 | 3 | 4 | 调度器集群 5 | - id 6 | - ip 机器ip 7 | - status 状态(1-leader 2-follower) 8 | 9 | 10 | 执行器组worker group 11 | - id 12 | - name 执行器组名 13 | - workers 执行器列表, 包含id和name, 显示name, id用于查询详细信息. 支持单个执行器增加, 按执行器类型tag增加 14 | - status 状态, 正常-组中全部执行器心跳正常, 异常-组中有执行器下线或者异常. 定时更新 15 | 16 | 执行器worker 17 | - id 18 | - host 机器ip 19 | - name 机器名 20 | - srvName 服务名, 用于向注册中心注册 21 | - tag 服务类,可选,用于调度时匹配特定机器) 22 | 23 | 24 | 任务job 25 | - id 26 | - jobId 全局唯一, uuid 27 | - name 任务名字 28 | - groupId 所属执行器组, 可选, 可自行指定执行器组 29 | - tag 所属执行器类型, 用于调度器下发任务时匹配特定执行器 30 | - cron cron表达式 31 | - type 任务类型, 包括命令行, shell脚本, code[golang,python], http 32 | - target 目标任务, 命令行-命令, shell脚本-shell代码, code-源代码, http-http接口 33 | - ip 任务执行所在执行器ip 34 | - status 执行状态, 1-执行中 2-完成 3-未知 4-错误 35 | - times 耗时 36 | - remark 备注 37 | - username 创建人userId,显示username 38 | - createTime 创建时间Unix() 39 | - startTime 开始时间Unix() 40 | - endTime 结束时间Unix() 41 | - result 返回结果, 1-ok 2-failed 42 | 43 | 44 | 执行日志记录 45 | - id 46 | - jobName 任务名 47 | - workerGroupName 执行器组名 48 | - workerName 执行器名 49 | - workerIp 执行器ip 50 | - cron cron表达式 51 | - type 任务类型, 包括命令行, shell脚本, code[golang,python], http 52 | - status 执行状态, 1-执行中 2-完成 3-未知 4-错误 53 | - result 返回结果, 1-ok 2-failed 54 | - times 耗时 55 | - username 创建人userId,显示username 56 | - createTime 创建时间Unix() 57 | 58 | 59 | ## web api 60 | ### 执行器页面 61 | - 新增 [POST] /v1/workers 62 | - 删除 [DELETE] /v1/workers/:id 63 | - 查找 [GET] /v1/workers/:id 64 | - 更新 [PUT] /v1/workers/:id 65 | - 列表 [GET] /v1/workers 66 | 67 | ###执行器组页面 68 | - 新增 [POST] /v1/worker-groups 69 | - 删除 [DELETE] /v1/worker-groups/:id 70 | - 查找 [GET] /v1/worker-groups/:id 71 | - 更新 [PUT] /v1/worker-groups/:id 72 | - 列表 [GET] /v1/worker-groups 73 | 74 | ###任务页面 75 | - 新增 [POST] /v1/jobs 76 | - 删除 [DELETE] /v1/jobs/:id 77 | - 查找 [GET] /v1/jobs/:id 78 | - 更新 [PUT] /v1/jobs/:id 79 | - 列表 [GET] /v1/jobs 80 | 81 | ###调度器集群页面 82 | - 列表 [GET] /v1/masters 83 | 84 | ###调度日志页面 85 | - 列表 [GET] /v1/scheduler-logs 86 | 87 | ###调度 88 | - 开始调度 [POST] /v1/schedulers 支持批量,延迟,定时 89 | - 停止调度 [DELETE] /v1/schedulers 支持批量 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /docs/权限管理.md: -------------------------------------------------------------------------------- 1 | 权限管理 2 | 3 | casbin 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/组件.md: -------------------------------------------------------------------------------- 1 | 调度器 2 | 执行器 3 | 注册中心 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liangjfblue/cheetah 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect 7 | github.com/coreos/etcd v3.3.18+incompatible 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 9 | github.com/gin-gonic/gin v1.6.2 10 | github.com/go-delve/delve v1.4.0 // indirect 11 | github.com/golang/protobuf v1.4.0 12 | github.com/google/uuid v1.1.1 13 | github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a 14 | github.com/jinzhu/gorm v1.9.12 15 | github.com/liangjfblue/gglog v1.0.1 16 | github.com/micro/go-micro v1.16.0 17 | github.com/micro/go-micro/v2 v2.5.0 // indirect 18 | github.com/micro/go-plugins v1.5.1 19 | github.com/mitchellh/hashstructure v1.0.0 20 | github.com/opentracing/opentracing-go v1.1.0 21 | github.com/pkg/errors v0.9.1 22 | github.com/satori/go.uuid v1.2.0 23 | github.com/spf13/viper v1.6.3 24 | github.com/uber/jaeger-client-go v2.22.1+incompatible 25 | github.com/uber/jaeger-lib v2.2.0+incompatible 26 | go.etcd.io/etcd v3.3.19+incompatible 27 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 28 | google.golang.org/grpc v1.26.0 29 | gopkg.in/go-playground/validator.v9 v9.30.0 30 | ) 31 | -------------------------------------------------------------------------------- /scripts/README: -------------------------------------------------------------------------------- 1 | # scripts 2 | 3 | ## 目的 4 | 5 | 存放构建,安装,部署的脚本,使得根目录的Makefile文件尽量小,简单 6 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | BUILD_HOME=deployments/bin 6 | 7 | build() { 8 | if [[ ! -d ${BUILD_HOME} ]];then 9 | mkdir -p ${BUILD_HOME} 10 | fi 11 | 12 | buildDir=./app/interface/$2/cmd 13 | if [[ "$1" == "srv" ]]; then 14 | buildDir=./app/service/$2/cmd 15 | fi 16 | 17 | if [[ -d ${buildDir} ]];then 18 | for f in ${buildDir}/main.go; do 19 | if [[ -f ${f} ]];then 20 | dir=${BUILD_HOME}/$1/$1_$2 21 | if [[ ! -d ${dir} ]];then 22 | mkdir -p ${BUILD_HOME}/$1/$1_$2 23 | fi 24 | 25 | cp ${buildDir}/config.yaml ${BUILD_HOME}/$1/$1_$2 26 | CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' -i -o ${BUILD_HOME}/$1/$1_$2/$1_$2 ${buildDir} 27 | echo build over: $1_$2; 28 | fi \ 29 | done 30 | fi 31 | } 32 | 33 | buildAll() { 34 | # web 35 | build web web 36 | 37 | # srv 38 | build srv web 39 | } 40 | 41 | case $1 in 42 | all) echo "build all" 43 | buildAll 44 | echo "make all ok" 45 | ;; 46 | one) echo "build:"$2_$3 47 | if [[ -z $2 || -z $3 ]];then 48 | echo "param error" 49 | exit 2 50 | fi 51 | build $2 $3 52 | ;; 53 | *) 54 | echo -e "\n\tusage: \n\n\ 55 | \tbuild one: build.sh one srv web\n\n\ 56 | \tbuild all: build.sh all\n" 57 | exit 2 58 | ;; 59 | esac 60 | -------------------------------------------------------------------------------- /scripts/dockerfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | gen(){ 6 | pname=$1_$2 7 | 8 | filepath=./deployments/bin/$1/${pname} 9 | if [[ ! -d ${filepath} ]];then 10 | mkdir -p ${filepath} 11 | fi 12 | 13 | if [[ ! -f ${filepath}/Dockerfile ]];then 14 | touch ${filepath}/Dockerfile 15 | fi 16 | 17 | cat>${filepath}/Dockerfile<