yangmao 2 жил өмнө
commit
ec2fe958db
100 өөрчлөгдсөн 9426 нэмэгдсэн , 0 устгасан
  1. 81 0
      Makefile
  2. 322 0
      README.md
  3. 35 0
      go.mod
  4. 590 0
      go.sum
  5. 155 0
      pkg/boot/boot.go
  6. 246 0
      pkg/boot/boot_test.go
  7. 39 0
      pkg/boot/resourcetype_string.go
  8. 73 0
      pkg/boot/type.go
  9. 162 0
      pkg/cache/localcache/api.go
  10. 24 0
      pkg/cache/localcache/local_cache_test.go
  11. 37 0
      pkg/cache/localcache/localcache.go
  12. 277 0
      pkg/cache/redis/redis.go
  13. 145 0
      pkg/cache/redis/redis_cnn.go
  14. 94 0
      pkg/cache/redis/redis_test.go
  15. 12 0
      pkg/cache/redis/rediskey.go
  16. 43 0
      pkg/cerror/cerror.go
  17. 96 0
      pkg/common/array.go
  18. 46 0
      pkg/common/array_test.go
  19. 176 0
      pkg/common/excel.go
  20. 85 0
      pkg/common/excel_test.go
  21. 162 0
      pkg/common/goroutine.go
  22. 66 0
      pkg/common/goroutine_test.go
  23. 95 0
      pkg/common/gpool.go
  24. 21 0
      pkg/common/gpool_test.go
  25. 45 0
      pkg/common/json.go
  26. 28 0
      pkg/common/json_test.go
  27. 23 0
      pkg/common/num.go
  28. 8 0
      pkg/common/os.go
  29. 53 0
      pkg/common/page.go
  30. 12 0
      pkg/common/page_test.go
  31. 54 0
      pkg/common/response.go
  32. 13 0
      pkg/common/string.go
  33. 20 0
      pkg/common/string_test.go
  34. 31 0
      pkg/common/sync.go
  35. 82 0
      pkg/common/time.go
  36. 90 0
      pkg/common/time_struct.go
  37. 59 0
      pkg/common/time_struct_test.go
  38. 52 0
      pkg/common/time_test.go
  39. 34 0
      pkg/conf/common.go
  40. 169 0
      pkg/conf/config.go
  41. 555 0
      pkg/conf/goconfig/conf.go
  42. 294 0
      pkg/conf/goconfig/read.go
  43. 117 0
      pkg/conf/goconfig/write.go
  44. 15 0
      pkg/conf/init_config.go
  45. 101 0
      pkg/conf/nacos.go
  46. 40 0
      pkg/conf/nacos_test.go
  47. 42 0
      pkg/conf/new_config_func.go
  48. 14 0
      pkg/conf/new_config_func_test.go
  49. 56 0
      pkg/conf/old_config_func.go
  50. 643 0
      pkg/conf/remote/apollo/agollo/agollo.go
  51. 247 0
      pkg/conf/remote/apollo/agollo/apollo_client.go
  52. 40 0
      pkg/conf/remote/apollo/agollo/log.go
  53. 146 0
      pkg/conf/remote/apollo/agollo/options.go
  54. 84 0
      pkg/conf/remote/apollo/agollo/strings.go
  55. 20 0
      pkg/conf/remote/apollo/agollo/util.go
  56. 247 0
      pkg/conf/remote/apollo/apollo.go
  57. 175 0
      pkg/database/common_db/common.go
  58. 259 0
      pkg/database/common_db/conn.go
  59. 115 0
      pkg/database/common_db/conn_test.go
  60. 86 0
      pkg/database/common_db/logger.go
  61. 30 0
      pkg/database/common_db/test.sql
  62. 50 0
      pkg/database/db/conn.go
  63. 136 0
      pkg/database/elastic/demo.go
  64. 208 0
      pkg/database/elastic/elastic.go
  65. 49 0
      pkg/database/multi_db/cnn.go
  66. 1 0
      pkg/database/multi_db/conn.go
  67. 11 0
      pkg/internal/application/context.go
  68. 18 0
      pkg/internal/application/signal.go
  69. 13 0
      pkg/internal/application/signal_test.go
  70. 13 0
      pkg/internal/constant/constant.go
  71. 35 0
      pkg/internal/file/rotatefile.go
  72. 25 0
      pkg/internal/json/jsoniter.go
  73. 206 0
      pkg/internal/nacos/config.go
  74. 35 0
      pkg/internal/nacos/config_test.go
  75. 25 0
      pkg/internal/nacos/namingtype_string.go
  76. 203 0
      pkg/internal/prettyjson/json.go
  77. 174 0
      pkg/internal/properties/common.go
  78. 130 0
      pkg/internal/properties/common_test.go
  79. 3 0
      pkg/internal/util/constant.go
  80. 24 0
      pkg/internal/util/dir.go
  81. 13 0
      pkg/internal/util/errors.go
  82. 8 0
      pkg/internal/util/errors_test.go
  83. 31 0
      pkg/internal/util/goroutine.go
  84. 46 0
      pkg/internal/util/goroutine_test.go
  85. 60 0
      pkg/internal/util/host.go
  86. 11 0
      pkg/internal/util/host_test.go
  87. 8 0
      pkg/internal/util/if.go
  88. 34 0
      pkg/internal/util/if_test.go
  89. 101 0
      pkg/internal/util/kv_context.go
  90. 28 0
      pkg/internal/util/kv_context_test.go
  91. 104 0
      pkg/internal/util/log.go
  92. 15 0
      pkg/internal/util/log_test.go
  93. 27 0
      pkg/internal/util/map.go
  94. 129 0
      pkg/internal/util/num.go
  95. 27 0
      pkg/internal/util/num_test.go
  96. 20 0
      pkg/internal/util/os.go
  97. 52 0
      pkg/internal/util/response.go
  98. 114 0
      pkg/internal/util/string.go
  99. 40 0
      pkg/internal/util/string_test.go
  100. 18 0
      pkg/internal/util/time.go

+ 81 - 0
Makefile

@@ -0,0 +1,81 @@
+# #######################################################
+# Function :Makefile for go-common                      #
+# Platform :All Linux Based Platform                    #
+# Version  :1.0                                         #
+# Date     :2020-12-23                                  #
+# Usage    :make			                	        #
+# #######################################################
+
+
+# 项目路径
+PROJECT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+# go项目相关
+GO_FILE := $(shell find . -name '*.go' | grep -v vendor/ | grep -v _test.go)
+PKG_LIST := $(shell go list gitea.ckfah.com/cjjy/gocommon/... | grep -v vendor | grep -v cmd)
+
+ifndef test_pkg
+	test_pkg := \
+	gitea.ckfah.com/cjjy/gocommon/pkg/database/common_db \
+	gitea.ckfah.com/cjjy/gocommon/pkg/database/db \
+	gitea.ckfah.com/cjjy/gocommon/pkg/database/multi_db \
+	gitea.ckfah.com/cjjy/gocommon/pkg/internal/util
+endif
+
+# Go的全局的环境变量。GOFLAGS必须清空,防止其他参数干扰
+GOFLAGS :=
+GO111MODULE := on
+GOPROXY := https://goproxy.cn,direct
+GOPRIVATE := gitea.ckfah.com*
+export GO111MODULE
+export GOPROXY
+export GOPRIVATE
+export GOFLAGS
+# 项目配置文件位置
+export ROOT_PATH := $(PROJECT_DIR)
+
+# 防止本地文件有重名的问题
+.PHONY : all fmt tool gofmt goimports golint govet clean get test testall benchmark help
+
+# make默认启动
+all: testall benchmark  ## 测试全部test和benchmark
+
+fmt: gofmt goimports ## 格式化代码
+
+tool: fmt ## 构建go-app cli工具
+	go build -v -ldflags "-s -w" -o bin/go-app tool/main.go
+
+gofmt:
+	@for item in $(GO_FILE); do gofmt -d -w $$item; done
+
+goimports:
+	@if [ ! -d ${HOME}/go/bin ]; then mkdir -p ${HOME}/go/bin; fi
+	@if [ ! -e ${HOME}/go/bin/goimports ]; then curl -o ${HOME}/go/bin/goimports https://anthony-wangpan.oss-accelerate.aliyuncs.com/software/2020/12-29/788bd0e30957478488d4159859d29a0e && chmod 0744 ${HOME}/go/bin/goimports; fi
+	@for item in $(GO_FILE); do ${HOME}/go/bin/goimports -d -w $$item; done
+
+govet: ## 静态代码检测工具
+	@go vet $(PKG_LIST)
+
+golint: ## 检测代码规范工具
+	@if [ ! -d ${HOME}/go/bin ]; then mkdir -p ${HOME}/go/bin; fi
+	@if [ ! -e ${HOME}/go/bin/golint ]; then curl -o ${HOME}/go/bin/golint https://anthony-wangpan.oss-accelerate.aliyuncs.com/software/2020/12-30/6fda119141b84c77b0924e9d140704d0 && chmod 0744 ${HOME}/go/bin/golint; fi
+	@${HOME}/go/bin/golint $(PKG_LIST)
+
+clean: ## 清楚无用文件
+	$(RM) -r coverage.txt
+
+get: ## go get 包, eg: make get import=github.com/apache/rocketmq-client-go/v2@v2.0.0
+	go get -u -v $(import)
+
+test: fmt clean ## 单元测试, eg: make test test_func=TestInArrayUint64 test_pkg=./pkg/common
+	go test -v -race -short -cover -coverprofile=coverage.txt -covermode=atomic -run=$(test_func) $(test_pkg)
+	go tool cover -html=coverage.txt
+
+benchmark: fmt clean ## 性能测试, eg: make benchmark test_func=BenchmarkLoadDefaultOp test_pkg=./pkg/net/httpclent
+	go test -v -run=none -bench=$(test_func) -benchmem $(test_pkg)
+
+testall: fmt clean ## 测试整个项目
+	go test -v -race -short -cover -coverprofile=coverage.txt -covermode=atomic $(test_pkg)
+	go tool cover -html=coverage.txt
+
+help: ## 帮助
+	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf " \033[36m%-20s\033[0m  %s\n", $$1, $$2}' $(MAKEFILE_LIST)

+ 322 - 0
README.md

@@ -0,0 +1,322 @@
+# gocommon
+
+> Go 的开发框架组件,目前集成logger,http-server,http-client,db,redis,nacos,kafka,skywaling,qconf 等框架
+
+## 使用/开发须知:
+
+1、所有的配置项的key-value都是字符串格式!使用properties 格式
+
+2、下列是各个配置属性,和例子
+
+3、不允许在master分支上直接变更开发,需要merge
+
+4、所有的API都不相互依赖,抽象出来
+
+5、全部都需要走单元测试
+
+6、提交完代码走一遍`make` 测试代码
+
+## 全局配置
+
+| key                      | default_value | desc                                       | eg          | 是否必须 |
+| ------------------------ | ------------- | ------------------------------------------ | ----------- | :------: |
+| application.env          |               | 当前的环境配置,分为:`debug|test|release` | debug       |    是    |
+| application.project_name |               | 当前的应用名称                             | go-template |    是    |
+| application.port         |               | 当前应用的端口号                           | 13058       |    是    |
+
+远程配置项:
+
+```properties
+# app
+application.env=debug
+application.project_name = go-template
+application.port = 13088
+```
+
+本地`config/env.ini`配置必须包含项,如果使用nacos的需要配置一下信息,需要根据nacos具体配置信息走
+
+```ini
+[conf]
+# file|apollo|nacos,file直接读取文件
+driver = nacos
+
+[nacos-config]
+host = 10.100.101.20:8848,10.100.103.130:8848,10.100.99.14:8848
+log_path = /data/log/go-template
+namespace_id = ed35034b-634e-4ed5-9266-d7366d389351
+user_name = nacos
+password = nacos
+data_id = go-template
+```
+
+Apollo配置中心的话:
+
+`config/env.ini`
+
+```properties
+[application]
+# env : (debug|test|release) test测试环境
+env = debug
+
+[conf]
+# file|apollo|nacos,file直接读取文件
+driver = apollo
+# apollo 定时拉取的刷新时间,单位s
+remote_refresh_interval = 30
+```
+
+## MySQL
+
+| key                       | default_value | desc                                               | 是否必须 |
+| ------------------------- | ------------- | -------------------------------------------------- | -------- |
+| mysql.dbname              |               | 主库的数据库名称                                   | 是       |
+| mysql.host                |               |                                                    | 是       |
+| mysql.port                |               |                                                    | 是       |
+| mysql.user                |               |                                                    | 是       |
+| mysql.password            |               |                                                    | 是       |
+| mysql.charset             | utf8          |                                                    | 否       |
+| mysql.slave_dbname        |               | 从库的数据库名称                                   | 是       |
+| mysql.slave_host          |               |                                                    | 是       |
+| mysql.slave_port          |               |                                                    | 是       |
+| mysql.slave_host          |               |                                                    | 是       |
+| mysql.slave_user          |               |                                                    | 是       |
+| mysql.slave_password      |               |                                                    | 是       |
+| mysql.slave_charset       | utf8          |                                                    | 否       |
+| mysql.read_timeout        | 5             | 读超时,单位s                                      | 否       |
+| mysql.write_timeout       | 5             | 写超时,单位s                                      | 否       |
+| mysql.timeout             | 10            | 最大超时时间,单位s                                | 否       |
+| mysql.max_idle            | 10            | 最大空闲连接数                                     | 否       |
+| mysql.max_conn            | 100           | 最大连接数                                         | 否       |
+| mysql.conn_max_life_time  | -1            | 连接的最大时间,默认是无限制的,单位s              | 否       |
+| mysql.log_level           | 2             | 0:debug,1:info,2:warn,3:error,4:关闭打印,默认warn | 否       |
+| mysql.log_file            |               | 日志文件,不做日志切分                             | 是       |
+| mysql.refresh_config_time | 30            | 热跟新时间,单位s                                  | 否       |
+```properties
+mysql.host = 10.9.198.84
+mysql.port = 3306
+mysql.user = aaa
+mysql.password = aaa@2014
+mysql.dbname = aaa
+mysql.slave_host = 10.9.198.84
+mysql.slave_port = 3306
+mysql.slave_user = aaa
+mysql.slave_password = aaa@2014
+mysql.slave_dbname = aaa
+mysql.show_sql=true
+mysql.log_file=/data/log/go-template/aaa.log
+```
+
+## Redis
+
+| key                      | default_value | desc                           | eg | 是否必须 |
+| ------------------------ | ------------- | ------------------------------ | ------------------------ | ------------------------ |
+| redis.host |  | host , debug环境直接连接,其他环境qconf获取地址 |  | 是 |
+| redis.cache_prefix |               | cache 前缀 |  | 否 |
+| redis.password |  | 密码,没有就不需要设置 | | 否 |
+| redis.database | 0 | 默认0 | | 否 |
+| redis.max_conn | 10000 |最大连接数量|  | 否 |
+| redis.max_idle | 50 |最大空闲连接数|  | 否 |
+| redis.idle_timeout | 10 |最大空闲连接时间,单位s| | 否 |
+| redis.connect_time_out | 5 |最大连接超时时间,单位s| | 否 |
+| redis.read_time_out | 5 |最大读超时时间,单位s| | 否 |
+| redis.write_time_out | 5 |最大写超时时间,单位s| | 否 |
+| redis.is_direct | false |由于test/release环境qconf连接,兼容非qconf连接,采用的这个字段,如果标记为ture 则直接连接,debug环境无须设置| |  |
+
+```properties
+redis.host = 10.9.188.145:6379
+```
+
+## Logger
+
+| key                      | default_value | desc                           | eg | 是否必须 |
+| ------------------------ | ------------- | ------------------------------ | ------------------------ | ------------------------ |
+| log.access_log |               | 访问日志,按日切割     | /data/log/go-template/access.log | 是 |
+| log.monitor_log |               | 监控日志,按日切割 | /data/log/go-template/monitor.log | 是 |
+| log.task_log   |               |任务日志,按日切割| /data/log/go-template/task.log | 是 |
+| log.third_log   |               |调用第三方接口日志,按日切割| /data/log/go-template/third.log | 是 |
+| log.project_log | |项目日志,按小时和异常类型切割| /data/log/go-template/go-template.log | 是 |
+
+```properties
+log.access_log = /data/log/go-template/access.log
+log.monitor_log = /data/log/go-template/monitor.log
+log.task_log = /data/log/go-template/task.log
+log.third_log = /data/log/go-template/third.log
+log.third_log_switch = off #默认开启,可通过配置 off 关闭
+log.project_log = /data/log/go-template/go-template.log
+```
+
+## Qconf
+
+| key                      | default_value | desc                           | 是否有无                    |
+| ------------------------ | ------------- | ------------------------------ | ------------------------------ |
+| qconf.qconfIdc |               | qconf 配置        | 是       |
+
+```properties
+qconf.qconfIdc = rd_codis_test
+```
+
+## Kafka
+
+| key                      | default_value | desc                           | eg | 是否必须 |
+| ------------------------ | ------------- | ------------------------------ | ------------------------ | ------------------------ |
+| kafka.host |               | 多个host以英文,分割 | kafka01.dev.in.songguo7.com:9092,kafka02.dev.in.songguo7.com:9092 | 是 |
+| kafka.log_path | | 日志路径 | /data/log/go-template/kafka.log | 是 |
+
+### 消费者
+
+| key                      | default_value | desc                           | eg | 是否必须 |
+| ------------------------ | ------------- | ------------------------------ | ------------------------ | ------------------------ |
+| kafka.topic |               | topic信息多个以,分开, {topic_name1},{topic_name2} | business_event,city_op_event | 是 |
+| kafka.group_name | | 日志路径 | go-template_dev | 是 |
+| kafka_consumer_{topic_name}.topic | | topic 名称 | business_event_dev | 是 |
+| kafka_consumer_{topic_name}.process_num | 1 | 消费数量 | 3 | 否 |
+
+```properties
+## kafka
+kafka.host = kafka01.dev.in.songguo7.com:9092,kafka02.dev.in.songguo7.com:9092,kafka03.dev.in.songguo7.com:9092
+kafka.log_path = /data/log/go-template/kafka.log
+# 多个Topic可以配置多个
+kafka.topic = business_event,city_op_event
+kafka.group_name = go-template_dev
+
+# kafka-topic business_event
+kafka_consumer_business_event.topic = business_event_dev
+kafka_consumer_business_event.process_num = 3
+# kafka-topic city_op_event
+kafka_consumer_city_op_event.topic = city-op-event-dev
+kafka_consumer_city_op_event.process_num = 3
+```
+
+### 生产者
+
+| key                      | default_value | desc                           | eg | 是否必须 |
+| ------------------------ | ------------- | ------------------------------ | ------------------------ | ------------------------ |
+| kafka.group_name |               | 消费组 |  | 否 |
+
+## Nacos
+
+> ​	问题:nacos 由于 logging属于全局变量,但是每次初始化都会init,导致初始化时间发生data race现象.  [#issues](https://github.com/nacos-group/nacos-sdk-go/issues/148)
+
+
+### nacos-服务注册/服务发现中心
+
+| key                | default_value | desc                   | eg            | 是否必须 |
+| ------------------ | ------------- | ---------------------- | ------------- | :------: |
+| nacos-server.server_name | ${application.project_name} | 当前服务名称 | go-template | 是 |
+| nacos-server.server_port | ${application.port} | 当前服务暴露的对外端口 | 8080          |    是    |
+| nacos-server.server_host | 默认本地网卡地址 | 当前服务地址,默认本地网卡地址  | 172.15.66.93 |    否    |
+| nacos-server.group_name | DEFAULT_GROUP | 组名称                 | DEFAULT_GROUP |    否    |
+| nacos-server.cluster_name | DEFAULT       | 集群名称               | DEFAULT       |    否    |
+| nacos-server.host |               | 多个host以英文,分割                | 127.0.0.1:80,127.0.0.1:443           |    是    |
+| nacos-server.log_path |               | 日志路径                            | /data/log/nacos.log                  |    是    |
+| nacos-server.namespace_id |               | 空间名称,public空间不需要传递namespace_id | ed35034b-634e-4ed5-9266-d7366d389351 |    否    |
+| nacos-server.user_name |               | 用户名                              | nacos                                |    否    |
+| nacos-server.password |               | 密码                                | nacos                                |    否    |
+| nacos-server.timeout | 5000          | 连接超时时间,单位ms                | 1000                                 |    否    |
+| nacos-server.log_level | debug         | 日志打印级别:debug,info,warn,error |                                      |    否    |
+
+#### 通用配置:
+
+```properties
+nacos-server.host=10.100.101.20:8848,10.100.103.230:8848,10.100.99.14:8848
+nacos-server.log_path=/data/log/go-template
+nacos-server.log_level=warn
+nacos-server.namespace_id=84663e76-f64a-4331-a3a6-efd26ead7bf1
+nacos-server.user_name=cityservice_dev_nacos
+nacos-server.password=MrHu0qvmdK
+nacos-server.timeout=5000
+```
+
+#### 服务注册:
+
+```properties
+# 服务端口,如果为空取 application.port
+nacos-server.server_port=8080
+# 服务名称,如果为空取 application.project_name
+nacos-server.server_name=go-template
+# 默认为当前主机ipv4地址
+nacos-server.server_host=127.0.0.1	 
+# 组
+nacos-server.group_name=DEFAULT_GROUP
+# 集群(默认即可)
+nacos-server.cluster_name=DEFAULT
+```
+
+#### 服务发现:
+
+每个服务都需要配置以下配置:
+
+```properties
+# 必须设置
+ebike-factory-api.service_name = ebike-factory-api
+# 默认值 DEFAULT_GROUP
+ebike-factory-api.group_name = DEFAULT_GROUP
+# 默认值 DEFAULT
+ebike-factory-api.clusters = DEFAULT
+```
+
+### nacos-配置中心
+
+| key           | default_value | desc          | eg                      | 是否必须 |
+| ------------- | ------------- | ------------- | ----------------------- | :------: |
+| nacos-config.data_id |               | 所属的Data Id | go-template             |    是    |
+| nacos-config.group | DEFAULT_GROUP | 所属的Group   | 默认就是:DEFAULT_GROUP |    否    |
+| nacos-config.host         |               | 多个host以英文,分割                | 127.0.0.1:80,127.0.0.1:443           |    是    |
+| nacos-config.log_path     |               | 日志路径                            | /data/log/nacos.log                  |    是    |
+| nacos-config.namespace_id |               | 空间名称,public不需要配置这一项             | ed35034b-634e-4ed5-9266-d7366d389351 |    否    |
+| nacos-config.user_name    |               | 用户名                              | nacos                                |    否    |
+| nacos-config.password     |               | 密码                                | nacos                                |    否    |
+| nacos-config.timeout      | 5000          | 连接超时时间,单位ms                | 1000                                 |    否    |
+| nacos-config.log_level    | debug         | 日志打印级别:debug,info,warn,error |                                      |    否    |
+
+```properties
+[nacos-config]
+host = 10.100.101.20:8848,10.100.99.14:8848
+log_path = /data/log/go-template
+namespace_id = ed35034b-634e-4ed5-9266-d7366d389351
+user_name = nacos
+password = nacos
+data_id = go-template
+group = DEFAULT_GROUP
+log_level = error
+```
+
+## Elastic
+
+| key              | default_value | desc         | eg                                | 是否必须 |
+| ---------------- | ------------- | ------------ | --------------------------------- | :------: |
+| elastic.host     |               | IP           | 127.0.0.1:10086                   |    是    |
+| elastic.log_file |               | 日志文件地址 | /data/log/go-template/elastic.log |    是    |
+
+
+
+## Rocket-MQ
+
+| key                           | default_value | desc                                                         | eg                                  |                    是否必须                     |
+| ----------------------------- | ------------- | ------------------------------------------------------------ | ----------------------------------- | :---------------------------------------------: |
+| rocket_mq.host                |               | IP                                                           | 127.0.0.1:9871,127.0.0.1:9872       |                       是                        |
+| rocket_mq.consumer.group_name |               | 消费组                                                       | go-common_consumer                  |                       是                        |
+| rocket_mq.producer.group_name |               | 生产组                                                       | go-common_producer                  |                       是                        |
+| rocket_mq.access_key          |               | access_key                                                   |                                     |         否(根据服务端是否配置有密码)          |
+| rocket_mq.secret_key          |               | secret_key                                                   |                                     |         否(根据服务端是否配置有密码)          |
+| rocket_mq.security_token      |               | security_token                                               |                                     | 否(根据服务端是否配置有Token,目前业务不需要) |
+| rocket_mq.log.filename        |               | rocket-mq 日志,切割时间是1day,清理时间是30day              | /data/log/go-template/rocket-mq.log |       否(如果没有设置则会走console打印)       |
+| rocket_mq.log.level           | info          | debug\|info\|warn\|error                                     |                                     |                       否                        |
+| rocket_mq.trace.enable        | true          | 是否开启消息追踪(可以在RocketMQ-Console中查看),如果需要自定义配置trace 请设置为false |                                     |                 否(默认开启)                  |
+| rocket_mq.skywalking.enable   | true          | 是否开启skywalking进行链路追踪                               |                                     |                 否(默认开启)                  |
+
+eg:
+
+```properties
+rocket_mq.host = 127.0.0.1:9871,127.0.0.1:9872
+rocket_mq.consumer.group_name = go_template_consumer
+rocket_mq.producer.group_name = go_template_producer
+rocket_mq.access_key = rmq_access_key
+rocket_mq.secret_key = rmq_secret_key
+rocket_mq.trace.enable = true
+rocket_mq.skywalking.enable = true
+rocket_mq.log.filename = /data/log/go-template/rocket-mq.log
+```
+
+
+

+ 35 - 0
go.mod

@@ -0,0 +1,35 @@
+module gitea.ckfah.com/cjjy/gocommon
+
+go 1.13
+
+require (
+	github.com/360EntSecGroup-Skylar/excelize/v2 v2.3.1
+	github.com/Shopify/sarama v1.19.0
+	github.com/SkyAPM/go2sky v0.5.0
+	github.com/apache/rocketmq-client-go/v2 v2.0.0
+	github.com/fatih/color v1.7.0
+	github.com/fortytw2/leaktest v1.3.0 // indirect
+	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
+	github.com/garyburd/redigo v1.6.2
+	github.com/go-playground/universal-translator v0.17.0 // indirect
+	github.com/go-sql-driver/mysql v1.5.0
+	github.com/golang/mock v1.4.4 // indirect
+	github.com/golang/protobuf v1.4.3
+	github.com/json-iterator/go v1.1.10
+	github.com/leodido/go-urn v1.2.0 // indirect
+	github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
+	github.com/mailru/easyjson v0.7.6 // indirect
+	github.com/nacos-group/nacos-sdk-go v1.0.6
+	github.com/olivere/elastic v6.2.35+incompatible
+	github.com/prometheus/client_golang v1.8.0
+	github.com/sirupsen/logrus v1.7.0
+	github.com/stretchr/testify v1.6.1
+	github.com/tidwall/gjson v1.6.8
+	github.com/ugorji/go/codec v1.2.0
+	go.uber.org/atomic v1.7.0 // indirect
+	golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
+	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
+	gopkg.in/go-playground/validator.v9 v9.31.0
+	gopkg.in/yaml.v2 v2.3.0
+	xorm.io/xorm v1.0.5
+)

+ 590 - 0
go.sum

@@ -0,0 +1,590 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
+github.com/360EntSecGroup-Skylar/excelize/v2 v2.3.1 h1:j56fC19WoD3z+u+ZHxm2XwRGyS1XmdSMk7058BLhdsM=
+github.com/360EntSecGroup-Skylar/excelize/v2 v2.3.1/go.mod h1:gXEhMjm1VadSGjAzyDlBxmdYglP8eJpYWxpwJnmXRWw=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/SkyAPM/go2sky v0.5.0 h1:9RDBQviaeazG7PJMLLnMcU4U++PORbqEls4ix4OEgQw=
+github.com/SkyAPM/go2sky v0.5.0/go.mod h1:TANzYw5EvIlTidGWvQxtvO87rM6C746HkM0xkWqnPQw=
+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/apache/rocketmq-client-go/v2 v2.0.0 h1:D6jFj3DcNjWyjWn5N/R7Eq8v5kLqlgkFnT/DNQFnWlM=
+github.com/apache/rocketmq-client-go/v2 v2.0.0/go.mod h1:oEZKFDvS7sz/RWU0839+dQBupazyBV7WX5cP6nrio0Q=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
+github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
+github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
+github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
+github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
+github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
+github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
+github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
+github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
+github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo=
+github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE=
+github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4=
+github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8=
+github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc=
+github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0=
+github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
+github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
+github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
+github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nacos-group/nacos-sdk-go v1.0.6 h1:0OqjS37qIKKKZKRQSJ5pNFGRrfzP7+gD4L6dvOkPFZw=
+github.com/nacos-group/nacos-sdk-go v1.0.6/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olivere/elastic v6.2.35+incompatible h1:MMklYDy2ySi01s123CB2WLBuDMzFX4qhFcA5tKWJPgM=
+github.com/olivere/elastic v6.2.35+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
+github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw=
+github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4=
+github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
+github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI=
+github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
+github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o=
+github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto=
+github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
+github.com/tidwall/gjson v1.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
+github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
+github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
+github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
+github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
+github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
+github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk=
+github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE=
+github.com/ugorji/go v1.2.0 h1:6eXlzYLLwZwXroJx9NyqbYcbv/d93twiOzQLDewE6qM=
+github.com/ugorji/go v1.2.0/go.mod h1:1ny++pKMXhLWrwWV5Nf+CbOuZJhMoaFD+0GMFfd8fEc=
+github.com/ugorji/go/codec v1.2.0 h1:As6RccOIlbm9wHuWYMlB30dErcI+4WiKWsYsmPkyrUw=
+github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xuri/efp v0.0.0-20200605144744-ba689101faaf h1:spotWVWg9DP470pPFQ7LaYtUqDpWEOS/BUrSmwFZE4k=
+github.com/xuri/efp v0.0.0-20200605144744-ba689101faaf/go.mod h1:uBiSUepVYMhGTfDeBKKasV4GpgBlzJ46gXUBAqV8qLk=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
+go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/image v0.0.0-20200922025426-e59bae62ef32 h1:E+SEVulmY8U4+i6vSB88YSc2OKAFfvbHPU/uDTdQu7M=
+golang.org/x/image v0.0.0-20200922025426-e59bae62ef32/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
+golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
+golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20181010134911-4d1c5fb19474/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
+gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
+gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
+gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
+gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
+gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=
+stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=
+xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
+xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.0.5 h1:LRr5PfOUb4ODPR63YwbowkNDwcolT2LnkwP/TUaMaB0=
+xorm.io/xorm v1.0.5/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=

+ 155 - 0
pkg/boot/boot.go

@@ -0,0 +1,155 @@
+package boot
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/cache/localcache"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/cache/redis"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/database/db"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/database/elastic"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/database/multi_db"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/logger"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/mq/kafka"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/mq/rocketmq"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/net/nacos"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/net/pprof"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/net/qconf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/trace"
+)
+
+// go get golang.org/x/tools/cmd/stringer
+//go:generate stringer -type=ResourceType
+type ResourceType uint8
+
+type ConfigResource func(config *ResourceConfig)
+
+//  每变更一次类型都需要执行一次 go:generate
+const (
+	Config ResourceType = iota + 1
+	Logger
+	Qconf
+	DB
+	MultiDB
+	Redis
+	Trace
+	KafkaProducer
+	KafkaConsumer
+	LocalCache
+	NacosDiscover // 发现中心 nacos
+	NacosRegister // 注册中心 nacos
+	Elastic
+	Pprof
+	RocketMQProducer
+	RocketMQConsumer
+)
+
+type Resource interface {
+	Init() error
+	Start() error
+	Config() ResourceConfig
+}
+
+type initFunc func() error
+type startFunc func() error
+
+/**
+注册init 函数
+*/
+var (
+	resourceMap = map[ResourceType]Resource{
+		Config:           newResource(conf.Init, nil),
+		Logger:           newResource(logger.Init, nil),
+		DB:               newResource(db.Init, nil),
+		MultiDB:          newResource(multi_db.Init, nil),
+		Qconf:            newResource(qconf.InitQconf, nil),
+		Redis:            newResource(redis.Init, nil),
+		Trace:            newResource(trace.Init, nil),
+		KafkaProducer:    newResource(kafka.InitProducer, nil),
+		KafkaConsumer:    newResource(kafka.InitConsumer, nil),
+		LocalCache:       newResource(localcache.Init, localcache.Start),
+		Elastic:          newResource(elastic.Init, nil),
+		NacosDiscover:    newResource(nacos.InitNacosDiscoverClient, nil),
+		NacosRegister:    newResource(nacos.InitNacosRegisterClientV2, nacos.StartNacosRegisterClientV2),
+		Pprof:            newResource(pprof.Init, nil),
+		RocketMQProducer: newResource(rocketmq.InitProducer, nil),
+		RocketMQConsumer: newResource(rocketmq.InitConsumer, rocketmq.StartConsumer),
+	}
+
+	// 锁,防止重复init
+	initLock sync.Mutex
+
+	initializedMap = map[ResourceType]Resource{}
+	startedMap     = map[ResourceType]Resource{}
+
+	// 必须加载的资源
+	mustInitResource = []ResourceType{Config, Logger}
+)
+
+// 允许多次init
+func Init(resource ...ResourceType) {
+	if resource == nil || len(resource) == 0 {
+		resource = mustInitResource // 这俩是必须初始化的
+	}
+	// 排序和append_need_load_resource 加载资源的顺序
+	resource = sortResourceType(handlerResourceType(resource))
+
+	//
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	// 遍历初始化
+	for _, elem := range resource {
+		resource, isExist := resourceMap[elem]
+		// 存在
+		if isExist {
+			// 是否已经启动过
+			if _, isInit := initializedMap[elem]; isInit {
+				continue
+			}
+			util.Infof("[%s] start init...", elem)
+			err := resource.Init()
+			if err != nil {
+				util.Painc(errors.New(fmt.Sprintf("init %s find err: %s", elem, err)))
+			}
+			initializedMap[elem] = resource
+			util.Infof("[%s] end init...", elem)
+		}
+	}
+}
+
+func Start() {
+	initLock.Lock()
+	defer initLock.Unlock()
+
+	for _type, elem := range initializedMap {
+		if _, isStart := startedMap[_type]; isStart {
+			continue
+		}
+		if err := elem.Start(); err != nil {
+			util.Painc(err)
+		}
+		startedMap[_type] = elem
+	}
+	// 写进程pid
+	util.WritePid()
+}
+
+func handlerResourceType(resource []ResourceType) []ResourceType {
+	if resource == nil || len(resource) == 0 {
+		return mustInitResource
+	}
+	resource = append(resource, mustInitResource...)
+	filterMap := make(map[ResourceType]interface{}, 0)
+	for _, elem := range resource {
+		filterMap[elem] = nil
+	}
+	newResource := make([]ResourceType, 0)
+	for key, _ := range filterMap {
+		newResource = append(newResource, key)
+	}
+	return newResource
+}

+ 246 - 0
pkg/boot/boot_test.go

@@ -0,0 +1,246 @@
+package boot
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/Shopify/sarama"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/cache/localcache"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/cache/redis"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/database/db"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/application"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/logger"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/mq/kafka"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/net/httpclent"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/net/nacos"
+	"net/http"
+	"testing"
+	"time"
+)
+
+func TestBoot(t *testing.T) {
+	//TestAllowMultiInit(t)
+	//TestInitLogger(t)
+	//TestMultiDb(t)
+	TestHttp(t)
+	//TestInitConfig(t)
+	//TestInitNacosServer(t)
+	//TestRedis(t)
+	//TestNacosRegister(t)
+	//TestLocalCache(t)
+	//TestInitConfig(t)
+	//Init(KafkaProducer, KafkaConsumer)
+
+	//go TestKafkaProducer(t)
+	//
+	//TestKafkaConsumer(t)
+}
+
+func BenchmarkLogger(b *testing.B) {
+	Init()
+
+	for i := 0; i < b.N; i++ {
+		logger.Infof(fmt.Sprintf("hello world %s\n", time.Now().Format("2006-01-02 15:04:05.000")))
+	}
+}
+
+func TestSingle(t *testing.T) {
+	go func() {
+		<-application.GetSignal()
+		fmt.Println("single ........")
+	}()
+	time.Sleep(time.Second * 1000)
+}
+
+func TestInitConfig(t *testing.T) {
+	Init(Config, )
+	fmt.Println(conf.GetStringV2("log.access_log"))
+	fmt.Println(conf.GetString("log", "access_log"))
+	go func() {
+		for {
+			time.Sleep(time.Second)
+			fmt.Println(conf.GetStringV2("test.key1"))
+		}
+	}()
+	time.Sleep(time.Second * 100)
+}
+
+func TestInitNacosServer(t *testing.T) {
+	Init(NacosRegister, NacosDiscover)
+	s, e := nacos.GetHost("ebike-factory-api")
+	if e != nil {
+		t.Fatal(e)
+	}
+	fmt.Println("配置: ", s)
+}
+
+func TestInitLogger(t *testing.T) {
+	Init()
+
+	ctx := logger.NewTraceIdContext()
+	logger.Infof("hello, %s!", "Infof")
+	logger.Errorf("hello, %s!", "Errorf")
+	logger.Debugf("hello, %s!", "Debugf")
+	logger.Warnf("hello, %s!", "Warnf")
+
+	logger.Infoc(ctx, "hello, %s!", "Infof")
+	logger.Errorc(ctx, "hello, %s!", "Errorf")
+	logger.Debugc(ctx, "hello, %s!", "Debugf")
+	logger.Warnc(ctx, "hello, %s!", "Warnf")
+	logger.Fatalc(ctx, "hello, %s!", "Fatalf")
+
+	//logger.Fatalf("hello, %s!", "Fatalf")
+}
+
+func TestDb(t *testing.T) {
+	Init(DB)
+	for {
+		<-time.After(time.Second)
+		strings, err := db.NewSlaveSession(context.Background()).SQL("select * from peccancy_template_config").QueryString()
+		if err != nil {
+			t.Fatal(err)
+		}
+		fmt.Println(strings)
+	}
+}
+
+func TestNacosRegister(t *testing.T) {
+	Init(NacosRegister)
+}
+
+func TestNacosDiscover(t *testing.T) {
+	Init(NacosDiscover)
+	host, err := nacos.GetHost("ebike-factory-api")
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(host)
+}
+
+func TestLocalCache(t *testing.T) {
+	Init(LocalCache)
+
+	localcache.Register("demo", func(context context.Context, cache localcache.Cache) {
+		fmt.Println("refresh ............")
+		cache.Put("k1", time.Now())
+	})
+
+	for {
+		<-time.After(time.Second * 5)
+		fmt.Println(localcache.Get("demo", "k1"))
+	}
+}
+
+func TestKafkaProducer(t *testing.T) {
+	type BusinessEventMsg struct {
+		Data struct {
+			BikeSn string `json:"bike_sn"`
+		}
+		LbsServerTs int64  `json:"lbs_server_ts"`
+		Event       string `json:"event"`
+		Ts          int64  `json:"ts"`
+	}
+
+	for {
+		<-time.After(time.Second)
+		msg := BusinessEventMsg{}
+		msg.Data.BikeSn = "804615442"
+		msg.Ts = time.Now().Unix()
+		msg.Event = "bike_in_use"
+
+		bytes, err := json.Marshal(msg)
+		if err != nil {
+			t.Fatal(err)
+		}
+		err = kafka.GetKafkaProducer().ProducerMsg(application.NewContext(), "business_event_dev", string(bytes))
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+func TestKafkaConsumer(t *testing.T) {
+	err := kafka.SetConsumerHandler(func(ctx context.Context, msg *sarama.ConsumerMessage) error {
+		fmt.Printf("value: %s, key: %s, Offset:%d, Partition:%d\n", msg.Value, msg.Key, msg.Offset, msg.Partition)
+		return nil
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	time.Sleep(time.Second * 10000)
+}
+
+func TestRedis(t *testing.T) {
+	Init(Redis)
+
+	s, e := redis.Instance().GetString("k1")
+	if !redis.CheckIsNilErr(e) {
+		fmt.Println("k1 不为空, 值为: ", s)
+	}
+	if redis.CheckIsNilErr(e) {
+		fmt.Println("k1 为空")
+	}
+
+	{
+		s, e := redis.Instance().GetString("k2")
+		if !redis.CheckIsNilErr(e) {
+			fmt.Println("k2 不为空, 值为: ", s)
+		}
+		if redis.CheckIsNilErr(e) {
+			fmt.Println("k2 为空")
+		}
+	}
+}
+
+func TestAllowMultiInit(t *testing.T) {
+	Init()
+	Init(DB, Redis, NacosDiscover, NacosRegister)
+	Start()
+	Start()
+}
+
+func TestMultiDb(t *testing.T) {
+	Init(DB)
+
+	//conn, err := db.GetDb("mysql")/**/
+	//if err != nil {
+	//	panic(err)
+	//}
+	strings, err := db.NewSlaveSession(context.Background()).SQL("select * from peccancy_template_config").QueryString()
+	if err != nil {
+		panic(err)
+	}
+	fmt.Println(strings)
+}
+
+func TestHttp(t *testing.T) {
+	//{
+	//	Init(Qconf, Trace)
+	//	resp := httpclent.RequestParams{}
+	//	err := httpclent.HttpRequestAndDecode(context.Background(), "city-config", "/city-config/city/info", httpclent.RequestParams{
+	//		"city_id": 1,
+	//	}, &resp)
+	//	if err != nil {
+	//		t.Fatal(err)
+	//	}
+	//	fmt.Println(resp)
+	//}
+	{
+		Init(NacosDiscover)
+		resp := httpclent.RequestParams{}
+		err := httpclent.HttpRequestAndDecode(context.Background(), "ebike-factory-api", "/service/v1/op-worker/search-by-ids", httpclent.RequestParams{
+			"ids": []uint64{10010},
+		}, &resp, httpclent.NacosServerHostOption, func(options *httpclent.Options) {
+			options.AddRequestCookies = func(ctx context.Context, requestCookie []*http.Cookie) []*http.Cookie {
+				return nil
+			}
+		})
+		if err != nil {
+			t.Fatal(err)
+		}
+		fmt.Println(resp)
+	}
+
+}

+ 39 - 0
pkg/boot/resourcetype_string.go

@@ -0,0 +1,39 @@
+// Code generated by "stringer -type=ResourceType"; DO NOT EDIT.
+
+package boot
+
+import "strconv"
+
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[Config-1]
+	_ = x[Logger-2]
+	_ = x[Qconf-3]
+	_ = x[DB-4]
+	_ = x[MultiDB-5]
+	_ = x[Redis-6]
+	_ = x[Trace-7]
+	_ = x[KafkaProducer-8]
+	_ = x[KafkaConsumer-9]
+	_ = x[LocalCache-10]
+	_ = x[NacosDiscover-11]
+	_ = x[NacosRegister-12]
+	_ = x[Elastic-13]
+	_ = x[Pprof-14]
+	_ = x[RocketMQProducer-15]
+	_ = x[RocketMQConsumer-16]
+}
+
+const _ResourceType_name = "ConfigLoggerQconfDBMultiDBRedisTraceKafkaProducerKafkaConsumerLocalCacheNacosDiscoverNacosRegisterElasticPprofRocketMQProducerRocketMQConsumer"
+
+var _ResourceType_index = [...]uint8{0, 6, 12, 17, 19, 26, 31, 36, 49, 62, 72, 85, 98, 105, 110, 126, 142}
+
+func (i ResourceType) String() string {
+	i -= 1
+	if i >= ResourceType(len(_ResourceType_index)-1) {
+		return "ResourceType(" + strconv.FormatInt(int64(i+1), 10) + ")"
+	}
+	return _ResourceType_name[_ResourceType_index[i]:_ResourceType_index[i+1]]
+}

+ 73 - 0
pkg/boot/type.go

@@ -0,0 +1,73 @@
+package boot
+
+import "sort"
+
+type resource struct {
+	init   initFunc
+	start  startFunc
+	config ResourceConfig
+}
+
+func newResource(i initFunc, s startFunc, op ...ConfigResource) Resource {
+	config := ResourceConfig{}
+	for _, elem := range op {
+		elem(&config)
+	}
+	return &resource{
+		init:   i,
+		start:  s,
+		config: config,
+	}
+}
+
+func (i *resource) Init() error {
+	if i.init != nil {
+		return i.init()
+	}
+	return nil
+}
+func (i *resource) Start() error {
+	if i.start != nil {
+		return i.start()
+	}
+	return nil
+}
+
+func (i *resource) Config() ResourceConfig {
+	return i.config
+}
+
+type ResourceConfig struct {
+	Name string
+}
+
+func SetResourceName(name string) ConfigResource {
+	return func(config *ResourceConfig) {
+		config.Name = name
+	}
+}
+
+func sortResourceType(res []ResourceType) []ResourceType {
+	if res == nil || len(res) == 0 {
+		return []ResourceType{}
+	}
+	sort.Sort(SortedResource(res))
+	return res
+}
+
+/**
+排序资源
+*/
+type SortedResource []ResourceType
+
+func (s SortedResource) Len() int {
+	return len(s)
+}
+
+func (s SortedResource) Less(i, j int) bool {
+	return s[i] < s[j]
+}
+
+func (s SortedResource) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}

+ 162 - 0
pkg/cache/localcache/api.go

@@ -0,0 +1,162 @@
+package localcache
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/logger"
+)
+
+const (
+	defaultRefreshTime int64 = 600
+	defaultCacheName         = "localcache"
+)
+
+type Cache interface {
+	Get(key interface{}) interface{}
+	Put(key interface{}, value interface{})
+	Delete(key interface{})
+	KeySet() []interface{}
+	Refresh()
+}
+
+// 用于刷新任务的cache,不能替换cache无效,比如cache=cache1,刷新任务最好recover一下,防止程序出问题
+type Refresh func(context context.Context, cache Cache)
+
+type Option func(cache *localCache)
+
+type cacheEntry struct {
+	// 这个name主要是用来设置ttl时间,直接读取config
+	cacheName string
+	// 普通map 并发读写会抛出 fatal error: concurrent map read and map write
+	// 读多写少,使用sync-map
+	cache sync.Map
+	// 刷新缓存的脚本
+	refresh Refresh
+	// 刷新时间
+	refreshTime time.Duration
+}
+
+// 对外暴漏的接口
+func (this *cacheEntry) Put(key interface{}, value interface{}) {
+	this.cache.Store(key, value)
+}
+
+func (this *cacheEntry) Delete(key interface{}) {
+	this.cache.Delete(key)
+}
+
+func (this *cacheEntry) Get(key interface{}) interface{} {
+	value, _ := this.cache.Load(key)
+	return value
+}
+
+func (this *cacheEntry) KeySet() []interface{} {
+	keys := make([]interface{}, 0)
+	this.cache.Range(func(key, value interface{}) bool {
+		keys = append(keys, key)
+		return true
+	})
+	return keys
+}
+
+type localCache struct {
+	localCacheName     string
+	lock               sync.Mutex
+	entries            map[string]*cacheEntry
+	start              bool
+	defaultRefreshTime int64 //单位s
+}
+
+// 实例化
+func NewLocalCache(option ...Option) *localCache {
+	cache := new(localCache)
+	for _, e := range option {
+		if e != nil {
+			e(cache)
+		}
+	}
+	if cache.localCacheName == "" {
+		cache.localCacheName = defaultCacheName
+	}
+	if cache.entries == nil {
+		cache.entries = map[string]*cacheEntry{}
+	}
+	if cache.defaultRefreshTime == 0 {
+		cache.defaultRefreshTime = defaultRefreshTime
+	}
+	cache.start = false
+	return cache
+}
+
+// 获取数据,不返回bool,是因为业务中只需要判断nil
+func (this *localCache) Get(cacheName string, keyName interface{}) interface{} {
+	entry, isExist := this.entries[cacheName]
+	if !isExist || entry == nil {
+		return nil
+	}
+	return entry.Get(keyName)
+}
+
+// 注册
+func (this *localCache) Register(configName string, refresh Refresh) {
+	this.lock.Lock()
+	defer this.lock.Unlock()
+	if this.start {
+		return
+	}
+	cache, isExist := this.entries[configName]
+	if isExist || cache != nil {
+		return
+	}
+	entry := cacheEntry{
+		cacheName:   configName,
+		refresh:     refresh,
+		refreshTime: this.getRefreshTime(configName),
+	}
+	this.entries[configName] = &entry
+}
+
+func (this *localCache) Start() {
+	this.lock.Lock()
+	defer this.lock.Unlock()
+	if this.start {
+		return
+	}
+	// 同步初始化数据
+	for index, _ := range this.entries {
+		this.entries[index].Refresh()
+	}
+	// goroutine 跑需要recover
+	for _, elem := range this.entries {
+		go func(job *cacheEntry) {
+			timer := time.NewTimer(job.refreshTime)
+			for {
+				<-timer.C
+				job.Refresh()
+				job.refreshTime = this.getRefreshTime(job.cacheName)
+				timer.Reset(job.refreshTime)
+			}
+		}(elem)
+	}
+	this.start = true
+}
+
+func (this *cacheEntry) Refresh() {
+	defer func() {
+		if p := recover(); p != nil {
+			logger.Errorf("[local-cache] cache-job has panic,cache-name=%s,panic_info=%v", this.cacheName, p)
+		}
+	}()
+	start := time.Now()
+	ctx := logger.NewTraceIdContext()
+	logger.Infoc(ctx, "[local-cache] %s start, ttl=%.0fs", this.cacheName, this.refreshTime.Seconds())
+	this.refresh(ctx, this)
+	logger.Infoc(ctx, "[local-cache] %s end, ttl=%.0fs,refresh_spend=%fs", this.cacheName, this.refreshTime.Seconds(), time.Now().Sub(start).Seconds())
+}
+
+func (this *localCache) getRefreshTime(cacheName string) time.Duration {
+	return time.Duration(conf.GetInt64(this.localCacheName, cacheName, this.defaultRefreshTime)) * time.Second
+}

+ 24 - 0
pkg/cache/localcache/local_cache_test.go

@@ -0,0 +1,24 @@
+package localcache
+
+import (
+	"context"
+	"fmt"
+	"testing"
+	"time"
+)
+
+func TestNewLocalCache(t *testing.T) {
+	Register("test", func(context context.Context, cache Cache) {
+		fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
+		for index, _ := range [100]int{} {
+			cache.Put(uint64(index), fmt.Sprintf("cache-%d", index))
+		}
+		panic("1111")
+	})
+	Start()
+	tick := time.NewTicker(time.Millisecond * 500)
+	for {
+		<-tick.C
+		fmt.Println(Get("test", uint64(1)))
+	}
+}

+ 37 - 0
pkg/cache/localcache/localcache.go

@@ -0,0 +1,37 @@
+package localcache
+
+import (
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+)
+
+const (
+	localCacheName = "localcache"
+	refreshTime    = "refresh_time"
+)
+
+var (
+	defaultCache *localCache
+)
+
+func Init() error {
+	time := conf.GetInt64(localCacheName, refreshTime, defaultRefreshTime)
+	defaultCache = NewLocalCache(func(cache *localCache) {
+		cache.defaultRefreshTime = time
+	})
+	return nil
+
+}
+func Register(configName string, refresh Refresh) {
+	defaultCache.Register(configName, refresh)
+}
+
+// 注意类型,强一致,比如int64 ,必须key是int64才能get到,int不行
+func Get(cacheName string, keyName interface{}) interface{} {
+	return defaultCache.Get(cacheName, keyName)
+}
+
+// 启动
+func Start() error {
+	defaultCache.Start() // 启动
+	return nil
+}

+ 277 - 0
pkg/cache/redis/redis.go

@@ -0,0 +1,277 @@
+package redis
+
+import (
+	"fmt"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/logger"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/net/qconf"
+	"github.com/garyburd/redigo/redis"
+)
+
+var (
+	redisInstance *ConnPool
+)
+
+// ConnPool is for Cache fd
+type ConnPool struct {
+	prefix    string
+	redisPool *redis.Pool
+}
+
+//获取实例
+func Instance() *ConnPool {
+	return redisInstance
+}
+
+// 将Pool暴露出去,记住需要defer close!
+func InstancePool() *redis.Pool {
+	return redisInstance.redisPool
+}
+
+func PrefixKey(key string) string {
+	return fmt.Sprintf("%s%s", redisInstance.prefix, key)
+}
+
+func CheckIsNilErr(err error) bool { // 检测是不是nil的问题,true是nil-err
+	return err == redis.ErrNil
+}
+
+func CheckIsNotNilErr(err error) bool { // 检测是不是nil的问题,true不是nil-err
+	return err != redis.ErrNil
+}
+
+func getConfig(section, key string, defaultVal ...string) string {
+	return conf.GetString(section, key, defaultVal...)
+}
+
+//初始化redis pool
+func Init() error {
+	config, err := getRedisConfig("redis", conf.SetAndAssertNil)
+	if err != nil {
+		return err
+	}
+	util.Debugf("Redis load config success, config=%+v", config)
+	redisConfig := getRedisConfigFromMap(config)
+	// 如果不是直接连接&&env不是debug则qconf获取
+	if (!util.String2Bool(config["is_direct"])) && conf.GetEnv() != conf.EnvDebug {
+		realHost, err := qconf.GetHost(redisConfig.Host, qconf.GetQconfIdc())
+		if err != nil {
+			return err
+		}
+		redisConfig.Host = realHost
+	}
+	pool, err := newRedisPool(redisConfig)
+	if err != nil {
+		return err
+	}
+	redisInstance = &ConnPool{redisPool: pool, prefix: config["cache_prefix"]}
+	return nil
+}
+
+// Close pool
+func (p *ConnPool) Close() error {
+	err := p.redisPool.Close()
+	return err
+}
+
+// Do commands
+func (p *ConnPool) Do(command string, args ...interface{}) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return conn.Do(command, args...)
+}
+
+// SetString for string
+func (p *ConnPool) SetString(key string, value interface{}) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return conn.Do("SET", key, value)
+}
+
+// SetString for string ttl,ttl 单位s
+func (p *ConnPool) SetExString(key string, value interface{}, ttl uint64) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return conn.Do("SET", key, value, "EX", ttl)
+}
+
+// GetString for string, 如果key不存在会抛出异常: redigo: nil returned
+func (p *ConnPool) GetString(key string) (string, error) {
+	// get one connection from pool
+	conn := p.redisPool.Get()
+	// put connection to pool
+	defer conn.Close()
+	return redis.String(conn.Do("GET", key))
+}
+
+// GetBytes for bytes
+func (p *ConnPool) GetBytes(key string) ([]byte, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Bytes(conn.Do("GET", key)) // if nil return errnil
+}
+
+// GetInt for int
+func (p *ConnPool) GetInt(key string) (int, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Int(conn.Do("GET", key))
+}
+
+//Incr
+func (p *ConnPool) Incr(key string) (int, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Int(conn.Do("INCR", key))
+}
+
+//Decr
+func (p *ConnPool) Decr(key string) (int, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Int(conn.Do("DECR", key))
+}
+
+// GetInt64 for int64
+func (p *ConnPool) GetInt64(key string) (int64, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Int64(conn.Do("GET", key))
+}
+
+// DelKey for key
+func (p *ConnPool) DelKey(key string) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return conn.Do("DEL", key)
+}
+
+// ExpireKey for key
+func (p *ConnPool) ExpireKey(key string, seconds int64) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return conn.Do("EXPIRE", key, seconds)
+}
+
+// Keys for key
+func (p *ConnPool) Keys(pattern string) ([]string, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Strings(conn.Do("KEYS", pattern))
+}
+
+// KeysByteSlices for key
+func (p *ConnPool) KeysByteSlices(pattern string) ([][]byte, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.ByteSlices(conn.Do("KEYS", pattern))
+}
+
+// SetHashMap for hash map
+func (p *ConnPool) SetHashMap(key string, fieldValue map[string]interface{}) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return conn.Do("HMSET", redis.Args{}.Add(key).AddFlat(fieldValue)...)
+}
+
+// GetHashMapString for hash map
+func (p *ConnPool) GetHashMapString(key string) (map[string]string, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.StringMap(conn.Do("HGETALL", key))
+}
+
+// HGet
+func (p *ConnPool) HGet(key, field string) (string, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.String(conn.Do("HGET", key, field))
+}
+
+// HSet
+func (p *ConnPool) HSet(key, field, val string) (int, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Int(conn.Do("HSET", key, field, val))
+}
+
+// GetHashMapInt for hash map
+func (p *ConnPool) GetHashMapInt(key string) (map[string]int, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.IntMap(conn.Do("HGETALL", key))
+}
+
+// GetHashMapInt64 for hash map
+func (p *ConnPool) GetHashMapInt64(key string) (map[string]int64, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Int64Map(conn.Do("HGETALL", key))
+}
+
+// LPUSH
+func (p *ConnPool) LPUSH(key string, fieldValue []interface{}) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return conn.Do("LPUSH", redis.Args{}.Add(key).AddFlat(fieldValue)...)
+}
+
+// LPOP
+func (p *ConnPool) LPOP(key string) (interface{}, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.String(conn.Do("LPOP", key))
+}
+
+// BLPOP
+func (p *ConnPool) BLPOP(key string, timeout int) ([]string, error) {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	return redis.Strings(conn.Do("BLPOP", key, timeout))
+}
+
+/*
+ Distributed mutual exclusion lock(Redis分布式排斥锁)
+*/
+//===========start===========
+func (p *ConnPool) LockAcquire(name string, value string, expiry int) bool {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	reply, err := redis.String(conn.Do("SET", name, value, "NX", "PX", expiry))
+	logger.Infof("Redis LockAcquire name:%s,value:%s,reply:%s", name, value, reply)
+	return err == nil && reply == "OK"
+}
+
+var lockDeleteScript = redis.NewScript(1, `
+	if redis.call("GET", KEYS[1]) == ARGV[1] then
+		return redis.call("DEL", KEYS[1])
+	else
+		return 0
+	end
+`)
+
+func (p *ConnPool) LockRelease(name string, value string) bool {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	status, err := lockDeleteScript.Do(conn, name, value)
+	return err == nil && status != 0
+}
+
+var lockTouchScript = redis.NewScript(1, `
+	if redis.call("GET", KEYS[1]) == ARGV[1] then
+		return redis.call("SET", KEYS[1], ARGV[1], "XX", "PX", ARGV[2])
+	else
+		return "ERR"
+	end
+`)
+
+func (p *ConnPool) LockTouch(name string, value string, expiry int) bool {
+	conn := p.redisPool.Get()
+	defer conn.Close()
+	status, err := redis.String(lockTouchScript.Do(conn, name, value, expiry))
+	return err == nil && status != "ERR"
+}
+
+//===========end===========

+ 145 - 0
pkg/cache/redis/redis_cnn.go

@@ -0,0 +1,145 @@
+package redis
+
+import (
+	"time"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"github.com/garyburd/redigo/redis"
+)
+
+// 先不支持tls
+type Config struct {
+	Host     string `json:"host"`
+	Password string `json:"password"` // 默认没有
+	Database int    `json:"database"` // 默认0
+
+	IdleTimeout time.Duration `json:"idle_timeout"` // 空闲的超时时间,默认10s
+	MaxIdle     int           `json:"max_idle"`     // 默认50
+	MaxActive   int           `json:"max_conn"`     // 默认10000
+
+	ConnectTimeOut time.Duration `json:"connect_time_out"` // 默认超时都是5s
+	ReadTimeOut    time.Duration `json:"read_time_out"`
+	WriteTimeOut   time.Duration `json:"write_time_out"`
+}
+
+func getRedisConfigFromMap(config map[string]string) Config {
+	return Config{
+		Host:           config["host"],
+		Password:       config["password"],
+		Database:       util.String2Int(config["database"]),
+		IdleTimeout:    util.StringSecToDuration(config["idle_timeout"]),
+		MaxIdle:        util.String2Int(config["max_idle"]),
+		MaxActive:      util.String2Int(config["max_conn"]),
+		ConnectTimeOut: util.StringSecToDuration(config["connect_time_out"]),
+		ReadTimeOut:    util.StringSecToDuration(config["read_time_out"]),
+		WriteTimeOut:   util.StringSecToDuration(config["write_time_out"]),
+	}
+}
+
+func getRedisConfig(key string, assertNil util.SetAndAssertNil) (map[string]string, error) {
+	config := make(map[string]string, 0)
+	if err := assertNil(config, key, "host", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(config, key, "cache_prefix", ""); err != nil {
+		//return nil, err
+		util.Warnf("%s config not set cache_prefix default nil ", key)
+	}
+	if err := assertNil(config, key, "password", ""); err != nil {
+		//return nil, err
+		util.Warnf("%s config not set password default nil ", key)
+	}
+	if err := assertNil(config, key, "database", ""); err != nil {
+		//return nil, err
+		util.Warnf("%s config not set database default 0", key)
+	}
+	if err := assertNil(config, key, "idle_timeout", "10"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(config, key, "max_idle", "50"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(config, key, "max_conn", "10000"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(config, key, "connect_time_out", "5"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(config, key, "read_time_out", "5"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(config, key, "write_time_out", "5"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(config, key, "is_direct", "false"); err != nil {
+		return nil, err
+	}
+	return config, nil
+}
+
+func newRedisPool(config Config) (*redis.Pool, error) {
+	ping := func(r *redis.Pool) error {
+		cnn := r.Get()
+		defer cnn.Close()
+		_, err := cnn.Do("PING")
+		return err
+	}
+	init := func(config Config) Config {
+		if config.ReadTimeOut == 0 {
+			config.ReadTimeOut = time.Second * 5
+		}
+		if config.ConnectTimeOut == 0 {
+			config.ConnectTimeOut = time.Second * 5
+		}
+		if config.WriteTimeOut == 0 {
+			config.WriteTimeOut = time.Second * 5
+		}
+		if config.MaxActive == 0 {
+			config.MaxActive = 10000
+		}
+		if config.MaxIdle == 0 {
+			config.MaxIdle = 10
+		}
+		if config.IdleTimeout == 0 {
+			config.IdleTimeout = time.Second * 10
+		}
+		return config
+	}
+	config = init(config)
+	pool := &redis.Pool{
+		//Wait:        true, // 是否等待从连接池中获取,这里最好设置为false,默认就是false
+		MaxActive:   config.MaxActive,
+		MaxIdle:     config.MaxIdle,
+		IdleTimeout: config.IdleTimeout,
+		Dial: func() (redis.Conn, error) {
+			c, err := redis.Dial("tcp", config.Host,
+				redis.DialConnectTimeout(config.ConnectTimeOut),
+				redis.DialReadTimeout(config.ReadTimeOut),
+				redis.DialWriteTimeout(config.WriteTimeOut),
+				redis.DialPassword(config.Password),
+				redis.DialDatabase(config.Database),
+			)
+			if err != nil {
+				return nil, err
+			}
+			return c, err
+		},
+		TestOnBorrow: func(c redis.Conn, t time.Time) error {
+			// 如果小于空闲时间,则不进行检测,优化好处不用每次拿去连接都ping一次(t是上次使用的时间)
+			//fmt.Printf("pre: %s, cur: %s, idel: %v\n", t.Format("2006-01-02 15:04:05"), time.Now().Format("2006-01-02 15:04:05"), config.IdleTimeout/time.Second)
+			if time.Since(t) < config.IdleTimeout {
+				return nil
+			}
+			//fmt.Println("ping")
+			_, err := c.Do("PING")
+			//defer c.Close() // 这里不能关闭,当去借连接的时候,回去测试一下连接是否可用
+			return err
+		},
+	}
+
+	if err := ping(pool); err != nil {
+		pool.Close()
+		return nil, err
+	}
+	return pool, nil
+}

+ 94 - 0
pkg/cache/redis/redis_test.go

@@ -0,0 +1,94 @@
+package redis
+
+import (
+	"fmt"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/properties"
+	"testing"
+	"time"
+)
+
+var (
+	pro properties.Properties
+)
+
+func mockRedisConfig(t *testing.T) {
+	config := `
+redis.host = 127.0.0.1:6379
+redis.password = 
+redis.database = 1
+application.env = debug
+`
+	var err error
+	pro, err = properties.ReadFromString(config)
+	if err != nil {
+		t.Fatal(err)
+	}
+	conf.MustValue = func(section, key string, defaultVal ...string) string {
+		return pro.GetString(section+"."+key, defaultVal...)
+	}
+}
+
+func TestInstancePool(t *testing.T) {
+	mockRedisConfig(t)
+	if err := Init(); err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(Instance().SetString("k11111", "v1"))
+	cnn := InstancePool().Get()
+	defer cnn.Close()
+	fmt.Println(cnn.Do("get", "k11111"))
+}
+
+func TestPUSH(t *testing.T) {
+	mockRedisConfig(t)
+	if err := Init(); err != nil {
+		t.Fatal(err)
+	}
+	Instance().LPUSH("mylist1", []interface{}{1, 2, 3, 4})
+	//cnn := InstancePool().Get()
+	//defer cnn.Close()
+	//fmt.Println(cnn.Do("BLPOP", "mylist1", 0))
+}
+
+func TestIsDirectInstancePoolFalse(t *testing.T) {
+	mockRedisConfig(t)
+	pro.SetString("application.env", "release")
+	if err := Init(); err != nil {
+		t.Fatal(err)
+	}
+	t.Log("success")
+}
+
+func TestIsDirectInstancePool(t *testing.T) {
+	mockRedisConfig(t)
+	pro.SetString("application.env", "release")
+	pro.SetString("redis.is_direct", "true")
+	if err := Init(); err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(Instance().SetString("k11111", "v1"))
+	cnn := InstancePool().Get()
+	defer cnn.Close()
+	fmt.Println(cnn.Do("get", "k11111"))
+}
+
+func TestInit(t *testing.T) {
+	mockRedisConfig(t)
+	pro.SetString("redis.password", "")
+	if err := Init(); err != nil {
+		t.Fatal(err)
+	}
+	t.Log("success")
+}
+
+func TestPing(t *testing.T) {
+	mockRedisConfig(t)
+	if err := Init(); err != nil {
+		t.Fatal(err)
+	}
+	for x := 0; x < 5; x++ {
+		<-time.After(time.Second * 2)
+		fmt.Println(Instance().GetString("k1"))
+	}
+}

+ 12 - 0
pkg/cache/redis/rediskey.go

@@ -0,0 +1,12 @@
+package redis
+
+import (
+	"fmt"
+)
+
+// key的组成 业务:服务名:唯一标实
+func NewRedisKey(serverKey string) func(interface{}) string {
+	return func(key interface{}) string {
+		return fmt.Sprintf("%s:%s:%v", redisInstance.prefix, serverKey, key)
+	}
+}

+ 43 - 0
pkg/cerror/cerror.go

@@ -0,0 +1,43 @@
+package cerror
+
+import "fmt"
+
+type Cerror interface {
+	Error() string
+	Code() int
+}
+
+type cerror struct {
+	C   int    //code编号
+	Msg string //错误消息
+}
+
+//构造函数
+func NewCerror(code int, msg string) Cerror {
+	return &cerror{
+		C:   code,
+		Msg: msg,
+	}
+}
+
+//返回错误信息
+func (this *cerror) Error() string {
+	return fmt.Sprintf("%s", this.Msg)
+}
+
+func (this *cerror) Code() int {
+	return this.C
+}
+
+// 通用方法
+func NewECerror(code int) func(err error) Cerror {
+	return func(err error) Cerror {
+		return NewCerror(code, err.Error())
+	}
+}
+
+func NewSCerror(code int) func(err string) Cerror {
+	return func(err string) Cerror {
+		return NewCerror(code, err)
+	}
+}

+ 96 - 0
pkg/common/array.go

@@ -0,0 +1,96 @@
+package common
+
+import (
+	"fmt"
+	"strings"
+)
+
+func ArraySearchOfString(item string, arr []string) (bool, int) {
+	for index, val := range arr {
+		if val == item {
+			return true, index
+		}
+	}
+	return false, 0
+}
+
+/**
+ * 通用的InArray(注意如果interface的动态值为指针类型 ==操作符的语义)
+ */
+func InArray(search int, arr []int) bool {
+	for _, val := range arr {
+		if val == search {
+			return true
+		}
+	}
+
+	return false
+}
+
+func InArrayUint64(search uint64, values []uint64) bool {
+	for _, value := range values {
+		if value == search {
+			return true
+		}
+	}
+	return false
+}
+
+/**
+ * 通用的InArray(注意如果interface的动态值为指针类型 ==操作符的语义)
+ */
+func InArrayString(search string, arr []string) bool {
+	for _, value := range arr {
+		if value == search {
+			return true
+		}
+	}
+	return false
+}
+
+/**
+ * 通用的InArray(注意如果interface的动态值为指针类型 ==操作符的语义)
+ */
+func InArrayStringMap(search string, arr map[string]int) bool {
+	for key, _ := range arr {
+		if key == search {
+			return true
+		}
+	}
+
+	return false
+}
+
+func InArrayUin64Map(search uint64, arr map[uint64]int) bool {
+	for key, _ := range arr {
+		if key == search {
+			return true
+		}
+	}
+
+	return false
+}
+
+func InArrayUint64MapV2(search uint64, arr map[uint64]uint64) bool {
+	for key, _ := range arr {
+		if key == search {
+			return true
+		}
+	}
+
+	return false
+}
+
+func InArrayStringMapV2(search string, arr map[string]uint64) bool {
+	for key, _ := range arr {
+		if key == search {
+			return true
+		}
+	}
+
+	return false
+}
+
+func ConvertArray2String(array interface{}) string {
+	return strings.Replace(strings.Trim(fmt.Sprint(array), "[]"), " ", ",", -1)
+}

+ 46 - 0
pkg/common/array_test.go

@@ -0,0 +1,46 @@
+package common
+
+import "testing"
+
+func TestArraySearchOfString(t *testing.T) {
+	t.Log(ArraySearchOfString("111", []string{"1", "11", "111", "1111"}))
+}
+
+func TestInArray(t *testing.T) {
+	t.Log(InArray(1, []int{1, 2, 3, 4}))
+	t.Log(InArray(1, []int{2, 3, 4}))
+}
+
+func TestInArrayUint64(t *testing.T) {
+	t.Log(InArrayUint64(1, nil))
+	t.Log(InArrayUint64(1, []uint64{}))
+	t.Log(InArrayUint64(1, []uint64{1, 2, 3, 4}))
+}
+
+func TestInArrayString(t *testing.T) {
+	t.Log(InArrayString("1", []string{}))
+	t.Log(InArrayString("1", []string{"1"}))
+	t.Log(InArrayString("1", []string{"2"}))
+}
+
+func TestInArrayStringMap(t *testing.T) {
+	t.Log(InArrayStringMap("1", map[string]int{"1": 1}))
+	t.Log(InArrayStringMap("1", nil))
+}
+
+func TestInArrayUin64Map(t *testing.T) {
+	t.Log(InArrayUin64Map(1, map[uint64]int{1: 1}))
+	t.Log(InArrayUin64Map(1, map[uint64]int{2: 2}))
+}
+
+func TestInArrayUint64MapV2(t *testing.T) {
+
+}
+
+func TestInArrayStringMapV2(t *testing.T) {
+
+}
+
+func TestConvertArray2String(t *testing.T) {
+
+}

+ 176 - 0
pkg/common/excel.go

@@ -0,0 +1,176 @@
+package common
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+
+	"github.com/360EntSecGroup-Skylar/excelize/v2"
+)
+
+// #################
+//   excel 对外接口 , demo 可以见test
+// #################
+type Excel interface {
+	WriteSheet(sheetName string, sheetHeader []string, writer RowWriter) error // 写入sheet
+	FlushWrite(writer io.Writer) error                                         // 写出去
+	GetSheetList() []string                                                    // 获取sheet-list
+	ReadSheet(sheetName string) ([][]string, error)                            // 读取sheet
+}
+
+type Row interface {
+	Write(v interface{}) // 每写入一个数据,ColumnIndex()就会+1, 推荐v为字符串类型
+	ColumnIndex() int    // 获取当前列所在的index,从0开始 (索引从0开始,主要是为了和数组对应起来)
+	RowIndex() int       // 获取当前行所在的index,从0开始 (索引从0开始,主要是为了和数组对应起来)
+}
+
+type RowWriter func(r Row) (hasNext bool) // 是否还要写入下一行
+
+// #################
+//   360 excel 实现
+// #################
+type _360Excel struct {
+	*excelize.File
+}
+
+func NewWriter360Excel() Excel {
+	file := excelize.NewFile()
+	return &_360Excel{
+		File: file,
+	}
+}
+
+func NewReader360Excel(reader io.Reader) (Excel, error) {
+	excel := new(_360Excel)
+	r, err := excelize.OpenReader(reader)
+	if err != nil {
+		return nil, err
+	}
+	excel.File = r
+	return excel, nil
+}
+
+type row struct {
+	*excelize.File        // out
+	columnNum      int    // 第几列,0开始
+	rowNum         int    // 第几行,excel的行数,从1开始,如果写了header从2开始
+	offset         int    // 行的偏移量
+	sheetName      string // sheet_name
+	err            error  // 运行中间的异常
+	builder        strings.Builder
+}
+
+func newRow(f *excelize.File, sheetName string, startRowNum int) *row {
+	return &row{
+		File:      f,
+		sheetName: sheetName,
+		rowNum:    startRowNum,
+		offset:    startRowNum, // row偏差
+		columnNum: 1,           // 初始化为1
+	}
+}
+
+func (r *row) Write(v interface{}) {
+	if r.err != nil {
+		return
+	}
+	columnName, err := excelize.ColumnNumberToName(r.columnNum)
+	if err != nil {
+		r.err = err
+		return
+	}
+
+	r.builder.WriteString(columnName)
+	r.builder.WriteString(rowNumberName(r.rowNum))
+	if err := r.SetCellValue(r.sheetName, r.builder.String(), v); err != nil {
+		r.err = err
+		return
+	}
+
+	// end handler
+	r.builder.Reset()
+	r.columnNum++
+}
+
+func (r *row) ColumnIndex() int {
+	return r.columnNum - 1 // 偏差
+}
+
+func (r *row) RowIndex() int {
+	return r.rowNum - r.offset // 偏差
+}
+
+func (r *row) increaseRow() {
+	r.rowNum++
+	r.columnNum = 1
+}
+
+func (e *_360Excel) FlushWrite(writer io.Writer) error {
+	e.DeleteSheet("Sheet1")
+	e.SetActiveSheet(0)
+	return e.Write(writer)
+}
+
+func rowNumberName(rowNum int) string {
+	return strconv.Itoa(rowNum)
+}
+
+// excel的index都是从1开始,和传统计数都有偏差!
+func (e *_360Excel) WriteSheet(sheetName string, sheetHeader []string, writer RowWriter) (err error) {
+	// 抓取recover
+	defer func() {
+		if rerr := recover(); rerr != nil {
+			err = errors.New(fmt.Sprintf("%+v", rerr))
+		}
+	}()
+
+	// 校验,默认会有一个sheet1,所以不能和他使用的一样,因此需要最后删除
+	if sheetName == "" || sheetName == "Sheet1" {
+		return errors.New("sheetName不能为空或为Sheet1")
+	}
+	// 初始化 sheet
+	e.NewSheet(sheetName)
+
+	var (
+		rowNum    = 1                                          // excel的行是从1开始计数
+		hasHeader = sheetHeader != nil && len(sheetHeader) > 0 // 是否有header
+	)
+	// header A1 B1 C1 D1 ... 写header
+	for index, title := range sheetHeader {
+		columnNumberName, err := excelize.ColumnNumberToName(index + 1) // excel列从A开始
+		if err != nil {
+			return err
+		}
+		if err := e.SetCellStr(sheetName, columnNumberName+rowNumberName(rowNum), title); err != nil {
+			return err
+		}
+	}
+	// row ++
+	if hasHeader {
+		rowNum++
+	}
+
+	row := newRow(e.File, sheetName, rowNum)
+	// 循环写
+	for writer(row) {
+		if err := row.err; err != nil {
+			return err
+		}
+		row.increaseRow()
+	}
+	return nil
+}
+
+func (e *_360Excel) GetSheetList() []string {
+	return e.File.GetSheetList()
+}
+
+func (e *_360Excel) ReadSheet(sheetName string) ([][]string, error) {
+	result, err := e.GetRows(sheetName)
+	if err != nil {
+		return nil, err
+	}
+	return result, nil
+}

+ 85 - 0
pkg/common/excel_test.go

@@ -0,0 +1,85 @@
+package common
+
+import (
+	"fmt"
+	"os"
+	"testing"
+)
+
+func TestNewWriter360Excel(t *testing.T) {
+	sheet := [][]string{
+		{"大小", "...", "2020-01-02 10:11:46"},
+		{"a bc", "</.<", "xxxxx"},
+		{"b zcv ", "...", "/..   .."},
+	}
+	excel := NewWriter360Excel()
+	err := excel.WriteSheet("中文1", []string{"中文1", "中文2", "中文3"}, func(r Row) (hasNext bool) {
+		if len(sheet) == 0 {
+			return false
+		}
+		for _, elem := range sheet[r.RowIndex()] {
+			r.Write(elem)
+		}
+		return r.RowIndex() < len(sheet)-1
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = excel.WriteSheet("中文2", []string{"h1", "h2", "h3"}, func(r Row) (hasNext bool) {
+		if len(sheet) == 0 {
+			return false
+		}
+		for _, elem := range sheet[r.RowIndex()] {
+			r.Write(elem)
+		}
+		return r.RowIndex() < len(sheet)-1
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	file, err := os.OpenFile("/data/test/new.xlsx", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer file.Close()
+	err = excel.FlushWrite(file)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestNewReader360Excel(t *testing.T) {
+	file, err := os.OpenFile("/data/test/new.xlsx", os.O_RDONLY, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer file.Close()
+	excel, err := NewReader360Excel(file)
+	if err != nil {
+		t.Fatal(err)
+	}
+	{
+		strings, err := excel.ReadSheet("中文1")
+		if err != nil {
+			t.Fatal(err)
+		}
+		for _, row := range strings {
+			for _, col := range row {
+				fmt.Printf("%v\t", col)
+			}
+			fmt.Println()
+		}
+	}
+	{
+		strings, err := excel.ReadSheet("中文2")
+		if err != nil {
+			t.Fatal(err)
+		}
+		for _, row := range strings {
+			for _, col := range row {
+				fmt.Printf("%v\t", col)
+			}
+			fmt.Println()
+		}
+	}
+}

+ 162 - 0
pkg/common/goroutine.go

@@ -0,0 +1,162 @@
+package common
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"math"
+	"sync"
+	"sync/atomic"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+)
+
+var (
+	GoWithRecover = util.GoWithRecover
+)
+
+/**
+fork join
+todo
+1、添加自定义 fork 方法
+//2、添加限制最大运行的g数量(但是Go里面没有池的概念,需要自己实现,池的效果没有重新开辟一个G合适,优化的方式可以是,保证当前运行中的最大G个数)
+*/
+
+// Job 并发处理,有线程安全问题
+// start , end 属于左闭右开
+// result 子任务返回的结果
+type Job func(ctx context.Context, startIndex, endIndex uint64) (result interface{}, err error)
+
+// JobHandler 串行处理,无线程安全问题
+// data 是每个job返回的结果
+type JobResultHandler func(ctx context.Context, result interface{}) error
+
+// ctx 上下文传递
+// totalCount 全部的任务数
+// forkCount  每个任务切分的数量
+// 所以开启的G的数量差不多是 totalCount/forkCount
+// job 并行处理子任务的逻辑
+// handlerData 子任务结果的处理逻辑
+// maxRunG 最大并发运行的g数量,目前来看 并发 100w个G=4800M内存,所以需要最大并发量限制
+func ParallelJobRun(ctx context.Context, totalCount, forkCount uint64, job Job, jobResultHandler JobResultHandler, maxRunG int64) error {
+	if ctx == nil || totalCount == 0 || forkCount == 0 || job == nil || jobResultHandler == nil {
+		return errors.New("the params has error")
+	}
+	if maxRunG <= 0 {
+		maxRunG = math.MaxInt32
+	}
+	receiveChannel := make(chan interface{}, 0) // 串行处理,所以不需要buffer,设置了buffer可以解决的问题是可以提前释放g
+	errChannel := make(chan error, 0)
+	ctx, cancel := context.WithCancel(ctx)
+	defer func() {
+		cancel()          // 最后关闭,强行通知上下文,关闭处理,防止的问题一个子任务出现问题,需要告知其他的全部出现问题,不做任何处理
+		close(errChannel) // 这个必须关闭 channel,如果在job线程中去关闭,我们的主线程会强行收到两个通知,也就是假如rc收到后return就不会关闭ec,所以job只关闭rc,主程序退出再关闭rc
+	}()
+	go func() {
+		getGNum := func() uint64 {
+			if totalCount%forkCount != 0 {
+				return (totalCount / forkCount) + 1
+			}
+			return totalCount / forkCount
+		}
+		var (
+			gNum = getGNum()
+			wg   = sync.WaitGroup{}
+		)
+		defer func() {
+			wg.Wait()
+			close(receiveChannel)
+		}()
+		jobInfo := jobInfo{
+			ctx:            ctx,
+			wg:             &wg,
+			forkCount:      forkCount,
+			totalCount:     totalCount,
+			errChannel:     errChannel,
+			receiveChannel: receiveChannel,
+			job:            job,
+		}
+		limitGoroutineRunJob(gNum, &jobInfo, maxRunG)
+	}()
+	for {
+		select {
+		case data, isOpen := <-receiveChannel:
+			if !isOpen {
+				return nil
+			}
+			// 同步执行
+			err := jobResultHandler(ctx, data)
+			if err != nil {
+				return err
+			}
+		case err := <-errChannel:
+			if err != nil {
+				return err
+			}
+		case <-ctx.Done():
+			return ctx.Err()
+		}
+	}
+}
+
+func runJob(jobInfo *jobInfo, num uint64) {
+	jobInfo.wg.Add(1)
+	defer func() {
+		if err := recover(); err != nil {
+			jobInfo.errChannel <- errors.New(fmt.Sprintf("panic:\n %v", err))
+		}
+		// 最后关闭wg,保证所有的channel都未关闭,防止panic
+		jobInfo.wg.Done()
+	}()
+	start := num * jobInfo.forkCount
+	end := (num + 1) * jobInfo.forkCount
+	// 左闭右开
+	if end > jobInfo.totalCount {
+		end = jobInfo.totalCount
+	}
+	result, err := jobInfo.job(jobInfo.ctx, start, end)
+	if err != nil {
+		select {
+		case <-jobInfo.ctx.Done():
+		case jobInfo.errChannel <- err:
+		}
+		return
+	}
+	select {
+	case <-jobInfo.ctx.Done():
+	case jobInfo.receiveChannel <- result:
+	}
+}
+
+type jobInfo struct {
+	ctx                   context.Context
+	wg                    *sync.WaitGroup
+	forkCount, totalCount uint64
+	errChannel            chan<- error
+	receiveChannel        chan<- interface{}
+	job                   Job
+}
+
+func limitGoroutineRunJob(gNum uint64, job *jobInfo, maxRunNum int64) {
+	var (
+		init          int64  = 0
+		curRunningJob        = &init
+		cond                 = NewCond()
+		count         uint64 = 0
+	)
+	for ; count < gNum; count++ {
+		if atomic.AddInt64(curRunningJob, 1) > maxRunNum {
+			cond.Wait()
+		}
+		go func(count uint64) {
+			defer func() {
+				atomic.AddInt64(curRunningJob, -1)
+				cond.Notify() // 可以多次notify,所以不需要条件判断
+				//if atomic.AddInt64(curRunningJob, -1) < maxRunNum {
+				//	cond.Notify()
+				//}
+			}()
+			runJob(job, count)
+		}(count)
+	}
+}

+ 66 - 0
pkg/common/goroutine_test.go

@@ -0,0 +1,66 @@
+package common
+
+import (
+	"context"
+	"fmt"
+	"math/rand"
+	"os"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+func TestParallelJobRun(t *testing.T) {
+	fmt.Println(os.Getpid())
+	start := time.Now()
+
+	// 累计计算
+	totalNum := 100*10000*100 + 15
+	// 每一个g 计算
+	eachNum := 100
+	// 累计睡眠
+	var sleepNum uint64 = 0
+	// 结果
+	result := 0
+	err := ParallelJobRun(context.Background(), uint64(totalNum), uint64(eachNum), func(ctx context.Context, start, end uint64) (interface{}, error) {
+		count := 0
+		for ; start < end; start++ {
+			count = count + int(start)
+		}
+		num := rand.Int31n(5)
+		atomic.AddUint64(&sleepNum, uint64(num))
+		time.Sleep(time.Millisecond * time.Duration(num))
+		return count, nil
+	}, func(ctx context.Context, data interface{}) error {
+		cdata := data.(int)
+		result += cdata
+		return nil
+	}, 1000)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Printf("fork-join count: %v,total_sleep_time: %vs,节省时间: %vs , spend : %vs\n", result, sleepNum/1000, float64(sleepNum/1000)-time.Now().Sub(start).Seconds(), time.Now().Sub(start).Seconds())
+
+	start = time.Now()
+	fmt.Printf("check count: %v, spend: %vs\n", count(totalNum), time.Now().Sub(start).Seconds())
+	time.Sleep(time.Second * 1000000)
+}
+
+func count(totalNum int) int {
+	num := 0
+	for x := 0; x < totalNum; x++ {
+		num = num + x
+	}
+	return num
+}
+
+func TestRunJob(t *testing.T) {
+	var jobs []func()
+	for x := 0; x < 12; x++ {
+		jobs = append(jobs, func() {
+			fmt.Println("run ", x)
+			time.Sleep(time.Second * 2)
+		})
+	}
+	//RunJob(jobs, 10)
+}

+ 95 - 0
pkg/common/gpool.go

@@ -0,0 +1,95 @@
+package common
+
+import "sync/atomic"
+
+type GPool interface {
+	AddJob(run Fun, args ...interface{})
+	Stop()
+}
+
+type task struct {
+	args []interface{}
+	run  Fun
+}
+
+type Fun func(args ...interface{})
+
+type gPool struct {
+	runningCount int64
+	maxG         int64
+	taskChannel  chan task
+	cond         Condition
+	down         chan uint8
+	isBlock      bool
+}
+
+func New(maxG int64, isBlock bool) GPool {
+	pool := new(gPool)
+	pool.maxG = maxG
+	pool.cond = NewCond()
+	pool.taskChannel = make(chan task, maxG)
+	pool.down = make(chan uint8, 0)
+	pool.isBlock = isBlock
+	pool.run()
+	return pool
+}
+
+func (g *gPool) AddJob(run Fun, args ...interface{}) {
+	if run == nil {
+		return
+	}
+	task := task{
+		run:  run,
+		args: args,
+	}
+	addJob := func() {
+		select {
+		case <-g.down:
+
+		case g.taskChannel <- task:
+
+		}
+	}
+	if g.isBlock {
+		addJob()
+	} else {
+		go addJob()
+	}
+
+}
+
+func (g *gPool) Stop() {
+	close(g.down)
+	close(g.taskChannel)
+	g.cond.NotifyAll()
+}
+
+func (g *gPool) run() {
+	go func() {
+		defer func() {
+			g.Stop()
+		}()
+		for {
+			select {
+			case job := <-g.taskChannel:
+				if atomic.AddInt64(&g.runningCount, 1) > g.maxG {
+					g.cond.Wait()
+				}
+				select {
+				case <-g.down:
+
+				default:
+					GoWithRecover(func() {
+						defer func() {
+							atomic.AddInt64(&g.runningCount, -1)
+							g.cond.Notify()
+						}()
+						job.run(job.args...)
+					}, nil)
+				}
+			case <-g.down:
+				return
+			}
+		}
+	}()
+}

+ 21 - 0
pkg/common/gpool_test.go

@@ -0,0 +1,21 @@
+package common
+
+import (
+	"fmt"
+	"sync"
+	"testing"
+	"time"
+)
+
+func Test_gPool_AddJob(t *testing.T) {
+	pool := New(10, true)
+	result := sync.Map{}
+	for x := 0; x < 1000; x++ {
+		pool.AddJob(func(args ...interface{}) {
+			result.Store(args[0],nil)
+			fmt.Println("run: ", args)
+			time.Sleep(time.Second)
+		}, x)
+	}
+	time.Sleep(time.Second * 100)
+}

+ 45 - 0
pkg/common/json.go

@@ -0,0 +1,45 @@
+package common
+
+import (
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/json"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/prettyjson"
+)
+
+var (
+	Marshal    = json.Marshal
+	Unmarshal  = json.Unmarshal
+	NewDecoder = json.NewDecoder
+	NewEncoder = json.NewEncoder
+
+	/**
+	格式化JSON
+	*/
+	JsonFormat = prettyjson.Format
+)
+
+/**
+格式化JSON
+*/
+func MarshalJsonFormat(v interface{}) ([]byte, error) {
+	jb, err := Marshal(v)
+	if err != nil {
+		return nil, err
+	}
+	return JsonFormat(jb)
+}
+
+func MarshalJsonFormatIgnoreError(v interface{}) []byte {
+	jb, err := MarshalJsonFormat(v)
+	if err != nil {
+		return []byte{}
+	}
+	return jb
+}
+
+func MarshalJsonIgnoreError(v interface{}) []byte {
+	jb, err := Marshal(v)
+	if err != nil {
+		return []byte{}
+	}
+	return jb
+}

+ 28 - 0
pkg/common/json_test.go

@@ -0,0 +1,28 @@
+package common
+
+import (
+	"fmt"
+	"testing"
+)
+
+var (
+	testObj = map[string]interface{}{"k1": "v1", "k2": "v2", "k3": []int{1, 2, 3, 4}}
+)
+
+func TestMarshalJsonFormat(t *testing.T) {
+	jb, err := MarshalJsonFormat(testObj)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(string(jb))
+}
+
+func TestMarshalJsonFormatIgnoreError(t *testing.T) {
+	jb := MarshalJsonFormatIgnoreError(testObj)
+	fmt.Println(string(jb))
+}
+
+func TestMarshalJsonIgnoreError(t *testing.T) {
+	jb := MarshalJsonIgnoreError(testObj)
+	fmt.Println(string(jb))
+}

+ 23 - 0
pkg/common/num.go

@@ -0,0 +1,23 @@
+package common
+
+import "gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+
+// 数字转化相关方法
+var (
+	Uint642String  = util.Uint642String
+	Int642String   = util.Int642String
+	Int2String     = util.Int2String
+	Float642String = util.Float642String
+
+	String2Uint64  = util.String2Uint64
+	String2Int64   = util.String2Int64
+	String2Int     = util.String2Int
+	String2Float64 = util.String2Float64
+	String2Bool    = util.String2Bool
+)
+
+// 数字相关方法
+var (
+	RandomIn100 = util.Rate100
+	RandomInNum = util.RateInNum
+)

+ 8 - 0
pkg/common/os.go

@@ -0,0 +1,8 @@
+package common
+
+import "gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+
+var (
+	// 获取当前主机的name
+	GetCurrentServerHostName = util.CurrentServerHostName
+)

+ 53 - 0
pkg/common/page.go

@@ -0,0 +1,53 @@
+package common
+
+const (
+	defaultPageSize = 10
+)
+
+type PageRequest struct {
+	Page     int `json:"page"`
+	PageSize int `json:"page_size"`
+}
+
+type PageResponse struct {
+	Page       int   `json:"page"`
+	PageSize   int   `json:"page_size"`
+	TotalCount int64 `json:"total_count"`
+}
+
+type Paginator struct {
+	Page       int
+	PageSize   int
+	LimitStart int
+}
+
+// 根据分页请求,获取分页结果
+func (this PageRequest) GetPaginator() Paginator {
+	return getPaginator(this.Page, this.PageSize)
+}
+
+func getPaginator(page int, pageSize int) Paginator {
+	paginator := Paginator{}
+	if pageSize == 0 {
+		paginator.PageSize = defaultPageSize
+	} else {
+		paginator.PageSize = pageSize
+	}
+	if page == 0 {
+		paginator.Page = 1
+		paginator.LimitStart = 0
+	} else {
+		paginator.Page = page
+		paginator.LimitStart = (paginator.Page - 1) * paginator.PageSize
+	}
+	return paginator
+}
+
+// 根据分页结果,获取分页响应
+func (this Paginator) GetPageResponse(count int64) PageResponse {
+	return PageResponse{
+		Page:       this.Page,
+		PageSize:   this.PageSize,
+		TotalCount: count,
+	}
+}

+ 12 - 0
pkg/common/page_test.go

@@ -0,0 +1,12 @@
+package common
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestPageRequest_GetPaginator(t *testing.T) {
+	request := PageRequest{}
+	fmt.Println(request.GetPaginator())
+	fmt.Println(request.GetPaginator().GetPageResponse(100))
+}

+ 54 - 0
pkg/common/response.go

@@ -0,0 +1,54 @@
+package common
+
+import (
+	"gitea.ckfah.com/cjjy/gocommon/pkg/cerror"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/net/engines"
+)
+
+const (
+	SuccessCode = util.SuccessCode
+	FailCode    = util.FailCode
+	HttpStatus  = util.HttpStatus
+)
+
+func SuccessJson(c *engines.Context, data interface{}) {
+	err := c.JSON(
+		HttpStatus,
+		util.NewSuccessHttpResponse(data),
+	)
+	if err != nil {
+		util.Errorf("[Http-Response] SuccessJson  find err, err: %v", err)
+	}
+}
+
+func FailJson(c *engines.Context, err cerror.Cerror) {
+	rerr := c.JSON(
+		HttpStatus,
+		util.NewFailHttpResponse(err),
+	)
+	if rerr != nil {
+		util.Errorf("[Http-Response] FailJson  find err, err: %v", err)
+	}
+}
+
+func FailJsonWithMsg(c *engines.Context, message string) {
+	err := c.JSON(
+		HttpStatus,
+		util.NewFailMessageHttpResponse(message),
+	)
+	if err != nil {
+		util.Errorf("[Http-Response] FailJsonWithMsg  find err, err: %v", err)
+	}
+}
+
+/**
+MiddlewareBind 中绑定的数据,key(header)->value(header_data)
+*/
+func GetMiddlewareBindHeaderData(c *engines.Context) map[string]string {
+	result, _ := c.Value(util.HeaderData).(map[string]string)
+	if result == nil {
+		return map[string]string{}
+	}
+	return result
+}

+ 13 - 0
pkg/common/string.go

@@ -0,0 +1,13 @@
+package common
+
+import (
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+)
+
+var (
+	SplitIgnoreSpace = util.SplitIgnoreSpace
+	ToString         = util.ToString
+	UrlDecode        = util.UrlDecode
+	UrlEncode        = util.UrlEncode
+	String2Md5       = util.String2Md5
+)

+ 20 - 0
pkg/common/string_test.go

@@ -0,0 +1,20 @@
+package common
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestUrlDecode(t *testing.T) {
+	fmt.Println(UrlDecode("hello%E4%BD%A0%E5%A5%BD"))
+	fmt.Println(UrlDecode("hello你好"))
+}
+
+func TestString2Md5(t *testing.T) {
+	fmt.Println(String2Md5("11111112321"))
+	fmt.Println(String2Md5("11111112321"))
+}
+
+func TestUrlEncode(t *testing.T) {
+	fmt.Println(UrlEncode(UrlDecode("hello%E4%BD%A0%E5%A5%BD")))
+}

+ 31 - 0
pkg/common/sync.go

@@ -0,0 +1,31 @@
+package common
+
+import "sync"
+
+type Condition interface {
+	Wait()
+	Notify()
+	NotifyAll()
+}
+
+type condition struct {
+	sync.Mutex
+	c *sync.Cond
+}
+
+func NewCond() Condition {
+	c := new(condition)
+	c.c = sync.NewCond(c)
+	return c
+}
+func (c *condition) Wait() {
+	c.Lock()
+	defer c.Unlock()
+	c.c.Wait()
+}
+func (c *condition) Notify() {
+	c.c.Signal()
+}
+func (c *condition) NotifyAll() {
+	c.c.Broadcast()
+}

+ 82 - 0
pkg/common/time.go

@@ -0,0 +1,82 @@
+package common
+
+import (
+	"time"
+)
+
+const (
+	zeroDateStr   = "0000-00-00 00:00:00"
+	FromatTime_V1 = "2006-01-02 15:04:05"
+	FromatTime_V2 = "2006-01-02"
+	FromatTime_V3 = "15:04:05"
+	FromatTime_V4 = "2006-01"
+)
+
+// 将时间转换为时间搓
+// 时间搓0为:1970年1月1日0时0分0秒,业务中只有数据库为0000-00-00 00:00:00,是一个绝对值很大的负数,小于0则直接返回0就可以。
+func Time2Second(_time time.Time) int64 {
+	sec := _time.Unix()
+	if sec < 0 {
+		return 0
+	}
+	return sec
+}
+
+// 转换成标准格式的时间
+func Time2StdString(_time time.Time) string {
+	if TimeIsZero(_time) {
+		return zeroDateStr
+	}
+	return _time.Format(FromatTime_V1)
+}
+
+// 获取昨天日期
+func GetTimeYesterday() time.Time {
+	return time.Now().AddDate(0, 0, -1)
+}
+
+// 将s转换成时间
+func Second2Time(timestamp int64) time.Time {
+	return time.Unix(timestamp, 0)
+}
+
+// 获取当前的s
+func GetCurrentSeconds() int64 {
+	return time.Now().Unix()
+}
+
+// orm返回的时间体,时间搓是否为0,time直接判断isZero是不对的,需要转换成utc时区
+func TimeIsZero(t time.Time) bool {
+	t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC)
+	return t.IsZero()
+}
+
+// 获取当前时间开始的时间点,比如 2020-1-2 11:01:00 ——> 2020-1-2 00:00:00
+func GetTimeStart(_time time.Time) time.Time {
+	year, month, day := _time.Date()
+	return time.Date(year, month, day, 0, 0, 0, 0, time.Local)
+}
+
+func StringStd2Time(time_ string) time.Time {
+	res, err := time.ParseInLocation(FromatTime_V1, time_, time.Local)
+	if err != nil {
+		return _zero // 生成个默认的,降级
+	}
+	return res
+}
+
+func GetTomorrow(time_ time.Time) time.Time {
+	year, month, day := time_.Date()
+	data := time.Date(year, month, day+1, 0, 0, 0, 0, time.Local)
+	return data
+}
+
+func GetYesterday(time_ time.Time) time.Time {
+	year, month, day := time_.Date()
+	data := time.Date(year, month, day-1, 0, 0, 0, 0, time.Local)
+	return data
+}
+
+func NewTimeWithYearAndMonthAndDay(year int, month uint8, day int) time.Time {
+	return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
+}

+ 90 - 0
pkg/common/time_struct.go

@@ -0,0 +1,90 @@
+package common
+
+import (
+	"errors"
+	"time"
+)
+
+var (
+	_zero              = time.Time{}
+	yearError          = errors.New("年份不能为负数")
+	monthError         = errors.New("月份不能大于12或小于1")
+	dayError           = errors.New("日期不能大于31或小于1")
+	outOfBindTimeError = errors.New("查询时间大于当前系统时间")
+)
+
+type TimeDay struct {
+	Year  int   `json:"year"`  // 年
+	Month uint8 `json:"month"` // 月份
+	Day   int   `json:"day"`   // 日期
+}
+
+// 根据日期获取每月的开始和结束时间
+/**
+规则:1、月份大于当前时间抛出异常
+     2、当前时间属于当前月份,最后时间返回当前时间的次日0点
+	 3、查询月份小于当前月,则返回正常区间
+	 4、格式不符合抛出异常,比如年份=0或者<0,月份是0或负数或者大于12
+*/
+func (day TimeDay) GetMonthWithError() (start time.Time, end time.Time, err error) {
+	if day.Year <= 0 {
+		return _zero, _zero, yearError
+	}
+	if day.Month < 1 || day.Month > 12 {
+		return _zero, _zero, monthError
+	}
+	now := time.Now()
+
+	// 月份开始时间,大于当前时间,直接异常
+	dateStart := time.Date(day.Year, time.Month(day.Month), 1, 0, 0, 0, 0, time.Local)
+	if dateStart.After(now) {
+		return _zero, _zero, outOfBindTimeError
+	}
+
+	// 月份结束时间,如果大于当前时间,取当前时间的 day+1
+	dateEnd := time.Date(day.Year, time.Month(day.Month+1), 1, 0, 0, 0, 0, time.Local)
+	if dateEnd.After(now) {
+		year, month, day := now.Date()
+		return dateStart, time.Date(year, month, day+1, 0, 0, 0, 0, time.Local), nil
+	}
+	// 正常结束
+	return dateStart, dateEnd, nil
+}
+
+func (day TimeDay) GetMonthSecWithError() (start int64, end int64, err error) {
+	s, e, err := day.GetMonthWithError()
+	if err != nil {
+		return 0, 0, err
+	}
+	return Time2Second(s), Time2Second(e), nil
+}
+
+// 根据日期获取 每日的开始和结束时间
+/**
+规则:1、规则不符合抛出异常
+	2、Mysql的Between属于左闭右闭,所以保证准确性应该是 2020-09-03 00:00:00 和  2020-09-03 23:59:59
+      这里推荐使用Between函数,因为between函数最终解析成了 2020-09-03 00:00:00 <= x <= 2020-09-03 23:59:59
+      2020-09-03 返回  2020-09-03 00:00:00 和  2020-09-03 23:59:59
+*/
+func (day TimeDay) GetDayWithError() (start time.Time, end time.Time, err error) {
+	if day.Year <= 0 {
+		return _zero, _zero, yearError
+	}
+	if day.Month < 1 || day.Month > 12 {
+		return _zero, _zero, monthError
+	}
+	if day.Day < 1 || day.Day > 31 {
+		return _zero, _zero, dayError
+	}
+	s := time.Date(day.Year, time.Month(day.Month), day.Day, 0, 0, 0, 0, time.Local)
+	e := GetTomorrow(s).Add(-time.Second)
+	return s, e, nil
+}
+
+func (day TimeDay) GetDaySecWithError() (start int64, end int64, err error) {
+	s, e, err := day.GetDayWithError()
+	if err != nil {
+		return 0, 0, err
+	}
+	return Time2Second(s), Time2Second(e), nil
+}

+ 59 - 0
pkg/common/time_struct_test.go

@@ -0,0 +1,59 @@
+package common
+
+import (
+	"testing"
+	"time"
+)
+
+func TestTimeDay_GetMonthWithError(t *testing.T) {
+	{
+		day := TimeDay{Year: 2020, Month: 10}
+		start, end, err := day.GetMonthWithError()
+		if err != nil {
+			t.Fatal(err)
+		}
+		t.Log(start.Format(FromatTime_V1))
+		t.Log(end.Format(FromatTime_V1))
+		//    time_struct_test.go:13: 2020-10-01 00:00:00
+		//    time_struct_test.go:14: 2020-11-01 00:00:00
+	}
+	{
+		day := TimeDay{Year: 2020, Month: 11}
+		start, end, err := day.GetMonthWithError()
+		if err != nil {
+			t.Fatal(err)
+		}
+		t.Log(time.Now().Format(FromatTime_V1))
+		t.Log(start.Format(FromatTime_V1))
+		t.Log(end.Format(FromatTime_V1))
+		//    time_struct_test.go:26: 2020-11-23 21:33:32
+		//    time_struct_test.go:27: 2020-11-01 00:00:00
+		//    time_struct_test.go:28: 2020-11-24 00:00:00
+	}
+}
+
+func TestTimeDay_GetDayWithError(t *testing.T) {
+	{
+		day := TimeDay{Year: 2020, Month: 10, Day: 1}
+		start, end, err := day.GetDayWithError()
+		if err != nil {
+			t.Fatal(err)
+		}
+		t.Log(start.Format(FromatTime_V1))
+		t.Log(end.Format(FromatTime_V1))
+		//    time_struct_test.go:42: 2020-10-01 00:00:00
+		//    time_struct_test.go:43: 2020-10-01 23:59:59
+	}
+	{
+		t.Log(time.Now().Format(FromatTime_V1))
+		day := TimeDay{Year: 2020, Month: 12, Day: 31}
+		start, end, err := day.GetMonthWithError()
+		if err != nil {
+			t.Fatal(err)
+		}
+		t.Log(start.Format(FromatTime_V1))
+		t.Log(end.Format(FromatTime_V1))
+		//    time_struct_test.go:48: 2020-11-23 21:36:18
+		//    time_struct_test.go:52: 查询时间大于当前系统时间
+	}
+}

+ 52 - 0
pkg/common/time_test.go

@@ -0,0 +1,52 @@
+package common
+
+import (
+	"testing"
+	"time"
+)
+
+func TestTime2Second(t *testing.T) {
+	t.Log(Time2Second(time.Now()))
+}
+
+func TestTime2StdString(t *testing.T) {
+	t.Log(Time2StdString(time.Now()))
+}
+
+func TestGetTimeYesterday(t *testing.T) {
+	t.Log(GetTimeYesterday().Format(FromatTime_V1))
+}
+
+func TestSecond2Time(t *testing.T) {
+	t.Log(Second2Time(GetCurrentSeconds()).Format(FromatTime_V1))
+}
+
+func TestGetCurrentSeconds(t *testing.T) {
+	t.Log(GetCurrentSeconds())
+}
+
+func TestTimeIsZero(t *testing.T) {
+	t.Log(TimeIsZero(time.Time{}))
+}
+
+func TestGetTimeStart(t *testing.T) {
+	t.Log(GetTimeStart(time.Now()).Format(FromatTime_V1))
+}
+
+func TestStringStd2Time(t *testing.T) {
+	t.Log(StringStd2Time("2020-10-11 10:24:00").Format(FromatTime_V1))
+}
+
+// 11-24
+func TestGetTomorrow(t *testing.T) {
+	t.Log(GetTomorrow(time.Now()).Format(FromatTime_V1))
+}
+
+// 11-24
+func TestGetYesterday(t *testing.T) {
+	t.Log(GetYesterday(time.Now()).Format(FromatTime_V1))
+}
+
+func TestNewTimeWithYearAndMonthAndDay(t *testing.T) {
+	t.Log(NewTimeWithYearAndMonthAndDay(2020, 10, 35).Format(FromatTime_V1))
+}

+ 34 - 0
pkg/conf/common.go

@@ -0,0 +1,34 @@
+package conf
+
+import (
+	"errors"
+	"fmt"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/constant"
+)
+
+func SetAndAssertNil(config map[string]string, parent, str string, defaultValue ...string) error {
+	if value := GetString(parent, str, defaultValue...); value != "" {
+		config[str] = value
+		return nil
+	}
+	return errors.New(fmt.Sprintf("config %s.%s is empty string or not found", parent, str))
+}
+
+const (
+	EnvDebug   = "debug"
+	EnvTest    = "test"
+	EnvRelease = "release"
+)
+
+func GetEnv() string {
+	return GetString(constant.ApplicationName, constant.EnvName)
+}
+
+func GetProjectName() string {
+	return GetString(constant.ApplicationName, constant.ProjectName)
+}
+
+func GetAppPort() uint64 {
+	return GetUint64(constant.ApplicationName, constant.PortName)
+}

+ 169 - 0
pkg/conf/config.go

@@ -0,0 +1,169 @@
+package conf
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf/goconfig"
+	apollo2 "gitea.ckfah.com/cjjy/gocommon/pkg/conf/remote/apollo"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+)
+
+type DriverType uint8
+
+const (
+	Local DriverType = iota + 1
+	Apollo
+	Nacos
+)
+
+var (
+	driverTypeMap = map[DriverType]string{
+		Local:  "Local",
+		Apollo: "Apollo",
+		Nacos:  "Nacos",
+	}
+)
+
+var (
+	rootPath string
+
+	config *goconfig.ConfigFile
+
+	confMutex sync.RWMutex
+
+	mainIniPath = "/config/env.ini"
+
+	globalDriver = Local // 全局的driver 类型
+
+	thirdLogSwitch bool // 第三方日志开关:true 开启,false 关闭;没有配置则默认开启; 启动时才会获取到值
+)
+
+func getConfig() *goconfig.ConfigFile {
+	confMutex.RLock()
+	defer confMutex.RUnlock()
+	return config
+}
+
+func reloadConfig() {
+	confMutex.Lock()
+	defer confMutex.Unlock()
+	var err error
+	configPath := rootPath + mainIniPath
+	config, err = goconfig.LoadConfigFile(configPath)
+	if err != nil {
+		fmt.Println("conf:reload config file, error:", err)
+		return
+	}
+	driver := config.MustValue("conf", "driver", "file")
+	if driver == "apollo" {
+		//apollo.Start()
+		config.SetValue("include_files", "path", apollo2.GetConfigPath())
+	}
+	if err = loadIncludeFiles(config); err != nil {
+		fmt.Println("conf:reload files include files error:", err)
+		return
+	}
+}
+
+func Init() error {
+	var err error
+	rootPath = util.GetRootPath()
+	config, err = newConfig()
+	if err != nil {
+		return err
+	}
+	driver := config.MustValue("conf", "driver", "file")
+	if driver == "apollo" {
+		globalDriver = Apollo
+		interval := config.MustInt("conf", "remote_refresh_interval", 30)
+		apollo2.SetRefreshInterval(interval)
+		apollo2.Start()
+		config.SetValue("include_files", "path", apollo2.GetConfigPath())
+		go listen(apollo2.Watch())
+	}
+	if driver == "nacos" {
+		globalDriver = Nacos
+		if err := initNacosConfig(); err != nil {
+			return err
+		}
+	}
+	if err := loadIncludeFiles(config); err != nil {
+		return err
+	}
+	err = configMustInit()
+	if err != nil {
+		return err
+	}
+
+	// 设置第三方日志开关
+	setThirdLogSwitch()
+
+	util.Debugf("Config load config success, driver=%v, env=%v, project_name=%v, port=%v", driverTypeMap[globalDriver], GetEnv(), GetProjectName(), GetAppPort())
+	return nil
+}
+
+func listen(watch <-chan struct{}) {
+	for {
+		if _, ok := <-watch; ok {
+			reloadConfig()
+		}
+	}
+}
+
+func newConfig() (*goconfig.ConfigFile, error) {
+	var (
+		err       error
+		newConfig *goconfig.ConfigFile
+	)
+	configPath := rootPath + mainIniPath
+	if !fileExist(configPath) {
+		curDir, _ := os.Getwd()
+		pos := strings.LastIndex(curDir, "src")
+		if pos == -1 {
+			panic("conf:can't find " + configPath)
+		}
+		rootPath = curDir[:pos]
+		configPath = rootPath + mainIniPath
+	}
+	newConfig, err = goconfig.LoadConfigFile(configPath)
+	if err != nil {
+		return nil, err
+	}
+	return newConfig, nil
+}
+
+func loadIncludeFiles(config *goconfig.ConfigFile) error {
+	includeFile := config.MustValue("include_files", "path", "")
+	if includeFile != "" {
+		includeFiles := strings.Split(includeFile, ",")
+		incFiles := make([]string, len(includeFiles))
+		for i, incFile := range includeFiles {
+			incFiles[i] = incFile
+		}
+		return config.AppendFiles(incFiles...)
+	}
+	return nil
+}
+
+// fileExist 检查文件或目录是否存在
+// 如果由 filename 指定的文件或目录存在则返回 true,否则返回 false
+func fileExist(filename string) bool {
+	_, err := os.Stat(filename)
+	return err == nil || os.IsExist(err)
+}
+
+func setThirdLogSwitch() {
+	if GetString("log", "third_log_switch") == "off" {
+		thirdLogSwitch = false
+	} else {
+		thirdLogSwitch = true
+	}
+}
+
+func GetThirdLogWitch() bool {
+	return thirdLogSwitch
+}

+ 555 - 0
pkg/conf/goconfig/conf.go

@@ -0,0 +1,555 @@
+// Copyright 2013 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package goconfig is a fully functional and comments-support configuration file(.ini) parser.
+package goconfig
+
+import (
+	"fmt"
+	"regexp"
+	"runtime"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+const (
+	// Default section name.
+	DEFAULT_SECTION = "DEFAULT"
+	// Maximum allowed depth when recursively substituing variable names.
+	_DEPTH_VALUES = 200
+)
+
+type ParseError int
+
+const (
+	ERR_SECTION_NOT_FOUND ParseError = iota + 1
+	ERR_KEY_NOT_FOUND
+	ERR_BLANK_SECTION_NAME
+	ERR_COULD_NOT_PARSE
+)
+
+var LineBreak = "\n"
+
+// Variable regexp pattern: %(variable)s
+var varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
+
+func init() {
+	if runtime.GOOS == "windows" {
+		LineBreak = "\r\n"
+	}
+}
+
+// A ConfigFile represents a INI formar configuration file.
+type ConfigFile struct {
+	lock      sync.RWMutex                 // Go map is not safe.
+	fileNames []string                     // Support mutil-files.
+	data      map[string]map[string]string // Section -> key : value
+
+	// Lists can keep sections and keys in order.
+	sectionList []string            // Section name list.
+	keyList     map[string][]string // Section -> Key name list
+
+	sectionComments map[string]string            // Sections comments.
+	keyComments     map[string]map[string]string // Keys comments.
+	BlockMode       bool                         // Indicates whether use lock or not.
+}
+
+// newConfigFile creates an empty configuration representation.
+func newConfigFile(fileNames []string) *ConfigFile {
+	c := new(ConfigFile)
+	c.fileNames = fileNames
+	c.data = make(map[string]map[string]string)
+	c.keyList = make(map[string][]string)
+	c.sectionComments = make(map[string]string)
+	c.keyComments = make(map[string]map[string]string)
+	c.BlockMode = true
+	return c
+}
+
+// SetValue adds a new section-key-value to the configuration.
+// It returns true if the key and value were inserted,
+// or returns false if the value was overwritten.
+// If the section does not exist in advance, it will be created.
+func (c *ConfigFile) SetValue(section, key, value string) bool {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+	if len(key) == 0 {
+		return false
+	}
+
+	if c.BlockMode {
+		c.lock.Lock()
+		defer c.lock.Unlock()
+	}
+
+	// Check if section exists.
+	if _, ok := c.data[section]; !ok {
+		// Execute add operation.
+		c.data[section] = make(map[string]string)
+		// Append section to list.
+		c.sectionList = append(c.sectionList, section)
+	}
+
+	// Check if key exists.
+	_, ok := c.data[section][key]
+	c.data[section][key] = value
+	if !ok {
+		// If not exists, append to key list.
+		c.keyList[section] = append(c.keyList[section], key)
+	}
+	return !ok
+}
+
+// DeleteKey deletes the key in given section.
+// It returns true if the key was deleted,
+// or returns false if the section or key didn't exist.
+func (c *ConfigFile) DeleteKey(section, key string) bool {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	if c.BlockMode {
+		c.lock.Lock()
+		defer c.lock.Unlock()
+	}
+
+	// Check if section exists.
+	if _, ok := c.data[section]; !ok {
+		return false
+	}
+
+	// Check if key exists.
+	if _, ok := c.data[section][key]; ok {
+		delete(c.data[section], key)
+		// Remove comments of key.
+		c.SetKeyComments(section, key, "")
+		// Get index of key.
+		i := 0
+		for _, keyName := range c.keyList[section] {
+			if keyName == key {
+				break
+			}
+			i++
+		}
+		// Remove from key list.
+		c.keyList[section] = append(c.keyList[section][:i], c.keyList[section][i+1:]...)
+		return true
+	}
+	return false
+}
+
+// GetValue returns the value of key available in the given section.
+// If the value needs to be unfolded
+// (see e.g. %(google)s example in the GoConfig_test.go),
+// then String does this unfolding automatically, up to
+// _DEPTH_VALUES number of iterations.
+// It returns an error and empty string value if the section does not exist,
+// or key does not exist in DEFAULT and current sections.
+func (c *ConfigFile) GetValue(section, key string) (string, error) {
+	if c.BlockMode {
+		c.lock.RLock()
+		defer c.lock.RUnlock()
+	}
+
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	// Check if section exists
+	if _, ok := c.data[section]; !ok {
+		// Section does not exist.
+		return "", getError{ERR_SECTION_NOT_FOUND, section}
+	}
+
+	// Section exists.
+	// Check if key exists or empty value.
+	value, ok := c.data[section][key]
+	if !ok {
+		// Check if it is a sub-section.
+		if i := strings.LastIndex(section, "."); i > -1 {
+			return c.GetValue(section[:i], key)
+		}
+
+		// Return empty value.
+		return "", getError{ERR_KEY_NOT_FOUND, key}
+	}
+
+	// Key exists.
+	var i int
+	for i = 0; i < _DEPTH_VALUES; i++ {
+		vr := varPattern.FindString(value)
+		if len(vr) == 0 {
+			break
+		}
+
+		// Take off leading '%(' and trailing ')s'.
+		noption := strings.TrimLeft(vr, "%(")
+		noption = strings.TrimRight(noption, ")s")
+
+		// Search variable in default section.
+		nvalue, err := c.GetValue(DEFAULT_SECTION, noption)
+		if err != nil && section != DEFAULT_SECTION {
+			// Search in the same section.
+			if _, ok := c.data[section][noption]; ok {
+				nvalue = c.data[section][noption]
+			}
+		}
+
+		// Substitute by new value and take off leading '%(' and trailing ')s'.
+		value = strings.Replace(value, vr, nvalue, -1)
+	}
+	return value, nil
+}
+
+// Bool returns bool type value.
+func (c *ConfigFile) Bool(section, key string) (bool, error) {
+	value, err := c.GetValue(section, key)
+	if err != nil {
+		return false, err
+	}
+	return strconv.ParseBool(value)
+}
+
+// Float64 returns float64 type value.
+func (c *ConfigFile) Float64(section, key string) (float64, error) {
+	value, err := c.GetValue(section, key)
+	if err != nil {
+		return 0.0, err
+	}
+	return strconv.ParseFloat(value, 64)
+}
+
+// Int returns int type value.
+func (c *ConfigFile) Int(section, key string) (int, error) {
+	value, err := c.GetValue(section, key)
+	if err != nil {
+		return 0, err
+	}
+	return strconv.Atoi(value)
+}
+
+// Int64 returns int64 type value.
+func (c *ConfigFile) Int64(section, key string) (int64, error) {
+	value, err := c.GetValue(section, key)
+	if err != nil {
+		return 0, err
+	}
+	return strconv.ParseInt(value, 10, 64)
+}
+
+// MustValue always returns value without error.
+// It returns empty string if error occurs, or the default value if given.
+func (c *ConfigFile) MustValue(section, key string, defaultVal ...string) string {
+	val, err := c.GetValue(section, key)
+	if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustValueSet always returns value without error,
+// It returns empty string if error occurs, or the default value if given,
+// and a bool value indicates whether default value is returned.
+func (c *ConfigFile) MustValueSet(section, key string, defaultVal ...string) (string, bool) {
+	val, err := c.GetValue(section, key)
+	if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
+		c.SetValue(section, key, defaultVal[0])
+		return defaultVal[0], true
+	}
+	return val, false
+}
+
+// MustValueRange always returns value without error,
+// it returns default value if error occurs or doesn't fit into range.
+func (c *ConfigFile) MustValueRange(section, key, defaultVal string, candidates []string) string {
+	val, err := c.GetValue(section, key)
+	if err != nil || len(val) == 0 {
+		return defaultVal
+	}
+
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// MustValueArray always returns value array without error,
+// it returns empty array if error occurs, split by delimiter otherwise.
+func (c *ConfigFile) MustValueArray(section, key, delim string) []string {
+	val, err := c.GetValue(section, key)
+	if err != nil || len(val) == 0 {
+		return []string{}
+	}
+
+	vals := strings.Split(val, delim)
+	for i := range vals {
+		vals[i] = strings.TrimSpace(vals[i])
+	}
+	return vals
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (c *ConfigFile) MustBool(section, key string, defaultVal ...bool) bool {
+	val, err := c.Bool(section, key)
+	if len(defaultVal) > 0 && err != nil {
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (c *ConfigFile) MustFloat64(section, key string, defaultVal ...float64) float64 {
+	value, err := c.Float64(section, key)
+	if len(defaultVal) > 0 && err != nil {
+		return defaultVal[0]
+	}
+	return value
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (c *ConfigFile) MustInt(section, key string, defaultVal ...int) int {
+	value, err := c.Int(section, key)
+	if len(defaultVal) > 0 && err != nil {
+		return defaultVal[0]
+	}
+	return value
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (c *ConfigFile) MustInt64(section, key string, defaultVal ...int64) int64 {
+	value, err := c.Int64(section, key)
+	if len(defaultVal) > 0 && err != nil {
+		return defaultVal[0]
+	}
+	return value
+}
+
+// GetSectionList returns the list of all sections
+// in the same order in the file.
+func (c *ConfigFile) GetSectionList() []string {
+	list := make([]string, len(c.sectionList))
+	copy(list, c.sectionList)
+	return list
+}
+
+// GetKeyList returns the list of all keys in give section
+// in the same order in the file.
+// It returns nil if given section does not exist.
+func (c *ConfigFile) GetKeyList(section string) []string {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	if c.BlockMode {
+		c.lock.RLock()
+		defer c.lock.RUnlock()
+	}
+
+	// Check if section exists.
+	if _, ok := c.data[section]; !ok {
+		return nil
+	}
+
+	// Non-default section has a blank key as section keeper.
+	list := make([]string, 0, len(c.keyList[section]))
+	for _, key := range c.keyList[section] {
+		if key != " " {
+			list = append(list, key)
+		}
+	}
+	return list
+}
+
+// DeleteSection deletes the entire section by given name.
+// It returns true if the section was deleted, and false if the section didn't exist.
+func (c *ConfigFile) DeleteSection(section string) bool {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	if c.BlockMode {
+		c.lock.Lock()
+		defer c.lock.Unlock()
+	}
+
+	// Check if section exists.
+	if _, ok := c.data[section]; !ok {
+		return false
+	}
+
+	delete(c.data, section)
+	// Remove comments of section.
+	c.SetSectionComments(section, "")
+	// Get index of section.
+	i := 0
+	for _, secName := range c.sectionList {
+		if secName == section {
+			break
+		}
+		i++
+	}
+	// Remove from section and key list.
+	c.sectionList = append(c.sectionList[:i], c.sectionList[i+1:]...)
+	delete(c.keyList, section)
+	return true
+}
+
+// GetSection returns key-value pairs in given section.
+// If section does not exist, returns nil and error.
+func (c *ConfigFile) GetSection(section string) (map[string]string, error) {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	if c.BlockMode {
+		c.lock.Lock()
+		defer c.lock.Unlock()
+	}
+
+	// Check if section exists.
+	if _, ok := c.data[section]; !ok {
+		// Section does not exist.
+		return nil, getError{ERR_SECTION_NOT_FOUND, section}
+	}
+
+	// Remove pre-defined key.
+	secMap := c.data[section]
+	delete(c.data[section], " ")
+
+	// Section exists.
+	return secMap, nil
+}
+
+// SetSectionComments adds new section comments to the configuration.
+// If comments are empty(0 length), it will remove its section comments!
+// It returns true if the comments were inserted or removed,
+// or returns false if the comments were overwritten.
+func (c *ConfigFile) SetSectionComments(section, comments string) bool {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	if len(comments) == 0 {
+		if _, ok := c.sectionComments[section]; ok {
+			delete(c.sectionComments, section)
+		}
+
+		// Not exists can be seen as remove.
+		return true
+	}
+
+	// Check if comments exists.
+	_, ok := c.sectionComments[section]
+	if comments[0] != '#' && comments[0] != ';' {
+		comments = "; " + comments
+	}
+	c.sectionComments[section] = comments
+	return !ok
+}
+
+// SetKeyComments adds new section-key comments to the configuration.
+// If comments are empty(0 length), it will remove its section-key comments!
+// It returns true if the comments were inserted or removed,
+// or returns false if the comments were overwritten.
+// If the section does not exist in advance, it is created.
+func (c *ConfigFile) SetKeyComments(section, key, comments string) bool {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	// Check if section exists.
+	if _, ok := c.keyComments[section]; ok {
+		if len(comments) == 0 {
+			if _, ok := c.keyComments[section][key]; ok {
+				delete(c.keyComments[section], key)
+			}
+
+			// Not exists can be seen as remove.
+			return true
+		}
+	} else {
+		if len(comments) == 0 {
+			// Not exists can be seen as remove.
+			return true
+		} else {
+			// Execute add operation.
+			c.keyComments[section] = make(map[string]string)
+		}
+	}
+
+	// Check if key exists.
+	_, ok := c.keyComments[section][key]
+	if comments[0] != '#' && comments[0] != ';' {
+		comments = "; " + comments
+	}
+	c.keyComments[section][key] = comments
+	return !ok
+}
+
+// GetSectionComments returns the comments in the given section.
+// It returns an empty string(0 length) if the comments do not exist.
+func (c *ConfigFile) GetSectionComments(section string) (comments string) {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+	return c.sectionComments[section]
+}
+
+// GetKeyComments returns the comments of key in the given section.
+// It returns an empty string(0 length) if the comments do not exist.
+func (c *ConfigFile) GetKeyComments(section, key string) (comments string) {
+	// Blank section name represents DEFAULT section.
+	if len(section) == 0 {
+		section = DEFAULT_SECTION
+	}
+
+	if _, ok := c.keyComments[section]; ok {
+		return c.keyComments[section][key]
+	}
+	return ""
+}
+
+// getError occurs when get value in configuration file with invalid parameter.
+type getError struct {
+	Reason ParseError
+	Name   string
+}
+
+// Error implements Error interface.
+func (err getError) Error() string {
+	switch err.Reason {
+	case ERR_SECTION_NOT_FOUND:
+		return fmt.Sprintf("section '%s' not found", err.Name)
+	case ERR_KEY_NOT_FOUND:
+		return fmt.Sprintf("key '%s' not found", err.Name)
+	}
+	return "invalid get error"
+}

+ 294 - 0
pkg/conf/goconfig/read.go

@@ -0,0 +1,294 @@
+// Copyright 2013 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package goconfig
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+	"time"
+)
+
+// Read reads an io.Reader and returns a configuration representation.
+// This representation can be queried with GetValue.
+func (c *ConfigFile) read(reader io.Reader) (err error) {
+	buf := bufio.NewReader(reader)
+
+	// Handle BOM-UTF8.
+	// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
+	mask, err := buf.Peek(3)
+	if err == nil && len(mask) >= 3 &&
+		mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
+		buf.Read(mask)
+	}
+
+	count := 1 // Counter for auto increment.
+	// Current section name.
+	section := DEFAULT_SECTION
+	var comments string
+	// Parse line-by-line
+	for {
+		line, err := buf.ReadString('\n')
+		line = strings.TrimSpace(line)
+		lineLengh := len(line) //[SWH|+]
+		if err != nil {
+			if err != io.EOF {
+				return err
+			}
+
+			// Reached end of file, if nothing to read then break,
+			// otherwise handle the last line.
+			if lineLengh == 0 {
+				break
+			}
+		}
+
+		// switch written for readability (not performance)
+		switch {
+		case lineLengh == 0: // Empty line
+			continue
+		case line[0] == '#' || line[0] == ';': // Comment
+			// Append comments
+			if len(comments) == 0 {
+				comments = line
+			} else {
+				comments += LineBreak + line
+			}
+			continue
+		case line[0] == '[' && line[lineLengh-1] == ']': // New section.
+			// Get section name.
+			section = strings.TrimSpace(line[1 : lineLengh-1])
+			// Set section comments and empty if it has comments.
+			if len(comments) > 0 {
+				c.SetSectionComments(section, comments)
+				comments = ""
+			}
+			// Make section exist even though it does not have any key.
+			c.SetValue(section, " ", " ")
+			// Reset counter.
+			count = 1
+			continue
+		case section == "": // No section defined so far
+			return readError{ERR_BLANK_SECTION_NAME, line}
+		default: // Other alternatives
+			var (
+				i        int
+				keyQuote string
+				key      string
+				valQuote string
+				value    string
+			)
+			//[SWH|+]:支持引号包围起来的字串
+			if line[0] == '"' {
+				if lineLengh >= 6 && line[0:3] == `"""` {
+					keyQuote = `"""`
+				} else {
+					keyQuote = `"`
+				}
+			} else if line[0] == '`' {
+				keyQuote = "`"
+			}
+			if keyQuote != "" {
+				qLen := len(keyQuote)
+				pos := strings.Index(line[qLen:], keyQuote)
+				if pos == -1 {
+					return readError{ERR_COULD_NOT_PARSE, line}
+				}
+				pos = pos + qLen
+				i = strings.IndexAny(line[pos:], "=:")
+				if i <= 0 {
+					return readError{ERR_COULD_NOT_PARSE, line}
+				}
+				i = i + pos
+				key = line[qLen:pos] //保留引号内的两端的空格
+			} else {
+				i = strings.IndexAny(line, "=:")
+				if i <= 0 {
+					return readError{ERR_COULD_NOT_PARSE, line}
+				}
+				key = strings.TrimSpace(line[0:i])
+			}
+			//[SWH|+];
+
+			// Check if it needs auto increment.
+			if key == "-" {
+				key = "#" + fmt.Sprint(count)
+				count++
+			}
+
+			//[SWH|+]:支持引号包围起来的字串
+			lineRight := strings.TrimSpace(line[i+1:])
+			lineRightLength := len(lineRight)
+			firstChar := ""
+			if lineRightLength >= 2 {
+				firstChar = lineRight[0:1]
+			}
+			if firstChar == "`" {
+				valQuote = "`"
+			} else if lineRightLength >= 6 && lineRight[0:3] == `"""` {
+				valQuote = `"""`
+			}
+			if valQuote != "" {
+				qLen := len(valQuote)
+				pos := strings.LastIndex(lineRight[qLen:], valQuote)
+				if pos == -1 {
+					return readError{ERR_COULD_NOT_PARSE, line}
+				}
+				pos = pos + qLen
+				value = lineRight[qLen:pos]
+			} else {
+				value = strings.TrimSpace(lineRight[0:])
+			}
+			//[SWH|+];
+
+			c.SetValue(section, key, value)
+			// Set key comments and empty if it has comments.
+			if len(comments) > 0 {
+				c.SetKeyComments(section, key, comments)
+				comments = ""
+			}
+		}
+
+		// Reached end of file.
+		if err == io.EOF {
+			break
+		}
+	}
+	return nil
+}
+
+// LoadFromData accepts raw data directly from memory
+// and returns a new configuration representation.
+// Note that the configuration is written to the system
+// temporary folder, so your file should not contain
+// sensitive information.
+func LoadFromData(data []byte) (c *ConfigFile, err error) {
+	// Save memory data to temporary file to support further operations.
+	tmpName := path.Join(os.TempDir(), "goconfig", fmt.Sprintf("%d", time.Now().Nanosecond()))
+	if err = os.MkdirAll(path.Dir(tmpName), os.ModePerm); err != nil {
+		return nil, err
+	}
+	if err = ioutil.WriteFile(tmpName, data, 0655); err != nil {
+		return nil, err
+	}
+
+	c = newConfigFile([]string{tmpName})
+	err = c.read(bytes.NewBuffer(data))
+	return c, err
+}
+
+// LoadFromReader accepts raw data directly from a reader
+// and returns a new configuration representation.
+// You must use ReloadData to reload.
+// You cannot append files a configfile read this way.
+func LoadFromReader(in io.Reader) (c *ConfigFile, err error) {
+	c = newConfigFile([]string{""})
+	err = c.read(in)
+	return c, err
+}
+
+func (c *ConfigFile) loadFile(fileName string) (err error) {
+	f, err := os.Open(fileName)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	return c.read(f)
+}
+
+// LoadConfigFile reads a file and returns a new configuration representation.
+// This representation can be queried with GetValue.
+func LoadConfigFile(fileName string, moreFiles ...string) (c *ConfigFile, err error) {
+	// Append files' name together.
+	fileNames := make([]string, 1, len(moreFiles)+1)
+	fileNames[0] = fileName
+	if len(moreFiles) > 0 {
+		fileNames = append(fileNames, moreFiles...)
+	}
+
+	c = newConfigFile(fileNames)
+
+	for _, name := range fileNames {
+		if err = c.loadFile(name); err != nil {
+			return nil, err
+		}
+	}
+
+	return c, nil
+}
+
+// Reload reloads configuration file in case it has changes.
+func (c *ConfigFile) Reload() (err error) {
+	var cfg *ConfigFile
+	if len(c.fileNames) == 1 {
+		if c.fileNames[0] == "" {
+			return fmt.Errorf("file opened from in-memory data, use ReloadData to reload")
+		}
+		cfg, err = LoadConfigFile(c.fileNames[0])
+	} else {
+		cfg, err = LoadConfigFile(c.fileNames[0], c.fileNames[1:]...)
+	}
+
+	if err == nil {
+		*c = *cfg
+	}
+	return err
+}
+
+// ReloadData reloads configuration file from memory
+func (c *ConfigFile) ReloadData(in io.Reader) (err error) {
+	var cfg *ConfigFile
+	if len(c.fileNames) != 1 {
+		return fmt.Errorf("Multiple files loaded, unable to mix in-memory and file data")
+	}
+
+	cfg, err = LoadFromReader(in)
+	if err == nil {
+		*c = *cfg
+	}
+	return err
+}
+
+// AppendFiles appends more files to ConfigFile and reload automatically.
+func (c *ConfigFile) AppendFiles(files ...string) error {
+	if len(c.fileNames) == 1 && c.fileNames[0] == "" {
+		return fmt.Errorf("Cannot append file data to in-memory data")
+	}
+	c.fileNames = append(c.fileNames, files...)
+	return c.Reload()
+}
+
+// readError occurs when read configuration file with wrong format.
+type readError struct {
+	Reason  ParseError
+	Content string // Line content
+}
+
+// Error implement Error interface.
+func (err readError) Error() string {
+	switch err.Reason {
+	case ERR_BLANK_SECTION_NAME:
+		return "empty section name not allowed"
+	case ERR_COULD_NOT_PARSE:
+		return fmt.Sprintf("could not parse line: %s", string(err.Content))
+	}
+	return "invalid read error"
+}

+ 117 - 0
pkg/conf/goconfig/write.go

@@ -0,0 +1,117 @@
+// Copyright 2013 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package goconfig
+
+import (
+	"bytes"
+	"io"
+	"os"
+	"strings"
+)
+
+// Write spaces around "=" to look better.
+var PrettyFormat = true
+
+// SaveConfigData writes configuration to a writer
+func SaveConfigData(c *ConfigFile, out io.Writer) (err error) {
+	equalSign := "="
+	if PrettyFormat {
+		equalSign = " = "
+	}
+
+	buf := bytes.NewBuffer(nil)
+	for _, section := range c.sectionList {
+		// Write section comments.
+		if len(c.GetSectionComments(section)) > 0 {
+			if _, err = buf.WriteString(c.GetSectionComments(section) + LineBreak); err != nil {
+				return err
+			}
+		}
+
+		if section != DEFAULT_SECTION {
+			// Write section name.
+			if _, err = buf.WriteString("[" + section + "]" + LineBreak); err != nil {
+				return err
+			}
+		}
+
+		for _, key := range c.keyList[section] {
+			if key != " " {
+				// Write key comments.
+				if len(c.GetKeyComments(section, key)) > 0 {
+					if _, err = buf.WriteString(c.GetKeyComments(section, key) + LineBreak); err != nil {
+						return err
+					}
+				}
+
+				keyName := key
+				// Check if it's auto increment.
+				if keyName[0] == '#' {
+					keyName = "-"
+				}
+				//[SWH|+]:支持键名包含等号和冒号
+				if strings.Contains(keyName, `=`) || strings.Contains(keyName, `:`) {
+					if strings.Contains(keyName, "`") {
+						if strings.Contains(keyName, `"`) {
+							keyName = `"""` + keyName + `"""`
+						} else {
+							keyName = `"` + keyName + `"`
+						}
+					} else {
+						keyName = "`" + keyName + "`"
+					}
+				}
+				value := c.data[section][key]
+				// In case key value contains "`" or "\"".
+				if strings.Contains(value, "`") {
+					if strings.Contains(value, `"`) {
+						value = `"""` + value + `"""`
+					} else {
+						value = `"` + value + `"`
+					}
+				}
+
+				// Write key and value.
+				if _, err = buf.WriteString(keyName + equalSign + value + LineBreak); err != nil {
+					return err
+				}
+			}
+		}
+
+		// Put a line between sections.
+		if _, err = buf.WriteString(LineBreak); err != nil {
+			return err
+		}
+	}
+
+	if _, err := buf.WriteTo(out); err != nil {
+		return err
+	}
+	return nil
+}
+
+// SaveConfigFile writes configuration file to local file system
+func SaveConfigFile(c *ConfigFile, filename string) (err error) {
+	// Write configuration file by filename.
+	var f *os.File
+	if f, err = os.Create(filename); err != nil {
+		return err
+	}
+
+	if err := SaveConfigData(c, f); err != nil {
+		return err
+	}
+	return f.Close()
+}

+ 15 - 0
pkg/conf/init_config.go

@@ -0,0 +1,15 @@
+package conf
+
+func configMustInit() error {
+	conf := make(map[string]string, 0)
+	if err := SetAndAssertNil(conf, "application", "env"); err != nil {
+		return err
+	}
+	if err := SetAndAssertNil(conf, "application", "project_name"); err != nil {
+		return err
+	}
+	if err := SetAndAssertNil(conf, "application", "port"); err != nil {
+		return err
+	}
+	return nil
+}

+ 101 - 0
pkg/conf/nacos.go

@@ -0,0 +1,101 @@
+package conf
+
+import (
+	"fmt"
+	"sync/atomic"
+	"time"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/nacos"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/properties"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"github.com/nacos-group/nacos-sdk-go/vo"
+)
+
+var (
+	_nacosConfig atomic.Value
+)
+
+func loadNacosConfig() properties.Properties {
+	config, _ := _nacosConfig.Load().(properties.Properties)
+	return config
+}
+
+func setNacosConfig(properties properties.Properties) {
+	_nacosConfig.Store(properties)
+}
+
+func initNacosConfig() error {
+	configClient, err := nacos.NewNacosConfigClient(SetAndAssertNil)
+	if err != nil {
+		return err
+	}
+	content, err := configClient.GetConfig(vo.ConfigParam{
+		DataId: configClient.DataId,
+		Group:  configClient.Group,
+	})
+	if err != nil {
+		return err
+	}
+	config, err := properties.ReadFromString(content)
+	if err != nil {
+		return err
+	}
+	setNacosConfig(config)
+	err = configClient.ListenConfig(vo.ConfigParam{
+		DataId: configClient.DataId,
+		Group:  configClient.Group,
+		OnChange: func(namespace, group, dataId, data string) {
+			newConfig, err := properties.ReadFromString(data)
+			if err != nil {
+				util.Errorf("[Nacos] configClient.ListenConfig#OnChange err, err=%v", err)
+				return
+			}
+			oldConfig := loadNacosConfig()
+			setNacosConfig(newConfig)
+			go printChangedNacosConfig(oldConfig, newConfig)
+		},
+	})
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 打印配置文件变更记录
+func printChangedNacosConfig(oldConfig properties.Properties, newConfig properties.Properties) {
+	defer func() {
+		if err := recover(); err != nil {
+			util.Errorf("[Nacos] configClient.ListenConfig#OnChange print diff find, err=%v", err)
+		}
+	}()
+	printProperties := func(config map[string]string, op string, changeId string) {
+		if config == nil || len(config) == 0 {
+			return
+		}
+		changeIdLine := fmt.Sprintf("[Nacos] [change_id=%s] %s-config: ", changeId, op)
+		for key, value := range config {
+			util.Infof(changeIdLine + key + " = " + value)
+		}
+		return
+	}
+	printDiffPorperties := func(config map[string][]string, op string, changeId string) string {
+		if config == nil || len(config) == 0 {
+			return ""
+		}
+		result := ""
+		changeIdLine := fmt.Sprintf("[Nacos-Config] [change_id=%s] %s-config: ", changeId, op)
+		for key, value := range config {
+			if value == nil || len(value) < 2 {
+				continue
+			}
+			util.Infof(changeIdLine + key + " = " + value[0] + " => " + value[1])
+		}
+		return result
+	}
+	deleteMap, addMap, changeMap := oldConfig.DiffProperties(newConfig)
+	changeId := util.ToString(time.Now().UnixNano())
+	util.Infof("[Nacos-Config] configClient.ListenConfig#OnChange find config changed change_id=%s", changeId)
+	printProperties(deleteMap, "delete", changeId)
+	printProperties(addMap, "add", changeId)
+	printDiffPorperties(changeMap, "change", changeId)
+}

+ 40 - 0
pkg/conf/nacos_test.go

@@ -0,0 +1,40 @@
+package conf
+
+import (
+	"fmt"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/properties"
+	"testing"
+	"time"
+)
+
+func mockConfig(t *testing.T, config string) {
+	pro, err := properties.ReadFromString(config)
+	if err != nil {
+		t.Fatal(err)
+	}
+	MustValue = func(section, key string, defaultVal ...string) string {
+		return pro.GetString(section+"."+key, defaultVal ...)
+	}
+}
+
+func Test_initNacosConfig(t *testing.T) {
+	mockConfig(t, `
+nacos-config.data_id = go-template
+nacos-config.group = DEFAULT_GROUP
+nacos-config.host = 10.100.101.20:8848,10.100.99.14:8848
+nacos-config.log_path = /data/log/go-template
+nacos-config.namespace_id = 1bc32d85-1884-49b2-979d-8ced1f90ef35
+nacos-config.user_name = cityservice_dev_nacos
+nacos-config.password = MrHu0qvmdK
+nacos-config.log_level = error
+nacos-config.timeout = 5000
+`)
+	if err := initNacosConfig(); err != nil {
+		t.Fatal(err)
+	}
+	ticker := time.NewTicker(time.Second)
+	for {
+		<-ticker.C
+		fmt.Println("k1: ", loadNacosConfig().GetString("k1"))
+	}
+}

+ 42 - 0
pkg/conf/new_config_func.go

@@ -0,0 +1,42 @@
+package conf
+
+import (
+	"strings"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+)
+
+/**
+兼容旧逻辑:key 通过.分开,第一个是selection,第二个是key
+兼容新逻辑:key 是通过 selection.key 连接起来即可
+*/
+func GetStringV2(key string, defaultVal ...string) string {
+	split := strings.Split(key, ".")
+	if len(split) < 2 {
+		if defaultVal != nil && len(defaultVal) > 0 {
+			return defaultVal[0]
+		}
+		return ""
+	}
+	return MustValue(split[0], strings.Join(split[1:], "."), defaultVal...)
+}
+
+func GetUint64V2(key string, defaultVal ...uint64) uint64 {
+	value := GetStringV2(key)
+	return util.String2Uint64(value, defaultVal...)
+}
+
+func GetInt64V2(key string, defaultVal ...int64) int64 {
+	value := GetStringV2(key)
+	return util.String2Int64(value, defaultVal...)
+}
+
+func GetFloat64V2(key string, defaultVal ...float64) float64 {
+	value := GetStringV2(key)
+	return util.String2Float64(value, defaultVal...)
+}
+
+func GetBoolV2(key string, defaultVal ...bool) bool {
+	value := GetStringV2(key)
+	return util.String2Bool(value, defaultVal...)
+}

+ 14 - 0
pkg/conf/new_config_func_test.go

@@ -0,0 +1,14 @@
+package conf
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestGetStringV2(t *testing.T) {
+	MustValue = func(section, key string, defaultVal ...string) string {
+		fmt.Println(section, key)
+		return ""
+	}
+	GetStringV2("k1.k2.k3")
+}

+ 56 - 0
pkg/conf/old_config_func.go

@@ -0,0 +1,56 @@
+package conf
+
+import (
+	"strings"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+)
+
+var (
+	/**
+	唯一暴露的可变更方法 !运行对外修改
+	*/
+	MustValue = func(section, key string, defaultVal ...string) string {
+		switch globalDriver {
+		case Local, Apollo:
+			return getConfig().MustValue(section, key, defaultVal...)
+		case Nacos:
+			var result string
+			// 如果nacos获取不到去本地获取,所以没有设置default
+			if config := loadNacosConfig(); config != nil {
+				result = config.GetString(strings.Join([]string{section, key}, "."))
+			}
+			// 如果远程没有,则加载本地
+			if result == "" {
+				return getConfig().MustValue(section, key, defaultVal...)
+			}
+			return result
+		default:
+			return ""
+		}
+	}
+)
+
+func GetString(section, key string, defaultVal ...string) string {
+	return MustValue(section, key, defaultVal...)
+}
+
+func GetUint64(section, key string, defaultVal ...uint64) uint64 {
+	value := MustValue(section, key)
+	return util.String2Uint64(value, defaultVal...)
+}
+
+func GetInt64(section, key string, defaultVal ...int64) int64 {
+	value := MustValue(section, key)
+	return util.String2Int64(value, defaultVal...)
+}
+
+func GetFloat64(section, key string, defaultVal ...float64) float64 {
+	value := MustValue(section, key)
+	return util.String2Float64(value, defaultVal...)
+}
+
+func GetBool(section, key string, defaultVal ...bool) bool {
+	value := MustValue(section, key)
+	return util.String2Bool(value, defaultVal...)
+}

+ 643 - 0
pkg/conf/remote/apollo/agollo/agollo.go

@@ -0,0 +1,643 @@
+package agollo
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"sync"
+	"time"
+)
+
+var (
+	localIP                           = getLocalIP()
+	defaultConfigFilePath             = "app.properties"
+	defaultCluster                    = "default"
+	defaultNamespace                  = "application"
+	defaultConfigType                 = "properties"
+	defaultBackupFile                 = ".agollo"
+	defaultClientTimeout              = 90 * time.Second
+	defaultNotificationID             = -1
+	defaultLongPollInterval           = 1 * time.Second
+	defaultAutoFetchOnCacheMiss       = false
+	defaultFailTolerantOnBackupExists = false
+	defaultWatchTimeout               = 500 * time.Millisecond
+	watchLock                         sync.RWMutex
+	timeZone                          = time.FixedZone("CST", 8*3600)
+
+	defaultAgollo Agollo
+)
+
+type Agollo interface {
+	Start() <-chan *LongPollerError
+	Stop()
+	Reload() error
+	Get(key string, opts ...GetOption) string
+	GetNameSpace(namespace string) Configurations
+	GetAll() map[string]interface{}
+	Watch() <-chan *ApolloResponse
+	WatchNamespace(namespace string, stop chan bool) <-chan *ApolloResponse
+	Options() Options
+}
+
+type Configurations map[string]interface{}
+
+type ApolloResponse struct {
+	Namespace string
+	OldValue  Configurations
+	NewValue  Configurations
+	Error     error
+}
+
+type LongPollerError struct {
+	ConfigServerURL string
+	AppID           string
+	Cluster         string
+	Notifications   []Notification
+	Namespace       string // 服务响应200后去非缓存接口拉取时的namespace
+	Err             error
+}
+
+type agollo struct {
+	opts Options
+
+	notificationMapLock sync.Mutex
+	notificationMap     map[string]int // key: namespace value: notificationId
+	namespaceMapLock    sync.Mutex
+	namespaceMap        map[string]string // key: namespace value: releaseKey
+	cacheLock           sync.Mutex
+	cache               map[string]interface{} // key: namespace value: Configurations
+
+	watchCh             chan *ApolloResponse            // watch all namespace
+	watchNamespaceChMap map[string]chan *ApolloResponse // key: namespace value: chan *ApolloResponse
+
+	errorsCh chan *LongPollerError
+
+	runOnce  sync.Once
+	stop     bool
+	stopCh   chan struct{}
+	stopLock sync.Mutex
+}
+
+func New(configServerURL, appID string, opts ...Option) (Agollo, error) {
+	a := &agollo{
+		stopCh:   make(chan struct{}),
+		errorsCh: make(chan *LongPollerError),
+		opts:     newOptions(opts...),
+	}
+
+	a.opts.ConfigServerURL = normalizeURL(configServerURL)
+	a.opts.AppID = appID
+
+	return a.preload()
+}
+
+func NewWithConfigFile(configFilePath string, opts ...Option) (Agollo, error) {
+	f, err := os.Open(configFilePath)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	var conf struct {
+		AppID   string `json:"appId,omitempty"`
+		Cluster string `json:"cluster,omitempty"`
+		IP      string `json:"ip,omitempty"`
+	}
+	if err := json.NewDecoder(f).Decode(&conf); err != nil {
+		return nil, err
+	}
+
+	return New(
+		conf.IP,
+		conf.AppID,
+		append(
+			[]Option{
+				Cluster(conf.Cluster),
+			},
+			opts...,
+		)...,
+	)
+}
+
+func (a *agollo) preload() (Agollo, error) {
+	for _, namespace := range a.opts.PreloadNamespaces {
+		_, err := a.loadConfigFromNonCache(namespace)
+		if err != nil {
+			if a.opts.FailTolerantOnBackupExists {
+				_, err = a.loadBackup(namespace)
+				if err != nil {
+					return nil, err
+				}
+				continue
+			}
+			return nil, err
+		}
+	}
+	return a, nil
+}
+
+func (a *agollo) Reload() error {
+	err := a.loadAllConfigFromNonCacheAndThenCache()
+	if err == nil {
+		return nil
+	}
+	//a.log("ReloadAllNamespace", "From api", "Error", err.Error())
+	err = a.loadAllFromBackup()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (a *agollo) GetAll() map[string]interface{} {
+	return a.cache
+}
+
+func (a *agollo) Get(key string, opts ...GetOption) string {
+	getOpts := newGetOptions(
+		append(
+			[]GetOption{
+				WithNamespace(a.opts.DefaultNamespace),
+			},
+			opts...,
+		)...,
+	)
+
+	val, found := a.GetNameSpace(getOpts.Namespace)[key]
+	if !found {
+		return getOpts.DefaultValue
+	}
+
+	v, _ := ToStringE(val)
+	return v
+}
+
+func (a *agollo) GetNameSpace(namespace string) Configurations {
+	if len(a.cache) == 0 {
+		a.cache = make(map[string]interface{})
+	}
+	//LoadOrStore  函数的意义就是当前没有关系的时候 绑定关系  有的话返回关系
+	configs, val := a.cache[namespace]
+	if val == false {
+		a.cache[namespace] = Configurations{}
+		configs = Configurations{}
+		return a.loadNameSpace(namespace)
+	} else {
+		configs = a.cache[namespace]
+	}
+
+	//a.log("Namesapce", namespace, "From", "cache")
+
+	return configs.(Configurations)
+}
+
+func (a *agollo) loadNameSpace(namespace string) Configurations {
+	// 存储不存在的namespace, 之后在longPoller中拉取配置
+	a.notificationMapLock.Lock()
+	defer a.notificationMapLock.Unlock()
+	if len(a.notificationMap) == 0 {
+		a.notificationMap = make(map[string]int)
+	}
+	_, val := a.notificationMap[namespace]
+	if val == false {
+		a.notificationMap[namespace] = defaultNotificationID
+	}
+
+	if a.opts.AutoFetchOnCacheMiss {
+		configs, err := a.loadConfigFromCache(namespace)
+		if err == nil {
+			//a.log("Namesapce", namespace, "From", "cache-api")
+			return configs
+		}
+	}
+
+	if a.opts.FailTolerantOnBackupExists {
+		configs, err := a.loadBackup(namespace)
+		if err == nil {
+			a.log("Namesapce", namespace, "From", "local")
+			return configs
+		}
+	}
+	return Configurations{}
+}
+
+func (a *agollo) Options() Options {
+	return a.opts
+}
+
+func (a *agollo) Watch() <-chan *ApolloResponse {
+	watchLock.Lock()
+	defer watchLock.Unlock()
+	if a.watchCh == nil {
+		a.watchCh = make(chan *ApolloResponse)
+	}
+
+	return a.watchCh
+}
+
+func (a *agollo) WatchNamespace(namespace string, stop chan bool) <-chan *ApolloResponse {
+	watchCh, val := a.watchNamespaceChMap[namespace]
+	if val == false {
+		go func(stop chan bool) {
+			select {
+			case <-stop:
+				delete(a.watchNamespaceChMap, namespace)
+				//a.watchNamespaceChMap.Delete(namespace)
+			}
+		}(stop)
+		watchCh = make(chan *ApolloResponse)
+	}
+	return watchCh
+}
+
+func (a *agollo) sendWatchCh(namespace string, oldVal, newVal Configurations) {
+	resp := &ApolloResponse{
+		Namespace: namespace,
+		OldValue:  oldVal,
+		NewValue:  newVal,
+	}
+
+	timer := time.NewTimer(defaultWatchTimeout)
+	for _, watchCh := range a.getWatchChs(namespace) {
+		select {
+		case watchCh <- resp:
+		case <-timer.C: // 防止创建全局监听或者某个namespace监听却不消费死锁问题
+			timer.Reset(defaultWatchTimeout)
+		}
+	}
+}
+
+func (a *agollo) getWatchChs(namespace string) []chan *ApolloResponse {
+	var chs []chan *ApolloResponse
+	watchLock.Lock()
+	defer watchLock.Unlock()
+	if a.watchCh != nil {
+		chs = append(chs, a.watchCh)
+	}
+
+	if watchNamespaceCh, found := a.watchNamespaceChMap[namespace]; found {
+		chs = append(chs, watchNamespaceCh)
+	}
+
+	return chs
+}
+
+func (a *agollo) sendErrorsCh(notifications []Notification, namespace string, err error) {
+	longPollerError := &LongPollerError{
+		ConfigServerURL: a.opts.ConfigServerURL,
+		AppID:           a.opts.AppID,
+		Cluster:         a.opts.Cluster,
+		Notifications:   notifications,
+		Namespace:       namespace,
+		Err:             err,
+	}
+	select {
+	case a.errorsCh <- longPollerError:
+	default:
+	}
+}
+
+func (a *agollo) log(kvs ...interface{}) {
+	timeStr := time.Now().In(timeZone).Format("2006-01-02 15:04:05")
+	a.opts.Logger.Log(
+		append([]interface{}{
+			"[" + timeStr + "]",
+			"[Agollo]",
+			"ConfigServerUrl", a.opts.ConfigServerURL,
+			"AppID", a.opts.AppID,
+			"Cluster", a.opts.Cluster,
+		},
+			kvs...,
+		)...,
+	)
+}
+
+func (a *agollo) loadConfigFromCache(namespace string) (configurations Configurations, err error) {
+	configurations, err = a.opts.ApolloClient.GetConfigsFromCache(
+		a.opts.ConfigServerURL,
+		a.opts.AppID,
+		a.opts.Cluster,
+		namespace)
+	if err != nil {
+		a.log("Namespace", namespace, "Action", "LoadConfigFromCache", "Error", err.Error())
+		return
+	}
+
+	err = a.handleConfig(namespace, configurations)
+
+	return
+}
+
+func (a *agollo) loadConfigFromNonCache(namespace string) (configurations Configurations, err error) {
+
+	var (
+		status int
+		config *Config
+		//cachedReleaseKey, _ = a.namespaceMap.LoadOrStore(namespace, "")
+	)
+	a.namespaceMapLock.Lock()
+	defer a.namespaceMapLock.Unlock()
+	if len(a.namespaceMap) == 0 {
+		a.namespaceMap = make(map[string]string)
+	}
+	cachedReleaseKey, val := a.namespaceMap[namespace]
+	if val == false {
+		a.namespaceMap[namespace] = ""
+		cachedReleaseKey = ""
+	} else {
+		cachedReleaseKey = a.namespaceMap[namespace]
+	}
+	status, config, err = a.opts.ApolloClient.GetConfigsFromNonCache(
+		a.opts.ConfigServerURL,
+		a.opts.AppID,
+		a.opts.Cluster,
+		namespace,
+		ReleaseKey(cachedReleaseKey),
+	)
+	if err != nil {
+		a.log("Namespace", namespace, "Action", "LoadConfigFromNonCache", "Error", err.Error())
+		return
+	}
+
+	if status == http.StatusOK {
+		configurations = config.Configurations
+		a.namespaceMap[namespace] = config.ReleaseKey
+		err = a.handleConfig(namespace, config.Configurations)
+		return
+	}
+
+	return
+}
+
+func (a *agollo) loadAllConfigFromNonCacheAndThenCache() (err error) {
+	var (
+		status int
+		config *Config
+		//cachedReleaseKey, _ = a.namespaceMap.LoadOrStore(namespace, "")
+	)
+	a.namespaceMapLock.Lock()
+	defer a.namespaceMapLock.Unlock()
+	if len(a.cache) == 0 {
+		a.cache = make(map[string]interface{})
+	}
+	for _, namespace := range a.opts.PreloadNamespaces {
+		if len(a.namespaceMap) == 0 {
+			a.namespaceMap = make(map[string]string)
+		}
+		cachedReleaseKey, val := a.namespaceMap[namespace]
+		if val == false {
+			a.namespaceMap[namespace] = ""
+			cachedReleaseKey = ""
+		} else {
+			cachedReleaseKey = a.namespaceMap[namespace]
+		}
+		status, config, err = a.opts.ApolloClient.GetConfigsFromNonCache(
+			a.opts.ConfigServerURL,
+			a.opts.AppID,
+			a.opts.Cluster,
+			namespace,
+			ReleaseKey(cachedReleaseKey),
+		)
+		if err != nil {
+			a.log("Namespace", namespace, "Action", "LoadConfigFromNonCache", "Error", err.Error())
+			return err
+		}
+		if status == http.StatusOK {
+			configurations := config.Configurations
+			a.namespaceMap[namespace] = config.ReleaseKey
+			a.cacheLock.Lock()
+			a.cache[namespace] = configurations
+			a.cacheLock.Unlock()
+		}
+	}
+	//a.log("ReloadAllNamespace", "From api")
+	return a.backup()
+}
+
+func (a *agollo) handleConfig(namespace string, configurations Configurations) error {
+	// 读取旧缓存用来给监听队列
+	oldValue := a.GetNameSpace(namespace)
+	// 覆盖旧缓存
+	a.cacheLock.Lock()
+	a.cache[namespace] = configurations
+	a.cacheLock.Unlock()
+
+	// 发送到监听channel
+	a.sendWatchCh(namespace, oldValue, configurations)
+	// 备份配置
+	return a.backup()
+}
+
+func (a *agollo) backup() error {
+	backup := map[string]Configurations{}
+
+	a.cacheLock.Lock()
+	defer a.cacheLock.Unlock()
+	for key, val := range a.cache {
+		conf, _ := val.(Configurations)
+		backup[key] = conf
+	}
+
+	data, err := json.Marshal(backup)
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(a.opts.BackupFile, data, 0644)
+}
+
+func (a *agollo) loadAllFromBackup() error {
+	if len(a.cache) == 0 {
+		a.cache = make(map[string]interface{})
+	}
+	if _, err := os.Stat(a.opts.BackupFile); err != nil {
+		return err
+	}
+
+	data, err := ioutil.ReadFile(a.opts.BackupFile)
+	if err != nil {
+		return err
+	}
+
+	backup := map[string]Configurations{}
+	err = json.Unmarshal(data, &backup)
+	if err != nil {
+		return err
+	}
+	a.cacheLock.Lock()
+	defer a.cacheLock.Unlock()
+	for _, specifyNamespace := range a.opts.PreloadNamespaces {
+		for namespace, configs := range backup {
+			if namespace == specifyNamespace {
+				a.cache[namespace] = configs
+			}
+		}
+	}
+	a.log("ReloadAllNamesapce", "From", "Backup")
+	return nil
+}
+
+func (a *agollo) loadBackup(specifyNamespace string) (Configurations, error) {
+	if _, err := os.Stat(a.opts.BackupFile); err != nil {
+		return nil, nil
+	}
+
+	data, err := ioutil.ReadFile(a.opts.BackupFile)
+	if err != nil {
+		return nil, err
+	}
+
+	backup := map[string]Configurations{}
+	err = json.Unmarshal(data, &backup)
+	if err != nil {
+		return nil, err
+	}
+
+	for namespace, configs := range backup {
+		if namespace == specifyNamespace {
+			if len(a.cache) == 0 {
+				a.cache = make(map[string]interface{})
+			}
+			a.cache[namespace] = configs
+			return configs, nil
+		}
+	}
+
+	return nil, nil
+}
+
+func (a *agollo) longPoll() {
+	notifications := a.notifications()
+	status, notifications, err := a.opts.ApolloClient.Notifications(
+		a.opts.ConfigServerURL,
+		a.opts.AppID,
+		a.opts.Cluster,
+		notifications,
+	)
+	if err != nil {
+		a.log("Notifications", Notifications(a.notifications()).String(),
+			"Error", err.Error(), "Action", "LongPoll")
+		a.sendErrorsCh(notifications, "", err)
+	}
+
+	if status == http.StatusOK {
+		// 服务端判断没有改变,不会返回结果,这个时候不需要修改,遍历空数组跳过
+		for _, notification := range notifications {
+			_, err = a.loadConfigFromNonCache(notification.NamespaceName)
+			if err == nil {
+				a.notificationMapLock.Lock()
+				a.notificationMap[notification.NamespaceName] = notification.NotificationID
+				a.notificationMapLock.Unlock()
+				continue
+			} else {
+				a.sendErrorsCh(notifications, notification.NamespaceName, err)
+			}
+		}
+	}
+}
+
+func (a *agollo) notifications() []Notification {
+	var notifications []Notification
+	a.notificationMapLock.Lock()
+	for key, val := range a.notificationMap {
+		notifications = append(notifications, Notification{
+			NamespaceName:  key,
+			NotificationID: val,
+		})
+
+	}
+	a.notificationMapLock.Unlock()
+	return notifications
+}
+
+// 启动goroutine去轮训apollo通知接口
+func (a *agollo) Start() <-chan *LongPollerError {
+	a.runOnce.Do(func() {
+		go func() {
+			timer := time.NewTimer(a.opts.LongPollerInterval)
+			defer timer.Stop()
+
+			for !a.shouldStop() {
+				select {
+				case <-timer.C:
+					a.longPoll()
+					timer.Reset(a.opts.LongPollerInterval)
+				case <-a.stopCh:
+					return
+				}
+			}
+		}()
+	})
+
+	return a.errorsCh
+}
+
+func (a *agollo) Stop() {
+	a.stopLock.Lock()
+	defer a.stopLock.Unlock()
+	if a.stop {
+		return
+	}
+	a.stop = true
+	close(a.stopCh)
+}
+
+func (a *agollo) shouldStop() bool {
+	select {
+	case <-a.stopCh:
+		return true
+	default:
+		return false
+	}
+}
+
+func Init(configServerURL, appID string, opts ...Option) (err error) {
+	defaultAgollo, err = New(configServerURL, appID, opts...)
+	return
+}
+
+func InitWithConfigFile(configFilePath string, opts ...Option) (err error) {
+	defaultAgollo, err = NewWithConfigFile(configFilePath, opts...)
+	return
+}
+
+func InitWithDefaultConfigFile(opts ...Option) error {
+	return InitWithConfigFile(defaultConfigFilePath, opts...)
+}
+
+func Start() <-chan *LongPollerError {
+	return defaultAgollo.Start()
+}
+
+func Stop() {
+	defaultAgollo.Stop()
+}
+
+func Reload() error {
+	return defaultAgollo.Reload()
+}
+
+func GetAll() map[string]interface{} {
+	return defaultAgollo.GetAll()
+}
+
+func Get(key string, opts ...GetOption) string {
+	return defaultAgollo.Get(key, opts...)
+}
+
+func GetNameSpace(namespace string) Configurations {
+	return defaultAgollo.GetNameSpace(namespace)
+}
+
+func Watch() <-chan *ApolloResponse {
+	return defaultAgollo.Watch()
+}
+
+func WatchNamespace(namespace string, stop chan bool) <-chan *ApolloResponse {
+	return defaultAgollo.WatchNamespace(namespace, stop)
+}
+
+func GetAgollo() Agollo {
+	return defaultAgollo
+}

+ 247 - 0
pkg/conf/remote/apollo/agollo/apollo_client.go

@@ -0,0 +1,247 @@
+package agollo
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+)
+
+// https://github.com/ctripcorp/apollo/wiki/%E5%85%B6%E5%AE%83%E8%AF%AD%E8%A8%80%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
+type ApolloClient interface {
+	Notifications(configServerURL, appID, clusterName string, notifications []Notification) (int, []Notification, error)
+
+	// 该接口会直接从数据库中获取配置,可以配合配置推送通知实现实时更新配置。
+	GetConfigsFromNonCache(configServerURL, appID, cluster, namespace string, opts ...NotificationsOption) (int, *Config, error)
+
+	// 该接口会从缓存中获取配置,适合频率较高的配置拉取请求,如简单的每30秒轮询一次配置。
+	GetConfigsFromCache(configServerURL, appID, cluster, namespace string) (Configurations, error)
+}
+
+type Notifications []Notification
+
+func (n Notifications) String() string {
+	bytes, _ := json.Marshal(n)
+	return string(bytes)
+}
+
+type Notification struct {
+	NamespaceName  string `json:"namespaceName"`  // namespaceName: "application",
+	NotificationID int    `json:"notificationId"` // notificationId: 107
+}
+
+type NotificationsOptions struct {
+	ReleaseKey string
+}
+
+type NotificationsOption func(*NotificationsOptions)
+
+func ReleaseKey(releaseKey string) NotificationsOption {
+	return func(o *NotificationsOptions) {
+		o.ReleaseKey = releaseKey
+	}
+}
+
+type Config struct {
+	AppID          string         `json:"appId"`          // appId: "AppTest",
+	Cluster        string         `json:"cluster"`        // cluster: "default",
+	NamespaceName  string         `json:"namespaceName"`  // namespaceName: "TEST.Namespace1",
+	Configurations Configurations `json:"configurations"` // configurations: {Name: "Foo"},
+	ReleaseKey     string         `json:"releaseKey"`     // releaseKey: "20181017110222-5ce3b2da895720e8"
+}
+
+type Doer interface {
+	Do(*http.Request) (*http.Response, error)
+}
+
+type apolloClient struct {
+	Doer       Doer
+	IP         string
+	ConfigType string // 默认properties不需要在namespace后加后缀名,其他情况例如application.json {xml,yml,yaml,json,...}
+}
+
+type ApolloClientOption func(*apolloClient)
+
+func WithDoer(d Doer) ApolloClientOption {
+	return func(a *apolloClient) {
+		a.Doer = d
+	}
+}
+
+func WithIP(ip string) ApolloClientOption {
+	return func(a *apolloClient) {
+		a.IP = ip
+	}
+}
+
+func WithConfigType(configType string) ApolloClientOption {
+	return func(a *apolloClient) {
+		a.ConfigType = configType
+	}
+}
+
+func NewApolloClient(opts ...ApolloClientOption) ApolloClient {
+	c := &apolloClient{}
+	for _, opt := range opts {
+		opt(c)
+	}
+
+	if c.Doer == nil {
+		c.Doer = &http.Client{
+			Timeout: defaultClientTimeout, // Notifications由于服务端会hold住请求60秒,所以请确保客户端访问服务端的超时时间要大于60秒。
+		}
+	}
+
+	if c.IP == "" {
+		c.IP = localIP
+	}
+
+	if c.ConfigType == "" {
+		c.ConfigType = defaultConfigType
+	}
+
+	return c
+}
+
+func (c *apolloClient) Notifications(configServerURL, appID, cluster string, notifications []Notification) (status int, result []Notification, err error) {
+	configServerURL = normalizeURL(configServerURL)
+	url := fmt.Sprintf("%s/notifications/v2?appId=%s&cluster=%s&notifications=%s",
+		configServerURL,
+		url.QueryEscape(appID),
+		url.QueryEscape(cluster),
+		url.QueryEscape(Notifications(notifications).String()),
+	)
+	var req *http.Request
+	req, err = http.NewRequest("GET", url, nil)
+	if err != nil {
+		return
+	}
+
+	var body []byte
+	status, body, err = parseResponseBody(c.Doer, req)
+	if err != nil {
+		return
+	}
+
+	if status == http.StatusOK {
+		err = json.Unmarshal(body, &result)
+		return
+	}
+
+	return
+}
+
+func (c *apolloClient) GetConfigsFromNonCache(configServerURL, appID, cluster, namespace string, opts ...NotificationsOption) (status int, config *Config, err error) {
+	var options = NotificationsOptions{}
+	for _, opt := range opts {
+		opt(&options)
+	}
+
+	configServerURL = normalizeURL(configServerURL)
+	url := fmt.Sprintf("%s/configs/%s/%s/%s?releaseKey=%s&ip=%s",
+		configServerURL,
+		url.QueryEscape(appID),
+		url.QueryEscape(cluster),
+		url.QueryEscape(c.getNamespace(namespace)),
+		options.ReleaseKey,
+		c.IP,
+	)
+	var req *http.Request
+	req, err = http.NewRequest("GET", url, nil)
+	if err != nil {
+		return
+	}
+
+	var body []byte
+	status, body, err = parseResponseBody(c.Doer, req)
+	if err != nil {
+		return
+	}
+
+	if status == http.StatusOK {
+		config = new(Config)
+		err = json.Unmarshal(body, config)
+		return
+	}
+
+	return
+}
+
+func (c *apolloClient) GetConfigsFromCache(configServerURL, appID, cluster, namespace string) (config Configurations, err error) {
+	configServerURL = normalizeURL(configServerURL)
+	url := fmt.Sprintf("%s/configfiles/json/%s/%s/%s?ip=%s",
+		configServerURL,
+		url.QueryEscape(appID),
+		url.QueryEscape(cluster),
+		url.QueryEscape(c.getNamespace(namespace)),
+		c.IP,
+	)
+
+	var req *http.Request
+	req, err = http.NewRequest("GET", url, nil)
+	if err != nil {
+		return
+	}
+
+	var (
+		body   []byte
+		status int
+	)
+	status, body, err = parseResponseBody(c.Doer, req)
+	if err != nil {
+		return
+	}
+
+	if status == http.StatusOK {
+		config = make(Configurations)
+		err = json.Unmarshal(body, &config)
+	} else {
+		err = errors.New(string(body))
+	}
+	return
+}
+
+// 配置文件有多种格式,例如:properties、xml、yml、yaml、json等。同样Namespace也具有这些格式。在Portal UI中可以看到“application”的Namespace上有一个“properties”标签,表明“application”是properties格式的。
+// 如果使用Http接口直接调用时,对应的namespace参数需要传入namespace的名字加上后缀名,如datasources.json。
+func (c *apolloClient) getNamespace(namespace string) string {
+	if c.ConfigType == "" || c.ConfigType == defaultConfigType {
+		return namespace
+	}
+	return namespace + "." + c.ConfigType
+}
+
+func getLocalIP() string {
+	addrs, err := net.InterfaceAddrs()
+	if err != nil {
+		return ""
+	}
+
+	for _, address := range addrs {
+		// check the address type and if it is not a loopback the display it
+		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
+			if ipnet.IP.To4() != nil {
+				return ipnet.IP.String()
+			}
+
+		}
+	}
+	return ""
+}
+
+func parseResponseBody(doer Doer, req *http.Request) (int, []byte, error) {
+	resp, err := doer.Do(req)
+	if err != nil {
+		return 0, nil, err
+	}
+	defer resp.Body.Close()
+
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return 0, nil, err
+	}
+
+	return resp.StatusCode, body, nil
+}

+ 40 - 0
pkg/conf/remote/apollo/agollo/log.go

@@ -0,0 +1,40 @@
+package agollo
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+)
+
+type Logger interface {
+	Log(kvs ...interface{})
+}
+
+type LoggerOption func(*logger)
+
+func LoggerWriter(w io.Writer) LoggerOption {
+	return func(l *logger) {
+		l.w = w
+	}
+}
+
+func NewLogger(opts ...LoggerOption) Logger {
+	l := &logger{}
+	for _, opt := range opts {
+		opt(l)
+	}
+
+	if l.w == nil {
+		l.w = ioutil.Discard
+	}
+
+	return l
+}
+
+type logger struct {
+	w io.Writer
+}
+
+func (l *logger) Log(kvs ...interface{}) {
+	fmt.Fprintln(l.w, kvs...)
+}

+ 146 - 0
pkg/conf/remote/apollo/agollo/options.go

@@ -0,0 +1,146 @@
+package agollo
+
+import (
+	"time"
+)
+
+type Options struct {
+	ConfigServerURL            string        // apollo 服务地址
+	AppID                      string        // appid
+	Cluster                    string        // 默认的集群名称,默认:default
+	DefaultNamespace           string        // 默认的命名空间,默认:application
+	PreloadNamespaces          []string      // 预加载命名空间,默认:application
+	ApolloClient               ApolloClient  // apollo HTTP api实现
+	Logger                     Logger        // 需要日志需要设置实现,或者注入有效的io.Writer,默认: ioutil.Discard
+	AutoFetchOnCacheMiss       bool          // 自动获取非预设以外的namespace的配置,默认:false
+	LongPollerInterval         time.Duration // 轮训间隔时间,默认:1s
+	BackupFile                 string        // 备份文件存放地址,默认:.agollo
+	FailTolerantOnBackupExists bool          // 服务器连接失败时允许读取备份,默认:false
+}
+
+func newOptions(opts ...Option) Options {
+	var options = Options{
+		AutoFetchOnCacheMiss:       defaultAutoFetchOnCacheMiss,
+		FailTolerantOnBackupExists: defaultFailTolerantOnBackupExists,
+	}
+	for _, opt := range opts {
+		opt(&options)
+	}
+
+	if options.Cluster == "" {
+		options.Cluster = defaultCluster
+	}
+
+	if options.DefaultNamespace == "" {
+		options.DefaultNamespace = defaultNamespace
+	}
+
+	if len(options.PreloadNamespaces) == 0 {
+		options.PreloadNamespaces = []string{defaultNamespace}
+	} else {
+		if !stringInSlice(defaultNamespace, options.PreloadNamespaces) {
+			PreloadNamespaces(defaultNamespace)(&options)
+		}
+	}
+
+	if options.ApolloClient == nil {
+		options.ApolloClient = NewApolloClient()
+	}
+
+	if options.Logger == nil {
+		options.Logger = NewLogger()
+	}
+
+	if options.LongPollerInterval <= time.Duration(0) {
+		options.LongPollerInterval = defaultLongPollInterval
+	}
+
+	if options.BackupFile == "" {
+		options.BackupFile = defaultBackupFile
+	}
+
+	return options
+}
+
+type Option func(*Options)
+
+func Cluster(cluster string) Option {
+	return func(o *Options) {
+		o.Cluster = cluster
+	}
+}
+
+func DefaultNamespace(defaultNamespace string) Option {
+	return func(o *Options) {
+		o.DefaultNamespace = defaultNamespace
+	}
+}
+
+func PreloadNamespaces(namespaces ...string) Option {
+	return func(o *Options) {
+		o.PreloadNamespaces = append(o.PreloadNamespaces, namespaces...)
+	}
+}
+
+func WithApolloClient(c ApolloClient) Option {
+	return func(o *Options) {
+		o.ApolloClient = c
+	}
+}
+
+func WithLogger(l Logger) Option {
+	return func(o *Options) {
+		o.Logger = l
+	}
+}
+
+func AutoFetchOnCacheMiss() Option {
+	return func(o *Options) {
+		o.AutoFetchOnCacheMiss = true
+	}
+}
+
+func LongPollerInterval(i time.Duration) Option {
+	return func(o *Options) {
+		o.LongPollerInterval = i
+	}
+}
+
+func BackupFile(backupFile string) Option {
+	return func(o *Options) {
+		o.BackupFile = backupFile
+	}
+}
+
+func FailTolerantOnBackupExists() Option {
+	return func(o *Options) {
+		o.FailTolerantOnBackupExists = true
+	}
+}
+
+type GetOptions struct {
+	DefaultValue string
+	Namespace    string
+}
+
+func newGetOptions(opts ...GetOption) GetOptions {
+	var getOpts GetOptions
+	for _, opt := range opts {
+		opt(&getOpts)
+	}
+	return getOpts
+}
+
+type GetOption func(*GetOptions)
+
+func WithDefault(defVal string) GetOption {
+	return func(o *GetOptions) {
+		o.DefaultValue = defVal
+	}
+}
+
+func WithNamespace(namespace string) GetOption {
+	return func(o *GetOptions) {
+		o.Namespace = namespace
+	}
+}

+ 84 - 0
pkg/conf/remote/apollo/agollo/strings.go

@@ -0,0 +1,84 @@
+package agollo
+
+import (
+	"fmt"
+	"html/template"
+	"reflect"
+	"strconv"
+)
+
+// From html/template/content.go
+// Copyright 2011 The Go Authors. All rights reserved.
+// indirectToStringerOrError returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
+// or error,
+func indirectToStringerOrError(a interface{}) interface{} {
+	if a == nil {
+		return nil
+	}
+
+	var errorType = reflect.TypeOf((*error)(nil)).Elem()
+	var fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+
+	v := reflect.ValueOf(a)
+	for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+		v = v.Elem()
+	}
+	return v.Interface()
+}
+
+// ToStringE casts an interface to a string type.
+func ToStringE(i interface{}) (string, error) {
+	i = indirectToStringerOrError(i)
+
+	switch s := i.(type) {
+	case string:
+		return s, nil
+	case bool:
+		return strconv.FormatBool(s), nil
+	case float64:
+		return strconv.FormatFloat(s, 'f', -1, 64), nil
+	case float32:
+		return strconv.FormatFloat(float64(s), 'f', -1, 32), nil
+	case int:
+		return strconv.Itoa(s), nil
+	case int64:
+		return strconv.FormatInt(s, 10), nil
+	case int32:
+		return strconv.Itoa(int(s)), nil
+	case int16:
+		return strconv.FormatInt(int64(s), 10), nil
+	case int8:
+		return strconv.FormatInt(int64(s), 10), nil
+	case uint:
+		return strconv.FormatInt(int64(s), 10), nil
+	case uint64:
+		return strconv.FormatInt(int64(s), 10), nil
+	case uint32:
+		return strconv.FormatInt(int64(s), 10), nil
+	case uint16:
+		return strconv.FormatInt(int64(s), 10), nil
+	case uint8:
+		return strconv.FormatInt(int64(s), 10), nil
+	case []byte:
+		return string(s), nil
+	case template.HTML:
+		return string(s), nil
+	case template.URL:
+		return string(s), nil
+	case template.JS:
+		return string(s), nil
+	case template.CSS:
+		return string(s), nil
+	case template.HTMLAttr:
+		return string(s), nil
+	case nil:
+		return "", nil
+	case fmt.Stringer:
+		return s.String(), nil
+	case error:
+		return s.Error(), nil
+	default:
+		return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i)
+	}
+}

+ 20 - 0
pkg/conf/remote/apollo/agollo/util.go

@@ -0,0 +1,20 @@
+package agollo
+
+import "strings"
+
+func stringInSlice(a string, list []string) bool {
+	for _, b := range list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}
+
+func normalizeURL(url string) string {
+	if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
+		url = "http://" + url
+	}
+
+	return strings.TrimSuffix(url, "/")
+}

+ 247 - 0
pkg/conf/remote/apollo/apollo.go

@@ -0,0 +1,247 @@
+package apollo
+
+import (
+	"bufio"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/signal"
+	"reflect"
+	"sync"
+	"time"
+
+	agollo2 "gitea.ckfah.com/cjjy/gocommon/pkg/conf/remote/apollo/agollo"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+
+	"github.com/tidwall/gjson"
+)
+
+var (
+	readLock               sync.Mutex
+	rootPath               string
+	lastConfig             map[string]interface{}
+	watchChannel           = make(chan struct{})
+	nameSpaceConfig        gjson.Result
+	regularMonitorInterval = 30 * time.Second
+)
+
+func Start() {
+	rootPath = util.GetRootPath() + "/config"
+	err := loadNameSpacesConfig(getNameSpacesConfigPath())
+	if err != nil {
+		panic(fmt.Sprintf("apollo:load err: %s", err.Error()))
+	}
+	err = agollo2.InitWithConfigFile(
+		getApolloConfigPath(),
+		agollo2.WithLogger(agollo2.NewLogger(agollo2.LoggerWriter(os.Stdout))), // 打印日志信息
+		agollo2.PreloadNamespaces(getNameSpaces()...),                          // 预先加载的namespace列表,如果是通过配置启动,会在app.properties配置的基础上追加
+		agollo2.AutoFetchOnCacheMiss(),                                         // 在配置未找到时,去apollo的带缓存的获取配置接口,获取配置
+		agollo2.FailTolerantOnBackupExists(),
+		agollo2.BackupFile(getBackFilePath()), // 在连接apollo失败时,如果在配置的目录下存在.agollo备份配置,会读取备份在服务器无法连接的情况下
+	)
+	if err != nil {
+		panic(fmt.Sprintf("apollo:init err: %s", err.Error()))
+	}
+	err = writeMapToFile()
+	if err != nil {
+		panic(fmt.Sprintf("apollo:write file err: %s", err.Error()))
+	}
+	go listen()
+}
+
+func GetConfigPath() string {
+	return rootPath + "/apollo/env.ini"
+}
+
+func SetRefreshInterval(second int) {
+	regularMonitorInterval = time.Duration(second) * time.Second
+}
+
+func Watch() <-chan struct{} {
+	return watchChannel
+}
+
+func listen() {
+	signals := make(chan os.Signal)
+	signal.Notify(signals, os.Interrupt)
+	lastConfig = deepCopy(agollo2.GetAll())
+	for {
+		timer := time.NewTimer(regularMonitorInterval)
+		select {
+		case <-timer.C:
+			err := agollo2.Reload()
+			if err != nil {
+				fmt.Printf("[apollo] reload err: %s\n", err)
+				continue
+			}
+			newConfig := deepCopy(agollo2.GetAll())
+			if reflect.DeepEqual(lastConfig, newConfig) {
+				continue
+			}
+			lastConfig = newConfig
+			err = writeMapToFile()
+			if err != nil {
+				fmt.Printf("[apollo] write env err: %s\n", err)
+				continue
+			}
+			watchChannel <- struct{}{}
+		case <-signals:
+			fmt.Println("[apollo] receive signal, exited")
+			return
+		}
+	}
+	select {}
+}
+
+func getBackFilePath() string {
+	return rootPath + "/apollo/.agollo"
+}
+
+func getApolloConfigPath() string {
+	return rootPath + "/apollo/apollo.json"
+}
+
+func getNameSpacesConfigPath() string {
+	return rootPath + "/apollo/namespaces.json"
+}
+
+func getNameSpaces() []string {
+	nameSpaceList := make([]string, 0)
+	nameSpaceConfig.ForEach(func(key gjson.Result, value gjson.Result) bool {
+		nameSpaceList = append(nameSpaceList, getNameSpace(value))
+		return true
+	})
+	return nameSpaceList
+}
+
+func getNameSpace(nameSpace gjson.Result) string {
+	return nameSpace.Get("namespace").String()
+}
+
+func getSections(sections gjson.Result) gjson.Result {
+	return sections.Get("sections")
+}
+
+func getSectionName(section gjson.Result) string {
+	return section.Get("name").String()
+}
+
+func getKeys(section gjson.Result) gjson.Result {
+	return section.Get("keys")
+}
+
+func getKeyName(key gjson.Result) string {
+	return key.Get("name").String()
+}
+
+func getKeyMapTo(key gjson.Result) string {
+	return key.Get("mapTo").String()
+}
+
+func getKeysMap() map[string]map[string]map[string]string {
+	maps := map[string]map[string]map[string]string{}
+	nameSpaceConfig.ForEach(func(nameSpaceKey gjson.Result, value gjson.Result) bool {
+		nameSpace := getNameSpace(value)
+		_, ok := maps[nameSpace]
+		if !ok {
+			maps[nameSpace] = map[string]map[string]string{}
+		}
+		sections := getSections(value)
+		sections.ForEach(func(sectionKey gjson.Result, section gjson.Result) bool {
+			sectionName := getSectionName(section)
+			keys := getKeys(section)
+			keys.ForEach(func(keys gjson.Result, key gjson.Result) bool {
+				keyName := getKeyName(key)
+				keyMapTo := getKeyMapTo(key)
+				_, ok := maps[nameSpace][keyName]
+				if ok {
+					return true
+				}
+				maps[nameSpace][keyName] = map[string]string{}
+				maps[nameSpace][keyName]["section"] = sectionName
+				keyRealName := keyMapTo
+				if keyMapTo == "" {
+					keyRealName = keyName
+				}
+				maps[nameSpace][keyName]["mapTo"] = keyRealName
+				return true
+			})
+			return true
+		})
+		return true
+	})
+	return maps
+}
+
+func loadNameSpacesConfig(configPath string) error {
+	file, err := os.Open(configPath)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+	contents, err := ioutil.ReadAll(file)
+	if err != nil {
+		return err
+	}
+	nameSpaceConfig = gjson.Get(string(contents), "namespaces")
+	return nil
+}
+
+//将所有的配置信息写入标准的配置文件里面
+func writeMapToFile() error {
+	readLock.Lock()
+	defer readLock.Unlock()
+	f, err := os.Create(GetConfigPath())
+	if err != nil {
+		fmt.Printf("apollo:create config file err, err: %s", err.Error())
+		return err
+	}
+	defer f.Close()
+	w := bufio.NewWriter(f)
+	keysMap := getKeysMap()
+	for _, nameSpace := range getNameSpaces() {
+		sections := map[string][]string{}
+		_, ok := keysMap[nameSpace]
+		if !ok {
+			continue
+		}
+		ConfigInfo := agollo2.GetNameSpace(nameSpace)
+		for key, val := range ConfigInfo {
+			_, ok = keysMap[nameSpace][key]
+			if !ok {
+				continue
+			}
+			_, ok = sections[keysMap[nameSpace][key]["section"]]
+			if !ok {
+				sections[keysMap[nameSpace][key]["section"]] = make([]string, 0)
+			}
+			lineStr := fmt.Sprintf("%s=%s", keysMap[nameSpace][key]["mapTo"], val)
+			sections[keysMap[nameSpace][key]["section"]] = append(sections[keysMap[nameSpace][key]["section"]], lineStr)
+		}
+		if len(sections) == 0 {
+			continue
+		}
+		for section, lineStrs := range sections {
+			lineSection := fmt.Sprintf("[%s]", section)
+			fmt.Fprintln(w, lineSection)
+			for _, lineStr := range lineStrs {
+				fmt.Fprintln(w, lineStr)
+			}
+		}
+	}
+	err = w.Flush()
+	if err == nil {
+		return nil
+	}
+	return err
+}
+
+func deepCopy(value map[string]interface{}) map[string]interface{} {
+	newMap := make(map[string]interface{})
+	for k, v := range value {
+		newMap[k] = v
+	}
+	return newMap
+
+}

+ 175 - 0
pkg/database/common_db/common.go

@@ -0,0 +1,175 @@
+package common_db
+
+import (
+	_ "database/sql"
+	"fmt"
+	"os"
+	"strconv"
+	"time"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	. "gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	_ "github.com/go-sql-driver/mysql"
+	"xorm.io/xorm"
+	"xorm.io/xorm/log"
+)
+
+func getMysqlConfig(key string, assertNil SetAndAssertNil) (map[string]string, error) {
+	var (
+		mysqlConfig = make(map[string]string)
+	)
+	if err := assertNil(mysqlConfig, key, "dbname", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "host", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "port", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "user", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "password", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "charset", "utf8"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "slave_dbname", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "slave_host", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "slave_port", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "slave_user", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "slave_password", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "slave_charset", "utf8"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "read_timeout", "5"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "write_timeout", "5"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "timeout", "10"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "max_idle", "10"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "max_conn", "100"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "conn_max_life_time", "-1"); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "log_level", strconv.FormatInt(int64(log.LOG_WARNING), 10)); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "log_file", ""); err != nil {
+		return nil, err
+	}
+	if err := assertNil(mysqlConfig, key, "refresh_config_time", "30"); err != nil {
+		return nil, err
+	}
+	//if err := assertNil(mysqlConfig, key, "ping_connection", "30"); err != nil {
+	//	return nil, err
+	//}
+	mysqlConfig["read_timeout"] = mysqlConfig["read_timeout"] + "s"
+	mysqlConfig["write_timeout"] = mysqlConfig["write_timeout"] + "s"
+	mysqlConfig["timeout"] = mysqlConfig["timeout"] + "s"
+
+	return mysqlConfig, nil
+}
+
+func fillDns(mysqlConfig map[string]string) string {
+	return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local&readTimeout=%s&writeTimeout=%s&timeout=%s",
+		mysqlConfig["user"],
+		mysqlConfig["password"],
+		mysqlConfig["host"],
+		mysqlConfig["port"],
+		mysqlConfig["dbname"],
+		mysqlConfig["charset"],
+		mysqlConfig["read_timeout"],
+		mysqlConfig["write_timeout"],
+		mysqlConfig["timeout"])
+}
+
+func fillDnsSlave(mysqlConfig map[string]string) string {
+	return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local&readTimeout=%s&writeTimeout=%s&timeout=%s",
+		mysqlConfig["slave_user"],
+		mysqlConfig["slave_password"],
+		mysqlConfig["slave_host"],
+		mysqlConfig["slave_port"],
+		mysqlConfig["slave_dbname"],
+		mysqlConfig["slave_charset"],
+		mysqlConfig["read_timeout"],
+		mysqlConfig["write_timeout"],
+		mysqlConfig["timeout"])
+}
+
+func newSqlLogFile(sqlLogFile string) (*os.File, error) {
+	file, err := os.OpenFile(sqlLogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		return nil, err
+	}
+	return file, err
+}
+
+func newEngine(dbName string) (*xorm.EngineGroup, map[string]string, error) {
+	// 获取配置信息
+	mysqlConfig, err := getMysqlConfig(dbName, conf.SetAndAssertNil)
+	if err != nil {
+		return nil, nil, err
+	}
+	//主库
+	masterEngine, err := xorm.NewEngine("mysql", fillDns(mysqlConfig))
+	if err != nil {
+		return nil, nil, err
+	}
+	//从库
+	slaveEngine, err := xorm.NewEngine("mysql", fillDnsSlave(mysqlConfig))
+	if err != nil {
+		return nil, nil, err
+	}
+	// 1主1从
+	engineGroup, err := xorm.NewEngineGroup(masterEngine, []*xorm.Engine{slaveEngine})
+	if err != nil {
+		return nil, nil, err
+	}
+	// 最大空闲<最大连接
+	engineGroup.SetMaxIdleConns(String2Int(mysqlConfig["max_idle"]))
+	engineGroup.SetMaxOpenConns(String2Int(mysqlConfig["max_conn"]))
+	engineGroup.SetConnMaxLifetime(time.Second * time.Duration(String2Int64(mysqlConfig["conn_max_life_time"])))
+	file, err := newSqlLogFile(mysqlConfig["log_file"])
+	if err != nil {
+		// 异常,需要先释放资源
+		nerr := engineGroup.Close()
+		if nerr != nil {
+			return nil, nil, nerr
+		}
+		return nil, nil, err
+	}
+	engineGroup.SetLogger(NewDbLog(log.NewSimpleLogger(file)))                                              // todo 可能存在重新reload文件没有close释放掉
+	engineGroup.ShowSQL(true)                                                                               // 必须打印trace,否则会出现问题,span无法关闭,所以不把是否打印日志暴露出去了
+	engineGroup.Logger().SetLevel(log.LogLevel(String2Int(mysqlConfig["log_level"], int(log.LOG_WARNING)))) // 默认打印warn级别
+
+	Debugf("Db load %s config success, config=%+v.", dbName, mysqlConfig)
+	return engineGroup, mysqlConfig, nil
+}
+
+func closeEngine(group *xorm.EngineGroup) error {
+	if group == nil {
+		return nil
+	}
+	return group.Close()
+}

+ 259 - 0
pkg/database/common_db/conn.go

@@ -0,0 +1,259 @@
+package common_db
+
+import (
+	"context"
+	_ "database/sql"
+	"reflect"
+	"sync/atomic"
+	"time"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/trace"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/constant"
+	. "gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	_ "github.com/go-sql-driver/mysql"
+	"xorm.io/xorm"
+)
+
+type DbConn interface {
+	Close() error
+	DbName() string
+	NewSlaveEngine() *xorm.Engine
+	NewMasterEngine() *xorm.Engine
+	NewSlaveSession() *xorm.Session
+	NewMasterSession() *xorm.Session
+	NewSlaveCtxSession(ctx context.Context) *xorm.Session
+	NewMasterCtxSession(ctx context.Context) *xorm.Session
+}
+
+const (
+	defaultPingConnection    = 30
+	defaultRefreshConfigTime = 30
+)
+
+type dbConn struct {
+	dbName     string
+	realDbName string
+
+	config map[string]string
+	/**
+	// 原子包的速度
+	BenchmarkName-8   	1000000000	         0.817 ns/op
+	// 速写锁的速度
+	BenchmarkName-8   	28494505	        39.3 ns/op
+	// 完全不是一个量级
+	*/
+	engineGroup atomic.Value
+}
+
+func (d *dbConn) getConfig(key string) string {
+	return d.config[key]
+}
+
+func (d *dbConn) setConfig(conf map[string]string) {
+	d.config = conf
+}
+
+func (d *dbConn) getEngineGroup() *xorm.EngineGroup {
+	group := d.engineGroup.Load()
+	if group == nil {
+		return nil
+	}
+	return group.(*xorm.EngineGroup)
+}
+
+func (d *dbConn) setEngineGroup(group *xorm.EngineGroup) {
+	d.engineGroup.Store(group)
+}
+
+//主库从库engine group
+func NewDbConn(dbName string) (DbConn, error) {
+	conn := &dbConn{
+		dbName: dbName,
+	}
+	err := conn.reloadEngine()
+	if err != nil {
+		return nil, err
+	}
+
+	// 热加载
+	GoWithRecover(func() {
+		conn.checkRefreshConfig()
+	}, func(error interface{}) {
+		Errorf("[database.checkConn] run panic find err, err=%v", err)
+	})
+
+	// 健康检测 v1.0.2.3 取消,go std driver的feature支持 自动处理断开的连接, https://github.com/go-sql-driver/mysql#features
+	//go func() {
+	//	conn.checkConn()
+	//}()
+
+	return conn, nil
+}
+
+func (d *dbConn) NewSlaveEngine() *xorm.Engine {
+	return d.getEngineGroup().Slave()
+}
+
+func (d *dbConn) NewMasterEngine() *xorm.Engine {
+	return d.getEngineGroup().Master()
+}
+
+func (d *dbConn) NewSlaveSession() *xorm.Session {
+	return d.NewSlaveEngine().NewSession()
+}
+
+func (d *dbConn) NewMasterSession() *xorm.Session {
+	return d.NewMasterEngine().NewSession()
+}
+
+func (d *dbConn) NewSlaveCtxSession(ctx context.Context) *xorm.Session {
+	ctx = d.withContext(ctx)
+	return d.NewSlaveEngine().Context(ctx)
+}
+
+func (d *dbConn) NewMasterCtxSession(ctx context.Context) *xorm.Session {
+	ctx = d.withContext(ctx)
+	return d.NewMasterEngine().Context(ctx)
+}
+
+func (d *dbConn) withContext(ctx context.Context) context.Context {
+	if ctx == nil {
+		ctx = context.Background()
+	}
+	ctx = context.WithValue(ctx, constant.DbName, d.dbName)
+	span, err := trace.NewMysqlExitSpan(ctx, d.dbName)
+	if span != nil && err == nil {
+		ctx = trace.SetMySqlExitSpan(ctx, span)
+	}
+	return ctx
+}
+
+func (d *dbConn) DbName() string {
+	return d.realDbName
+}
+
+func (d *dbConn) Close() error {
+	return closeEngine(d.getEngineGroup())
+}
+
+/**
+定期检测!健康!!
+*/
+//func (d *dbConn) checkConn() {
+//	getRefreshTime := func() time.Duration {
+//		return time.Second * time.Duration(String2Int64(d.getConfig("ping_connection"), defaultPingConnection))
+//	}
+//	timer := time.NewTimer(getRefreshTime())
+//	for {
+//		<-timer.C
+//		func() {
+//			defer func() {
+//				if err := recover(); err != nil {
+//					Errorf("[database.checkConn] find panic,err=%v", err)
+//				}
+//			}()
+//			serr := d.NewSlaveSession().Ping()
+//			merr := d.NewMasterSession().Ping()
+//			if serr != nil || merr != nil {
+//				Errorf("[database.checkConn] ping err,err=%v", serr, merr)
+//				oldEngine := d.getEngineGroup()
+//				err := d.reloadEngine()
+//				if err != nil {
+//					Errorf("[database.checkConn] reload err,err=%v", err)
+//					return
+//				}
+//				go func() {
+//					err := closeEngine(oldEngine) // 异步关闭
+//					if err != nil {
+//						Errorf("[database.checkConn] close err,err=%v", err)
+//					}
+//					return
+//				}()
+//			}
+//		}()
+//		timer.Reset(getRefreshTime())
+//	}
+//}
+
+/**
+定期刷新配置,查看db是否变更!!
+*/
+func (d *dbConn) checkRefreshConfig() {
+	getRefreshTime := func() time.Duration {
+		return time.Second * time.Duration(String2Int64(d.getConfig("refresh_config_time"), defaultRefreshConfigTime))
+	}
+	timer := time.NewTimer(getRefreshTime())
+	for {
+		<-timer.C
+		func() {
+			defer func() {
+				if err := recover(); err != nil {
+					Errorf("[database.checkRefreshConfig] panic,err=%v", err)
+				}
+			}()
+			newConf, err := getMysqlConfig(d.dbName, conf.SetAndAssertNil)
+			if err != nil {
+				Errorf("[database.checkRefreshConfig] getMysqlConfig err,err=%v", err)
+				return
+			}
+			isSame := reflect.DeepEqual(d.config, newConf)
+			if !isSame {
+				oldEngine := d.getEngineGroup()
+				if err := d.reloadEngine(); err != nil {
+					Errorf("[database.checkRefreshConfig] reload err,err=%v", err)
+					return
+				}
+				go func(oldEngine *xorm.EngineGroup) {
+					defer func() {
+						if err := recover(); err != nil {
+							Errorf("[database.checkRefreshConfig] close engine panic, err=%v", err)
+						}
+					}()
+					err := closeEngine(oldEngine) // 异步关闭
+					if err != nil {
+						Errorf("[database.checkConn] close err, err=%v", err)
+					}
+				}(oldEngine)
+			}
+		}()
+		timer.Reset(getRefreshTime())
+	}
+}
+
+func (d *dbConn) reloadEngine() error {
+	group, config, err := newEngine(d.dbName)
+	if err != nil {
+		return err
+	}
+	if err := testConn(group); err != nil {
+		return err
+	}
+	d.realDbName = If(config["dbname"] == "", d.dbName, config["dbname"]).(string)
+	d.setConfig(config)
+	d.setEngineGroup(group)
+	return nil
+}
+
+/**
+测试 engine 连接, 创建 Engine时候需要测试连接
+*/
+func testConn(engine *xorm.EngineGroup) error {
+	if engine == nil {
+		return NewError("the engine group is nil can not test")
+	}
+	if engine.Slave() == nil {
+		return NewError("the slave engine is nil can not test")
+	}
+	if err := engine.Slave().Ping(); err != nil {
+		return NewError("slave engine ping find err, err: %s", err)
+	}
+	if engine.Master() == nil {
+		return NewError("the master engine is nil can not test")
+	}
+	if err := engine.Master().Ping(); err != nil {
+		return NewError("master engine ping find err, err: %s", err)
+	}
+	return nil
+}

+ 115 - 0
pkg/database/common_db/conn_test.go

@@ -0,0 +1,115 @@
+package common_db
+
+import (
+	"context"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/properties"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/trace"
+	"sync"
+	"testing"
+	"time"
+)
+
+type UserInfo struct {
+	Id         uint64    `xorm:"pk autoincr id"`
+	UserId     uint64    `xorm:"user_id"`
+	UserName   string    `xorm:"user_name"`
+	IdCard     string    `xorm:"id_card"`
+	Gender     uint8     `xorm:"gender"`
+	CreateTime time.Time `xorm:"created create_time"`
+	UpdateTime time.Time `xorm:"updated update_time"`
+}
+
+func (*UserInfo) TableName() string {
+	return "user_info"
+}
+
+// docker run -d --rm  --name mysql -m 512m -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
+var (
+	mysqlConfig = `
+mysql.host=localhost
+mysql.port=3306
+mysql.user=root
+mysql.password=123456
+mysql.dbname=gocommon
+mysql.slave_host=localhost
+mysql.slave_port=3306
+mysql.slave_user=root
+mysql.slave_password=123456
+mysql.slave_dbname=gocommon
+mysql.log_file=/data/log/gocommon/gocommon_mysql.log
+mysql.refresh_config_time=1
+mysql.read_timeout=1
+mysql.write_timeout=1
+mysql.timeout=1
+
+# trace
+trace.sky_walking_host = localhost:11800
+trace.application_name = gocommon_dev
+`
+)
+
+func initConfig(t testing.TB) properties.Properties {
+	config, err := properties.ReadFromString(mysqlConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+	conf.MustValue = func(section, key string, defaultVal ...string) string {
+		return config.GetString(section+"."+key, defaultVal ...)
+	}
+	return config
+}
+
+func TestNewDbConn(t *testing.T) {
+	config := initConfig(t)
+	conn, err := NewDbConn("mysql")
+	if err != nil {
+		t.Fatal(err)
+	}
+	wg := sync.WaitGroup{}
+	wg.Add(2)
+	// 启动后修改配置 host
+	util.GoWithRecover(func() {
+		defer wg.Done()
+		<-time.After(time.Second * 2)
+		config.SetString("mysql.slave_host", "127.0.0.2")
+	}, nil)
+
+	// 10s查询db
+	util.GoWithRecover(func() {
+		defer wg.Done()
+		for x := 0; x < 10; x++ {
+			<-time.After(time.Second)
+			info := new(UserInfo)
+			if _, err := conn.NewSlaveCtxSession(context.Background()).Where("id=?", 1).Get(info); err != nil {
+				t.Fatal(err)
+			}
+		}
+	}, nil)
+	wg.Wait()
+
+}
+
+func TestTraceDb(t *testing.T) {
+	initConfig(t)
+	if err := trace.Init(); err != nil {
+		t.Fatal(err)
+	}
+	conn, err := NewDbConn("mysql")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	ctx, span := trace.MockContext(context.Background(), "test")
+	defer func() {
+		span.End()
+		time.Sleep(time.Second * 5)
+	}()
+	t.Log(trace.GetTraceId(ctx))
+	list := make([]UserInfo, 0)
+	if err := conn.NewSlaveCtxSession(ctx).Find(&list); err != nil {
+		t.Fatal(err)
+	}
+	t.Logf("RESULT: %v", list)
+}

+ 86 - 0
pkg/database/common_db/logger.go

@@ -0,0 +1,86 @@
+package common_db
+
+import (
+	"fmt"
+	"time"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/trace"
+	"xorm.io/xorm/log"
+)
+
+/**
+copy ContextLogger
+*/
+type DbLog struct {
+	logger log.ContextLogger
+}
+
+func NewDbLog(logger *log.SimpleLogger) *DbLog {
+	if logger == nil {
+		return nil
+	}
+	return &DbLog{logger: log.NewLoggerAdapter(logger)}
+}
+
+// Debugf implements ContextLogger
+func (l *DbLog) Debugf(format string, v ...interface{}) {
+	l.logger.Debugf(format, v...)
+}
+
+// Errorf implements ContextLogger
+func (l *DbLog) Errorf(format string, v ...interface{}) {
+	l.logger.Errorf(format, v...)
+}
+
+// Infof implements ContextLogger
+func (l *DbLog) Infof(format string, v ...interface{}) {
+	l.logger.Infof(format, v...)
+}
+
+// Warnf implements ContextLogger
+func (l *DbLog) Warnf(format string, v ...interface{}) {
+	l.logger.Warnf(format, v...)
+}
+
+// Level implements ContextLogger
+func (l *DbLog) Level() log.LogLevel {
+	return l.logger.Level()
+}
+
+// SetLevel implements ContextLogger
+func (l *DbLog) SetLevel(lv log.LogLevel) {
+	l.logger.SetLevel(lv)
+}
+
+// ShowSQL implements ContextLogger
+func (l *DbLog) ShowSQL(show ...bool) {
+	l.logger.ShowSQL(show...)
+}
+
+// IsShowSQL implements ContextLogger
+func (l *DbLog) IsShowSQL() bool {
+	return l.logger.IsShowSQL()
+}
+
+// BeforeSQL implements ContextLogger
+func (m *DbLog) BeforeSQL(ctx log.LogContext) {
+	m.logger.BeforeSQL(ctx)
+}
+
+// AfterSQL implements ContextLogger
+func (m *DbLog) AfterSQL(ctx log.LogContext) {
+	m.logger.AfterSQL(ctx)
+	if ctx.Err != nil {
+		m.Errorf("Err: %v, SQL: %v, Args: %v, Time: %v", ctx.Err, ctx.SQL, ctx.Args, util.TimeToSeconds(ctx.ExecuteTime.Seconds()))
+	}
+	span := trace.GetMySqlExitSpan(ctx.Ctx)
+	if span == nil {
+		return
+	}
+	defer span.End()
+	span.Log(time.Now(), "SQL", ctx.SQL, "Args", fmt.Sprintf("%+v", ctx.Args), "Time", util.TimeToSeconds(ctx.ExecuteTime.Seconds()))
+	if ctx.Err != nil {
+		span.Log(time.Now(), "ERROR", fmt.Sprintf("%v", ctx.Err))
+	}
+}

+ 30 - 0
pkg/database/common_db/test.sql

@@ -0,0 +1,30 @@
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for user_info
+-- ----------------------------
+DROP TABLE IF EXISTS `user_info`;
+CREATE TABLE `user_info` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
+  `user_name` varchar(10) NOT NULL COMMENT '用户名称',
+  `id_card` char(18) NOT NULL DEFAULT '' COMMENT '身份证号',
+  `gender` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别 0:未知 1:男 2:女',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`) USING BTREE,
+  UNIQUE KEY `uk_user_id` (`user_id`),
+  KEY `idx_user_name` (`user_name`),
+  KEY `idx_id_card` (`id_card`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='用户测试表';
+
+-- ----------------------------
+-- Records of user_info
+-- ----------------------------
+BEGIN;
+INSERT INTO `user_info` VALUES (1, 1, 'tom', '14051119980526572X', 1, '2021-05-22 03:10:11', '2021-05-22 03:10:11');
+INSERT INTO `user_info` VALUES (2, 2, 'tony', '140511199907236411', 2, '2021-05-22 03:10:35', '2021-05-22 03:10:37');
+COMMIT;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 50 - 0
pkg/database/db/conn.go

@@ -0,0 +1,50 @@
+package db
+
+import (
+	"context"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/database/common_db"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"xorm.io/xorm"
+)
+
+var (
+	group common_db.DbConn
+)
+
+//主库从库engine group
+func Init() error {
+	newGroup, err := common_db.NewDbConn("mysql")
+	if err != nil {
+		return err
+	}
+	group = newGroup
+	return nil
+}
+
+func Close() {
+	err := group.Close()
+	if err != nil {
+		util.Errorf("[DB] Close err, err=%v", err)
+	}
+}
+
+//主库engine
+func StdMasterDB() *xorm.Engine {
+	return group.NewMasterEngine()
+}
+
+//从库engine
+func StdSlaveDB() *xorm.Engine {
+	return group.NewSlaveEngine()
+}
+
+//主库engine
+func NewSlaveSession(ctx context.Context) *xorm.Session {
+	return group.NewSlaveCtxSession(ctx)
+}
+
+//从库engine
+func NewMasterSession(ctx context.Context) *xorm.Session {
+	return group.NewMasterCtxSession(ctx)
+}

+ 136 - 0
pkg/database/elastic/demo.go

@@ -0,0 +1,136 @@
+package elastic
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"time"
+
+	"github.com/olivere/elastic"
+)
+
+const mapping = `
+{
+	"settings":{
+		"number_of_shards": 1,
+		"number_of_replicas": 0
+	},
+	"mappings":{
+		"tweet":{
+			"properties":{
+				"user":{
+					"type":"keyword"
+				},
+				"message":{
+					"type":"text",
+					"store": true,
+					"fielddata": true
+				},
+				"image":{
+					"type":"keyword"
+				},
+				"created":{
+					"type":"date"
+				},
+				"tags":{
+					"type":"keyword"
+				},
+				"location":{
+					"type":"geo_point"
+				},
+				"suggest_field":{
+					"type":"completion"
+				}
+			}
+		}
+	}
+}`
+
+type Tweet struct {
+	User     string                `json:"user"`
+	Message  string                `json:"message"`
+	Retweets int                   `json:"retweets"`
+	Image    string                `json:"image,omitempty"`
+	Created  time.Time             `json:"created,omitempty"`
+	Tags     []string              `json:"tags,omitempty"`
+	Location string                `json:"location,omitempty"`
+	Suggest  *elastic.SuggestField `json:"suggest_field,omitempty"`
+}
+
+func Demo() {
+	ctx := context.Background()
+	client := getElasticClient()
+	index := "twitter"
+	indexType := "tweet"
+	DeleteIndex(index)
+	CreateIndex(index, mapping)
+	// Index a tweet (using JSON serialization)
+	tweet1 := Tweet{User: "olivere", Message: "Take Five", Retweets: 0}
+	PutBodyJson(index, indexType, "1", tweet1)
+	// Index a second tweet (by string)
+	tweet2 := `{"user" : "olivere", "message" : "It's a Raggy Waltz"}`
+	PutBodyString(index, indexType, "2", tweet2)
+
+	// Get tweet with specified ID
+	get1, err := GetResult(index, indexType, "1")
+	if err == nil {
+		var tweetGet1 Tweet
+		tweetGetByte1, _ := get1.Source.MarshalJSON()
+		err := json.Unmarshal(tweetGetByte1, &tweetGet1)
+		fmt.Printf("Got err %s, tweetGetByte1 %+v \n", err, tweetGet1)
+	}
+
+	// Flush to make sure the documents got written.
+	//Flush(index)
+
+	// Search with a term query
+	termQuery := elastic.NewTermQuery("user", "olivere")
+	searchResult, err := client.Search().
+		Index(index).       // search in index "twitter"
+		Query(termQuery).   // specify the query
+		Sort("user", true). // sort by "user" field, ascending
+		From(0).Size(10).   // take documents 0-9
+		Pretty(true).       // pretty print request and response JSON
+		Do(ctx)             // execute
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("Query took %d milliseconds\n", searchResult.TookInMillis)
+
+	var ttyp Tweet
+	for _, item := range searchResult.Each(reflect.TypeOf(ttyp)) {
+		if t, ok := item.(Tweet); ok {
+			fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
+		}
+	}
+	fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())
+
+	if searchResult.Hits.TotalHits > 0 {
+		fmt.Printf("Found a total of %d tweets\n", searchResult.Hits.TotalHits)
+		for _, hit := range searchResult.Hits.Hits {
+
+			var t Tweet
+			err := json.Unmarshal(*hit.Source, &t)
+			if err != nil {
+			}
+
+			fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
+		}
+	} else {
+		fmt.Print("Found no tweets\n")
+	}
+
+	update, err := client.Update().Index("twitter").Type("tweet").Id("1").
+		Script(elastic.NewScriptInline("ctx._source.retweets += params.num").Lang("painless").Param("num", 1)).
+		Upsert(map[string]interface{}{"retweets": 0}).
+		Do(ctx)
+	if err != nil {
+		panic(err)
+	}
+	fmt.Printf("New version of tweet %q is now %d\n", update.Id, update.Version)
+
+	// Delete an index.
+	//DeleteIndex(index)
+}

+ 208 - 0
pkg/database/elastic/elastic.go

@@ -0,0 +1,208 @@
+package elastic
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+
+	"log"
+
+	"github.com/olivere/elastic"
+)
+
+var (
+	elasticClient *elastic.Client
+	elasticHost   string
+)
+
+func Init() error {
+	return initElastic()
+}
+
+func initElastic() error {
+	ctx := context.Background()
+	config := make(map[string]string, 0)
+	if err := conf.SetAndAssertNil(config, "elastic", "host"); err != nil {
+		return err
+	}
+	if err := conf.SetAndAssertNil(config, "elastic", "log_file"); err != nil {
+		return err
+	}
+	var (
+		host = config["host"]
+	)
+	errorLog, err := getLogger(config["log_file"])
+	client, err := elastic.NewClient(elastic.SetErrorLog(errorLog), elastic.SetURL(host))
+	if err != nil {
+		panic(err)
+	}
+	info, code, err := client.Ping(host).Do(ctx)
+	if err != nil {
+		return err
+	}
+	esVersion, err := client.ElasticsearchVersion(host)
+	if err != nil {
+		return err
+	}
+	util.Infof("[Elasticsearch] version %s, returned with code %d and version %s ", esVersion, code, info.Version.Number)
+	elasticClient = client
+	elasticHost = host
+	return nil
+}
+
+func ping() {
+	ctx := context.Background()
+	info, code, err := elasticClient.Ping(elasticHost).Do(ctx)
+	if err != nil {
+		initElastic()
+		return
+	}
+	fmt.Printf("Elasticsearch Ping returned with code %d and version %s\n", code, info.Version.Number)
+}
+
+func getLogger(logFile string) (*log.Logger, error) {
+	file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
+	if err != nil {
+		return nil, err
+	}
+	return log.New(file, "report", log.LstdFlags), nil
+}
+
+func getElasticClient() *elastic.Client {
+	ping()
+	return elasticClient
+}
+
+func CreateIndex(index, mapping string) {
+	ctx := context.Background()
+	client := getElasticClient()
+	exists := IndexExists(index)
+	if !exists {
+		// Create a new index.
+		createIndex, err := client.CreateIndex(index).BodyString(mapping).Do(ctx)
+		if err != nil {
+			// Handle error
+			panic(err)
+		}
+		if !createIndex.Acknowledged {
+			// Not acknowledged
+		}
+		fmt.Printf("CreateIndex Index %s , mapping %s, createIndex:%+v \n", index, mapping, createIndex)
+	}
+
+}
+
+func IndexExists(index string) bool {
+	ctx := context.Background()
+	client := getElasticClient()
+	exists, err := client.IndexExists(index).Do(ctx)
+	if err != nil {
+		panic(err)
+	}
+	return exists
+}
+
+func PutBodyJson(index, indexType, id string, bodyJson interface{}) bool {
+	ctx := context.Background()
+	client := getElasticClient()
+	exists := IndexExists(index)
+	if exists {
+		put, err := client.Index().
+			Index(index).
+			Type(indexType).
+			Id(id).
+			BodyJson(bodyJson).
+			Do(ctx)
+		if err != nil {
+			// Handle error
+			panic(err)
+		}
+		fmt.Printf("PutBodyJson Index put %+v id:%s to index:%s, type %s , put %+v \n", bodyJson, put.Id, put.Index, put.Type, put)
+		return put.Status == 0
+	} else {
+		return false
+	}
+}
+
+func PutBodyString(index, indexType, id, BodyString string) bool {
+	ctx := context.Background()
+	client := getElasticClient()
+	exists := IndexExists(index)
+	if exists {
+		put, err := client.Index().
+			Index(index).
+			Type(indexType).
+			Id(id).
+			BodyString(BodyString).
+			Do(ctx)
+		if err != nil {
+			// Handle error
+			panic(err)
+		}
+		fmt.Printf("PutBodyString Index put %+v id:%s to index:%s, type %s , put %+v \n", BodyString, put.Id, put.Index, put.Type, put)
+		return put.Status == 0
+	} else {
+		return false
+	}
+}
+
+func DeleteIndex(index string) bool {
+	ctx := context.Background()
+	client := getElasticClient()
+	exists := IndexExists(index)
+	if exists {
+		deleteIndex, err := client.DeleteIndex(index).Do(ctx)
+		if err != nil {
+			panic(err)
+		}
+		if !deleteIndex.Acknowledged {
+		}
+		fmt.Printf("DeleteIndex Index index:%s, deleteIndex: %+v \n", index, deleteIndex)
+		return deleteIndex.Acknowledged
+	} else {
+		return false
+	}
+}
+
+func Flush(index string) bool {
+	ctx := context.Background()
+	client := getElasticClient()
+	exists := IndexExists(index)
+	if exists {
+		// Flush to make sure the documents got written.
+		_, err := client.Flush().Index(index).Do(ctx)
+		if err != nil {
+			panic(err)
+		}
+		return true
+	} else {
+		return false
+	}
+}
+
+func GetResult(index, indexType, id string) (*elastic.GetResult, error) {
+	ctx := context.Background()
+	client := getElasticClient()
+	exists := IndexExists(index)
+	if exists {
+		get, err := client.Get().
+			Index(index).
+			Type(indexType).
+			Id(id).
+			Do(ctx)
+		if err != nil {
+			// Handle error
+			panic(err)
+		}
+		if get.Found {
+			fmt.Printf("Got document %s in version %d from index %s, type %s\n", get.Id, get.Version, get.Index, get.Type)
+		}
+		return get, err
+	} else {
+		return nil, fmt.Errorf("index not found")
+	}
+}

+ 49 - 0
pkg/database/multi_db/cnn.go

@@ -0,0 +1,49 @@
+package multi_db
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/conf"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/database/common_db"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+)
+
+var (
+	notFoundError = func(dbName string) error {
+		return errors.New(fmt.Sprintf("MultiDb not fount %s db", dbName))
+	}
+	lock     sync.RWMutex
+	multiMap map[string]common_db.DbConn = map[string]common_db.DbConn{}
+)
+
+func Init() error {
+	dbNameStr := conf.GetString("multi_db", "db_name")
+	dbNames := util.SplitIgnoreSpace(dbNameStr, ",")
+	for _, elem := range dbNames {
+		conn, err := common_db.NewDbConn(elem)
+		if err != nil {
+			return err
+		}
+		setDb(elem, conn)
+		util.Infof("[MultiDB] init db %s success !", elem)
+	}
+	return nil
+}
+
+func setDb(dbName string, conn common_db.DbConn) {
+	lock.Lock()
+	defer lock.Unlock()
+	multiMap[dbName] = conn
+}
+
+func GetDb(dbName string) (common_db.DbConn, error) {
+	lock.RLock()
+	defer lock.RUnlock()
+	db, isExist := multiMap[dbName]
+	if isExist {
+		return db, nil
+	}
+	return nil, notFoundError(dbName)
+}

+ 1 - 0
pkg/database/multi_db/conn.go

@@ -0,0 +1 @@
+package multi_db

+ 11 - 0
pkg/internal/application/context.go

@@ -0,0 +1,11 @@
+package application
+
+import (
+	"context"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/logger"
+)
+
+func NewContext() context.Context {
+	return logger.NewTraceIdContext()
+}

+ 18 - 0
pkg/internal/application/signal.go

@@ -0,0 +1,18 @@
+package application
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+// 这个会通知所有的 channel, 如果敲击了 Ctrl+C 或者 Stop 应用程序 或 结束程序(可以被捕获、阻塞或忽略)
+func GetSignal() chan os.Signal {
+	signals := make(chan os.Signal)
+	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) // ctrl + C
+	return signals
+}
+
+func GetPID() int {
+	return os.Getpid()
+}

+ 13 - 0
pkg/internal/application/signal_test.go

@@ -0,0 +1,13 @@
+package application
+
+import (
+	"fmt"
+	"os"
+	"testing"
+)
+
+func TestGetSignal(t *testing.T) {
+	fmt.Println(os.Getpid())
+	<-GetSignal()
+	fmt.Println("signal")
+}

+ 13 - 0
pkg/internal/constant/constant.go

@@ -0,0 +1,13 @@
+package constant
+
+// ctx 常量
+const (
+	DbName    = "_db_name"
+	DbSpanKey = "_db_span_key"
+
+	// 配置项
+	ApplicationName = "application"
+	EnvName         = "env"
+	ProjectName     = "project_name"
+	PortName        = "port"
+)

+ 35 - 0
pkg/internal/file/rotatefile.go

@@ -0,0 +1,35 @@
+package file
+
+import (
+	"io"
+	"os"
+	"path/filepath"
+	"time"
+
+	rotatelogs "github.com/lestrrat/go-file-rotatelogs"
+)
+
+// 参考 https://github.com/nacos-group/nacos-sdk-go/blob/v1.0.4/common/logger/logger.go#L130
+type RotateWriterConfig struct {
+	Pattern        string        `json:"pattern"`
+	FileName       string        `json:"file_path"`
+	MaxAge         time.Duration `json:"max_age"`
+	RotateDuration time.Duration `json:"rotate_time"`
+}
+
+func NewDefaultRotateWriter(fileName string) RotateWriterConfig {
+	return RotateWriterConfig{
+		FileName:       fileName,
+		MaxAge:         time.Hour * 24 * 30,
+		RotateDuration: time.Hour * 24,
+		Pattern:        "%Y%m%d%H%M",
+	}
+}
+
+func (config RotateWriterConfig) NewWriter() (io.Writer, error) {
+	fileSimpleName := filepath.Base(config.FileName)
+	outputPath := filepath.Dir(config.FileName) + string(os.PathSeparator)
+	return rotatelogs.New(filepath.Join(outputPath, fileSimpleName+"-"+config.Pattern),
+		rotatelogs.WithRotationTime(config.RotateDuration), rotatelogs.WithMaxAge(config.MaxAge),
+		rotatelogs.WithLinkName(filepath.Join(outputPath, fileSimpleName)))
+}

+ 25 - 0
pkg/internal/json/jsoniter.go

@@ -0,0 +1,25 @@
+// Copyright 2017 Bo-Yi Wu.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+/**
+// +build jsoniter
+*/
+
+package json
+
+import jsoniter "github.com/json-iterator/go"
+
+var (
+	json = jsoniter.ConfigCompatibleWithStandardLibrary
+	// Marshal is exported by gin/json package.
+	Marshal = json.Marshal
+	// Unmarshal is exported by gin/json package.
+	Unmarshal = json.Unmarshal
+	// MarshalIndent is exported by gin/json package.
+	MarshalIndent = json.MarshalIndent
+	// NewDecoder is exported by gin/json package.
+	NewDecoder = json.NewDecoder
+	// NewEncoder is exported by gin/json package.
+	NewEncoder = json.NewEncoder
+)

+ 206 - 0
pkg/internal/nacos/config.go

@@ -0,0 +1,206 @@
+package nacos
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	cons "gitea.ckfah.com/cjjy/gocommon/pkg/internal/constant"
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+	"github.com/nacos-group/nacos-sdk-go/clients"
+	"github.com/nacos-group/nacos-sdk-go/clients/config_client"
+	"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
+	"github.com/nacos-group/nacos-sdk-go/common/constant"
+)
+
+// go get golang.org/x/tools/cmd/stringer
+//go:generate stringer -type=NamingType
+type NamingType uint8
+
+const (
+	NacosDiscover NamingType = iota + 1
+	NacosRegister
+)
+
+type NamingInfo struct {
+	ProjectName string
+	GroupName   string
+	ClusterName string
+	ServerPort  uint64
+	ServerHost  string
+	naming_client.INamingClient
+}
+
+type ConfigInfo struct {
+	DataId string
+	Group  string
+	config_client.IConfigClient
+}
+
+func NewHost(str string) ([]constant.ServerConfig, error) {
+	split := strings.Split(str, ",")
+	result := make([]constant.ServerConfig, 0, len(split))
+	for _, elem := range split {
+		if strings.TrimSpace(elem) == "" {
+			continue
+		}
+		ipAndPort := strings.Split(elem, ":")
+		if len(ipAndPort) != 2 {
+			return nil, errors.New("ip and port 解析失败")
+		}
+		result = append(result, constant.ServerConfig{
+			IpAddr: ipAndPort[0],
+			Port:   util.String2Uint64(ipAndPort[1]),
+		})
+	}
+	if len(result) == 0 {
+		return nil, errors.New("nacos not set host")
+	}
+	return result, nil
+}
+
+func getNacosConfig(key string, setAndAssertNilFunc util.SetAndAssertNil) (map[string]string, error) {
+	config := make(map[string]string, 0)
+	if err := setAndAssertNilFunc(config, key, "host"); err != nil {
+		return nil, err
+	}
+	if err := setAndAssertNilFunc(config, key, "log_path"); err != nil {
+		return nil, err
+	}
+	if err := setAndAssertNilFunc(config, key, "log_level", "debug"); err != nil {
+		return nil, err
+	}
+	if err := setAndAssertNilFunc(config, key, "namespace_id"); err != nil {
+		util.Warnf("Nacos config find warn: %v", err)
+	}
+	if err := setAndAssertNilFunc(config, key, "user_name"); err != nil {
+		//return nil, err
+		util.Warnf("Nacos config find warn: %v", err)
+	}
+	if err := setAndAssertNilFunc(config, key, "password"); err != nil {
+		//return nil, err
+		util.Warnf("Nacos config find warn: %v", err)
+	}
+	if err := setAndAssertNilFunc(config, key, "timeout", "5000"); err != nil {
+		return nil, err
+	}
+	//if err := setAndAssertNilFunc(config, key, "listen_interval", "10000"); err != nil {
+	//	return nil, err
+	//}
+	return config, nil
+}
+
+func NewNacosNameClient(_type NamingType, setAndAssertNilFunc util.SetAndAssertNil) (*NamingInfo, error) {
+	config, err := getNacosConfig("nacos-server", setAndAssertNilFunc)
+	if err != nil {
+		return nil, err
+	}
+	hosts, err := NewHost(config["host"])
+	if err != nil {
+		return nil, err
+	}
+
+	switch _type {
+	case NacosRegister:
+		_ = setAndAssertNilFunc(config, "nacos-server", "server_port")
+		if config["server_port"] == "" {
+			configTmp := map[string]string{}
+			if err := setAndAssertNilFunc(configTmp, cons.ApplicationName, cons.PortName); err != nil {
+				return nil, errors.New(fmt.Sprintf("config nacos.server_port or %s.%s == nil", cons.ApplicationName, cons.PortName))
+			}
+			// 兼容历史逻辑
+			config["server_port"] = configTmp[cons.PortName]
+		}
+		_ = setAndAssertNilFunc(config, "nacos-server", "server_name")
+		if config["server_name"] == "" {
+			configTmp := map[string]string{}
+			if err := setAndAssertNilFunc(configTmp, cons.ApplicationName, cons.ProjectName); err != nil {
+				return nil, errors.New(fmt.Sprintf("config nacos.project_name or %s.%s is null", cons.ApplicationName, cons.ProjectName))
+			}
+			// 兼容历史逻辑
+			config["server_name"] = configTmp[cons.ProjectName]
+		}
+		// 这些默认值的不需要校验
+		if err := setAndAssertNilFunc(config, "nacos-server", "server_host", ""); err != nil {
+			util.Warnf("Nacos config find warn: %v", err)
+			localIP, ipErr := util.GetLocalIP()
+			if ipErr != nil {
+				return nil, ipErr
+			}
+			config["server_host"] = localIP
+		}
+		if err := setAndAssertNilFunc(config, "nacos-server", "group_name", "DEFAULT_GROUP"); err != nil {
+			return nil, err
+		}
+		if err := setAndAssertNilFunc(config, "nacos-server", "cluster_name", "DEFAULT"); err != nil {
+			return nil, err
+		}
+	case NacosDiscover:
+
+	}
+
+	nacosClient, err := clients.CreateNamingClient(map[string]interface{}{
+		"serverConfigs": hosts,
+		"clientConfig": constant.ClientConfig{
+			LogLevel:  config["log_level"], // debug,info,warn,error
+			TimeoutMs: util.String2Uint64(config["timeout"]),
+			//ListenInterval:      util.String2Uint64(config["listen_interval"]), // 无效字段
+			NotLoadCacheAtStart: true,
+			LogDir:              config["log_path"],
+			Username:            config["user_name"],
+			Password:            config["password"],
+			NamespaceId:         config["namespace_id"],
+		},
+	})
+	if err != nil {
+		return nil, err
+	}
+	util.Debugf("%s load config success, config=%+v", _type, config)
+	return &NamingInfo{
+		ProjectName:   config["server_name"],
+		ServerPort:    util.String2Uint64(config["server_port"]),
+		ServerHost:    config["server_host"],
+		GroupName:     config["group_name"],
+		ClusterName:   config["cluster_name"],
+		INamingClient: nacosClient,
+	}, nil
+}
+
+func NewNacosConfigClient(setAndAssertNilFunc util.SetAndAssertNil) (*ConfigInfo, error) {
+	config, err := getNacosConfig("nacos-config", setAndAssertNilFunc)
+	if err != nil {
+		return nil, err
+	}
+	if err := setAndAssertNilFunc(config, "nacos-config", "data_id"); err != nil {
+		return nil, err
+	}
+	if err := setAndAssertNilFunc(config, "nacos-config", "group", "DEFAULT_GROUP"); err != nil {
+		return nil, err
+	}
+	hosts, err := NewHost(config["host"])
+	if err != nil {
+		return nil, err
+	}
+	client, err := clients.CreateConfigClient(map[string]interface{}{
+		"serverConfigs": hosts,
+		"clientConfig": constant.ClientConfig{
+			LogLevel:  config["log_level"], // debug,info,warn,error
+			TimeoutMs: util.String2Uint64(config["timeout"]),
+			//ListenInterval:      util.String2Uint64(config["listen_interval"]), // 无效字段
+			NotLoadCacheAtStart: true,
+			LogDir:              config["log_dir"],
+			Username:            config["user_name"],
+			Password:            config["password"],
+			NamespaceId:         config["namespace_id"],
+		},
+	})
+	if err != nil {
+		return nil, err
+	}
+	util.Debugf("NacosConfig load config success, config=%+v", config)
+	return &ConfigInfo{
+		DataId:        config["data_id"],
+		Group:         config["group"],
+		IConfigClient: client,
+	}, nil
+}

+ 35 - 0
pkg/internal/nacos/config_test.go

@@ -0,0 +1,35 @@
+package nacos
+
+import (
+	"fmt"
+	"github.com/nacos-group/nacos-sdk-go/clients"
+	"github.com/nacos-group/nacos-sdk-go/common/constant"
+	"github.com/nacos-group/nacos-sdk-go/vo"
+	"testing"
+)
+
+func TestName(t *testing.T) {
+	hosts, _ := NewHost("10.100.101.20:8848,10.100.99.14:8848")
+	iClient, err := clients.CreateNamingClient(map[string]interface{}{
+		"serverConfigs": hosts,
+		"clientConfig": constant.ClientConfig{
+			TimeoutMs: 5000,
+			//ListenInterval:      util.String2Uint64(config["listen_interval"]), // 无效字段
+			NotLoadCacheAtStart: true,
+			LogDir:              "/data/log/go-template",
+			Username:            "nacos",
+			Password:            "nacos",
+			//NamespaceId:         "ed35034b-634e-4ed5-9266-d7366d389351",
+		},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	instances, err := iClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{
+		ServiceName: "mep",
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(instances)
+}

+ 25 - 0
pkg/internal/nacos/namingtype_string.go

@@ -0,0 +1,25 @@
+// Code generated by "stringer -type=NamingType"; DO NOT EDIT.
+
+package nacos
+
+import "strconv"
+
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[NacosDiscover-1]
+	_ = x[NacosRegister-2]
+}
+
+const _NamingType_name = "NacosDiscoverNacosRegister"
+
+var _NamingType_index = [...]uint8{0, 13, 26}
+
+func (i NamingType) String() string {
+	i -= 1
+	if i >= NamingType(len(_NamingType_index)-1) {
+		return "NamingType(" + strconv.FormatInt(int64(i+1), 10) + ")"
+	}
+	return _NamingType_name[_NamingType_index[i]:_NamingType_index[i+1]]
+}

+ 203 - 0
pkg/internal/prettyjson/json.go

@@ -0,0 +1,203 @@
+/**
+COPY https://github.com/hokaccha/go-prettyjson/blob/master/prettyjson.go
+*/
+// Package prettyjson provides JSON pretty print.
+package prettyjson
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+
+	"github.com/fatih/color"
+)
+
+// Formatter is a struct to format JSON data. `color` is github.com/fatih/color: https://github.com/fatih/color
+type Formatter struct {
+	// JSON key color. Default is `color.New(color.FgBlue, color.Bold)`.
+	KeyColor *color.Color
+
+	// JSON string value color. Default is `color.New(color.FgGreen, color.Bold)`.
+	StringColor *color.Color
+
+	// JSON boolean value color. Default is `color.New(color.FgYellow, color.Bold)`.
+	BoolColor *color.Color
+
+	// JSON number value color. Default is `color.New(color.FgCyan, color.Bold)`.
+	NumberColor *color.Color
+
+	// JSON null value color. Default is `color.New(color.FgBlack, color.Bold)`.
+	NullColor *color.Color
+
+	// Max length of JSON string value. When the value is 1 and over, string is truncated to length of the value.
+	// Default is 0 (not truncated).
+	StringMaxLength int
+
+	// Boolean to disable color. Default is false.
+	DisabledColor bool
+
+	// Indent space number. Default is 2.
+	Indent int
+
+	// Newline string. To print without new lines set it to empty string. Default is \n.
+	Newline string
+}
+
+// NewFormatter returns a new formatter with following default values.
+func NewFormatter() *Formatter {
+	return &Formatter{
+		KeyColor:        color.New(color.FgBlue, color.Bold),
+		StringColor:     color.New(color.FgGreen, color.Bold),
+		BoolColor:       color.New(color.FgYellow, color.Bold),
+		NumberColor:     color.New(color.FgCyan, color.Bold),
+		NullColor:       color.New(color.FgBlack, color.Bold),
+		StringMaxLength: 0,
+		DisabledColor:   false,
+		Indent:          2,
+		Newline:         "\n",
+	}
+}
+
+// Marshal marshals and formats JSON data.
+func (f *Formatter) Marshal(v interface{}) ([]byte, error) {
+	data, err := json.Marshal(v)
+	if err != nil {
+		return nil, err
+	}
+
+	return f.Format(data)
+}
+
+// Format formats JSON string.
+func (f *Formatter) Format(data []byte) ([]byte, error) {
+	var v interface{}
+	decoder := json.NewDecoder(bytes.NewReader(data))
+	decoder.UseNumber()
+	if err := decoder.Decode(&v); err != nil {
+		return nil, err
+	}
+
+	return []byte(f.pretty(v, 1)), nil
+}
+
+func (f *Formatter) sprintfColor(c *color.Color, format string, args ...interface{}) string {
+	if f.DisabledColor || c == nil {
+		return fmt.Sprintf(format, args...)
+	}
+	return c.SprintfFunc()(format, args...)
+}
+
+func (f *Formatter) sprintColor(c *color.Color, s string) string {
+	if f.DisabledColor || c == nil {
+		return fmt.Sprint(s)
+	}
+	return c.SprintFunc()(s)
+}
+
+func (f *Formatter) pretty(v interface{}, depth int) string {
+	switch val := v.(type) {
+	case string:
+		return f.processString(val)
+	case float64:
+		return f.sprintColor(f.NumberColor, strconv.FormatFloat(val, 'f', -1, 64))
+	case json.Number:
+		return f.sprintColor(f.NumberColor, string(val))
+	case bool:
+		return f.sprintColor(f.BoolColor, strconv.FormatBool(val))
+	case nil:
+		return f.sprintColor(f.NullColor, "null")
+	case map[string]interface{}:
+		return f.processMap(val, depth)
+	case []interface{}:
+		return f.processArray(val, depth)
+	}
+
+	return ""
+}
+
+func (f *Formatter) processString(s string) string {
+	r := []rune(s)
+	if f.StringMaxLength != 0 && len(r) >= f.StringMaxLength {
+		s = string(r[0:f.StringMaxLength]) + "..."
+	}
+
+	buf := &bytes.Buffer{}
+	encoder := json.NewEncoder(buf)
+	encoder.SetEscapeHTML(false)
+	encoder.Encode(s)
+	s = string(buf.Bytes())
+	s = strings.TrimSuffix(s, "\n")
+
+	return f.sprintColor(f.StringColor, s)
+}
+
+func (f *Formatter) processMap(m map[string]interface{}, depth int) string {
+	if len(m) == 0 {
+		return "{}"
+	}
+
+	currentIndent := f.generateIndent(depth - 1)
+	nextIndent := f.generateIndent(depth)
+	rows := []string{}
+	keys := []string{}
+
+	for key := range m {
+		keys = append(keys, key)
+	}
+
+	sort.Strings(keys)
+
+	for _, key := range keys {
+		val := m[key]
+		buf := &bytes.Buffer{}
+		encoder := json.NewEncoder(buf)
+		encoder.SetEscapeHTML(false)
+		encoder.Encode(key)
+		s := strings.TrimSuffix(string(buf.Bytes()), "\n")
+		k := f.sprintColor(f.KeyColor, s)
+		v := f.pretty(val, depth+1)
+
+		valueIndent := " "
+		if f.Newline == "" {
+			valueIndent = ""
+		}
+		row := fmt.Sprintf("%s%s:%s%s", nextIndent, k, valueIndent, v)
+		rows = append(rows, row)
+	}
+
+	return fmt.Sprintf("{%s%s%s%s}", f.Newline, strings.Join(rows, ","+f.Newline), f.Newline, currentIndent)
+}
+
+func (f *Formatter) processArray(a []interface{}, depth int) string {
+	if len(a) == 0 {
+		return "[]"
+	}
+
+	currentIndent := f.generateIndent(depth - 1)
+	nextIndent := f.generateIndent(depth)
+	rows := []string{}
+
+	for _, val := range a {
+		c := f.pretty(val, depth+1)
+		row := nextIndent + c
+		rows = append(rows, row)
+	}
+	return fmt.Sprintf("[%s%s%s%s]", f.Newline, strings.Join(rows, ","+f.Newline), f.Newline, currentIndent)
+}
+
+func (f *Formatter) generateIndent(depth int) string {
+	return strings.Repeat(" ", f.Indent*depth)
+}
+
+// Marshal JSON data with default options.
+func Marshal(v interface{}) ([]byte, error) {
+	return NewFormatter().Marshal(v)
+}
+
+// Format JSON string with default options.
+func Format(data []byte) ([]byte, error) {
+	return NewFormatter().Format(data)
+}

+ 174 - 0
pkg/internal/properties/common.go

@@ -0,0 +1,174 @@
+package properties
+
+import (
+	"bufio"
+	"bytes"
+	"io"
+	"strings"
+	"sync"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/internal/util"
+)
+
+type Properties interface {
+	// changeMap 切片两个值,第一个是老得值,第二个是新的值,并发安全
+	DiffProperties(pro Properties) (deleteMap, addMap map[string]string, changeMap map[string][]string)
+	SetString(key string, value string)
+	GetString(key string, defaultValue ...string) (value string)
+	GetBool(key string, defaultValue ...bool) (value bool)
+	GetInt64(key string, defaultValue ...int64) (value int64)
+	GetUint64(key string, defaultValue ...uint64) (value uint64)
+	GetFloat64(key string, defaultValue ...float64) (value float64)
+	GetAll() map[string]string
+}
+
+type properties struct {
+	config map[string]string
+	sync.RWMutex
+}
+
+func NewProperties() Properties {
+	return &properties{
+		config: map[string]string{},
+	}
+}
+
+func ReadFromBytes(content []byte) (Properties, error) {
+	config, err := readStr(content)
+	if err != nil {
+		return nil, err
+	}
+	return &properties{
+		config: config,
+	}, nil
+}
+
+func ReadFromString(content string) (Properties, error) {
+	config, err := readStr([]byte(content))
+	if err != nil {
+		return nil, err
+	}
+	return &properties{
+		config: config,
+	}, nil
+}
+
+func ReadFromReader(reader io.Reader) (Properties, error) {
+	config, err := readReader(reader)
+	if err != nil {
+		return nil, err
+	}
+	return &properties{
+		config: config,
+	}, nil
+}
+
+func (p *properties) GetString(key string, defaultValue ...string) string {
+	p.RLock()
+	defer p.RUnlock()
+	value, isExist := p.config[key]
+	if !isExist || value == "" {
+		if defaultValue == nil || len(defaultValue) == 0 {
+			return ""
+		}
+		return defaultValue[0]
+	}
+	return value
+}
+
+func (p *properties) SetString(key string, value string) {
+	p.Lock()
+	defer p.Unlock()
+	p.config[key] = value
+}
+
+func (p *properties) GetInt64(key string, defaultValue ...int64) (value int64) {
+	p.RLock()
+	defer p.RUnlock()
+	return util.String2Int64(p.config[key], defaultValue...)
+}
+
+func (p *properties) GetUint64(key string, defaultValue ...uint64) (value uint64) {
+	p.RLock()
+	defer p.RUnlock()
+	return util.String2Uint64(p.config[key], defaultValue...)
+}
+
+func (p *properties) GetFloat64(key string, defaultValue ...float64) (value float64) {
+	p.RLock()
+	defer p.RUnlock()
+	return util.String2Float64(p.config[key], defaultValue...)
+}
+
+func (p *properties) GetBool(key string, defaultValue ...bool) (value bool) {
+	p.RLock()
+	defer p.RUnlock()
+	return util.String2Bool(p.config[key], defaultValue...)
+}
+
+func (p *properties) GetAll() map[string]string {
+	p.RLock()
+	defer p.RUnlock()
+	result := make(map[string]string, len(p.config))
+	for key, value := range p.config {
+		result[key] = value
+	}
+	return result
+}
+
+func readStr(str []byte) (map[string]string, error) {
+	if str == nil || len(str) == 0 {
+		return map[string]string{}, nil
+	}
+	buffer := bytes.Buffer{}
+	_, err := buffer.Write(str)
+	if err != nil {
+		return nil, err
+	}
+	return readReader(&buffer)
+}
+
+func readReader(reader io.Reader) (map[string]string, error) {
+	if reader == nil {
+		return map[string]string{}, nil
+	}
+	r := bufio.NewReader(reader)
+	config := make(map[string]string, 0)
+	for {
+		b, _, err := r.ReadLine()
+		if err != nil {
+			if err == io.EOF {
+				break
+			}
+			return nil, err
+		}
+		s := strings.TrimSpace(string(b))
+		if s == "" {
+			continue
+		}
+		if s[0] == '#' { // 过滤注释
+			continue
+		}
+		// 获取key=value
+		index := strings.Index(s, "=")
+		if index < 0 {
+			continue
+		}
+		key := strings.TrimSpace(s[:index])
+		if len(key) == 0 {
+			continue
+		}
+		value := strings.TrimSpace(s[index+1:])
+		if len(value) == 0 {
+			continue
+		}
+		config[key] = value
+	}
+	return config, nil
+}
+
+func (p *properties) DiffProperties(pro Properties) (deleteMap, addMap map[string]string, changeMap map[string][]string) {
+	oldConfig := p.GetAll()
+	newConfig := pro.GetAll()
+	return util.DiffMap(oldConfig, newConfig)
+}

+ 130 - 0
pkg/internal/properties/common_test.go

@@ -0,0 +1,130 @@
+package properties
+
+import (
+	"fmt"
+	"testing"
+)
+
+func Test_properties_GetString(t *testing.T) {
+	pro, err := ReadFromString(test)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(pro.GetString("# logger key"))
+	fmt.Println(pro.GetInt64("key2"))
+	fmt.Println(pro.GetFloat64("key3"))
+	fmt.Println(pro.GetBool("key4"))
+	fmt.Println(pro.GetUint64("key5", 10))
+	fmt.Println(pro.GetAll())
+}
+
+var (
+	test = `
+# logger key=velue
+log.access_log = /data/log/go-template/access.log
+log.monitor_log = /data/log/go-template/monitor.log
+log.task_log = /data/log/go-template/task.log
+log.third_log = /data/log/go-template/third.log
+log.project_log = /data/log/go-template/go-template.log
+
+# mysql
+mysql.host = 10.9.198.84
+mysql.port = 3306
+mysql.user = aaa
+mysql.password = aaa@2014
+mysql.dbname = aaa
+mysql.slave_host = 10.9.198.84
+mysql.slave_port = 3306
+mysql.slave_user = aaa
+mysql.slave_password = aaa@2014
+mysql.slave_dbname = aaa
+mysql.log_file=/data/log/go-template/aaa.log
+
+# nacos
+nacos.server_port=13058
+
+
+ebike-factory-api.service_name=ebike-factory-api
+ebike-factory-api.group_name=DEFAULT_GROUP
+ebike-factory-api.clusters=DEFAULT
+
+
+localcache.refresh_time=600
+localcache.demo=10
+`
+
+	test2 = `
+# logger key=velue
+log.access_log = /data/log/go-template/access.log
+log.monitor_log = /data/log/go-template/monitor.log
+log.task_log = /data/log/go-template/task.log
+log.third_log = /data/log/go-template/third.log
+log.project_log = /data/log/go-template/go-template.log
+
+# nacos
+nacos.server_port=13059
+
+
+ebike-factory-api.service_name=ebike-factory-api
+ebike-factory-api.group_name=DEFAULT_GROUP
+ebike-factory-api.clusters=DEFAULT
+
+
+localcache.refresh_time=600
+localcache.demo=10
+
+
+trace.sky_walking_host = localhost:11800
+trace.application_name = go-template_dev
+`
+)
+
+func Test_properties_DiffProperties(t *testing.T) {
+	pro, err := ReadFromString(test)
+	if err != nil {
+		t.Fatal(err)
+	}
+	pro2, err := ReadFromString(test2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	printProperties := func(config map[string]string) string {
+		if config == nil || len(config) == 0 {
+			return " No Changed\n"
+		}
+		result := " Find Changed\n"
+		count := len(config)
+		num := 0
+		for key, value := range config {
+			num++
+			result = result + key + " = " + value
+			if num < count {
+				result = result + "\n"
+			}
+		}
+		return result + "\n"
+	}
+	printDiffPorperties := func(config map[string][]string) string {
+		if config == nil || len(config) == 0 {
+			return " No Changed\n"
+		}
+		result := " Find Changed\n"
+		count := len(config)
+		num := 0
+		for key, value := range config {
+			num++
+			if value == nil || len(value) < 2 {
+				continue
+			}
+			result = result + "old: " + key + " = " + value[0] + "\n"
+			result = result + "new: " + key + " = " + value[1]
+			if num < count {
+				result = result + "\n"
+			}
+		}
+		return result
+	}
+
+	_, _, changeMap := pro.DiffProperties(pro2)
+	fmt.Printf("[nacos] delete-config:%s[nacos] add-config:%s[nacos] change-config:%s", printProperties(nil), printProperties(nil), printDiffPorperties(changeMap))
+}

+ 3 - 0
pkg/internal/util/constant.go

@@ -0,0 +1,3 @@
+package util
+
+const ()

+ 24 - 0
pkg/internal/util/dir.go

@@ -0,0 +1,24 @@
+package util
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+)
+
+func GetRootPath() string {
+	path := os.Getenv("ROOT_PATH")
+	if path != "" {
+		return filepath.Clean(path)
+	}
+	curFilename := os.Args[0]
+	binaryPath, err := exec.LookPath(curFilename)
+	if err != nil {
+		panic(err)
+	}
+	binaryPath, err = filepath.Abs(binaryPath)
+	if err != nil {
+		panic(err)
+	}
+	return filepath.Dir(filepath.Dir(binaryPath))
+}

+ 13 - 0
pkg/internal/util/errors.go

@@ -0,0 +1,13 @@
+package util
+
+import (
+	"errors"
+	"fmt"
+)
+
+func NewError(format string, a ...interface{}) error {
+	if a == nil || len(a) == 0 {
+		return errors.New(format)
+	}
+	return errors.New(fmt.Sprintf(format, a...))
+}

+ 8 - 0
pkg/internal/util/errors_test.go

@@ -0,0 +1,8 @@
+package util
+
+import "testing"
+
+func TestNewError(t *testing.T) {
+	t.Log(NewError("err"))
+	t.Log(NewError("err: %s", "yes"))
+}

+ 31 - 0
pkg/internal/util/goroutine.go

@@ -0,0 +1,31 @@
+package util
+
+import (
+	"fmt"
+	"os"
+)
+
+func GoWithRecover(handler func(), recoverHandler func(r interface{})) {
+	if handler == nil {
+		return
+	}
+	go func() {
+		defer func() {
+			if r := recover(); r != nil {
+				if recoverHandler == nil {
+					fmt.Fprintf(os.Stderr, "panic:\n %v\n", r)
+					return
+				}
+				go func() {
+					defer func() {
+						if r := recover(); r != nil {
+							fmt.Fprintf(os.Stderr, "panic:\n %v\n", r)
+						}
+					}()
+					recoverHandler(r)
+				}()
+			}
+		}()
+		handler()
+	}()
+}

+ 46 - 0
pkg/internal/util/goroutine_test.go

@@ -0,0 +1,46 @@
+package util
+
+import (
+	"sync"
+	"testing"
+)
+
+/**
+对于非闭包函数的支持
+*/
+func TestGoWithRecover(t *testing.T) {
+	wg := sync.WaitGroup{}
+	x := 0
+	wg.Add(1)
+	GoWithRecover(func() {
+		defer func() {
+			wg.Done()
+		}()
+		t.Logf("run : %d\n", x)
+		t.Log(10 / x)
+	}, func(r interface{}) {
+		t.Logf("find err: %s\n", r)
+	})
+	wg.Wait()
+}
+
+/**
+不支持闭包函数循环结构
+*/
+func TestGoWithRecoverBiBao(t *testing.T) {
+	wg := sync.WaitGroup{}
+	x := 0
+	wg.Add(10)
+	for n := 0; n < 10; n++ {
+		GoWithRecover(func() {
+			defer func() {
+				wg.Done()
+			}()
+			t.Logf("run : %d\n", n)
+			t.Log(10 / x)
+		}, func(r interface{}) {
+			t.Logf("find err: %s\n", r)
+		})
+	}
+	wg.Wait()
+}

+ 60 - 0
pkg/internal/util/host.go

@@ -0,0 +1,60 @@
+package util
+
+import (
+	"errors"
+	"net"
+	"os"
+)
+
+func AllIPV4() (ipv4s []string) {
+	adders, err := net.InterfaceAddrs()
+	if err != nil {
+		return
+	}
+
+	for _, addr := range adders {
+		if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
+			if ipNet.IP.To4() != nil {
+				ipv4 := ipNet.IP.String()
+				if ipv4 == "127.0.0.1" || ipv4 == "localhost" {
+					continue
+				}
+				ipv4s = append(ipv4s, ipv4)
+			}
+		}
+	}
+	return
+}
+
+func GetLocalIP() (ip string, err error) {
+	netInterfaces, err := net.Interfaces()
+	if err != nil {
+		return
+	}
+	for i := 0; i < len(netInterfaces); i++ {
+		if ((netInterfaces[i].Flags & net.FlagUp) != 0) && ((netInterfaces[i].Flags & net.FlagLoopback) == 0) {
+			addresses, _ := netInterfaces[i].Addrs()
+			for _, address := range addresses {
+				if ipNet, ok := address.(*net.IPNet); ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
+					ip = ipNet.IP.String()
+					return
+				}
+			}
+		}
+	}
+	return ip, errors.New("can not get local localIP")
+}
+
+func CurrentServerHostName() string {
+	name, err := os.Hostname()
+	if err != nil {
+		Errorf("[CurrentServerHostName] get os.Hostnam err, err=%v\n", err.Error())
+		localIP, ipErr := GetLocalIP()
+		if ipErr != nil {
+			Errorf("[CurrentServerHostName] get GetLocalIP err, err=%v\n", ipErr.Error())
+			return "127.0.0.1"
+		}
+		return localIP
+	}
+	return name
+}

+ 11 - 0
pkg/internal/util/host_test.go

@@ -0,0 +1,11 @@
+package util
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestAllIPV4(t *testing.T) {
+	fmt.Println(AllIPV4())
+	fmt.Println(GetLocalIP())
+}

+ 8 - 0
pkg/internal/util/if.go

@@ -0,0 +1,8 @@
+package util
+
+func If(isOk bool, trueValue, falseValue interface{}) interface{} {
+	if isOk {
+		return trueValue
+	}
+	return falseValue
+}

+ 34 - 0
pkg/internal/util/if_test.go

@@ -0,0 +1,34 @@
+package util
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestIf(t *testing.T) {
+	str := If("a" == "a", "ok", "false").(string)
+	fmt.Println(str)
+}
+
+//
+//var (
+//	_map     = make(map[int]interface{}, 0)
+//	_mapLock sync.Mutex
+//	wg       sync.WaitGroup
+//)
+//
+//func TestSafeIf(t *testing.T) {
+//	for x := 0; x < 10000; x++ {
+//		wg.Add(1)
+//		go test(x)
+//	}
+//	wg.Wait()
+//	fmt.Println(len(_map))
+//}
+//
+//func test(num int) {
+//	defer wg.Done()
+//	_mapLock.Lock()
+//	defer _mapLock.Unlock()
+//	_map[num] = 0
+//}

+ 101 - 0
pkg/internal/util/kv_context.go

@@ -0,0 +1,101 @@
+package util
+
+import (
+	"context"
+	"sync"
+	"time"
+)
+
+type KVContext interface {
+	context.Context
+	Set(key string, value interface{})
+	Get(key string) (interface{}, bool)
+}
+
+func AssertKVContext(ctx context.Context) KVContext {
+	c, isOk := ctx.(KVContext)
+	if isOk {
+		return c
+	}
+	return nil
+}
+
+func NewKVContext(ctx context.Context) KVContext {
+	// 防止panic
+	if ctx == nil {
+		ctx = context.Background()
+	}
+	return &kVContext{
+		p:    ctx,
+		_map: map[string]interface{}{},
+	}
+}
+
+/**
+具体实现
+*/
+type kVContext struct {
+	/**
+	不使用 并发map的原因:
+	1、并不是所有的key都可以做value
+	2、原生map效率高于sync.map
+	*/
+	sync.RWMutex
+	_map map[string]interface{} // 防止并发读写问题
+	p    context.Context
+}
+
+func (c *kVContext) Set(key string, value interface{}) {
+	c.Lock()
+	defer c.Unlock()
+	c._map[key] = value
+}
+
+func (c *kVContext) Get(key string) (interface{}, bool) {
+	c.RLock()
+	defer c.RUnlock()
+	value, isExist := c._map[key]
+	return value, isExist
+}
+
+/************************************/
+/***** GOLANG.ORG/X/NET/CONTEXT *****/
+/************************************/
+
+// Deadline returns the time when work done on behalf of this context
+// should be canceled. Deadline returns ok==false when no deadline is
+// set. Successive calls to Deadline return the same results.
+func (c *kVContext) Deadline() (deadline time.Time, ok bool) {
+	return
+}
+
+// Done returns a channel that's closed when work done on behalf of this
+// context should be canceled. Done may return nil if this context can
+// never be canceled. Successive calls to Done return the same value.
+func (c *kVContext) Done() <-chan struct{} {
+	return nil
+}
+
+// Err returns a non-nil error value after Done is closed,
+// successive calls to Err return the same error.
+// If Done is not yet closed, Err returns nil.
+// If Done is closed, Err returns a non-nil error explaining why:
+// Canceled if the context was canceled
+// or DeadlineExceeded if the context's deadline passed.
+func (c *kVContext) Err() error {
+	return nil
+}
+
+// Value returns the value associated with this context for key, or nil
+// if no value is associated with key. Successive calls to Value with
+// the same key returns the same result.
+func (c *kVContext) Value(key interface{}) interface{} {
+	if k, ok := key.(string); ok {
+		value, _ := c.Get(k)
+		if value == nil {
+			return c.p.Value(key)
+		}
+		return value
+	}
+	return c.p.Value(key)
+}

+ 28 - 0
pkg/internal/util/kv_context_test.go

@@ -0,0 +1,28 @@
+package util
+
+import (
+	"context"
+	"fmt"
+	"testing"
+)
+
+func TestKVContext(t *testing.T) {
+	cxt := context.WithValue(context.Background(), "k1", "v1")
+	newCtx := NewKVContext(cxt)
+	newCtx.Set("k2", "v2")
+	fmt.Println(newCtx.Value("k1"))
+	fmt.Println(newCtx.Value("k2"))
+
+	var cctx context.Context = newCtx
+
+	fmt.Println(AssertKVContext(cctx).Get("k2"))
+
+}
+
+func BenchmarkName(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		newCtx := NewKVContext(context.Background())
+		newCtx.Set("k2", "v2")
+		newCtx.Value("k2")
+	}
+}

+ 104 - 0
pkg/internal/util/log.go

@@ -0,0 +1,104 @@
+package util
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"runtime"
+	"strings"
+)
+
+var (
+	_log = NewStdOutLogger("[GO-COMMON]", 3)
+)
+
+var (
+	Debugf = _log.Debugf
+	Warnf  = _log.Warnf
+	Infof  = _log.Infof
+	Errorf = _log.Errorf
+	Fatalf = _log.Fatalf
+)
+
+func Painc(err error) {
+	if err != nil {
+		Errorf("%v", err)
+		buf := make([]byte, 64<<10)
+		buf = buf[:runtime.Stack(buf, false)]
+		Fatalf("Happened panic:\n%s", buf)
+	}
+}
+
+type Logger interface {
+	Debugf(format string, v ...interface{})
+	Infof(format string, v ...interface{})
+	Errorf(format string, v ...interface{})
+	Warnf(format string, v ...interface{})
+	Fatalf(format string, v ...interface{})
+}
+
+type stdOutLogger struct {
+	*log.Logger
+	caller int
+}
+
+func NewStdOutLogger(prefix string, caller int) Logger {
+	if prefix != "" && !strings.HasSuffix(prefix, " ") {
+		prefix = prefix + " "
+	}
+	if caller == 0 {
+		caller = 3
+	}
+	return &stdOutLogger{
+		Logger: log.New(os.Stdout, prefix, log.Lshortfile|log.LstdFlags),
+		caller: caller,
+	}
+}
+
+func (s *stdOutLogger) output(level level, str string) {
+	formatStr := ""
+	switch level {
+	case levelFatal:
+		formatStr = "\033[35m[FATAL]\033[0m " + str
+	case levelError:
+		formatStr = "\033[31m[ERROR]\033[0m " + str
+	case levelWarning:
+		formatStr = "\033[33m[WARN]\033[0m " + str
+	case levelInfo:
+		formatStr = "\033[32m[INFO]\033[0m " + str
+	case levelDebug:
+		formatStr = "\033[36m[DEBUG]\033[0m " + str
+	}
+	_ = s.Output(s.caller, formatStr)
+}
+
+func (s *stdOutLogger) Debugf(format string, v ...interface{}) {
+	s.output(levelDebug, fmt.Sprintf(format, v...))
+}
+
+func (s *stdOutLogger) Infof(format string, v ...interface{}) {
+	s.output(levelInfo, fmt.Sprintf(format, v...))
+}
+func (s *stdOutLogger) Warnf(format string, v ...interface{}) {
+	s.output(levelWarning, fmt.Sprintf(format, v...))
+}
+func (s *stdOutLogger) Errorf(format string, v ...interface{}) {
+	s.output(levelError, fmt.Sprintf(format, v...))
+}
+
+func (s *stdOutLogger) Fatalf(format string, v ...interface{}) {
+	s.output(levelFatal, fmt.Sprintf(format, v...))
+	os.Exit(-1)
+}
+
+type (
+	level int
+)
+
+const (
+	levelFatal level = iota + 1
+	levelError
+	levelWarning
+	levelInfo
+	levelDebug
+)

+ 15 - 0
pkg/internal/util/log_test.go

@@ -0,0 +1,15 @@
+package util
+
+import (
+	"testing"
+)
+
+func TestInfof(t *testing.T) {
+	Debugf("hello world : %s", "Debugf")
+	Infof("hello world : %s", "Infof")
+	Warnf("hello world : %s", "Warnf")
+	Errorf("hello world : %s", "Errorf")
+	//Fatalf("hello world : %s", "Fatalf")
+
+	//Painc(errors.New("异常"))
+}

+ 27 - 0
pkg/internal/util/map.go

@@ -0,0 +1,27 @@
+package util
+
+/**
+diff map的变更
+*/
+func DiffMap(oldConfig, newConfig map[string]string) (deleteMap, addMap map[string]string, changeMap map[string][]string) {
+	deleteMap = make(map[string]string, 0)
+	changeMap = make(map[string][]string, 0)
+	addMap = make(map[string]string, 0)
+	for key, value := range oldConfig {
+		newValue, isExist := newConfig[key]
+		if !isExist {
+			deleteMap[key] = value
+			continue
+		}
+		if newValue != value {
+			changeMap[key] = []string{value, newValue}
+		}
+	}
+	for key, value := range newConfig {
+		_, isExist := oldConfig[key]
+		if !isExist {
+			addMap[key] = value
+		}
+	}
+	return
+}

+ 129 - 0
pkg/internal/util/num.go

@@ -0,0 +1,129 @@
+package util
+
+import (
+	"math/rand"
+	"strconv"
+	"time"
+)
+
+func Uint642String(num uint64) string {
+	return strconv.FormatUint(num, 10)
+}
+
+func StringSecToDuration(str string, defaultValue ...string) time.Duration {
+	result := String2Int64(str)
+	if result == 0 {
+		if defaultValue == nil || len(defaultValue) == 0 {
+			return 0
+		}
+		result = String2Int64(defaultValue[0])
+	}
+	return time.Second * time.Duration(result)
+}
+
+func String2Uint64(nums string, defaultValue ...uint64) uint64 {
+	loadDefaultValue := func() uint64 {
+		if defaultValue == nil || len(defaultValue) == 0 {
+			return 0
+		}
+		return defaultValue[0]
+	}
+	if nums == "" {
+		return loadDefaultValue()
+	}
+	num, err := strconv.ParseUint(nums, 10, 64)
+	if err != nil {
+		// 异常,直接
+		return loadDefaultValue()
+	}
+	return num
+}
+
+func Int642String(num int64) string {
+	return strconv.FormatInt(num, 10)
+}
+
+func String2Int64(nums string, defaultValue ...int64) int64 {
+	loadDefaultValue := func() int64 {
+		if defaultValue == nil || len(defaultValue) == 0 {
+			return 0
+		}
+		return defaultValue[0]
+	}
+	if nums == "" {
+		return loadDefaultValue()
+	}
+	num, err := strconv.ParseInt(nums, 10, 64)
+	if err != nil {
+		return loadDefaultValue()
+	}
+	return num
+}
+
+func String2Int(nums string, defaultValue ...int) int {
+	loadDefaultValue := func() int {
+		if defaultValue == nil || len(defaultValue) == 0 {
+			return 0
+		}
+		return defaultValue[0]
+	}
+	if nums == "" {
+		return loadDefaultValue()
+	}
+	num, err := strconv.ParseInt(nums, 10, 64)
+	if err != nil {
+		return loadDefaultValue()
+	}
+	return int(num)
+}
+
+func Int2String(num int) string {
+	return strconv.FormatInt(int64(num), 10)
+}
+
+func String2Bool(str string, defaultValue ...bool) bool {
+	loadDefaultValue := func() bool {
+		if defaultValue == nil || len(defaultValue) == 0 {
+			return false
+		}
+		return defaultValue[0]
+	}
+	b, err := strconv.ParseBool(str)
+	if err != nil {
+		return loadDefaultValue()
+	}
+	return b
+}
+
+func String2Float64(str string, defaultValue ...float64) float64 {
+	loadDefaultValue := func() float64 {
+		if defaultValue == nil || len(defaultValue) == 0 {
+			return 0
+		}
+		return defaultValue[0]
+	}
+	b, err := strconv.ParseFloat(str, 64)
+	if err != nil {
+		return loadDefaultValue()
+	}
+	return b
+}
+
+// 除固定值,保留固定小数位
+func Float642String(num float64, saveDecimalPoint int) string {
+	return strconv.FormatFloat(num, 'f', saveDecimalPoint, 64)
+}
+
+/**
+比率限制,如果=0关闭,如果等于100全量
+*/
+func Rate100(threshold int64) bool {
+	return RateInNum(threshold, 100)
+}
+
+/**
+比率限制,如果=0关闭,如果等于100全量
+*/
+func RateInNum(threshold int64, totalSize int64) bool {
+	return threshold > rand.Int63n(totalSize) // 0-99
+}

+ 27 - 0
pkg/internal/util/num_test.go

@@ -0,0 +1,27 @@
+package util
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestString2Uint64(t *testing.T) {
+	fmt.Println(String2Uint64("100001"))
+}
+
+func BenchmarkString2Uint64(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		String2Uint64("100", 100)
+	}
+}
+
+func TestRate100(t *testing.T) {
+	count := 0
+	total := 10000000
+	for x := 0; x < total; x++ {
+		if Rate100(20) {
+			count++
+		}
+	}
+	fmt.Println(float64(count) / float64(total))
+}

+ 20 - 0
pkg/internal/util/os.go

@@ -0,0 +1,20 @@
+package util
+
+import (
+	"os"
+	"path/filepath"
+)
+
+func WritePid() {
+	pid := os.Getpid()
+	file, err := os.OpenFile(filepath.Join(GetRootPath(), "app.pid"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
+	if err != nil {
+		Errorf("[WritePid] create app.pid find err, err=%v", err)
+		return
+	}
+	_, err = file.Write([]byte(Int642String(int64(pid))))
+	if err != nil {
+		Errorf("[WritePid] write pid err, err=%v", err)
+		return
+	}
+}

+ 52 - 0
pkg/internal/util/response.go

@@ -0,0 +1,52 @@
+package util
+
+import (
+	"net/http"
+
+	"gitea.ckfah.com/cjjy/gocommon/pkg/cerror"
+)
+
+const (
+	SuccessCode    = 0
+	SuccessMessage = "success"
+	FailCode       = -1
+	HttpStatus     = http.StatusOK
+	HeaderData     = "_header_bind_data"
+)
+
+type HttpResponse struct {
+	Code    int         `json:"code"`
+	Data    interface{} `json:"data"`
+	Message string      `json:"message"`
+}
+
+func NewSuccessHttpResponse(data interface{}) *HttpResponse {
+	return &HttpResponse{
+		Code:    SuccessCode,
+		Data:    data,
+		Message: SuccessMessage,
+	}
+}
+
+func NewFailHttpResponse(err cerror.Cerror) *HttpResponse {
+	if err == nil {
+		return &HttpResponse{
+			Code:    FailCode,
+			Data:    nil,
+			Message: "error message is nil",
+		}
+	}
+	return &HttpResponse{
+		Data:    nil,
+		Code:    err.Code(),
+		Message: err.Error(),
+	}
+}
+
+func NewFailMessageHttpResponse(err string) *HttpResponse {
+	return &HttpResponse{
+		Code:    FailCode,
+		Data:    nil,
+		Message: err,
+	}
+}

+ 114 - 0
pkg/internal/util/string.go

@@ -0,0 +1,114 @@
+package util
+
+import (
+	"crypto/md5"
+	"fmt"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+func SplitIgnoreSpace(str string, sep string) []string {
+	split := strings.Split(str, sep)
+	result := make([]string, 0, len(split))
+	for _, elem := range split {
+		value := strings.TrimSpace(elem)
+		if value == "" {
+			continue
+		}
+		result = append(result, value)
+	}
+	return result
+}
+
+func JoinIgnoreSpace(str []string, sep string) string {
+	if str == nil || len(str) == 0 {
+		return ""
+	}
+	builder := strings.Builder{}
+	for index, elem := range str {
+		if elem == "" {
+			continue
+		}
+		builder.WriteString(elem)
+		if index != len(str)-1 { // not latest index
+			builder.WriteString(sep)
+		}
+	}
+	return builder.String()
+}
+
+func ToString(value interface{}) string {
+	switch v := value.(type) {
+	case string:
+		return v
+	case uint8, uint16, uint32, uint64:
+		convertUint64 := func(value interface{}) uint64 {
+			switch v := value.(type) {
+			case uint8:
+				return uint64(v)
+			case uint16:
+				return uint64(v)
+			case uint32:
+				return uint64(v)
+			case uint64:
+				return v
+			default:
+				return 0
+			}
+		}
+		return strconv.FormatUint(convertUint64(value), 10)
+	case int, int8, int16, int32, int64:
+		convertInt64 := func(value interface{}) int64 {
+			switch v := value.(type) {
+			case int8:
+				return int64(v)
+			case int16:
+				return int64(v)
+			case int32:
+				return int64(v)
+			case int64:
+				return v
+			case int:
+				return int64(v)
+			default:
+				return 0
+			}
+		}
+		return strconv.FormatInt(convertInt64(value), 10)
+	case bool:
+		if v {
+			return "true"
+		}
+		return "false"
+	// float 效率和直接fmt差距相差不大,为了保证不出问题,还是使用%v
+	default:
+		str, isOk := value.(fmt.Stringer)
+		if isOk {
+			return str.String()
+		}
+		return fmt.Sprintf("%+v", value)
+	}
+}
+
+// 解码失败则返回原数据
+func UrlDecode(str string) string {
+	oldStr := str
+	str, err := url.QueryUnescape(str)
+	if err != nil {
+		return oldStr
+	}
+	return str
+}
+
+// 编码url
+func UrlEncode(str string) string {
+	return url.QueryEscape(str)
+}
+
+func String2Md5(str string) string {
+	data := []byte(str)
+	has := md5.Sum(data)
+	md5str := fmt.Sprintf("%x", has)
+	return md5str
+}

+ 40 - 0
pkg/internal/util/string_test.go

@@ -0,0 +1,40 @@
+package util
+
+import (
+	"fmt"
+	"testing"
+	"time"
+)
+
+func TestSplitChar(t *testing.T) {
+	{
+		char := SplitIgnoreSpace("  hello.world  ", ".")
+		fmt.Println(char)
+	}
+	{
+		char := SplitIgnoreSpace(".hello.world", ".")
+		fmt.Println(char)
+	}
+	{
+		char := SplitIgnoreSpace("hello. .world.", ".")
+		fmt.Println(char)
+	}
+	{
+		char := SplitIgnoreSpace(".hello.world.", ".")
+		fmt.Println(char)
+	}
+}
+
+func TestToString(t *testing.T) {
+	t.Log(ToString("11111"))
+	t.Log(ToString(1111))
+	t.Log(ToString(1111.111))
+	t.Log(ToString(time.Now()))
+	t.Log(ToString([]string{"1", "2"}))
+	t.Log(ToString(map[string]string{"1": "2", "2": "3"}))
+}
+
+func TestJoinIgnoreSpace(t *testing.T) {
+	t.Log(JoinIgnoreSpace([]string{"1", "2", "3"}, ","))
+	t.Log(JoinIgnoreSpace([]string{"1", "", "3"}, " || "))
+}

+ 18 - 0
pkg/internal/util/time.go

@@ -0,0 +1,18 @@
+package util
+
+import (
+	"time"
+)
+
+// 时间之差 s 输出 0.100010s
+func SubTimeToSeconds(startTime, endTime time.Time) string {
+	seconds := endTime.Sub(startTime).Seconds()
+	// 1s=1000ms 1ms=1000us  保留6位到us
+	return Float642String(seconds, 6) + "s"
+}
+
+// 时间之差 s 输出 0.100010s
+func TimeToSeconds(seconds float64) string {
+	// 1s=1000ms 1ms=1000us  保留6位到us
+	return Float642String(seconds, 6) + "s"
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно