├── .gitignore ├── LICENSE ├── README.md ├── app ├── app.go ├── cron.go ├── grpc.go ├── http.go └── queue.go ├── config ├── conf.go └── setting │ └── setting.go ├── const.go ├── go.mod ├── go.sum ├── internal ├── config │ ├── conf.go │ └── env.go ├── logging │ └── log.go ├── metrics_mux │ ├── elastic_metrics.go │ ├── pprof.go │ └── prometheus_metrics.go ├── service │ └── slb │ │ ├── etcdconfig │ │ └── service_config.go │ │ └── service.go ├── setup │ ├── gateway.go │ ├── grpc.go │ ├── http_server.go │ └── server_mux.go ├── util │ ├── etcd.go │ └── rand.go └── vars │ └── var.go ├── kelvins-http开启H2调用抓包.png ├── kelvins-rpc调用抓包.png ├── kelvins.go ├── logo.png ├── setup ├── g2cache.go ├── mongodb.go ├── mysql.go ├── queue.go └── redis.go ├── util ├── client_conn │ ├── balancer.go │ ├── client_conn.go │ ├── color.go │ ├── conn_cache.go │ └── resolver.go ├── gin_helper │ ├── code.go │ ├── cors.go │ ├── form.go │ ├── jwt.go │ ├── metadata.go │ ├── msg.go │ ├── rate_limit.go │ └── response.go ├── goroutine │ ├── errgroup.go │ └── grpool.go ├── grpc_interceptor │ ├── client_interceptor.go │ └── server_interceptor.go ├── http_helper │ └── websocket.go ├── kprocess │ ├── tableflip_darwin.go │ ├── tableflip_linux.go │ └── tableflip_windows.go ├── middleware │ ├── rate_limit.go │ ├── rpc_auth.go │ ├── rpc_ratelimit.go │ ├── rpc_token.go │ └── rpc_token_test.go ├── mysql_callback │ └── time_callback.go ├── mysql_model │ └── mysql_model.go ├── queue_helper │ └── publish.go ├── rpc_helper │ └── metadata.go ├── startup │ ├── startup.go │ ├── startup_darwin.go │ ├── startup_linux.go │ └── startup_windows.go └── test_tool │ └── ghz.go ├── vars.go └── 交流群.JPG /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | vendor -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, cristiane 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kelvins 2 | [![kelvins](logo.png)](https://gitee.com/kelvins-io) 3 | 4 | go/golang微服务框架 5 | 6 | ### 支持特性 7 | 注册服务,发现服务,grpc/http gateway,cron,queue,http/gin服务(兼容h1.1,h2),插拔式配置加载,双orm支持,mysql,mongo支持,事件总线,日志,异步任务池, 8 | Prometheus/pprof监控,进程优雅重启,应用自定义配置,启动flag参数指定,应用hook,工具类(由kelvins-io/common支持),全局变量vars, 9 | 在线应用负载均衡,启动命令,RPC健康检查,接入授权,ghz压力测试tool,gRPC服务端&客户端参数配置,在线服务限流,kelvins-tools工具箱,watch服务在线状态,g2cache多级缓存 10 | 11 | #### 即将支持 12 | 熔断,异常接入sentry 13 | 14 | ### 软件环境 15 | > go 1.13.15+ 16 | 17 | rpc采用gRPC,如果使用请安装依赖库 18 | ```shell 19 | protoc 安装方法如下 20 | wget https://github.com/google/protobuf/releases/download/v3.14.0/protobuf-all-3.14.0.zip 21 | unzip protobuf-all-3.14.0.zip 22 | cd protobuf-3.14.0/ 23 | ./configure 24 | make 25 | make install 26 | # 如果报错请执行 27 | ldconfig 28 | # grpc相关 29 | go get -u google.golang.org/grpc@v1.32.0 30 | go get -u google.golang.org/protobuf@v1.25.0 31 | go get -u github.com/golang/protobuf/protoc-gen-go@v.1.4.3 32 | go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.14.3 33 | go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger 34 | go get -u github.com/jteeuwen/go-bindata/... 35 | go get -u github.com/elazarl/go-bindata-assetfs/... 36 | python 2.7或3.5 37 | ``` 38 | ### 运行环境变量 39 | etcd集群地址 40 | ETCDV3_SERVER_URLS 41 | ``` 42 | 笔者自己环境的配置(仅做参考) 43 | export ETCDCTL_API=3 44 | export ETCDV3_SERVER_URLS=http://10.211.55.4:2379,http://10.211.55.7:2379 45 | 如果自己搭建etcd集群最少需要两个节点(一主一从)本地搭建参考:https://gitee.com/cristiane/micro-mall-api/blob/master/%E5%BE%AE%E5%95%86%E5%9F%8EETCD%E9%83%A8%E7%BD%B2.pdf 46 | ``` 47 | 48 | ### 生成应用 49 | 参考kelvins-tools工具箱:https://gitee.com/kelvins-io/kelvins-tools/blob/master/README.md 50 | 51 | ### 目前最新版支持的配置文件 52 | ``` 53 | 配置文件默认就在项目根目录下/etc/app.ini文件,大多数micro-mall-开头的项目已经包含了所需的配置文件,根据自己本地的配置修改即可 54 | ``` 55 | 截止最新版kelvins,全部支持的项目配置(./etc/app.ini)内容 56 | 配置项为空则使用默认值,当然相应业务对应的功能也不能用 57 | 58 | **建议配置项: 59 | kelvins-server 60 | Environment可选值:dev,test,release,prod 61 | AppName如果不为空则优先级高于代码注入的名字 62 | PIDFile:注意Windows环境下的路径格式 63 | ```ini 64 | [kelvins-server] 65 | AppName = "kelvins-template" 66 | Environment = "dev" 67 | PIDFile = "./kelvins-app.pid" 68 | ``` 69 | 70 | kelvins-logger 71 | 日志:级别,路径等 72 | Level可选值:debug,warn,info,error 73 | RootPath:注意Windows环境下的路径格式 74 | ```ini 75 | [kelvins-logger] 76 | RootPath = "./logs" 77 | Level = "debug" 78 | ``` 79 | 80 | --自选配置项: 81 | kelvins-http-server 82 | 配置rpc,http服务 83 | *Timeout时间单位#秒,Network和*Timeout仅对在线应用(h2c->gPRC,http)有效 84 | SupportH2仅对http服务有效,RPC调用默认就是H2 85 | http服务开启H2后任然兼容HTTP1.1,调用方想使用http2需要配置http客户端transport将协议升级到http2 86 | ```ini 87 | [kelvins-http-server] 88 | Network = "tcp" 89 | ReadTimeout = 30 90 | WriteTimeout = 30 91 | IdleTimeout = 30 92 | SupportH2 = false 93 | ``` 94 | 95 | kelvins-http-rate-limit 96 | 配置http服务限流 97 | MaxConcurrent 最大并发数(大于0有效) 98 | ```ini 99 | [kelvins-http-rate-limit] 100 | MaxConcurrent = 0 101 | ``` 102 | 103 | kelvins-mysql 104 | MySQL:连接配置信息 105 | *Timeout 单位为时间表达字符串 106 | ```ini 107 | [kelvins-mysql] 108 | Host = "127.0.0.1:3306" 109 | UserName = "root" 110 | Password = "2123afsdfadsffasdf" 111 | DBName = "micro_mall_user" 112 | Charset = "utf8" 113 | PoolNum = 10 114 | MaxIdleConns = 5 115 | ConnMaxLifeSecond = 3600 116 | MultiStatements = true 117 | ParseTime = true 118 | ConnectionTimeout = "30s" 119 | WriteTimeout = "30s" 120 | ReadTimeout = "30s" 121 | ``` 122 | 123 | kelvins-redis 124 | Redis:连接配置信息 125 | *Timeout 时间单位秒 126 | ```ini 127 | [kelvins-redis] 128 | Host = "127.0.0.1:6379" 129 | Password = "f434rtafadsfasd" 130 | DB = 1 131 | PoolNum = 10 132 | IdleTimeout = 300 133 | ConnectTimeout = 30 134 | ReadTimeout = 30 135 | WriteTimeout = 30 136 | ``` 137 | 138 | kelvins-g2cache 139 | CacheDebug: 是否开启debug模式 140 | CacheMonitor: 是否打开监控统计 141 | FreeCacheSize: 默认local缓存(freecache)最大内存字节数 142 | OutCachePubSub: 是否为实例开启发布订阅功能 143 | ```ini 144 | [kelvins-g2cache] 145 | CacheDebug = false 146 | CacheMonitor = false 147 | FreeCacheSize = 52428800 148 | RedisConfDSN = "127.0.0.1:6379" 149 | RedisConfDB = 3 150 | RedisConfPwd = "xxxx" 151 | RedisConfMaxConn = 10 152 | OutCachePubSub = false 153 | PubSubRedisChannel = "kelvins-g2cache-pubsub-channel" 154 | PubSubRedisConfDSN = "127.0.0.1:6379" 155 | PubSubRedisConfDB = 5 156 | PubSubRedisConfPwd = "xxxx" 157 | PubSubRedisConfMaxConn = 3 158 | ``` 159 | 160 | kelvins-mongodb 161 | MongoDB:连接配置信息 162 | ```ini 163 | [kelvins-mongodb] 164 | Uri = "mongodb://127.0.0.1:27017" 165 | Username = "admin" 166 | Password = "fadfadsf3" 167 | Database = "micro_mall_sku" 168 | AuthSource = "admin" 169 | MaxPoolSize = 9 170 | MinPoolSize = 3 171 | ``` 172 | 173 | kelvins-queue-redis 174 | 队列功能需要的Redis配置 175 | ResultsExpireIn 单位秒 176 | DisableConsume 表示作为queue类型应用时是否在该队列上执行消费任务 177 | ```ini 178 | [kelvins-queue-redis] 179 | Broker = "redis://xxx" 180 | DefaultQueue = "user_register_notice" 181 | ResultBackend = "redis://fdfsfds@127.0.0.1:6379/8" 182 | ResultsExpireIn = 3600 183 | DisableConsume = false 184 | TaskRetryCount = 3 185 | TaskRetryTimeout = 60 186 | ``` 187 | 188 | kelvins-queue-ali-amqp 189 | 队列功能-阿里云队列(在阿里云购买的amqp) 190 | ResultsExpireIn 单位秒 191 | PrefetchCount 一次获取消息数量 192 | DisableConsume 表示作为queue类型应用时是否在该队列上执行消费任务 193 | ```ini 194 | [kelvins-queue-ali-amqp] 195 | AccessKey = "ffwefwettgt" 196 | SecretKey = "dfadfasdfasd" 197 | AliUid = 11 198 | EndPoint = "localhost:0909" 199 | VHost = "/kelvins-io" 200 | DefaultQueue = "queue1" 201 | ResultBackend = "redis://xxx@127.0.0.1:6379/8" 202 | ResultsExpireIn = 3600 203 | Exchange = "user_register_notice" 204 | ExchangeType = "direct" 205 | BindingKey = "user_register_notice" 206 | PrefetchCount = 6 207 | DisableConsume = false 208 | TaskRetryCount = 3 209 | TaskRetryTimeout = 60 210 | ``` 211 | 212 | kelvins-queue-amqp 213 | 队列功能-amqp协议(也就是自己搭建的rabbitmq) 214 | ResultsExpireIn 单位秒 215 | PrefetchCount 一次获取消息数量 216 | DisableConsume 表示作为queue类型应用时是否在该队列上执行消费任务 217 | ```ini 218 | [kelvins-queue-amqp] 219 | Broker = "amqp://micro-mall:xx@127.0.0.1:5672/micro-mall" 220 | DefaultQueue = "user_register_notice" 221 | ResultBackend = "redis://xxx@127.0.0.1:6379/8" 222 | ResultsExpireIn = 3600 223 | Exchange = "user_register_notice" 224 | ExchangeType = "direct" 225 | BindingKey = "user_register_notice" 226 | PrefetchCount = 5 227 | DisableConsume = false 228 | TaskRetryCount = 3 229 | TaskRetryTimeout = 60 230 | ``` 231 | 232 | kelvins-queue-ali-rocketmq 233 | 队列功能,事件订阅功能(阿里云) 234 | ```ini 235 | [kelvins-queue-ali-rocketmq] 236 | BusinessName = "kelvins-io" 237 | RegionId = "firuutu" 238 | AccessKey = "dqwkjd8njf" 239 | SecretKey = "xoik-94m3" 240 | InstanceId = "8fdac-90jcc" 241 | HttpEndpoint = "https://aliyun.com" 242 | ``` 243 | 244 | kelvins-queue-server 245 | 队列消费者配置 246 | WorkerConcurrency 表示启用多少个协程执行消费任务(默认值为任务列表长度) 247 | CustomQueueList 表示在配置的[kelvins-queue-amqp/ali-amqp/redis]队列上执行消费任务 248 | ```ini 249 | [kelvins-queue-server] 250 | WorkerConcurrency = 5 251 | CustomQueueList = "queue1,queue2,queue3" 252 | ``` 253 | 254 | kelvins-gpool 255 | 异步任务池 256 | WorkerNum 异步协程的数量 257 | JobChanLen 等待任务队列长度上限 258 | ```ini 259 | [kelvins-gpool] 260 | WorkerNum = 10 261 | JobChanLen = 1000 262 | ``` 263 | 264 | kelvins-jwt 265 | gin中间件 266 | Secret 签名token用到的私钥 267 | TokenExpireSecond 签出的token有效期,单位秒 268 | ```ini 269 | [kelvins-jwt] 270 | Secret = "私钥" 271 | TokenExpireSecond = 3600 272 | ``` 273 | 274 | kelvins-auth 275 | RPC接入授权,不配置或者token为空表示不开启auth 276 | TransportSecurity 表示是否必须加密传输 277 | ExpireSecond token签名有效期,接收到请求的当前时间前后ExpireSecond秒都有效(默认30s) 278 | 推荐使用如下配置: 279 | ```ini 280 | [kelvins-rpc-auth] 281 | Token = "abc1234" 282 | ExpireSecond = 100 283 | TransportSecurity = false 284 | ``` 285 | 286 | kelvins RPC-gRPC采用h2c(非TLS的http2) 接入方式(为了兼容http gateway) 287 | 下面这些RPC参数(如无特殊无需配置)生效的优先级:配置文件 > 代码设置 > 默认值 288 | kelvins-rpc-server 289 | NumServerWorkers RPC服务端启用多少个常驻协程处理请求(为0表示每个请求一个协程处理) 290 | ConnectionTimeout 连接超时(单位秒,为0则使用默认值120s)(h2c接入rpc方式则无效) 291 | DisableHealthServer 为true表示当前服务不注册健康server(调用方调用时健康检查将无效) 292 | DisableClientDialHealthCheck 为true表示作为调用RPC服务的客户端不检查已建立的其它服务的rpc连接的健康状态 293 | RPC服务端参数,各参数为零则使用默认值 294 | ```ini 295 | [kelvins-rpc-server] 296 | NumServerWorkers = 50 297 | ConnectionTimeout = 120 298 | DisableHealthServer = false 299 | DisableClientDialHealthCheck = false 300 | ``` 301 | 302 | kelvins-rpc-rate-limit 303 | rpc服务限流 304 | MaxConcurrent 最大并发数(大于0有效) 305 | ```ini 306 | [kelvins-rpc-rate-limit] 307 | MaxConcurrent = 0 308 | ``` 309 | 310 | kelvins-rpc-server-kp 311 | RPC服务端keepalive参数 312 | PingClientIntervalTime 在这段时间后客户端没有任何活动服务器将主动ping客户端,单位秒 313 | MaxConnectionIdle 连接最大闲置时间,时间单位#秒 314 | ```ini 315 | [kelvins-rpc-server-kp] 316 | PingClientIntervalTime = 3600 317 | MaxConnectionIdle = 7200 318 | ``` 319 | 320 | kelvins-rpc-server-kep 321 | RPC服务端keepalive应对策略 322 | ClientMinIntervalTime 客户端keepalive的最小时间间隔,单位秒 323 | PermitWithoutStream 为true表示即使没有活动的RPC服务端也允许客户端发送心跳 324 | ```ini 325 | [kelvins-rpc-server-kep] 326 | ClientMinIntervalTime = 300 327 | PermitWithoutStream = true 328 | ``` 329 | 330 | kelvins-rpc-client-kp 331 | RPC客户端keepalive参数 332 | PingServerIntervalTime 客户端保活ping服务器的时间间隔,单位秒 333 | PermitWithoutStream 允许客户端在没有活动请求时也向服务器发送心跳 334 | ```ini 335 | [kelvins-rpc-client-kp] 336 | PingServerIntervalTime = 360 337 | PermitWithoutStream = true 338 | ``` 339 | 340 | kelvins-rpc-transport-buffer 341 | RPC传输buffer 342 | 单位#KB 343 | ```ini 344 | [kelvins-rpc-transport-buffer] 345 | ServerReadBufSizeKB = 32 346 | ServerWriteBufSizeKB = 32 347 | ClientReadBufSizeKB = 32 348 | ClientWriteBufSizeKB = 32 349 | ``` 350 | ++自定义配置项,根据项目本身而定 351 | micro-mall-api/etc/app.ini#EmailConfig就属于自定义配置项 352 | 353 | --启动flag参数 354 | 说明:flag参数优先级高于配置文件中同名配置参数,flag参数均可不指定,默认从进程运行目录etc/app.ini加载,日志文件路径默认在进程运行目录logs 355 | -logger_level 日志级别 356 | -logger_path 日志文件路径 357 | -conf_file 配置文件(ini文件)路径 358 | -env 运行环境变量:dev test release prod 359 | -s start 启动进程 360 | -s restart 重启当前进程(Windows平台无效) 361 | -s stop 停止当前进程 362 | 363 | ### 使用参考 364 | 1. 注册APP,在main.go中注册application 365 | ```go 366 | package main 367 | 368 | import ( 369 | "crypto/tls" 370 | "gitee.com/cristiane/micro-mall-users/startup" 371 | "gitee.com/kelvins-io/kelvins" 372 | "gitee.com/kelvins-io/kelvins/app" 373 | ) 374 | 375 | const APP_NAME = "micro-mall-users" 376 | 377 | func main() { 378 | application := &kelvins.GRPCApplication{ 379 | Application: &kelvins.Application{ 380 | LoadConfig: startup.LoadConfig, // 加载自定义配置 381 | SetupVars: startup.SetupVars, // 初始自定义变量 382 | Name: APP_NAME, 383 | }, 384 | TlsConfig: &tls.Config{ 385 | // 配置应用证书,仅仅对grpc,http类应用支持 386 | }, 387 | NumServerWorkers: 50, // rpc工作协程数 388 | RegisterGRPCHealthHandle: startup.RegisterGRPCHealthHandle, // 异步RPC健康状态维护 389 | RegisterGRPCServer: startup.RegisterGRPCServer, // 注册RPC 390 | RegisterGateway: startup.RegisterGateway, // 注册gateway接入 391 | RegisterHttpRoute: startup.RegisterHttpRoute, // 注册HTTP mutex 392 | } 393 | app.RunGRPCApplication(application) // 只能运行一个类型APP 394 | } 395 | ``` 396 | 397 | 2. RPC健康检查 398 | 当RPC APP的 RegisterGRPCHealthHandle 不为nil且没有关闭health server时,kelvins就会为服务注入健康检查server,并在协程中启动监控维护函数 399 | 使用grpc-health-probe工具命令进行健康检查 400 | kelvins rpc对健康检查接入做了免授权,所以即使服务开启了token验证也是可用的 401 | ```shell 402 | # 安装grpc-health-probe 403 | git clone https://github.com/grpc-ecosystem/grpc-health-probe && cd grpc-health-probe && go build 404 | # 查看命令 405 | grpc-health-probe --help 406 | # 对指定服务监控检查,服务名必须正确 完整:服务包名.服务名 407 | grpc-health-probe -addr=127.0.0.1:58688 -service="kelvins_template.YourService" 408 | # 对整体服务健康检查 409 | grpc-health-probe -addr=127.0.0.1:58688 -service="" 410 | ``` 411 | 3. 基于http方式请求RPC服务(前提是注册了rpc-gateway),http服务 412 | ```shell 413 | # 获取rpc-gateway header 414 | # 根据proto定义的扩展选项拼接URL 415 | # option (google.api.http) = { 416 | # get: "/v1/service3" 417 | # }; 418 | curl http://service_name:service_port/v1/service3?id=100 -i -H 'authorization:Bearer v1.5da4f611ce5bc2052b303f6310e98b60941641498e33095c46e7aba17cadbfa9.1632475768' 419 | HTTP/1.1 200 OK 420 | Content-Type: application/json 421 | Grpc-Metadata-Content-Type: application/grpc 422 | Grpc-Metadata-Trailer: Grpc-Status 423 | Grpc-Metadata-Trailer: Grpc-Message 424 | Grpc-Metadata-Trailer: Grpc-Status-Details-Bin 425 | Grpc-Metadata-X-Service-Name: kelvins-template 426 | Grpc-Metadata-X-Service-Node: 192.168.0.101(hostname) 427 | Grpc-Metadata-X-Powered-By: kelvins/rpc 1.5.10 428 | Grpc-Metadata-X-Request-Id: 5664beaa-1c43-4eec-98d7-c611972c7303 429 | Trailer: Grpc-Trailer-X-Response-Time 430 | Trailer: Grpc-Trailer-X-Handle-Time 431 | Date: Mon, 20 Sep 2021 08:11:19 GMT 432 | Transfer-Encoding: chunked 433 | {"common":{"msg":"99"},"result":"service3 "} 434 | Grpc-Trailer-X-Handle-Time: 0.002057175/s 435 | Grpc-Trailer-X-Response-Time: 2021-09-20 16:11:19.776 436 | 437 | # 获取http服务的header 438 | curl http://service_name:service_port/index -i 439 | HTTP/1.1 200 OK 440 | Content-Type: application/json; charset=utf-8 441 | X-Handle-Time: 0.000097/s 442 | X-Service-Name: kelvins-template-http 443 | X-Powered-By: kelvins/http(gin) 1.5.10 444 | X-Request-Id: 69b8e804-4c5b-4091-b485-88400fceedc7 445 | X-Response-Time: 2021-09-20 16:19:39.783 446 | Date: Mon, 20 Sep 2021 08:19:39 GMT 447 | Content-Length: 105 448 | ``` 449 | 450 | 4. 在线服务(rpc,http)调用接口,使用wireshark抓包 451 | rpc服务调用 /kelvins_template.YourService/Service3 => 452 | ![avatar](./kelvins-rpc调用抓包.png) 453 | http/h2服务调用 /hello接口 => 454 | 进行http2调用需要客户端发起协议升级,配置http2 transport 455 | ![avatar](./kelvins-http开启H2调用抓包.png) 456 | 457 | ### 更新日志 458 | 时间 | 内容 | 贡献者 | 备注 459 | ---|------|------|--- 460 | 2020-6-1 | 基础开发阶段 | https://gitee.com/cristiane | 结构思考,基础搭建 461 | 2020-8-27 | 预览版上线 | https://gitee.com/cristiane | 支持gRPC,HTTP,crontab,queue类应用 462 | 2020-9-10 | 增加MongoDB支持 | https://gitee.com/cristiane | 基于配置加载MongoDB来初始化应用 463 | 2020-9-13 | 支持Redis队列 | https://gitee.com/cristiane | 基于配置加载queue-Redis来初始化应用 464 | 2020-11-24 | 若干更新 | https://gitee.com/cristiane | 若干更新 465 | 2021-4-5 | 支持应用优雅重启,退出 | https://gitee.com/cristiane | 基于操作系统信号,各平台有差异 466 | 2021-4-19 | 支持gin | https://gitee.com/cristiane | 允许将gin http handler注册到应用 467 | 2021-7-9 | 兼容Windows | https://gitee.com/cristiane | 修复Windows平台应用不能启动问题 468 | 2021-8-1 | 应用退出执行函数优化 | https://gitee.com/cristiane | 应用退出时异常处理 469 | 2021-8-1 | 应用支持负载均衡 | https://gitee.com/cristiane | 针对gRPC,http应用;同一应用多实例自动负载均衡 470 | 2021-8-7 | 启动命令 | https://gitee.com/cristiane | -s启动参数,支持启动进程,重启进程,停止进程 471 | 2021-8-13 | RPC健康检查 | https://gitee.com/cristiane | 支持使用grpc-health-probe等工具进行健康检查 472 | 2021-8-14 | RPC接入授权-token | https://gitee.com/cristiane | RPC应用支持开启接入授权 473 | 2021-8-14 | RPC-ghz压测试工具 | https://gitee.com/cristiane | 支持对RPC应用进行压力测试并输出报告 474 | 2021-9-1 | 若干更新 | https://gitee.com/cristiane | rpc日志对齐&rpc server参数配置化&启动优化 475 | 2021-9-11 | 若干更新 | https://gitee.com/cristiane | client_service,print,queue等若干优化 476 | 2021-9-18 | 运行环境优化,http优化 | https://gitee.com/cristiane | 根据运行环境日志打印 477 | 2021-9-20 | rpc,http注入metadata,服务注册优化 | https://gitee.com/cristiane | 诸如request-id,version,请求耗时,服务名,服务节点等 478 | 2021-9-25 | 重构rpc服务拦截器,http服务支持启用H2 | https://gitee.com/cristiane | 拦截器,http2 479 | 2021-10-1 | RPC,http服务支持限流器 | https://gitee.com/cristiane | 限流 480 | 2021-10-15 | watch在线服务在线状态 | https://gitee.com/cristiane | 监听etcd服务节点触发resolver 481 | 2021-10-15 | 在线服务注册etcd增加所处网络IP,服务类型 | https://gitee.com/cristiane | 不需要再单独配置服务host 482 | 2021-11-5 | 引入g2cache多级缓存库,修复bug | https://gitee.com/cristiane | 基于配置启用g2cache模块 483 | 484 | 485 | ### 业务应用 486 | micro-mall-api系列共计22+个服务:https://gitee.com/cristiane/micro-mall-api 487 | 488 | ###技术交流 489 | QQ群:852053097 490 | ![avatar](./交流群.JPG) 491 | 邮件:1225807604@qq.com -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "math/big" 9 | "net" 10 | "net/url" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "sync/atomic" 17 | "time" 18 | 19 | "gitee.com/kelvins-io/common/event" 20 | "gitee.com/kelvins-io/common/log" 21 | "gitee.com/kelvins-io/kelvins" 22 | "gitee.com/kelvins-io/kelvins/internal/config" 23 | "gitee.com/kelvins-io/kelvins/internal/logging" 24 | "gitee.com/kelvins-io/kelvins/internal/service/slb" 25 | "gitee.com/kelvins-io/kelvins/internal/service/slb/etcdconfig" 26 | "gitee.com/kelvins-io/kelvins/internal/util" 27 | "gitee.com/kelvins-io/kelvins/internal/vars" 28 | "gitee.com/kelvins-io/kelvins/setup" 29 | "gitee.com/kelvins-io/kelvins/util/goroutine" 30 | "gitee.com/kelvins-io/kelvins/util/startup" 31 | ) 32 | 33 | const ( 34 | DefaultLoggerRootPath = "./logs" 35 | DefaultLoggerLevel = "info" 36 | ) 37 | 38 | var ( 39 | flagLoggerLevel = flag.String("logger_level", "", "set logger level eg: debug,warn,error,info") 40 | flagLoggerPath = flag.String("logger_path", "", "set logger root path eg: /tmp/kelvins-app") 41 | flagEnv = flag.String("env", "", "set exec environment eg: dev,test,prod") 42 | ) 43 | 44 | func initApplication(application *kelvins.Application) error { 45 | // 1 show app version 46 | showAppVersion(application) 47 | 48 | // 2. load app config 49 | err := config.LoadDefaultConfig(application) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | // 3 setup app proc 55 | setupApplicationProcess(application) 56 | 57 | // 4 startup control 58 | next, err := startUpControl(kelvins.PIDFile) 59 | if err != nil { 60 | return err 61 | } 62 | if !next { 63 | close(appCloseCh) 64 | <-kelvins.AppCloseCh 65 | return nil 66 | } 67 | 68 | // 5 init user config 69 | if application.LoadConfig != nil { 70 | err = application.LoadConfig() 71 | if err != nil { 72 | return err 73 | } 74 | } 75 | 76 | // 6 init system vars 77 | kelvins.AppName = application.Name 78 | if kelvins.ServerSetting != nil && kelvins.ServerSetting.AppName != "" { 79 | kelvins.AppName = kelvins.ServerSetting.AppName 80 | application.Name = kelvins.ServerSetting.AppName 81 | } 82 | if kelvins.AppName == "" { 83 | logging.Fatal("Application name can not be empty") 84 | } 85 | 86 | // init logger environ vars 87 | flag.Parse() 88 | loggerPath := DefaultLoggerRootPath 89 | if kelvins.LoggerSetting != nil && kelvins.LoggerSetting.RootPath != "" { 90 | loggerPath = kelvins.LoggerSetting.RootPath 91 | } 92 | if application.LoggerRootPath != "" { 93 | loggerPath = application.LoggerRootPath 94 | } 95 | if *flagLoggerPath != "" { 96 | loggerPath = *flagLoggerPath 97 | } 98 | application.LoggerRootPath = loggerPath 99 | 100 | loggerLevel := DefaultLoggerLevel 101 | if kelvins.LoggerSetting != nil && kelvins.LoggerSetting.Level != "" { 102 | loggerLevel = kelvins.LoggerSetting.Level 103 | } 104 | if application.LoggerLevel != "" { 105 | loggerLevel = application.LoggerLevel 106 | } 107 | if *flagLoggerLevel != "" { 108 | loggerLevel = *flagLoggerLevel 109 | } 110 | application.LoggerLevel = loggerLevel 111 | 112 | // init environment 113 | environment := config.DefaultEnvironmentProd 114 | if kelvins.ServerSetting != nil && kelvins.ServerSetting.Environment != "" { 115 | environment = kelvins.ServerSetting.Environment 116 | } 117 | if application.Environment != "" { 118 | environment = application.Environment 119 | } 120 | if *flagEnv != "" { 121 | environment = *flagEnv 122 | } 123 | application.Environment = environment 124 | 125 | // 7 init log 126 | err = log.InitGlobalConfig(loggerPath, loggerLevel, application.Name) 127 | if err != nil { 128 | return fmt.Errorf("log.InitGlobalConfig: %v", err) 129 | } 130 | 131 | // 8. setup vars 132 | // setup app vars 133 | err = setupCommonVars(application) 134 | if err != nil { 135 | return err 136 | } 137 | // setup user vars 138 | if application.SetupVars != nil { 139 | err = application.SetupVars() 140 | if err != nil { 141 | return fmt.Errorf("application.SetupVars err: %v", err) 142 | } 143 | } 144 | return nil 145 | } 146 | 147 | // setup AppCloseCh pid 148 | func setupApplicationProcess(application *kelvins.Application) { 149 | kelvins.AppCloseCh = appCloseCh 150 | vars.AppCloseCh = appCloseCh 151 | vars.Version = kelvins.Version 152 | if kelvins.ServerSetting != nil { 153 | if kelvins.ServerSetting.PIDFile != "" { 154 | kelvins.PIDFile = filepath.Dir(kelvins.ServerSetting.PIDFile) 155 | } else { 156 | wd, _ := os.Getwd() 157 | kelvins.PIDFile = fmt.Sprintf("%s/%s.pid", wd, application.Name) 158 | //if runtime.GOOS == "windows" { 159 | // kelvins.PIDFile = filepath.ToSlash(kelvins.PIDFile) 160 | //} 161 | } 162 | } 163 | } 164 | 165 | // setupCommonVars setup application global vars. 166 | func setupCommonVars(application *kelvins.Application) error { 167 | var err error 168 | if kelvins.MysqlSetting != nil && kelvins.MysqlSetting.Host != "" { 169 | kelvins.MysqlSetting.LoggerLevel = application.LoggerLevel 170 | kelvins.MysqlSetting.Environment = application.Environment 171 | logger, err := log.GetCustomLogger("db-log", "mysql") 172 | if err != nil { 173 | return err 174 | } 175 | kelvins.MysqlSetting.Logger = logger 176 | kelvins.GORM_DBEngine, err = setup.NewMySQLWithGORM(kelvins.MysqlSetting) 177 | kelvins.XORM_DBEngine, err = setup.NewMySQLWithXORM(kelvins.MysqlSetting) 178 | if err != nil { 179 | return err 180 | } 181 | } 182 | 183 | if kelvins.MongoDBSetting != nil && kelvins.MongoDBSetting.Uri != "" { 184 | kelvins.MongoDBClient, err = setup.NewMongoDBClient(kelvins.MongoDBSetting) 185 | if err != nil { 186 | return err 187 | } 188 | } 189 | 190 | if kelvins.RedisSetting != nil && kelvins.RedisSetting.Host != "" { 191 | kelvins.RedisConn, err = setup.NewRedis(kelvins.RedisSetting) 192 | if err != nil { 193 | return err 194 | } 195 | } 196 | 197 | if kelvins.GPoolSetting != nil && kelvins.GPoolSetting.JobChanLen > 0 && kelvins.GPoolSetting.WorkerNum > 0 { 198 | kelvins.GPool = goroutine.NewPool(kelvins.GPoolSetting.WorkerNum, kelvins.GPoolSetting.JobChanLen) 199 | } 200 | 201 | if kelvins.G2CacheSetting != nil && kelvins.G2CacheSetting.RedisConfDSN != "" { 202 | kelvins.G2CacheEngine, err = setup.NewG2Cache(kelvins.G2CacheSetting, nil, nil) 203 | if err != nil { 204 | return err 205 | } 206 | } 207 | 208 | kelvins.FrameworkLogger, err = log.GetCustomLogger("framework", "framework") 209 | if err != nil { 210 | return err 211 | } 212 | vars.FrameworkLogger = kelvins.FrameworkLogger 213 | 214 | kelvins.ErrLogger, err = log.GetErrLogger("err") 215 | if err != nil { 216 | return err 217 | } 218 | vars.ErrLogger = kelvins.ErrLogger 219 | 220 | kelvins.BusinessLogger, err = log.GetBusinessLogger("business") 221 | if err != nil { 222 | return err 223 | } 224 | vars.BusinessLogger = kelvins.BusinessLogger 225 | 226 | kelvins.AccessLogger, err = log.GetAccessLogger("access") 227 | if err != nil { 228 | return err 229 | } 230 | vars.AccessLogger = kelvins.AccessLogger 231 | 232 | // init event server 233 | if kelvins.AliRocketMQSetting != nil && kelvins.AliRocketMQSetting.InstanceId != "" { 234 | // new event server 235 | var queueLogger log.LoggerContextIface 236 | if kelvins.ServerSetting != nil { 237 | switch kelvins.ServerSetting.Environment { 238 | case config.DefaultEnvironmentDev: 239 | queueLogger = kelvins.BusinessLogger 240 | case config.DefaultEnvironmentTest: 241 | queueLogger = kelvins.BusinessLogger 242 | default: 243 | } 244 | } 245 | eventServer, err := event.NewEventServer(&event.Config{ 246 | BusinessName: kelvins.AliRocketMQSetting.BusinessName, 247 | RegionId: kelvins.AliRocketMQSetting.RegionId, 248 | AccessKey: kelvins.AliRocketMQSetting.AccessKey, 249 | SecretKey: kelvins.AliRocketMQSetting.SecretKey, 250 | InstanceId: kelvins.AliRocketMQSetting.InstanceId, 251 | HttpEndpoint: kelvins.AliRocketMQSetting.HttpEndpoint, 252 | }, queueLogger) 253 | if err != nil { 254 | return err 255 | } 256 | kelvins.EventServerAliRocketMQ = eventServer 257 | return nil 258 | } 259 | 260 | return nil 261 | } 262 | 263 | func setupCommonQueue(namedTaskFunc map[string]interface{}) error { 264 | if kelvins.QueueRedisSetting != nil && kelvins.QueueRedisSetting.Broker != "" { 265 | queueServ, err := setup.NewRedisQueue(kelvins.QueueRedisSetting, namedTaskFunc) 266 | if err != nil { 267 | return err 268 | } 269 | kelvins.QueueServerRedis = queueServ 270 | } 271 | if kelvins.QueueAMQPSetting != nil && kelvins.QueueAMQPSetting.Broker != "" { 272 | queueServ, err := setup.NewAMQPQueue(kelvins.QueueAMQPSetting, namedTaskFunc) 273 | if err != nil { 274 | return err 275 | } 276 | kelvins.QueueServerAMQP = queueServ 277 | } 278 | if kelvins.QueueAliAMQPSetting != nil && kelvins.QueueAliAMQPSetting.VHost != "" { 279 | queueServ, err := setup.NewAliAMQPQueue(kelvins.QueueAliAMQPSetting, namedTaskFunc) 280 | if err != nil { 281 | return err 282 | } 283 | kelvins.QueueServerAliAMQP = queueServ 284 | } 285 | 286 | return nil 287 | } 288 | 289 | // appCloseChOne is appCloseCh sync.Once 290 | var appCloseChOne sync.Once 291 | var appCloseCh = make(chan struct{}) 292 | 293 | func appShutdown(application *kelvins.Application) error { 294 | if !appProcessNext { 295 | return nil 296 | } 297 | appCloseChOne.Do(func() { 298 | close(appCloseCh) 299 | }) 300 | if application.StopFunc != nil { 301 | err := application.StopFunc() 302 | if err != nil { 303 | return err 304 | } 305 | } 306 | if kelvins.GPool != nil { 307 | kelvins.GPool.Release() 308 | kelvins.GPool.WaitAll() 309 | } 310 | if kelvins.RedisConn != nil { 311 | err := kelvins.RedisConn.Close() 312 | if err != nil { 313 | return err 314 | } 315 | } 316 | if kelvins.GORM_DBEngine != nil { 317 | err := kelvins.GORM_DBEngine.Close() 318 | if err != nil { 319 | return err 320 | } 321 | } 322 | if kelvins.MongoDBClient != nil { 323 | err := kelvins.MongoDBClient.Close(context.Background()) 324 | if err != nil { 325 | return err 326 | } 327 | } 328 | 329 | return nil 330 | } 331 | 332 | func appPrepareForceExit() { 333 | // Make sure to set a deadline on exiting the process 334 | // after upg.Exit() is closed. No new upgrades can be 335 | // performed if the parent doesn't exit. 336 | if !appProcessNext { 337 | return 338 | } 339 | time.AfterFunc(30*time.Second, func() { 340 | logging.Info("App Graceful shutdown timed out, force exit") 341 | os.Exit(1) 342 | }) 343 | } 344 | 345 | var appProcessNext bool 346 | 347 | func startUpControl(pidFile string) (next bool, err error) { 348 | next, err = startup.ParseCliCommand(pidFile) 349 | if next { 350 | appProcessNext = true 351 | } 352 | return 353 | } 354 | 355 | func showAppVersion(app *kelvins.Application) { 356 | var logo = `%20__%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20___%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%2F%5C%20%5C%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%5C_%20%5C%20%20%20%20%20%20%20%20%20%20%20%20%20__%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%5C%20%5C%20%5C%2F'%5C%20%20%20%20%20%20%20__%5C%2F%2F%5C%20%5C%20%20%20%20__%20%20__%20%2F%5C_%5C%20%20%20%20%20___%20%20%20%20%20%20____%20%20%0A%20%5C%20%5C%20%2C%20%3C%20%20%20%20%20%2F'__%60%5C%5C%20%5C%20%5C%20%20%2F%5C%20%5C%2F%5C%20%5C%5C%2F%5C%20%5C%20%20%2F'%20_%20%60%5C%20%20%20%2F'%2C__%5C%20%0A%20%20%5C%20%5C%20%5C%5C%60%5C%20%20%2F%5C%20%20__%2F%20%5C_%5C%20%5C_%5C%20%5C%20%5C_%2F%20%7C%5C%20%5C%20%5C%20%2F%5C%20%5C%2F%5C%20%5C%20%2F%5C__%2C%20%60%5C%0A%20%20%20%5C%20%5C_%5C%20%5C_%5C%5C%20%5C____%5C%2F%5C____%5C%5C%20%5C___%2F%20%20%5C%20%5C_%5C%5C%20%5C_%5C%20%5C_%5C%5C%2F%5C____%2F%0A%20%20%20%20%5C%2F_%2F%5C%2F_%2F%20%5C%2F____%2F%5C%2F____%2F%20%5C%2F__%2F%20%20%20%20%5C%2F_%2F%20%5C%2F_%2F%5C%2F_%2F%20%5C%2F___%2F%20` 357 | var version = `[Major Version:%v Type:%v]` 358 | var remote = `┌───────────────────────────────────────────────────┐ 359 | │ [Gitee] https://gitee.com/kelvins-io/kelvins │ 360 | │ [GitHub] https://github.com/kelvins-io/kelvins │ 361 | └───────────────────────────────────────────────────┘` 362 | fmt.Println("based on") 363 | logoS, _ := url.QueryUnescape(logo) 364 | fmt.Println(logoS) 365 | fmt.Println("") 366 | fmt.Println(fmt.Sprintf(version, kelvins.Version, kelvins.AppTypeText[app.Type])) 367 | 368 | fmt.Println("") 369 | fmt.Println(remote) 370 | fmt.Println("Go Go Go ==>", app.Name) 371 | } 372 | 373 | func appRegisterEventProducer(register func(event.ProducerIface) error, appType int32) { 374 | server := kelvins.EventServerAliRocketMQ 375 | if server == nil { 376 | return 377 | } 378 | endPoint := server.GetEndpoint() 379 | // should not rely on the behavior of the application code 380 | producerFunc := func() { 381 | err := register(server) 382 | if err != nil { 383 | // kelvins.BusinessLogger must not be nil 384 | if kelvins.BusinessLogger != nil { 385 | kelvins.BusinessLogger.Errorf(context.Background(), "App(type:%v).EventServer endpoint(%v) RegisterEventProducer publish err: %v", 386 | kelvins.AppTypeText[appType], endPoint, err) 387 | } 388 | return 389 | } 390 | } 391 | if kelvins.GPool != nil { 392 | ok := kelvins.GPool.SendJobWithTimeout(producerFunc, 1*time.Second) 393 | if !ok { 394 | go producerFunc() 395 | } 396 | } else { 397 | go producerFunc() 398 | } 399 | } 400 | 401 | func appRegisterEventHandler(register func(event.EventServerIface) error, appType int32) { 402 | server := kelvins.EventServerAliRocketMQ 403 | if server == nil { 404 | return 405 | } 406 | endPoint := server.GetEndpoint() 407 | // should not rely on the behavior of the application code 408 | subscribeFunc := func() { 409 | err := register(server) 410 | if err != nil { 411 | // kelvins.BusinessLogger must not be nil 412 | if kelvins.BusinessLogger != nil { 413 | kelvins.BusinessLogger.Errorf(context.Background(), "App(type:%v).EventServer endpoint(%v) RegisterEventHandler subscribe err: %v", 414 | kelvins.AppTypeText[appType], endPoint, err) 415 | } 416 | return 417 | } 418 | // start event server 419 | err = server.Start() 420 | if err != nil { 421 | // kelvins.BusinessLogger must not be nil 422 | if kelvins.BusinessLogger != nil { 423 | kelvins.BusinessLogger.Errorf(context.Background(), "App(type:%v).EventServer endpoint(%v) Start consume err: %v", 424 | kelvins.AppTypeText[appType], endPoint, err) 425 | } 426 | return 427 | } 428 | } 429 | if kelvins.GPool != nil { 430 | ok := kelvins.GPool.SendJobWithTimeout(subscribeFunc, 1*time.Second) 431 | if !ok { 432 | go subscribeFunc() 433 | } 434 | } else { 435 | go subscribeFunc() 436 | } 437 | } 438 | 439 | func appUnRegisterServiceToEtcd(appName string, port int64) error { 440 | etcdServerUrls := config.GetEtcdV3ServerURLs() 441 | if etcdServerUrls == "" { 442 | if kelvins.ErrLogger != nil { 443 | kelvins.ErrLogger.Errorf(context.TODO(), "etcd not found environment variable(%v)", config.ENV_ETCDV3_SERVER_URLS) 444 | } 445 | return fmt.Errorf("etcd not found environment variable(%v)", config.ENV_ETCDV3_SERVER_URLS) 446 | } 447 | serviceLB := slb.NewService(etcdServerUrls, appName) 448 | serviceConfigClient := etcdconfig.NewServiceConfigClient(serviceLB) 449 | var registerSequence = getServiceSequence(serviceIP, strconv.Itoa(int(port))) 450 | err := serviceConfigClient.ClearConfig(registerSequence) 451 | if err != nil && err != etcdconfig.ErrServiceConfigKeyNotExist { 452 | if kelvins.ErrLogger != nil { 453 | kelvins.ErrLogger.Errorf(context.TODO(), "etcd serviceConfigClient ClearConfig err: %v, key: %v", 454 | err, serviceConfigClient.GetKeyName(appName, registerSequence)) 455 | } 456 | return fmt.Errorf("etcd clear service port exception") 457 | } 458 | 459 | return nil 460 | } 461 | 462 | var serviceIP string 463 | 464 | func appRegisterServiceToEtcd(serviceKind, appName string, initialPort int64) (int64, error) { 465 | var flagPort int64 466 | if initialPort > 0 { // use self define port to start process 467 | flagPort = initialPort 468 | } else { 469 | flagPort = int64(util.RandInt(50000, 60000)) 470 | } 471 | currentPort := strconv.Itoa(int(flagPort)) 472 | etcdServerUrls := config.GetEtcdV3ServerURLs() 473 | if etcdServerUrls == "" { 474 | if kelvins.ErrLogger != nil { 475 | kelvins.ErrLogger.Errorf(context.TODO(), "etcd not found environment variable(%v)", config.ENV_ETCDV3_SERVER_URLS) 476 | } 477 | return flagPort, fmt.Errorf("etcd not found environment variable(%v)", config.ENV_ETCDV3_SERVER_URLS) 478 | } 479 | var err error 480 | serviceIP, err = getOutBoundIP() 481 | if err != nil { 482 | return 0, fmt.Errorf("lookup out bound ip err(%v)", err) 483 | } 484 | 485 | var registerSequence = getServiceSequence(serviceIP, currentPort) 486 | serviceLB := slb.NewService(etcdServerUrls, appName) 487 | serviceConfigClient := etcdconfig.NewServiceConfigClient(serviceLB) 488 | serviceConfig, err := serviceConfigClient.GetConfig(registerSequence) 489 | if err != nil && err != etcdconfig.ErrServiceConfigKeyNotExist { 490 | if kelvins.ErrLogger != nil { 491 | kelvins.ErrLogger.Errorf(context.TODO(), "etcd serviceConfig.GetConfig err: %v ,sequence(%v)", err, registerSequence) 492 | } 493 | return flagPort, fmt.Errorf("etcd register service sequence(%v) exception", registerSequence) 494 | } 495 | if serviceConfig != nil { 496 | isExist := getServiceSequence(serviceConfig.ServiceIP, serviceConfig.ServicePort) == getServiceSequence(serviceIP, currentPort) 497 | if isExist { 498 | if kelvins.ErrLogger != nil { 499 | kelvins.ErrLogger.Errorf(context.TODO(), "etcd serviceConfig.GetConfig sequence(%v) exist", registerSequence) 500 | } 501 | return flagPort, fmt.Errorf("etcd register service sequence(%v) exist", registerSequence) 502 | } 503 | } 504 | 505 | err = serviceConfigClient.WriteConfig(registerSequence, etcdconfig.Config{ 506 | ServiceVersion: kelvins.Version, 507 | ServicePort: currentPort, 508 | ServiceIP: serviceIP, 509 | ServiceKind: serviceKind, 510 | LastModified: time.Now().Format(kelvins.ResponseTimeLayout), 511 | }) 512 | if err != nil { 513 | if kelvins.ErrLogger != nil { 514 | kelvins.ErrLogger.Errorf(context.TODO(), "etcd writeConfig err: %v,sequence(%v) ", err, currentPort) 515 | } 516 | err = fmt.Errorf("etcd register service port(%v) exception", currentPort) 517 | } 518 | vars.ServicePort = currentPort 519 | vars.ServiceIp = serviceIP 520 | return flagPort, err 521 | } 522 | 523 | func getServiceSequence(ip, port string) (key string) { 524 | ret := big.NewInt(0) 525 | ret.SetBytes(net.ParseIP(ip).To4()) 526 | key = fmt.Sprintf("%v_%v", ret.Int64(), port) 527 | return 528 | } 529 | 530 | func getOutBoundIP() (ip string, err error) { 531 | conn, err := net.Dial("udp", "255.255.255.255:53") 532 | if err != nil { 533 | return 534 | } 535 | localAddr := conn.LocalAddr().(*net.UDPAddr) 536 | ip = strings.Split(localAddr.String(), ":")[0] 537 | return 538 | } 539 | 540 | var ( 541 | appInstanceOnce int32 542 | appInstanceOnceErr = errors.New("the same app type can only be registered once") 543 | ) 544 | 545 | func appInstanceOnceValidate() error { 546 | ok := atomic.CompareAndSwapInt32(&appInstanceOnce, 0, 1) 547 | if !ok { 548 | return appInstanceOnceErr 549 | } 550 | return nil 551 | } 552 | -------------------------------------------------------------------------------- /app/cron.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "gitee.com/kelvins-io/common/log" 9 | "gitee.com/kelvins-io/kelvins" 10 | "gitee.com/kelvins-io/kelvins/internal/config" 11 | "gitee.com/kelvins-io/kelvins/internal/logging" 12 | "gitee.com/kelvins-io/kelvins/util/kprocess" 13 | "github.com/google/uuid" 14 | "github.com/robfig/cron/v3" 15 | ) 16 | 17 | // RunCronApplication runs cron application. 18 | func RunCronApplication(application *kelvins.CronApplication) { 19 | if application == nil || application.Application == nil { 20 | panic("cronApplication is nil or application is nil") 21 | } 22 | // app instance once validate 23 | { 24 | err := appInstanceOnceValidate() 25 | if err != nil { 26 | logging.Fatal(err.Error()) 27 | } 28 | } 29 | 30 | application.Type = kelvins.AppTypeCron 31 | kelvins.CronAppInstance = application 32 | 33 | err := runCron(application) 34 | if err != nil { 35 | logging.Infof("cronApp runCron err: %v\n", err) 36 | } 37 | 38 | appPrepareForceExit() 39 | if application.Cron != nil { 40 | application.Cron.Stop() 41 | logging.Info("cronApp Task Stop over") 42 | } 43 | err = appShutdown(application.Application) 44 | if err != nil { 45 | logging.Infof("cronApp appShutdown err: %v\n", err) 46 | } 47 | } 48 | 49 | // runCron prepares cron application. 50 | func runCron(cronApp *kelvins.CronApplication) error { 51 | var err error 52 | 53 | // 1. init application 54 | err = initApplication(cronApp.Application) 55 | if err != nil { 56 | return err 57 | } 58 | if !appProcessNext { 59 | return err 60 | } 61 | 62 | // 2 init cron vars 63 | err = setupCronVars(cronApp) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | // 3 register event handler 69 | if kelvins.EventServerAliRocketMQ != nil { 70 | logging.Info("cronApp Start event server") 71 | if cronApp.RegisterEventProducer != nil { 72 | appRegisterEventProducer(cronApp.RegisterEventProducer, cronApp.Type) 73 | } 74 | if cronApp.RegisterEventHandler != nil { 75 | appRegisterEventHandler(cronApp.RegisterEventHandler, cronApp.Type) 76 | } 77 | } 78 | 79 | // 4. register cron jobs 80 | if cronApp.GenCronJobs != nil { 81 | cronJobs := cronApp.GenCronJobs() 82 | if len(cronJobs) != 0 { 83 | jobNameDict := map[string]int{} 84 | for _, j := range cronJobs { 85 | if j.Name == "" { 86 | return fmt.Errorf("lack of CronJob.Name") 87 | } 88 | if j.Spec == "" { 89 | return fmt.Errorf("lack of CronJob.Spec") 90 | } 91 | if j.Job == nil { 92 | return fmt.Errorf("lack of CronJob.Job") 93 | } 94 | if _, ok := jobNameDict[j.Name]; ok { 95 | return fmt.Errorf("repeat job name: %s", j.Name) 96 | } 97 | jobNameDict[j.Name] = 1 98 | job := &cronJob{ 99 | name: j.Name, 100 | } 101 | var logger log.LoggerContextIface 102 | if kelvins.ServerSetting != nil { 103 | switch kelvins.ServerSetting.Environment { 104 | case config.DefaultEnvironmentDev: 105 | logger = kelvins.AccessLogger 106 | case config.DefaultEnvironmentTest: 107 | logger = kelvins.AccessLogger 108 | default: 109 | } 110 | } 111 | job.logger = logger 112 | _, err = cronApp.Cron.AddFunc(j.Spec, job.warpJob(j.Job)) 113 | if err != nil { 114 | return fmt.Errorf("addFunc err: %v", err) 115 | } 116 | } 117 | } 118 | } 119 | 120 | // 5. run cron app 121 | kp := new(kprocess.KProcess) 122 | _, err = kp.Listen("", "", kelvins.PIDFile) 123 | if err != nil { 124 | return fmt.Errorf("kprocess listen pidFile(%v) err: %v", kelvins.PIDFile, err) 125 | } 126 | logging.Info("cronApp Start cron task") 127 | cronApp.Cron.Start() 128 | 129 | <-kp.Exit() 130 | 131 | return nil 132 | } 133 | 134 | // setupCronVars ... 135 | func setupCronVars(cronApp *kelvins.CronApplication) error { 136 | cronApp.Cron = cron.New(cron.WithSeconds()) 137 | 138 | err := setupCommonQueue(nil) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | return nil 144 | } 145 | 146 | // cronJob ... 147 | type cronJob struct { 148 | name string 149 | logger log.LoggerContextIface 150 | } 151 | 152 | var cronJobCtx = context.Background() 153 | 154 | // warpJob warps job with log and panic recover. 155 | func (c *cronJob) warpJob(job func()) func() { 156 | return func() { 157 | defer func() { 158 | if r := recover(); r != nil { 159 | if c.logger != nil { 160 | c.logger.Errorf(cronJobCtx, "cron Job name: %s recover err: %v", c.name, r) 161 | } else { 162 | logging.Infof("cron Job name: %s recover err: %v\n", c.name, r) 163 | } 164 | } 165 | }() 166 | UUID := uuid.New() 167 | startTime := time.Now() 168 | if c.logger != nil { 169 | c.logger.Infof(cronJobCtx, "Name: %s Uuid: %s StartTime: %s", 170 | c.name, UUID, startTime.Format("2006-01-02 15:04:05.000")) 171 | } 172 | job() 173 | endTime := time.Now() 174 | duration := endTime.Sub(startTime) 175 | if c.logger != nil { 176 | c.logger.Infof(cronJobCtx, "Name: %s Uuid: %s EndTime: %s Duration: %fs", 177 | c.name, UUID, endTime.Format("2006-01-02 15:04:05.000"), duration.Seconds()) 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/grpc.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "time" 8 | 9 | "gitee.com/kelvins-io/kelvins" 10 | "gitee.com/kelvins-io/kelvins/config/setting" 11 | "gitee.com/kelvins-io/kelvins/internal/config" 12 | "gitee.com/kelvins-io/kelvins/internal/logging" 13 | setupInternal "gitee.com/kelvins-io/kelvins/internal/setup" 14 | "gitee.com/kelvins-io/kelvins/util/client_conn" 15 | "gitee.com/kelvins-io/kelvins/util/grpc_interceptor" 16 | "gitee.com/kelvins-io/kelvins/util/kprocess" 17 | "gitee.com/kelvins-io/kelvins/util/middleware" 18 | grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware" 19 | "google.golang.org/grpc" 20 | "google.golang.org/grpc/health" 21 | healthpb "google.golang.org/grpc/health/grpc_health_v1" 22 | "google.golang.org/grpc/keepalive" 23 | ) 24 | 25 | // RunGRPCApplication runs grpc application. 26 | func RunGRPCApplication(application *kelvins.GRPCApplication) { 27 | if application == nil || application.Application == nil { 28 | panic("grpcApplication is nil or application is nil") 29 | } 30 | // app instance once validate 31 | { 32 | err := appInstanceOnceValidate() 33 | if err != nil { 34 | logging.Fatal(err.Error()) 35 | } 36 | } 37 | 38 | application.Type = kelvins.AppTypeGrpc 39 | kelvins.GRPCAppInstance = application 40 | 41 | err := runGRPC(application) 42 | if err != nil { 43 | logging.Infof("grpcApp runGRPC err: %v\n", err) 44 | } 45 | 46 | appPrepareForceExit() 47 | // Wait for connections to drain. 48 | if application.HttpServer != nil { 49 | err = application.HttpServer.Shutdown(context.Background()) 50 | if err != nil { 51 | logging.Infof("grpcApp HttpServer.Shutdown err: %v\n", err) 52 | } 53 | } 54 | if application.GRPCServer != nil { 55 | err = stopGRPC(application) 56 | if err != nil { 57 | logging.Infof("grpcApp stopGRPC err: %v\n", err) 58 | } 59 | } 60 | err = appShutdown(application.Application) 61 | if err != nil { 62 | logging.Infof("grpcApp appShutdown err: %v\n", err) 63 | } 64 | } 65 | 66 | // runGRPC runs grpc application. 67 | func runGRPC(grpcApp *kelvins.GRPCApplication) error { 68 | var err error 69 | 70 | // 1. init application 71 | err = initApplication(grpcApp.Application) 72 | if err != nil { 73 | return err 74 | } 75 | if !appProcessNext { 76 | return err 77 | } 78 | 79 | // 2 init grpc vars 80 | err = setupGRPCVars(grpcApp) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | // 3. register service port 86 | portEtcd, err := appRegisterServiceToEtcd(kelvins.AppTypeText[grpcApp.Type], grpcApp.Name, grpcApp.Port) 87 | if err != nil { 88 | return err 89 | } 90 | defer func() { 91 | err := appUnRegisterServiceToEtcd(grpcApp.Name, grpcApp.Port) 92 | if err != nil { 93 | return 94 | } 95 | }() 96 | grpcApp.Port = portEtcd 97 | 98 | // 4. register grpc and http 99 | if grpcApp.RegisterGRPCServer != nil { 100 | err = grpcApp.RegisterGRPCServer(grpcApp.GRPCServer) 101 | if err != nil { 102 | return fmt.Errorf("registerGRPCServer err: %v", err) 103 | } 104 | } 105 | if grpcApp.RegisterGateway != nil { 106 | var opts []grpc.DialOption 107 | opts = append(opts, grpc.WithInsecure()) 108 | err = grpcApp.RegisterGateway( 109 | context.Background(), 110 | grpcApp.GatewayServeMux, 111 | fmt.Sprintf(":%d", grpcApp.Port), 112 | opts, 113 | ) 114 | if err != nil { 115 | return fmt.Errorf("registerGateway err: %v", err) 116 | } 117 | } 118 | if grpcApp.RegisterHttpRoute != nil { 119 | err = grpcApp.RegisterHttpRoute(grpcApp.Mux) 120 | if err != nil { 121 | return fmt.Errorf("registerHttpRoute err: %v", err) 122 | } 123 | } 124 | 125 | // 5. register event producer 126 | if kelvins.EventServerAliRocketMQ != nil { 127 | logging.Info("grpcApp Start event server") 128 | if grpcApp.RegisterEventProducer != nil { 129 | appRegisterEventProducer(grpcApp.RegisterEventProducer, grpcApp.Type) 130 | } 131 | if grpcApp.RegisterEventHandler != nil { 132 | appRegisterEventHandler(grpcApp.RegisterEventHandler, grpcApp.Type) 133 | } 134 | } 135 | 136 | // 6. start server 137 | network := "tcp" 138 | if kelvins.HttpServerSetting.Network != "" { 139 | network = kelvins.HttpServerSetting.Network 140 | } 141 | kp := new(kprocess.KProcess) 142 | ln, err := kp.Listen(network, fmt.Sprintf(":%d", grpcApp.Port), kelvins.PIDFile) 143 | if err != nil { 144 | return fmt.Errorf("kprocess listen(%s:%d) pidFile(%v) err: %v", network, grpcApp.Port, kelvins.PIDFile, err) 145 | } 146 | logging.Infof("grpcApp server listen(%s:%d) \n", network, grpcApp.Port) 147 | serverClose := make(chan struct{}) 148 | go func() { 149 | defer func() { 150 | close(serverClose) 151 | }() 152 | err := grpcApp.HttpServer.Serve(ln) 153 | if err != nil { 154 | logging.Infof("grpcApp HttpServer serve err: %v", err) 155 | } 156 | }() 157 | 158 | select { 159 | case <-serverClose: 160 | case <-kp.Exit(): 161 | } 162 | 163 | return err 164 | } 165 | 166 | const ( 167 | defaultWriteBufSize = 32 * 1024 168 | defaultReadBufSize = 32 * 1024 169 | ) 170 | 171 | // setupGRPCVars ... 172 | func setupGRPCVars(grpcApp *kelvins.GRPCApplication) error { 173 | var err error 174 | grpcApp.GKelvinsLogger = kelvins.AccessLogger 175 | grpcApp.GSysErrLogger = kelvins.ErrLogger 176 | var debug bool 177 | environ := grpcApp.Environment 178 | if environ == config.DefaultEnvironmentDev || environ == config.DefaultEnvironmentTest { 179 | debug = true 180 | } 181 | if kelvins.RPCRateLimitSetting == nil { 182 | kelvins.RPCRateLimitSetting = new(setting.RPCRateLimitSettingS) 183 | } 184 | var ( 185 | serverUnaryInterceptors []grpc.UnaryServerInterceptor 186 | serverStreamInterceptors []grpc.StreamServerInterceptor 187 | appInterceptor = grpc_interceptor.NewAppServerInterceptor(debug, grpcApp.GKelvinsLogger, grpcApp.GKelvinsLogger) 188 | authInterceptor = middleware.NewRPCPerAuthInterceptor(grpcApp.GKelvinsLogger) 189 | rateLimitParam = kelvins.RPCRateLimitSetting 190 | rateLimitInterceptor = middleware.NewRPCRateLimitInterceptor(rateLimitParam.MaxConcurrent) 191 | ) 192 | serverUnaryInterceptors = append(serverUnaryInterceptors, appInterceptor.Metadata) 193 | serverUnaryInterceptors = append(serverUnaryInterceptors, appInterceptor.Recovery) 194 | if rateLimitParam.MaxConcurrent > 0 { 195 | serverUnaryInterceptors = append(serverUnaryInterceptors, rateLimitInterceptor.UnaryServerInterceptor()) 196 | } 197 | serverUnaryInterceptors = append(serverUnaryInterceptors, appInterceptor.Logger) 198 | if kelvins.RPCAuthSetting == nil { 199 | kelvins.RPCAuthSetting = new(setting.RPCAuthSettingS) 200 | } 201 | serverUnaryInterceptors = append(serverUnaryInterceptors, authInterceptor.UnaryServerInterceptor(kelvins.RPCAuthSetting)) 202 | if len(grpcApp.UnaryServerInterceptors) > 0 { 203 | serverUnaryInterceptors = append(serverUnaryInterceptors, grpcApp.UnaryServerInterceptors...) 204 | } 205 | serverStreamInterceptors = append(serverStreamInterceptors, appInterceptor.StreamMetadata) 206 | serverStreamInterceptors = append(serverStreamInterceptors, appInterceptor.RecoveryStream) 207 | if rateLimitParam.MaxConcurrent > 0 { 208 | serverStreamInterceptors = append(serverStreamInterceptors, rateLimitInterceptor.StreamServerInterceptor()) 209 | } 210 | serverStreamInterceptors = append(serverStreamInterceptors, appInterceptor.StreamLogger) 211 | serverStreamInterceptors = append(serverStreamInterceptors, authInterceptor.StreamServerInterceptor(kelvins.RPCAuthSetting)) 212 | if len(grpcApp.StreamServerInterceptors) > 0 { 213 | serverStreamInterceptors = append(serverStreamInterceptors, grpcApp.StreamServerInterceptors...) 214 | } 215 | 216 | var serverOptions []grpc.ServerOption 217 | serverOptions = append(serverOptions, grpcMiddleware.WithUnaryServerChain(serverUnaryInterceptors...)) 218 | serverOptions = append(serverOptions, grpcMiddleware.WithStreamServerChain(serverStreamInterceptors...)) 219 | keepaliveParams := keepalive.ServerParameters{ 220 | MaxConnectionIdle: 5 * time.Hour, // 空闲连接在持续一段时间后关闭 221 | MaxConnectionAge: time.Duration(math.MaxInt64), // 连接的最长持续时间 222 | MaxConnectionAgeGrace: time.Duration(math.MaxInt64), // 最长持续时间后的 加成期,超过这个时间后强制关闭 223 | Time: 2 * time.Hour, // 服务端在这段时间后没有看到活动RPC,将给客户端发送PING 224 | Timeout: 20 * time.Second, // 服务端发送PING后等待客户端应答时间,超过将关闭 225 | } 226 | keepEnforcementPolicy := keepalive.EnforcementPolicy{ 227 | MinTime: 5 * time.Minute, // 客户端发送PING前 应该等待的最短时间 228 | PermitWithoutStream: true, // 为true表示及时没有活动RPC,服务端也允许保活,为false表示客户端在没有活动RPC时发送PING将导致GoAway 229 | } 230 | serverOptions = append(serverOptions, grpc.KeepaliveParams(keepaliveParams), grpc.KeepaliveEnforcementPolicy(keepEnforcementPolicy)) 231 | writeBufSize := grpc.WriteBufferSize(defaultWriteBufSize) 232 | readBufSize := grpc.ReadBufferSize(defaultReadBufSize) 233 | serverOptions = append(serverOptions, writeBufSize, readBufSize) 234 | if grpcApp.NumServerWorkers > 0 { 235 | serverOptions = append(serverOptions, grpc.NumStreamWorkers(grpcApp.NumServerWorkers)) 236 | } 237 | // grpc app server option 238 | serverOptions = append(serverOptions, grpcApp.ServerOptions...) 239 | // server worker 240 | { 241 | cg := kelvins.RPCServerParamsSetting 242 | // rpc server goroutine worker num default 0 243 | if cg != nil && cg.NumServerWorkers > 0 { 244 | serverOptions = append(serverOptions, grpc.NumStreamWorkers(uint32(cg.NumServerWorkers))) 245 | } 246 | // connection time is rawConn deadline default 120s 247 | if cg != nil && cg.ConnectionTimeout > 0 { 248 | serverOptions = append(serverOptions, grpc.ConnectionTimeout(time.Duration(cg.ConnectionTimeout)*time.Second)) 249 | } 250 | } 251 | // keep alive limit client 252 | { 253 | cg := kelvins.RPCServerKeepaliveEnforcementPolicySetting 254 | if cg != nil && cg.ClientMinIntervalTime > 0 { 255 | keepEnforcementPolicy.MinTime = time.Duration(cg.ClientMinIntervalTime) * time.Second 256 | } 257 | if cg != nil && cg.PermitWithoutStream { 258 | keepEnforcementPolicy.PermitWithoutStream = cg.PermitWithoutStream 259 | } 260 | if cg != nil { 261 | serverOptions = append(serverOptions, grpc.KeepaliveEnforcementPolicy(keepEnforcementPolicy)) 262 | } 263 | } 264 | // keep alive 265 | { 266 | cg := kelvins.RPCServerKeepaliveParamsSetting 267 | if cg != nil && cg.MaxConnectionIdle > 0 { 268 | keepaliveParams.MaxConnectionIdle = time.Duration(cg.MaxConnectionIdle) * time.Second 269 | } 270 | if cg != nil && cg.PingClientIntervalTime > 0 { 271 | keepaliveParams.Time = time.Duration(cg.PingClientIntervalTime) * time.Second 272 | } 273 | if cg != nil { 274 | serverOptions = append(serverOptions, grpc.KeepaliveParams(keepaliveParams)) 275 | } 276 | } 277 | // client rpc keep alive 278 | { 279 | cg := kelvins.RPCClientKeepaliveParamsSetting 280 | pingServerTime := 6 * time.Minute 281 | permitWithoutStream := true 282 | if cg != nil && cg.PingServerIntervalTime > 0 { 283 | pingServerTime = time.Duration(cg.PingServerIntervalTime) * time.Second 284 | } 285 | if cg != nil && cg.PermitWithoutStream { 286 | permitWithoutStream = cg.PermitWithoutStream 287 | } 288 | if cg != nil { 289 | opts := []grpc.DialOption{ 290 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 291 | Time: pingServerTime, // 客户端在这段时间之后如果没有活动的RPC,客户端将给服务器发送PING 292 | Timeout: 20 * time.Second, // 连接服务端后等待一段时间后没有收到响应则关闭连接 293 | PermitWithoutStream: permitWithoutStream, // 允许客户端在没有活动RPC的情况下向服务端发送PING 294 | }), 295 | } 296 | client_conn.RPCClientDialOptionAppend(opts) 297 | } 298 | } 299 | // transport buffer 300 | { 301 | cg := kelvins.RPCTransportBufferSetting 302 | if cg != nil { 303 | const kb = 1024 304 | if cg.ServerReadBufSizeKB > 0 { 305 | serverOptions = append(serverOptions, grpc.ReadBufferSize(cg.ServerReadBufSizeKB*kb)) 306 | } 307 | if cg.ServerWriteBufSizeKB > 0 { 308 | serverOptions = append(serverOptions, grpc.WriteBufferSize(cg.ServerWriteBufSizeKB*kb)) 309 | } 310 | if cg.ClientReadBufSizeKB > 0 { 311 | client_conn.RPCClientDialOptionAppend([]grpc.DialOption{grpc.WithReadBufferSize(cg.ClientReadBufSizeKB * kb)}) 312 | } 313 | if cg.ClientWriteBufSizeKB > 0 { 314 | client_conn.RPCClientDialOptionAppend([]grpc.DialOption{grpc.WithWriteBufferSize(cg.ClientWriteBufSizeKB * kb)}) 315 | } 316 | } 317 | } 318 | 319 | // health server 320 | grpcApp.GRPCServer = setupInternal.NewGRPC(serverOptions) 321 | if grpcApp.GRPCServer != nil { 322 | if kelvins.RPCServerParamsSetting != nil && !kelvins.RPCServerParamsSetting.DisableHealthServer { 323 | grpcApp.HealthServer = &kelvins.GRPCHealthServer{Server: health.NewServer()} 324 | healthpb.RegisterHealthServer(grpcApp.GRPCServer, grpcApp.HealthServer) 325 | if grpcApp.RegisterGRPCHealthHandle != nil { 326 | go func() { 327 | grpcApp.RegisterGRPCHealthHandle(grpcApp.HealthServer) 328 | }() 329 | } 330 | } 331 | healthCheck := true 332 | if kelvins.RPCServerParamsSetting != nil && kelvins.RPCServerParamsSetting.DisableClientDialHealthCheck { 333 | healthCheck = false 334 | } 335 | if !healthCheck { 336 | client_conn.RPCClientDialOptionAppend([]grpc.DialOption{grpc.WithDisableHealthCheck()}) 337 | } 338 | } 339 | grpcApp.GatewayServeMux = setupInternal.NewGateway() 340 | grpcApp.Mux = setupInternal.NewGatewayServerMux(grpcApp.GatewayServeMux, debug) 341 | if kelvins.HttpServerSetting == nil { 342 | kelvins.HttpServerSetting = new(setting.HttpServerSettingS) 343 | } 344 | kelvins.HttpServerSetting.SetAddr(fmt.Sprintf(":%d", grpcApp.Port)) 345 | grpcApp.HttpServer = setupInternal.NewHttpServer( 346 | setupInternal.GRPCHandlerFunc(grpcApp.GRPCServer, grpcApp.Mux, kelvins.HttpServerSetting), 347 | grpcApp.TlsConfig, 348 | kelvins.HttpServerSetting, 349 | ) 350 | // queue 351 | err = setupCommonQueue(nil) 352 | if err != nil { 353 | return err 354 | } 355 | 356 | return nil 357 | } 358 | 359 | func stopGRPC(grpcApp *kelvins.GRPCApplication) error { 360 | if grpcApp.HealthServer != nil { 361 | grpcApp.HealthServer.Shutdown() 362 | } 363 | grpcApp.GRPCServer.GracefulStop() 364 | 365 | return nil 366 | } 367 | -------------------------------------------------------------------------------- /app/http.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | "gitee.com/kelvins-io/kelvins/config/setting" 12 | "gitee.com/kelvins-io/kelvins" 13 | "gitee.com/kelvins-io/kelvins/internal/config" 14 | "gitee.com/kelvins-io/kelvins/internal/logging" 15 | setupInternal "gitee.com/kelvins-io/kelvins/internal/setup" 16 | "gitee.com/kelvins-io/kelvins/util/gin_helper" 17 | "gitee.com/kelvins-io/kelvins/util/kprocess" 18 | "github.com/gin-contrib/pprof" 19 | "github.com/gin-gonic/gin" 20 | "github.com/prometheus/client_golang/prometheus/promhttp" 21 | ) 22 | 23 | func RunHTTPApplication(application *kelvins.HTTPApplication) { 24 | if application == nil || application.Application == nil { 25 | panic("httpApplication is nil or application is nil") 26 | } 27 | // app instance once validate 28 | { 29 | err := appInstanceOnceValidate() 30 | if err != nil { 31 | logging.Fatal(err.Error()) 32 | } 33 | } 34 | 35 | application.Type = kelvins.AppTypeHttp 36 | kelvins.HttpAppInstance = application 37 | 38 | err := runHTTP(application) 39 | if err != nil { 40 | logging.Infof("HttpApp runHTTP err: %v\n", err) 41 | } 42 | 43 | appPrepareForceExit() 44 | // Wait for connections to drain. 45 | if application.HttpServer != nil { 46 | err = application.HttpServer.Shutdown(context.Background()) 47 | if err != nil { 48 | logging.Infof("HttpApp HttpServer Shutdown err: %v\n", err) 49 | } 50 | } 51 | err = appShutdown(application.Application) 52 | if err != nil { 53 | logging.Infof("HttpApp appShutdown err: %v\n", err) 54 | } 55 | } 56 | 57 | func runHTTP(httpApp *kelvins.HTTPApplication) error { 58 | var err error 59 | 60 | // 1. init application 61 | err = initApplication(httpApp.Application) 62 | if err != nil { 63 | return err 64 | } 65 | if !appProcessNext { 66 | return err 67 | } 68 | 69 | // 2 init http vars 70 | err = setupHTTPVars(httpApp) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | // 3. set init service port 76 | portEtcd, err := appRegisterServiceToEtcd(kelvins.AppTypeText[httpApp.Type], httpApp.Name, httpApp.Port) 77 | if err != nil { 78 | return err 79 | } 80 | defer func() { 81 | err := appUnRegisterServiceToEtcd(httpApp.Name, httpApp.Port) 82 | if err != nil { 83 | return 84 | } 85 | }() 86 | httpApp.Port = portEtcd 87 | 88 | // 4. register http 89 | var handler http.Handler 90 | debug := false 91 | if kelvins.ServerSetting != nil { 92 | switch kelvins.ServerSetting.Environment { 93 | case config.DefaultEnvironmentDev: 94 | debug = true 95 | case config.DefaultEnvironmentTest: 96 | debug = true 97 | default: 98 | } 99 | } 100 | if httpApp.RegisterHttpGinRoute != nil { 101 | logging.Info("httpApp http handler selected [gin]") 102 | ginEngineInit() 103 | var httpGinEng = gin.Default() 104 | handler = httpGinEng 105 | httpGinEng.Use(gin_helper.Metadata(debug)) 106 | httpGinEng.Use(gin_helper.Cors()) 107 | if kelvins.HttpRateLimitSetting != nil && kelvins.HttpRateLimitSetting.MaxConcurrent > 0 { 108 | httpGinEng.Use(gin_helper.RateLimit(kelvins.HttpRateLimitSetting.MaxConcurrent)) 109 | } 110 | if debug { 111 | pprof.Register(httpGinEng, "/debug") 112 | httpGinEng.GET("/debug/metrics", ginMetricsApi) 113 | } 114 | httpGinEng.GET("/", ginIndexApi) 115 | httpGinEng.GET("/ping", ginPingApi) 116 | httpApp.RegisterHttpGinRoute(httpGinEng) 117 | } else { 118 | httpApp.Mux = setupInternal.NewServerMux(debug) 119 | handler = httpApp.Mux 120 | httpApp.Mux.HandleFunc("/", indexApi) 121 | httpApp.Mux.HandleFunc("/ping", pingApi) 122 | if httpApp.RegisterHttpRoute != nil { 123 | err = httpApp.RegisterHttpRoute(httpApp.Mux) 124 | if err != nil { 125 | return fmt.Errorf("registerHttpRoute err: %v", err) 126 | } 127 | } 128 | logging.Info("httpApp http handler selected [http.ServeMux]") 129 | } 130 | if handler == nil { 131 | return fmt.Errorf("no http handler??? ") 132 | } 133 | 134 | // 5. set http server 135 | var httpSer *http.Server 136 | if kelvins.HttpServerSetting == nil { 137 | kelvins.HttpServerSetting = new(setting.HttpServerSettingS) 138 | } 139 | kelvins.HttpServerSetting.SetAddr(fmt.Sprintf(":%d", httpApp.Port)) 140 | if kelvins.HttpServerSetting != nil && kelvins.HttpServerSetting.SupportH2 { 141 | logging.Info("httpApp enable http2/h2c") 142 | httpSer = setupInternal.NewHttp2Server(handler, httpApp.TlsConfig, kelvins.HttpServerSetting) 143 | } else { 144 | httpSer = setupInternal.NewHttpServer(handler, httpApp.TlsConfig, kelvins.HttpServerSetting) 145 | } 146 | httpApp.HttpServer = httpSer 147 | 148 | // 6. register event producer 149 | if kelvins.EventServerAliRocketMQ != nil { 150 | logging.Info("httpApp start event server") 151 | if httpApp.RegisterEventProducer != nil { 152 | appRegisterEventProducer(httpApp.RegisterEventProducer, httpApp.Type) 153 | } 154 | if httpApp.RegisterEventHandler != nil { 155 | appRegisterEventHandler(httpApp.RegisterEventHandler, httpApp.Type) 156 | } 157 | } 158 | 159 | // 7. start server 160 | network := "tcp" 161 | if kelvins.HttpServerSetting.Network != "" { 162 | network = kelvins.HttpServerSetting.Network 163 | } 164 | kp := new(kprocess.KProcess) 165 | ln, err := kp.Listen(network, fmt.Sprintf(":%d", httpApp.Port), kelvins.PIDFile) 166 | if err != nil { 167 | return fmt.Errorf("kprocess listen(%s:%d) pidFile(%v) err: %v", network, httpApp.Port, kelvins.PIDFile, err) 168 | } 169 | logging.Infof("httpApp server listen(%s:%d) \n", network, httpApp.Port) 170 | serverClose := make(chan struct{}) 171 | go func() { 172 | defer func() { 173 | close(serverClose) 174 | }() 175 | err = httpApp.HttpServer.Serve(ln) 176 | if err != nil { 177 | logging.Infof("httpApp HttpServer serve err: %v", err) 178 | } 179 | }() 180 | 181 | select { 182 | case <-serverClose: 183 | case <-kp.Exit(): 184 | } 185 | 186 | return nil 187 | } 188 | 189 | func setupHTTPVars(httpApp *kelvins.HTTPApplication) error { 190 | err := setupCommonQueue(nil) 191 | if err != nil { 192 | return err 193 | } 194 | 195 | return nil 196 | } 197 | 198 | func ginEngineInit() { 199 | var accessLogWriter io.Writer = &accessInfoLogger{} 200 | var errLogWriter io.Writer = &accessErrLogger{} 201 | if kelvins.ServerSetting != nil { 202 | environ := kelvins.ServerSetting.Environment 203 | if environ == config.DefaultEnvironmentDev || environ == config.DefaultEnvironmentTest { 204 | if environ == config.DefaultEnvironmentDev { 205 | accessLogWriter = io.MultiWriter(accessLogWriter, os.Stdout) 206 | errLogWriter = io.MultiWriter(errLogWriter, os.Stdout) 207 | } 208 | gin.DefaultWriter = accessLogWriter 209 | } 210 | } 211 | gin.DefaultErrorWriter = errLogWriter 212 | 213 | gin.SetMode(gin.ReleaseMode) // 默认生产 214 | if kelvins.ServerSetting != nil { 215 | switch kelvins.ServerSetting.Environment { 216 | case config.DefaultEnvironmentDev: 217 | gin.SetMode(gin.DebugMode) 218 | case config.DefaultEnvironmentTest: 219 | gin.SetMode(gin.TestMode) 220 | case config.DefaultEnvironmentRelease, config.DefaultEnvironmentProd: 221 | gin.SetMode(gin.ReleaseMode) 222 | default: 223 | gin.SetMode(gin.ReleaseMode) 224 | } 225 | } 226 | } 227 | 228 | type accessInfoLogger struct{} 229 | 230 | func (a *accessInfoLogger) Write(p []byte) (n int, err error) { 231 | if kelvins.AccessLogger != nil { 232 | kelvins.AccessLogger.Infof(context.Background(), "[gin-info] %s", p) 233 | } 234 | return 0, nil 235 | } 236 | 237 | type accessErrLogger struct{} 238 | 239 | func (a *accessErrLogger) Write(p []byte) (n int, err error) { 240 | if kelvins.AccessLogger != nil { 241 | kelvins.AccessLogger.Errorf(context.Background(), "[gin-err] %s", p) 242 | } 243 | return 0, nil 244 | } 245 | 246 | func indexApi(writer http.ResponseWriter, request *http.Request) { 247 | writer.WriteHeader(http.StatusOK) 248 | writer.Write([]byte("Welcome to " + kelvins.AppName)) 249 | } 250 | 251 | func pingApi(writer http.ResponseWriter, request *http.Request) { 252 | writer.WriteHeader(http.StatusOK) 253 | writer.Write([]byte(time.Now().Format(kelvins.ResponseTimeLayout))) 254 | } 255 | 256 | func ginMetricsApi(c *gin.Context) { 257 | promhttp.Handler().ServeHTTP(c.Writer, c.Request) 258 | } 259 | 260 | func ginIndexApi(c *gin.Context) { 261 | gin_helper.JsonResponse(c, http.StatusOK, gin_helper.SUCCESS, "Welcome to "+kelvins.AppName) 262 | } 263 | 264 | func ginPingApi(c *gin.Context) { 265 | gin_helper.JsonResponse(c, http.StatusOK, gin_helper.SUCCESS, time.Now().Format(kelvins.ResponseTimeLayout)) 266 | } 267 | -------------------------------------------------------------------------------- /app/queue.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "time" 8 | 9 | "gitee.com/kelvins-io/common/convert" 10 | "gitee.com/kelvins-io/common/log" 11 | "gitee.com/kelvins-io/common/queue" 12 | "gitee.com/kelvins-io/kelvins" 13 | "gitee.com/kelvins-io/kelvins/internal/config" 14 | "gitee.com/kelvins-io/kelvins/internal/logging" 15 | "gitee.com/kelvins-io/kelvins/util/kprocess" 16 | "github.com/RichardKnop/machinery/v1" 17 | queueLog "github.com/RichardKnop/machinery/v1/log" 18 | ) 19 | 20 | // RunQueueApplication runs queue application. 21 | func RunQueueApplication(application *kelvins.QueueApplication) { 22 | if application == nil || application.Application == nil { 23 | panic("queueApplication is nil or application is nil") 24 | } 25 | // app instance once validate 26 | { 27 | err := appInstanceOnceValidate() 28 | if err != nil { 29 | logging.Fatal(err.Error()) 30 | } 31 | } 32 | 33 | // type instance vars 34 | application.Type = kelvins.AppTypeQueue 35 | kelvins.QueueAppInstance = application 36 | 37 | err := runQueue(application) 38 | if err != nil { 39 | logging.Infof("queueApp runQueue err: %v\n", err) 40 | } 41 | 42 | appPrepareForceExit() 43 | // Wait for connections to drain. 44 | err = appShutdown(application.Application) 45 | if err != nil { 46 | logging.Infof("queueApp appShutdown err: %v\n", err) 47 | } 48 | } 49 | 50 | var queueToWorker = map[*queue.MachineryQueue][]*machinery.Worker{} 51 | 52 | // runQueue runs queue application. 53 | func runQueue(queueApp *kelvins.QueueApplication) error { 54 | // 1. init application 55 | var err error 56 | err = initApplication(queueApp.Application) 57 | if err != nil { 58 | return err 59 | } 60 | if !appProcessNext { 61 | return err 62 | } 63 | 64 | // 2 init queue vars 65 | err = setupQueueVars(queueApp) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | // 3. event server 71 | if kelvins.EventServerAliRocketMQ != nil { 72 | logging.Info("queueApp start event server ") 73 | if queueApp.RegisterEventProducer != nil { 74 | appRegisterEventProducer(queueApp.RegisterEventProducer, queueApp.Type) 75 | } 76 | if queueApp.RegisterEventHandler != nil { 77 | appRegisterEventHandler(queueApp.RegisterEventHandler, queueApp.Type) 78 | } 79 | } 80 | 81 | // 4. queue server 82 | logging.Info("queueApp start queue server consume") 83 | concurrency := len(queueApp.GetNamedTaskFuncs()) 84 | if kelvins.QueueServerSetting != nil { 85 | concurrency = kelvins.QueueServerSetting.WorkerConcurrency 86 | } 87 | logging.Infof("queueApp count of worker goroutine: %d\n", concurrency) 88 | consumerTag := queueApp.Application.Name + convert.Int64ToStr(time.Now().Local().UnixNano()) 89 | 90 | kp := new(kprocess.KProcess) 91 | _, err = kp.Listen("", "", kelvins.PIDFile) 92 | if err != nil { 93 | return fmt.Errorf("kprocess listen pidFile(%v) err: %v", kelvins.PIDFile, err) 94 | } 95 | var queueList []string 96 | queueList = append(queueList, kelvins.QueueServerSetting.CustomQueueList...) 97 | errorsChanSize := 0 98 | if kelvins.QueueRedisSetting != nil && !kelvins.QueueRedisSetting.DisableConsume { 99 | errorsChanSize += len(queueList) 100 | } 101 | if kelvins.QueueAMQPSetting != nil && !kelvins.QueueAMQPSetting.DisableConsume { 102 | errorsChanSize += len(queueList) 103 | } 104 | if kelvins.QueueAliAMQPSetting != nil && !kelvins.QueueAliAMQPSetting.DisableConsume { 105 | errorsChanSize += len(queueList) 106 | } 107 | errorsChan := make(chan error, errorsChanSize) 108 | for _, customQueue := range queueList { 109 | cTag := consumerTag 110 | if len(customQueue) > 0 { 111 | cTag = customQueue + "-" + consumerTag 112 | } 113 | if kelvins.QueueRedisSetting != nil && !kelvins.QueueRedisSetting.DisableConsume && kelvins.QueueServerRedis != nil { 114 | logging.Infof("queueApp queueServerRedis Consumer Tag: %s\n", cTag) 115 | worker := kelvins.QueueServerRedis.TaskServer.NewCustomQueueWorker(cTag, concurrency, customQueue) 116 | worker.LaunchAsync(errorsChan) 117 | queueToWorker[kelvins.QueueServerRedis] = append(queueToWorker[kelvins.QueueServerRedis], worker) 118 | } 119 | if kelvins.QueueAMQPSetting != nil && !kelvins.QueueAMQPSetting.DisableConsume && kelvins.QueueServerAMQP != nil { 120 | logging.Infof("queueApp queueServerAMQP Consumer Tag: %s\n", cTag) 121 | worker := kelvins.QueueServerAMQP.TaskServer.NewCustomQueueWorker(cTag, concurrency, customQueue) 122 | worker.LaunchAsync(errorsChan) 123 | queueToWorker[kelvins.QueueServerAMQP] = append(queueToWorker[kelvins.QueueServerAMQP], worker) 124 | } 125 | if kelvins.QueueAliAMQPSetting != nil && !kelvins.QueueAliAMQPSetting.DisableConsume && kelvins.QueueServerAliAMQP != nil { 126 | logging.Infof("queueApp queueServerAliAMQP Consumer Tag: %s\n", cTag) 127 | worker := kelvins.QueueServerAliAMQP.TaskServer.NewCustomQueueWorker(cTag, concurrency, customQueue) 128 | worker.LaunchAsync(errorsChan) 129 | queueToWorker[kelvins.QueueServerAliAMQP] = append(queueToWorker[kelvins.QueueServerAliAMQP], worker) 130 | } 131 | } 132 | queueApp.QueueServerToWorker = queueToWorker 133 | <-kp.Exit() // worker not listen Interrupt,SIGTERM signal stop 134 | 135 | // 5 close 136 | queueWorkerStop() 137 | close(errorsChan) 138 | queueWorkerErr := bytes.Buffer{} 139 | for c := range errorsChan { 140 | if queueWorkerErr.String() == "" { 141 | queueWorkerErr.WriteString("worker err=>") 142 | } 143 | queueWorkerErr.WriteString(c.Error()) 144 | } 145 | if queueWorkerErr.String() != "" { 146 | err = fmt.Errorf(queueWorkerErr.String()) 147 | } 148 | 149 | return err 150 | } 151 | 152 | // setupQueueVars ... 153 | func setupQueueVars(queueApp *kelvins.QueueApplication) error { 154 | var logger log.LoggerContextIface 155 | if kelvins.ServerSetting != nil { 156 | switch kelvins.ServerSetting.Environment { 157 | case config.DefaultEnvironmentDev: 158 | logger = kelvins.AccessLogger 159 | case config.DefaultEnvironmentTest: 160 | logger = kelvins.AccessLogger 161 | default: 162 | } 163 | } 164 | if logger != nil { 165 | queueLog.Set(&queueLogger{ 166 | logger: logger, 167 | }) 168 | } 169 | 170 | // only queueApp need check GetNamedTaskFuncs or RegisterEventHandler 171 | if queueApp.GetNamedTaskFuncs == nil && queueApp.RegisterEventHandler == nil { 172 | return fmt.Errorf("lack of implement GetNamedTaskFuncs And RegisterEventHandler") 173 | } 174 | err := setupCommonQueue(queueApp.GetNamedTaskFuncs()) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | return nil 180 | } 181 | 182 | func queueWorkerStop() { 183 | //for queue,worker := range queueWorker { 184 | // // process exit queue worker should exit 185 | // // worker.Quit() 186 | // // return 187 | //} 188 | logging.Info("queueApp queue worker stop over") 189 | } 190 | 191 | var queueLoggerCtx = context.Background() 192 | 193 | // queueLogger implements machinery log interface. 194 | type queueLogger struct { 195 | logger log.LoggerContextIface 196 | } 197 | 198 | // Print uses logger to log info msg. 199 | func (q *queueLogger) Print(a ...interface{}) { 200 | q.logger.Info(queueLoggerCtx, fmt.Sprint(a...)) 201 | } 202 | 203 | // Printf uses logger to log info msg. 204 | func (q *queueLogger) Printf(format string, a ...interface{}) { 205 | q.logger.Infof(queueLoggerCtx, format, a...) 206 | } 207 | 208 | // Println uses logger to log info msg. 209 | func (q *queueLogger) Println(a ...interface{}) { 210 | q.logger.Info(queueLoggerCtx, fmt.Sprint(a...)) 211 | } 212 | 213 | // Fatal uses kelvins.ErrLogger to log err msg. 214 | func (q *queueLogger) Fatal(a ...interface{}) { 215 | q.logger.Error(queueLoggerCtx, fmt.Sprint(a...)) 216 | } 217 | 218 | // Fatalf uses kelvins.ErrLogger to log err msg. 219 | func (q *queueLogger) Fatalf(format string, a ...interface{}) { 220 | q.logger.Errorf(queueLoggerCtx, format, a...) 221 | } 222 | 223 | // Fatalln uses kelvins.ErrLogger to log err msg. 224 | func (q *queueLogger) Fatalln(a ...interface{}) { 225 | q.logger.Error(queueLoggerCtx, fmt.Sprint(a...)) 226 | } 227 | 228 | // Panic uses kelvins.ErrLogger to log err msg. 229 | func (q *queueLogger) Panic(a ...interface{}) { 230 | q.logger.Error(queueLoggerCtx, fmt.Sprint(a...)) 231 | } 232 | 233 | // Panicf uses kelvins.ErrLogger to log err msg. 234 | func (q *queueLogger) Panicf(format string, a ...interface{}) { 235 | q.logger.Errorf(queueLoggerCtx, format, a) 236 | } 237 | 238 | // Panicln uses kelvins.ErrLogger to log err msg. 239 | func (q *queueLogger) Panicln(a ...interface{}) { 240 | q.logger.Error(queueLoggerCtx, fmt.Sprint(a...)) 241 | } 242 | -------------------------------------------------------------------------------- /config/conf.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins/internal/config" 5 | ) 6 | 7 | // MapConfig loads config to struct v. 8 | func MapConfig(section string, v interface{}) { 9 | config.MapConfig(section, v) 10 | } 11 | -------------------------------------------------------------------------------- /config/setting/setting.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "gitee.com/kelvins-io/common/log" 5 | "time" 6 | ) 7 | 8 | // ServerSettingS defines for server. 9 | type ServerSettingS struct { 10 | AppName string 11 | PIDFile string 12 | Environment string 13 | } 14 | 15 | func (s *HttpServerSettingS) GetReadTimeout() time.Duration { 16 | return time.Duration(s.ReadTimeout) * time.Second 17 | } 18 | 19 | func (s *HttpServerSettingS) GetWriteTimeout() time.Duration { 20 | return time.Duration(s.WriteTimeout) * time.Second 21 | } 22 | 23 | func (s *HttpServerSettingS) GetIdleTimeout() time.Duration { 24 | return time.Duration(s.IdleTimeout) * time.Second 25 | } 26 | 27 | func (s *HttpServerSettingS) SetAddr(addr string) { 28 | s.addr = addr 29 | } 30 | 31 | func (s *HttpServerSettingS) GetAddr() string { 32 | return s.addr 33 | } 34 | 35 | type HttpServerSettingS struct { 36 | Network string 37 | ReadTimeout int 38 | WriteTimeout int 39 | IdleTimeout int 40 | SupportH2 bool 41 | addr string 42 | } 43 | 44 | type HttpRateLimitSettingS struct { 45 | MaxConcurrent int 46 | } 47 | 48 | type JwtSettingS struct { 49 | Secret string 50 | TokenExpireSecond int 51 | } 52 | 53 | type RPCServerParamsS struct { 54 | NumServerWorkers int64 55 | ConnectionTimeout int64 // unit second 56 | DisableClientDialHealthCheck bool 57 | DisableHealthServer bool 58 | } 59 | 60 | type RPCAuthSettingS struct { 61 | Token string 62 | ExpireSecond int 63 | TransportSecurity bool 64 | } 65 | 66 | type RPCRateLimitSettingS struct { 67 | MaxConcurrent int 68 | } 69 | 70 | type RPCServerKeepaliveParamsS struct { 71 | PingClientIntervalTime int64 72 | MaxConnectionIdle int64 73 | } 74 | 75 | type RPCServerKeepaliveEnforcementPolicyS struct { 76 | ClientMinIntervalTime int64 77 | PermitWithoutStream bool 78 | } 79 | 80 | type RPCClientKeepaliveParamsS struct { 81 | PingServerIntervalTime int64 82 | PermitWithoutStream bool 83 | } 84 | 85 | type RPCTransportBufferS struct { 86 | ServerReadBufSizeKB int 87 | ServerWriteBufSizeKB int 88 | ClientReadBufSizeKB int 89 | ClientWriteBufSizeKB int 90 | } 91 | 92 | type LoggerSettingS struct { 93 | RootPath string 94 | Level string 95 | } 96 | 97 | // MysqlSettingS defines for connecting mysql. 98 | type MysqlSettingS struct { 99 | Host string 100 | UserName string 101 | Password string 102 | DBName string 103 | Charset string 104 | MaxIdle int 105 | MaxOpen int 106 | Loc string 107 | ConnMaxLifeSecond int 108 | MultiStatements bool 109 | ParseTime bool 110 | ConnectionTimeout string // time unit eg: 2h 3s 111 | WriteTimeout string // time unit eg: 2h 3s 112 | ReadTimeout string // time unit eg: 2h 3s 113 | // only app use 114 | LoggerLevel string 115 | Environment string 116 | Logger log.LoggerContextIface 117 | } 118 | 119 | // RedisSettingS defines for connecting redis. 120 | type RedisSettingS struct { 121 | Host string 122 | Password string 123 | MaxIdle int 124 | MaxActive int 125 | IdleTimeout int // unit second 126 | ConnectTimeout int // unit second 127 | ReadTimeout int // unit second 128 | WriteTimeout int // unit second 129 | DB int 130 | } 131 | 132 | type G2CacheSettingS struct { 133 | CacheDebug bool 134 | CacheMonitor bool 135 | OutCachePubSub bool 136 | CacheMonitorSecond int 137 | EntryLazyFactor int 138 | GPoolWorkerNum int 139 | GPoolJobQueueChanLen int 140 | FreeCacheSize int // byte size 141 | PubSubRedisChannel string 142 | RedisConfDSN string 143 | RedisConfDB int 144 | RedisConfPwd string 145 | RedisConfMaxConn int 146 | PubSubRedisConfDSN string 147 | PubSubRedisConfDB int 148 | PubSubRedisConfPwd string 149 | PubSubRedisConfMaxConn int 150 | } 151 | 152 | // QueueServerSettingS defines what queue server needs. 153 | type QueueServerSettingS struct { 154 | WorkerConcurrency int 155 | CustomQueueList []string 156 | } 157 | 158 | // QueueRedisSettingS defines for redis queue. 159 | type QueueRedisSettingS struct { 160 | Broker string 161 | DefaultQueue string 162 | ResultBackend string 163 | ResultsExpireIn int 164 | DisableConsume bool 165 | TaskRetryCount int 166 | TaskRetryTimeout int 167 | } 168 | 169 | // QueueAliAMQPSettingS defines for ali yun AMQP queue 170 | type QueueAliAMQPSettingS struct { 171 | AccessKey string 172 | SecretKey string 173 | AliUid int 174 | EndPoint string 175 | VHost string 176 | DefaultQueue string 177 | ResultBackend string 178 | ResultsExpireIn int 179 | Exchange string 180 | ExchangeType string 181 | BindingKey string 182 | PrefetchCount int 183 | TaskRetryCount int 184 | TaskRetryTimeout int 185 | DisableConsume bool 186 | } 187 | 188 | type QueueAMQPSettingS struct { 189 | Broker string 190 | DefaultQueue string 191 | ResultBackend string 192 | ResultsExpireIn int 193 | Exchange string 194 | ExchangeType string 195 | BindingKey string 196 | PrefetchCount int 197 | TaskRetryCount int 198 | TaskRetryTimeout int 199 | DisableConsume bool 200 | } 201 | 202 | // AliRocketMQSettingS defines for ali yun RocketMQ queue 203 | type AliRocketMQSettingS struct { 204 | BusinessName string 205 | RegionId string 206 | AccessKey string 207 | SecretKey string 208 | InstanceId string 209 | HttpEndpoint string 210 | } 211 | 212 | type MongoDBSettingS struct { 213 | Uri string 214 | Username string 215 | Password string 216 | Database string 217 | AuthSource string 218 | MaxPoolSize int 219 | MinPoolSize int 220 | } 221 | 222 | type GPoolSettingS struct { 223 | WorkerNum int 224 | JobChanLen int 225 | } 226 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package kelvins 2 | 3 | const ( 4 | Version = "1.6.3" 5 | ) 6 | 7 | const ( 8 | RPCMetadataRequestId = "x-request-id" 9 | RPCMetadataServiceNode = "x-service-node" 10 | RPCMetadataServiceName = "x-service-name" 11 | RPCMetadataResponseTime = "x-response-time" 12 | RPCMetadataHandleTime = "x-handle-time" 13 | RPCMetadataPowerBy = "x-powered-by" 14 | ) 15 | 16 | const ( 17 | HttpMetadataRequestId = "X-Request-Id" 18 | HttpMetadataServiceNode = "X-Service-Node" 19 | HttpMetadataServiceName = "X-Service-Name" 20 | HttpMetadataResponseTime = "X-Response-Time" 21 | HttpMetadataHandleTime = "X-Handle-Time" 22 | HttpMetadataPowerBy = "X-Powered-By" 23 | ) 24 | 25 | const ( 26 | ResponseTimeLayout = "2006-01-02 15:04:05" 27 | ) 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gitee.com/kelvins-io/kelvins 2 | 3 | go 1.13 4 | 5 | require ( 6 | gitee.com/kelvins-io/common v1.1.5 7 | gitee.com/kelvins-io/g2cache v4.0.5+incompatible 8 | github.com/RichardKnop/machinery v1.9.1 9 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 10 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.685 // indirect 11 | github.com/aliyunmq/mq-http-go-sdk v0.0.0-20190911115909-92078b373925 // indirect 12 | github.com/astaxie/beego v1.12.3 13 | github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833 14 | github.com/bojand/ghz v0.100.0 15 | github.com/cloudflare/tableflip v1.2.2 16 | github.com/coocood/freecache v1.1.1 // indirect 17 | github.com/etcd-io/etcd v3.3.25+incompatible 18 | github.com/gin-contrib/pprof v1.3.0 19 | github.com/gin-gonic/gin v1.7.1 20 | github.com/go-ole/go-ole v1.2.4 // indirect 21 | github.com/go-sql-driver/mysql v1.5.0 22 | github.com/gogap/errors v0.0.0-20200228125012-531a6449b28c // indirect 23 | github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 // indirect 24 | github.com/golang-jwt/jwt v3.2.2+incompatible 25 | github.com/gomodule/redigo v2.0.0+incompatible 26 | github.com/google/uuid v1.1.2 27 | github.com/gorilla/websocket v1.4.2 28 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 29 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 30 | github.com/jinzhu/gorm v1.9.15 31 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 32 | github.com/prometheus/client_golang v1.7.0 33 | github.com/qiniu/qmgo v0.7.0 34 | github.com/robfig/cron/v3 v3.0.1 35 | github.com/rs/zerolog v1.20.0 // indirect 36 | github.com/shirou/gopsutil v3.20.10+incompatible // indirect 37 | github.com/valyala/fasthttp v1.17.0 // indirect 38 | go.elastic.co/apm v1.9.0 // indirect 39 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 40 | google.golang.org/grpc v1.40.0 41 | gopkg.in/ini.v1 v1.51.0 42 | xorm.io/xorm v1.0.3 43 | ) 44 | -------------------------------------------------------------------------------- /internal/config/conf.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "gitee.com/kelvins-io/kelvins" 6 | "gitee.com/kelvins-io/kelvins/config/setting" 7 | "gopkg.in/ini.v1" 8 | "log" 9 | ) 10 | 11 | const ( 12 | // ConfFileName defines config file name. 13 | ConfFileName = "./etc/app.ini" 14 | // SectionServer is a section name for grpc server. 15 | SectionServer = "kelvins-server" 16 | // SectionHttpServer is a section name for http 17 | SectionHttpServer = "kelvins-http-server" 18 | // SectionHttpRateLimit is a section mame for http 19 | SectionHttpRateLimit = "kelvins-http-rate-limit" 20 | // SectionLogger is a section name for logger. 21 | SectionLogger = "kelvins-logger" 22 | // SectionMysql is a sectoin name for mysql. 23 | SectionMysql = "kelvins-mysql" 24 | // SectionG2cache is a section name for g2cache 25 | SectionG2cache = "kelvins-g2cache" 26 | // SectionRedis is a section name for redis 27 | SectionRedis = "kelvins-redis" 28 | // SectionMongodb is a section name for mongodb 29 | SectionMongoDB = "kelvins-mongodb" 30 | // SectionQueueRedis is a section name for redis queue 31 | SectionQueueRedis = "kelvins-queue-redis" 32 | // SectionQueueAliAMQP is a section name for ali amqp 33 | SectionQueueAliAMQP = "kelvins-queue-ali-amqp" 34 | // SectionQueueAMQP is a section name for amqp 35 | SectionQueueAMQP = "kelvins-queue-amqp" 36 | // SectionQueueAliRocketMQ is a section name for ali-rocketmq 37 | SectionQueueAliRocketMQ = "kelvins-queue-ali-rocketmq" 38 | // SectionQueueServer is a section name for queue-server 39 | SectionQueueServer = "kelvins-queue-server" 40 | // SectionGPool is goroutine pool 41 | SectionGPool = "kelvins-gpool" 42 | // SectionJwt is jwt 43 | SectionJwt = "kelvins-jwt" 44 | // SectionAuth is rpc auth ,just for compatibility, it will be deleted in the future 45 | SectionAuth = "kelvins-auth" 46 | // SectionRPCAuth is rpc auth 47 | SectionRPCAuth = "kelvins-rpc-auth" 48 | // SectionRPCServerParams is server rpc params 49 | SectionRPCServerParams = "kelvins-rpc-server" 50 | // SectionRPCServerKeepaliveParams is server rpc keep alive params 51 | SectionRPCServerKeepaliveParams = "kelvins-rpc-server-kp" 52 | // SectionRPCServerKeepaliveEnforcementPolicy is server rpc keep alive enf policy 53 | SectionRPCServerKeepaliveEnforcementPolicy = "kelvins-rpc-server-kep" 54 | // SectionRPCClientKeepaliveParams is client rpc keep alive params 55 | SectionRPCClientKeepaliveParams = "kelvins-rpc-client-kp" 56 | // SectionRPCTransportBuffer is rpc transport buffer 57 | SectionRPCTransportBuffer = "kelvins-rpc-transport-buffer" 58 | // SectionRPCRateLimit is rpc rate limit 59 | SectionRPCRateLimit = "kelvins-rpc-rate-limit" 60 | ) 61 | 62 | // cfg reads file app.ini. 63 | var ( 64 | cfg *ini.File 65 | flagConfigPath = flag.String("conf_file", "", "set config file path") 66 | ) 67 | 68 | // LoadDefaultConfig loads config form cfg. 69 | func LoadDefaultConfig(application *kelvins.Application) error { 70 | flag.Parse() 71 | var configFile = ConfFileName 72 | if *flagConfigPath != "" { 73 | configFile = *flagConfigPath 74 | } 75 | 76 | // Setup cfg object 77 | var err error 78 | cfg, err = ini.Load(configFile) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | // Setup default settings 84 | for _, sectionName := range cfg.SectionStrings() { 85 | if sectionName == SectionServer { 86 | kelvins.ServerSetting = new(setting.ServerSettingS) 87 | MapConfig(sectionName, kelvins.ServerSetting) 88 | continue 89 | } 90 | if sectionName == SectionHttpServer { 91 | kelvins.HttpServerSetting = new(setting.HttpServerSettingS) 92 | MapConfig(sectionName, kelvins.HttpServerSetting) 93 | } 94 | if sectionName == SectionHttpRateLimit { 95 | kelvins.HttpRateLimitSetting = new(setting.HttpRateLimitSettingS) 96 | MapConfig(sectionName, kelvins.HttpRateLimitSetting) 97 | } 98 | if sectionName == SectionJwt { 99 | kelvins.JwtSetting = new(setting.JwtSettingS) 100 | MapConfig(sectionName, kelvins.JwtSetting) 101 | continue 102 | } 103 | if sectionName == SectionRPCAuth || sectionName == SectionAuth { 104 | kelvins.RPCAuthSetting = new(setting.RPCAuthSettingS) 105 | MapConfig(sectionName, kelvins.RPCAuthSetting) 106 | continue 107 | } 108 | if sectionName == SectionRPCServerParams { 109 | kelvins.RPCServerParamsSetting = new(setting.RPCServerParamsS) 110 | MapConfig(sectionName, kelvins.RPCServerParamsSetting) 111 | continue 112 | } 113 | if sectionName == SectionRPCServerKeepaliveParams { 114 | kelvins.RPCServerKeepaliveParamsSetting = new(setting.RPCServerKeepaliveParamsS) 115 | MapConfig(sectionName, kelvins.RPCServerKeepaliveParamsSetting) 116 | continue 117 | } 118 | if sectionName == SectionRPCServerKeepaliveEnforcementPolicy { 119 | kelvins.RPCServerKeepaliveEnforcementPolicySetting = new(setting.RPCServerKeepaliveEnforcementPolicyS) 120 | MapConfig(sectionName, kelvins.RPCServerKeepaliveEnforcementPolicySetting) 121 | continue 122 | } 123 | if sectionName == SectionRPCClientKeepaliveParams { 124 | kelvins.RPCClientKeepaliveParamsSetting = new(setting.RPCClientKeepaliveParamsS) 125 | MapConfig(sectionName, kelvins.RPCClientKeepaliveParamsSetting) 126 | continue 127 | } 128 | if sectionName == SectionRPCTransportBuffer { 129 | kelvins.RPCTransportBufferSetting = new(setting.RPCTransportBufferS) 130 | MapConfig(sectionName, kelvins.RPCTransportBufferSetting) 131 | continue 132 | } 133 | if sectionName == SectionRPCRateLimit { 134 | kelvins.RPCRateLimitSetting = new(setting.RPCRateLimitSettingS) 135 | MapConfig(sectionName, kelvins.RPCRateLimitSetting) 136 | continue 137 | } 138 | if sectionName == SectionLogger { 139 | kelvins.LoggerSetting = new(setting.LoggerSettingS) 140 | MapConfig(sectionName, kelvins.LoggerSetting) 141 | continue 142 | } 143 | if sectionName == SectionMysql { 144 | kelvins.MysqlSetting = new(setting.MysqlSettingS) 145 | MapConfig(sectionName, kelvins.MysqlSetting) 146 | continue 147 | } 148 | if sectionName == SectionRedis { 149 | kelvins.RedisSetting = new(setting.RedisSettingS) 150 | MapConfig(sectionName, kelvins.RedisSetting) 151 | continue 152 | } 153 | if sectionName == SectionG2cache { 154 | kelvins.G2CacheSetting = new(setting.G2CacheSettingS) 155 | MapConfig(sectionName, kelvins.G2CacheSetting) 156 | continue 157 | } 158 | if sectionName == SectionMongoDB { 159 | kelvins.MongoDBSetting = new(setting.MongoDBSettingS) 160 | MapConfig(sectionName, kelvins.MongoDBSetting) 161 | continue 162 | } 163 | if sectionName == SectionQueueRedis { 164 | kelvins.QueueRedisSetting = new(setting.QueueRedisSettingS) 165 | MapConfig(sectionName, kelvins.QueueRedisSetting) 166 | continue 167 | } 168 | if sectionName == SectionQueueAliAMQP { 169 | kelvins.QueueAliAMQPSetting = new(setting.QueueAliAMQPSettingS) 170 | MapConfig(sectionName, kelvins.QueueAliAMQPSetting) 171 | continue 172 | } 173 | if sectionName == SectionQueueAMQP { 174 | kelvins.QueueAMQPSetting = new(setting.QueueAMQPSettingS) 175 | MapConfig(sectionName, kelvins.QueueAMQPSetting) 176 | continue 177 | } 178 | if sectionName == SectionQueueAliRocketMQ { 179 | kelvins.AliRocketMQSetting = new(setting.AliRocketMQSettingS) 180 | MapConfig(sectionName, kelvins.AliRocketMQSetting) 181 | continue 182 | } 183 | if sectionName == SectionQueueServer { 184 | kelvins.QueueServerSetting = new(setting.QueueServerSettingS) 185 | MapConfig(sectionName, kelvins.QueueServerSetting) 186 | continue 187 | } 188 | if sectionName == SectionGPool { 189 | kelvins.GPoolSetting = new(setting.GPoolSettingS) 190 | MapConfig(sectionName, kelvins.GPoolSetting) 191 | continue 192 | } 193 | } 194 | return nil 195 | } 196 | 197 | // MapConfig uses cfg to map config. 198 | func MapConfig(section string, v interface{}) { 199 | log.Printf("[info] Load default config %s", section) 200 | sec, err := cfg.GetSection(section) 201 | if err != nil { 202 | log.Fatalf("[err] Fail to parse '%s': %v", section, err) 203 | } 204 | err = sec.MapTo(v) 205 | if err != nil { 206 | log.Fatalf("[err] %s section map to setting err: %v", section, err) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /internal/config/env.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | const ( 8 | // ETCD V3 Server URL 9 | ENV_ETCDV3_SERVER_URL = "ETCDV3_SERVER_URL" 10 | // ETCD V3 Server URLs 11 | ENV_ETCDV3_SERVER_URLS = "ETCDV3_SERVER_URLS" 12 | // GO_ENV 13 | GO_ENV = "GO_ENV" 14 | ) 15 | 16 | // GetEtcdV3ServerURL gets etcd v3 server url config from env. 17 | func GetEtcdV3ServerURL() string { 18 | return os.Getenv(ENV_ETCDV3_SERVER_URL) 19 | } 20 | 21 | // GetEtcdV3ServerURLs gets etcd v3 server urls config from env. 22 | func GetEtcdV3ServerURLs() string { 23 | values := os.Getenv(ENV_ETCDV3_SERVER_URLS) 24 | if values != "" { 25 | return values 26 | } 27 | 28 | return GetEtcdV3ServerURL() 29 | } 30 | 31 | const ( 32 | DefaultEnvironmentDev = "dev" 33 | DefaultEnvironmentTest = "test" 34 | DefaultEnvironmentRelease = "release" 35 | DefaultEnvironmentProd = "prod" 36 | ) 37 | -------------------------------------------------------------------------------- /internal/logging/log.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import "log" 4 | 5 | const ( 6 | ERR = "[err] " 7 | INFO = "[info] " 8 | ) 9 | 10 | func Fatal(v string) { 11 | log.Fatalf(ERR+"%s", v) 12 | } 13 | 14 | func Fatalf(format string, v ...interface{}) { 15 | log.Fatalf(ERR+format, v...) 16 | } 17 | 18 | func Err(v string) { 19 | log.Printf(ERR+"%s", v) 20 | } 21 | 22 | func Errf(format string, v ...interface{}) { 23 | log.Printf(ERR+format, v...) 24 | } 25 | 26 | func Info(v string) { 27 | log.Printf(INFO+"%s", v) 28 | } 29 | 30 | func Infof(format string, v ...interface{}) { 31 | log.Printf(INFO+format, v...) 32 | } 33 | -------------------------------------------------------------------------------- /internal/metrics_mux/elastic_metrics.go: -------------------------------------------------------------------------------- 1 | package metrics_mux 2 | 3 | import ( 4 | "expvar" 5 | "fmt" 6 | "gitee.com/kelvins-io/common/ptool" 7 | "net/http" 8 | ) 9 | 10 | var appStats = expvar.NewMap("appstats") 11 | 12 | func GetElasticMux(mux *http.ServeMux) *http.ServeMux { 13 | mux.HandleFunc("/debug/vars", metricsHandler) 14 | return mux 15 | } 16 | 17 | // metricsHandler print expvar data. 18 | func metricsHandler(w http.ResponseWriter, r *http.Request) { 19 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 20 | 21 | appStats.Set("Goroutine", expvar.Func(ptool.GetGoroutineCount)) 22 | appStats.Set("Threadcreate", expvar.Func(ptool.GetThreadCreateCount)) 23 | appStats.Set("Block", expvar.Func(ptool.GetBlockCount)) 24 | appStats.Set("Mutex", expvar.Func(ptool.GetMutexCount)) 25 | appStats.Set("Heap", expvar.Func(ptool.GetHeapCount)) 26 | 27 | first := true 28 | report := func(key string, value interface{}) { 29 | if !first { 30 | fmt.Fprintf(w, ",\n") 31 | } 32 | first = false 33 | if str, ok := value.(string); ok { 34 | fmt.Fprintf(w, "%q: %q", key, str) 35 | } else { 36 | fmt.Fprintf(w, "%q: %v", key, value) 37 | } 38 | } 39 | 40 | fmt.Fprintf(w, "{\n") 41 | expvar.Do(func(kv expvar.KeyValue) { 42 | report(kv.Key, kv.Value) 43 | }) 44 | fmt.Fprintf(w, "\n}\n") 45 | } 46 | -------------------------------------------------------------------------------- /internal/metrics_mux/pprof.go: -------------------------------------------------------------------------------- 1 | package metrics_mux 2 | 3 | import ( 4 | "net/http" 5 | "net/http/pprof" 6 | ) 7 | 8 | func GetPProfMux(mux *http.ServeMux) *http.ServeMux { 9 | mux.HandleFunc("/debug/pprof/", pprof.Index) 10 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 11 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 12 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 13 | mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 14 | return mux 15 | } 16 | -------------------------------------------------------------------------------- /internal/metrics_mux/prometheus_metrics.go: -------------------------------------------------------------------------------- 1 | package metrics_mux 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus/promhttp" 5 | "net/http" 6 | ) 7 | 8 | func GetPrometheusMux(mux *http.ServeMux) *http.ServeMux { 9 | mux.Handle("/metrics", promhttp.Handler()) 10 | return mux 11 | } 12 | -------------------------------------------------------------------------------- /internal/service/slb/etcdconfig/service_config.go: -------------------------------------------------------------------------------- 1 | package etcdconfig 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "gitee.com/kelvins-io/common/json" 8 | "gitee.com/kelvins-io/kelvins/internal/service/slb" 9 | "gitee.com/kelvins-io/kelvins/internal/util" 10 | "gitee.com/kelvins-io/kelvins/internal/vars" 11 | "github.com/etcd-io/etcd/client" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | Service = "/kelvins-service" 18 | DefaultCluster = "cluster" 19 | ) 20 | 21 | var ErrServiceConfigKeyNotExist = errors.New("service config key not exist") 22 | 23 | type ServiceConfigClient struct { 24 | ServiceLB *slb.ServiceLB 25 | Config 26 | } 27 | 28 | type Config struct { 29 | ServiceVersion string `json:"service_version"` 30 | ServicePort string `json:"service_port"` 31 | ServiceIP string `json:"service_ip"` 32 | ServiceKind string `json:"service_kind"` 33 | LastModified string `json:"last_modified"` 34 | } 35 | 36 | func NewServiceConfigClient(slb *slb.ServiceLB) *ServiceConfigClient { 37 | return &ServiceConfigClient{ServiceLB: slb} 38 | } 39 | 40 | // GetKeyName etcd key cannot end with a number 41 | func (s *ServiceConfigClient) GetKeyName(serverName string, sequences ...string) string { 42 | key := Service + "." + serverName + "." + DefaultCluster 43 | for _, s := range sequences { 44 | key += "/" + s 45 | } 46 | return key 47 | } 48 | 49 | func (s *ServiceConfigClient) GetConfig(sequence string) (*Config, error) { 50 | cli, err := util.NewEtcd(s.ServiceLB.EtcdServerUrl) 51 | if err != nil { 52 | return nil, fmt.Errorf("util.NewEtcd err: %v,etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl) 53 | } 54 | 55 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 56 | defer cancel() 57 | 58 | key := s.GetKeyName(s.ServiceLB.ServerName, sequence) 59 | serviceInfo, err := client.NewKeysAPI(cli).Get(ctx, key, nil) 60 | if err != nil { 61 | if client.IsKeyNotFound(err) { 62 | return nil, ErrServiceConfigKeyNotExist 63 | } 64 | return nil, fmt.Errorf("cli.Get err: %v, key: %v", err, key) 65 | } 66 | 67 | var config Config 68 | if len(serviceInfo.Node.Value) > 0 { 69 | err = json.Unmarshal(serviceInfo.Node.Value, &config) 70 | if err != nil { 71 | return nil, fmt.Errorf("json.Unmarshal err: %v, key: %v,values: %v", err, key, serviceInfo.Node.Value) 72 | } 73 | } 74 | 75 | if config.ServicePort == "" { 76 | return nil, fmt.Errorf("servicePort is empty, key: %s", key) 77 | } 78 | 79 | return &config, nil 80 | } 81 | 82 | func (s *ServiceConfigClient) ClearConfig(sequence string) error { 83 | cli, err := util.NewEtcd(s.ServiceLB.EtcdServerUrl) 84 | if err != nil { 85 | return fmt.Errorf("util.NewEtcd err: %v,etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl) 86 | } 87 | 88 | key := s.GetKeyName(s.ServiceLB.ServerName, sequence) 89 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 90 | defer cancel() 91 | 92 | _, err = client.NewKeysAPI(cli).Delete(ctx, key, nil) 93 | if err != nil { 94 | if client.IsKeyNotFound(err) { 95 | return ErrServiceConfigKeyNotExist 96 | } 97 | return fmt.Errorf("cli.Delete err: %v key: %v", err, key) 98 | } 99 | 100 | return nil 101 | } 102 | 103 | func (s *ServiceConfigClient) WriteConfig(sequence string, c Config) error { 104 | cli, err := util.NewEtcd(s.ServiceLB.EtcdServerUrl) 105 | if err != nil { 106 | return fmt.Errorf("util.NewEtcd err: %v,etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl) 107 | } 108 | 109 | key := s.GetKeyName(s.ServiceLB.ServerName, sequence) 110 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 111 | defer cancel() 112 | 113 | jsonConfig, err := json.MarshalToString(&c) 114 | if err != nil { 115 | return fmt.Errorf("json.MarshalToString err: %v key: %v config: %+v", err, key, c) 116 | } 117 | _, err = client.NewKeysAPI(cli).Set(ctx, key, jsonConfig, &client.SetOptions{ 118 | PrevExist: client.PrevNoExist, 119 | }) 120 | if err != nil { 121 | return fmt.Errorf("cli.Set err: %v key: %v values: %v", err, key, jsonConfig) 122 | } 123 | 124 | return nil 125 | } 126 | 127 | func (s *ServiceConfigClient) ListConfigs() (map[string]*Config, error) { 128 | return s.listConfigs("/") 129 | } 130 | 131 | func (s *ServiceConfigClient) GetConfigs() (map[string]*Config, error) { 132 | return s.listConfigs(s.GetKeyName(s.ServiceLB.ServerName)) 133 | } 134 | 135 | func (s *ServiceConfigClient) Watch(ctx context.Context) (<-chan struct{}, error) { 136 | notice := make(chan struct{}, 1) 137 | cli, err := util.NewEtcd(s.ServiceLB.EtcdServerUrl) 138 | if err != nil { 139 | return notice, fmt.Errorf("util.NewEtcd err: %v,etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl) 140 | } 141 | 142 | kapi := client.NewKeysAPI(cli) 143 | ctx, cancel := context.WithCancel(ctx) 144 | watcher := kapi.Watcher(s.GetKeyName(s.ServiceLB.ServerName), &client.WatcherOptions{ 145 | AfterIndex: 0, 146 | Recursive: true, 147 | }) 148 | go func() { 149 | defer func() { 150 | cancel() 151 | close(notice) 152 | }() 153 | for { 154 | select { 155 | case <-ctx.Done(): 156 | return 157 | case <-vars.AppCloseCh: 158 | return 159 | default: 160 | } 161 | resp, err := watcher.Next(ctx) 162 | if err != nil { 163 | time.Sleep(500 * time.Millisecond) 164 | continue 165 | } 166 | 167 | if strings.ToLower(resp.Action) != "get" { 168 | // 防止notice来不及被客户端消费 169 | select { 170 | case <-notice: 171 | default: 172 | } 173 | notice <- struct{}{} 174 | } 175 | } 176 | }() 177 | 178 | return notice, nil 179 | } 180 | 181 | func (s *ServiceConfigClient) listConfigs(key string) (map[string]*Config, error) { 182 | cli, err := util.NewEtcd(s.ServiceLB.EtcdServerUrl) 183 | if err != nil { 184 | return nil, fmt.Errorf("util.NewEtcd err: %v,etcdUrl: %v", err, s.ServiceLB.EtcdServerUrl) 185 | } 186 | 187 | kapi := client.NewKeysAPI(cli) 188 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 189 | defer cancel() 190 | 191 | serviceInfos, err := kapi.Get(ctx, key, nil) 192 | if err != nil { 193 | if client.IsKeyNotFound(err) { 194 | return nil, ErrServiceConfigKeyNotExist 195 | } 196 | return nil, fmt.Errorf("cli.Get err: %v key: %v", err, key) 197 | } 198 | 199 | configs := make(map[string]*Config) 200 | for _, info := range serviceInfos.Node.Nodes { 201 | if len(info.Value) > 0 { 202 | index := strings.Index(info.Key, Service) 203 | if index == 0 { 204 | config := &Config{} 205 | err := json.Unmarshal(info.Value, config) 206 | if err != nil { 207 | return nil, fmt.Errorf("json.UnmarshalByte err: %v values: %v", err, info.Value) 208 | } 209 | 210 | configs[info.Key] = config 211 | } 212 | } 213 | } 214 | 215 | return configs, nil 216 | } 217 | -------------------------------------------------------------------------------- /internal/service/slb/service.go: -------------------------------------------------------------------------------- 1 | package slb 2 | 3 | type ServiceLB struct { 4 | EtcdServerUrl string 5 | ServerName string 6 | } 7 | 8 | // NewService return ServiceLB instance. 9 | func NewService(etcdServerUrl, serverName string) *ServiceLB { 10 | return &ServiceLB{EtcdServerUrl: etcdServerUrl, ServerName: serverName} 11 | } 12 | -------------------------------------------------------------------------------- /internal/setup/gateway.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "context" 5 | "gitee.com/kelvins-io/common/errcode" 6 | "gitee.com/kelvins-io/common/json" 7 | "gitee.com/kelvins-io/common/proto/common" 8 | "gitee.com/kelvins-io/kelvins/internal/vars" 9 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | "log" 13 | "net/http" 14 | ) 15 | 16 | type GRPCErrReturn struct { 17 | ErrCode int32 `json:"code,omitempty"` 18 | ErrMsg string `json:"error,omitempty"` 19 | ErrDetail string `json:"detail,omitempty"` 20 | } 21 | 22 | // NewGateway ... 23 | func NewGateway() *runtime.ServeMux { 24 | runtime.HTTPError = customHTTPError 25 | return runtime.NewServeMux() 26 | } 27 | 28 | // customHTTPError customs grpc-gateway response json. 29 | func customHTTPError(ctx context.Context, _ *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, r *http.Request, err error) { 30 | s, ok := status.FromError(err) 31 | if !ok { 32 | s = status.New(codes.Unknown, err.Error()) 33 | } 34 | 35 | grpcErrReturn := GRPCErrReturn{} 36 | details := s.Details() 37 | isDetail := false 38 | for _, detail := range details { 39 | if v, ok := detail.(*common.Error); ok { 40 | grpcErrReturn.ErrCode = v.Code 41 | grpcErrReturn.ErrMsg = v.Message 42 | isDetail = true 43 | break 44 | } 45 | } 46 | 47 | if isDetail == false && s.Message() != "" { 48 | errCode := errcode.FAIL 49 | if s.Code() == codes.DeadlineExceeded { 50 | errCode = errcode.DEADLINE_EXCEEDED 51 | } 52 | 53 | grpcErrReturn.ErrCode = int32(errCode) 54 | grpcErrReturn.ErrMsg = errcode.GetErrMsg(errCode) 55 | grpcErrReturn.ErrDetail = s.Message() 56 | 57 | if vars.ErrLogger != nil { 58 | vars.ErrLogger.Errorf(ctx, "grpc-gateway(%s) err: %s", r.RemoteAddr+":"+r.RequestURI, s.Message()) 59 | } else { 60 | log.Printf("grpc-gateway(%s) err: %s\n", r.RemoteAddr+":"+r.RequestURI, s.Message()) 61 | } 62 | } 63 | 64 | respMessage, _ := json.Marshal(grpcErrReturn) 65 | 66 | w.Header().Set("Content-type", marshaler.ContentType()) 67 | w.WriteHeader(errcode.ToHttpStatusCode(s.Code())) 68 | _, err = w.Write(respMessage) 69 | if err != nil { 70 | if vars.ErrLogger != nil { 71 | vars.ErrLogger.Errorf(ctx, "Gateway(%s) response write err: %v, msg: %s", r.RemoteAddr+":"+r.RequestURI, err, s.Message()) 72 | } else { 73 | log.Printf("Gateway(%s) response write err: %v, msg: %s\n", r.RemoteAddr+":"+r.RequestURI, err, s.Message()) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/setup/grpc.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "google.golang.org/grpc" 5 | ) 6 | 7 | // NewGRPC ... 8 | func NewGRPC(opts []grpc.ServerOption) *grpc.Server { 9 | return grpc.NewServer(opts...) 10 | } 11 | -------------------------------------------------------------------------------- /internal/setup/http_server.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "crypto/tls" 5 | "gitee.com/kelvins-io/kelvins/config/setting" 6 | "golang.org/x/net/http2" 7 | "golang.org/x/net/http2/h2c" 8 | "google.golang.org/grpc" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | // NewHttpServer ... 14 | func NewHttpServer(handler http.Handler, tlsConfig *tls.Config, serverSetting *setting.HttpServerSettingS) *http.Server { 15 | return &http.Server{ 16 | Handler: handler, 17 | TLSConfig: tlsConfig, 18 | Addr: serverSetting.GetAddr(), 19 | ReadTimeout: serverSetting.GetReadTimeout(), 20 | WriteTimeout: serverSetting.GetWriteTimeout(), 21 | IdleTimeout: serverSetting.GetIdleTimeout(), 22 | } 23 | } 24 | 25 | func NewHttp2Server(handler http.Handler, tlsConfig *tls.Config, serverSetting *setting.HttpServerSettingS) *http.Server { 26 | return NewHttpServer(h2c.NewHandler(handler, &http2.Server{IdleTimeout: serverSetting.GetIdleTimeout()}), tlsConfig, serverSetting) 27 | } 28 | 29 | // GRPCHandlerFunc gRPCHandlerFunc ... 30 | func GRPCHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler, serverSetting *setting.HttpServerSettingS) http.Handler { 31 | if otherHandler == nil { 32 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 | grpcServer.ServeHTTP(w, r) 34 | }) 35 | } 36 | return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 | if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { 38 | grpcServer.ServeHTTP(w, r) 39 | } else { 40 | if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" { 41 | // CORS 42 | w.Header().Set("Access-Control-Allow-Origin", "*") 43 | headers := []string{"Content-Type", "Accept"} 44 | w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ",")) 45 | methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE"} 46 | w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ",")) 47 | return 48 | } 49 | otherHandler.ServeHTTP(w, r) 50 | } 51 | }), &http2.Server{ 52 | IdleTimeout: serverSetting.GetIdleTimeout(), 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /internal/setup/server_mux.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins/internal/metrics_mux" 5 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 6 | "net/http" 7 | ) 8 | 9 | // NewServerMux ... 10 | func NewServerMux(debug bool) *http.ServeMux { 11 | mux := http.NewServeMux() 12 | if !debug { 13 | return mux 14 | } 15 | mux = metrics_mux.GetElasticMux(mux) 16 | mux = metrics_mux.GetPProfMux(mux) 17 | mux = metrics_mux.GetPrometheusMux(mux) 18 | return mux 19 | } 20 | 21 | // NewGatewayServerMux ... 22 | func NewGatewayServerMux(gateway *runtime.ServeMux, debug bool) *http.ServeMux { 23 | mux := NewServerMux(debug) 24 | mux.Handle("/", gateway) 25 | return mux 26 | } 27 | -------------------------------------------------------------------------------- /internal/util/etcd.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/etcd-io/etcd/client" 8 | ) 9 | 10 | func NewEtcd(urls string) (client.Client, error) { 11 | cli, err := client.New(client.Config{ 12 | Endpoints: strings.Split(urls, ","), 13 | Transport: client.DefaultTransport, 14 | HeaderTimeoutPerRequest: 10 * time.Second, 15 | }) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return cli, nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | var r = rand.New(rand.NewSource(time.Now().UnixNano())) 9 | 10 | func RandInt(min, max int) int { 11 | if min >= max || min == 0 || max == 0 { 12 | return max 13 | } 14 | return r.Intn(max-min) + min 15 | } 16 | -------------------------------------------------------------------------------- /internal/vars/var.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | import "gitee.com/kelvins-io/common/log" 4 | 5 | // Version is internal vars fork root path vars.go 6 | var Version = "1.5.x" 7 | 8 | // FrameworkLogger is a internal var for Framework log 9 | var FrameworkLogger log.LoggerContextIface 10 | 11 | // ErrLogger is a internal vars for application to log err msg. 12 | var ErrLogger log.LoggerContextIface 13 | 14 | // AccessLogger is a internal vars for application to log access log 15 | var AccessLogger log.LoggerContextIface 16 | 17 | // BusinessLogger is a internal vars for application to log business log 18 | var BusinessLogger log.LoggerContextIface 19 | 20 | // AppCloseCh is a internal vars for app close notice 21 | var AppCloseCh chan struct{} 22 | 23 | // ServiceIp is current service ip addr 24 | var ServiceIp string 25 | 26 | // ServicePort is current service port 27 | var ServicePort string 28 | -------------------------------------------------------------------------------- /kelvins-http开启H2调用抓包.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvins-io/kelvins/09d3727bdaf012976d068120a038da1f386aa15c/kelvins-http开启H2调用抓包.png -------------------------------------------------------------------------------- /kelvins-rpc调用抓包.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvins-io/kelvins/09d3727bdaf012976d068120a038da1f386aa15c/kelvins-rpc调用抓包.png -------------------------------------------------------------------------------- /kelvins.go: -------------------------------------------------------------------------------- 1 | package kelvins 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "gitee.com/kelvins-io/common/event" 7 | "gitee.com/kelvins-io/common/log" 8 | "gitee.com/kelvins-io/common/queue" 9 | "github.com/RichardKnop/machinery/v1" 10 | "github.com/gin-gonic/gin" 11 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 12 | "github.com/robfig/cron/v3" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/health" 15 | "net/http" 16 | ) 17 | 18 | const ( 19 | AppTypeGrpc = 1 20 | AppTypeCron = 2 21 | AppTypeQueue = 3 22 | AppTypeHttp = 4 23 | ) 24 | 25 | var ( 26 | AppTypeText = map[int32]string{ 27 | AppTypeGrpc: "gRPC", 28 | AppTypeCron: "Cron", 29 | AppTypeQueue: "Queue", 30 | AppTypeHttp: "Http", 31 | } 32 | ) 33 | 34 | // Application ... 35 | type Application struct { 36 | Name string 37 | Type int32 38 | LoggerRootPath string 39 | LoggerLevel string 40 | Environment string 41 | LoadConfig func() error 42 | SetupVars func() error 43 | StopFunc func() error 44 | } 45 | 46 | // GRPCApplication ... 47 | type GRPCApplication struct { 48 | *Application 49 | Port int64 50 | GRPCServer *grpc.Server 51 | HealthServer *GRPCHealthServer 52 | RegisterGRPCHealthHandle func(*GRPCHealthServer) // execute in the coroutine 53 | NumServerWorkers uint32 54 | GatewayServeMux *runtime.ServeMux 55 | Mux *http.ServeMux 56 | HttpServer *http.Server 57 | TlsConfig *tls.Config 58 | GKelvinsLogger log.LoggerContextIface 59 | GSysErrLogger log.LoggerContextIface 60 | UnaryServerInterceptors []grpc.UnaryServerInterceptor 61 | StreamServerInterceptors []grpc.StreamServerInterceptor 62 | ServerOptions []grpc.ServerOption 63 | RegisterGRPCServer func(*grpc.Server) error 64 | RegisterGateway func(context.Context, *runtime.ServeMux, string, []grpc.DialOption) error 65 | RegisterHttpRoute func(*http.ServeMux) error 66 | RegisterEventProducer func(event.ProducerIface) error 67 | RegisterEventHandler func(event.EventServerIface) error 68 | } 69 | 70 | type GRPCHealthServer struct { 71 | *health.Server 72 | } 73 | 74 | // AuthFuncOverride let go of health check 75 | func (a *GRPCHealthServer) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) { 76 | return ctx, nil 77 | } 78 | 79 | // CronJob warps job define. 80 | type CronJob struct { 81 | Name string // Job unique name 82 | Spec string // Job specification 83 | Job func() // Job func 84 | } 85 | 86 | // CronApplication ... 87 | type CronApplication struct { 88 | *Application 89 | Cron *cron.Cron 90 | GenCronJobs func() []*CronJob 91 | RegisterEventProducer func(event.ProducerIface) error 92 | RegisterEventHandler func(event.EventServerIface) error 93 | } 94 | 95 | // QueueApplication ... 96 | type QueueApplication struct { 97 | *Application 98 | QueueServerToWorker map[*queue.MachineryQueue][]*machinery.Worker 99 | GetNamedTaskFuncs func() map[string]interface{} 100 | RegisterEventProducer func(event.ProducerIface) error 101 | RegisterEventHandler func(event.EventServerIface) error 102 | } 103 | 104 | // HTTPApplication ... 105 | type HTTPApplication struct { 106 | *Application 107 | Port int64 108 | TlsConfig *tls.Config 109 | Mux *http.ServeMux 110 | HttpServer *http.Server 111 | RegisterHttpRoute func(*http.ServeMux) error 112 | RegisterHttpGinRoute func(*gin.Engine) // is not nil will over RegisterHttpGinRoute 113 | RegisterEventProducer func(event.ProducerIface) error 114 | RegisterEventHandler func(event.EventServerIface) error 115 | } 116 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvins-io/kelvins/09d3727bdaf012976d068120a038da1f386aa15c/logo.png -------------------------------------------------------------------------------- /setup/g2cache.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/kelvins-io/g2cache" 6 | "gitee.com/kelvins-io/kelvins/config/setting" 7 | ) 8 | 9 | func NewG2Cache(g2cacheSetting *setting.G2CacheSettingS, out g2cache.OutCache, local g2cache.LocalCache) (*g2cache.G2Cache, error) { 10 | if g2cacheSetting == nil { 11 | return nil, fmt.Errorf("g2cacheSetting is nil") 12 | } 13 | if g2cacheSetting.CacheMonitor { 14 | g2cache.CacheMonitor = true 15 | if g2cacheSetting.CacheMonitorSecond > 0 { 16 | g2cache.CacheMonitorSecond = g2cacheSetting.CacheMonitorSecond 17 | } 18 | } 19 | if g2cacheSetting.CacheDebug { 20 | g2cache.CacheDebug = true 21 | } 22 | if g2cacheSetting.EntryLazyFactor > 0 { 23 | g2cache.EntryLazyFactor = g2cacheSetting.EntryLazyFactor 24 | } 25 | if g2cacheSetting.GPoolWorkerNum > 0 { 26 | g2cache.DefaultGPoolWorkerNum = g2cacheSetting.GPoolWorkerNum 27 | } 28 | if g2cacheSetting.GPoolJobQueueChanLen > 0 { 29 | g2cache.DefaultGPoolJobQueueChanLen = g2cacheSetting.GPoolJobQueueChanLen 30 | } 31 | if g2cacheSetting.FreeCacheSize > 0 { 32 | g2cache.DefaultFreeCacheSize = g2cacheSetting.FreeCacheSize 33 | } 34 | if len(g2cacheSetting.RedisConfDSN) <= 0 { 35 | return nil, fmt.Errorf("g2cacheSetting.RedisConfDSN is empty") 36 | } else { 37 | g2cache.DefaultRedisConf.DSN = g2cacheSetting.RedisConfDSN 38 | } 39 | if g2cacheSetting.RedisConfDB >= 0 { 40 | g2cache.DefaultRedisConf.DB = g2cacheSetting.RedisConfDB 41 | } 42 | if len(g2cacheSetting.RedisConfPwd) > 0 { 43 | g2cache.DefaultRedisConf.Pwd = g2cacheSetting.RedisConfPwd 44 | } 45 | if g2cacheSetting.RedisConfMaxConn > 0 { 46 | g2cache.DefaultRedisConf.MaxConn = g2cacheSetting.RedisConfMaxConn 47 | } 48 | if g2cacheSetting.OutCachePubSub { 49 | g2cache.OutCachePubSub = true 50 | if len(g2cacheSetting.PubSubRedisChannel) != 0 { 51 | g2cache.DefaultPubSubRedisChannel = g2cacheSetting.PubSubRedisChannel 52 | } 53 | if g2cacheSetting.PubSubRedisConfDSN != "" { 54 | g2cache.DefaultPubSubRedisConf.DSN = g2cacheSetting.PubSubRedisConfDSN 55 | } else { 56 | g2cache.DefaultPubSubRedisConf.DSN = g2cacheSetting.RedisConfDSN 57 | } 58 | if g2cache.DefaultPubSubRedisConf.DSN == "" { 59 | return nil, fmt.Errorf("g2cacheSetting.RedisConfDSN & PubSubRedisConfDSN is empty") 60 | } 61 | if g2cacheSetting.PubSubRedisConfDB >= 0 { 62 | g2cache.DefaultPubSubRedisConf.DB = g2cacheSetting.PubSubRedisConfDB 63 | } else { 64 | g2cache.DefaultPubSubRedisConf.DB = g2cacheSetting.RedisConfDB 65 | } 66 | if g2cacheSetting.PubSubRedisConfPwd != "" { 67 | g2cache.DefaultPubSubRedisConf.Pwd = g2cacheSetting.PubSubRedisConfPwd 68 | } else { 69 | g2cache.DefaultPubSubRedisConf.Pwd = g2cacheSetting.RedisConfPwd 70 | } 71 | if g2cacheSetting.PubSubRedisConfMaxConn > 0 { 72 | g2cache.DefaultPubSubRedisConf.MaxConn = g2cacheSetting.PubSubRedisConfMaxConn 73 | } else { 74 | g2cache.DefaultPubSubRedisConf.MaxConn = g2cacheSetting.RedisConfMaxConn 75 | } 76 | } 77 | 78 | return g2cache.New(out, local) 79 | } 80 | -------------------------------------------------------------------------------- /setup/mongodb.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "gitee.com/kelvins-io/kelvins/config/setting" 7 | "gitee.com/kelvins-io/kelvins/internal/logging" 8 | "github.com/qiniu/qmgo" 9 | ) 10 | 11 | func NewMongoDBClient(mongodbSetting *setting.MongoDBSettingS) (*qmgo.QmgoClient, error) { 12 | if mongodbSetting == nil { 13 | return nil, fmt.Errorf("mongodbSetting is nil") 14 | } 15 | if mongodbSetting.Uri == "" { 16 | return nil, fmt.Errorf("mongodbSetting.Uri is empty") 17 | } 18 | if mongodbSetting.Database == "" { 19 | return nil, fmt.Errorf("mongodbSetting.Database is empty") 20 | } 21 | if mongodbSetting.Username == "" { 22 | return nil, fmt.Errorf("mongodbSetting.Username is empty") 23 | } 24 | if mongodbSetting.Password == "" { 25 | return nil, fmt.Errorf("mongodbSetting.Password is empty") 26 | } 27 | if mongodbSetting.AuthSource == "" { 28 | return nil, fmt.Errorf("mongodbSetting.AuthSource is empty") 29 | } 30 | if mongodbSetting.MaxPoolSize <= 0 { 31 | return nil, fmt.Errorf("mongodbSetting.MaxPoolSize is letter or equal zero") 32 | } 33 | if mongodbSetting.MinPoolSize <= 0 { 34 | return nil, fmt.Errorf("mongodbSetting.MinPoolSize is letter or equal zero") 35 | } 36 | // 初始化mongodb 37 | ctx := context.Background() 38 | var maxPoolSize = uint64(mongodbSetting.MaxPoolSize) 39 | var minPoolSize = uint64(mongodbSetting.MinPoolSize) 40 | 41 | mgoCfg := &qmgo.Config{ 42 | Uri: mongodbSetting.Uri, 43 | Database: mongodbSetting.Database, 44 | MaxPoolSize: &maxPoolSize, 45 | MinPoolSize: &minPoolSize, 46 | Auth: &qmgo.Credential{ 47 | AuthMechanism: "", 48 | AuthSource: mongodbSetting.AuthSource, 49 | Username: mongodbSetting.Username, 50 | Password: mongodbSetting.Password, 51 | PasswordSet: false, 52 | }, 53 | } 54 | client, err := qmgo.Open(ctx, mgoCfg) 55 | if err != nil { 56 | logging.Errf("mongodb(%s) open err: %v\n", mongodbSetting.Uri, err) 57 | return nil, err 58 | } 59 | 60 | return client, nil 61 | } 62 | -------------------------------------------------------------------------------- /setup/mysql.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "gitee.com/kelvins-io/common/log" 9 | "gitee.com/kelvins-io/kelvins/config/setting" 10 | "gitee.com/kelvins-io/kelvins/internal/config" 11 | "io" 12 | "net/url" 13 | "os" 14 | "strconv" 15 | "sync" 16 | "time" 17 | 18 | _ "github.com/go-sql-driver/mysql" 19 | "github.com/jinzhu/gorm" 20 | "xorm.io/xorm" 21 | xormLog "xorm.io/xorm/log" 22 | ) 23 | 24 | // NewMySQLWithGORM NewMySQL returns *gorm.DB instance. 25 | func NewMySQLWithGORM(mysqlSetting *setting.MysqlSettingS) (*gorm.DB, error) { 26 | if mysqlSetting == nil { 27 | return nil, fmt.Errorf("mysqlSetting is nil") 28 | } 29 | if mysqlSetting.UserName == "" { 30 | return nil, fmt.Errorf("lack of mysqlSetting.UserName") 31 | } 32 | if mysqlSetting.Password == "" { 33 | return nil, fmt.Errorf("lack of mysqlSetting.Password") 34 | } 35 | if mysqlSetting.Host == "" { 36 | return nil, fmt.Errorf("lack of mysqlSetting.Host") 37 | } 38 | if mysqlSetting.DBName == "" { 39 | return nil, fmt.Errorf("lack of mysqlSetting.DBName") 40 | } 41 | if mysqlSetting.Charset == "" { 42 | return nil, fmt.Errorf("lack of mysqlSetting.Charset") 43 | } 44 | 45 | var buf bytes.Buffer 46 | buf.WriteString(mysqlSetting.UserName) 47 | buf.WriteString(":") 48 | buf.WriteString(mysqlSetting.Password) 49 | buf.WriteString("@tcp(") 50 | buf.WriteString(mysqlSetting.Host) 51 | buf.WriteString(")/") 52 | buf.WriteString(mysqlSetting.DBName) 53 | buf.WriteString("?charset=") 54 | buf.WriteString(mysqlSetting.Charset) 55 | buf.WriteString("&parseTime=" + strconv.FormatBool(mysqlSetting.ParseTime)) 56 | buf.WriteString("&multiStatements=" + strconv.FormatBool(mysqlSetting.MultiStatements)) 57 | if mysqlSetting.ConnectionTimeout != "" { 58 | buf.WriteString(fmt.Sprintf("&timeout=%v", mysqlSetting.ConnectionTimeout)) 59 | } 60 | if mysqlSetting.WriteTimeout != "" { 61 | buf.WriteString(fmt.Sprintf("&writeTimeout=%v", mysqlSetting.WriteTimeout)) 62 | } 63 | if mysqlSetting.ReadTimeout != "" { 64 | buf.WriteString(fmt.Sprintf("&readTimeout=%v", mysqlSetting.ReadTimeout)) 65 | } 66 | if mysqlSetting.Loc == "" { 67 | buf.WriteString("&loc=Local") 68 | } else { 69 | buf.WriteString("&loc=" + url.QueryEscape(mysqlSetting.Loc)) 70 | } 71 | 72 | db, err := gorm.Open("mysql", buf.String()) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | environ := mysqlSetting.Environment 78 | if environ == config.DefaultEnvironmentDev || environ == config.DefaultEnvironmentTest { 79 | gormLogger := &gormLogger{ 80 | logger: mysqlSetting.Logger, 81 | } 82 | db.LogMode(true) 83 | if environ == config.DefaultEnvironmentDev { 84 | gormLogger.out = os.Stdout 85 | } 86 | db.SetLogger(gormLogger) 87 | } 88 | 89 | db.DB().SetConnMaxLifetime(3600 * time.Second) 90 | if mysqlSetting.ConnMaxLifeSecond > 0 { 91 | db.DB().SetConnMaxLifetime(time.Duration(mysqlSetting.ConnMaxLifeSecond) * time.Second) 92 | } 93 | 94 | db.DB().SetMaxIdleConns(10) 95 | if mysqlSetting.MaxIdle > 0 { 96 | db.DB().SetMaxIdleConns(mysqlSetting.MaxIdle) 97 | } 98 | 99 | db.DB().SetMaxOpenConns(10) 100 | if mysqlSetting.MaxOpen > 0 { 101 | db.DB().SetMaxOpenConns(mysqlSetting.MaxOpen) 102 | } 103 | 104 | return db, nil 105 | } 106 | 107 | type gormLogger struct { 108 | logger log.LoggerContextIface 109 | out io.Writer 110 | } 111 | 112 | var logBufPool = sync.Pool{ 113 | New: func() interface{} { 114 | b := make([]byte, 32*1024) 115 | return &b 116 | }, 117 | } 118 | 119 | var gormLoggerCtx = context.Background() 120 | 121 | func (l *gormLogger) Print(vv ...interface{}) { 122 | l.logger.Info(gormLoggerCtx, vv) 123 | if l.out != nil { 124 | buf := logBufPool.Get().(*[]byte) 125 | defer logBufPool.Put(buf) 126 | w := bytes.NewBuffer(*buf) 127 | w.WriteString("[gorm] ") 128 | for _, v := range vv { 129 | vLog, _ := json.Marshal(v) 130 | w.Write(vLog) 131 | } 132 | _, _ = l.out.Write(w.Bytes()) 133 | } 134 | } 135 | 136 | var xormLogLevel = map[string]xormLog.LogLevel{ 137 | "debug": xormLog.LOG_DEBUG, 138 | "info": xormLog.LOG_INFO, 139 | "warn": xormLog.LOG_WARNING, 140 | "error": xormLog.LOG_ERR, 141 | } 142 | 143 | // NewMySQLWithXORM NewMySQL returns *xorm.DB instance. 144 | func NewMySQLWithXORM(mysqlSetting *setting.MysqlSettingS) (xorm.EngineInterface, error) { 145 | if mysqlSetting == nil { 146 | return nil, fmt.Errorf("mysqlSetting is nil") 147 | } 148 | if mysqlSetting.UserName == "" { 149 | return nil, fmt.Errorf("lack of mysqlSetting.UserName") 150 | } 151 | if mysqlSetting.Password == "" { 152 | return nil, fmt.Errorf("lack of mysqlSetting.Password") 153 | } 154 | if mysqlSetting.Host == "" { 155 | return nil, fmt.Errorf("lack of mysqlSetting.Host") 156 | } 157 | if mysqlSetting.DBName == "" { 158 | return nil, fmt.Errorf("lack of mysqlSetting.DBName") 159 | } 160 | if mysqlSetting.Charset == "" { 161 | return nil, fmt.Errorf("lack of mysqlSetting.Charset") 162 | } 163 | 164 | var buf bytes.Buffer 165 | buf.WriteString(mysqlSetting.UserName) 166 | buf.WriteString(":") 167 | buf.WriteString(mysqlSetting.Password) 168 | buf.WriteString("@tcp(") 169 | buf.WriteString(mysqlSetting.Host) 170 | buf.WriteString(")/") 171 | buf.WriteString(mysqlSetting.DBName) 172 | buf.WriteString("?charset=") 173 | buf.WriteString(mysqlSetting.Charset) 174 | buf.WriteString("&parseTime=" + strconv.FormatBool(mysqlSetting.ParseTime)) 175 | buf.WriteString("&multiStatements=" + strconv.FormatBool(mysqlSetting.MultiStatements)) 176 | if mysqlSetting.ConnectionTimeout != "" { 177 | buf.WriteString(fmt.Sprintf("&timeout=%v", mysqlSetting.ConnectionTimeout)) 178 | } 179 | if mysqlSetting.WriteTimeout != "" { 180 | buf.WriteString(fmt.Sprintf("&writeTimeout=%v", mysqlSetting.WriteTimeout)) 181 | } 182 | if mysqlSetting.ReadTimeout != "" { 183 | buf.WriteString(fmt.Sprintf("&readTimeout=%v", mysqlSetting.ReadTimeout)) 184 | } 185 | if mysqlSetting.Loc == "" { 186 | buf.WriteString("&loc=Local") 187 | } else { 188 | buf.WriteString("&loc=" + url.QueryEscape(mysqlSetting.Loc)) 189 | } 190 | 191 | engine, err := xorm.NewEngine("mysql", buf.String()) 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | environ := mysqlSetting.Environment 197 | if environ == config.DefaultEnvironmentDev || environ == config.DefaultEnvironmentTest { 198 | var writer io.Writer 199 | writer = &xormLogger{ 200 | logger: mysqlSetting.Logger, 201 | } 202 | engine.SetLogLevel(xormLogLevel[mysqlSetting.LoggerLevel]) 203 | if environ == config.DefaultEnvironmentDev { 204 | writer = io.MultiWriter(writer, os.Stdout) 205 | } 206 | engine.SetLogger(xormLog.NewSimpleLogger(writer)) 207 | engine.ShowSQL(true) 208 | } 209 | 210 | engine.SetConnMaxLifetime(3600 * time.Second) 211 | if mysqlSetting.ConnMaxLifeSecond > 0 { 212 | engine.SetConnMaxLifetime(time.Duration(mysqlSetting.ConnMaxLifeSecond) * time.Second) 213 | } 214 | 215 | engine.SetMaxIdleConns(10) 216 | if mysqlSetting.MaxIdle > 0 { 217 | engine.SetMaxIdleConns(mysqlSetting.MaxIdle) 218 | } 219 | 220 | engine.SetMaxOpenConns(10) 221 | if mysqlSetting.MaxOpen > 0 { 222 | engine.SetMaxOpenConns(mysqlSetting.MaxOpen) 223 | } 224 | 225 | return engine, nil 226 | } 227 | 228 | var xormLoggerCtx = context.Background() 229 | 230 | type xormLogger struct { 231 | logger log.LoggerContextIface 232 | } 233 | 234 | func (l *xormLogger) Write(p []byte) (n int, err error) { 235 | l.logger.Info(xormLoggerCtx, string(p)) 236 | return 0, nil 237 | } 238 | 239 | // SetGORMCreateCallback is set create callback 240 | func SetGORMCreateCallback(db *gorm.DB, callback func(scope *gorm.Scope)) { 241 | db.Callback().Create().Replace("gorm:update_time_stamp", callback) 242 | } 243 | 244 | // SetGORMUpdateCallback is set update callback 245 | func SetGORMUpdateCallback(db *gorm.DB, callback func(scope *gorm.Scope)) { 246 | db.Callback().Update().Replace("gorm:update_time_stamp", callback) 247 | } 248 | 249 | // SetGORMDeleteCallback is set delete callback 250 | func SetGORMDeleteCallback(db *gorm.DB, callback func(scope *gorm.Scope)) { 251 | db.Callback().Delete().Replace("gorm:delete", callback) 252 | } 253 | -------------------------------------------------------------------------------- /setup/queue.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/kelvins-io/common/queue" 6 | "gitee.com/kelvins-io/kelvins/config/setting" 7 | ) 8 | 9 | // NewRedisQueue returns *kelvinsqueue.MachineryQueue instance of redis queue. 10 | func NewRedisQueue(redisQueueSetting *setting.QueueRedisSettingS, namedTaskFuncs map[string]interface{}) (*queue.MachineryQueue, error) { 11 | if redisQueueSetting == nil { 12 | return nil, fmt.Errorf("RedisQueueSetting is nil") 13 | } 14 | if redisQueueSetting.Broker == "" { 15 | return nil, fmt.Errorf("Lack of redisQueueSetting.Broker") 16 | } 17 | if redisQueueSetting.DefaultQueue == "" { 18 | return nil, fmt.Errorf("Lack of redisQueueSetting.DefaultQueue") 19 | } 20 | if redisQueueSetting.ResultBackend == "" { 21 | return nil, fmt.Errorf("Lack of redisQueueSetting.ResultBackend") 22 | } 23 | if redisQueueSetting.ResultsExpireIn < 0 { 24 | return nil, fmt.Errorf("RedisQueueSetting.ResultsExpireIn must >= 0") 25 | } 26 | 27 | redisQueue, err := queue.NewRedisQueue( 28 | redisQueueSetting.Broker, 29 | redisQueueSetting.DefaultQueue, 30 | redisQueueSetting.ResultBackend, 31 | redisQueueSetting.ResultsExpireIn, 32 | namedTaskFuncs, 33 | ) 34 | if err != nil { 35 | return nil, fmt.Errorf("kelvinsqueue.NewRedisQueue: %v", err) 36 | } 37 | 38 | return redisQueue, nil 39 | } 40 | 41 | // NewAliAMQPQueue returns *kelvinsqueue.MachineryQueue instance of aliyun AMQP queue. 42 | func NewAliAMQPQueue(aliAMQPQueueSetting *setting.QueueAliAMQPSettingS, namedTaskFuncs map[string]interface{}) (*queue.MachineryQueue, error) { 43 | if aliAMQPQueueSetting == nil { 44 | return nil, fmt.Errorf("AliAMQPQueueSetting is nil") 45 | } 46 | if aliAMQPQueueSetting.AccessKey == "" { 47 | return nil, fmt.Errorf("Lack of aliAMQPQueueSetting.AccessKey") 48 | } 49 | if aliAMQPQueueSetting.SecretKey == "" { 50 | return nil, fmt.Errorf("Lack of aliAMQPQueueSetting.SecretKey") 51 | } 52 | if aliAMQPQueueSetting.AliUid < 0 { 53 | return nil, fmt.Errorf("AliAMQPQueueSetting.AliUid must >= 0") 54 | } 55 | if aliAMQPQueueSetting.EndPoint == "" { 56 | return nil, fmt.Errorf("Lack of aliAMQPQueueSetting.EndPoint") 57 | } 58 | if aliAMQPQueueSetting.VHost == "" { 59 | return nil, fmt.Errorf("Lack of aliAMQPQueueSetting.VHost") 60 | } 61 | if aliAMQPQueueSetting.DefaultQueue == "" { 62 | return nil, fmt.Errorf("Lack of aliAMQPQueueSetting.DefaultQueue") 63 | } 64 | if aliAMQPQueueSetting.ResultBackend == "" { 65 | return nil, fmt.Errorf("Lack of aliAMQPQueueSetting.ResultBackend") 66 | } 67 | if aliAMQPQueueSetting.ResultsExpireIn < 0 { 68 | return nil, fmt.Errorf("AliAMQPQueueSetting.ResultsExpireIn must >= 0") 69 | } 70 | 71 | var aliAMQPConfig = &queue.AliAMQPConfig{ 72 | AccessKey: aliAMQPQueueSetting.AccessKey, 73 | SecretKey: aliAMQPQueueSetting.SecretKey, 74 | AliUid: aliAMQPQueueSetting.AliUid, 75 | EndPoint: aliAMQPQueueSetting.EndPoint, 76 | VHost: aliAMQPQueueSetting.VHost, 77 | DefaultQueue: aliAMQPQueueSetting.DefaultQueue, 78 | ResultBackend: aliAMQPQueueSetting.ResultBackend, 79 | ResultsExpireIn: aliAMQPQueueSetting.ResultsExpireIn, 80 | Exchange: aliAMQPQueueSetting.Exchange, 81 | ExchangeType: aliAMQPQueueSetting.ExchangeType, 82 | BindingKey: aliAMQPQueueSetting.BindingKey, 83 | PrefetchCount: aliAMQPQueueSetting.PrefetchCount, 84 | QueueBindingArgs: nil, 85 | NamedTaskFuncs: namedTaskFuncs, 86 | } 87 | 88 | var aliAMQPQueue, err = queue.NewAliAMQPMqQueue(aliAMQPConfig) 89 | if err != nil { 90 | return nil, fmt.Errorf("kelvinsqueue.NewAliAMQPMqQueue: %v", err) 91 | } 92 | 93 | return aliAMQPQueue, nil 94 | } 95 | 96 | // SetUpAMQPQueue returns *queue.MachineryQueue instance of AMQP queue. 97 | func NewAMQPQueue(amqpQueueSetting *setting.QueueAMQPSettingS, namedTaskFuncs map[string]interface{}) (*queue.MachineryQueue, error) { 98 | if amqpQueueSetting == nil { 99 | return nil, fmt.Errorf("[err] amqpQueueSetting is nil") 100 | } 101 | if amqpQueueSetting.Broker == "" { 102 | return nil, fmt.Errorf("[err] Lack of amqpQueueSetting.Broker") 103 | } 104 | if amqpQueueSetting.DefaultQueue == "" { 105 | return nil, fmt.Errorf("[err] Lack of amqpQueueSetting.DefaultQueue") 106 | } 107 | if amqpQueueSetting.ResultBackend == "" { 108 | return nil, fmt.Errorf("[err] Lack of amqpQueueSetting.ResultBackend") 109 | } 110 | if amqpQueueSetting.ResultsExpireIn < 0 { 111 | return nil, fmt.Errorf("[err] amqpQueueSetting.ResultsExpireIn must >= 0") 112 | } 113 | 114 | var amqpQueue, err = queue.NewRabbitMqQueue( 115 | amqpQueueSetting.Broker, 116 | amqpQueueSetting.DefaultQueue, 117 | amqpQueueSetting.ResultBackend, 118 | amqpQueueSetting.ResultsExpireIn, 119 | amqpQueueSetting.Exchange, 120 | amqpQueueSetting.ExchangeType, 121 | amqpQueueSetting.BindingKey, 122 | amqpQueueSetting.PrefetchCount, 123 | nil, 124 | namedTaskFuncs) 125 | if err != nil { 126 | return nil, fmt.Errorf("kelvinsqueue.NewAliAMQPMqQueue: %v", err) 127 | } 128 | 129 | return amqpQueue, nil 130 | } 131 | -------------------------------------------------------------------------------- /setup/redis.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/kelvins-io/kelvins/config/setting" 6 | "time" 7 | 8 | "github.com/gomodule/redigo/redis" 9 | ) 10 | 11 | // NewRedis returns *redis.Pool instance. 12 | func NewRedis(redisSetting *setting.RedisSettingS) (*redis.Pool, error) { 13 | if redisSetting == nil { 14 | return nil, fmt.Errorf("RedisSetting is nil") 15 | } 16 | if redisSetting.Host == "" { 17 | return nil, fmt.Errorf("Lack of redisSetting.Host") 18 | } 19 | if redisSetting.Password == "" { 20 | return nil, fmt.Errorf("Lack of redisSetting.Password") 21 | } 22 | 23 | maxIdle := redisSetting.MaxIdle 24 | maxActive := redisSetting.MaxActive 25 | if redisSetting.MaxActive > 0 && redisSetting.MaxIdle > 0 { 26 | maxIdle = redisSetting.MaxIdle 27 | maxActive = redisSetting.MaxActive 28 | } 29 | idleTimeout := 240 30 | if redisSetting.IdleTimeout > 0 { 31 | idleTimeout = redisSetting.IdleTimeout 32 | } 33 | return &redis.Pool{ 34 | MaxIdle: maxIdle, 35 | MaxActive: maxActive, 36 | IdleTimeout: time.Duration(idleTimeout) * time.Second, 37 | Dial: func() (redis.Conn, error) { 38 | var opts []redis.DialOption 39 | if redisSetting.ConnectTimeout > 0 { 40 | opts = append(opts, redis.DialConnectTimeout(time.Duration(redisSetting.ConnectTimeout)*time.Second)) 41 | } 42 | if redisSetting.ReadTimeout > 0 { 43 | opts = append(opts, redis.DialReadTimeout(time.Duration(redisSetting.ReadTimeout)*time.Second)) 44 | } 45 | if redisSetting.WriteTimeout > 0 { 46 | opts = append(opts, redis.DialWriteTimeout(time.Duration(redisSetting.WriteTimeout)*time.Second)) 47 | } 48 | c, err := redis.Dial("tcp", redisSetting.Host, opts...) 49 | if err != nil { 50 | return nil, err 51 | } 52 | if redisSetting.Password != "" { 53 | if _, err := c.Do("AUTH", redisSetting.Password); err != nil { 54 | c.Close() 55 | return nil, err 56 | } 57 | } 58 | if redisSetting.DB > 0 { 59 | if _, err := c.Do("SELECT", redisSetting.DB); err != nil { 60 | c.Close() 61 | return nil, err 62 | } 63 | } 64 | 65 | return c, err 66 | }, 67 | TestOnBorrow: func(c redis.Conn, t time.Time) error { 68 | _, err := c.Do("PING") 69 | return err 70 | }, 71 | }, nil 72 | } 73 | -------------------------------------------------------------------------------- /util/client_conn/balancer.go: -------------------------------------------------------------------------------- 1 | package client_conn 2 | 3 | import ( 4 | "google.golang.org/grpc/balancer" 5 | "google.golang.org/grpc/balancer/base" 6 | "math/rand" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | const Name = "kelvins-balancer" 12 | 13 | func newBuilder() balancer.Builder { 14 | return base.NewBalancerBuilder(Name, &rrPickerBuilder{}, base.Config{HealthCheck: true}) 15 | } 16 | 17 | func init() { 18 | balancer.Register(newBuilder()) 19 | } 20 | 21 | type rrPickerBuilder struct{} 22 | 23 | func (*rrPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker { 24 | if len(info.ReadySCs) == 0 { 25 | return base.NewErrPicker(balancer.ErrNoSubConnAvailable) 26 | } 27 | var scs []balancer.SubConn 28 | for sc := range info.ReadySCs { 29 | scs = append(scs, sc) 30 | } 31 | return &rrPicker{ 32 | subConnes: scs, 33 | next: randIntn(len(scs)), 34 | } 35 | } 36 | 37 | type rrPicker struct { 38 | subConnes []balancer.SubConn 39 | mu sync.Mutex 40 | next int 41 | } 42 | 43 | func (p *rrPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) { 44 | p.mu.Lock() 45 | sc := p.subConnes[p.next] 46 | p.next = (p.next + 1) % len(p.subConnes) 47 | p.mu.Unlock() 48 | return balancer.PickResult{SubConn: sc}, nil 49 | } 50 | 51 | var ( 52 | r = rand.New(rand.NewSource(time.Now().UnixNano())) 53 | mu sync.Mutex 54 | ) 55 | 56 | func randInt() int { 57 | mu.Lock() 58 | defer mu.Unlock() 59 | return r.Int() 60 | } 61 | 62 | func randInt63n(n int64) int64 { 63 | mu.Lock() 64 | defer mu.Unlock() 65 | return r.Int63n(n) 66 | } 67 | 68 | func randIntn(n int) int { 69 | mu.Lock() 70 | defer mu.Unlock() 71 | return r.Intn(n) 72 | } 73 | 74 | func randFloat64() float64 { 75 | mu.Lock() 76 | defer mu.Unlock() 77 | return r.Float64() 78 | } 79 | 80 | func randUint64() uint64 { 81 | mu.Lock() 82 | defer mu.Unlock() 83 | return r.Uint64() 84 | } 85 | -------------------------------------------------------------------------------- /util/client_conn/client_conn.go: -------------------------------------------------------------------------------- 1 | package client_conn 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "gitee.com/kelvins-io/kelvins/internal/config" 7 | "gitee.com/kelvins-io/kelvins/internal/logging" 8 | "gitee.com/kelvins-io/kelvins/internal/service/slb" 9 | "gitee.com/kelvins-io/kelvins/internal/service/slb/etcdconfig" 10 | "gitee.com/kelvins-io/kelvins/internal/vars" 11 | "gitee.com/kelvins-io/kelvins/util/grpc_interceptor" 12 | grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware" 13 | grpcRetry "github.com/grpc-ecosystem/go-grpc-middleware/retry" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/connectivity" 17 | "google.golang.org/grpc/keepalive" 18 | "log" 19 | "strings" 20 | "sync" 21 | "time" 22 | ) 23 | 24 | var ( 25 | optsDefault []grpc.DialOption 26 | optsStartup []grpc.DialOption 27 | ) 28 | 29 | var ( 30 | Debug bool 31 | ) 32 | 33 | var ( 34 | mutexCreateConn sync.Map 35 | ) 36 | 37 | type ConnClient struct { 38 | ServerName string 39 | } 40 | 41 | func NewConnClient(serviceName string) (*ConnClient, error) { 42 | serviceNames := strings.Split(serviceName, "-") 43 | if len(serviceNames) < 1 { 44 | return nil, fmt.Errorf("serviceNames(%v) format not contain `-` ", serviceName) 45 | } 46 | 47 | return &ConnClient{ 48 | ServerName: serviceName, 49 | }, nil 50 | } 51 | 52 | // GetConn return a valid connection as much as possible 53 | func (c *ConnClient) GetConn(ctx context.Context, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 54 | conn, err := getRPCConn(c.ServerName) 55 | if err == nil && justConnEffective(conn) { 56 | if Debug { 57 | log.Println(Green("[kelvins] ConnClient conn from cache and conn is effective")) 58 | } 59 | return conn, nil 60 | } 61 | 62 | // prevent the same service from concurrently creating connections 63 | { 64 | v, _ := mutexCreateConn.LoadOrStore(c.ServerName, &sync.Mutex{}) 65 | mutex := v.(*sync.Mutex) 66 | mutex.Lock() 67 | defer mutex.Unlock() 68 | } 69 | 70 | conn, err = getRPCConn(c.ServerName) 71 | if err == nil && justConnEffective(conn) { 72 | if Debug { 73 | log.Println(Green("[kelvins] ConnClient conn from cache and conn is effective")) 74 | } 75 | return conn, nil 76 | } 77 | 78 | if Debug { 79 | log.Println(Red("[kelvins] ConnClient ConnClient from origin")) 80 | } 81 | 82 | // priority order: optsStartup > opts > optsDefault 83 | optsUse := append(optsDefault, opts...) 84 | target := fmt.Sprintf("%s:///%s", kelvinsScheme, c.ServerName) 85 | conn, err = grpc.DialContext( 86 | ctx, 87 | target, 88 | append(optsUse, optsStartup...)..., 89 | ) 90 | 91 | if err == nil && justConnEffective(conn) { 92 | _ee := storageRPCConn(c.ServerName, conn) 93 | if _ee != nil { 94 | if vars.FrameworkLogger != nil { 95 | vars.FrameworkLogger.Errorf(ctx, "storageRPCConn(%s) err %v", c.ServerName, _ee) 96 | } else { 97 | logging.Errf("storageRPCConn(%s) err %v\n", c.ServerName, _ee) 98 | } 99 | } 100 | } 101 | 102 | return conn, err 103 | } 104 | 105 | // GetEndpoints the returned endpoint list may have invalid nodes 106 | func (c *ConnClient) GetEndpoints(ctx context.Context) (endpoints []string, err error) { 107 | etcdServerUrls := config.GetEtcdV3ServerURLs() 108 | serviceLB := slb.NewService(etcdServerUrls, c.ServerName) 109 | serviceConfigClient := etcdconfig.NewServiceConfigClient(serviceLB) 110 | serviceConfigs, err := serviceConfigClient.GetConfigs() 111 | if err != nil { 112 | if vars.FrameworkLogger != nil { 113 | vars.FrameworkLogger.Errorf(ctx, "etcd GetConfig(%v) err %v", c.ServerName, err) 114 | } else { 115 | logging.Errf("etcd GetConfig(%v) err %v\n", c.ServerName, err) 116 | } 117 | return 118 | } 119 | for _, value := range serviceConfigs { 120 | if value.ServiceIP != "" { 121 | endpoints = append(endpoints, fmt.Sprintf("%v:%v", value.ServiceIP, value.ServicePort)) 122 | } else { 123 | endpoints = append(endpoints, fmt.Sprintf("%v:%v", c.ServerName, value.ServicePort)) 124 | } 125 | } 126 | return 127 | } 128 | 129 | func justConnEffective(conn *grpc.ClientConn) bool { 130 | if conn == nil { 131 | return false 132 | } 133 | state := conn.GetState() 134 | if state == connectivity.Idle || state == connectivity.Ready { 135 | return true 136 | } else { 137 | conn.Close() 138 | return false 139 | } 140 | } 141 | 142 | const ( 143 | grpcServiceConfig = `{ 144 | "loadBalancingPolicy": "kelvins-balancer", 145 | "healthCheckConfig": { 146 | "serviceName": "" 147 | } 148 | }` 149 | ) 150 | 151 | const ( 152 | defaultWriteBufSize = 32 * 1024 153 | defaultReadBufSize = 32 * 1024 154 | ) 155 | 156 | func init() { 157 | optsDefault = append(optsDefault, grpc.WithInsecure()) 158 | optsDefault = append(optsDefault, grpc.WithDefaultServiceConfig(grpcServiceConfig)) 159 | optsDefault = append(optsDefault, grpc.WithUnaryInterceptor( 160 | grpcMiddleware.ChainUnaryClient( 161 | grpc_interceptor.UnaryCtxHandleGRPC(), 162 | grpcRetry.UnaryClientInterceptor( 163 | grpcRetry.WithMax(2), 164 | grpcRetry.WithCodes( 165 | codes.Internal, 166 | codes.DeadlineExceeded, 167 | ), 168 | ), 169 | ), 170 | )) 171 | optsDefault = append(optsDefault, grpc.WithStreamInterceptor( 172 | grpcMiddleware.ChainStreamClient( 173 | grpc_interceptor.StreamCtxHandleGRPC(), 174 | ), 175 | )) 176 | optsDefault = append(optsDefault, grpc.WithKeepaliveParams(keepalive.ClientParameters{ 177 | Time: 6 * time.Minute, // 客户端在这段时间之后如果没有活动的RPC,客户端将给服务器发送PING 178 | Timeout: 20 * time.Second, // 连接服务端后等待一段时间后没有收到响应则关闭连接 179 | PermitWithoutStream: true, // 允许客户端在没有活动RPC的情况下向服务端发送PING 180 | })) 181 | optsDefault = append(optsDefault, grpc.WithReadBufferSize(defaultReadBufSize)) 182 | optsDefault = append(optsDefault, grpc.WithWriteBufferSize(defaultWriteBufSize)) 183 | } 184 | 185 | // RPCClientDialOptionAppend only executed at boot load, so no locks are used 186 | func RPCClientDialOptionAppend(opts []grpc.DialOption) { 187 | optsStartup = append(optsStartup, opts...) 188 | } 189 | -------------------------------------------------------------------------------- /util/client_conn/color.go: -------------------------------------------------------------------------------- 1 | package client_conn 2 | 3 | import "fmt" 4 | 5 | const ( 6 | FormatRed = "\033[31m%s\033[0m" 7 | FormatGreen = "\033[32m%s\033[0m" 8 | FormatYellow = "\033[33m%s\033[0m" 9 | FormatBlue = "\033[34m%s\033[0m" 10 | ) 11 | 12 | func Red(msg string) string { 13 | return fmt.Sprintf(FormatRed, msg) 14 | } 15 | 16 | func Green(msg string) string { 17 | return fmt.Sprintf(FormatGreen, msg) 18 | } 19 | 20 | func Yellow(msg string) string { 21 | return fmt.Sprintf(FormatYellow, msg) 22 | } 23 | 24 | func Blue(msg string) string { 25 | return fmt.Sprintf(FormatBlue, msg) 26 | } 27 | -------------------------------------------------------------------------------- /util/client_conn/conn_cache.go: -------------------------------------------------------------------------------- 1 | package client_conn 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/bluele/gcache" 7 | "google.golang.org/grpc" 8 | "math" 9 | "time" 10 | ) 11 | 12 | var ( 13 | internalCache gcache.Cache 14 | internalNotFound = errors.New("object not found") 15 | ) 16 | 17 | const ( 18 | internalCachePrefix = "kelvins-%v" 19 | internalCacheExpire = 3 * time.Hour 20 | ) 21 | 22 | func init() { 23 | internalCache = gcache.New(math.MaxInt16).LRU().Build() 24 | } 25 | 26 | func getRPCConn(serviceName string) (conn *grpc.ClientConn, err error) { 27 | key := genInternalCacheKey(serviceName) 28 | exist := internalCache.Has(key) 29 | if exist { 30 | var value interface{} 31 | value, err = internalCache.Get(key) 32 | if err != nil { 33 | return 34 | } 35 | if value == nil { 36 | err = internalNotFound 37 | return 38 | } 39 | connCache, ok := value.(*grpc.ClientConn) 40 | if ok { 41 | conn = connCache 42 | return 43 | } 44 | } 45 | err = internalNotFound 46 | return 47 | } 48 | 49 | func storageRPCConn(serviceName string, conn *grpc.ClientConn) error { 50 | key := genInternalCacheKey(serviceName) 51 | return internalCache.Set(key, conn) 52 | } 53 | 54 | func genInternalCacheKey(serviceName string) string { 55 | return fmt.Sprintf(internalCachePrefix, serviceName) 56 | } 57 | -------------------------------------------------------------------------------- /util/client_conn/resolver.go: -------------------------------------------------------------------------------- 1 | package client_conn 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "gitee.com/kelvins-io/kelvins" 7 | "gitee.com/kelvins-io/kelvins/internal/config" 8 | "gitee.com/kelvins-io/kelvins/internal/logging" 9 | "gitee.com/kelvins-io/kelvins/internal/service/slb" 10 | "gitee.com/kelvins-io/kelvins/internal/service/slb/etcdconfig" 11 | "gitee.com/kelvins-io/kelvins/internal/vars" 12 | "google.golang.org/grpc/attributes" 13 | "google.golang.org/grpc/resolver" 14 | "time" 15 | ) 16 | 17 | const ( 18 | kelvinsScheme = "kelvins-scheme" 19 | minResolverRate = 3 * time.Second 20 | ) 21 | 22 | type kelvinsResolverBuilder struct{} 23 | 24 | func (*kelvinsResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 25 | ctx, cancel := context.WithCancel(context.Background()) 26 | r := &kelvinsResolver{ 27 | target: target, 28 | cc: cc, 29 | rn: make(chan struct{}, 1), 30 | ctx: ctx, 31 | cancel: cancel, 32 | } 33 | 34 | go r.watcher() 35 | go r.listenEtcd() 36 | 37 | r.ResolveNow(resolver.ResolveNowOptions{}) 38 | 39 | return r, nil 40 | } 41 | 42 | func (*kelvinsResolverBuilder) Scheme() string { return kelvinsScheme } 43 | 44 | type kelvinsResolver struct { 45 | target resolver.Target 46 | cc resolver.ClientConn 47 | rn chan struct{} 48 | ctx context.Context 49 | cancel context.CancelFunc 50 | } 51 | 52 | func (r *kelvinsResolver) watcher() { 53 | for { 54 | select { 55 | case <-kelvins.AppCloseCh: 56 | return 57 | case <-r.ctx.Done(): 58 | return 59 | case <-r.rn: 60 | } 61 | 62 | // 执行解析 63 | r.resolverServiceConfig() 64 | 65 | // 休眠以防止过度重新解析。 传入的解决请求 66 | // 将在 d.rn 中排队。 67 | t := time.NewTimer(minResolverRate) 68 | select { 69 | case <-t.C: 70 | case <-kelvins.AppCloseCh: 71 | t.Stop() 72 | return 73 | case <-r.ctx.Done(): 74 | t.Stop() 75 | return 76 | } 77 | } 78 | } 79 | 80 | var emptyCtx = context.Background() 81 | 82 | func (r *kelvinsResolver) resolverServiceConfig() { 83 | serviceName := r.target.Endpoint 84 | etcdServerUrls := config.GetEtcdV3ServerURLs() 85 | serviceLB := slb.NewService(etcdServerUrls, serviceName) 86 | serviceConfigClient := etcdconfig.NewServiceConfigClient(serviceLB) 87 | var serviceConfigs map[string]*etcdconfig.Config 88 | var err error 89 | // 有限的重试 90 | for i := 0; i < 3; i++ { 91 | serviceConfigs, err = serviceConfigClient.GetConfigs() 92 | if err == nil { 93 | break 94 | } 95 | } 96 | if err != nil { 97 | r.cc.ReportError(fmt.Errorf("etcd GetConfig(%v) err: %v", serviceName, err)) 98 | if vars.FrameworkLogger != nil { 99 | vars.FrameworkLogger.Errorf(emptyCtx, "etcd GetConfig(%v) err: %v", serviceName, err) 100 | } else { 101 | logging.Errf("etcd GetConfig(%v) err: %v\n", serviceName, err) 102 | } 103 | return 104 | } 105 | 106 | if len(serviceConfigs) == 0 { 107 | return 108 | } 109 | 110 | address := make([]resolver.Address, 0, len(serviceConfigs)) 111 | for _, value := range serviceConfigs { 112 | var addr string 113 | if value.ServiceIP != "" { 114 | addr = fmt.Sprintf("%v:%v", value.ServiceIP, value.ServicePort) 115 | } else { 116 | addr = fmt.Sprintf("%v:%v", serviceName, value.ServicePort) 117 | } 118 | // 可以在服务启动时注入机器info,然后在这里把机器info发给gRPC用于balance判断 119 | address = append(address, resolver.Address{ 120 | Addr: addr, 121 | Attributes: attributes.New(kelvins.RPCMetadataServiceNode, addr), 122 | }) 123 | } 124 | if len(address) > 0 { 125 | r.cc.UpdateState(resolver.State{Addresses: address}) 126 | } 127 | } 128 | 129 | func (r *kelvinsResolver) ResolveNow(o resolver.ResolveNowOptions) { 130 | // 防止rn未来得及消费 131 | select { 132 | case <-r.rn: 133 | default: 134 | } 135 | 136 | select { 137 | case r.rn <- struct{}{}: 138 | default: 139 | } 140 | } 141 | 142 | func (r *kelvinsResolver) Close() { r.cancel() } 143 | 144 | func (r *kelvinsResolver) listenEtcd() { 145 | serviceName := r.target.Endpoint 146 | etcdServerUrls := config.GetEtcdV3ServerURLs() 147 | serviceLB := slb.NewService(etcdServerUrls, serviceName) 148 | serviceConfigClient := etcdconfig.NewServiceConfigClient(serviceLB) 149 | notice, err := serviceConfigClient.Watch(r.ctx) 150 | if err != nil { 151 | return 152 | } 153 | for range notice { 154 | r.ResolveNow(resolver.ResolveNowOptions{}) 155 | } 156 | } 157 | 158 | func init() { 159 | resolver.Register(&kelvinsResolverBuilder{}) 160 | } 161 | -------------------------------------------------------------------------------- /util/gin_helper/code.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | const ( 4 | SUCCESS = 200 5 | InvalidParams = 400 6 | Unknown = 0000 7 | ERROR = 500 8 | ErrorTokenEmpty = 4002 9 | ErrorTokenInvalid = 4003 10 | ErrorTokenExpire = 4004 11 | ErrorUserNotExist = 4005 12 | TooManyRequests = 4006 13 | ) 14 | -------------------------------------------------------------------------------- /util/gin_helper/cors.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | func Cors() gin.HandlerFunc { 11 | // 跨域处理 12 | return func(c *gin.Context) { 13 | method := c.Request.Method 14 | origin := c.Request.Header.Get("Origin") 15 | var headerKeys []string 16 | for k, _ := range c.Request.Header { 17 | headerKeys = append(headerKeys, k) 18 | } 19 | headerStr := strings.Join(headerKeys, ", ") 20 | if headerStr != "" { 21 | headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr) 22 | } else { 23 | headerStr = "access-control-allow-origin, access-control-allow-headers" 24 | } 25 | if origin != "" { 26 | c.Header("Access-Control-Allow-Origin", "*") 27 | c.Header("Access-Control-Allow-Headers", headerStr) 28 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 29 | c.Header("Access-Control-Expose-Headers", "Expires,Last-Modified,Pragma,Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, Cache-Control,Content-Language") 30 | c.Header("Access-Control-Allow-Credentials", "true") 31 | c.Header("Vary", "Origin") 32 | c.Header("Content-Type", "application/json") 33 | } 34 | // 放行所有OPTIONS方法 35 | if method == "OPTIONS" { 36 | c.JSON(http.StatusOK, "Options Request!") 37 | } 38 | c.Next() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /util/gin_helper/form.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "gitee.com/kelvins-io/common/json" 7 | "gitee.com/kelvins-io/kelvins" 8 | "github.com/astaxie/beego/validation" 9 | "github.com/gin-gonic/gin" 10 | "log" 11 | "strings" 12 | ) 13 | 14 | func BindAndValid(c *gin.Context, form interface{}) error { 15 | if err := c.Bind(form); err != nil { 16 | return err 17 | } 18 | valid := validation.Validation{} 19 | ok, err := valid.Valid(form) 20 | if err != nil { 21 | return err 22 | } 23 | if !ok { 24 | markErrors(c, valid.Errors) 25 | return buildFormErr(valid.Errors) 26 | } 27 | return nil 28 | } 29 | 30 | func buildFormErr(errs []*validation.Error) error { 31 | var msg strings.Builder 32 | for _, v := range errs { 33 | if v.Field != "" { 34 | msg.WriteString(v.Field) 35 | } else if v.Key != "" { 36 | msg.WriteString(v.Key) 37 | } else { 38 | msg.WriteString(v.Name) 39 | } 40 | msg.WriteString(" : ") 41 | msg.WriteString(json.MarshalToStringNoError(v.Value)) 42 | msg.WriteString(" => ") 43 | msg.WriteString(v.Error()) 44 | msg.WriteString(" should=> ") 45 | msg.WriteString(json.MarshalToStringNoError(v.LimitValue)) 46 | } 47 | return errors.New(msg.String()) 48 | } 49 | 50 | func markErrors(ctx *gin.Context, errors []*validation.Error) { 51 | buf := strings.Builder{} 52 | buf.WriteString(fmt.Sprintf("%v %v %v 400 ", ctx.ClientIP(), ctx.Request.Method, ctx.Request.RequestURI)) 53 | buf.WriteString("{") 54 | for _, err := range errors { 55 | buf.WriteString(fmt.Sprintf("%v:%v ", err.Key, err.Message)) 56 | } 57 | buf.WriteString(" }") 58 | buf.WriteString(fmt.Sprintf(" %v", ctx.Request.Header)) 59 | if kelvins.AccessLogger != nil { 60 | kelvins.AccessLogger.Error(ctx, buf.String()) 61 | } else { 62 | log.Println(buf.String()) 63 | } 64 | 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /util/gin_helper/jwt.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins" 5 | "github.com/gin-gonic/gin" 6 | "github.com/golang-jwt/jwt" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func CheckUserToken() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | token := c.Request.Header.Get("token") 14 | if token == "" { 15 | JsonResponse(c, http.StatusUnauthorized, ErrorTokenEmpty, GetMsg(ErrorTokenEmpty)) 16 | c.Abort() 17 | return 18 | } 19 | claims, err := ParseToken(token) 20 | if err != nil { 21 | JsonResponse(c, http.StatusForbidden, ErrorTokenInvalid, GetMsg(ErrorTokenInvalid)) 22 | c.Abort() 23 | return 24 | } else if claims == nil || claims.Uid == 0 { 25 | JsonResponse(c, http.StatusForbidden, ErrorUserNotExist, GetMsg(ErrorUserNotExist)) 26 | c.Abort() 27 | return 28 | } else if time.Now().Unix() > claims.ExpiresAt { 29 | JsonResponse(c, http.StatusForbidden, ErrorTokenExpire, GetMsg(ErrorTokenExpire)) 30 | c.Abort() 31 | return 32 | } 33 | 34 | c.Set("uid", claims.Uid) 35 | c.Next() 36 | } 37 | } 38 | 39 | const ( 40 | jwtSecret = "&WJof0jaY4ByTHR2" 41 | jwtExpireTime = 2 * time.Hour 42 | ) 43 | 44 | type Claims struct { 45 | UserName string `json:"user_name"` 46 | Uid int `json:"uid"` 47 | jwt.StandardClaims 48 | } 49 | 50 | func GenerateToken(username string, uid int) (string, error) { 51 | var expire = jwtExpireTime 52 | if kelvins.JwtSetting != nil && kelvins.JwtSetting.TokenExpireSecond > 0 { 53 | expire = time.Duration(kelvins.JwtSetting.TokenExpireSecond) * time.Second 54 | } 55 | var secret = jwtSecret 56 | if kelvins.JwtSetting != nil && kelvins.JwtSetting.Secret != "" { 57 | secret = kelvins.JwtSetting.Secret 58 | } 59 | nowTime := time.Now() 60 | expireTime := nowTime.Add(expire) 61 | 62 | claims := Claims{ 63 | UserName: username, 64 | Uid: uid, 65 | StandardClaims: jwt.StandardClaims{ 66 | ExpiresAt: expireTime.Unix(), 67 | Issuer: "kelvins-io/kelvins", 68 | }, 69 | } 70 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS384, claims) 71 | token, err := tokenClaims.SignedString([]byte(secret)) 72 | return token, err 73 | } 74 | 75 | func ParseToken(token string) (*Claims, error) { 76 | var secret = jwtSecret 77 | if kelvins.JwtSetting != nil && kelvins.JwtSetting.Secret != "" { 78 | secret = kelvins.JwtSetting.Secret 79 | } 80 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (i interface{}, err error) { 81 | return []byte(secret), nil 82 | }) 83 | if tokenClaims != nil { 84 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 85 | return claims, nil 86 | } 87 | } 88 | return nil, err 89 | } 90 | -------------------------------------------------------------------------------- /util/gin_helper/metadata.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/kelvins-io/kelvins" 6 | "gitee.com/kelvins-io/kelvins/internal/vars" 7 | "github.com/gin-gonic/gin" 8 | "github.com/google/uuid" 9 | "os" 10 | "time" 11 | ) 12 | 13 | const ( 14 | startTimeKey = "income-time" 15 | ) 16 | 17 | func Metadata(debug bool) gin.HandlerFunc { 18 | return func(c *gin.Context) { 19 | startTime := time.Now() 20 | c.Set(startTimeKey, startTime) 21 | requestId := c.Request.Header.Get(kelvins.HttpMetadataRequestId) 22 | if requestId == "" { 23 | requestId = uuid.New().String() 24 | } 25 | c.Header(kelvins.HttpMetadataRequestId, requestId) 26 | c.Set(kelvins.HttpMetadataRequestId, requestId) 27 | c.Header(kelvins.HttpMetadataPowerBy, "kelvins/http(gin) "+kelvins.Version) 28 | c.Header(kelvins.HttpMetadataServiceName, kelvins.AppName) 29 | if debug { 30 | c.Header(kelvins.HttpMetadataServiceNode, getRPCNodeInfo()) 31 | } 32 | 33 | c.Next() 34 | // next after set header is invalid because the header must be sent before sending the body 35 | } 36 | } 37 | 38 | func GetRequestId(ctx *gin.Context) (requestId string) { 39 | v, ok := ctx.Get(kelvins.HttpMetadataRequestId) 40 | if ok { 41 | requestId, _ = v.(string) 42 | } 43 | return 44 | } 45 | 46 | var ( 47 | hostName, _ = os.Hostname() 48 | ) 49 | 50 | func getRPCNodeInfo() (nodeInfo string) { 51 | nodeInfo = fmt.Sprintf("%v:%v(%v)", vars.ServiceIp, vars.ServicePort, hostName) 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /util/gin_helper/msg.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | var MsgFlags = map[int]string{ 4 | SUCCESS: "ok", 5 | ERROR: "服务器出错", 6 | Unknown: "未知", 7 | TooManyRequests: "请求太多,稍后再试", 8 | InvalidParams: "请求参数错误", 9 | ErrorTokenEmpty: "用户token为空", 10 | ErrorTokenInvalid: "用户token无效", 11 | ErrorTokenExpire: "用户token过期", 12 | ErrorUserNotExist: "用户不存在", 13 | } 14 | 15 | func GetMsg(code int) string { 16 | msg, ok := MsgFlags[code] 17 | if ok { 18 | return msg 19 | } 20 | return MsgFlags[Unknown] 21 | } 22 | -------------------------------------------------------------------------------- /util/gin_helper/rate_limit.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins/util/middleware" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | func RateLimit(maxConcurrent int) gin.HandlerFunc { 10 | var limiter middleware.Limiter 11 | if maxConcurrent > 0 { 12 | limiter = middleware.NewKelvinsRateLimit(maxConcurrent) 13 | } 14 | return func(c *gin.Context) { 15 | if limiter != nil { 16 | if limiter.Limit() { 17 | JsonResponse(c, http.StatusTooManyRequests, TooManyRequests, GetMsg(TooManyRequests)) 18 | c.Abort() 19 | return 20 | } 21 | defer limiter.ReturnTicket() 22 | } 23 | c.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /util/gin_helper/response.go: -------------------------------------------------------------------------------- 1 | package gin_helper 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/kelvins-io/kelvins" 6 | "github.com/gin-gonic/gin" 7 | "time" 8 | ) 9 | 10 | func JsonResponse(ctx *gin.Context, httpCode, retCode int, data interface{}) { 11 | echoStatistics(ctx) 12 | ctx.JSON(httpCode, gin.H{ 13 | "code": retCode, 14 | "msg": GetMsg(retCode), 15 | "data": data, 16 | }) 17 | } 18 | 19 | func ProtoBufResponse(ctx *gin.Context, httpCode int, data interface{}) { 20 | echoStatistics(ctx) 21 | ctx.ProtoBuf(httpCode, data) 22 | } 23 | 24 | func echoStatistics(ctx *gin.Context) { 25 | startTimeVal, ok := ctx.Get(startTimeKey) 26 | if ok { 27 | startTime, ok := startTimeVal.(time.Time) 28 | if !ok { 29 | startTime = time.Time{} 30 | } 31 | endTime := time.Now() 32 | ctx.Header(kelvins.HttpMetadataHandleTime, fmt.Sprintf("%f/s", endTime.Sub(startTime).Seconds())) 33 | ctx.Header(kelvins.HttpMetadataResponseTime, endTime.Format(kelvins.ResponseTimeLayout)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /util/goroutine/errgroup.go: -------------------------------------------------------------------------------- 1 | package goroutine 2 | 3 | import "context" 4 | 5 | func CheckGoroutineErr(errCtx context.Context) error { 6 | select { 7 | case <-errCtx.Done(): 8 | return errCtx.Err() 9 | default: 10 | return nil 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /util/goroutine/grpool.go: -------------------------------------------------------------------------------- 1 | package goroutine 2 | 3 | // thank https://github.com/ivpusic/grpool 4 | import ( 5 | "context" 6 | "gitee.com/kelvins-io/kelvins/internal/logging" 7 | "gitee.com/kelvins-io/kelvins/internal/vars" 8 | "runtime/debug" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // Grouting instance which can accept client jobs 14 | type worker struct { 15 | workerPool chan *worker 16 | jobChannel chan Job 17 | stop chan struct{} 18 | } 19 | 20 | func (w *worker) start() { 21 | go func() { 22 | var job Job 23 | for { 24 | // worker free, add it to pool 25 | w.workerPool <- w 26 | 27 | select { 28 | case job = <-w.jobChannel: 29 | runJob(job) 30 | case <-w.stop: 31 | w.stop <- struct{}{} 32 | return 33 | } 34 | } 35 | }() 36 | } 37 | 38 | func runJob(f func()) { 39 | defer func() { 40 | if err := recover(); err != nil { 41 | if vars.FrameworkLogger != nil { 42 | vars.FrameworkLogger.Error(context.Background(), "[gPool] runJob panic err %v, stack: %v", 43 | err, string(debug.Stack()[:])) 44 | } else { 45 | logging.Errf("[gPool] runJob panic err %v, stack: %v\n", 46 | err, string(debug.Stack()[:])) 47 | } 48 | } 49 | }() 50 | f() 51 | } 52 | 53 | func newWorker(pool chan *worker) *worker { 54 | return &worker{ 55 | workerPool: pool, 56 | jobChannel: make(chan Job), 57 | stop: make(chan struct{}), 58 | } 59 | } 60 | 61 | // Accepts jobs from clients, and waits for first free worker to deliver job 62 | type dispatcher struct { 63 | workerPool chan *worker 64 | jobQueue chan Job 65 | stop chan struct{} 66 | } 67 | 68 | func (d *dispatcher) dispatch() { 69 | for { 70 | select { 71 | case job := <-d.jobQueue: 72 | worker := <-d.workerPool 73 | worker.jobChannel <- job 74 | case <-d.stop: 75 | for i := 0; i < cap(d.workerPool); i++ { 76 | worker := <-d.workerPool 77 | 78 | worker.stop <- struct{}{} 79 | <-worker.stop 80 | } 81 | 82 | d.stop <- struct{}{} 83 | return 84 | } 85 | } 86 | } 87 | 88 | func newDispatcher(workerPool chan *worker, jobQueue chan Job) *dispatcher { 89 | d := &dispatcher{ 90 | workerPool: workerPool, 91 | jobQueue: jobQueue, 92 | stop: make(chan struct{}), 93 | } 94 | 95 | for i := 0; i < cap(d.workerPool); i++ { 96 | worker := newWorker(d.workerPool) 97 | worker.start() 98 | } 99 | 100 | go d.dispatch() 101 | return d 102 | } 103 | 104 | // Job Represents user request, function which should be executed in some worker. 105 | type Job func() 106 | 107 | type Pool struct { 108 | JobQueue chan Job 109 | dispatcher *dispatcher 110 | wg sync.WaitGroup 111 | } 112 | 113 | // NewPool Will make pool of gorouting workers. 114 | // numWorkers - how many workers will be created for this pool 115 | // queueLen - how many jobs can we accept until we block 116 | // 117 | // Returned object contains JobQueue reference, which you can use to send job to pool. 118 | func NewPool(numWorkers int, jobQueueLen int) *Pool { 119 | if numWorkers <= 0 { 120 | numWorkers = 2 121 | } 122 | if jobQueueLen <= 0 { 123 | jobQueueLen = 5 124 | } 125 | jobQueue := make(chan Job, jobQueueLen) 126 | workerPool := make(chan *worker, numWorkers) 127 | 128 | pool := &Pool{ 129 | JobQueue: jobQueue, 130 | dispatcher: newDispatcher(workerPool, jobQueue), 131 | } 132 | 133 | return pool 134 | } 135 | 136 | func (p *Pool) wrapJob(job func()) func() { 137 | return func() { 138 | defer p.JobDone() 139 | job() 140 | } 141 | } 142 | 143 | func (p *Pool) SendJobWithTimeout(job func(), t time.Duration) bool { 144 | select { 145 | case <-time.After(t): 146 | return false 147 | case p.JobQueue <- p.wrapJob(job): 148 | p.WaitCount(1) 149 | return true 150 | } 151 | } 152 | 153 | func (p *Pool) SendJobWithDeadline(job func(), t time.Time) bool { 154 | s := t.Sub(time.Now()) 155 | if s <= 0 { 156 | s = time.Second // timeout 157 | } 158 | select { 159 | case <-time.After(s): 160 | return false 161 | case p.JobQueue <- p.wrapJob(job): 162 | p.WaitCount(1) 163 | return true 164 | } 165 | } 166 | 167 | func (p *Pool) SendJob(job func()) { 168 | p.WaitCount(1) 169 | p.JobQueue <- p.wrapJob(job) 170 | } 171 | 172 | // JobDone In case you are using WaitAll fn, you should call this method 173 | // every time your job is done. 174 | // 175 | // If you are not using WaitAll then we assume you have your own way of synchronizing. 176 | func (p *Pool) JobDone() { 177 | p.wg.Done() 178 | } 179 | 180 | // WaitCount How many jobs we should wait when calling WaitAll. 181 | // It is using WaitGroup Add/Done/Wait 182 | func (p *Pool) WaitCount(count int) { 183 | p.wg.Add(count) 184 | } 185 | 186 | // WaitAll Will wait for all jobs to finish. 187 | func (p *Pool) WaitAll() { 188 | p.wg.Wait() 189 | } 190 | 191 | // Release Will release resources used by pool 192 | func (p *Pool) Release() { 193 | p.dispatcher.stop <- struct{}{} 194 | <-p.dispatcher.stop 195 | } 196 | -------------------------------------------------------------------------------- /util/grpc_interceptor/client_interceptor.go: -------------------------------------------------------------------------------- 1 | package grpc_interceptor 2 | 3 | import ( 4 | "context" 5 | "gitee.com/kelvins-io/kelvins" 6 | "github.com/google/uuid" 7 | "google.golang.org/grpc/metadata" 8 | "time" 9 | 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func UnaryCtxHandleGRPC() grpc.UnaryClientInterceptor { 14 | return func( 15 | ctx context.Context, 16 | method string, 17 | req, resp interface{}, 18 | cc *grpc.ClientConn, 19 | invoker grpc.UnaryInvoker, 20 | opts ...grpc.CallOption, 21 | ) error { 22 | ctx, cancel := ctxHandler(ctx) 23 | if cancel != nil { 24 | defer cancel() 25 | } 26 | 27 | return invoker(ctx, method, req, resp, cc, opts...) 28 | } 29 | } 30 | 31 | func StreamCtxHandleGRPC() grpc.StreamClientInterceptor { 32 | return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 33 | ctx, cancel := ctxHandler(ctx) 34 | if cancel != nil { 35 | defer cancel() 36 | } 37 | 38 | return streamer(ctx, desc, cc, method, opts...) 39 | } 40 | } 41 | 42 | func ctxHandler(ctx context.Context) (context.Context, context.CancelFunc) { 43 | var cancel context.CancelFunc 44 | if _, ok := ctx.Deadline(); !ok { 45 | var defaultTimeout = 60 * time.Second 46 | 47 | ctx, cancel = context.WithTimeout(ctx, defaultTimeout) 48 | } 49 | 50 | // set client metadata 51 | md := metadata.Pairs(kelvins.RPCMetadataRequestId, uuid.New().String()) 52 | ctx = metadata.NewOutgoingContext(ctx, md) 53 | 54 | return ctx, cancel 55 | } 56 | -------------------------------------------------------------------------------- /util/grpc_interceptor/server_interceptor.go: -------------------------------------------------------------------------------- 1 | package grpc_interceptor 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "gitee.com/kelvins-io/common/json" 7 | "gitee.com/kelvins-io/common/log" 8 | "gitee.com/kelvins-io/kelvins" 9 | "gitee.com/kelvins-io/kelvins/internal/vars" 10 | "gitee.com/kelvins-io/kelvins/util/rpc_helper" 11 | "github.com/google/uuid" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/metadata" 14 | "google.golang.org/grpc/status" 15 | "os" 16 | "regexp" 17 | "runtime/debug" 18 | "time" 19 | ) 20 | 21 | type AppServerInterceptor struct { 22 | accessLogger, errLogger log.LoggerContextIface 23 | debug bool 24 | } 25 | 26 | func NewAppServerInterceptor(debug bool, accessLogger, errLogger log.LoggerContextIface) *AppServerInterceptor { 27 | return &AppServerInterceptor{accessLogger: accessLogger, errLogger: errLogger, debug: debug} 28 | } 29 | 30 | func (i *AppServerInterceptor) Metadata(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 31 | if methodIgnore(info.FullMethod) { 32 | return handler(ctx, req) 33 | } 34 | i.handleMetadata(ctx) 35 | return handler(ctx, req) 36 | } 37 | 38 | // Logger add app info in ctx. 39 | func (i *AppServerInterceptor) Logger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 40 | if methodIgnore(info.FullMethod) { 41 | return handler(ctx, req) 42 | } 43 | incomeTime := time.Now() 44 | requestMeta := rpc_helper.GetRequestMetadata(ctx) 45 | var outcomeTime time.Time 46 | var resp interface{} 47 | var err error 48 | defer func() { 49 | outcomeTime = time.Now() 50 | i.echoStatistics(ctx, incomeTime, outcomeTime) 51 | // unary interceptor record req resp err 52 | if err != nil { 53 | if i.errLogger != nil { 54 | s, _ := status.FromError(err) 55 | i.errLogger.Errorf( 56 | ctx, 57 | "grpc access response err:%s, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s, req: %s, response:%s, details: %s", 58 | s.Err().Error(), 59 | info.FullMethod, 60 | json.MarshalToStringNoError(requestMeta), 61 | outcomeTime.Format(kelvins.ResponseTimeLayout), 62 | outcomeTime.Sub(incomeTime).Seconds(), 63 | json.MarshalToStringNoError(req), 64 | json.MarshalToStringNoError(resp), 65 | json.MarshalToStringNoError(s.Details()), 66 | ) 67 | } 68 | } else { 69 | if i.debug && i.accessLogger != nil { 70 | i.accessLogger.Infof( 71 | ctx, 72 | "grpc access response ok, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s, req: %s, response: %s", 73 | info.FullMethod, 74 | json.MarshalToStringNoError(requestMeta), 75 | outcomeTime.Format(kelvins.ResponseTimeLayout), 76 | outcomeTime.Sub(incomeTime).Seconds(), 77 | json.MarshalToStringNoError(req), 78 | json.MarshalToStringNoError(resp), 79 | ) 80 | } 81 | } 82 | }() 83 | 84 | resp, err = handler(ctx, req) 85 | return resp, err 86 | } 87 | 88 | func (i *AppServerInterceptor) StreamMetadata(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 89 | if methodIgnore(info.FullMethod) { 90 | return handler(srv, ss) 91 | } 92 | i.handleMetadata(ss.Context()) 93 | return handler(srv, ss) 94 | } 95 | 96 | // StreamLogger is experimental function 97 | func (i *AppServerInterceptor) StreamLogger(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 98 | if methodIgnore(info.FullMethod) { 99 | return handler(srv, ss) 100 | } 101 | incomeTime := time.Now() 102 | requestMeta := rpc_helper.GetRequestMetadata(ss.Context()) 103 | var err error 104 | defer func() { 105 | outcomeTime := time.Now() 106 | i.echoStatistics(ss.Context(), incomeTime, outcomeTime) 107 | if err != nil { 108 | if i.errLogger != nil { 109 | s, _ := status.FromError(err) 110 | // stream interceptor only record error 111 | i.errLogger.Errorf( 112 | ss.Context(), 113 | "grpc access stream handle err:%s, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s, details: %s", 114 | s.Err().Error(), 115 | info.FullMethod, 116 | json.MarshalToStringNoError(requestMeta), 117 | outcomeTime.Format(kelvins.ResponseTimeLayout), 118 | outcomeTime.Sub(incomeTime).Seconds(), 119 | json.MarshalToStringNoError(s.Details()), 120 | ) 121 | } 122 | } else { 123 | if i.debug && i.accessLogger != nil { 124 | i.accessLogger.Infof( 125 | ss.Context(), 126 | "grpc access stream handle ok, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s", 127 | info.FullMethod, 128 | json.MarshalToStringNoError(requestMeta), 129 | outcomeTime.Format(kelvins.ResponseTimeLayout), 130 | outcomeTime.Sub(incomeTime).Seconds(), 131 | ) 132 | } 133 | } 134 | }() 135 | 136 | err = handler(srv, newStreamWrapper(ss.Context(), i.accessLogger, i.errLogger, ss, info, requestMeta, i.debug)) 137 | return err 138 | } 139 | 140 | // Recovery recovers GRPC panic. 141 | func (i *AppServerInterceptor) Recovery(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 142 | requestMeta := rpc_helper.GetRequestMetadata(ctx) 143 | defer func() { 144 | if e := recover(); e != nil { 145 | if i.errLogger != nil { 146 | i.errLogger.Errorf(ctx, "grpc panic err: %v, grpc method: %s,requestMeta: %v, req: %s, stack: %s", 147 | e, info.FullMethod, json.MarshalToStringNoError(requestMeta), json.MarshalToStringNoError(req), string(debug.Stack()[:])) 148 | } 149 | } 150 | }() 151 | 152 | return handler(ctx, req) 153 | } 154 | 155 | // RecoveryStream is experimental function 156 | func (i *AppServerInterceptor) RecoveryStream(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 157 | requestMeta := rpc_helper.GetRequestMetadata(ss.Context()) 158 | defer func() { 159 | if e := recover(); e != nil { 160 | if i.errLogger != nil { 161 | i.errLogger.Errorf(ss.Context(), "grpc stream panic err: %v, grpc method: %s, requestMeta: %v, stack: %s", 162 | e, info.FullMethod, json.MarshalToStringNoError(requestMeta), string(debug.Stack()[:])) 163 | } 164 | } 165 | }() 166 | 167 | return handler(srv, newStreamRecoverWrapper(ss.Context(), i.errLogger, ss, info, requestMeta, i.debug)) 168 | } 169 | 170 | func (i *AppServerInterceptor) handleMetadata(ctx context.Context) (rId string) { 171 | // request id 172 | existRequestId, requestId := getRPCRequestId(ctx) 173 | if !existRequestId { 174 | // set request id to server 175 | md := metadata.Pairs(kelvins.RPCMetadataRequestId, requestId) 176 | ctx = metadata.NewOutgoingContext(ctx, md) 177 | } 178 | 179 | // return client info 180 | header := metadata.New(map[string]string{ 181 | kelvins.RPCMetadataRequestId: requestId, 182 | kelvins.RPCMetadataServiceName: kelvins.AppName, 183 | kelvins.RPCMetadataPowerBy: "kelvins/rpc " + vars.Version, 184 | }) 185 | if i.debug { 186 | header.Set(kelvins.RPCMetadataServiceNode, getRPCNodeInfo()) 187 | } 188 | grpc.SetHeader(ctx, header) 189 | 190 | return requestId 191 | } 192 | 193 | func (i *AppServerInterceptor) echoStatistics(ctx context.Context, incomeTime, outcomeTime time.Time) { 194 | handleTime := fmt.Sprintf("%f/s", outcomeTime.Sub(incomeTime).Seconds()) 195 | md := metadata.Pairs(kelvins.RPCMetadataResponseTime, outcomeTime.Format(kelvins.ResponseTimeLayout), kelvins.RPCMetadataHandleTime, handleTime) 196 | grpc.SetTrailer(ctx, md) 197 | } 198 | 199 | func getRPCNodeInfo() (nodeInfo string) { 200 | nodeInfo = fmt.Sprintf("%v:%v(%v)", vars.ServiceIp, vars.ServicePort, hostName) 201 | return 202 | } 203 | 204 | var ( 205 | hostName, _ = os.Hostname() 206 | ) 207 | 208 | func getRPCRequestId(ctx context.Context) (ok bool, requestId string) { 209 | ok = false 210 | md, exist := metadata.FromIncomingContext(ctx) 211 | if exist { 212 | if t, ok := md[kelvins.RPCMetadataRequestId]; ok { 213 | for _, e := range t { 214 | if e != "" { 215 | requestId = e 216 | ok = true 217 | break 218 | } 219 | } 220 | } 221 | } 222 | if requestId == "" { 223 | requestId = uuid.New().String() 224 | } 225 | return 226 | } 227 | 228 | type streamRecoverWrapper struct { 229 | accessLogger, errLogger log.LoggerContextIface 230 | ss grpc.ServerStream 231 | ctx context.Context 232 | info *grpc.StreamServerInfo 233 | requestMeta *rpc_helper.RequestMeta 234 | debug bool 235 | } 236 | 237 | func newStreamRecoverWrapper(ctx context.Context, 238 | errLogger log.LoggerContextIface, 239 | ss grpc.ServerStream, 240 | info *grpc.StreamServerInfo, 241 | requestMeta *rpc_helper.RequestMeta, 242 | debug bool) *streamRecoverWrapper { 243 | return &streamRecoverWrapper{ctx: ctx, errLogger: errLogger, ss: ss, info: info, requestMeta: requestMeta, debug: debug} 244 | } 245 | func (s *streamRecoverWrapper) SetHeader(md metadata.MD) error { return s.ss.SetHeader(md) } 246 | func (s *streamRecoverWrapper) SendHeader(md metadata.MD) error { return s.ss.SendHeader(md) } 247 | func (s *streamRecoverWrapper) SetTrailer(md metadata.MD) { s.ss.SetTrailer(md) } 248 | func (s *streamRecoverWrapper) Context() context.Context { return s.ss.Context() } 249 | func (s *streamRecoverWrapper) SendMsg(m interface{}) error { 250 | defer func() { 251 | if e := recover(); e != nil { 252 | if s.errLogger != nil { 253 | s.errLogger.Errorf(s.ctx, "grpc stream/send panic err: %v, grpc method: %s, requestMeta: %v, data: %v, stack: %s", 254 | e, s.info.FullMethod, json.MarshalToStringNoError(s.requestMeta), json.MarshalToStringNoError(m), string(debug.Stack()[:])) 255 | } 256 | } 257 | }() 258 | return s.ss.SendMsg(m) 259 | } 260 | func (s *streamRecoverWrapper) RecvMsg(m interface{}) error { 261 | defer func() { 262 | if e := recover(); e != nil { 263 | if s.errLogger != nil { 264 | s.errLogger.Errorf(s.ctx, "grpc stream/recv panic err: %v, grpc method: %s, requestMeta: %v, data: %v, stack: %s", 265 | e, s.info.FullMethod, json.MarshalToStringNoError(s.requestMeta), json.MarshalToStringNoError(m), string(debug.Stack()[:])) 266 | } 267 | } 268 | }() 269 | return s.ss.RecvMsg(m) 270 | } 271 | 272 | type streamWrapper struct { 273 | accessLogger, errLogger log.LoggerContextIface 274 | ss grpc.ServerStream 275 | ctx context.Context 276 | info *grpc.StreamServerInfo 277 | requestMeta *rpc_helper.RequestMeta 278 | debug bool 279 | } 280 | 281 | func newStreamWrapper(ctx context.Context, 282 | accessLogger, errLogger log.LoggerContextIface, 283 | ss grpc.ServerStream, 284 | info *grpc.StreamServerInfo, 285 | requestMeta *rpc_helper.RequestMeta, 286 | debug bool) *streamWrapper { 287 | return &streamWrapper{ctx: ctx, accessLogger: accessLogger, errLogger: errLogger, ss: ss, info: info, requestMeta: requestMeta, debug: debug} 288 | } 289 | func (s *streamWrapper) SetHeader(md metadata.MD) error { return s.ss.SetHeader(md) } 290 | func (s *streamWrapper) SendHeader(md metadata.MD) error { return s.ss.SendHeader(md) } 291 | func (s *streamWrapper) SetTrailer(md metadata.MD) { s.ss.SetTrailer(md) } 292 | func (s *streamWrapper) Context() context.Context { return s.ss.Context() } 293 | func (s *streamWrapper) SendMsg(m interface{}) error { 294 | if methodIgnore(s.info.FullMethod) { 295 | return s.ss.SendMsg(m) 296 | } 297 | var err error 298 | incomeTime := time.Now() 299 | var outcomeTime time.Time 300 | defer func() { 301 | outcomeTime = time.Now() 302 | if err != nil { 303 | if s.errLogger != nil { 304 | sts, _ := status.FromError(err) 305 | s.errLogger.Errorf( 306 | s.ctx, 307 | "grpc stream/send err:%s, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s, data: %s, details: %s", 308 | sts.Err().Error(), 309 | s.info.FullMethod, 310 | json.MarshalToStringNoError(s.requestMeta), 311 | outcomeTime.Format(kelvins.ResponseTimeLayout), 312 | outcomeTime.Sub(incomeTime).Seconds(), 313 | json.MarshalToStringNoError(m), 314 | json.MarshalToStringNoError(sts.Details()), 315 | ) 316 | } 317 | } else { 318 | if s.debug && s.accessLogger != nil { 319 | s.accessLogger.Infof( 320 | s.ctx, 321 | "grpc stream/send ok, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s, data: %s", 322 | s.info.FullMethod, 323 | json.MarshalToStringNoError(s.requestMeta), 324 | outcomeTime.Format(kelvins.ResponseTimeLayout), 325 | outcomeTime.Sub(incomeTime).Seconds(), 326 | json.MarshalToStringNoError(m), 327 | ) 328 | } 329 | } 330 | }() 331 | 332 | err = s.ss.SendMsg(m) 333 | return err 334 | } 335 | func (s *streamWrapper) RecvMsg(m interface{}) error { 336 | if methodIgnore(s.info.FullMethod) { 337 | return s.ss.RecvMsg(m) 338 | } 339 | var err error 340 | incomeTime := time.Now() 341 | var outcomeTime time.Time 342 | defer func() { 343 | outcomeTime = time.Now() 344 | if err != nil { 345 | if s.errLogger != nil { 346 | sts, _ := status.FromError(err) 347 | s.errLogger.Errorf( 348 | s.ctx, 349 | "grpc stream/recv err:%s, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s, data: %s, details: %s", 350 | sts.Err().Error(), 351 | s.info.FullMethod, 352 | json.MarshalToStringNoError(s.requestMeta), 353 | outcomeTime.Format(kelvins.ResponseTimeLayout), 354 | outcomeTime.Sub(incomeTime).Seconds(), 355 | json.MarshalToStringNoError(m), 356 | json.MarshalToStringNoError(sts.Details()), 357 | ) 358 | } 359 | } else { 360 | if s.debug && s.accessLogger != nil { 361 | s.accessLogger.Infof( 362 | s.ctx, 363 | "grpc stream/recv ok, grpc method: %s, requestMeta: %v, outcomeTime: %v, handleTime: %f/s, data: %s", 364 | s.info.FullMethod, 365 | json.MarshalToStringNoError(s.requestMeta), 366 | outcomeTime.Format(kelvins.ResponseTimeLayout), 367 | outcomeTime.Sub(incomeTime).Seconds(), 368 | json.MarshalToStringNoError(m), 369 | ) 370 | } 371 | } 372 | }() 373 | 374 | err = s.ss.RecvMsg(m) 375 | return err 376 | } 377 | 378 | func methodIgnore(fullMethod string) (ignore bool) { 379 | ignore = ignoreStreamMethod.MatchString(fullMethod) 380 | return ignore 381 | } 382 | 383 | var ( 384 | ignoreStreamMethod = regexp.MustCompilePOSIX(`^/grpc\.health\..*`) 385 | ) 386 | -------------------------------------------------------------------------------- /util/http_helper/websocket.go: -------------------------------------------------------------------------------- 1 | package http_helper 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "net/http" 6 | ) 7 | 8 | var ( 9 | wsUpgrade = websocket.Upgrader{ 10 | // 允许所有CORS跨域请求 11 | CheckOrigin: func(r *http.Request) bool { 12 | return true 13 | }, 14 | } 15 | ) 16 | 17 | func UpgradeWebsocket(resp http.ResponseWriter, req *http.Request) (conn *websocket.Conn, err error) { 18 | // WebSocket握手 19 | return wsUpgrade.Upgrade(resp, req, nil) 20 | } 21 | -------------------------------------------------------------------------------- /util/kprocess/tableflip_darwin.go: -------------------------------------------------------------------------------- 1 | // +build amd64,darwin 2 | 3 | package kprocess 4 | 5 | import ( 6 | "fmt" 7 | "gitee.com/kelvins-io/kelvins/internal/logging" 8 | "net" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/cloudflare/tableflip" 15 | ) 16 | 17 | type KProcess struct { 18 | pidFile string 19 | pid int 20 | processUp *tableflip.Upgrader 21 | } 22 | 23 | // This shows how to use the upgrader 24 | // with the graceful shutdown facilities of net/http. 25 | func (k *KProcess) Listen(network, addr, pidFile string) (ln net.Listener, err error) { 26 | k.pid = os.Getpid() 27 | logging.Infof(fmt.Sprintf("exec process pid %d \n", k.pid)) 28 | 29 | k.processUp, err = tableflip.New(tableflip.Options{ 30 | UpgradeTimeout: 5 * time.Second, 31 | PIDFile: pidFile, 32 | }) 33 | if err != nil { 34 | return nil, err 35 | } 36 | k.pidFile = pidFile 37 | 38 | go k.signal(k.upgrade, k.stop) 39 | 40 | // Listen must be called before Ready 41 | if network != "" && addr != "" { 42 | ln, err = k.processUp.Listen(network, addr) 43 | if err != nil { 44 | return nil, err 45 | } 46 | } 47 | if err := k.processUp.Ready(); err != nil { 48 | return nil, err 49 | } 50 | 51 | return ln, nil 52 | } 53 | 54 | func (k *KProcess) stop() error { 55 | if k.processUp != nil { 56 | k.processUp.Stop() 57 | return os.Remove(k.pidFile) 58 | } 59 | return nil 60 | } 61 | 62 | func (k *KProcess) upgrade() error { 63 | if k.processUp != nil { 64 | return k.processUp.Upgrade() 65 | } 66 | return nil 67 | } 68 | 69 | func (k *KProcess) Exit() <-chan struct{} { 70 | if k.processUp != nil { 71 | return k.processUp.Exit() 72 | } 73 | ch := make(chan struct{}) 74 | close(ch) 75 | return ch 76 | } 77 | 78 | func (k *KProcess) signal(upgradeFunc, stopFunc func() error) { 79 | sig := make(chan os.Signal, 1) 80 | signal.Notify(sig, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM, os.Interrupt, os.Kill) 81 | for s := range sig { 82 | switch s { 83 | case syscall.SIGTERM, os.Interrupt, os.Kill: 84 | if stopFunc != nil { 85 | err := stopFunc() 86 | if err != nil { 87 | logging.Infof("KProcess exec stopFunc failed:%v\n", err) 88 | } 89 | logging.Infof("process %d stop...\n", k.pid) 90 | } 91 | return 92 | case syscall.SIGUSR1, syscall.SIGUSR2: 93 | if upgradeFunc != nil { 94 | err := upgradeFunc() 95 | if err != nil { 96 | logging.Infof("KProcess exec Upgrade failed:%v\n", err) 97 | } 98 | logging.Infof("process %d restart...\n", k.pid) 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /util/kprocess/tableflip_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package kprocess 4 | 5 | import ( 6 | "fmt" 7 | "gitee.com/kelvins-io/kelvins/internal/logging" 8 | "net" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/cloudflare/tableflip" 15 | ) 16 | 17 | type KProcess struct { 18 | pidFile string 19 | pid int 20 | processUp *tableflip.Upgrader 21 | } 22 | 23 | // This shows how to use the upgrader 24 | // with the graceful shutdown facilities of net/http. 25 | func (k *KProcess) Listen(network, addr, pidFile string) (ln net.Listener, err error) { 26 | k.pid = os.Getpid() 27 | logging.Infof(fmt.Sprintf("exec process pid %d \n", k.pid)) 28 | 29 | k.processUp, err = tableflip.New(tableflip.Options{ 30 | UpgradeTimeout: 5 * time.Second, 31 | PIDFile: pidFile, 32 | }) 33 | if err != nil { 34 | return nil, err 35 | } 36 | k.pidFile = pidFile 37 | 38 | go k.signal(k.upgrade, k.stop) 39 | 40 | // Listen must be called before Ready 41 | if network != "" && addr != "" { 42 | ln, err = k.processUp.Listen(network, addr) 43 | if err != nil { 44 | return nil, err 45 | } 46 | } 47 | if err := k.processUp.Ready(); err != nil { 48 | return nil, err 49 | } 50 | 51 | return ln, nil 52 | } 53 | 54 | func (k *KProcess) stop() error { 55 | if k.processUp != nil { 56 | k.processUp.Stop() 57 | return os.Remove(k.pidFile) 58 | } 59 | return nil 60 | } 61 | 62 | func (k *KProcess) upgrade() error { 63 | if k.processUp != nil { 64 | return k.processUp.Upgrade() 65 | } 66 | return nil 67 | } 68 | 69 | func (k *KProcess) Exit() <-chan struct{} { 70 | if k.processUp != nil { 71 | return k.processUp.Exit() 72 | } 73 | ch := make(chan struct{}) 74 | close(ch) 75 | return ch 76 | } 77 | 78 | func (k *KProcess) signal(upgradeFunc, stopFunc func() error) { 79 | sig := make(chan os.Signal, 1) 80 | signal.Notify(sig, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM, os.Interrupt, os.Kill) 81 | for s := range sig { 82 | switch s { 83 | case syscall.SIGTERM, os.Interrupt, os.Kill: 84 | if stopFunc != nil { 85 | err := stopFunc() 86 | if err != nil { 87 | logging.Infof("KProcess exec stopFunc failed:%v\n", err) 88 | } 89 | logging.Infof("process %d stop...\n", k.pid) 90 | } 91 | return 92 | case syscall.SIGUSR1, syscall.SIGUSR2: 93 | if upgradeFunc != nil { 94 | err := upgradeFunc() 95 | if err != nil { 96 | logging.Infof("KProcess exec Upgrade failed:%v\n", err) 97 | } 98 | logging.Infof("process %d restart...\n", k.pid) 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /util/kprocess/tableflip_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package kprocess 4 | 5 | import ( 6 | "fmt" 7 | "gitee.com/kelvins-io/kelvins/internal/logging" 8 | "net" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | ) 13 | 14 | type KProcess struct { 15 | pidFile string 16 | pid int 17 | ch chan struct{} 18 | } 19 | 20 | // This shows how to use the upgrader 21 | // with the graceful shutdown facilities of net/http. 22 | func (k *KProcess) Listen(network, addr, pidFile string) (ln net.Listener, err error) { 23 | if k.ch == nil { 24 | k.ch = make(chan struct{}) 25 | } 26 | k.pid = os.Getpid() 27 | logging.Info(fmt.Sprintf("exec process pid %d \n", k.pid)) 28 | logging.Info("warning windows only support process shutdown ") 29 | 30 | go k.signal(k.stop) 31 | 32 | if network != "" && addr != "" { 33 | return net.Listen(network, addr) 34 | } 35 | return nil, nil 36 | } 37 | 38 | func (k *KProcess) stop() error { 39 | close(k.ch) 40 | return nil 41 | } 42 | 43 | func (k *KProcess) upgrade() error { 44 | return nil 45 | } 46 | 47 | func (k *KProcess) Exit() <-chan struct{} { 48 | return k.ch 49 | } 50 | 51 | func (k *KProcess) signal(stopFunc func() error) { 52 | sig := make(chan os.Signal, 1) 53 | signal.Notify(sig, syscall.SIGTERM) 54 | for s := range sig { 55 | switch s { 56 | case syscall.SIGTERM: 57 | if stopFunc != nil { 58 | err := stopFunc() 59 | if err != nil { 60 | logging.Infof("KProcess exec stopFunc failed:%v\n", err) 61 | } 62 | logging.Infof("process %d stop...\n", k.pid) 63 | } 64 | return 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /util/middleware/rate_limit.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins" 5 | "sync/atomic" 6 | ) 7 | 8 | func NewKelvinsRateLimit(maxConcurrent int) Limiter { 9 | limiter := &kelvinsRateLimit{} 10 | if maxConcurrent > 0 { 11 | limiter.maxConcurrent = maxConcurrent 12 | limiter.tickets = make(chan struct{}, maxConcurrent+1) 13 | } 14 | return limiter 15 | } 16 | 17 | type kelvinsRateLimit struct { 18 | maxConcurrent int 19 | tickets chan struct{} 20 | ticketsState int32 21 | } 22 | 23 | func (r *kelvinsRateLimit) Limit() bool { 24 | // no limit 25 | if r.maxConcurrent == 0 { 26 | return false 27 | } 28 | // take ticket 29 | take := r.takeTicket() 30 | if take { 31 | return false 32 | } 33 | 34 | return true 35 | } 36 | 37 | func (r *kelvinsRateLimit) takeTicket() bool { 38 | if r.maxConcurrent == 0 { 39 | return true 40 | } 41 | if r.tickets == nil { 42 | return true 43 | } else { 44 | if atomic.LoadInt32(&r.ticketsState) == 1 { 45 | return false 46 | } 47 | } 48 | 49 | select { 50 | case r.tickets <- struct{}{}: 51 | return true 52 | case <-kelvins.AppCloseCh: 53 | atomic.StoreInt32(&r.ticketsState, 1) 54 | close(r.tickets) 55 | return false 56 | default: 57 | return false 58 | } 59 | } 60 | 61 | func (r *kelvinsRateLimit) ReturnTicket() { 62 | if r.maxConcurrent == 0 { 63 | return 64 | } 65 | if r.tickets == nil { 66 | return 67 | } 68 | select { 69 | case <-r.tickets: 70 | default: 71 | } 72 | } 73 | 74 | type Limiter interface { 75 | Limit() bool 76 | ReturnTicket() 77 | } 78 | -------------------------------------------------------------------------------- /util/middleware/rpc_auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "gitee.com/kelvins-io/common/json" 6 | "gitee.com/kelvins-io/common/log" 7 | "gitee.com/kelvins-io/kelvins/config/setting" 8 | "gitee.com/kelvins-io/kelvins/util/rpc_helper" 9 | grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/auth" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | "time" 14 | ) 15 | 16 | func GetRPCAuthDialOptions(conf *setting.RPCAuthSettingS) (opts []grpc.DialOption) { 17 | if conf != nil { 18 | if conf.Token != "" { 19 | opts = append(opts, grpc.WithPerRPCCredentials(RPCPerCredentials(conf.Token))) 20 | } 21 | if !conf.TransportSecurity { 22 | opts = append(opts, grpc.WithInsecure()) 23 | } 24 | } 25 | return 26 | } 27 | 28 | type RPCPerAuthInterceptor struct { 29 | errLogger log.LoggerContextIface 30 | tokenValidityDuration time.Duration 31 | } 32 | 33 | func NewRPCPerAuthInterceptor(errLogger log.LoggerContextIface) *RPCPerAuthInterceptor { 34 | return &RPCPerAuthInterceptor{errLogger: errLogger} 35 | } 36 | 37 | func (i *RPCPerAuthInterceptor) StreamServerInterceptor(conf *setting.RPCAuthSettingS) grpc.StreamServerInterceptor { 38 | if conf.ExpireSecond > 0 { 39 | i.tokenValidityDuration = time.Duration(conf.ExpireSecond) * time.Second 40 | } 41 | return grpcAuth.StreamServerInterceptor(i.checkFunc(conf)) 42 | } 43 | 44 | func (i *RPCPerAuthInterceptor) UnaryServerInterceptor(conf *setting.RPCAuthSettingS) grpc.UnaryServerInterceptor { 45 | if conf.ExpireSecond > 0 { 46 | i.tokenValidityDuration = time.Duration(conf.ExpireSecond) * time.Second 47 | } 48 | return grpcAuth.UnaryServerInterceptor(i.checkFunc(conf)) 49 | } 50 | 51 | func (i *RPCPerAuthInterceptor) checkFunc(conf *setting.RPCAuthSettingS) func(ctx context.Context) (context.Context, error) { 52 | return func(ctx context.Context) (context.Context, error) { 53 | if conf == nil || len(conf.Token) == 0 { 54 | return ctx, nil 55 | } 56 | 57 | authInfo, err := checkToken(ctx, conf.Token, time.Now(), i.tokenValidityDuration) 58 | if err != nil { 59 | if i.errLogger != nil { 60 | requestMeta := rpc_helper.GetRequestMetadata(ctx) 61 | i.errLogger.Errorf(ctx, "AuthInterceptor requestMeta: %v, checkFunc: %v err: %v", json.MarshalToStringNoError(requestMeta), authInfo, err) 62 | } 63 | } 64 | 65 | switch status.Code(err) { 66 | case codes.OK: 67 | case codes.Unauthenticated: 68 | case codes.PermissionDenied: 69 | default: 70 | } 71 | if conf.TransportSecurity { 72 | err = nil 73 | } 74 | 75 | return ctx, err 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /util/middleware/rpc_ratelimit.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "gitee.com/kelvins-io/common/json" 6 | "gitee.com/kelvins-io/kelvins/util/rpc_helper" 7 | "google.golang.org/grpc" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | type RPCRateLimitInterceptor struct { 13 | limiter Limiter 14 | } 15 | 16 | func NewRPCRateLimitInterceptor(maxConcurrent int) *RPCRateLimitInterceptor { 17 | return &RPCRateLimitInterceptor{ 18 | limiter: NewKelvinsRateLimit(maxConcurrent), 19 | } 20 | } 21 | 22 | func (r *RPCRateLimitInterceptor) StreamServerInterceptor() grpc.StreamServerInterceptor { 23 | return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 24 | if r.limiter.Limit() { 25 | requestMeta := rpc_helper.GetRequestMetadata(stream.Context()) 26 | return status.Errorf(codes.ResourceExhausted, "%s requestMeta:%v is rejected by grpc_ratelimit middleware, please retry later.", info.FullMethod, json.MarshalToStringNoError(requestMeta)) 27 | } 28 | defer func() { 29 | r.limiter.ReturnTicket() 30 | }() 31 | return handler(srv, stream) 32 | } 33 | } 34 | 35 | func (r *RPCRateLimitInterceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor { 36 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 37 | if r.limiter.Limit() { 38 | requestMeta := rpc_helper.GetRequestMetadata(ctx) 39 | return nil, status.Errorf(codes.ResourceExhausted, "%s requestMeta:%v is rejected by grpc_ratelimit middleware, please retry later.", info.FullMethod, json.MarshalToStringNoError(requestMeta)) 40 | } 41 | defer func() { 42 | r.limiter.ReturnTicket() 43 | }() 44 | return handler(ctx, req) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /util/middleware/rpc_token.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "fmt" 9 | grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/auth" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/credentials" 12 | "google.golang.org/grpc/status" 13 | "strconv" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | var ( 19 | tokenValidityDurationDefault = 30 * time.Second 20 | errUnauthenticated = status.Errorf(codes.Unauthenticated, "authentication required") 21 | errDenied = status.Errorf(codes.PermissionDenied, "permission denied") 22 | ) 23 | 24 | func RPCPerCredentials(sharedSecret string) credentials.PerRPCCredentials { 25 | return &rpcPerAuthCredentials{sharedSecret: sharedSecret} 26 | } 27 | 28 | // GetRPCPerAuthHeader 根据私钥获取RPC 接入auth header 29 | func GetRPCPerAuthHeader(secret string) (map[string]string, error) { 30 | x := rpcPerAuthCredentials{ 31 | sharedSecret: secret, 32 | } 33 | return x.GetRequestMetadata(context.Background()) 34 | } 35 | 36 | type rpcPerAuthCredentials struct { 37 | sharedSecret string 38 | } 39 | 40 | func (*rpcPerAuthCredentials) RequireTransportSecurity() bool { return false } 41 | 42 | func (rc *rpcPerAuthCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 43 | message := strconv.FormatInt(time.Now().Unix(), 10) 44 | signature := hmacSign([]byte(rc.sharedSecret), message) 45 | 46 | return map[string]string{ 47 | "authorization": "Bearer " + fmt.Sprintf("v1.%x.%s", signature, message), 48 | }, nil 49 | } 50 | 51 | func hmacSign(secret []byte, message string) []byte { 52 | mac := hmac.New(sha256.New, secret) 53 | // hash.Hash never returns an error. 54 | _, _ = mac.Write([]byte(message)) 55 | 56 | return mac.Sum(nil) 57 | } 58 | 59 | func hmacInfoValid(message string, signedMessage, secret []byte, targetTime time.Time, tokenValidity time.Duration) bool { 60 | expectedHMAC := hmacSign(secret, message) 61 | if !hmac.Equal(signedMessage, expectedHMAC) { 62 | return false 63 | } 64 | 65 | timestamp, err := strconv.ParseInt(message, 10, 64) 66 | if err != nil { 67 | return false 68 | } 69 | 70 | issuedAt := time.Unix(timestamp, 0) 71 | lowerBound := targetTime.Add(-tokenValidity) 72 | upperBound := targetTime.Add(tokenValidity) 73 | 74 | if issuedAt.Before(lowerBound) { 75 | return false 76 | } 77 | 78 | if issuedAt.After(upperBound) { 79 | return false 80 | } 81 | 82 | return true 83 | } 84 | 85 | type AuthInfo struct { 86 | Version string 87 | SignedMessage []byte 88 | Message string 89 | } 90 | 91 | func checkToken(ctx context.Context, secret string, targetTime time.Time, tokenValidityDuration time.Duration) (*AuthInfo, error) { 92 | if len(secret) == 0 { 93 | panic("checkToken: secret may not be empty") 94 | } 95 | if tokenValidityDuration < 0 { 96 | tokenValidityDuration = tokenValidityDurationDefault 97 | } 98 | 99 | authInfo, err := extractAuthInfo(ctx) 100 | if err != nil { 101 | return nil, errUnauthenticated 102 | } 103 | 104 | if authInfo.Version == "v1" { 105 | if hmacInfoValid(authInfo.Message, authInfo.SignedMessage, []byte(secret), targetTime, tokenValidityDuration) { 106 | return authInfo, nil 107 | } 108 | } 109 | 110 | return nil, errDenied 111 | } 112 | 113 | func extractAuthInfo(ctx context.Context) (*AuthInfo, error) { 114 | token, err := grpcAuth.AuthFromMD(ctx, "bearer") 115 | 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | split := strings.SplitN(token, ".", 3) 121 | 122 | if len(split) != 3 { 123 | return nil, fmt.Errorf("invalid token format") 124 | } 125 | 126 | version, sig, msg := split[0], split[1], split[2] 127 | decodedSig, err := hex.DecodeString(sig) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return &AuthInfo{Version: version, SignedMessage: decodedSig, Message: msg}, nil 133 | } 134 | -------------------------------------------------------------------------------- /util/middleware/rpc_token_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestRpcCredentials_GetRequestMetadata(t *testing.T) { 9 | x := rpcPerAuthCredentials{ 10 | sharedSecret: "c9VW6ForlmzdeDkZE2i8", 11 | } 12 | m, err := x.GetRequestMetadata(context.Background()) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | t.Log(m) 17 | } 18 | -------------------------------------------------------------------------------- /util/mysql_callback/time_callback.go: -------------------------------------------------------------------------------- 1 | package mysql_callback 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/jinzhu/gorm" 8 | ) 9 | 10 | // UpdateTimeStampForCreateCallback sets `CreateTime`, `ModifyTime` when creating. 11 | func UpdateTimeStampForCreateCallback(scope *gorm.Scope) { 12 | if !scope.HasError() { 13 | now := time.Now().Unix() 14 | 15 | if createTimeField, ok := scope.FieldByName("CreateTime"); ok { 16 | if createTimeField.IsBlank { 17 | createTimeField.Set(now) 18 | } 19 | } 20 | 21 | if modifyTimeField, ok := scope.FieldByName("ModifyTime"); ok { 22 | if modifyTimeField.IsBlank { 23 | modifyTimeField.Set(now) 24 | } 25 | } 26 | } 27 | } 28 | 29 | // UpdateTimeStampForUpdateCallback sets `ModifyTime` when updating. 30 | func UpdateTimeStampForUpdateCallback(scope *gorm.Scope) { 31 | if _, ok := scope.Get("gorm:update_column"); !ok { 32 | now := time.Now().Unix() 33 | scope.SetColumn("ModifyTime", now) 34 | } 35 | } 36 | 37 | // DeleteCallback used to delete data from database or set DeletedTime to 38 | // current time and is_del = 1(when using with soft delete). 39 | func DeleteCallback(scope *gorm.Scope) { 40 | if !scope.HasError() { 41 | var extraOption string 42 | if str, ok := scope.Get("gorm:delete_option"); ok { 43 | extraOption = fmt.Sprint(str) 44 | } 45 | 46 | deletedTimeField, hasDeletedAtField := scope.FieldByName("DeletedTime") 47 | isDelField, hasIsDelField := scope.FieldByName("IsDel") 48 | 49 | if !scope.Search.Unscoped && hasDeletedAtField && hasIsDelField { 50 | now := time.Now().Unix() 51 | scope.Raw(fmt.Sprintf( 52 | "UPDATE %v SET %v=%v,%v=%v%v%v", 53 | scope.QuotedTableName(), 54 | scope.Quote(deletedTimeField.DBName), 55 | scope.AddToVars(now), 56 | scope.Quote(isDelField.DBName), 57 | scope.AddToVars(1), 58 | addExtraSpaceIfExist(scope.CombinedConditionSql()), 59 | addExtraSpaceIfExist(extraOption), 60 | )).Exec() 61 | } else { 62 | scope.Raw(fmt.Sprintf( 63 | "DELETE FROM %v%v%v", 64 | scope.QuotedTableName(), 65 | addExtraSpaceIfExist(scope.CombinedConditionSql()), 66 | addExtraSpaceIfExist(extraOption), 67 | )).Exec() 68 | } 69 | } 70 | } 71 | 72 | // addExtraSpaceIfExist used to add extra space if exists str. 73 | func addExtraSpaceIfExist(str string) string { 74 | if str != "" { 75 | return " " + str 76 | } 77 | return "" 78 | } 79 | -------------------------------------------------------------------------------- /util/mysql_model/mysql_model.go: -------------------------------------------------------------------------------- 1 | package mysql_model 2 | 3 | // ColumnCreateModifyDeleteTime is a basic model for mysql. 4 | type ColumnCreateModifyDeleteTime struct { 5 | ID int64 `gorm:"primary_key;AUTO_INCREMENT" json:"id" db:"id"` 6 | CreateTime int64 `json:"create_time" db:"create_time"` 7 | ModifyTime int64 `json:"modify_time" db:"modify_time"` 8 | DeletedTime int64 `json:"deleted_time" db:"deleted_time"` 9 | IsDel int32 `json:"is_del" db:"is_del"` 10 | } 11 | -------------------------------------------------------------------------------- /util/queue_helper/publish.go: -------------------------------------------------------------------------------- 1 | package queue_helper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "gitee.com/kelvins-io/common/errcode" 7 | "gitee.com/kelvins-io/common/json" 8 | "gitee.com/kelvins-io/common/log" 9 | "gitee.com/kelvins-io/common/queue" 10 | "gitee.com/kelvins-io/kelvins/internal/logging" 11 | "github.com/RichardKnop/machinery/v1/tasks" 12 | ) 13 | 14 | type PublishService struct { 15 | logger log.LoggerContextIface 16 | server *queue.MachineryQueue 17 | tag *PushMsgTag 18 | } 19 | 20 | type PushMsgTag struct { 21 | DeliveryTag string // consume func name 22 | DeliveryErrTag string // consume err func name 23 | RetryCount int // default 3 cycle 24 | RetryTimeout int // default 10s 25 | } 26 | 27 | func NewPublishService(server *queue.MachineryQueue, tag *PushMsgTag, logger log.LoggerContextIface) (*PublishService, error) { 28 | if server == nil { 29 | return nil, fmt.Errorf("server nil !!! ") 30 | } 31 | if tag == nil { 32 | return nil, fmt.Errorf("tag nil !!! ") 33 | } 34 | if tag.DeliveryTag == "" { 35 | return nil, fmt.Errorf("tag.DeliveryTag empty !!! ") 36 | } 37 | if tag.DeliveryErrTag == "" { 38 | return nil, fmt.Errorf("tag.DeliveryErrTag empty !!! ") 39 | } 40 | if tag.RetryCount <= 0 { 41 | tag.RetryCount = 3 42 | } 43 | if tag.RetryTimeout <= 0 { 44 | tag.RetryTimeout = 10 45 | } 46 | return &PublishService{ 47 | server: server, 48 | tag: tag, 49 | logger: logger, 50 | }, nil 51 | } 52 | 53 | func (p *PublishService) PushMessage(ctx context.Context, args interface{}) (string, int) { 54 | 55 | taskSign, retCode := p.buildQueueData(ctx, args) 56 | if retCode != errcode.SUCCESS { 57 | return "", retCode 58 | } 59 | 60 | taskId, retCode := p.sendTaskToQueue(ctx, taskSign) 61 | if retCode != errcode.SUCCESS { 62 | return "", retCode 63 | } 64 | 65 | return taskId, errcode.SUCCESS 66 | } 67 | 68 | // 构建队列数据 69 | func (p *PublishService) buildQueueData(ctx context.Context, args interface{}) (*tasks.Signature, int) { 70 | 71 | sign := p.buildTaskSignature(args) 72 | 73 | errSign, err := tasks.NewSignature( 74 | p.tag.DeliveryErrTag, []tasks.Arg{ 75 | { 76 | Name: "data", 77 | Type: "string", 78 | Value: json.MarshalToStringNoError(args), 79 | }, 80 | }) 81 | 82 | if err != nil { 83 | if p.logger != nil { 84 | p.logger.Errorf(ctx, "queue_helper buildQueueData err: %v, taskSign: %v", err, json.MarshalToStringNoError(sign)) 85 | } else { 86 | logging.Errf("queue_helper buildQueueData err: %v, taskSign: %v\n", err, json.MarshalToStringNoError(sign)) 87 | } 88 | return nil, errcode.FAIL 89 | } 90 | 91 | errCallback := make([]*tasks.Signature, 0) 92 | errCallback = append(errCallback, errSign) 93 | sign.OnError = errCallback 94 | 95 | return sign, errcode.SUCCESS 96 | } 97 | 98 | // 构建任务签名 99 | func (p *PublishService) buildTaskSignature(args interface{}) *tasks.Signature { 100 | 101 | taskSignature := &tasks.Signature{ 102 | Name: p.tag.DeliveryTag, 103 | RetryCount: p.tag.RetryCount, 104 | RetryTimeout: p.tag.RetryTimeout, 105 | Args: []tasks.Arg{ 106 | { 107 | Name: "data", 108 | Type: "string", 109 | Value: json.MarshalToStringNoError(args), 110 | }, 111 | }, 112 | } 113 | 114 | return taskSignature 115 | } 116 | 117 | // 将任务发送到队列 118 | func (p *PublishService) sendTaskToQueue(ctx context.Context, taskSign *tasks.Signature) (string, int) { 119 | 120 | result, err := p.server.TaskServer.SendTaskWithContext(ctx, taskSign) 121 | if err != nil { 122 | if p.logger != nil { 123 | p.logger.Errorf(ctx, "queue_helper sendTaskToQueue err:%v, data:%v", err, json.MarshalToStringNoError(taskSign)) 124 | } else { 125 | logging.Errf("queue_helper sendTaskToQueue err:%v, data:%v\n", err, json.MarshalToStringNoError(taskSign)) 126 | } 127 | return "", errcode.FAIL 128 | } 129 | 130 | return result.Signature.UUID, errcode.SUCCESS 131 | } 132 | 133 | func (p *PublishService) GetTaskState(taskId string) (*tasks.TaskState, error) { 134 | 135 | return p.server.TaskServer.GetBackend().GetState(taskId) 136 | } 137 | -------------------------------------------------------------------------------- /util/rpc_helper/metadata.go: -------------------------------------------------------------------------------- 1 | package rpc_helper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "gitee.com/kelvins-io/kelvins" 7 | "gitee.com/kelvins-io/kelvins/internal/vars" 8 | "google.golang.org/grpc/metadata" 9 | ) 10 | 11 | func GetRequestId(ctx context.Context) (requestId string) { 12 | md, ok := metadata.FromIncomingContext(ctx) 13 | if ok { 14 | if t, ok := md[kelvins.RPCMetadataRequestId]; ok { 15 | for _, e := range t { 16 | if e != "" { 17 | requestId = e 18 | ok = true 19 | break 20 | } 21 | } 22 | } 23 | } 24 | return 25 | } 26 | 27 | type RequestMeta struct { 28 | RequestId string 29 | Version string 30 | } 31 | 32 | func GetRequestMetadata(ctx context.Context) *RequestMeta { 33 | return &RequestMeta{ 34 | RequestId: GetRequestId(ctx), 35 | Version: fmt.Sprintf("kelvins/rpc %v", vars.Version), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /util/startup/startup.go: -------------------------------------------------------------------------------- 1 | package startup 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "gitee.com/kelvins-io/kelvins/internal/logging" 7 | "io/ioutil" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | type startUpType string 13 | 14 | const ( 15 | startUpStart startUpType = "start" 16 | startUpReStart startUpType = "restart" 17 | startUpStop startUpType = "stop" 18 | ) 19 | 20 | var ( 21 | control = flag.String("s", string(startUpStart), "control cmd eg: start,stop,restart") 22 | ) 23 | 24 | func ParseCliCommand(pidFile string) (next bool, err error) { 25 | flag.Parse() 26 | cmd := startUpType(*control) 27 | switch cmd { 28 | case startUpStart: 29 | next = true 30 | errLook := lookupFile(pidFile) 31 | if errLook == nil { 32 | err = fmt.Errorf("process pid file already exist") 33 | } 34 | return 35 | case startUpReStart: 36 | case startUpStop: 37 | default: 38 | next = false 39 | logging.Info("unsupported command!!!") 40 | return 41 | } 42 | 43 | pid, err := parsePidFile(pidFile) 44 | if err != nil { 45 | return 46 | } 47 | 48 | return execProcessCmd(pid, cmd) 49 | } 50 | 51 | func parsePidFile(pidFile string) (pid int, err error) { 52 | _, err = os.Stat(pidFile) 53 | if err != nil { 54 | return 55 | } 56 | var f *os.File 57 | f, err = os.OpenFile(pidFile, os.O_RDWR, 0666) 58 | if err != nil { 59 | return 60 | } 61 | defer f.Close() 62 | content, err := ioutil.ReadAll(f) 63 | if err != nil { 64 | return 65 | } 66 | pid, err = strconv.Atoi(string(content)) 67 | return 68 | } 69 | 70 | func processControl(pid int, signal os.Signal) error { 71 | p, err := os.FindProcess(pid) 72 | if err != nil { 73 | return err 74 | } 75 | return p.Signal(signal) 76 | } 77 | 78 | func lookupFile(pidFile string) (err error) { 79 | _, err = os.Stat(pidFile) 80 | return 81 | } 82 | -------------------------------------------------------------------------------- /util/startup/startup_darwin.go: -------------------------------------------------------------------------------- 1 | package startup 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins/internal/logging" 5 | "syscall" 6 | ) 7 | 8 | func execProcessCmd(pid int, upType startUpType) (next bool, err error) { 9 | switch upType { 10 | case startUpReStart: 11 | logging.Infof("process %d restart...\n", pid) 12 | err = processControl(pid, syscall.SIGUSR1) 13 | logging.Infof("process %d restart over\n", pid) 14 | case startUpStop: 15 | logging.Infof("process %d stop...\n", pid) 16 | err = processControl(pid, syscall.SIGTERM) 17 | logging.Infof("process %d stop over\n", pid) 18 | default: 19 | next = true 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /util/startup/startup_linux.go: -------------------------------------------------------------------------------- 1 | package startup 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins/internal/logging" 5 | "syscall" 6 | ) 7 | 8 | func execProcessCmd(pid int, upType startUpType) (next bool, err error) { 9 | switch upType { 10 | case startUpReStart: 11 | logging.Infof("process %d restart...\n", pid) 12 | err = processControl(pid, syscall.SIGUSR1) 13 | logging.Infof("process %d restart over\n", pid) 14 | case startUpStop: 15 | logging.Infof("process %d stop...\n", pid) 16 | err = processControl(pid, syscall.SIGTERM) 17 | logging.Infof("process %d stop over\n", pid) 18 | default: 19 | next = true 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /util/startup/startup_windows.go: -------------------------------------------------------------------------------- 1 | package startup 2 | 3 | import ( 4 | "gitee.com/kelvins-io/kelvins/internal/logging" 5 | "runtime" 6 | "syscall" 7 | ) 8 | 9 | func execProcessCmd(pid int, upType startUpType) (next bool, err error) { 10 | switch upType { 11 | case startUpReStart: 12 | logging.Infof("process platform(%s) not support restart\n", runtime.GOOS) 13 | case startUpStop: 14 | logging.Infof("process %d stop...\n", pid) 15 | err = processControl(pid, syscall.SIGTERM) 16 | logging.Infof("process %d stop over\n", pid) 17 | default: 18 | next = true 19 | } 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /util/test_tool/ghz.go: -------------------------------------------------------------------------------- 1 | package test_tool 2 | 3 | import ( 4 | "fmt" 5 | "gitee.com/kelvins-io/kelvins/util/middleware" 6 | "github.com/bojand/ghz/printer" 7 | "github.com/bojand/ghz/runner" 8 | "google.golang.org/grpc" 9 | "io" 10 | "os" 11 | ) 12 | 13 | type ReportFormat string 14 | 15 | const ( 16 | ReportHTML ReportFormat = "html" 17 | ReportCSV ReportFormat = "csv" 18 | ReportSummary ReportFormat = "summary" 19 | ReportJSON ReportFormat = "json" 20 | ReportPretty ReportFormat = "pretty" 21 | ReportInfluxSummary ReportFormat = "influx-summary" 22 | ReportInfluxDetails ReportFormat = "influx-details" 23 | ) 24 | 25 | type GhzTestOption struct { 26 | Call string 27 | Host string 28 | Token string 29 | ReportFormat ReportFormat 30 | Out io.Writer 31 | Options []runner.Option 32 | } 33 | 34 | func ExecuteRPCGhzTest(opt *GhzTestOption) error { 35 | if opt == nil { 36 | return fmt.Errorf("ghz test opt nil") 37 | } 38 | if opt.ReportFormat == "" { 39 | opt.ReportFormat = ReportHTML 40 | } 41 | if opt.Out == nil { 42 | opt.Out = os.Stdout 43 | } 44 | var optsCall []grpc.CallOption 45 | var optsGhz []runner.Option 46 | if opt.Token != "" { 47 | optsCall = append(optsCall, grpc.PerRPCCredentials(middleware.RPCPerCredentials(opt.Token))) 48 | } 49 | optsGhz = append(optsGhz, runner.WithDefaultCallOptions(optsCall)) 50 | optsGhz = append(optsGhz, opt.Options...) 51 | report, err := runner.Run( 52 | opt.Call, 53 | opt.Host, 54 | optsGhz..., 55 | ) 56 | if err != nil { 57 | return err 58 | } 59 | reportPrinter := printer.ReportPrinter{ 60 | Out: opt.Out, 61 | Report: report, 62 | } 63 | return reportPrinter.Print(string(opt.ReportFormat)) 64 | } 65 | -------------------------------------------------------------------------------- /vars.go: -------------------------------------------------------------------------------- 1 | package kelvins 2 | 3 | import ( 4 | "gitee.com/kelvins-io/common/event" 5 | "gitee.com/kelvins-io/common/log" 6 | "gitee.com/kelvins-io/common/queue" 7 | "gitee.com/kelvins-io/g2cache" 8 | "gitee.com/kelvins-io/kelvins/config/setting" 9 | "gitee.com/kelvins-io/kelvins/util/goroutine" 10 | "github.com/gomodule/redigo/redis" 11 | "github.com/jinzhu/gorm" 12 | "github.com/qiniu/qmgo" 13 | "xorm.io/xorm" 14 | ) 15 | 16 | // this VARS user should only read 17 | 18 | // RedisConn is a global vars for redis connect,close by Framework exit May be nil 19 | var RedisConn *redis.Pool 20 | 21 | // GORM_DBEngine is a global vars for mysql connect,close by Framework exit May be nil 22 | var GORM_DBEngine *gorm.DB 23 | 24 | // XORM_DBEngine is a global vars for mysql connect,close by Framework exit May be nil 25 | var XORM_DBEngine xorm.EngineInterface 26 | 27 | // FrameworkLogger is a global var for Framework log 28 | var FrameworkLogger log.LoggerContextIface 29 | 30 | // ErrLogger is a global vars for application to log err msg. 31 | var ErrLogger log.LoggerContextIface 32 | 33 | // AccessLogger is a global vars for application to log access log 34 | var AccessLogger log.LoggerContextIface 35 | 36 | // BusinessLogger is a global vars for application to log business log 37 | var BusinessLogger log.LoggerContextIface 38 | 39 | // LoggerSetting is maps config section "kelvins-logger" May be nil 40 | var LoggerSetting *setting.LoggerSettingS 41 | 42 | // ServerSetting is maps config section "kelvins-server" May be nil 43 | var ServerSetting *setting.ServerSettingS 44 | 45 | // HttpServerSetting is maps config section "kelvins-http-server" May be nil 46 | var HttpServerSetting *setting.HttpServerSettingS 47 | 48 | // HttpRateLimitSetting is maps config section "kelvins-http-rate-limit" may be nil 49 | var HttpRateLimitSetting *setting.HttpRateLimitSettingS 50 | 51 | // JwtSetting is maps config section "kelvins-jwt" may be nil 52 | var JwtSetting *setting.JwtSettingS 53 | 54 | // RPCServerParamsSetting is maps config section "kelvins-rpc-server" May be nil 55 | var RPCServerParamsSetting *setting.RPCServerParamsS 56 | 57 | // RPCAuthSetting is maps config section "kelvins-rpc-auth" May be nil 58 | var RPCAuthSetting *setting.RPCAuthSettingS 59 | 60 | // RPCRateLimitSetting is maps config section "kelvins-rpc-rate-limit" may be nil 61 | var RPCRateLimitSetting *setting.RPCRateLimitSettingS 62 | 63 | // RPCServerKeepaliveParamsSetting is maps config section "kelvins-rpc-server-kp" May be nil 64 | var RPCServerKeepaliveParamsSetting *setting.RPCServerKeepaliveParamsS 65 | 66 | // RPCServerKeepaliveEnforcementPolicySetting is maps config section "kelvins-rpc-server-kep" May be nil 67 | var RPCServerKeepaliveEnforcementPolicySetting *setting.RPCServerKeepaliveEnforcementPolicyS 68 | 69 | // RPCClientKeepaliveParamsSetting is maps config section "kelvins-rpc-client-kp" May be nil 70 | var RPCClientKeepaliveParamsSetting *setting.RPCClientKeepaliveParamsS 71 | 72 | // RPCTransportBufferSetting is maps config section "kelvins-rpc-transport-buffer" May be nil 73 | var RPCTransportBufferSetting *setting.RPCTransportBufferS 74 | 75 | // MysqlSetting is maps config section "kelvins-mysql" May be nil 76 | var MysqlSetting *setting.MysqlSettingS 77 | 78 | // RedisSetting is maps config section "kelvins-redis" May be nil 79 | var RedisSetting *setting.RedisSettingS 80 | 81 | // G2CacheSetting is maps config section "kelvins-g2cache" May be nil 82 | var G2CacheSetting *setting.G2CacheSettingS 83 | 84 | // G2CacheSetting is maps config section "kelvins-g2cache" May be nil 85 | var G2CacheEngine *g2cache.G2Cache 86 | 87 | // QueueRedisSetting is maps config section "kelvins-queue-redis" May be nil 88 | var QueueRedisSetting *setting.QueueRedisSettingS 89 | 90 | // QueueServerSetting is maps config section "kelvins-queue-server" May be nil 91 | var QueueServerSetting *setting.QueueServerSettingS 92 | 93 | // QueueAliAMQPSetting is maps config section "kelvins-queue-amqp" May be nil 94 | var QueueAliAMQPSetting *setting.QueueAliAMQPSettingS 95 | 96 | // AliRocketMQSetting is maps config section "kelvins-queue-ali-rocketmq" May be nil 97 | var AliRocketMQSetting *setting.AliRocketMQSettingS 98 | 99 | // QueueAMQPSetting is maps config section "kelvins-queue-ali-amqp" May be nil 100 | var QueueAMQPSetting *setting.QueueAMQPSettingS 101 | 102 | // QueueServerRedis is maps config section "kelvins-queue-redis" May be nil 103 | var QueueServerRedis *queue.MachineryQueue 104 | 105 | // QueueServerAMQP is maps config section "kelvins-queue-amqp" May be nil 106 | var QueueServerAMQP *queue.MachineryQueue 107 | 108 | // QueueServerAliAMQP is maps config section "kelvins-queue-ali-amqp" May be nil 109 | var QueueServerAliAMQP *queue.MachineryQueue 110 | 111 | // EventServerAliRocketMQ is maps config section "kelvins-queue-ali-rocketmq" May be nil 112 | var EventServerAliRocketMQ *event.EventServer 113 | 114 | // MongoDBSetting is maps config section "kelvins-mongodb" May be nil 115 | var MongoDBSetting *setting.MongoDBSettingS 116 | 117 | // MongoDBClient is qmgo-client for mongodb,close by Framework exit May be nil 118 | var MongoDBClient *qmgo.QmgoClient 119 | 120 | // GPoolSetting is maps config section "kelvins-gpool" May be nil 121 | var GPoolSetting *setting.GPoolSettingS 122 | 123 | // GPool is goroutine pool,close by Framework exit May be nil 124 | var GPool *goroutine.Pool 125 | 126 | // PIDFile is process pid,manage by Framework user only read 127 | var PIDFile string 128 | 129 | // AppName is app name 130 | var AppName string 131 | 132 | // AppCloseCh is app shutdown notice,close by Framework exit; user only read 133 | var AppCloseCh <-chan struct{} 134 | 135 | // GRPCAppInstance is *GRPCApplication instance May be nil 136 | var GRPCAppInstance *GRPCApplication 137 | 138 | // CronAppInstance is *CronApplication instance May be nil 139 | var CronAppInstance *CronApplication 140 | 141 | // QueueAppInstance is **QueueApplication instance May be nil 142 | var QueueAppInstance *QueueApplication 143 | 144 | // HttpAppInstance is *HTTPApplication instance May be nil 145 | var HttpAppInstance *HTTPApplication 146 | -------------------------------------------------------------------------------- /交流群.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvins-io/kelvins/09d3727bdaf012976d068120a038da1f386aa15c/交流群.JPG --------------------------------------------------------------------------------