作者 陈志颖

合并分支 'test' 到 'master'

Test



查看合并请求 !19
正在显示 62 个修改的文件 包含 2627 行增加1079 行删除

要显示太多修改。

为保证性能只显示 62 of 62+ 个文件。

@@ -25,3 +25,4 @@ @@ -25,3 +25,4 @@
25 25
26 /*.exe~ 26 /*.exe~
27 /logs 27 /logs
  28 +download
@@ -79,9 +79,13 @@ spec: @@ -79,9 +79,13 @@ spec:
79 - name: BUSINESS_ADMIN_HOST 79 - name: BUSINESS_ADMIN_HOST
80 value: "http://suplus-business-admin-dev.fjmaimaimai.com" 80 value: "http://suplus-business-admin-dev.fjmaimaimai.com"
81 - name: KAFKA_HOST 81 - name: KAFKA_HOST
82 - value: "192.168.0.250:9092;192.168.0.251:9092;192.168.0.252:9092" 82 + value: ""
83 - name: KAFKA_CONSUMER_ID 83 - name: KAFKA_CONSUMER_ID
84 value: "partnermg_dev" 84 value: "partnermg_dev"
  85 + - name: RUN_MODE
  86 + value: "dev"
  87 + - name: Log_PREFIX
  88 + value: "[partnermg_dev]"
85 volumes: 89 volumes:
86 - name: accesslogs 90 - name: accesslogs
87 emptyDir: {} 91 emptyDir: {}
@@ -79,6 +79,10 @@ spec: @@ -79,6 +79,10 @@ spec:
79 value: "192.168.0.250:9092;192.168.0.251:9092;192.168.0.252:9092" 79 value: "192.168.0.250:9092;192.168.0.251:9092;192.168.0.252:9092"
80 - name: KAFKA_CONSUMER_ID 80 - name: KAFKA_CONSUMER_ID
81 value: "partnermg_prd" 81 value: "partnermg_prd"
  82 + - name: RUN_MODE
  83 + value: "dev"
  84 + - name: Log_PREFIX
  85 + value: "[partnermg_prd]"
82 volumes: 86 volumes:
83 - name: accesslogs 87 - name: accesslogs
84 emptyDir: {} 88 emptyDir: {}
@@ -79,6 +79,10 @@ spec: @@ -79,6 +79,10 @@ spec:
79 value: "192.168.0.250:9092;192.168.0.251:9092;192.168.0.252:9092" 79 value: "192.168.0.250:9092;192.168.0.251:9092;192.168.0.252:9092"
80 - name: KAFKA_CONSUMER_ID 80 - name: KAFKA_CONSUMER_ID
81 value: "partnermg_test" 81 value: "partnermg_test"
  82 + - name: RUN_MODE
  83 + value: "dev"
  84 + - name: Log_PREFIX
  85 + value: "[partnermg_test]"
82 volumes: 86 volumes:
83 - name: accesslogs 87 - name: accesslogs
84 emptyDir: {} 88 emptyDir: {}
@@ -7,12 +7,13 @@ require ( @@ -7,12 +7,13 @@ require (
7 github.com/Shopify/sarama v1.23.1 7 github.com/Shopify/sarama v1.23.1
8 github.com/ajg/form v1.5.1 // indirect 8 github.com/ajg/form v1.5.1 // indirect
9 github.com/astaxie/beego v1.12.2 9 github.com/astaxie/beego v1.12.2
  10 + github.com/beego/beego/v2 v2.0.1
10 github.com/bsm/sarama-cluster v2.1.15+incompatible 11 github.com/bsm/sarama-cluster v2.1.15+incompatible
11 github.com/dgrijalva/jwt-go v3.2.0+incompatible 12 github.com/dgrijalva/jwt-go v3.2.0+incompatible
12 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect 13 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
13 github.com/fatih/structs v1.1.0 // indirect 14 github.com/fatih/structs v1.1.0 // indirect
14 github.com/gavv/httpexpect v2.0.0+incompatible 15 github.com/gavv/httpexpect v2.0.0+incompatible
15 - github.com/go-pg/pg/v10 v10.0.0-beta.2 16 + github.com/go-pg/pg/v10 v10.7.3
16 github.com/google/go-querystring v1.0.0 // indirect 17 github.com/google/go-querystring v1.0.0 // indirect
17 github.com/gorilla/websocket v1.4.2 // indirect 18 github.com/gorilla/websocket v1.4.2 // indirect
18 github.com/imkira/go-interpol v1.1.0 // indirect 19 github.com/imkira/go-interpol v1.1.0 // indirect
@@ -20,8 +21,9 @@ require ( @@ -20,8 +21,9 @@ require (
20 github.com/linmadan/egglib-go v0.0.0-20191217144343-ca4539f95bf9 21 github.com/linmadan/egglib-go v0.0.0-20191217144343-ca4539f95bf9
21 github.com/mattn/go-colorable v0.1.6 // indirect 22 github.com/mattn/go-colorable v0.1.6 // indirect
22 github.com/moul/http2curl v1.0.0 // indirect 23 github.com/moul/http2curl v1.0.0 // indirect
23 - github.com/onsi/ginkgo v1.13.0  
24 - github.com/onsi/gomega v1.10.1 24 + github.com/onsi/ginkgo v1.14.2
  25 + github.com/onsi/gomega v1.10.3
  26 + github.com/sclevine/agouti v3.0.0+incompatible // indirect
25 github.com/sergi/go-diff v1.1.0 // indirect 27 github.com/sergi/go-diff v1.1.0 // indirect
26 github.com/shopspring/decimal v1.2.0 28 github.com/shopspring/decimal v1.2.0
27 github.com/smartystreets/goconvey v1.6.4 // indirect 29 github.com/smartystreets/goconvey v1.6.4 // indirect
1 package command 1 package command
2 2
  3 +import "errors"
  4 +
3 //创建订单 5 //创建订单
4 type CreateOrderCommand struct { 6 type CreateOrderCommand struct {
5 //订单类型 7 //订单类型
@@ -18,7 +20,43 @@ type CreateOrderCommand struct { @@ -18,7 +20,43 @@ type CreateOrderCommand struct {
18 SalesmanBonusPercent float64 `json:"salesmanBonusPercent"` 20 SalesmanBonusPercent float64 `json:"salesmanBonusPercent"`
19 //货品 21 //货品
20 Goods []OrderGoodData `json:"goods"` 22 Goods []OrderGoodData `json:"goods"`
  23 + //公司id
21 CompanyId int64 `json:"companyId"` 24 CompanyId int64 `json:"companyId"`
22 - 25 + //合伙人类型
23 PartnerCategory int64 `json:"partner_category"` 26 PartnerCategory int64 `json:"partner_category"`
  27 + //行号-错误信息返回
  28 + LineNumbers []int `json:"lineNumber"`
  29 + //合伙人姓名
  30 + PartnerName string `json:"partnerName"`
  31 + //编号-错误信息返回
  32 + Code string `json:"code"`
  33 + //合伙人类型名称-错误信息返回
  34 + PartnerCategoryName string `json:"partnerCategoryName"`
  35 +}
  36 +
  37 +func (postData *CreateOrderCommand) Valid() error {
  38 + if len(postData.OrderCode) == 0 {
  39 + return errors.New("订单编号必填")
  40 + }
  41 + if len(postData.BuyerName) == 0 {
  42 + return errors.New("买家信息必填")
  43 + }
  44 + if postData.PartnerId == 0 {
  45 + return errors.New("合伙人信息必填")
  46 + }
  47 + if len(postData.OrderRegion) == 0 {
  48 + return errors.New("订单区域必填")
  49 + }
  50 + if len(postData.Goods) == 0 {
  51 + return errors.New("货品列表必填")
  52 + }
  53 + if len(postData.Goods) > 50 {
  54 + return errors.New("货品列表最多50项")
  55 + }
  56 + for i := range postData.Goods {
  57 + if err := postData.Goods[i].Valid(); err != nil {
  58 + return err
  59 + }
  60 + }
  61 + return nil
24 } 62 }
1 package command 1 package command
2 2
  3 +import (
  4 + "errors"
  5 + "fmt"
  6 + "regexp"
  7 + "unicode/utf8"
  8 +)
  9 +
3 type OrderGoodData struct { 10 type OrderGoodData struct {
4 //货品id 11 //货品id
5 Id int64 `json:"id"` 12 Id int64 `json:"id"`
@@ -13,4 +20,33 @@ type OrderGoodData struct { @@ -13,4 +20,33 @@ type OrderGoodData struct {
13 PartnerBonusPercent float64 `json:"partnerBonusPercent"` 20 PartnerBonusPercent float64 `json:"partnerBonusPercent"`
14 //备注信息 21 //备注信息
15 Remark string `json:"remark"` 22 Remark string `json:"remark"`
  23 + //行号-错误信息返回
  24 + LineNumber int `json:"lineNumber"`
  25 +}
  26 +
  27 +func (postData OrderGoodData) Valid() error {
  28 + lenProductName := utf8.RuneCountInString(postData.GoodName)
  29 + if lenProductName == 0 {
  30 + return errors.New("商品名称必填")
  31 + }
  32 + if lenProductName > 50 {
  33 + return errors.New("商品名称最多50位")
  34 + }
  35 + if postData.PlanGoodNumber >= 1e16 {
  36 + return errors.New("商品数量最多16位")
  37 + }
  38 + if postData.Price >= 1e16 {
  39 + return errors.New("商品单价最多16位")
  40 + }
  41 + if postData.PartnerBonusPercent > 100 {
  42 + return errors.New("合伙人分红比例超额")
  43 + }
  44 + partnerRatio := fmt.Sprint(postData.PartnerBonusPercent)
  45 + regexpStr := `^(100|[1-9]\d|\d)(.\d{1,2})?$`
  46 + ok := regexp.MustCompile(regexpStr).MatchString(partnerRatio)
  47 + if !ok {
  48 + return errors.New("合伙人分红比例精确到小数点2位")
  49 + }
  50 +
  51 + return nil
16 } 52 }
@@ -19,7 +19,8 @@ type UpdateOrderCommand struct { @@ -19,7 +19,8 @@ type UpdateOrderCommand struct {
19 OrderType int `json:"orderType"` 19 OrderType int `json:"orderType"`
20 //货品 20 //货品
21 Goods []OrderGoodData `json:"goods"` 21 Goods []OrderGoodData `json:"goods"`
  22 + //公司id
22 CompanyId int64 `json:"companyId"` 23 CompanyId int64 `json:"companyId"`
23 - 24 + // 合伙人类型
24 PartnerCategory int64 `json:"partner_category"` 25 PartnerCategory int64 `json:"partner_category"`
25 } 26 }
  1 +/**
  2 + @author: stevechan
  3 + @date: 2021/1/6
  4 + @note:
  5 +**/
  6 +
  7 +package query
  8 +
  9 +/**
  10 + * @Author SteveChan
  11 + * @Description //TODO 查询合伙人id
  12 + * @Date 23:18 2021/1/6
  13 + **/
  14 +type GetPartnerIdQuery struct {
  15 + Code string `json:"code"`
  16 + PartnerCategory int `json:"partnerCategory"`
  17 + CompanyId int64 `json:"companyId"`
  18 +}
  1 +/**
  2 + @author: stevechan
  3 + @date: 2021/1/6
  4 + @note:
  5 +**/
  6 +
  7 +package query
  8 +
  9 +/**
  10 + * @Author SteveChan
  11 + * @Description //TODO 查询产品id
  12 + * @Date 23:18 2021/1/6
  13 + **/
  14 +type GetProductIdQuery struct {
  15 + ProductName int64 `json:"productName"`
  16 +}
@@ -7,14 +7,25 @@ type ListOrderBaseQuery struct { @@ -7,14 +7,25 @@ type ListOrderBaseQuery struct {
7 // 查询限制 7 // 查询限制
8 Limit int `json:"limit"` 8 Limit int `json:"limit"`
9 //发货单号 9 //发货单号
10 - PartnerOrCode string `json:"partnerOrCode"` 10 + //PartnerOrCode string `json:"partnerOrCode"`
  11 + //合伙人姓名
  12 + PartnerName string `json:"partnerName"`
  13 + //订单号
  14 + OrderCode string `json:"orderCode"`
  15 + //发货单号
  16 + DeliveryCode string `json:"deliveryCode"`
  17 + //公司id
11 CompanyId int64 `json:"companyId"` 18 CompanyId int64 `json:"companyId"`
12 //订单类型 19 //订单类型
13 OrderType int `json:"orderType"` 20 OrderType int `json:"orderType"`
14 //合伙人分类 21 //合伙人分类
15 PartnerCategory int `json:"partnerCategory"` 22 PartnerCategory int `json:"partnerCategory"`
  23 + //更新时间开始
16 UpdateTimeBegin string `json:"updateTimeBegin"` 24 UpdateTimeBegin string `json:"updateTimeBegin"`
  25 + //更新时间截止
17 UpdateTimeEnd string `json:"updateTimeEnd"` 26 UpdateTimeEnd string `json:"updateTimeEnd"`
  27 + //创建时间开始
18 CreateTimeBegin string `json:"createTimeBegin"` 28 CreateTimeBegin string `json:"createTimeBegin"`
  29 + //创建时间截止
19 CreateTimeEnd string `json:"createTimeEnd"` 30 CreateTimeEnd string `json:"createTimeEnd"`
20 } 31 }
@@ -26,7 +26,13 @@ func NewOrderInfoService(option map[string]interface{}) *OrderInfoService { @@ -26,7 +26,13 @@ func NewOrderInfoService(option map[string]interface{}) *OrderInfoService {
26 return newAdminUserService 26 return newAdminUserService
27 } 27 }
28 28
29 -// PageListOrderBase 获取订单列表 29 +/**
  30 + * @Author SteveChan
  31 + * @Description // 获取订单列表
  32 + * @Date 22:05 2021/1/10
  33 + * @Param
  34 + * @return
  35 + **/
30 func (service OrderInfoService) PageListOrderBase(listOrderQuery query.ListOrderBaseQuery) ([]map[string]interface{}, int, error) { 36 func (service OrderInfoService) PageListOrderBase(listOrderQuery query.ListOrderBaseQuery) ([]map[string]interface{}, int, error) {
31 var err error 37 var err error
32 transactionContext, err := factory.CreateTransactionContext(nil) 38 transactionContext, err := factory.CreateTransactionContext(nil)
@@ -53,7 +59,9 @@ func (service OrderInfoService) PageListOrderBase(listOrderQuery query.ListOrder @@ -53,7 +59,9 @@ func (service OrderInfoService) PageListOrderBase(listOrderQuery query.ListOrder
53 orders, cnt, err = orderDao.OrderListByCondition( 59 orders, cnt, err = orderDao.OrderListByCondition(
54 listOrderQuery.CompanyId, 60 listOrderQuery.CompanyId,
55 listOrderQuery.OrderType, 61 listOrderQuery.OrderType,
56 - listOrderQuery.PartnerOrCode, 62 + listOrderQuery.PartnerName, // 合伙人姓名
  63 + listOrderQuery.OrderCode, // 订单号
  64 + listOrderQuery.DeliveryCode, // 发货单号
57 [2]string{listOrderQuery.UpdateTimeBegin, listOrderQuery.UpdateTimeEnd}, 65 [2]string{listOrderQuery.UpdateTimeBegin, listOrderQuery.UpdateTimeEnd},
58 [2]string{listOrderQuery.CreateTimeBegin, listOrderQuery.CreateTimeEnd}, 66 [2]string{listOrderQuery.CreateTimeBegin, listOrderQuery.CreateTimeEnd},
59 listOrderQuery.PartnerCategory, 67 listOrderQuery.PartnerCategory,
@@ -186,7 +194,9 @@ func (service OrderInfoService) CreateNewOrder(cmd command.CreateOrderCommand) ( @@ -186,7 +194,9 @@ func (service OrderInfoService) CreateNewOrder(cmd command.CreateOrderCommand) (
186 transactionContext, _ = factory.CreateTransactionContext(nil) 194 transactionContext, _ = factory.CreateTransactionContext(nil)
187 err error 195 err error
188 ) 196 )
189 - 197 + if err = cmd.Valid(); err != nil {
  198 + return nil, lib.ThrowError(lib.BUSINESS_ERROR, err.Error())
  199 + }
190 if err = transactionContext.StartTransaction(); err != nil { 200 if err = transactionContext.StartTransaction(); err != nil {
191 return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error()) 201 return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
192 } 202 }
@@ -225,19 +235,24 @@ func (service OrderInfoService) CreateNewOrder(cmd command.CreateOrderCommand) ( @@ -225,19 +235,24 @@ func (service OrderInfoService) CreateNewOrder(cmd command.CreateOrderCommand) (
225 }); err != nil { 235 }); err != nil {
226 return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error()) 236 return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error())
227 } 237 }
228 - //检查order_code是否重复  
229 - // if ok, err := orderBaseDao.OrderCodeExist(cmd.OrderCode, cmd.PartnerCategory, cmd.PartnerId); err != nil {  
230 - // return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error())  
231 - // } else if ok {  
232 - // return nil, lib.ThrowError(lib.BUSINESS_ERROR, "订单号已存在")  
233 - // }  
234 - //检查delivery_code是否重复  
235 - if len(cmd.DeliveryCode) > 0 {  
236 - if ok, err := orderBaseDao.DeliveryCodeExist(cmd.DeliveryCode, cmd.CompanyId); err != nil { 238 + goodNames := []string{}
  239 + for _, v := range cmd.Goods {
  240 + goodNames = append(goodNames, v.GoodName)
  241 + }
  242 + if ok, err := orderBaseDao.CheckOrderExist(cmd.CompanyId, cmd.OrderCode, cmd.DeliveryCode,
  243 + cmd.PartnerCategory, cmd.PartnerId, 0, goodNames); err != nil {
237 return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error()) 244 return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error())
238 } else if ok { 245 } else if ok {
239 - return nil, lib.ThrowError(lib.BUSINESS_ERROR, "发货号已存在") 246 + return nil, lib.ThrowError(lib.BUSINESS_ERROR, "订单已存在")
  247 + }
  248 + //检查货品数据
  249 + var goodMap = map[string]int{}
  250 + for i := range cmd.Goods {
  251 + goodname := cmd.Goods[i].GoodName
  252 + if _, ok := goodMap[goodname]; ok {
  253 + return nil, lib.ThrowError(lib.BUSINESS_ERROR, "订单中货品重复已存在")
240 } 254 }
  255 + goodMap[goodname] = 1
241 } 256 }
242 newOrder := &domain.OrderBase{ 257 newOrder := &domain.OrderBase{
243 OrderType: cmd.OrderType, OrderCode: cmd.OrderCode, 258 OrderType: cmd.OrderType, OrderCode: cmd.OrderCode,
@@ -435,13 +450,25 @@ func (service OrderInfoService) UpdateOrderData(cmd command.UpdateOrderCommand) @@ -435,13 +450,25 @@ func (service OrderInfoService) UpdateOrderData(cmd command.UpdateOrderCommand)
435 // return nil, lib.ThrowError(lib.BUSINESS_ERROR, "订单号已存在") 450 // return nil, lib.ThrowError(lib.BUSINESS_ERROR, "订单号已存在")
436 // } 451 // }
437 // } 452 // }
  453 + goodNames := []string{}
  454 + for _, v := range cmd.Goods {
  455 + goodNames = append(goodNames, v.GoodName)
  456 + }
438 //检查delivery_code是否重复 457 //检查delivery_code是否重复
439 - if cmd.DeliveryCode != oldOrderData.DeliveryCode {  
440 - if ok, err := orderBaseDao.DeliveryCodeExist(cmd.DeliveryCode, cmd.CompanyId, cmd.Id); err != nil { 458 + if ok, err := orderBaseDao.CheckOrderExist(cmd.CompanyId, cmd.OrderCode, cmd.DeliveryCode,
  459 + cmd.PartnerCategory, cmd.PartnerId, cmd.Id, goodNames); err != nil {
441 return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error()) 460 return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error())
442 } else if ok { 461 } else if ok {
443 - return nil, lib.ThrowError(lib.BUSINESS_ERROR, "发货号已存在") 462 + return nil, lib.ThrowError(lib.BUSINESS_ERROR, "订单已存在")
444 } 463 }
  464 + //检查货品数据
  465 + var goodMap = map[string]int{}
  466 + for i := range cmd.Goods {
  467 + goodname := cmd.Goods[i].GoodName
  468 + if _, ok := goodMap[goodname]; ok {
  469 + return nil, lib.ThrowError(lib.BUSINESS_ERROR, "订单中货品重复已存在")
  470 + }
  471 + goodMap[goodname] = 1
445 } 472 }
446 //获取旧的订单中的商品 473 //获取旧的订单中的商品
447 oldOrderGoods, _, err = orderGoodRepository.Find(domain.OrderGoodFindQuery{ 474 oldOrderGoods, _, err = orderGoodRepository.Find(domain.OrderGoodFindQuery{
@@ -854,6 +881,13 @@ func (service OrderInfoService) ListOrderBonusForExcel(listOrderQuery query.List @@ -854,6 +881,13 @@ func (service OrderInfoService) ListOrderBonusForExcel(listOrderQuery query.List
854 return resultMaps, column, nil 881 return resultMaps, column, nil
855 } 882 }
856 883
  884 +/**
  885 + * @Author SteveChan
  886 + * @Description // 导出订单数据
  887 + * @Date 22:05 2021/1/10
  888 + * @Param
  889 + * @return
  890 + **/
857 func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrderBaseQuery) ([]map[string]string, [][2]string, error) { 891 func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrderBaseQuery) ([]map[string]string, [][2]string, error) {
858 transactionContext, err := factory.CreateTransactionContext(nil) 892 transactionContext, err := factory.CreateTransactionContext(nil)
859 if err != nil { 893 if err != nil {
@@ -865,6 +899,7 @@ func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrder @@ -865,6 +899,7 @@ func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrder
865 defer func() { 899 defer func() {
866 transactionContext.RollbackTransaction() 900 transactionContext.RollbackTransaction()
867 }() 901 }()
  902 +
868 var ( 903 var (
869 orderBaseDao *dao.OrderBaseDao 904 orderBaseDao *dao.OrderBaseDao
870 ) 905 )
@@ -876,7 +911,9 @@ func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrder @@ -876,7 +911,9 @@ func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrder
876 } 911 }
877 ordersData, err := orderBaseDao.OrderListForExcel( 912 ordersData, err := orderBaseDao.OrderListForExcel(
878 listOrderQuery.CompanyId, 913 listOrderQuery.CompanyId,
879 - listOrderQuery.PartnerOrCode, 914 + listOrderQuery.PartnerName, // 合伙人姓名
  915 + listOrderQuery.OrderCode, // 订单号
  916 + listOrderQuery.DeliveryCode, // 发货单号
880 [2]string{listOrderQuery.UpdateTimeBegin, listOrderQuery.UpdateTimeEnd}, 917 [2]string{listOrderQuery.UpdateTimeBegin, listOrderQuery.UpdateTimeEnd},
881 [2]string{listOrderQuery.CreateTimeBegin, listOrderQuery.CreateTimeEnd}, 918 [2]string{listOrderQuery.CreateTimeBegin, listOrderQuery.CreateTimeEnd},
882 listOrderQuery.PartnerCategory, 919 listOrderQuery.PartnerCategory,
@@ -930,3 +967,333 @@ func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrder @@ -930,3 +967,333 @@ func (service OrderInfoService) ListOrderForExcel(listOrderQuery query.ListOrder
930 } 967 }
931 return resultMaps, column, nil 968 return resultMaps, column, nil
932 } 969 }
  970 +
  971 +/**
  972 + * @Author SteveChan
  973 + * @Description //TODO 批量导入创建订单
  974 + * @Date 11:00 2021/1/7
  975 + * @Param
  976 + * @return
  977 + **/
  978 +func (service OrderInfoService) CreateNewOrderByImport(createOrderCommands []*command.CreateOrderCommand) ([]*domain.ImportInfo, error) {
  979 + // 事务初始化
  980 + var (
  981 + transactionContext, _ = factory.CreateTransactionContext(nil)
  982 + err error
  983 + errorDataList []*domain.ImportInfo // 错误数据返回
  984 + )
  985 +
  986 + // 循环校验命令
  987 + for _, cmd := range createOrderCommands {
  988 + if err = cmd.Valid(); err != nil {
  989 + // 返回信息 0: 订单号, 1: 发货单号, 2: 客户名称, 3: 订单区域, 4: 编号, 5: 合伙人, 6: 类型, 7: 业务抽成比例, 8: 产品名称, 9: 数量, 10: 单价, 11: 合伙人分红比例
  990 + row := &domain.ImportInfo{
  991 + Error: lib.ThrowError(lib.BUSINESS_ERROR, err.Error()), // 错误信息
  992 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  993 + GoodLine: map[int]interface{}{},
  994 + }
  995 + errorDataList = append(errorDataList, row)
  996 + continue
  997 + }
  998 + }
  999 +
  1000 + // 开始事务
  1001 + if err = transactionContext.StartTransaction(); err != nil {
  1002 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1003 + }
  1004 +
  1005 + defer func() {
  1006 + transactionContext.RollbackTransaction()
  1007 + }()
  1008 +
  1009 + // 仓储、数据访问对象初始化
  1010 + var (
  1011 + orderBaseRepository domain.OrderBaseRepository
  1012 + orderGoodRepository domain.OrderGoodRepository
  1013 + PartnerInfoRepository domain.PartnerInfoRepository
  1014 + categoryRepository domain.PartnerCategoryRepository
  1015 + orderBaseDao *dao.OrderBaseDao
  1016 + )
  1017 +
  1018 + // 合伙人信息仓储初始化
  1019 + if PartnerInfoRepository, err = factory.CreatePartnerInfoRepository(map[string]interface{}{
  1020 + "transactionContext": transactionContext,
  1021 + }); err != nil {
  1022 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1023 + }
  1024 +
  1025 + // 订单仓储初始化
  1026 + if orderBaseRepository, err = factory.CreateOrderBaseRepository(map[string]interface{}{
  1027 + "transactionContext": transactionContext,
  1028 + }); err != nil {
  1029 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1030 + }
  1031 +
  1032 + // 订单产品仓储初始化
  1033 + if orderGoodRepository, err = factory.CreateOrderGoodRepository(map[string]interface{}{
  1034 + "transactionContext": transactionContext,
  1035 + }); err != nil {
  1036 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1037 + }
  1038 +
  1039 + // 合伙人类型仓储初始化
  1040 + if categoryRepository, err = factory.CreatePartnerCategoryRepository(map[string]interface{}{
  1041 + "transactionContext": transactionContext,
  1042 + }); err != nil {
  1043 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1044 + }
  1045 +
  1046 + // 订单数据访问对象初始化
  1047 + if orderBaseDao, err = factory.CreateOrderBaseDao(map[string]interface{}{
  1048 + "transactionContext": transactionContext,
  1049 + }); err != nil {
  1050 + return nil, lib.ThrowError(lib.TRANSACTION_ERROR, err.Error())
  1051 + }
  1052 +
  1053 + // 批量创建订单
  1054 + for _, cmd := range createOrderCommands {
  1055 + // 批量校验合伙人信息
  1056 + var partnerData *domain.PartnerInfo
  1057 + partnerData, err = PartnerInfoRepository.FindOne(domain.PartnerFindOneQuery{UserId: cmd.PartnerId})
  1058 + if err != nil {
  1059 + row := &domain.ImportInfo{
  1060 + Error: lib.ThrowError(lib.INTERNAL_SERVER_ERROR, fmt.Sprintf("检索合伙人数据失败")),
  1061 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1062 + GoodLine: map[int]interface{}{},
  1063 + }
  1064 + errorDataList = append(errorDataList, row)
  1065 + continue
  1066 + }
  1067 + goodNames := []string{}
  1068 + for _, v := range cmd.Goods {
  1069 + goodNames = append(goodNames, v.GoodName)
  1070 + }
  1071 + // 批量校验订单
  1072 + if ok, err := orderBaseDao.CheckOrderExist(cmd.CompanyId, cmd.OrderCode, cmd.DeliveryCode,
  1073 + cmd.PartnerCategory, cmd.PartnerId, 0, goodNames); err != nil {
  1074 + row := &domain.ImportInfo{
  1075 + Error: lib.ThrowError(lib.TRANSACTION_ERROR, err.Error()),
  1076 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1077 + GoodLine: map[int]interface{}{},
  1078 + }
  1079 + errorDataList = append(errorDataList, row)
  1080 + continue
  1081 + } else if ok {
  1082 + row := &domain.ImportInfo{
  1083 + Error: lib.ThrowError(lib.BUSINESS_ERROR, "订单已存在"),
  1084 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1085 + GoodLine: map[int]interface{}{},
  1086 + }
  1087 + errorDataList = append(errorDataList, row)
  1088 + continue
  1089 + }
  1090 +
  1091 + // 批量校验产品
  1092 + var goodMap = map[string]int{}
  1093 + goodErrMap := make(map[int]interface{}, 0)
  1094 + for i := range cmd.Goods {
  1095 + goodName := cmd.Goods[i].GoodName
  1096 + if _, ok := goodMap[goodName]; ok {
  1097 + goodErrMap[cmd.Goods[i].LineNumber] = lib.ThrowError(lib.BUSINESS_ERROR, "订单中货品重复已存在")
  1098 + continue
  1099 + }
  1100 + goodMap[goodName] = 1
  1101 + }
  1102 + if len(goodErrMap) > 0 {
  1103 + row := &domain.ImportInfo{
  1104 + Error: lib.ThrowError(lib.BUSINESS_ERROR, "订单中货品重复已存在"),
  1105 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1106 + GoodLine: goodErrMap, // 错误产品行号记录
  1107 + }
  1108 + errorDataList = append(errorDataList, row)
  1109 + continue
  1110 + }
  1111 +
  1112 + newOrder := &domain.OrderBase{
  1113 + OrderType: cmd.OrderType, OrderCode: cmd.OrderCode,
  1114 + DeliveryCode: cmd.DeliveryCode,
  1115 + Buyer: domain.Buyer{
  1116 + BuyerName: cmd.BuyerName,
  1117 + },
  1118 + RegionInfo: domain.RegionInfo{
  1119 + RegionName: cmd.OrderRegion,
  1120 + },
  1121 + PartnerId: cmd.PartnerId,
  1122 + PartnerInfo: partnerData.Partner,
  1123 + SalesmanBonusPercent: cmd.SalesmanBonusPercent,
  1124 + CompanyId: cmd.CompanyId,
  1125 + }
  1126 +
  1127 + // 批量校验合伙人分类数据
  1128 + var cmdPartnerCategoryOk bool
  1129 + for _, v := range partnerData.PartnerCategoryInfos {
  1130 + if v.Id == cmd.PartnerCategory {
  1131 + _, categories, err := categoryRepository.Find(domain.PartnerCategoryFindQuery{
  1132 + Ids: []int64{v.Id},
  1133 + })
  1134 + if err != nil {
  1135 + e := fmt.Sprintf("获取合伙人分类数据失败:%s", err)
  1136 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, e)
  1137 + }
  1138 + if len(categories) > 0 {
  1139 + newOrder.PartnerCategory = categories[0]
  1140 + cmdPartnerCategoryOk = true
  1141 + }
  1142 + break
  1143 + }
  1144 + }
  1145 + if !cmdPartnerCategoryOk {
  1146 + row := &domain.ImportInfo{
  1147 + Error: lib.ThrowError(lib.BUSINESS_ERROR, "合伙人类型选择错误"),
  1148 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1149 + GoodLine: map[int]interface{}{},
  1150 + }
  1151 + errorDataList = append(errorDataList, row)
  1152 + continue
  1153 + }
  1154 +
  1155 + // 订单产品分红核算
  1156 + var orderGoods []domain.OrderGood
  1157 + orderGoodErrMap := make(map[int]interface{}, 0)
  1158 + for i, good := range cmd.Goods {
  1159 + m := domain.NewOrderGood()
  1160 + m.OrderId = 0
  1161 + m.GoodName = good.GoodName
  1162 + m.PlanGoodNumber = good.PlanGoodNumber
  1163 + m.Price = good.Price
  1164 + m.PartnerBonusPercent = good.PartnerBonusPercent
  1165 + m.Remark = good.Remark
  1166 + m.CompanyId = cmd.CompanyId
  1167 +
  1168 + err = m.Compute()
  1169 + if err != nil {
  1170 + orderGoodErrMap[cmd.Goods[i].LineNumber] = lib.ThrowError(lib.INTERNAL_SERVER_ERROR, fmt.Sprintf("核算订单中商品的数值失败:%s", err))
  1171 + continue
  1172 + }
  1173 +
  1174 + err = m.CurrentBonusStatus.WartPayPartnerBonus(&m)
  1175 + if err != nil {
  1176 + orderGoodErrMap[cmd.Goods[i].LineNumber] = lib.ThrowError(lib.INTERNAL_SERVER_ERROR, fmt.Sprintf("核算订单中商品的分红数值失败:%s", err))
  1177 + continue
  1178 + }
  1179 +
  1180 + orderGoods = append(orderGoods, m)
  1181 + }
  1182 + if len(orderGoodErrMap) > 0 {
  1183 + row := &domain.ImportInfo{
  1184 + Error: lib.ThrowError(lib.BUSINESS_ERROR, "核算订单中商品错误"),
  1185 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1186 + GoodLine: orderGoodErrMap, // 错误产品行号记录
  1187 + }
  1188 + errorDataList = append(errorDataList, row)
  1189 + continue
  1190 + }
  1191 +
  1192 + newOrder.Goods = orderGoods
  1193 +
  1194 + err = newOrder.Compute()
  1195 + if err != nil {
  1196 + row := &domain.ImportInfo{
  1197 + Error: lib.ThrowError(lib.INTERNAL_SERVER_ERROR, fmt.Sprintf("核算订单中合计的数值失败:%s", err)),
  1198 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1199 + GoodLine: map[int]interface{}{},
  1200 + }
  1201 + errorDataList = append(errorDataList, row)
  1202 + continue
  1203 + }
  1204 +
  1205 + // 保存订单数据
  1206 + err = orderBaseRepository.Save(newOrder)
  1207 + if err != nil {
  1208 + row := &domain.ImportInfo{
  1209 + Error: lib.ThrowError(lib.INTERNAL_SERVER_ERROR, fmt.Sprintf("保存订单数据失败:%s", err)),
  1210 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1211 + GoodLine: map[int]interface{}{},
  1212 + }
  1213 + errorDataList = append(errorDataList, row)
  1214 + continue
  1215 + }
  1216 +
  1217 + for i := range newOrder.Goods {
  1218 + newOrder.Goods[i].OrderId = newOrder.Id
  1219 + }
  1220 +
  1221 + // 保存订单产品
  1222 + err = orderGoodRepository.Save(orderGoods)
  1223 + if err != nil {
  1224 + row := &domain.ImportInfo{
  1225 + Error: lib.ThrowError(lib.INTERNAL_SERVER_ERROR, fmt.Sprintf("保存订单中的商品数据失败:%s", err)),
  1226 + LineNumbers: cmd.LineNumbers, // 错误影响的行
  1227 + GoodLine: map[int]interface{}{},
  1228 + }
  1229 + errorDataList = append(errorDataList, row)
  1230 + continue
  1231 + }
  1232 + newOrder.Goods = orderGoods
  1233 + }
  1234 +
  1235 + if len(errorDataList) == 0 {
  1236 + // 完成事务
  1237 + err = transactionContext.CommitTransaction()
  1238 + if err != nil {
  1239 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1240 + }
  1241 + return errorDataList, nil
  1242 + }
  1243 +
  1244 + return errorDataList, nil
  1245 +}
  1246 +
  1247 +/**
  1248 + * @Author SteveChan
  1249 + * @Description // 根据合伙人编号和合伙人类型获取合伙人id
  1250 + * @Date 23:15 2021/1/6
  1251 + * @Param
  1252 + * @return
  1253 + **/
  1254 +func (service OrderInfoService) GetPartnerIdByCodeAndCategory(getPartnerIdQuery query.GetPartnerIdQuery) (*domain.PartnerInfo, error) {
  1255 + // 事务初始化
  1256 + var (
  1257 + transactionContext, _ = factory.CreateTransactionContext(nil)
  1258 + err error
  1259 + partnerData *domain.PartnerInfo
  1260 + )
  1261 +
  1262 + // 开始事务
  1263 + if err = transactionContext.StartTransaction(); err != nil {
  1264 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1265 + }
  1266 +
  1267 + // 收尾
  1268 + defer func() {
  1269 + transactionContext.RollbackTransaction()
  1270 + }()
  1271 +
  1272 + // 仓储、数据访问对象初始化
  1273 + var (
  1274 + PartnerInfoRepository domain.PartnerInfoRepository
  1275 + )
  1276 + // 合伙人信息仓储初始化
  1277 + if PartnerInfoRepository, err = factory.CreatePartnerInfoRepository(map[string]interface{}{
  1278 + "transactionContext": transactionContext,
  1279 + }); err != nil {
  1280 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1281 + }
  1282 +
  1283 + //var partnerData *domain.PartnerInfo
  1284 + partnerData, err = PartnerInfoRepository.FindOne(domain.PartnerFindOneQuery{
  1285 + CompanyId: getPartnerIdQuery.CompanyId,
  1286 + Code: getPartnerIdQuery.Code,
  1287 + PartnerCategory: getPartnerIdQuery.PartnerCategory,
  1288 + })
  1289 + if err != nil {
  1290 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, fmt.Sprintf("检索合伙人数据失败"))
  1291 + }
  1292 +
  1293 + // 完成事务
  1294 + err = transactionContext.CommitTransaction()
  1295 + if err != nil {
  1296 + return nil, lib.ThrowError(lib.INTERNAL_SERVER_ERROR, err.Error())
  1297 + }
  1298 + return partnerData, nil
  1299 +}
@@ -196,6 +196,7 @@ func (service SyncEmployeeService) addEmployeeData(datas []EmployeeData) error { @@ -196,6 +196,7 @@ func (service SyncEmployeeService) addEmployeeData(datas []EmployeeData) error {
196 Permission: []domain.AdminPermissionBase{}, //初始化权限 196 Permission: []domain.AdminPermissionBase{}, //初始化权限
197 AccessPartners: []domain.Partner{}, //默认初始化 197 AccessPartners: []domain.Partner{}, //默认初始化
198 AdminType: data.AdminType, 198 AdminType: data.AdminType,
  199 + IsSenior: 2,
199 } 200 }
200 newUser.EntryTime, _ = time.Parse("2006-01-02", data.EntryTime) 201 newUser.EntryTime, _ = time.Parse("2006-01-02", data.EntryTime)
201 if err = usersRepository.Add(&newUser); err != nil { 202 if err = usersRepository.Add(&newUser); err != nil {
1 package command 1 package command
2 2
  3 +import (
  4 + "errors"
  5 +
  6 + "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/domain"
  7 +)
  8 +
3 type EditUserPermissionCommand struct { 9 type EditUserPermissionCommand struct {
4 Id int64 `json:"id"` 10 Id int64 `json:"id"`
5 CompanyId int64 `json:"-"` 11 CompanyId int64 `json:"-"`
6 PermissionType []int64 `json:"permissionType"` //权限数据 12 PermissionType []int64 `json:"permissionType"` //权限数据
7 CheckedPartner []int64 `json:"checkedPartner"` //可查看合伙人列表合伙人 13 CheckedPartner []int64 `json:"checkedPartner"` //可查看合伙人列表合伙人
  14 + IsSenior int8 `json:"isSenior"`
  15 +}
  16 +
  17 +func (cmd EditUserPermissionCommand) Validate() error {
  18 + if cmd.IsSenior <= 0 {
  19 + return errors.New("是否是高管必填")
  20 + }
  21 + if !(cmd.IsSenior == domain.UserIsSeniorNo || cmd.IsSenior == domain.UserIsSeniorYes) {
  22 + return errors.New("是否是高管必填")
  23 + }
  24 + return nil
8 } 25 }
@@ -280,7 +280,13 @@ func (service UsersService) GetUserList(queryOption query.UserListQuery) (int, [ @@ -280,7 +280,13 @@ func (service UsersService) GetUserList(queryOption query.UserListQuery) (int, [
280 return cnt, result, nil 280 return cnt, result, nil
281 } 281 }
282 282
283 -//buildGetUserList 组装构建前端需要的用户列表数据 283 +/**
  284 + * @Author SteveChan
  285 + * @Description // 组装构建前端需要的用户列表数据
  286 + * @Date 00:22 2021/1/8
  287 + * @Param
  288 + * @return
  289 + **/
284 func (service UsersService) buildGetUserList(usersData []domain.Users, permissionData []domain.AdminPermission) []map[string]interface{} { 290 func (service UsersService) buildGetUserList(usersData []domain.Users, permissionData []domain.AdminPermission) []map[string]interface{} {
285 result := make([]map[string]interface{}, 0, len(usersData)) 291 result := make([]map[string]interface{}, 0, len(usersData))
286 permissionMap := map[int64]domain.AdminPermission{} 292 permissionMap := map[int64]domain.AdminPermission{}
@@ -313,9 +319,11 @@ func (service UsersService) buildGetUserList(usersData []domain.Users, permissio @@ -313,9 +319,11 @@ func (service UsersService) buildGetUserList(usersData []domain.Users, permissio
313 "permission": permissionTypes, 319 "permission": permissionTypes,
314 "isAdmin": 0, 320 "isAdmin": 0,
315 "partnership": len(usersData[i].AccessPartners), 321 "partnership": len(usersData[i].AccessPartners),
  322 + "isSenior": usersData[i].IsSenior,
316 } 323 }
317 if usersData[i].IsSuperAdmin() { 324 if usersData[i].IsSuperAdmin() {
318 m["isAdmin"] = 1 325 m["isAdmin"] = 1
  326 + m["name"] = m["name"].(string) + "(管理员)"
319 } 327 }
320 result = append(result, m) 328 result = append(result, m)
321 } 329 }
@@ -383,6 +391,7 @@ func (service UsersService) buildGetUserData(userData *domain.Users, partnerList @@ -383,6 +391,7 @@ func (service UsersService) buildGetUserData(userData *domain.Users, partnerList
383 "isAdmin": 0, 391 "isAdmin": 0,
384 "status": 0, 392 "status": 0,
385 "checkedPartner": []map[string]interface{}{}, 393 "checkedPartner": []map[string]interface{}{},
  394 + "isSenior": userData.IsSenior,
386 } 395 }
387 if userData.IsSuperAdmin() { 396 if userData.IsSuperAdmin() {
388 result["isAdmin"] = 1 397 result["isAdmin"] = 1
@@ -433,6 +442,9 @@ func (service UsersService) EditUserPermission(cmd command.EditUserPermissionCom @@ -433,6 +442,9 @@ func (service UsersService) EditUserPermission(cmd command.EditUserPermissionCom
433 transactionContext, _ = factory.CreateTransactionContext(nil) 442 transactionContext, _ = factory.CreateTransactionContext(nil)
434 err error 443 err error
435 ) 444 )
  445 + if err = cmd.Validate(); err != nil {
  446 + return lib.ThrowError(lib.BUSINESS_ERROR, err.Error())
  447 + }
436 if err = transactionContext.StartTransaction(); err != nil { 448 if err = transactionContext.StartTransaction(); err != nil {
437 return lib.ThrowError(lib.TRANSACTION_ERROR, err.Error()) 449 return lib.ThrowError(lib.TRANSACTION_ERROR, err.Error())
438 } 450 }
@@ -501,9 +513,6 @@ func (service UsersService) EditUserPermission(cmd command.EditUserPermissionCom @@ -501,9 +513,6 @@ func (service UsersService) EditUserPermission(cmd command.EditUserPermissionCom
501 partners = append(partners, p) 513 partners = append(partners, p)
502 } 514 }
503 for i := range permissionList { 515 for i := range permissionList {
504 - // if permissionList[i].Code == domain.PERMINSSION_ADMIN_USER && !usersData.IsSuperAdmin() {  
505 - // return lib.ThrowError(lib.BUSINESS_ERROR, "操作异常")  
506 - // }  
507 p := domain.AdminPermissionBase{ 516 p := domain.AdminPermissionBase{
508 Id: permissionList[i].Id, 517 Id: permissionList[i].Id,
509 Code: permissionList[i].Code, 518 Code: permissionList[i].Code,
@@ -512,6 +521,7 @@ func (service UsersService) EditUserPermission(cmd command.EditUserPermissionCom @@ -512,6 +521,7 @@ func (service UsersService) EditUserPermission(cmd command.EditUserPermissionCom
512 } 521 }
513 updateMap := map[string]interface{}{ 522 updateMap := map[string]interface{}{
514 "AccessPartners": partners, 523 "AccessPartners": partners,
  524 + "IsSenior": cmd.IsSenior,
515 } 525 }
516 if !usersData.IsSuperAdmin() { 526 if !usersData.IsSuperAdmin() {
517 updateMap["Permission"] = permissionsBase 527 updateMap["Permission"] = permissionsBase
@@ -6,7 +6,8 @@ const SERVICE_NAME = "partnermg" @@ -6,7 +6,8 @@ const SERVICE_NAME = "partnermg"
6 6
7 var LOG_LEVEL = "debug" 7 var LOG_LEVEL = "debug"
8 var LOG_File = "./logs/partnermg.log" 8 var LOG_File = "./logs/partnermg.log"
9 - 9 +var IMPORT_EXCEL = "./download/订单数据模板.xlsx"
  10 +var Log_PREFIX = "[partnermg_dev]"
10 var ( 11 var (
11 UCENTER_HOST = "https://suplus-ucenter-test.fjmaimaimai.com" //统一用户中心地址 12 UCENTER_HOST = "https://suplus-ucenter-test.fjmaimaimai.com" //统一用户中心地址
12 UCENTER_SECRET = "cykbjnfqgctn" 13 UCENTER_SECRET = "cykbjnfqgctn"
@@ -18,6 +19,8 @@ var ( @@ -18,6 +19,8 @@ var (
18 BUSINESS_ADMIN_HOST = "http://suplus-business-admin-test.fjmaimaimai.com" //企业平台的地址 19 BUSINESS_ADMIN_HOST = "http://suplus-business-admin-test.fjmaimaimai.com" //企业平台的地址
19 ) 20 )
20 21
  22 +var EXCEL_COLUMN = 12
  23 +
21 func init() { 24 func init() {
22 if os.Getenv("LOG_LEVEL") != "" { 25 if os.Getenv("LOG_LEVEL") != "" {
23 LOG_LEVEL = os.Getenv("LOG_LEVEL") 26 LOG_LEVEL = os.Getenv("LOG_LEVEL")
@@ -37,4 +40,7 @@ func init() { @@ -37,4 +40,7 @@ func init() {
37 if os.Getenv("BUSINESS_ADMIN_HOST") != "" { 40 if os.Getenv("BUSINESS_ADMIN_HOST") != "" {
38 BUSINESS_ADMIN_HOST = os.Getenv("BUSINESS_ADMIN_HOST") 41 BUSINESS_ADMIN_HOST = os.Getenv("BUSINESS_ADMIN_HOST")
39 } 42 }
  43 + if os.Getenv("Log_PREFIX") != "" {
  44 + Log_PREFIX = os.Getenv("Log_PREFIX")
  45 + }
40 } 46 }
@@ -15,7 +15,7 @@ var KafkaCfg KafkaConfig @@ -15,7 +15,7 @@ var KafkaCfg KafkaConfig
15 func init() { 15 func init() {
16 KafkaCfg = KafkaConfig{ 16 KafkaCfg = KafkaConfig{
17 Servers: []string{"127.0.0.1:9092"}, 17 Servers: []string{"127.0.0.1:9092"},
18 - ConsumerId: "partnermg_local", 18 + ConsumerId: "partnermg_dev",
19 } 19 }
20 if os.Getenv("KAFKA_HOST") != "" { 20 if os.Getenv("KAFKA_HOST") != "" {
21 kafkaHost := os.Getenv("KAFKA_HOST") 21 kafkaHost := os.Getenv("KAFKA_HOST")
@@ -35,7 +35,7 @@ type AdminUserFindOneQuery struct { @@ -35,7 +35,7 @@ type AdminUserFindOneQuery struct {
35 35
36 type AdminUserRepository interface { 36 type AdminUserRepository interface {
37 Save(AdminUser) (*AdminUser, error) 37 Save(AdminUser) (*AdminUser, error)
38 - FindOne(qureyOptions AdminUserFindOneQuery) (*AdminUser, error) 38 + FindOne(queryOptions AdminUserFindOneQuery) (*AdminUser, error)
39 Find(queryOptions AdminUserFindQuery) ([]AdminUser, error) 39 Find(queryOptions AdminUserFindQuery) ([]AdminUser, error)
40 CountAll(queryOption AdminUserFindQuery) (int, error) 40 CountAll(queryOption AdminUserFindQuery) (int, error)
41 } 41 }
@@ -314,6 +314,17 @@ type OrderBaseFindQuery struct { @@ -314,6 +314,17 @@ type OrderBaseFindQuery struct {
314 CompanyId int64 314 CompanyId int64
315 } 315 }
316 316
  317 +// 导入错误信息
  318 +type ImportInfo struct {
  319 + Error error
  320 + LineNumbers []int
  321 + GoodLine map[int]interface{}
  322 +}
  323 +
  324 +// 导入产品错误信息
  325 +type GoodErrInfo struct {
  326 +}
  327 +
317 type OrderBaseRepository interface { 328 type OrderBaseRepository interface {
318 Save(order *OrderBase) error 329 Save(order *OrderBase) error
319 FindOne(qureyOptions OrderBaseFindOneQuery) (*OrderBase, error) 330 FindOne(qureyOptions OrderBaseFindOneQuery) (*OrderBase, error)
@@ -84,6 +84,7 @@ type OrderGood struct { @@ -84,6 +84,7 @@ type OrderGood struct {
84 CompanyId int64 `json:"companyId"` 84 CompanyId int64 `json:"companyId"`
85 //原因备注 85 //原因备注
86 RemarkReason OrderGoodRemarkReason `json:"remarkReason"` 86 RemarkReason OrderGoodRemarkReason `json:"remarkReason"`
  87 + //数据来源
87 DataFrom OrderDataFrom `json:"data_from"` 88 DataFrom OrderDataFrom `json:"data_from"`
88 } 89 }
89 90
@@ -64,6 +64,8 @@ type PartnerFindOneQuery struct { @@ -64,6 +64,8 @@ type PartnerFindOneQuery struct {
64 UserId int64 64 UserId int64
65 AccountEqual string 65 AccountEqual string
66 CompanyId int64 66 CompanyId int64
  67 + Code string // 合伙人编码
  68 + PartnerCategory int // 合伙人类型
67 } 69 }
68 70
69 type PartnerFindQuery struct { 71 type PartnerFindQuery struct {
@@ -2,24 +2,30 @@ package domain @@ -2,24 +2,30 @@ package domain
2 2
3 import "time" 3 import "time"
4 4
5 -//用户是否可用状态:【1:正常】【 2:禁用】 5 +//Users.Status用户是否可用状态:【1:正常】【 2:禁用】
6 const ( 6 const (
7 userStatusUsable int8 = 1 7 userStatusUsable int8 = 1
8 userStatusUnusable int8 = 2 8 userStatusUnusable int8 = 2
9 ) 9 )
10 10
11 -//用户是否是主管 :【1:是主管】【 2:不是主管】 11 +//Users.ChargeStatus用户是否是主管 :【1:是主管】【 2:不是主管】
12 const ( 12 const (
13 UserIsCompanyCharge int8 = 1 13 UserIsCompanyCharge int8 = 1
14 UserIsNotCompanyCharge int8 = 2 14 UserIsNotCompanyCharge int8 = 2
15 ) 15 )
16 16
17 -//用户类型 1普通用户 2主管理员 17 +//Users.AdminType 用户类型 1普通用户 2主管理员
18 const ( 18 const (
19 UserIsNotAdmin int8 = 1 19 UserIsNotAdmin int8 = 1
20 UserIsAdmin int8 = 2 20 UserIsAdmin int8 = 2
21 ) 21 )
22 22
  23 +//Users.IsSenior 用户是否是公司高管【1:是】【2:否】
  24 +const (
  25 + UserIsSeniorYes int8 = 1
  26 + UserIsSeniorNo int8 = 2
  27 +)
  28 +
23 //Users 企业平台的用户 29 //Users 企业平台的用户
24 type Users struct { 30 type Users struct {
25 Id int64 //用户id 31 Id int64 //用户id
@@ -38,11 +44,12 @@ type Users struct { @@ -38,11 +44,12 @@ type Users struct {
38 Avatar string ///头像 44 Avatar string ///头像
39 Remarks string //备注 45 Remarks string //备注
40 ChargeStatus int8 //是否为当前公司主管 【1:是】【2:否】 46 ChargeStatus int8 //是否为当前公司主管 【1:是】【2:否】
41 - CreateAt time.Time  
42 - UpdateAt time.Time 47 + CreateAt time.Time //
  48 + UpdateAt time.Time //
43 Permission []AdminPermissionBase //权限 49 Permission []AdminPermissionBase //权限
44 - AccessPartners []Partner 50 + AccessPartners []Partner //
45 AdminType int8 //是否是公司负责人,即超级管理员 1普通用户 2主管理员 51 AdminType int8 //是否是公司负责人,即超级管理员 1普通用户 2主管理员
  52 + IsSenior int8 //是否是公司高管【1:是】【2:否】;用于确定是否可以拥有“可查看的合伙人”
46 } 53 }
47 54
48 //IsUsable 用户是否可用 55 //IsUsable 用户是否可用
@@ -71,6 +78,17 @@ func (u Users) HasPermissionByCode(code string) bool { @@ -71,6 +78,17 @@ func (u Users) HasPermissionByCode(code string) bool {
71 return false 78 return false
72 } 79 }
73 80
  81 +func (u *Users) SetIsSenior(senior int8) {
  82 + switch senior {
  83 + case UserIsSeniorYes:
  84 + u.IsSenior = senior
  85 + case UserIsSeniorNo:
  86 + u.IsSenior = senior
  87 + u.AccessPartners = make([]Partner, 0)
  88 + default:
  89 + }
  90 +}
  91 +
74 func (u *Users) Update(m map[string]interface{}) error { 92 func (u *Users) Update(m map[string]interface{}) error {
75 if v, ok := m["CompanyId"]; ok { 93 if v, ok := m["CompanyId"]; ok {
76 u.CompanyId = v.(int64) 94 u.CompanyId = v.(int64)
@@ -126,6 +144,10 @@ func (u *Users) Update(m map[string]interface{}) error { @@ -126,6 +144,10 @@ func (u *Users) Update(m map[string]interface{}) error {
126 if v, ok := m["AdminType"]; ok { 144 if v, ok := m["AdminType"]; ok {
127 u.AdminType = v.(int8) 145 u.AdminType = v.(int8)
128 } 146 }
  147 + if v, ok := m["IsSenior"]; ok {
  148 + senior := v.(int8)
  149 + u.SetIsSenior(senior)
  150 + }
129 return nil 151 return nil
130 } 152 }
131 153
@@ -23,26 +23,24 @@ func NewOrderBaseDao(transactionContext *transaction.TransactionContext) (*Order @@ -23,26 +23,24 @@ func NewOrderBaseDao(transactionContext *transaction.TransactionContext) (*Order
23 } 23 }
24 } 24 }
25 25
26 -//OrderCodeExist 检查order_code是否重复  
27 -//  
28 -func (dao OrderBaseDao) OrderCodeExist(code string, partnerCategory int64, partnerId int64) (bool, error) { 26 +//CheckOrderUnique 检查订单的是否已存在
  27 +//@companyId 公司id
  28 +//@orderCode 订单号
  29 +//@deliveryCode 发货单号
  30 +//@partnerCategoryCode 合伙人类型编号
  31 +//@goodNames 货品名称列表
  32 +func (dao OrderBaseDao) CheckOrderExist(companyId int64, orderCode string,
  33 + deliveryCode string, partnerCategory int64, partnerId int64, notId int64, goodNames []string) (bool, error) {
29 tx := dao.transactionContext.GetDB() 34 tx := dao.transactionContext.GetDB()
30 - m := &models.OrderBase{}  
31 - query := tx.Model(m).  
32 - Where("order_code=?", code).  
33 - Where("partner_id=?", partnerId).  
34 - Where(`partner_category @>'{"id":?}'`, partnerCategory)  
35 - ok, err := query.Exists()  
36 - return ok, err  
37 -}  
38 -  
39 -func (dao OrderBaseDao) DeliveryCodeExist(code string, companyId int64, notId ...int64) (bool, error) {  
40 - tx := dao.transactionContext.GetDB()  
41 - m := &models.OrderBase{}  
42 - query := tx.Model(m).Where("delivery_code=?", code).Where("company_id=?", companyId)  
43 - if len(notId) > 0 {  
44 - query = query.WhereIn("id not in(?)", notId)  
45 - } 35 + query := tx.Model(&models.OrderBase{}).
  36 + Join("JOIN order_good ON order_base.id=order_good.order_id").
  37 + Where("order_base.company_id=?", companyId).
  38 + Where("order_base.order_code=?", orderCode).
  39 + Where("order_base.delivery_code=?", deliveryCode).
  40 + Where("order_base.partner_id=?", partnerId).
  41 + Where(`order_base.partner_category @>'{"id":?}'`, partnerCategory).
  42 + Where("order_base.id<>?", notId).
  43 + WhereIn("order_good.good_name in(?)", goodNames)
46 ok, err := query.Exists() 44 ok, err := query.Exists()
47 return ok, err 45 return ok, err
48 } 46 }
@@ -192,7 +190,7 @@ func (dao OrderBaseDao) OrderBonusListForExcel(companyId int64, orderType int, p @@ -192,7 +190,7 @@ func (dao OrderBaseDao) OrderBonusListForExcel(companyId int64, orderType int, p
192 //@param partnerCategory 合伙人类型id 190 //@param partnerCategory 合伙人类型id
193 //@param updateTime 订单更新时间范围"[开始时间,结束时间]",时间格式"2006-01-02 15:04:05+07" 191 //@param updateTime 订单更新时间范围"[开始时间,结束时间]",时间格式"2006-01-02 15:04:05+07"
194 //@param createTime 订单的创建时间范围"[开始时间,结束时间]" 时间格式"2006-01-02 15:04:05+07" 192 //@param createTime 订单的创建时间范围"[开始时间,结束时间]" 时间格式"2006-01-02 15:04:05+07"
195 -func (dao OrderBaseDao) OrderListByCondition(companyId int64, orderType int, partnerOrCode string, 193 +func (dao OrderBaseDao) OrderListByCondition(companyId int64, orderType int, partnerName string, orderCode string, deliveryCode string,
196 updateTime [2]string, createTime [2]string, partnerCategory int, limit, offset int) ([]models.OrderBase, int, error) { 194 updateTime [2]string, createTime [2]string, partnerCategory int, limit, offset int) ([]models.OrderBase, int, error) {
197 tx := dao.transactionContext.GetDB() 195 tx := dao.transactionContext.GetDB()
198 var orders []models.OrderBase 196 var orders []models.OrderBase
@@ -217,16 +215,25 @@ func (dao OrderBaseDao) OrderListByCondition(companyId int64, orderType int, par @@ -217,16 +215,25 @@ func (dao OrderBaseDao) OrderListByCondition(companyId int64, orderType int, par
217 if len(createTime[1]) > 0 { 215 if len(createTime[1]) > 0 {
218 query = query.Where(`order_base.create_time<=?`, createTime[1]) 216 query = query.Where(`order_base.create_time<=?`, createTime[1])
219 } 217 }
220 -  
221 - if len(partnerOrCode) > 0 { 218 + if len(partnerName) > 0 {
222 query = query.Join("LEFT JOIN partner_info as p ON order_base.partner_id=p.id"). 219 query = query.Join("LEFT JOIN partner_info as p ON order_base.partner_id=p.id").
223 - WhereGroup(func(q *orm.Query) (*orm.Query, error) {  
224 - q = q.WhereOr("order_base.order_code like ? ", "%"+partnerOrCode+"%").  
225 - WhereOr("order_base.delivery_code like ? ", "%"+partnerOrCode+"%").  
226 - WhereOr("p.partner_name like ? ", "%"+partnerOrCode+"%")  
227 - return q, nil  
228 - }) 220 + Where("p.partner_name like ? ", "%"+partnerName+"%")
  221 + }
  222 + if len(orderCode) > 0 {
  223 + query = query.Where("order_base.order_code like ? ", "%"+orderCode+"%")
229 } 224 }
  225 + if len(deliveryCode) > 0 {
  226 + query = query.Where("order_base.delivery_code like ? ", "%"+deliveryCode+"%")
  227 + }
  228 + //if len(partnerOrCode) > 0 {
  229 + // query = query.Join("LEFT JOIN partner_info as p ON order_base.partner_id=p.id").
  230 + // WhereGroup(func(q *orm.Query) (*orm.Query, error) {
  231 + // q = q.WhereOr("order_base.order_code like ? ", "%"+partnerOrCode+"%").
  232 + // WhereOr("order_base.delivery_code like ? ", "%"+partnerOrCode+"%").
  233 + // WhereOr("p.partner_name like ? ", "%"+partnerOrCode+"%")
  234 + // return q, nil
  235 + // })
  236 + //}
230 query = query.Order("order_base.create_time DESC"). 237 query = query.Order("order_base.create_time DESC").
231 Offset(offset). 238 Offset(offset).
232 Limit(limit) 239 Limit(limit)
@@ -259,7 +266,7 @@ type CustomOrderListForExcel struct { @@ -259,7 +266,7 @@ type CustomOrderListForExcel struct {
259 //@param partnerCategory 合伙人类型id 266 //@param partnerCategory 合伙人类型id
260 //@param updateTime 订单更新时间范围"[开始时间,结束时间]",时间格式"2006-01-02 15:04:05+07" 267 //@param updateTime 订单更新时间范围"[开始时间,结束时间]",时间格式"2006-01-02 15:04:05+07"
261 //@param createTime 订单的创建时间范围"[开始时间,结束时间]" 时间格式"2006-01-02 15:04:05+07" 268 //@param createTime 订单的创建时间范围"[开始时间,结束时间]" 时间格式"2006-01-02 15:04:05+07"
262 -func (dao OrderBaseDao) OrderListForExcel(companyId int64, partnerOrCode string, 269 +func (dao OrderBaseDao) OrderListForExcel(companyId int64, partnerName string, orderCode string, deliveryCode string,
263 updateTime [2]string, createTime [2]string, partnerCategory int) ( 270 updateTime [2]string, createTime [2]string, partnerCategory int) (
264 result []CustomOrderListForExcel, err error) { 271 result []CustomOrderListForExcel, err error) {
265 sqlstr := ` 272 sqlstr := `
@@ -275,12 +282,26 @@ func (dao OrderBaseDao) OrderListForExcel(companyId int64, partnerOrCode string, @@ -275,12 +282,26 @@ func (dao OrderBaseDao) OrderListForExcel(companyId int64, partnerOrCode string,
275 WHERE 1=1 AND t1.order_type = 1 AND t1.company_id=? 282 WHERE 1=1 AND t1.order_type = 1 AND t1.company_id=?
276 ` 283 `
277 params := []interface{}{companyId} 284 params := []interface{}{companyId}
278 - if len(partnerOrCode) > 0 {  
279 - like := "%" + partnerOrCode + "%"  
280 - params = append(params, like, like, like)  
281 - sqlstr += " AND (t1.order_code like ? OR t1.delivery_code like ? OR t2.partner_name like ? ) " 285 + //if len(partnerOrCode) > 0 {
  286 + // like := "%" + partnerOrCode + "%"
  287 + // params = append(params, like, like, like)
  288 + // sqlstr += " AND (t1.order_code like ? OR t1.delivery_code like ? OR t2.partner_name like ? ) "
  289 + //}
  290 + if len(partnerName) > 0 {
  291 + like := "%" + partnerName + "%"
  292 + params = append(params, like)
  293 + sqlstr += ` AND t2.partner_name like ? `
  294 + }
  295 + if len(orderCode) > 0 {
  296 + like := "%" + orderCode + "%"
  297 + params = append(params, like)
  298 + sqlstr += ` AND t1.order_code like ? `
  299 + }
  300 + if len(deliveryCode) > 0 {
  301 + like := "%" + deliveryCode + "%"
  302 + params = append(params, like)
  303 + sqlstr += ` AND t1.delivery_code like ? `
282 } 304 }
283 -  
284 if partnerCategory > 0 { 305 if partnerCategory > 0 {
285 params = append(params, partnerCategory) 306 params = append(params, partnerCategory)
286 sqlstr += ` AND t1.partner_category@>'{"id":?}' ` 307 sqlstr += ` AND t1.partner_category@>'{"id":?}' `
@@ -43,6 +43,6 @@ type OrderGood struct { @@ -43,6 +43,6 @@ type OrderGood struct {
43 CompanyId int64 43 CompanyId int64
44 //原因备注 44 //原因备注
45 RemarkReason domain.OrderGoodRemarkReason `` 45 RemarkReason domain.OrderGoodRemarkReason ``
46 - 46 + //数据来源
47 DataFrom domain.OrderDataFrom `` 47 DataFrom domain.OrderDataFrom ``
48 } 48 }
@@ -29,6 +29,7 @@ type Users struct { @@ -29,6 +29,7 @@ type Users struct {
29 ChargeStatus int8 //是否为当前公司主管 【1:是】【2:否】 29 ChargeStatus int8 //是否为当前公司主管 【1:是】【2:否】
30 Permission []domain.AdminPermissionBase //权限 30 Permission []domain.AdminPermissionBase //权限
31 AccessPartners []domain.Partner //可查看的合伙人 31 AccessPartners []domain.Partner //可查看的合伙人
  32 + IsSenior int8 //是否是公司高管【1:是】【2:否】;用于确定是否可以拥有“可查看的合伙人”
32 CreateAt time.Time 33 CreateAt time.Time
33 UpdateAt time.Time 34 UpdateAt time.Time
34 DeleteAt time.Time 35 DeleteAt time.Time
@@ -3,6 +3,7 @@ package repository @@ -3,6 +3,7 @@ package repository
3 import ( 3 import (
4 "errors" 4 "errors"
5 "fmt" 5 "fmt"
  6 + "github.com/go-pg/pg/v10"
6 7
7 "github.com/go-pg/pg/v10/orm" 8 "github.com/go-pg/pg/v10/orm"
8 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/domain" 9 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/domain"
@@ -47,7 +48,7 @@ func (repository *PartnerInfoRepository) Save(dm *domain.PartnerInfo) error { @@ -47,7 +48,7 @@ func (repository *PartnerInfoRepository) Save(dm *domain.PartnerInfo) error {
47 Remark: dm.Remark, 48 Remark: dm.Remark,
48 } 49 }
49 if m.Id == 0 { 50 if m.Id == 0 {
50 - err = tx.Insert(m) 51 + _, err = tx.Model(m).Insert()
51 dm.Partner.Id = m.Id 52 dm.Partner.Id = m.Id
52 if err != nil { 53 if err != nil {
53 return err 54 return err
@@ -81,6 +82,10 @@ func (repository *PartnerInfoRepository) FindOne(queryOptions domain.PartnerFind @@ -81,6 +82,10 @@ func (repository *PartnerInfoRepository) FindOne(queryOptions domain.PartnerFind
81 hasCondition = true 82 hasCondition = true
82 query = query.Where("company_id=?", queryOptions.CompanyId) 83 query = query.Where("company_id=?", queryOptions.CompanyId)
83 } 84 }
  85 + if queryOptions.PartnerCategory > 0 && queryOptions.Code != "" { // 合伙人类型和编码判断
  86 + hasCondition = true
  87 + query = query.Where(`partner_category_infos@> '[{"id":?,"code":?}]'`, queryOptions.PartnerCategory, pg.Ident(queryOptions.Code))
  88 + }
84 if !hasCondition { 89 if !hasCondition {
85 return nil, errors.New("FindOne 必须要有查询条件") 90 return nil, errors.New("FindOne 必须要有查询条件")
86 } 91 }
@@ -48,6 +48,7 @@ func (repository UsersRepository) transformPgModelToDomainModel(m *models.Users) @@ -48,6 +48,7 @@ func (repository UsersRepository) transformPgModelToDomainModel(m *models.Users)
48 Permission: m.Permission, 48 Permission: m.Permission,
49 AccessPartners: m.AccessPartners, 49 AccessPartners: m.AccessPartners,
50 AdminType: m.AdminType, 50 AdminType: m.AdminType,
  51 + IsSenior: m.IsSenior,
51 }, nil 52 }, nil
52 } 53 }
53 54
@@ -78,6 +79,7 @@ func (reponsitory UsersRepository) Add(u *domain.Users) error { @@ -78,6 +79,7 @@ func (reponsitory UsersRepository) Add(u *domain.Users) error {
78 Permission: u.Permission, 79 Permission: u.Permission,
79 AccessPartners: u.AccessPartners, 80 AccessPartners: u.AccessPartners,
80 AdminType: u.AdminType, 81 AdminType: u.AdminType,
  82 + IsSenior: u.IsSenior,
81 } 83 }
82 _, err = tx.Model(m).Insert() 84 _, err = tx.Model(m).Insert()
83 return err 85 return err
@@ -110,6 +112,7 @@ func (reponsitory UsersRepository) Edit(u *domain.Users) error { @@ -110,6 +112,7 @@ func (reponsitory UsersRepository) Edit(u *domain.Users) error {
110 Permission: u.Permission, 112 Permission: u.Permission,
111 AccessPartners: u.AccessPartners, 113 AccessPartners: u.AccessPartners,
112 AdminType: u.AdminType, 114 AdminType: u.AdminType,
  115 + IsSenior: u.IsSenior,
113 } 116 }
114 _, err = tx.Model(m).WherePK().Update() 117 _, err = tx.Model(m).WherePK().Update()
115 return err 118 return err
@@ -180,7 +183,8 @@ func (reponsitory UsersRepository) Find(queryOption domain.UsersFindQuery) (int, @@ -180,7 +183,8 @@ func (reponsitory UsersRepository) Find(queryOption domain.UsersFindQuery) (int,
180 usersReturn = make([]domain.Users, 0) 183 usersReturn = make([]domain.Users, 0)
181 cnt int 184 cnt int
182 ) 185 )
183 - query = query.Order("id DESC") 186 + //query = query.Order("id DESC")
  187 + query = query.Order("admin_type DESC")
184 cnt, err = query.SelectAndCount() 188 cnt, err = query.SelectAndCount()
185 if err != nil { 189 if err != nil {
186 return 0, usersReturn, err 190 return 0, usersReturn, err
@@ -31,3 +31,19 @@ func GenerateRangeNum(min, max int) int { @@ -31,3 +31,19 @@ func GenerateRangeNum(min, max int) int {
31 randNum := rand.Intn(max-min) + min 31 randNum := rand.Intn(max-min) + min
32 return randNum 32 return randNum
33 } 33 }
  34 +
  35 +/**
  36 + * @Author SteveChan
  37 + * @Description // 判断数组是否包含
  38 + * @Date 14:30 2021/1/6
  39 + * @Param
  40 + * @return
  41 + **/
  42 +func IsContain(items []string, item string) bool {
  43 + for _, eachItem := range items {
  44 + if eachItem == item {
  45 + return true
  46 + }
  47 + }
  48 + return false
  49 +}
  1 +package exceltool
  2 +
  3 +import (
  4 + "io"
  5 +
  6 + excelize "github.com/360EntSecGroup-Skylar/excelize/v2"
  7 +)
  8 +
  9 +// ExcelListReader 读取基础excel表格,
  10 +// 指定读取的列表区域的第一行作为表头字段处理,表头字段唯一
  11 +type ExcelListReader struct {
  12 + RowStart int //从第几行开始,零值做为起始
  13 + RowEnd func(index int, rowsData []string) bool //第几行结束,
  14 + ColStart int //第几列开始,零值做为起始
  15 + ColEnd int //第几列结束,
  16 + Sheet string //获取的表格
  17 +}
  18 +
  19 +func NewExcelListReader() *ExcelListReader {
  20 + rowEnd := func(index int, rowsData []string) bool {
  21 + var allEmpty bool = true
  22 + for _, v := range rowsData {
  23 + if allEmpty && len(v) > 0 {
  24 + allEmpty = false
  25 + break
  26 + }
  27 + }
  28 + return allEmpty
  29 + }
  30 + return &ExcelListReader{
  31 + RowEnd: rowEnd,
  32 + }
  33 +}
  34 +
  35 +func (eRead ExcelListReader) OpenReader(r io.Reader) ([]map[string]string, error) {
  36 + xlsxFile, err := excelize.OpenReader(r)
  37 + if err != nil {
  38 + return nil, err
  39 + }
  40 + rows, err := xlsxFile.Rows(eRead.Sheet)
  41 + if err != nil {
  42 + return nil, err
  43 + }
  44 + var (
  45 + datas = make([]map[string]string, 0) //数据列表
  46 + listHead = make(map[int]string) //map[索引数字]列表头字符串
  47 + rowIndex int = 0
  48 + )
  49 + for rows.Next() {
  50 + cols, err := rows.Columns()
  51 + if err != nil {
  52 + return nil, err
  53 + }
  54 + if readEnd := eRead.RowEnd(rowIndex, cols); readEnd {
  55 + break
  56 + }
  57 + if rowIndex < eRead.RowStart {
  58 + rowIndex++
  59 + continue
  60 + }
  61 + listRowData := make(map[string]string)
  62 + for colK, colV := range cols {
  63 + if eRead.ColEnd != 0 && colK > eRead.ColEnd {
  64 + break
  65 + }
  66 + if colK < eRead.ColStart {
  67 + continue
  68 + }
  69 + if rowIndex == eRead.RowStart {
  70 + //指定的数据列表第一行作为列表头处理
  71 + listHead[colK] = colV
  72 + }
  73 + if rowIndex > eRead.RowStart {
  74 + //指定的数据列表第二行开始作为列表数据内容处理
  75 + headK := listHead[colK]
  76 + listRowData[headK] = colV
  77 + }
  78 + }
  79 + if rowIndex > eRead.RowStart {
  80 + //指定的数据列表第二行开始作为列表数据内容处理
  81 + datas = append(datas, listRowData)
  82 + }
  83 + rowIndex++
  84 + }
  85 + return datas, nil
  86 +}
@@ -8,10 +8,10 @@ import ( @@ -8,10 +8,10 @@ import (
8 ) 8 )
9 9
10 func init() { 10 func init() {
11 -  
12 logs.SetLevel(logLevel(constant.LOG_LEVEL)) 11 logs.SetLevel(logLevel(constant.LOG_LEVEL))
13 logs.SetLogFuncCall(false) 12 logs.SetLogFuncCall(false)
14 logs.SetLogger("file", getlogFileConfig()) 13 logs.SetLogger("file", getlogFileConfig())
  14 + logs.SetPrefix(constant.Log_PREFIX)
15 logs.Async() 15 logs.Async()
16 logs.Async(2 * 1e3) 16 logs.Async(2 * 1e3)
17 } 17 }
1 package controllers 1 package controllers
2 2
3 import ( 3 import (
  4 + "crypto/md5"
  5 + "encoding/hex"
4 "errors" 6 "errors"
5 "fmt" 7 "fmt"
  8 + "github.com/beego/beego/v2/client/httplib"
  9 + "os"
  10 + "path"
6 "regexp" 11 "regexp"
7 "strconv" 12 "strconv"
8 "strings" 13 "strings"
9 "time" 14 "time"
10 "unicode/utf8" 15 "unicode/utf8"
11 16
  17 + "github.com/360EntSecGroup-Skylar/excelize/v2"
  18 + "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/constant"
  19 +
12 "github.com/astaxie/beego/logs" 20 "github.com/astaxie/beego/logs"
13 orderCmd "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/application/orderinfo/command" 21 orderCmd "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/application/orderinfo/command"
14 orderQuery "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/application/orderinfo/query" 22 orderQuery "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/application/orderinfo/query"
15 orderService "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/application/orderinfo/service" 23 orderService "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/application/orderinfo/service"
16 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/domain" 24 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/domain"
  25 + "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/infrastructure/utils"
17 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/lib" 26 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/lib"
18 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/lib/exceltool" 27 "gitlab.fjmaimaimai.com/mmm-go/partnermg/pkg/lib/exceltool"
19 ) 28 )
@@ -62,6 +71,7 @@ func (postData *postPurposeOrderDetail) Valid() error { @@ -62,6 +71,7 @@ func (postData *postPurposeOrderDetail) Valid() error {
62 } 71 }
63 if postData.PartnerId == 0 { 72 if postData.PartnerId == 0 {
64 return lib.ThrowError(lib.ARG_ERROR, "合伙人信息必填") 73 return lib.ThrowError(lib.ARG_ERROR, "合伙人信息必填")
  74 +
65 } 75 }
66 if len(postData.OrderDist) == 0 { 76 if len(postData.OrderDist) == 0 {
67 return lib.ThrowError(lib.ARG_ERROR, "订单区域必填") 77 return lib.ThrowError(lib.ARG_ERROR, "订单区域必填")
@@ -141,10 +151,19 @@ func (postData *postOrderPurposeDelivery) Valid() error { @@ -141,10 +151,19 @@ func (postData *postOrderPurposeDelivery) Valid() error {
141 return nil 151 return nil
142 } 152 }
143 153
144 -//PageListOrderReal 获取实发订单列表 154 +/**
  155 + * @Author SteveChan
  156 + * @Description // 获取实发订单列表,修改搜索条件
  157 + * @Date 20:23 2021/1/10
  158 + * @Param
  159 + * @return
  160 + **/
145 func (c *OrderInfoController) PageListOrderReal() { 161 func (c *OrderInfoController) PageListOrderReal() {
146 type Parameter struct { 162 type Parameter struct {
147 - SearchText string `json:"searchText"` 163 + //SearchText string `json:"searchText"`
  164 + PartnerName string `json:"partnerName"` // 合伙人姓名
  165 + OrderCode string `json:"orderCode"` // 订单号
  166 + DeliveryCode string `json:"deliveryCode"` // 发货单号
148 PartnerCategory int `json:"PartnerCategory"` 167 PartnerCategory int `json:"PartnerCategory"`
149 PageSize int `json:"pageSize"` 168 PageSize int `json:"pageSize"`
150 PageNumber int `json:"pageNumber"` 169 PageNumber int `json:"pageNumber"`
@@ -222,7 +241,10 @@ func (c *OrderInfoController) PageListOrderReal() { @@ -222,7 +241,10 @@ func (c *OrderInfoController) PageListOrderReal() {
222 companyId := c.GetUserCompany() 241 companyId := c.GetUserCompany()
223 orderSrv := orderService.NewOrderInfoService(nil) 242 orderSrv := orderService.NewOrderInfoService(nil)
224 orderinfos, cnt, err := orderSrv.PageListOrderBase(orderQuery.ListOrderBaseQuery{ 243 orderinfos, cnt, err := orderSrv.PageListOrderBase(orderQuery.ListOrderBaseQuery{
225 - PartnerOrCode: param.SearchText, 244 + //PartnerOrCode: param.SearchText,
  245 + PartnerName: param.PartnerName,
  246 + OrderCode: param.OrderCode,
  247 + DeliveryCode: param.DeliveryCode,
226 OrderType: domain.OrderReal, 248 OrderType: domain.OrderReal,
227 Limit: param.PageSize, 249 Limit: param.PageSize,
228 Offset: (param.PageNumber - 1) * param.PageSize, 250 Offset: (param.PageNumber - 1) * param.PageSize,
@@ -506,7 +528,10 @@ func (c *OrderInfoController) RemoveOrderReal() { @@ -506,7 +528,10 @@ func (c *OrderInfoController) RemoveOrderReal() {
506 //ListOrderForExcel excel 导出实际订单的列表 528 //ListOrderForExcel excel 导出实际订单的列表
507 func (c *OrderInfoController) ListOrderForExcel() { 529 func (c *OrderInfoController) ListOrderForExcel() {
508 type Parameter struct { 530 type Parameter struct {
509 - SearchText string `json:"searchText"` 531 + //SearchText string `json:"searchText"`
  532 + PartnerName string `json:"partnerName"` // 合伙人姓名
  533 + OrderCode string `json:"orderCode"` // 订单号
  534 + DeliveryCode string `json:"deliveryCode"` // 发货单号
510 PartnerCategory int `json:"PartnerCategory"` 535 PartnerCategory int `json:"PartnerCategory"`
511 UpdateTime []string `json:"updateTime"` 536 UpdateTime []string `json:"updateTime"`
512 CreateTime []string `json:"createTime"` 537 CreateTime []string `json:"createTime"`
@@ -576,7 +601,10 @@ func (c *OrderInfoController) ListOrderForExcel() { @@ -576,7 +601,10 @@ func (c *OrderInfoController) ListOrderForExcel() {
576 companyId := c.GetUserCompany() 601 companyId := c.GetUserCompany()
577 orderSrv := orderService.NewOrderInfoService(nil) 602 orderSrv := orderService.NewOrderInfoService(nil)
578 orderinfos, columns, err := orderSrv.ListOrderForExcel(orderQuery.ListOrderBaseQuery{ 603 orderinfos, columns, err := orderSrv.ListOrderForExcel(orderQuery.ListOrderBaseQuery{
579 - PartnerOrCode: param.SearchText, 604 + //PartnerOrCode: param.SearchText,
  605 + PartnerName: param.PartnerName,
  606 + OrderCode: param.OrderCode,
  607 + DeliveryCode: param.DeliveryCode,
580 OrderType: domain.OrderReal, 608 OrderType: domain.OrderReal,
581 CompanyId: companyId, 609 CompanyId: companyId,
582 PartnerCategory: param.PartnerCategory, 610 PartnerCategory: param.PartnerCategory,
@@ -603,3 +631,576 @@ func (c *OrderInfoController) ListOrderForExcel() { @@ -603,3 +631,576 @@ func (c *OrderInfoController) ListOrderForExcel() {
603 c.ResponseExcelByFile(c.Ctx, excelMaker) 631 c.ResponseExcelByFile(c.Ctx, excelMaker)
604 return 632 return
605 } 633 }
  634 +
  635 +/**
  636 + * @Author SteveChan
  637 + * @Description // 下载导入模板
  638 + * @Date 16:48 2021/1/8
  639 + * @Param
  640 + * @return
  641 + **/
  642 +func (c *OrderInfoController) DownloadTemplate() {
  643 + type Parameter struct {
  644 + TYPE string `json:"type"`
  645 + }
  646 +
  647 + var (
  648 + param Parameter
  649 + err error
  650 + )
  651 +
  652 + if err = c.BindJsonData(&param); err != nil {
  653 + logs.Error(err)
  654 + c.ResponseError(errors.New("json数据解析失败"))
  655 + return
  656 + }
  657 +
  658 + // 校验类型编码
  659 + if param.TYPE != "PARTNER_ORDER_FILE" {
  660 + c.ResponseError(errors.New("类型编码错误"))
  661 + }
  662 +
  663 + // 创建下载文件夹
  664 + mkErr := os.Mkdir("download", os.ModePerm)
  665 + if mkErr != nil {
  666 + fmt.Println(mkErr)
  667 + }
  668 +
  669 + // 获取导入模板
  670 + req := httplib.Get("http://suplus-file-dev.fjmaimaimai.com/upload/file/2021010803305336443.xlsx")
  671 + err = req.ToFile(constant.IMPORT_EXCEL)
  672 + if err != nil {
  673 + logs.Error("could not save to file: ", err)
  674 + }
  675 +
  676 + // 返回字段定义
  677 + ret := map[string]interface{}{}
  678 +
  679 + resp, err := req.Response()
  680 + if err != nil {
  681 + logs.Error("could not get response: ", err)
  682 + } else {
  683 + logs.Info(resp)
  684 + ret = map[string]interface{}{
  685 + "url": "http://" + c.Ctx.Request.Host + "/partnermg/download/订单数据模板.xlsx",
  686 + }
  687 + c.ResponseData(ret)
  688 + }
  689 +}
  690 +
  691 +/**
  692 + * @Author SteveChan
  693 + * @Description // 导入excel订单
  694 + * @Date 10:52 2021/1/6
  695 + * @Param
  696 + * @return
  697 + **/
  698 +func (c *OrderInfoController) ImportOrderFromExcel() {
  699 + // 获取参数
  700 + typeCode := c.GetString("type")
  701 + file, h, _ := c.GetFile("file")
  702 + companyId := c.GetUserCompany()
  703 +
  704 + if typeCode != "PARTNER_ORDER_FILE" {
  705 + c.ResponseError(errors.New("类型编码错误"))
  706 + }
  707 +
  708 + // 返回字段定义
  709 + ret := map[string]interface{}{}
  710 +
  711 + // 返回信息表头定义 0: 订单号, 1: 发货单号, 2: 客户名称, 3: 订单区域, 4: 编号, 5: 合伙人, 6: 类型, 7: 业务员抽成比例, 8: 产品名称, 9: 数量, 10: 单价, 11: 合伙人分红比例
  712 + var tableHeader = []string{"错误详情", "行号", "订单号", "发货单号", "客户名称", "订单区域", "编号", "合伙人", "类型", "业务员抽成比例", "产品名称", "数量", "单价", "合伙人分红比例"}
  713 +
  714 + // 文件后缀名校验
  715 + ext := path.Ext(h.Filename)
  716 + AllowExtMap := map[string]bool{
  717 + ".xlsx": true,
  718 + }
  719 + if _, ok := AllowExtMap[ext]; !ok {
  720 + c.ResponseError(errors.New("文件后缀名不符合上传要求,请上传正确格式的文件"))
  721 + return
  722 + }
  723 +
  724 + // 打开文件
  725 + xlsx, err := excelize.OpenReader(file)
  726 + if err != nil {
  727 + c.ResponseError(errors.New("文件打开失败,请确定文件能够正常打开"))
  728 + return
  729 + }
  730 +
  731 + // 文件行数校验
  732 + rows, _ := xlsx.GetRows("工作表1")
  733 + if len(rows) > 303 {
  734 + c.ResponseError(errors.New("导入文件的行数超过300行,请调整行数后重新导入"))
  735 + return
  736 + }
  737 +
  738 + // 文件列数据校验
  739 + overColumnLine := make([]interface{}, 0)
  740 + for i, row := range rows {
  741 + if i > 2 && row != nil {
  742 + if len(row) > constant.EXCEL_COLUMN {
  743 + var tmpRow []string
  744 + tmpRow = append(tmpRow, "存在无效的数据列,请删除无效的列数据") // 错误信息
  745 + s := strconv.Itoa(i + 1)
  746 + tmpRow = append(tmpRow, s) // 行号
  747 + tmpRow = append(tmpRow, row...) // 错误行数据
  748 + overColumnLine = append(overColumnLine, tmpRow)
  749 + }
  750 + }
  751 + }
  752 + if len(overColumnLine) > 0 {
  753 + ret = map[string]interface{}{
  754 + "successCount": 0,
  755 + "fail": map[string]interface{}{
  756 + "tableHeader": tableHeader,
  757 + "tableData": overColumnLine,
  758 + },
  759 + }
  760 + c.ResponseData(ret)
  761 + return
  762 + }
  763 +
  764 + // 数据行计数
  765 + rowCnt := 0
  766 +
  767 + // 空文件校验
  768 + if len(rows) < 3 {
  769 + c.ResponseError(errors.New("导入的excel文件为空文件,请上传正确的文件"))
  770 + }
  771 +
  772 + // 必填项校验
  773 + nullLine := make([]interface{}, 0)
  774 +
  775 + nullFlag := false
  776 + for i, row := range rows {
  777 + if i > 2 && row != nil {
  778 + rowCnt++
  779 + if len(row) == constant.EXCEL_COLUMN { // 必填项内容为空
  780 + var tmpRow = row
  781 + nullCell := make([]interface{}, 0)
  782 + var myRow []string
  783 + for j, _ := range row {
  784 + if j != 7 { // 业务员抽成比例非必填
  785 + if row[j] == "" || row[j] == " " { // 空字符补位
  786 + tmpRow[j] = ""
  787 + col := strconv.Itoa(j + 1)
  788 + nullCell = append(nullCell, col)
  789 + nullFlag = true
  790 + }
  791 + }
  792 + }
  793 + if nullFlag {
  794 + s := strconv.Itoa(i + 1)
  795 + b := strings.Replace(strings.Trim(fmt.Sprint(nullCell), "[]"), " ", ",", -1)
  796 + myRow = append(myRow, "第"+s+"行的第"+b+"列必填项为空") // 错误信息
  797 + myRow = append(myRow, s) // 行号
  798 + myRow = append(myRow, tmpRow...) // 错误行数据
  799 + nullLine = append(nullLine, myRow)
  800 + nullFlag = false
  801 + }
  802 + } else if len(row) > 0 && len(row) < constant.EXCEL_COLUMN { // 必填项不存在
  803 + var myRow []string
  804 + myRow = append(myRow, "必填项为空")
  805 + s := strconv.Itoa(i + 1)
  806 + myRow = append(myRow, s) // 行号
  807 + myRow = append(myRow, row...) // 错误行数据
  808 + emptyCell := make([]interface{}, 0)
  809 + // 错误信息
  810 + for k := 0; k < constant.EXCEL_COLUMN-len(row); k++ { // 空字符补位
  811 + myRow = append(myRow, "")
  812 + }
  813 + for k, cell := range myRow {
  814 + if k != 0 && cell == "" {
  815 + emptyCell = append(emptyCell, k-1)
  816 + }
  817 + }
  818 + b := strings.Replace(strings.Trim(fmt.Sprint(emptyCell), "[]"), " ", ",", -1)
  819 + myRow[0] = "第" + s + "行的第" + b + "列必填项为空"
  820 + nullLine = append(nullLine, myRow)
  821 + }
  822 + }
  823 + }
  824 +
  825 + // 空单元格返回
  826 + if len(nullLine) > 0 {
  827 + ret = map[string]interface{}{
  828 + "successCount": 0,
  829 + "fail": map[string]interface{}{
  830 + "tableHeader": tableHeader,
  831 + "tableData": nullLine,
  832 + },
  833 + }
  834 + c.ResponseData(ret)
  835 + return
  836 + }
  837 +
  838 + // 内容校验
  839 + errorLine := make([]interface{}, 0)
  840 + var partnerType = []string{"事业合伙", "业务合伙", "研发合伙", "业务-产品应用合伙"}
  841 + for i, row := range rows {
  842 + if i > 2 && row != nil && len(row) == constant.EXCEL_COLUMN { // 数据行
  843 + var myRow []string
  844 + for j, cell := range row {
  845 +
  846 + r := strconv.Itoa(i + 1)
  847 + col := strconv.Itoa(j + 1)
  848 +
  849 + switch j {
  850 + case 0, 1, 2, 3, 4, 5, 8: // 订单号、发货单号、客户名称、订单区域、编号、合伙人、产品名称长度校验
  851 + {
  852 + cellStr := strings.TrimSpace(cell)
  853 + lenCellStr := utf8.RuneCountInString(cellStr)
  854 +
  855 + if lenCellStr > 50 {
  856 + var tmpRow []string
  857 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列"+tableHeader[j+2]+"长度超过50位,请重新输入") // 错误信息
  858 + tmpRow = append(tmpRow, r) // 行号
  859 + tmpRow = append(tmpRow, row...) // 错误行数据
  860 + myRow = tmpRow
  861 + }
  862 + }
  863 + case 6: // 合伙人类型校验(事业合伙、业务合伙、研发合伙、业务-产品应用合伙)
  864 + {
  865 + if !utils.IsContain(partnerType, cell) {
  866 + var tmpRow []string
  867 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列合伙人类型错误,合伙人类型必须为以下类型:事业合伙、业务合伙、研发合伙、业务-产品应用合伙") // 错误信息
  868 + tmpRow = append(tmpRow, r) // 行号
  869 + tmpRow = append(tmpRow, row...) // 错误行数据
  870 + myRow = tmpRow
  871 + }
  872 + }
  873 + case 7: // 业务员抽成比例,非必填,精确到小数点后两位
  874 + {
  875 + if len(cell) > 0 {
  876 +
  877 + // 参数类型转换
  878 + shareRatio, err := strconv.ParseFloat(cell, 64)
  879 + if err != nil {
  880 + var tmpRow []string
  881 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列业务员抽成比例格式错误,业务员抽成比例必须为数字") // 错误信息
  882 + tmpRow = append(tmpRow, r) // 行号
  883 + tmpRow = append(tmpRow, row...) // 错误行数据
  884 + myRow = tmpRow
  885 + }
  886 +
  887 + // 比例不能超过100%
  888 + if shareRatio > 100 {
  889 + var tmpRow []string
  890 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列业务员抽成比例超过限额,请输入正确的业务员抽成比例,并保留两位小数") // 错误信息
  891 + tmpRow = append(tmpRow, r) // 行号
  892 + tmpRow = append(tmpRow, row...) // 错误行数据
  893 + myRow = tmpRow
  894 + }
  895 +
  896 + // 长度校验
  897 + regexpStr := `^(100|[1-9]\d|\d)(.\d{1,2})?$`
  898 + ok := regexp.MustCompile(regexpStr).MatchString(cell)
  899 + if !ok {
  900 + var tmpRow []string
  901 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列业务员抽成比例超过最大长度,请输入正确的业务员抽成比例,并保留两位小数") // 错误信息
  902 + tmpRow = append(tmpRow, r) // 行号
  903 + tmpRow = append(tmpRow, row...) // 错误行数据
  904 + myRow = tmpRow
  905 + }
  906 + }
  907 + }
  908 + case 9: // 数量不超过16位正整数
  909 + {
  910 + //参数类型转换
  911 + orderNum, err := strconv.ParseInt(cell, 10, 64)
  912 + if err != nil {
  913 + var tmpRow []string
  914 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列产品数量格式错误,产品数量必须整数") // 错误信息
  915 + tmpRow = append(tmpRow, r) // 行号
  916 + tmpRow = append(tmpRow, row...) // 错误行数据
  917 + myRow = tmpRow
  918 + }
  919 +
  920 + // 长度校验
  921 + if orderNum > 1e16 {
  922 + var tmpRow []string
  923 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列产品数量长度超过最大限制十六位整数,请重新填写") // 错误信息
  924 + tmpRow = append(tmpRow, r) // 行号
  925 + tmpRow = append(tmpRow, row...) // 错误行数据
  926 + myRow = tmpRow
  927 + }
  928 + }
  929 + case 10: // 单价,精确到小数点后两位,小数点左侧最多可输入16位数字
  930 + {
  931 +
  932 + // 参数类型转换
  933 + univalent, typeErr := strconv.ParseFloat(cell, 64)
  934 + if typeErr != nil {
  935 + var tmpRow []string
  936 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列单价格式错误,产品单价必须为数字类型") // 错误信息
  937 + tmpRow = append(tmpRow, r) // 行号
  938 + tmpRow = append(tmpRow, row...) // 错误行数据
  939 + myRow = tmpRow
  940 + }
  941 +
  942 + // 长度校验
  943 + if univalent >= 1e16 {
  944 + var tmpRow []string
  945 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列产品单价超过最大限制,产品单价小数点前面不能超过十六位数字,并保留两位小数") // 错误信息
  946 + tmpRow = append(tmpRow, r) // 行号
  947 + tmpRow = append(tmpRow, row...) // 错误行数据
  948 + myRow = tmpRow
  949 + }
  950 + }
  951 + case 11: // 合伙人分红比例,精确到小数点后两位
  952 + {
  953 + //参数类型转换
  954 + partnerRatio, parseErr := strconv.ParseFloat(cell, 64)
  955 + if parseErr != nil {
  956 + var tmpRow []string
  957 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列合伙人分红比例类型错误,合伙人分红比例必须为数字") // 错误信息
  958 + tmpRow = append(tmpRow, r) // 行号
  959 + tmpRow = append(tmpRow, row...) // 错误行数据
  960 + myRow = tmpRow
  961 + }
  962 +
  963 + // 合伙人分红比例超额
  964 + if partnerRatio > 100 {
  965 + var tmpRow []string
  966 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列合伙人分红比例超过限额,请输入正确的合伙人分红比例,并保留两位小数") // 错误信息
  967 + tmpRow = append(tmpRow, r) // 行号
  968 + tmpRow = append(tmpRow, row...) // 错误行数据
  969 + myRow = tmpRow
  970 + }
  971 +
  972 + // 长度判断
  973 + regexpStr := `^(100|[1-9]\d|\d)(.\d{1,2})?$`
  974 + ok := regexp.MustCompile(regexpStr).MatchString(cell)
  975 + if !ok {
  976 + var tmpRow []string
  977 + tmpRow = append(tmpRow, "第"+r+"行第"+col+"列合伙人分红比例超过最大长度,请输入正确的合伙人分红比例,并保留两位小数") // 错误信息
  978 + tmpRow = append(tmpRow, r) // 行号
  979 + tmpRow = append(tmpRow, row...) // 错误行数据
  980 + myRow = tmpRow
  981 + }
  982 + }
  983 + }
  984 + }
  985 + if myRow != nil {
  986 + errorLine = append(errorLine, myRow)
  987 + }
  988 + }
  989 + }
  990 +
  991 + // 内容错误行返回
  992 + if len(errorLine) > 0 {
  993 + ret = map[string]interface{}{
  994 + "successCount": 0,
  995 + "fail": map[string]interface{}{
  996 + "tableHeader": tableHeader,
  997 + "tableData": errorLine,
  998 + },
  999 + }
  1000 + c.ResponseData(ret)
  1001 + return
  1002 + }
  1003 +
  1004 + // 创建订单服务
  1005 + orderSrv := orderService.NewOrderInfoService(nil)
  1006 +
  1007 + // 合伙人检索错误
  1008 + partnerDataList := make([]interface{}, 0)
  1009 +
  1010 + // 聚合订单产品
  1011 + var orderCommands = make(map[string]*orderCmd.CreateOrderCommand, 0)
  1012 + for i, row := range rows {
  1013 + if i > 2 && len(row) == constant.EXCEL_COLUMN {
  1014 + hashValue := md5.Sum([]byte(row[0] + row[1] + row[4] + row[6])) // 根据:订单号+发货单号+合伙人编号+合伙类型计算哈希值
  1015 + hashString := hex.EncodeToString(hashValue[:])
  1016 +
  1017 + if _, ok := orderCommands[hashString]; !ok {
  1018 + //订单相关,0: 订单号, 1: 发货单号, 2: 客户名称, 3: 订单区域, 4: 编号, 5: 合伙人, 6: 类型, 7: 业务抽成比例,
  1019 + sbPercent, _ := strconv.ParseFloat(row[7], 64) //业务抽成比例
  1020 +
  1021 + //产品相关,8: 产品名称, 9: 数量, 10: 单价, 11: 合伙人分红比例
  1022 + amount, _ := strconv.ParseInt(row[9], 10, 64) // 数量
  1023 + price, _ := strconv.ParseFloat(row[10], 64) // 单价
  1024 + percent, _ := strconv.ParseFloat(row[11], 64) // 合伙人分红比例
  1025 +
  1026 + // 获取partnerId
  1027 + var partnerInfo *domain.PartnerInfo
  1028 +
  1029 + orderQueryData := orderQuery.GetPartnerIdQuery{
  1030 + Code: row[4],
  1031 + PartnerCategory: 0,
  1032 + CompanyId: companyId,
  1033 + }
  1034 +
  1035 + // 1: 事业合伙、2: 业务合伙、3: 研发合伙、4: 业务-产品应用合伙
  1036 + switch row[6] {
  1037 + case "事业合伙":
  1038 + orderQueryData.PartnerCategory = 1
  1039 + case "业务合伙":
  1040 + orderQueryData.PartnerCategory = 2
  1041 + case "研发合伙":
  1042 + orderQueryData.PartnerCategory = 3
  1043 + case "业务-产品应用合伙":
  1044 + orderQueryData.PartnerCategory = 4
  1045 + default:
  1046 + orderQueryData.PartnerCategory = 0
  1047 + }
  1048 +
  1049 + // 初始化建订单命令集
  1050 + orderCommands[hashString] = &orderCmd.CreateOrderCommand{
  1051 + OrderType: 0,
  1052 + OrderCode: row[0],
  1053 + DeliveryCode: row[1],
  1054 + BuyerName: row[2],
  1055 + OrderRegion: row[3],
  1056 + PartnerId: 0, // 根据合伙人类型+合伙人编号查找合伙人id
  1057 + SalesmanBonusPercent: sbPercent,
  1058 + Goods: []orderCmd.OrderGoodData{
  1059 + {
  1060 + GoodName: row[8],
  1061 + PlanGoodNumber: int(amount),
  1062 + Price: price,
  1063 + PartnerBonusPercent: percent,
  1064 + LineNumber: i,
  1065 + },
  1066 + },
  1067 + CompanyId: companyId,
  1068 + PartnerCategory: int64(orderQueryData.PartnerCategory),
  1069 + LineNumbers: []int{i}, // 记录行号
  1070 + }
  1071 +
  1072 + partnerInfo, err = orderSrv.GetPartnerIdByCodeAndCategory(orderQueryData)
  1073 + if err != nil || partnerInfo == nil { // 检索合伙人错误
  1074 + var tmpRow []string
  1075 + tmpRow = append(tmpRow, err.Error()) // 错误信息
  1076 + s := strconv.Itoa(i + 1)
  1077 + tmpRow = append(tmpRow, s) // 行号
  1078 + tmpRow = append(tmpRow, row...) // 错误行数据
  1079 + partnerDataList = append(partnerDataList, tmpRow)
  1080 + }
  1081 + if partnerInfo != nil {
  1082 + orderCommands[hashString].PartnerId = partnerInfo.Partner.Id
  1083 + }
  1084 + } else {
  1085 + //产品相关,8: 产品名称, 9: 数量, 10: 单价, 11: 合伙人分红比例
  1086 + amount, _ := strconv.ParseInt(row[9], 10, 64) // 数量
  1087 + price, _ := strconv.ParseFloat(row[10], 64) // 单价
  1088 + percent, _ := strconv.ParseFloat(row[11], 64) // 合伙人分红比例
  1089 +
  1090 + // 记录同一笔订单产品
  1091 + orderCommands[hashString].Goods = append(orderCommands[hashString].Goods, orderCmd.OrderGoodData{
  1092 + GoodName: row[8],
  1093 + PlanGoodNumber: int(amount),
  1094 + Price: price,
  1095 + PartnerBonusPercent: percent,
  1096 + LineNumber: i, // 记录行号
  1097 + })
  1098 +
  1099 + // 记录聚合行号
  1100 + orderCommands[hashString].LineNumbers = append(orderCommands[hashString].LineNumbers, i)
  1101 + }
  1102 + }
  1103 + }
  1104 + if len(partnerDataList) > 0 {
  1105 + ret = map[string]interface{}{
  1106 + "successCount": 0,
  1107 + "fail": map[string]interface{}{
  1108 + "tableHeader": tableHeader,
  1109 + "tableData": partnerDataList,
  1110 + },
  1111 + }
  1112 + c.ResponseData(ret)
  1113 + return
  1114 + }
  1115 +
  1116 + // 产品数量校验
  1117 + productNumberError := make([]interface{}, 0)
  1118 +
  1119 + // 批量创建订单命令集和产品数量校验
  1120 + var createOrderCommands []*orderCmd.CreateOrderCommand
  1121 + for _, orderCommand := range orderCommands {
  1122 + if len(orderCommand.Goods) > 50 { // 产品数量校验
  1123 + for _, line := range orderCommand.LineNumbers {
  1124 + var tmpRow []string
  1125 + tmpRow = append(tmpRow, "单笔订单产品超过50种") // 错误信息
  1126 + s := strconv.Itoa(line + 1)
  1127 + tmpRow = append(tmpRow, s) // 行号
  1128 + tmpRow = append(tmpRow, rows[line]...) // 错误行数据
  1129 + productNumberError = append(productNumberError, tmpRow)
  1130 + }
  1131 + } else {
  1132 + createOrderCommands = append(createOrderCommands, orderCommand)
  1133 + }
  1134 + }
  1135 +
  1136 + if len(productNumberError) > 0 {
  1137 + ret = map[string]interface{}{
  1138 + "successCount": 0,
  1139 + "fail": map[string]interface{}{
  1140 + "tableHeader": tableHeader,
  1141 + "tableData": productNumberError,
  1142 + },
  1143 + }
  1144 + c.ResponseData(ret)
  1145 + return
  1146 + }
  1147 +
  1148 + // 新增失败记录
  1149 + failureDataList := make([]interface{}, 0)
  1150 +
  1151 + // 新增成功记录计数
  1152 + var successDataCount int64
  1153 +
  1154 + // 批量新增订单
  1155 + errorDataList, createError := orderSrv.CreateNewOrderByImport(createOrderCommands)
  1156 + if createError != nil {
  1157 + c.ResponseError(createError)
  1158 + return
  1159 + } else {
  1160 + if len(errorDataList) > 0 { // 导入失败返回
  1161 + successDataCount = 0
  1162 + // 错误记录处理
  1163 + for _, errorData := range errorDataList {
  1164 + if len(errorData.GoodLine) == 0 { // 订单错误
  1165 + for _, line := range errorData.LineNumbers {
  1166 + var tmpRow []string
  1167 + tmpRow = append(tmpRow, errorData.Error.Error()) // 错误信息
  1168 + s := strconv.Itoa(line + 1)
  1169 + tmpRow = append(tmpRow, s) // 行号
  1170 + tmpRow = append(tmpRow, rows[line]...) // 错误行数据
  1171 + failureDataList = append(failureDataList, tmpRow)
  1172 + }
  1173 + } else if len(errorData.GoodLine) > 0 { // 订单产品错误
  1174 + for line := range errorData.GoodLine {
  1175 + var tmpRow []string
  1176 + tmpRow = append(tmpRow, errorData.Error.Error()) // 错误信息
  1177 + s := strconv.Itoa(line + 1)
  1178 + tmpRow = append(tmpRow, s) // 行号
  1179 + tmpRow = append(tmpRow, rows[line]...) // 错误行数据
  1180 + failureDataList = append(failureDataList, tmpRow)
  1181 + }
  1182 + }
  1183 + }
  1184 +
  1185 + ret = map[string]interface{}{
  1186 + "successCount": successDataCount,
  1187 + "fail": map[string]interface{}{
  1188 + "tableHeader": tableHeader,
  1189 + "tableData": failureDataList,
  1190 + },
  1191 + }
  1192 + } else { // 导入成功返回
  1193 + successDataCount = int64(rowCnt - len(failureDataList))
  1194 + if successDataCount == int64(rowCnt) {
  1195 + ret = map[string]interface{}{
  1196 + "successCount": successDataCount,
  1197 + "fail": nil,
  1198 + }
  1199 + }
  1200 + }
  1201 + }
  1202 +
  1203 + // 返回错误详情
  1204 + c.ResponseData(ret)
  1205 + return
  1206 +}
@@ -122,6 +122,7 @@ func (c *UserController) EditUserPermission() { @@ -122,6 +122,7 @@ func (c *UserController) EditUserPermission() {
122 Id int64 `json:"id"` 122 Id int64 `json:"id"`
123 PermissionType []int64 `json:"permissionType"` 123 PermissionType []int64 `json:"permissionType"`
124 CheckedPartner []int64 `json:"checkedPartner"` //合伙人 124 CheckedPartner []int64 `json:"checkedPartner"` //合伙人
  125 + IsSenior int8 `json:"isSenior"` //是否是高管【1:是】【2:否】
125 } 126 }
126 var ( 127 var (
127 param UserDetailParam 128 param UserDetailParam
@@ -139,6 +140,7 @@ func (c *UserController) EditUserPermission() { @@ -139,6 +140,7 @@ func (c *UserController) EditUserPermission() {
139 CompanyId: companyId, 140 CompanyId: companyId,
140 PermissionType: param.PermissionType, 141 PermissionType: param.PermissionType,
141 CheckedPartner: param.CheckedPartner, 142 CheckedPartner: param.CheckedPartner,
  143 + IsSenior: param.IsSenior,
142 }) 144 })
143 if err != nil { 145 if err != nil {
144 c.ResponseError(err) 146 c.ResponseError(err)
@@ -35,20 +35,20 @@ func init() { @@ -35,20 +35,20 @@ func init() {
35 beego.NSRouter("/list/excel", &controllers.OrderDividendController{}, "POST:ListOrderBonusForExcel"), 35 beego.NSRouter("/list/excel", &controllers.OrderDividendController{}, "POST:ListOrderBonusForExcel"),
36 ), 36 ),
37 beego.NSNamespace("/order", 37 beego.NSNamespace("/order",
38 - beego.NSRouter("/actual/list", &controllers.OrderInfoController{}, "POST:PageListOrderReal"),  
39 - beego.NSRouter("/actual/list/excel", &controllers.OrderInfoController{}, "POST:ListOrderForExcel"),  
40 - beego.NSRouter("/actual/detail", &controllers.OrderInfoController{}, "POST:GetOrderReal"),  
41 - beego.NSRouter("/actual/del", &controllers.OrderInfoController{}, "POST:RemoveOrderReal"),  
42 - beego.NSRouter("/actual/update", &controllers.OrderInfoController{}, "POST:UpdateOrderReal"), 38 + beego.NSRouter("/actual/list", &controllers.OrderInfoController{}, "POST:PageListOrderReal"), // 返归订单列表
  39 + beego.NSRouter("/actual/list/excel", &controllers.OrderInfoController{}, "POST:ListOrderForExcel"), // 导出订单记录
  40 + beego.NSRouter("/actual/detail", &controllers.OrderInfoController{}, "POST:GetOrderReal"), // 查看实际订单详情
  41 + beego.NSRouter("/actual/del", &controllers.OrderInfoController{}, "POST:RemoveOrderReal"), // 删除实际订单
  42 + beego.NSRouter("/actual/update", &controllers.OrderInfoController{}, "POST:UpdateOrderReal"), // 新增实际订单
43 beego.NSRouter("/actual/close", &controllers.OrderInfoController{}, "POST:OrderDisable"), 43 beego.NSRouter("/actual/close", &controllers.OrderInfoController{}, "POST:OrderDisable"),
  44 + beego.NSRouter("/fileImportTemplate", &controllers.OrderInfoController{}, "POST:DownloadTemplate"), // 下载导入模板
  45 + beego.NSRouter("/fileImport", &controllers.OrderInfoController{}, "POST:ImportOrderFromExcel"), // 导入订单数据
44 ), 46 ),
45 -  
46 beego.NSNamespace("/common", 47 beego.NSNamespace("/common",
47 beego.NSRouter("/partner", &controllers.CommonController{}, "POST:GetPartnerList"), 48 beego.NSRouter("/partner", &controllers.CommonController{}, "POST:GetPartnerList"),
48 beego.NSRouter("/partnerType", &controllers.CommonController{}, "POST:GetPartnerCategory"), 49 beego.NSRouter("/partnerType", &controllers.CommonController{}, "POST:GetPartnerCategory"),
49 beego.NSRouter("/orderType", &controllers.CommonController{}, "POST:GetOrderType"), 50 beego.NSRouter("/orderType", &controllers.CommonController{}, "POST:GetOrderType"),
50 ), 51 ),
51 -  
52 beego.NSNamespace("/enterprises", 52 beego.NSNamespace("/enterprises",
53 beego.NSRouter("/setPhone", &controllers.CompanyController{}, "POST:SetPhone"), 53 beego.NSRouter("/setPhone", &controllers.CompanyController{}, "POST:SetPhone"),
54 ), 54 ),
@@ -20,4 +20,7 @@ func init() { @@ -20,4 +20,7 @@ func init() {
20 http.ServeFile(ctx.ResponseWriter, ctx.Request, constant.LOG_File) 20 http.ServeFile(ctx.ResponseWriter, ctx.Request, constant.LOG_File)
21 return 21 return
22 }) 22 })
  23 +
  24 + // 静态文件路径映射
  25 + beego.SetStaticPath("/download", "download")
23 } 26 }
此 diff 太大无法显示。
  1 +Copyright 2014 astaxie
  2 +
  3 +Licensed under the Apache License, Version 2.0 (the "License");
  4 +you may not use this file except in compliance with the License.
  5 +You may obtain a copy of the License at
  6 +
  7 + http://www.apache.org/licenses/LICENSE-2.0
  8 +
  9 +Unless required by applicable law or agreed to in writing, software
  10 +distributed under the License is distributed on an "AS IS" BASIS,
  11 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12 +See the License for the specific language governing permissions and
  13 +limitations under the License.
  1 +# httplib
  2 +
  3 +httplib is an libs help you to curl remote url.
  4 +
  5 +# How to use?
  6 +
  7 +## GET
  8 +
  9 +you can use Get to crawl data.
  10 +
  11 + import "github.com/beego/beego/v2/httplib"
  12 +
  13 + str, err := httplib.Get("http://beego.me/").String()
  14 + if err != nil {
  15 + // error
  16 + }
  17 + fmt.Println(str)
  18 +
  19 +## POST
  20 +
  21 +POST data to remote url
  22 +
  23 + req := httplib.Post("http://beego.me/")
  24 + req.Param("username","astaxie")
  25 + req.Param("password","123456")
  26 + str, err := req.String()
  27 + if err != nil {
  28 + // error
  29 + }
  30 + fmt.Println(str)
  31 +
  32 +## Set timeout
  33 +
  34 +The default timeout is `60` seconds, function prototype:
  35 +
  36 + SetTimeout(connectTimeout, readWriteTimeout time.Duration)
  37 +
  38 +Example:
  39 +
  40 + // GET
  41 + httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
  42 +
  43 + // POST
  44 + httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
  45 +
  46 +## Debug
  47 +
  48 +If you want to debug the request info, set the debug on
  49 +
  50 + httplib.Get("http://beego.me/").Debug(true)
  51 +
  52 +## Set HTTP Basic Auth
  53 +
  54 + str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String()
  55 + if err != nil {
  56 + // error
  57 + }
  58 + fmt.Println(str)
  59 +
  60 +## Set HTTPS
  61 +
  62 +If request url is https, You can set the client support TSL:
  63 +
  64 + httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
  65 +
  66 +More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config
  67 +
  68 +## Set HTTP Version
  69 +
  70 +some servers need to specify the protocol version of HTTP
  71 +
  72 + httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1")
  73 +
  74 +## Set Cookie
  75 +
  76 +some http request need setcookie. So set it like this:
  77 +
  78 + cookie := &http.Cookie{}
  79 + cookie.Name = "username"
  80 + cookie.Value = "astaxie"
  81 + httplib.Get("http://beego.me/").SetCookie(cookie)
  82 +
  83 +## Upload file
  84 +
  85 +httplib support mutil file upload, use `req.PostFile()`
  86 +
  87 + req := httplib.Post("http://beego.me/")
  88 + req.Param("username","astaxie")
  89 + req.PostFile("uploadfile1", "httplib.pdf")
  90 + str, err := req.String()
  91 + if err != nil {
  92 + // error
  93 + }
  94 + fmt.Println(str)
  95 +
  96 +See godoc for further documentation and examples.
  97 +
  98 +* [godoc.org/github.com/beego/beego/v2/httplib](https://godoc.org/github.com/beego/beego/v2/httplib)
  1 +// Copyright 2020 beego
  2 +//
  3 +// Licensed under the Apache License, Version 2.0 (the "License");
  4 +// you may not use this file except in compliance with the License.
  5 +// You may obtain a copy of the License at
  6 +//
  7 +// http://www.apache.org/licenses/LICENSE-2.0
  8 +//
  9 +// Unless required by applicable law or agreed to in writing, software
  10 +// distributed under the License is distributed on an "AS IS" BASIS,
  11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12 +// See the License for the specific language governing permissions and
  13 +// limitations under the License.
  14 +
  15 +package httplib
  16 +
  17 +import (
  18 + "context"
  19 + "net/http"
  20 +)
  21 +
  22 +type FilterChain func(next Filter) Filter
  23 +
  24 +type Filter func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error)
  1 +// Copyright 2014 beego Author. All Rights Reserved.
  2 +//
  3 +// Licensed under the Apache License, Version 2.0 (the "License");
  4 +// you may not use this file except in compliance with the License.
  5 +// You may obtain a copy of the License at
  6 +//
  7 +// http://www.apache.org/licenses/LICENSE-2.0
  8 +//
  9 +// Unless required by applicable law or agreed to in writing, software
  10 +// distributed under the License is distributed on an "AS IS" BASIS,
  11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12 +// See the License for the specific language governing permissions and
  13 +// limitations under the License.
  14 +
  15 +// Package httplib is used as http.Client
  16 +// Usage:
  17 +//
  18 +// import "github.com/beego/beego/v2/httplib"
  19 +//
  20 +// b := httplib.Post("http://beego.me/")
  21 +// b.Param("username","astaxie")
  22 +// b.Param("password","123456")
  23 +// b.PostFile("uploadfile1", "httplib.pdf")
  24 +// b.PostFile("uploadfile2", "httplib.txt")
  25 +// str, err := b.String()
  26 +// if err != nil {
  27 +// t.Fatal(err)
  28 +// }
  29 +// fmt.Println(str)
  30 +//
  31 +// more docs http://beego.me/docs/module/httplib.md
  32 +package httplib
  33 +
  34 +import (
  35 + "bytes"
  36 + "compress/gzip"
  37 + "context"
  38 + "crypto/tls"
  39 + "encoding/json"
  40 + "encoding/xml"
  41 + "io"
  42 + "io/ioutil"
  43 + "log"
  44 + "mime/multipart"
  45 + "net"
  46 + "net/http"
  47 + "net/http/cookiejar"
  48 + "net/http/httputil"
  49 + "net/url"
  50 + "os"
  51 + "path"
  52 + "strings"
  53 + "sync"
  54 + "time"
  55 +
  56 + "gopkg.in/yaml.v2"
  57 +)
  58 +
  59 +var defaultSetting = BeegoHTTPSettings{
  60 + UserAgent: "beegoServer",
  61 + ConnectTimeout: 60 * time.Second,
  62 + ReadWriteTimeout: 60 * time.Second,
  63 + Gzip: true,
  64 + DumpBody: true,
  65 +}
  66 +
  67 +var defaultCookieJar http.CookieJar
  68 +var settingMutex sync.Mutex
  69 +
  70 +// it will be the last filter and execute request.Do
  71 +var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
  72 + return req.doRequest(ctx)
  73 +}
  74 +
  75 +// createDefaultCookie creates a global cookiejar to store cookies.
  76 +func createDefaultCookie() {
  77 + settingMutex.Lock()
  78 + defer settingMutex.Unlock()
  79 + defaultCookieJar, _ = cookiejar.New(nil)
  80 +}
  81 +
  82 +// SetDefaultSetting overwrites default settings
  83 +func SetDefaultSetting(setting BeegoHTTPSettings) {
  84 + settingMutex.Lock()
  85 + defer settingMutex.Unlock()
  86 + defaultSetting = setting
  87 +}
  88 +
  89 +// NewBeegoRequest returns *BeegoHttpRequest with specific method
  90 +func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest {
  91 + var resp http.Response
  92 + u, err := url.Parse(rawurl)
  93 + if err != nil {
  94 + log.Println("Httplib:", err)
  95 + }
  96 + req := http.Request{
  97 + URL: u,
  98 + Method: method,
  99 + Header: make(http.Header),
  100 + Proto: "HTTP/1.1",
  101 + ProtoMajor: 1,
  102 + ProtoMinor: 1,
  103 + }
  104 + return &BeegoHTTPRequest{
  105 + url: rawurl,
  106 + req: &req,
  107 + params: map[string][]string{},
  108 + files: map[string]string{},
  109 + setting: defaultSetting,
  110 + resp: &resp,
  111 + }
  112 +}
  113 +
  114 +// Get returns *BeegoHttpRequest with GET method.
  115 +func Get(url string) *BeegoHTTPRequest {
  116 + return NewBeegoRequest(url, "GET")
  117 +}
  118 +
  119 +// Post returns *BeegoHttpRequest with POST method.
  120 +func Post(url string) *BeegoHTTPRequest {
  121 + return NewBeegoRequest(url, "POST")
  122 +}
  123 +
  124 +// Put returns *BeegoHttpRequest with PUT method.
  125 +func Put(url string) *BeegoHTTPRequest {
  126 + return NewBeegoRequest(url, "PUT")
  127 +}
  128 +
  129 +// Delete returns *BeegoHttpRequest DELETE method.
  130 +func Delete(url string) *BeegoHTTPRequest {
  131 + return NewBeegoRequest(url, "DELETE")
  132 +}
  133 +
  134 +// Head returns *BeegoHttpRequest with HEAD method.
  135 +func Head(url string) *BeegoHTTPRequest {
  136 + return NewBeegoRequest(url, "HEAD")
  137 +}
  138 +
  139 +// BeegoHTTPSettings is the http.Client setting
  140 +type BeegoHTTPSettings struct {
  141 + ShowDebug bool
  142 + UserAgent string
  143 + ConnectTimeout time.Duration
  144 + ReadWriteTimeout time.Duration
  145 + TLSClientConfig *tls.Config
  146 + Proxy func(*http.Request) (*url.URL, error)
  147 + Transport http.RoundTripper
  148 + CheckRedirect func(req *http.Request, via []*http.Request) error
  149 + EnableCookie bool
  150 + Gzip bool
  151 + DumpBody bool
  152 + Retries int // if set to -1 means will retry forever
  153 + RetryDelay time.Duration
  154 + FilterChains []FilterChain
  155 +}
  156 +
  157 +// BeegoHTTPRequest provides more useful methods than http.Request for requesting a url.
  158 +type BeegoHTTPRequest struct {
  159 + url string
  160 + req *http.Request
  161 + params map[string][]string
  162 + files map[string]string
  163 + setting BeegoHTTPSettings
  164 + resp *http.Response
  165 + body []byte
  166 + dump []byte
  167 +}
  168 +
  169 +// GetRequest returns the request object
  170 +func (b *BeegoHTTPRequest) GetRequest() *http.Request {
  171 + return b.req
  172 +}
  173 +
  174 +// Setting changes request settings
  175 +func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest {
  176 + b.setting = setting
  177 + return b
  178 +}
  179 +
  180 +// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
  181 +func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest {
  182 + b.req.SetBasicAuth(username, password)
  183 + return b
  184 +}
  185 +
  186 +// SetEnableCookie sets enable/disable cookiejar
  187 +func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest {
  188 + b.setting.EnableCookie = enable
  189 + return b
  190 +}
  191 +
  192 +// SetUserAgent sets User-Agent header field
  193 +func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest {
  194 + b.setting.UserAgent = useragent
  195 + return b
  196 +}
  197 +
  198 +// Debug sets show debug or not when executing request.
  199 +func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest {
  200 + b.setting.ShowDebug = isdebug
  201 + return b
  202 +}
  203 +
  204 +// Retries sets Retries times.
  205 +// default is 0 (never retry)
  206 +// -1 retry indefinitely (forever)
  207 +// Other numbers specify the exact retry amount
  208 +func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest {
  209 + b.setting.Retries = times
  210 + return b
  211 +}
  212 +
  213 +// RetryDelay sets the time to sleep between reconnection attempts
  214 +func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest {
  215 + b.setting.RetryDelay = delay
  216 + return b
  217 +}
  218 +
  219 +// DumpBody sets the DumbBody field
  220 +func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
  221 + b.setting.DumpBody = isdump
  222 + return b
  223 +}
  224 +
  225 +// DumpRequest returns the DumpRequest
  226 +func (b *BeegoHTTPRequest) DumpRequest() []byte {
  227 + return b.dump
  228 +}
  229 +
  230 +// SetTimeout sets connect time out and read-write time out for BeegoRequest.
  231 +func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest {
  232 + b.setting.ConnectTimeout = connectTimeout
  233 + b.setting.ReadWriteTimeout = readWriteTimeout
  234 + return b
  235 +}
  236 +
  237 +// SetTLSClientConfig sets TLS connection configuration if visiting HTTPS url.
  238 +func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest {
  239 + b.setting.TLSClientConfig = config
  240 + return b
  241 +}
  242 +
  243 +// Header adds header item string in request.
  244 +func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest {
  245 + b.req.Header.Set(key, value)
  246 + return b
  247 +}
  248 +
  249 +// SetHost set the request host
  250 +func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest {
  251 + b.req.Host = host
  252 + return b
  253 +}
  254 +
  255 +// SetProtocolVersion sets the protocol version for incoming requests.
  256 +// Client requests always use HTTP/1.1.
  257 +func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
  258 + if len(vers) == 0 {
  259 + vers = "HTTP/1.1"
  260 + }
  261 +
  262 + major, minor, ok := http.ParseHTTPVersion(vers)
  263 + if ok {
  264 + b.req.Proto = vers
  265 + b.req.ProtoMajor = major
  266 + b.req.ProtoMinor = minor
  267 + }
  268 +
  269 + return b
  270 +}
  271 +
  272 +// SetCookie adds a cookie to the request.
  273 +func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest {
  274 + b.req.Header.Add("Cookie", cookie.String())
  275 + return b
  276 +}
  277 +
  278 +// SetTransport sets the transport field
  279 +func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest {
  280 + b.setting.Transport = transport
  281 + return b
  282 +}
  283 +
  284 +// SetProxy sets the HTTP proxy
  285 +// example:
  286 +//
  287 +// func(req *http.Request) (*url.URL, error) {
  288 +// u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
  289 +// return u, nil
  290 +// }
  291 +func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest {
  292 + b.setting.Proxy = proxy
  293 + return b
  294 +}
  295 +
  296 +// SetCheckRedirect specifies the policy for handling redirects.
  297 +//
  298 +// If CheckRedirect is nil, the Client uses its default policy,
  299 +// which is to stop after 10 consecutive requests.
  300 +func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest {
  301 + b.setting.CheckRedirect = redirect
  302 + return b
  303 +}
  304 +
  305 +// SetFilters will use the filter as the invocation filters
  306 +func (b *BeegoHTTPRequest) SetFilters(fcs ...FilterChain) *BeegoHTTPRequest {
  307 + b.setting.FilterChains = fcs
  308 + return b
  309 +}
  310 +
  311 +// AddFilters adds filter
  312 +func (b *BeegoHTTPRequest) AddFilters(fcs ...FilterChain) *BeegoHTTPRequest {
  313 + b.setting.FilterChains = append(b.setting.FilterChains, fcs...)
  314 + return b
  315 +}
  316 +
  317 +// Param adds query param in to request.
  318 +// params build query string as ?key1=value1&key2=value2...
  319 +func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest {
  320 + if param, ok := b.params[key]; ok {
  321 + b.params[key] = append(param, value)
  322 + } else {
  323 + b.params[key] = []string{value}
  324 + }
  325 + return b
  326 +}
  327 +
  328 +// PostFile adds a post file to the request
  329 +func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest {
  330 + b.files[formname] = filename
  331 + return b
  332 +}
  333 +
  334 +// Body adds request raw body.
  335 +// Supports string and []byte.
  336 +func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
  337 + switch t := data.(type) {
  338 + case string:
  339 + bf := bytes.NewBufferString(t)
  340 + b.req.Body = ioutil.NopCloser(bf)
  341 + b.req.ContentLength = int64(len(t))
  342 + case []byte:
  343 + bf := bytes.NewBuffer(t)
  344 + b.req.Body = ioutil.NopCloser(bf)
  345 + b.req.ContentLength = int64(len(t))
  346 + }
  347 + return b
  348 +}
  349 +
  350 +// XMLBody adds the request raw body encoded in XML.
  351 +func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
  352 + if b.req.Body == nil && obj != nil {
  353 + byts, err := xml.Marshal(obj)
  354 + if err != nil {
  355 + return b, err
  356 + }
  357 + b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
  358 + b.req.ContentLength = int64(len(byts))
  359 + b.req.Header.Set("Content-Type", "application/xml")
  360 + }
  361 + return b, nil
  362 +}
  363 +
  364 +// YAMLBody adds the request raw body encoded in YAML.
  365 +func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
  366 + if b.req.Body == nil && obj != nil {
  367 + byts, err := yaml.Marshal(obj)
  368 + if err != nil {
  369 + return b, err
  370 + }
  371 + b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
  372 + b.req.ContentLength = int64(len(byts))
  373 + b.req.Header.Set("Content-Type", "application/x+yaml")
  374 + }
  375 + return b, nil
  376 +}
  377 +
  378 +// JSONBody adds the request raw body encoded in JSON.
  379 +func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
  380 + if b.req.Body == nil && obj != nil {
  381 + byts, err := json.Marshal(obj)
  382 + if err != nil {
  383 + return b, err
  384 + }
  385 + b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
  386 + b.req.ContentLength = int64(len(byts))
  387 + b.req.Header.Set("Content-Type", "application/json")
  388 + }
  389 + return b, nil
  390 +}
  391 +
  392 +func (b *BeegoHTTPRequest) buildURL(paramBody string) {
  393 + // build GET url with query string
  394 + if b.req.Method == "GET" && len(paramBody) > 0 {
  395 + if strings.Contains(b.url, "?") {
  396 + b.url += "&" + paramBody
  397 + } else {
  398 + b.url = b.url + "?" + paramBody
  399 + }
  400 + return
  401 + }
  402 +
  403 + // build POST/PUT/PATCH url and body
  404 + if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH" || b.req.Method == "DELETE") && b.req.Body == nil {
  405 + // with files
  406 + if len(b.files) > 0 {
  407 + pr, pw := io.Pipe()
  408 + bodyWriter := multipart.NewWriter(pw)
  409 + go func() {
  410 + for formname, filename := range b.files {
  411 + fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
  412 + if err != nil {
  413 + log.Println("Httplib:", err)
  414 + }
  415 + fh, err := os.Open(filename)
  416 + if err != nil {
  417 + log.Println("Httplib:", err)
  418 + }
  419 + // iocopy
  420 + _, err = io.Copy(fileWriter, fh)
  421 + fh.Close()
  422 + if err != nil {
  423 + log.Println("Httplib:", err)
  424 + }
  425 + }
  426 + for k, v := range b.params {
  427 + for _, vv := range v {
  428 + bodyWriter.WriteField(k, vv)
  429 + }
  430 + }
  431 + bodyWriter.Close()
  432 + pw.Close()
  433 + }()
  434 + b.Header("Content-Type", bodyWriter.FormDataContentType())
  435 + b.req.Body = ioutil.NopCloser(pr)
  436 + b.Header("Transfer-Encoding", "chunked")
  437 + return
  438 + }
  439 +
  440 + // with params
  441 + if len(paramBody) > 0 {
  442 + b.Header("Content-Type", "application/x-www-form-urlencoded")
  443 + b.Body(paramBody)
  444 + }
  445 + }
  446 +}
  447 +
  448 +func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) {
  449 + if b.resp.StatusCode != 0 {
  450 + return b.resp, nil
  451 + }
  452 + resp, err := b.DoRequest()
  453 + if err != nil {
  454 + return nil, err
  455 + }
  456 + b.resp = resp
  457 + return resp, nil
  458 +}
  459 +
  460 +// DoRequest executes client.Do
  461 +func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
  462 + return b.DoRequestWithCtx(context.Background())
  463 +}
  464 +
  465 +func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Response, err error) {
  466 +
  467 + root := doRequestFilter
  468 + if len(b.setting.FilterChains) > 0 {
  469 + for i := len(b.setting.FilterChains) - 1; i >= 0; i-- {
  470 + root = b.setting.FilterChains[i](root)
  471 + }
  472 + }
  473 + return root(ctx, b)
  474 +}
  475 +
  476 +func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) {
  477 + var paramBody string
  478 + if len(b.params) > 0 {
  479 + var buf bytes.Buffer
  480 + for k, v := range b.params {
  481 + for _, vv := range v {
  482 + buf.WriteString(url.QueryEscape(k))
  483 + buf.WriteByte('=')
  484 + buf.WriteString(url.QueryEscape(vv))
  485 + buf.WriteByte('&')
  486 + }
  487 + }
  488 + paramBody = buf.String()
  489 + paramBody = paramBody[0 : len(paramBody)-1]
  490 + }
  491 +
  492 + b.buildURL(paramBody)
  493 + urlParsed, err := url.Parse(b.url)
  494 + if err != nil {
  495 + return nil, err
  496 + }
  497 +
  498 + b.req.URL = urlParsed
  499 +
  500 + trans := b.setting.Transport
  501 +
  502 + if trans == nil {
  503 + // create default transport
  504 + trans = &http.Transport{
  505 + TLSClientConfig: b.setting.TLSClientConfig,
  506 + Proxy: b.setting.Proxy,
  507 + Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
  508 + MaxIdleConnsPerHost: 100,
  509 + }
  510 + } else {
  511 + // if b.transport is *http.Transport then set the settings.
  512 + if t, ok := trans.(*http.Transport); ok {
  513 + if t.TLSClientConfig == nil {
  514 + t.TLSClientConfig = b.setting.TLSClientConfig
  515 + }
  516 + if t.Proxy == nil {
  517 + t.Proxy = b.setting.Proxy
  518 + }
  519 + if t.Dial == nil {
  520 + t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
  521 + }
  522 + }
  523 + }
  524 +
  525 + var jar http.CookieJar
  526 + if b.setting.EnableCookie {
  527 + if defaultCookieJar == nil {
  528 + createDefaultCookie()
  529 + }
  530 + jar = defaultCookieJar
  531 + }
  532 +
  533 + client := &http.Client{
  534 + Transport: trans,
  535 + Jar: jar,
  536 + }
  537 +
  538 + if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" {
  539 + b.req.Header.Set("User-Agent", b.setting.UserAgent)
  540 + }
  541 +
  542 + if b.setting.CheckRedirect != nil {
  543 + client.CheckRedirect = b.setting.CheckRedirect
  544 + }
  545 +
  546 + if b.setting.ShowDebug {
  547 + dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
  548 + if err != nil {
  549 + log.Println(err.Error())
  550 + }
  551 + b.dump = dump
  552 + }
  553 + // retries default value is 0, it will run once.
  554 + // retries equal to -1, it will run forever until success
  555 + // retries is setted, it will retries fixed times.
  556 + // Sleeps for a 400ms between calls to reduce spam
  557 + for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
  558 + resp, err = client.Do(b.req)
  559 + if err == nil {
  560 + break
  561 + }
  562 + time.Sleep(b.setting.RetryDelay)
  563 + }
  564 + return resp, err
  565 +}
  566 +
  567 +// String returns the body string in response.
  568 +// Calls Response inner.
  569 +func (b *BeegoHTTPRequest) String() (string, error) {
  570 + data, err := b.Bytes()
  571 + if err != nil {
  572 + return "", err
  573 + }
  574 +
  575 + return string(data), nil
  576 +}
  577 +
  578 +// Bytes returns the body []byte in response.
  579 +// Calls Response inner.
  580 +func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
  581 + if b.body != nil {
  582 + return b.body, nil
  583 + }
  584 + resp, err := b.getResponse()
  585 + if err != nil {
  586 + return nil, err
  587 + }
  588 + if resp.Body == nil {
  589 + return nil, nil
  590 + }
  591 + defer resp.Body.Close()
  592 + if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" {
  593 + reader, err := gzip.NewReader(resp.Body)
  594 + if err != nil {
  595 + return nil, err
  596 + }
  597 + b.body, err = ioutil.ReadAll(reader)
  598 + return b.body, err
  599 + }
  600 + b.body, err = ioutil.ReadAll(resp.Body)
  601 + return b.body, err
  602 +}
  603 +
  604 +// ToFile saves the body data in response to one file.
  605 +// Calls Response inner.
  606 +func (b *BeegoHTTPRequest) ToFile(filename string) error {
  607 + resp, err := b.getResponse()
  608 + if err != nil {
  609 + return err
  610 + }
  611 + if resp.Body == nil {
  612 + return nil
  613 + }
  614 + defer resp.Body.Close()
  615 + err = pathExistAndMkdir(filename)
  616 + if err != nil {
  617 + return err
  618 + }
  619 + f, err := os.Create(filename)
  620 + if err != nil {
  621 + return err
  622 + }
  623 + defer f.Close()
  624 + _, err = io.Copy(f, resp.Body)
  625 + return err
  626 +}
  627 +
  628 +// Check if the file directory exists. If it doesn't then it's created
  629 +func pathExistAndMkdir(filename string) (err error) {
  630 + filename = path.Dir(filename)
  631 + _, err = os.Stat(filename)
  632 + if err == nil {
  633 + return nil
  634 + }
  635 + if os.IsNotExist(err) {
  636 + err = os.MkdirAll(filename, os.ModePerm)
  637 + if err == nil {
  638 + return nil
  639 + }
  640 + }
  641 + return err
  642 +}
  643 +
  644 +// ToJSON returns the map that marshals from the body bytes as json in response.
  645 +// Calls Response inner.
  646 +func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {
  647 + data, err := b.Bytes()
  648 + if err != nil {
  649 + return err
  650 + }
  651 + return json.Unmarshal(data, v)
  652 +}
  653 +
  654 +// ToXML returns the map that marshals from the body bytes as xml in response .
  655 +// Calls Response inner.
  656 +func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
  657 + data, err := b.Bytes()
  658 + if err != nil {
  659 + return err
  660 + }
  661 + return xml.Unmarshal(data, v)
  662 +}
  663 +
  664 +// ToYAML returns the map that marshals from the body bytes as yaml in response .
  665 +// Calls Response inner.
  666 +func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
  667 + data, err := b.Bytes()
  668 + if err != nil {
  669 + return err
  670 + }
  671 + return yaml.Unmarshal(data, v)
  672 +}
  673 +
  674 +// Response executes request client gets response manually.
  675 +func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
  676 + return b.getResponse()
  677 +}
  678 +
  679 +// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
  680 +func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
  681 + return func(netw, addr string) (net.Conn, error) {
  682 + conn, err := net.DialTimeout(netw, addr, cTimeout)
  683 + if err != nil {
  684 + return nil, err
  685 + }
  686 + err = conn.SetDeadline(time.Now().Add(rwTimeout))
  687 + return conn, err
  688 + }
  689 +}
1 -# Compiled Object files, Static and Dynamic libs (Shared Objects)  
2 -*.o  
3 -*.a  
4 -*.so  
5 -  
6 -# Folders  
7 -_obj  
8 -_test  
9 -  
10 -# Architecture specific extensions/prefixes  
11 -*.[568vq]  
12 -[568vq].out  
13 -  
14 -*.cgo1.go  
15 -*.cgo2.c  
16 -_cgo_defun.c  
17 -_cgo_gotypes.go  
18 -_cgo_export.*  
19 -  
20 -_testmain.go  
21 -  
22 -*.exe  
23 -*.test  
24 -*.prof  
1 -The MIT License (MIT)  
2 -  
3 -Copyright (c) 2015 codemodus  
4 -  
5 -Permission is hereby granted, free of charge, to any person obtaining a copy  
6 -of this software and associated documentation files (the "Software"), to deal  
7 -in the Software without restriction, including without limitation the rights  
8 -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
9 -copies of the Software, and to permit persons to whom the Software is  
10 -furnished to do so, subject to the following conditions:  
11 -  
12 -The above copyright notice and this permission notice shall be included in all  
13 -copies or substantial portions of the Software.  
14 -  
15 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
16 -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
17 -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
18 -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
19 -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  
20 -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  
21 -SOFTWARE.  
22 -  
1 -# kace  
2 -  
3 - go get "github.com/codemodus/kace"  
4 -  
5 -Package kace provides common case conversion functions which take into  
6 -consideration common initialisms.  
7 -  
8 -## Usage  
9 -  
10 -```go  
11 -func Camel(s string) string  
12 -func Kebab(s string) string  
13 -func KebabUpper(s string) string  
14 -func Pascal(s string) string  
15 -func Snake(s string) string  
16 -func SnakeUpper(s string) string  
17 -type Kace  
18 - func New(initialisms map[string]bool) (*Kace, error)  
19 - func (k *Kace) Camel(s string) string  
20 - func (k *Kace) Kebab(s string) string  
21 - func (k *Kace) KebabUpper(s string) string  
22 - func (k *Kace) Pascal(s string) string  
23 - func (k *Kace) Snake(s string) string  
24 - func (k *Kace) SnakeUpper(s string) string  
25 -```  
26 -  
27 -### Setup  
28 -  
29 -```go  
30 -import (  
31 - "fmt"  
32 -  
33 - "github.com/codemodus/kace"  
34 -)  
35 -  
36 -func main() {  
37 - s := "this is a test sql."  
38 -  
39 - fmt.Println(kace.Camel(s))  
40 - fmt.Println(kace.Pascal(s))  
41 -  
42 - fmt.Println(kace.Snake(s))  
43 - fmt.Println(kace.SnakeUpper(s))  
44 -  
45 - fmt.Println(kace.Kebab(s))  
46 - fmt.Println(kace.KebabUpper(s))  
47 -  
48 - customInitialisms := map[string]bool{  
49 - "THIS": true,  
50 - }  
51 - k, err := kace.New(customInitialisms)  
52 - if err != nil {  
53 - // handle error  
54 - }  
55 -  
56 - fmt.Println(k.Camel(s))  
57 - fmt.Println(k.Pascal(s))  
58 -  
59 - fmt.Println(k.Snake(s))  
60 - fmt.Println(k.SnakeUpper(s))  
61 -  
62 - fmt.Println(k.Kebab(s))  
63 - fmt.Println(k.KebabUpper(s))  
64 -  
65 - // Output:  
66 - // thisIsATestSQL  
67 - // ThisIsATestSQL  
68 - // this_is_a_test_sql  
69 - // THIS_IS_A_TEST_SQL  
70 - // this-is-a-test-sql  
71 - // THIS-IS-A-TEST-SQL  
72 - // thisIsATestSql  
73 - // THISIsATestSql  
74 - // this_is_a_test_sql  
75 - // THIS_IS_A_TEST_SQL  
76 - // this-is-a-test-sql  
77 - // THIS-IS-A-TEST-SQL  
78 -}  
79 -```  
80 -  
81 -## More Info  
82 -  
83 -### TODO  
84 -  
85 -#### Test Trie  
86 -  
87 - Test the current trie.  
88 -  
89 -## Documentation  
90 -  
91 -View the [GoDoc](http://godoc.org/github.com/codemodus/kace)  
92 -  
93 -## Benchmarks  
94 -  
95 - benchmark iter time/iter bytes alloc allocs  
96 - --------- ---- --------- ----------- ------  
97 - BenchmarkCamel4 2000000 947.00 ns/op 112 B/op 3 allocs/op  
98 - BenchmarkSnake4 2000000 696.00 ns/op 128 B/op 2 allocs/op  
99 - BenchmarkSnakeUpper4 2000000 679.00 ns/op 128 B/op 2 allocs/op  
100 - BenchmarkKebab4 2000000 691.00 ns/op 128 B/op 2 allocs/op  
101 - BenchmarkKebabUpper4 2000000 677.00 ns/op 128 B/op 2 allocs/op  
1 -module github.com/codemodus/kace  
1 -// Package kace provides common case conversion functions which take into  
2 -// consideration common initialisms.  
3 -package kace  
4 -  
5 -import (  
6 - "fmt"  
7 - "strings"  
8 - "unicode"  
9 -  
10 - "github.com/codemodus/kace/ktrie"  
11 -)  
12 -  
13 -const (  
14 - kebabDelim = '-'  
15 - snakeDelim = '_'  
16 - none = rune(-1)  
17 -)  
18 -  
19 -var (  
20 - ciTrie *ktrie.KTrie  
21 -)  
22 -  
23 -func init() {  
24 - var err error  
25 - if ciTrie, err = ktrie.NewKTrie(ciMap); err != nil {  
26 - panic(err)  
27 - }  
28 -}  
29 -  
30 -// Camel returns a camelCased string.  
31 -func Camel(s string) string {  
32 - return camelCase(ciTrie, s, false)  
33 -}  
34 -  
35 -// Pascal returns a PascalCased string.  
36 -func Pascal(s string) string {  
37 - return camelCase(ciTrie, s, true)  
38 -}  
39 -  
40 -// Kebab returns a kebab-cased string with all lowercase letters.  
41 -func Kebab(s string) string {  
42 - return delimitedCase(s, kebabDelim, false)  
43 -}  
44 -  
45 -// KebabUpper returns a KEBAB-CASED string with all upper case letters.  
46 -func KebabUpper(s string) string {  
47 - return delimitedCase(s, kebabDelim, true)  
48 -}  
49 -  
50 -// Snake returns a snake_cased string with all lowercase letters.  
51 -func Snake(s string) string {  
52 - return delimitedCase(s, snakeDelim, false)  
53 -}  
54 -  
55 -// SnakeUpper returns a SNAKE_CASED string with all upper case letters.  
56 -func SnakeUpper(s string) string {  
57 - return delimitedCase(s, snakeDelim, true)  
58 -}  
59 -  
60 -// Kace provides common case conversion methods which take into  
61 -// consideration common initialisms set by the user.  
62 -type Kace struct {  
63 - t *ktrie.KTrie  
64 -}  
65 -  
66 -// New returns a pointer to an instance of kace loaded with a common  
67 -// initialsms trie based on the provided map. Before conversion to a  
68 -// trie, the provided map keys are all upper cased.  
69 -func New(initialisms map[string]bool) (*Kace, error) {  
70 - ci := initialisms  
71 - if ci == nil {  
72 - ci = map[string]bool{}  
73 - }  
74 -  
75 - ci = sanitizeCI(ci)  
76 -  
77 - t, err := ktrie.NewKTrie(ci)  
78 - if err != nil {  
79 - return nil, fmt.Errorf("kace: cannot create trie: %s", err)  
80 - }  
81 -  
82 - k := &Kace{  
83 - t: t,  
84 - }  
85 -  
86 - return k, nil  
87 -}  
88 -  
89 -// Camel returns a camelCased string.  
90 -func (k *Kace) Camel(s string) string {  
91 - return camelCase(k.t, s, false)  
92 -}  
93 -  
94 -// Pascal returns a PascalCased string.  
95 -func (k *Kace) Pascal(s string) string {  
96 - return camelCase(k.t, s, true)  
97 -}  
98 -  
99 -// Snake returns a snake_cased string with all lowercase letters.  
100 -func (k *Kace) Snake(s string) string {  
101 - return delimitedCase(s, snakeDelim, false)  
102 -}  
103 -  
104 -// SnakeUpper returns a SNAKE_CASED string with all upper case letters.  
105 -func (k *Kace) SnakeUpper(s string) string {  
106 - return delimitedCase(s, snakeDelim, true)  
107 -}  
108 -  
109 -// Kebab returns a kebab-cased string with all lowercase letters.  
110 -func (k *Kace) Kebab(s string) string {  
111 - return delimitedCase(s, kebabDelim, false)  
112 -}  
113 -  
114 -// KebabUpper returns a KEBAB-CASED string with all upper case letters.  
115 -func (k *Kace) KebabUpper(s string) string {  
116 - return delimitedCase(s, kebabDelim, true)  
117 -}  
118 -  
119 -func camelCase(t *ktrie.KTrie, s string, ucFirst bool) string {  
120 - rs := []rune(s)  
121 - offset := 0  
122 - prev := none  
123 -  
124 - for i := 0; i < len(rs); i++ {  
125 - r := rs[i]  
126 -  
127 - switch {  
128 - case unicode.IsLetter(r):  
129 - ucCurr := isToBeUpper(r, prev, ucFirst)  
130 -  
131 - if ucCurr || isSegmentStart(r, prev) {  
132 - prv, skip := updateRunes(rs, i, offset, t, ucCurr)  
133 - if skip > 0 {  
134 - i += skip - 1  
135 - prev = prv  
136 - continue  
137 - }  
138 - }  
139 -  
140 - prev = updateRune(rs, i, offset, ucCurr)  
141 - continue  
142 -  
143 - case unicode.IsNumber(r):  
144 - prev = updateRune(rs, i, offset, false)  
145 - continue  
146 -  
147 - default:  
148 - prev = r  
149 - offset--  
150 - }  
151 - }  
152 -  
153 - return string(rs[:len(rs)+offset])  
154 -}  
155 -  
156 -func isToBeUpper(curr, prev rune, ucFirst bool) bool {  
157 - if prev == none {  
158 - return ucFirst  
159 - }  
160 -  
161 - return isSegmentStart(curr, prev)  
162 -}  
163 -  
164 -func isSegmentStart(curr, prev rune) bool {  
165 - if !unicode.IsLetter(prev) || unicode.IsUpper(curr) && unicode.IsLower(prev) {  
166 - return true  
167 - }  
168 -  
169 - return false  
170 -}  
171 -  
172 -func updateRune(rs []rune, i, offset int, upper bool) rune {  
173 - r := rs[i]  
174 -  
175 - dest := i + offset  
176 - if dest < 0 || i > len(rs)-1 {  
177 - panic("this function has been used or designed incorrectly")  
178 - }  
179 -  
180 - fn := unicode.ToLower  
181 - if upper {  
182 - fn = unicode.ToUpper  
183 - }  
184 -  
185 - rs[dest] = fn(r)  
186 -  
187 - return r  
188 -}  
189 -  
190 -func updateRunes(rs []rune, i, offset int, t *ktrie.KTrie, upper bool) (rune, int) {  
191 - r := rs[i]  
192 - ns := nextSegment(rs, i)  
193 - ct := len(ns)  
194 -  
195 - if ct < t.MinDepth() || ct > t.MaxDepth() || !t.FindAsUpper(ns) {  
196 - return r, 0  
197 - }  
198 -  
199 - for j := i; j < i+ct; j++ {  
200 - r = updateRune(rs, j, offset, upper)  
201 - }  
202 -  
203 - return r, ct  
204 -}  
205 -  
206 -func nextSegment(rs []rune, i int) []rune {  
207 - for j := i; j < len(rs); j++ {  
208 - if !unicode.IsLetter(rs[j]) && !unicode.IsNumber(rs[j]) {  
209 - return rs[i:j]  
210 - }  
211 -  
212 - if j == len(rs)-1 {  
213 - return rs[i : j+1]  
214 - }  
215 - }  
216 -  
217 - return nil  
218 -}  
219 -  
220 -func delimitedCase(s string, delim rune, upper bool) string {  
221 - buf := make([]rune, 0, len(s)*2)  
222 -  
223 - for i := len(s); i > 0; i-- {  
224 - switch {  
225 - case unicode.IsLetter(rune(s[i-1])):  
226 - if i < len(s) && unicode.IsUpper(rune(s[i])) {  
227 - if i > 1 && unicode.IsLower(rune(s[i-1])) || i < len(s)-2 && unicode.IsLower(rune(s[i+1])) {  
228 - buf = append(buf, delim)  
229 - }  
230 - }  
231 -  
232 - buf = appendCased(buf, upper, rune(s[i-1]))  
233 -  
234 - case unicode.IsNumber(rune(s[i-1])):  
235 - if i == len(s) || i == 1 || unicode.IsNumber(rune(s[i])) {  
236 - buf = append(buf, rune(s[i-1]))  
237 - continue  
238 - }  
239 -  
240 - buf = append(buf, delim, rune(s[i-1]))  
241 -  
242 - default:  
243 - if i == len(s) {  
244 - continue  
245 - }  
246 -  
247 - buf = append(buf, delim)  
248 - }  
249 - }  
250 -  
251 - reverse(buf)  
252 -  
253 - return string(buf)  
254 -}  
255 -  
256 -func appendCased(rs []rune, upper bool, r rune) []rune {  
257 - if upper {  
258 - rs = append(rs, unicode.ToUpper(r))  
259 - return rs  
260 - }  
261 -  
262 - rs = append(rs, unicode.ToLower(r))  
263 -  
264 - return rs  
265 -}  
266 -  
267 -func reverse(s []rune) {  
268 - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {  
269 - s[i], s[j] = s[j], s[i]  
270 - }  
271 -}  
272 -  
273 -var (  
274 - // github.com/golang/lint/blob/master/lint.go  
275 - ciMap = map[string]bool{  
276 - "ACL": true,  
277 - "API": true,  
278 - "ASCII": true,  
279 - "CPU": true,  
280 - "CSS": true,  
281 - "DNS": true,  
282 - "EOF": true,  
283 - "GUID": true,  
284 - "HTML": true,  
285 - "HTTP": true,  
286 - "HTTPS": true,  
287 - "ID": true,  
288 - "IP": true,  
289 - "JSON": true,  
290 - "LHS": true,  
291 - "QPS": true,  
292 - "RAM": true,  
293 - "RHS": true,  
294 - "RPC": true,  
295 - "SLA": true,  
296 - "SMTP": true,  
297 - "SQL": true,  
298 - "SSH": true,  
299 - "TCP": true,  
300 - "TLS": true,  
301 - "TTL": true,  
302 - "UDP": true,  
303 - "UI": true,  
304 - "UID": true,  
305 - "UUID": true,  
306 - "URI": true,  
307 - "URL": true,  
308 - "UTF8": true,  
309 - "VM": true,  
310 - "XML": true,  
311 - "XMPP": true,  
312 - "XSRF": true,  
313 - "XSS": true,  
314 - }  
315 -)  
316 -  
317 -func sanitizeCI(m map[string]bool) map[string]bool {  
318 - r := map[string]bool{}  
319 -  
320 - for k := range m {  
321 - fn := func(r rune) rune {  
322 - if !unicode.IsLetter(r) && !unicode.IsNumber(r) {  
323 - return -1  
324 - }  
325 - return r  
326 - }  
327 -  
328 - k = strings.Map(fn, k)  
329 - k = strings.ToUpper(k)  
330 -  
331 - if k == "" {  
332 - continue  
333 - }  
334 -  
335 - r[k] = true  
336 - }  
337 -  
338 - return r  
339 -}  
1 -package ktrie  
2 -  
3 -import "unicode"  
4 -  
5 -// KNode ...  
6 -type KNode struct {  
7 - val rune  
8 - end bool  
9 - links []*KNode  
10 -}  
11 -  
12 -// NewKNode ...  
13 -func NewKNode(val rune) *KNode {  
14 - return &KNode{  
15 - val: val,  
16 - links: make([]*KNode, 0),  
17 - }  
18 -}  
19 -  
20 -// Add ...  
21 -func (n *KNode) Add(rs []rune) {  
22 - cur := n  
23 -  
24 - for k, v := range rs {  
25 - link := cur.linkByVal(v)  
26 -  
27 - if link == nil {  
28 - link = NewKNode(v)  
29 - cur.links = append(cur.links, link)  
30 - }  
31 -  
32 - if k == len(rs)-1 {  
33 - link.end = true  
34 - }  
35 -  
36 - cur = link  
37 - }  
38 -}  
39 -  
40 -// Find ...  
41 -func (n *KNode) Find(rs []rune) bool {  
42 - cur := n  
43 -  
44 - for _, v := range rs {  
45 - cur = cur.linkByVal(v)  
46 -  
47 - if cur == nil {  
48 - return false  
49 - }  
50 - }  
51 -  
52 - return cur.end  
53 -}  
54 -  
55 -// FindAsUpper ...  
56 -func (n *KNode) FindAsUpper(rs []rune) bool {  
57 - cur := n  
58 -  
59 - for _, v := range rs {  
60 - cur = cur.linkByVal(unicode.ToUpper(v))  
61 -  
62 - if cur == nil {  
63 - return false  
64 - }  
65 - }  
66 -  
67 - return cur.end  
68 -}  
69 -  
70 -func (n *KNode) linkByVal(val rune) *KNode {  
71 - for _, v := range n.links {  
72 - if v.val == val {  
73 - return v  
74 - }  
75 - }  
76 -  
77 - return nil  
78 -}  
79 -  
80 -// KTrie ...  
81 -type KTrie struct {  
82 - *KNode  
83 -  
84 - maxDepth int  
85 - minDepth int  
86 -}  
87 -  
88 -// NewKTrie ...  
89 -func NewKTrie(data map[string]bool) (*KTrie, error) {  
90 - n := NewKNode(0)  
91 -  
92 - maxDepth := 0  
93 - minDepth := 9001  
94 -  
95 - for k := range data {  
96 - rs := []rune(k)  
97 - l := len(rs)  
98 -  
99 - n.Add(rs)  
100 -  
101 - if l > maxDepth {  
102 - maxDepth = l  
103 - }  
104 - if l < minDepth {  
105 - minDepth = l  
106 - }  
107 - }  
108 -  
109 - t := &KTrie{  
110 - maxDepth: maxDepth,  
111 - minDepth: minDepth,  
112 - KNode: n,  
113 - }  
114 -  
115 - return t, nil  
116 -}  
117 -  
118 -// MaxDepth ...  
119 -func (t *KTrie) MaxDepth() int {  
120 - return t.maxDepth  
121 -}  
122 -  
123 -// MinDepth ...  
124 -func (t *KTrie) MinDepth() int {  
125 - return t.minDepth  
126 -}  
@@ -11,3 +11,8 @@ linters: @@ -11,3 +11,8 @@ linters:
11 - wsl 11 - wsl
12 - funlen 12 - funlen
13 - godox 13 - godox
  14 + - goerr113
  15 + - exhaustive
  16 + - nestif
  17 + - gofumpt
  18 + - goconst
1 semi: false 1 semi: false
2 singleQuote: true 2 singleQuote: true
3 proseWrap: always 3 proseWrap: always
4 -printWidth: 80 4 +printWidth: 100
1 dist: xenial 1 dist: xenial
2 -sudo: false  
3 language: go 2 language: go
4 3
5 addons: 4 addons:
6 - postgresql: "9.6" 5 + postgresql: '9.6'
7 6
8 go: 7 go:
9 - - 1.13.x  
10 - 1.14.x 8 - 1.14.x
  9 + - 1.15.x
11 - tip 10 - tip
12 11
13 matrix: 12 matrix:
14 allow_failures: 13 allow_failures:
15 - go: tip 14 - go: tip
16 15
17 -env:  
18 - - GO111MODULE=on  
19 -  
20 go_import_path: github.com/go-pg/pg 16 go_import_path: github.com/go-pg/pg
21 17
22 before_install: 18 before_install:
23 - psql -U postgres -c "CREATE EXTENSION hstore" 19 - psql -U postgres -c "CREATE EXTENSION hstore"
24 - - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0 20 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s --
  21 + -b $(go env GOPATH)/bin v1.28.3
1 # Changelog 1 # Changelog
2 2
3 -## v10 (unreleased) 3 +> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev)
4 4
5 -- Added `pgext.OpenTemetryHook` that adds OpenTelemetry  
6 - [instrumentation](https://pg.uptrace.dev/tracing/).  
7 -- Added `pgext.DebugHook` that logs queries and errors.  
8 -- Added `db.Ping` to check if database is healthy.  
9 -- Changed `pg.QueryHook` to return temp byte slice to reduce memory usage.  
10 -- `,msgpack` struct tag marshals data in MessagePack format using  
11 - https://github.com/vmihailenco/msgpack  
12 -- Deprecated types and funcs are removed.  
13 -  
14 -## v9  
15 -  
16 -- `pg:",notnull"` is reworked. Now it means SQL `NOT NULL` constraint and  
17 - nothing more.  
18 -- Added `pg:",use_zero"` to prevent go-pg from converting Go zero values to SQL  
19 - `NULL`.  
20 -- UpdateNotNull is renamed to UpdateNotZero. As previously it omits zero Go  
21 - values, but it does not take in account if field is nullable or not.  
22 -- ORM supports DistinctOn.  
23 -- Hooks accept and return context.  
24 -- Client respects Context.Deadline when setting net.Conn deadline.  
25 -- Client listens on Context.Done while waiting for a connection from the pool  
26 - and returns an error when context is cancelled.  
27 -- `Query.Column` does not accept relation name any more. Use `Query.Relation`  
28 - instead which returns an error if relation does not exist.  
29 -- urlvalues package is removed in favor of https://github.com/go-pg/urlstruct.  
30 - You can also use struct based filters via `Query.WhereStruct`.  
31 -- `NewModel` and `AddModel` methods of `HooklessModel` interface were renamed to  
32 - `NextColumnScanner` and `AddColumnScanner` respectively.  
33 -- `types.F` and `pg.F` are deprecated in favor of `pg.Ident`.  
34 -- `types.Q` is deprecated in favor of `pg.Safe`.  
35 -- `pg.Q` is deprecated in favor of `pg.SafeQuery`.  
36 -- `TableName` field is deprecated in favor of `tableName`.  
37 -- Always use `pg:"..."` struct field tag instead of `sql:"..."`.  
38 -- `pg:",override"` is deprecated in favor of `pg:",inherit"`.  
39 -  
40 -## v8  
41 -  
42 -- Added `QueryContext`, `ExecContext`, and `ModelContext` which accept  
43 - `context.Context`. Queries are cancelled when context is cancelled.  
44 -- Model hooks are changed to accept `context.Context` as first argument.  
45 -- Fixed array and hstore parsers to handle multiple single quotes (#1235).  
46 -  
47 -## v7  
48 -  
49 -- DB.OnQueryProcessed is replaced with DB.AddQueryHook.  
50 -- Added WhereStruct.  
51 -- orm.Pager is moved to urlvalues.Pager. Pager.FromURLValues returns an error if  
52 - page or limit params can't be parsed.  
53 -  
54 -## v6.16  
55 -  
56 -- Read buffer is re-worked. Default read buffer is increased to 65kb.  
57 -  
58 -## v6.15  
59 -  
60 -- Added Options.MinIdleConns.  
61 -- Options.MaxAge renamed to Options.MaxConnAge.  
62 -- PoolStats.FreeConns is renamed to PoolStats.IdleConns.  
63 -- New hook BeforeSelectQuery.  
64 -- `,override` is renamed to `,inherit`.  
65 -- Dialer.KeepAlive is set to 5 minutes by default.  
66 -- Added support "scram-sha-256" authentication.  
67 -  
68 -## v6.14  
69 -  
70 -- Fields ignored with `sql:"-"` tag are no longer considered by ORM relation  
71 - detector.  
72 -  
73 -## v6.12  
74 -  
75 -- `Insert`, `Update`, and `Delete` can return `pg.ErrNoRows` and  
76 - `pg.ErrMultiRows` when `Returning` is used and model expects single row.  
77 -  
78 -## v6.11  
79 -  
80 -- `db.Model(&strct).Update()` and `db.Model(&strct).Delete()` no longer adds  
81 - WHERE condition based on primary key when there are no conditions. Instead you  
82 - should use `db.Update(&strct)` or `db.Model(&strct).WherePK().Update()`.  
83 -  
84 -## v6.10  
85 -  
86 -- `?Columns` is renamed to `?TableColumns`. `?Columns` is changed to produce  
87 - column names without table alias.  
88 -  
89 -## v6.9  
90 -  
91 -- `pg:"fk"` tag now accepts SQL names instead of Go names, e.g.  
92 - `pg:"fk:ParentId"` becomes `pg:"fk:parent_id"`. Old code should continue  
93 - working in most cases, but it is strongly advised to start using new  
94 - convention.  
95 -- uint and uint64 SQL type is changed from decimal to bigint according to the  
96 - lesser of two evils principle. Use `sql:"type:decimal"` to get old behavior.  
97 -  
98 -## v6.8  
99 -  
100 -- `CreateTable` no longer adds ON DELETE hook by default. To get old behavior  
101 - users should add `sql:"on_delete:CASCADE"` tag on foreign key field.  
102 -  
103 -## v6  
104 -  
105 -- `types.Result` is renamed to `orm.Result`.  
106 -- Added `OnQueryProcessed` event that can be used to log / report queries  
107 - timing. Query logger is removed.  
108 -- `orm.URLValues` is renamed to `orm.URLFilters`. It no longer adds ORDER  
109 - clause.  
110 -- `orm.Pager` is renamed to `orm.Pagination`.  
111 -- Support for net.IP and net.IPNet.  
112 -- Support for context.Context.  
113 -- Bulk/multi updates.  
114 -- Query.WhereGroup for enclosing conditions in parentheses.  
115 -  
116 -## v5  
117 -  
118 -- All fields are nullable by default. `,null` tag is replaced with `,notnull`.  
119 -- `Result.Affected` renamed to `Result.RowsAffected`.  
120 -- Added `Result.RowsReturned`.  
121 -- `Create` renamed to `Insert`, `BeforeCreate` to `BeforeInsert`, `AfterCreate`  
122 - to `AfterInsert`.  
123 -- Indexed placeholders support, e.g. `db.Exec("SELECT ?0 + ?0", 1)`.  
124 -- Named placeholders are evaluated when query is executed.  
125 -- Added Update and Delete hooks.  
126 -- Order reworked to quote column names. OrderExpr added to bypass Order quoting  
127 - restrictions.  
128 -- Group reworked to quote column names. GroupExpr added to bypass Group quoting  
129 - restrictions.  
130 -  
131 -## v4  
132 -  
133 -- `Options.Host` and `Options.Port` merged into `Options.Addr`.  
134 -- Added `Options.MaxRetries`. Now queries are not retried by default.  
135 -- `LoadInto` renamed to `Scan`, `ColumnLoader` renamed to `ColumnScanner`,  
136 - LoadColumn renamed to ScanColumn, `NewRecord() interface{}` changed to  
137 - `NewModel() ColumnScanner`, `AppendQuery(dst []byte) []byte` changed to  
138 - `AppendValue(dst []byte, quote bool) ([]byte, error)`.  
139 -- Structs, maps and slices are marshalled to JSON by default.  
140 -- Added support for scanning slices, .e.g. scanning `[]int`.  
141 -- Added object relational mapping. 5 +See https://pg.uptrace.dev/changelog/
1 all: 1 all:
2 - go test ./...  
3 - go test ./... -short -race  
4 - go test ./... -run=NONE -bench=. -benchmem 2 + TZ= go test ./...
  3 + TZ= go test ./... -short -race
  4 + TZ= go test ./... -run=NONE -bench=. -benchmem
5 env GOOS=linux GOARCH=386 go test ./... 5 env GOOS=linux GOARCH=386 go test ./...
  6 + go vet
6 golangci-lint run 7 golangci-lint run
  8 +
  9 +.PHONY: cleanTest
  10 +cleanTest:
  11 + docker rm -fv pg || true
  12 +
  13 +.PHONY: pre-test
  14 +pre-test: cleanTest
  15 + docker run -d --name pg -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres:9.6
  16 + sleep 10
  17 + docker exec pg psql -U postgres -c "CREATE EXTENSION hstore"
  18 +
  19 +.PHONY: test
  20 +test: pre-test
  21 + TZ= PGSSLMODE=disable go test ./... -v
1 # PostgreSQL client and ORM for Golang 1 # PostgreSQL client and ORM for Golang
2 2
3 -[![Build Status](https://travis-ci.org/go-pg/pg.svg?branch=master)](https://travis-ci.org/go-pg/pg)  
4 -[![GoDoc](https://godoc.org/github.com/go-pg/pg?status.svg)](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc) 3 +[![Build Status](https://travis-ci.org/go-pg/pg.svg?branch=v10)](https://travis-ci.org/go-pg/pg)
  4 +[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-pg/pg/v10)](https://pkg.go.dev/github.com/go-pg/pg/v10)
  5 +[![Documentation](https://img.shields.io/badge/pg-documentation-informational)](https://pg.uptrace.dev/)
  6 +[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj)
5 7
6 -- [Docs](https://pg.uptrace.dev) 8 +> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev)
  9 +
  10 +- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions.
  11 +- [Documentation](https://pg.uptrace.dev)
7 - [Reference](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc) 12 - [Reference](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc)
8 - [Examples](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#pkg-examples) 13 - [Examples](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#pkg-examples)
  14 +- Example projects:
  15 + - [treemux](https://github.com/uptrace/go-treemux-realworld-example-app)
  16 + - [gin](https://github.com/gogjango/gjango)
  17 + - [go-kit](https://github.com/Tsovak/rest-api-demo)
  18 + - [aah framework](https://github.com/kieusonlam/golamapi)
  19 +- [GraphQL Tutorial on YouTube](https://www.youtube.com/playlist?list=PLzQWIQOqeUSNwXcneWYJHUREAIucJ5UZn).
9 20
10 ## Ecosystem 21 ## Ecosystem
11 22
12 - Migrations by [vmihailenco](https://github.com/go-pg/migrations) and 23 - Migrations by [vmihailenco](https://github.com/go-pg/migrations) and
13 [robinjoseph08](https://github.com/robinjoseph08/go-pg-migrations). 24 [robinjoseph08](https://github.com/robinjoseph08/go-pg-migrations).
  25 +- [Genna - cli tool for generating go-pg models](https://github.com/dizzyfool/genna).
  26 +- [urlstruct](https://github.com/go-pg/urlstruct) to decode `url.Values` into structs.
14 - [Sharding](https://github.com/go-pg/sharding). 27 - [Sharding](https://github.com/go-pg/sharding).
15 -- [Model generator from SQL tables](https://github.com/dizzyfool/genna).  
16 -- [urlstruct](https://github.com/go-pg/urlstruct) to decode `url.Values` into  
17 - structs.  
18 -  
19 -## Sponsors  
20 -  
21 -- [**Uptrace.dev** - distributed traces and metrics](https://uptrace.dev)  
22 28
23 ## Features 29 ## Features
24 30
@@ -26,71 +32,200 @@ @@ -26,71 +32,200 @@
26 - sql.NullBool, sql.NullString, sql.NullInt64, sql.NullFloat64 and 32 - sql.NullBool, sql.NullString, sql.NullInt64, sql.NullFloat64 and
27 [pg.NullTime](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#NullTime). 33 [pg.NullTime](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#NullTime).
28 - [sql.Scanner](http://golang.org/pkg/database/sql/#Scanner) and 34 - [sql.Scanner](http://golang.org/pkg/database/sql/#Scanner) and
29 - [sql/driver.Valuer](http://golang.org/pkg/database/sql/driver/#Valuer)  
30 - interfaces. 35 + [sql/driver.Valuer](http://golang.org/pkg/database/sql/driver/#Valuer) interfaces.
31 - Structs, maps and arrays are marshalled as JSON by default. 36 - Structs, maps and arrays are marshalled as JSON by default.
32 - PostgreSQL multidimensional Arrays using 37 - PostgreSQL multidimensional Arrays using
33 [array tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-PostgresArrayStructTag) 38 [array tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-PostgresArrayStructTag)
34 - and  
35 - [Array wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Array). 39 + and [Array wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Array).
36 - Hstore using 40 - Hstore using
37 [hstore tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HstoreStructTag) 41 [hstore tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HstoreStructTag)
38 - and  
39 - [Hstore wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Hstore). 42 + and [Hstore wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Hstore).
40 - [Composite types](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-CompositeType). 43 - [Composite types](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-CompositeType).
41 -- All struct fields are nullable by default and zero values (empty string, 0,  
42 - zero time, empty map or slice, nil ptr) are marshalled as SQL `NULL`.  
43 - `pg:",notnull"` is used to add SQL `NOT NULL` constraint and `pg:",use_zero"`  
44 - to allow Go zero values. 44 +- All struct fields are nullable by default and zero values (empty string, 0, zero time, empty map
  45 + or slice, nil ptr) are marshalled as SQL `NULL`. `pg:",notnull"` is used to add SQL `NOT NULL`
  46 + constraint and `pg:",use_zero"` to allow Go zero values.
45 - [Transactions](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Begin). 47 - [Transactions](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Begin).
46 - [Prepared statements](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Prepare). 48 - [Prepared statements](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Prepare).
47 -- [Notifications](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Listener)  
48 - using `LISTEN` and `NOTIFY`.  
49 -- [Copying data](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-CopyFrom)  
50 - using `COPY FROM` and `COPY TO`.  
51 -- [Timeouts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#Options) and  
52 - canceling queries using context.Context. 49 +- [Notifications](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Listener) using
  50 + `LISTEN` and `NOTIFY`.
  51 +- [Copying data](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-CopyFrom) using
  52 + `COPY FROM` and `COPY TO`.
  53 +- [Timeouts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#Options) and canceling queries using
  54 + context.Context.
53 - Automatic connection pooling with 55 - Automatic connection pooling with
54 - [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern)  
55 - support. 56 + [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support.
56 - Queries retry on network errors. 57 - Queries retry on network errors.
57 - Working with models using 58 - Working with models using
58 - [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model) and  
59 - [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Query). 59 + [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model) and
  60 + [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Query).
60 - Scanning variables using 61 - Scanning variables using
61 - [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Select-SomeColumnsIntoVars) 62 + [ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectSomeColumnsIntoVars)
62 and [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Scan). 63 and [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Scan).
63 -- [SelectOrInsert](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Insert-SelectOrInsert) 64 +- [SelectOrInsert](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-InsertSelectOrInsert)
64 using on-conflict. 65 using on-conflict.
65 -- [INSERT ... ON CONFLICT DO UPDATE](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Insert-OnConflictDoUpdate) 66 +- [INSERT ... ON CONFLICT DO UPDATE](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-InsertOnConflictDoUpdate)
66 using ORM. 67 using ORM.
67 - Bulk/batch 68 - Bulk/batch
68 - [inserts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Insert-BulkInsert),  
69 - [updates](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Update-BulkUpdate),  
70 - and  
71 - [deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Delete-BulkDelete). 69 + [inserts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkInsert),
  70 + [updates](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkUpdate), and
  71 + [deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkDelete).
72 - Common table expressions using 72 - Common table expressions using
73 - [WITH](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Select-With)  
74 - and  
75 - [WrapWith](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Select-WrapWith).  
76 -- [CountEstimate](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-CountEstimate) 73 + [WITH](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectWith) and
  74 + [WrapWith](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectWrapWith).
  75 +- [CountEstimate](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-CountEstimate)
77 using `EXPLAIN` to get 76 using `EXPLAIN` to get
78 [estimated number of matching rows](https://wiki.postgresql.org/wiki/Count_estimate). 77 [estimated number of matching rows](https://wiki.postgresql.org/wiki/Count_estimate).
79 - ORM supports 78 - ORM supports
80 - [has one](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HasOne),  
81 - [belongs to](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-BelongsTo),  
82 - [has many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HasMany),  
83 - and  
84 - [many to many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-ManyToMany) 79 + [has one](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-HasOne),
  80 + [belongs to](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BelongsTo),
  81 + [has many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-HasMany), and
  82 + [many to many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-ManyToMany)
85 with composite/multi-column primary keys. 83 with composite/multi-column primary keys.
86 -- [Soft deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-SoftDelete).  
87 -- [Creating tables from structs](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-CreateTable).  
88 -- [ForEach](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-ForEach)  
89 - that calls a function for each row returned by the query without loading all  
90 - rows into the memory. 84 +- [Soft deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SoftDelete).
  85 +- [Creating tables from structs](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-CreateTable).
  86 +- [ForEach](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-ForEach) that calls
  87 + a function for each row returned by the query without loading all rows into the memory.
91 - Works with PgBouncer in transaction pooling mode. 88 - Works with PgBouncer in transaction pooling mode.
92 89
  90 +## Installation
  91 +
  92 +go-pg supports 2 last Go versions and requires a Go version with
  93 +[modules](https://github.com/golang/go/wiki/Modules) support. So make sure to initialize a Go
  94 +module:
  95 +
  96 +```shell
  97 +go mod init github.com/my/repo
  98 +```
  99 +
  100 +And then install go-pg (note _v10_ in the import; omitting it is a popular mistake):
  101 +
  102 +```shell
  103 +go get github.com/go-pg/pg/v10
  104 +```
  105 +
  106 +## Quickstart
  107 +
  108 +```go
  109 +package pg_test
  110 +
  111 +import (
  112 + "fmt"
  113 +
  114 + "github.com/go-pg/pg/v10"
  115 + "github.com/go-pg/pg/v10/orm"
  116 +)
  117 +
  118 +type User struct {
  119 + Id int64
  120 + Name string
  121 + Emails []string
  122 +}
  123 +
  124 +func (u User) String() string {
  125 + return fmt.Sprintf("User<%d %s %v>", u.Id, u.Name, u.Emails)
  126 +}
  127 +
  128 +type Story struct {
  129 + Id int64
  130 + Title string
  131 + AuthorId int64
  132 + Author *User `pg:"rel:has-one"`
  133 +}
  134 +
  135 +func (s Story) String() string {
  136 + return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author)
  137 +}
  138 +
  139 +func ExampleDB_Model() {
  140 + db := pg.Connect(&pg.Options{
  141 + User: "postgres",
  142 + })
  143 + defer db.Close()
  144 +
  145 + err := createSchema(db)
  146 + if err != nil {
  147 + panic(err)
  148 + }
  149 +
  150 + user1 := &User{
  151 + Name: "admin",
  152 + Emails: []string{"admin1@admin", "admin2@admin"},
  153 + }
  154 + _, err = db.Model(user1).Insert()
  155 + if err != nil {
  156 + panic(err)
  157 + }
  158 +
  159 + _, err = db.Model(&User{
  160 + Name: "root",
  161 + Emails: []string{"root1@root", "root2@root"},
  162 + }).Insert()
  163 + if err != nil {
  164 + panic(err)
  165 + }
  166 +
  167 + story1 := &Story{
  168 + Title: "Cool story",
  169 + AuthorId: user1.Id,
  170 + }
  171 + _, err = db.Model(story1).Insert()
  172 + if err != nil {
  173 + panic(err)
  174 + }
  175 +
  176 + // Select user by primary key.
  177 + user := &User{Id: user1.Id}
  178 + err = db.Model(user).WherePK().Select()
  179 + if err != nil {
  180 + panic(err)
  181 + }
  182 +
  183 + // Select all users.
  184 + var users []User
  185 + err = db.Model(&users).Select()
  186 + if err != nil {
  187 + panic(err)
  188 + }
  189 +
  190 + // Select story and associated author in one query.
  191 + story := new(Story)
  192 + err = db.Model(story).
  193 + Relation("Author").
  194 + Where("story.id = ?", story1.Id).
  195 + Select()
  196 + if err != nil {
  197 + panic(err)
  198 + }
  199 +
  200 + fmt.Println(user)
  201 + fmt.Println(users)
  202 + fmt.Println(story)
  203 + // Output: User<1 admin [admin1@admin admin2@admin]>
  204 + // [User<1 admin [admin1@admin admin2@admin]> User<2 root [root1@root root2@root]>]
  205 + // Story<1 Cool story User<1 admin [admin1@admin admin2@admin]>>
  206 +}
  207 +
  208 +// createSchema creates database schema for User and Story models.
  209 +func createSchema(db *pg.DB) error {
  210 + models := []interface{}{
  211 + (*User)(nil),
  212 + (*Story)(nil),
  213 + }
  214 +
  215 + for _, model := range models {
  216 + err := db.Model(model).CreateTable(&orm.CreateTableOptions{
  217 + Temp: true,
  218 + })
  219 + if err != nil {
  220 + return err
  221 + }
  222 + }
  223 + return nil
  224 +}
  225 +```
  226 +
93 ## See also 227 ## See also
94 228
  229 +- [Fast and flexible HTTP router](https://github.com/vmihailenco/treemux)
95 - [Golang msgpack](https://github.com/vmihailenco/msgpack) 230 - [Golang msgpack](https://github.com/vmihailenco/msgpack)
96 - [Golang message task queue](https://github.com/vmihailenco/taskq) 231 - [Golang message task queue](https://github.com/vmihailenco/taskq)
@@ -5,12 +5,13 @@ import ( @@ -5,12 +5,13 @@ import (
5 "io" 5 "io"
6 "time" 6 "time"
7 7
8 - "go.opentelemetry.io/otel/api/kv"  
9 - "go.opentelemetry.io/otel/api/trace" 8 + "go.opentelemetry.io/otel/label"
  9 + "go.opentelemetry.io/otel/trace"
10 10
11 "github.com/go-pg/pg/v10/internal" 11 "github.com/go-pg/pg/v10/internal"
12 "github.com/go-pg/pg/v10/internal/pool" 12 "github.com/go-pg/pg/v10/internal/pool"
13 "github.com/go-pg/pg/v10/orm" 13 "github.com/go-pg/pg/v10/orm"
  14 + "github.com/go-pg/pg/v10/types"
14 ) 15 )
15 16
16 type baseDB struct { 17 type baseDB struct {
@@ -83,14 +84,14 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) { @@ -83,14 +84,14 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) {
83 return cn, nil 84 return cn, nil
84 } 85 }
85 86
86 - err = internal.WithSpan(ctx, "init_conn", func(ctx context.Context, span trace.Span) error { 87 + err = internal.WithSpan(ctx, "pg.init_conn", func(ctx context.Context, span trace.Span) error {
87 return db.initConn(ctx, cn) 88 return db.initConn(ctx, cn)
88 }) 89 })
89 if err != nil { 90 if err != nil {
90 - db.pool.Remove(cn, err)  
91 - // It is safe to reset SingleConnPool if conn can't be initialized.  
92 - if p, ok := db.pool.(*pool.SingleConnPool); ok {  
93 - _ = p.Reset() 91 + db.pool.Remove(ctx, cn, err)
  92 + // It is safe to reset StickyConnPool if conn can't be initialized.
  93 + if p, ok := db.pool.(*pool.StickyConnPool); ok {
  94 + _ = p.Reset(ctx)
94 } 95 }
95 if err := internal.Unwrap(err); err != nil { 96 if err := internal.Unwrap(err); err != nil {
96 return nil, err 97 return nil, err
@@ -101,45 +102,44 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) { @@ -101,45 +102,44 @@ func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) {
101 return cn, nil 102 return cn, nil
102 } 103 }
103 104
104 -func (db *baseDB) initConn(c context.Context, cn *pool.Conn) error { 105 +func (db *baseDB) initConn(ctx context.Context, cn *pool.Conn) error {
105 if cn.Inited { 106 if cn.Inited {
106 return nil 107 return nil
107 } 108 }
108 cn.Inited = true 109 cn.Inited = true
109 110
110 if db.opt.TLSConfig != nil { 111 if db.opt.TLSConfig != nil {
111 - err := db.enableSSL(c, cn, db.opt.TLSConfig) 112 + err := db.enableSSL(ctx, cn, db.opt.TLSConfig)
112 if err != nil { 113 if err != nil {
113 return err 114 return err
114 } 115 }
115 } 116 }
116 117
117 - err := db.startup(c, cn, db.opt.User, db.opt.Password, db.opt.Database, db.opt.ApplicationName) 118 + err := db.startup(ctx, cn, db.opt.User, db.opt.Password, db.opt.Database, db.opt.ApplicationName)
118 if err != nil { 119 if err != nil {
119 return err 120 return err
120 } 121 }
121 122
122 if db.opt.OnConnect != nil { 123 if db.opt.OnConnect != nil {
123 - p := pool.NewSingleConnPool(nil)  
124 - p.SetConn(cn)  
125 - return db.opt.OnConnect(newConn(c, db.withPool(p))) 124 + p := pool.NewSingleConnPool(db.pool, cn)
  125 + return db.opt.OnConnect(ctx, newConn(ctx, db.withPool(p)))
126 } 126 }
127 127
128 return nil 128 return nil
129 } 129 }
130 130
131 -func (db *baseDB) releaseConn(cn *pool.Conn, err error) { 131 +func (db *baseDB) releaseConn(ctx context.Context, cn *pool.Conn, err error) {
132 if isBadConn(err, false) { 132 if isBadConn(err, false) {
133 - db.pool.Remove(cn, err) 133 + db.pool.Remove(ctx, cn, err)
134 } else { 134 } else {
135 - db.pool.Put(cn) 135 + db.pool.Put(ctx, cn)
136 } 136 }
137 } 137 }
138 138
139 func (db *baseDB) withConn( 139 func (db *baseDB) withConn(
140 ctx context.Context, fn func(context.Context, *pool.Conn) error, 140 ctx context.Context, fn func(context.Context, *pool.Conn) error,
141 ) error { 141 ) error {
142 - return internal.WithSpan(ctx, "with_conn", func(ctx context.Context, span trace.Span) error { 142 + return internal.WithSpan(ctx, "pg.with_conn", func(ctx context.Context, span trace.Span) error {
143 cn, err := db.getConn(ctx) 143 cn, err := db.getConn(ctx)
144 if err != nil { 144 if err != nil {
145 return err 145 return err
@@ -154,7 +154,7 @@ func (db *baseDB) withConn( @@ -154,7 +154,7 @@ func (db *baseDB) withConn(
154 case <-ctx.Done(): 154 case <-ctx.Done():
155 err := db.cancelRequest(cn.ProcessID, cn.SecretKey) 155 err := db.cancelRequest(cn.ProcessID, cn.SecretKey)
156 if err != nil { 156 if err != nil {
157 - internal.Logger.Printf("cancelRequest failed: %s", err) 157 + internal.Logger.Printf(ctx, "cancelRequest failed: %s", err)
158 } 158 }
159 // Signal end of conn use. 159 // Signal end of conn use.
160 fnDone <- struct{}{} 160 fnDone <- struct{}{}
@@ -169,7 +169,7 @@ func (db *baseDB) withConn( @@ -169,7 +169,7 @@ func (db *baseDB) withConn(
169 case fnDone <- struct{}{}: // signal fn finish, skip cancel goroutine 169 case fnDone <- struct{}{}: // signal fn finish, skip cancel goroutine
170 } 170 }
171 } 171 }
172 - db.releaseConn(cn, err) 172 + db.releaseConn(ctx, cn, err)
173 }() 173 }()
174 174
175 err = fn(ctx, cn) 175 err = fn(ctx, cn)
@@ -179,9 +179,12 @@ func (db *baseDB) withConn( @@ -179,9 +179,12 @@ func (db *baseDB) withConn(
179 179
180 func (db *baseDB) shouldRetry(err error) bool { 180 func (db *baseDB) shouldRetry(err error) bool {
181 switch err { 181 switch err {
  182 + case io.EOF, io.ErrUnexpectedEOF:
  183 + return true
182 case nil, context.Canceled, context.DeadlineExceeded: 184 case nil, context.Canceled, context.DeadlineExceeded:
183 return false 185 return false
184 } 186 }
  187 +
185 if pgerr, ok := err.(Error); ok { 188 if pgerr, ok := err.(Error); ok {
186 switch pgerr.Field('C') { 189 switch pgerr.Field('C') {
187 case "40001", // serialization_failure 190 case "40001", // serialization_failure
@@ -194,7 +197,12 @@ func (db *baseDB) shouldRetry(err error) bool { @@ -194,7 +197,12 @@ func (db *baseDB) shouldRetry(err error) bool {
194 return false 197 return false
195 } 198 }
196 } 199 }
197 - return isNetworkError(err) 200 +
  201 + if _, ok := err.(timeoutError); ok {
  202 + return true
  203 + }
  204 +
  205 + return false
198 } 206 }
199 207
200 // Close closes the database client, releasing any open resources. 208 // Close closes the database client, releasing any open resources.
@@ -233,9 +241,9 @@ func (db *baseDB) exec(ctx context.Context, query interface{}, params ...interfa @@ -233,9 +241,9 @@ func (db *baseDB) exec(ctx context.Context, query interface{}, params ...interfa
233 for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ { 241 for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ {
234 attempt := attempt 242 attempt := attempt
235 243
236 - lastErr = internal.WithSpan(ctx, "exec", func(ctx context.Context, span trace.Span) error { 244 + lastErr = internal.WithSpan(ctx, "pg.exec", func(ctx context.Context, span trace.Span) error {
237 if attempt > 0 { 245 if attempt > 0 {
238 - span.SetAttributes(kv.Int("retry", attempt)) 246 + span.SetAttributes(label.Int("retry", attempt))
239 247
240 if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil { 248 if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil {
241 return err 249 return err
@@ -311,9 +319,9 @@ func (db *baseDB) query(ctx context.Context, model, query interface{}, params .. @@ -311,9 +319,9 @@ func (db *baseDB) query(ctx context.Context, model, query interface{}, params ..
311 for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ { 319 for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ {
312 attempt := attempt 320 attempt := attempt
313 321
314 - lastErr = internal.WithSpan(ctx, "query", func(ctx context.Context, span trace.Span) error { 322 + lastErr = internal.WithSpan(ctx, "pg.query", func(ctx context.Context, span trace.Span) error {
315 if attempt > 0 { 323 if attempt > 0 {
316 - span.SetAttributes(kv.Int("retry", attempt)) 324 + span.SetAttributes(label.Int("retry", attempt))
317 325
318 if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil { 326 if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil {
319 return err 327 return err
@@ -373,7 +381,7 @@ func (db *baseDB) CopyFrom(r io.Reader, query interface{}, params ...interface{} @@ -373,7 +381,7 @@ func (db *baseDB) CopyFrom(r io.Reader, query interface{}, params ...interface{}
373 return res, err 381 return res, err
374 } 382 }
375 383
376 -// TODO: don't get/put conn in the pool 384 +// TODO: don't get/put conn in the pool.
377 func (db *baseDB) copyFrom( 385 func (db *baseDB) copyFrom(
378 ctx context.Context, cn *pool.Conn, r io.Reader, query interface{}, params ...interface{}, 386 ctx context.Context, cn *pool.Conn, r io.Reader, query interface{}, params ...interface{},
379 ) (res Result, err error) { 387 ) (res Result, err error) {
@@ -396,6 +404,7 @@ func (db *baseDB) copyFrom( @@ -396,6 +404,7 @@ func (db *baseDB) copyFrom(
396 return nil, err 404 return nil, err
397 } 405 }
398 406
  407 + // Note that afterQuery uses the err.
399 defer func() { 408 defer func() {
400 if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil { 409 if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil {
401 err = afterQueryErr 410 err = afterQueryErr
@@ -434,7 +443,7 @@ func (db *baseDB) copyFrom( @@ -434,7 +443,7 @@ func (db *baseDB) copyFrom(
434 return nil, err 443 return nil, err
435 } 444 }
436 445
437 - err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.BufReader) error { 446 + err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
438 res, err = readReadyForQuery(rd) 447 res, err = readReadyForQuery(rd)
439 return err 448 return err
440 }) 449 })
@@ -456,7 +465,7 @@ func (db *baseDB) CopyTo(w io.Writer, query interface{}, params ...interface{}) @@ -456,7 +465,7 @@ func (db *baseDB) CopyTo(w io.Writer, query interface{}, params ...interface{})
456 } 465 }
457 466
458 func (db *baseDB) copyTo( 467 func (db *baseDB) copyTo(
459 - c context.Context, cn *pool.Conn, w io.Writer, query interface{}, params ...interface{}, 468 + ctx context.Context, cn *pool.Conn, w io.Writer, query interface{}, params ...interface{},
460 ) (res Result, err error) { 469 ) (res Result, err error) {
461 var evt *QueryEvent 470 var evt *QueryEvent
462 471
@@ -472,25 +481,26 @@ func (db *baseDB) copyTo( @@ -472,25 +481,26 @@ func (db *baseDB) copyTo(
472 model, _ = params[len(params)-1].(orm.TableModel) 481 model, _ = params[len(params)-1].(orm.TableModel)
473 } 482 }
474 483
475 - c, evt, err = db.beforeQuery(c, db.db, model, query, params, wb.Query()) 484 + ctx, evt, err = db.beforeQuery(ctx, db.db, model, query, params, wb.Query())
476 if err != nil { 485 if err != nil {
477 return nil, err 486 return nil, err
478 } 487 }
479 488
  489 + // Note that afterQuery uses the err.
480 defer func() { 490 defer func() {
481 - if afterQueryErr := db.afterQuery(c, evt, res, err); afterQueryErr != nil { 491 + if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil {
482 err = afterQueryErr 492 err = afterQueryErr
483 } 493 }
484 }() 494 }()
485 495
486 - err = cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error { 496 + err = cn.WithWriter(ctx, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
487 return writeQueryMsg(wb, db.fmter, query, params...) 497 return writeQueryMsg(wb, db.fmter, query, params...)
488 }) 498 })
489 if err != nil { 499 if err != nil {
490 return nil, err 500 return nil, err
491 } 501 }
492 502
493 - err = cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { 503 + err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
494 err := readCopyOutResponse(rd) 504 err := readCopyOutResponse(rd)
495 if err != nil { 505 if err != nil {
496 return err 506 return err
@@ -522,52 +532,6 @@ func (db *baseDB) ModelContext(c context.Context, model ...interface{}) *orm.Que @@ -522,52 +532,6 @@ func (db *baseDB) ModelContext(c context.Context, model ...interface{}) *orm.Que
522 return orm.NewQueryContext(c, db.db, model...) 532 return orm.NewQueryContext(c, db.db, model...)
523 } 533 }
524 534
525 -// Select selects the model by primary key.  
526 -func (db *baseDB) Select(model interface{}) error {  
527 - return orm.Select(db.db, model)  
528 -}  
529 -  
530 -// Insert inserts the model updating primary keys if they are empty.  
531 -func (db *baseDB) Insert(model ...interface{}) error {  
532 - return orm.Insert(db.db, model...)  
533 -}  
534 -  
535 -// Update updates the model by primary key.  
536 -func (db *baseDB) Update(model interface{}) error {  
537 - return orm.Update(db.db, model)  
538 -}  
539 -  
540 -// Delete deletes the model by primary key.  
541 -func (db *baseDB) Delete(model interface{}) error {  
542 - return orm.Delete(db.db, model)  
543 -}  
544 -  
545 -// Delete forces delete of the model with deleted_at column.  
546 -func (db *baseDB) ForceDelete(model interface{}) error {  
547 - return orm.ForceDelete(db.db, model)  
548 -}  
549 -  
550 -// CreateTable creates table for the model. It recognizes following field tags:  
551 -// - notnull - sets NOT NULL constraint.  
552 -// - unique - sets UNIQUE constraint.  
553 -// - default:value - sets default value.  
554 -func (db *baseDB) CreateTable(model interface{}, opt *orm.CreateTableOptions) error {  
555 - return orm.CreateTable(db.db, model, opt)  
556 -}  
557 -  
558 -// DropTable drops table for the model.  
559 -func (db *baseDB) DropTable(model interface{}, opt *orm.DropTableOptions) error {  
560 - return orm.DropTable(db.db, model, opt)  
561 -}  
562 -  
563 -func (db *baseDB) CreateComposite(model interface{}, opt *orm.CreateCompositeOptions) error {  
564 - return orm.CreateComposite(db.db, model, opt)  
565 -}  
566 -  
567 -func (db *baseDB) DropComposite(model interface{}, opt *orm.DropCompositeOptions) error {  
568 - return orm.DropComposite(db.db, model, opt)  
569 -}  
570 -  
571 func (db *baseDB) Formatter() orm.QueryFormatter { 535 func (db *baseDB) Formatter() orm.QueryFormatter {
572 return db.fmter 536 return db.fmter
573 } 537 }
@@ -597,7 +561,7 @@ func (db *baseDB) simpleQuery( @@ -597,7 +561,7 @@ func (db *baseDB) simpleQuery(
597 } 561 }
598 562
599 var res *result 563 var res *result
600 - if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { 564 + if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
601 var err error 565 var err error
602 res, err = readSimpleQuery(rd) 566 res, err = readSimpleQuery(rd)
603 return err 567 return err
@@ -616,7 +580,7 @@ func (db *baseDB) simpleQueryData( @@ -616,7 +580,7 @@ func (db *baseDB) simpleQueryData(
616 } 580 }
617 581
618 var res *result 582 var res *result
619 - if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { 583 + if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
620 var err error 584 var err error
621 res, err = readSimpleQueryData(c, rd, model) 585 res, err = readSimpleQueryData(c, rd, model)
622 return err 586 return err
@@ -631,12 +595,12 @@ func (db *baseDB) simpleQueryData( @@ -631,12 +595,12 @@ func (db *baseDB) simpleQueryData(
631 // executions. Multiple queries or executions may be run concurrently 595 // executions. Multiple queries or executions may be run concurrently
632 // from the returned statement. 596 // from the returned statement.
633 func (db *baseDB) Prepare(q string) (*Stmt, error) { 597 func (db *baseDB) Prepare(q string) (*Stmt, error) {
634 - return prepareStmt(db.withPool(pool.NewSingleConnPool(db.pool)), q) 598 + return prepareStmt(db.withPool(pool.NewStickyConnPool(db.pool)), q)
635 } 599 }
636 600
637 func (db *baseDB) prepare( 601 func (db *baseDB) prepare(
638 c context.Context, cn *pool.Conn, q string, 602 c context.Context, cn *pool.Conn, q string,
639 -) (string, [][]byte, error) { 603 +) (string, []types.ColumnInfo, error) {
640 name := cn.NextID() 604 name := cn.NextID()
641 err := cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error { 605 err := cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
642 writeParseDescribeSyncMsg(wb, name, q) 606 writeParseDescribeSyncMsg(wb, name, q)
@@ -646,8 +610,8 @@ func (db *baseDB) prepare( @@ -646,8 +610,8 @@ func (db *baseDB) prepare(
646 return "", nil, err 610 return "", nil, err
647 } 611 }
648 612
649 - var columns [][]byte  
650 - err = cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.BufReader) error { 613 + var columns []types.ColumnInfo
  614 + err = cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
651 columns, err = readParseDescribeSync(rd) 615 columns, err = readParseDescribeSync(rd)
652 return err 616 return err
653 }) 617 })
@@ -75,12 +75,12 @@ func (db *DB) WithParam(param string, value interface{}) *DB { @@ -75,12 +75,12 @@ func (db *DB) WithParam(param string, value interface{}) *DB {
75 } 75 }
76 76
77 // Listen listens for notifications sent with NOTIFY command. 77 // Listen listens for notifications sent with NOTIFY command.
78 -func (db *DB) Listen(channels ...string) *Listener { 78 +func (db *DB) Listen(ctx context.Context, channels ...string) *Listener {
79 ln := &Listener{ 79 ln := &Listener{
80 db: db, 80 db: db,
81 } 81 }
82 ln.init() 82 ln.init()
83 - _ = ln.Listen(channels...) 83 + _ = ln.Listen(ctx, channels...)
84 return ln 84 return ln
85 } 85 }
86 86
@@ -105,7 +105,7 @@ var _ orm.DB = (*Conn)(nil) @@ -105,7 +105,7 @@ var _ orm.DB = (*Conn)(nil)
105 // Every Conn must be returned to the database pool after use by 105 // Every Conn must be returned to the database pool after use by
106 // calling Conn.Close. 106 // calling Conn.Close.
107 func (db *DB) Conn() *Conn { 107 func (db *DB) Conn() *Conn {
108 - return newConn(db.ctx, db.baseDB.withPool(pool.NewSingleConnPool(db.pool))) 108 + return newConn(db.ctx, db.baseDB.withPool(pool.NewStickyConnPool(db.pool)))
109 } 109 }
110 110
111 func newConn(ctx context.Context, baseDB *baseDB) *Conn { 111 func newConn(ctx context.Context, baseDB *baseDB) *Conn {
1 package pg 1 package pg
2 2
3 import ( 3 import (
4 - "io"  
5 "net" 4 "net"
6 5
7 "github.com/go-pg/pg/v10/internal" 6 "github.com/go-pg/pg/v10/internal"
@@ -22,10 +21,10 @@ var ErrMultiRows = internal.ErrMultiRows @@ -22,10 +21,10 @@ var ErrMultiRows = internal.ErrMultiRows
22 type Error interface { 21 type Error interface {
23 error 22 error
24 23
25 - // Field returns a string value associated with an error code. 24 + // Field returns a string value associated with an error field.
26 // 25 //
27 // https://www.postgresql.org/docs/10/static/protocol-error-fields.html 26 // https://www.postgresql.org/docs/10/static/protocol-error-fields.html
28 - Field(byte) string 27 + Field(field byte) string
29 28
30 // IntegrityViolation reports whether an error is a part of 29 // IntegrityViolation reports whether an error is a part of
31 // Integrity Constraint Violation class of errors. 30 // Integrity Constraint Violation class of errors.
@@ -43,21 +42,19 @@ func isBadConn(err error, allowTimeout bool) bool { @@ -43,21 +42,19 @@ func isBadConn(err error, allowTimeout bool) bool {
43 if _, ok := err.(internal.Error); ok { 42 if _, ok := err.(internal.Error); ok {
44 return false 43 return false
45 } 44 }
46 - if pgErr, ok := err.(Error); ok && pgErr.Field('S') != "FATAL" {  
47 - return false 45 + if pgErr, ok := err.(Error); ok {
  46 + return pgErr.Field('S') == "FATAL"
48 } 47 }
49 if allowTimeout { 48 if allowTimeout {
50 if netErr, ok := err.(net.Error); ok && netErr.Timeout() { 49 if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
51 - return false 50 + return !netErr.Temporary()
52 } 51 }
53 } 52 }
54 return true 53 return true
55 } 54 }
56 55
57 -func isNetworkError(err error) bool {  
58 - if err == io.EOF {  
59 - return true  
60 - }  
61 - _, ok := err.(net.Error)  
62 - return ok 56 +//------------------------------------------------------------------------------
  57 +
  58 +type timeoutError interface {
  59 + Timeout() bool
63 } 60 }
@@ -3,25 +3,24 @@ module github.com/go-pg/pg/v10 @@ -3,25 +3,24 @@ module github.com/go-pg/pg/v10
3 go 1.11 3 go 1.11
4 4
5 require ( 5 require (
6 - github.com/go-pg/pg/v9 v9.1.6 // indirect  
7 - github.com/go-pg/urlstruct v0.4.0  
8 - github.com/go-pg/zerochecker v0.1.1  
9 - github.com/golang/protobuf v1.4.2 // indirect 6 + github.com/go-pg/zerochecker v0.2.0
  7 + github.com/golang/protobuf v1.4.3 // indirect
10 github.com/jinzhu/inflection v1.0.0 8 github.com/jinzhu/inflection v1.0.0
11 - github.com/onsi/ginkgo v1.10.1  
12 - github.com/onsi/gomega v1.7.0  
13 - github.com/segmentio/encoding v0.1.13 9 + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
  10 + github.com/onsi/ginkgo v1.14.2
  11 + github.com/onsi/gomega v1.10.3
  12 + github.com/stretchr/testify v1.6.1
14 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc 13 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
15 github.com/vmihailenco/bufpool v0.1.11 14 github.com/vmihailenco/bufpool v0.1.11
16 - github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1  
17 - github.com/vmihailenco/tagparser v0.1.1  
18 - go.opentelemetry.io/otel v0.6.0  
19 - golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect  
20 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect  
21 - golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect  
22 - google.golang.org/appengine v1.6.6 // indirect  
23 - google.golang.org/grpc v1.29.1  
24 - google.golang.org/protobuf v1.24.0 // indirect  
25 - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 15 + github.com/vmihailenco/msgpack/v4 v4.3.11 // indirect
  16 + github.com/vmihailenco/msgpack/v5 v5.0.0
  17 + github.com/vmihailenco/tagparser v0.1.2
  18 + go.opentelemetry.io/otel v0.14.0
  19 + golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
  20 + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
  21 + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
  22 + google.golang.org/appengine v1.6.7 // indirect
  23 + google.golang.org/protobuf v1.25.0 // indirect
  24 + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
26 mellium.im/sasl v0.2.1 25 mellium.im/sasl v0.2.1
27 ) 26 )
@@ -8,15 +8,17 @@ import ( @@ -8,15 +8,17 @@ import (
8 "github.com/go-pg/pg/v10/orm" 8 "github.com/go-pg/pg/v10/orm"
9 ) 9 )
10 10
11 -type BeforeScanHook = orm.BeforeScanHook  
12 -type AfterScanHook = orm.AfterScanHook  
13 -type AfterSelectHook = orm.AfterSelectHook  
14 -type BeforeInsertHook = orm.BeforeInsertHook  
15 -type AfterInsertHook = orm.AfterInsertHook  
16 -type BeforeUpdateHook = orm.BeforeUpdateHook  
17 -type AfterUpdateHook = orm.AfterUpdateHook  
18 -type BeforeDeleteHook = orm.BeforeDeleteHook  
19 -type AfterDeleteHook = orm.AfterDeleteHook 11 +type (
  12 + BeforeScanHook = orm.BeforeScanHook
  13 + AfterScanHook = orm.AfterScanHook
  14 + AfterSelectHook = orm.AfterSelectHook
  15 + BeforeInsertHook = orm.BeforeInsertHook
  16 + AfterInsertHook = orm.AfterInsertHook
  17 + BeforeUpdateHook = orm.BeforeUpdateHook
  18 + AfterUpdateHook = orm.AfterUpdateHook
  19 + BeforeDeleteHook = orm.BeforeDeleteHook
  20 + AfterDeleteHook = orm.AfterDeleteHook
  21 +)
20 22
21 //------------------------------------------------------------------------------ 23 //------------------------------------------------------------------------------
22 24
@@ -94,11 +96,14 @@ func (db *baseDB) beforeQuery( @@ -94,11 +96,14 @@ func (db *baseDB) beforeQuery(
94 fmtedQuery: fmtedQuery, 96 fmtedQuery: fmtedQuery,
95 } 97 }
96 98
97 - for _, hook := range db.queryHooks { 99 + for i, hook := range db.queryHooks {
98 var err error 100 var err error
99 ctx, err = hook.BeforeQuery(ctx, event) 101 ctx, err = hook.BeforeQuery(ctx, event)
100 if err != nil { 102 if err != nil {
101 - return nil, nil, err 103 + if err := db.afterQueryFromIndex(ctx, event, i); err != nil {
  104 + return ctx, nil, err
  105 + }
  106 + return ctx, nil, err
102 } 107 }
103 } 108 }
104 109
@@ -117,14 +122,15 @@ func (db *baseDB) afterQuery( @@ -117,14 +122,15 @@ func (db *baseDB) afterQuery(
117 122
118 event.Err = err 123 event.Err = err
119 event.Result = res 124 event.Result = res
  125 + return db.afterQueryFromIndex(ctx, event, len(db.queryHooks)-1)
  126 +}
120 127
121 - for _, hook := range db.queryHooks {  
122 - err := hook.AfterQuery(ctx, event)  
123 - if err != nil { 128 +func (db *baseDB) afterQueryFromIndex(ctx context.Context, event *QueryEvent, hookIndex int) error {
  129 + for ; hookIndex >= 0; hookIndex-- {
  130 + if err := db.queryHooks[hookIndex].AfterQuery(ctx, event); err != nil {
124 return err 131 return err
125 } 132 }
126 } 133 }
127 -  
128 return nil 134 return nil
129 } 135 }
130 136
@@ -4,8 +4,10 @@ import ( @@ -4,8 +4,10 @@ import (
4 "fmt" 4 "fmt"
5 ) 5 )
6 6
7 -var ErrNoRows = Errorf("pg: no rows in result set")  
8 -var ErrMultiRows = Errorf("pg: multiple rows in result set") 7 +var (
  8 + ErrNoRows = Errorf("pg: no rows in result set")
  9 + ErrMultiRows = Errorf("pg: multiple rows in result set")
  10 +)
9 11
10 type Error struct { 12 type Error struct {
11 s string 13 s string
@@ -8,20 +8,20 @@ import ( @@ -8,20 +8,20 @@ import (
8 "time" 8 "time"
9 ) 9 )
10 10
11 -// Retry backoff with jitter sleep to prevent overloaded conditions during intervals  
12 -// https://www.awsarchitectureblog.com/2015/03/backoff.html  
13 func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { 11 func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration {
14 if retry < 0 { 12 if retry < 0 {
15 - retry = 0 13 + panic("not reached")
16 } 14 }
17 -  
18 - backoff := minBackoff << uint(retry)  
19 - if backoff > maxBackoff || backoff < minBackoff {  
20 - backoff = maxBackoff 15 + if minBackoff == 0 {
  16 + return 0
21 } 17 }
22 18
23 - if backoff == 0 {  
24 - return 0 19 + d := minBackoff << uint(retry)
  20 + d = minBackoff + time.Duration(rand.Int63n(int64(d)))
  21 +
  22 + if d > maxBackoff || d < minBackoff {
  23 + d = maxBackoff
25 } 24 }
26 - return time.Duration(rand.Int63n(int64(backoff))) 25 +
  26 + return d
27 } 27 }
1 package internal 1 package internal
2 2
3 import ( 3 import (
  4 + "context"
  5 + "fmt"
4 "log" 6 "log"
5 "os" 7 "os"
6 ) 8 )
7 9
8 -var Logger = log.New(os.Stderr, "pg: ", log.LstdFlags|log.Lshortfile) 10 +var Warn = log.New(os.Stderr, "WARN: pg: ", log.LstdFlags)
  11 +
  12 +var Deprecated = log.New(os.Stderr, "DEPRECATED: pg: ", log.LstdFlags)
  13 +
  14 +type Logging interface {
  15 + Printf(ctx context.Context, format string, v ...interface{})
  16 +}
  17 +
  18 +type logger struct {
  19 + log *log.Logger
  20 +}
  21 +
  22 +func (l *logger) Printf(ctx context.Context, format string, v ...interface{}) {
  23 + _ = l.log.Output(2, fmt.Sprintf(format, v...))
  24 +}
  25 +
  26 +var Logger Logging = &logger{
  27 + log: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile),
  28 +}
@@ -8,16 +8,15 @@ import ( @@ -8,16 +8,15 @@ import (
8 "time" 8 "time"
9 9
10 "github.com/go-pg/pg/v10/internal" 10 "github.com/go-pg/pg/v10/internal"
11 - "go.opentelemetry.io/otel/api/kv"  
12 - "go.opentelemetry.io/otel/api/trace" 11 + "go.opentelemetry.io/otel/label"
  12 + "go.opentelemetry.io/otel/trace"
13 ) 13 )
14 14
15 var noDeadline = time.Time{} 15 var noDeadline = time.Time{}
16 16
17 type Conn struct { 17 type Conn struct {
18 netConn net.Conn 18 netConn net.Conn
19 -  
20 - rd *BufReader 19 + rd *ReaderContext
21 20
22 ProcessID int32 21 ProcessID int32
23 SecretKey int32 22 SecretKey int32
@@ -31,8 +30,6 @@ type Conn struct { @@ -31,8 +30,6 @@ type Conn struct {
31 30
32 func NewConn(netConn net.Conn) *Conn { 31 func NewConn(netConn net.Conn) *Conn {
33 cn := &Conn{ 32 cn := &Conn{
34 - rd: NewBufReader(netConn),  
35 -  
36 createdAt: time.Now(), 33 createdAt: time.Now(),
37 } 34 }
38 cn.SetNetConn(netConn) 35 cn.SetNetConn(netConn)
@@ -55,7 +52,17 @@ func (cn *Conn) RemoteAddr() net.Addr { @@ -55,7 +52,17 @@ func (cn *Conn) RemoteAddr() net.Addr {
55 52
56 func (cn *Conn) SetNetConn(netConn net.Conn) { 53 func (cn *Conn) SetNetConn(netConn net.Conn) {
57 cn.netConn = netConn 54 cn.netConn = netConn
  55 + if cn.rd != nil {
58 cn.rd.Reset(netConn) 56 cn.rd.Reset(netConn)
  57 + }
  58 +}
  59 +
  60 +func (cn *Conn) LockReader() {
  61 + if cn.rd != nil {
  62 + panic("not reached")
  63 + }
  64 + cn.rd = NewReaderContext()
  65 + cn.rd.Reset(cn.netConn)
59 } 66 }
60 67
61 func (cn *Conn) NetConn() net.Conn { 68 func (cn *Conn) NetConn() net.Conn {
@@ -68,30 +75,44 @@ func (cn *Conn) NextID() string { @@ -68,30 +75,44 @@ func (cn *Conn) NextID() string {
68 } 75 }
69 76
70 func (cn *Conn) WithReader( 77 func (cn *Conn) WithReader(
71 - ctx context.Context, timeout time.Duration, fn func(rd *BufReader) error, 78 + ctx context.Context, timeout time.Duration, fn func(rd *ReaderContext) error,
72 ) error { 79 ) error {
73 - return internal.WithSpan(ctx, "with_reader", func(ctx context.Context, span trace.Span) error {  
74 - err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout))  
75 - if err != nil { 80 + return internal.WithSpan(ctx, "pg.with_reader", func(ctx context.Context, span trace.Span) error {
  81 + if err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)); err != nil {
  82 + span.RecordError(err)
76 return err 83 return err
77 } 84 }
78 85
79 - cn.rd.bytesRead = 0  
80 - err = fn(cn.rd)  
81 - span.SetAttributes(kv.Int64("net.read_bytes", cn.rd.bytesRead)) 86 + rd := cn.rd
  87 + if rd == nil {
  88 + rd = GetReaderContext()
  89 + defer PutReaderContext(rd)
  90 +
  91 + rd.Reset(cn.netConn)
  92 + }
82 93
  94 + rd.bytesRead = 0
  95 +
  96 + if err := fn(rd); err != nil {
  97 + span.RecordError(err)
83 return err 98 return err
  99 + }
  100 +
  101 + span.SetAttributes(label.Int64("net.read_bytes", rd.bytesRead))
  102 +
  103 + return nil
84 }) 104 })
85 } 105 }
86 106
87 func (cn *Conn) WithWriter( 107 func (cn *Conn) WithWriter(
88 ctx context.Context, timeout time.Duration, fn func(wb *WriteBuffer) error, 108 ctx context.Context, timeout time.Duration, fn func(wb *WriteBuffer) error,
89 ) error { 109 ) error {
90 - return internal.WithSpan(ctx, "with_writer", func(ctx context.Context, span trace.Span) error { 110 + return internal.WithSpan(ctx, "pg.with_writer", func(ctx context.Context, span trace.Span) error {
91 wb := GetWriteBuffer() 111 wb := GetWriteBuffer()
92 defer PutWriteBuffer(wb) 112 defer PutWriteBuffer(wb)
93 113
94 if err := fn(wb); err != nil { 114 if err := fn(wb); err != nil {
  115 + span.RecordError(err)
95 return err 116 return err
96 } 117 }
97 118
@@ -100,7 +121,7 @@ func (cn *Conn) WithWriter( @@ -100,7 +121,7 @@ func (cn *Conn) WithWriter(
100 } 121 }
101 122
102 func (cn *Conn) WriteBuffer(ctx context.Context, timeout time.Duration, wb *WriteBuffer) error { 123 func (cn *Conn) WriteBuffer(ctx context.Context, timeout time.Duration, wb *WriteBuffer) error {
103 - return internal.WithSpan(ctx, "with_writer", func(ctx context.Context, span trace.Span) error { 124 + return internal.WithSpan(ctx, "pg.with_writer", func(ctx context.Context, span trace.Span) error {
104 return cn.writeBuffer(ctx, span, timeout, wb) 125 return cn.writeBuffer(ctx, span, timeout, wb)
105 }) 126 })
106 } 127 }
@@ -111,14 +132,19 @@ func (cn *Conn) writeBuffer( @@ -111,14 +132,19 @@ func (cn *Conn) writeBuffer(
111 timeout time.Duration, 132 timeout time.Duration,
112 wb *WriteBuffer, 133 wb *WriteBuffer,
113 ) error { 134 ) error {
114 - err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout))  
115 - if err != nil { 135 + if err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)); err != nil {
  136 + span.RecordError(err)
116 return err 137 return err
117 } 138 }
118 139
119 - span.SetAttributes(kv.Int("net.wrote_bytes", len(wb.Bytes)))  
120 - _, err = cn.netConn.Write(wb.Bytes) 140 + span.SetAttributes(label.Int("net.wrote_bytes", len(wb.Bytes)))
  141 +
  142 + if _, err := cn.netConn.Write(wb.Bytes); err != nil {
  143 + span.RecordError(err)
121 return err 144 return err
  145 + }
  146 +
  147 + return nil
122 } 148 }
123 149
124 func (cn *Conn) Close() error { 150 func (cn *Conn) Close() error {
@@ -11,8 +11,10 @@ import ( @@ -11,8 +11,10 @@ import (
11 "github.com/go-pg/pg/v10/internal" 11 "github.com/go-pg/pg/v10/internal"
12 ) 12 )
13 13
14 -var ErrClosed = errors.New("pg: database is closed")  
15 -var ErrPoolTimeout = errors.New("pg: connection pool timeout") 14 +var (
  15 + ErrClosed = errors.New("pg: database is closed")
  16 + ErrPoolTimeout = errors.New("pg: connection pool timeout")
  17 +)
16 18
17 var timers = sync.Pool{ 19 var timers = sync.Pool{
18 New: func() interface{} { 20 New: func() interface{} {
@@ -38,8 +40,8 @@ type Pooler interface { @@ -38,8 +40,8 @@ type Pooler interface {
38 CloseConn(*Conn) error 40 CloseConn(*Conn) error
39 41
40 Get(context.Context) (*Conn, error) 42 Get(context.Context) (*Conn, error)
41 - Put(*Conn)  
42 - Remove(*Conn, error) 43 + Put(context.Context, *Conn)
  44 + Remove(context.Context, *Conn, error)
43 45
44 Len() int 46 Len() int
45 IdleLen() int 47 IdleLen() int
@@ -216,12 +218,12 @@ func (p *ConnPool) getLastDialError() error { @@ -216,12 +218,12 @@ func (p *ConnPool) getLastDialError() error {
216 } 218 }
217 219
218 // Get returns existed connection from the pool or creates a new one. 220 // Get returns existed connection from the pool or creates a new one.
219 -func (p *ConnPool) Get(c context.Context) (*Conn, error) { 221 +func (p *ConnPool) Get(ctx context.Context) (*Conn, error) {
220 if p.closed() { 222 if p.closed() {
221 return nil, ErrClosed 223 return nil, ErrClosed
222 } 224 }
223 225
224 - err := p.waitTurn(c) 226 + err := p.waitTurn(ctx)
225 if err != nil { 227 if err != nil {
226 return nil, err 228 return nil, err
227 } 229 }
@@ -246,7 +248,7 @@ func (p *ConnPool) Get(c context.Context) (*Conn, error) { @@ -246,7 +248,7 @@ func (p *ConnPool) Get(c context.Context) (*Conn, error) {
246 248
247 atomic.AddUint32(&p.stats.Misses, 1) 249 atomic.AddUint32(&p.stats.Misses, 1)
248 250
249 - newcn, err := p.newConn(c, true) 251 + newcn, err := p.newConn(ctx, true)
250 if err != nil { 252 if err != nil {
251 p.freeTurn() 253 p.freeTurn()
252 return nil, err 254 return nil, err
@@ -312,15 +314,9 @@ func (p *ConnPool) popIdle() *Conn { @@ -312,15 +314,9 @@ func (p *ConnPool) popIdle() *Conn {
312 return cn 314 return cn
313 } 315 }
314 316
315 -func (p *ConnPool) Put(cn *Conn) {  
316 - if cn.rd.Buffered() > 0 {  
317 - internal.Logger.Printf("Conn has unread data")  
318 - p.Remove(cn, BadConnError{})  
319 - return  
320 - }  
321 - 317 +func (p *ConnPool) Put(ctx context.Context, cn *Conn) {
322 if !cn.pooled { 318 if !cn.pooled {
323 - p.Remove(cn, nil) 319 + p.Remove(ctx, cn, nil)
324 return 320 return
325 } 321 }
326 322
@@ -331,7 +327,7 @@ func (p *ConnPool) Put(cn *Conn) { @@ -331,7 +327,7 @@ func (p *ConnPool) Put(cn *Conn) {
331 p.freeTurn() 327 p.freeTurn()
332 } 328 }
333 329
334 -func (p *ConnPool) Remove(cn *Conn, reason error) { 330 +func (p *ConnPool) Remove(ctx context.Context, cn *Conn, reason error) {
335 p.removeConnWithLock(cn) 331 p.removeConnWithLock(cn)
336 p.freeTurn() 332 p.freeTurn()
337 _ = p.closeConn(cn) 333 _ = p.closeConn(cn)
@@ -446,7 +442,7 @@ func (p *ConnPool) reaper(frequency time.Duration) { @@ -446,7 +442,7 @@ func (p *ConnPool) reaper(frequency time.Duration) {
446 } 442 }
447 n, err := p.ReapStaleConns() 443 n, err := p.ReapStaleConns()
448 if err != nil { 444 if err != nil {
449 - internal.Logger.Printf("ReapStaleConns failed: %s", err) 445 + internal.Logger.Printf(context.TODO(), "ReapStaleConns failed: %s", err)
450 continue 446 continue
451 } 447 }
452 atomic.AddUint32(&p.stats.StaleConns, uint32(n)) 448 atomic.AddUint32(&p.stats.StaleConns, uint32(n))