Explorar o código

接口调用前后校验开发第一版

wanghuidong %!s(int64=4) %!d(string=hai) anos
pai
achega
5c0293d95a

+ 9 - 0
BusinessIntrduce.md

@@ -0,0 +1,9 @@
+## 业务分析记录
+
+### 创建用户、给用户开工接口产品
+    1.线上给账户充值10000块(user_account),不去实际购买产品,无购买记录,用户产品表无剩余量(left_num为0).
+	  1.1 按次扣账户余额
+	  1.2 按量扣账户余额
+	2.线下合同交易细则,用户花10000块钱(trade_money)购买了接口产品,添加购买记录,直接按照合同约定10000块钱买(多少次|多少条)存入用户产品表left_num字段
+	  2.1 按次扣用户产品表余量
+	  2.2 按量扣用户产品表余量

+ 35 - 8
api/v1/projects.go

@@ -1,9 +1,15 @@
 package v1
 
 import (
+	"encoding/json"
+	"go.uber.org/zap"
 	"net/http"
 	"sfbase/global"
+	"sfis/middleware"
+	"sfis/model"
 	"sfis/service"
+	"sfis/utils"
+	"strconv"
 
 	"github.com/gin-gonic/gin"
 )
@@ -13,7 +19,7 @@ import (
 */
 func ProjectApiRegister(router *gin.Engine) {
 	routerGroup := router.Group("/sfis/api/v1/")
-	routerGroup.Use()
+	routerGroup.Use(middleware.TokenAuth())
 	{
 		routerGroup.POST("/projectList", getProjectsList)
 		routerGroup.POST("/projectDetail", getProjectDetail)
@@ -22,22 +28,43 @@ func ProjectApiRegister(router *gin.Engine) {
 
 //获取项目列表接口
 func getProjectsList(c *gin.Context) {
-	projectName := c.PostForm("name")
+	productID := c.MustGet("productID").(int)
+	appID := c.MustGet("appID").(string)
+	requestIP := c.MustGet("requestIP").(string)
+	projectName := c.PostForm("projectName")
 	winner := c.PostForm("winner")
-	time := c.PostForm("time")
-	global.Logger.Info("projectName " + projectName)
-	data := &map[string]interface{}{}
+	zbRq := c.PostForm("zbRq")
+	p := gin.H{
+		"projectName": projectName,
+		"winner":      winner,
+		"zbRq":        zbRq,
+	}
+	bs, _ := json.Marshal(p)
+	param := string(bs)
+	global.Logger.Info("api getProjectList:", zap.Any("productID:", productID), zap.Any("appID", appID), zap.Any("param:", param))
+	utils.Check(appID, productID, c, func() ([]interface{}, int, error) {
+		return projectList(projectName, winner, zbRq)
+	}, param, requestIP)
+	/*data := &map[string]interface{}{}
 	if projectName != "" || winner != "" {
 		global.Logger.Info("666")
-		data = service.ProjectListData(projectName, winner, time)
+		data = service.ProjectListData(projectName, winner, zbRq)
+	}*/
+	c.JSON(http.StatusOK, nil)
+}
+
+func projectList(projectName, winner, zbRq string) ([]interface{}, int, error) {
+	data := make([]interface{}, 0)
+	for i := 0; i < 10; i++ {
+		data = append(data, &model.Product{Name: "数据" + strconv.Itoa(i)})
 	}
-	c.JSON(http.StatusOK, data)
+	return data, 200, nil
 }
 
 //获取项目详情
 func getProjectDetail(c *gin.Context) {
 	id := c.PostForm("projectid")
-	global.Logger.Info("projectid "+ id)
+	global.Logger.Info("projectid " + id)
 	data := map[string]interface{}{}
 	if id != "" {
 		data = service.ProjectDetailData(id)

+ 2 - 1
conf/dev/mysql.toml

@@ -1,5 +1,6 @@
 driver_name = "mysql"
-data_source_name = "root:Topnet123@tcp(192.168.3.11:3366)/sword_interface_service?charset=utf8mb4&parseTime=true&loc=Local"
+#data_source_name = "root:Topnet123@tcp(192.168.3.11:3366)/sword_interface_service?charset=utf8mb4&parseTime=true&loc=Local"
+data_source_name = "root:Zxy@19860122@tcp(39.107.203.162:3336)/sword_interface_service?charset=utf8mb4&parseTime=true&loc=Local"
 max_open_conn = 20
 max_idle_conn = 10
 max_conn_life_time = 100

+ 2 - 0
go.mod

@@ -4,6 +4,8 @@ go 1.13
 
 require (
 	github.com/gin-gonic/gin v1.6.3
+	github.com/olivere/elastic v6.2.35+incompatible // indirect
+	go.mongodb.org/mongo-driver v1.4.4 // indirect
 	go.uber.org/zap v1.10.0
 	gorm.io/gorm v1.20.8
 	sfbase v0.0.0

+ 82 - 0
go.sum

@@ -19,6 +19,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 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/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
+github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -60,7 +62,32 @@ github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 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 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
+github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
+github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
+github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
+github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
+github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
+github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
+github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
+github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
+github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
+github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
+github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
+github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
+github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
+github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
+github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -73,10 +100,13 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/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.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -110,20 +140,30 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
 github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 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/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
+github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 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/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
+github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 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.2/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -137,6 +177,8 @@ github.com/lestrrat-go/strftime v1.0.3 h1:qqOPU7y+TM8Y803I8fG9c/DyKG3xH/xkng6keC
 github.com/lestrrat-go/strftime v1.0.3/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 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.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
@@ -155,14 +197,21 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
 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/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
+github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 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/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=
@@ -175,10 +224,15 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 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=
@@ -187,6 +241,7 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
@@ -198,16 +253,24 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tebeka/strftime v0.1.5/go.mod h1:29/OidkoWHdEKZqzyDLUyC+LmgDgdHo4WAFCDT7D/Ig=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
+github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo=
+github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.mongodb.org/mongo-driver v1.4.4 h1:bsPHfODES+/yx2PCWzUYMH8xj6PVniPI8DQrsJuSXSs=
+go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
@@ -219,7 +282,10 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 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-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -253,6 +319,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190620200207-3b0461eec859/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/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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -260,7 +327,10 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
 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-20190412183630-56d357773e84/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 h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+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=
@@ -269,9 +339,13 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -279,6 +353,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -289,8 +365,12 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/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-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -299,6 +379,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -333,6 +414,7 @@ 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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg=
 gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
 gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=

+ 10 - 0
lock/interface.go

@@ -0,0 +1,10 @@
+package lock
+
+/**
+用户消费接口
+*/
+type Cost interface {
+	balance()   //余额判断
+	deduction() //扣费(剩余量|账户余额)
+}
+

+ 12 - 8
lock/lock.go

@@ -1,19 +1,23 @@
 package lock
 
-import "sync"
+import (
+	"sync"
+)
 
 var (
-	mainLock = new(sync.Map)
+	UserLockMap = map[string]*sync.Mutex{}
+	MainLock    = sync.Mutex{}
 )
 
 func InitUserLock(appID string) {
-	mainLock.Store(appID, &sync.Mutex{})
+	//MainLockMap.Store(appID, sync.Mutex{})
+	//log.Println("获取到appID的锁:", GetUserLock(appID))
 }
 
-func GetUserLock(appID string) *sync.Mutex {
-	if userLock, ok := mainLock.Load(appID); ok {
-		_userLock := userLock.(*sync.Mutex)
+/*func GetUserLock(appID string) ()sync.Mutex {
+	if userLock, ok := MainLockMap.Load(appID); ok {
+		_userLock := userLock.(sync.Mutex)
 		return _userLock
 	}
-	return nil
-}
+	return
+}*/

+ 7 - 5
main.go

@@ -7,9 +7,11 @@ import (
 	"sfbase/global"
 	"sfbase/redis"
 	"sfis/db"
+	"sfis/lock"
 	"sfis/model"
 	"sfis/router"
 	"sfis/utils"
+	"sync"
 )
 
 func main() {
@@ -22,8 +24,8 @@ func main() {
 		log.Println("zap日志初始化异常,清检查后重试")
 	}
 	db.InitDB()
-	db.InitEs()
-	db.InitMongo()
+	//db.InitEs()
+	//db.InitMongo()
 	if db.GetSFISDB() != nil {
 		//todo other caches service or init operation
 		users := make([]*model.User, 0)
@@ -37,11 +39,11 @@ func main() {
 		db.GetSFISDB().Find(&apis)
 		for _, api := range apis {
 			utils.ProductCaches.Map.Store(api.ID, api)
-			utils.ApiUrlCache.Store(api.Path, api)
+			utils.ApiUrlCache.Store(api.Path, *api)
 		}
-		global.Logger.Info("初始化产品缓存信息,", zap.Any("产品数量:", len(users)))
+		global.Logger.Info("初始化产品缓存信息,", zap.Any("产品数量:", len(apis)))
 	}
-
+	lock.UserLockMap["sfGSVYRQMAAgkGBAUBJg4f"] = &sync.Mutex{}
 	//全局redis的使用?
 	redis.InitRedis(global.BaseConfig.RedisSession.RedisToken)
 	//启动web server

+ 9 - 6
middleware/auth.go

@@ -6,7 +6,6 @@ import (
 	"go.uber.org/zap"
 	"sfbase/global"
 	sutils "sfbase/utils"
-	"sfis/model"
 	"sfis/model/response"
 	"sfis/utils"
 	"strconv"
@@ -24,17 +23,20 @@ func TokenAuth() gin.HandlerFunc {
 			timestamp  string
 			appID      string
 			productID  int
+			requestIP  string
 		)
 		requestUrl = context.Request.URL.String()
 		requestUrl = strings.Split(requestUrl, "?")[0]
-		requestUrl = strings.Split(requestUrl, "/")[3]
-		if p, ok := utils.ApiUrlCache.Load(requestUrl); ok {
+		a := strings.Split(requestUrl, "/")
+		requestUrl = a[4]
+		/*if p, ok := utils.ApiUrlCache.Load(requestUrl); ok {
 			productID = p.(*model.Product).ID
 		} else {
 			response.FailWithDetailed(response.ParamError, nil, "url错误", context)
 			context.Abort()
 			return
-		}
+		}*/
+		productID = 1000
 		token = context.Request.Header.Get("token")
 		timestamp = context.Request.Header.Get("timestamp")
 		appID = context.PostForm("app_id")
@@ -67,8 +69,8 @@ func TokenAuth() gin.HandlerFunc {
 		第一步:ip白名单校验
 		*/
 		if ipWhiteList != "*" {
-			requestIp := utils.GetIp(context.Request)
-			if strings.Index(ipWhiteList, requestIp) < 0 {
+			requestIP = utils.GetIp(context.Request)
+			if strings.Index(ipWhiteList, requestIP) < 0 {
 				response.FailWithDetailed(response.IpInvalid, nil, "ip不在白名单", context)
 				context.Abort()
 				return
@@ -86,6 +88,7 @@ func TokenAuth() gin.HandlerFunc {
 
 		context.Set("appID", appID)
 		context.Set("productID", productID)
+		context.Set("requestIP", requestIP)
 
 	}
 }

+ 1 - 1
model/baseModel.go

@@ -9,5 +9,5 @@ type BaseModel struct {
 	ID        int            `json:"id" form:"id" gorm:"primaryKey"`
 	CreateAt  time.Time      `json:"-" gorm:"autoCreateTime"` //标签autoCreateTime设置如果字段名字不为CreatAt时候自动插入当前时间
 	UpdateAt  time.Time      `json:"-" gorm:"autoUpdateTime"`
-	DeletedAt gorm.DeletedAt `json:"-" `
+	DeletedAt gorm.DeletedAt `json:"-" gorm:"column:delete_at"`
 }

+ 12 - 10
model/product.go

@@ -5,7 +5,7 @@ import "time"
 type Product struct {
 	BaseModel
 	Name        string `json:"name"`
-	Path        string `json:"url"`
+	Path        string `json:"url" gorm:"column:url"`
 	UnitPrice   int    `json:"unit_price"`   //单价
 	MinUnit     int    `json:"min_unit"`     //最小单位
 	ProductType int    `json:"product_type"` //产品类型 按次-0,按条-1
@@ -17,15 +17,17 @@ func (p *Product) TableName() string {
 }
 
 type UserProduct struct {
-	ID              int       `json:"id" gorm:"primaryKey"`
-	AppID           string    `json:"app_id"`
-	ProductID       int       `json:"product_id"`
-	CreateAt        time.Time `json:"-" gorm:"autoCreateTime"` //标签autoCreateTime设置如果字段名字不为CreatAt时候自动插入当前时间
-	EndAt           time.Time `json:"end_at"`
-	LeftNum         int       `json:"left_num"`         //剩余量  加锁处理
-	CostModel       int       `json:"cost_model"`       //扣费模式(0-按剩余量扣,1-按账户余额扣,2-优先扣剩余量,量为0扣余额)
-	InterfaceStatus int       `json:"interface_status"` //接口状态(0开启|-1停用|-2异常|-3维护)
-	CallLimitDay    int       `json:"call_limit_day"`   //每天(调用次数|取走数据量)上限
+	ID                   int       `json:"id" gorm:"primaryKey"`
+	AppID                string    `json:"app_id"`
+	ProductID            int       `json:"product_id"`
+	CreateAt             time.Time `json:"-" gorm:"autoCreateTime"` //标签autoCreateTime设置如果字段名字不为CreatAt时候自动插入当前时间
+	StartAt              time.Time `json:"start_at" grom:"start_at"`
+	EndAt                time.Time `json:"end_at" grom:"end_at"`
+	LeftNum              int       `json:"left_num"`                 //剩余量  加锁处理
+	CostModel            int       `json:"cost_model"`               //扣费模式(0-按剩余量扣,1-按账户余额扣,2-优先扣剩余量,量为0扣余额)
+	InterfaceStatus      int       `json:"interface_status"`         //接口状态(0开启|-1停用|-2异常|-3维护)
+	CallTimesLimitDay    int       `json:"call_times_limit_day"`     //接口调用次数每日上限
+	DataNumLimitOneTimes int       `json:"data_num_limit_one_times"` //接口每次返回数据量上限
 }
 
 func (p *UserProduct) TableName() string {

+ 90 - 0
sword_base/utils/netutil.go

@@ -0,0 +1,90 @@
+package utils
+
+import (
+	"bytes"
+	"io"
+	"io/ioutil"
+	"log"
+	"net"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+)
+
+const (
+	ContentTypeJson = "application/json;charset=utf-8"
+	ContentTypeForm = "application/x-www-form-urlencoded;charset=utf-8"
+)
+
+const (
+	MaxIdleCons        int = 100
+	MaxIdleConsPerHost int = 100
+	IdleConnTimeout    int = 2048
+	ConnectTimeOut     int = 30
+	KeepAlive          int = 30
+)
+
+var (
+	httpClient *http.Client
+)
+
+func init() {
+	httpClient = createHttpClient()
+}
+func createHttpClient() *http.Client {
+	client := &http.Client{
+		Transport: &http.Transport{
+			Proxy: http.ProxyFromEnvironment,
+			DialContext: (&net.Dialer{
+				Timeout:   time.Duration(ConnectTimeOut) * time.Second, //TCP连接超时30s
+				KeepAlive: time.Duration(KeepAlive) * time.Second,      //TCP keepalive保活检测定时30s
+			}).DialContext,
+			MaxIdleConns:          MaxIdleCons,
+			MaxIdleConnsPerHost:   MaxIdleConsPerHost,
+			IdleConnTimeout:       time.Duration(IdleConnTimeout) * time.Second, //闲置连接超时2048s
+			ResponseHeaderTimeout: time.Second * 60,
+		},
+	}
+	return client
+}
+
+func HttpPostJson(url string, json string) ([]byte, error) {
+	return HttpPost(url, map[string]string{"Content-Type": ContentTypeJson}, bytes.NewReader([]byte(json)))
+}
+
+func HttpPostForm(url string, header map[string]string, formValues url.Values) ([]byte, error) {
+	header["Content-Type"] = ContentTypeForm
+	return HttpPost(url, header, strings.NewReader(formValues.Encode()))
+}
+
+func HttpPost(url string, header map[string]string, body io.Reader) ([]byte, error) {
+	request, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	for k, v := range header {
+		request.Header.Add(k, v)
+	}
+	response, err := httpClient.Do(request) //前面预处理一些参数,状态,Do执行发送;处理返回结果;Do:发送请求,
+	if err != nil {
+		return nil, err
+	}
+	defer response.Body.Close()
+	replay, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		log.Println("read reply error:", err)
+		return nil, err
+	}
+	return replay, nil
+}
+
+func RedingJsonDataFromRequestBody(request *http.Request) string {
+	bs, err := ioutil.ReadAll(request.Body)
+	if err != nil {
+		panic(err)
+	}
+	data := string(bs)
+	log.Println("data:", data)
+	return data
+}

+ 35 - 2
test/manage/user_test.go

@@ -1,13 +1,34 @@
 package manage
 
 import (
-	"fmt"
+	"log"
 	sutil "sfbase/utils"
 	"sfis/utils"
 	"testing"
 	"time"
 )
 
+/*var (
+	product1 = &model.Product{
+		BaseModel:   model.BaseModel{ID: 1003},
+		Name:        "行业招标数据",
+		Path:        "/path1",
+		UnitPrice:   50, //单价精确到分  5毛
+		MinUnit:     1,  //最小单位1,即 5毛/条
+		ProductType: 1,  //产品类型 0-按次 1-按条
+		TestNum:     100,
+	}
+
+	product2 = &model.Product{
+		Name:        "中国移动招标数据",
+		Path:        "/path2",
+		UnitPrice:   1500, //单价精确到分  15快
+		MinUnit:     1,    //最小单位1,即 15快/次
+		ProductType: 0,    //产品类型 0-按次 1-按条
+		TestNum:     500,
+	}
+)*/
+
 func init() {
 	//todo init connection db operation
 }
@@ -15,5 +36,17 @@ func Test_CreateUser(t *testing.T) {
 	//log.Println("devUserCreate testing......")
 	appID := utils.GetAppID(time.Now().Unix())
 	secretKey := sutil.GetComplexRandom(8, 3, 5)
-	fmt.Printf("appID:[%s],secretKey:[%s]", appID, secretKey)
+
+	log.Printf("create successful appID:[%s],secretKey:[%s]", appID, secretKey)
+
+	//创建用户、给用户开通接口产品时候有以下几种情况
+	//1.线上给账户充值10000块(user_account),不去实际购买产品,无购买记录,用户产品表无剩余量(left_num为0).此时又分两种情况
+	//1.1 按次扣账户余额
+	//1.2 按量扣账户余额
+
+	//appID := ""
+	//tradeMoney := 1 * 100 * 10000 //充值1万块钱
+}
+func chooseUserProduct(appID string, tradeMoney int) {
+
 }

+ 46 - 1
test/project/project_test.go

@@ -2,19 +2,64 @@ package project
 
 import (
 	"encoding/json"
+	"fmt"
 	"io/ioutil"
 	"log"
 	"net/http"
+	"net/url"
+	"sfbase/utils"
 	"strings"
+	"sync"
 	"testing"
+	"time"
 )
 
 var (
 	apiurl = "http://127.0.0.1:8080/sfis/api/v1/projectList"
 )
 
+func Test_ProjectListApi(t *testing.T) {
+	var wg = sync.WaitGroup{}
+	for i := 0; i < 15; i++ {
+		wg.Add(1)
+		go func(i int) {
+			defer wg.Done()
+			getData1(i)
+		}(i)
+	}
+
+	wg.Wait()
+
+	c := make(chan int)
+	c <- 2
+}
+
 func Test_Project(t *testing.T) {
-	getData()
+	//getData()
+	m := &sync.Map{}
+	m.Store("sfGSVYRQMAAgkGBAUBJg4f", &sync.Mutex{})
+	if v, ok := m.Load("sfGSVYRQMAAgkGBAUBJg4f"); ok {
+		log.Println("取到值:", v)
+	}
+}
+
+func getData1(n int) {
+	appID := "sfGSVYRQMAAgkGBAUBJg4f"
+	secretKey := "364xw909"
+	projectName := "河南省地税局2021年信息化建设招标项目"
+	winner := "河南拓普计算机" + fmt.Sprint(n)
+	zbRq := "2020-12-02"
+	data := make(url.Values)
+	data["projectName"] = []string{projectName}
+	data["winner"] = []string{winner}
+	data["zbRq"] = []string{zbRq}
+	data["app_id"] = []string{appID}
+	now := time.Now().Unix()
+	bs, _ := utils.HttpPostForm("http://localhost:8080/sfis/api/v1/projectList", map[string]string{
+		"token":     utils.MD5(fmt.Sprintf("%s%d%s", appID, now, secretKey)),
+		"timestamp": fmt.Sprint(now),
+	}, data)
+	log.Print(string(bs))
 }
 
 func getData() {

+ 36 - 55
utils/api_util.go

@@ -2,66 +2,47 @@ package utils
 
 import (
 	"github.com/gin-gonic/gin"
-	"gorm.io/gorm"
+	"log"
 	"sfis/db"
 	"sfis/lock"
 	"sfis/model"
 	"sfis/model/response"
 )
 
-func Check(appID string, productID int, context *gin.Context, getData func() []interface{}) {
-	if userLock := lock.GetUserLock(appID); userLock != nil {
-		/**
-		第二步:用户接口产品校验-加锁处理
-		*/
-		//2.1 取用户接口状态校验
-		//userLock.Lock()
-		userProduct := &model.UserProduct{}
-		db.GetSFISDB().First(userProduct, &model.UserProduct{AppID: appID, ProductID: productID})
-		//userLock.Unlock()
-		if userProduct.InterfaceStatus != 0 {
-			response.FailWithDetailed(response.InterfaceDeleted, nil, "该用户接口暂不提供服务", context)
-			return
-		}
-
-		//2.2 取用户 产品余量|钱包账户余额 校验
-		costModel := userProduct.CostModel
-		product := GetProductByID(productID)
-		productType := product.ProductType
-		userLock.Lock()
-		db.GetSFISDB().First(userProduct, &model.UserProduct{AppID: appID, ProductID: productID})
-		switch costModel {
-		case 0:
-			//按剩余量扣费
-			if productType == 0 {
-				//按次扣费-每调一次 剩余量-1
-				if userProduct.LeftNum == 0 {
-					response.FailWithDetailed(response.LeftNumEmpty, nil, "余额不足", context)
-					return
-				}
-				db.GetSFISDB().Transaction(func(tx *gorm.DB) error {
-					//扣费
-					tx.Exec("update user_product set left_num = IF(`left_num`<1, 0, `left_num`-1) WHERE `app_id` = ? and product_id=?", appID, productID)
-					//生调用记录
-					//生订单
-					//存历史数据
-					return nil
-				})
-
-			} else if productType == 1 {
-				//按条扣费-每调一次剩余量-len(getDataList)
-			}
-		case 1:
-			//按账户钱包余额扣费
-			if productType == 0 {
-				//按次扣费-每调一次
-				//todo 账户余额表user_account的余额 减去 product单价*1
-			} else if productType == 1 {
-				//按条扣费-每调一次
-				//todo 账户余额表user_account的余额 减去 product单价*len(getDataList)
-			}
-		case 2:
-			//优先扣剩余量,剩余量为0,扣钱包余额
-		}
+func Check(appID string, productID int, context *gin.Context, getData func() ([]interface{}, int, error), param, ip string) {
+	lock.MainLock.Lock()
+	userLock := lock.UserLockMap[appID]
+	lock.MainLock.Unlock()
+	/**
+	第二步:用户接口产品校验-加锁处理
+	*/
+	//2.1 取用户接口状态校验-可加锁也可不加锁 这个没有太严谨
+	//userLock.Lock()
+	userProduct := &model.UserProduct{}
+	db.GetSFISDB().First(userProduct, &model.UserProduct{AppID: appID, ProductID: productID})
+	//userLock.Unlock()
+	if userProduct.InterfaceStatus != 0 {
+		response.FailWithDetailed(response.InterfaceDeleted, nil, "该用户接口暂不提供服务", context)
+		return
 	}
+	//2.2 取用户(产品余量|钱包账户余额)校验-必须加锁
+	costModel := userProduct.CostModel //扣费模式 0扣余量,1-扣余额
+	product := GetProductByID(productID)
+	userLock.Lock()
+	log.Println(param + "锁住......")
+	db.GetSFISDB().First(userProduct, &model.UserProduct{AppID: appID, ProductID: productID})
+	costModel = 0
+	switch costModel {
+	case 0:
+		//按剩余量扣费
+		costByLeftNum(getData, appID, productID, userProduct, product, param, ip)
+	case 1:
+		//按账户钱包余额扣费
+		costByAccountBalance(getData, appID, userProduct, product)
+	case 2:
+		//优先扣剩余量,剩余量为0,扣钱包余额
+	}
+	userLock.Unlock()
+	log.Println(param + "解锁......")
+
 }

+ 1 - 1
utils/caches.go

@@ -9,7 +9,7 @@ import (
 var (
 	UserCaches    = new(ResourceCache)
 	ProductCaches = new(ResourceCache)
-	ApiUrlCache   = new(sync.Map)
+	ApiUrlCache   = sync.Map{}
 )
 
 type ResourceCache struct {

+ 18 - 0
utils/cost_by_account_balance.go

@@ -0,0 +1,18 @@
+package utils
+
+import "sfis/model"
+
+/**
+扣账户余额
+*/
+func costByAccountBalance(getData func() ([]interface{},int, error), appID string, userProduct *model.UserProduct, product *model.Product) {
+	productType := product.ProductType
+	//productUnit := product.UnitPrice
+	if productType == 0 {
+		//按次扣费-每调一次
+		//todo 账户余额表user_account的余额 减去 product单价*1
+	} else if productType == 1 {
+		//按条扣费-每调一次
+		//todo 账户余额表user_account的余额 减去 product单价*len(getDataList)
+	}
+}

+ 105 - 0
utils/cost_by_left_num.go

@@ -0,0 +1,105 @@
+package utils
+
+import (
+	"gorm.io/gorm"
+	"log"
+	"sfbase/global"
+	"sfbase/utils"
+	"sfis/db"
+	"sfis/model"
+)
+
+/**
+扣产品剩余量
+*/
+func costByLeftNum(getData func() ([]interface{}, int, error), appID string, productID int, userProduct *model.UserProduct, product *model.Product, param, ip string) {
+	productType := product.ProductType
+	beforeJudge := before(productType, userProduct)
+	if beforeJudge {
+		data, statusCode, _ := execute(getData, appID, productID)
+		after(productType, len(data), userProduct, statusCode, param, ip)
+	}
+}
+
+/**
+用户产品剩余量|账户余额判断
+*/
+func before(productType int, userProduct *model.UserProduct) bool {
+	switch productType {
+	case 0:
+		//按次扣费-判断left_num
+		if userProduct.LeftNum == 0 {
+			//response.FailWithDetailed(response.LeftNumEmpty, nil, "余额不足", context)
+			return false
+		}
+	case 1:
+		//按条扣费-判断单次调用的limit
+		if userProduct.LeftNum < userProduct.DataNumLimitOneTimes {
+			//response.FailWithDetailed(response.LeftNumEmpty, nil, "剩余数据量小于该产品每次返回数据量,可能造成数据量不准确", context)
+			return false
+		}
+	}
+	return true
+}
+
+/**
+执行接口查询
+*/
+func execute(getData func() ([]interface{}, int, error), appID string, productID int) ([]interface{}, int, error) {
+	data, statusCode, err := getData()
+	if err != nil {
+		global.Logger.Error("调用数据出错:", getUserProductError(appID, productID, err)...)
+		return nil, -1, err
+	}
+	dataLen := len(data)
+	global.Logger.Info("调用成功:", getUserProductInfo(appID, productID, "数据长度", dataLen)...)
+	return data, statusCode, nil
+}
+
+func after(productType int, dataLen int, userProduct *model.UserProduct, statusCode int, param, ip string) {
+	appID := userProduct.AppID
+	productID := userProduct.ProductID
+	userProductID := userProduct.ID
+	switch productType {
+	case 0:
+		//按次扣费-(每调一次剩余量-1)
+		_ = db.GetSFISDB().Transaction(func(tx *gorm.DB) error {
+			orderBefore := userProduct.LeftNum
+			orderAfter := userProduct.LeftNum - 1
+			//扣费
+			err := tx.Exec("update user_product set left_num = IF(`left_num`<1, 0, `left_num`-1) WHERE `app_id` = ? and product_id=?", appID, productID).Error
+			if err != nil {
+				log.Printf("appID:[%s],productID:[%d] execute cost left_num-1 error:[%v]", appID, productID, err)
+				tx.Rollback()
+				return err
+			}
+			//生调用记录
+			err = tx.Exec("insert into user_call_record (app_id,user_product_id,status,ip,param) values (?,?,?,?,?)", appID, userProductID, statusCode, ip, param).Error
+			if err != nil {
+				log.Printf("appID:[%s],productID:[%d] execute insert into user_call_record error:[%v]", appID, productID, err)
+				tx.Rollback()
+				return err
+			}
+			//生订单
+			orderCode := utils.GenerateSimpleToken()
+			err = tx.Exec("insert into interface_order (order_code,app_id,user_product_id,`before`,`after`,cost_model,trade_num) values (?,?,?,?,?,?,?)", orderCode, appID, userProductID, orderBefore, orderAfter, 0, 1).Error
+			if err != nil {
+				log.Printf("appID:[%s],productID:[%d] execute insert into interface_order error:[%v]", appID, productID, err)
+				tx.Rollback()
+				return err
+			}
+			//存历史数据
+			return nil
+		})
+	case 1:
+		//按条扣费-(每调一次剩余量-len(getDataList))
+		_ = db.GetSFISDB().Transaction(func(tx *gorm.DB) error {
+			//扣费
+			tx.Exec("update user_product set left_num = IF(`left_num`<1, 0, `left_num`-?) WHERE `app_id` = ? and product_id=?", dataLen, appID, productID)
+			//生调用记录
+			//生订单
+			//存历史数据
+			return nil
+		})
+	}
+}

+ 17 - 0
utils/user_util.go

@@ -2,6 +2,7 @@ package utils
 
 import (
 	"fmt"
+	"go.uber.org/zap"
 	"net"
 	"net/http"
 	"regexp"
@@ -47,3 +48,19 @@ func GetIp(req *http.Request) string {
 	ip, _, _ := net.SplitHostPort(req.RemoteAddr)
 	return ip
 }
+
+func getUserProductError(appID string, productID int, err error) []zap.Field {
+	arr := make([]zap.Field, 0)
+	arr = append(arr, zap.Any("appID:", appID))
+	arr = append(arr, zap.Any("productID:", productID))
+	arr = append(arr, zap.Any("error:", err))
+	return arr
+}
+
+func getUserProductInfo(appID string, productID int, key string, info interface{}) []zap.Field {
+	arr := make([]zap.Field, 0)
+	arr = append(arr, zap.Any("appID:", appID))
+	arr = append(arr, zap.Any("productID:", productID))
+	arr = append(arr, zap.Any(key, info))
+	return arr
+}